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,
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 = [

View File

@ -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()

View File

@ -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

View File

@ -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')