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 <pcrismaru@cloudbasesolutions.com>
This commit is contained in:
Alessandro Pilotti 2017-01-24 18:28:17 +02:00 committed by Paula Madalina Crismaru
parent 18f4b25972
commit 834f19058c
4 changed files with 80 additions and 21 deletions

View File

@ -271,6 +271,14 @@ class GlobalOptions(conf_base.Options):
'enable_automatic_updates', default=None, 'enable_automatic_updates', default=None,
help='If set, enables or disables automatic operating ' help='If set, enables or disables automatic operating '
'system updates.'), '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 = [ self._cli_options = [

View File

@ -52,6 +52,8 @@ class InitManager(object):
def _exec_plugin(self, osutils, service, plugin, instance_id, shared_data): def _exec_plugin(self, osutils, service, plugin, instance_id, shared_data):
plugin_name = plugin.get_name() plugin_name = plugin.get_name()
reboot_required = None
success = True
status = None status = None
if instance_id is not None: if instance_id is not None:
status = self._get_plugin_status(osutils, instance_id, plugin_name) status = self._get_plugin_status(osutils, instance_id, plugin_name)
@ -66,11 +68,12 @@ class InitManager(object):
if instance_id is not None: if instance_id is not None:
self._set_plugin_status(osutils, instance_id, plugin_name, self._set_plugin_status(osutils, instance_id, plugin_name,
status) status)
return reboot_required
except Exception as ex: except Exception as ex:
LOG.error('plugin \'%(plugin_name)s\' failed with error ' LOG.error('plugin \'%(plugin_name)s\' failed with error '
'\'%(ex)s\'', {'plugin_name': plugin_name, 'ex': ex}) '\'%(ex)s\'', {'plugin_name': plugin_name, 'ex': ex})
LOG.exception(ex) LOG.exception(ex)
success = False
return success, reboot_required
def _check_plugin_os_requirements(self, osutils, plugin): def _check_plugin_os_requirements(self, osutils, plugin):
supported = False supported = False
@ -102,19 +105,22 @@ class InitManager(object):
def _handle_plugins_stage(self, osutils, service, instance_id, stage): def _handle_plugins_stage(self, osutils, service, instance_id, stage):
plugins_shared_data = {} plugins_shared_data = {}
reboot_required = False reboot_required = False
stage_success = True
plugins = plugins_factory.load_plugins(stage) plugins = plugins_factory.load_plugins(stage)
LOG.info('Executing plugins for stage %r:', stage) LOG.info('Executing plugins for stage %r:', stage)
for plugin in plugins: for plugin in plugins:
if self._check_plugin_os_requirements(osutils, plugin): if self._check_plugin_os_requirements(osutils, plugin):
if self._exec_plugin(osutils, service, plugin, success, reboot_required = self._exec_plugin(
instance_id, plugins_shared_data): osutils, service, plugin, instance_id,
reboot_required = True plugins_shared_data)
if CONF.allow_reboot: if not success:
stage_success = False
if reboot_required and CONF.allow_reboot:
break break
return reboot_required return stage_success, reboot_required
@staticmethod @staticmethod
def _reset_service_password_and_respawn(osutils): def _reset_service_password_and_respawn(osutils):
@ -168,14 +174,14 @@ class InitManager(object):
LOG.info('Cloudbase-Init version: %s', version.get_version()) LOG.info('Cloudbase-Init version: %s', version.get_version())
osutils.wait_for_boot_completion() osutils.wait_for_boot_completion()
reboot_required = self._handle_plugins_stage( stage_success, reboot_required = self._handle_plugins_stage(
osutils, None, None, osutils, None, None,
plugins_base.PLUGIN_STAGE_PRE_NETWORKING) plugins_base.PLUGIN_STAGE_PRE_NETWORKING)
self._check_latest_version() self._check_latest_version()
if not (reboot_required and CONF.allow_reboot): 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, osutils, None, None,
plugins_base.PLUGIN_STAGE_PRE_METADATA_DISCOVERY) plugins_base.PLUGIN_STAGE_PRE_METADATA_DISCOVERY)
@ -188,16 +194,28 @@ class InitManager(object):
LOG.info('Metadata service loaded: \'%s\'' % LOG.info('Metadata service loaded: \'%s\'' %
service.get_name()) service.get_name())
if CONF.metadata_report_provisioning_started:
LOG.info("Reporting provisioning started")
service.provisioning_started()
instance_id = service.get_instance_id() instance_id = service.get_instance_id()
LOG.debug('Instance id: %s', instance_id) LOG.debug('Instance id: %s', instance_id)
try: try:
reboot_required = self._handle_plugins_stage( stage_success, reboot_required = self._handle_plugins_stage(
osutils, service, instance_id, osutils, service, instance_id,
plugins_base.PLUGIN_STAGE_MAIN) plugins_base.PLUGIN_STAGE_MAIN)
finally: finally:
service.cleanup() 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: if reboot_required and CONF.allow_reboot:
try: try:
LOG.info("Rebooting") LOG.info("Rebooting")
@ -206,6 +224,15 @@ class InitManager(object):
LOG.error('reboot failed with error \'%s\'' % ex) LOG.error('reboot failed with error \'%s\'' % ex)
else: else:
LOG.info("Plugins execution done") 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: if CONF.stop_service_on_exit:
LOG.info("Stopping Cloudbase-Init service") LOG.info("Stopping Cloudbase-Init service")
osutils.terminate() osutils.terminate()

View File

@ -189,6 +189,15 @@ class BaseMetadataService(object):
""" """
return False return False
def provisioning_started(self):
pass
def provisioning_completed(self):
pass
def provisioning_failed(self):
pass
@property @property
def can_post_rdp_cert_thumbprint(self): def can_post_rdp_cert_thumbprint(self):
return False return False

View File

@ -186,12 +186,13 @@ class TestInitManager(unittest.TestCase):
def _test_handle_plugins_stage(self, mock_load_plugins, def _test_handle_plugins_stage(self, mock_load_plugins,
mock_check_plugin_os_requirements, mock_check_plugin_os_requirements,
mock_exec_plugin, mock_exec_plugin,
reboot=True, fast_reboot=True): reboot=True, fast_reboot=True,
success=True):
stage = "fake stage" stage = "fake stage"
service, instance_id = mock.Mock(), mock.Mock() service, instance_id = mock.Mock(), mock.Mock()
plugins = [mock.Mock() for _ in range(3)] plugins = [mock.Mock() for _ in range(3)]
mock_check_plugin_os_requirements.return_value = True 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 mock_load_plugins.return_value = plugins
requirements_calls = [mock.call(self.osutils, plugin) requirements_calls = [mock.call(self.osutils, plugin)
for plugin in plugins] for plugin in plugins]
@ -210,7 +211,7 @@ class TestInitManager(unittest.TestCase):
mock_check_plugin_os_requirements.assert_has_calls( mock_check_plugin_os_requirements.assert_has_calls(
requirements_calls[:idx]) requirements_calls[:idx])
mock_exec_plugin.assert_has_calls(exec_plugin_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): def test_handle_plugins_stage(self):
self._test_handle_plugins_stage() self._test_handle_plugins_stage()
@ -222,6 +223,9 @@ class TestInitManager(unittest.TestCase):
def test_handle_plugins_stage_no_fast_reboot(self): def test_handle_plugins_stage_no_fast_reboot(self):
self._test_handle_plugins_stage(fast_reboot=False) 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.' @mock.patch('cloudbaseinit.init.InitManager.'
'_reset_service_password_and_respawn') '_reset_service_password_and_respawn')
@mock.patch('cloudbaseinit.init.InitManager' @mock.patch('cloudbaseinit.init.InitManager'
@ -236,7 +240,8 @@ class TestInitManager(unittest.TestCase):
mock_get_version, mock_check_latest_version, mock_get_version, mock_check_latest_version,
mock_handle_plugins_stage, mock_reset_service, mock_handle_plugins_stage, mock_reset_service,
expected_logging, expected_logging,
version, name, instance_id, reboot=True): version, name, instance_id, reboot=True,
last_stage=False):
sys.platform = 'win32' sys.platform = 'win32'
mock_get_version.return_value = version mock_get_version.return_value = version
fake_service = mock.MagicMock() fake_service = mock.MagicMock()
@ -246,7 +251,8 @@ class TestInitManager(unittest.TestCase):
mock_get_metadata_service.return_value = fake_service mock_get_metadata_service.return_value = fake_service
fake_service.get_name.return_value = name fake_service.get_name.return_value = name
fake_service.get_instance_id.return_value = instance_id 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 = [ stages = [
base.PLUGIN_STAGE_PRE_NETWORKING, base.PLUGIN_STAGE_PRE_NETWORKING,
base.PLUGIN_STAGE_PRE_METADATA_DISCOVERY, 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][1] = fake_service
stage_calls_list[2][2] = instance_id stage_calls_list[2][2] = instance_id
stage_calls = [mock.call(*args) for args in stage_calls_list] stage_calls = [mock.call(*args) for args in stage_calls_list]
with testutils.LogSnatcher('cloudbaseinit.init') as snatcher: with testutils.LogSnatcher('cloudbaseinit.init') as snatcher:
self._init.configure_host() self._init.configure_host()
self.assertEqual(expected_logging, snatcher.output) self.assertEqual(expected_logging, snatcher.output)
mock_check_latest_version.assert_called_once_with() mock_check_latest_version.assert_called_once_with()
if CONF.reset_service_password: if CONF.reset_service_password:
mock_reset_service.assert_called_once_with(self.osutils) 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() self.osutils.wait_for_boot_completion.assert_called_once_with()
mock_get_metadata_service.assert_called_once_with() mock_get_metadata_service.assert_called_once_with()
@ -275,7 +282,8 @@ class TestInitManager(unittest.TestCase):
else: else:
self.assertFalse(self.osutils.reboot.called) 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' instance_id = 'fake id'
name = 'fake name' name = 'fake name'
version = 'version' version = 'version'
@ -284,17 +292,23 @@ class TestInitManager(unittest.TestCase):
'Metadata service loaded: %r' % name, 'Metadata service loaded: %r' % name,
'Instance id: %s' % instance_id, 'Instance id: %s' % instance_id,
] ]
if CONF.metadata_report_provisioning_started:
expected_logging.insert(2, 'Reporting provisioning started')
self._test_configure_host( self._test_configure_host(
expected_logging=expected_logging + extra_logging, expected_logging=expected_logging + extra_logging,
version=version, name=name, instance_id=instance_id, 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('allow_reboot', False)
@testutils.ConfPatcher('stop_service_on_exit', 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( self._test_configure_host_with_logging(
reboot=False, 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('allow_reboot', False)
@testutils.ConfPatcher('stop_service_on_exit', True) @testutils.ConfPatcher('stop_service_on_exit', True)
@ -305,10 +319,11 @@ class TestInitManager(unittest.TestCase):
'Stopping Cloudbase-Init service']) 'Stopping Cloudbase-Init service'])
self.osutils.terminate.assert_called_once_with() self.osutils.terminate.assert_called_once_with()
@testutils.ConfPatcher('metadata_report_provisioning_completed', True)
@testutils.ConfPatcher('allow_reboot', 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( self._test_configure_host_with_logging(
extra_logging=['Rebooting']) extra_logging=['Reporting provisioning failed', 'Rebooting'])
@testutils.ConfPatcher('check_latest_version', False) @testutils.ConfPatcher('check_latest_version', False)
@mock.patch('cloudbaseinit.version.check_latest_version') @mock.patch('cloudbaseinit.version.check_latest_version')