diff --git a/trove/guestagent/datastore/manager.py b/trove/guestagent/datastore/manager.py index 61bc100ab4..4b8c51bb4b 100644 --- a/trove/guestagent/datastore/manager.py +++ b/trove/guestagent/datastore/manager.py @@ -260,6 +260,10 @@ class Manager(periodic_task.PeriodicTasks): users, device_path, mount_point, backup_info, config_contents, root_password, overrides, cluster_config, snapshot) + if overrides: + LOG.info(_("Applying user-specified configuration " + "(called from 'prepare').")) + self.apply_overrides_on_prepare(context, overrides) except Exception as ex: self.prepare_error = True LOG.exception(_("An error occurred preparing datastore: %s") % @@ -307,6 +311,10 @@ class Manager(periodic_task.PeriodicTasks): ex.message) raise + def apply_overrides_on_prepare(self, context, overrides): + self.update_overrides(context, overrides) + self.restart(context) + @abc.abstractmethod def do_prepare(self, context, packages, databases, memory_mb, users, device_path, mount_point, backup_info, config_contents, @@ -336,6 +344,14 @@ class Manager(periodic_task.PeriodicTasks): LOG.info(_('No post_prepare work has been defined.')) pass + ################# + # Service related + ################# + @abc.abstractmethod + def restart(self, context): + """Restart the database service.""" + pass + ##################### # File System related ##################### diff --git a/trove/guestagent/datastore/mysql_common/manager.py b/trove/guestagent/datastore/mysql_common/manager.py index 0ceaa51a92..cb212ad15c 100644 --- a/trove/guestagent/datastore/mysql_common/manager.py +++ b/trove/guestagent/datastore/mysql_common/manager.py @@ -222,7 +222,7 @@ class MySqlManager(manager.Manager): self._perform_restore(backup_info, context, mount_point + "/data", app) LOG.debug("Securing MySQL now.") - app.secure(config_contents, overrides) + app.secure(config_contents) enable_root_on_restore = (backup_info and self.mysql_admin().is_root_enabled()) if root_password and not backup_info: diff --git a/trove/guestagent/datastore/mysql_common/service.py b/trove/guestagent/datastore/mysql_common/service.py index 985b76ba4a..071838d948 100644 --- a/trove/guestagent/datastore/mysql_common/service.py +++ b/trove/guestagent/datastore/mysql_common/service.py @@ -656,7 +656,7 @@ class BaseMySqlApp(object): LOG.info(_("Finished installing MySQL server.")) self.start_mysql() - def secure(self, config_contents, overrides): + def secure(self, config_contents): LOG.info(_("Generating admin password.")) admin_password = utils.generate_random_password() clear_expired_password() @@ -669,7 +669,6 @@ class BaseMySqlApp(object): self.stop_db() self._reset_configuration(config_contents, admin_password) - self._apply_user_overrides(overrides) self.start_mysql() LOG.debug("MySQL secure complete.") diff --git a/trove/tests/unittests/guestagent/test_dbaas.py b/trove/tests/unittests/guestagent/test_dbaas.py index 0916dca182..95d13c8db2 100644 --- a/trove/tests/unittests/guestagent/test_dbaas.py +++ b/trove/tests/unittests/guestagent/test_dbaas.py @@ -1358,7 +1358,7 @@ class MySqlAppTest(trove_testtools.TestCase): @patch.object(utils, 'generate_random_password', return_value='some_password') @patch.object(dbaas_base, 'clear_expired_password') - def test_secure(self, auth_pwd_mock, clear_pwd_mock): + def test_secure(self, clear_pwd_mock, auth_pwd_mock): self.mySqlApp.start_mysql = Mock() self.mySqlApp.stop_db = Mock() @@ -1370,16 +1370,12 @@ class MySqlAppTest(trove_testtools.TestCase): with patch.object(BaseDbStatus, 'prepare_completed') as patch_pc: patch_pc.__get__ = Mock(return_value=True) - self.mySqlApp.secure('contents', 'overrides') + self.mySqlApp.secure('contents') self.assertTrue(self.mySqlApp.stop_db.called) - reset_config_calls = [call('contents', auth_pwd_mock.return_value), - call('contents', auth_pwd_mock.return_value)] - self.mySqlApp._reset_configuration.has_calls(reset_config_calls) + self.mySqlApp._reset_configuration.assert_has_calls( + [call('contents', auth_pwd_mock.return_value)]) - apply_overrides_calls = [call('overrides'), - call('overrides')] - self.mySqlApp._reset_configuration.has_calls(apply_overrides_calls) self.assertTrue(self.mySqlApp.start_mysql.called) self.assert_reported_status(rd_instance.ServiceStatuses.NEW) @@ -1470,7 +1466,7 @@ class MySqlAppTest(trove_testtools.TestCase): self.mysql_starts_successfully() sqlalchemy.create_engine = Mock() - self.assertRaises(IOError, self.mySqlApp.secure, "foo", None) + self.assertRaises(IOError, self.mySqlApp.secure, "foo") self.assertTrue(self.mySqlApp.stop_db.called) self.assertFalse(self.mySqlApp.start_mysql.called) @@ -1545,7 +1541,7 @@ class MySqlAppMockTest(trove_testtools.TestCase): app._wait_for_mysql_to_be_really_alive = MagicMock( return_value=True) app.stop_db = MagicMock(return_value=None) - app.secure('foo', None) + app.secure('foo') reset_config_calls = [call('foo', auth_pwd_mock.return_value)] app._reset_configuration.assert_has_calls(reset_config_calls) self.assertTrue(mock_execute.called) @@ -1570,7 +1566,7 @@ class MySqlAppMockTest(trove_testtools.TestCase): app = MySqlApp(mock_status) dbaas_base.clear_expired_password = \ MagicMock(return_value=None) - self.assertRaises(RuntimeError, app.secure, None, None) + self.assertRaises(RuntimeError, app.secure, None) self.assertTrue(mock_execute.called) # At least called twice self.assertTrue(mock_execute.call_count >= 2) diff --git a/trove/tests/unittests/guestagent/test_manager.py b/trove/tests/unittests/guestagent/test_manager.py index a38d9fc219..a2cfdaf252 100644 --- a/trove/tests/unittests/guestagent/test_manager.py +++ b/trove/tests/unittests/guestagent/test_manager.py @@ -1,4 +1,5 @@ # Copyright 2015 Tesora Inc. +# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain @@ -12,11 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. + import getpass import os +from mock import ANY from mock import DEFAULT from mock import MagicMock +from mock import Mock from mock import patch from proboscis.asserts import assert_equal from proboscis.asserts import assert_true @@ -30,6 +34,7 @@ from trove.tests.unittests import trove_testtools class MockManager(manager.Manager): + def __init__(self): super(MockManager, self).__init__('mysql') self._app = MagicMock() @@ -50,6 +55,7 @@ class MockManager(manager.Manager): class ManagerTest(trove_testtools.TestCase): + def setUp(self): super(ManagerTest, self).setUp() @@ -312,3 +318,146 @@ class ManagerTest(trove_testtools.TestCase): for key in os_mocks.keys(): assert_true(os_mocks[key].call_count == 1, "%s not called" % key) + + def test_prepare_single(self): + packages = Mock() + databases = Mock() + memory_mb = Mock() + users = Mock() + device_path = Mock() + mount_point = Mock() + backup_info = Mock() + config_contents = Mock() + root_password = Mock() + overrides = Mock() + snapshot = Mock() + + self._assert_prepare( + self.context, packages, databases, memory_mb, users, device_path, + mount_point, backup_info, config_contents, root_password, + overrides, None, snapshot) + + def test_prepare_cluster(self): + packages = Mock() + databases = Mock() + memory_mb = Mock() + users = Mock() + device_path = Mock() + mount_point = Mock() + backup_info = Mock() + config_contents = Mock() + root_password = Mock() + overrides = Mock() + cluster_config = Mock() + snapshot = Mock() + + self._assert_prepare( + self.context, packages, databases, memory_mb, users, device_path, + mount_point, backup_info, config_contents, root_password, + overrides, cluster_config, snapshot) + + def _assert_prepare(self, context, packages, databases, memory_mb, users, + device_path, mount_point, backup_info, config_contents, + root_password, overrides, cluster_config, snapshot): + + is_error_expected = False + is_post_process_expected = cluster_config is not None + + with patch.multiple(self.manager, + do_prepare=DEFAULT, post_prepare=DEFAULT, + apply_overrides_on_prepare=DEFAULT, + create_database=DEFAULT, create_user=DEFAULT): + self.manager.prepare( + context, packages, databases, memory_mb, users, + device_path, mount_point, backup_info, config_contents, + root_password, overrides, cluster_config, snapshot) + + self.manager.status.begin_install.assert_called_once_with() + self.manager.do_prepare.assert_called_once_with( + context, + packages, + databases, + memory_mb, + users, + device_path, + mount_point, + backup_info, + config_contents, + root_password, + overrides, + cluster_config, + snapshot) + self.manager.apply_overrides_on_prepare.assert_called_once_with( + context, + overrides) + self.manager.status.end_install( + error_occurred=is_error_expected, + post_processing=is_post_process_expected) + self.manager.post_prepare.assert_called_once_with( + context, + packages, + databases, + memory_mb, + users, + device_path, + mount_point, + backup_info, + config_contents, + root_password, + overrides, + cluster_config, + snapshot) + + if not is_post_process_expected: + self.manager.create_database.assert_called_once_with( + context, + databases) + self.manager.create_user.assert_called_once_with( + context, + users) + else: + self.assertEqual(0, self.manager.create_database.call_count) + self.assertEqual(0, self.manager.create_user.call_count) + + def test_apply_overrides_on_prepare(self): + overrides = Mock() + with patch.multiple(self.manager, + update_overrides=DEFAULT, restart=DEFAULT): + self.manager.apply_overrides_on_prepare(self.context, overrides) + + self.manager.update_overrides.assert_called_once_with( + self.context, overrides) + self.manager.restart.assert_called_once_with(self.context) + + def test_apply_overrides_on_prepare_failure(self): + packages = Mock() + databases = Mock() + memory_mb = Mock() + users = Mock() + device_path = Mock() + mount_point = Mock() + backup_info = Mock() + config_contents = Mock() + root_password = Mock() + overrides = Mock() + cluster_config = Mock() + snapshot = Mock() + + expected_failure = Exception("Error in 'apply_overrides_on_prepare'.") + + with patch.multiple( + self.manager, do_prepare=DEFAULT, + apply_overrides_on_prepare=MagicMock( + side_effect=expected_failure + )): + self.assertRaisesRegexp( + Exception, "Error in 'apply_overrides_on_prepare'.", + self.manager.prepare, + self.context, packages, databases, memory_mb, users, + device_path, mount_point, backup_info, config_contents, + root_password, overrides, cluster_config, snapshot) + + self.manager.status.begin_install.assert_called_once_with() + self.manager.status.end_install( + error_occurred=True, + post_processing=ANY) diff --git a/trove/tests/unittests/guestagent/test_mysql_manager.py b/trove/tests/unittests/guestagent/test_mysql_manager.py index 4dd68b83d8..5b6f5923e1 100644 --- a/trove/tests/unittests/guestagent/test_mysql_manager.py +++ b/trove/tests/unittests/guestagent/test_mysql_manager.py @@ -37,6 +37,7 @@ from trove.guestagent.volume import VolumeDevice class GuestAgentManagerTest(testtools.TestCase): + def setUp(self): super(GuestAgentManagerTest, self).setUp() self.context = TroveContext() @@ -282,7 +283,7 @@ class GuestAgentManagerTest(testtools.TestCase): '/var/lib/mysql/data') dbaas.MySqlApp.install_if_needed.assert_any_call(None) # We don't need to make sure the exact contents are there - dbaas.MySqlApp.secure.assert_any_call(None, None) + dbaas.MySqlApp.secure.assert_any_call(None) dbaas.MySqlApp.secure_root.assert_any_call( secure_remote_root=not is_root_enabled)