
In Python 3, the mock module is now part of the unittest module. We no longer support Python 2, so there's no reason for the import checks. Change-Id: I7b4d68cba4b587ad9f0121058ba615a9bb0aec93
395 lines
18 KiB
Python
395 lines
18 KiB
Python
# Copyright 2013 Cloudbase Solutions Srl
|
|
#
|
|
# 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
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import sys
|
|
import unittest
|
|
import unittest.mock as mock
|
|
|
|
from cloudbaseinit import conf as cloudbaseinit_conf
|
|
from cloudbaseinit import init
|
|
from cloudbaseinit.plugins.common import base
|
|
from cloudbaseinit.tests import testutils
|
|
|
|
CONF = cloudbaseinit_conf.CONF
|
|
|
|
|
|
class TestInitManager(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self._win32com_mock = mock.MagicMock()
|
|
self._comtypes_mock = mock.MagicMock()
|
|
self._pywintypes_mock = mock.MagicMock()
|
|
self._ctypes_mock = mock.MagicMock()
|
|
self._ctypes_util_mock = mock.MagicMock()
|
|
|
|
self._module_patcher = mock.patch.dict(
|
|
'sys.modules',
|
|
{'ctypes.util': self._ctypes_util_mock,
|
|
'win32com': self._win32com_mock,
|
|
'comtypes': self._comtypes_mock,
|
|
'pywintypes': self._pywintypes_mock,
|
|
'ctypes': self._ctypes_mock})
|
|
|
|
self._module_patcher.start()
|
|
|
|
self.osutils = mock.MagicMock()
|
|
self.plugin = mock.MagicMock()
|
|
|
|
self._init = init.InitManager()
|
|
|
|
def tearDown(self):
|
|
self._module_patcher.stop()
|
|
|
|
def _test_get_plugin_section(self, instance_id):
|
|
response = self._init._get_plugins_section(instance_id=instance_id)
|
|
if not instance_id:
|
|
self.assertEqual(self._init._PLUGINS_CONFIG_SECTION, response)
|
|
else:
|
|
expected_response = (
|
|
"%s/%s" % (instance_id, self._init._PLUGINS_CONFIG_SECTION))
|
|
self.assertEqual(expected_response, response)
|
|
|
|
def test_get_plugin_section_id(self):
|
|
fake_id = "100"
|
|
self._test_get_plugin_section(instance_id=fake_id)
|
|
|
|
def test_get_plugin_section_id_int(self):
|
|
fake_id = 100
|
|
self._test_get_plugin_section(instance_id=fake_id)
|
|
|
|
def test_get_plugin_section_no_id(self):
|
|
self._test_get_plugin_section(instance_id=None)
|
|
|
|
@mock.patch('cloudbaseinit.init.InitManager._get_plugins_section')
|
|
def test_get_plugin_status(self, mock_get_plugins_section):
|
|
self.osutils.get_config_value.return_value = 1
|
|
response = self._init._get_plugin_status(self.osutils, 'fake id',
|
|
'fake plugin')
|
|
mock_get_plugins_section.assert_called_once_with('fake id')
|
|
self.osutils.get_config_value.assert_called_once_with(
|
|
'fake plugin', mock_get_plugins_section())
|
|
self.assertTrue(response == 1)
|
|
|
|
@mock.patch('cloudbaseinit.init.InitManager._get_plugins_section')
|
|
def test_set_plugin_status(self, mock_get_plugins_section):
|
|
self._init._set_plugin_status(self.osutils, 'fake id',
|
|
'fake plugin', 'status')
|
|
mock_get_plugins_section.assert_called_once_with('fake id')
|
|
self.osutils.set_config_value.assert_called_once_with(
|
|
'fake plugin', 'status', mock_get_plugins_section())
|
|
|
|
@mock.patch('cloudbaseinit.init.InitManager._get_plugin_status')
|
|
@mock.patch('cloudbaseinit.init.InitManager._set_plugin_status')
|
|
def _test_exec_plugin(self, status, mock_set_plugin_status,
|
|
mock_get_plugin_status):
|
|
fake_name = 'fake name'
|
|
self.plugin.get_name.return_value = fake_name
|
|
self.plugin.execute.return_value = (status, True)
|
|
mock_get_plugin_status.return_value = status
|
|
|
|
response = self._init._exec_plugin(osutils=self.osutils,
|
|
service='fake service',
|
|
plugin=self.plugin,
|
|
instance_id='fake id',
|
|
shared_data='shared data')
|
|
|
|
mock_get_plugin_status.assert_called_once_with(self.osutils,
|
|
'fake id',
|
|
fake_name)
|
|
if status is base.PLUGIN_EXECUTE_ON_NEXT_BOOT:
|
|
self.plugin.execute.assert_called_once_with('fake service',
|
|
'shared data')
|
|
mock_set_plugin_status.assert_called_once_with(self.osutils,
|
|
'fake id',
|
|
fake_name, status)
|
|
self.assertTrue(response)
|
|
|
|
def test_exec_plugin_exception_occurs(self):
|
|
fake_name = 'fake name'
|
|
mock_plugin = mock.MagicMock()
|
|
mock_plugin.get_name.return_value = fake_name
|
|
mock_plugin.execute.side_effect = Exception
|
|
expected_logging = ["Executing plugin 'fake name'",
|
|
"plugin 'fake name' failed with error ''"]
|
|
with testutils.LogSnatcher('cloudbaseinit.init') as snatcher:
|
|
self._init._exec_plugin(osutils=self.osutils,
|
|
service='fake service',
|
|
plugin=mock_plugin,
|
|
instance_id='fake id',
|
|
shared_data='shared data')
|
|
self.assertEqual(expected_logging, snatcher.output[:2])
|
|
|
|
def test_exec_plugin_execution_done(self):
|
|
self._test_exec_plugin(base.PLUGIN_EXECUTION_DONE)
|
|
|
|
def test_exec_plugin(self):
|
|
self._test_exec_plugin(base.PLUGIN_EXECUTE_ON_NEXT_BOOT)
|
|
|
|
def _test_check_plugin_os_requirements(self, requirements):
|
|
sys.platform = 'win32'
|
|
fake_name = 'fake name'
|
|
self.plugin.get_name.return_value = fake_name
|
|
self.plugin.get_os_requirements.return_value = requirements
|
|
|
|
response = self._init._check_plugin_os_requirements(self.osutils,
|
|
self.plugin)
|
|
|
|
self.plugin.get_name.assert_called_once_with()
|
|
self.plugin.get_os_requirements.assert_called_once_with()
|
|
if requirements[0] == 'win32':
|
|
self.assertTrue(response)
|
|
else:
|
|
self.assertFalse(response)
|
|
|
|
def test_check_plugin_os_requirements(self):
|
|
self._test_check_plugin_os_requirements(('win32', (5, 2)))
|
|
|
|
def test_check_plugin_os_requirements_other_requirenments(self):
|
|
self._test_check_plugin_os_requirements(('linux', (5, 2)))
|
|
|
|
def test_check_plugins_os_not_min_os_version(self):
|
|
sys.platform = 'win32'
|
|
fake_name = 'fake name'
|
|
self.plugin.get_name.return_value = fake_name
|
|
self.plugin.get_os_requirements.return_value = ('win32', 0)
|
|
response = self._init._check_plugin_os_requirements(self.osutils,
|
|
self.plugin)
|
|
self.plugin.get_name.assert_called_once_with()
|
|
self.assertTrue(response)
|
|
|
|
def test_check_plugins_os_not_supported(self):
|
|
fake_name = 'fake name'
|
|
self.plugin.get_name.return_value = fake_name
|
|
mock_osutils = mock.MagicMock()
|
|
mock_osutils.check_os_version.return_value = None
|
|
self.plugin.get_os_requirements.return_value = ('win32', (5, 2))
|
|
response = self._init._check_plugin_os_requirements(mock_osutils,
|
|
self.plugin)
|
|
self.assertFalse(response)
|
|
|
|
@mock.patch('cloudbaseinit.init.InitManager.'
|
|
'_exec_plugin')
|
|
@mock.patch('cloudbaseinit.init.InitManager.'
|
|
'_check_plugin_os_requirements')
|
|
@mock.patch('cloudbaseinit.plugins.factory.load_plugins')
|
|
def _test_handle_plugins_stage(self, mock_load_plugins,
|
|
mock_check_plugin_os_requirements,
|
|
mock_exec_plugin,
|
|
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 = success, reboot
|
|
mock_load_plugins.return_value = plugins
|
|
requirements_calls = [mock.call(self.osutils, plugin)
|
|
for plugin in plugins]
|
|
exec_plugin_calls = [mock.call(self.osutils, service, plugin,
|
|
instance_id, {})
|
|
for plugin in plugins]
|
|
|
|
with testutils.LogSnatcher('cloudbaseinit.init') as snatcher:
|
|
response = self._init._handle_plugins_stage(
|
|
self.osutils, service, instance_id, stage)
|
|
self.assertEqual(
|
|
["Executing plugins for stage '{}':".format(stage)],
|
|
snatcher.output)
|
|
mock_load_plugins.assert_called_once_with(stage)
|
|
idx = 1 if (reboot and fast_reboot) else len(plugins)
|
|
mock_check_plugin_os_requirements.assert_has_calls(
|
|
requirements_calls[:idx])
|
|
mock_exec_plugin.assert_has_calls(exec_plugin_calls[:idx])
|
|
self.assertEqual((success, reboot), response)
|
|
|
|
def test_handle_plugins_stage(self):
|
|
self._test_handle_plugins_stage()
|
|
|
|
def test_handle_plugins_stage_no_reboot(self):
|
|
self._test_handle_plugins_stage(reboot=False, fast_reboot=False)
|
|
|
|
@testutils.ConfPatcher('allow_reboot', False)
|
|
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'
|
|
'._handle_plugins_stage')
|
|
@mock.patch('cloudbaseinit.init.InitManager._check_latest_version')
|
|
@mock.patch('cloudbaseinit.version.get_version')
|
|
@mock.patch('cloudbaseinit.plugins.factory.load_plugins')
|
|
@mock.patch('cloudbaseinit.osutils.factory.get_os_utils')
|
|
@mock.patch('cloudbaseinit.metadata.factory.get_metadata_service')
|
|
def _test_configure_host(self, mock_get_metadata_service,
|
|
mock_get_os_utils, mock_load_plugins,
|
|
mock_get_version, mock_check_latest_version,
|
|
mock_handle_plugins_stage, mock_reset_service,
|
|
expected_logging,
|
|
version, name, instance_id, reboot=True,
|
|
last_stage=False):
|
|
sys.platform = 'win32'
|
|
mock_get_version.return_value = version
|
|
fake_service = mock.MagicMock()
|
|
fake_plugin = mock.MagicMock()
|
|
mock_load_plugins.return_value = [fake_plugin]
|
|
mock_get_os_utils.return_value = self.osutils
|
|
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 = [(True, False), (True, False),
|
|
(last_stage, True)]
|
|
stages = [
|
|
base.PLUGIN_STAGE_PRE_NETWORKING,
|
|
base.PLUGIN_STAGE_PRE_METADATA_DISCOVERY,
|
|
base.PLUGIN_STAGE_MAIN]
|
|
stage_calls_list = [[self.osutils, None, None, stage]
|
|
for stage in stages]
|
|
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()
|
|
fake_service.get_name.assert_called_once_with()
|
|
fake_service.get_instance_id.assert_called_once_with()
|
|
fake_service.cleanup.assert_called_once_with()
|
|
mock_handle_plugins_stage.assert_has_calls(stage_calls)
|
|
if reboot:
|
|
self.osutils.reboot.assert_called_once_with()
|
|
else:
|
|
self.assertFalse(self.osutils.reboot.called)
|
|
|
|
def _test_configure_host_with_logging(self, extra_logging, reboot=True,
|
|
last_stage=False):
|
|
instance_id = 'fake id'
|
|
name = 'fake name'
|
|
version = 'version'
|
|
expected_logging = [
|
|
'Cloudbase-Init version: %s' % version,
|
|
'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, 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_reporting_done(self):
|
|
self._test_configure_host_with_logging(
|
|
reboot=False,
|
|
extra_logging=['Plugins execution done',
|
|
'Reporting provisioning completed'],
|
|
last_stage=True)
|
|
|
|
@testutils.ConfPatcher('allow_reboot', False)
|
|
@testutils.ConfPatcher('stop_service_on_exit', True)
|
|
def test_configure_host_no_reboot_allow_service_stopping(self):
|
|
self._test_configure_host_with_logging(
|
|
reboot=False,
|
|
extra_logging=['Plugins execution done',
|
|
'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_reporting_started_and_failed(self):
|
|
self._test_configure_host_with_logging(
|
|
extra_logging=['Reporting provisioning failed', 'Rebooting'])
|
|
|
|
@testutils.ConfPatcher('check_latest_version', False)
|
|
@mock.patch('cloudbaseinit.version.check_latest_version')
|
|
def test_configure_host(self, mock_check_last_version):
|
|
self._init._check_latest_version()
|
|
|
|
self.assertFalse(mock_check_last_version.called)
|
|
|
|
@testutils.ConfPatcher('check_latest_version', True)
|
|
@mock.patch('cloudbaseinit.version.check_latest_version')
|
|
def test_configure_host_with_version_check(self, mock_check_last_version):
|
|
self._init._check_latest_version()
|
|
|
|
mock_check_last_version.assert_called_once()
|
|
|
|
@mock.patch('os.path.basename')
|
|
@mock.patch("sys.executable")
|
|
@mock.patch("sys.argv")
|
|
@mock.patch("sys.exit")
|
|
def _test_reset_service_password_and_respawn(self, mock_exit, mock_argv,
|
|
mock_executable, mock_os_path,
|
|
credentials, current_user):
|
|
token = mock.sentinel.token
|
|
self.osutils.create_user_logon_session.return_value = token
|
|
self.osutils.execute_process_as_user.return_value = 0
|
|
self.osutils.reset_service_password.return_value = credentials
|
|
self.osutils.get_current_user.return_value = current_user
|
|
expected_logging = []
|
|
arguments = sys.argv + ["--noreset_service_password"]
|
|
|
|
with testutils.LogSnatcher('cloudbaseinit.init') as snatcher:
|
|
self._init._reset_service_password_and_respawn(self.osutils)
|
|
|
|
if not credentials:
|
|
return
|
|
|
|
if credentials[1] != current_user[1]:
|
|
expected_logging = [
|
|
"No need to respawn process. Current user: "
|
|
"%(current_user)s. Service user: %(service_user)s" %
|
|
{"current_user": current_user[1],
|
|
"service_user": credentials[1]}
|
|
]
|
|
self.assertEqual(expected_logging, snatcher.output)
|
|
else:
|
|
self.osutils.create_user_logon_session.assert_called_once_with(
|
|
credentials[1], credentials[2], credentials[0],
|
|
logon_type=self.osutils.LOGON32_LOGON_BATCH)
|
|
self.osutils.execute_process_as_user.assert_called_once_with(
|
|
token, arguments)
|
|
mock_exit.assert_called_once_with(0)
|
|
|
|
def test_reset_service_password_and_respawn(self):
|
|
current_user = [mock.sentinel.domain, mock.sentinel.current_user]
|
|
self._test_reset_service_password_and_respawn(
|
|
credentials=None,
|
|
current_user=current_user
|
|
)
|
|
self._test_reset_service_password_and_respawn(
|
|
credentials=[mock.sentinel.domain, mock.sentinel.user,
|
|
mock.sentinel.password],
|
|
current_user=current_user
|
|
)
|
|
self._test_reset_service_password_and_respawn(
|
|
credentials=[mock.sentinel.domain, mock.sentinel.current_user,
|
|
mock.sentinel.password],
|
|
current_user=current_user
|
|
)
|