Projet

Général

Profil

0001-command-add-a-delete_tenant-command-15636.patch

Jean-Baptiste Jaillet, 31 mai 2017 16:31

Télécharger (16,9 ko)

Voir les différences:

Subject: [PATCH] command: add a delete_tenant command (#15636)

 tests/test_ctl.py        | 105 ++++++++++++++++++++++++++++++++++++++++++++
 wcs/ctl/check_hobos.py   |   9 +++-
 wcs/ctl/delete_tenant.py | 111 +++++++++++++++++++++++++++++++++++++++++++++++
 wcs/sql.py               |  42 ++++++++++++------
 4 files changed, 252 insertions(+), 15 deletions(-)
 create mode 100644 wcs/ctl/delete_tenant.py
tests/test_ctl.py
3 3
import collections
4 4
from email.mime.text import MIMEText
5 5
from email.mime.multipart import MIMEMultipart
6
import psycopg2
6 7

  
7 8
from wcs.formdef import FormDef
8 9
from wcs.workflows import Workflow
......
13 14
from wcs.ctl.process_bounce import CmdProcessBounce
14 15
from wcs.ctl.wipe_data import CmdWipeData
15 16
from wcs.ctl.trigger_jumps import select_and_jump_formdata
17
from wcs.ctl.delete_tenant import CmdDeleteTenant
18
from wcs.sql import get_connection_and_cursor, cleanup_connection
16 19

  
17 20
from utilities import create_temporary_pub, clean_temporary_pub
18 21

  
......
186 189
    assert f1.status == f2.status == 'wf-%s' % st1.id
187 190
    assert not f1.workflow_data
188 191
    assert not f2.workflow_data
192

  
193

  
194
def test_delete_tenant():
195
    pub = create_temporary_pub(sql_mode=True)
196
    delete_cmd = CmdDeleteTenant()
197

  
198
    assert os.path.isdir(pub.app_dir)
199

  
200
    sub_options_class = collections.namedtuple('Options', ['force_drop'])
201
    sub_options = sub_options_class(False)
202

  
203
    delete_cmd.delete_tenant(pub, sub_options, [])
204

  
205
    assert not os.path.isdir(pub.app_dir)
206
    parent_dir = os.path.dirname(pub.app_dir)
207
    if not [filename for filename in os.listdir(parent_dir) if 'removed' in filename]:
208
        assert False
209

  
210
    conn, cur = get_connection_and_cursor()
211
    cur.execute("""SELECT schema_name
212
                FROM information_schema.schemata
213
                WHERE schema_name like '%removed%'""")
214

  
215
    assert len(cur.fetchall()) == 1
216

  
217
    clean_temporary_pub()
218
    pub = create_temporary_pub(sql_mode=True)
219

  
220
    sub_options = sub_options_class(True)
221
    delete_cmd.delete_tenant(pub, sub_options, [])
222

  
223
    conn, cur = get_connection_and_cursor(new=True)
224

  
225
    assert not os.path.isdir(pub.app_dir)
226
    cur.execute("""SELECT table_name
227
                FROM information_schema.tables
228
                WHERE table_schema = 'public' AND
229
                table_type = 'BASE TABLE'""")
230

  
231
    assert not cur.fetchall()
232

  
233
    cur.execute("""SELECT datname
234
                FROM pg_database
235
                WHERE datname = '%s'""" % pub.cfg['postgresql']['database'])
236

  
237
    assert cur.fetchall()
238

  
239
    clean_temporary_pub()
240
    pub = create_temporary_pub(sql_mode=True)
241

  
242
    cleanup_connection()
243
    sub_options = sub_options_class(True)
244
    pub.cfg['postgresql']['createdb-connection-params'] = {
245
        'user': pub.cfg['postgresql']['user'],
246
        'database': 'postgres'
247
    }
248
    delete_cmd.delete_tenant(pub, sub_options, [])
249

  
250
    pgconn = psycopg2.connect(**pub.cfg['postgresql']['createdb-connection-params'])
251
    cur = pgconn.cursor()
252

  
253
    cur.execute("""SELECT datname
254
                FROM pg_database
255
                WHERE datname = '%s'""" % pub.cfg['postgresql']['database'])
256
    assert not cur.fetchall()
257
    cur.close()
258
    pgconn.close()
259

  
260
    clean_temporary_pub()
261
    pub = create_temporary_pub(sql_mode=True)
262
    cleanup_connection()
263

  
264
    sub_options = sub_options_class(False)
265
    pub.cfg['postgresql']['createdb-connection-params'] = {
266
        'user': pub.cfg['postgresql']['user'],
267
        'database': 'postgres'
268
    }
269
    delete_cmd.delete_tenant(pub, sub_options, [])
270

  
271
    pgconn = psycopg2.connect(**pub.cfg['postgresql']['createdb-connection-params'])
272
    pgconn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
273
    cur = pgconn.cursor()
274

  
275
    cur.execute("""SELECT datname
276
                FROM pg_database
277
                WHERE datname like '%removed%'""")
278

  
279
    result = cur.fetchall()
280
    assert len(result) == 1
281

  
282
    #clean this db after test
283
    cur.execute("""DROP DATABASE %s""" % result[0][0])
284

  
285
    cur.execute("""SELECT datname
286
                FROM pg_database
287
                WHERE datname = '%s'""" % pub.cfg['postgresql']['database'])
288

  
289
    assert not cur.fetchall()
290
    cur.close()
291
    conn.close()
292

  
293
    clean_temporary_pub()
wcs/ctl/check_hobos.py
422 422
            cur.execute('''CREATE DATABASE %s''' % database_name)
423 423
        except psycopg2.Error as e:
424 424
            if e.pgcode == psycopg2.errorcodes.DUPLICATE_DATABASE:
425
                new_database = False
425
                cur.execute("""SELECT table_name
426
                               FROM information_schema.tables
427
                               WHERE table_schema = 'public'
428
                               AND table_type = 'BASE TABLE'
429
                               AND table_name = 'wcs_meta'""")
430

  
431
                if cur.fetchall():
432
                    new_database = False
426 433
            else:
427 434
                print >> sys.stderr, 'failed to create database (%s)' % \
428 435
                        psycopg2.errorcodes.lookup(e.pgcode)
wcs/ctl/delete_tenant.py
1
#w.c.s. -  web application for online forms
2
# Copyright (C) 2005-2014  Entr'ouvert
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16

  
17
import os
18
import sys
19
import psycopg2
20
import psycopg2.errorcodes
21
from datetime import datetime
22
from shutil import rmtree
23

  
24
from qommon.ctl import Command, make_option
25

  
26

  
27
class CmdDeleteTenant(Command):
28
    name = 'delete_tenant'
29

  
30
    def __init__(self):
31
        Command.__init__(self, [
32
                make_option('--force-drop', action='store_true', default=False,
33
                            dest='force_drop'),
34
                ])
35

  
36
    def execute(self, base_options, sub_options, args):
37
        import publisher
38

  
39
        publisher.WcsPublisher.configure(self.config)
40
        pub = publisher.WcsPublisher.create_publisher(
41
                register_cron=False, register_tld_names=False)
42

  
43
        hostname = args[0]
44
        pub.app_dir = os.path.join(pub.app_dir, hostname)
45
        pub.set_config()
46
        self.delete_tenant(pub, sub_options, args)
47

  
48
    def delete_tenant(self, pub, options, args):
49
        postgresql_cfg = {}
50
        for k, v in pub.cfg['postgresql'].items():
51
            if v and isinstance(v, basestring):
52
                postgresql_cfg[k] = v
53

  
54
        # if there's a createdb-connection-params, we can do a DROP DATABASE with
55
        # the option --force-drop, rename it if not
56
        createdb_cfg = pub.cfg['postgresql'].get('createdb-connection-params', {})
57
        createdb = True
58
        if not createdb_cfg:
59
            createdb_cfg = postgresql_cfg
60
            createdb = False
61
        try:
62
            pgconn = psycopg2.connect(**createdb_cfg)
63
        except psycopg2.Error as e:
64
            print >> sys.stderr, 'failed to connect to postgresql (%s)' % psycopg2.errorcodes.lookup(e.pgcode)
65
            return
66

  
67
        pgconn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
68
        cur = pgconn.cursor()
69
        try:
70
            dbname = pub.cfg['postgresql']['database']
71
            if createdb:
72
                if options.force_drop:
73
                    cur.execute('DROP DATABASE %s' % dbname)
74
                else:
75
                    deletion_date = datetime.now().strftime('%Y%m%d_%H%M%S_%f')
76
                    cur.execute('ALTER DATABASE %s RENAME TO removed_%s_%s' % (dbname,
77
                                                                               deletion_date,
78
                                                                               dbname))
79
            else:
80
                cur.execute("""SELECT table_name
81
                               FROM information_schema.tables
82
                               WHERE table_schema = 'public'
83
                               AND table_type = 'BASE TABLE'""")
84

  
85
                tables_names = [x[0] for x in cur.fetchall()]
86

  
87
                if options.force_drop:
88
                    for table_name in tables_names:
89
                        cur.execute('DROP TABLE %s CASCADE' % table_name)
90

  
91
                else:
92
                    deletion_date = datetime.now().strftime('%Y%m%d_%H%M%S_%f')
93
                    schema_name = 'removed_%s_%s' % (deletion_date, dbname)
94
                    cur.execute("CREATE SCHEMA %s" % schema_name[:63])
95
                    for table_name in tables_names:
96
                        cur.execute('ALTER TABLE %s SET SCHEMA %s' %
97
                                    (table_name, schema_name[:63]))
98

  
99
            if options.force_drop:
100
                rmtree(pub.app_dir)
101
            else:
102
                os.rename(pub.app_dir, pub.app_dir + '_removed_%s.invalid' % deletion_date)
103

  
104
        except psycopg2.Error as e:
105
            print >> sys.stderr, 'failed to alter database %s : (%s)' % (createdb_cfg['database'],
106
                                                                         psycopg2.errorcodes.lookup(e.pgcode))
107
            return
108

  
109
        cur.close()
110

  
111
CmdDeleteTenant.register()
wcs/sql.py
295 295
    conn, cur = get_connection_and_cursor()
296 296
    while True:
297 297
        cur.execute('''SELECT COUNT(*) FROM information_schema.tables
298
                       WHERE table_name LIKE %s''', ('formdata\\_%s\\_%%' % new_id,))
298
                       WHERE table_schema = 'public'
299
                       AND table_name LIKE %s''', ('formdata\\_%s\\_%%' % new_id,))
299 300
        if cur.fetchone()[0] == 0:
300 301
            break
301 302
        new_id += 1
......
306 307
def formdef_wipe():
307 308
    conn, cur = get_connection_and_cursor()
308 309
    cur.execute('''SELECT table_name FROM information_schema.tables
309
                   WHERE table_name LIKE %s''', ('formdata\\_%%\\_%%',))
310
                   WHERE table_schema = 'public'
311
                   AND table_name LIKE %s''', ('formdata\\_%%\\_%%',))
310 312
    for table_name in [x[0] for x in cur.fetchall()]:
311 313
        cur.execute('''DROP TABLE %s CASCADE''' % table_name)
312 314
    conn.commit()
......
338 340
    table_name = get_formdef_table_name(formdef)
339 341

  
340 342
    cur.execute('''SELECT COUNT(*) FROM information_schema.tables
341
                   WHERE table_name = %s''', (table_name,))
343
                   WHERE table_schema = 'public'
344
                   AND table_name = %s''', (table_name,))
342 345
    if cur.fetchone()[0] == 0:
343 346
        cur.execute('''CREATE TABLE %s (id serial PRIMARY KEY,
344 347
                                    user_id varchar,
......
358 361
                                    formdata_id integer REFERENCES %s (id) ON DELETE CASCADE)''' % (
359 362
                                    table_name, table_name))
360 363
    cur.execute('''SELECT column_name FROM information_schema.columns
361
                    WHERE table_name = %s''', (table_name,))
364
                   WHERE table_schema = 'public'
365
                   AND table_name = %s''', (table_name,))
362 366
    existing_fields = set([x[0] for x in cur.fetchall()])
363 367

  
364 368
    needed_fields = set(['id', 'user_id', 'receipt_time',
......
469 473
    table_name = 'users'
470 474

  
471 475
    cur.execute('''SELECT COUNT(*) FROM information_schema.tables
472
                   WHERE table_name = %s''', (table_name,))
476
                   WHERE table_schema = 'public'
477
                   AND table_name = %s''', (table_name,))
473 478
    if cur.fetchone()[0] == 0:
474 479
        cur.execute('''CREATE TABLE %s (id serial PRIMARY KEY,
475 480
                                    name varchar,
......
483 488
                                    lasso_dump text,
484 489
                                    last_seen timestamp)''' % table_name)
485 490
    cur.execute('''SELECT column_name FROM information_schema.columns
486
                    WHERE table_name = %s''', (table_name,))
491
                   WHERE table_schema = 'public'
492
                   AND table_name = %s''', (table_name,))
487 493
    existing_fields = set([x[0] for x in cur.fetchall()])
488 494

  
489 495
    needed_fields = set(['id', 'name', 'email', 'roles', 'is_admin',
......
546 552
    table_name = 'tracking_codes'
547 553

  
548 554
    cur.execute('''SELECT COUNT(*) FROM information_schema.tables
549
                   WHERE table_name = %s''', (table_name,))
555
                   WHERE table_schema = 'public'
556
                   AND table_name = %s''', (table_name,))
550 557
    if cur.fetchone()[0] == 0:
551 558
        cur.execute('''CREATE TABLE %s (id varchar PRIMARY KEY,
552 559
                                    formdef_id varchar,
553 560
                                    formdata_id varchar)''' % table_name)
554 561
    cur.execute('''SELECT column_name FROM information_schema.columns
555
                    WHERE table_name = %s''', (table_name,))
562
                   WHERE table_schema = 'public'
563
                   AND table_name = %s''', (table_name,))
556 564
    existing_fields = set([x[0] for x in cur.fetchall()])
557 565

  
558 566
    needed_fields = set(['id', 'formdef_id', 'formdata_id'])
......
572 580
        conn, cur = get_connection_and_cursor()
573 581

  
574 582
    cur.execute('''SELECT COUNT(*) FROM information_schema.tables
575
                   WHERE table_name = %s''', ('wcs_meta',))
583
                   WHERE table_schema = 'public'
584
                   AND table_name = %s''', ('wcs_meta',))
576 585
    if cur.fetchone()[0] == 0:
577 586
        cur.execute('''CREATE TABLE wcs_meta (id serial PRIMARY KEY,
578 587
                                    key varchar,
......
607 616
    if formdef:
608 617
        # remove the form view itself
609 618
        cur.execute('''SELECT table_name FROM information_schema.views
610
                       WHERE table_name LIKE %s''', ('wcs\\_view\\_%s\\_%%' % formdef.id ,))
619
                       WHERE table_schema = 'public'
620
                       AND table_name LIKE %s''', ('wcs\\_view\\_%s\\_%%' % formdef.id ,))
611 621
    else:
612 622
        # if there's no formdef specified, remove all form views
613 623
        cur.execute('''SELECT table_name FROM information_schema.views
614
                       WHERE table_name LIKE %s''', ('wcs\\_view\\_%',))
624
                       WHERE table_schema = 'public'
625
                       AND table_name LIKE %s''', ('wcs\\_view\\_%',))
615 626
    view_names = []
616 627
    while True:
617 628
        row = cur.fetchone()
......
715 726

  
716 727
def drop_global_views(conn, cur):
717 728
    cur.execute('''SELECT table_name FROM information_schema.views
718
                   WHERE table_name LIKE %s''', ('wcs\\_category\\_%',))
729
                   WHERE table_schema = 'public'
730
                   AND table_name LIKE %s''', ('wcs\\_category\\_%',))
719 731
    view_names = []
720 732
    while True:
721 733
        row = cur.fetchone()
......
739 751
    view_names = [get_formdef_view_name(x) for x in FormDef.select()]
740 752

  
741 753
    cur.execute('''SELECT table_name FROM information_schema.views
742
                   WHERE table_name LIKE %s''', ('wcs\\_view\\_%',))
754
                   WHERE table_schema = 'public'
755
                   AND table_name LIKE %s''', ('wcs\\_view\\_%',))
743 756
    existing_views = set()
744 757
    while True:
745 758
        row = cur.fetchone()
......
1884 1897

  
1885 1898
def migrate_global_views(conn, cur):
1886 1899
    cur.execute('''SELECT COUNT(*) FROM information_schema.tables
1887
                   WHERE table_name = %s''', ('wcs_all_forms',))
1900
                   WHERE table_schema = 'public'
1901
                   AND table_name = %s''', ('wcs_all_forms',))
1888 1902
    existing_fields = set([x[0] for x in cur.fetchall()])
1889 1903
    if 'formdef_id' not in existing_fields:
1890 1904
        drop_global_views(conn, cur)
1891
-