Projet

Général

Profil

0001-ctl-add-dump-in-backup-command-if-postgresql-is-true.patch

Jean-Baptiste Jaillet, 26 juillet 2017 02:11

Télécharger (9,18 ko)

Voir les différences:

Subject: [PATCH] ctl: add dump in backup command if postgresql is true
 (#17726)

 tests/test_ctl.py |  44 ++++++++++++++++++
 wcs/ctl/backup.py | 131 ++++++++++++++++++++++++++++++++++++++++++++++--------
 2 files changed, 156 insertions(+), 19 deletions(-)
tests/test_ctl.py
4 4
from email.mime.text import MIMEText
5 5
from email.mime.multipart import MIMEMultipart
6 6
import psycopg2
7
import shutil
8
import zipfile
7 9

  
8 10
from wcs.formdef import FormDef
9 11
from wcs.workflows import Workflow
......
15 17
from wcs.ctl.wipe_data import CmdWipeData
16 18
from wcs.ctl.trigger_jumps import select_and_jump_formdata
17 19
from wcs.ctl.delete_tenant import CmdDeleteTenant
20
from wcs.ctl.backup import CmdBackup
18 21
from wcs.sql import get_connection_and_cursor, cleanup_connection
19 22

  
20 23
from utilities import create_temporary_pub, clean_temporary_pub
......
324 327
        assert False
325 328

  
326 329
    clean_temporary_pub()
330

  
331

  
332
def test_backup_tenant():
333
    pub = create_temporary_pub(sql_mode=True)
334
    backup_cmd = CmdBackup()
335
    if os.path.exists('/tmp/test'):
336
        shutil.rmtree('/tmp/test')
337
    os.mkdir('/tmp/test')
338

  
339
    sub_options_class = collections.namedtuple('Options', ['destination'])
340
    sub_options = sub_options_class('/tmp/test')
341

  
342
    backup_cmd.backup_instance(pub, sub_options, ['example.test.com'])
343

  
344
    if not any(z for z in os.listdir('/tmp/test') if '.zip' in z):
345
        assert False
346

  
347
    for z in os.listdir('/tmp/test'):
348
        if '.zip' in z:
349
            with zipfile.ZipFile(os.path.join('/tmp/test', z), 'r') as zip_file:
350
                zip_list = zip_file.namelist()
351

  
352
    if not any(tar for tar in zip_list if '.tar.gz' in tar):
353
        assert False
354

  
355
    if not any(dump for dump in zip_list if '.sql' in dump):
356
        assert False
357

  
358
    clean_temporary_pub()
359
    shutil.rmtree('/tmp/test')
360

  
361
    pub = create_temporary_pub()
362
    os.mkdir('/tmp/test')
363

  
364
    backup_cmd.backup_instance(pub, sub_options, ['example.test.com'])
365

  
366
    if not any(tar for tar in os.listdir('/tmp/test') if '.tar.gz' in tar):
367
        assert False
368

  
369
    clean_temporary_pub()
370
    shutil.rmtree('/tmp/test')
wcs/ctl/backup.py
15 15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
import tarfile
18
import time
19 18
import os
19
import shutil
20
import zipfile
21
import subprocess
22
import tempfile
23
from datetime import datetime
20 24

  
21 25
from qommon.ctl import Command, make_option
22 26

  
23 27

  
28
class CmdError(Exception):
29
    pass
30

  
31

  
24 32
class CmdBackup(Command):
25 33
    name = 'backup'
26 34

  
27 35
    def __init__(self):
28 36
        Command.__init__(self, [
29
                make_option('--file', metavar='FILENAME', action='store',
30
                            dest='filename', default=None)
37
                make_option('--destination', action='store',
38
                            dest='destination', default=None)
31 39
                ])
32 40

  
33 41
    def execute(self, base_options, sub_options, args):
......
42 50
        if not os.path.exists(pub.app_dir):
43 51
            return 1
44 52

  
45
        if sub_options.filename:
46
            backup_filepath = sub_options.filename
53
        pub.set_config()
54
        self.backup_instance(pub, sub_options, args)
55

  
56
    def backup_instance(self, pub, sub_options, args):
57
        instance_name = args[0].replace('.', '_').replace('-', '_')
58
        file_name = 'wcs__%s__%s' % (instance_name,
59
                                     datetime.now().date().isoformat())
60
        dest_dir = sub_options.destination
61
        if dest_dir:
62
            if not os.path.exists(dest_dir):
63
                raise CmdError('destination directory does not exist')
47 64
        else:
48
            backup_dir = os.path.join(pub.app_dir, 'backups')
49
            if not os.path.exists(backup_dir):
50
                os.mkdir(backup_dir)
51
            backup_filepath = os.path.join(backup_dir,
52
                            'backup-%s%s%s-%s%s%s.tar.gz' % time.localtime()[:6])
53

  
54
        backup = tarfile.open(backup_filepath, mode='w:gz')
55
        for basename, dirnames, filenames in os.walk(pub.app_dir):
56
            if 'backups' in dirnames: # do not recurse in backup directory
57
                idx = dirnames.index('backups')
58
                dirnames[idx:idx+1] = []
59
            for filename in filenames:
60
                backup.add(os.path.join(basename, filename),
61
                           os.path.join(basename, filename)[len(pub.app_dir):])
65
            dest_dir = os.path.join(pub.app_dir, 'backups')
66
            if not os.path.exists(dest_dir):
67
                os.mkdir(dest_dir)
68
        try:
69
            temp_dir = tempfile.mkdtemp()
70
        except IOError as e:
71
            raise CmdError('an error occurred while creating temporary directory: %s' % e.message)
72

  
73
        try:
74
            instance_tar_path = self.backup_instance_dir(pub, file_name, temp_dir)
75
            if pub.is_using_postgresql():
76
                dump_path = self.backup_postgresql(pub, file_name, temp_dir)
77
                self.create_backup_zip(dump_path, instance_tar_path, file_name, dest_dir)
78
            else:
79
                try:
80
                    # move only the tar file to the destination directory as there's no need
81
                    # to make a zip
82
                    shutil.move(instance_tar_path, dest_dir)
83
                except IOError as e:
84
                    raise CmdError('could not move instance tar %s to %s: %s' % (instance_tar_path,
85
                                                                                 dest_dir,
86
                                                                                 e.message))
87
        finally:
88
            try:
89
                shutil.rmtree(temp_dir)
90
            except IOError as e:
91
                print 'could not remove temporary directory %s: %s' % (temp_dir, e.message)
92

  
93

  
94
    def backup_instance_dir(self, pub, file_name, temp_dir):
95
        backup_filepath = os.path.join(temp_dir, '%s.tar.gz' % file_name)
96
        try:
97
            backup = tarfile.open(backup_filepath, mode='w:gz')
98

  
99
            for basename, dirnames, filenames in os.walk(pub.app_dir):
100
                if 'backups' in dirnames: # do not recurse in backup directory
101
                    idx = dirnames.index('backups')
102
                    dirnames[idx:idx+1] = []
103
                for filename in filenames:
104
                    backup.add(os.path.join(basename, filename),
105
                               os.path.join(basename, filename)[len(pub.app_dir):])
106
        except IOError as e:
107
            raise CmdError('could not open tar file %s: %s' % (backup_filepath,
108
                                                               e.message))
109
        except tarfile.TarError:
110
            raise CmdError('an error occured while making tar archive for %s' % file_name)
62 111

  
63 112
        backup.close()
64 113

  
114
        return backup_filepath
115

  
116
    def backup_postgresql(self, pub, file_name, temp_dir):
117
        db_conf = pub.cfg['postgresql']
118
        # use 'or' as value are at None when not set
119
        db_name = db_conf.get('database') or ''
120
        db_host = db_conf.get('host') or ''
121
        db_user = db_conf.get('user') or ''
122
        db_port = db_conf.get('port') or ''
123
        db_pwd = db_conf.get('password') or ''
124
        if db_pwd:
125
            os.environ['PGPASSWORD'] = db_pwd
126

  
127
        dump_path = os.path.join(temp_dir, '%s.sql' % file_name)
128

  
129
        try:
130
            subprocess.check_call(['pg_dump', '-Oc', '-h', db_host,
131
                                   '-U', db_user, '-p', db_port,
132
                                   '-w', '-f', dump_path, '-d', db_name])
133
        except subprocess.CalledProcessError as e:
134
            raise CmdError('an error occured while dumping instance database %s: %s' % (db_name,
135
                                                                                        e.output))
136
        if db_pwd:
137
            os.environ.pop('PGPASSWORD')
138

  
139
        return dump_path
140

  
141
    def create_backup_zip(self, dump_path, instance_tar_path, file_name, dest_dir):
142
        zip_path = os.path.join(dest_dir, '%s.zip' % file_name)
143
        try:
144
            with zipfile.ZipFile(zip_path, 'a') as zip_file:
145
                zip_file.write(instance_tar_path,
146
                               os.path.basename(instance_tar_path))
147
                zip_file.write(dump_path,
148
                               os.path.basename(dump_path))
149
        except RuntimeError as e:
150
            if os.path.exists(zip_path):
151
                try:
152
                    os.remove(zip_path)
153
                except OSError as e:
154
                    print 'could not remove zip file %s: %s' % (zip_path, e.message)
155
            raise CmdError('an error occured while making zip %s: %s' % (zip_path,
156
                                                                         e.message))
157

  
65 158

  
66 159
CmdBackup.register()
67
-