From 834f19058ce0251820a6d92c180fb2a2c2117cef Mon Sep 17 00:00:00 2001 From: Alessandro Pilotti Date: Tue, 24 Jan 2017 18:28:17 +0200 Subject: [PATCH] Add reporting provisioning status to the metadata service Add support for reporting the provisioning status to the metadata service. The following config options have been added: - metadata_report_provisioning_started: Reports to the metadata service that provisioning has started type=bool, default=False - metadata_report_provisioning_completed: Reports to the metadata service that provisioning completed or failed type=bool, default=False Change-Id: I23a5a8e5473dd103ce5e0acd0c4647cdec86f97f Implements: blueprint add-reporting-provisioning-status-to-metadata-service Co-Authored-By: Paula Madalina Crismaru --- cloudbaseinit/conf/default.py | 8 +++++ cloudbaseinit/init.py | 45 ++++++++++++++++++++----- cloudbaseinit/metadata/services/base.py | 9 +++++ cloudbaseinit/tests/test_init.py | 39 ++++++++++++++------- 4 files changed, 80 insertions(+), 21 deletions(-) diff --git a/cloudbaseinit/conf/default.py b/cloudbaseinit/conf/default.py index 3c1d0750..7fbbacb3 100644 --- a/cloudbaseinit/conf/default.py +++ b/cloudbaseinit/conf/default.py @@ -271,6 +271,14 @@ class GlobalOptions(conf_base.Options): 'enable_automatic_updates', default=None, help='If set, enables or disables automatic operating ' 'system updates.'), + cfg.BoolOpt( + 'metadata_report_provisioning_started', default=False, + help='Reports to the metadata service that provisioning has ' + 'started'), + cfg.BoolOpt( + 'metadata_report_provisioning_completed', default=False, + help='Reports to the metadata service that provisioning ' + 'completed successfully or failed'), ] self._cli_options = [ diff --git a/cloudbaseinit/init.py b/cloudbaseinit/init.py index 55a6f805..a8720960 100644 --- a/cloudbaseinit/init.py +++ b/cloudbaseinit/init.py @@ -52,6 +52,8 @@ class InitManager(object): def _exec_plugin(self, osutils, service, plugin, instance_id, shared_data): plugin_name = plugin.get_name() + reboot_required = None + success = True status = None if instance_id is not None: status = self._get_plugin_status(osutils, instance_id, plugin_name) @@ -66,11 +68,12 @@ class InitManager(object): if instance_id is not None: self._set_plugin_status(osutils, instance_id, plugin_name, status) - return reboot_required except Exception as ex: LOG.error('plugin \'%(plugin_name)s\' failed with error ' '\'%(ex)s\'', {'plugin_name': plugin_name, 'ex': ex}) LOG.exception(ex) + success = False + return success, reboot_required def _check_plugin_os_requirements(self, osutils, plugin): supported = False @@ -102,19 +105,22 @@ class InitManager(object): def _handle_plugins_stage(self, osutils, service, instance_id, stage): plugins_shared_data = {} reboot_required = False + stage_success = True plugins = plugins_factory.load_plugins(stage) LOG.info('Executing plugins for stage %r:', stage) for plugin in plugins: if self._check_plugin_os_requirements(osutils, plugin): - if self._exec_plugin(osutils, service, plugin, - instance_id, plugins_shared_data): - reboot_required = True - if CONF.allow_reboot: + success, reboot_required = self._exec_plugin( + osutils, service, plugin, instance_id, + plugins_shared_data) + if not success: + stage_success = False + if reboot_required and CONF.allow_reboot: break - return reboot_required + return stage_success, reboot_required @staticmethod def _reset_service_password_and_respawn(osutils): @@ -168,14 +174,14 @@ class InitManager(object): LOG.info('Cloudbase-Init version: %s', version.get_version()) osutils.wait_for_boot_completion() - reboot_required = self._handle_plugins_stage( + stage_success, reboot_required = self._handle_plugins_stage( osutils, None, None, plugins_base.PLUGIN_STAGE_PRE_NETWORKING) self._check_latest_version() if not (reboot_required and CONF.allow_reboot): - reboot_required = self._handle_plugins_stage( + stage_success, reboot_required = self._handle_plugins_stage( osutils, None, None, plugins_base.PLUGIN_STAGE_PRE_METADATA_DISCOVERY) @@ -188,16 +194,28 @@ class InitManager(object): LOG.info('Metadata service loaded: \'%s\'' % service.get_name()) + if CONF.metadata_report_provisioning_started: + LOG.info("Reporting provisioning started") + service.provisioning_started() + instance_id = service.get_instance_id() LOG.debug('Instance id: %s', instance_id) try: - reboot_required = self._handle_plugins_stage( + stage_success, reboot_required = self._handle_plugins_stage( osutils, service, instance_id, plugins_base.PLUGIN_STAGE_MAIN) finally: service.cleanup() + if (CONF.metadata_report_provisioning_completed and + not stage_success): + try: + LOG.info("Reporting provisioning failed") + service.provisioning_failed() + except Exception as ex: + LOG.exception(ex) + if reboot_required and CONF.allow_reboot: try: LOG.info("Rebooting") @@ -206,6 +224,15 @@ class InitManager(object): LOG.error('reboot failed with error \'%s\'' % ex) else: LOG.info("Plugins execution done") + + if (service and CONF.metadata_report_provisioning_completed and + stage_success): + try: + LOG.info("Reporting provisioning completed") + service.provisioning_completed() + except Exception as ex: + LOG.exception(ex) + if CONF.stop_service_on_exit: LOG.info("Stopping Cloudbase-Init service") osutils.terminate() diff --git a/cloudbaseinit/metadata/services/base.py b/cloudbaseinit/metadata/services/base.py index f43eda94..6d17362d 100644 --- a/cloudbaseinit/metadata/services/base.py +++ b/cloudbaseinit/metadata/services/base.py @@ -189,6 +189,15 @@ class BaseMetadataService(object): """ return False + def provisioning_started(self): + pass + + def provisioning_completed(self): + pass + + def provisioning_failed(self): + pass + @property def can_post_rdp_cert_thumbprint(self): return False diff --git a/cloudbaseinit/tests/test_init.py b/cloudbaseinit/tests/test_init.py index 3ba18f2d..11ee2c4b 100644 --- a/cloudbaseinit/tests/test_init.py +++ b/cloudbaseinit/tests/test_init.py @@ -186,12 +186,13 @@ class TestInitManager(unittest.TestCase): def _test_handle_plugins_stage(self, mock_load_plugins, mock_check_plugin_os_requirements, mock_exec_plugin, - reboot=True, fast_reboot=True): + reboot=True, fast_reboot=True, + success=True): stage = "fake stage" service, instance_id = mock.Mock(), mock.Mock() plugins = [mock.Mock() for _ in range(3)] mock_check_plugin_os_requirements.return_value = True - mock_exec_plugin.return_value = reboot + mock_exec_plugin.return_value = success, reboot mock_load_plugins.return_value = plugins requirements_calls = [mock.call(self.osutils, plugin) for plugin in plugins] @@ -210,7 +211,7 @@ class TestInitManager(unittest.TestCase): mock_check_plugin_os_requirements.assert_has_calls( requirements_calls[:idx]) mock_exec_plugin.assert_has_calls(exec_plugin_calls[:idx]) - self.assertEqual(reboot, response) + self.assertEqual((success, reboot), response) def test_handle_plugins_stage(self): self._test_handle_plugins_stage() @@ -222,6 +223,9 @@ class TestInitManager(unittest.TestCase): def test_handle_plugins_stage_no_fast_reboot(self): self._test_handle_plugins_stage(fast_reboot=False) + def test_handle_plugins_stage_stage_fails(self): + self._test_handle_plugins_stage(success=False) + @mock.patch('cloudbaseinit.init.InitManager.' '_reset_service_password_and_respawn') @mock.patch('cloudbaseinit.init.InitManager' @@ -236,7 +240,8 @@ class TestInitManager(unittest.TestCase): mock_get_version, mock_check_latest_version, mock_handle_plugins_stage, mock_reset_service, expected_logging, - version, name, instance_id, reboot=True): + version, name, instance_id, reboot=True, + last_stage=False): sys.platform = 'win32' mock_get_version.return_value = version fake_service = mock.MagicMock() @@ -246,7 +251,8 @@ class TestInitManager(unittest.TestCase): mock_get_metadata_service.return_value = fake_service fake_service.get_name.return_value = name fake_service.get_instance_id.return_value = instance_id - mock_handle_plugins_stage.side_effect = [False, False, True] + mock_handle_plugins_stage.side_effect = [(True, False), (True, False), + (last_stage, True)] stages = [ base.PLUGIN_STAGE_PRE_NETWORKING, base.PLUGIN_STAGE_PRE_METADATA_DISCOVERY, @@ -256,13 +262,14 @@ class TestInitManager(unittest.TestCase): stage_calls_list[2][1] = fake_service stage_calls_list[2][2] = instance_id stage_calls = [mock.call(*args) for args in stage_calls_list] - with testutils.LogSnatcher('cloudbaseinit.init') as snatcher: self._init.configure_host() self.assertEqual(expected_logging, snatcher.output) mock_check_latest_version.assert_called_once_with() if CONF.reset_service_password: mock_reset_service.assert_called_once_with(self.osutils) + if last_stage: + fake_service.provisioning_completed.assert_called_once_with() self.osutils.wait_for_boot_completion.assert_called_once_with() mock_get_metadata_service.assert_called_once_with() @@ -275,7 +282,8 @@ class TestInitManager(unittest.TestCase): else: self.assertFalse(self.osutils.reboot.called) - def _test_configure_host_with_logging(self, extra_logging, reboot=True): + def _test_configure_host_with_logging(self, extra_logging, reboot=True, + last_stage=False): instance_id = 'fake id' name = 'fake name' version = 'version' @@ -284,17 +292,23 @@ class TestInitManager(unittest.TestCase): 'Metadata service loaded: %r' % name, 'Instance id: %s' % instance_id, ] + if CONF.metadata_report_provisioning_started: + expected_logging.insert(2, 'Reporting provisioning started') + self._test_configure_host( expected_logging=expected_logging + extra_logging, version=version, name=name, instance_id=instance_id, - reboot=reboot) + reboot=reboot, last_stage=last_stage) + @testutils.ConfPatcher('metadata_report_provisioning_completed', True) @testutils.ConfPatcher('allow_reboot', False) @testutils.ConfPatcher('stop_service_on_exit', False) - def test_configure_host_no_reboot_no_service_stopping(self): + def test_configure_host_no_reboot_no_service_stopping_reporting_done(self): self._test_configure_host_with_logging( reboot=False, - extra_logging=['Plugins execution done']) + extra_logging=['Plugins execution done', + 'Reporting provisioning completed'], + last_stage=True) @testutils.ConfPatcher('allow_reboot', False) @testutils.ConfPatcher('stop_service_on_exit', True) @@ -305,10 +319,11 @@ class TestInitManager(unittest.TestCase): 'Stopping Cloudbase-Init service']) self.osutils.terminate.assert_called_once_with() + @testutils.ConfPatcher('metadata_report_provisioning_completed', True) @testutils.ConfPatcher('allow_reboot', True) - def test_configure_host_reboot(self): + def test_configure_host_reboot_reporting_started_and_failed(self): self._test_configure_host_with_logging( - extra_logging=['Rebooting']) + extra_logging=['Reporting provisioning failed', 'Rebooting']) @testutils.ConfPatcher('check_latest_version', False) @mock.patch('cloudbaseinit.version.check_latest_version')