diff --git a/hooks/nova_cc_utils.py b/hooks/nova_cc_utils.py index 7b610cac..3f09fe6d 100644 --- a/hooks/nova_cc_utils.py +++ b/hooks/nova_cc_utils.py @@ -526,6 +526,7 @@ def get_step_upgrade_source(new_src): 'precise-icehouse/proposed': ('precise-proposed/grizzly', 'cloud:precise-havana/proposed'), 'trusty-liberty': ('*', 'cloud:trusty-kilo'), + 'xenial-ocata': ('*', 'cloud:xenial-newton'), # LP: #1711209 } try: os_codename = get_os_codename_install_source(new_src) @@ -551,12 +552,12 @@ def get_step_upgrade_source(new_src): continue with open(fpath, 'r') as f: - line = f.readline() - for target_src, (cur_pocket, step_src) in sources.items(): - if target_src != new_src: - continue - if cur_pocket in line: - return step_src + for line in f.readlines(): + for target_src, (cur_pocket, step_src) in sources.items(): + if target_src != new_src: + continue + if cur_pocket in line: + return step_src return None @@ -611,6 +612,12 @@ def _do_openstack_upgrade(new_src): if (CompareOpenStackReleases(os_release('nova-common')) == 'kilo' and is_leader()): migrate_nova_flavors() + + # 'nova-manage db online_data_migrations' needs to be run before moving to + # the next release for environments upgraded using old charms where this + # step was not being executed (LP: #1711209). + online_data_migrations_if_needed() + new_os_rel = get_os_codename_install_source(new_src) cmp_new_os_rel = CompareOpenStackReleases(new_os_rel) log('Performing OpenStack upgrade to %s.' % (new_os_rel)) @@ -655,6 +662,7 @@ def _do_openstack_upgrade(new_src): if is_leader(): status_set('maintenance', 'Running nova db migration') migrate_nova_databases() + if not is_unit_paused_set(): [service_start(s) for s in services()] @@ -695,6 +703,16 @@ def migrate_nova_flavors(): subprocess.check_output(cmd) +@retry_on_exception(5, base_delay=3, exc_type=subprocess.CalledProcessError) +def online_data_migrations_if_needed(): + '''Runs nova-manage to run online data migrations available since Mitaka''' + if (is_leader() and + CompareOpenStackReleases(os_release('nova-common')) >= 'mitaka'): + log('Running online_data_migrations', level=INFO) + subprocess.check_output(['nova-manage', 'db', + 'online_data_migrations']) + + def migrate_nova_api_database(): '''Initialize or migrate the nova_api database''' if CompareOpenStackReleases(os_release('nova-common')) >= 'mitaka': @@ -793,6 +811,7 @@ def migrate_nova_databases(): if CompareOpenStackReleases(os_release('nova-common')) < 'ocata': migrate_nova_api_database() migrate_nova_database() + online_data_migrations_if_needed() finalize_migrate_nova_databases() # TODO: Replace the following checks with a Cellsv2 context check. @@ -805,6 +824,7 @@ def migrate_nova_databases(): migrate_nova_api_database() initialize_cell_databases() migrate_nova_database() + online_data_migrations_if_needed() add_hosts_to_cell() finalize_migrate_nova_databases() diff --git a/unit_tests/test_nova_cc_hooks.py b/unit_tests/test_nova_cc_hooks.py index bebf24ec..65d150bd 100644 --- a/unit_tests/test_nova_cc_hooks.py +++ b/unit_tests/test_nova_cc_hooks.py @@ -566,9 +566,12 @@ class NovaCCHooksTests(CharmTestCase): self.assertFalse(self.migrate_nova_databases.called) api_joined.asert_called_with(rid='nova-api/0') + @patch.object(utils, 'is_leader') + @patch.object(utils, 'os_release') @patch.object(hooks, 'is_db_initialised') @patch.object(hooks, 'CONFIGS') - def test_db_changed_allowed(self, configs, mock_is_db_initialised): + def test_db_changed_allowed(self, configs, mock_is_db_initialised, + utils_os_release, utils_is_leader): mock_is_db_initialised.return_value = False allowed_units = 'nova-cloud-controller/0 nova-cloud-controller/3' self.test_relation.set({ @@ -576,6 +579,8 @@ class NovaCCHooksTests(CharmTestCase): }) self.local_unit.return_value = 'nova-cloud-controller/3' self.os_release.return_value = 'diablo' + utils_os_release.return_value = 'diablo' + utils_is_leader.return_value = False self._shared_db_test(configs) self.assertTrue(configs.write_all.called) self.migrate_nova_databases.assert_called_with() @@ -594,23 +599,30 @@ class NovaCCHooksTests(CharmTestCase): self.assertTrue(configs.write_all.called) self.assertFalse(self.migrate_nova_databases.called) + @patch.object(utils, 'is_leader') + @patch.object(utils, 'os_release') @patch.object(hooks, 'quantum_joined') @patch.object(hooks, 'nova_api_relation_joined') @patch.object(hooks, 'is_db_initialised') @patch.object(hooks, 'CONFIGS') def test_postgresql_db_changed(self, configs, mock_is_db_initialised, - api_joined, quantum_joined): + api_joined, quantum_joined, + utils_os_release, utils_is_leader): self.relation_ids.side_effect = [ [], ['neutron-gateway/0'], ['nova-api/0']] mock_is_db_initialised.return_value = False self.os_release.return_value = 'diablo' + utils_os_release.return_value = 'diablo' + utils_is_leader.return_value = True self._postgresql_db_test(configs) self.assertTrue(configs.write_all.called) self.migrate_nova_databases.assert_called_with() api_joined.assert_called_with(rid='nova-api/0') + @patch.object(utils, 'is_leader') + @patch.object(utils, 'os_release') @patch.object(hooks, 'quantum_joined') @patch.object(hooks, 'is_db_initialised') @patch.object(hooks, 'nova_cell_relation_joined') @@ -618,7 +630,8 @@ class NovaCCHooksTests(CharmTestCase): @patch.object(hooks, 'CONFIGS') def test_db_changed_remote_restarts(self, configs, comp_joined, cell_joined, mock_is_db_initialised, - quantum_joined): + quantum_joined, utils_os_release, + utils_is_leader): mock_is_db_initialised.return_value = False def _relation_ids(rel): @@ -636,6 +649,8 @@ class NovaCCHooksTests(CharmTestCase): }) self.local_unit.return_value = 'nova-cloud-controller/0' self.os_release.return_value = 'diablo' + utils_os_release.return_value = 'diablo' + utils_is_leader.return_value = False self._shared_db_test(configs) comp_joined.assert_called_with(remote_restart=True, rid='nova-compute/0') diff --git a/unit_tests/test_nova_cc_utils.py b/unit_tests/test_nova_cc_utils.py index 2a98e4fe..a21c7f79 100644 --- a/unit_tests/test_nova_cc_utils.py +++ b/unit_tests/test_nova_cc_utils.py @@ -505,6 +505,18 @@ class NovaCCUtilsTests(CharmTestCase): step_src = utils.get_step_upgrade_source(OS_ORIGIN_NEWTON_STAGING) self.assertEqual(step_src, None) + @patch('charmhelpers.contrib.openstack.utils.lsb_release') + def test_get_setup_upgrade_source_target_ocata(self, lsb_release): + # mitaka -> ocata + self.lsb_release.return_value = {'DISTRIB_CODENAME': 'xenial'} + lsb_release.return_value = {'DISTRIB_CODENAME': 'xenial'} + self.os_release.return_value = 'mitaka' + self.get_os_codename_install_source.side_effect = self.originals[ + 'get_os_codename_install_source'] + + step_src = utils.get_step_upgrade_source("cloud:xenial-ocata") + self.assertEqual(step_src, "cloud:xenial-newton") + @patch('charmhelpers.contrib.openstack.utils.lsb_release') def test_get_setup_upgrade_source_target_liberty_with_mirror(self, lsb_release): @@ -691,6 +703,8 @@ class NovaCCUtilsTests(CharmTestCase): self.os_release.return_value = 'diablo' utils.migrate_nova_databases() check_output.assert_called_with(['nova-manage', 'db', 'sync']) + self.assertNotIn(call(['nova-manage', 'db', 'online_data_migrations']), + check_output.mock_calls) self.peer_store.assert_called_with('dbsync_state', 'complete') self.assertTrue(self.enable_services.called) self.cmd_all_services.assert_called_with('start') @@ -704,6 +718,7 @@ class NovaCCUtilsTests(CharmTestCase): check_output.assert_has_calls([ call(['nova-manage', 'api_db', 'sync']), call(['nova-manage', 'db', 'sync']), + call(['nova-manage', 'db', 'online_data_migrations']), ]) self.peer_store.assert_called_with('dbsync_state', 'complete') self.assertTrue(self.enable_services.called) @@ -724,6 +739,7 @@ class NovaCCUtilsTests(CharmTestCase): call(['nova-manage', 'api_db', 'sync']), call(['nova-manage', 'cell_v2', 'map_cell0']), call(['nova-manage', 'db', 'sync']), + call(['nova-manage', 'db', 'online_data_migrations']), call(['nova-manage', 'cell_v2', 'list_cells']), ANY, ])