diff --git a/docs/topics/development/data_management.md b/docs/topics/development/data_management.md index 7f81dcd8b7a..2c749e7f346 100644 --- a/docs/topics/development/data_management.md +++ b/docs/topics/development/data_management.md @@ -45,7 +45,7 @@ with specific logic to save/load backups in our specified backup directory. ``` This command creates a dump of the current MySQL database. The command accepts an optional `name` argument which will determine - the name of the directory created in the `DATA_BACKUP_DIR` directory. By default it uses a timestamp to ensure uniqueness. + the name of the directory created in the `DATA_BACKUP_DIRNAME` directory. By default it uses a timestamp to ensure uniqueness. You can also specify the `--force` argument to overwrite an existing backup with the same name. @@ -55,7 +55,7 @@ with specific logic to save/load backups in our specified backup directory. make data_load [ARGS="--name "] ``` - This command will load data from an existing backup directory. The name is required and must match a directory in the `DATA_BACKUP_DIR` directory. + This command will load data from an existing backup directory. The name is required and must match a directory in the `DATA_BACKUP_DIRNAME` directory. > NOTE: This command will NOT reindex elasticsearch. In most cases you should use the `make initialize_data` command instead. > You can specify the `--load ` argument to load a specific backup and ensure the index is recreated. diff --git a/requirements/dev.txt b/requirements/dev.txt index 10357445fea..e27c3970559 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -308,3 +308,6 @@ pytest-split==0.9.0 \ pytest-reportlog==0.4.0 \ --hash=sha256:5db4d00586546d8c6b95c66466629f1e913440c36d97795a673d2e19c5cedd5c \ --hash=sha256:c9f2079504ee51f776d3118dcf5e4730f163d3dcf26ebc8f600c1fa307bf638c +django-dbbackup==4.2.1 \ + --hash=sha256:157a2ec10d482345cd75092e510ac40d6e2ee6084604a1d17abe178c2f06bc69 \ + --hash=sha256:b23265600ead0780ca781b1b4b594949aaa8a20d74f08701f91ee9d7eb1f08cd diff --git a/settings.py b/settings.py index dbb5be584c9..21241f65370 100644 --- a/settings.py +++ b/settings.py @@ -17,7 +17,22 @@ INTERNAL_ROUTES_ALLOWED = True # These apps are great during development. -INSTALLED_APPS += ('olympia.landfill',) +INSTALLED_APPS += ( + 'olympia.landfill', + 'dbbackup', +) + +# Settings for django-dbbackup +DATA_BACKUP_DIRNAME = path('backups') +DATA_BACKUP_INIT = '_init' +DATA_BACKUP_DB_FILENAME = 'db.sql' +DATA_BACKUP_STORAGE_FILENAME = 'storage.tar' + +DBBACKUP_STORAGE = 'django.core.files.storage.FileSystemStorage' +DBBACKUP_STORAGE_OPTIONS = {'location': DATA_BACKUP_DIRNAME} +DBBACKUP_CONNECTOR_MAPPING = { + 'olympia.core.db.mysql': 'dbbackup.db.mysql.MysqlDumpConnector', +} # Override logging config to enable DEBUG logs for (almost) everything. LOGGING['root']['level'] = logging.DEBUG diff --git a/src/olympia/amo/management/base.py b/src/olympia/amo/management/base.py new file mode 100644 index 00000000000..b27f1f3675a --- /dev/null +++ b/src/olympia/amo/management/base.py @@ -0,0 +1,50 @@ +import logging +import os +import shutil + +from django.conf import settings +from django.core.management import call_command +from django.core.management.base import BaseCommand, CommandError + + +class BaseDataCommand(BaseCommand): + # Settings for django-dbbackup + data_backup_dirname = os.path.abspath( + os.path.join(settings.ROOT, 'backups') + ) + data_backup_init = '_init' + data_backup_db_filename = 'db.sql' + data_backup_storage_filename = 'storage.tar' + + call_command = call_command + logger = logging + + def backup_dir_path(self, name): + return os.path.abspath(os.path.join(self.data_backup_dirname, name)) + + def backup_db_path(self, name): + return os.path.abspath( + os.path.join(self.backup_dir_path(name), self.data_backup_db_filename) + ) + + def backup_storage_path(self, name): + return os.path.abspath( + os.path.join(self.backup_dir_path(name), self.data_backup_storage_filename) + ) + + def clean_dir(self, name: str) -> None: + path = self.backup_dir_path(name) + logging.info(f'Clearing {path}') + shutil.rmtree(path, ignore_errors=True) + + def make_dir(self, name: str, force: bool = False) -> None: + path = self.backup_dir_path(name) + path_exists = os.path.exists(path) + + if path_exists and not force: + raise CommandError( + f'path {path} already exists.' 'Use --force to overwrite.' + ) + + self.clean_dir(name) + os.makedirs(path, exist_ok=True) diff --git a/src/olympia/amo/management/commands/dump_data.py b/src/olympia/amo/management/commands/dump_data.py index c041bf5c5b8..08eac4731b5 100644 --- a/src/olympia/amo/management/commands/dump_data.py +++ b/src/olympia/amo/management/commands/dump_data.py @@ -1,23 +1,16 @@ -import logging -import os -import shutil from datetime import datetime -from django.conf import settings -from django.core.management import call_command -from django.core.management.base import BaseCommand, CommandError +from ..base import BaseDataCommand -class Command(BaseCommand): +class Command(BaseDataCommand): help = 'Dump data with a specified name' def add_arguments(self, parser): parser.add_argument( '--name', type=str, - default=datetime.now().strftime( - '%Y%m%d%H%M%S' - ), # Default to current timestamp + default=datetime.now().strftime('%Y%m%d%H%M%S'), help='Name of the data dump', ) parser.add_argument( @@ -28,37 +21,23 @@ def handle(self, *args, **options): name = options.get('name') force = options.get('force') - dump_path = os.path.abspath(os.path.join(settings.DATA_BACKUP_DIR, name)) + dump_path = self.backup_dir_path(name) + db_path = self.backup_db_path(name) + storage_path = self.backup_storage_path(name) - logging.info(f'Dumping data to {dump_path}') + self.make_dir(dump_path, force=force) - if os.path.exists(dump_path): - if force: - shutil.rmtree(dump_path) - else: - raise CommandError( - f'Dump path {dump_path} already exists.' - 'Use --force to overwrite or --init to reseed the initial data.' - ) - - os.makedirs(dump_path, exist_ok=True) - - data_file_path = os.path.join(dump_path, 'data.json') - call_command( - 'dumpdata', - format='json', - indent=2, - output=data_file_path, - all=True, - natural_foreign=True, - natural_primary=True, - exclude=[ - 'contenttypes.contenttype', - 'auth.permission', - 'sessions.session', - ] + # For some reason you cannot send the options like this.. but you cannot test for them as named arguments.. fixd it. + self.call_command( + 'dbbackup', + output_path=db_path, + interactive=False, + compress=True, ) - storage_from = settings.STORAGE_ROOT - storage_to = os.path.join(dump_path, 'storage') - shutil.copytree(storage_from, storage_to) + self.call_command( + 'mediabackup', + output_path=storage_path, + interactive=False, + compress=True, + ) diff --git a/src/olympia/amo/management/commands/load_data.py b/src/olympia/amo/management/commands/load_data.py index 64546ee2a87..005722bae6c 100644 --- a/src/olympia/amo/management/commands/load_data.py +++ b/src/olympia/amo/management/commands/load_data.py @@ -1,13 +1,7 @@ -import logging -import os -import shutil +from ..base import BaseDataCommand -from django.conf import settings -from django.core.management import call_command -from django.core.management.base import BaseCommand, CommandError - -class Command(BaseCommand): +class Command(BaseDataCommand): help = 'Load data from a specified name' def add_arguments(self, parser): @@ -20,17 +14,20 @@ def add_arguments(self, parser): def handle(self, *args, **options): name = options.get('name') - load_path = os.path.abspath(os.path.join(settings.DATA_BACKUP_DIR, name)) - - logging.info(f'Loading data from {load_path}') - - if not os.path.exists(load_path): - raise CommandError(f'Dump path {load_path} does not exist.') - - data_file_path = os.path.join(load_path, 'data.json') - call_command('loaddata', data_file_path) + db_path = self.backup_db_path(name) + storage_path = self.backup_storage_path(name) + + self.call_command( + 'dbrestore', + input_path=db_path, + interactive=False, + uncompress=True, + ) - storage_from = os.path.join(load_path, 'storage') - storage_to = os.path.abspath(settings.STORAGE_ROOT) - logging.info(f'Copying storage from {storage_from} to {storage_to}') - shutil.copytree(storage_from, storage_to, dirs_exist_ok=True) + self.call_command( + 'mediarestore', + input_path=storage_path, + interactive=False, + uncompress=True, + replace=True, + ) diff --git a/src/olympia/amo/management/commands/seed_data.py b/src/olympia/amo/management/commands/seed_data.py index 9037d0352ce..bcb19216cfa 100644 --- a/src/olympia/amo/management/commands/seed_data.py +++ b/src/olympia/amo/management/commands/seed_data.py @@ -1,32 +1,28 @@ -import logging -import os -import shutil - from django.conf import settings -from django.core.management import call_command -from django.core.management.base import BaseCommand + +from ..base import BaseDataCommand -class Command(BaseCommand): +class Command(BaseDataCommand): help = 'Seed the _init data dir with fresh data from the database' def handle(self, *args, **options): - init_name = settings.DATA_BACKUP_INIT - init_path = os.path.abspath(os.path.join(settings.DATA_BACKUP_DIR, init_name)) - logging.info(f'Clearing {init_path}') - shutil.rmtree(init_path, ignore_errors=True) - - logging.info('Resetting database...') - call_command('flush', '--noinput') - call_command('migrate', '--noinput') + num_addons = 10 + num_themes = 5 + + self.clean_dir(self.data_backup_init) + + self.logger.info('Resetting database...') + self.call_command('flush', '--noinput') + self.call_command('migrate', '--noinput') # reindex --wipe will force the ES mapping to be re-installed. Useful to # make sure the mapping is correct before adding a bunch of add-ons. - call_command('reindex', '--wipe', '--force', '--noinput') + self.call_command('reindex', '--wipe', '--force', '--noinput') - logging.info('Loading initial data...') - call_command('loaddata', 'initial.json') - call_command('import_prod_versions') - call_command( + self.logger.info('Loading initial data...') + self.call_command('loaddata', 'initial.json') + self.call_command('import_prod_versions') + self.call_command( 'createsuperuser', '--no-input', '--username', @@ -34,15 +30,13 @@ def handle(self, *args, **options): '--email', settings.LOCAL_ADMIN_EMAIL, ) - call_command('loaddata', 'zadmin/users') - - logging.info('Generating add-ons...') - call_command('generate_addons', '--app', 'firefox', 10) - call_command('generate_addons', '--app', 'android', 10) - call_command('generate_themes', 5) - # These add-ons are specifically useful for the addons-frontend - # homepage. You may have to re-run this, in case the data there - # changes. - call_command('generate_default_addons_for_frontend') - logging.info(f'Dumping data to {init_path}') - call_command('dump_data', '--name', init_name) + self.call_command('loaddata', 'zadmin/users') + + self.logger.info('Generating add-ons...') + self.call_command('generate_addons', '--app', 'firefox', num_addons) + self.call_command('generate_addons', '--app', 'android', num_addons) + self.call_command('generate_themes', num_themes) + + self.call_command('generate_default_addons_for_frontend') + + self.call_command('dump_data', '--name', self.data_backup_init) diff --git a/src/olympia/amo/tests/test_commands.py b/src/olympia/amo/tests/test_commands.py index c99661e3f4c..46deb25650c 100644 --- a/src/olympia/amo/tests/test_commands.py +++ b/src/olympia/amo/tests/test_commands.py @@ -13,6 +13,7 @@ from freezegun import freeze_time from olympia.addons.models import Preview +from olympia.amo.management.base import BaseDataCommand from olympia.amo.management.commands.get_changed_files import ( collect_addon_icons, collect_addon_previews, @@ -335,42 +336,142 @@ def path(self): ] -class TestBaseDataCommand(TestCase): +class BaseTestDataCommand(TestCase): class Commands: - migrate = ('migrate', '--noinput') - seed_data = ('seed_data',) - reindex = ('reindex', '--noinput', '--force') + flush = mock.call('flush', '--noinput') + migrate = mock.call('migrate', '--noinput') + seed_data = mock.call('seed_data') + reindex = mock.call('reindex', '--noinput', '--force') + + flush = mock.call('flush', '--noinput') + reindex = mock.call('reindex', '--wipe', '--force', '--noinput') + load_initial_data = mock.call('loaddata', 'initial.json') + import_prod_versions = mock.call('import_prod_versions') + createsuperuser = mock.call('createsuperuser', '--no-input', '--username', settings.LOCAL_ADMIN_USERNAME, '--email', settings.LOCAL_ADMIN_EMAIL) + load_zadmin_users = mock.call('loaddata', 'zadmin/users') + generate_default_addons_for_frontend = mock.call('generate_default_addons_for_frontend') + + def dump_data(self, name='_init'): + return mock.call('dump_data', '--name', name) + + def generate_addons(self, app, num_addons): + return mock.call('generate_addons', '--app', app, num_addons) + + def generate_themes(self, num_themes): + return mock.call('generate_themes', num_themes) def load_data(self, name='_init'): - return ('load_data', f'--name={name}') + return mock.call('load_data', f'--name={name}') + + def db_backup(self, output_path): + return mock.call('dbbackup', output_path=output_path, interactive=False, compress=True) + + def db_restore(self, input_path): + return mock.call('dbrestore', input_path=input_path, interactive=False, uncompress=True) + + def media_backup(self, output_path): + return mock.call('mediabackup', output_path=output_path, interactive=False, compress=True) + + def media_restore(self, input_path): + return mock.call( + 'mediarestore', input_path=input_path, interactive=False, uncompress=True, replace=True + ) + + base_data_command = BaseDataCommand() + backup_dir = '/data/olympia/backups' + db_file = 'db.sql' + storage_file = 'storage.tar' def setUp(self): - self.commands = self.Commands() + self.mock_commands = self.Commands() def _assert_commands_called_in_order(self, mock_call_command, expected_commands): - actual_commands = [ - call_args.args for call_args in mock_call_command.call_args_list - ] + actual_commands = mock_call_command.mock_calls assert actual_commands == expected_commands, ( f'Commands were not called in the expected order. ' f'Expected: {expected_commands}, Actual: {actual_commands}' ) -class TestDumpDataCommand(TestBaseDataCommand): +class TestBaseDataCommand(BaseTestDataCommand): + def setUp(self): + super().setUp() + self + + def test_backup_dir_path(self): + name = 'test_backup' + expected_path = os.path.join(self.backup_dir, name) + + actual_path = self.base_data_command.backup_dir_path(name) + assert actual_path == expected_path, f"Expected {expected_path}, got {actual_path}" + + def test_backup_db_path(self): + name = 'db_backup' + expected_path = os.path.join(self.backup_dir, name, self.db_file) + actual_path = self.base_data_command.backup_db_path(name) + assert actual_path == expected_path, f"Expected {expected_path}, got {actual_path}" + + def test_backup_storage_path(self): + name = 'storage_backup' + expected_path = os.path.join(self.backup_dir, name, self.storage_file) + actual_path = self.base_data_command.backup_storage_path(name) + assert actual_path == expected_path, f"Expected {expected_path}, got {actual_path}" + + @mock.patch('olympia.amo.management.base.shutil.rmtree') + @mock.patch('olympia.amo.management.base.logging') + def test_clean_dir(self, mock_logging, mock_rmtree): + name = 'cleanup_test' + backup_path = self.base_data_command.backup_dir_path(name) + + self.base_data_command.clean_dir(name) + + mock_logging.info.assert_called_with(f'Clearing {backup_path}') + mock_rmtree.assert_called_with(backup_path, ignore_errors=True) + + @mock.patch('olympia.amo.management.base.os.path.exists') + @mock.patch('olympia.amo.management.base.shutil.rmtree') + @mock.patch('olympia.amo.management.base.os.makedirs') + def test_make_dir_existing_path_no_force(self, mock_makedirs, mock_rmtree, mock_exists): + name = 'existing_dir' + mock_exists.return_value = True + + with self.assertRaises(CommandError) as context: + self.base_data_command.make_dir(name, force=False) + assert 'Directory already exists' in str(context.exception) + + @mock.patch('olympia.amo.management.base.os.path.exists') + @mock.patch('olympia.amo.management.base.shutil.rmtree') + @mock.patch('olympia.amo.management.base.os.makedirs') + def test_make_dir_existing_path_with_force(self, mock_makedirs, mock_rmtree, mock_exists): + name = 'existing_dir_force' + backup_path = self.base_data_command.backup_dir_path(name) + mock_exists.return_value = True + + self.base_data_command.make_dir(name, force=True) + + mock_exists.assert_called_with(backup_path) + mock_rmtree.assert_called_with(backup_path, ignore_errors=True) + mock_makedirs.assert_called_with(backup_path, exist_ok=True) + + @mock.patch('olympia.amo.management.base.os.path.exists') + @mock.patch('olympia.amo.management.base.os.makedirs') + def test_make_dir_non_existing_path(self, mock_makedirs, mock_exists): + name = 'new_dir' + backup_path = self.base_data_command.backup_dir_path(name) + mock_exists.return_value = False + + self.base_data_command.make_dir(name, force=False) + + mock_exists.assert_called_with(backup_path) + mock_makedirs.assert_called_with(backup_path, exist_ok=True) + + +class TestDumpDataCommand(BaseTestDataCommand): def setUp(self): + super().setUp() patches = ( - ('mock_exists', 'olympia.amo.management.commands.dump_data.os.path.exists'), - ('mock_rmtree', 'olympia.amo.management.commands.dump_data.shutil.rmtree'), - ( - 'mock_copytree', - 'olympia.amo.management.commands.dump_data.shutil.copytree', - ), - ('mock_makedirs', 'olympia.amo.management.commands.dump_data.os.makedirs'), - ( - 'mock_call_command', - 'olympia.amo.management.commands.dump_data.call_command', - ), + ('mock_make_dir', 'olympia.amo.management.commands.dump_data.BaseDataCommand.make_dir'), + ('mock_call_command', 'olympia.amo.management.commands.dump_data.BaseDataCommand.call_command'), ) self.mocks = {} @@ -381,134 +482,93 @@ def setUp(self): @freeze_time('2023-06-26 11:00:44') def test_default_name(self): - self.mocks['mock_exists'].return_value = False + print('backup', self.backup_dir) + backup_dir = os.path.join(self.backup_dir, '20230626110044') + db_path = os.path.join(backup_dir, self.db_file) + storage_path = os.path.join(backup_dir, self.storage_file) + call_command('dump_data') - self.mocks['mock_exists'].assert_called_with( - os.path.abspath(os.path.join(settings.DATA_BACKUP_DIR, '20230626110044')) + self.mocks['mock_make_dir'].assert_called_with(backup_dir, force=False) + self._assert_commands_called_in_order( + self.mocks['mock_call_command'], + [ + self.mock_commands.db_backup(db_path), + self.mock_commands.media_backup(storage_path), + ], ) def test_custom_name(self): name = 'test' - self.mocks['mock_exists'].return_value = False - call_command('dump_data', name=name) - self.mocks['mock_exists'].assert_called_with( - os.path.abspath(os.path.join(settings.DATA_BACKUP_DIR, name)) - ) - - def test_existing_dump_dir_no_force(self): - self.mocks['mock_exists'].return_value = True - - with pytest.raises(CommandError): - call_command('dump_data') + backup_dir = os.path.join(self.backup_dir, name) - def test_existing_dump_dir_with_force(self): - self.mocks['mock_exists'].return_value = True - call_command('dump_data', force=True) - self.mocks['mock_rmtree'].assert_called_once() - - def test_dumps_data(self): - name = 'test' - dump_path = os.path.abspath(os.path.join(settings.DATA_BACKUP_DIR, name)) - self.mocks['mock_exists'].return_value = False call_command('dump_data', name=name) - - self.mocks['mock_call_command'].assert_called_once_with( - 'dumpdata', - format='json', - indent=2, - output=os.path.join(dump_path, 'data.json'), - ) - self.mocks['mock_copytree'].assert_called_once_with( - settings.STORAGE_ROOT, os.path.join(dump_path, 'storage') - ) + self.mocks['mock_make_dir'].assert_called_with(backup_dir, force=False) -class TestLoadDataCommand(TestBaseDataCommand): +class TestLoadDataCommand(BaseTestDataCommand): def setUp(self): - self.data_backup_dir = os.path.abspath(settings.DATA_BACKUP_DIR) - self.storage_root = os.path.abspath(settings.STORAGE_ROOT) + super().setUp() def test_missing_name(self): with pytest.raises(CommandError): call_command('load_data') - @mock.patch('olympia.amo.management.commands.load_data.call_command') - @mock.patch('olympia.amo.management.commands.load_data.shutil.copytree') - @mock.patch('olympia.amo.management.commands.load_data.os.path.exists') - def test_custom_name(self, mock_exists, mock_copytree, mock_call_command): - mock_exists.return_value = True - custom_name = 'custom_backup' - call_command('load_data', name=custom_name) - mock_call_command.assert_called_with( - 'loaddata', os.path.join(self.data_backup_dir, custom_name, 'data.json') - ) - mock_copytree.assert_called_with( - os.path.join(self.data_backup_dir, custom_name, 'storage'), - self.storage_root, - dirs_exist_ok=True, - ) - - @mock.patch('olympia.amo.management.commands.load_data.call_command') - @mock.patch('olympia.amo.management.commands.load_data.shutil.copytree') - @mock.patch('olympia.amo.management.commands.load_data.os.path.exists') - def test_loaddata_called_with_correct_args( - self, mock_exists, mock_copytree, mock_call_command - ): - mock_exists.return_value = True - name = 'test_backup' - call_command('load_data', name=name) - data_file = os.path.join(self.data_backup_dir, name, 'data.json') - mock_call_command.assert_called_with('loaddata', data_file) - - @mock.patch('olympia.amo.management.commands.load_data.call_command') - @mock.patch('olympia.amo.management.commands.load_data.shutil.copytree') - @mock.patch('olympia.amo.management.commands.load_data.os.path.exists') - def test_copytree_called_with_correct_args( - self, mock_exists, mock_copytree, mock_call_command - ): - mock_exists.return_value = True + @mock.patch('olympia.amo.management.commands.load_data.BaseDataCommand.call_command') + def test_loads_correct_path(self, mock_call_command): name = 'test_backup' + backup_dir = os.path.join(self.backup_dir, name) + db_path = os.path.join(backup_dir, self.db_file) + storage_path = os.path.join(backup_dir, self.storage_file) + call_command('load_data', name=name) - storage_from = os.path.join(self.data_backup_dir, name, 'storage') - storage_to = os.path.abspath(settings.STORAGE_ROOT) - mock_copytree.assert_called_with(storage_from, storage_to, dirs_exist_ok=True) - def test_load_data_with_missing_json_file(self): - pass + self._assert_commands_called_in_order( + mock_call_command, + [ + self.mock_commands.db_restore(db_path), + self.mock_commands.media_restore(storage_path), + ], + ) - def test_load_data_with_missing_storage_dir(self): - pass + def test_load_data_with_missing_data(self): + with pytest.raises(CommandError) as context: + call_command('load_data', name='test_backup') + assert 'Directory not found' in str(context.value) + + +class TestSeedDataCommand(BaseTestDataCommand): + def setUp(self): + super().setUp() + + patches = ( + ('mock_call_command', 'olympia.amo.management.commands.seed_data.BaseDataCommand.call_command'), + ('mock_clean_dir', 'olympia.amo.management.commands.seed_data.BaseDataCommand.clean_dir'), + ) + self.mocks = {} -class TestSeedDataCommand(TestBaseDataCommand): - @mock.patch('olympia.amo.management.commands.seed_data.call_command') - @mock.patch('olympia.amo.management.commands.seed_data.shutil.rmtree') - def test_handle_seed_data(self, mock_rmtree, mock_call_command): - init_name = settings.DATA_BACKUP_INIT - init_path = os.path.abspath(os.path.join(settings.DATA_BACKUP_DIR, init_name)) + for mock_name, mock_path in patches: + patcher = mock.patch(mock_path) + self.addCleanup(patcher.stop) + self.mocks[mock_name] = patcher.start() + def test_default(self): call_command('seed_data') - mock_rmtree.assert_called_with(init_path, ignore_errors=True) - expected_calls = [ - mock.call('flush', '--noinput'), - mock.call('migrate', '--noinput'), - mock.call('reindex', '--wipe', '--force', '--noinput'), - mock.call('loaddata', 'initial.json'), - mock.call('import_prod_versions'), - mock.call( - 'createsuperuser', - '--no-input', - '--username', - 'local_admin', - '--email', - 'local_admin@mozilla.com', - ), - mock.call('loaddata', 'zadmin/users'), - mock.call('generate_addons', '--app', 'firefox', 10), - mock.call('generate_addons', '--app', 'android', 10), - mock.call('generate_themes', 5), - mock.call('generate_default_addons_for_frontend'), - mock.call('dump_data', '--name', init_name), - ] - mock_call_command.assert_has_calls(expected_calls, any_order=False) + self._assert_commands_called_in_order( + self.mocks['mock_call_command'], + [ + self.mock_commands.flush, + self.mock_commands.migrate, + self.mock_commands.reindex, + self.mock_commands.load_initial_data, + self.mock_commands.import_prod_versions, + self.mock_commands.createsuperuser, + self.mock_commands.load_zadmin_users, + self.mock_commands.generate_addons('firefox', 10), + self.mock_commands.generate_addons('android', 10), + self.mock_commands.generate_themes(5), + self.mock_commands.generate_default_addons_for_frontend, + self.mock_commands.dump_data(self.base_data_command.data_backup_init), + ], + ) diff --git a/src/olympia/lib/settings_base.py b/src/olympia/lib/settings_base.py index dc40fe48475..a26cc2c9897 100644 --- a/src/olympia/lib/settings_base.py +++ b/src/olympia/lib/settings_base.py @@ -1328,9 +1328,6 @@ def read_only_mode(env): MEDIA_ROOT = os.path.join(SHARED_STORAGE, 'uploads') TMP_PATH = os.path.join(SHARED_STORAGE, 'tmp') -DATA_BACKUP_DIR = path('backups') -DATA_BACKUP_INIT = '_init' - # These are key files that must be present on disk to encrypt/decrypt certain # database fields. # {'api_key:secret': os.path.join(ROOT, 'path', 'to', 'file.key'),}