Projet

Général

Profil

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

Jean-Baptiste Jaillet, 01 juin 2017 17:49

Télécharger (18,1 ko)

Voir les différences:

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

 tests/test_ctl.py        | 139 +++++++++++++++++++++++++++++++++++++++++++++++
 wcs/ctl/check_hobos.py   |   9 ++-
 wcs/ctl/delete_tenant.py | 112 ++++++++++++++++++++++++++++++++++++++
 wcs/sql.py               |  42 +++++++++-----
 4 files changed, 287 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_with_sql():
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'
229
                   AND 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()
294

  
295

  
296
def test_delete_tenant_without_sql():
297
    pub = create_temporary_pub()
298
    delete_cmd = CmdDeleteTenant()
299

  
300
    assert os.path.isdir(pub.app_dir)
301

  
302
    sub_options_class = collections.namedtuple('Options', ['force_drop'])
303
    sub_options = sub_options_class(False)
304

  
305
    delete_cmd.delete_tenant(pub, sub_options, [])
306

  
307
    assert not os.path.isdir(pub.app_dir)
308
    parent_dir = os.path.dirname(pub.app_dir)
309
    if not [filename for filename in os.listdir(parent_dir) if 'removed' in filename]:
310
        assert False
311

  
312
    clean_temporary_pub()
313

  
314
    pub = create_temporary_pub()
315
    assert os.path.isdir(pub.app_dir)
316

  
317
    sub_options = sub_options_class(True)
318

  
319
    delete_cmd.delete_tenant(pub, sub_options, [])
320

  
321
    assert not os.path.isdir(pub.app_dir)
322
    parent_dir = os.path.dirname(pub.app_dir)
323
    if [filename for filename in os.listdir(parent_dir) if 'removed' in filename]:
324
        assert False
325

  
326
    clean_temporary_pub()
327

  
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
        if options.force_drop:
50
            rmtree(pub.app_dir)
51
        else:
52
            deletion_date = datetime.now().strftime('%Y%m%d_%H%M%S_%f')
53
            os.rename(pub.app_dir, pub.app_dir + '_removed_%s.invalid' % deletion_date)
54

  
55
        # do this only if the wcs has a postgresql configuration
56
        if pub.is_using_postgresql():
57
            postgresql_cfg = {}
58
            for k, v in pub.cfg['postgresql'].items():
59
                if v and isinstance(v, basestring):
60
                    postgresql_cfg[k] = v
61

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

  
75
            pgconn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
76
            cur = pgconn.cursor()
77
            try:
78
                dbname = pub.cfg['postgresql']['database']
79
                if createdb:
80
                    if options.force_drop:
81
                        cur.execute('DROP DATABASE %s' % dbname)
82
                    else:
83
                        cur.execute('ALTER DATABASE %s RENAME TO removed_%s_%s' % (dbname,
84
                                                                                   deletion_date,
85
                                                                                   dbname))
86
                else:
87
                    cur.execute("""SELECT table_name
88
                                   FROM information_schema.tables
89
                                   WHERE table_schema = 'public'
90
                                   AND table_type = 'BASE TABLE'""")
91

  
92
                    tables_names = [x[0] for x in cur.fetchall()]
93

  
94
                    if options.force_drop:
95
                        for table_name in tables_names:
96
                            cur.execute('DROP TABLE %s CASCADE' % table_name)
97

  
98
                    else:
99
                        schema_name = 'removed_%s_%s' % (deletion_date, dbname)
100
                        cur.execute("CREATE SCHEMA %s" % schema_name[:63])
101
                        for table_name in tables_names:
102
                            cur.execute('ALTER TABLE %s SET SCHEMA %s' %
103
                                        (table_name, schema_name[:63]))
104

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

  
110
            cur.close()
111

  
112
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
-