summaryrefslogtreecommitdiff
path: root/ironic/tests/unit/drivers/modules
diff options
context:
space:
mode:
Diffstat (limited to 'ironic/tests/unit/drivers/modules')
-rw-r--r--ironic/tests/unit/drivers/modules/__init__.py0
-rw-r--r--ironic/tests/unit/drivers/modules/amt/__init__.py0
-rw-r--r--ironic/tests/unit/drivers/modules/amt/test_common.py173
-rw-r--r--ironic/tests/unit/drivers/modules/amt/test_management.py233
-rw-r--r--ironic/tests/unit/drivers/modules/amt/test_power.py282
-rw-r--r--ironic/tests/unit/drivers/modules/amt/test_vendor.py132
-rw-r--r--ironic/tests/unit/drivers/modules/cimc/__init__.py0
-rw-r--r--ironic/tests/unit/drivers/modules/cimc/test_common.py125
-rw-r--r--ironic/tests/unit/drivers/modules/cimc/test_management.py126
-rw-r--r--ironic/tests/unit/drivers/modules/cimc/test_power.py302
-rw-r--r--ironic/tests/unit/drivers/modules/drac/__init__.py0
-rw-r--r--ironic/tests/unit/drivers/modules/drac/bios_wsman_mock.py273
-rw-r--r--ironic/tests/unit/drivers/modules/drac/test_bios.py199
-rw-r--r--ironic/tests/unit/drivers/modules/drac/test_client.py256
-rw-r--r--ironic/tests/unit/drivers/modules/drac/test_common.py135
-rw-r--r--ironic/tests/unit/drivers/modules/drac/test_management.py461
-rw-r--r--ironic/tests/unit/drivers/modules/drac/test_power.py175
-rw-r--r--ironic/tests/unit/drivers/modules/drac/utils.py72
-rw-r--r--ironic/tests/unit/drivers/modules/ilo/__init__.py0
-rw-r--r--ironic/tests/unit/drivers/modules/ilo/test_common.py675
-rw-r--r--ironic/tests/unit/drivers/modules/ilo/test_deploy.py1860
-rw-r--r--ironic/tests/unit/drivers/modules/ilo/test_inspect.py365
-rw-r--r--ironic/tests/unit/drivers/modules/ilo/test_management.py298
-rw-r--r--ironic/tests/unit/drivers/modules/ilo/test_power.py231
-rw-r--r--ironic/tests/unit/drivers/modules/irmc/__init__.py0
-rw-r--r--ironic/tests/unit/drivers/modules/irmc/fake_sensors_data_ng.xml156
-rw-r--r--ironic/tests/unit/drivers/modules/irmc/fake_sensors_data_ok.xml156
-rw-r--r--ironic/tests/unit/drivers/modules/irmc/test_common.py168
-rw-r--r--ironic/tests/unit/drivers/modules/irmc/test_deploy.py1536
-rw-r--r--ironic/tests/unit/drivers/modules/irmc/test_management.py302
-rw-r--r--ironic/tests/unit/drivers/modules/irmc/test_power.py224
-rw-r--r--ironic/tests/unit/drivers/modules/msftocs/__init__.py0
-rw-r--r--ironic/tests/unit/drivers/modules/msftocs/test_common.py110
-rw-r--r--ironic/tests/unit/drivers/modules/msftocs/test_management.py133
-rw-r--r--ironic/tests/unit/drivers/modules/msftocs/test_msftocsclient.py182
-rw-r--r--ironic/tests/unit/drivers/modules/msftocs/test_power.py164
-rw-r--r--ironic/tests/unit/drivers/modules/test_agent.py805
-rw-r--r--ironic/tests/unit/drivers/modules/test_agent_base_vendor.py948
-rw-r--r--ironic/tests/unit/drivers/modules/test_agent_client.py220
-rw-r--r--ironic/tests/unit/drivers/modules/test_console_utils.py348
-rw-r--r--ironic/tests/unit/drivers/modules/test_deploy_utils.py2288
-rw-r--r--ironic/tests/unit/drivers/modules/test_iboot.py384
-rw-r--r--ironic/tests/unit/drivers/modules/test_image_cache.py697
-rw-r--r--ironic/tests/unit/drivers/modules/test_inspector.py239
-rw-r--r--ironic/tests/unit/drivers/modules/test_ipminative.py610
-rw-r--r--ironic/tests/unit/drivers/modules/test_ipmitool.py1899
-rw-r--r--ironic/tests/unit/drivers/modules/test_iscsi_deploy.py1402
-rw-r--r--ironic/tests/unit/drivers/modules/test_seamicro.py676
-rw-r--r--ironic/tests/unit/drivers/modules/test_snmp.py1263
-rw-r--r--ironic/tests/unit/drivers/modules/test_ssh.py975
-rw-r--r--ironic/tests/unit/drivers/modules/test_virtualbox.py374
-rw-r--r--ironic/tests/unit/drivers/modules/test_wol.py194
-rw-r--r--ironic/tests/unit/drivers/modules/ucs/__init__.py0
-rw-r--r--ironic/tests/unit/drivers/modules/ucs/test_helper.py161
-rw-r--r--ironic/tests/unit/drivers/modules/ucs/test_management.py139
-rw-r--r--ironic/tests/unit/drivers/modules/ucs/test_power.py302
56 files changed, 23428 insertions, 0 deletions
diff --git a/ironic/tests/unit/drivers/modules/__init__.py b/ironic/tests/unit/drivers/modules/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/__init__.py
diff --git a/ironic/tests/unit/drivers/modules/amt/__init__.py b/ironic/tests/unit/drivers/modules/amt/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/amt/__init__.py
diff --git a/ironic/tests/unit/drivers/modules/amt/test_common.py b/ironic/tests/unit/drivers/modules/amt/test_common.py
new file mode 100644
index 000000000..d6ab30f5c
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/amt/test_common.py
@@ -0,0 +1,173 @@
+#
+# 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.
+
+"""
+Test class for AMT Common
+"""
+
+import mock
+from oslo_config import cfg
+
+from ironic.common import exception
+from ironic.drivers.modules.amt import common as amt_common
+from ironic.drivers.modules.amt import resource_uris
+from ironic.tests.unit import base
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.drivers.modules.drac import utils as test_utils
+from ironic.tests.unit.drivers import third_party_driver_mock_specs \
+ as mock_specs
+from ironic.tests.unit.objects import utils as obj_utils
+
+INFO_DICT = db_utils.get_test_amt_info()
+CONF = cfg.CONF
+
+
+class AMTCommonMethodsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(AMTCommonMethodsTestCase, self).setUp()
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_amt',
+ driver_info=INFO_DICT)
+
+ def test_parse_driver_info(self):
+ info = amt_common.parse_driver_info(self.node)
+
+ self.assertIsNotNone(info.get('address'))
+ self.assertIsNotNone(info.get('username'))
+ self.assertIsNotNone(info.get('password'))
+ self.assertIsNotNone(info.get('protocol'))
+ self.assertIsNotNone(info.get('uuid'))
+
+ def test_parse_driver_info_missing_address(self):
+ del self.node.driver_info['amt_address']
+
+ self.assertRaises(exception.MissingParameterValue,
+ amt_common.parse_driver_info, self.node)
+
+ def test_parse_driver_info_missing_username(self):
+ del self.node.driver_info['amt_username']
+
+ self.assertRaises(exception.MissingParameterValue,
+ amt_common.parse_driver_info, self.node)
+
+ def test_parse_driver_info_missing_password(self):
+ del self.node.driver_info['amt_password']
+ self.assertRaises(exception.MissingParameterValue,
+ amt_common.parse_driver_info, self.node)
+
+ def test_parse_driver_info_missing_protocol(self):
+ del self.node.driver_info['amt_protocol']
+ info = amt_common.parse_driver_info(self.node)
+ self.assertEqual('http', info.get('protocol'))
+
+ def test_parse_driver_info_wrong_protocol(self):
+ self.node.driver_info['amt_protocol'] = 'fake-protocol'
+ self.assertRaises(exception.InvalidParameterValue,
+ amt_common.parse_driver_info, self.node)
+
+ @mock.patch.object(amt_common, 'Client', spec_set=True, autospec=True)
+ def test_get_wsman_client(self, mock_client):
+ info = amt_common.parse_driver_info(self.node)
+ amt_common.get_wsman_client(self.node)
+ options = {'address': info['address'],
+ 'protocol': info['protocol'],
+ 'username': info['username'],
+ 'password': info['password']}
+
+ mock_client.assert_called_once_with(**options)
+
+ def test_xml_find(self):
+ namespace = 'http://fake'
+ value = 'fake_value'
+ test_xml = test_utils.build_soap_xml([{'test_element': value}],
+ namespace)
+ mock_doc = test_utils.mock_wsman_root(test_xml)
+
+ result = amt_common.xml_find(mock_doc, namespace, 'test_element')
+ self.assertEqual(value, result.text)
+
+ def test_xml_find_fail(self):
+ mock_doc = None
+ self.assertRaises(exception.AMTConnectFailure,
+ amt_common.xml_find,
+ mock_doc, 'namespace', 'test_element')
+
+
+@mock.patch.object(amt_common, 'pywsman', spec_set=mock_specs.PYWSMAN_SPEC)
+class AMTCommonClientTestCase(base.TestCase):
+ def setUp(self):
+ super(AMTCommonClientTestCase, self).setUp()
+ self.info = {key[4:]: INFO_DICT[key] for key in INFO_DICT.keys()}
+
+ def test_wsman_get(self, mock_client_pywsman):
+ namespace = resource_uris.CIM_AssociatedPowerManagementService
+ result_xml = test_utils.build_soap_xml([{'PowerState':
+ '2'}],
+ namespace)
+ mock_doc = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.get.return_value = mock_doc
+ client = amt_common.Client(**self.info)
+
+ client.wsman_get(namespace)
+ mock_pywsman.get.assert_called_once_with(mock.ANY, namespace)
+
+ def test_wsman_get_fail(self, mock_client_pywsman):
+ namespace = amt_common._SOAP_ENVELOPE
+ result_xml = test_utils.build_soap_xml([{'Fault': 'fault'}],
+ namespace)
+ mock_doc = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.get.return_value = mock_doc
+ client = amt_common.Client(**self.info)
+
+ self.assertRaises(exception.AMTFailure, client.wsman_get, namespace)
+ mock_pywsman.get.assert_called_once_with(mock.ANY, namespace)
+
+ def test_wsman_invoke(self, mock_client_pywsman):
+ namespace = resource_uris.CIM_BootSourceSetting
+ result_xml = test_utils.build_soap_xml([{'ReturnValue':
+ '0'}],
+ namespace)
+ mock_doc = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.invoke.return_value = mock_doc
+ method = 'ChangeBootOrder'
+ options = mock.Mock(spec_set=[])
+ client = amt_common.Client(**self.info)
+ doc = None
+ client.wsman_invoke(options, namespace, method, doc)
+ mock_pywsman.invoke.assert_called_once_with(options, namespace, method)
+ doc = 'fake-input'
+ client.wsman_invoke(options, namespace, method, doc)
+ mock_pywsman.invoke.assert_called_with(options, namespace, method, doc)
+
+ def test_wsman_invoke_fail(self, mock_client_pywsman):
+ namespace = resource_uris.CIM_BootSourceSetting
+ result_xml = test_utils.build_soap_xml([{'ReturnValue':
+ '2'}],
+ namespace)
+ mock_doc = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.invoke.return_value = mock_doc
+ method = 'fake-method'
+ options = mock.Mock(spec_set=[])
+
+ client = amt_common.Client(**self.info)
+
+ self.assertRaises(exception.AMTFailure,
+ client.wsman_invoke,
+ options, namespace, method)
+ mock_pywsman.invoke.assert_called_once_with(options, namespace, method)
diff --git a/ironic/tests/unit/drivers/modules/amt/test_management.py b/ironic/tests/unit/drivers/modules/amt/test_management.py
new file mode 100644
index 000000000..2935389a7
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/amt/test_management.py
@@ -0,0 +1,233 @@
+#
+# 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.
+
+"""
+Test class for AMT ManagementInterface
+"""
+
+import mock
+from oslo_config import cfg
+
+from ironic.common import boot_devices
+from ironic.common import exception
+from ironic.conductor import task_manager
+from ironic.drivers.modules.amt import common as amt_common
+from ironic.drivers.modules.amt import management as amt_mgmt
+from ironic.drivers.modules.amt import resource_uris
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.drivers.modules.drac import utils as test_utils
+from ironic.tests.unit.drivers import third_party_driver_mock_specs \
+ as mock_specs
+from ironic.tests.unit.objects import utils as obj_utils
+
+INFO_DICT = db_utils.get_test_amt_info()
+CONF = cfg.CONF
+
+
+@mock.patch.object(amt_common, 'pywsman', spec_set=mock_specs.PYWSMAN_SPEC)
+class AMTManagementInteralMethodsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(AMTManagementInteralMethodsTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_amt')
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_amt',
+ driver_info=INFO_DICT)
+
+ def test__set_boot_device_order(self, mock_client_pywsman):
+ namespace = resource_uris.CIM_BootConfigSetting
+ device = boot_devices.PXE
+ result_xml = test_utils.build_soap_xml([{'ReturnValue': '0'}],
+ namespace)
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.invoke.return_value = mock_xml
+
+ amt_mgmt._set_boot_device_order(self.node, device)
+
+ mock_pywsman.invoke.assert_called_once_with(
+ mock.ANY, namespace, 'ChangeBootOrder', mock.ANY)
+
+ def test__set_boot_device_order_fail(self, mock_client_pywsman):
+ namespace = resource_uris.CIM_BootConfigSetting
+ device = boot_devices.PXE
+ result_xml = test_utils.build_soap_xml([{'ReturnValue': '2'}],
+ namespace)
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.invoke.return_value = mock_xml
+
+ self.assertRaises(exception.AMTFailure,
+ amt_mgmt._set_boot_device_order, self.node, device)
+ mock_pywsman.invoke.assert_called_once_with(
+ mock.ANY, namespace, 'ChangeBootOrder', mock.ANY)
+
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.invoke.return_value = None
+
+ self.assertRaises(exception.AMTConnectFailure,
+ amt_mgmt._set_boot_device_order, self.node, device)
+
+ def test__enable_boot_config(self, mock_client_pywsman):
+ namespace = resource_uris.CIM_BootService
+ result_xml = test_utils.build_soap_xml([{'ReturnValue': '0'}],
+ namespace)
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.invoke.return_value = mock_xml
+
+ amt_mgmt._enable_boot_config(self.node)
+
+ mock_pywsman.invoke.assert_called_once_with(
+ mock.ANY, namespace, 'SetBootConfigRole', mock.ANY)
+
+ def test__enable_boot_config_fail(self, mock_client_pywsman):
+ namespace = resource_uris.CIM_BootService
+ result_xml = test_utils.build_soap_xml([{'ReturnValue': '2'}],
+ namespace)
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.invoke.return_value = mock_xml
+
+ self.assertRaises(exception.AMTFailure,
+ amt_mgmt._enable_boot_config, self.node)
+ mock_pywsman.invoke.assert_called_once_with(
+ mock.ANY, namespace, 'SetBootConfigRole', mock.ANY)
+
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.invoke.return_value = None
+
+ self.assertRaises(exception.AMTConnectFailure,
+ amt_mgmt._enable_boot_config, self.node)
+
+
+class AMTManagementTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(AMTManagementTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_amt')
+ self.info = INFO_DICT
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_amt',
+ driver_info=self.info)
+
+ def test_get_properties(self):
+ expected = amt_common.COMMON_PROPERTIES
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertEqual(expected, task.driver.get_properties())
+
+ @mock.patch.object(amt_common, 'parse_driver_info', spec_set=True,
+ autospec=True)
+ def test_validate(self, mock_drvinfo):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.driver.management.validate(task)
+ mock_drvinfo.assert_called_once_with(task.node)
+
+ @mock.patch.object(amt_common, 'parse_driver_info', spec_set=True,
+ autospec=True)
+ def test_validate_fail(self, mock_drvinfo):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ mock_drvinfo.side_effect = iter(
+ [exception.InvalidParameterValue('x')])
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.management.validate,
+ task)
+
+ def test_get_supported_boot_devices(self):
+ expected = [boot_devices.PXE, boot_devices.DISK, boot_devices.CDROM]
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertEqual(
+ sorted(expected),
+ sorted(task.driver.management.
+ get_supported_boot_devices(task)))
+
+ def test_set_boot_device_one_time(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.set_boot_device(task, 'pxe')
+ self.assertEqual('pxe',
+ task.node.driver_internal_info["amt_boot_device"])
+ self.assertFalse(
+ task.node.driver_internal_info["amt_boot_persistent"])
+
+ def test_set_boot_device_persistent(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.set_boot_device(task, 'pxe',
+ persistent=True)
+ self.assertEqual('pxe',
+ task.node.driver_internal_info["amt_boot_device"])
+ self.assertTrue(
+ task.node.driver_internal_info["amt_boot_persistent"])
+
+ def test_set_boot_device_fail(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.management.set_boot_device,
+ task, 'fake-device')
+
+ @mock.patch.object(amt_mgmt, '_enable_boot_config', spec_set=True,
+ autospec=True)
+ @mock.patch.object(amt_mgmt, '_set_boot_device_order', spec_set=True,
+ autospec=True)
+ def test_ensure_next_boot_device_one_time(self, mock_sbdo, mock_ebc):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ device = boot_devices.PXE
+ task.node.driver_internal_info['amt_boot_device'] = 'pxe'
+ task.driver.management.ensure_next_boot_device(task.node, device)
+ self.assertEqual('disk',
+ task.node.driver_internal_info["amt_boot_device"])
+ self.assertTrue(
+ task.node.driver_internal_info["amt_boot_persistent"])
+ mock_sbdo.assert_called_once_with(task.node, device)
+ mock_ebc.assert_called_once_with(task.node)
+
+ @mock.patch.object(amt_mgmt, '_enable_boot_config', spec_set=True,
+ autospec=True)
+ @mock.patch.object(amt_mgmt, '_set_boot_device_order', spec_set=True,
+ autospec=True)
+ def test_ensure_next_boot_device_persistent(self, mock_sbdo, mock_ebc):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ device = boot_devices.PXE
+ task.node.driver_internal_info['amt_boot_device'] = 'pxe'
+ task.node.driver_internal_info['amt_boot_persistent'] = True
+ task.driver.management.ensure_next_boot_device(task.node, device)
+ self.assertEqual('pxe',
+ task.node.driver_internal_info["amt_boot_device"])
+ self.assertTrue(
+ task.node.driver_internal_info["amt_boot_persistent"])
+ mock_sbdo.assert_called_once_with(task.node, device)
+ mock_ebc.assert_called_once_with(task.node)
+
+ def test_get_boot_device(self):
+ expected = {'boot_device': boot_devices.DISK, 'persistent': True}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertEqual(expected,
+ task.driver.management.get_boot_device(task))
+
+ def test_get_sensor_data(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(NotImplementedError,
+ task.driver.management.get_sensors_data,
+ task)
diff --git a/ironic/tests/unit/drivers/modules/amt/test_power.py b/ironic/tests/unit/drivers/modules/amt/test_power.py
new file mode 100644
index 000000000..878f71d5f
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/amt/test_power.py
@@ -0,0 +1,282 @@
+#
+# 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.
+
+"""
+Test class for AMT ManagementInterface
+"""
+
+import mock
+from oslo_config import cfg
+
+from ironic.common import boot_devices
+from ironic.common import exception
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.drivers.modules.amt import common as amt_common
+from ironic.drivers.modules.amt import management as amt_mgmt
+from ironic.drivers.modules.amt import power as amt_power
+from ironic.drivers.modules.amt import resource_uris
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.drivers.modules.drac import utils as test_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+INFO_DICT = db_utils.get_test_amt_info()
+CONF = cfg.CONF
+
+
+class AMTPowerInteralMethodsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(AMTPowerInteralMethodsTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_amt')
+ self.info = INFO_DICT
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_amt',
+ driver_info=self.info)
+ CONF.set_override('max_attempts', 2, 'amt')
+ CONF.set_override('action_wait', 0, 'amt')
+
+ @mock.patch.object(amt_common, 'get_wsman_client', spec_set=True,
+ autospec=True)
+ def test__set_power_state(self, mock_client_pywsman):
+ namespace = resource_uris.CIM_PowerManagementService
+ mock_client = mock_client_pywsman.return_value
+ amt_power._set_power_state(self.node, states.POWER_ON)
+ mock_client.wsman_invoke.assert_called_once_with(
+ mock.ANY, namespace, 'RequestPowerStateChange', mock.ANY)
+
+ @mock.patch.object(amt_common, 'get_wsman_client', spec_set=True,
+ autospec=True)
+ def test__set_power_state_fail(self, mock_client_pywsman):
+ mock_client = mock_client_pywsman.return_value
+ mock_client.wsman_invoke.side_effect = exception.AMTFailure('x')
+ self.assertRaises(exception.AMTFailure,
+ amt_power._set_power_state,
+ self.node, states.POWER_ON)
+
+ @mock.patch.object(amt_common, 'get_wsman_client', spec_set=True,
+ autospec=True)
+ def test__power_status(self, mock_gwc):
+ namespace = resource_uris.CIM_AssociatedPowerManagementService
+ result_xml = test_utils.build_soap_xml([{'PowerState':
+ '2'}],
+ namespace)
+ mock_doc = test_utils.mock_wsman_root(result_xml)
+ mock_client = mock_gwc.return_value
+ mock_client.wsman_get.return_value = mock_doc
+ self.assertEqual(
+ states.POWER_ON, amt_power._power_status(self.node))
+
+ result_xml = test_utils.build_soap_xml([{'PowerState':
+ '8'}],
+ namespace)
+ mock_doc = test_utils.mock_wsman_root(result_xml)
+ mock_client = mock_gwc.return_value
+ mock_client.wsman_get.return_value = mock_doc
+ self.assertEqual(
+ states.POWER_OFF, amt_power._power_status(self.node))
+
+ result_xml = test_utils.build_soap_xml([{'PowerState':
+ '4'}],
+ namespace)
+ mock_doc = test_utils.mock_wsman_root(result_xml)
+ mock_client = mock_gwc.return_value
+ mock_client.wsman_get.return_value = mock_doc
+ self.assertEqual(
+ states.ERROR, amt_power._power_status(self.node))
+
+ @mock.patch.object(amt_common, 'get_wsman_client', spec_set=True,
+ autospec=True)
+ def test__power_status_fail(self, mock_gwc):
+ mock_client = mock_gwc.return_value
+ mock_client.wsman_get.side_effect = exception.AMTFailure('x')
+ self.assertRaises(exception.AMTFailure,
+ amt_power._power_status,
+ self.node)
+
+ @mock.patch.object(amt_mgmt.AMTManagement, 'ensure_next_boot_device',
+ spec_set=True, autospec=True)
+ @mock.patch.object(amt_power, '_power_status', spec_set=True,
+ autospec=True)
+ @mock.patch.object(amt_power, '_set_power_state', spec_set=True,
+ autospec=True)
+ def test__set_and_wait_power_on_with_boot_device(self, mock_sps,
+ mock_ps, mock_enbd):
+ target_state = states.POWER_ON
+ boot_device = boot_devices.PXE
+ mock_ps.side_effect = iter([states.POWER_OFF, states.POWER_ON])
+ mock_enbd.return_value = None
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.node.driver_internal_info['amt_boot_device'] = boot_device
+ result = amt_power._set_and_wait(task, target_state)
+ self.assertEqual(states.POWER_ON, result)
+ mock_enbd.assert_called_with(task.driver.management, task.node,
+ boot_devices.PXE)
+ mock_sps.assert_called_once_with(task.node, states.POWER_ON)
+ mock_ps.assert_called_with(task.node)
+
+ @mock.patch.object(amt_power, '_power_status', spec_set=True,
+ autospec=True)
+ @mock.patch.object(amt_power, '_set_power_state', spec_set=True,
+ autospec=True)
+ def test__set_and_wait_power_on_without_boot_device(self, mock_sps,
+ mock_ps):
+ target_state = states.POWER_ON
+ mock_ps.side_effect = iter([states.POWER_OFF, states.POWER_ON])
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertEqual(states.POWER_ON,
+ amt_power._set_and_wait(task, target_state))
+ mock_sps.assert_called_once_with(task.node, states.POWER_ON)
+ mock_ps.assert_called_with(task.node)
+
+ boot_device = boot_devices.DISK
+ self.node.driver_internal_info['amt_boot_device'] = boot_device
+ mock_ps.side_effect = iter([states.POWER_OFF, states.POWER_ON])
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertEqual(states.POWER_ON,
+ amt_power._set_and_wait(task, target_state))
+ mock_sps.assert_called_with(task.node, states.POWER_ON)
+ mock_ps.assert_called_with(task.node)
+
+ def test__set_and_wait_wrong_target_state(self):
+ target_state = 'fake-state'
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ amt_power._set_and_wait, task, target_state)
+
+ @mock.patch.object(amt_power, '_power_status', spec_set=True,
+ autospec=True)
+ @mock.patch.object(amt_power, '_set_power_state', spec_set=True,
+ autospec=True)
+ def test__set_and_wait_exceed_iterations(self, mock_sps,
+ mock_ps):
+ target_state = states.POWER_ON
+ mock_ps.side_effect = iter([states.POWER_OFF, states.POWER_OFF,
+ states.POWER_OFF])
+ mock_sps.return_value = exception.AMTFailure('x')
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.PowerStateFailure,
+ amt_power._set_and_wait, task, target_state)
+ mock_sps.assert_called_with(task.node, states.POWER_ON)
+ mock_ps.assert_called_with(task.node)
+ self.assertEqual(3, mock_ps.call_count)
+
+ @mock.patch.object(amt_power, '_power_status', spec_set=True,
+ autospec=True)
+ def test__set_and_wait_already_target_state(self, mock_ps):
+ target_state = states.POWER_ON
+ mock_ps.side_effect = iter([states.POWER_ON])
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertEqual(states.POWER_ON,
+ amt_power._set_and_wait(task, target_state))
+ mock_ps.assert_called_with(task.node)
+
+ @mock.patch.object(amt_power, '_power_status', spec_set=True,
+ autospec=True)
+ @mock.patch.object(amt_power, '_set_power_state', spec_set=True,
+ autospec=True)
+ def test__set_and_wait_power_off(self, mock_sps, mock_ps):
+ target_state = states.POWER_OFF
+ mock_ps.side_effect = iter([states.POWER_ON, states.POWER_OFF])
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertEqual(states.POWER_OFF,
+ amt_power._set_and_wait(task, target_state))
+ mock_sps.assert_called_once_with(task.node, states.POWER_OFF)
+ mock_ps.assert_called_with(task.node)
+
+
+class AMTPowerTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(AMTPowerTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_amt')
+ self.info = INFO_DICT
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_amt',
+ driver_info=self.info)
+
+ def test_get_properties(self):
+ expected = amt_common.COMMON_PROPERTIES
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertEqual(expected, task.driver.get_properties())
+
+ @mock.patch.object(amt_common, 'parse_driver_info', spec_set=True,
+ autospec=True)
+ def test_validate(self, mock_drvinfo):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.driver.power.validate(task)
+ mock_drvinfo.assert_called_once_with(task.node)
+
+ @mock.patch.object(amt_common, 'parse_driver_info', spec_set=True,
+ autospec=True)
+ def test_validate_fail(self, mock_drvinfo):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ mock_drvinfo.side_effect = iter(
+ [exception.InvalidParameterValue('x')])
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.power.validate,
+ task)
+
+ @mock.patch.object(amt_power, '_power_status', spec_set=True,
+ autospec=True)
+ def test_get_power_state(self, mock_ps):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ mock_ps.return_value = states.POWER_ON
+ self.assertEqual(states.POWER_ON,
+ task.driver.power.get_power_state(task))
+ mock_ps.assert_called_once_with(task.node)
+
+ @mock.patch.object(amt_power, '_set_and_wait', spec_set=True,
+ autospec=True)
+ def test_set_power_state(self, mock_saw):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ pstate = states.POWER_ON
+ mock_saw.return_value = states.POWER_ON
+ task.driver.power.set_power_state(task, pstate)
+ mock_saw.assert_called_once_with(task, pstate)
+
+ @mock.patch.object(amt_power, '_set_and_wait', spec_set=True,
+ autospec=True)
+ def test_set_power_state_fail(self, mock_saw):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ pstate = states.POWER_ON
+ mock_saw.side_effect = iter([exception.PowerStateFailure('x')])
+ self.assertRaises(exception.PowerStateFailure,
+ task.driver.power.set_power_state,
+ task, pstate)
+ mock_saw.assert_called_once_with(task, pstate)
+
+ @mock.patch.object(amt_power, '_set_and_wait', spec_set=True,
+ autospec=True)
+ def test_reboot(self, mock_saw):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.power.reboot(task)
+ calls = [mock.call(task, states.POWER_OFF),
+ mock.call(task, states.POWER_ON)]
+ mock_saw.assert_has_calls(calls)
diff --git a/ironic/tests/unit/drivers/modules/amt/test_vendor.py b/ironic/tests/unit/drivers/modules/amt/test_vendor.py
new file mode 100644
index 000000000..07d893251
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/amt/test_vendor.py
@@ -0,0 +1,132 @@
+#
+# 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.
+
+"""Test class for AMT Vendor methods."""
+
+import mock
+
+from ironic.common import boot_devices
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.drivers.modules.amt import management as amt_mgmt
+from ironic.drivers.modules import iscsi_deploy
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+INFO_DICT = db_utils.get_test_amt_info()
+
+
+class AMTPXEVendorPassthruTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(AMTPXEVendorPassthruTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="pxe_amt")
+ self.node = obj_utils.create_test_node(
+ self.context, driver='pxe_amt', driver_info=INFO_DICT)
+
+ def test_vendor_routes(self):
+ expected = ['heartbeat', 'pass_deploy_info',
+ 'pass_bootloader_install_info']
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ vendor_routes = task.driver.vendor.vendor_routes
+ self.assertIsInstance(vendor_routes, dict)
+ self.assertEqual(sorted(expected), sorted(list(vendor_routes)))
+
+ def test_driver_routes(self):
+ expected = ['lookup']
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ driver_routes = task.driver.vendor.driver_routes
+ self.assertIsInstance(driver_routes, dict)
+ self.assertEqual(sorted(expected), sorted(list(driver_routes)))
+
+ @mock.patch.object(amt_mgmt.AMTManagement, 'ensure_next_boot_device',
+ spec_set=True, autospec=True)
+ @mock.patch.object(iscsi_deploy.VendorPassthru, 'pass_deploy_info',
+ spec_set=True, autospec=True)
+ def test_vendorpassthru_pass_deploy_info_netboot(self,
+ mock_pxe_vendorpassthru,
+ mock_ensure):
+ kwargs = {'address': '123456'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.provision_state = states.DEPLOYWAIT
+ task.node.target_provision_state = states.ACTIVE
+ task.node.instance_info['capabilities'] = {
+ "boot_option": "netboot"
+ }
+ task.driver.vendor.pass_deploy_info(task, **kwargs)
+ mock_ensure.assert_called_with(
+ task.driver.management, task.node, boot_devices.PXE)
+ mock_pxe_vendorpassthru.assert_called_once_with(
+ task.driver.vendor, task, **kwargs)
+
+ @mock.patch.object(amt_mgmt.AMTManagement, 'ensure_next_boot_device',
+ spec_set=True, autospec=True)
+ @mock.patch.object(iscsi_deploy.VendorPassthru, 'pass_deploy_info',
+ spec_set=True, autospec=True)
+ def test_vendorpassthru_pass_deploy_info_localboot(self,
+ mock_pxe_vendorpassthru,
+ mock_ensure):
+ kwargs = {'address': '123456'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.provision_state = states.DEPLOYWAIT
+ task.node.target_provision_state = states.ACTIVE
+ task.node.instance_info['capabilities'] = {"boot_option": "local"}
+ task.driver.vendor.pass_deploy_info(task, **kwargs)
+ self.assertFalse(mock_ensure.called)
+ mock_pxe_vendorpassthru.assert_called_once_with(
+ task.driver.vendor, task, **kwargs)
+
+ @mock.patch.object(amt_mgmt.AMTManagement, 'ensure_next_boot_device',
+ spec_set=True, autospec=True)
+ @mock.patch.object(iscsi_deploy.VendorPassthru, 'continue_deploy',
+ spec_set=True, autospec=True)
+ def test_vendorpassthru_continue_deploy_netboot(self,
+ mock_pxe_vendorpassthru,
+ mock_ensure):
+ kwargs = {'address': '123456'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.provision_state = states.DEPLOYWAIT
+ task.node.target_provision_state = states.ACTIVE
+ task.node.instance_info['capabilities'] = {
+ "boot_option": "netboot"
+ }
+ task.driver.vendor.continue_deploy(task, **kwargs)
+ mock_ensure.assert_called_with(
+ task.driver.management, task.node, boot_devices.PXE)
+ mock_pxe_vendorpassthru.assert_called_once_with(
+ task.driver.vendor, task, **kwargs)
+
+ @mock.patch.object(amt_mgmt.AMTManagement, 'ensure_next_boot_device',
+ spec_set=True, autospec=True)
+ @mock.patch.object(iscsi_deploy.VendorPassthru, 'continue_deploy',
+ spec_set=True, autospec=True)
+ def test_vendorpassthru_continue_deploy_localboot(self,
+ mock_pxe_vendorpassthru,
+ mock_ensure):
+ kwargs = {'address': '123456'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.provision_state = states.DEPLOYWAIT
+ task.node.target_provision_state = states.ACTIVE
+ task.node.instance_info['capabilities'] = {"boot_option": "local"}
+ task.driver.vendor.continue_deploy(task, **kwargs)
+ self.assertFalse(mock_ensure.called)
+ mock_pxe_vendorpassthru.assert_called_once_with(
+ task.driver.vendor, task, **kwargs)
diff --git a/ironic/tests/unit/drivers/modules/cimc/__init__.py b/ironic/tests/unit/drivers/modules/cimc/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/cimc/__init__.py
diff --git a/ironic/tests/unit/drivers/modules/cimc/test_common.py b/ironic/tests/unit/drivers/modules/cimc/test_common.py
new file mode 100644
index 000000000..0924f81de
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/cimc/test_common.py
@@ -0,0 +1,125 @@
+# Copyright 2015, Cisco Systems.
+#
+# 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 mock
+
+from oslo_config import cfg
+from oslo_utils import importutils
+
+from ironic.common import exception
+from ironic.conductor import task_manager
+from ironic.drivers.modules.cimc import common as cimc_common
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+imcsdk = importutils.try_import('ImcSdk')
+
+CONF = cfg.CONF
+
+
+class CIMCBaseTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(CIMCBaseTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="fake_cimc")
+ self.node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_cimc',
+ driver_info=db_utils.get_test_cimc_info(),
+ instance_uuid="fake_uuid")
+ CONF.set_override('max_retry', 2, 'cimc')
+ CONF.set_override('action_interval', 0, 'cimc')
+
+
+class ParseDriverInfoTestCase(CIMCBaseTestCase):
+
+ def test_parse_driver_info(self):
+ info = cimc_common.parse_driver_info(self.node)
+
+ self.assertIsNotNone(info.get('cimc_address'))
+ self.assertIsNotNone(info.get('cimc_username'))
+ self.assertIsNotNone(info.get('cimc_password'))
+
+ def test_parse_driver_info_missing_address(self):
+ del self.node.driver_info['cimc_address']
+ self.assertRaises(exception.MissingParameterValue,
+ cimc_common.parse_driver_info, self.node)
+
+ def test_parse_driver_info_missing_username(self):
+ del self.node.driver_info['cimc_username']
+ self.assertRaises(exception.MissingParameterValue,
+ cimc_common.parse_driver_info, self.node)
+
+ def test_parse_driver_info_missing_password(self):
+ del self.node.driver_info['cimc_password']
+ self.assertRaises(exception.MissingParameterValue,
+ cimc_common.parse_driver_info, self.node)
+
+
+@mock.patch.object(cimc_common, 'cimc_handle', autospec=True)
+class CIMCHandleLogin(CIMCBaseTestCase):
+
+ def test_cimc_handle_login(self, mock_handle):
+ info = cimc_common.parse_driver_info(self.node)
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with mock_handle(task) as handle:
+ cimc_common.handle_login(task, handle, info)
+
+ handle.login.assert_called_once_with(
+ self.node.driver_info['cimc_address'],
+ self.node.driver_info['cimc_username'],
+ self.node.driver_info['cimc_password'])
+
+ def test_cimc_handle_login_exception(self, mock_handle):
+ info = cimc_common.parse_driver_info(self.node)
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with mock_handle(task) as handle:
+ handle.login.side_effect = imcsdk.ImcException('Boom')
+
+ self.assertRaises(exception.CIMCException,
+ cimc_common.handle_login,
+ task, handle, info)
+
+ handle.login.assert_called_once_with(
+ self.node.driver_info['cimc_address'],
+ self.node.driver_info['cimc_username'],
+ self.node.driver_info['cimc_password'])
+
+
+class CIMCHandleTestCase(CIMCBaseTestCase):
+
+ @mock.patch.object(imcsdk, 'ImcHandle', autospec=True)
+ @mock.patch.object(cimc_common, 'handle_login', autospec=True)
+ def test_cimc_handle(self, mock_login, mock_handle):
+ mo_hand = mock.MagicMock()
+ mo_hand.username = self.node.driver_info.get('cimc_username')
+ mo_hand.password = self.node.driver_info.get('cimc_password')
+ mo_hand.name = self.node.driver_info.get('cimc_address')
+ mock_handle.return_value = mo_hand
+ info = cimc_common.parse_driver_info(self.node)
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with cimc_common.cimc_handle(task) as handle:
+ self.assertEqual(handle, mock_handle.return_value)
+
+ mock_login.assert_called_once_with(task, mock_handle.return_value,
+ info)
+ mock_handle.return_value.logout.assert_called_once_with()
diff --git a/ironic/tests/unit/drivers/modules/cimc/test_management.py b/ironic/tests/unit/drivers/modules/cimc/test_management.py
new file mode 100644
index 000000000..3a8d53493
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/cimc/test_management.py
@@ -0,0 +1,126 @@
+# Copyright 2015, Cisco Systems.
+#
+# 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 mock
+
+from oslo_utils import importutils
+
+from ironic.common import boot_devices
+from ironic.common import exception
+from ironic.conductor import task_manager
+from ironic.drivers.modules.cimc import common
+from ironic.tests.unit.drivers.modules.cimc import test_common
+
+imcsdk = importutils.try_import('ImcSdk')
+
+
+@mock.patch.object(common, 'cimc_handle', autospec=True)
+class CIMCManagementTestCase(test_common.CIMCBaseTestCase):
+
+ def test_get_properties(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertEqual(common.COMMON_PROPERTIES,
+ task.driver.management.get_properties())
+
+ @mock.patch.object(common, "parse_driver_info", autospec=True)
+ def test_validate(self, mock_driver_info, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.validate(task)
+ mock_driver_info.assert_called_once_with(task.node)
+
+ def test_get_supported_boot_devices(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ expected = [boot_devices.PXE, boot_devices.DISK,
+ boot_devices.CDROM]
+ result = task.driver.management.get_supported_boot_devices(task)
+ self.assertEqual(sorted(expected), sorted(result))
+
+ def test_get_boot_device(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with mock_handle(task) as handle:
+ handle.xml_query.return_value.error_code = None
+ mock_dev = mock.MagicMock()
+ mock_dev.Order = 1
+ mock_dev.Rn = 'storage-read-write'
+ handle.xml_query().OutConfigs.child[0].child = [mock_dev]
+
+ device = task.driver.management.get_boot_device(task)
+ self.assertEqual(
+ {'boot_device': boot_devices.DISK, 'persistent': True},
+ device)
+
+ def test_get_boot_device_fail(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with mock_handle(task) as handle:
+ handle.xml_query.return_value.error_code = None
+ mock_dev = mock.MagicMock()
+ mock_dev.Order = 1
+ mock_dev.Rn = 'storage-read-write'
+ handle.xml_query().OutConfigs.child[0].child = [mock_dev]
+
+ device = task.driver.management.get_boot_device(task)
+
+ self.assertEqual(
+ {'boot_device': boot_devices.DISK, 'persistent': True},
+ device)
+
+ def test_set_boot_device(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with mock_handle(task) as handle:
+ handle.xml_query.return_value.error_code = None
+ task.driver.management.set_boot_device(task, boot_devices.DISK)
+ method = imcsdk.ImcCore.ExternalMethod("ConfigConfMo")
+ method.Cookie = handle.cookie
+ method.Dn = "sys/rack-unit-1/boot-policy"
+ method.InHierarchical = "true"
+
+ config = imcsdk.Imc.ConfigConfig()
+
+ bootMode = imcsdk.ImcCore.ManagedObject('lsbootStorage')
+ bootMode.set_attr("access", 'read-write')
+ bootMode.set_attr("type", 'storage')
+ bootMode.set_attr("Rn", 'storage-read-write')
+ bootMode.set_attr("order", "1")
+
+ config.add_child(bootMode)
+ method.InConfig = config
+
+ handle.xml_query.assert_called_once_with(
+ method, imcsdk.WriteXmlOption.DIRTY)
+
+ def test_set_boot_device_fail(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with mock_handle(task) as handle:
+ method = imcsdk.ImcCore.ExternalMethod("ConfigConfMo")
+ handle.xml_query.return_value.error_code = "404"
+
+ self.assertRaises(exception.CIMCException,
+ task.driver.management.set_boot_device,
+ task, boot_devices.DISK)
+
+ handle.xml_query.assert_called_once_with(
+ method, imcsdk.WriteXmlOption.DIRTY)
+
+ def test_get_sensors_data(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(NotImplementedError,
+ task.driver.management.get_sensors_data, task)
diff --git a/ironic/tests/unit/drivers/modules/cimc/test_power.py b/ironic/tests/unit/drivers/modules/cimc/test_power.py
new file mode 100644
index 000000000..f6a05bb8d
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/cimc/test_power.py
@@ -0,0 +1,302 @@
+# Copyright 2015, Cisco Systems.
+#
+# 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 mock
+
+from oslo_config import cfg
+from oslo_utils import importutils
+
+from ironic.common import exception
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.drivers.modules.cimc import common
+from ironic.drivers.modules.cimc import power
+from ironic.tests.unit.drivers.modules.cimc import test_common
+
+imcsdk = importutils.try_import('ImcSdk')
+
+CONF = cfg.CONF
+
+
+@mock.patch.object(common, 'cimc_handle', autospec=True)
+class WaitForStateChangeTestCase(test_common.CIMCBaseTestCase):
+
+ def setUp(self):
+ super(WaitForStateChangeTestCase, self).setUp()
+ CONF.set_override('max_retry', 2, 'cimc')
+ CONF.set_override('action_interval', 0, 'cimc')
+
+ def test__wait_for_state_change(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with mock_handle(task) as handle:
+ mock_rack_unit = mock.MagicMock()
+ mock_rack_unit.get_attr.return_value = (
+ imcsdk.ComputeRackUnit.CONST_OPER_POWER_ON)
+
+ handle.get_imc_managedobject.return_value = [mock_rack_unit]
+
+ state = power._wait_for_state_change(states.POWER_ON, task)
+
+ handle.get_imc_managedobject.assert_called_once_with(
+ None, None, params={"Dn": "sys/rack-unit-1"})
+
+ self.assertEqual(state, states.POWER_ON)
+
+ def test__wait_for_state_change_fail(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with mock_handle(task) as handle:
+ mock_rack_unit = mock.MagicMock()
+ mock_rack_unit.get_attr.return_value = (
+ imcsdk.ComputeRackUnit.CONST_OPER_POWER_OFF)
+
+ handle.get_imc_managedobject.return_value = [mock_rack_unit]
+
+ state = power._wait_for_state_change(states.POWER_ON, task)
+
+ calls = [
+ mock.call(None, None, params={"Dn": "sys/rack-unit-1"}),
+ mock.call(None, None, params={"Dn": "sys/rack-unit-1"})
+ ]
+ handle.get_imc_managedobject.assert_has_calls(calls)
+ self.assertEqual(state, states.ERROR)
+
+ def test__wait_for_state_change_imc_exception(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with mock_handle(task) as handle:
+ handle.get_imc_managedobject.side_effect = (
+ imcsdk.ImcException('Boom'))
+
+ self.assertRaises(
+ exception.CIMCException,
+ power._wait_for_state_change, states.POWER_ON, task)
+
+ handle.get_imc_managedobject.assert_called_once_with(
+ None, None, params={"Dn": "sys/rack-unit-1"})
+
+
+@mock.patch.object(common, 'cimc_handle', autospec=True)
+class PowerTestCase(test_common.CIMCBaseTestCase):
+
+ def test_get_properties(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertEqual(common.COMMON_PROPERTIES,
+ task.driver.power.get_properties())
+
+ @mock.patch.object(common, "parse_driver_info", autospec=True)
+ def test_validate(self, mock_driver_info, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.power.validate(task)
+ mock_driver_info.assert_called_once_with(task.node)
+
+ def test_get_power_state(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with mock_handle(task) as handle:
+ mock_rack_unit = mock.MagicMock()
+ mock_rack_unit.get_attr.return_value = (
+ imcsdk.ComputeRackUnit.CONST_OPER_POWER_ON)
+
+ handle.get_imc_managedobject.return_value = [mock_rack_unit]
+
+ state = task.driver.power.get_power_state(task)
+
+ handle.get_imc_managedobject.assert_called_once_with(
+ None, None, params={"Dn": "sys/rack-unit-1"})
+ self.assertEqual(states.POWER_ON, state)
+
+ def test_get_power_state_fail(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with mock_handle(task) as handle:
+ mock_rack_unit = mock.MagicMock()
+ mock_rack_unit.get_attr.return_value = (
+ imcsdk.ComputeRackUnit.CONST_OPER_POWER_ON)
+
+ handle.get_imc_managedobject.side_effect = (
+ imcsdk.ImcException("boom"))
+
+ self.assertRaises(exception.CIMCException,
+ task.driver.power.get_power_state, task)
+
+ handle.get_imc_managedobject.assert_called_once_with(
+ None, None, params={"Dn": "sys/rack-unit-1"})
+
+ def test_set_power_state_invalid_state(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.power.set_power_state,
+ task, states.ERROR)
+
+ def test_set_power_state_reboot_ok(self, mock_handle):
+ hri = imcsdk.ComputeRackUnit.CONST_ADMIN_POWER_HARD_RESET_IMMEDIATE
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with mock_handle(task) as handle:
+ mock_rack_unit = mock.MagicMock()
+ mock_rack_unit.get_attr.side_effect = [
+ imcsdk.ComputeRackUnit.CONST_OPER_POWER_OFF,
+ imcsdk.ComputeRackUnit.CONST_OPER_POWER_ON
+ ]
+ handle.get_imc_managedobject.return_value = [mock_rack_unit]
+
+ task.driver.power.set_power_state(task, states.REBOOT)
+
+ handle.set_imc_managedobject.assert_called_once_with(
+ None, class_id="ComputeRackUnit",
+ params={
+ imcsdk.ComputeRackUnit.ADMIN_POWER: hri,
+ imcsdk.ComputeRackUnit.DN: "sys/rack-unit-1"
+ })
+
+ handle.get_imc_managedobject.assert_called_with(
+ None, None, params={"Dn": "sys/rack-unit-1"})
+
+ def test_set_power_state_reboot_fail(self, mock_handle):
+ hri = imcsdk.ComputeRackUnit.CONST_ADMIN_POWER_HARD_RESET_IMMEDIATE
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with mock_handle(task) as handle:
+ handle.get_imc_managedobject.side_effect = (
+ imcsdk.ImcException("boom"))
+
+ self.assertRaises(exception.CIMCException,
+ task.driver.power.set_power_state,
+ task, states.REBOOT)
+
+ handle.set_imc_managedobject.assert_called_once_with(
+ None, class_id="ComputeRackUnit",
+ params={
+ imcsdk.ComputeRackUnit.ADMIN_POWER: hri,
+ imcsdk.ComputeRackUnit.DN: "sys/rack-unit-1"
+ })
+
+ handle.get_imc_managedobject.assert_called_with(
+ None, None, params={"Dn": "sys/rack-unit-1"})
+
+ def test_set_power_state_on_ok(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with mock_handle(task) as handle:
+ mock_rack_unit = mock.MagicMock()
+ mock_rack_unit.get_attr.side_effect = [
+ imcsdk.ComputeRackUnit.CONST_OPER_POWER_OFF,
+ imcsdk.ComputeRackUnit.CONST_OPER_POWER_ON
+ ]
+ handle.get_imc_managedobject.return_value = [mock_rack_unit]
+
+ task.driver.power.set_power_state(task, states.POWER_ON)
+
+ handle.set_imc_managedobject.assert_called_once_with(
+ None, class_id="ComputeRackUnit",
+ params={
+ imcsdk.ComputeRackUnit.ADMIN_POWER:
+ imcsdk.ComputeRackUnit.CONST_ADMIN_POWER_UP,
+ imcsdk.ComputeRackUnit.DN: "sys/rack-unit-1"
+ })
+
+ handle.get_imc_managedobject.assert_called_with(
+ None, None, params={"Dn": "sys/rack-unit-1"})
+
+ def test_set_power_state_on_fail(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with mock_handle(task) as handle:
+ handle.get_imc_managedobject.side_effect = (
+ imcsdk.ImcException("boom"))
+
+ self.assertRaises(exception.CIMCException,
+ task.driver.power.set_power_state,
+ task, states.POWER_ON)
+
+ handle.set_imc_managedobject.assert_called_once_with(
+ None, class_id="ComputeRackUnit",
+ params={
+ imcsdk.ComputeRackUnit.ADMIN_POWER:
+ imcsdk.ComputeRackUnit.CONST_ADMIN_POWER_UP,
+ imcsdk.ComputeRackUnit.DN: "sys/rack-unit-1"
+ })
+
+ handle.get_imc_managedobject.assert_called_with(
+ None, None, params={"Dn": "sys/rack-unit-1"})
+
+ def test_set_power_state_off_ok(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with mock_handle(task) as handle:
+ mock_rack_unit = mock.MagicMock()
+ mock_rack_unit.get_attr.side_effect = [
+ imcsdk.ComputeRackUnit.CONST_OPER_POWER_ON,
+ imcsdk.ComputeRackUnit.CONST_OPER_POWER_OFF
+ ]
+ handle.get_imc_managedobject.return_value = [mock_rack_unit]
+
+ task.driver.power.set_power_state(task, states.POWER_OFF)
+
+ handle.set_imc_managedobject.assert_called_once_with(
+ None, class_id="ComputeRackUnit",
+ params={
+ imcsdk.ComputeRackUnit.ADMIN_POWER:
+ imcsdk.ComputeRackUnit.CONST_ADMIN_POWER_DOWN,
+ imcsdk.ComputeRackUnit.DN: "sys/rack-unit-1"
+ })
+
+ handle.get_imc_managedobject.assert_called_with(
+ None, None, params={"Dn": "sys/rack-unit-1"})
+
+ def test_set_power_state_off_fail(self, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ with mock_handle(task) as handle:
+ handle.get_imc_managedobject.side_effect = (
+ imcsdk.ImcException("boom"))
+
+ self.assertRaises(exception.CIMCException,
+ task.driver.power.set_power_state,
+ task, states.POWER_OFF)
+
+ handle.set_imc_managedobject.assert_called_once_with(
+ None, class_id="ComputeRackUnit",
+ params={
+ imcsdk.ComputeRackUnit.ADMIN_POWER:
+ imcsdk.ComputeRackUnit.CONST_ADMIN_POWER_DOWN,
+ imcsdk.ComputeRackUnit.DN: "sys/rack-unit-1"
+ })
+
+ handle.get_imc_managedobject.assert_called_with(
+ None, None, params={"Dn": "sys/rack-unit-1"})
+
+ @mock.patch.object(power.Power, "set_power_state", autospec=True)
+ @mock.patch.object(power.Power, "get_power_state", autospec=True)
+ def test_reboot_on(self, mock_get_state, mock_set_state, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ mock_get_state.return_value = states.POWER_ON
+ task.driver.power.reboot(task)
+ mock_set_state.assert_called_with(mock.ANY, task, states.REBOOT)
+
+ @mock.patch.object(power.Power, "set_power_state", autospec=True)
+ @mock.patch.object(power.Power, "get_power_state", autospec=True)
+ def test_reboot_off(self, mock_get_state, mock_set_state, mock_handle):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ mock_get_state.return_value = states.POWER_OFF
+ task.driver.power.reboot(task)
+ mock_set_state.assert_called_with(mock.ANY, task, states.POWER_ON)
diff --git a/ironic/tests/unit/drivers/modules/drac/__init__.py b/ironic/tests/unit/drivers/modules/drac/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/drac/__init__.py
diff --git a/ironic/tests/unit/drivers/modules/drac/bios_wsman_mock.py b/ironic/tests/unit/drivers/modules/drac/bios_wsman_mock.py
new file mode 100644
index 000000000..245d27c01
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/drac/bios_wsman_mock.py
@@ -0,0 +1,273 @@
+#
+# Copyright 2015 Dell, 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
+# 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.
+
+"""
+Test class for DRAC BIOS interface
+"""
+
+from ironic.drivers.modules.drac import resource_uris
+
+Enumerations = {
+ resource_uris.DCIM_BIOSEnumeration: {
+ 'XML': """<ns0:Envelope
+xmlns:ns0="http://www.w3.org/2003/05/soap-envelope"
+xmlns:ns1="http://schemas.xmlsoap.org/ws/2004/08/addressing"
+xmlns:ns2="http://schemas.xmlsoap.org/ws/2004/09/enumeration"
+xmlns:ns3="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"
+xmlns:ns4="http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_BIOSEnumeration"
+xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <ns0:Header>
+ <ns1:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
+</ns1:To>
+ <ns1:Action>
+http://schemas.xmlsoap.org/ws/2004/09/enumeration/EnumerateResponse</ns1:Action>
+ <ns1:RelatesTo>uuid:1f5cd907-0e6f-1e6f-8002-4f266e3acab8</ns1:RelatesTo>
+ <ns1:MessageID>uuid:219ca357-0e6f-1e6f-a828-f0e4fb722ab8</ns1:MessageID>
+ </ns0:Header>
+ <ns0:Body>
+ <ns2:EnumerateResponse>
+ <ns3:Items>
+ <ns4:DCIM_BIOSEnumeration>
+ <ns4:AttributeName>MemTest</ns4:AttributeName>
+ <ns4:CurrentValue>Disabled</ns4:CurrentValue>
+ <ns4:Dependency xsi:nil="true" />
+ <ns4:DisplayOrder>310</ns4:DisplayOrder>
+ <ns4:FQDD>BIOS.Setup.1-1</ns4:FQDD>
+ <ns4:GroupDisplayName>Memory Settings</ns4:GroupDisplayName>
+ <ns4:GroupID>MemSettings</ns4:GroupID>
+ <ns4:InstanceID>BIOS.Setup.1-1:MemTest</ns4:InstanceID>
+ <ns4:IsReadOnly>false</ns4:IsReadOnly>
+ <ns4:PendingValue xsi:nil="true" />
+ <ns4:PossibleValues>Enabled</ns4:PossibleValues>
+ <ns4:PossibleValues>Disabled</ns4:PossibleValues>
+ </ns4:DCIM_BIOSEnumeration>
+ <ns4:DCIM_BIOSEnumeration>
+ <ns4:AttributeDisplayName>C States</ns4:AttributeDisplayName>
+ <ns4:AttributeName>ProcCStates</ns4:AttributeName>
+ <ns4:CurrentValue>Disabled</ns4:CurrentValue>
+ <ns4:DisplayOrder>1706</ns4:DisplayOrder>
+ <ns4:FQDD>BIOS.Setup.1-1</ns4:FQDD>
+ <ns4:GroupDisplayName>System Profile Settings</ns4:GroupDisplayName>
+ <ns4:GroupID>SysProfileSettings</ns4:GroupID>
+ <ns4:InstanceID>BIOS.Setup.1-1:ProcCStates</ns4:InstanceID>
+ <ns4:IsReadOnly>true</ns4:IsReadOnly>
+ <ns4:PendingValue xsi:nil="true" />
+ <ns4:PossibleValues>Enabled</ns4:PossibleValues>
+ <ns4:PossibleValues>Disabled</ns4:PossibleValues>
+ </ns4:DCIM_BIOSEnumeration>
+ </ns3:Items>
+ </ns2:EnumerateResponse>
+ </ns0:Body>
+ </ns0:Envelope>""",
+ 'Dict': {
+ 'MemTest': {
+ 'name': 'MemTest',
+ 'current_value': 'Disabled',
+ 'pending_value': None,
+ 'read_only': False,
+ 'possible_values': ['Disabled', 'Enabled']},
+ 'ProcCStates': {
+ 'name': 'ProcCStates',
+ 'current_value': 'Disabled',
+ 'pending_value': None,
+ 'read_only': True,
+ 'possible_values': ['Disabled', 'Enabled']}}},
+ resource_uris.DCIM_BIOSString: {
+ 'XML': """<ns0:Envelope
+xmlns:ns0="http://www.w3.org/2003/05/soap-envelope"
+xmlns:ns1="http://schemas.xmlsoap.org/ws/2004/08/addressing"
+xmlns:ns2="http://schemas.xmlsoap.org/ws/2004/09/enumeration"
+xmlns:ns3="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"
+xmlns:ns4="http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_BIOSString"
+xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <ns0:Header>
+ <ns1:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
+</ns1:To>
+ <ns1:Action>
+http://schemas.xmlsoap.org/ws/2004/09/enumeration/EnumerateResponse
+</ns1:Action>
+ <ns1:RelatesTo>uuid:1f877bcb-0e6f-1e6f-8004-4f266e3acab8</ns1:RelatesTo>
+ <ns1:MessageID>uuid:21bea321-0e6f-1e6f-a82b-f0e4fb722ab8</ns1:MessageID>
+ </ns0:Header>
+ <ns0:Body>
+ <ns2:EnumerateResponse>
+ <ns3:Items>
+ <ns4:DCIM_BIOSString>
+ <ns4:AttributeName>SystemModelName</ns4:AttributeName>
+ <ns4:CurrentValue>PowerEdge R630</ns4:CurrentValue>
+ <ns4:Dependency xsi:nil="true" />
+ <ns4:DisplayOrder>201</ns4:DisplayOrder>
+ <ns4:FQDD>BIOS.Setup.1-1</ns4:FQDD>
+ <ns4:GroupDisplayName>System Information</ns4:GroupDisplayName>
+ <ns4:GroupID>SysInformation</ns4:GroupID>
+ <ns4:InstanceID>BIOS.Setup.1-1:SystemModelName</ns4:InstanceID>
+ <ns4:IsReadOnly>true</ns4:IsReadOnly>
+ <ns4:MaxLength>40</ns4:MaxLength>
+ <ns4:MinLength>0</ns4:MinLength>
+ <ns4:PendingValue xsi:nil="true" />
+ <ns4:ValueExpression xsi:nil="true" />
+ </ns4:DCIM_BIOSString>
+ <ns4:DCIM_BIOSString>
+ <ns4:AttributeName>SystemModelName2</ns4:AttributeName>
+ <ns4:CurrentValue>PowerEdge R630</ns4:CurrentValue>
+ <ns4:Dependency xsi:nil="true" />
+ <ns4:DisplayOrder>201</ns4:DisplayOrder>
+ <ns4:FQDD>BIOS.Setup.1-1</ns4:FQDD>
+ <ns4:GroupDisplayName>System Information</ns4:GroupDisplayName>
+ <ns4:GroupID>SysInformation</ns4:GroupID>
+ <ns4:InstanceID>BIOS.Setup.1-1:SystemModelName2</ns4:InstanceID>
+ <ns4:IsReadOnly>true</ns4:IsReadOnly>
+ <ns4:MaxLength>40</ns4:MaxLength>
+ <ns4:MinLength>0</ns4:MinLength>
+ <ns4:PendingValue xsi:nil="true" />
+ </ns4:DCIM_BIOSString>
+ <ns4:DCIM_BIOSString>
+ <ns4:AttributeDisplayName>Asset Tag</ns4:AttributeDisplayName>
+ <ns4:AttributeName>AssetTag</ns4:AttributeName>
+ <ns4:CurrentValue xsi:nil="true" />
+ <ns4:Dependency xsi:nil="true" />
+ <ns4:DisplayOrder>1903</ns4:DisplayOrder>
+ <ns4:FQDD>BIOS.Setup.1-1</ns4:FQDD>
+ <ns4:GroupDisplayName>Miscellaneous Settings</ns4:GroupDisplayName>
+ <ns4:GroupID>MiscSettings</ns4:GroupID>
+ <ns4:InstanceID>BIOS.Setup.1-1:AssetTag</ns4:InstanceID>
+ <ns4:IsReadOnly>false</ns4:IsReadOnly>
+ <ns4:MaxLength>63</ns4:MaxLength>
+ <ns4:MinLength>0</ns4:MinLength>
+ <ns4:PendingValue xsi:nil="true" />
+ <ns4:ValueExpression>^[ -~]{0,63}$</ns4:ValueExpression>
+ </ns4:DCIM_BIOSString>
+ </ns3:Items>
+ <ns2:EnumerationContext />
+ <ns3:EndOfSequence />
+ </ns2:EnumerateResponse>
+ </ns0:Body>
+ </ns0:Envelope>""",
+ 'Dict': {
+ 'SystemModelName': {
+ 'name': 'SystemModelName',
+ 'current_value': 'PowerEdge R630',
+ 'pending_value': None,
+ 'read_only': True,
+ 'min_length': 0,
+ 'max_length': 40,
+ 'pcre_regex': None},
+ 'SystemModelName2': {
+ 'name': 'SystemModelName2',
+ 'current_value': 'PowerEdge R630',
+ 'pending_value': None,
+ 'read_only': True,
+ 'min_length': 0,
+ 'max_length': 40,
+ 'pcre_regex': None},
+ 'AssetTag': {
+ 'name': 'AssetTag',
+ 'current_value': None,
+ 'pending_value': None,
+ 'read_only': False,
+ 'min_length': 0,
+ 'max_length': 63,
+ 'pcre_regex': '^[ -~]{0,63}$'}}},
+ resource_uris.DCIM_BIOSInteger: {
+ 'XML': """<ns0:Envelope
+xmlns:ns0="http://www.w3.org/2003/05/soap-envelope"
+xmlns:ns1="http://schemas.xmlsoap.org/ws/2004/08/addressing"
+xmlns:ns2="http://schemas.xmlsoap.org/ws/2004/09/enumeration"
+xmlns:ns3="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"
+xmlns:ns4="http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_BIOSInteger"
+xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <ns0:Header>
+ <ns1:To>
+http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</ns1:To>
+ <ns1:Action>
+http://schemas.xmlsoap.org/ws/2004/09/enumeration/EnumerateResponse</ns1:Action>
+ <ns1:RelatesTo>uuid:1fa60792-0e6f-1e6f-8005-4f266e3acab8</ns1:RelatesTo>
+ <ns1:MessageID>uuid:21ccf01d-0e6f-1e6f-a82d-f0e4fb722ab8</ns1:MessageID>
+ </ns0:Header>
+ <ns0:Body>
+ <ns2:EnumerateResponse>
+ <ns3:Items>
+ <ns4:DCIM_BIOSInteger>
+ <ns4:AttributeName>Proc1NumCores</ns4:AttributeName>
+ <ns4:CurrentValue>8</ns4:CurrentValue>
+ <ns4:Dependency xsi:nil="true" />
+ <ns4:DisplayOrder>439</ns4:DisplayOrder>
+ <ns4:FQDD>BIOS.Setup.1-1</ns4:FQDD>
+ <ns4:GroupDisplayName>Processor Settings</ns4:GroupDisplayName>
+ <ns4:GroupID>ProcSettings</ns4:GroupID>
+ <ns4:InstanceID>BIOS.Setup.1-1:Proc1NumCores</ns4:InstanceID>
+ <ns4:IsReadOnly>true</ns4:IsReadOnly>
+ <ns4:LowerBound>0</ns4:LowerBound>
+ <ns4:PendingValue xsi:nil="true" />
+ <ns4:UpperBound>65535</ns4:UpperBound>
+ </ns4:DCIM_BIOSInteger>
+ <ns4:DCIM_BIOSInteger>
+ <ns4:AttributeName>AcPwrRcvryUserDelay</ns4:AttributeName>
+ <ns4:CurrentValue>60</ns4:CurrentValue>
+ <ns4:DisplayOrder>1825</ns4:DisplayOrder>
+ <ns4:FQDD>BIOS.Setup.1-1</ns4:FQDD>
+ <ns4:GroupDisplayName>System Security</ns4:GroupDisplayName>
+ <ns4:GroupID>SysSecurity</ns4:GroupID>
+ <ns4:InstanceID>BIOS.Setup.1-1:AcPwrRcvryUserDelay</ns4:InstanceID>
+ <ns4:IsReadOnly>false</ns4:IsReadOnly>
+ <ns4:LowerBound>60</ns4:LowerBound>
+ <ns4:PendingValue xsi:nil="true" />
+ <ns4:UpperBound>240</ns4:UpperBound>
+ </ns4:DCIM_BIOSInteger>
+ </ns3:Items>
+ <ns2:EnumerationContext />
+ <ns3:EndOfSequence />
+ </ns2:EnumerateResponse>
+ </ns0:Body>
+ </ns0:Envelope>""",
+ 'Dict': {
+ 'Proc1NumCores': {
+ 'name': 'Proc1NumCores',
+ 'current_value': 8,
+ 'pending_value': None,
+ 'read_only': True,
+ 'lower_bound': 0,
+ 'upper_bound': 65535},
+ 'AcPwrRcvryUserDelay': {
+ 'name': 'AcPwrRcvryUserDelay',
+ 'current_value': 60,
+ 'pending_value': None,
+ 'read_only': False,
+ 'lower_bound': 60,
+ 'upper_bound': 240}}}}
+
+Invoke_Commit = """<ns0:Envelope
+xmlns:ns0="http://www.w3.org/2003/05/soap-envelope"
+xmlns:ns1="http://schemas.xmlsoap.org/ws/2004/08/addressing"
+xmlns:ns2="http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_BIOSService">
+ <ns0:Header>
+ <ns1:To>
+http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</ns1:To>
+ <ns1:Action>
+http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_BIOSService/SetAttributesResponse</ns1:Action>
+ <ns1:RelatesTo>uuid:42baa476-0ee9-1ee9-8020-4f266e3acab8</ns1:RelatesTo>
+ <ns1:MessageID>uuid:fadae2f8-0eea-1eea-9626-76a8f1d9bed4</ns1:MessageID>
+ </ns0:Header>
+ <ns0:Body>
+ <ns2:SetAttributes_OUTPUT>
+ <ns2:Message>The command was successful.</ns2:Message>
+ <ns2:MessageID>BIOS001</ns2:MessageID>
+ <ns2:RebootRequired>Yes</ns2:RebootRequired>
+ <ns2:ReturnValue>0</ns2:ReturnValue>
+ <ns2:SetResult>Set PendingValue</ns2:SetResult>
+ </ns2:SetAttributes_OUTPUT>
+ </ns0:Body>
+</ns0:Envelope>"""
diff --git a/ironic/tests/unit/drivers/modules/drac/test_bios.py b/ironic/tests/unit/drivers/modules/drac/test_bios.py
new file mode 100644
index 000000000..8d62c152e
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/drac/test_bios.py
@@ -0,0 +1,199 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2015 Dell, 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
+# 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.
+
+"""
+Test class for DRAC BIOS interface
+"""
+
+import mock
+
+from ironic.common import exception
+from ironic.conductor import task_manager
+from ironic.drivers.modules.drac import bios
+from ironic.drivers.modules.drac import client as drac_client
+from ironic.drivers.modules.drac import management as drac_mgmt
+from ironic.drivers.modules.drac import resource_uris
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.drivers.modules.drac import bios_wsman_mock
+from ironic.tests.unit.drivers.modules.drac import utils as test_utils
+from ironic.tests.unit.objects import utils as obj_utils
+from six.moves.urllib.parse import unquote
+
+FAKE_DRAC = db_utils.get_test_drac_info()
+
+
+def _base_config(responses=[]):
+ for resource in [resource_uris.DCIM_BIOSEnumeration,
+ resource_uris.DCIM_BIOSString,
+ resource_uris.DCIM_BIOSInteger]:
+ xml_root = test_utils.mock_wsman_root(
+ bios_wsman_mock.Enumerations[resource]['XML'])
+ responses.append(xml_root)
+ return responses
+
+
+def _set_config(responses=[]):
+ ccj_xml = test_utils.build_soap_xml([{'DCIM_LifecycleJob':
+ {'Name': 'fake'}}],
+ resource_uris.DCIM_LifecycleJob)
+ responses.append(test_utils.mock_wsman_root(ccj_xml))
+ return _base_config(responses)
+
+
+def _mock_pywsman_responses(client, responses):
+ mpw = client.Client.return_value
+ mpw.enumerate.side_effect = responses
+ return mpw
+
+
+@mock.patch.object(drac_client, 'pywsman')
+class DracBiosTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(DracBiosTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_drac')
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_drac',
+ driver_info=FAKE_DRAC)
+
+ def test_get_config(self, client):
+ _mock_pywsman_responses(client, _base_config())
+ expected = {}
+ for resource in [resource_uris.DCIM_BIOSEnumeration,
+ resource_uris.DCIM_BIOSString,
+ resource_uris.DCIM_BIOSInteger]:
+ expected.update(bios_wsman_mock.Enumerations[resource]['Dict'])
+ result = bios.get_config(self.node)
+ self.assertEqual(expected, result)
+
+ def test_set_config_empty(self, client):
+ _mock_pywsman_responses(client, _set_config())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ res = bios.set_config(task)
+ self.assertFalse(res)
+
+ def test_set_config_nochange(self, client):
+ _mock_pywsman_responses(client, _set_config())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ res = bios.set_config(task,
+ MemTest='Disabled',
+ ProcCStates='Disabled',
+ SystemModelName='PowerEdge R630',
+ AssetTag=None,
+ Proc1NumCores=8,
+ AcPwrRcvryUserDelay=60)
+ self.assertFalse(res)
+
+ def test_set_config_ro(self, client):
+ _mock_pywsman_responses(client, _set_config())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ self.assertRaises(exception.DracOperationFailed,
+ bios.set_config, task,
+ ProcCStates="Enabled")
+
+ def test_set_config_enum_invalid(self, client):
+ _mock_pywsman_responses(client, _set_config())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ self.assertRaises(exception.DracOperationFailed,
+ bios.set_config, task,
+ MemTest="Never")
+
+ def test_set_config_string_toolong(self, client):
+ _mock_pywsman_responses(client, _set_config())
+ tag = ('Never have I seen such a silly long asset tag! '
+ 'It is really rather ridiculous, don\'t you think?')
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ self.assertRaises(exception.DracOperationFailed,
+ bios.set_config, task,
+ AssetTag=tag)
+
+ def test_set_config_string_nomatch(self, client):
+ _mock_pywsman_responses(client, _set_config())
+ tag = unquote('%80')
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ self.assertRaises(exception.DracOperationFailed,
+ bios.set_config, task,
+ AssetTag=tag)
+
+ def test_set_config_integer_toosmall(self, client):
+ _mock_pywsman_responses(client, _set_config())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ self.assertRaises(exception.DracOperationFailed,
+ bios.set_config, task,
+ AcPwrRcvryUserDelay=0)
+
+ def test_set_config_integer_toobig(self, client):
+ _mock_pywsman_responses(client, _set_config())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ self.assertRaises(exception.DracOperationFailed,
+ bios.set_config, task,
+ AcPwrRcvryUserDelay=600)
+
+ def test_set_config_needreboot(self, client):
+ mock_pywsman = _mock_pywsman_responses(client, _set_config())
+ invoke_xml = test_utils.mock_wsman_root(
+ bios_wsman_mock.Invoke_Commit)
+ # TODO(victor-lowther) This needs more work.
+ # Specifically, we will need to verify that
+ # invoke was handed the XML blob we expected.
+ mock_pywsman.invoke.return_value = invoke_xml
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ res = bios.set_config(task,
+ AssetTag="An Asset Tag",
+ MemTest="Enabled")
+ self.assertTrue(res)
+
+ @mock.patch.object(drac_mgmt, 'check_for_config_job',
+ spec_set=True, autospec=True)
+ @mock.patch.object(drac_mgmt, 'create_config_job', spec_set=True,
+ autospec=True)
+ def test_commit_config(self, mock_ccj, mock_cfcj, client):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ bios.commit_config(task)
+ self.assertTrue(mock_cfcj.called)
+ self.assertTrue(mock_ccj.called)
+
+ @mock.patch.object(drac_client.Client, 'wsman_invoke', spec_set=True,
+ autospec=True)
+ def test_abandon_config(self, mock_wi, client):
+ _mock_pywsman_responses(client, _set_config())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ bios.abandon_config(task)
+ self.assertTrue(mock_wi.called)
diff --git a/ironic/tests/unit/drivers/modules/drac/test_client.py b/ironic/tests/unit/drivers/modules/drac/test_client.py
new file mode 100644
index 000000000..c31a1b78e
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/drac/test_client.py
@@ -0,0 +1,256 @@
+#
+# 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.
+
+"""
+Test class for DRAC client wrapper.
+"""
+
+import time
+from xml.etree import ElementTree
+
+import mock
+
+from ironic.common import exception
+from ironic.drivers.modules.drac import client as drac_client
+from ironic.tests.unit import base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.drivers.modules.drac import utils as test_utils
+from ironic.tests.unit.drivers import third_party_driver_mock_specs \
+ as mock_specs
+
+INFO_DICT = db_utils.get_test_drac_info()
+
+
+@mock.patch.object(drac_client, 'pywsman', spec_set=mock_specs.PYWSMAN_SPEC)
+class DracClientTestCase(base.TestCase):
+
+ def setUp(self):
+ super(DracClientTestCase, self).setUp()
+ self.resource_uri = 'http://foo/wsman'
+
+ def test_wsman_enumerate(self, mock_client_pywsman):
+ mock_xml = test_utils.mock_wsman_root('<test></test>')
+ mock_pywsman_client = mock_client_pywsman.Client.return_value
+ mock_pywsman_client.enumerate.return_value = mock_xml
+
+ client = drac_client.Client(**INFO_DICT)
+ client.wsman_enumerate(self.resource_uri)
+
+ mock_options = mock_client_pywsman.ClientOptions.return_value
+ mock_options.set_flags.assert_called_once_with(
+ mock_client_pywsman.FLAG_ENUMERATION_OPTIMIZATION)
+ mock_options.set_max_elements.assert_called_once_with(100)
+ mock_pywsman_client.enumerate.assert_called_once_with(
+ mock_options, None, self.resource_uri)
+ mock_xml.context.assert_called_once_with()
+
+ @mock.patch.object(time, 'sleep', lambda seconds: None)
+ def test_wsman_enumerate_retry(self, mock_client_pywsman):
+ mock_xml = test_utils.mock_wsman_root('<test></test>')
+ mock_pywsman_client = mock_client_pywsman.Client.return_value
+ mock_pywsman_client.enumerate.side_effect = [None, mock_xml]
+
+ client = drac_client.Client(**INFO_DICT)
+ client.wsman_enumerate(self.resource_uri)
+
+ mock_options = mock_client_pywsman.ClientOptions.return_value
+ mock_options.set_flags.assert_called_once_with(
+ mock_client_pywsman.FLAG_ENUMERATION_OPTIMIZATION)
+ mock_options.set_max_elements.assert_called_once_with(100)
+ mock_pywsman_client.enumerate.assert_has_calls([
+ mock.call(mock_options, None, self.resource_uri),
+ mock.call(mock_options, None, self.resource_uri)
+ ])
+ mock_xml.context.assert_called_once_with()
+
+ def test_wsman_enumerate_with_additional_pull(self, mock_client_pywsman):
+ mock_root = mock.Mock(spec=['string'])
+ mock_root.string.side_effect = [
+ test_utils.build_soap_xml([{'item1': 'test1'}]),
+ test_utils.build_soap_xml([{'item2': 'test2'}])
+ ]
+ mock_xml = mock.Mock(spec=['context', 'root'])
+ mock_xml.root.return_value = mock_root
+ mock_xml.context.side_effect = [42, 42, None]
+
+ mock_pywsman_client = mock_client_pywsman.Client.return_value
+ mock_pywsman_client.enumerate.return_value = mock_xml
+ mock_pywsman_client.pull.return_value = mock_xml
+
+ client = drac_client.Client(**INFO_DICT)
+ result = client.wsman_enumerate(self.resource_uri)
+
+ # assert the XML was merged
+ result_string = ElementTree.tostring(result)
+ self.assertIn(b'<item1>test1</item1>', result_string)
+ self.assertIn(b'<item2>test2</item2>', result_string)
+
+ mock_options = mock_client_pywsman.ClientOptions.return_value
+ mock_options.set_flags.assert_called_once_with(
+ mock_client_pywsman.FLAG_ENUMERATION_OPTIMIZATION)
+ mock_options.set_max_elements.assert_called_once_with(100)
+ mock_pywsman_client.enumerate.assert_called_once_with(
+ mock_options, None, self.resource_uri)
+
+ def test_wsman_enumerate_filter_query(self, mock_client_pywsman):
+ mock_xml = test_utils.mock_wsman_root('<test></test>')
+ mock_pywsman_client = mock_client_pywsman.Client.return_value
+ mock_pywsman_client.enumerate.return_value = mock_xml
+
+ client = drac_client.Client(**INFO_DICT)
+ filter_query = 'SELECT * FROM foo'
+ client.wsman_enumerate(self.resource_uri, filter_query=filter_query)
+
+ mock_options = mock_client_pywsman.ClientOptions.return_value
+ mock_filter = mock_client_pywsman.Filter.return_value
+ mock_filter.simple.assert_called_once_with(mock.ANY, filter_query)
+ mock_pywsman_client.enumerate.assert_called_once_with(
+ mock_options, mock_filter, self.resource_uri)
+ mock_xml.context.assert_called_once_with()
+
+ def test_wsman_enumerate_invalid_filter_dialect(self, mock_client_pywsman):
+ client = drac_client.Client(**INFO_DICT)
+ self.assertRaises(exception.DracInvalidFilterDialect,
+ client.wsman_enumerate, self.resource_uri,
+ filter_query='foo',
+ filter_dialect='invalid')
+
+ def test_wsman_invoke(self, mock_client_pywsman):
+ result_xml = test_utils.build_soap_xml(
+ [{'ReturnValue': drac_client.RET_SUCCESS}], self.resource_uri)
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman_client = mock_client_pywsman.Client.return_value
+ mock_pywsman_client.invoke.return_value = mock_xml
+
+ method_name = 'method'
+ client = drac_client.Client(**INFO_DICT)
+ client.wsman_invoke(self.resource_uri, method_name)
+
+ mock_options = mock_client_pywsman.ClientOptions.return_value
+ mock_pywsman_client.invoke.assert_called_once_with(
+ mock_options, self.resource_uri, method_name, None)
+
+ @mock.patch.object(time, 'sleep', lambda seconds: None)
+ def test_wsman_invoke_retry(self, mock_client_pywsman):
+ result_xml = test_utils.build_soap_xml(
+ [{'ReturnValue': drac_client.RET_SUCCESS}], self.resource_uri)
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman_client = mock_client_pywsman.Client.return_value
+ mock_pywsman_client.invoke.side_effect = [None, mock_xml]
+
+ method_name = 'method'
+ client = drac_client.Client(**INFO_DICT)
+ client.wsman_invoke(self.resource_uri, method_name)
+
+ mock_options = mock_client_pywsman.ClientOptions.return_value
+ mock_pywsman_client.invoke.assert_has_calls([
+ mock.call(mock_options, self.resource_uri, method_name, None),
+ mock.call(mock_options, self.resource_uri, method_name, None)
+ ])
+
+ def test_wsman_invoke_with_selectors(self, mock_client_pywsman):
+ result_xml = test_utils.build_soap_xml(
+ [{'ReturnValue': drac_client.RET_SUCCESS}], self.resource_uri)
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman_client = mock_client_pywsman.Client.return_value
+ mock_pywsman_client.invoke.return_value = mock_xml
+
+ method_name = 'method'
+ selectors = {'foo': 'bar'}
+ client = drac_client.Client(**INFO_DICT)
+ client.wsman_invoke(self.resource_uri, method_name,
+ selectors=selectors)
+
+ mock_options = mock_client_pywsman.ClientOptions.return_value
+ mock_pywsman_client.invoke.assert_called_once_with(
+ mock_options, self.resource_uri, method_name, None)
+ mock_options.add_selector.assert_called_once_with('foo', 'bar')
+
+ def test_wsman_invoke_with_properties(self, mock_client_pywsman):
+ result_xml = test_utils.build_soap_xml(
+ [{'ReturnValue': drac_client.RET_SUCCESS}], self.resource_uri)
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman_client = mock_client_pywsman.Client.return_value
+ mock_pywsman_client.invoke.return_value = mock_xml
+
+ method_name = 'method'
+ properties = {'foo': 'bar'}
+ client = drac_client.Client(**INFO_DICT)
+ client.wsman_invoke(self.resource_uri, method_name,
+ properties=properties)
+
+ mock_options = mock_client_pywsman.ClientOptions.return_value
+ mock_pywsman_client.invoke.assert_called_once_with(
+ mock_options, self.resource_uri, method_name, None)
+ mock_options.add_property.assert_called_once_with('foo', 'bar')
+
+ def test_wsman_invoke_with_properties_including_a_list(
+ self, mock_client_pywsman):
+ result_xml = test_utils.build_soap_xml(
+ [{'ReturnValue': drac_client.RET_SUCCESS}], self.resource_uri)
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman_client = mock_client_pywsman.Client.return_value
+ mock_pywsman_client.invoke.return_value = mock_xml
+ mock_request_xml = mock_client_pywsman.XmlDoc.return_value
+
+ method_name = 'method'
+ properties = {'foo': ['bar', 'baz']}
+ client = drac_client.Client(**INFO_DICT)
+ client.wsman_invoke(self.resource_uri, method_name,
+ properties=properties)
+
+ mock_options = mock_client_pywsman.ClientOptions.return_value
+ mock_pywsman_client.invoke.assert_called_once_with(
+ mock_options, self.resource_uri, method_name, mock_request_xml)
+ mock_request_xml.root().add.assert_has_calls([
+ mock.call(self.resource_uri, 'foo', 'bar'),
+ mock.call(self.resource_uri, 'foo', 'baz')
+ ])
+ self.assertEqual(2, mock_request_xml.root().add.call_count)
+
+ def test_wsman_invoke_receives_error_return_value(
+ self, mock_client_pywsman):
+ result_xml = test_utils.build_soap_xml(
+ [{'ReturnValue': drac_client.RET_ERROR,
+ 'Message': 'error message'}],
+ self.resource_uri)
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman_client = mock_client_pywsman.Client.return_value
+ mock_pywsman_client.invoke.return_value = mock_xml
+
+ method_name = 'method'
+ client = drac_client.Client(**INFO_DICT)
+ self.assertRaises(exception.DracOperationFailed,
+ client.wsman_invoke, self.resource_uri, method_name)
+
+ mock_options = mock_client_pywsman.ClientOptions.return_value
+ mock_pywsman_client.invoke.assert_called_once_with(
+ mock_options, self.resource_uri, method_name, None)
+
+ def test_wsman_invoke_receives_unexpected_return_value(
+ self, mock_client_pywsman):
+ result_xml = test_utils.build_soap_xml(
+ [{'ReturnValue': '42'}], self.resource_uri)
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman_client = mock_client_pywsman.Client.return_value
+ mock_pywsman_client.invoke.return_value = mock_xml
+
+ method_name = 'method'
+ client = drac_client.Client(**INFO_DICT)
+ self.assertRaises(exception.DracUnexpectedReturnValue,
+ client.wsman_invoke, self.resource_uri, method_name,
+ {}, {}, drac_client.RET_SUCCESS)
+
+ mock_options = mock_client_pywsman.ClientOptions.return_value
+ mock_pywsman_client.invoke.assert_called_once_with(
+ mock_options, self.resource_uri, method_name, None)
diff --git a/ironic/tests/unit/drivers/modules/drac/test_common.py b/ironic/tests/unit/drivers/modules/drac/test_common.py
new file mode 100644
index 000000000..2f2b7b7d4
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/drac/test_common.py
@@ -0,0 +1,135 @@
+#
+# 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.
+
+"""
+Test class for common methods used by DRAC modules.
+"""
+
+from xml.etree import ElementTree
+
+from testtools.matchers import HasLength
+
+from ironic.common import exception
+from ironic.drivers.modules.drac import common as drac_common
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+INFO_DICT = db_utils.get_test_drac_info()
+
+
+class DracCommonMethodsTestCase(db_base.DbTestCase):
+
+ def test_parse_driver_info(self):
+ node = obj_utils.create_test_node(self.context,
+ driver='fake_drac',
+ driver_info=INFO_DICT)
+ info = drac_common.parse_driver_info(node)
+
+ self.assertIsNotNone(info.get('drac_host'))
+ self.assertIsNotNone(info.get('drac_port'))
+ self.assertIsNotNone(info.get('drac_path'))
+ self.assertIsNotNone(info.get('drac_protocol'))
+ self.assertIsNotNone(info.get('drac_username'))
+ self.assertIsNotNone(info.get('drac_password'))
+
+ def test_parse_driver_info_missing_host(self):
+ node = obj_utils.create_test_node(self.context,
+ driver='fake_drac',
+ driver_info=INFO_DICT)
+ del node.driver_info['drac_host']
+ self.assertRaises(exception.InvalidParameterValue,
+ drac_common.parse_driver_info, node)
+
+ def test_parse_driver_info_missing_port(self):
+ node = obj_utils.create_test_node(self.context,
+ driver='fake_drac',
+ driver_info=INFO_DICT)
+ del node.driver_info['drac_port']
+
+ info = drac_common.parse_driver_info(node)
+ self.assertEqual(443, info.get('drac_port'))
+
+ def test_parse_driver_info_invalid_port(self):
+ node = obj_utils.create_test_node(self.context,
+ driver='fake_drac',
+ driver_info=INFO_DICT)
+ node.driver_info['drac_port'] = 'foo'
+ self.assertRaises(exception.InvalidParameterValue,
+ drac_common.parse_driver_info, node)
+
+ def test_parse_driver_info_missing_path(self):
+ node = obj_utils.create_test_node(self.context,
+ driver='fake_drac',
+ driver_info=INFO_DICT)
+ del node.driver_info['drac_path']
+
+ info = drac_common.parse_driver_info(node)
+ self.assertEqual('/wsman', info.get('drac_path'))
+
+ def test_parse_driver_info_missing_protocol(self):
+ node = obj_utils.create_test_node(self.context,
+ driver='fake_drac',
+ driver_info=INFO_DICT)
+ del node.driver_info['drac_protocol']
+
+ info = drac_common.parse_driver_info(node)
+ self.assertEqual('https', info.get('drac_protocol'))
+
+ def test_parse_driver_info_missing_username(self):
+ node = obj_utils.create_test_node(self.context,
+ driver='fake_drac',
+ driver_info=INFO_DICT)
+ del node.driver_info['drac_username']
+ self.assertRaises(exception.InvalidParameterValue,
+ drac_common.parse_driver_info, node)
+
+ def test_parse_driver_info_missing_password(self):
+ node = obj_utils.create_test_node(self.context,
+ driver='fake_drac',
+ driver_info=INFO_DICT)
+ del node.driver_info['drac_password']
+ self.assertRaises(exception.InvalidParameterValue,
+ drac_common.parse_driver_info, node)
+
+ def test_find_xml(self):
+ namespace = 'http://fake'
+ value = 'fake_value'
+ test_doc = ElementTree.fromstring("""<Envelope xmlns:ns1="%(ns)s">
+ <Body>
+ <ns1:test_element>%(value)s</ns1:test_element>
+ </Body>
+ </Envelope>""" % {'ns': namespace, 'value': value})
+
+ result = drac_common.find_xml(test_doc, 'test_element', namespace)
+ self.assertEqual(value, result.text)
+
+ def test_find_xml_find_all(self):
+ namespace = 'http://fake'
+ value1 = 'fake_value1'
+ value2 = 'fake_value2'
+ test_doc = ElementTree.fromstring("""<Envelope xmlns:ns1="%(ns)s">
+ <Body>
+ <ns1:test_element>%(value1)s</ns1:test_element>
+ <ns1:cat>meow</ns1:cat>
+ <ns1:test_element>%(value2)s</ns1:test_element>
+ <ns1:dog>bark</ns1:dog>
+ </Body>
+ </Envelope>""" % {'ns': namespace, 'value1': value1,
+ 'value2': value2})
+
+ result = drac_common.find_xml(test_doc, 'test_element',
+ namespace, find_all=True)
+ self.assertThat(result, HasLength(2))
+ result_text = [v.text for v in result]
+ self.assertEqual(sorted([value1, value2]), sorted(result_text))
diff --git a/ironic/tests/unit/drivers/modules/drac/test_management.py b/ironic/tests/unit/drivers/modules/drac/test_management.py
new file mode 100644
index 000000000..c8e348931
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/drac/test_management.py
@@ -0,0 +1,461 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2014 Red Hat, 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
+# 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.
+
+"""
+Test class for DRAC ManagementInterface
+"""
+
+import mock
+
+from ironic.common import boot_devices
+from ironic.common import exception
+from ironic.conductor import task_manager
+from ironic.drivers.modules.drac import client as drac_client
+from ironic.drivers.modules.drac import common as drac_common
+from ironic.drivers.modules.drac import management as drac_mgmt
+from ironic.drivers.modules.drac import resource_uris
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.drivers.modules.drac import utils as test_utils
+from ironic.tests.unit.drivers import third_party_driver_mock_specs \
+ as mock_specs
+from ironic.tests.unit.objects import utils as obj_utils
+
+INFO_DICT = db_utils.get_test_drac_info()
+
+
+@mock.patch.object(drac_client, 'pywsman', spec_set=mock_specs.PYWSMAN_SPEC)
+class DracManagementInternalMethodsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(DracManagementInternalMethodsTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_drac')
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_drac',
+ driver_info=INFO_DICT)
+
+ def test__get_next_boot_list(self, mock_client_pywsman):
+ result_xml = test_utils.build_soap_xml(
+ [{'DCIM_BootConfigSetting': {'InstanceID': 'IPL',
+ 'IsNext': drac_mgmt.PERSISTENT}}],
+ resource_uris.DCIM_BootConfigSetting)
+
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.enumerate.return_value = mock_xml
+
+ expected = {'instance_id': 'IPL', 'is_next': drac_mgmt.PERSISTENT}
+ result = drac_mgmt._get_next_boot_list(self.node)
+
+ self.assertEqual(expected, result)
+ mock_pywsman.enumerate.assert_called_once_with(
+ mock.ANY, mock.ANY, resource_uris.DCIM_BootConfigSetting)
+
+ def test__get_next_boot_list_onetime(self, mock_client_pywsman):
+ result_xml = test_utils.build_soap_xml(
+ [{'DCIM_BootConfigSetting': {'InstanceID': 'IPL',
+ 'IsNext': drac_mgmt.PERSISTENT}},
+ {'DCIM_BootConfigSetting': {'InstanceID': 'OneTime',
+ 'IsNext': drac_mgmt.ONE_TIME_BOOT}}],
+ resource_uris.DCIM_BootConfigSetting)
+
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.enumerate.return_value = mock_xml
+
+ expected = {'instance_id': 'OneTime',
+ 'is_next': drac_mgmt.ONE_TIME_BOOT}
+ result = drac_mgmt._get_next_boot_list(self.node)
+
+ self.assertEqual(expected, result)
+ mock_pywsman.enumerate.assert_called_once_with(
+ mock.ANY, mock.ANY, resource_uris.DCIM_BootConfigSetting)
+
+ def test__check_for_config_job(self, mock_client_pywsman):
+ result_xml = test_utils.build_soap_xml(
+ [{'DCIM_LifecycleJob': {'Name': 'fake'}}],
+ resource_uris.DCIM_LifecycleJob)
+
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.enumerate.return_value = mock_xml
+
+ result = drac_mgmt.check_for_config_job(self.node)
+
+ self.assertIsNone(result)
+ mock_pywsman.enumerate.assert_called_once_with(
+ mock.ANY, mock.ANY, resource_uris.DCIM_LifecycleJob)
+
+ def test__check_for_config_job_already_exist(self, mock_client_pywsman):
+ result_xml = test_utils.build_soap_xml(
+ [{'DCIM_LifecycleJob': {'Name': 'BIOS.Setup.1-1',
+ 'JobStatus': 'scheduled',
+ 'InstanceID': 'fake'}}],
+ resource_uris.DCIM_LifecycleJob)
+
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.enumerate.return_value = mock_xml
+
+ self.assertRaises(exception.DracPendingConfigJobExists,
+ drac_mgmt.check_for_config_job, self.node)
+ mock_pywsman.enumerate.assert_called_once_with(
+ mock.ANY, mock.ANY, resource_uris.DCIM_LifecycleJob)
+
+ def test__check_for_config_job_not_exist(self, mock_client_pywsman):
+ job_statuses = ["Completed", "Completed with Errors", "Failed"]
+ for job_status in job_statuses:
+ result_xml = test_utils.build_soap_xml(
+ [{'DCIM_LifecycleJob': {'Name': 'BIOS.Setup.1-1',
+ 'JobStatus': job_status,
+ 'InstanceID': 'fake'}}],
+ resource_uris.DCIM_LifecycleJob)
+
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.enumerate.return_value = mock_xml
+
+ try:
+ drac_mgmt.check_for_config_job(self.node)
+ except (exception.DracClientError,
+ exception.DracPendingConfigJobExists):
+ self.fail("Failed to detect completed job due to "
+ "\"{}\" job status".format(job_status))
+
+ def test_create_config_job(self, mock_client_pywsman):
+ result_xml = test_utils.build_soap_xml(
+ [{'ReturnValue': drac_client.RET_CREATED}],
+ resource_uris.DCIM_BIOSService)
+
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.invoke.return_value = mock_xml
+
+ result = drac_mgmt.create_config_job(self.node)
+
+ self.assertIsNone(result)
+ mock_pywsman.invoke.assert_called_once_with(
+ mock.ANY, resource_uris.DCIM_BIOSService,
+ 'CreateTargetedConfigJob', None)
+
+ def test_create_config_job_error(self, mock_client_pywsman):
+ result_xml = test_utils.build_soap_xml(
+ [{'ReturnValue': drac_client.RET_ERROR,
+ 'Message': 'E_FAKE'}],
+ resource_uris.DCIM_BIOSService)
+
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.invoke.return_value = mock_xml
+
+ self.assertRaises(exception.DracOperationFailed,
+ drac_mgmt.create_config_job, self.node)
+ mock_pywsman.invoke.assert_called_once_with(
+ mock.ANY, resource_uris.DCIM_BIOSService,
+ 'CreateTargetedConfigJob', None)
+
+ def test__get_lifecycle_controller_version(self, mock_client_pywsman):
+ result_xml = test_utils.build_soap_xml(
+ [{'DCIM_SystemView': {'LifecycleControllerVersion': '42'}}],
+ resource_uris.DCIM_SystemView)
+
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.enumerate.return_value = mock_xml
+
+ result = drac_mgmt._get_lifecycle_controller_version(self.node)
+
+ self.assertEqual('42', result)
+ mock_pywsman.enumerate.assert_called_once_with(
+ mock.ANY, mock.ANY, resource_uris.DCIM_SystemView)
+
+
+@mock.patch.object(drac_client, 'pywsman', spec_set=mock_specs.PYWSMAN_SPEC)
+class DracManagementTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(DracManagementTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_drac')
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_drac',
+ driver_info=INFO_DICT)
+ self.driver = drac_mgmt.DracManagement()
+ self.task = mock.Mock(spec=['node'])
+ self.task.node = self.node
+
+ def test_get_properties(self, mock_client_pywsman):
+ expected = drac_common.COMMON_PROPERTIES
+ self.assertEqual(expected, self.driver.get_properties())
+
+ def test_get_supported_boot_devices(self, mock_client_pywsman):
+ expected = [boot_devices.PXE, boot_devices.DISK, boot_devices.CDROM]
+ self.assertEqual(sorted(expected),
+ sorted(self.driver.
+ get_supported_boot_devices(self.task)))
+
+ @mock.patch.object(drac_mgmt, '_get_next_boot_list', spec_set=True,
+ autospec=True)
+ @mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version',
+ spec_set=True, autospec=True)
+ def test_get_boot_device(self, mock_glcv, mock_gnbl, mock_client_pywsman):
+ controller_version = '2.1.5.0'
+ mock_glcv.return_value = controller_version
+ mock_gnbl.return_value = {'instance_id': 'OneTime',
+ 'is_next': drac_mgmt.ONE_TIME_BOOT}
+
+ result_xml = test_utils.build_soap_xml(
+ [{'InstanceID': 'HardDisk'}], resource_uris.DCIM_BootSourceSetting)
+
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.enumerate.return_value = mock_xml
+
+ result = self.driver.get_boot_device(self.task)
+ expected = {'boot_device': boot_devices.DISK, 'persistent': False}
+
+ self.assertEqual(expected, result)
+ mock_pywsman.enumerate.assert_called_once_with(
+ mock.ANY, mock.ANY, resource_uris.DCIM_BootSourceSetting)
+
+ @mock.patch.object(drac_mgmt, '_get_next_boot_list', spec_set=True,
+ autospec=True)
+ @mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version',
+ spec_set=True, autospec=True)
+ def test_get_boot_device_persistent(self, mock_glcv, mock_gnbl,
+ mock_client_pywsman):
+ controller_version = '2.1.5.0'
+ mock_glcv.return_value = controller_version
+ mock_gnbl.return_value = {'instance_id': 'IPL',
+ 'is_next': drac_mgmt.PERSISTENT}
+
+ result_xml = test_utils.build_soap_xml(
+ [{'InstanceID': 'NIC', 'BootSourceType': 'IPL'}],
+ resource_uris.DCIM_BootSourceSetting)
+
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.enumerate.return_value = mock_xml
+
+ result = self.driver.get_boot_device(self.task)
+ expected = {'boot_device': boot_devices.PXE, 'persistent': True}
+
+ self.assertEqual(expected, result)
+ mock_pywsman.enumerate.assert_called_once_with(
+ mock.ANY, mock.ANY, resource_uris.DCIM_BootSourceSetting)
+
+ @mock.patch.object(drac_client.Client, 'wsman_enumerate', spec_set=True,
+ autospec=True)
+ @mock.patch.object(drac_mgmt, '_get_next_boot_list', spec_set=True,
+ autospec=True)
+ @mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version',
+ spec_set=True, autospec=True)
+ def test_get_boot_device_client_error(self, mock_glcv, mock_gnbl, mock_we,
+ mock_client_pywsman):
+ controller_version = '2.1.5.0'
+ mock_glcv.return_value = controller_version
+ mock_gnbl.return_value = {'instance_id': 'OneTime',
+ 'is_next': drac_mgmt.ONE_TIME_BOOT}
+ mock_we.side_effect = iter([exception.DracClientError('E_FAKE')])
+
+ self.assertRaises(exception.DracClientError,
+ self.driver.get_boot_device, self.task)
+ mock_we.assert_called_once_with(
+ mock.ANY, resource_uris.DCIM_BootSourceSetting,
+ filter_query=mock.ANY)
+
+ @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version',
+ spec_set=True, autospec=True)
+ @mock.patch.object(drac_mgmt, 'check_for_config_job', spec_set=True,
+ autospec=True)
+ @mock.patch.object(drac_mgmt, 'create_config_job', spec_set=True,
+ autospec=True)
+ def test_set_boot_device(self, mock_ccj, mock_cfcj, mock_glcv, mock_gbd,
+ mock_client_pywsman):
+ controller_version = '2.1.5.0'
+ mock_glcv.return_value = controller_version
+ mock_gbd.return_value = {'boot_device': boot_devices.PXE,
+ 'persistent': True}
+ result_xml_enum = test_utils.build_soap_xml(
+ [{'InstanceID': 'NIC', 'BootSourceType': 'IPL'}],
+ resource_uris.DCIM_BootSourceSetting)
+ result_xml_invk = test_utils.build_soap_xml(
+ [{'ReturnValue': drac_client.RET_SUCCESS}],
+ resource_uris.DCIM_BootConfigSetting)
+
+ mock_xml_enum = test_utils.mock_wsman_root(result_xml_enum)
+ mock_xml_invk = test_utils.mock_wsman_root(result_xml_invk)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.enumerate.return_value = mock_xml_enum
+ mock_pywsman.invoke.return_value = mock_xml_invk
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ result = self.driver.set_boot_device(task, boot_devices.PXE)
+
+ self.assertIsNone(result)
+ mock_pywsman.enumerate.assert_called_once_with(
+ mock.ANY, mock.ANY, resource_uris.DCIM_BootSourceSetting)
+ mock_pywsman.invoke.assert_called_once_with(
+ mock.ANY, resource_uris.DCIM_BootConfigSetting,
+ 'ChangeBootOrderByInstanceID', None)
+ mock_glcv.assert_called_once_with(self.node)
+ mock_gbd.assert_called_once_with(self.node, controller_version)
+ mock_cfcj.assert_called_once_with(self.node)
+ mock_ccj.assert_called_once_with(self.node)
+
+ @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version',
+ spec_set=True, autospec=True)
+ @mock.patch.object(drac_mgmt, 'check_for_config_job', spec_set=True,
+ autospec=True)
+ @mock.patch.object(drac_mgmt, 'create_config_job', spec_set=True,
+ autospec=True)
+ def test_set_boot_device_fail(self, mock_ccj, mock_cfcj, mock_glcv,
+ mock_gbd, mock_client_pywsman):
+ controller_version = '2.1.5.0'
+ mock_glcv.return_value = controller_version
+ mock_gbd.return_value = {'boot_device': boot_devices.PXE,
+ 'persistent': True}
+ result_xml_enum = test_utils.build_soap_xml(
+ [{'InstanceID': 'NIC', 'BootSourceType': 'IPL'}],
+ resource_uris.DCIM_BootSourceSetting)
+ result_xml_invk = test_utils.build_soap_xml(
+ [{'ReturnValue': drac_client.RET_ERROR, 'Message': 'E_FAKE'}],
+ resource_uris.DCIM_BootConfigSetting)
+
+ mock_xml_enum = test_utils.mock_wsman_root(result_xml_enum)
+ mock_xml_invk = test_utils.mock_wsman_root(result_xml_invk)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.enumerate.return_value = mock_xml_enum
+ mock_pywsman.invoke.return_value = mock_xml_invk
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ self.assertRaises(exception.DracOperationFailed,
+ self.driver.set_boot_device, task,
+ boot_devices.PXE)
+
+ mock_pywsman.enumerate.assert_called_once_with(
+ mock.ANY, mock.ANY, resource_uris.DCIM_BootSourceSetting)
+ mock_pywsman.invoke.assert_called_once_with(
+ mock.ANY, resource_uris.DCIM_BootConfigSetting,
+ 'ChangeBootOrderByInstanceID', None)
+ mock_glcv.assert_called_once_with(self.node)
+ mock_gbd.assert_called_once_with(self.node, controller_version)
+ mock_cfcj.assert_called_once_with(self.node)
+ self.assertFalse(mock_ccj.called)
+
+ @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version',
+ spec_set=True, autospec=True)
+ @mock.patch.object(drac_client.Client, 'wsman_enumerate', spec_set=True,
+ autospec=True)
+ @mock.patch.object(drac_mgmt, 'check_for_config_job', spec_set=True,
+ autospec=True)
+ def test_set_boot_device_client_error(self, mock_cfcj, mock_we, mock_glcv,
+ mock_gbd,
+ mock_client_pywsman):
+ controller_version = '2.1.5.0'
+ mock_glcv.return_value = controller_version
+ mock_gbd.return_value = {'boot_device': boot_devices.PXE,
+ 'persistent': True}
+ mock_we.side_effect = iter([exception.DracClientError('E_FAKE')])
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ self.assertRaises(exception.DracClientError,
+ self.driver.set_boot_device, task,
+ boot_devices.PXE)
+ mock_glcv.assert_called_once_with(self.node)
+ mock_gbd.assert_called_once_with(self.node, controller_version)
+ mock_we.assert_called_once_with(
+ mock.ANY, resource_uris.DCIM_BootSourceSetting,
+ filter_query=mock.ANY)
+
+ @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version',
+ spec_set=True, autospec=True)
+ @mock.patch.object(drac_mgmt, 'check_for_config_job', spec_set=True,
+ autospec=True)
+ def test_set_boot_device_noop(self, mock_cfcj, mock_glcv, mock_gbd,
+ mock_client_pywsman):
+ controller_version = '2.1.5.0'
+ mock_glcv.return_value = controller_version
+ mock_gbd.return_value = {'boot_device': boot_devices.PXE,
+ 'persistent': False}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ result = self.driver.set_boot_device(task, boot_devices.PXE)
+ self.assertIsNone(result)
+ mock_glcv.assert_called_once_with(self.node)
+ mock_gbd.assert_called_once_with(self.node, controller_version)
+ self.assertFalse(mock_cfcj.called)
+
+ def test_get_sensors_data(self, mock_client_pywsman):
+ self.assertRaises(NotImplementedError,
+ self.driver.get_sensors_data, self.task)
+
+ @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version',
+ spec_set=True, autospec=True)
+ @mock.patch.object(drac_mgmt, 'check_for_config_job', spec_set=True,
+ autospec=True)
+ @mock.patch.object(drac_mgmt, 'create_config_job', spec_set=True,
+ autospec=True)
+ def test_set_boot_device_11g(self, mock_ccj, mock_cfcj, mock_glcv,
+ mock_gbd, mock_client_pywsman):
+ controller_version = '1.5.0.0'
+ mock_glcv.return_value = controller_version
+ mock_gbd.return_value = {'boot_device': boot_devices.PXE,
+ 'persistent': True}
+ result_xml_enum = test_utils.build_soap_xml(
+ [{'InstanceID': 'NIC'}],
+ resource_uris.DCIM_BootSourceSetting)
+ result_xml_invk = test_utils.build_soap_xml(
+ [{'ReturnValue': drac_client.RET_SUCCESS}],
+ resource_uris.DCIM_BootConfigSetting)
+
+ mock_xml_enum = test_utils.mock_wsman_root(result_xml_enum)
+ mock_xml_invk = test_utils.mock_wsman_root(result_xml_invk)
+ mock_pywsman = mock_client_pywsman.Client.return_value
+ mock_pywsman.enumerate.return_value = mock_xml_enum
+ mock_pywsman.invoke.return_value = mock_xml_invk
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node = self.node
+ result = self.driver.set_boot_device(task, boot_devices.PXE)
+
+ self.assertIsNone(result)
+ mock_pywsman.enumerate.assert_called_once_with(
+ mock.ANY, mock.ANY, resource_uris.DCIM_BootSourceSetting)
+ mock_pywsman.invoke.assert_called_once_with(
+ mock.ANY, resource_uris.DCIM_BootConfigSetting,
+ 'ChangeBootOrderByInstanceID', None)
+ mock_glcv.assert_called_once_with(self.node)
+ mock_gbd.assert_called_once_with(self.node, controller_version)
+ mock_cfcj.assert_called_once_with(self.node)
+ mock_ccj.assert_called_once_with(self.node)
diff --git a/ironic/tests/unit/drivers/modules/drac/test_power.py b/ironic/tests/unit/drivers/modules/drac/test_power.py
new file mode 100644
index 000000000..9b4587ef7
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/drac/test_power.py
@@ -0,0 +1,175 @@
+#
+# 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.
+
+"""
+Test class for DRAC Power Driver
+"""
+
+import mock
+
+from ironic.common import exception
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.drivers.modules.drac import client as drac_client
+from ironic.drivers.modules.drac import common as drac_common
+from ironic.drivers.modules.drac import power as drac_power
+from ironic.drivers.modules.drac import resource_uris
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.drivers.modules.drac import utils as test_utils
+from ironic.tests.unit.drivers import third_party_driver_mock_specs \
+ as mock_specs
+
+INFO_DICT = db_utils.get_test_drac_info()
+
+
+@mock.patch.object(drac_client, 'pywsman', spec_set=mock_specs.PYWSMAN_SPEC)
+@mock.patch.object(drac_power, 'pywsman', spec_set=mock_specs.PYWSMAN_SPEC)
+class DracPowerInternalMethodsTestCase(base.DbTestCase):
+
+ def setUp(self):
+ super(DracPowerInternalMethodsTestCase, self).setUp()
+ driver_info = INFO_DICT
+ self.node = db_utils.create_test_node(
+ driver='fake_drac',
+ driver_info=driver_info,
+ instance_uuid='instance_uuid_123')
+
+ def test__get_power_state(self, mock_power_pywsman, mock_client_pywsman):
+ result_xml = test_utils.build_soap_xml(
+ [{'EnabledState': '2'}], resource_uris.DCIM_ComputerSystem)
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman_client = mock_client_pywsman.Client.return_value
+ mock_pywsman_client.enumerate.return_value = mock_xml
+
+ self.assertEqual(states.POWER_ON,
+ drac_power._get_power_state(self.node))
+
+ mock_pywsman_client.enumerate.assert_called_once_with(
+ mock.ANY, mock.ANY, resource_uris.DCIM_ComputerSystem)
+
+ def test__set_power_state(self, mock_power_pywsman, mock_client_pywsman):
+ result_xml = test_utils.build_soap_xml(
+ [{'ReturnValue': drac_client.RET_SUCCESS}],
+ resource_uris.DCIM_ComputerSystem)
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman_client = mock_client_pywsman.Client.return_value
+ mock_pywsman_client.invoke.return_value = mock_xml
+
+ mock_pywsman_clientopts = (
+ mock_client_pywsman.ClientOptions.return_value)
+
+ drac_power._set_power_state(self.node, states.POWER_ON)
+
+ mock_pywsman_clientopts.add_selector.assert_has_calls([
+ mock.call('CreationClassName', 'DCIM_ComputerSystem'),
+ mock.call('Name', 'srv:system')
+ ], any_order=True)
+ mock_pywsman_clientopts.add_property.assert_called_once_with(
+ 'RequestedState', '2')
+
+ mock_pywsman_client.invoke.assert_called_once_with(
+ mock.ANY, resource_uris.DCIM_ComputerSystem,
+ 'RequestStateChange', None)
+
+ def test__set_power_state_fail(self, mock_power_pywsman,
+ mock_client_pywsman):
+ result_xml = test_utils.build_soap_xml(
+ [{'ReturnValue': drac_client.RET_ERROR,
+ 'Message': 'error message'}],
+ resource_uris.DCIM_ComputerSystem)
+
+ mock_xml = test_utils.mock_wsman_root(result_xml)
+ mock_pywsman_client = mock_client_pywsman.Client.return_value
+ mock_pywsman_client.invoke.return_value = mock_xml
+
+ mock_pywsman_clientopts = (
+ mock_client_pywsman.ClientOptions.return_value)
+
+ self.assertRaises(exception.DracOperationFailed,
+ drac_power._set_power_state, self.node,
+ states.POWER_ON)
+
+ mock_pywsman_clientopts.add_selector.assert_has_calls([
+ mock.call('CreationClassName', 'DCIM_ComputerSystem'),
+ mock.call('Name', 'srv:system')
+ ], any_order=True)
+ mock_pywsman_clientopts.add_property.assert_called_once_with(
+ 'RequestedState', '2')
+
+ mock_pywsman_client.invoke.assert_called_once_with(
+ mock.ANY, resource_uris.DCIM_ComputerSystem,
+ 'RequestStateChange', None)
+
+
+class DracPowerTestCase(base.DbTestCase):
+
+ def setUp(self):
+ super(DracPowerTestCase, self).setUp()
+ driver_info = INFO_DICT
+ mgr_utils.mock_the_extension_manager(driver="fake_drac")
+ self.node = db_utils.create_test_node(
+ driver='fake_drac',
+ driver_info=driver_info,
+ instance_uuid='instance_uuid_123')
+
+ def test_get_properties(self):
+ expected = drac_common.COMMON_PROPERTIES
+ driver = drac_power.DracPower()
+ self.assertEqual(expected, driver.get_properties())
+
+ @mock.patch.object(drac_power, '_get_power_state', spec_set=True,
+ autospec=True)
+ def test_get_power_state(self, mock_get_power_state):
+ mock_get_power_state.return_value = states.POWER_ON
+ driver = drac_power.DracPower()
+ task = mock.Mock()
+ task.node.return_value = self.node
+
+ self.assertEqual(states.POWER_ON, driver.get_power_state(task))
+ mock_get_power_state.assert_called_once_with(task.node)
+
+ @mock.patch.object(drac_power, '_set_power_state', spec_set=True,
+ autospec=True)
+ def test_set_power_state(self, mock_set_power_state):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.power.set_power_state(task, states.POWER_ON)
+ mock_set_power_state.assert_called_once_with(task.node,
+ states.POWER_ON)
+
+ @mock.patch.object(drac_power, '_set_power_state', spec_set=True,
+ autospec=True)
+ @mock.patch.object(drac_power, '_get_power_state', spec_set=True,
+ autospec=True)
+ def test_reboot(self, mock_get_power_state, mock_set_power_state):
+ mock_get_power_state.return_value = states.POWER_ON
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.power.reboot(task)
+ mock_set_power_state.assert_called_once_with(task.node,
+ states.REBOOT)
+
+ @mock.patch.object(drac_power, '_set_power_state', spec_set=True,
+ autospec=True)
+ @mock.patch.object(drac_power, '_get_power_state', spec_set=True,
+ autospec=True)
+ def test_reboot_in_power_off(self, mock_get_power_state,
+ mock_set_power_state):
+ mock_get_power_state.return_value = states.POWER_OFF
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.power.reboot(task)
+ mock_set_power_state.assert_called_once_with(task.node,
+ states.POWER_ON)
diff --git a/ironic/tests/unit/drivers/modules/drac/utils.py b/ironic/tests/unit/drivers/modules/drac/utils.py
new file mode 100644
index 000000000..6338f7168
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/drac/utils.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2014 Red Hat, 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
+# 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.
+
+from xml.etree import ElementTree
+
+import mock
+
+
+def build_soap_xml(items, namespace=None):
+ """Build a SOAP XML.
+
+ :param items: a list of dictionaries where key is the element name
+ and the value is the element text.
+ :param namespace: the namespace for the elements, None for no
+ namespace. Defaults to None
+ :returns: a XML string.
+
+ """
+
+ def _create_element(name, value=None):
+ xml_string = name
+ if namespace:
+ xml_string = "{%(namespace)s}%(item)s" % {'namespace': namespace,
+ 'item': xml_string}
+
+ element = ElementTree.Element(xml_string)
+ element.text = value
+ return element
+
+ soap_namespace = "http://www.w3.org/2003/05/soap-envelope"
+ envelope_element = ElementTree.Element("{%s}Envelope" % soap_namespace)
+ body_element = ElementTree.Element("{%s}Body" % soap_namespace)
+
+ for item in items:
+ for i in item:
+ insertion_point = _create_element(i)
+ if isinstance(item[i], dict):
+ for j, value in item[i].items():
+ insertion_point.append(_create_element(j, value))
+ else:
+ insertion_point.text = item[i]
+
+ body_element.append(insertion_point)
+
+ envelope_element.append(body_element)
+ return ElementTree.tostring(envelope_element)
+
+
+def mock_wsman_root(return_value):
+ """Helper function to mock the root() from wsman client."""
+ mock_xml_root = mock.Mock(spec_set=['string'])
+ mock_xml_root.string.return_value = return_value
+
+ mock_xml = mock.Mock(spec_set=['context', 'root'])
+ mock_xml.context.return_value = None
+ mock_xml.root.return_value = mock_xml_root
+
+ return mock_xml
diff --git a/ironic/tests/unit/drivers/modules/ilo/__init__.py b/ironic/tests/unit/drivers/modules/ilo/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ilo/__init__.py
diff --git a/ironic/tests/unit/drivers/modules/ilo/test_common.py b/ironic/tests/unit/drivers/modules/ilo/test_common.py
new file mode 100644
index 000000000..10b1ad305
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ilo/test_common.py
@@ -0,0 +1,675 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+# 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
+# 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.
+
+"""Test class for common methods used by iLO modules."""
+
+import os
+import shutil
+import tempfile
+
+import mock
+from oslo_config import cfg
+from oslo_utils import importutils
+import six
+
+from ironic.common import exception
+from ironic.common import images
+from ironic.common import swift
+from ironic.common import utils
+from ironic.conductor import task_manager
+from ironic.drivers.modules.ilo import common as ilo_common
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+ilo_client = importutils.try_import('proliantutils.ilo.client')
+ilo_error = importutils.try_import('proliantutils.exception')
+
+if six.PY3:
+ import io
+ file = io.BytesIO
+
+
+CONF = cfg.CONF
+
+
+class IloValidateParametersTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IloValidateParametersTestCase, self).setUp()
+ self.node = obj_utils.create_test_node(
+ self.context, driver='fake_ilo',
+ driver_info=db_utils.get_test_ilo_info())
+
+ def test_parse_driver_info(self):
+ info = ilo_common.parse_driver_info(self.node)
+
+ self.assertIsNotNone(info.get('ilo_address'))
+ self.assertIsNotNone(info.get('ilo_username'))
+ self.assertIsNotNone(info.get('ilo_password'))
+ self.assertIsNotNone(info.get('client_timeout'))
+ self.assertIsNotNone(info.get('client_port'))
+
+ def test_parse_driver_info_missing_address(self):
+ del self.node.driver_info['ilo_address']
+ self.assertRaises(exception.MissingParameterValue,
+ ilo_common.parse_driver_info, self.node)
+
+ def test_parse_driver_info_missing_username(self):
+ del self.node.driver_info['ilo_username']
+ self.assertRaises(exception.MissingParameterValue,
+ ilo_common.parse_driver_info, self.node)
+
+ def test_parse_driver_info_missing_password(self):
+ del self.node.driver_info['ilo_password']
+ self.assertRaises(exception.MissingParameterValue,
+ ilo_common.parse_driver_info, self.node)
+
+ def test_parse_driver_info_invalid_timeout(self):
+ self.node.driver_info['client_timeout'] = 'qwe'
+ self.assertRaises(exception.InvalidParameterValue,
+ ilo_common.parse_driver_info, self.node)
+
+ def test_parse_driver_info_invalid_port(self):
+ self.node.driver_info['client_port'] = 'qwe'
+ self.assertRaises(exception.InvalidParameterValue,
+ ilo_common.parse_driver_info, self.node)
+
+ def test_parse_driver_info_missing_multiple_params(self):
+ del self.node.driver_info['ilo_password']
+ del self.node.driver_info['ilo_address']
+ try:
+ ilo_common.parse_driver_info(self.node)
+ self.fail("parse_driver_info did not throw exception.")
+ except exception.MissingParameterValue as e:
+ self.assertIn('ilo_password', str(e))
+ self.assertIn('ilo_address', str(e))
+
+ def test_parse_driver_info_invalid_multiple_params(self):
+ self.node.driver_info['client_timeout'] = 'qwe'
+ self.node.driver_info['console_port'] = 'not-int'
+ try:
+ ilo_common.parse_driver_info(self.node)
+ self.fail("parse_driver_info did not throw exception.")
+ except exception.InvalidParameterValue as e:
+ self.assertIn('client_timeout', str(e))
+ self.assertIn('console_port', str(e))
+
+
+class IloCommonMethodsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IloCommonMethodsTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="fake_ilo")
+ self.info = db_utils.get_test_ilo_info()
+ self.node = obj_utils.create_test_node(
+ self.context, driver='fake_ilo', driver_info=self.info)
+
+ @mock.patch.object(ilo_client, 'IloClient', spec_set=True,
+ autospec=True)
+ def test_get_ilo_object(self, ilo_client_mock):
+ self.info['client_timeout'] = 60
+ self.info['client_port'] = 443
+ ilo_client_mock.return_value = 'ilo_object'
+ returned_ilo_object = ilo_common.get_ilo_object(self.node)
+ ilo_client_mock.assert_called_with(
+ self.info['ilo_address'],
+ self.info['ilo_username'],
+ self.info['ilo_password'],
+ self.info['client_timeout'],
+ self.info['client_port'])
+ self.assertEqual('ilo_object', returned_ilo_object)
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_get_ilo_license(self, get_ilo_object_mock):
+ ilo_advanced_license = {'LICENSE_TYPE': 'iLO 3 Advanced'}
+ ilo_standard_license = {'LICENSE_TYPE': 'iLO 3'}
+
+ ilo_mock_object = get_ilo_object_mock.return_value
+ ilo_mock_object.get_all_licenses.return_value = ilo_advanced_license
+
+ license = ilo_common.get_ilo_license(self.node)
+ self.assertEqual(ilo_common.ADVANCED_LICENSE, license)
+
+ ilo_mock_object.get_all_licenses.return_value = ilo_standard_license
+ license = ilo_common.get_ilo_license(self.node)
+ self.assertEqual(ilo_common.STANDARD_LICENSE, license)
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_get_ilo_license_fail(self, get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+ exc = ilo_error.IloError('error')
+ ilo_mock_object.get_all_licenses.side_effect = exc
+ self.assertRaises(exception.IloOperationError,
+ ilo_common.get_ilo_license,
+ self.node)
+
+ def test_update_ipmi_properties(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ipmi_info = {
+ "ipmi_address": "1.2.3.4",
+ "ipmi_username": "admin",
+ "ipmi_password": "fake",
+ "ipmi_terminal_port": 60
+ }
+ self.info['console_port'] = 60
+ task.node.driver_info = self.info
+ ilo_common.update_ipmi_properties(task)
+ actual_info = task.node.driver_info
+ expected_info = dict(self.info, **ipmi_info)
+ self.assertEqual(expected_info, actual_info)
+
+ def test__get_floppy_image_name(self):
+ image_name_expected = 'image-' + self.node.uuid
+ image_name_actual = ilo_common._get_floppy_image_name(self.node)
+ self.assertEqual(image_name_expected, image_name_actual)
+
+ @mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True)
+ @mock.patch.object(images, 'create_vfat_image', spec_set=True,
+ autospec=True)
+ @mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True,
+ autospec=True)
+ def test__prepare_floppy_image(self, tempfile_mock, fatimage_mock,
+ swift_api_mock):
+ mock_image_file_handle = mock.MagicMock(spec=file)
+ mock_image_file_obj = mock.MagicMock(spec=file)
+ mock_image_file_obj.name = 'image-tmp-file'
+ mock_image_file_handle.__enter__.return_value = mock_image_file_obj
+
+ tempfile_mock.return_value = mock_image_file_handle
+
+ swift_obj_mock = swift_api_mock.return_value
+ self.config(swift_ilo_container='ilo_cont', group='ilo')
+ self.config(swift_object_expiry_timeout=1, group='ilo')
+ deploy_args = {'arg1': 'val1', 'arg2': 'val2'}
+ swift_obj_mock.get_temp_url.return_value = 'temp-url'
+ timeout = CONF.ilo.swift_object_expiry_timeout
+ object_headers = {'X-Delete-After': timeout}
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ temp_url = ilo_common._prepare_floppy_image(task, deploy_args)
+ node_uuid = task.node.uuid
+
+ object_name = 'image-' + node_uuid
+ fatimage_mock.assert_called_once_with('image-tmp-file',
+ parameters=deploy_args)
+
+ swift_obj_mock.create_object.assert_called_once_with(
+ 'ilo_cont', object_name, 'image-tmp-file',
+ object_headers=object_headers)
+ swift_obj_mock.get_temp_url.assert_called_once_with(
+ 'ilo_cont', object_name, timeout)
+ self.assertEqual('temp-url', temp_url)
+
+ @mock.patch.object(ilo_common, 'copy_image_to_web_server',
+ spec_set=True, autospec=True)
+ @mock.patch.object(images, 'create_vfat_image', spec_set=True,
+ autospec=True)
+ @mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True,
+ autospec=True)
+ def test__prepare_floppy_image_use_webserver(self, tempfile_mock,
+ fatimage_mock,
+ copy_mock):
+ mock_image_file_handle = mock.MagicMock(spec=file)
+ mock_image_file_obj = mock.MagicMock(spec=file)
+ mock_image_file_obj.name = 'image-tmp-file'
+ mock_image_file_handle.__enter__.return_value = mock_image_file_obj
+
+ tempfile_mock.return_value = mock_image_file_handle
+ self.config(use_web_server_for_images=True, group='ilo')
+ deploy_args = {'arg1': 'val1', 'arg2': 'val2'}
+ CONF.deploy.http_url = "http://abc.com/httpboot"
+ CONF.deploy.http_root = "/httpboot"
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ node_uuid = task.node.uuid
+ object_name = 'image-' + node_uuid
+ http_url = CONF.deploy.http_url + '/' + object_name
+ copy_mock.return_value = "http://abc.com/httpboot/" + object_name
+ temp_url = ilo_common._prepare_floppy_image(task, deploy_args)
+
+ fatimage_mock.assert_called_once_with('image-tmp-file',
+ parameters=deploy_args)
+ copy_mock.assert_called_once_with('image-tmp-file', object_name)
+ self.assertEqual(http_url, temp_url)
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_attach_vmedia(self, get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+ insert_media_mock = ilo_mock_object.insert_virtual_media
+ set_status_mock = ilo_mock_object.set_vm_status
+
+ ilo_common.attach_vmedia(self.node, 'FLOPPY', 'url')
+ insert_media_mock.assert_called_once_with('url', device='FLOPPY')
+ set_status_mock.assert_called_once_with(
+ device='FLOPPY', boot_option='CONNECT', write_protect='YES')
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_attach_vmedia_fails(self, get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+ set_status_mock = ilo_mock_object.set_vm_status
+ exc = ilo_error.IloError('error')
+ set_status_mock.side_effect = exc
+ self.assertRaises(exception.IloOperationError,
+ ilo_common.attach_vmedia, self.node,
+ 'FLOPPY', 'url')
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_set_boot_mode(self, get_ilo_object_mock):
+ ilo_object_mock = get_ilo_object_mock.return_value
+ get_pending_boot_mode_mock = ilo_object_mock.get_pending_boot_mode
+ set_pending_boot_mode_mock = ilo_object_mock.set_pending_boot_mode
+ get_pending_boot_mode_mock.return_value = 'LEGACY'
+ ilo_common.set_boot_mode(self.node, 'uefi')
+ get_ilo_object_mock.assert_called_once_with(self.node)
+ get_pending_boot_mode_mock.assert_called_once_with()
+ set_pending_boot_mode_mock.assert_called_once_with('UEFI')
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_set_boot_mode_without_set_pending_boot_mode(self,
+ get_ilo_object_mock):
+ ilo_object_mock = get_ilo_object_mock.return_value
+ get_pending_boot_mode_mock = ilo_object_mock.get_pending_boot_mode
+ get_pending_boot_mode_mock.return_value = 'LEGACY'
+ ilo_common.set_boot_mode(self.node, 'bios')
+ get_ilo_object_mock.assert_called_once_with(self.node)
+ get_pending_boot_mode_mock.assert_called_once_with()
+ self.assertFalse(ilo_object_mock.set_pending_boot_mode.called)
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_set_boot_mode_with_IloOperationError(self,
+ get_ilo_object_mock):
+ ilo_object_mock = get_ilo_object_mock.return_value
+ get_pending_boot_mode_mock = ilo_object_mock.get_pending_boot_mode
+ get_pending_boot_mode_mock.return_value = 'UEFI'
+ set_pending_boot_mode_mock = ilo_object_mock.set_pending_boot_mode
+ exc = ilo_error.IloError('error')
+ set_pending_boot_mode_mock.side_effect = exc
+ self.assertRaises(exception.IloOperationError,
+ ilo_common.set_boot_mode, self.node, 'bios')
+ get_ilo_object_mock.assert_called_once_with(self.node)
+ get_pending_boot_mode_mock.assert_called_once_with()
+
+ @mock.patch.object(ilo_common, 'set_boot_mode', spec_set=True,
+ autospec=True)
+ def test_update_boot_mode_instance_info_exists(self,
+ set_boot_mode_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.instance_info['deploy_boot_mode'] = 'bios'
+ ilo_common.update_boot_mode(task)
+ set_boot_mode_mock.assert_called_once_with(task.node, 'bios')
+
+ @mock.patch.object(ilo_common, 'set_boot_mode', spec_set=True,
+ autospec=True)
+ def test_update_boot_mode_capabilities_exist(self,
+ set_boot_mode_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.properties['capabilities'] = 'boot_mode:bios'
+ ilo_common.update_boot_mode(task)
+ set_boot_mode_mock.assert_called_once_with(task.node, 'bios')
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_update_boot_mode(self, get_ilo_object_mock):
+ ilo_mock_obj = get_ilo_object_mock.return_value
+ ilo_mock_obj.get_pending_boot_mode.return_value = 'LEGACY'
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ilo_common.update_boot_mode(task)
+ get_ilo_object_mock.assert_called_once_with(task.node)
+ ilo_mock_obj.get_pending_boot_mode.assert_called_once_with()
+ self.assertEqual('bios',
+ task.node.instance_info['deploy_boot_mode'])
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_update_boot_mode_unknown(self,
+ get_ilo_object_mock):
+ ilo_mock_obj = get_ilo_object_mock.return_value
+ ilo_mock_obj.get_pending_boot_mode.return_value = 'UNKNOWN'
+ set_pending_boot_mode_mock = ilo_mock_obj.set_pending_boot_mode
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ilo_common.update_boot_mode(task)
+ get_ilo_object_mock.assert_called_once_with(task.node)
+ ilo_mock_obj.get_pending_boot_mode.assert_called_once_with()
+ set_pending_boot_mode_mock.assert_called_once_with('UEFI')
+ self.assertEqual('uefi',
+ task.node.instance_info['deploy_boot_mode'])
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_update_boot_mode_unknown_except(self,
+ get_ilo_object_mock):
+ ilo_mock_obj = get_ilo_object_mock.return_value
+ ilo_mock_obj.get_pending_boot_mode.return_value = 'UNKNOWN'
+ set_pending_boot_mode_mock = ilo_mock_obj.set_pending_boot_mode
+ exc = ilo_error.IloError('error')
+ set_pending_boot_mode_mock.side_effect = exc
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.IloOperationError,
+ ilo_common.update_boot_mode, task)
+ get_ilo_object_mock.assert_called_once_with(task.node)
+ ilo_mock_obj.get_pending_boot_mode.assert_called_once_with()
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_update_boot_mode_legacy(self,
+ get_ilo_object_mock):
+ ilo_mock_obj = get_ilo_object_mock.return_value
+ exc = ilo_error.IloCommandNotSupportedError('error')
+ ilo_mock_obj.get_pending_boot_mode.side_effect = exc
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ilo_common.update_boot_mode(task)
+ get_ilo_object_mock.assert_called_once_with(task.node)
+ ilo_mock_obj.get_pending_boot_mode.assert_called_once_with()
+ self.assertEqual('bios',
+ task.node.instance_info['deploy_boot_mode'])
+
+ @mock.patch.object(ilo_common, 'set_boot_mode', spec_set=True,
+ autospec=True)
+ def test_update_boot_mode_prop_boot_mode_exist(self,
+ set_boot_mode_mock):
+
+ properties = {'capabilities': 'boot_mode:uefi'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.properties = properties
+ ilo_common.update_boot_mode(task)
+ set_boot_mode_mock.assert_called_once_with(task.node, 'uefi')
+
+ @mock.patch.object(images, 'get_temp_url_for_glance_image', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'attach_vmedia', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, '_prepare_floppy_image', spec_set=True,
+ autospec=True)
+ def test_setup_vmedia_for_boot_with_parameters(
+ self, prepare_image_mock, attach_vmedia_mock, temp_url_mock):
+ parameters = {'a': 'b'}
+ boot_iso = '733d1c44-a2ea-414b-aca7-69decf20d810'
+ prepare_image_mock.return_value = 'floppy_url'
+ temp_url_mock.return_value = 'image_url'
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ilo_common.setup_vmedia_for_boot(task, boot_iso, parameters)
+ prepare_image_mock.assert_called_once_with(task, parameters)
+ attach_vmedia_mock.assert_any_call(task.node, 'FLOPPY',
+ 'floppy_url')
+
+ temp_url_mock.assert_called_once_with(
+ task.context, '733d1c44-a2ea-414b-aca7-69decf20d810')
+ attach_vmedia_mock.assert_any_call(task.node, 'CDROM', 'image_url')
+
+ @mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True)
+ @mock.patch.object(ilo_common, 'attach_vmedia', spec_set=True,
+ autospec=True)
+ def test_setup_vmedia_for_boot_with_swift(self, attach_vmedia_mock,
+ swift_api_mock):
+ swift_obj_mock = swift_api_mock.return_value
+ boot_iso = 'swift:object-name'
+ swift_obj_mock.get_temp_url.return_value = 'image_url'
+ CONF.keystone_authtoken.auth_uri = 'http://authurl'
+ CONF.ilo.swift_ilo_container = 'ilo_cont'
+ CONF.ilo.swift_object_expiry_timeout = 1
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ilo_common.setup_vmedia_for_boot(task, boot_iso)
+ swift_obj_mock.get_temp_url.assert_called_once_with(
+ 'ilo_cont', 'object-name', 1)
+ attach_vmedia_mock.assert_called_once_with(
+ task.node, 'CDROM', 'image_url')
+
+ @mock.patch.object(ilo_common, 'attach_vmedia', spec_set=True,
+ autospec=True)
+ def test_setup_vmedia_for_boot_with_url(self, attach_vmedia_mock):
+ boot_iso = 'http://abc.com/img.iso'
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ilo_common.setup_vmedia_for_boot(task, boot_iso)
+ attach_vmedia_mock.assert_called_once_with(task.node, 'CDROM',
+ boot_iso)
+
+ @mock.patch.object(ilo_common, 'eject_vmedia_devices',
+ spec_set=True, autospec=True)
+ @mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True)
+ @mock.patch.object(ilo_common, '_get_floppy_image_name', spec_set=True,
+ autospec=True)
+ def test_cleanup_vmedia_boot(self, get_name_mock, swift_api_mock,
+ eject_mock):
+ swift_obj_mock = swift_api_mock.return_value
+ CONF.ilo.swift_ilo_container = 'ilo_cont'
+
+ get_name_mock.return_value = 'image-node-uuid'
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ilo_common.cleanup_vmedia_boot(task)
+ swift_obj_mock.delete_object.assert_called_once_with(
+ 'ilo_cont', 'image-node-uuid')
+ eject_mock.assert_called_once_with(task)
+
+ @mock.patch.object(ilo_common, 'eject_vmedia_devices',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_common, 'destroy_floppy_image_from_web_server',
+ spec_set=True, autospec=True)
+ def test_cleanup_vmedia_boot_for_webserver(self,
+ destroy_image_mock,
+ eject_mock):
+ CONF.ilo.use_web_server_for_images = True
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ilo_common.cleanup_vmedia_boot(task)
+ destroy_image_mock.assert_called_once_with(task.node)
+ eject_mock.assert_called_once_with(task)
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_eject_vmedia_devices(self, get_ilo_object_mock):
+ ilo_object_mock = mock.MagicMock(spec=['eject_virtual_media'])
+ get_ilo_object_mock.return_value = ilo_object_mock
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ilo_common.eject_vmedia_devices(task)
+
+ ilo_object_mock.eject_virtual_media.assert_has_calls(
+ [mock.call('FLOPPY'), mock.call('CDROM')])
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_eject_vmedia_devices_raises(
+ self, get_ilo_object_mock):
+ ilo_object_mock = mock.MagicMock(spec=['eject_virtual_media'])
+ get_ilo_object_mock.return_value = ilo_object_mock
+ exc = ilo_error.IloError('error')
+ ilo_object_mock.eject_virtual_media.side_effect = exc
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.IloOperationError,
+ ilo_common.eject_vmedia_devices,
+ task)
+
+ ilo_object_mock.eject_virtual_media.assert_called_once_with(
+ 'FLOPPY')
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_get_secure_boot_mode(self,
+ get_ilo_object_mock):
+ ilo_object_mock = get_ilo_object_mock.return_value
+ ilo_object_mock.get_current_boot_mode.return_value = 'UEFI'
+ ilo_object_mock.get_secure_boot_mode.return_value = True
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ret = ilo_common.get_secure_boot_mode(task)
+ ilo_object_mock.get_current_boot_mode.assert_called_once_with()
+ ilo_object_mock.get_secure_boot_mode.assert_called_once_with()
+ self.assertTrue(ret)
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_get_secure_boot_mode_bios(self,
+ get_ilo_object_mock):
+ ilo_object_mock = get_ilo_object_mock.return_value
+ ilo_object_mock.get_current_boot_mode.return_value = 'BIOS'
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ret = ilo_common.get_secure_boot_mode(task)
+ ilo_object_mock.get_current_boot_mode.assert_called_once_with()
+ self.assertFalse(ilo_object_mock.get_secure_boot_mode.called)
+ self.assertFalse(ret)
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_get_secure_boot_mode_fail(self,
+ get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+ exc = ilo_error.IloError('error')
+ ilo_mock_object.get_current_boot_mode.side_effect = exc
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.IloOperationError,
+ ilo_common.get_secure_boot_mode,
+ task)
+ ilo_mock_object.get_current_boot_mode.assert_called_once_with()
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_get_secure_boot_mode_not_supported(self,
+ ilo_object_mock):
+ ilo_mock_object = ilo_object_mock.return_value
+ exc = ilo_error.IloCommandNotSupportedError('error')
+ ilo_mock_object.get_current_boot_mode.return_value = 'UEFI'
+ ilo_mock_object.get_secure_boot_mode.side_effect = exc
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.IloOperationNotSupported,
+ ilo_common.get_secure_boot_mode,
+ task)
+ ilo_mock_object.get_current_boot_mode.assert_called_once_with()
+ ilo_mock_object.get_secure_boot_mode.assert_called_once_with()
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_set_secure_boot_mode(self,
+ get_ilo_object_mock):
+ ilo_object_mock = get_ilo_object_mock.return_value
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ilo_common.set_secure_boot_mode(task, True)
+ ilo_object_mock.set_secure_boot_mode.assert_called_once_with(True)
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_set_secure_boot_mode_fail(self,
+ get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+ exc = ilo_error.IloError('error')
+ ilo_mock_object.set_secure_boot_mode.side_effect = exc
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.IloOperationError,
+ ilo_common.set_secure_boot_mode,
+ task, False)
+ ilo_mock_object.set_secure_boot_mode.assert_called_once_with(False)
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_set_secure_boot_mode_not_supported(self,
+ ilo_object_mock):
+ ilo_mock_object = ilo_object_mock.return_value
+ exc = ilo_error.IloCommandNotSupportedError('error')
+ ilo_mock_object.set_secure_boot_mode.side_effect = exc
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.IloOperationNotSupported,
+ ilo_common.set_secure_boot_mode,
+ task, False)
+ ilo_mock_object.set_secure_boot_mode.assert_called_once_with(False)
+
+ @mock.patch.object(os, 'chmod', spec_set=True,
+ autospec=True)
+ @mock.patch.object(shutil, 'copyfile', spec_set=True,
+ autospec=True)
+ def test_copy_image_to_web_server(self, copy_mock,
+ chmod_mock):
+ CONF.deploy.http_url = "http://x.y.z.a/webserver/"
+ CONF.deploy.http_root = "/webserver"
+ expected_url = "http://x.y.z.a/webserver/image-UUID"
+ source = 'tmp_image_file'
+ destination = "image-UUID"
+ image_path = "/webserver/image-UUID"
+ actual_url = ilo_common.copy_image_to_web_server(source, destination)
+ self.assertEqual(expected_url, actual_url)
+ copy_mock.assert_called_once_with(source, image_path)
+ chmod_mock.assert_called_once_with(image_path, 0o644)
+
+ @mock.patch.object(os, 'chmod', spec_set=True,
+ autospec=True)
+ @mock.patch.object(shutil, 'copyfile', spec_set=True,
+ autospec=True)
+ def test_copy_image_to_web_server_fails(self, copy_mock,
+ chmod_mock):
+ CONF.deploy.http_url = "http://x.y.z.a/webserver/"
+ CONF.deploy.http_root = "/webserver"
+ source = 'tmp_image_file'
+ destination = "image-UUID"
+ image_path = "/webserver/image-UUID"
+ exc = exception.ImageUploadFailed('reason')
+ copy_mock.side_effect = exc
+ self.assertRaises(exception.ImageUploadFailed,
+ ilo_common.copy_image_to_web_server,
+ source, destination)
+ copy_mock.assert_called_once_with(source, image_path)
+ self.assertFalse(chmod_mock.called)
+
+ @mock.patch.object(utils, 'unlink_without_raise', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, '_get_floppy_image_name', spec_set=True,
+ autospec=True)
+ def test_destroy_floppy_image_from_web_server(self, get_floppy_name_mock,
+ utils_mock):
+ get_floppy_name_mock.return_value = 'image-uuid'
+ CONF.deploy.http_root = "/webserver/"
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ilo_common.destroy_floppy_image_from_web_server(task.node)
+ get_floppy_name_mock.assert_called_once_with(task.node)
+ utils_mock.assert_called_once_with('/webserver/image-uuid')
diff --git a/ironic/tests/unit/drivers/modules/ilo/test_deploy.py b/ironic/tests/unit/drivers/modules/ilo/test_deploy.py
new file mode 100644
index 000000000..451761bed
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ilo/test_deploy.py
@@ -0,0 +1,1860 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+# 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
+# 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.
+
+"""Test class for common methods used by iLO modules."""
+
+import tempfile
+
+import mock
+from oslo_config import cfg
+import six
+
+from ironic.common import boot_devices
+from ironic.common import exception
+from ironic.common.glance_service import service_utils
+from ironic.common import image_service
+from ironic.common import images
+from ironic.common import states
+from ironic.common import swift
+from ironic.common import utils
+from ironic.conductor import task_manager
+from ironic.conductor import utils as manager_utils
+from ironic.drivers.modules import agent
+from ironic.drivers.modules import agent_base_vendor
+from ironic.drivers.modules import deploy_utils
+from ironic.drivers.modules.ilo import common as ilo_common
+from ironic.drivers.modules.ilo import deploy as ilo_deploy
+from ironic.drivers.modules import iscsi_deploy
+from ironic.drivers import utils as driver_utils
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+
+if six.PY3:
+ import io
+ file = io.BytesIO
+
+INFO_DICT = db_utils.get_test_ilo_info()
+CONF = cfg.CONF
+
+
+class IloDeployPrivateMethodsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IloDeployPrivateMethodsTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="iscsi_ilo")
+ self.node = obj_utils.create_test_node(
+ self.context, driver='iscsi_ilo', driver_info=INFO_DICT)
+
+ def test__get_boot_iso_object_name(self):
+ boot_iso_actual = ilo_deploy._get_boot_iso_object_name(self.node)
+ boot_iso_expected = "boot-%s" % self.node.uuid
+ self.assertEqual(boot_iso_expected, boot_iso_actual)
+
+ @mock.patch.object(image_service.HttpImageService, 'validate_href',
+ spec_set=True, autospec=True)
+ def test__get_boot_iso_http_url(self, service_mock):
+ url = 'http://abc.org/image/qcow2'
+ i_info = self.node.instance_info
+ i_info['ilo_boot_iso'] = url
+ self.node.instance_info = i_info
+ self.node.save()
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ boot_iso_actual = ilo_deploy._get_boot_iso(task, 'root-uuid')
+ service_mock.assert_called_once_with(mock.ANY, url)
+ self.assertEqual(url, boot_iso_actual)
+
+ @mock.patch.object(image_service.HttpImageService, 'validate_href',
+ spec_set=True, autospec=True)
+ def test__get_boot_iso_url(self, mock_validate):
+ url = 'http://aaa/bbb'
+ i_info = self.node.instance_info
+ i_info['ilo_boot_iso'] = url
+ self.node.instance_info = i_info
+ self.node.save()
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ boot_iso_actual = ilo_deploy._get_boot_iso(task, 'root-uuid')
+ self.assertEqual(url, boot_iso_actual)
+ mock_validate.assert_called_once_with(mock.ANY, url)
+
+ @mock.patch.object(image_service.HttpImageService, 'validate_href',
+ spec_set=True, autospec=True)
+ def test__get_boot_iso_unsupported_url(self, validate_href_mock):
+ validate_href_mock.side_effect = iter(
+ [exception.ImageRefValidationFailed(
+ image_href='file://img.qcow2', reason='fail')])
+ url = 'file://img.qcow2'
+ i_info = self.node.instance_info
+ i_info['ilo_boot_iso'] = url
+ self.node.instance_info = i_info
+ self.node.save()
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.ImageRefValidationFailed,
+ ilo_deploy._get_boot_iso, task, 'root-uuid')
+
+ @mock.patch.object(images, 'get_image_properties', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_parse_deploy_info', spec_set=True,
+ autospec=True)
+ def test__get_boot_iso_glance_image(self, deploy_info_mock,
+ image_props_mock):
+ deploy_info_mock.return_value = {'image_source': 'image-uuid',
+ 'ilo_deploy_iso': 'deploy_iso_uuid'}
+ image_props_mock.return_value = {'boot_iso': 'boot-iso-uuid',
+ 'kernel_id': None,
+ 'ramdisk_id': None}
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ boot_iso_actual = ilo_deploy._get_boot_iso(task, 'root-uuid')
+ deploy_info_mock.assert_called_once_with(task.node)
+ image_props_mock.assert_called_once_with(
+ task.context, 'image-uuid',
+ ['boot_iso', 'kernel_id', 'ramdisk_id'])
+ boot_iso_expected = 'boot-iso-uuid'
+ self.assertEqual(boot_iso_expected, boot_iso_actual)
+
+ @mock.patch.object(deploy_utils, 'get_boot_mode_for_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(images, 'get_image_properties', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_parse_deploy_info', spec_set=True,
+ autospec=True)
+ def test__get_boot_iso_uefi_no_glance_image(self,
+ deploy_info_mock,
+ image_props_mock,
+ boot_mode_mock):
+ deploy_info_mock.return_value = {'image_source': 'image-uuid',
+ 'ilo_deploy_iso': 'deploy_iso_uuid'}
+ image_props_mock.return_value = {'boot_iso': None,
+ 'kernel_id': None,
+ 'ramdisk_id': None}
+ properties = {'capabilities': 'boot_mode:uefi'}
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.properties = properties
+ boot_iso_result = ilo_deploy._get_boot_iso(task, 'root-uuid')
+ deploy_info_mock.assert_called_once_with(task.node)
+ image_props_mock.assert_called_once_with(
+ task.context, 'image-uuid',
+ ['boot_iso', 'kernel_id', 'ramdisk_id'])
+ self.assertFalse(boot_mode_mock.called)
+ self.assertIsNone(boot_iso_result)
+
+ @mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True,
+ autospec=True)
+ @mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True)
+ @mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_get_boot_iso_object_name', spec_set=True,
+ autospec=True)
+ @mock.patch.object(driver_utils, 'get_node_capability', spec_set=True,
+ autospec=True)
+ @mock.patch.object(images, 'get_image_properties', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_parse_deploy_info', spec_set=True,
+ autospec=True)
+ def test__get_boot_iso_create(self, deploy_info_mock, image_props_mock,
+ capability_mock, boot_object_name_mock,
+ swift_api_mock,
+ create_boot_iso_mock, tempfile_mock):
+ CONF.ilo.swift_ilo_container = 'ilo-cont'
+ CONF.pxe.pxe_append_params = 'kernel-params'
+
+ swift_obj_mock = swift_api_mock.return_value
+ fileobj_mock = mock.MagicMock(spec=file)
+ fileobj_mock.name = 'tmpfile'
+ mock_file_handle = mock.MagicMock(spec=file)
+ mock_file_handle.__enter__.return_value = fileobj_mock
+ tempfile_mock.return_value = mock_file_handle
+
+ deploy_info_mock.return_value = {'image_source': 'image-uuid',
+ 'ilo_deploy_iso': 'deploy_iso_uuid'}
+ image_props_mock.return_value = {'boot_iso': None,
+ 'kernel_id': 'kernel_uuid',
+ 'ramdisk_id': 'ramdisk_uuid'}
+ boot_object_name_mock.return_value = 'abcdef'
+ create_boot_iso_mock.return_value = '/path/to/boot-iso'
+ capability_mock.return_value = 'uefi'
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ boot_iso_actual = ilo_deploy._get_boot_iso(task, 'root-uuid')
+ deploy_info_mock.assert_called_once_with(task.node)
+ image_props_mock.assert_called_once_with(
+ task.context, 'image-uuid',
+ ['boot_iso', 'kernel_id', 'ramdisk_id'])
+ boot_object_name_mock.assert_called_once_with(task.node)
+ create_boot_iso_mock.assert_called_once_with(task.context,
+ 'tmpfile',
+ 'kernel_uuid',
+ 'ramdisk_uuid',
+ 'deploy_iso_uuid',
+ 'root-uuid',
+ 'kernel-params',
+ 'uefi')
+ swift_obj_mock.create_object.assert_called_once_with('ilo-cont',
+ 'abcdef',
+ 'tmpfile')
+ boot_iso_expected = 'swift:abcdef'
+ self.assertEqual(boot_iso_expected, boot_iso_actual)
+
+ @mock.patch.object(ilo_common, 'copy_image_to_web_server', spec_set=True,
+ autospec=True)
+ @mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True,
+ autospec=True)
+ @mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_get_boot_iso_object_name', spec_set=True,
+ autospec=True)
+ @mock.patch.object(driver_utils, 'get_node_capability', spec_set=True,
+ autospec=True)
+ @mock.patch.object(images, 'get_image_properties', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_parse_deploy_info', spec_set=True,
+ autospec=True)
+ def test__get_boot_iso_create_use_webserver_true_ramdisk_webserver(
+ self, deploy_info_mock, image_props_mock,
+ capability_mock, boot_object_name_mock,
+ create_boot_iso_mock, tempfile_mock,
+ copy_file_mock):
+ CONF.ilo.swift_ilo_container = 'ilo-cont'
+ CONF.ilo.use_web_server_for_images = True
+ CONF.deploy.http_url = "http://10.10.1.30/httpboot"
+ CONF.deploy.http_root = "/httpboot"
+ CONF.pxe.pxe_append_params = 'kernel-params'
+
+ fileobj_mock = mock.MagicMock(spec=file)
+ fileobj_mock.name = 'tmpfile'
+ mock_file_handle = mock.MagicMock(spec=file)
+ mock_file_handle.__enter__.return_value = fileobj_mock
+ tempfile_mock.return_value = mock_file_handle
+
+ ramdisk_href = "http://10.10.1.30/httpboot/ramdisk"
+ kernel_href = "http://10.10.1.30/httpboot/kernel"
+ deploy_info_mock.return_value = {'image_source': 'image-uuid',
+ 'ilo_deploy_iso': 'deploy_iso_uuid'}
+ image_props_mock.return_value = {'boot_iso': None,
+ 'kernel_id': kernel_href,
+ 'ramdisk_id': ramdisk_href}
+ boot_object_name_mock.return_value = 'abcdef'
+ create_boot_iso_mock.return_value = '/path/to/boot-iso'
+ capability_mock.return_value = 'uefi'
+ copy_file_mock.return_value = "http://10.10.1.30/httpboot/abcdef"
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ boot_iso_actual = ilo_deploy._get_boot_iso(task, 'root-uuid')
+ deploy_info_mock.assert_called_once_with(task.node)
+ image_props_mock.assert_called_once_with(
+ task.context, 'image-uuid',
+ ['boot_iso', 'kernel_id', 'ramdisk_id'])
+ boot_object_name_mock.assert_called_once_with(task.node)
+ create_boot_iso_mock.assert_called_once_with(task.context,
+ 'tmpfile',
+ kernel_href,
+ ramdisk_href,
+ 'deploy_iso_uuid',
+ 'root-uuid',
+ 'kernel-params',
+ 'uefi')
+ boot_iso_expected = 'http://10.10.1.30/httpboot/abcdef'
+ self.assertEqual(boot_iso_expected, boot_iso_actual)
+ copy_file_mock.assert_called_once_with(fileobj_mock.name,
+ 'abcdef')
+
+ @mock.patch.object(ilo_deploy, '_get_boot_iso_object_name', spec_set=True,
+ autospec=True)
+ @mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True)
+ def test__clean_up_boot_iso_for_instance(self, swift_mock,
+ boot_object_name_mock):
+ swift_obj_mock = swift_mock.return_value
+ CONF.ilo.swift_ilo_container = 'ilo-cont'
+ boot_object_name_mock.return_value = 'boot-object'
+ i_info = self.node.instance_info
+ i_info['ilo_boot_iso'] = 'swift:bootiso'
+ self.node.instance_info = i_info
+ self.node.save()
+ ilo_deploy._clean_up_boot_iso_for_instance(self.node)
+ swift_obj_mock.delete_object.assert_called_once_with('ilo-cont',
+ 'boot-object')
+
+ @mock.patch.object(utils, 'unlink_without_raise', spec_set=True,
+ autospec=True)
+ def test__clean_up_boot_iso_for_instance_on_webserver(self, unlink_mock):
+
+ CONF.ilo.use_web_server_for_images = True
+ CONF.deploy.http_root = "/webserver"
+ i_info = self.node.instance_info
+ i_info['ilo_boot_iso'] = 'http://x.y.z.a/webserver/boot-object'
+ self.node.instance_info = i_info
+ self.node.save()
+ boot_iso_path = "/webserver/boot-object"
+ ilo_deploy._clean_up_boot_iso_for_instance(self.node)
+ unlink_mock.assert_called_once_with(boot_iso_path)
+
+ @mock.patch.object(ilo_deploy, '_get_boot_iso_object_name', spec_set=True,
+ autospec=True)
+ def test__clean_up_boot_iso_for_instance_no_boot_iso(
+ self, boot_object_name_mock):
+ ilo_deploy._clean_up_boot_iso_for_instance(self.node)
+ self.assertFalse(boot_object_name_mock.called)
+
+ @mock.patch.object(deploy_utils, 'check_for_missing_params', spec_set=True,
+ autospec=True)
+ def test__parse_driver_info(self, check_params_mock):
+ self.node.driver_info['ilo_deploy_iso'] = 'deploy-iso-uuid'
+ driver_info_expected = {'ilo_deploy_iso': 'deploy-iso-uuid'}
+ driver_info_actual = ilo_deploy._parse_driver_info(self.node)
+ error_msg = ("Error validating iLO virtual media deploy. Some"
+ " parameters were missing in node's driver_info")
+ check_params_mock.assert_called_once_with(driver_info_expected,
+ error_msg)
+ self.assertEqual(driver_info_expected, driver_info_actual)
+
+ @mock.patch.object(ilo_deploy, '_parse_driver_info', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'parse_instance_info', spec_set=True,
+ autospec=True)
+ def test__parse_deploy_info(self, instance_info_mock, driver_info_mock):
+ instance_info_mock.return_value = {'a': 'b'}
+ driver_info_mock.return_value = {'c': 'd'}
+ expected_info = {'a': 'b', 'c': 'd'}
+ actual_info = ilo_deploy._parse_deploy_info(self.node)
+ self.assertEqual(expected_info, actual_info)
+
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'setup_vmedia_for_boot', spec_set=True,
+ autospec=True)
+ def test__reboot_into(self, setup_vmedia_mock, set_boot_device_mock,
+ node_power_action_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ opts = {'a': 'b'}
+ ilo_deploy._reboot_into(task, 'iso', opts)
+ setup_vmedia_mock.assert_called_once_with(task, 'iso', opts)
+ set_boot_device_mock.assert_called_once_with(task,
+ boot_devices.CDROM)
+ node_power_action_mock.assert_called_once_with(task, states.REBOOT)
+
+ @mock.patch.object(ilo_common, 'eject_vmedia_devices',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_reboot_into', spec_set=True,
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'build_agent_options', spec_set=True,
+ autospec=True)
+ def test__prepare_agent_vmedia_boot(self, build_options_mock,
+ reboot_into_mock, eject_mock):
+ deploy_opts = {'a': 'b'}
+ build_options_mock.return_value = deploy_opts
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.driver_info['ilo_deploy_iso'] = 'deploy-iso-uuid'
+
+ ilo_deploy._prepare_agent_vmedia_boot(task)
+
+ eject_mock.assert_called_once_with(task)
+ build_options_mock.assert_called_once_with(task.node)
+ reboot_into_mock.assert_called_once_with(task,
+ 'deploy-iso-uuid',
+ deploy_opts)
+
+ @mock.patch.object(deploy_utils, 'is_secure_boot_requested', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'set_secure_boot_mode', spec_set=True,
+ autospec=True)
+ def test__update_secure_boot_mode_passed_true(self,
+ func_set_secure_boot_mode,
+ func_is_secure_boot_req):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ func_is_secure_boot_req.return_value = True
+ ilo_deploy._update_secure_boot_mode(task, True)
+ func_set_secure_boot_mode.assert_called_once_with(task, True)
+
+ @mock.patch.object(deploy_utils, 'is_secure_boot_requested', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'set_secure_boot_mode', spec_set=True,
+ autospec=True)
+ def test__update_secure_boot_mode_passed_False(self,
+ func_set_secure_boot_mode,
+ func_is_secure_boot_req):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ func_is_secure_boot_req.return_value = False
+ ilo_deploy._update_secure_boot_mode(task, False)
+ self.assertFalse(func_set_secure_boot_mode.called)
+
+ @mock.patch.object(ilo_common, 'set_secure_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'get_secure_boot_mode', spec_set=True,
+ autospec=True)
+ def test__disable_secure_boot_false(self,
+ func_get_secure_boot_mode,
+ func_set_secure_boot_mode):
+ func_get_secure_boot_mode.return_value = False
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ returned_state = ilo_deploy._disable_secure_boot(task)
+ func_get_secure_boot_mode.assert_called_once_with(task)
+ self.assertFalse(func_set_secure_boot_mode.called)
+ self.assertFalse(returned_state)
+
+ @mock.patch.object(ilo_common, 'set_secure_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'get_secure_boot_mode', spec_set=True,
+ autospec=True)
+ def test__disable_secure_boot_true(self,
+ func_get_secure_boot_mode,
+ func_set_secure_boot_mode):
+ func_get_secure_boot_mode.return_value = True
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ returned_state = ilo_deploy._disable_secure_boot(task)
+ func_get_secure_boot_mode.assert_called_once_with(task)
+ func_set_secure_boot_mode.assert_called_once_with(task, False)
+ self.assertTrue(returned_state)
+
+ @mock.patch.object(ilo_deploy.LOG, 'debug', spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, 'exception', spec_set=True, autospec=True)
+ @mock.patch.object(ilo_common, 'get_secure_boot_mode', spec_set=True,
+ autospec=True)
+ def test__disable_secure_boot_exception(self,
+ func_get_secure_boot_mode,
+ exception_mock,
+ mock_log):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ exception_mock.IloOperationNotSupported = Exception
+ func_get_secure_boot_mode.side_effect = Exception
+ returned_state = ilo_deploy._disable_secure_boot(task)
+ func_get_secure_boot_mode.assert_called_once_with(task)
+ self.assertTrue(mock_log.called)
+ self.assertFalse(returned_state)
+
+ @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_disable_secure_boot', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ def test__prepare_node_for_deploy(self,
+ func_node_power_action,
+ func_disable_secure_boot,
+ func_update_boot_mode):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ func_disable_secure_boot.return_value = False
+ ilo_deploy._prepare_node_for_deploy(task)
+ func_node_power_action.assert_called_once_with(task,
+ states.POWER_OFF)
+ func_disable_secure_boot.assert_called_once_with(task)
+ func_update_boot_mode.assert_called_once_with(task)
+ bootmode = driver_utils.get_node_capability(task.node, "boot_mode")
+ self.assertIsNone(bootmode)
+
+ @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_disable_secure_boot', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ def test__prepare_node_for_deploy_sec_boot_on(self,
+ func_node_power_action,
+ func_disable_secure_boot,
+ func_update_boot_mode):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ func_disable_secure_boot.return_value = True
+ ilo_deploy._prepare_node_for_deploy(task)
+ func_node_power_action.assert_called_once_with(task,
+ states.POWER_OFF)
+ func_disable_secure_boot.assert_called_once_with(task)
+ self.assertFalse(func_update_boot_mode.called)
+ ret_boot_mode = task.node.instance_info['deploy_boot_mode']
+ self.assertEqual('uefi', ret_boot_mode)
+ bootmode = driver_utils.get_node_capability(task.node, "boot_mode")
+ self.assertIsNone(bootmode)
+
+ @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_disable_secure_boot', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ def test__prepare_node_for_deploy_inst_info(self,
+ func_node_power_action,
+ func_disable_secure_boot,
+ func_update_boot_mode):
+ instance_info = {'capabilities': '{"secure_boot": "true"}'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ func_disable_secure_boot.return_value = False
+ task.node.instance_info = instance_info
+ ilo_deploy._prepare_node_for_deploy(task)
+ func_node_power_action.assert_called_once_with(task,
+ states.POWER_OFF)
+ func_disable_secure_boot.assert_called_once_with(task)
+ func_update_boot_mode.assert_called_once_with(task)
+ bootmode = driver_utils.get_node_capability(task.node, "boot_mode")
+ self.assertIsNone(bootmode)
+ deploy_boot_mode = task.node.instance_info.get('deploy_boot_mode')
+ self.assertIsNone(deploy_boot_mode)
+
+ @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_disable_secure_boot', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ def test__prepare_node_for_deploy_sec_boot_on_inst_info(
+ self, func_node_power_action, func_disable_secure_boot,
+ func_update_boot_mode):
+ instance_info = {'capabilities': '{"secure_boot": "true"}'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ func_disable_secure_boot.return_value = True
+ task.node.instance_info = instance_info
+ ilo_deploy._prepare_node_for_deploy(task)
+ func_node_power_action.assert_called_once_with(task,
+ states.POWER_OFF)
+ func_disable_secure_boot.assert_called_once_with(task)
+ self.assertFalse(func_update_boot_mode.called)
+ bootmode = driver_utils.get_node_capability(task.node, "boot_mode")
+ self.assertIsNone(bootmode)
+ deploy_boot_mode = task.node.instance_info.get('deploy_boot_mode')
+ self.assertIsNone(deploy_boot_mode)
+
+ @mock.patch.object(ilo_deploy.LOG, 'warning', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_get_boot_iso', spec_set=True,
+ autospec=True)
+ def test__recreate_and_populate_boot_iso_root_uuid_set(self,
+ get_boot_iso_mock,
+ log_mock):
+ driver_internal_info = {}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ driver_internal_info['root_uuid_or_disk_id'] = 'root-uuid'
+ task.node.driver_internal_info = driver_internal_info
+ r_uuid = task.node.driver_internal_info['root_uuid_or_disk_id']
+ get_boot_iso_mock.return_value = 'boot-uuid'
+ ilo_deploy._recreate_and_populate_ilo_boot_iso(task)
+ self.assertEqual(task.node.instance_info['ilo_boot_iso'],
+ 'boot-uuid')
+ get_boot_iso_mock.assert_called_once_with(task, r_uuid)
+ self.assertFalse(log_mock.called)
+
+ @mock.patch.object(ilo_deploy.LOG, 'warning', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_get_boot_iso', spec_set=True,
+ autospec=True)
+ def test__recreate_and_populate_boot_iso_root_not_set(self,
+ get_boot_iso_mock,
+ log_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.instance_info['ilo_boot_iso'] = 'boot-uuid-old-iso'
+ ilo_deploy._recreate_and_populate_ilo_boot_iso(task)
+ self.assertEqual(task.node.instance_info['ilo_boot_iso'],
+ 'boot-uuid-old-iso')
+ self.assertFalse(get_boot_iso_mock.called)
+ self.assertTrue(log_mock.called)
+
+ @mock.patch.object(ilo_deploy.LOG, 'warning',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_get_boot_iso',
+ spec_set=True, autospec=True)
+ def test__recreate_and_populate_get_boot_iso_fails(self,
+ get_boot_iso_mock,
+ log_mock):
+ driver_internal_info = {}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+
+ driver_internal_info['boot_iso_created_in_web_server'] = True
+ driver_internal_info['root_uuid_or_disk_id'] = 'uuid'
+ task.node.instance_info['ilo_boot_iso'] = 'boot-uuid-old-iso'
+ task.node.driver_internal_info = driver_internal_info
+ task.node.save()
+ r_uuid = task.node.driver_internal_info.get('root_uuid_or_disk_id')
+ get_boot_iso_mock.side_effect = Exception
+ ilo_deploy._recreate_and_populate_ilo_boot_iso(task)
+ self.assertEqual(task.node.instance_info['ilo_boot_iso'],
+ 'boot-uuid-old-iso')
+ get_boot_iso_mock.assert_called_once_with(task, r_uuid)
+ self.assertTrue(log_mock.called)
+
+ @mock.patch.object(ilo_deploy.LOG, 'warning',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_get_boot_iso',
+ spec_set=True, autospec=True)
+ def test__recreate_and_populate_get_boot_iso_none(self,
+ boot_iso_mock,
+ log_mock):
+ driver_internal_info = {}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ driver_internal_info['boot_iso_created_in_web_server'] = True
+ driver_internal_info['root_uuid_or_disk_id'] = 'uuid'
+ task.node.driver_internal_info = driver_internal_info
+ r_uuid = task.node.driver_internal_info.get('root_uuid_or_disk_id')
+ task.node.instance_info['ilo_boot_iso'] = 'boot-uuid-old-iso'
+ task.node.save()
+ boot_iso_mock.return_value = None
+ ilo_deploy._recreate_and_populate_ilo_boot_iso(task)
+ boot_iso_mock.assert_called_once_with(task, r_uuid)
+ self.assertEqual(task.node.instance_info['ilo_boot_iso'],
+ 'boot-uuid-old-iso')
+ self.assertTrue(log_mock.called)
+
+
+class IloVirtualMediaIscsiDeployTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IloVirtualMediaIscsiDeployTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="iscsi_ilo")
+ self.node = obj_utils.create_test_node(
+ self.context, driver='iscsi_ilo', driver_info=INFO_DICT)
+
+ @mock.patch.object(deploy_utils, 'validate_capabilities',
+ spec_set=True, autospec=True)
+ @mock.patch.object(deploy_utils, 'validate_image_properties',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_parse_deploy_info', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'validate', spec_set=True, autospec=True)
+ def _test_validate(self, validate_mock,
+ deploy_info_mock,
+ validate_prop_mock,
+ validate_capability_mock,
+ props_expected):
+ d_info = {'image_source': 'uuid'}
+ deploy_info_mock.return_value = d_info
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.deploy.validate(task)
+ validate_mock.assert_called_once_with(task)
+ deploy_info_mock.assert_called_once_with(task.node)
+ validate_prop_mock.assert_called_once_with(
+ task.context, d_info, props_expected)
+ validate_capability_mock.assert_called_once_with(task.node)
+
+ @mock.patch.object(deploy_utils, 'validate_image_properties',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_parse_deploy_info', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'validate', spec_set=True, autospec=True)
+ def test_validate_invalid_boot_option(self,
+ validate_mock,
+ deploy_info_mock,
+ validate_prop_mock):
+ d_info = {'image_source': '733d1c44-a2ea-414b-aca7-69decf20d810'}
+ properties = {'capabilities': 'boot_mode:uefi,boot_option:foo'}
+ deploy_info_mock.return_value = d_info
+ props = ['kernel_id', 'ramdisk_id']
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.properties = properties
+ exc = self.assertRaises(exception.InvalidParameterValue,
+ task.driver.deploy.validate,
+ task)
+ validate_mock.assert_called_once_with(task)
+ deploy_info_mock.assert_called_once_with(task.node)
+ validate_prop_mock.assert_called_once_with(task.context,
+ d_info, props)
+ self.assertIn('boot_option', str(exc))
+
+ @mock.patch.object(deploy_utils, 'validate_image_properties',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_parse_deploy_info', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'validate', spec_set=True, autospec=True)
+ def test_validate_invalid_boot_mode(self,
+ validate_mock,
+ deploy_info_mock,
+ validate_prop_mock):
+ d_info = {'image_source': '733d1c44-a2ea-414b-aca7-69decf20d810'}
+ properties = {'capabilities': 'boot_mode:foo,boot_option:local'}
+ deploy_info_mock.return_value = d_info
+ props = ['kernel_id', 'ramdisk_id']
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.properties = properties
+ exc = self.assertRaises(exception.InvalidParameterValue,
+ task.driver.deploy.validate,
+ task)
+ validate_mock.assert_called_once_with(task)
+ deploy_info_mock.assert_called_once_with(task.node)
+ validate_prop_mock.assert_called_once_with(task.context,
+ d_info, props)
+ self.assertIn('boot_mode', str(exc))
+
+ @mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
+ autospec=True)
+ def test_validate_glance_partition_image(self, is_glance_image_mock):
+ is_glance_image_mock.return_value = True
+ self._test_validate(props_expected=['kernel_id', 'ramdisk_id'])
+
+ def test_validate_whole_disk_image(self):
+ self.node.driver_internal_info = {'is_whole_disk_image': True}
+ self.node.save()
+ self._test_validate(props_expected=[])
+
+ @mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
+ autospec=True)
+ def test_validate_non_glance_partition_image(self, is_glance_image_mock):
+ is_glance_image_mock.return_value = False
+ self._test_validate(props_expected=['kernel', 'ramdisk'])
+
+ @mock.patch.object(ilo_common, 'eject_vmedia_devices',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_reboot_into', spec_set=True,
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'get_single_nic_with_vif_port_id',
+ spec_set=True, autospec=True)
+ @mock.patch.object(deploy_utils, 'build_agent_options', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options',
+ spec_set=True, autospec=True)
+ @mock.patch.object(iscsi_deploy, 'check_image_size', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'cache_instance_image', spec_set=True,
+ autospec=True)
+ def _test_deploy(self,
+ cache_instance_image_mock,
+ check_image_size_mock,
+ build_opts_mock,
+ agent_options_mock,
+ get_nic_mock,
+ reboot_into_mock,
+ eject_mock,
+ ilo_boot_iso,
+ image_source
+ ):
+ instance_info = self.node.instance_info
+ instance_info['ilo_boot_iso'] = ilo_boot_iso
+ instance_info['image_source'] = image_source
+ self.node.instance_info = instance_info
+ self.node.save()
+
+ deploy_opts = {'a': 'b'}
+ agent_options_mock.return_value = {
+ 'ipa-api-url': 'http://1.2.3.4:6385'}
+ build_opts_mock.return_value = deploy_opts
+ get_nic_mock.return_value = '12:34:56:78:90:ab'
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+
+ task.node.driver_info['ilo_deploy_iso'] = 'deploy-iso'
+ returned_state = task.driver.deploy.deploy(task)
+
+ eject_mock.assert_called_once_with(task)
+ cache_instance_image_mock.assert_called_once_with(task.context,
+ task.node)
+ check_image_size_mock.assert_called_once_with(task)
+ expected_ramdisk_opts = {'a': 'b', 'BOOTIF': '12:34:56:78:90:ab',
+ 'ipa-api-url': 'http://1.2.3.4:6385'}
+ build_opts_mock.assert_called_once_with(task.node)
+ get_nic_mock.assert_called_once_with(task)
+ reboot_into_mock.assert_called_once_with(task, 'deploy-iso',
+ expected_ramdisk_opts)
+
+ self.assertEqual(states.DEPLOYWAIT, returned_state)
+
+ def test_deploy_glance_image(self):
+ self._test_deploy(
+ ilo_boot_iso='swift:abcdef',
+ image_source='6b2f0c0c-79e8-4db6-842e-43c9764204af')
+ self.node.refresh()
+ self.assertNotIn('ilo_boot_iso', self.node.instance_info)
+
+ def test_deploy_not_a_glance_image(self):
+ self._test_deploy(
+ ilo_boot_iso='http://mybootiso',
+ image_source='http://myimage')
+ self.node.refresh()
+ self.assertEqual('http://mybootiso',
+ self.node.instance_info['ilo_boot_iso'])
+
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ def test_tear_down(self,
+ node_power_action_mock,
+ update_secure_boot_mode_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ driver_internal_info = task.node.driver_internal_info
+ driver_internal_info['boot_iso_created_in_web_server'] = True
+ driver_internal_info['root_uuid_or_disk_id'] = 'uuid'
+ task.node.driver_internal_info = driver_internal_info
+ task.node.save()
+ returned_state = task.driver.deploy.tear_down(task)
+ node_power_action_mock.assert_called_once_with(task,
+ states.POWER_OFF)
+ update_secure_boot_mode_mock.assert_called_once_with(task, False)
+ self.assertEqual(states.DELETED, returned_state)
+ dinfo = task.node.driver_internal_info
+ self.assertNotIn('boot_iso_created_in_web_server', dinfo)
+ self.assertNotIn('root_uuid_or_disk_id', dinfo)
+
+ @mock.patch.object(ilo_deploy.LOG, 'warn', spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, 'exception', spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ def test_tear_down_handle_exception(self,
+ node_power_action_mock,
+ update_secure_boot_mode_mock,
+ exception_mock,
+ mock_log):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ exception_mock.IloOperationNotSupported = Exception
+ update_secure_boot_mode_mock.side_effect = Exception
+ returned_state = task.driver.deploy.tear_down(task)
+ node_power_action_mock.assert_called_once_with(task,
+ states.POWER_OFF)
+ update_secure_boot_mode_mock.assert_called_once_with(task, False)
+ self.assertTrue(mock_log.called)
+ self.assertEqual(states.DELETED, returned_state)
+
+ @mock.patch.object(ilo_deploy, '_clean_up_boot_iso_for_instance',
+ spec_set=True, autospec=True)
+ @mock.patch.object(iscsi_deploy, 'destroy_images', spec_set=True,
+ autospec=True)
+ def test_clean_up(self, destroy_images_mock, clean_up_boot_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.deploy.clean_up(task)
+ destroy_images_mock.assert_called_once_with(task.node.uuid)
+ clean_up_boot_mock.assert_called_once_with(task.node)
+
+ @mock.patch.object(ilo_deploy, '_clean_up_boot_iso_for_instance',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_common, 'destroy_floppy_image_from_web_server',
+ spec_set=True, autospec=True)
+ def test_clean_up_of_webserver_images(self, destroy_images_mock,
+ clean_up_boot_mock):
+ CONF.ilo.use_web_server_for_images = True
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.deploy.clean_up(task)
+ destroy_images_mock.assert_called_once_with(task.node)
+ clean_up_boot_mock.assert_called_once_with(task.node)
+
+ @mock.patch.object(ilo_deploy, '_prepare_node_for_deploy', spec_set=True,
+ autospec=True)
+ def test_prepare(self, func_prepare_node_for_deploy):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.deploy.prepare(task)
+ func_prepare_node_for_deploy.assert_called_once_with(task)
+
+ @mock.patch.object(ilo_deploy, '_prepare_node_for_deploy', spec_set=True,
+ autospec=True)
+ def test_prepare_active_node(self, func_prepare_node_for_deploy):
+ self.node.provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.deploy.prepare(task)
+ self.assertFalse(func_prepare_node_for_deploy.called)
+
+ @mock.patch.object(ilo_deploy, '_recreate_and_populate_ilo_boot_iso',
+ spec_set=True, autospec=True)
+ def test_take_over_recreate_iso_config_and_dif_set(self, mock_recreate):
+ driver_internal_info = {}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ CONF.ilo.use_web_server_for_images = True
+ driver_internal_info['boot_iso_created_in_web_server'] = True
+ task.node.driver_internal_info = driver_internal_info
+ task.node.save()
+ task.driver.deploy.take_over(task)
+ mock_recreate.assert_called_once_with(task)
+
+ @mock.patch.object(ilo_deploy, '_recreate_and_populate_ilo_boot_iso',
+ spec_set=True, autospec=True)
+ def test_take_over_recreate_iso_config_set_and_dif_not_set(self,
+ mock_recreate):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ CONF.ilo.use_web_server_for_images = True
+ task.node.save()
+ task.driver.deploy.take_over(task)
+ self.assertFalse(mock_recreate.called)
+
+ @mock.patch.object(ilo_deploy, '_recreate_and_populate_ilo_boot_iso',
+ spec_set=True, autospec=True)
+ def test_take_over_recreate_iso_config_not_set(self, mock_recreate):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ CONF.ilo.use_web_server_for_images = False
+ task.node.save()
+ task.driver.deploy.take_over(task)
+ self.assertFalse(mock_recreate.called)
+
+
+class IloVirtualMediaAgentDeployTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IloVirtualMediaAgentDeployTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="agent_ilo")
+ self.node = obj_utils.create_test_node(
+ self.context, driver='agent_ilo', driver_info=INFO_DICT)
+
+ @mock.patch.object(deploy_utils, 'validate_capabilities',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_parse_driver_info', spec_set=True,
+ autospec=True)
+ def test_validate(self,
+ parse_driver_info_mock,
+ validate_capability_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.deploy.validate(task)
+ parse_driver_info_mock.assert_called_once_with(task.node)
+ validate_capability_mock.assert_called_once_with(task.node)
+
+ @mock.patch.object(ilo_deploy, '_prepare_agent_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_deploy(self, vmedia_boot_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ returned_state = task.driver.deploy.deploy(task)
+ vmedia_boot_mock.assert_called_once_with(task)
+ self.assertEqual(states.DEPLOYWAIT, returned_state)
+
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ def test_tear_down(self,
+ node_power_action_mock,
+ update_secure_boot_mode_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ returned_state = task.driver.deploy.tear_down(task)
+ node_power_action_mock.assert_called_once_with(task,
+ states.POWER_OFF)
+ update_secure_boot_mode_mock.assert_called_once_with(task, False)
+ self.assertEqual(states.DELETED, returned_state)
+
+ @mock.patch.object(ilo_deploy.LOG, 'warn', spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, 'exception', spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ def test_tear_down_handle_exception(self,
+ node_power_action_mock,
+ update_secure_boot_mode_mock,
+ exception_mock,
+ mock_log):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ exception_mock.IloOperationNotSupported = Exception
+ update_secure_boot_mode_mock.side_effect = Exception
+ returned_state = task.driver.deploy.tear_down(task)
+ node_power_action_mock.assert_called_once_with(task,
+ states.POWER_OFF)
+ update_secure_boot_mode_mock.assert_called_once_with(task, False)
+ self.assertTrue(mock_log.called)
+ self.assertEqual(states.DELETED, returned_state)
+
+ @mock.patch.object(ilo_deploy, '_prepare_node_for_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(agent, 'build_instance_info_for_deploy', spec_set=True,
+ autospec=True)
+ def test_prepare(self,
+ build_instance_info_mock,
+ func_prepare_node_for_deploy):
+ deploy_opts = {'a': 'b'}
+ build_instance_info_mock.return_value = deploy_opts
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.deploy.prepare(task)
+ self.assertEqual(deploy_opts, task.node.instance_info)
+ func_prepare_node_for_deploy.assert_called_once_with(task)
+
+ @mock.patch.object(ilo_deploy, '_prepare_node_for_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(agent, 'build_instance_info_for_deploy', spec_set=True,
+ autospec=True)
+ def test_prepare_active_node(self,
+ build_instance_info_mock,
+ func_prepare_node_for_deploy):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.provision_state = states.ACTIVE
+ task.driver.deploy.prepare(task)
+ self.assertFalse(build_instance_info_mock.called)
+ self.assertFalse(func_prepare_node_for_deploy.called)
+
+ @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.delete_cleaning_ports',
+ spec_set=True, autospec=True)
+ @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.create_cleaning_ports',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_prepare_agent_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_prepare_cleaning(self, vmedia_boot_mock, create_port_mock,
+ delete_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ returned_state = task.driver.deploy.prepare_cleaning(task)
+ vmedia_boot_mock.assert_called_once_with(task)
+ self.assertEqual(states.CLEANWAIT, returned_state)
+ create_port_mock.assert_called_once_with(mock.ANY, task)
+ delete_mock.assert_called_once_with(mock.ANY, task)
+ self.assertEqual(task.node.driver_internal_info.get(
+ 'agent_erase_devices_iterations'), 1)
+
+ @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.delete_cleaning_ports',
+ spec_set=True, autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ def test_tear_down_cleaning(self, power_mock, delete_mock):
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ task.driver.deploy.tear_down_cleaning(task)
+ power_mock.assert_called_once_with(task, states.POWER_OFF)
+ delete_mock.assert_called_once_with(mock.ANY, task)
+
+ @mock.patch.object(deploy_utils, 'agent_execute_clean_step', spec_set=True,
+ autospec=True)
+ def test_execute_clean_step(self, execute_mock):
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ task.driver.deploy.execute_clean_step(task, 'fake-step')
+ execute_mock.assert_called_once_with(task, 'fake-step')
+
+ @mock.patch.object(deploy_utils, 'agent_get_clean_steps', spec_set=True,
+ autospec=True)
+ def test_get_clean_steps_with_conf_option(self, get_clean_step_mock):
+ self.config(clean_priority_erase_devices=20, group='ilo')
+ get_clean_step_mock.return_value = [{
+ 'step': 'erase_devices',
+ 'priority': 10,
+ 'interface': 'deploy',
+ 'reboot_requested': False
+ }]
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ step = task.driver.deploy.get_clean_steps(task)
+ get_clean_step_mock.assert_called_once_with(task)
+ self.assertEqual(step[0].get('priority'),
+ CONF.ilo.clean_priority_erase_devices)
+
+ @mock.patch.object(deploy_utils, 'agent_get_clean_steps', spec_set=True,
+ autospec=True)
+ def test_get_clean_steps_erase_devices_disable(self, get_clean_step_mock):
+ self.config(clean_priority_erase_devices=0, group='ilo')
+ get_clean_step_mock.return_value = [{
+ 'step': 'erase_devices',
+ 'priority': 10,
+ 'interface': 'deploy',
+ 'reboot_requested': False
+ }]
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ step = task.driver.deploy.get_clean_steps(task)
+ get_clean_step_mock.assert_called_once_with(task)
+ self.assertEqual(step[0].get('priority'),
+ CONF.ilo.clean_priority_erase_devices)
+
+ @mock.patch.object(deploy_utils, 'agent_get_clean_steps', spec_set=True,
+ autospec=True)
+ def test_get_clean_steps_without_conf_option(self, get_clean_step_mock):
+ get_clean_step_mock.return_value = [{
+ 'step': 'erase_devices',
+ 'priority': 10,
+ 'interface': 'deploy',
+ 'reboot_requested': False
+ }]
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ step = task.driver.deploy.get_clean_steps(task)
+ get_clean_step_mock.assert_called_once_with(task)
+ self.assertEqual(step[0].get('priority'), 10)
+
+
+class VendorPassthruTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(VendorPassthruTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="iscsi_ilo")
+ self.node = obj_utils.create_test_node(self.context,
+ driver='iscsi_ilo',
+ driver_info=INFO_DICT)
+
+ @mock.patch.object(iscsi_deploy, 'get_deploy_info', spec_set=True,
+ autospec=True)
+ def test_validate_pass_deploy_info(self, get_deploy_info_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ vendor = ilo_deploy.VendorPassthru()
+ vendor.validate(task, method='pass_deploy_info', foo='bar')
+ get_deploy_info_mock.assert_called_once_with(task.node,
+ foo='bar')
+
+ @mock.patch.object(iscsi_deploy, 'validate_pass_bootloader_info_input',
+ spec_set=True, autospec=True)
+ def test_validate_pass_bootloader_install_info(self,
+ validate_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ kwargs = {'address': '1.2.3.4', 'key': 'fake-key',
+ 'status': 'SUCCEEDED', 'error': ''}
+ task.driver.vendor.validate(
+ task, method='pass_bootloader_install_info', **kwargs)
+ validate_mock.assert_called_once_with(task, kwargs)
+
+ @mock.patch.object(iscsi_deploy, 'get_deploy_info', spec_set=True,
+ autospec=True)
+ def test_validate_heartbeat(self, get_deploy_info_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ vendor = ilo_deploy.VendorPassthru()
+ vendor.validate(task, method='heartbeat', foo='bar')
+ self.assertFalse(get_deploy_info_mock.called)
+
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'setup_vmedia_for_boot', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_get_boot_iso', spec_set=True,
+ autospec=True)
+ def test__configure_vmedia_boot_with_boot_iso(
+ self, get_boot_iso_mock, setup_vmedia_mock, set_boot_device_mock):
+ root_uuid = {'root uuid': 'root_uuid'}
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ get_boot_iso_mock.return_value = 'boot.iso'
+
+ task.driver.vendor._configure_vmedia_boot(
+ task, root_uuid)
+
+ get_boot_iso_mock.assert_called_once_with(
+ task, root_uuid)
+ setup_vmedia_mock.assert_called_once_with(
+ task, 'boot.iso')
+ set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.CDROM, persistent=True)
+ self.assertEqual('boot.iso',
+ task.node.instance_info['ilo_boot_iso'])
+
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'setup_vmedia_for_boot', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_get_boot_iso', spec_set=True,
+ autospec=True)
+ def test__configure_vmedia_boot_without_boot_iso(
+ self, get_boot_iso_mock, setup_vmedia_mock, set_boot_device_mock):
+ root_uuid = {'root uuid': 'root_uuid'}
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ get_boot_iso_mock.return_value = None
+
+ task.driver.vendor._configure_vmedia_boot(
+ task, root_uuid)
+
+ get_boot_iso_mock.assert_called_once_with(
+ task, root_uuid)
+ self.assertFalse(setup_vmedia_mock.called)
+ self.assertFalse(set_boot_device_mock.called)
+
+ @mock.patch.object(iscsi_deploy, 'validate_bootloader_install_status',
+ spec_set=True, autospec=True)
+ @mock.patch.object(iscsi_deploy, 'finish_deploy', spec_set=True,
+ autospec=True)
+ def test_pass_bootloader_install_info(self, finish_deploy_mock,
+ validate_input_mock):
+ kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.pass_bootloader_install_info(task, **kwargs)
+ finish_deploy_mock.assert_called_once_with(task, '123456')
+ validate_input_mock.assert_called_once_with(task, kwargs)
+
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'setup_vmedia_for_boot', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_get_boot_iso', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'continue_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_pass_deploy_info_good(self, cleanup_vmedia_boot_mock,
+ continue_deploy_mock, get_boot_iso_mock,
+ setup_vmedia_mock, set_boot_device_mock,
+ func_update_boot_mode,
+ func_update_secure_boot_mode,
+ notify_ramdisk_to_proceed_mock):
+ kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
+ continue_deploy_mock.return_value = {'root uuid': 'root-uuid'}
+ get_boot_iso_mock.return_value = 'boot-iso'
+
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.pass_deploy_info(task, **kwargs)
+
+ cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ continue_deploy_mock.assert_called_once_with(task, **kwargs)
+ get_boot_iso_mock.assert_called_once_with(task, 'root-uuid')
+ setup_vmedia_mock.assert_called_once_with(task, 'boot-iso')
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+ set_boot_device_mock.assert_called_once_with(task,
+ boot_devices.CDROM,
+ persistent=True)
+ func_update_boot_mode.assert_called_once_with(task)
+ func_update_secure_boot_mode.assert_called_once_with(task, True)
+
+ self.assertEqual('boot-iso',
+ task.node.instance_info['ilo_boot_iso'])
+ info = task.node.driver_internal_info['root_uuid_or_disk_id']
+ self.assertEqual('root-uuid', info)
+ notify_ramdisk_to_proceed_mock.assert_called_once_with('123456')
+
+ @mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_pass_deploy_info_bad(self, cleanup_vmedia_boot_mock):
+ kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
+
+ self.node.provision_state = states.AVAILABLE
+ self.node.target_provision_state = states.NOSTATE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ vendor = task.driver.vendor
+ self.assertRaises(exception.InvalidState,
+ vendor.pass_deploy_info,
+ task, **kwargs)
+ self.assertEqual(states.AVAILABLE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+ self.assertFalse(cleanup_vmedia_boot_mock.called)
+
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'continue_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_get_boot_iso', spec_set=True,
+ autospec=True)
+ def test_pass_deploy_info_create_boot_iso_fail(
+ self, get_iso_mock, cleanup_vmedia_boot_mock, continue_deploy_mock,
+ node_power_mock, update_boot_mode_mock,
+ update_secure_boot_mode_mock):
+ kwargs = {'address': '123456'}
+ continue_deploy_mock.return_value = {'root uuid': 'root-uuid'}
+ get_iso_mock.side_effect = iter([exception.ImageCreationFailed(
+ image_type='iso', error="error")])
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.pass_deploy_info(task, **kwargs)
+
+ cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ update_boot_mode_mock.assert_called_once_with(task)
+ update_secure_boot_mode_mock.assert_called_once_with(task, True)
+ continue_deploy_mock.assert_called_once_with(task, **kwargs)
+ get_iso_mock.assert_called_once_with(task, 'root-uuid')
+ node_power_mock.assert_called_once_with(task, states.POWER_OFF)
+ self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+ self.assertIsNotNone(task.node.last_error)
+
+ @mock.patch.object(iscsi_deploy, 'finish_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed',
+ spec_set=True, autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'continue_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_pass_deploy_info_boot_option_local(
+ self, cleanup_vmedia_boot_mock, continue_deploy_mock,
+ func_update_boot_mode, func_update_secure_boot_mode,
+ set_boot_device_mock, notify_ramdisk_to_proceed_mock,
+ finish_deploy_mock):
+ kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
+ continue_deploy_mock.return_value = {'root uuid': '<some-uuid>'}
+
+ self.node.instance_info = {'capabilities': '{"boot_option": "local"}'}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ vendor = task.driver.vendor
+ vendor.pass_deploy_info(task, **kwargs)
+
+ cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ continue_deploy_mock.assert_called_once_with(task, **kwargs)
+ set_boot_device_mock.assert_called_once_with(task,
+ boot_devices.DISK,
+ persistent=True)
+ func_update_boot_mode.assert_called_once_with(task)
+ func_update_secure_boot_mode.assert_called_once_with(task, True)
+ notify_ramdisk_to_proceed_mock.assert_called_once_with('123456')
+ self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+ self.assertFalse(finish_deploy_mock.called)
+
+ @mock.patch.object(iscsi_deploy, 'finish_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'continue_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def _test_pass_deploy_info_whole_disk_image(
+ self, cleanup_vmedia_boot_mock, continue_deploy_mock,
+ func_update_boot_mode, func_update_secure_boot_mode,
+ set_boot_device_mock, notify_ramdisk_to_proceed_mock):
+ kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
+ continue_deploy_mock.return_value = {'root uuid': '<some-uuid>'}
+
+ self.node.driver_internal_info = {'is_whole_disk_image': True}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ vendor = task.driver.vendor
+ vendor.pass_deploy_info(task, **kwargs)
+
+ cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ continue_deploy_mock.assert_called_once_with(task, **kwargs)
+ set_boot_device_mock.assert_called_once_with(task,
+ boot_devices.DISK,
+ persistent=True)
+ func_update_boot_mode.assert_called_once_with(task)
+ func_update_secure_boot_mode.assert_called_once_with(task, True)
+ iscsi_deploy.finish_deploy.assert_called_once_with(task, '123456')
+
+ def test_pass_deploy_info_whole_disk_image_local(self):
+ self.node.instance_info = {'capabilities': '{"boot_option": "local"}'}
+ self.node.save()
+ self._test_pass_deploy_info_whole_disk_image()
+
+ def test_pass_deploy_info_whole_disk_image(self):
+ self._test_pass_deploy_info_whole_disk_image()
+
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'reboot_and_finish_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy.VendorPassthru, '_configure_vmedia_boot',
+ spec_set=True, autospec=True)
+ @mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_continue_deploy_netboot(self, cleanup_vmedia_boot_mock,
+ do_agent_iscsi_deploy_mock,
+ configure_vmedia_boot_mock,
+ reboot_and_finish_deploy_mock,
+ boot_mode_cap_mock,
+ update_secure_boot_mock):
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.DEPLOYING
+ self.node.save()
+ do_agent_iscsi_deploy_mock.return_value = {
+ 'root uuid': 'some-root-uuid'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.continue_deploy(task)
+ cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ do_agent_iscsi_deploy_mock.assert_called_once_with(task,
+ mock.ANY)
+ configure_vmedia_boot_mock.assert_called_once_with(
+ mock.ANY, task, 'some-root-uuid')
+ boot_mode_cap_mock.assert_called_once_with(task)
+ update_secure_boot_mock.assert_called_once_with(task, True)
+ reboot_and_finish_deploy_mock.assert_called_once_with(
+ mock.ANY, task)
+
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'reboot_and_finish_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'configure_local_boot', spec_set=True, autospec=True)
+ @mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_continue_deploy_localboot(self, cleanup_vmedia_boot_mock,
+ do_agent_iscsi_deploy_mock,
+ configure_local_boot_mock,
+ reboot_and_finish_deploy_mock,
+ boot_mode_cap_mock,
+ update_secure_boot_mock):
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.DEPLOYING
+ self.node.instance_info = {
+ 'capabilities': {'boot_option': 'local'}}
+ self.node.save()
+ do_agent_iscsi_deploy_mock.return_value = {
+ 'root uuid': 'some-root-uuid'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.continue_deploy(task)
+ cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ do_agent_iscsi_deploy_mock.assert_called_once_with(task,
+ mock.ANY)
+ configure_local_boot_mock.assert_called_once_with(
+ mock.ANY, task, root_uuid='some-root-uuid',
+ efi_system_part_uuid=None)
+ boot_mode_cap_mock.assert_called_once_with(task)
+ update_secure_boot_mock.assert_called_once_with(task, True)
+ reboot_and_finish_deploy_mock.assert_called_once_with(
+ mock.ANY, task)
+
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'reboot_and_finish_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'configure_local_boot', spec_set=True, autospec=True)
+ @mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_continue_deploy_whole_disk_image(
+ self, cleanup_vmedia_boot_mock, do_agent_iscsi_deploy_mock,
+ configure_local_boot_mock, reboot_and_finish_deploy_mock,
+ boot_mode_cap_mock, update_secure_boot_mock):
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.DEPLOYING
+ self.node.driver_internal_info = {'is_whole_disk_image': True}
+ self.node.save()
+ do_agent_iscsi_deploy_mock.return_value = {
+ 'disk identifier': 'some-disk-id'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.continue_deploy(task)
+ cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ do_agent_iscsi_deploy_mock.assert_called_once_with(task,
+ mock.ANY)
+ configure_local_boot_mock.assert_called_once_with(
+ mock.ANY, task, root_uuid=None, efi_system_part_uuid=None)
+ reboot_and_finish_deploy_mock.assert_called_once_with(
+ mock.ANY, task)
+
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'reboot_and_finish_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'configure_local_boot', spec_set=True, autospec=True)
+ @mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_continue_deploy_localboot_uefi(self, cleanup_vmedia_boot_mock,
+ do_agent_iscsi_deploy_mock,
+ configure_local_boot_mock,
+ reboot_and_finish_deploy_mock,
+ boot_mode_cap_mock,
+ update_secure_boot_mock):
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.DEPLOYING
+ self.node.instance_info = {
+ 'capabilities': {'boot_option': 'local'}}
+ self.node.save()
+ do_agent_iscsi_deploy_mock.return_value = {
+ 'root uuid': 'some-root-uuid',
+ 'efi system partition uuid': 'efi-system-part-uuid'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.continue_deploy(task)
+ cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ do_agent_iscsi_deploy_mock.assert_called_once_with(task,
+ mock.ANY)
+ configure_local_boot_mock.assert_called_once_with(
+ mock.ANY, task, root_uuid='some-root-uuid',
+ efi_system_part_uuid='efi-system-part-uuid')
+ boot_mode_cap_mock.assert_called_once_with(task)
+ update_secure_boot_mock.assert_called_once_with(task, True)
+ reboot_and_finish_deploy_mock.assert_called_once_with(
+ mock.ANY, task)
+
+ @mock.patch.object(ilo_deploy, '_reboot_into', spec_set=True,
+ autospec=True)
+ def test_boot_into_iso(self, reboot_into_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.boot_into_iso(task, boot_iso_href='foo')
+ reboot_into_mock.assert_called_once_with(task, 'foo',
+ ramdisk_options=None)
+
+ @mock.patch.object(ilo_deploy.VendorPassthru, '_validate_boot_into_iso',
+ spec_set=True, autospec=True)
+ def test_validate_boot_into_iso(self, validate_boot_into_iso_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ vendor = ilo_deploy.VendorPassthru()
+ vendor.validate(task, method='boot_into_iso', foo='bar')
+ validate_boot_into_iso_mock.assert_called_once_with(
+ vendor, task, {'foo': 'bar'})
+
+ def test__validate_boot_into_iso_invalid_state(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.provision_state = states.AVAILABLE
+ self.assertRaises(
+ exception.InvalidStateRequested,
+ task.driver.vendor._validate_boot_into_iso,
+ task, {})
+
+ def test__validate_boot_into_iso_missing_boot_iso_href(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.provision_state = states.MANAGEABLE
+ self.assertRaises(
+ exception.MissingParameterValue,
+ task.driver.vendor._validate_boot_into_iso,
+ task, {})
+
+ @mock.patch.object(deploy_utils, 'validate_image_properties',
+ spec_set=True, autospec=True)
+ def test__validate_boot_into_iso_manage(self, validate_image_prop_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ info = {'boot_iso_href': 'foo'}
+ task.node.provision_state = states.MANAGEABLE
+ task.driver.vendor._validate_boot_into_iso(
+ task, info)
+ validate_image_prop_mock.assert_called_once_with(
+ task.context, {'image_source': 'foo'}, [])
+
+ @mock.patch.object(deploy_utils, 'validate_image_properties',
+ spec_set=True, autospec=True)
+ def test__validate_boot_into_iso_maintenance(
+ self, validate_image_prop_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ info = {'boot_iso_href': 'foo'}
+ task.node.maintenance = True
+ task.driver.vendor._validate_boot_into_iso(
+ task, info)
+ validate_image_prop_mock.assert_called_once_with(
+ task.context, {'image_source': 'foo'}, [])
+
+
+class IloPXEDeployTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IloPXEDeployTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="pxe_ilo")
+ self.node = obj_utils.create_test_node(
+ self.context, driver='pxe_ilo', driver_info=INFO_DICT)
+
+ @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'validate', spec_set=True,
+ autospec=True)
+ def test_validate(self, pxe_validate_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.deploy.validate(task)
+ pxe_validate_mock.assert_called_once_with(mock.ANY, task)
+
+ @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'prepare', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_prepare_node_for_deploy', spec_set=True,
+ autospec=True)
+ def test_prepare(self,
+ prepare_node_mock,
+ pxe_prepare_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.properties['capabilities'] = 'boot_mode:uefi'
+ task.driver.deploy.prepare(task)
+ prepare_node_mock.assert_called_once_with(task)
+ pxe_prepare_mock.assert_called_once_with(mock.ANY, task)
+
+ @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'prepare', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_prepare_node_for_deploy', spec_set=True,
+ autospec=True)
+ def test_prepare_active_node(self,
+ prepare_node_mock,
+ pxe_prepare_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.provision_state = states.ACTIVE
+ task.node.properties['capabilities'] = 'boot_mode:uefi'
+ task.driver.deploy.prepare(task)
+ self.assertFalse(prepare_node_mock.called)
+ pxe_prepare_mock.assert_called_once_with(mock.ANY, task)
+
+ @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'prepare', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_prepare_node_for_deploy', spec_set=True,
+ autospec=True)
+ def test_prepare_uefi_whole_disk_image_fail(self,
+ prepare_node_for_deploy_mock,
+ pxe_prepare_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.properties['capabilities'] = 'boot_mode:uefi'
+ task.node.driver_internal_info['is_whole_disk_image'] = True
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.deploy.prepare, task)
+ prepare_node_for_deploy_mock.assert_called_once_with(task)
+ self.assertFalse(pxe_prepare_mock.called)
+
+ @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ def test_deploy_boot_mode_exists(self, set_persistent_mock,
+ pxe_deploy_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.deploy.deploy(task)
+ set_persistent_mock.assert_called_with(task, boot_devices.PXE)
+ pxe_deploy_mock.assert_called_once_with(mock.ANY, task)
+
+ @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'tear_down',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ def test_tear_down(self, node_power_action_mock,
+ update_secure_boot_mode_mock, pxe_tear_down_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ pxe_tear_down_mock.return_value = states.DELETED
+ returned_state = task.driver.deploy.tear_down(task)
+ node_power_action_mock.assert_called_once_with(task,
+ states.POWER_OFF)
+ update_secure_boot_mode_mock.assert_called_once_with(task, False)
+ pxe_tear_down_mock.assert_called_once_with(mock.ANY, task)
+ self.assertEqual(states.DELETED, returned_state)
+
+ @mock.patch.object(ilo_deploy.LOG, 'warn', spec_set=True, autospec=True)
+ @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'tear_down',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, 'exception', spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode',
+ spec_set=True, autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ def test_tear_down_handle_exception(self, node_power_action_mock,
+ update_secure_boot_mode_mock,
+ exception_mock, pxe_tear_down_mock,
+ mock_log):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ pxe_tear_down_mock.return_value = states.DELETED
+ exception_mock.IloOperationNotSupported = Exception
+ update_secure_boot_mode_mock.side_effect = Exception
+ returned_state = task.driver.deploy.tear_down(task)
+ update_secure_boot_mode_mock.assert_called_once_with(task, False)
+ pxe_tear_down_mock.assert_called_once_with(mock.ANY, task)
+ node_power_action_mock.assert_called_once_with(task,
+ states.POWER_OFF)
+ self.assertTrue(mock_log.called)
+ self.assertEqual(states.DELETED, returned_state)
+
+
+class IloPXEVendorPassthruTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IloPXEVendorPassthruTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="pxe_ilo")
+ self.node = obj_utils.create_test_node(
+ self.context, driver='pxe_ilo', driver_info=INFO_DICT)
+
+ def test_vendor_routes(self):
+ expected = ['heartbeat', 'pass_deploy_info',
+ 'pass_bootloader_install_info']
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ vendor_routes = task.driver.vendor.vendor_routes
+ self.assertIsInstance(vendor_routes, dict)
+ self.assertEqual(sorted(expected), sorted(list(vendor_routes)))
+
+ def test_driver_routes(self):
+ expected = ['lookup']
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ driver_routes = task.driver.vendor.driver_routes
+ self.assertIsInstance(driver_routes, dict)
+ self.assertEqual(sorted(expected), sorted(list(driver_routes)))
+
+ @mock.patch.object(iscsi_deploy.VendorPassthru, 'pass_deploy_info',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ def test_vendorpassthru_pass_deploy_info(self, set_boot_device_mock,
+ func_update_boot_mode,
+ func_update_secure_boot_mode,
+ pxe_vendorpassthru_mock):
+ kwargs = {'address': '123456'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.provision_state = states.DEPLOYWAIT
+ task.node.target_provision_state = states.ACTIVE
+ task.driver.vendor.pass_deploy_info(task, **kwargs)
+ set_boot_device_mock.assert_called_with(task, boot_devices.PXE,
+ persistent=True)
+ func_update_boot_mode.assert_called_once_with(task)
+ func_update_secure_boot_mode.assert_called_once_with(task, True)
+ pxe_vendorpassthru_mock.assert_called_once_with(
+ mock.ANY, task, **kwargs)
+
+ @mock.patch.object(iscsi_deploy.VendorPassthru, 'continue_deploy',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', autospec=True)
+ @mock.patch.object(ilo_common, 'update_boot_mode', autospec=True)
+ def test_vendorpassthru_continue_deploy(self,
+ func_update_boot_mode,
+ func_update_secure_boot_mode,
+ pxe_vendorpassthru_mock):
+ kwargs = {'address': '123456'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.provision_state = states.DEPLOYWAIT
+ task.node.target_provision_state = states.ACTIVE
+ task.driver.vendor.continue_deploy(task, **kwargs)
+ func_update_boot_mode.assert_called_once_with(task)
+ func_update_secure_boot_mode.assert_called_once_with(task, True)
+ pxe_vendorpassthru_mock.assert_called_once_with(
+ mock.ANY, task, **kwargs)
+
+
+class IloVirtualMediaAgentVendorInterfaceTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IloVirtualMediaAgentVendorInterfaceTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="agent_ilo")
+ self.node = obj_utils.create_test_node(
+ self.context, driver='agent_ilo', driver_info=INFO_DICT)
+
+ @mock.patch.object(agent.AgentVendorInterface, 'reboot_to_instance',
+ spec_set=True, autospec=True)
+ @mock.patch.object(agent.AgentVendorInterface, 'check_deploy_success',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', spec_set=True,
+ autospec=True)
+ def test_reboot_to_instance(self, func_update_secure_boot_mode,
+ func_update_boot_mode,
+ check_deploy_success_mock,
+ agent_reboot_to_instance_mock):
+ kwargs = {'address': '123456'}
+ check_deploy_success_mock.return_value = None
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.reboot_to_instance(task, **kwargs)
+ check_deploy_success_mock.assert_called_once_with(
+ mock.ANY, task.node)
+ func_update_boot_mode.assert_called_once_with(task)
+ func_update_secure_boot_mode.assert_called_once_with(task, True)
+ agent_reboot_to_instance_mock.assert_called_once_with(
+ mock.ANY, task, **kwargs)
+
+ @mock.patch.object(agent.AgentVendorInterface, 'reboot_to_instance',
+ spec_set=True, autospec=True)
+ @mock.patch.object(agent.AgentVendorInterface, 'check_deploy_success',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', spec_set=True,
+ autospec=True)
+ def test_reboot_to_instance_deploy_fail(self, func_update_secure_boot_mode,
+ func_update_boot_mode,
+ check_deploy_success_mock,
+ agent_reboot_to_instance_mock):
+ kwargs = {'address': '123456'}
+ check_deploy_success_mock.return_value = "Error"
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.reboot_to_instance(task, **kwargs)
+ check_deploy_success_mock.assert_called_once_with(
+ mock.ANY, task.node)
+ self.assertFalse(func_update_boot_mode.called)
+ self.assertFalse(func_update_secure_boot_mode.called)
+ agent_reboot_to_instance_mock.assert_called_once_with(
+ mock.ANY, task, **kwargs)
diff --git a/ironic/tests/unit/drivers/modules/ilo/test_inspect.py b/ironic/tests/unit/drivers/modules/ilo/test_inspect.py
new file mode 100644
index 000000000..94ddbdd45
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ilo/test_inspect.py
@@ -0,0 +1,365 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+#
+# 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.
+
+
+"""Test class for Management Interface used by iLO modules."""
+
+import mock
+from oslo_config import cfg
+import six
+
+from ironic.common import exception
+from ironic.common import states
+from ironic.common import utils
+from ironic.conductor import task_manager
+from ironic.conductor import utils as conductor_utils
+from ironic.db import api as dbapi
+from ironic.drivers.modules.ilo import common as ilo_common
+from ironic.drivers.modules.ilo import inspect as ilo_inspect
+from ironic.drivers.modules.ilo import power as ilo_power
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+
+INFO_DICT = db_utils.get_test_ilo_info()
+CONF = cfg.CONF
+
+
+class IloInspectTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IloInspectTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="fake_ilo")
+ self.node = obj_utils.create_test_node(
+ self.context, driver='fake_ilo', driver_info=INFO_DICT)
+
+ def test_get_properties(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ properties = ilo_common.REQUIRED_PROPERTIES.copy()
+ self.assertEqual(properties,
+ task.driver.inspect.get_properties())
+
+ @mock.patch.object(ilo_common, 'parse_driver_info', spec_set=True,
+ autospec=True)
+ def test_validate(self, driver_info_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.inspect.validate(task)
+ driver_info_mock.assert_called_once_with(task.node)
+
+ @mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_inspect, '_create_ports_if_not_exist',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_power.IloPower, 'get_power_state', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_inspect_essential_ok(self, get_ilo_object_mock,
+ power_mock,
+ get_essential_mock,
+ create_port_mock,
+ get_capabilities_mock):
+ ilo_object_mock = get_ilo_object_mock.return_value
+ properties = {'memory_mb': '512', 'local_gb': '10',
+ 'cpus': '1', 'cpu_arch': 'x86_64'}
+ macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
+ capabilities = ''
+ result = {'properties': properties, 'macs': macs}
+ get_essential_mock.return_value = result
+ get_capabilities_mock.return_value = capabilities
+ power_mock.return_value = states.POWER_ON
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.inspect.inspect_hardware(task)
+ self.assertEqual(properties, task.node.properties)
+ power_mock.assert_called_once_with(mock.ANY, task)
+ get_essential_mock.assert_called_once_with(task.node,
+ ilo_object_mock)
+ get_capabilities_mock.assert_called_once_with(task.node,
+ ilo_object_mock)
+ create_port_mock.assert_called_once_with(task.node, macs)
+
+ @mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_inspect, '_create_ports_if_not_exist',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
+ autospec=True)
+ @mock.patch.object(conductor_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_power.IloPower, 'get_power_state', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_inspect_essential_ok_power_off(self, get_ilo_object_mock,
+ power_mock,
+ set_power_mock,
+ get_essential_mock,
+ create_port_mock,
+ get_capabilities_mock):
+ ilo_object_mock = get_ilo_object_mock.return_value
+ properties = {'memory_mb': '512', 'local_gb': '10',
+ 'cpus': '1', 'cpu_arch': 'x86_64'}
+ macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
+ capabilities = ''
+ result = {'properties': properties, 'macs': macs}
+ get_essential_mock.return_value = result
+ get_capabilities_mock.return_value = capabilities
+ power_mock.return_value = states.POWER_OFF
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.inspect.inspect_hardware(task)
+ self.assertEqual(properties, task.node.properties)
+ power_mock.assert_called_once_with(mock.ANY, task)
+ set_power_mock.assert_any_call(task, states.POWER_ON)
+ get_essential_mock.assert_called_once_with(task.node,
+ ilo_object_mock)
+ get_capabilities_mock.assert_called_once_with(task.node,
+ ilo_object_mock)
+ create_port_mock.assert_called_once_with(task.node, macs)
+
+ @mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_inspect, '_create_ports_if_not_exist',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_power.IloPower, 'get_power_state', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_inspect_essential_capabilities_ok(self, get_ilo_object_mock,
+ power_mock,
+ get_essential_mock,
+ create_port_mock,
+ get_capabilities_mock):
+ ilo_object_mock = get_ilo_object_mock.return_value
+ properties = {'memory_mb': '512', 'local_gb': '10',
+ 'cpus': '1', 'cpu_arch': 'x86_64'}
+ macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
+ capability_str = 'BootMode:uefi'
+ capabilities = {'BootMode': 'uefi'}
+ result = {'properties': properties, 'macs': macs}
+ get_essential_mock.return_value = result
+ get_capabilities_mock.return_value = capabilities
+ power_mock.return_value = states.POWER_ON
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.inspect.inspect_hardware(task)
+ expected_properties = {'memory_mb': '512', 'local_gb': '10',
+ 'cpus': '1', 'cpu_arch': 'x86_64',
+ 'capabilities': capability_str}
+ self.assertEqual(expected_properties, task.node.properties)
+ power_mock.assert_called_once_with(mock.ANY, task)
+ get_essential_mock.assert_called_once_with(task.node,
+ ilo_object_mock)
+ get_capabilities_mock.assert_called_once_with(task.node,
+ ilo_object_mock)
+ create_port_mock.assert_called_once_with(task.node, macs)
+
+ @mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_inspect, '_create_ports_if_not_exist',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_power.IloPower, 'get_power_state', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_inspect_essential_capabilities_exist_ok(self, get_ilo_object_mock,
+ power_mock,
+ get_essential_mock,
+ create_port_mock,
+ get_capabilities_mock):
+ ilo_object_mock = get_ilo_object_mock.return_value
+ properties = {'memory_mb': '512', 'local_gb': '10',
+ 'cpus': '1', 'cpu_arch': 'x86_64',
+ 'somekey': 'somevalue'}
+ macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
+ result = {'properties': properties, 'macs': macs}
+ capabilities = {'BootMode': 'uefi'}
+ get_essential_mock.return_value = result
+ get_capabilities_mock.return_value = capabilities
+ power_mock.return_value = states.POWER_ON
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.properties = {'capabilities': 'foo:bar'}
+ expected_capabilities = ('BootMode:uefi,'
+ 'foo:bar')
+ set1 = set(expected_capabilities.split(','))
+ task.driver.inspect.inspect_hardware(task)
+ end_capabilities = task.node.properties['capabilities']
+ set2 = set(end_capabilities.split(','))
+ self.assertEqual(set1, set2)
+ expected_properties = {'memory_mb': '512', 'local_gb': '10',
+ 'cpus': '1', 'cpu_arch': 'x86_64',
+ 'capabilities': end_capabilities}
+ power_mock.assert_called_once_with(mock.ANY, task)
+ self.assertEqual(task.node.properties, expected_properties)
+ get_essential_mock.assert_called_once_with(task.node,
+ ilo_object_mock)
+ get_capabilities_mock.assert_called_once_with(task.node,
+ ilo_object_mock)
+ create_port_mock.assert_called_once_with(task.node, macs)
+
+
+class TestInspectPrivateMethods(db_base.DbTestCase):
+
+ def setUp(self):
+ super(TestInspectPrivateMethods, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="fake_ilo")
+ self.node = obj_utils.create_test_node(
+ self.context, driver='fake_ilo', driver_info=INFO_DICT)
+
+ @mock.patch.object(ilo_inspect.LOG, 'info', spec_set=True, autospec=True)
+ @mock.patch.object(dbapi, 'get_instance', spec_set=True, autospec=True)
+ def test__create_ports_if_not_exist(self, instance_mock, log_mock):
+ db_obj = instance_mock.return_value
+ macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
+ node_id = self.node.id
+ port_dict1 = {'address': 'aa:aa:aa:aa:aa:aa', 'node_id': node_id}
+ port_dict2 = {'address': 'bb:bb:bb:bb:bb:bb', 'node_id': node_id}
+ ilo_inspect._create_ports_if_not_exist(self.node, macs)
+ instance_mock.assert_called_once_with()
+ self.assertTrue(log_mock.called)
+ db_obj.create_port.assert_any_call(port_dict1)
+ db_obj.create_port.assert_any_call(port_dict2)
+
+ @mock.patch.object(ilo_inspect.LOG, 'warn', spec_set=True, autospec=True)
+ @mock.patch.object(dbapi, 'get_instance', spec_set=True, autospec=True)
+ def test__create_ports_if_not_exist_mac_exception(self,
+ instance_mock,
+ log_mock):
+ dbapi_mock = instance_mock.return_value
+ dbapi_mock.create_port.side_effect = exception.MACAlreadyExists('f')
+ macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
+ ilo_inspect._create_ports_if_not_exist(self.node, macs)
+ instance_mock.assert_called_once_with()
+ self.assertTrue(log_mock.called)
+
+ def test__get_essential_properties_ok(self):
+ ilo_mock = mock.MagicMock(spec=['get_essential_properties'])
+ properties = {'memory_mb': '512', 'local_gb': '10',
+ 'cpus': '1', 'cpu_arch': 'x86_64'}
+ macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
+ result = {'properties': properties, 'macs': macs}
+ ilo_mock.get_essential_properties.return_value = result
+ actual_result = ilo_inspect._get_essential_properties(self.node,
+ ilo_mock)
+ self.assertEqual(result, actual_result)
+
+ def test__get_essential_properties_fail(self):
+ ilo_mock = mock.MagicMock(
+ spec=['get_additional_capabilities', 'get_essential_properties'])
+ # Missing key: cpu_arch
+ properties = {'memory_mb': '512', 'local_gb': '10',
+ 'cpus': '1'}
+ macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
+ result = {'properties': properties, 'macs': macs}
+ ilo_mock.get_essential_properties.return_value = result
+ result = self.assertRaises(exception.HardwareInspectionFailure,
+ ilo_inspect._get_essential_properties,
+ self.node,
+ ilo_mock)
+ self.assertEqual(
+ six.text_type(result),
+ ("Failed to inspect hardware. Reason: Server didn't return the "
+ "key(s): cpu_arch"))
+
+ def test__get_essential_properties_fail_invalid_format(self):
+ ilo_mock = mock.MagicMock(
+ spec=['get_additional_capabilities', 'get_essential_properties'])
+ # Not a dict
+ properties = ['memory_mb', '512', 'local_gb', '10',
+ 'cpus', '1']
+ macs = ['aa:aa:aa:aa:aa:aa', 'bb:bb:bb:bb:bb:bb']
+ capabilities = ''
+ result = {'properties': properties, 'macs': macs}
+ ilo_mock.get_essential_properties.return_value = result
+ ilo_mock.get_additional_capabilities.return_value = capabilities
+ self.assertRaises(exception.HardwareInspectionFailure,
+ ilo_inspect._get_essential_properties,
+ self.node, ilo_mock)
+
+ def test__get_essential_properties_fail_mac_invalid_format(self):
+ ilo_mock = mock.MagicMock(spec=['get_essential_properties'])
+ properties = {'memory_mb': '512', 'local_gb': '10',
+ 'cpus': '1', 'cpu_arch': 'x86_64'}
+ # Not a dict
+ macs = 'aa:aa:aa:aa:aa:aa'
+ result = {'properties': properties, 'macs': macs}
+ ilo_mock.get_essential_properties.return_value = result
+ self.assertRaises(exception.HardwareInspectionFailure,
+ ilo_inspect._get_essential_properties,
+ self.node, ilo_mock)
+
+ def test__get_essential_properties_hardware_port_empty(self):
+ ilo_mock = mock.MagicMock(
+ spec=['get_additional_capabilities', 'get_essential_properties'])
+ properties = {'memory_mb': '512', 'local_gb': '10',
+ 'cpus': '1', 'cpu_arch': 'x86_64'}
+ # Not a dictionary
+ macs = None
+ result = {'properties': properties, 'macs': macs}
+ capabilities = ''
+ ilo_mock.get_essential_properties.return_value = result
+ ilo_mock.get_additional_capabilities.return_value = capabilities
+ self.assertRaises(exception.HardwareInspectionFailure,
+ ilo_inspect._get_essential_properties,
+ self.node, ilo_mock)
+
+ def test__get_essential_properties_hardware_port_not_dict(self):
+ ilo_mock = mock.MagicMock(spec=['get_essential_properties'])
+ properties = {'memory_mb': '512', 'local_gb': '10',
+ 'cpus': '1', 'cpu_arch': 'x86_64'}
+ # Not a dict
+ macs = 'aa:bb:cc:dd:ee:ff'
+ result = {'properties': properties, 'macs': macs}
+ ilo_mock.get_essential_properties.return_value = result
+ result = self.assertRaises(
+ exception.HardwareInspectionFailure,
+ ilo_inspect._get_essential_properties, self.node, ilo_mock)
+
+ @mock.patch.object(utils, 'get_updated_capabilities', spec_set=True,
+ autospec=True)
+ def test__get_capabilities_ok(self, capability_mock):
+ ilo_mock = mock.MagicMock(spec=['get_server_capabilities'])
+ capabilities = {'ilo_firmware_version': 'xyz'}
+ ilo_mock.get_server_capabilities.return_value = capabilities
+ cap = ilo_inspect._get_capabilities(self.node, ilo_mock)
+ self.assertEqual(cap, capabilities)
+
+ def test__validate_ok(self):
+ properties = {'memory_mb': '512', 'local_gb': '10',
+ 'cpus': '2', 'cpu_arch': 'x86_arch'}
+ macs = {'Port 1': 'aa:aa:aa:aa:aa:aa'}
+ data = {'properties': properties, 'macs': macs}
+ valid_keys = ilo_inspect.IloInspect.ESSENTIAL_PROPERTIES
+ ilo_inspect._validate(self.node, data)
+ self.assertEqual(sorted(set(properties)), sorted(valid_keys))
+
+ def test__validate_essential_keys_fail_missing_key(self):
+ properties = {'memory_mb': '512', 'local_gb': '10',
+ 'cpus': '1'}
+ macs = {'Port 1': 'aa:aa:aa:aa:aa:aa'}
+ data = {'properties': properties, 'macs': macs}
+ self.assertRaises(exception.HardwareInspectionFailure,
+ ilo_inspect._validate, self.node, data)
diff --git a/ironic/tests/unit/drivers/modules/ilo/test_management.py b/ironic/tests/unit/drivers/modules/ilo/test_management.py
new file mode 100644
index 000000000..a3a9b7910
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ilo/test_management.py
@@ -0,0 +1,298 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+#
+# 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.
+
+"""Test class for Management Interface used by iLO modules."""
+
+import mock
+from oslo_config import cfg
+from oslo_utils import importutils
+
+from ironic.common import boot_devices
+from ironic.common import exception
+from ironic.conductor import task_manager
+from ironic.drivers.modules.ilo import common as ilo_common
+from ironic.drivers.modules.ilo import management as ilo_management
+from ironic.drivers.modules import ipmitool
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+ilo_error = importutils.try_import('proliantutils.exception')
+
+INFO_DICT = db_utils.get_test_ilo_info()
+CONF = cfg.CONF
+
+
+class IloManagementTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IloManagementTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="fake_ilo")
+ self.node = obj_utils.create_test_node(
+ self.context, driver='fake_ilo', driver_info=INFO_DICT)
+
+ def test_get_properties(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ expected = ilo_management.MANAGEMENT_PROPERTIES
+ self.assertEqual(expected,
+ task.driver.management.get_properties())
+
+ @mock.patch.object(ilo_common, 'parse_driver_info', spec_set=True,
+ autospec=True)
+ def test_validate(self, driver_info_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.validate(task)
+ driver_info_mock.assert_called_once_with(task.node)
+
+ def test_get_supported_boot_devices(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ expected = [boot_devices.PXE, boot_devices.DISK,
+ boot_devices.CDROM]
+ self.assertEqual(
+ sorted(expected),
+ sorted(task.driver.management.
+ get_supported_boot_devices(task)))
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_get_boot_device_next_boot(self, get_ilo_object_mock):
+ ilo_object_mock = get_ilo_object_mock.return_value
+ ilo_object_mock.get_one_time_boot.return_value = 'CDROM'
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ expected_device = boot_devices.CDROM
+ expected_response = {'boot_device': expected_device,
+ 'persistent': False}
+ self.assertEqual(expected_response,
+ task.driver.management.get_boot_device(task))
+ ilo_object_mock.get_one_time_boot.assert_called_once_with()
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_get_boot_device_persistent(self, get_ilo_object_mock):
+ ilo_mock = get_ilo_object_mock.return_value
+ ilo_mock.get_one_time_boot.return_value = 'Normal'
+ ilo_mock.get_persistent_boot_device.return_value = 'NETWORK'
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ expected_device = boot_devices.PXE
+ expected_response = {'boot_device': expected_device,
+ 'persistent': True}
+ self.assertEqual(expected_response,
+ task.driver.management.get_boot_device(task))
+ ilo_mock.get_one_time_boot.assert_called_once_with()
+ ilo_mock.get_persistent_boot_device.assert_called_once_with()
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_get_boot_device_fail(self, get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+ exc = ilo_error.IloError('error')
+ ilo_mock_object.get_one_time_boot.side_effect = exc
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.IloOperationError,
+ task.driver.management.get_boot_device,
+ task)
+ ilo_mock_object.get_one_time_boot.assert_called_once_with()
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_get_boot_device_persistent_fail(self, get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+ ilo_mock_object.get_one_time_boot.return_value = 'Normal'
+ exc = ilo_error.IloError('error')
+ ilo_mock_object.get_persistent_boot_device.side_effect = exc
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.IloOperationError,
+ task.driver.management.get_boot_device,
+ task)
+ ilo_mock_object.get_one_time_boot.assert_called_once_with()
+ ilo_mock_object.get_persistent_boot_device.assert_called_once_with()
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_set_boot_device_ok(self, get_ilo_object_mock):
+ ilo_object_mock = get_ilo_object_mock.return_value
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.set_boot_device(task, boot_devices.CDROM,
+ False)
+ get_ilo_object_mock.assert_called_once_with(task.node)
+ ilo_object_mock.set_one_time_boot.assert_called_once_with('CDROM')
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_set_boot_device_persistent_true(self, get_ilo_object_mock):
+ ilo_mock = get_ilo_object_mock.return_value
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.set_boot_device(task, boot_devices.PXE,
+ True)
+ get_ilo_object_mock.assert_called_once_with(task.node)
+ ilo_mock.update_persistent_boot.assert_called_once_with(
+ ['NETWORK'])
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_set_boot_device_fail(self, get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+ exc = ilo_error.IloError('error')
+ ilo_mock_object.set_one_time_boot.side_effect = exc
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.IloOperationError,
+ task.driver.management.set_boot_device,
+ task, boot_devices.PXE)
+ ilo_mock_object.set_one_time_boot.assert_called_once_with('NETWORK')
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_set_boot_device_persistent_fail(self, get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+ exc = ilo_error.IloError('error')
+ ilo_mock_object.update_persistent_boot.side_effect = exc
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.IloOperationError,
+ task.driver.management.set_boot_device,
+ task, boot_devices.PXE, True)
+ ilo_mock_object.update_persistent_boot.assert_called_once_with(
+ ['NETWORK'])
+
+ def test_set_boot_device_invalid_device(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.management.set_boot_device,
+ task, 'fake-device')
+
+ @mock.patch.object(ilo_common, 'update_ipmi_properties', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ipmitool.IPMIManagement, 'get_sensors_data',
+ spec_set=True, autospec=True)
+ def test_get_sensor_data(self, get_sensors_data_mock, update_ipmi_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.get_sensors_data(task)
+ update_ipmi_mock.assert_called_once_with(task)
+ get_sensors_data_mock.assert_called_once_with(mock.ANY, task)
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test__execute_ilo_clean_step_ok(self, get_ilo_object_mock):
+ ilo_mock = get_ilo_object_mock.return_value
+ clean_step_mock = getattr(ilo_mock, 'fake-step')
+ ilo_management._execute_ilo_clean_step(
+ self.node, 'fake-step', 'args', kwarg='kwarg')
+ clean_step_mock.assert_called_once_with('args', kwarg='kwarg')
+
+ @mock.patch.object(ilo_management, 'LOG', spec_set=True, autospec=True)
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test__execute_ilo_clean_step_not_supported(self, get_ilo_object_mock,
+ log_mock):
+ ilo_mock = get_ilo_object_mock.return_value
+ exc = ilo_error.IloCommandNotSupportedError("error")
+ clean_step_mock = getattr(ilo_mock, 'fake-step')
+ clean_step_mock.side_effect = exc
+ ilo_management._execute_ilo_clean_step(
+ self.node, 'fake-step', 'args', kwarg='kwarg')
+ clean_step_mock.assert_called_once_with('args', kwarg='kwarg')
+ self.assertTrue(log_mock.warn.called)
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test__execute_ilo_clean_step_fail(self, get_ilo_object_mock):
+ ilo_mock = get_ilo_object_mock.return_value
+ exc = ilo_error.IloError("error")
+ clean_step_mock = getattr(ilo_mock, 'fake-step')
+ clean_step_mock.side_effect = exc
+ self.assertRaises(exception.NodeCleaningFailure,
+ ilo_management._execute_ilo_clean_step,
+ self.node, 'fake-step', 'args', kwarg='kwarg')
+ clean_step_mock.assert_called_once_with('args', kwarg='kwarg')
+
+ @mock.patch.object(ilo_management, '_execute_ilo_clean_step',
+ spec_set=True, autospec=True)
+ def test_reset_ilo(self, clean_step_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.reset_ilo(task)
+ clean_step_mock.assert_called_once_with(task.node, 'reset_ilo')
+
+ @mock.patch.object(ilo_management, '_execute_ilo_clean_step',
+ spec_set=True, autospec=True)
+ def test_reset_ilo_credential_ok(self, clean_step_mock):
+ info = self.node.driver_info
+ info['ilo_change_password'] = "fake-password"
+ self.node.driver_info = info
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.reset_ilo_credential(task)
+ clean_step_mock.assert_called_once_with(
+ task.node, 'reset_ilo_credential', 'fake-password')
+ self.assertIsNone(
+ task.node.driver_info.get('ilo_change_password'))
+ self.assertEqual(task.node.driver_info['ilo_password'],
+ 'fake-password')
+
+ @mock.patch.object(ilo_management, 'LOG', spec_set=True, autospec=True)
+ @mock.patch.object(ilo_management, '_execute_ilo_clean_step',
+ spec_set=True, autospec=True)
+ def test_reset_ilo_credential_no_password(self, clean_step_mock,
+ log_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.reset_ilo_credential(task)
+ self.assertFalse(clean_step_mock.called)
+ self.assertTrue(log_mock.info.called)
+
+ @mock.patch.object(ilo_management, '_execute_ilo_clean_step',
+ spec_set=True, autospec=True)
+ def test_reset_bios_to_default(self, clean_step_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.reset_bios_to_default(task)
+ clean_step_mock.assert_called_once_with(task.node,
+ 'reset_bios_to_default')
+
+ @mock.patch.object(ilo_management, '_execute_ilo_clean_step',
+ spec_set=True, autospec=True)
+ def test_reset_secure_boot_keys_to_default(self, clean_step_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.reset_secure_boot_keys_to_default(task)
+ clean_step_mock.assert_called_once_with(task.node,
+ 'reset_secure_boot_keys')
+
+ @mock.patch.object(ilo_management, '_execute_ilo_clean_step',
+ spec_set=True, autospec=True)
+ def test_clear_secure_boot_keys(self, clean_step_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.clear_secure_boot_keys(task)
+ clean_step_mock.assert_called_once_with(task.node,
+ 'clear_secure_boot_keys')
diff --git a/ironic/tests/unit/drivers/modules/ilo/test_power.py b/ironic/tests/unit/drivers/modules/ilo/test_power.py
new file mode 100644
index 000000000..13fade795
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ilo/test_power.py
@@ -0,0 +1,231 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+# 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
+# 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.
+
+"""Test class for IloPower module."""
+
+import mock
+from oslo_config import cfg
+from oslo_utils import importutils
+
+from ironic.common import boot_devices
+from ironic.common import exception
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.conductor import utils as manager_utils
+from ironic.drivers.modules.ilo import common as ilo_common
+from ironic.drivers.modules.ilo import power as ilo_power
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+ilo_error = importutils.try_import('proliantutils.exception')
+
+INFO_DICT = db_utils.get_test_ilo_info()
+CONF = cfg.CONF
+
+
+@mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True, autospec=True)
+class IloPowerInternalMethodsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IloPowerInternalMethodsTestCase, self).setUp()
+ driver_info = INFO_DICT
+ mgr_utils.mock_the_extension_manager(driver="fake_ilo")
+ self.node = db_utils.create_test_node(
+ driver='fake_ilo',
+ driver_info=driver_info,
+ instance_uuid='instance_uuid_123')
+ CONF.set_override('power_retry', 2, 'ilo')
+ CONF.set_override('power_wait', 0, 'ilo')
+
+ def test__get_power_state(self, get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+ ilo_mock_object.get_host_power_status.return_value = 'ON'
+
+ self.assertEqual(
+ states.POWER_ON, ilo_power._get_power_state(self.node))
+
+ ilo_mock_object.get_host_power_status.return_value = 'OFF'
+ self.assertEqual(
+ states.POWER_OFF, ilo_power._get_power_state(self.node))
+
+ ilo_mock_object.get_host_power_status.return_value = 'ERROR'
+ self.assertEqual(states.ERROR, ilo_power._get_power_state(self.node))
+
+ def test__get_power_state_fail(self, get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+ exc = ilo_error.IloError('error')
+ ilo_mock_object.get_host_power_status.side_effect = exc
+
+ self.assertRaises(exception.IloOperationError,
+ ilo_power._get_power_state,
+ self.node)
+ ilo_mock_object.get_host_power_status.assert_called_once_with()
+
+ def test__set_power_state_invalid_state(self, get_ilo_object_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ ilo_power._set_power_state,
+ task,
+ states.ERROR)
+
+ def test__set_power_state_reboot_fail(self, get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+ exc = ilo_error.IloError('error')
+ ilo_mock_object.reset_server.side_effect = exc
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.IloOperationError,
+ ilo_power._set_power_state,
+ task,
+ states.REBOOT)
+ ilo_mock_object.reset_server.assert_called_once_with()
+
+ def test__set_power_state_reboot_ok(self, get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+ ilo_mock_object.get_host_power_status.side_effect = ['ON', 'OFF', 'ON']
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ ilo_power._set_power_state(task, states.REBOOT)
+
+ ilo_mock_object.reset_server.assert_called_once_with()
+
+ def test__set_power_state_off_fail(self, get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+ ilo_mock_object.get_host_power_status.return_value = 'ON'
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.PowerStateFailure,
+ ilo_power._set_power_state,
+ task,
+ states.POWER_OFF)
+
+ ilo_mock_object.get_host_power_status.assert_called_with()
+ ilo_mock_object.hold_pwr_btn.assert_called_once_with()
+
+ def test__set_power_state_on_ok(self, get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+ ilo_mock_object.get_host_power_status.side_effect = ['OFF', 'ON']
+
+ target_state = states.POWER_ON
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ ilo_power._set_power_state(task, target_state)
+ ilo_mock_object.get_host_power_status.assert_called_with()
+ ilo_mock_object.set_host_power.assert_called_once_with('ON')
+
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'setup_vmedia_for_boot', spec_set=True,
+ autospec=True)
+ def test__attach_boot_iso_if_needed(
+ self, setup_vmedia_mock, set_boot_device_mock,
+ get_ilo_object_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.node.provision_state = states.ACTIVE
+ task.node.instance_info['ilo_boot_iso'] = 'boot-iso'
+ ilo_power._attach_boot_iso_if_needed(task)
+ setup_vmedia_mock.assert_called_once_with(task, 'boot-iso')
+ set_boot_device_mock.assert_called_once_with(task,
+ boot_devices.CDROM)
+
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'setup_vmedia_for_boot', spec_set=True,
+ autospec=True)
+ def test__attach_boot_iso_if_needed_on_rebuild(
+ self, setup_vmedia_mock, set_boot_device_mock,
+ get_ilo_object_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.node.provision_state = states.DEPLOYING
+ task.node.instance_info['ilo_boot_iso'] = 'boot-iso'
+ ilo_power._attach_boot_iso_if_needed(task)
+ self.assertFalse(setup_vmedia_mock.called)
+ self.assertFalse(set_boot_device_mock.called)
+
+
+class IloPowerTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IloPowerTestCase, self).setUp()
+ driver_info = INFO_DICT
+ mgr_utils.mock_the_extension_manager(driver="fake_ilo")
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_ilo',
+ driver_info=driver_info)
+
+ def test_get_properties(self):
+ expected = ilo_common.COMMON_PROPERTIES
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertEqual(expected, task.driver.power.get_properties())
+
+ @mock.patch.object(ilo_common, 'parse_driver_info', spec_set=True,
+ autospec=True)
+ def test_validate(self, mock_drvinfo):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.driver.power.validate(task)
+ mock_drvinfo.assert_called_once_with(task.node)
+
+ @mock.patch.object(ilo_common, 'parse_driver_info', spec_set=True,
+ autospec=True)
+ def test_validate_fail(self, mock_drvinfo):
+ side_effect = iter([exception.InvalidParameterValue("Invalid Input")])
+ mock_drvinfo.side_effect = side_effect
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.power.validate,
+ task)
+
+ @mock.patch.object(ilo_power, '_get_power_state', spec_set=True,
+ autospec=True)
+ def test_get_power_state(self, mock_get_power):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ mock_get_power.return_value = states.POWER_ON
+ self.assertEqual(states.POWER_ON,
+ task.driver.power.get_power_state(task))
+ mock_get_power.assert_called_once_with(task.node)
+
+ @mock.patch.object(ilo_power, '_set_power_state', spec_set=True,
+ autospec=True)
+ def test_set_power_state(self, mock_set_power):
+ mock_set_power.return_value = states.POWER_ON
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.power.set_power_state(task, states.POWER_ON)
+ mock_set_power.assert_called_once_with(task, states.POWER_ON)
+
+ @mock.patch.object(ilo_power, '_set_power_state', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_power, '_get_power_state', spec_set=True,
+ autospec=True)
+ def test_reboot(self, mock_get_power, mock_set_power):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ mock_get_power.return_value = states.POWER_ON
+ mock_set_power.return_value = states.POWER_ON
+ task.driver.power.reboot(task)
+ mock_get_power.assert_called_once_with(task.node)
+ mock_set_power.assert_called_once_with(task, states.REBOOT)
diff --git a/ironic/tests/unit/drivers/modules/irmc/__init__.py b/ironic/tests/unit/drivers/modules/irmc/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/irmc/__init__.py
diff --git a/ironic/tests/unit/drivers/modules/irmc/fake_sensors_data_ng.xml b/ironic/tests/unit/drivers/modules/irmc/fake_sensors_data_ng.xml
new file mode 100644
index 000000000..c8788427b
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/irmc/fake_sensors_data_ng.xml
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet type="text/xsl" href="Report.xslt"?>
+<Root Schema="2" Version="7.65F" OS="iRMC S4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <System>
+ <SensorDataRecords Schema="1">
+ <SDR Nr="1" RecordID="1" RecordType="1" RecordTypeName="Full SDR" Version="1.5" Length="64">
+ <Data Size="64">
+ <Decoded>
+ <Key>
+ <SensorOwner Type="IPMB">
+ <ID>20</ID>
+ <LUN>0</LUN>
+ <Channel>0</Channel>
+ </SensorOwner>
+ <SensorNumber>1</SensorNumber>
+ </Key>
+ <Entity>
+ <ID>55</ID>
+ <Instance>0</Instance>
+ <Name>Ambient</Name>
+ </Entity>
+ <Sensor>
+ <Type>1</Type>
+ <!-- <TypeName>Temperature</TypeName> -->
+ <BaseUnit>1</BaseUnit>
+ <ModifierUnit>0</ModifierUnit>
+ <BaseUnitName>degree C</BaseUnitName>
+ <ModifierUnitName>unspecified</ModifierUnitName>
+ <Thresholds>
+ <UpperCritical>
+ <Raw>168</Raw>
+ <Normalized>42</Normalized>
+ </UpperCritical>
+ <LowerCritical>
+ <Raw>4</Raw>
+ <Normalized>1</Normalized>
+ </LowerCritical>
+ <UpperNonCritical>
+ <Raw>148</Raw>
+ <Normalized>37</Normalized>
+ </UpperNonCritical>
+ <LowerNonCritical>
+ <Raw>24</Raw>
+ <Normalized>6</Normalized>
+ </LowerNonCritical>
+ </Thresholds>
+ </Sensor>
+ </Decoded>
+ </Data>
+ </SDR>
+ <SDR Nr="2" RecordID="2" RecordType="1" RecordTypeName="Full SDR" Version="1.5" Length="64">
+ <Data Size="64">
+ <Decoded>
+ <Key>
+ <SensorOwner Type="IPMB">
+ <ID>20</ID>
+ <LUN>0</LUN>
+ <Channel>0</Channel>
+ </SensorOwner>
+ <SensorNumber>2</SensorNumber>
+ </Key>
+ <Entity>
+ <ID>7</ID>
+ <Instance>0</Instance>
+ <Name>Systemboard 1</Name>
+ </Entity>
+ <Sensor>
+ <!-- <Type>1</Type> -->
+ <TypeName>Temperature</TypeName>
+ <BaseUnit>1</BaseUnit>
+ <ModifierUnit>0</ModifierUnit>
+ <BaseUnitName>degree C</BaseUnitName>
+ <ModifierUnitName>unspecified</ModifierUnitName>
+ <Thresholds>
+ <UpperCritical>
+ <Raw>80</Raw>
+ <Normalized>80</Normalized>
+ </UpperCritical>
+ <UpperNonCritical>
+ <Raw>75</Raw>
+ <Normalized>75</Normalized>
+ </UpperNonCritical>
+ </Thresholds>
+ </Sensor>
+ </Decoded>
+ </Data>
+ </SDR>
+ <SDR Nr="34" RecordID="34" RecordType="1" RecordTypeName="Full SDR" Version="1.5" Length="64">
+ <Data Size="64">
+ <Decoded>
+ <Key>
+ <SensorOwner Type="IPMB">
+ <ID>20</ID>
+ <LUN>0</LUN>
+ <Channel>0</Channel>
+ </SensorOwner>
+ <SensorNumber>35</SensorNumber>
+ </Key>
+ <Entity>
+ <ID>29</ID>
+ <Instance>0</Instance>
+ <!-- <Name>FAN1 SYS</Name> -->
+ </Entity>
+ <Sensor>
+ <Type>4</Type>
+ <TypeName>Fan</TypeName>
+ <BaseUnit>18</BaseUnit>
+ <ModifierUnit>0</ModifierUnit>
+ <BaseUnitName>RPM</BaseUnitName>
+ <ModifierUnitName>unspecified</ModifierUnitName>
+ <Thresholds>
+ <LowerCritical>
+ <Raw>10</Raw>
+ <Normalized>600</Normalized>
+ </LowerCritical>
+ </Thresholds>
+ </Sensor>
+ </Decoded>
+ </Data>
+ </SDR>
+ <SDR Nr="35" RecordID="35" RecordType="1" RecordTypeName="Full SDR" Version="1.5" Length="64">
+ <Data Size="64">
+ <Decoded>
+ <Key>
+ <SensorOwner Type="IPMB">
+ <ID>20</ID>
+ <LUN>0</LUN>
+ <Channel>0</Channel>
+ </SensorOwner>
+ <SensorNumber>36</SensorNumber>
+ </Key>
+ <Entity>
+ <!-- <ID>29</ID> -->
+ <Instance>1</Instance>
+ <Name>FAN2 SYS</Name>
+ </Entity>
+ <Sensor>
+ <Type>4</Type>
+ <TypeName>Fan</TypeName>
+ <BaseUnit>18</BaseUnit>
+ <ModifierUnit>0</ModifierUnit>
+ <BaseUnitName>RPM</BaseUnitName>
+ <ModifierUnitName>unspecified</ModifierUnitName>
+ <Thresholds>
+ <LowerCritical>
+ <Raw>10</Raw>
+ <Normalized>600</Normalized>
+ </LowerCritical>
+ </Thresholds>
+ </Sensor>
+ </Decoded>
+ </Data>
+ </SDR>
+ </SensorDataRecords>
+ </System>
+</Root>
diff --git a/ironic/tests/unit/drivers/modules/irmc/fake_sensors_data_ok.xml b/ironic/tests/unit/drivers/modules/irmc/fake_sensors_data_ok.xml
new file mode 100644
index 000000000..fb8edba68
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/irmc/fake_sensors_data_ok.xml
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet type="text/xsl" href="Report.xslt"?>
+<Root Schema="2" Version="7.65F" OS="iRMC S4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <System>
+ <SensorDataRecords Schema="1">
+ <SDR Nr="1" RecordID="1" RecordType="1" RecordTypeName="Full SDR" Version="1.5" Length="64">
+ <Data Size="64">
+ <Decoded>
+ <Key>
+ <SensorOwner Type="IPMB">
+ <ID>20</ID>
+ <LUN>0</LUN>
+ <Channel>0</Channel>
+ </SensorOwner>
+ <SensorNumber>1</SensorNumber>
+ </Key>
+ <Entity>
+ <ID>55</ID>
+ <Instance>0</Instance>
+ <Name>Ambient</Name>
+ </Entity>
+ <Sensor>
+ <Type>1</Type>
+ <TypeName>Temperature</TypeName>
+ <BaseUnit>1</BaseUnit>
+ <ModifierUnit>0</ModifierUnit>
+ <BaseUnitName>degree C</BaseUnitName>
+ <ModifierUnitName>unspecified</ModifierUnitName>
+ <Thresholds>
+ <UpperCritical>
+ <Raw>168</Raw>
+ <Normalized>42</Normalized>
+ </UpperCritical>
+ <LowerCritical>
+ <Raw>4</Raw>
+ <Normalized>1</Normalized>
+ </LowerCritical>
+ <UpperNonCritical>
+ <Raw>148</Raw>
+ <Normalized>37</Normalized>
+ </UpperNonCritical>
+ <LowerNonCritical>
+ <Raw>24</Raw>
+ <Normalized>6</Normalized>
+ </LowerNonCritical>
+ </Thresholds>
+ </Sensor>
+ </Decoded>
+ </Data>
+ </SDR>
+ <SDR Nr="2" RecordID="2" RecordType="1" RecordTypeName="Full SDR" Version="1.5" Length="64">
+ <Data Size="64">
+ <Decoded>
+ <Key>
+ <SensorOwner Type="IPMB">
+ <ID>20</ID>
+ <LUN>0</LUN>
+ <Channel>0</Channel>
+ </SensorOwner>
+ <SensorNumber>2</SensorNumber>
+ </Key>
+ <Entity>
+ <ID>7</ID>
+ <Instance>0</Instance>
+ <Name>Systemboard 1</Name>
+ </Entity>
+ <Sensor>
+ <Type>1</Type>
+ <TypeName>Temperature</TypeName>
+ <BaseUnit>1</BaseUnit>
+ <ModifierUnit>0</ModifierUnit>
+ <BaseUnitName>degree C</BaseUnitName>
+ <ModifierUnitName>unspecified</ModifierUnitName>
+ <Thresholds>
+ <UpperCritical>
+ <Raw>80</Raw>
+ <Normalized>80</Normalized>
+ </UpperCritical>
+ <UpperNonCritical>
+ <Raw>75</Raw>
+ <Normalized>75</Normalized>
+ </UpperNonCritical>
+ </Thresholds>
+ </Sensor>
+ </Decoded>
+ </Data>
+ </SDR>
+ <SDR Nr="34" RecordID="34" RecordType="1" RecordTypeName="Full SDR" Version="1.5" Length="64">
+ <Data Size="64">
+ <Decoded>
+ <Key>
+ <SensorOwner Type="IPMB">
+ <ID>20</ID>
+ <LUN>0</LUN>
+ <Channel>0</Channel>
+ </SensorOwner>
+ <SensorNumber>35</SensorNumber>
+ </Key>
+ <Entity>
+ <ID>29</ID>
+ <Instance>0</Instance>
+ <Name>FAN1 SYS</Name>
+ </Entity>
+ <Sensor>
+ <Type>4</Type>
+ <TypeName>Fan</TypeName>
+ <BaseUnit>18</BaseUnit>
+ <ModifierUnit>0</ModifierUnit>
+ <BaseUnitName>RPM</BaseUnitName>
+ <ModifierUnitName>unspecified</ModifierUnitName>
+ <Thresholds>
+ <LowerCritical>
+ <Raw>10</Raw>
+ <Normalized>600</Normalized>
+ </LowerCritical>
+ </Thresholds>
+ </Sensor>
+ </Decoded>
+ </Data>
+ </SDR>
+ <SDR Nr="35" RecordID="35" RecordType="1" RecordTypeName="Full SDR" Version="1.5" Length="64">
+ <Data Size="64">
+ <Decoded>
+ <Key>
+ <SensorOwner Type="IPMB">
+ <ID>20</ID>
+ <LUN>0</LUN>
+ <Channel>0</Channel>
+ </SensorOwner>
+ <SensorNumber>36</SensorNumber>
+ </Key>
+ <Entity>
+ <ID>29</ID>
+ <Instance>1</Instance>
+ <Name>FAN2 SYS</Name>
+ </Entity>
+ <Sensor>
+ <Type>4</Type>
+ <TypeName>Fan</TypeName>
+ <BaseUnit>18</BaseUnit>
+ <ModifierUnit>0</ModifierUnit>
+ <!-- <BaseUnitName>RPM</BaseUnitName> -->
+ <ModifierUnitName>unspecified</ModifierUnitName>
+ <Thresholds>
+ <LowerCritical>
+ <Raw>10</Raw>
+ <Normalized></Normalized>
+ </LowerCritical>
+ </Thresholds>
+ </Sensor>
+ </Decoded>
+ </Data>
+ </SDR>
+ </SensorDataRecords>
+ </System>
+</Root>
diff --git a/ironic/tests/unit/drivers/modules/irmc/test_common.py b/ironic/tests/unit/drivers/modules/irmc/test_common.py
new file mode 100644
index 000000000..0876e76d0
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/irmc/test_common.py
@@ -0,0 +1,168 @@
+# Copyright 2015 FUJITSU LIMITED
+#
+# 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.
+
+"""
+Test class for common methods used by iRMC modules.
+"""
+
+import mock
+
+from ironic.common import exception
+from ironic.conductor import task_manager
+from ironic.drivers.modules.irmc import common as irmc_common
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.drivers import third_party_driver_mock_specs \
+ as mock_specs
+from ironic.tests.unit.objects import utils as obj_utils
+
+
+class IRMCValidateParametersTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IRMCValidateParametersTestCase, self).setUp()
+ self.node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_irmc',
+ driver_info=db_utils.get_test_irmc_info())
+
+ def test_parse_driver_info(self):
+ info = irmc_common.parse_driver_info(self.node)
+
+ self.assertIsNotNone(info.get('irmc_address'))
+ self.assertIsNotNone(info.get('irmc_username'))
+ self.assertIsNotNone(info.get('irmc_password'))
+ self.assertIsNotNone(info.get('irmc_client_timeout'))
+ self.assertIsNotNone(info.get('irmc_port'))
+ self.assertIsNotNone(info.get('irmc_auth_method'))
+ self.assertIsNotNone(info.get('irmc_sensor_method'))
+
+ def test_parse_driver_option_default(self):
+ self.node.driver_info = {
+ "irmc_address": "1.2.3.4",
+ "irmc_username": "admin0",
+ "irmc_password": "fake0",
+ }
+ info = irmc_common.parse_driver_info(self.node)
+
+ self.assertEqual('basic', info.get('irmc_auth_method'))
+ self.assertEqual(443, info.get('irmc_port'))
+ self.assertEqual(60, info.get('irmc_client_timeout'))
+ self.assertEqual('ipmitool', info.get('irmc_sensor_method'))
+
+ def test_parse_driver_info_missing_address(self):
+ del self.node.driver_info['irmc_address']
+ self.assertRaises(exception.MissingParameterValue,
+ irmc_common.parse_driver_info, self.node)
+
+ def test_parse_driver_info_missing_username(self):
+ del self.node.driver_info['irmc_username']
+ self.assertRaises(exception.MissingParameterValue,
+ irmc_common.parse_driver_info, self.node)
+
+ def test_parse_driver_info_missing_password(self):
+ del self.node.driver_info['irmc_password']
+ self.assertRaises(exception.MissingParameterValue,
+ irmc_common.parse_driver_info, self.node)
+
+ def test_parse_driver_info_invalid_timeout(self):
+ self.node.driver_info['irmc_client_timeout'] = 'qwe'
+ self.assertRaises(exception.InvalidParameterValue,
+ irmc_common.parse_driver_info, self.node)
+
+ def test_parse_driver_info_invalid_port(self):
+ self.node.driver_info['irmc_port'] = 'qwe'
+ self.assertRaises(exception.InvalidParameterValue,
+ irmc_common.parse_driver_info, self.node)
+
+ def test_parse_driver_info_invalid_auth_method(self):
+ self.node.driver_info['irmc_auth_method'] = 'qwe'
+ self.assertRaises(exception.InvalidParameterValue,
+ irmc_common.parse_driver_info, self.node)
+
+ def test_parse_driver_info_invalid_sensor_method(self):
+ self.node.driver_info['irmc_sensor_method'] = 'qwe'
+ self.assertRaises(exception.InvalidParameterValue,
+ irmc_common.parse_driver_info, self.node)
+
+ def test_parse_driver_info_missing_multiple_params(self):
+ del self.node.driver_info['irmc_password']
+ del self.node.driver_info['irmc_address']
+ try:
+ irmc_common.parse_driver_info(self.node)
+ self.fail("parse_driver_info did not throw exception.")
+ except exception.MissingParameterValue as e:
+ self.assertIn('irmc_password', str(e))
+ self.assertIn('irmc_address', str(e))
+
+
+class IRMCCommonMethodsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IRMCCommonMethodsTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="fake_irmc")
+ self.info = db_utils.get_test_irmc_info()
+ self.node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_irmc',
+ driver_info=self.info)
+
+ @mock.patch.object(irmc_common, 'scci',
+ spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC)
+ def test_get_irmc_client(self, mock_scci):
+ self.info['irmc_port'] = 80
+ self.info['irmc_auth_method'] = 'digest'
+ self.info['irmc_client_timeout'] = 60
+ mock_scci.get_client.return_value = 'get_client'
+ returned_mock_scci_get_client = irmc_common.get_irmc_client(self.node)
+ mock_scci.get_client.assert_called_with(
+ self.info['irmc_address'],
+ self.info['irmc_username'],
+ self.info['irmc_password'],
+ port=self.info['irmc_port'],
+ auth_method=self.info['irmc_auth_method'],
+ client_timeout=self.info['irmc_client_timeout'])
+ self.assertEqual('get_client', returned_mock_scci_get_client)
+
+ def test_update_ipmi_properties(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ipmi_info = {
+ "ipmi_address": "1.2.3.4",
+ "ipmi_username": "admin0",
+ "ipmi_password": "fake0",
+ }
+ task.node.driver_info = self.info
+ irmc_common.update_ipmi_properties(task)
+ actual_info = task.node.driver_info
+ expected_info = dict(self.info, **ipmi_info)
+ self.assertEqual(expected_info, actual_info)
+
+ @mock.patch.object(irmc_common, 'scci',
+ spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC)
+ def test_get_irmc_report(self, mock_scci):
+ self.info['irmc_port'] = 80
+ self.info['irmc_auth_method'] = 'digest'
+ self.info['irmc_client_timeout'] = 60
+ mock_scci.get_report.return_value = 'get_report'
+ returned_mock_scci_get_report = irmc_common.get_irmc_report(self.node)
+ mock_scci.get_report.assert_called_with(
+ self.info['irmc_address'],
+ self.info['irmc_username'],
+ self.info['irmc_password'],
+ port=self.info['irmc_port'],
+ auth_method=self.info['irmc_auth_method'],
+ client_timeout=self.info['irmc_client_timeout'])
+ self.assertEqual('get_report', returned_mock_scci_get_report)
diff --git a/ironic/tests/unit/drivers/modules/irmc/test_deploy.py b/ironic/tests/unit/drivers/modules/irmc/test_deploy.py
new file mode 100644
index 000000000..bba950cbf
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/irmc/test_deploy.py
@@ -0,0 +1,1536 @@
+# Copyright 2015 FUJITSU LIMITED
+#
+# 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.
+
+"""
+Test class for iRMC Deploy Driver
+"""
+
+import os
+import shutil
+import tempfile
+
+import mock
+from oslo_config import cfg
+import six
+
+from ironic.common import boot_devices
+from ironic.common import exception
+from ironic.common.glance_service import service_utils
+from ironic.common.i18n import _
+from ironic.common import images
+from ironic.common import states
+from ironic.common import utils
+from ironic.conductor import task_manager
+from ironic.conductor import utils as manager_utils
+from ironic.drivers.modules import agent
+from ironic.drivers.modules import agent_base_vendor
+from ironic.drivers.modules import deploy_utils
+from ironic.drivers.modules.irmc import common as irmc_common
+from ironic.drivers.modules.irmc import deploy as irmc_deploy
+from ironic.drivers.modules import iscsi_deploy
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+
+if six.PY3:
+ import io
+ file = io.BytesIO
+
+
+INFO_DICT = db_utils.get_test_irmc_info()
+CONF = cfg.CONF
+
+
+class IRMCDeployPrivateMethodsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ irmc_deploy._check_share_fs_mounted_patcher.start()
+ super(IRMCDeployPrivateMethodsTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='iscsi_irmc')
+ self.node = obj_utils.create_test_node(
+ self.context, driver='iscsi_irmc', driver_info=INFO_DICT)
+
+ CONF.irmc.remote_image_share_root = '/remote_image_share_root'
+ CONF.irmc.remote_image_server = '10.20.30.40'
+ CONF.irmc.remote_image_share_type = 'NFS'
+ CONF.irmc.remote_image_share_name = 'share'
+ CONF.irmc.remote_image_user_name = 'admin'
+ CONF.irmc.remote_image_user_password = 'admin0'
+ CONF.irmc.remote_image_user_domain = 'local'
+
+ @mock.patch.object(os.path, 'isdir', spec_set=True, autospec=True)
+ def test__parse_config_option(self, isdir_mock):
+ isdir_mock.return_value = True
+
+ result = irmc_deploy._parse_config_option()
+
+ isdir_mock.assert_called_once_with('/remote_image_share_root')
+ self.assertIsNone(result)
+
+ @mock.patch.object(os.path, 'isdir', spec_set=True, autospec=True)
+ def test__parse_config_option_non_existed_root(self, isdir_mock):
+ CONF.irmc.remote_image_share_root = '/non_existed_root'
+ isdir_mock.return_value = False
+
+ self.assertRaises(exception.InvalidParameterValue,
+ irmc_deploy._parse_config_option)
+ isdir_mock.assert_called_once_with('/non_existed_root')
+
+ @mock.patch.object(os.path, 'isdir', spec_set=True, autospec=True)
+ def test__parse_config_option_wrong_share_type(self, isdir_mock):
+ CONF.irmc.remote_image_share_type = 'NTFS'
+ isdir_mock.return_value = True
+
+ self.assertRaises(exception.InvalidParameterValue,
+ irmc_deploy._parse_config_option)
+ isdir_mock.assert_called_once_with('/remote_image_share_root')
+
+ @mock.patch.object(os.path, 'isfile', spec_set=True, autospec=True)
+ def test__parse_driver_info_in_share(self, isfile_mock):
+ """With required 'irmc_deploy_iso' in share."""
+ isfile_mock.return_value = True
+ self.node.driver_info['irmc_deploy_iso'] = 'deploy.iso'
+ driver_info_expected = {'irmc_deploy_iso': 'deploy.iso'}
+
+ driver_info_actual = irmc_deploy._parse_driver_info(self.node)
+
+ isfile_mock.assert_called_once_with(
+ '/remote_image_share_root/deploy.iso')
+ self.assertEqual(driver_info_expected, driver_info_actual)
+
+ @mock.patch.object(service_utils, 'is_image_href_ordinary_file_name',
+ spec_set=True, autospec=True)
+ def test__parse_driver_info_not_in_share(
+ self, is_image_href_ordinary_file_name_mock):
+ """With required 'irmc_deploy_iso' not in share."""
+ self.node.driver_info[
+ 'irmc_deploy_iso'] = 'bc784057-a140-4130-add3-ef890457e6b3'
+ driver_info_expected = {'irmc_deploy_iso':
+ 'bc784057-a140-4130-add3-ef890457e6b3'}
+ is_image_href_ordinary_file_name_mock.return_value = False
+
+ driver_info_actual = irmc_deploy._parse_driver_info(self.node)
+
+ self.assertEqual(driver_info_expected, driver_info_actual)
+
+ @mock.patch.object(os.path, 'isfile', spec_set=True, autospec=True)
+ def test__parse_driver_info_with_deploy_iso_invalid(self, isfile_mock):
+ """With required 'irmc_deploy_iso' non existed."""
+ isfile_mock.return_value = False
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node.driver_info['irmc_deploy_iso'] = 'deploy.iso'
+ error_msg = (_("Deploy ISO file, %(deploy_iso)s, "
+ "not found for node: %(node)s.") %
+ {'deploy_iso': '/remote_image_share_root/deploy.iso',
+ 'node': task.node.uuid})
+
+ e = self.assertRaises(exception.InvalidParameterValue,
+ irmc_deploy._parse_driver_info,
+ task.node)
+ self.assertEqual(error_msg, str(e))
+
+ def test__parse_driver_info_with_deploy_iso_missing(self):
+ """With required 'irmc_deploy_iso' empty."""
+ self.node.driver_info['irmc_deploy_iso'] = None
+
+ error_msg = ("Error validating iRMC virtual media deploy. Some"
+ " parameters were missing in node's driver_info."
+ " Missing are: ['irmc_deploy_iso']")
+ e = self.assertRaises(exception.MissingParameterValue,
+ irmc_deploy._parse_driver_info,
+ self.node)
+ self.assertEqual(error_msg, str(e))
+
+ def test__parse_instance_info_with_boot_iso_file_name_ok(self):
+ """With optional 'irmc_boot_iso' file name."""
+ CONF.irmc.remote_image_share_root = '/etc'
+ self.node.instance_info['irmc_boot_iso'] = 'hosts'
+ instance_info_expected = {'irmc_boot_iso': 'hosts'}
+ instance_info_actual = irmc_deploy._parse_instance_info(self.node)
+
+ self.assertEqual(instance_info_expected, instance_info_actual)
+
+ def test__parse_instance_info_without_boot_iso_ok(self):
+ """With optional no 'irmc_boot_iso' file name."""
+ CONF.irmc.remote_image_share_root = '/etc'
+
+ self.node.instance_info['irmc_boot_iso'] = None
+ instance_info_expected = {}
+ instance_info_actual = irmc_deploy._parse_instance_info(self.node)
+
+ self.assertEqual(instance_info_expected, instance_info_actual)
+
+ def test__parse_instance_info_with_boot_iso_uuid_ok(self):
+ """With optional 'irmc_boot_iso' glance uuid."""
+ self.node.instance_info[
+ 'irmc_boot_iso'] = 'bc784057-a140-4130-add3-ef890457e6b3'
+ instance_info_expected = {'irmc_boot_iso':
+ 'bc784057-a140-4130-add3-ef890457e6b3'}
+ instance_info_actual = irmc_deploy._parse_instance_info(self.node)
+
+ self.assertEqual(instance_info_expected, instance_info_actual)
+
+ def test__parse_instance_info_with_boot_iso_glance_ok(self):
+ """With optional 'irmc_boot_iso' glance url."""
+ self.node.instance_info['irmc_boot_iso'] = (
+ 'glance://bc784057-a140-4130-add3-ef890457e6b3')
+ instance_info_expected = {
+ 'irmc_boot_iso': 'glance://bc784057-a140-4130-add3-ef890457e6b3',
+ }
+ instance_info_actual = irmc_deploy._parse_instance_info(self.node)
+
+ self.assertEqual(instance_info_expected, instance_info_actual)
+
+ def test__parse_instance_info_with_boot_iso_http_ok(self):
+ """With optional 'irmc_boot_iso' http url."""
+ self.node.driver_info[
+ 'irmc_deploy_iso'] = 'http://irmc_boot_iso'
+ driver_info_expected = {'irmc_deploy_iso': 'http://irmc_boot_iso'}
+ driver_info_actual = irmc_deploy._parse_driver_info(self.node)
+
+ self.assertEqual(driver_info_expected, driver_info_actual)
+
+ def test__parse_instance_info_with_boot_iso_https_ok(self):
+ """With optional 'irmc_boot_iso' https url."""
+ self.node.instance_info[
+ 'irmc_boot_iso'] = 'https://irmc_boot_iso'
+ instance_info_expected = {'irmc_boot_iso': 'https://irmc_boot_iso'}
+ instance_info_actual = irmc_deploy._parse_instance_info(self.node)
+
+ self.assertEqual(instance_info_expected, instance_info_actual)
+
+ def test__parse_instance_info_with_boot_iso_file_url_ok(self):
+ """With optional 'irmc_boot_iso' file url."""
+ self.node.instance_info[
+ 'irmc_boot_iso'] = 'file://irmc_boot_iso'
+ instance_info_expected = {'irmc_boot_iso': 'file://irmc_boot_iso'}
+ instance_info_actual = irmc_deploy._parse_instance_info(self.node)
+
+ self.assertEqual(instance_info_expected, instance_info_actual)
+
+ @mock.patch.object(os.path, 'isfile', spec_set=True, autospec=True)
+ def test__parse_instance_info_with_boot_iso_invalid(self, isfile_mock):
+ CONF.irmc.remote_image_share_root = '/etc'
+ isfile_mock.return_value = False
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node.instance_info['irmc_boot_iso'] = 'hosts~non~existed'
+
+ error_msg = (_("Boot ISO file, %(boot_iso)s, "
+ "not found for node: %(node)s.") %
+ {'boot_iso': '/etc/hosts~non~existed',
+ 'node': task.node.uuid})
+
+ e = self.assertRaises(exception.InvalidParameterValue,
+ irmc_deploy._parse_instance_info,
+ task.node)
+ self.assertEqual(error_msg, str(e))
+
+ @mock.patch.object(iscsi_deploy, 'parse_instance_info', spec_set=True,
+ autospec=True)
+ def test__parse_deploy_info_ok(self, instance_info_mock):
+ CONF.irmc.remote_image_share_root = '/etc'
+ instance_info_mock.return_value = {'a': 'b'}
+ driver_info_expected = {'a': 'b',
+ 'irmc_deploy_iso': 'hosts',
+ 'irmc_boot_iso': 'fstab'}
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node.driver_info['irmc_deploy_iso'] = 'hosts'
+ task.node.instance_info['irmc_boot_iso'] = 'fstab'
+ driver_info_actual = irmc_deploy._parse_deploy_info(task.node)
+ self.assertEqual(driver_info_expected, driver_info_actual)
+
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, 'setup_vmedia_for_boot', spec_set=True,
+ autospec=True)
+ @mock.patch.object(images, 'fetch', spec_set=True,
+ autospec=True)
+ def test__reboot_into_deploy_iso_with_file(self,
+ fetch_mock,
+ setup_vmedia_mock,
+ set_boot_device_mock,
+ node_power_action_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.driver_info['irmc_deploy_iso'] = 'deploy_iso_filename'
+ ramdisk_opts = {'a': 'b'}
+ irmc_deploy._reboot_into_deploy_iso(task, ramdisk_opts)
+
+ self.assertFalse(fetch_mock.called)
+
+ setup_vmedia_mock.assert_called_once_with(
+ task,
+ 'deploy_iso_filename',
+ ramdisk_opts)
+ set_boot_device_mock.assert_called_once_with(task,
+ boot_devices.CDROM)
+ node_power_action_mock.assert_called_once_with(task, states.REBOOT)
+
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, 'setup_vmedia_for_boot', spec_set=True,
+ autospec=True)
+ @mock.patch.object(images, 'fetch', spec_set=True,
+ autospec=True)
+ @mock.patch.object(service_utils, 'is_image_href_ordinary_file_name',
+ spec_set=True, autospec=True)
+ def test__reboot_into_deploy_iso_with_image_service(
+ self,
+ is_image_href_ordinary_file_name_mock,
+ fetch_mock,
+ setup_vmedia_mock,
+ set_boot_device_mock,
+ node_power_action_mock):
+ CONF.irmc.remote_image_share_root = '/'
+ is_image_href_ordinary_file_name_mock.return_value = False
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.driver_info['irmc_deploy_iso'] = 'glance://deploy_iso'
+ ramdisk_opts = {'a': 'b'}
+ irmc_deploy._reboot_into_deploy_iso(task, ramdisk_opts)
+
+ fetch_mock.assert_called_once_with(
+ task.context,
+ 'glance://deploy_iso',
+ "/deploy-%s.iso" % self.node.uuid)
+
+ setup_vmedia_mock.assert_called_once_with(
+ task,
+ "deploy-%s.iso" % self.node.uuid,
+ ramdisk_opts)
+ set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.CDROM)
+ node_power_action_mock.assert_called_once_with(
+ task, states.REBOOT)
+
+ def test__get_deploy_iso_name(self):
+ actual = irmc_deploy._get_deploy_iso_name(self.node)
+ expected = "deploy-%s.iso" % self.node.uuid
+ self.assertEqual(expected, actual)
+
+ def test__get_boot_iso_name(self):
+ actual = irmc_deploy._get_boot_iso_name(self.node)
+ expected = "boot-%s.iso" % self.node.uuid
+ self.assertEqual(expected, actual)
+
+ @mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True)
+ @mock.patch.object(deploy_utils, 'get_boot_mode_for_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(images, 'get_image_properties', spec_set=True,
+ autospec=True)
+ @mock.patch.object(images, 'fetch', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_parse_deploy_info', spec_set=True,
+ autospec=True)
+ def test__prepare_boot_iso_file(self,
+ deploy_info_mock,
+ fetch_mock,
+ image_props_mock,
+ boot_mode_mock,
+ create_boot_iso_mock):
+ deploy_info_mock.return_value = {'irmc_boot_iso': 'irmc_boot.iso'}
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ irmc_deploy._prepare_boot_iso(task, 'root-uuid')
+
+ deploy_info_mock.assert_called_once_with(task.node)
+ self.assertFalse(fetch_mock.called)
+ self.assertFalse(image_props_mock.called)
+ self.assertFalse(boot_mode_mock.called)
+ self.assertFalse(create_boot_iso_mock.called)
+ task.node.refresh()
+ self.assertEqual('irmc_boot.iso',
+ task.node.driver_internal_info['irmc_boot_iso'])
+
+ @mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True)
+ @mock.patch.object(deploy_utils, 'get_boot_mode_for_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(images, 'get_image_properties', spec_set=True,
+ autospec=True)
+ @mock.patch.object(images, 'fetch', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_parse_deploy_info', spec_set=True,
+ autospec=True)
+ @mock.patch.object(service_utils, 'is_image_href_ordinary_file_name',
+ spec_set=True, autospec=True)
+ def test__prepare_boot_iso_fetch_ok(self,
+ is_image_href_ordinary_file_name_mock,
+ deploy_info_mock,
+ fetch_mock,
+ image_props_mock,
+ boot_mode_mock,
+ create_boot_iso_mock):
+
+ CONF.irmc.remote_image_share_root = '/'
+ image = '733d1c44-a2ea-414b-aca7-69decf20d810'
+ is_image_href_ordinary_file_name_mock.return_value = False
+ deploy_info_mock.return_value = {'irmc_boot_iso': image}
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.instance_info['irmc_boot_iso'] = image
+ irmc_deploy._prepare_boot_iso(task, 'root-uuid')
+
+ deploy_info_mock.assert_called_once_with(task.node)
+ fetch_mock.assert_called_once_with(
+ task.context,
+ image,
+ "/boot-%s.iso" % self.node.uuid)
+ self.assertFalse(image_props_mock.called)
+ self.assertFalse(boot_mode_mock.called)
+ self.assertFalse(create_boot_iso_mock.called)
+ task.node.refresh()
+ self.assertEqual("boot-%s.iso" % self.node.uuid,
+ task.node.driver_internal_info['irmc_boot_iso'])
+
+ @mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True)
+ @mock.patch.object(deploy_utils, 'get_boot_mode_for_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(images, 'get_image_properties', spec_set=True,
+ autospec=True)
+ @mock.patch.object(images, 'fetch', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_parse_deploy_info', spec_set=True,
+ autospec=True)
+ def test__prepare_boot_iso_create_ok(self,
+ deploy_info_mock,
+ fetch_mock,
+ image_props_mock,
+ boot_mode_mock,
+ create_boot_iso_mock):
+ CONF.pxe.pxe_append_params = 'kernel-params'
+
+ deploy_info_mock.return_value = {'image_source': 'image-uuid'}
+ image_props_mock.return_value = {'kernel_id': 'kernel_uuid',
+ 'ramdisk_id': 'ramdisk_uuid'}
+
+ CONF.irmc.remote_image_share_name = '/remote_image_share_root'
+ boot_mode_mock.return_value = 'uefi'
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ irmc_deploy._prepare_boot_iso(task, 'root-uuid')
+
+ self.assertFalse(fetch_mock.called)
+ deploy_info_mock.assert_called_once_with(task.node)
+ image_props_mock.assert_called_once_with(
+ task.context, 'image-uuid', ['kernel_id', 'ramdisk_id'])
+ create_boot_iso_mock.assert_called_once_with(
+ task.context,
+ '/remote_image_share_root/' +
+ "boot-%s.iso" % self.node.uuid,
+ 'kernel_uuid', 'ramdisk_uuid',
+ 'file:///remote_image_share_root/' +
+ "deploy-%s.iso" % self.node.uuid,
+ 'root-uuid', 'kernel-params', 'uefi')
+ task.node.refresh()
+ self.assertEqual("boot-%s.iso" % self.node.uuid,
+ task.node.driver_internal_info['irmc_boot_iso'])
+
+ def test__get_floppy_image_name(self):
+ actual = irmc_deploy._get_floppy_image_name(self.node)
+ expected = "image-%s.img" % self.node.uuid
+ self.assertEqual(expected, actual)
+
+ @mock.patch.object(shutil, 'copyfile', spec_set=True, autospec=True)
+ @mock.patch.object(images, 'create_vfat_image', spec_set=True,
+ autospec=True)
+ @mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True,
+ autospec=True)
+ def test__prepare_floppy_image(self,
+ tempfile_mock,
+ create_vfat_image_mock,
+ copyfile_mock):
+ mock_image_file_handle = mock.MagicMock(spec=file)
+ mock_image_file_obj = mock.MagicMock()
+ mock_image_file_obj.name = 'image-tmp-file'
+ mock_image_file_handle.__enter__.return_value = mock_image_file_obj
+ tempfile_mock.side_effect = iter([mock_image_file_handle])
+
+ deploy_args = {'arg1': 'val1', 'arg2': 'val2'}
+ CONF.irmc.remote_image_share_name = '/remote_image_share_root'
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ irmc_deploy._prepare_floppy_image(task, deploy_args)
+
+ create_vfat_image_mock.assert_called_once_with(
+ 'image-tmp-file', parameters=deploy_args)
+ copyfile_mock.assert_called_once_with(
+ 'image-tmp-file',
+ '/remote_image_share_root/' + "image-%s.img" % self.node.uuid)
+
+ @mock.patch.object(shutil, 'copyfile', spec_set=True, autospec=True)
+ @mock.patch.object(images, 'create_vfat_image', spec_set=True,
+ autospec=True)
+ @mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True,
+ autospec=True)
+ def test__prepare_floppy_image_exception(self,
+ tempfile_mock,
+ create_vfat_image_mock,
+ copyfile_mock):
+ mock_image_file_handle = mock.MagicMock(spec=file)
+ mock_image_file_obj = mock.MagicMock()
+ mock_image_file_obj.name = 'image-tmp-file'
+ mock_image_file_handle.__enter__.return_value = mock_image_file_obj
+ tempfile_mock.side_effect = iter([mock_image_file_handle])
+
+ deploy_args = {'arg1': 'val1', 'arg2': 'val2'}
+ CONF.irmc.remote_image_share_name = '/remote_image_share_root'
+ copyfile_mock.side_effect = iter([IOError("fake error")])
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.IRMCOperationError,
+ irmc_deploy._prepare_floppy_image,
+ task,
+ deploy_args)
+
+ create_vfat_image_mock.assert_called_once_with(
+ 'image-tmp-file', parameters=deploy_args)
+ copyfile_mock.assert_called_once_with(
+ 'image-tmp-file',
+ '/remote_image_share_root/' + "image-%s.img" % self.node.uuid)
+
+ @mock.patch.object(irmc_deploy, '_attach_virtual_cd', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_attach_virtual_fd', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_prepare_floppy_image', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_detach_virtual_fd', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_detach_virtual_cd', spec_set=True,
+ autospec=True)
+ def test_setup_vmedia_for_boot_with_parameters(self,
+ _detach_virtual_cd_mock,
+ _detach_virtual_fd_mock,
+ _prepare_floppy_image_mock,
+ _attach_virtual_fd_mock,
+ _attach_virtual_cd_mock):
+ parameters = {'a': 'b'}
+ iso_filename = 'deploy_iso_or_boot_iso'
+ _prepare_floppy_image_mock.return_value = 'floppy_file_name'
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ irmc_deploy.setup_vmedia_for_boot(task, iso_filename, parameters)
+
+ _detach_virtual_cd_mock.assert_called_once_with(task.node)
+ _detach_virtual_fd_mock.assert_called_once_with(task.node)
+ _prepare_floppy_image_mock.assert_called_once_with(task,
+ parameters)
+ _attach_virtual_fd_mock.assert_called_once_with(task.node,
+ 'floppy_file_name')
+ _attach_virtual_cd_mock.assert_called_once_with(task.node,
+ iso_filename)
+
+ @mock.patch.object(irmc_deploy, '_attach_virtual_cd', autospec=True)
+ @mock.patch.object(irmc_deploy, '_detach_virtual_fd', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_detach_virtual_cd', spec_set=True,
+ autospec=True)
+ def test_setup_vmedia_for_boot_without_parameters(
+ self,
+ _detach_virtual_cd_mock,
+ _detach_virtual_fd_mock,
+ _attach_virtual_cd_mock):
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ irmc_deploy.setup_vmedia_for_boot(task, 'bootable_iso_filename')
+
+ _detach_virtual_cd_mock.assert_called_once_with(task.node)
+ _detach_virtual_fd_mock.assert_called_once_with(task.node)
+ _attach_virtual_cd_mock.assert_called_once_with(
+ task.node,
+ 'bootable_iso_filename')
+
+ @mock.patch.object(irmc_deploy, '_get_deploy_iso_name', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_get_floppy_image_name', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_remove_share_file', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_detach_virtual_fd', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_detach_virtual_cd', spec_set=True,
+ autospec=True)
+ def test__cleanup_vmedia_boot_ok(self,
+ _detach_virtual_cd_mock,
+ _detach_virtual_fd_mock,
+ _remove_share_file_mock,
+ _get_floppy_image_name_mock,
+ _get_deploy_iso_name_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ irmc_deploy._cleanup_vmedia_boot(task)
+
+ _detach_virtual_cd_mock.assert_called_once_with(task.node)
+ _detach_virtual_fd_mock.assert_called_once_with(task.node)
+ _get_floppy_image_name_mock.assert_called_once_with(task.node)
+ _get_deploy_iso_name_mock.assert_called_once_with(task.node)
+ self.assertTrue(_remove_share_file_mock.call_count, 2)
+ _remove_share_file_mock.assert_has_calls(
+ [mock.call(_get_floppy_image_name_mock(task.node)),
+ mock.call(_get_deploy_iso_name_mock(task.node))])
+
+ @mock.patch.object(utils, 'unlink_without_raise', spec_set=True,
+ autospec=True)
+ def test__remove_share_file(self, unlink_without_raise_mock):
+ CONF.irmc.remote_image_share_name = '/'
+
+ irmc_deploy._remove_share_file("boot.iso")
+
+ unlink_without_raise_mock.assert_called_once_with('/boot.iso')
+
+ @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True,
+ autospec=True)
+ def test__attach_virtual_cd_ok(self, get_irmc_client_mock):
+ irmc_client = get_irmc_client_mock.return_value
+ irmc_deploy.scci.get_virtual_cd_set_params_cmd = (
+ mock.MagicMock(sepc_set=[]))
+ cd_set_params = (irmc_deploy.scci
+ .get_virtual_cd_set_params_cmd.return_value)
+
+ CONF.irmc.remote_image_server = '10.20.30.40'
+ CONF.irmc.remote_image_user_domain = 'local'
+ CONF.irmc.remote_image_share_type = 'NFS'
+ CONF.irmc.remote_image_share_name = 'share'
+ CONF.irmc.remote_image_user_name = 'admin'
+ CONF.irmc.remote_image_user_password = 'admin0'
+
+ irmc_deploy.scci.get_share_type.return_value = 0
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ irmc_deploy._attach_virtual_cd(task.node, 'iso_filename')
+
+ get_irmc_client_mock.assert_called_once_with(task.node)
+ (irmc_deploy.scci.get_virtual_cd_set_params_cmd
+ .assert_called_once_with)('10.20.30.40',
+ 'local',
+ 0,
+ 'share',
+ 'iso_filename',
+ 'admin',
+ 'admin0')
+ irmc_client.assert_has_calls(
+ [mock.call(cd_set_params, async=False),
+ mock.call(irmc_deploy.scci.MOUNT_CD, async=False)])
+
+ @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True,
+ autospec=True)
+ def test__attach_virtual_cd_fail(self, get_irmc_client_mock):
+ irmc_client = get_irmc_client_mock.return_value
+ irmc_client.side_effect = Exception("fake error")
+ irmc_deploy.scci.SCCIClientError = Exception
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ e = self.assertRaises(exception.IRMCOperationError,
+ irmc_deploy._attach_virtual_cd,
+ task.node,
+ 'iso_filename')
+ get_irmc_client_mock.assert_called_once_with(task.node)
+ self.assertEqual("iRMC Inserting virtual cdrom failed. " +
+ "Reason: fake error", str(e))
+
+ @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True,
+ autospec=True)
+ def test__detach_virtual_cd_ok(self, get_irmc_client_mock):
+ irmc_client = get_irmc_client_mock.return_value
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ irmc_deploy._detach_virtual_cd(task.node)
+
+ irmc_client.assert_called_once_with(irmc_deploy.scci.UNMOUNT_CD)
+
+ @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True,
+ autospec=True)
+ def test__detach_virtual_cd_fail(self, get_irmc_client_mock):
+ irmc_client = get_irmc_client_mock.return_value
+ irmc_client.side_effect = Exception("fake error")
+ irmc_deploy.scci.SCCIClientError = Exception
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ e = self.assertRaises(exception.IRMCOperationError,
+ irmc_deploy._detach_virtual_cd,
+ task.node)
+ self.assertEqual("iRMC Ejecting virtual cdrom failed. " +
+ "Reason: fake error", str(e))
+
+ @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True,
+ autospec=True)
+ def test__attach_virtual_fd_ok(self, get_irmc_client_mock):
+ irmc_client = get_irmc_client_mock.return_value
+ irmc_deploy.scci.get_virtual_fd_set_params_cmd = (
+ mock.MagicMock(sepc_set=[]))
+ fd_set_params = (irmc_deploy.scci
+ .get_virtual_fd_set_params_cmd.return_value)
+
+ CONF.irmc.remote_image_server = '10.20.30.40'
+ CONF.irmc.remote_image_user_domain = 'local'
+ CONF.irmc.remote_image_share_type = 'NFS'
+ CONF.irmc.remote_image_share_name = 'share'
+ CONF.irmc.remote_image_user_name = 'admin'
+ CONF.irmc.remote_image_user_password = 'admin0'
+
+ irmc_deploy.scci.get_share_type.return_value = 0
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ irmc_deploy._attach_virtual_fd(task.node,
+ 'floppy_image_filename')
+
+ get_irmc_client_mock.assert_called_once_with(task.node)
+ (irmc_deploy.scci.get_virtual_fd_set_params_cmd
+ .assert_called_once_with)('10.20.30.40',
+ 'local',
+ 0,
+ 'share',
+ 'floppy_image_filename',
+ 'admin',
+ 'admin0')
+ irmc_client.assert_has_calls(
+ [mock.call(fd_set_params, async=False),
+ mock.call(irmc_deploy.scci.MOUNT_FD, async=False)])
+
+ @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True,
+ autospec=True)
+ def test__attach_virtual_fd_fail(self, get_irmc_client_mock):
+ irmc_client = get_irmc_client_mock.return_value
+ irmc_client.side_effect = Exception("fake error")
+ irmc_deploy.scci.SCCIClientError = Exception
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ e = self.assertRaises(exception.IRMCOperationError,
+ irmc_deploy._attach_virtual_fd,
+ task.node,
+ 'iso_filename')
+ get_irmc_client_mock.assert_called_once_with(task.node)
+ self.assertEqual("iRMC Inserting virtual floppy failed. " +
+ "Reason: fake error", str(e))
+
+ @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True,
+ autospec=True)
+ def test__detach_virtual_fd_ok(self, get_irmc_client_mock):
+ irmc_client = get_irmc_client_mock.return_value
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ irmc_deploy._detach_virtual_fd(task.node)
+
+ irmc_client.assert_called_once_with(irmc_deploy.scci.UNMOUNT_FD)
+
+ @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True,
+ autospec=True)
+ def test__detach_virtual_fd_fail(self, get_irmc_client_mock):
+ irmc_client = get_irmc_client_mock.return_value
+ irmc_client.side_effect = Exception("fake error")
+ irmc_deploy.scci.SCCIClientError = Exception
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ e = self.assertRaises(exception.IRMCOperationError,
+ irmc_deploy._detach_virtual_fd,
+ task.node)
+ self.assertEqual("iRMC Ejecting virtual floppy failed. "
+ "Reason: fake error", str(e))
+
+ @mock.patch.object(irmc_deploy, '_parse_config_option', spec_set=True,
+ autospec=True)
+ def test__check_share_fs_mounted_ok(self, parse_conf_mock):
+ # Note(naohirot): mock.patch.stop() and mock.patch.start() don't work.
+ # therefor monkey patching is used to
+ # irmc_deploy._check_share_fs_mounted.
+ # irmc_deploy._check_share_fs_mounted is mocked in
+ # third_party_driver_mocks.py.
+ # irmc_deploy._check_share_fs_mounted_orig is the real function.
+ CONF.irmc.remote_image_share_root = '/'
+ CONF.irmc.remote_image_share_type = 'nfs'
+ result = irmc_deploy._check_share_fs_mounted_orig()
+
+ parse_conf_mock.assert_called_once_with()
+ self.assertIsNone(result)
+
+ @mock.patch.object(irmc_deploy, '_parse_config_option', spec_set=True,
+ autospec=True)
+ def test__check_share_fs_mounted_exception(self, parse_conf_mock):
+ # Note(naohirot): mock.patch.stop() and mock.patch.start() don't work.
+ # therefor monkey patching is used to
+ # irmc_deploy._check_share_fs_mounted.
+ # irmc_deploy._check_share_fs_mounted is mocked in
+ # third_party_driver_mocks.py.
+ # irmc_deploy._check_share_fs_mounted_orig is the real function.
+ CONF.irmc.remote_image_share_root = '/etc'
+ CONF.irmc.remote_image_share_type = 'cifs'
+
+ self.assertRaises(exception.IRMCSharedFileSystemNotMounted,
+ irmc_deploy._check_share_fs_mounted_orig)
+ parse_conf_mock.assert_called_once_with()
+
+
+class IRMCVirtualMediaIscsiDeployTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ irmc_deploy._check_share_fs_mounted_patcher.start()
+ super(IRMCVirtualMediaIscsiDeployTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="iscsi_irmc")
+ self.node = obj_utils.create_test_node(
+ self.context, driver='iscsi_irmc', driver_info=INFO_DICT)
+
+ @mock.patch.object(deploy_utils, 'validate_capabilities',
+ spec_set=True, autospec=True)
+ @mock.patch.object(deploy_utils, 'validate_image_properties',
+ spec_set=True, autospec=True)
+ @mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_parse_deploy_info', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'validate', spec_set=True, autospec=True)
+ @mock.patch.object(irmc_deploy, '_check_share_fs_mounted', spec_set=True,
+ autospec=True)
+ def test_validate_whole_disk_image(self,
+ _check_share_fs_mounted_mock,
+ validate_mock,
+ deploy_info_mock,
+ is_glance_image_mock,
+ validate_prop_mock,
+ validate_capabilities_mock):
+ d_info = {'image_source': '733d1c44-a2ea-414b-aca7-69decf20d810'}
+ deploy_info_mock.return_value = d_info
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.driver_internal_info = {'is_whole_disk_image': True}
+ task.driver.deploy.validate(task)
+
+ _check_share_fs_mounted_mock.assert_called_once_with()
+ validate_mock.assert_called_once_with(task)
+ deploy_info_mock.assert_called_once_with(task.node)
+ self.assertFalse(is_glance_image_mock.called)
+ validate_prop_mock.assert_called_once_with(task.context,
+ d_info, [])
+ validate_capabilities_mock.assert_called_once_with(task.node)
+
+ @mock.patch.object(deploy_utils, 'validate_capabilities',
+ spec_set=True, autospec=True)
+ @mock.patch.object(deploy_utils, 'validate_image_properties',
+ spec_set=True, autospec=True)
+ @mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_parse_deploy_info', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'validate', spec_set=True, autospec=True)
+ @mock.patch.object(irmc_deploy, '_check_share_fs_mounted', spec_set=True,
+ autospec=True)
+ def test_validate_glance_image(self,
+ _check_share_fs_mounted_mock,
+ validate_mock,
+ deploy_info_mock,
+ is_glance_image_mock,
+ validate_prop_mock,
+ validate_capabilities_mock):
+ d_info = {'image_source': '733d1c44-a2ea-414b-aca7-69decf20d810'}
+ deploy_info_mock.return_value = d_info
+ is_glance_image_mock.return_value = True
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.deploy.validate(task)
+
+ _check_share_fs_mounted_mock.assert_called_once_with()
+ validate_mock.assert_called_once_with(task)
+ deploy_info_mock.assert_called_once_with(task.node)
+ validate_prop_mock.assert_called_once_with(
+ task.context, d_info, ['kernel_id', 'ramdisk_id'])
+ validate_capabilities_mock.assert_called_once_with(task.node)
+
+ @mock.patch.object(deploy_utils, 'validate_capabilities',
+ spec_set=True, autospec=True)
+ @mock.patch.object(deploy_utils, 'validate_image_properties',
+ spec_set=True, autospec=True)
+ @mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_parse_deploy_info', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'validate', spec_set=True, autospec=True)
+ @mock.patch.object(irmc_deploy, '_check_share_fs_mounted', spec_set=True,
+ autospec=True)
+ def test_validate_non_glance_image(self,
+ _check_share_fs_mounted_mock,
+ validate_mock,
+ deploy_info_mock,
+ is_glance_image_mock,
+ validate_prop_mock,
+ validate_capabilities_mock):
+ d_info = {'image_source': '733d1c44-a2ea-414b-aca7-69decf20d810'}
+ deploy_info_mock.return_value = d_info
+ is_glance_image_mock.return_value = False
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.deploy.validate(task)
+
+ _check_share_fs_mounted_mock.assert_called_once_with()
+ validate_mock.assert_called_once_with(task)
+ deploy_info_mock.assert_called_once_with(task.node)
+ validate_prop_mock.assert_called_once_with(
+ task.context, d_info, ['kernel', 'ramdisk'])
+ validate_capabilities_mock.assert_called_once_with(task.node)
+
+ @mock.patch.object(irmc_deploy, '_reboot_into_deploy_iso',
+ spec_set=True, autospec=True)
+ @mock.patch.object(deploy_utils, 'get_single_nic_with_vif_port_id',
+ spec_set=True, autospec=True)
+ @mock.patch.object(deploy_utils, 'build_agent_options', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options',
+ spec_set=True, autospec=True)
+ @mock.patch.object(iscsi_deploy, 'check_image_size', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'cache_instance_image', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ def test_deploy(self,
+ node_power_action_mock,
+ cache_instance_image_mock,
+ check_image_size_mock,
+ build_deploy_ramdisk_options_mock,
+ build_agent_options_mock,
+ get_single_nic_with_vif_port_id_mock,
+ _reboot_into_mock):
+ deploy_opts = {'a': 'b'}
+ build_agent_options_mock.return_value = {
+ 'ipa-api-url': 'http://1.2.3.4:6385'}
+ build_deploy_ramdisk_options_mock.return_value = deploy_opts
+ get_single_nic_with_vif_port_id_mock.return_value = '12:34:56:78:90:ab'
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ returned_state = task.driver.deploy.deploy(task)
+
+ node_power_action_mock.assert_called_once_with(
+ task, states.POWER_OFF)
+ cache_instance_image_mock.assert_called_once_with(
+ task.context, task.node)
+ check_image_size_mock.assert_called_once_with(task)
+ expected_ramdisk_opts = {'a': 'b', 'BOOTIF': '12:34:56:78:90:ab',
+ 'ipa-api-url': 'http://1.2.3.4:6385'}
+ build_agent_options_mock.assert_called_once_with(task.node)
+ build_deploy_ramdisk_options_mock.assert_called_once_with(
+ task.node)
+ get_single_nic_with_vif_port_id_mock.assert_called_once_with(
+ task)
+ _reboot_into_mock.assert_called_once_with(
+ task, expected_ramdisk_opts)
+ self.assertEqual(states.DEPLOYWAIT, returned_state)
+
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_remove_share_file', spec_set=True,
+ autospec=True)
+ def test_tear_down(self, _remove_share_file_mock, node_power_action_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.instance_info['irmc_boot_iso'] = 'glance://deploy_iso'
+ task.node.driver_internal_info['irmc_boot_iso'] = 'irmc_boot.iso'
+
+ returned_state = task.driver.deploy.tear_down(task)
+
+ _remove_share_file_mock.assert_called_once_with(
+ irmc_deploy._get_boot_iso_name(task.node))
+ node_power_action_mock.assert_called_once_with(
+ task, states.POWER_OFF)
+ self.assertFalse(
+ task.node.driver_internal_info.get('irmc_boot_iso'))
+ self.assertEqual(states.DELETED, returned_state)
+
+ @mock.patch.object(iscsi_deploy, 'destroy_images', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_clean_up(self, _cleanup_vmedia_boot_mock, destroy_images_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.deploy.clean_up(task)
+
+ _cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ destroy_images_mock.assert_called_once_with(task.node.uuid)
+
+
+class IRMCVirtualMediaAgentDeployTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ irmc_deploy._check_share_fs_mounted_patcher.start()
+ super(IRMCVirtualMediaAgentDeployTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="agent_irmc")
+ self.node = obj_utils.create_test_node(
+ self.context, driver='agent_irmc', driver_info=INFO_DICT)
+
+ @mock.patch.object(deploy_utils, 'validate_capabilities',
+ spec_set=True, autospec=True)
+ @mock.patch.object(irmc_deploy, '_parse_driver_info', spec_set=True,
+ autospec=True)
+ def test_validate(self, _parse_driver_info_mock,
+ validate_capabilities_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.deploy.validate(task)
+ _parse_driver_info_mock.assert_called_once_with(task.node)
+ validate_capabilities_mock.assert_called_once_with(task.node)
+
+ @mock.patch.object(irmc_deploy, '_reboot_into_deploy_iso',
+ spec_set=True, autospec=True)
+ @mock.patch.object(deploy_utils, 'build_agent_options', spec_set=True,
+ autospec=True)
+ def test_deploy(self, build_agent_options_mock,
+ _reboot_into_deploy_iso_mock):
+ deploy_ramdisk_opts = build_agent_options_mock.return_value
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ returned_state = task.driver.deploy.deploy(task)
+ build_agent_options_mock.assert_called_once_with(task.node)
+ _reboot_into_deploy_iso_mock.assert_called_once_with(
+ task, deploy_ramdisk_opts)
+ self.assertEqual(states.DEPLOYWAIT, returned_state)
+
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ def test_tear_down(self, node_power_action_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ returned_state = task.driver.deploy.tear_down(task)
+ node_power_action_mock.assert_called_once_with(
+ task, states.POWER_OFF)
+ self.assertEqual(states.DELETED, returned_state)
+
+ @mock.patch.object(agent, 'build_instance_info_for_deploy', spec_set=True,
+ autospec=True)
+ def test_prepare(self, build_instance_info_for_deploy_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.save = mock.MagicMock(sepc_set=[])
+ task.driver.deploy.prepare(task)
+ build_instance_info_for_deploy_mock.assert_called_once_with(
+ task)
+ task.node.save.assert_called_once_with()
+
+ @mock.patch.object(irmc_deploy, '_cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_clean_up(self, _cleanup_vmedia_boot_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.deploy.clean_up(task)
+ _cleanup_vmedia_boot_mock.assert_called_once_with(task)
+
+
+class VendorPassthruTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ irmc_deploy._check_share_fs_mounted_patcher.start()
+ super(VendorPassthruTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="iscsi_irmc")
+ self.node = obj_utils.create_test_node(
+ self.context, driver='iscsi_irmc', driver_info=INFO_DICT)
+
+ CONF.irmc.remote_image_share_root = '/remote_image_share_root'
+ CONF.irmc.remote_image_server = '10.20.30.40'
+ CONF.irmc.remote_image_share_type = 'NFS'
+ CONF.irmc.remote_image_share_name = 'share'
+ CONF.irmc.remote_image_user_name = 'admin'
+ CONF.irmc.remote_image_user_password = 'admin0'
+ CONF.irmc.remote_image_user_domain = 'local'
+
+ @mock.patch.object(iscsi_deploy, 'get_deploy_info', spec_set=True,
+ autospec=True)
+ def test_validate_pass_deploy_info(self, get_deploy_info_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.validate(task, method='pass_deploy_info', a=1)
+ get_deploy_info_mock.assert_called_once_with(task.node, a=1)
+
+ @mock.patch.object(iscsi_deploy, 'validate_pass_bootloader_info_input',
+ spec_set=True, autospec=True)
+ def test_validate_pass_bootloader_install_info(self, validate_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ kwargs = {'address': '1.2.3.4', 'key': 'fake-key',
+ 'status': 'SUCCEEDED', 'error': ''}
+ task.driver.vendor.validate(
+ task, method='pass_bootloader_install_info', **kwargs)
+ validate_mock.assert_called_once_with(task, kwargs)
+
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, 'setup_vmedia_for_boot', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_prepare_boot_iso', spec_set=True,
+ autospec=True)
+ def test__configure_vmedia_boot(self,
+ _prepare_boot_iso_mock,
+ setup_vmedia_for_boot_mock,
+ node_set_boot_device):
+ root_uuid_or_disk_id = {'root uuid': 'root_uuid'}
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.driver_internal_info['irmc_boot_iso'] = 'boot.iso'
+ task.driver.vendor._configure_vmedia_boot(
+ task, root_uuid_or_disk_id)
+
+ _prepare_boot_iso_mock.assert_called_once_with(
+ task, root_uuid_or_disk_id)
+ setup_vmedia_for_boot_mock.assert_called_once_with(
+ task, 'boot.iso')
+ node_set_boot_device.assert_called_once_with(
+ task, boot_devices.CDROM, persistent=True)
+
+ @mock.patch.object(iscsi_deploy, 'validate_bootloader_install_status',
+ spec_set=True, autospec=True)
+ @mock.patch.object(iscsi_deploy, 'finish_deploy', spec_set=True,
+ autospec=True)
+ def test_pass_bootloader_install_info(self, finish_deploy_mock,
+ validate_input_mock):
+ kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.pass_bootloader_install_info(task, **kwargs)
+ finish_deploy_mock.assert_called_once_with(task, '123456')
+ validate_input_mock.assert_called_once_with(task, kwargs)
+
+ @mock.patch.object(deploy_utils, 'set_failed_state', spec_set=True,
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed',
+ spec_set=True, autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, 'setup_vmedia_for_boot', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_prepare_boot_iso', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'continue_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_pass_deploy_info_ok(self,
+ _cleanup_vmedia_boot_mock,
+ continue_deploy_mock,
+ _prepare_boot_iso_mock,
+ setup_vmedia_for_boot_mock,
+ node_set_boot_device_mock,
+ notify_ramdisk_to_proceed_mock,
+ set_failed_state_mock):
+ kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
+ continue_deploy_mock.return_value = {'root uuid': 'root_uuid'}
+
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.driver_internal_info['irmc_boot_iso'] = 'irmc_boot.iso'
+ task.driver.vendor.pass_deploy_info(task, **kwargs)
+
+ _cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ continue_deploy_mock.assert_called_once_with(task, **kwargs)
+
+ _prepare_boot_iso_mock.assert_called_once_with(
+ task, 'root_uuid')
+ setup_vmedia_for_boot_mock.assert_called_once_with(
+ task, 'irmc_boot.iso')
+ node_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.CDROM, persistent=True)
+ notify_ramdisk_to_proceed_mock.assert_called_once_with(
+ '123456')
+
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+ self.assertFalse(set_failed_state_mock.called)
+
+ @mock.patch.object(deploy_utils, 'set_failed_state', spec_set=True,
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed',
+ spec_set=True, autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, 'setup_vmedia_for_boot', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_prepare_boot_iso', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'continue_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_pass_deploy_info_fail(self,
+ _cleanup_vmedia_boot_mock,
+ continue_deploy_mock,
+ _prepare_boot_iso_mock,
+ setup_vmedia_for_boot_mock,
+ node_set_boot_device_mock,
+ notify_ramdisk_to_proceed_mock,
+ set_failed_state_mock):
+ kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
+
+ self.node.provision_state = states.AVAILABLE
+ self.node.target_provision_state = states.NOSTATE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.InvalidState,
+ task.driver.vendor.pass_deploy_info,
+ task, **kwargs)
+
+ self.assertEqual(states.AVAILABLE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+
+ self.assertFalse(_cleanup_vmedia_boot_mock.called)
+ self.assertFalse(continue_deploy_mock.called)
+ self.assertFalse(_prepare_boot_iso_mock.called)
+ self.assertFalse(setup_vmedia_for_boot_mock.called)
+ self.assertFalse(node_set_boot_device_mock.called)
+ self.assertFalse(notify_ramdisk_to_proceed_mock.called)
+ self.assertFalse(set_failed_state_mock.called)
+
+ @mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed',
+ spec_set=True, autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, 'setup_vmedia_for_boot', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_prepare_boot_iso', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'continue_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_pass_deploy_info__prepare_boot_exception(
+ self,
+ _cleanup_vmedia_boot_mock,
+ continue_deploy_mock,
+ _prepare_boot_iso_mock,
+ setup_vmedia_for_boot_mock,
+ node_set_boot_device_mock,
+ notify_ramdisk_to_proceed_mock,
+ node_power_mock):
+ kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
+ continue_deploy_mock.return_value = {'root uuid': 'root_uuid'}
+ _prepare_boot_iso_mock.side_effect = Exception("fake error")
+
+ self.node.driver_internal_info = {'is_whole_disk_image': False}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.pass_deploy_info(task, **kwargs)
+
+ continue_deploy_mock.assert_called_once_with(
+ task, method='pass_deploy_info', address='123456')
+
+ _cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ _prepare_boot_iso_mock.assert_called_once_with(
+ task, 'root_uuid')
+
+ self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+ self.assertFalse(setup_vmedia_for_boot_mock.called)
+ self.assertFalse(node_set_boot_device_mock.called)
+ self.assertFalse(notify_ramdisk_to_proceed_mock.called)
+ node_power_mock.assert_called_once_with(task, states.POWER_OFF)
+
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed',
+ spec_set=True, autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'continue_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_pass_deploy_info_localboot(self,
+ _cleanup_vmedia_boot_mock,
+ continue_deploy_mock,
+ set_boot_device_mock,
+ notify_ramdisk_to_proceed_mock):
+
+ kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
+ continue_deploy_mock.return_value = {'root uuid': '<some-uuid>'}
+
+ self.node.driver_internal_info = {'is_whole_disk_image': False}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.instance_info = {'capabilities': '{"boot_option": "local"}'}
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ vendor = task.driver.vendor
+ vendor.pass_deploy_info(task, **kwargs)
+
+ _cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ continue_deploy_mock.assert_called_once_with(task, **kwargs)
+ set_boot_device_mock.assert_called_once_with(task,
+ boot_devices.DISK,
+ persistent=True)
+ notify_ramdisk_to_proceed_mock.assert_called_once_with('123456')
+ self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+
+ @mock.patch.object(iscsi_deploy, 'finish_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed',
+ spec_set=True, autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'continue_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_pass_deploy_info_whole_disk_image(
+ self,
+ _cleanup_vmedia_boot_mock,
+ continue_deploy_mock,
+ set_boot_device_mock,
+ notify_ramdisk_to_proceed_mock,
+ finish_deploy_mock):
+
+ kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
+ continue_deploy_mock.return_value = {'root uuid': '<some-uuid>'}
+
+ self.node.driver_internal_info = {'is_whole_disk_image': True}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ vendor = task.driver.vendor
+ vendor.pass_deploy_info(task, **kwargs)
+
+ _cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ continue_deploy_mock.assert_called_once_with(task, **kwargs)
+ set_boot_device_mock.assert_called_once_with(task,
+ boot_devices.DISK,
+ persistent=True)
+ self.assertFalse(notify_ramdisk_to_proceed_mock.called)
+ finish_deploy_mock.assert_called_once_with(task, '123456')
+
+ @mock.patch.object(iscsi_deploy, 'finish_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed',
+ spec_set=True, autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'continue_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_pass_deploy_info_whole_disk_image_local(
+ self,
+ _cleanup_vmedia_boot_mock,
+ continue_deploy_mock,
+ set_boot_device_mock,
+ notify_ramdisk_to_proceed_mock,
+ finish_deploy_mock):
+
+ kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
+ continue_deploy_mock.return_value = {'root uuid': '<some-uuid>'}
+
+ self.node.driver_internal_info = {'is_whole_disk_image': True}
+ self.node.instance_info = {'capabilities': '{"boot_option": "local"}'}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ vendor = task.driver.vendor
+ vendor.pass_deploy_info(task, **kwargs)
+
+ _cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ continue_deploy_mock.assert_called_once_with(task, **kwargs)
+ set_boot_device_mock.assert_called_once_with(task,
+ boot_devices.DISK,
+ persistent=True)
+ self.assertFalse(notify_ramdisk_to_proceed_mock.called)
+ finish_deploy_mock.assert_called_once_with(task, '123456')
+
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'reboot_and_finish_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy.VendorPassthru, '_configure_vmedia_boot',
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_continue_deploy_netboot(self,
+ _cleanup_vmedia_boot_mock,
+ do_agent_iscsi_deploy_mock,
+ _configure_vmedia_boot_mock,
+ reboot_and_finish_deploy_mock):
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.DEPLOYING
+ self.node.save()
+ do_agent_iscsi_deploy_mock.return_value = {
+ 'root uuid': 'some-root-uuid'}
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.continue_deploy(task)
+
+ _cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ do_agent_iscsi_deploy_mock.assert_called_once_with(task,
+ mock.ANY)
+ _configure_vmedia_boot_mock.assert_called_once_with(
+ mock.ANY, task, 'some-root-uuid')
+ reboot_and_finish_deploy_mock.assert_called_once_with(
+ task.driver.vendor, task)
+
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'reboot_and_finish_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'configure_local_boot', spec_set=True, autospec=True)
+ @mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_continue_deploy_localboot(self,
+ _cleanup_vmedia_boot_mock,
+ do_agent_iscsi_deploy_mock,
+ configure_local_boot_mock,
+ reboot_and_finish_deploy_mock):
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.DEPLOYING
+ self.node.instance_info = {
+ 'capabilities': {'boot_option': 'local'}}
+ self.node.save()
+ do_agent_iscsi_deploy_mock.return_value = {
+ 'root uuid': 'some-root-uuid'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.continue_deploy(task)
+
+ _cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ do_agent_iscsi_deploy_mock.assert_called_once_with(task,
+ mock.ANY)
+ configure_local_boot_mock.assert_called_once_with(
+ mock.ANY, task, root_uuid='some-root-uuid',
+ efi_system_part_uuid=None)
+ reboot_and_finish_deploy_mock.assert_called_once_with(
+ mock.ANY, task)
+
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'reboot_and_finish_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'configure_local_boot', spec_set=True, autospec=True)
+ @mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_cleanup_vmedia_boot', spec_set=True,
+ autospec=True)
+ def test_continue_deploy_whole_disk_image(self,
+ _cleanup_vmedia_boot_mock,
+ do_agent_iscsi_deploy_mock,
+ configure_local_boot_mock,
+ reboot_and_finish_deploy_mock):
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.DEPLOYING
+ self.node.driver_internal_info = {'is_whole_disk_image': True}
+ self.node.save()
+ do_agent_iscsi_deploy_mock.return_value = {
+ 'disk identifier': 'some-disk-id'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.continue_deploy(task)
+
+ _cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ do_agent_iscsi_deploy_mock.assert_called_once_with(task,
+ mock.ANY)
+ configure_local_boot_mock.assert_called_once_with(
+ mock.ANY, task, root_uuid=None, efi_system_part_uuid=None)
+ reboot_and_finish_deploy_mock.assert_called_once_with(
+ mock.ANY, task)
+
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'reboot_and_finish_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'configure_local_boot', spec_set=True, autospec=True)
+ @mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, '_cleanup_vmedia_boot', autospec=True)
+ def test_continue_deploy_localboot_uefi(self,
+ _cleanup_vmedia_boot_mock,
+ do_agent_iscsi_deploy_mock,
+ configure_local_boot_mock,
+ reboot_and_finish_deploy_mock):
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.DEPLOYING
+ self.node.instance_info = {
+ 'capabilities': {'boot_option': 'local'}}
+ self.node.save()
+ do_agent_iscsi_deploy_mock.return_value = {
+ 'root uuid': 'some-root-uuid',
+ 'efi system partition uuid': 'efi-system-part-uuid'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.continue_deploy(task)
+
+ _cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ do_agent_iscsi_deploy_mock.assert_called_once_with(task,
+ mock.ANY)
+ configure_local_boot_mock.assert_called_once_with(
+ mock.ANY, task, root_uuid='some-root-uuid',
+ efi_system_part_uuid='efi-system-part-uuid')
+ reboot_and_finish_deploy_mock.assert_called_once_with(
+ mock.ANY, task)
+
+
+class IRMCVirtualMediaAgentVendorInterfaceTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IRMCVirtualMediaAgentVendorInterfaceTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="agent_irmc")
+ self.node = obj_utils.create_test_node(
+ self.context, driver='agent_irmc', driver_info=INFO_DICT)
+
+ @mock.patch.object(agent.AgentVendorInterface, 'reboot_to_instance',
+ spec_set=True, autospec=True)
+ @mock.patch.object(irmc_deploy, '_cleanup_vmedia_boot', autospec=True)
+ def test_reboot_to_instance(self,
+ _cleanup_vmedia_boot_mock,
+ agent_reboot_to_instance_mock):
+ kwargs = {}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.reboot_to_instance(task, **kwargs)
+
+ _cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ agent_reboot_to_instance_mock.assert_called_once_with(
+ mock.ANY, task, **kwargs)
diff --git a/ironic/tests/unit/drivers/modules/irmc/test_management.py b/ironic/tests/unit/drivers/modules/irmc/test_management.py
new file mode 100644
index 000000000..be75321ce
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/irmc/test_management.py
@@ -0,0 +1,302 @@
+# Copyright 2015 FUJITSU LIMITED
+#
+# 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.
+
+"""
+Test class for iRMC Management Driver
+"""
+
+import os
+import xml.etree.ElementTree as ET
+
+import mock
+
+from ironic.common import boot_devices
+from ironic.common import driver_factory
+from ironic.common import exception
+from ironic.conductor import task_manager
+from ironic.drivers.modules import ipmitool
+from ironic.drivers.modules.irmc import common as irmc_common
+from ironic.drivers.modules.irmc import management as irmc_management
+from ironic.drivers import utils as driver_utils
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.drivers import third_party_driver_mock_specs \
+ as mock_specs
+from ironic.tests.unit.objects import utils as obj_utils
+
+INFO_DICT = db_utils.get_test_irmc_info()
+
+
+class IRMCManagementTestCase(db_base.DbTestCase):
+ def setUp(self):
+ super(IRMCManagementTestCase, self).setUp()
+ driver_info = INFO_DICT
+
+ mgr_utils.mock_the_extension_manager(driver="fake_irmc")
+ self.driver = driver_factory.get_driver("fake_irmc")
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_irmc',
+ driver_info=driver_info)
+ self.info = irmc_common.parse_driver_info(self.node)
+
+ def test_get_properties(self):
+ expected = irmc_common.COMMON_PROPERTIES
+ expected.update(ipmitool.COMMON_PROPERTIES)
+ expected.update(ipmitool.CONSOLE_PROPERTIES)
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertEqual(expected, task.driver.get_properties())
+
+ @mock.patch.object(irmc_common, 'parse_driver_info', spec_set=True,
+ autospec=True)
+ def test_validate(self, mock_drvinfo):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.driver.management.validate(task)
+ mock_drvinfo.assert_called_once_with(task.node)
+
+ @mock.patch.object(irmc_common, 'parse_driver_info', spec_set=True,
+ autospec=True)
+ def test_validate_fail(self, mock_drvinfo):
+ side_effect = iter([exception.InvalidParameterValue("Invalid Input")])
+ mock_drvinfo.side_effect = side_effect
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.management.validate,
+ task)
+
+ def test_management_interface_get_supported_boot_devices(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ expected = [boot_devices.PXE, boot_devices.DISK,
+ boot_devices.CDROM, boot_devices.BIOS,
+ boot_devices.SAFE]
+ self.assertEqual(sorted(expected), sorted(task.driver.management.
+ get_supported_boot_devices(task)))
+
+ @mock.patch.object(ipmitool.IPMIManagement, 'set_boot_device',
+ spec_set=True, autospec=True)
+ def test_management_interface_set_boot_device_no_mode_ok(
+ self,
+ set_boot_device_mock):
+ """no boot mode specified."""
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.set_boot_device(task, boot_devices.PXE)
+ set_boot_device_mock.assert_called_once_with(
+ task.driver.management, task,
+ boot_devices.PXE,
+ False)
+
+ @mock.patch.object(ipmitool.IPMIManagement, 'set_boot_device',
+ spec_set=True, autospec=True)
+ def test_management_interface_set_boot_device_bios_ok(
+ self,
+ set_boot_device_mock):
+ """bios mode specified."""
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ driver_utils.add_node_capability(task, 'boot_mode', 'bios')
+ task.driver.management.set_boot_device(task, boot_devices.PXE)
+ set_boot_device_mock.assert_called_once_with(
+ task.driver.management, task,
+ boot_devices.PXE,
+ False)
+
+ @mock.patch.object(irmc_management.ipmitool, "send_raw", spec_set=True,
+ autospec=True)
+ def _test_management_interface_set_boot_device_uefi_ok(self, params,
+ expected_raw_code,
+ send_raw_mock):
+ send_raw_mock.return_value = [None, None]
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node.properties['capabilities'] = ''
+ driver_utils.add_node_capability(task, 'boot_mode', 'uefi')
+ self.driver.management.set_boot_device(task, **params)
+ send_raw_mock.assert_has_calls([
+ mock.call(task, "0x00 0x08 0x03 0x08"),
+ mock.call(task, expected_raw_code)])
+
+ def test_management_interface_set_boot_device_uefi_ok_pxe(self):
+ params = {'device': boot_devices.PXE, 'persistent': False}
+ self._test_management_interface_set_boot_device_uefi_ok(
+ params,
+ "0x00 0x08 0x05 0xa0 0x04 0x00 0x00 0x00")
+
+ params['persistent'] = True
+ self._test_management_interface_set_boot_device_uefi_ok(
+ params,
+ "0x00 0x08 0x05 0xe0 0x04 0x00 0x00 0x00")
+
+ def test_management_interface_set_boot_device_uefi_ok_disk(self):
+ params = {'device': boot_devices.DISK, 'persistent': False}
+ self._test_management_interface_set_boot_device_uefi_ok(
+ params,
+ "0x00 0x08 0x05 0xa0 0x08 0x00 0x00 0x00")
+
+ params['persistent'] = True
+ self._test_management_interface_set_boot_device_uefi_ok(
+ params,
+ "0x00 0x08 0x05 0xe0 0x08 0x00 0x00 0x00")
+
+ def test_management_interface_set_boot_device_uefi_ok_cdrom(self):
+ params = {'device': boot_devices.CDROM, 'persistent': False}
+ self._test_management_interface_set_boot_device_uefi_ok(
+ params,
+ "0x00 0x08 0x05 0xa0 0x14 0x00 0x00 0x00")
+
+ params['persistent'] = True
+ self._test_management_interface_set_boot_device_uefi_ok(
+ params,
+ "0x00 0x08 0x05 0xe0 0x14 0x00 0x00 0x00")
+
+ def test_management_interface_set_boot_device_uefi_ok_bios(self):
+ params = {'device': boot_devices.BIOS, 'persistent': False}
+ self._test_management_interface_set_boot_device_uefi_ok(
+ params,
+ "0x00 0x08 0x05 0xa0 0x18 0x00 0x00 0x00")
+
+ params['persistent'] = True
+ self._test_management_interface_set_boot_device_uefi_ok(
+ params,
+ "0x00 0x08 0x05 0xe0 0x18 0x00 0x00 0x00")
+
+ def test_management_interface_set_boot_device_uefi_ok_safe(self):
+ params = {'device': boot_devices.SAFE, 'persistent': False}
+ self._test_management_interface_set_boot_device_uefi_ok(
+ params,
+ "0x00 0x08 0x05 0xa0 0x0c 0x00 0x00 0x00")
+
+ params['persistent'] = True
+ self._test_management_interface_set_boot_device_uefi_ok(
+ params,
+ "0x00 0x08 0x05 0xe0 0x0c 0x00 0x00 0x00")
+
+ @mock.patch.object(irmc_management.ipmitool, "send_raw", spec_set=True,
+ autospec=True)
+ def test_management_interface_set_boot_device_uefi_ng(self,
+ send_raw_mock):
+ """uefi mode, next boot only, unknown device."""
+ send_raw_mock.return_value = [None, None]
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ driver_utils.add_node_capability(task, 'boot_mode', 'uefi')
+ self.assertRaises(exception.InvalidParameterValue,
+ self.driver.management.set_boot_device,
+ task,
+ "unknown")
+
+ @mock.patch.object(irmc_management, 'scci',
+ spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC)
+ @mock.patch.object(irmc_common, 'get_irmc_report', spec_set=True,
+ autospec=True)
+ def test_management_interface_get_sensors_data_scci_ok(
+ self, mock_get_irmc_report, mock_scci):
+ """'irmc_sensor_method' = 'scci' specified and OK data."""
+ with open(os.path.join(os.path.dirname(__file__),
+ 'fake_sensors_data_ok.xml'), "r") as report:
+ fake_txt = report.read()
+ fake_xml = ET.fromstring(fake_txt)
+
+ mock_get_irmc_report.return_value = fake_xml
+ mock_scci.get_sensor_data.return_value = fake_xml.find(
+ "./System/SensorDataRecords")
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node.driver_info['irmc_sensor_method'] = 'scci'
+ sensor_dict = self.driver.management.get_sensors_data(task)
+
+ expected = {
+ 'Fan (4)': {
+ 'FAN1 SYS (29)': {
+ 'Units': 'RPM',
+ 'Sensor ID': 'FAN1 SYS (29)',
+ 'Sensor Reading': '600 RPM'
+ },
+ 'FAN2 SYS (29)': {
+ 'Units': 'None',
+ 'Sensor ID': 'FAN2 SYS (29)',
+ 'Sensor Reading': 'None None'
+ }
+ },
+ 'Temperature (1)': {
+ 'Systemboard 1 (7)': {
+ 'Units': 'degree C',
+ 'Sensor ID': 'Systemboard 1 (7)',
+ 'Sensor Reading': '80 degree C'
+ },
+ 'Ambient (55)': {
+ 'Units': 'degree C',
+ 'Sensor ID': 'Ambient (55)',
+ 'Sensor Reading': '42 degree C'
+ }
+ }
+ }
+ self.assertEqual(expected, sensor_dict)
+
+ @mock.patch.object(irmc_management, 'scci',
+ spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC)
+ @mock.patch.object(irmc_common, 'get_irmc_report', spec_set=True,
+ autospec=True)
+ def test_management_interface_get_sensors_data_scci_ng(
+ self, mock_get_irmc_report, mock_scci):
+ """'irmc_sensor_method' = 'scci' specified and NG data."""
+ with open(os.path.join(os.path.dirname(__file__),
+ 'fake_sensors_data_ng.xml'), "r") as report:
+ fake_txt = report.read()
+ fake_xml = ET.fromstring(fake_txt)
+
+ mock_get_irmc_report.return_value = fake_xml
+ mock_scci.get_sensor_data.return_value = fake_xml.find(
+ "./System/SensorDataRecords")
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node.driver_info['irmc_sensor_method'] = 'scci'
+ sensor_dict = self.driver.management.get_sensors_data(task)
+
+ self.assertEqual(len(sensor_dict), 0)
+
+ @mock.patch.object(ipmitool.IPMIManagement, 'get_sensors_data',
+ spec_set=True, autospec=True)
+ def test_management_interface_get_sensors_data_ipmitool_ok(
+ self,
+ get_sensors_data_mock):
+ """'irmc_sensor_method' = 'ipmitool' specified."""
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node.driver_info['irmc_sensor_method'] = 'ipmitool'
+ task.driver.management.get_sensors_data(task)
+ get_sensors_data_mock.assert_called_once_with(
+ task.driver.management, task)
+
+ @mock.patch.object(irmc_common, 'get_irmc_report', spec_set=True,
+ autospec=True)
+ def test_management_interface_get_sensors_data_exception(
+ self,
+ get_irmc_report_mock):
+ """'FailedToGetSensorData Exception."""
+
+ get_irmc_report_mock.side_effect = exception.InvalidParameterValue(
+ "Fake Error")
+ irmc_management.scci.SCCIInvalidInputError = Exception
+ irmc_management.scci.SCCIClientError = Exception
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node.driver_info['irmc_sensor_method'] = 'scci'
+ e = self.assertRaises(exception.FailedToGetSensorData,
+ self.driver.management.get_sensors_data,
+ task)
+ self.assertEqual("Failed to get sensor data for node 1be26c0b-" +
+ "03f2-4d2e-ae87-c02d7f33c123. Error: Fake Error",
+ str(e))
diff --git a/ironic/tests/unit/drivers/modules/irmc/test_power.py b/ironic/tests/unit/drivers/modules/irmc/test_power.py
new file mode 100644
index 000000000..1b51f9eac
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/irmc/test_power.py
@@ -0,0 +1,224 @@
+# Copyright 2015 FUJITSU LIMITED
+#
+# 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.
+
+"""
+Test class for iRMC Power Driver
+"""
+
+import mock
+from oslo_config import cfg
+
+from ironic.common import boot_devices
+from ironic.common import exception
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.conductor import utils as manager_utils
+from ironic.drivers.modules.irmc import common as irmc_common
+from ironic.drivers.modules.irmc import deploy as irmc_deploy
+from ironic.drivers.modules.irmc import power as irmc_power
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+INFO_DICT = db_utils.get_test_irmc_info()
+CONF = cfg.CONF
+
+
+@mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True,
+ autospec=True)
+class IRMCPowerInternalMethodsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IRMCPowerInternalMethodsTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_irmc')
+ driver_info = INFO_DICT
+ self.node = db_utils.create_test_node(
+ driver='fake_irmc',
+ driver_info=driver_info,
+ instance_uuid='instance_uuid_123')
+
+ @mock.patch.object(irmc_power, '_attach_boot_iso_if_needed')
+ def test__set_power_state_power_on_ok(
+ self,
+ _attach_boot_iso_if_needed_mock,
+ get_irmc_client_mock):
+ irmc_client = get_irmc_client_mock.return_value
+ target_state = states.POWER_ON
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ irmc_power._set_power_state(task, target_state)
+ _attach_boot_iso_if_needed_mock.assert_called_once_with(task)
+ irmc_client.assert_called_once_with(irmc_power.scci.POWER_ON)
+
+ def test__set_power_state_power_off_ok(self,
+ get_irmc_client_mock):
+ irmc_client = get_irmc_client_mock.return_value
+ target_state = states.POWER_OFF
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ irmc_power._set_power_state(task, target_state)
+ irmc_client.assert_called_once_with(irmc_power.scci.POWER_OFF)
+
+ @mock.patch.object(irmc_power, '_attach_boot_iso_if_needed')
+ def test__set_power_state_power_reboot_ok(
+ self,
+ _attach_boot_iso_if_needed_mock,
+ get_irmc_client_mock):
+ irmc_client = get_irmc_client_mock.return_value
+ target_state = states.REBOOT
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ irmc_power._set_power_state(task, target_state)
+ _attach_boot_iso_if_needed_mock.assert_called_once_with(task)
+ irmc_client.assert_called_once_with(irmc_power.scci.POWER_RESET)
+
+ def test__set_power_state_invalid_target_state(self,
+ get_irmc_client_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ irmc_power._set_power_state,
+ task,
+ states.ERROR)
+
+ def test__set_power_state_scci_exception(self,
+ get_irmc_client_mock):
+ irmc_client = get_irmc_client_mock.return_value
+ irmc_client.side_effect = Exception()
+ irmc_power.scci.SCCIClientError = Exception
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.IRMCOperationError,
+ irmc_power._set_power_state,
+ task,
+ states.POWER_ON)
+
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, 'setup_vmedia_for_boot', spec_set=True,
+ autospec=True)
+ def test__attach_boot_iso_if_needed(
+ self,
+ setup_vmedia_mock,
+ set_boot_device_mock,
+ get_irmc_client_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.node.provision_state = states.ACTIVE
+ task.node.driver_internal_info['irmc_boot_iso'] = 'boot-iso'
+ irmc_power._attach_boot_iso_if_needed(task)
+ setup_vmedia_mock.assert_called_once_with(task, 'boot-iso')
+ set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.CDROM)
+
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_deploy, 'setup_vmedia_for_boot', spec_set=True,
+ autospec=True)
+ def test__attach_boot_iso_if_needed_on_rebuild(
+ self,
+ setup_vmedia_mock,
+ set_boot_device_mock,
+ get_irmc_client_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.node.provision_state = states.DEPLOYING
+ task.node.driver_internal_info['irmc_boot_iso'] = 'boot-iso'
+ irmc_power._attach_boot_iso_if_needed(task)
+ self.assertFalse(setup_vmedia_mock.called)
+ self.assertFalse(set_boot_device_mock.called)
+
+
+class IRMCPowerTestCase(db_base.DbTestCase):
+ def setUp(self):
+ super(IRMCPowerTestCase, self).setUp()
+ driver_info = INFO_DICT
+ mgr_utils.mock_the_extension_manager(driver="fake_irmc")
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_irmc',
+ driver_info=driver_info)
+
+ def test_get_properties(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ properties = task.driver.get_properties()
+ for prop in irmc_common.COMMON_PROPERTIES:
+ self.assertIn(prop, properties)
+
+ @mock.patch.object(irmc_common, 'parse_driver_info', spec_set=True,
+ autospec=True)
+ def test_validate(self, mock_drvinfo):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.driver.power.validate(task)
+ mock_drvinfo.assert_called_once_with(task.node)
+
+ @mock.patch.object(irmc_common, 'parse_driver_info', spec_set=True,
+ autospec=True)
+ def test_validate_fail(self, mock_drvinfo):
+ side_effect = iter([exception.InvalidParameterValue("Invalid Input")])
+ mock_drvinfo.side_effect = side_effect
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.power.validate,
+ task)
+
+ @mock.patch('ironic.drivers.modules.irmc.power.ipmitool.IPMIPower',
+ spec_set=True, autospec=True)
+ def test_get_power_state(self, mock_IPMIPower):
+ ipmi_power = mock_IPMIPower.return_value
+ ipmi_power.get_power_state.return_value = states.POWER_ON
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertEqual(states.POWER_ON,
+ task.driver.power.get_power_state(task))
+ ipmi_power.get_power_state.assert_called_once_with(task)
+
+ @mock.patch.object(irmc_power, '_set_power_state', spec_set=True,
+ autospec=True)
+ def test_set_power_state(self, mock_set_power):
+ mock_set_power.return_value = states.POWER_ON
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.power.set_power_state(task, states.POWER_ON)
+ mock_set_power.assert_called_once_with(task, states.POWER_ON)
+
+ @mock.patch.object(irmc_power, '_set_power_state', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_power.IRMCPower, 'get_power_state', spec_set=True,
+ autospec=True)
+ def test_reboot_reboot(self, mock_get_power, mock_set_power):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ mock_get_power.return_value = states.POWER_ON
+ task.driver.power.reboot(task)
+ mock_get_power.assert_called_once_with(
+ task.driver.power, task)
+ mock_set_power.assert_called_once_with(task, states.REBOOT)
+
+ @mock.patch.object(irmc_power, '_set_power_state', spec_set=True,
+ autospec=True)
+ @mock.patch.object(irmc_power.IRMCPower, 'get_power_state', spec_set=True,
+ autospec=True)
+ def test_reboot_power_on(self, mock_get_power, mock_set_power):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ mock_get_power.return_value = states.POWER_OFF
+ task.driver.power.reboot(task)
+ mock_get_power.assert_called_once_with(
+ task.driver.power, task)
+ mock_set_power.assert_called_once_with(task, states.POWER_ON)
diff --git a/ironic/tests/unit/drivers/modules/msftocs/__init__.py b/ironic/tests/unit/drivers/modules/msftocs/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/msftocs/__init__.py
diff --git a/ironic/tests/unit/drivers/modules/msftocs/test_common.py b/ironic/tests/unit/drivers/modules/msftocs/test_common.py
new file mode 100644
index 000000000..c5891a743
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/msftocs/test_common.py
@@ -0,0 +1,110 @@
+# Copyright 2015 Cloudbase Solutions Srl
+# 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
+# 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.
+
+"""
+Test class for MSFT OCS common functions
+"""
+
+import mock
+
+from ironic.common import exception
+from ironic.conductor import task_manager
+from ironic.drivers.modules.msftocs import common as msftocs_common
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+INFO_DICT = db_utils.get_test_msftocs_info()
+
+
+class MSFTOCSCommonTestCase(db_base.DbTestCase):
+ def setUp(self):
+ super(MSFTOCSCommonTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_msftocs')
+ self.info = INFO_DICT
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_msftocs',
+ driver_info=self.info)
+
+ def test_get_client_info(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ driver_info = task.node.driver_info
+ (client, blade_id) = msftocs_common.get_client_info(driver_info)
+
+ self.assertEqual(driver_info['msftocs_base_url'], client._base_url)
+ self.assertEqual(driver_info['msftocs_username'], client._username)
+ self.assertEqual(driver_info['msftocs_password'], client._password)
+ self.assertEqual(driver_info['msftocs_blade_id'], blade_id)
+
+ @mock.patch.object(msftocs_common, '_is_valid_url', autospec=True)
+ def test_parse_driver_info(self, mock_is_valid_url):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ msftocs_common.parse_driver_info(task.node)
+ mock_is_valid_url.assert_called_once_with(
+ task.node.driver_info['msftocs_base_url'])
+
+ def test_parse_driver_info_fail_missing_param(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ del task.node.driver_info['msftocs_base_url']
+ self.assertRaises(exception.MissingParameterValue,
+ msftocs_common.parse_driver_info,
+ task.node)
+
+ def test_parse_driver_info_fail_bad_url(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.node.driver_info['msftocs_base_url'] = "bad-url"
+ self.assertRaises(exception.InvalidParameterValue,
+ msftocs_common.parse_driver_info,
+ task.node)
+
+ def test_parse_driver_info_fail_bad_blade_id_type(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.node.driver_info['msftocs_blade_id'] = "bad-blade-id"
+ self.assertRaises(exception.InvalidParameterValue,
+ msftocs_common.parse_driver_info,
+ task.node)
+
+ def test_parse_driver_info_fail_bad_blade_id_value(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.node.driver_info['msftocs_blade_id'] = 0
+ self.assertRaises(exception.InvalidParameterValue,
+ msftocs_common.parse_driver_info,
+ task.node)
+
+ def test__is_valid_url(self):
+ self.assertIs(True, msftocs_common._is_valid_url("http://fake.com"))
+ self.assertIs(
+ True, msftocs_common._is_valid_url("http://www.fake.com"))
+ self.assertIs(True, msftocs_common._is_valid_url("http://FAKE.com"))
+ self.assertIs(True, msftocs_common._is_valid_url("http://fake"))
+ self.assertIs(
+ True, msftocs_common._is_valid_url("http://fake.com/blah"))
+ self.assertIs(True, msftocs_common._is_valid_url("http://localhost"))
+ self.assertIs(True, msftocs_common._is_valid_url("https://fake.com"))
+ self.assertIs(True, msftocs_common._is_valid_url("http://10.0.0.1"))
+ self.assertIs(False, msftocs_common._is_valid_url("bad-url"))
+ self.assertIs(False, msftocs_common._is_valid_url("http://.bad-url"))
+ self.assertIs(False, msftocs_common._is_valid_url("http://bad-url$"))
+ self.assertIs(False, msftocs_common._is_valid_url("http://$bad-url"))
+ self.assertIs(False, msftocs_common._is_valid_url("http://bad$url"))
+ self.assertIs(False, msftocs_common._is_valid_url(None))
+ self.assertIs(False, msftocs_common._is_valid_url(0))
diff --git a/ironic/tests/unit/drivers/modules/msftocs/test_management.py b/ironic/tests/unit/drivers/modules/msftocs/test_management.py
new file mode 100644
index 000000000..a121a778d
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/msftocs/test_management.py
@@ -0,0 +1,133 @@
+# Copyright 2015 Cloudbase Solutions Srl
+# 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
+# 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.
+
+"""
+Test class for MSFT OCS ManagementInterface
+"""
+
+import mock
+
+from ironic.common import boot_devices
+from ironic.common import exception
+from ironic.conductor import task_manager
+from ironic.drivers.modules.msftocs import common as msftocs_common
+from ironic.drivers.modules.msftocs import msftocsclient
+from ironic.drivers import utils as drivers_utils
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+INFO_DICT = db_utils.get_test_msftocs_info()
+
+
+class MSFTOCSManagementTestCase(db_base.DbTestCase):
+ def setUp(self):
+ super(MSFTOCSManagementTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_msftocs')
+ self.info = INFO_DICT
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_msftocs',
+ driver_info=self.info)
+
+ def test_get_properties(self):
+ expected = msftocs_common.REQUIRED_PROPERTIES
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertEqual(expected, task.driver.get_properties())
+
+ @mock.patch.object(msftocs_common, 'parse_driver_info', autospec=True)
+ def test_validate(self, mock_drvinfo):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.driver.power.validate(task)
+ mock_drvinfo.assert_called_once_with(task.node)
+
+ @mock.patch.object(msftocs_common, 'parse_driver_info', autospec=True)
+ def test_validate_fail(self, mock_drvinfo):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ mock_drvinfo.side_effect = iter(
+ [exception.InvalidParameterValue('x')])
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.power.validate,
+ task)
+
+ def test_get_supported_boot_devices(self):
+ expected = [boot_devices.PXE, boot_devices.DISK, boot_devices.BIOS]
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertEqual(
+ sorted(expected),
+ sorted(task.driver.management.
+ get_supported_boot_devices(task)))
+
+ @mock.patch.object(msftocs_common, 'get_client_info', autospec=True)
+ def _test_set_boot_device_one_time(self, persistent, uefi,
+ mock_gci):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
+ blade_id = task.node.driver_info['msftocs_blade_id']
+ mock_gci.return_value = (mock_c, blade_id)
+
+ if uefi:
+ drivers_utils.add_node_capability(task, 'boot_mode', 'uefi')
+
+ task.driver.management.set_boot_device(
+ task, boot_devices.PXE, persistent)
+
+ mock_gci.assert_called_once_with(task.node.driver_info)
+ mock_c.set_next_boot.assert_called_once_with(
+ blade_id, msftocsclient.BOOT_TYPE_FORCE_PXE, persistent, uefi)
+
+ def test_set_boot_device_one_time(self):
+ self._test_set_boot_device_one_time(False, False)
+
+ def test_set_boot_device_persistent(self):
+ self._test_set_boot_device_one_time(True, False)
+
+ def test_set_boot_device_uefi(self):
+ self._test_set_boot_device_one_time(True, True)
+
+ def test_set_boot_device_fail(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.management.set_boot_device,
+ task, 'fake-device')
+
+ @mock.patch.object(msftocs_common, 'get_client_info', autospec=True)
+ def test_get_boot_device(self, mock_gci):
+ expected = {'boot_device': boot_devices.DISK, 'persistent': None}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
+ blade_id = task.node.driver_info['msftocs_blade_id']
+ mock_gci.return_value = (mock_c, blade_id)
+ force_hdd = msftocsclient.BOOT_TYPE_FORCE_DEFAULT_HDD
+ mock_c.get_next_boot.return_value = force_hdd
+
+ self.assertEqual(expected,
+ task.driver.management.get_boot_device(task))
+ mock_gci.assert_called_once_with(task.node.driver_info)
+ mock_c.get_next_boot.assert_called_once_with(blade_id)
+
+ def test_get_sensor_data(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(NotImplementedError,
+ task.driver.management.get_sensors_data,
+ task)
diff --git a/ironic/tests/unit/drivers/modules/msftocs/test_msftocsclient.py b/ironic/tests/unit/drivers/modules/msftocs/test_msftocsclient.py
new file mode 100644
index 000000000..15be486c5
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/msftocs/test_msftocsclient.py
@@ -0,0 +1,182 @@
+# Copyright 2015 Cloudbase Solutions Srl
+# 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
+# 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.
+
+"""
+Test class for MSFT OCS REST API client
+"""
+
+import mock
+import requests
+from requests import exceptions as requests_exceptions
+
+from ironic.common import exception
+from ironic.drivers.modules.msftocs import msftocsclient
+from ironic.tests.unit import base
+
+
+FAKE_BOOT_RESPONSE = (
+ '<BootResponse xmlns="%s" '
+ 'xmlns:i="http://www.w3.org/2001/XMLSchema-instance">'
+ '<completionCode>Success</completionCode>'
+ '<apiVersion>1</apiVersion>'
+ '<statusDescription>Success</statusDescription>'
+ '<bladeNumber>1</bladeNumber>'
+ '<nextBoot>ForcePxe</nextBoot>'
+ '</BootResponse>') % msftocsclient.WCSNS
+
+FAKE_BLADE_RESPONSE = (
+ '<BladeResponse xmlns="%s" '
+ 'xmlns:i="http://www.w3.org/2001/XMLSchema-instance">'
+ '<completionCode>Success</completionCode>'
+ '<apiVersion>1</apiVersion>'
+ '<statusDescription/>'
+ '<bladeNumber>1</bladeNumber>'
+ '</BladeResponse>') % msftocsclient.WCSNS
+
+FAKE_POWER_STATE_RESPONSE = (
+ '<PowerStateResponse xmlns="%s" '
+ 'xmlns:i="http://www.w3.org/2001/XMLSchema-instance">'
+ '<completionCode>Success</completionCode>'
+ '<apiVersion>1</apiVersion>'
+ '<statusDescription>Blade Power is On, firmware decompressed'
+ '</statusDescription>'
+ '<bladeNumber>1</bladeNumber>'
+ '<Decompression>0</Decompression>'
+ '<powerState>ON</powerState>'
+ '</PowerStateResponse>') % msftocsclient.WCSNS
+
+FAKE_BLADE_STATE_RESPONSE = (
+ '<BladeStateResponse xmlns="%s" '
+ 'xmlns:i="http://www.w3.org/2001/XMLSchema-instance">'
+ '<completionCode>Success</completionCode>'
+ '<apiVersion>1</apiVersion>'
+ '<statusDescription/>'
+ '<bladeNumber>1</bladeNumber>'
+ '<bladeState>ON</bladeState>'
+ '</BladeStateResponse>') % msftocsclient.WCSNS
+
+
+class MSFTOCSClientApiTestCase(base.TestCase):
+ def setUp(self):
+ super(MSFTOCSClientApiTestCase, self).setUp()
+ self._fake_base_url = "http://fakehost:8000"
+ self._fake_username = "admin"
+ self._fake_password = 'fake'
+ self._fake_blade_id = 1
+ self._client = msftocsclient.MSFTOCSClientApi(
+ self._fake_base_url, self._fake_username, self._fake_password)
+
+ @mock.patch.object(requests, 'get', autospec=True)
+ def test__exec_cmd(self, mock_get):
+ fake_response_text = 'fake_response_text'
+ fake_rel_url = 'fake_rel_url'
+ mock_get.return_value.text = 'fake_response_text'
+
+ self.assertEqual(fake_response_text,
+ self._client._exec_cmd(fake_rel_url))
+ mock_get.assert_called_once_with(
+ self._fake_base_url + "/" + fake_rel_url, auth=mock.ANY)
+
+ @mock.patch.object(requests, 'get', autospec=True)
+ def test__exec_cmd_http_get_fail(self, mock_get):
+ fake_rel_url = 'fake_rel_url'
+ mock_get.side_effect = iter([requests_exceptions.ConnectionError('x')])
+
+ self.assertRaises(exception.MSFTOCSClientApiException,
+ self._client._exec_cmd,
+ fake_rel_url)
+ mock_get.assert_called_once_with(
+ self._fake_base_url + "/" + fake_rel_url, auth=mock.ANY)
+
+ def test__check_completion_code(self):
+ et = self._client._check_completion_code(FAKE_BOOT_RESPONSE)
+ self.assertEqual('{%s}BootResponse' % msftocsclient.WCSNS, et.tag)
+
+ def test__check_completion_code_fail(self):
+ self.assertRaises(exception.MSFTOCSClientApiException,
+ self._client._check_completion_code,
+ '<fake xmlns="%s"></fake>' % msftocsclient.WCSNS)
+
+ def test__check_completion_with_bad_completion_code_fail(self):
+ self.assertRaises(exception.MSFTOCSClientApiException,
+ self._client._check_completion_code,
+ '<fake xmlns="%s">'
+ '<completionCode>Fail</completionCode>'
+ '</fake>' % msftocsclient.WCSNS)
+
+ def test__check_completion_code_xml_parsing_fail(self):
+ self.assertRaises(exception.MSFTOCSClientApiException,
+ self._client._check_completion_code,
+ 'bad_xml')
+
+ @mock.patch.object(
+ msftocsclient.MSFTOCSClientApi, '_exec_cmd', autospec=True)
+ def test_get_blade_state(self, mock_exec_cmd):
+ mock_exec_cmd.return_value = FAKE_BLADE_STATE_RESPONSE
+ self.assertEqual(
+ msftocsclient.POWER_STATUS_ON,
+ self._client.get_blade_state(self._fake_blade_id))
+ mock_exec_cmd.assert_called_once_with(
+ self._client, "GetBladeState?bladeId=%d" % self._fake_blade_id)
+
+ @mock.patch.object(
+ msftocsclient.MSFTOCSClientApi, '_exec_cmd', autospec=True)
+ def test_set_blade_on(self, mock_exec_cmd):
+ mock_exec_cmd.return_value = FAKE_BLADE_RESPONSE
+ self._client.set_blade_on(self._fake_blade_id)
+ mock_exec_cmd.assert_called_once_with(
+ self._client, "SetBladeOn?bladeId=%d" % self._fake_blade_id)
+
+ @mock.patch.object(
+ msftocsclient.MSFTOCSClientApi, '_exec_cmd', autospec=True)
+ def test_set_blade_off(self, mock_exec_cmd):
+ mock_exec_cmd.return_value = FAKE_BLADE_RESPONSE
+ self._client.set_blade_off(self._fake_blade_id)
+ mock_exec_cmd.assert_called_once_with(
+ self._client, "SetBladeOff?bladeId=%d" % self._fake_blade_id)
+
+ @mock.patch.object(
+ msftocsclient.MSFTOCSClientApi, '_exec_cmd', autospec=True)
+ def test_set_blade_power_cycle(self, mock_exec_cmd):
+ mock_exec_cmd.return_value = FAKE_BLADE_RESPONSE
+ self._client.set_blade_power_cycle(self._fake_blade_id)
+ mock_exec_cmd.assert_called_once_with(
+ self._client,
+ "SetBladeActivePowerCycle?bladeId=%d&offTime=0" %
+ self._fake_blade_id)
+
+ @mock.patch.object(
+ msftocsclient.MSFTOCSClientApi, '_exec_cmd', autospec=True)
+ def test_get_next_boot(self, mock_exec_cmd):
+ mock_exec_cmd.return_value = FAKE_BOOT_RESPONSE
+ self.assertEqual(
+ msftocsclient.BOOT_TYPE_FORCE_PXE,
+ self._client.get_next_boot(self._fake_blade_id))
+ mock_exec_cmd.assert_called_once_with(
+ self._client, "GetNextBoot?bladeId=%d" % self._fake_blade_id)
+
+ @mock.patch.object(
+ msftocsclient.MSFTOCSClientApi, '_exec_cmd', autospec=True)
+ def test_set_next_boot(self, mock_exec_cmd):
+ mock_exec_cmd.return_value = FAKE_BOOT_RESPONSE
+ self._client.set_next_boot(self._fake_blade_id,
+ msftocsclient.BOOT_TYPE_FORCE_PXE)
+ mock_exec_cmd.assert_called_once_with(
+ self._client,
+ "SetNextBoot?bladeId=%(blade_id)d&bootType=%(boot_type)d&"
+ "uefi=%(uefi)s&persistent=%(persistent)s" %
+ {"blade_id": self._fake_blade_id,
+ "boot_type": msftocsclient.BOOT_TYPE_FORCE_PXE,
+ "uefi": "true", "persistent": "true"})
diff --git a/ironic/tests/unit/drivers/modules/msftocs/test_power.py b/ironic/tests/unit/drivers/modules/msftocs/test_power.py
new file mode 100644
index 000000000..aeeabe710
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/msftocs/test_power.py
@@ -0,0 +1,164 @@
+# Copyright 2015 Cloudbase Solutions Srl
+# 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
+# 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.
+
+"""
+Test class for MSFT OCS PowerInterface
+"""
+
+import mock
+
+from ironic.common import exception
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.drivers.modules.msftocs import common as msftocs_common
+from ironic.drivers.modules.msftocs import msftocsclient
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+INFO_DICT = db_utils.get_test_msftocs_info()
+
+
+class MSFTOCSPowerTestCase(db_base.DbTestCase):
+ def setUp(self):
+ super(MSFTOCSPowerTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_msftocs')
+ self.info = INFO_DICT
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_msftocs',
+ driver_info=self.info)
+
+ def test_get_properties(self):
+ expected = msftocs_common.REQUIRED_PROPERTIES
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertEqual(expected, task.driver.get_properties())
+
+ @mock.patch.object(msftocs_common, 'parse_driver_info', autospec=True)
+ def test_validate(self, mock_drvinfo):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.driver.power.validate(task)
+ mock_drvinfo.assert_called_once_with(task.node)
+
+ @mock.patch.object(msftocs_common, 'parse_driver_info', autospec=True)
+ def test_validate_fail(self, mock_drvinfo):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ mock_drvinfo.side_effect = iter(
+ [exception.InvalidParameterValue('x')])
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.power.validate,
+ task)
+
+ @mock.patch.object(msftocs_common, 'get_client_info', autospec=True)
+ def test_get_power_state(self, mock_gci):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
+ blade_id = task.node.driver_info['msftocs_blade_id']
+ mock_gci.return_value = (mock_c, blade_id)
+ mock_c.get_blade_state.return_value = msftocsclient.POWER_STATUS_ON
+
+ self.assertEqual(states.POWER_ON,
+ task.driver.power.get_power_state(task))
+ mock_gci.assert_called_once_with(task.node.driver_info)
+ mock_c.get_blade_state.assert_called_once_with(blade_id)
+
+ @mock.patch.object(msftocs_common, 'get_client_info', autospec=True)
+ def test_set_power_state_on(self, mock_gci):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
+ blade_id = task.node.driver_info['msftocs_blade_id']
+ mock_gci.return_value = (mock_c, blade_id)
+
+ task.driver.power.set_power_state(task, states.POWER_ON)
+ mock_gci.assert_called_once_with(task.node.driver_info)
+ mock_c.set_blade_on.assert_called_once_with(blade_id)
+
+ @mock.patch.object(msftocs_common, 'get_client_info', autospec=True)
+ def test_set_power_state_off(self, mock_gci):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
+ blade_id = task.node.driver_info['msftocs_blade_id']
+ mock_gci.return_value = (mock_c, blade_id)
+
+ task.driver.power.set_power_state(task, states.POWER_OFF)
+ mock_gci.assert_called_once_with(task.node.driver_info)
+ mock_c.set_blade_off.assert_called_once_with(blade_id)
+
+ @mock.patch.object(msftocs_common, 'get_client_info', autospec=True)
+ def test_set_power_state_blade_on_fail(self, mock_gci):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
+ blade_id = task.node.driver_info['msftocs_blade_id']
+ mock_gci.return_value = (mock_c, blade_id)
+
+ ex = exception.MSFTOCSClientApiException('x')
+ mock_c.set_blade_on.side_effect = ex
+
+ pstate = states.POWER_ON
+ self.assertRaises(exception.PowerStateFailure,
+ task.driver.power.set_power_state,
+ task, pstate)
+ mock_gci.assert_called_once_with(task.node.driver_info)
+ mock_c.set_blade_on.assert_called_once_with(blade_id)
+
+ @mock.patch.object(msftocs_common, 'get_client_info', autospec=True)
+ def test_set_power_state_invalid_parameter_fail(self, mock_gci):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
+ blade_id = task.node.driver_info['msftocs_blade_id']
+ mock_gci.return_value = (mock_c, blade_id)
+
+ pstate = states.ERROR
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.power.set_power_state,
+ task, pstate)
+ mock_gci.assert_called_once_with(task.node.driver_info)
+
+ @mock.patch.object(msftocs_common, 'get_client_info', autospec=True)
+ def test_reboot(self, mock_gci):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
+ blade_id = task.node.driver_info['msftocs_blade_id']
+ mock_gci.return_value = (mock_c, blade_id)
+
+ task.driver.power.reboot(task)
+ mock_gci.assert_called_once_with(task.node.driver_info)
+ mock_c.set_blade_power_cycle.assert_called_once_with(blade_id)
+
+ @mock.patch.object(msftocs_common, 'get_client_info', autospec=True)
+ def test_reboot_fail(self, mock_gci):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
+ blade_id = task.node.driver_info['msftocs_blade_id']
+ mock_gci.return_value = (mock_c, blade_id)
+
+ ex = exception.MSFTOCSClientApiException('x')
+ mock_c.set_blade_power_cycle.side_effect = ex
+
+ self.assertRaises(exception.PowerStateFailure,
+ task.driver.power.reboot,
+ task)
+ mock_gci.assert_called_once_with(task.node.driver_info)
+ mock_c.set_blade_power_cycle.assert_called_once_with(blade_id)
diff --git a/ironic/tests/unit/drivers/modules/test_agent.py b/ironic/tests/unit/drivers/modules/test_agent.py
new file mode 100644
index 000000000..41fe87a77
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/test_agent.py
@@ -0,0 +1,805 @@
+# Copyright 2014 Rackspace, Inc.
+#
+# 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 types
+
+import mock
+from oslo_config import cfg
+
+from ironic.common import exception
+from ironic.common import image_service
+from ironic.common import images
+from ironic.common import raid
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.conductor import utils as manager_utils
+from ironic.drivers.modules import agent
+from ironic.drivers.modules import agent_base_vendor
+from ironic.drivers.modules import agent_client
+from ironic.drivers.modules import deploy_utils
+from ironic.drivers.modules import fake
+from ironic.drivers.modules import pxe
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as object_utils
+
+
+INSTANCE_INFO = db_utils.get_test_agent_instance_info()
+DRIVER_INFO = db_utils.get_test_agent_driver_info()
+DRIVER_INTERNAL_INFO = db_utils.get_test_agent_driver_internal_info()
+
+CONF = cfg.CONF
+
+
+class TestAgentMethods(db_base.DbTestCase):
+ def setUp(self):
+ super(TestAgentMethods, self).setUp()
+ self.node = object_utils.create_test_node(self.context,
+ driver='fake_agent')
+
+ @mock.patch.object(image_service, 'GlanceImageService', autospec=True)
+ def test_build_instance_info_for_deploy_glance_image(self, glance_mock):
+ i_info = self.node.instance_info
+ i_info['image_source'] = '733d1c44-a2ea-414b-aca7-69decf20d810'
+ self.node.instance_info = i_info
+ self.node.save()
+
+ image_info = {'checksum': 'aa', 'disk_format': 'qcow2',
+ 'container_format': 'bare'}
+ glance_mock.return_value.show = mock.MagicMock(spec_set=[],
+ return_value=image_info)
+
+ mgr_utils.mock_the_extension_manager(driver='fake_agent')
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+
+ agent.build_instance_info_for_deploy(task)
+
+ glance_mock.assert_called_once_with(version=2,
+ context=task.context)
+ glance_mock.return_value.show.assert_called_once_with(
+ self.node.instance_info['image_source'])
+ glance_mock.return_value.swift_temp_url.assert_called_once_with(
+ image_info)
+
+ @mock.patch.object(image_service.HttpImageService, 'validate_href',
+ autospec=True)
+ def test_build_instance_info_for_deploy_nonglance_image(
+ self, validate_href_mock):
+ i_info = self.node.instance_info
+ i_info['image_source'] = 'http://image-ref'
+ i_info['image_checksum'] = 'aa'
+ self.node.instance_info = i_info
+ self.node.save()
+
+ mgr_utils.mock_the_extension_manager(driver='fake_agent')
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+
+ info = agent.build_instance_info_for_deploy(task)
+
+ self.assertEqual(self.node.instance_info['image_source'],
+ info['image_url'])
+ validate_href_mock.assert_called_once_with(
+ mock.ANY, 'http://image-ref')
+
+ @mock.patch.object(image_service.HttpImageService, 'validate_href',
+ autospec=True)
+ def test_build_instance_info_for_deploy_nonsupported_image(
+ self, validate_href_mock):
+ validate_href_mock.side_effect = iter(
+ [exception.ImageRefValidationFailed(
+ image_href='file://img.qcow2', reason='fail')])
+ i_info = self.node.instance_info
+ i_info['image_source'] = 'file://img.qcow2'
+ i_info['image_checksum'] = 'aa'
+ self.node.instance_info = i_info
+ self.node.save()
+
+ mgr_utils.mock_the_extension_manager(driver='fake_agent')
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+
+ self.assertRaises(exception.ImageRefValidationFailed,
+ agent.build_instance_info_for_deploy, task)
+
+ @mock.patch.object(images, 'download_size', autospec=True)
+ def test_check_image_size(self, size_mock):
+ size_mock.return_value = 10 * 1024 * 1024
+ mgr_utils.mock_the_extension_manager(driver='fake_agent')
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.properties['memory_mb'] = 10
+ agent.check_image_size(task, 'fake-image')
+ size_mock.assert_called_once_with(self.context, 'fake-image')
+
+ @mock.patch.object(images, 'download_size', autospec=True)
+ def test_check_image_size_without_memory_mb(self, size_mock):
+ mgr_utils.mock_the_extension_manager(driver='fake_agent')
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.properties.pop('memory_mb', None)
+ agent.check_image_size(task, 'fake-image')
+ self.assertFalse(size_mock.called)
+
+ @mock.patch.object(images, 'download_size', autospec=True)
+ def test_check_image_size_fail(self, size_mock):
+ size_mock.return_value = 11 * 1024 * 1024
+
+ mgr_utils.mock_the_extension_manager(driver='fake_agent')
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.properties['memory_mb'] = 10
+ self.assertRaises(exception.InvalidParameterValue,
+ agent.check_image_size,
+ task, 'fake-image')
+ size_mock.assert_called_once_with(self.context, 'fake-image')
+
+ @mock.patch.object(images, 'download_size', autospec=True)
+ def test_check_image_size_fail_by_agent_consumed_memory(self, size_mock):
+ self.config(memory_consumed_by_agent=2, group='agent')
+ size_mock.return_value = 9 * 1024 * 1024
+ mgr_utils.mock_the_extension_manager(driver='fake_agent')
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.properties['memory_mb'] = 10
+ self.assertRaises(exception.InvalidParameterValue,
+ agent.check_image_size,
+ task, 'fake-image')
+ size_mock.assert_called_once_with(self.context, 'fake-image')
+
+
+class TestAgentDeploy(db_base.DbTestCase):
+ def setUp(self):
+ super(TestAgentDeploy, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_agent')
+ self.driver = agent.AgentDeploy()
+ n = {
+ 'driver': 'fake_agent',
+ 'instance_info': INSTANCE_INFO,
+ 'driver_info': DRIVER_INFO,
+ 'driver_internal_info': DRIVER_INTERNAL_INFO,
+ }
+ self.node = object_utils.create_test_node(self.context, **n)
+ self.ports = [
+ object_utils.create_test_port(self.context, node_id=self.node.id)]
+
+ def test_get_properties(self):
+ expected = agent.COMMON_PROPERTIES
+ self.assertEqual(expected, self.driver.get_properties())
+
+ @mock.patch.object(images, 'download_size', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
+ def test_validate(self, pxe_boot_validate_mock, size_mock):
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ self.driver.validate(task)
+ pxe_boot_validate_mock.assert_called_once_with(
+ task.driver.boot, task)
+ size_mock.assert_called_once_with(self.context, 'fake-image')
+
+ @mock.patch.object(images, 'download_size', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
+ def test_validate_driver_info_manage_agent_boot_false(
+ self, pxe_boot_validate_mock, size_mock):
+ self.config(manage_agent_boot=False, group='agent')
+ self.node.driver_info = {}
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ self.driver.validate(task)
+ self.assertFalse(pxe_boot_validate_mock.called)
+ size_mock.assert_called_once_with(self.context, 'fake-image')
+
+ @mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
+ def test_validate_instance_info_missing_params(
+ self, pxe_boot_validate_mock):
+ self.node.instance_info = {}
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ e = self.assertRaises(exception.MissingParameterValue,
+ self.driver.validate, task)
+ pxe_boot_validate_mock.assert_called_once_with(
+ task.driver.boot, task)
+
+ self.assertIn('instance_info.image_source', str(e))
+
+ @mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
+ def test_validate_nonglance_image_no_checksum(
+ self, pxe_boot_validate_mock):
+ i_info = self.node.instance_info
+ i_info['image_source'] = 'http://image-ref'
+ del i_info['image_checksum']
+ self.node.instance_info = i_info
+ self.node.save()
+
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.assertRaises(exception.MissingParameterValue,
+ self.driver.validate, task)
+ pxe_boot_validate_mock.assert_called_once_with(
+ task.driver.boot, task)
+
+ @mock.patch.object(images, 'download_size', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
+ def test_validate_agent_fail_partition_image(
+ self, pxe_boot_validate_mock, size_mock):
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+ self.assertRaises(exception.InvalidParameterValue,
+ self.driver.validate, task)
+ pxe_boot_validate_mock.assert_called_once_with(
+ task.driver.boot, task)
+ size_mock.assert_called_once_with(self.context, 'fake-image')
+
+ @mock.patch.object(images, 'download_size', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
+ def test_validate_invalid_root_device_hints(
+ self, pxe_boot_validate_mock, size_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.node.properties['root_device'] = {'size': 'not-int'}
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.deploy.validate, task)
+ pxe_boot_validate_mock.assert_called_once_with(
+ task.driver.boot, task)
+ size_mock.assert_called_once_with(self.context, 'fake-image')
+
+ @mock.patch('ironic.conductor.utils.node_power_action', autospec=True)
+ def test_deploy(self, power_mock):
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ driver_return = self.driver.deploy(task)
+ self.assertEqual(driver_return, states.DEPLOYWAIT)
+ power_mock.assert_called_once_with(task, states.REBOOT)
+
+ @mock.patch('ironic.conductor.utils.node_power_action', autospec=True)
+ def test_tear_down(self, power_mock):
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ driver_return = self.driver.tear_down(task)
+ power_mock.assert_called_once_with(task, states.POWER_OFF)
+ self.assertEqual(driver_return, states.DELETED)
+
+ @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk')
+ @mock.patch.object(deploy_utils, 'build_agent_options')
+ @mock.patch.object(agent, 'build_instance_info_for_deploy')
+ def test_prepare(self, build_instance_info_mock, build_options_mock,
+ pxe_prepare_ramdisk_mock):
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ task.node.provision_state = states.DEPLOYING
+ build_instance_info_mock.return_value = {'foo': 'bar'}
+ build_options_mock.return_value = {'a': 'b'}
+
+ self.driver.prepare(task)
+
+ build_instance_info_mock.assert_called_once_with(task)
+ build_options_mock.assert_called_once_with(task.node)
+ pxe_prepare_ramdisk_mock.assert_called_once_with(
+ task, {'a': 'b'})
+
+ self.node.refresh()
+ self.assertEqual('bar', self.node.instance_info['foo'])
+
+ @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk')
+ @mock.patch.object(deploy_utils, 'build_agent_options')
+ @mock.patch.object(agent, 'build_instance_info_for_deploy')
+ def test_prepare_manage_agent_boot_false(
+ self, build_instance_info_mock, build_options_mock,
+ pxe_prepare_ramdisk_mock):
+ self.config(group='agent', manage_agent_boot=False)
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ task.node.provision_state = states.DEPLOYING
+ build_instance_info_mock.return_value = {'foo': 'bar'}
+
+ self.driver.prepare(task)
+
+ build_instance_info_mock.assert_called_once_with(task)
+ self.assertFalse(build_options_mock.called)
+ self.assertFalse(pxe_prepare_ramdisk_mock.called)
+
+ self.node.refresh()
+ self.assertEqual('bar', self.node.instance_info['foo'])
+
+ @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk')
+ @mock.patch.object(deploy_utils, 'build_agent_options')
+ @mock.patch.object(agent, 'build_instance_info_for_deploy')
+ def test_prepare_active(
+ self, build_instance_info_mock, build_options_mock,
+ pxe_prepare_ramdisk_mock):
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ task.node.provision_state = states.ACTIVE
+
+ self.driver.prepare(task)
+
+ self.assertFalse(build_instance_info_mock.called)
+ self.assertFalse(build_options_mock.called)
+ self.assertFalse(pxe_prepare_ramdisk_mock.called)
+
+ @mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk')
+ def test_clean_up(self, pxe_clean_up_ramdisk_mock):
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ self.driver.clean_up(task)
+ pxe_clean_up_ramdisk_mock.assert_called_once_with(task)
+
+ @mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk')
+ def test_clean_up_manage_agent_boot_false(self, pxe_clean_up_ramdisk_mock):
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ self.config(group='agent', manage_agent_boot=False)
+ self.driver.clean_up(task)
+ self.assertFalse(pxe_clean_up_ramdisk_mock.called)
+
+ @mock.patch('ironic.drivers.modules.deploy_utils.agent_get_clean_steps',
+ autospec=True)
+ def test_get_clean_steps(self, mock_get_clean_steps):
+ # Test getting clean steps
+ mock_steps = [{'priority': 10, 'interface': 'deploy',
+ 'step': 'erase_devices'}]
+ mock_get_clean_steps.return_value = mock_steps
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ steps = self.driver.get_clean_steps(task)
+ mock_get_clean_steps.assert_called_once_with(task)
+ self.assertEqual(mock_steps, steps)
+
+ @mock.patch('ironic.drivers.modules.deploy_utils.agent_get_clean_steps',
+ autospec=True)
+ def test_get_clean_steps_config_priority(self, mock_get_clean_steps):
+ # Test that we can override the priority of get clean steps
+ # Use 0 because it is an edge case (false-y) and used in devstack
+ self.config(erase_devices_priority=0, group='deploy')
+ mock_steps = [{'priority': 10, 'interface': 'deploy',
+ 'step': 'erase_devices'}]
+ expected_steps = [{'priority': 0, 'interface': 'deploy',
+ 'step': 'erase_devices'}]
+ mock_get_clean_steps.return_value = mock_steps
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ steps = self.driver.get_clean_steps(task)
+ mock_get_clean_steps.assert_called_once_with(task)
+ self.assertEqual(expected_steps, steps)
+
+ @mock.patch.object(deploy_utils, 'prepare_inband_cleaning', autospec=True)
+ def test_prepare_cleaning(self, prepare_inband_cleaning_mock):
+ prepare_inband_cleaning_mock.return_value = states.CLEANWAIT
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertEqual(
+ states.CLEANWAIT, self.driver.prepare_cleaning(task))
+ prepare_inband_cleaning_mock.assert_called_once_with(
+ task, manage_boot=True)
+
+ @mock.patch.object(deploy_utils, 'prepare_inband_cleaning', autospec=True)
+ def test_prepare_cleaning_manage_agent_boot_false(
+ self, prepare_inband_cleaning_mock):
+ prepare_inband_cleaning_mock.return_value = states.CLEANWAIT
+ self.config(group='agent', manage_agent_boot=False)
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertEqual(
+ states.CLEANWAIT, self.driver.prepare_cleaning(task))
+ prepare_inband_cleaning_mock.assert_called_once_with(
+ task, manage_boot=False)
+
+ @mock.patch.object(deploy_utils, 'tear_down_inband_cleaning',
+ autospec=True)
+ def test_tear_down_cleaning(self, tear_down_cleaning_mock):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.driver.tear_down_cleaning(task)
+ tear_down_cleaning_mock.assert_called_once_with(
+ task, manage_boot=True)
+
+ @mock.patch.object(deploy_utils, 'tear_down_inband_cleaning',
+ autospec=True)
+ def test_tear_down_cleaning_manage_agent_boot_false(
+ self, tear_down_cleaning_mock):
+ self.config(group='agent', manage_agent_boot=False)
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.driver.tear_down_cleaning(task)
+ tear_down_cleaning_mock.assert_called_once_with(
+ task, manage_boot=False)
+
+
+class TestAgentVendor(db_base.DbTestCase):
+
+ def setUp(self):
+ super(TestAgentVendor, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="fake_agent")
+ self.passthru = agent.AgentVendorInterface()
+ n = {
+ 'driver': 'fake_agent',
+ 'instance_info': INSTANCE_INFO,
+ 'driver_info': DRIVER_INFO,
+ 'driver_internal_info': DRIVER_INTERNAL_INFO,
+ }
+ self.node = object_utils.create_test_node(self.context, **n)
+
+ def test_continue_deploy(self):
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ test_temp_url = 'http://image'
+ expected_image_info = {
+ 'urls': [test_temp_url],
+ 'id': 'fake-image',
+ 'checksum': 'checksum',
+ 'disk_format': 'qcow2',
+ 'container_format': 'bare',
+ }
+
+ client_mock = mock.MagicMock(spec_set=['prepare_image'])
+ self.passthru._client = client_mock
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.passthru.continue_deploy(task)
+
+ client_mock.prepare_image.assert_called_with(task.node,
+ expected_image_info)
+ self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
+ self.assertEqual(states.ACTIVE,
+ task.node.target_provision_state)
+
+ def test_continue_deploy_image_source_is_url(self):
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ test_temp_url = 'http://image'
+ expected_image_info = {
+ 'urls': [test_temp_url],
+ 'id': self.node.instance_info['image_source'],
+ 'checksum': 'checksum',
+ 'disk_format': 'qcow2',
+ 'container_format': 'bare',
+ }
+
+ client_mock = mock.MagicMock(spec_set=['prepare_image'])
+ self.passthru._client = client_mock
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.passthru.continue_deploy(task)
+
+ client_mock.prepare_image.assert_called_with(task.node,
+ expected_image_info)
+ self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
+ self.assertEqual(states.ACTIVE,
+ task.node.target_provision_state)
+
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(fake.FakePower, 'get_power_state',
+ spec=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ @mock.patch('ironic.conductor.utils.node_set_boot_device', autospec=True)
+ @mock.patch('ironic.drivers.modules.agent.AgentVendorInterface'
+ '.check_deploy_success', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True)
+ def test_reboot_to_instance(self, clean_pxe_mock, check_deploy_mock,
+ bootdev_mock, power_off_mock,
+ get_power_state_mock, node_power_action_mock):
+ check_deploy_mock.return_value = None
+
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ get_power_state_mock.return_value = states.POWER_OFF
+ task.node.driver_internal_info['is_whole_disk_image'] = True
+
+ self.passthru.reboot_to_instance(task)
+
+ clean_pxe_mock.assert_called_once_with(task.driver.boot, task)
+ check_deploy_mock.assert_called_once_with(mock.ANY, task.node)
+ bootdev_mock.assert_called_once_with(task, 'disk', persistent=True)
+ power_off_mock.assert_called_once_with(task.node)
+ get_power_state_mock.assert_called_once_with(task)
+ node_power_action_mock.assert_called_once_with(
+ task, states.POWER_ON)
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(fake.FakePower, 'get_power_state',
+ spec=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ @mock.patch('ironic.conductor.utils.node_set_boot_device', autospec=True)
+ @mock.patch('ironic.drivers.modules.agent.AgentVendorInterface'
+ '.check_deploy_success', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True)
+ def test_reboot_to_instance_boot_none(self, clean_pxe_mock,
+ check_deploy_mock,
+ bootdev_mock, power_off_mock,
+ get_power_state_mock,
+ node_power_action_mock):
+ check_deploy_mock.return_value = None
+
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ get_power_state_mock.return_value = states.POWER_OFF
+ task.node.driver_internal_info['is_whole_disk_image'] = True
+ task.driver.boot = None
+
+ self.passthru.reboot_to_instance(task)
+
+ self.assertFalse(clean_pxe_mock.called)
+ check_deploy_mock.assert_called_once_with(mock.ANY, task.node)
+ bootdev_mock.assert_called_once_with(task, 'disk', persistent=True)
+ power_off_mock.assert_called_once_with(task.node)
+ get_power_state_mock.assert_called_once_with(task)
+ node_power_action_mock.assert_called_once_with(
+ task, states.POWER_ON)
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_deploy_has_started(self, mock_get_cmd):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ mock_get_cmd.return_value = []
+ self.assertFalse(self.passthru.deploy_has_started(task))
+
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_deploy_has_started_is_done(self, mock_get_cmd):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ mock_get_cmd.return_value = [{'command_name': 'prepare_image',
+ 'command_status': 'SUCCESS'}]
+ self.assertTrue(self.passthru.deploy_has_started(task))
+
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_deploy_has_started_did_start(self, mock_get_cmd):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ mock_get_cmd.return_value = [{'command_name': 'prepare_image',
+ 'command_status': 'RUNNING'}]
+ self.assertTrue(self.passthru.deploy_has_started(task))
+
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_deploy_has_started_multiple_commands(self, mock_get_cmd):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ mock_get_cmd.return_value = [{'command_name': 'cache_image',
+ 'command_status': 'SUCCESS'},
+ {'command_name': 'prepare_image',
+ 'command_status': 'RUNNING'}]
+ self.assertTrue(self.passthru.deploy_has_started(task))
+
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_deploy_has_started_other_commands(self, mock_get_cmd):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ mock_get_cmd.return_value = [{'command_name': 'cache_image',
+ 'command_status': 'SUCCESS'}]
+ self.assertFalse(self.passthru.deploy_has_started(task))
+
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_deploy_is_done(self, mock_get_cmd):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ mock_get_cmd.return_value = [{'command_name': 'prepare_image',
+ 'command_status': 'SUCCESS'}]
+ self.assertTrue(self.passthru.deploy_is_done(task))
+
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_deploy_is_done_empty_response(self, mock_get_cmd):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ mock_get_cmd.return_value = []
+ self.assertFalse(self.passthru.deploy_is_done(task))
+
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_deploy_is_done_race(self, mock_get_cmd):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ mock_get_cmd.return_value = [{'command_name': 'some_other_command',
+ 'command_status': 'SUCCESS'}]
+ self.assertFalse(self.passthru.deploy_is_done(task))
+
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_deploy_is_done_still_running(self, mock_get_cmd):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ mock_get_cmd.return_value = [{'command_name': 'prepare_image',
+ 'command_status': 'RUNNING'}]
+ self.assertFalse(self.passthru.deploy_is_done(task))
+
+
+class AgentRAIDTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(AgentRAIDTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="fake_agent")
+ self.passthru = agent.AgentVendorInterface()
+ self.target_raid_config = {
+ "logical_disks": [
+ {'size_gb': 200, 'raid_level': 0, 'is_root_volume': True},
+ {'size_gb': 200, 'raid_level': 5}
+ ]}
+ self.clean_step = {'step': 'create_configuration',
+ 'interface': 'raid'}
+ n = {
+ 'driver': 'fake_agent',
+ 'instance_info': INSTANCE_INFO,
+ 'driver_info': DRIVER_INFO,
+ 'driver_internal_info': DRIVER_INTERNAL_INFO,
+ 'target_raid_config': self.target_raid_config,
+ 'clean_step': self.clean_step,
+ }
+ self.node = object_utils.create_test_node(self.context, **n)
+
+ @mock.patch.object(deploy_utils, 'agent_get_clean_steps', autospec=True)
+ def test_get_clean_steps(self, get_steps_mock):
+ get_steps_mock.return_value = [
+ {'step': 'create_configuration', 'interface': 'raid',
+ 'priority': 1},
+ {'step': 'delete_configuration', 'interface': 'raid',
+ 'priority': 2}]
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ ret = task.driver.raid.get_clean_steps(task)
+
+ self.assertEqual(0, ret[0]['priority'])
+ self.assertEqual(0, ret[1]['priority'])
+
+ @mock.patch.object(deploy_utils, 'agent_execute_clean_step',
+ autospec=True)
+ def test_create_configuration(self, execute_mock):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ execute_mock.return_value = states.CLEANWAIT
+
+ return_value = task.driver.raid.create_configuration(task)
+
+ self.assertEqual(states.CLEANWAIT, return_value)
+ self.assertEqual(
+ self.target_raid_config,
+ task.node.driver_internal_info['target_raid_config'])
+ execute_mock.assert_called_once_with(task, self.clean_step)
+
+ @mock.patch.object(deploy_utils, 'agent_execute_clean_step',
+ autospec=True)
+ def test_create_configuration_skip_root(self, execute_mock):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ execute_mock.return_value = states.CLEANWAIT
+
+ return_value = task.driver.raid.create_configuration(
+ task, create_root_volume=False)
+
+ self.assertEqual(states.CLEANWAIT, return_value)
+ execute_mock.assert_called_once_with(task, self.clean_step)
+ exp_target_raid_config = {
+ "logical_disks": [
+ {'size_gb': 200, 'raid_level': 5}
+ ]}
+ self.assertEqual(
+ exp_target_raid_config,
+ task.node.driver_internal_info['target_raid_config'])
+
+ @mock.patch.object(deploy_utils, 'agent_execute_clean_step',
+ autospec=True)
+ def test_create_configuration_skip_nonroot(self, execute_mock):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ execute_mock.return_value = states.CLEANWAIT
+
+ return_value = task.driver.raid.create_configuration(
+ task, create_nonroot_volumes=False)
+
+ self.assertEqual(states.CLEANWAIT, return_value)
+ execute_mock.assert_called_once_with(task, self.clean_step)
+ exp_target_raid_config = {
+ "logical_disks": [
+ {'size_gb': 200, 'raid_level': 0, 'is_root_volume': True},
+ ]}
+ self.assertEqual(
+ exp_target_raid_config,
+ task.node.driver_internal_info['target_raid_config'])
+
+ @mock.patch.object(deploy_utils, 'agent_execute_clean_step',
+ autospec=True)
+ def test_create_configuration_no_target_raid_config_after_skipping(
+ self, execute_mock):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(
+ exception.MissingParameterValue,
+ task.driver.raid.create_configuration,
+ task, create_root_volume=False,
+ create_nonroot_volumes=False)
+
+ self.assertFalse(execute_mock.called)
+
+ @mock.patch.object(deploy_utils, 'agent_execute_clean_step',
+ autospec=True)
+ def test_create_configuration_empty_target_raid_config(
+ self, execute_mock):
+ execute_mock.return_value = states.CLEANING
+ self.node.target_raid_config = {}
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.MissingParameterValue,
+ task.driver.raid.create_configuration,
+ task)
+ self.assertFalse(execute_mock.called)
+
+ @mock.patch.object(raid, 'update_raid_info', autospec=True)
+ def test__create_configuration_final(
+ self, update_raid_info_mock):
+ command = {'command_result': {'clean_result': 'foo'}}
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ raid_mgmt = agent.AgentRAID
+ raid_mgmt._create_configuration_final(task, command)
+ update_raid_info_mock.assert_called_once_with(task.node, 'foo')
+
+ @mock.patch.object(raid, 'update_raid_info', autospec=True)
+ def test__create_configuration_final_registered(
+ self, update_raid_info_mock):
+ self.node.clean_step = {'interface': 'raid',
+ 'step': 'create_configuration'}
+ command = {'command_result': {'clean_result': 'foo'}}
+ create_hook = agent_base_vendor._get_post_clean_step_hook(self.node)
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ create_hook(task, command)
+ update_raid_info_mock.assert_called_once_with(task.node, 'foo')
+
+ @mock.patch.object(raid, 'update_raid_info', autospec=True)
+ def test__create_configuration_final_bad_command_result(
+ self, update_raid_info_mock):
+ command = {}
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ raid_mgmt = agent.AgentRAID
+ self.assertRaises(exception.IronicException,
+ raid_mgmt._create_configuration_final,
+ task, command)
+ self.assertFalse(update_raid_info_mock.called)
+
+ @mock.patch.object(deploy_utils, 'agent_execute_clean_step',
+ autospec=True)
+ def test_delete_configuration(self, execute_mock):
+ execute_mock.return_value = states.CLEANING
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ return_value = task.driver.raid.delete_configuration(task)
+
+ execute_mock.assert_called_once_with(task, self.clean_step)
+ self.assertEqual(states.CLEANING, return_value)
+
+ def test__delete_configuration_final(self):
+ command = {'command_result': {'clean_result': 'foo'}}
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node.raid_config = {'foo': 'bar'}
+ raid_mgmt = agent.AgentRAID
+ raid_mgmt._delete_configuration_final(task, command)
+
+ self.node.refresh()
+ self.assertEqual({}, self.node.raid_config)
+
+ def test__delete_configuration_final_registered(
+ self):
+ self.node.clean_step = {'interface': 'raid',
+ 'step': 'delete_configuration'}
+ self.node.raid_config = {'foo': 'bar'}
+ command = {'command_result': {'clean_result': 'foo'}}
+ delete_hook = agent_base_vendor._get_post_clean_step_hook(self.node)
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ delete_hook(task, command)
+
+ self.node.refresh()
+ self.assertEqual({}, self.node.raid_config)
diff --git a/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py b/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py
new file mode 100644
index 000000000..2cf23ab3e
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py
@@ -0,0 +1,948 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2015 Red Hat, 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
+# 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 time
+import types
+
+import mock
+
+from ironic.common import boot_devices
+from ironic.common import exception
+from ironic.common import states
+from ironic.conductor import manager
+from ironic.conductor import task_manager
+from ironic.conductor import utils as manager_utils
+from ironic.drivers.modules import agent_base_vendor
+from ironic.drivers.modules import agent_client
+from ironic.drivers.modules import deploy_utils
+from ironic.drivers.modules import fake
+from ironic import objects
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as object_utils
+
+INSTANCE_INFO = db_utils.get_test_agent_instance_info()
+DRIVER_INFO = db_utils.get_test_agent_driver_info()
+DRIVER_INTERNAL_INFO = db_utils.get_test_agent_driver_internal_info()
+
+
+class TestBaseAgentVendor(db_base.DbTestCase):
+
+ def setUp(self):
+ super(TestBaseAgentVendor, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="fake_agent")
+ self.passthru = agent_base_vendor.BaseAgentVendor()
+ n = {
+ 'driver': 'fake_agent',
+ 'instance_info': INSTANCE_INFO,
+ 'driver_info': DRIVER_INFO,
+ 'driver_internal_info': DRIVER_INTERNAL_INFO,
+ }
+ self.node = object_utils.create_test_node(self.context, **n)
+
+ def test_validate(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ method = 'heartbeat'
+ self.passthru.validate(task, method)
+
+ def test_driver_validate(self):
+ kwargs = {'version': '2'}
+ method = 'lookup'
+ self.passthru.driver_validate(method, **kwargs)
+
+ def test_driver_validate_invalid_paremeter(self):
+ method = 'lookup'
+ kwargs = {'version': '1'}
+ self.assertRaises(exception.InvalidParameterValue,
+ self.passthru.driver_validate,
+ method, **kwargs)
+
+ def test_driver_validate_missing_parameter(self):
+ method = 'lookup'
+ kwargs = {}
+ self.assertRaises(exception.MissingParameterValue,
+ self.passthru.driver_validate,
+ method, **kwargs)
+
+ def test_lookup_version_not_found(self):
+ kwargs = {
+ 'version': '999',
+ }
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ self.passthru.lookup,
+ task.context,
+ **kwargs)
+
+ @mock.patch('ironic.drivers.modules.agent_base_vendor.BaseAgentVendor'
+ '._find_node_by_macs', autospec=True)
+ def test_lookup_v2(self, find_mock):
+ kwargs = {
+ 'version': '2',
+ 'inventory': {
+ 'interfaces': [
+ {
+ 'mac_address': 'aa:bb:cc:dd:ee:ff',
+ 'name': 'eth0'
+ },
+ {
+ 'mac_address': 'ff:ee:dd:cc:bb:aa',
+ 'name': 'eth1'
+ }
+
+ ]
+ }
+ }
+ find_mock.return_value = self.node
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ node = self.passthru.lookup(task.context, **kwargs)
+ self.assertEqual(self.node.as_dict(), node['node'])
+
+ def test_lookup_v2_missing_inventory(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ self.passthru.lookup,
+ task.context)
+
+ def test_lookup_v2_empty_inventory(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ self.passthru.lookup,
+ task.context,
+ inventory={})
+
+ def test_lookup_v2_empty_interfaces(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.NodeNotFound,
+ self.passthru.lookup,
+ task.context,
+ version='2',
+ inventory={'interfaces': []})
+
+ @mock.patch.object(objects.Node, 'get_by_uuid')
+ def test_lookup_v2_with_node_uuid(self, mock_get_node):
+ kwargs = {
+ 'version': '2',
+ 'node_uuid': 'fake uuid',
+ 'inventory': {
+ 'interfaces': [
+ {
+ 'mac_address': 'aa:bb:cc:dd:ee:ff',
+ 'name': 'eth0'
+ },
+ {
+ 'mac_address': 'ff:ee:dd:cc:bb:aa',
+ 'name': 'eth1'
+ }
+
+ ]
+ }
+ }
+ mock_get_node.return_value = self.node
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ node = self.passthru.lookup(task.context, **kwargs)
+ self.assertEqual(self.node.as_dict(), node['node'])
+ mock_get_node.assert_called_once_with(mock.ANY, 'fake uuid')
+
+ @mock.patch.object(objects.port.Port, 'get_by_address',
+ spec_set=types.FunctionType)
+ def test_find_ports_by_macs(self, mock_get_port):
+ fake_port = object_utils.get_test_port(self.context)
+ mock_get_port.return_value = fake_port
+
+ macs = ['aa:bb:cc:dd:ee:ff']
+
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=True) as task:
+ ports = self.passthru._find_ports_by_macs(task, macs)
+ self.assertEqual(1, len(ports))
+ self.assertEqual(fake_port.uuid, ports[0].uuid)
+ self.assertEqual(fake_port.node_id, ports[0].node_id)
+
+ @mock.patch.object(objects.port.Port, 'get_by_address',
+ spec_set=types.FunctionType)
+ def test_find_ports_by_macs_bad_params(self, mock_get_port):
+ mock_get_port.side_effect = exception.PortNotFound(port="123")
+
+ macs = ['aa:bb:cc:dd:ee:ff']
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=True) as task:
+ empty_ids = self.passthru._find_ports_by_macs(task, macs)
+ self.assertEqual([], empty_ids)
+
+ @mock.patch('ironic.objects.node.Node.get_by_id',
+ spec_set=types.FunctionType)
+ @mock.patch('ironic.drivers.modules.agent_base_vendor.BaseAgentVendor'
+ '._get_node_id', autospec=True)
+ @mock.patch('ironic.drivers.modules.agent_base_vendor.BaseAgentVendor'
+ '._find_ports_by_macs', autospec=True)
+ def test_find_node_by_macs(self, ports_mock, node_id_mock, node_mock):
+ ports_mock.return_value = object_utils.get_test_port(self.context)
+ node_id_mock.return_value = '1'
+ node_mock.return_value = self.node
+
+ macs = ['aa:bb:cc:dd:ee:ff']
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=True) as task:
+ node = self.passthru._find_node_by_macs(task, macs)
+ self.assertEqual(node, node)
+
+ @mock.patch('ironic.drivers.modules.agent_base_vendor.BaseAgentVendor'
+ '._find_ports_by_macs', autospec=True)
+ def test_find_node_by_macs_no_ports(self, ports_mock):
+ ports_mock.return_value = []
+
+ macs = ['aa:bb:cc:dd:ee:ff']
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=True) as task:
+ self.assertRaises(exception.NodeNotFound,
+ self.passthru._find_node_by_macs,
+ task,
+ macs)
+
+ @mock.patch('ironic.objects.node.Node.get_by_uuid',
+ spec_set=types.FunctionType)
+ @mock.patch('ironic.drivers.modules.agent_base_vendor.BaseAgentVendor'
+ '._get_node_id', autospec=True)
+ @mock.patch('ironic.drivers.modules.agent_base_vendor.BaseAgentVendor'
+ '._find_ports_by_macs', autospec=True)
+ def test_find_node_by_macs_nodenotfound(self, ports_mock, node_id_mock,
+ node_mock):
+ port = object_utils.get_test_port(self.context)
+ ports_mock.return_value = [port]
+ node_id_mock.return_value = self.node['uuid']
+ node_mock.side_effect = [self.node,
+ exception.NodeNotFound(node=self.node)]
+
+ macs = ['aa:bb:cc:dd:ee:ff']
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=True) as task:
+ self.assertRaises(exception.NodeNotFound,
+ self.passthru._find_node_by_macs,
+ task,
+ macs)
+
+ def test_get_node_id(self):
+ fake_port1 = object_utils.get_test_port(self.context,
+ node_id=123,
+ address="aa:bb:cc:dd:ee:fe")
+ fake_port2 = object_utils.get_test_port(self.context,
+ node_id=123,
+ id=42,
+ address="aa:bb:cc:dd:ee:fb",
+ uuid='1be26c0b-03f2-4d2e-ae87-'
+ 'c02d7f33c782')
+
+ node_id = self.passthru._get_node_id([fake_port1, fake_port2])
+ self.assertEqual(fake_port2.node_id, node_id)
+
+ def test_get_node_id_exception(self):
+ fake_port1 = object_utils.get_test_port(self.context,
+ node_id=123,
+ address="aa:bb:cc:dd:ee:fc")
+ fake_port2 = object_utils.get_test_port(self.context,
+ node_id=321,
+ id=42,
+ address="aa:bb:cc:dd:ee:fd",
+ uuid='1be26c0b-03f2-4d2e-ae87-'
+ 'c02d7f33c782')
+
+ self.assertRaises(exception.NodeNotFound,
+ self.passthru._get_node_id,
+ [fake_port1, fake_port2])
+
+ def test_get_interfaces(self):
+ fake_inventory = {
+ 'interfaces': [
+ {
+ 'mac_address': 'aa:bb:cc:dd:ee:ff',
+ 'name': 'eth0'
+ }
+ ]
+ }
+ interfaces = self.passthru._get_interfaces(fake_inventory)
+ self.assertEqual(fake_inventory['interfaces'], interfaces)
+
+ def test_get_interfaces_bad(self):
+ self.assertRaises(exception.InvalidParameterValue,
+ self.passthru._get_interfaces,
+ inventory={})
+
+ def test_heartbeat(self):
+ kwargs = {
+ 'agent_url': 'http://127.0.0.1:9999/bar'
+ }
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=True) as task:
+ self.passthru.heartbeat(task, **kwargs)
+
+ def test_heartbeat_bad(self):
+ kwargs = {}
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=True) as task:
+ self.assertRaises(exception.MissingParameterValue,
+ self.passthru.heartbeat, task, **kwargs)
+
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor, 'deploy_has_started',
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor, 'deploy_is_done',
+ autospec=True)
+ @mock.patch.object(agent_base_vendor.LOG, 'exception', autospec=True)
+ def test_heartbeat_deploy_done_fails(self, log_mock, done_mock,
+ failed_mock, deploy_started_mock):
+ deploy_started_mock.return_value = True
+ kwargs = {
+ 'agent_url': 'http://127.0.0.1:9999/bar'
+ }
+ done_mock.side_effect = iter([Exception('LlamaException')])
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=True) as task:
+ task.node.provision_state = states.DEPLOYWAIT
+ task.node.target_provision_state = states.ACTIVE
+ self.passthru.heartbeat(task, **kwargs)
+ failed_mock.assert_called_once_with(task, mock.ANY)
+ log_mock.assert_called_once_with(
+ 'Asynchronous exception for node '
+ '1be26c0b-03f2-4d2e-ae87-c02d7f33c123: Failed checking if deploy '
+ 'is done. exception: LlamaException')
+
+ @mock.patch.object(objects.node.Node, 'touch_provisioning', autospec=True)
+ @mock.patch.object(manager, 'set_node_cleaning_steps', autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ '_notify_conductor_resume_clean', autospec=True)
+ def test_heartbeat_resume_clean(self, mock_notify, mock_set_steps,
+ mock_touch):
+ kwargs = {
+ 'agent_url': 'http://127.0.0.1:9999/bar'
+ }
+ self.node.clean_step = {}
+ for state in (states.CLEANWAIT, states.CLEANING):
+ self.node.provision_state = state
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=True) as task:
+ self.passthru.heartbeat(task, **kwargs)
+
+ mock_touch.assert_called_once_with(mock.ANY)
+ mock_notify.assert_called_once_with(mock.ANY, task)
+ mock_set_steps.assert_called_once_with(task)
+ # Reset mocks for the next interaction
+ mock_touch.reset_mock()
+ mock_notify.reset_mock()
+ mock_set_steps.reset_mock()
+
+ @mock.patch.object(objects.node.Node, 'touch_provisioning', autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'continue_cleaning', autospec=True)
+ def test_heartbeat_continue_cleaning(self, mock_continue, mock_touch):
+ kwargs = {
+ 'agent_url': 'http://127.0.0.1:9999/bar'
+ }
+ self.node.clean_step = {
+ 'priority': 10,
+ 'interface': 'deploy',
+ 'step': 'foo',
+ 'reboot_requested': False
+ }
+ for state in (states.CLEANWAIT, states.CLEANING):
+ self.node.provision_state = state
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=True) as task:
+ self.passthru.heartbeat(task, **kwargs)
+
+ mock_touch.assert_called_once_with(mock.ANY)
+ mock_continue.assert_called_once_with(mock.ANY, task, **kwargs)
+ # Reset mocks for the next interaction
+ mock_touch.reset_mock()
+ mock_continue.reset_mock()
+
+ @mock.patch('ironic.conductor.manager.cleaning_error_handler')
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'continue_cleaning', autospec=True)
+ def test_heartbeat_continue_cleaning_fails(self, mock_continue,
+ mock_handler):
+ kwargs = {
+ 'agent_url': 'http://127.0.0.1:9999/bar'
+ }
+ self.node.clean_step = {
+ 'priority': 10,
+ 'interface': 'deploy',
+ 'step': 'foo',
+ 'reboot_requested': False
+ }
+
+ mock_continue.side_effect = Exception()
+
+ for state in (states.CLEANWAIT, states.CLEANING):
+ self.node.provision_state = state
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=True) as task:
+ self.passthru.heartbeat(task, **kwargs)
+
+ mock_continue.assert_called_once_with(mock.ANY, task, **kwargs)
+ mock_handler.assert_called_once_with(task, mock.ANY)
+ mock_handler.reset_mock()
+ mock_continue.reset_mock()
+
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor, 'continue_deploy',
+ autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor, 'reboot_to_instance',
+ autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ '_notify_conductor_resume_clean', autospec=True)
+ def test_heartbeat_noops_maintenance_mode(self, ncrc_mock, rti_mock,
+ cd_mock):
+ """Ensures that heartbeat() no-ops for a maintenance node."""
+ kwargs = {
+ 'agent_url': 'http://127.0.0.1:9999/bar'
+ }
+ self.node.maintenance = True
+ for state in (states.AVAILABLE, states.DEPLOYWAIT, states.DEPLOYING,
+ states.CLEANING):
+ self.node.provision_state = state
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=True) as task:
+ self.passthru.heartbeat(task, **kwargs)
+
+ self.assertEqual(0, ncrc_mock.call_count)
+ self.assertEqual(0, rti_mock.call_count)
+ self.assertEqual(0, cd_mock.call_count)
+
+ @mock.patch.object(objects.node.Node, 'touch_provisioning', autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor, 'deploy_has_started',
+ autospec=True)
+ def test_heartbeat_touch_provisioning(self, mock_deploy_started,
+ mock_touch):
+ mock_deploy_started.return_value = True
+ kwargs = {
+ 'agent_url': 'http://127.0.0.1:9999/bar'
+ }
+
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=True) as task:
+ self.passthru.heartbeat(task, **kwargs)
+
+ mock_touch.assert_called_once_with(mock.ANY)
+
+ def test_vendor_passthru_vendor_routes(self):
+ expected = ['heartbeat']
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ vendor_routes = task.driver.vendor.vendor_routes
+ self.assertIsInstance(vendor_routes, dict)
+ self.assertEqual(expected, list(vendor_routes))
+
+ def test_vendor_passthru_driver_routes(self):
+ expected = ['lookup']
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ driver_routes = task.driver.vendor.driver_routes
+ self.assertIsInstance(driver_routes, dict)
+ self.assertEqual(expected, list(driver_routes))
+
+ @mock.patch.object(time, 'sleep', lambda seconds: None)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(fake.FakePower, 'get_power_state',
+ spec=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ def test_reboot_and_finish_deploy(self, power_off_mock,
+ get_power_state_mock,
+ node_power_action_mock):
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ get_power_state_mock.side_effect = [states.POWER_ON,
+ states.POWER_OFF]
+ self.passthru.reboot_and_finish_deploy(task)
+ power_off_mock.assert_called_once_with(task.node)
+ self.assertEqual(2, get_power_state_mock.call_count)
+ node_power_action_mock.assert_called_once_with(
+ task, states.POWER_ON)
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+
+ @mock.patch.object(time, 'sleep', lambda seconds: None)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(fake.FakePower, 'get_power_state',
+ spec=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ def test_reboot_and_finish_deploy_soft_poweroff_doesnt_complete(
+ self, power_off_mock, get_power_state_mock,
+ node_power_action_mock):
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ get_power_state_mock.return_value = states.POWER_ON
+ self.passthru.reboot_and_finish_deploy(task)
+ power_off_mock.assert_called_once_with(task.node)
+ self.assertEqual(7, get_power_state_mock.call_count)
+ node_power_action_mock.assert_called_once_with(
+ task, states.REBOOT)
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ def test_reboot_and_finish_deploy_soft_poweroff_fails(
+ self, power_off_mock, node_power_action_mock):
+ power_off_mock.side_effect = iter([RuntimeError("boom")])
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.passthru.reboot_and_finish_deploy(task)
+ power_off_mock.assert_called_once_with(task.node)
+ node_power_action_mock.assert_called_once_with(
+ task, states.REBOOT)
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+
+ @mock.patch.object(time, 'sleep', lambda seconds: None)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(fake.FakePower, 'get_power_state',
+ spec=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ def test_reboot_and_finish_deploy_get_power_state_fails(
+ self, power_off_mock, get_power_state_mock,
+ node_power_action_mock):
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ get_power_state_mock.side_effect = iter([RuntimeError("boom")])
+ self.passthru.reboot_and_finish_deploy(task)
+ power_off_mock.assert_called_once_with(task.node)
+ self.assertEqual(7, get_power_state_mock.call_count)
+ node_power_action_mock.assert_called_once_with(
+ task, states.REBOOT)
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+
+ @mock.patch.object(time, 'sleep', lambda seconds: None)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(fake.FakePower, 'get_power_state',
+ spec=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ def test_reboot_and_finish_deploy_power_action_fails(
+ self, power_off_mock, get_power_state_mock,
+ node_power_action_mock):
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ get_power_state_mock.return_value = states.POWER_ON
+ node_power_action_mock.side_effect = iter([RuntimeError("boom")])
+ self.assertRaises(exception.InstanceDeployFailure,
+ self.passthru.reboot_and_finish_deploy,
+ task)
+ power_off_mock.assert_called_once_with(task.node)
+ self.assertEqual(7, get_power_state_mock.call_count)
+ node_power_action_mock.assert_has_calls([
+ mock.call(task, states.REBOOT),
+ mock.call(task, states.POWER_OFF)])
+ self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ def test_configure_local_boot(self, try_set_boot_device_mock,
+ install_bootloader_mock):
+ install_bootloader_mock.return_value = {
+ 'command_status': 'SUCCESS', 'command_error': None}
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+ self.passthru.configure_local_boot(task,
+ root_uuid='some-root-uuid')
+ try_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK)
+ install_bootloader_mock.assert_called_once_with(
+ mock.ANY, task.node, root_uuid='some-root-uuid',
+ efi_system_part_uuid=None)
+
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ def test_configure_local_boot_uefi(self, try_set_boot_device_mock,
+ install_bootloader_mock):
+ install_bootloader_mock.return_value = {
+ 'command_status': 'SUCCESS', 'command_error': None}
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+ self.passthru.configure_local_boot(
+ task, root_uuid='some-root-uuid',
+ efi_system_part_uuid='efi-system-part-uuid')
+ try_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK)
+ install_bootloader_mock.assert_called_once_with(
+ mock.ANY, task.node, root_uuid='some-root-uuid',
+ efi_system_part_uuid='efi-system-part-uuid')
+
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ def test_configure_local_boot_whole_disk_image(
+ self, install_bootloader_mock, try_set_boot_device_mock):
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.passthru.configure_local_boot(task)
+ self.assertFalse(install_bootloader_mock.called)
+ try_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK)
+
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ def test_configure_local_boot_no_root_uuid(
+ self, install_bootloader_mock, try_set_boot_device_mock):
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+ self.passthru.configure_local_boot(task)
+ self.assertFalse(install_bootloader_mock.called)
+ try_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK)
+
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ def test_configure_local_boot_boot_loader_install_fail(
+ self, install_bootloader_mock):
+ install_bootloader_mock.return_value = {
+ 'command_status': 'FAILED', 'command_error': 'boom'}
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+ self.assertRaises(exception.InstanceDeployFailure,
+ self.passthru.configure_local_boot,
+ task, root_uuid='some-root-uuid')
+ install_bootloader_mock.assert_called_once_with(
+ mock.ANY, task.node, root_uuid='some-root-uuid',
+ efi_system_part_uuid=None)
+ self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ def test_configure_local_boot_set_boot_device_fail(
+ self, install_bootloader_mock, try_set_boot_device_mock):
+ install_bootloader_mock.return_value = {
+ 'command_status': 'SUCCESS', 'command_error': None}
+ try_set_boot_device_mock.side_effect = iter([RuntimeError('error')])
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+ self.assertRaises(exception.InstanceDeployFailure,
+ self.passthru.configure_local_boot,
+ task, root_uuid='some-root-uuid')
+ install_bootloader_mock.assert_called_once_with(
+ mock.ANY, task.node, root_uuid='some-root-uuid',
+ efi_system_part_uuid=None)
+ try_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK)
+ self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ '_notify_conductor_resume_clean', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning(self, status_mock, notify_mock):
+ # Test a successful execute clean step on the agent
+ self.node.clean_step = {
+ 'priority': 10,
+ 'interface': 'deploy',
+ 'step': 'erase_devices',
+ 'reboot_requested': False
+ }
+ self.node.save()
+ status_mock.return_value = [{
+ 'command_status': 'SUCCEEDED',
+ 'command_name': 'execute_clean_step',
+ 'command_result': {
+ 'clean_step': self.node.clean_step
+ }
+ }]
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.passthru.continue_cleaning(task)
+ notify_mock.assert_called_once_with(mock.ANY, task)
+
+ @mock.patch.object(agent_base_vendor,
+ '_get_post_clean_step_hook', autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ '_notify_conductor_resume_clean', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning_with_hook(
+ self, status_mock, notify_mock, get_hook_mock):
+ self.node.clean_step = {
+ 'priority': 10,
+ 'interface': 'raid',
+ 'step': 'create_configuration',
+ }
+ self.node.save()
+ command_status = {
+ 'command_status': 'SUCCEEDED',
+ 'command_name': 'execute_clean_step',
+ 'command_result': {'clean_step': self.node.clean_step}}
+ status_mock.return_value = [command_status]
+ hook_mock = mock.MagicMock(spec=types.FunctionType, __name__='foo')
+ get_hook_mock.return_value = hook_mock
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.passthru.continue_cleaning(task)
+
+ get_hook_mock.assert_called_once_with(task.node)
+ hook_mock.assert_called_once_with(task, command_status)
+ notify_mock.assert_called_once_with(mock.ANY, task)
+
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ '_notify_conductor_resume_clean', autospec=True)
+ @mock.patch.object(agent_base_vendor,
+ '_get_post_clean_step_hook', autospec=True)
+ @mock.patch.object(manager, 'cleaning_error_handler', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning_with_hook_fails(
+ self, status_mock, error_handler_mock, get_hook_mock,
+ notify_mock):
+ self.node.clean_step = {
+ 'priority': 10,
+ 'interface': 'raid',
+ 'step': 'create_configuration',
+ }
+ self.node.save()
+ command_status = {
+ 'command_status': 'SUCCEEDED',
+ 'command_name': 'execute_clean_step',
+ 'command_result': {'clean_step': self.node.clean_step}}
+ status_mock.return_value = [command_status]
+ hook_mock = mock.MagicMock(spec=types.FunctionType, __name__='foo')
+ hook_mock.side_effect = RuntimeError('error')
+ get_hook_mock.return_value = hook_mock
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.passthru.continue_cleaning(task)
+
+ get_hook_mock.assert_called_once_with(task.node)
+ hook_mock.assert_called_once_with(task, command_status)
+ error_handler_mock.assert_called_once_with(task, mock.ANY)
+ self.assertFalse(notify_mock.called)
+
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ '_notify_conductor_resume_clean', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning_old_command(self, status_mock, notify_mock):
+ # Test when a second execute_clean_step happens to the agent, but
+ # the new step hasn't started yet.
+ self.node.clean_step = {
+ 'priority': 10,
+ 'interface': 'deploy',
+ 'step': 'erase_devices',
+ 'reboot_requested': False
+ }
+ self.node.save()
+ status_mock.return_value = [{
+ 'command_status': 'SUCCEEDED',
+ 'command_name': 'execute_clean_step',
+ 'command_result': {
+ 'priority': 20,
+ 'interface': 'deploy',
+ 'step': 'update_firmware',
+ 'reboot_requested': False
+ }
+ }]
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.passthru.continue_cleaning(task)
+ self.assertFalse(notify_mock.called)
+
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ '_notify_conductor_resume_clean', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning_running(self, status_mock, notify_mock):
+ # Test that no action is taken while a clean step is executing
+ status_mock.return_value = [{
+ 'command_status': 'RUNNING',
+ 'command_name': 'execute_clean_step',
+ 'command_result': None
+ }]
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.passthru.continue_cleaning(task)
+ self.assertFalse(notify_mock.called)
+
+ @mock.patch('ironic.conductor.manager.cleaning_error_handler',
+ autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning_fail(self, status_mock, error_mock):
+ # Test the a failure puts the node in CLEANFAIL
+ status_mock.return_value = [{
+ 'command_status': 'FAILED',
+ 'command_name': 'execute_clean_step',
+ 'command_result': {}
+ }]
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.passthru.continue_cleaning(task)
+ error_mock.assert_called_once_with(task, mock.ANY)
+
+ @mock.patch('ironic.conductor.manager.set_node_cleaning_steps',
+ autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ '_notify_conductor_resume_clean', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning_clean_version_mismatch(
+ self, status_mock, notify_mock, steps_mock):
+ # Test that cleaning is restarted if there is a version mismatch
+ status_mock.return_value = [{
+ 'command_status': 'CLEAN_VERSION_MISMATCH',
+ 'command_name': 'execute_clean_step',
+ 'command_result': {}
+ }]
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.passthru.continue_cleaning(task)
+ steps_mock.assert_called_once_with(task)
+ notify_mock.assert_called_once_with(mock.ANY, task)
+
+ @mock.patch('ironic.conductor.manager.cleaning_error_handler',
+ autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning_unknown(self, status_mock, error_mock):
+ # Test that unknown commands are treated as failures
+ status_mock.return_value = [{
+ 'command_status': 'UNKNOWN',
+ 'command_name': 'execute_clean_step',
+ 'command_result': {}
+ }]
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.passthru.continue_cleaning(task)
+ error_mock.assert_called_once_with(task, mock.ANY)
+
+ def _test_clean_step_hook(self, hook_dict_mock):
+ """Helper method for unit tests related to clean step hooks.
+
+ This is a helper method for other unit tests related to
+ clean step hooks. It acceps a mock 'hook_dict_mock' which is
+ a MagicMock and sets it up to function as a mock dictionary.
+ After that, it defines a dummy hook_method for two clean steps
+ raid.create_configuration and raid.delete_configuration.
+
+ :param hook_dict_mock: An instance of mock.MagicMock() which
+ is the mocked value of agent_base_vendor.POST_CLEAN_STEP_HOOKS
+ :returns: a tuple, where the first item is the hook method created
+ by this method and second item is the backend dictionary for
+ the mocked hook_dict_mock
+ """
+ hook_dict = {}
+
+ def get(key, default):
+ return hook_dict.get(key, default)
+
+ def getitem(self, key):
+ return hook_dict[key]
+
+ def setdefault(key, default):
+ if key not in hook_dict:
+ hook_dict[key] = default
+ return hook_dict[key]
+
+ hook_dict_mock.get = get
+ hook_dict_mock.__getitem__ = getitem
+ hook_dict_mock.setdefault = setdefault
+ some_function_mock = mock.MagicMock()
+
+ @agent_base_vendor.post_clean_step_hook(
+ interface='raid', step='delete_configuration')
+ @agent_base_vendor.post_clean_step_hook(
+ interface='raid', step='create_configuration')
+ def hook_method():
+ some_function_mock('some-arguments')
+
+ return hook_method, hook_dict
+
+ @mock.patch.object(agent_base_vendor, 'POST_CLEAN_STEP_HOOKS',
+ spec_set=dict)
+ def test_post_clean_step_hook(self, hook_dict_mock):
+ # This unit test makes sure that hook methods are registered
+ # properly and entries are made in
+ # agent_base_vendor.POST_CLEAN_STEP_HOOKS
+ hook_method, hook_dict = self._test_clean_step_hook(hook_dict_mock)
+ self.assertEqual(hook_method,
+ hook_dict['raid']['create_configuration'])
+ self.assertEqual(hook_method,
+ hook_dict['raid']['delete_configuration'])
+
+ @mock.patch.object(agent_base_vendor, 'POST_CLEAN_STEP_HOOKS',
+ spec_set=dict)
+ def test__get_post_clean_step_hook(self, hook_dict_mock):
+ # Check if agent_base_vendor._get_post_clean_step_hook can get
+ # clean step for which hook is registered.
+ hook_method, hook_dict = self._test_clean_step_hook(hook_dict_mock)
+ self.node.clean_step = {'step': 'create_configuration',
+ 'interface': 'raid'}
+ self.node.save()
+ hook_returned = agent_base_vendor._get_post_clean_step_hook(self.node)
+ self.assertEqual(hook_method, hook_returned)
+
+ @mock.patch.object(agent_base_vendor, 'POST_CLEAN_STEP_HOOKS',
+ spec_set=dict)
+ def test__get_post_clean_step_hook_no_hook_registered(
+ self, hook_dict_mock):
+ # Make sure agent_base_vendor._get_post_clean_step_hook returns
+ # None when no clean step hook is registered for the clean step.
+ hook_method, hook_dict = self._test_clean_step_hook(hook_dict_mock)
+ self.node.clean_step = {'step': 'some-clean-step',
+ 'interface': 'some-other-interface'}
+ self.node.save()
+ hook_returned = agent_base_vendor._get_post_clean_step_hook(self.node)
+ self.assertIsNone(hook_returned)
diff --git a/ironic/tests/unit/drivers/modules/test_agent_client.py b/ironic/tests/unit/drivers/modules/test_agent_client.py
new file mode 100644
index 000000000..223844882
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/test_agent_client.py
@@ -0,0 +1,220 @@
+# Copyright 2014 Rackspace, Inc.
+#
+# 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 json
+
+import mock
+import requests
+import six
+
+from ironic.common import exception
+from ironic.drivers.modules import agent_client
+from ironic.tests.unit import base
+
+
+class MockResponse(object):
+ status_code = 200
+
+ def __init__(self, text):
+ assert isinstance(text, six.string_types)
+ self.text = text
+
+ def json(self):
+ return json.loads(self.text)
+
+
+class MockNode(object):
+ def __init__(self):
+ self.uuid = 'uuid'
+ self.driver_info = {}
+ self.driver_internal_info = {
+ 'agent_url': "http://127.0.0.1:9999",
+ 'clean_version': {'generic': '1'}
+ }
+ self.instance_info = {}
+
+ def as_dict(self):
+ return {
+ 'uuid': self.uuid,
+ 'driver_info': self.driver_info,
+ 'driver_internal_info': self.driver_internal_info,
+ 'instance_info': self.instance_info
+ }
+
+
+class TestAgentClient(base.TestCase):
+ def setUp(self):
+ super(TestAgentClient, self).setUp()
+ self.client = agent_client.AgentClient()
+ self.client.session = mock.MagicMock(autospec=requests.Session)
+ self.node = MockNode()
+
+ def test_content_type_header(self):
+ client = agent_client.AgentClient()
+ self.assertEqual('application/json',
+ client.session.headers['Content-Type'])
+
+ def test__get_command_url(self):
+ command_url = self.client._get_command_url(self.node)
+ expected = self.node.driver_internal_info['agent_url'] + '/v1/commands'
+ self.assertEqual(expected, command_url)
+
+ def test__get_command_url_fail(self):
+ del self.node.driver_internal_info['agent_url']
+ self.assertRaises(exception.IronicException,
+ self.client._get_command_url,
+ self.node)
+
+ def test__get_command_body(self):
+ expected = json.dumps({'name': 'prepare_image', 'params': {}})
+ self.assertEqual(expected,
+ self.client._get_command_body('prepare_image', {}))
+
+ def test__command(self):
+ response_data = {'status': 'ok'}
+ response_text = json.dumps(response_data)
+ self.client.session.post.return_value = MockResponse(response_text)
+ method = 'standby.run_image'
+ image_info = {'image_id': 'test_image'}
+ params = {'image_info': image_info}
+
+ url = self.client._get_command_url(self.node)
+ body = self.client._get_command_body(method, params)
+
+ response = self.client._command(self.node, method, params)
+ self.assertEqual(response, response_data)
+ self.client.session.post.assert_called_once_with(
+ url,
+ data=body,
+ params={'wait': 'false'})
+
+ def test__command_fail_json(self):
+ response_text = 'this be not json matey!'
+ self.client.session.post.return_value = MockResponse(response_text)
+ method = 'standby.run_image'
+ image_info = {'image_id': 'test_image'}
+ params = {'image_info': image_info}
+
+ url = self.client._get_command_url(self.node)
+ body = self.client._get_command_body(method, params)
+
+ self.assertRaises(exception.IronicException,
+ self.client._command,
+ self.node, method, params)
+ self.client.session.post.assert_called_once_with(
+ url,
+ data=body,
+ params={'wait': 'false'})
+
+ def test_get_commands_status(self):
+ with mock.patch.object(self.client.session, 'get',
+ autospec=True) as mock_get:
+ res = mock.MagicMock(spec_set=['json'])
+ res.json.return_value = {'commands': []}
+ mock_get.return_value = res
+ self.assertEqual([], self.client.get_commands_status(self.node))
+
+ @mock.patch('uuid.uuid4', mock.MagicMock(spec_set=[], return_value='uuid'))
+ def test_prepare_image(self):
+ self.client._command = mock.MagicMock(spec_set=[])
+ image_info = {'image_id': 'image'}
+ params = {'image_info': image_info}
+
+ self.client.prepare_image(self.node,
+ image_info,
+ wait=False)
+ self.client._command.assert_called_once_with(
+ node=self.node, method='standby.prepare_image',
+ params=params, wait=False)
+
+ @mock.patch('uuid.uuid4', mock.MagicMock(spec_set=[], return_value='uuid'))
+ def test_prepare_image_with_configdrive(self):
+ self.client._command = mock.MagicMock(spec_set=[])
+ configdrive_url = 'http://swift/configdrive'
+ self.node.instance_info['configdrive'] = configdrive_url
+ image_info = {'image_id': 'image'}
+ params = {
+ 'image_info': image_info,
+ 'configdrive': configdrive_url,
+ }
+
+ self.client.prepare_image(self.node,
+ image_info,
+ wait=False)
+ self.client._command.assert_called_once_with(
+ node=self.node, method='standby.prepare_image',
+ params=params, wait=False)
+
+ @mock.patch('uuid.uuid4', mock.MagicMock(spec_set=[], return_value='uuid'))
+ def test_start_iscsi_target(self):
+ self.client._command = mock.MagicMock(spec_set=[])
+ iqn = 'fake-iqn'
+ params = {'iqn': iqn}
+
+ self.client.start_iscsi_target(self.node, iqn)
+ self.client._command.assert_called_once_with(
+ node=self.node, method='iscsi.start_iscsi_target',
+ params=params, wait=True)
+
+ @mock.patch('uuid.uuid4', mock.MagicMock(spec_set=[], return_value='uuid'))
+ def test_install_bootloader(self):
+ self.client._command = mock.MagicMock(spec_set=[])
+ root_uuid = 'fake-root-uuid'
+ efi_system_part_uuid = 'fake-efi-system-part-uuid'
+ params = {'root_uuid': root_uuid,
+ 'efi_system_part_uuid': efi_system_part_uuid}
+
+ self.client.install_bootloader(
+ self.node, root_uuid, efi_system_part_uuid=efi_system_part_uuid)
+ self.client._command.assert_called_once_with(
+ node=self.node, method='image.install_bootloader', params=params,
+ wait=True)
+
+ def test_get_clean_steps(self):
+ self.client._command = mock.MagicMock(spec_set=[])
+ ports = []
+ expected_params = {
+ 'node': self.node.as_dict(),
+ 'ports': []
+ }
+
+ self.client.get_clean_steps(self.node,
+ ports)
+ self.client._command.assert_called_once_with(
+ node=self.node, method='clean.get_clean_steps',
+ params=expected_params, wait=True)
+
+ def test_execute_clean_step(self):
+ self.client._command = mock.MagicMock(spec_set=[])
+ ports = []
+ step = {'priority': 10, 'step': 'erase_devices', 'interface': 'deploy'}
+ expected_params = {
+ 'step': step,
+ 'node': self.node.as_dict(),
+ 'ports': [],
+ 'clean_version': self.node.driver_internal_info.get(
+ 'hardware_manager_version')
+ }
+ self.client.execute_clean_step(step,
+ self.node,
+ ports)
+ self.client._command.assert_called_once_with(
+ node=self.node, method='clean.execute_clean_step',
+ params=expected_params, wait=False)
+
+ def test_power_off(self):
+ self.client._command = mock.MagicMock(spec_set=[])
+ self.client.power_off(self.node)
+ self.client._command.assert_called_once_with(
+ node=self.node, method='standby.power_off', params={})
diff --git a/ironic/tests/unit/drivers/modules/test_console_utils.py b/ironic/tests/unit/drivers/modules/test_console_utils.py
new file mode 100644
index 000000000..23762e9f8
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/test_console_utils.py
@@ -0,0 +1,348 @@
+# coding=utf-8
+
+# Copyright 2014 International Business Machines Corporation
+# 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
+# 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.
+
+"""Test class for console_utils driver module."""
+
+import os
+import random
+import string
+import subprocess
+import tempfile
+
+import mock
+from oslo_concurrency import processutils
+from oslo_config import cfg
+from oslo_utils import netutils
+
+from ironic.common import exception
+from ironic.common import utils
+from ironic.drivers.modules import console_utils
+from ironic.drivers.modules import ipmitool as ipmi
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+
+CONF = cfg.CONF
+
+INFO_DICT = db_utils.get_test_ipmi_info()
+
+
+class ConsoleUtilsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(ConsoleUtilsTestCase, self).setUp()
+ self.node = obj_utils.get_test_node(
+ self.context,
+ driver='fake_ipmitool',
+ driver_info=INFO_DICT)
+ self.info = ipmi._parse_driver_info(self.node)
+
+ def test__get_console_pid_dir(self):
+ pid_dir = '/tmp/pid_dir'
+ self.config(terminal_pid_dir=pid_dir, group='console')
+ dir = console_utils._get_console_pid_dir()
+ self.assertEqual(pid_dir, dir)
+
+ def test__get_console_pid_dir_tempdir(self):
+ self.config(tempdir='/tmp/fake_dir')
+ dir = console_utils._get_console_pid_dir()
+ self.assertEqual(CONF.tempdir, dir)
+
+ @mock.patch.object(os, 'makedirs', autospec=True)
+ @mock.patch.object(os.path, 'exists', autospec=True)
+ def test__ensure_console_pid_dir_exists(self, mock_path_exists,
+ mock_makedirs):
+ mock_path_exists.return_value = True
+ mock_makedirs.side_effect = OSError
+ pid_dir = console_utils._get_console_pid_dir()
+
+ console_utils._ensure_console_pid_dir_exists()
+
+ mock_path_exists.assert_called_once_with(pid_dir)
+ self.assertFalse(mock_makedirs.called)
+
+ @mock.patch.object(os, 'makedirs', autospec=True)
+ @mock.patch.object(os.path, 'exists', autospec=True)
+ def test__ensure_console_pid_dir_exists_fail(self, mock_path_exists,
+ mock_makedirs):
+ mock_path_exists.return_value = False
+ mock_makedirs.side_effect = OSError
+ pid_dir = console_utils._get_console_pid_dir()
+
+ self.assertRaises(exception.ConsoleError,
+ console_utils._ensure_console_pid_dir_exists)
+
+ mock_path_exists.assert_called_once_with(pid_dir)
+ mock_makedirs.assert_called_once_with(pid_dir)
+
+ @mock.patch.object(console_utils, '_get_console_pid_dir', autospec=True)
+ def test__get_console_pid_file(self, mock_dir):
+ mock_dir.return_value = tempfile.gettempdir()
+ expected_path = '%(tempdir)s/%(uuid)s.pid' % {
+ 'tempdir': mock_dir.return_value,
+ 'uuid': self.info.get('uuid')}
+ path = console_utils._get_console_pid_file(self.info['uuid'])
+ self.assertEqual(expected_path, path)
+ mock_dir.assert_called_once_with()
+
+ @mock.patch.object(console_utils, '_get_console_pid_file', autospec=True)
+ def test__get_console_pid(self, mock_exec):
+ tmp_file_handle = tempfile.NamedTemporaryFile()
+ tmp_file = tmp_file_handle.name
+ self.addCleanup(utils.unlink_without_raise, tmp_file)
+ with open(tmp_file, "w") as f:
+ f.write("12345\n")
+
+ mock_exec.return_value = tmp_file
+
+ pid = console_utils._get_console_pid(self.info['uuid'])
+
+ mock_exec.assert_called_once_with(self.info['uuid'])
+ self.assertEqual(pid, 12345)
+
+ @mock.patch.object(console_utils, '_get_console_pid_file', autospec=True)
+ def test__get_console_pid_not_a_num(self, mock_exec):
+ tmp_file_handle = tempfile.NamedTemporaryFile()
+ tmp_file = tmp_file_handle.name
+ self.addCleanup(utils.unlink_without_raise, tmp_file)
+ with open(tmp_file, "w") as f:
+ f.write("Hello World\n")
+
+ mock_exec.return_value = tmp_file
+
+ self.assertRaises(exception.NoConsolePid,
+ console_utils._get_console_pid,
+ self.info['uuid'])
+ mock_exec.assert_called_once_with(self.info['uuid'])
+
+ def test__get_console_pid_file_not_found(self):
+ self.assertRaises(exception.NoConsolePid,
+ console_utils._get_console_pid,
+ self.info['uuid'])
+
+ @mock.patch.object(utils, 'unlink_without_raise', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ @mock.patch.object(console_utils, '_get_console_pid', autospec=True)
+ def test__stop_console(self, mock_pid, mock_execute, mock_unlink):
+ pid_file = console_utils._get_console_pid_file(self.info['uuid'])
+ mock_pid.return_value = '12345'
+
+ console_utils._stop_console(self.info['uuid'])
+
+ mock_pid.assert_called_once_with(self.info['uuid'])
+ mock_execute.assert_called_once_with('kill', mock_pid.return_value,
+ check_exit_code=[0, 99])
+ mock_unlink.assert_called_once_with(pid_file)
+
+ @mock.patch.object(utils, 'unlink_without_raise', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ @mock.patch.object(console_utils, '_get_console_pid', autospec=True)
+ def test__stop_console_nopid(self, mock_pid, mock_execute, mock_unlink):
+ pid_file = console_utils._get_console_pid_file(self.info['uuid'])
+ mock_pid.side_effect = iter(
+ [exception.NoConsolePid(pid_path="/tmp/blah")])
+
+ self.assertRaises(exception.NoConsolePid,
+ console_utils._stop_console,
+ self.info['uuid'])
+
+ mock_pid.assert_called_once_with(self.info['uuid'])
+ self.assertFalse(mock_execute.called)
+ mock_unlink.assert_called_once_with(pid_file)
+
+ @mock.patch.object(utils, 'unlink_without_raise', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ @mock.patch.object(console_utils, '_get_console_pid', autospec=True)
+ def test__stop_console_nokill(self, mock_pid, mock_execute, mock_unlink):
+ pid_file = console_utils._get_console_pid_file(self.info['uuid'])
+ mock_pid.return_value = '12345'
+ mock_execute.side_effect = iter([processutils.ProcessExecutionError()])
+
+ self.assertRaises(processutils.ProcessExecutionError,
+ console_utils._stop_console,
+ self.info['uuid'])
+
+ mock_pid.assert_called_once_with(self.info['uuid'])
+ mock_execute.assert_called_once_with('kill', mock_pid.return_value,
+ check_exit_code=[0, 99])
+ mock_unlink.assert_called_once_with(pid_file)
+
+ def _get_shellinabox_console(self, scheme):
+ generated_url = (
+ console_utils.get_shellinabox_console_url(self.info['port']))
+ console_host = CONF.my_ip
+ if netutils.is_valid_ipv6(console_host):
+ console_host = '[%s]' % console_host
+ http_url = "%s://%s:%s" % (scheme, console_host, self.info['port'])
+ self.assertEqual(http_url, generated_url)
+
+ def test_get_shellinabox_console_url(self):
+ self._get_shellinabox_console('http')
+
+ def test_get_shellinabox_console_https_url(self):
+ # specify terminal_cert_dir in /etc/ironic/ironic.conf
+ self.config(terminal_cert_dir='/tmp', group='console')
+ # use https
+ self._get_shellinabox_console('https')
+
+ def test_make_persistent_password_file(self):
+ filepath = '%(tempdir)s/%(node_uuid)s' % {
+ 'tempdir': tempfile.gettempdir(),
+ 'node_uuid': self.info['uuid']}
+ password = ''.join([random.choice(string.ascii_letters)
+ for n in range(16)])
+ console_utils.make_persistent_password_file(filepath, password)
+ # make sure file exists
+ self.assertTrue(os.path.exists(filepath))
+ # make sure the content is correct
+ with open(filepath) as file:
+ content = file.read()
+ self.assertEqual(password, content)
+ # delete the file
+ os.unlink(filepath)
+
+ @mock.patch.object(os, 'chmod', autospec=True)
+ def test_make_persistent_password_file_fail(self, mock_chmod):
+ mock_chmod.side_effect = IOError()
+ filepath = '%(tempdir)s/%(node_uuid)s' % {
+ 'tempdir': tempfile.gettempdir(),
+ 'node_uuid': self.info['uuid']}
+ self.assertRaises(exception.PasswordFileFailedToCreate,
+ console_utils.make_persistent_password_file,
+ filepath,
+ 'password')
+
+ @mock.patch.object(subprocess, 'Popen', autospec=True)
+ @mock.patch.object(console_utils, '_ensure_console_pid_dir_exists',
+ autospec=True)
+ @mock.patch.object(console_utils, '_stop_console', autospec=True)
+ def test_start_shellinabox_console(self, mock_stop, mock_dir_exists,
+ mock_popen):
+ mock_popen.return_value.poll.return_value = 0
+
+ # touch the pid file
+ pid_file = console_utils._get_console_pid_file(self.info['uuid'])
+ open(pid_file, 'a').close()
+ self.addCleanup(os.remove, pid_file)
+ self.assertTrue(os.path.exists(pid_file))
+
+ console_utils.start_shellinabox_console(self.info['uuid'],
+ self.info['port'],
+ 'ls&')
+
+ mock_stop.assert_called_once_with(self.info['uuid'])
+ mock_dir_exists.assert_called_once_with()
+ mock_popen.assert_called_once_with(mock.ANY,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ mock_popen.return_value.poll.assert_called_once_with()
+
+ @mock.patch.object(subprocess, 'Popen', autospec=True)
+ @mock.patch.object(console_utils, '_ensure_console_pid_dir_exists',
+ autospec=True)
+ @mock.patch.object(console_utils, '_stop_console', autospec=True)
+ def test_start_shellinabox_console_nopid(self, mock_stop, mock_dir_exists,
+ mock_popen):
+ # no existing PID file before starting
+ mock_stop.side_effect = iter([exception.NoConsolePid('/tmp/blah')])
+ mock_popen.return_value.poll.return_value = 0
+
+ # touch the pid file
+ pid_file = console_utils._get_console_pid_file(self.info['uuid'])
+ open(pid_file, 'a').close()
+ self.addCleanup(os.remove, pid_file)
+ self.assertTrue(os.path.exists(pid_file))
+
+ console_utils.start_shellinabox_console(self.info['uuid'],
+ self.info['port'],
+ 'ls&')
+
+ mock_stop.assert_called_once_with(self.info['uuid'])
+ mock_dir_exists.assert_called_once_with()
+ mock_popen.assert_called_once_with(mock.ANY,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ mock_popen.return_value.poll.assert_called_once_with()
+
+ @mock.patch.object(subprocess, 'Popen', autospec=True)
+ @mock.patch.object(console_utils, '_ensure_console_pid_dir_exists',
+ autospec=True)
+ @mock.patch.object(console_utils, '_stop_console', autospec=True)
+ def test_start_shellinabox_console_fail(self, mock_stop, mock_dir_exists,
+ mock_popen):
+ mock_popen.return_value.poll.return_value = 1
+ mock_popen.return_value.communicate.return_value = ('output', 'error')
+
+ self.assertRaises(exception.ConsoleSubprocessFailed,
+ console_utils.start_shellinabox_console,
+ self.info['uuid'],
+ self.info['port'],
+ 'ls&')
+
+ mock_stop.assert_called_once_with(self.info['uuid'])
+ mock_dir_exists.assert_called_once_with()
+ mock_popen.assert_called_once_with(mock.ANY,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ mock_popen.return_value.poll.assert_called_once_with()
+
+ @mock.patch.object(subprocess, 'Popen', autospec=True)
+ @mock.patch.object(console_utils, '_ensure_console_pid_dir_exists',
+ autospec=True)
+ @mock.patch.object(console_utils, '_stop_console', autospec=True)
+ def test_start_shellinabox_console_fail_nopiddir(self, mock_stop,
+ mock_dir_exists,
+ mock_popen):
+ mock_dir_exists.side_effect = iter(
+ [exception.ConsoleError(message='fail')])
+ mock_popen.return_value.poll.return_value = 0
+
+ self.assertRaises(exception.ConsoleError,
+ console_utils.start_shellinabox_console,
+ self.info['uuid'],
+ self.info['port'],
+ 'ls&')
+
+ mock_stop.assert_called_once_with(self.info['uuid'])
+ mock_dir_exists.assert_called_once_with()
+ self.assertFalse(mock_popen.called)
+
+ @mock.patch.object(console_utils, '_stop_console', autospec=True)
+ def test_stop_shellinabox_console(self, mock_stop):
+
+ console_utils.stop_shellinabox_console(self.info['uuid'])
+
+ mock_stop.assert_called_once_with(self.info['uuid'])
+
+ @mock.patch.object(console_utils, '_stop_console', autospec=True)
+ def test_stop_shellinabox_console_fail_nopid(self, mock_stop):
+ mock_stop.side_effect = iter([exception.NoConsolePid('/tmp/blah')])
+
+ console_utils.stop_shellinabox_console(self.info['uuid'])
+
+ mock_stop.assert_called_once_with(self.info['uuid'])
+
+ @mock.patch.object(console_utils, '_stop_console', autospec=True)
+ def test_stop_shellinabox_console_fail_nokill(self, mock_stop):
+ mock_stop.side_effect = iter([processutils.ProcessExecutionError()])
+
+ self.assertRaises(exception.ConsoleError,
+ console_utils.stop_shellinabox_console,
+ self.info['uuid'])
+
+ mock_stop.assert_called_once_with(self.info['uuid'])
diff --git a/ironic/tests/unit/drivers/modules/test_deploy_utils.py b/ironic/tests/unit/drivers/modules/test_deploy_utils.py
new file mode 100644
index 000000000..d43e7e716
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/test_deploy_utils.py
@@ -0,0 +1,2288 @@
+# Copyright (c) 2012 NTT DOCOMO, INC.
+# Copyright 2011 OpenStack Foundation
+# Copyright 2011 Ilya Alekseyev
+#
+# 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 base64
+import gzip
+import os
+import shutil
+import stat
+import tempfile
+import time
+import types
+
+import mock
+from oslo_concurrency import processutils
+from oslo_config import cfg
+from oslo_utils import uuidutils
+import requests
+import testtools
+
+from ironic.common import boot_devices
+from ironic.common import disk_partitioner
+from ironic.common import exception
+from ironic.common import image_service
+from ironic.common import images
+from ironic.common import keystone
+from ironic.common import states
+from ironic.common import utils as common_utils
+from ironic.conductor import task_manager
+from ironic.conductor import utils as manager_utils
+from ironic.drivers.modules import agent_client
+from ironic.drivers.modules import deploy_utils as utils
+from ironic.drivers.modules import image_cache
+from ironic.drivers.modules import pxe
+from ironic.tests.unit import base as tests_base
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+INST_INFO_DICT = db_utils.get_test_pxe_instance_info()
+DRV_INFO_DICT = db_utils.get_test_pxe_driver_info()
+DRV_INTERNAL_INFO_DICT = db_utils.get_test_pxe_driver_internal_info()
+
+_PXECONF_DEPLOY = b"""
+default deploy
+
+label deploy
+kernel deploy_kernel
+append initrd=deploy_ramdisk
+ipappend 3
+
+label boot_partition
+kernel kernel
+append initrd=ramdisk root={{ ROOT }}
+
+label boot_whole_disk
+COM32 chain.c32
+append mbr:{{ DISK_IDENTIFIER }}
+
+label trusted_boot
+kernel mboot
+append tboot.gz --- kernel root={{ ROOT }} --- ramdisk
+"""
+
+_PXECONF_BOOT_PARTITION = """
+default boot_partition
+
+label deploy
+kernel deploy_kernel
+append initrd=deploy_ramdisk
+ipappend 3
+
+label boot_partition
+kernel kernel
+append initrd=ramdisk root=UUID=12345678-1234-1234-1234-1234567890abcdef
+
+label boot_whole_disk
+COM32 chain.c32
+append mbr:{{ DISK_IDENTIFIER }}
+
+label trusted_boot
+kernel mboot
+append tboot.gz --- kernel root=UUID=12345678-1234-1234-1234-1234567890abcdef \
+--- ramdisk
+"""
+
+_PXECONF_BOOT_WHOLE_DISK = """
+default boot_whole_disk
+
+label deploy
+kernel deploy_kernel
+append initrd=deploy_ramdisk
+ipappend 3
+
+label boot_partition
+kernel kernel
+append initrd=ramdisk root={{ ROOT }}
+
+label boot_whole_disk
+COM32 chain.c32
+append mbr:0x12345678
+
+label trusted_boot
+kernel mboot
+append tboot.gz --- kernel root={{ ROOT }} --- ramdisk
+"""
+
+_PXECONF_TRUSTED_BOOT = """
+default trusted_boot
+
+label deploy
+kernel deploy_kernel
+append initrd=deploy_ramdisk
+ipappend 3
+
+label boot_partition
+kernel kernel
+append initrd=ramdisk root=UUID=12345678-1234-1234-1234-1234567890abcdef
+
+label boot_whole_disk
+COM32 chain.c32
+append mbr:{{ DISK_IDENTIFIER }}
+
+label trusted_boot
+kernel mboot
+append tboot.gz --- kernel root=UUID=12345678-1234-1234-1234-1234567890abcdef \
+--- ramdisk
+"""
+
+_IPXECONF_DEPLOY = b"""
+#!ipxe
+
+dhcp
+
+goto deploy
+
+:deploy
+kernel deploy_kernel
+initrd deploy_ramdisk
+boot
+
+:boot_partition
+kernel kernel
+append initrd=ramdisk root={{ ROOT }}
+boot
+
+:boot_whole_disk
+kernel chain.c32
+append mbr:{{ DISK_IDENTIFIER }}
+boot
+"""
+
+_IPXECONF_BOOT_PARTITION = """
+#!ipxe
+
+dhcp
+
+goto boot_partition
+
+:deploy
+kernel deploy_kernel
+initrd deploy_ramdisk
+boot
+
+:boot_partition
+kernel kernel
+append initrd=ramdisk root=UUID=12345678-1234-1234-1234-1234567890abcdef
+boot
+
+:boot_whole_disk
+kernel chain.c32
+append mbr:{{ DISK_IDENTIFIER }}
+boot
+"""
+
+_IPXECONF_BOOT_WHOLE_DISK = """
+#!ipxe
+
+dhcp
+
+goto boot_whole_disk
+
+:deploy
+kernel deploy_kernel
+initrd deploy_ramdisk
+boot
+
+:boot_partition
+kernel kernel
+append initrd=ramdisk root={{ ROOT }}
+boot
+
+:boot_whole_disk
+kernel chain.c32
+append mbr:0x12345678
+boot
+"""
+
+_UEFI_PXECONF_DEPLOY = b"""
+default=deploy
+
+image=deploy_kernel
+ label=deploy
+ initrd=deploy_ramdisk
+ append="ro text"
+
+image=kernel
+ label=boot_partition
+ initrd=ramdisk
+ append="root={{ ROOT }}"
+
+image=chain.c32
+ label=boot_whole_disk
+ append="mbr:{{ DISK_IDENTIFIER }}"
+"""
+
+_UEFI_PXECONF_BOOT_PARTITION = """
+default=boot_partition
+
+image=deploy_kernel
+ label=deploy
+ initrd=deploy_ramdisk
+ append="ro text"
+
+image=kernel
+ label=boot_partition
+ initrd=ramdisk
+ append="root=UUID=12345678-1234-1234-1234-1234567890abcdef"
+
+image=chain.c32
+ label=boot_whole_disk
+ append="mbr:{{ DISK_IDENTIFIER }}"
+"""
+
+_UEFI_PXECONF_BOOT_WHOLE_DISK = """
+default=boot_whole_disk
+
+image=deploy_kernel
+ label=deploy
+ initrd=deploy_ramdisk
+ append="ro text"
+
+image=kernel
+ label=boot_partition
+ initrd=ramdisk
+ append="root={{ ROOT }}"
+
+image=chain.c32
+ label=boot_whole_disk
+ append="mbr:0x12345678"
+"""
+
+_UEFI_PXECONF_DEPLOY_GRUB = b"""
+set default=deploy
+set timeout=5
+set hidden_timeout_quiet=false
+
+menuentry "deploy" {
+ linuxefi deploy_kernel "ro text"
+ initrdefi deploy_ramdisk
+}
+
+menuentry "boot_partition" {
+ linuxefi kernel "root=(( ROOT ))"
+ initrdefi ramdisk
+}
+
+menuentry "boot_whole_disk" {
+ linuxefi chain.c32 mbr:(( DISK_IDENTIFIER ))
+}
+"""
+
+_UEFI_PXECONF_BOOT_PARTITION_GRUB = """
+set default=boot_partition
+set timeout=5
+set hidden_timeout_quiet=false
+
+menuentry "deploy" {
+ linuxefi deploy_kernel "ro text"
+ initrdefi deploy_ramdisk
+}
+
+menuentry "boot_partition" {
+ linuxefi kernel "root=UUID=12345678-1234-1234-1234-1234567890abcdef"
+ initrdefi ramdisk
+}
+
+menuentry "boot_whole_disk" {
+ linuxefi chain.c32 mbr:(( DISK_IDENTIFIER ))
+}
+"""
+
+_UEFI_PXECONF_BOOT_WHOLE_DISK_GRUB = """
+set default=boot_whole_disk
+set timeout=5
+set hidden_timeout_quiet=false
+
+menuentry "deploy" {
+ linuxefi deploy_kernel "ro text"
+ initrdefi deploy_ramdisk
+}
+
+menuentry "boot_partition" {
+ linuxefi kernel "root=(( ROOT ))"
+ initrdefi ramdisk
+}
+
+menuentry "boot_whole_disk" {
+ linuxefi chain.c32 mbr:0x12345678
+}
+"""
+
+
+@mock.patch.object(time, 'sleep', lambda seconds: None)
+class PhysicalWorkTestCase(tests_base.TestCase):
+
+ def _mock_calls(self, name_list):
+ patch_list = [mock.patch.object(utils, name,
+ spec_set=types.FunctionType)
+ for name in name_list]
+ mock_list = [patcher.start() for patcher in patch_list]
+ for patcher in patch_list:
+ self.addCleanup(patcher.stop)
+
+ parent_mock = mock.MagicMock(spec=[])
+ for mocker, name in zip(mock_list, name_list):
+ parent_mock.attach_mock(mocker, name)
+ return parent_mock
+
+ @mock.patch.object(common_utils, 'mkfs', autospec=True)
+ def _test_deploy_partition_image(self, mock_mkfs, boot_option=None,
+ boot_mode=None):
+ """Check loosely all functions are called with right args."""
+ address = '127.0.0.1'
+ port = 3306
+ iqn = 'iqn.xyz'
+ lun = 1
+ image_path = '/tmp/xyz/image'
+ root_mb = 128
+ swap_mb = 64
+ ephemeral_mb = 0
+ ephemeral_format = None
+ configdrive_mb = 0
+ node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
+
+ dev = '/dev/fake'
+ swap_part = '/dev/fake-part1'
+ root_part = '/dev/fake-part2'
+ root_uuid = '12345678-1234-1234-12345678-12345678abcdef'
+
+ name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi',
+ 'logout_iscsi', 'delete_iscsi', 'make_partitions',
+ 'is_block_device', 'populate_image', 'block_uuid',
+ 'notify', 'destroy_disk_metadata']
+ parent_mock = self._mock_calls(name_list)
+ parent_mock.get_dev.return_value = dev
+ parent_mock.get_image_mb.return_value = 1
+ parent_mock.is_block_device.return_value = True
+ parent_mock.block_uuid.return_value = root_uuid
+ parent_mock.make_partitions.return_value = {'root': root_part,
+ 'swap': swap_part}
+
+ make_partitions_expected_args = [dev, root_mb, swap_mb, ephemeral_mb,
+ configdrive_mb, node_uuid]
+ make_partitions_expected_kwargs = {'commit': True}
+ deploy_kwargs = {}
+
+ if boot_option:
+ make_partitions_expected_kwargs['boot_option'] = boot_option
+ deploy_kwargs['boot_option'] = boot_option
+ else:
+ make_partitions_expected_kwargs['boot_option'] = 'netboot'
+
+ if boot_mode:
+ make_partitions_expected_kwargs['boot_mode'] = boot_mode
+ deploy_kwargs['boot_mode'] = boot_mode
+ else:
+ make_partitions_expected_kwargs['boot_mode'] = 'bios'
+
+ # If no boot_option, then it should default to netboot.
+ calls_expected = [mock.call.get_image_mb(image_path),
+ mock.call.get_dev(address, port, iqn, lun),
+ mock.call.discovery(address, port),
+ mock.call.login_iscsi(address, port, iqn),
+ mock.call.is_block_device(dev),
+ mock.call.destroy_disk_metadata(dev, node_uuid),
+ mock.call.make_partitions(
+ *make_partitions_expected_args,
+ **make_partitions_expected_kwargs),
+ mock.call.is_block_device(root_part),
+ mock.call.is_block_device(swap_part),
+ mock.call.populate_image(image_path, root_part),
+ mock.call.block_uuid(root_part),
+ mock.call.logout_iscsi(address, port, iqn),
+ mock.call.delete_iscsi(address, port, iqn)]
+
+ uuids_dict_returned = utils.deploy_partition_image(
+ address, port, iqn, lun, image_path, root_mb, swap_mb,
+ ephemeral_mb, ephemeral_format, node_uuid, **deploy_kwargs)
+
+ self.assertEqual(calls_expected, parent_mock.mock_calls)
+ expected_uuid_dict = {
+ 'root uuid': root_uuid,
+ 'efi system partition uuid': None}
+ self.assertEqual(expected_uuid_dict, uuids_dict_returned)
+ mock_mkfs.assert_called_once_with('swap', swap_part, 'swap1')
+
+ def test_deploy_partition_image_without_boot_option(self):
+ self._test_deploy_partition_image()
+
+ def test_deploy_partition_image_netboot(self):
+ self._test_deploy_partition_image(boot_option="netboot")
+
+ def test_deploy_partition_image_localboot(self):
+ self._test_deploy_partition_image(boot_option="local")
+
+ def test_deploy_partition_image_wo_boot_option_and_wo_boot_mode(self):
+ self._test_deploy_partition_image()
+
+ def test_deploy_partition_image_netboot_bios(self):
+ self._test_deploy_partition_image(boot_option="netboot",
+ boot_mode="bios")
+
+ def test_deploy_partition_image_localboot_bios(self):
+ self._test_deploy_partition_image(boot_option="local",
+ boot_mode="bios")
+
+ def test_deploy_partition_image_netboot_uefi(self):
+ self._test_deploy_partition_image(boot_option="netboot",
+ boot_mode="uefi")
+
+ @mock.patch.object(utils, 'get_image_mb', return_value=129, autospec=True)
+ def test_deploy_partition_image_image_exceeds_root_partition(self,
+ gim_mock):
+ address = '127.0.0.1'
+ port = 3306
+ iqn = 'iqn.xyz'
+ lun = 1
+ image_path = '/tmp/xyz/image'
+ root_mb = 128
+ swap_mb = 64
+ ephemeral_mb = 0
+ ephemeral_format = None
+ node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
+
+ self.assertRaises(exception.InstanceDeployFailure,
+ utils.deploy_partition_image, address, port, iqn,
+ lun, image_path, root_mb, swap_mb, ephemeral_mb,
+ ephemeral_format, node_uuid)
+
+ gim_mock.assert_called_once_with(image_path)
+
+ # We mock utils.block_uuid separately here because we can't predict
+ # the order in which it will be called.
+ @mock.patch.object(utils, 'block_uuid', autospec=True)
+ @mock.patch.object(common_utils, 'mkfs', autospec=True)
+ def test_deploy_partition_image_localboot_uefi(self, mock_mkfs,
+ block_uuid_mock):
+ """Check loosely all functions are called with right args."""
+ address = '127.0.0.1'
+ port = 3306
+ iqn = 'iqn.xyz'
+ lun = 1
+ image_path = '/tmp/xyz/image'
+ root_mb = 128
+ swap_mb = 64
+ ephemeral_mb = 0
+ ephemeral_format = None
+ configdrive_mb = 0
+ node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
+
+ dev = '/dev/fake'
+ swap_part = '/dev/fake-part2'
+ root_part = '/dev/fake-part3'
+ efi_system_part = '/dev/fake-part1'
+ root_uuid = '12345678-1234-1234-12345678-12345678abcdef'
+ efi_system_part_uuid = '9036-482'
+
+ name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi',
+ 'logout_iscsi', 'delete_iscsi', 'make_partitions',
+ 'is_block_device', 'populate_image', 'notify',
+ 'destroy_disk_metadata']
+ parent_mock = self._mock_calls(name_list)
+ parent_mock.get_dev.return_value = dev
+ parent_mock.get_image_mb.return_value = 1
+ parent_mock.is_block_device.return_value = True
+
+ def block_uuid_side_effect(device):
+ if device == root_part:
+ return root_uuid
+ if device == efi_system_part:
+ return efi_system_part_uuid
+
+ block_uuid_mock.side_effect = block_uuid_side_effect
+ parent_mock.make_partitions.return_value = {
+ 'root': root_part, 'swap': swap_part,
+ 'efi system partition': efi_system_part}
+
+ # If no boot_option, then it should default to netboot.
+ calls_expected = [mock.call.get_image_mb(image_path),
+ mock.call.get_dev(address, port, iqn, lun),
+ mock.call.discovery(address, port),
+ mock.call.login_iscsi(address, port, iqn),
+ mock.call.is_block_device(dev),
+ mock.call.destroy_disk_metadata(dev, node_uuid),
+ mock.call.make_partitions(dev, root_mb, swap_mb,
+ ephemeral_mb,
+ configdrive_mb,
+ node_uuid,
+ commit=True,
+ boot_option="local",
+ boot_mode="uefi"),
+ mock.call.is_block_device(root_part),
+ mock.call.is_block_device(swap_part),
+ mock.call.is_block_device(efi_system_part),
+ mock.call.populate_image(image_path, root_part),
+ mock.call.logout_iscsi(address, port, iqn),
+ mock.call.delete_iscsi(address, port, iqn)]
+
+ uuid_dict_returned = utils.deploy_partition_image(
+ address, port, iqn, lun, image_path, root_mb, swap_mb,
+ ephemeral_mb, ephemeral_format, node_uuid, boot_option="local",
+ boot_mode="uefi")
+
+ self.assertEqual(calls_expected, parent_mock.mock_calls)
+ block_uuid_mock.assert_any_call('/dev/fake-part1')
+ block_uuid_mock.assert_any_call('/dev/fake-part3')
+ expected_uuid_dict = {
+ 'root uuid': root_uuid,
+ 'efi system partition uuid': efi_system_part_uuid}
+ self.assertEqual(expected_uuid_dict, uuid_dict_returned)
+ expected_calls = [mock.call('vfat', efi_system_part, 'efi-part'),
+ mock.call('swap', swap_part, 'swap1')]
+ mock_mkfs.assert_has_calls(expected_calls)
+
+ def test_deploy_partition_image_without_swap(self):
+ """Check loosely all functions are called with right args."""
+ address = '127.0.0.1'
+ port = 3306
+ iqn = 'iqn.xyz'
+ lun = 1
+ image_path = '/tmp/xyz/image'
+ root_mb = 128
+ swap_mb = 0
+ ephemeral_mb = 0
+ ephemeral_format = None
+ configdrive_mb = 0
+ node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
+
+ dev = '/dev/fake'
+ root_part = '/dev/fake-part1'
+ root_uuid = '12345678-1234-1234-12345678-12345678abcdef'
+
+ name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi',
+ 'logout_iscsi', 'delete_iscsi', 'make_partitions',
+ 'is_block_device', 'populate_image', 'block_uuid',
+ 'notify', 'destroy_disk_metadata']
+ parent_mock = self._mock_calls(name_list)
+ parent_mock.get_dev.return_value = dev
+ parent_mock.get_image_mb.return_value = 1
+ parent_mock.is_block_device.return_value = True
+ parent_mock.block_uuid.return_value = root_uuid
+ parent_mock.make_partitions.return_value = {'root': root_part}
+ calls_expected = [mock.call.get_image_mb(image_path),
+ mock.call.get_dev(address, port, iqn, lun),
+ mock.call.discovery(address, port),
+ mock.call.login_iscsi(address, port, iqn),
+ mock.call.is_block_device(dev),
+ mock.call.destroy_disk_metadata(dev, node_uuid),
+ mock.call.make_partitions(dev, root_mb, swap_mb,
+ ephemeral_mb,
+ configdrive_mb,
+ node_uuid,
+ commit=True,
+ boot_option="netboot",
+ boot_mode="bios"),
+ mock.call.is_block_device(root_part),
+ mock.call.populate_image(image_path, root_part),
+ mock.call.block_uuid(root_part),
+ mock.call.logout_iscsi(address, port, iqn),
+ mock.call.delete_iscsi(address, port, iqn)]
+
+ uuid_dict_returned = utils.deploy_partition_image(address, port, iqn,
+ lun, image_path,
+ root_mb, swap_mb,
+ ephemeral_mb,
+ ephemeral_format,
+ node_uuid)
+
+ self.assertEqual(calls_expected, parent_mock.mock_calls)
+ self.assertEqual(root_uuid, uuid_dict_returned['root uuid'])
+
+ @mock.patch.object(common_utils, 'mkfs', autospec=True)
+ def test_deploy_partition_image_with_ephemeral(self, mock_mkfs):
+ """Check loosely all functions are called with right args."""
+ address = '127.0.0.1'
+ port = 3306
+ iqn = 'iqn.xyz'
+ lun = 1
+ image_path = '/tmp/xyz/image'
+ root_mb = 128
+ swap_mb = 64
+ ephemeral_mb = 256
+ configdrive_mb = 0
+ ephemeral_format = 'exttest'
+ node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
+
+ dev = '/dev/fake'
+ ephemeral_part = '/dev/fake-part1'
+ swap_part = '/dev/fake-part2'
+ root_part = '/dev/fake-part3'
+ root_uuid = '12345678-1234-1234-12345678-12345678abcdef'
+
+ name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi',
+ 'logout_iscsi', 'delete_iscsi', 'make_partitions',
+ 'is_block_device', 'populate_image', 'block_uuid',
+ 'notify', 'destroy_disk_metadata']
+ parent_mock = self._mock_calls(name_list)
+ parent_mock.get_dev.return_value = dev
+ parent_mock.get_image_mb.return_value = 1
+ parent_mock.is_block_device.return_value = True
+ parent_mock.block_uuid.return_value = root_uuid
+ parent_mock.make_partitions.return_value = {
+ 'swap': swap_part, 'ephemeral': ephemeral_part, 'root': root_part}
+ calls_expected = [mock.call.get_image_mb(image_path),
+ mock.call.get_dev(address, port, iqn, lun),
+ mock.call.discovery(address, port),
+ mock.call.login_iscsi(address, port, iqn),
+ mock.call.is_block_device(dev),
+ mock.call.destroy_disk_metadata(dev, node_uuid),
+ mock.call.make_partitions(dev, root_mb, swap_mb,
+ ephemeral_mb,
+ configdrive_mb,
+ node_uuid,
+ commit=True,
+ boot_option="netboot",
+ boot_mode="bios"),
+ mock.call.is_block_device(root_part),
+ mock.call.is_block_device(swap_part),
+ mock.call.is_block_device(ephemeral_part),
+ mock.call.populate_image(image_path, root_part),
+ mock.call.block_uuid(root_part),
+ mock.call.logout_iscsi(address, port, iqn),
+ mock.call.delete_iscsi(address, port, iqn)]
+
+ uuid_dict_returned = utils.deploy_partition_image(address, port, iqn,
+ lun, image_path,
+ root_mb, swap_mb,
+ ephemeral_mb,
+ ephemeral_format,
+ node_uuid)
+
+ self.assertEqual(calls_expected, parent_mock.mock_calls)
+ self.assertEqual(root_uuid, uuid_dict_returned['root uuid'])
+ expected_calls = [mock.call('swap', swap_part, 'swap1'),
+ mock.call(ephemeral_format, ephemeral_part,
+ 'ephemeral0')]
+ mock_mkfs.assert_has_calls(expected_calls)
+
+ @mock.patch.object(common_utils, 'mkfs', autospec=True)
+ def test_deploy_partition_image_preserve_ephemeral(self, mock_mkfs):
+ """Check if all functions are called with right args."""
+ address = '127.0.0.1'
+ port = 3306
+ iqn = 'iqn.xyz'
+ lun = 1
+ image_path = '/tmp/xyz/image'
+ root_mb = 128
+ swap_mb = 64
+ ephemeral_mb = 256
+ ephemeral_format = 'exttest'
+ configdrive_mb = 0
+ node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
+
+ dev = '/dev/fake'
+ ephemeral_part = '/dev/fake-part1'
+ swap_part = '/dev/fake-part2'
+ root_part = '/dev/fake-part3'
+ root_uuid = '12345678-1234-1234-12345678-12345678abcdef'
+
+ name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi',
+ 'logout_iscsi', 'delete_iscsi', 'make_partitions',
+ 'is_block_device', 'populate_image', 'block_uuid',
+ 'notify', 'get_dev_block_size']
+ parent_mock = self._mock_calls(name_list)
+ parent_mock.get_dev.return_value = dev
+ parent_mock.get_image_mb.return_value = 1
+ parent_mock.is_block_device.return_value = True
+ parent_mock.block_uuid.return_value = root_uuid
+ parent_mock.make_partitions.return_value = {
+ 'swap': swap_part, 'ephemeral': ephemeral_part, 'root': root_part}
+ parent_mock.block_uuid.return_value = root_uuid
+ calls_expected = [mock.call.get_image_mb(image_path),
+ mock.call.get_dev(address, port, iqn, lun),
+ mock.call.discovery(address, port),
+ mock.call.login_iscsi(address, port, iqn),
+ mock.call.is_block_device(dev),
+ mock.call.make_partitions(dev, root_mb, swap_mb,
+ ephemeral_mb,
+ configdrive_mb,
+ node_uuid,
+ commit=False,
+ boot_option="netboot",
+ boot_mode="bios"),
+ mock.call.is_block_device(root_part),
+ mock.call.is_block_device(swap_part),
+ mock.call.is_block_device(ephemeral_part),
+ mock.call.populate_image(image_path, root_part),
+ mock.call.block_uuid(root_part),
+ mock.call.logout_iscsi(address, port, iqn),
+ mock.call.delete_iscsi(address, port, iqn)]
+
+ uuid_dict_returned = utils.deploy_partition_image(
+ address, port, iqn, lun, image_path, root_mb, swap_mb,
+ ephemeral_mb, ephemeral_format, node_uuid,
+ preserve_ephemeral=True, boot_option="netboot")
+ self.assertEqual(calls_expected, parent_mock.mock_calls)
+ self.assertFalse(parent_mock.get_dev_block_size.called)
+ self.assertEqual(root_uuid, uuid_dict_returned['root uuid'])
+ mock_mkfs.assert_called_once_with('swap', swap_part, 'swap1')
+
+ @mock.patch.object(common_utils, 'unlink_without_raise', autospec=True)
+ def test_deploy_partition_image_with_configdrive(self, mock_unlink):
+ """Check loosely all functions are called with right args."""
+ address = '127.0.0.1'
+ port = 3306
+ iqn = 'iqn.xyz'
+ lun = 1
+ image_path = '/tmp/xyz/image'
+ root_mb = 128
+ swap_mb = 0
+ ephemeral_mb = 0
+ configdrive_mb = 10
+ ephemeral_format = None
+ node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
+ configdrive_url = 'http://1.2.3.4/cd'
+
+ dev = '/dev/fake'
+ configdrive_part = '/dev/fake-part1'
+ root_part = '/dev/fake-part2'
+ root_uuid = '12345678-1234-1234-12345678-12345678abcdef'
+
+ name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi',
+ 'logout_iscsi', 'delete_iscsi', 'make_partitions',
+ 'is_block_device', 'populate_image', 'block_uuid',
+ 'notify', 'destroy_disk_metadata', 'dd',
+ '_get_configdrive']
+ parent_mock = self._mock_calls(name_list)
+ parent_mock.get_dev.return_value = dev
+ parent_mock.get_image_mb.return_value = 1
+ parent_mock.is_block_device.return_value = True
+ parent_mock.block_uuid.return_value = root_uuid
+ parent_mock.make_partitions.return_value = {'root': root_part,
+ 'configdrive':
+ configdrive_part}
+ parent_mock._get_configdrive.return_value = (10, 'configdrive-path')
+ calls_expected = [mock.call.get_image_mb(image_path),
+ mock.call.get_dev(address, port, iqn, lun),
+ mock.call.discovery(address, port),
+ mock.call.login_iscsi(address, port, iqn),
+ mock.call.is_block_device(dev),
+ mock.call.destroy_disk_metadata(dev, node_uuid),
+ mock.call._get_configdrive(configdrive_url,
+ node_uuid),
+ mock.call.make_partitions(dev, root_mb, swap_mb,
+ ephemeral_mb,
+ configdrive_mb,
+ node_uuid,
+ commit=True,
+ boot_option="netboot",
+ boot_mode="bios"),
+ mock.call.is_block_device(root_part),
+ mock.call.is_block_device(configdrive_part),
+ mock.call.dd(mock.ANY, configdrive_part),
+ mock.call.populate_image(image_path, root_part),
+ mock.call.block_uuid(root_part),
+ mock.call.logout_iscsi(address, port, iqn),
+ mock.call.delete_iscsi(address, port, iqn)]
+
+ uuid_dict_returned = utils.deploy_partition_image(
+ address, port, iqn, lun, image_path, root_mb, swap_mb,
+ ephemeral_mb, ephemeral_format, node_uuid,
+ configdrive=configdrive_url)
+
+ self.assertEqual(calls_expected, parent_mock.mock_calls)
+ self.assertEqual(root_uuid, uuid_dict_returned['root uuid'])
+ mock_unlink.assert_called_once_with('configdrive-path')
+
+ @mock.patch.object(utils, 'get_disk_identifier', autospec=True)
+ def test_deploy_whole_disk_image(self, mock_gdi):
+ """Check loosely all functions are called with right args."""
+ address = '127.0.0.1'
+ port = 3306
+ iqn = 'iqn.xyz'
+ lun = 1
+ image_path = '/tmp/xyz/image'
+ node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
+
+ dev = '/dev/fake'
+ name_list = ['get_dev', 'discovery', 'login_iscsi', 'logout_iscsi',
+ 'delete_iscsi', 'is_block_device', 'populate_image',
+ 'notify']
+ parent_mock = self._mock_calls(name_list)
+ parent_mock.get_dev.return_value = dev
+ parent_mock.is_block_device.return_value = True
+ mock_gdi.return_value = '0x12345678'
+ calls_expected = [mock.call.get_dev(address, port, iqn, lun),
+ mock.call.discovery(address, port),
+ mock.call.login_iscsi(address, port, iqn),
+ mock.call.is_block_device(dev),
+ mock.call.populate_image(image_path, dev),
+ mock.call.logout_iscsi(address, port, iqn),
+ mock.call.delete_iscsi(address, port, iqn)]
+
+ uuid_dict_returned = utils.deploy_disk_image(address, port, iqn, lun,
+ image_path, node_uuid)
+
+ self.assertEqual(calls_expected, parent_mock.mock_calls)
+ self.assertEqual('0x12345678', uuid_dict_returned['disk identifier'])
+
+ @mock.patch.object(common_utils, 'execute', autospec=True)
+ def test_verify_iscsi_connection_raises(self, mock_exec):
+ iqn = 'iqn.xyz'
+ mock_exec.return_value = ['iqn.abc', '']
+ self.assertRaises(exception.InstanceDeployFailure,
+ utils.verify_iscsi_connection, iqn)
+ self.assertEqual(3, mock_exec.call_count)
+
+ @mock.patch.object(os.path, 'exists', autospec=True)
+ def test_check_file_system_for_iscsi_device_raises(self, mock_os):
+ iqn = 'iqn.xyz'
+ ip = "127.0.0.1"
+ port = "22"
+ mock_os.return_value = False
+ self.assertRaises(exception.InstanceDeployFailure,
+ utils.check_file_system_for_iscsi_device,
+ ip, port, iqn)
+ self.assertEqual(3, mock_os.call_count)
+
+ @mock.patch.object(os.path, 'exists', autospec=True)
+ def test_check_file_system_for_iscsi_device(self, mock_os):
+ iqn = 'iqn.xyz'
+ ip = "127.0.0.1"
+ port = "22"
+ check_dir = "/dev/disk/by-path/ip-%s:%s-iscsi-%s-lun-1" % (ip,
+ port,
+ iqn)
+
+ mock_os.return_value = True
+ utils.check_file_system_for_iscsi_device(ip, port, iqn)
+ mock_os.assert_called_once_with(check_dir)
+
+ @mock.patch.object(common_utils, 'execute', autospec=True)
+ def test_verify_iscsi_connection(self, mock_exec):
+ iqn = 'iqn.xyz'
+ mock_exec.return_value = ['iqn.xyz', '']
+ utils.verify_iscsi_connection(iqn)
+ mock_exec.assert_called_once_with(
+ 'iscsiadm',
+ '-m', 'node',
+ '-S',
+ run_as_root=True,
+ check_exit_code=[0])
+
+ @mock.patch.object(common_utils, 'execute', autospec=True)
+ def test_force_iscsi_lun_update(self, mock_exec):
+ iqn = 'iqn.xyz'
+ utils.force_iscsi_lun_update(iqn)
+ mock_exec.assert_called_once_with(
+ 'iscsiadm',
+ '-m', 'node',
+ '-T', iqn,
+ '-R',
+ run_as_root=True,
+ check_exit_code=[0])
+
+ @mock.patch.object(common_utils, 'execute', autospec=True)
+ @mock.patch.object(utils, 'verify_iscsi_connection', autospec=True)
+ @mock.patch.object(utils, 'force_iscsi_lun_update', autospec=True)
+ @mock.patch.object(utils, 'check_file_system_for_iscsi_device',
+ autospec=True)
+ def test_login_iscsi_calls_verify_and_update(self,
+ mock_check_dev,
+ mock_update,
+ mock_verify,
+ mock_exec):
+ address = '127.0.0.1'
+ port = 3306
+ iqn = 'iqn.xyz'
+ mock_exec.return_value = ['iqn.xyz', '']
+ utils.login_iscsi(address, port, iqn)
+ mock_exec.assert_called_once_with(
+ 'iscsiadm',
+ '-m', 'node',
+ '-p', '%s:%s' % (address, port),
+ '-T', iqn,
+ '--login',
+ run_as_root=True,
+ check_exit_code=[0],
+ attempts=5,
+ delay_on_retry=True)
+
+ mock_verify.assert_called_once_with(iqn)
+
+ mock_update.assert_called_once_with(iqn)
+
+ mock_check_dev.assert_called_once_with(address, port, iqn)
+
+ @mock.patch.object(utils, 'is_block_device', lambda d: True)
+ def test_always_logout_and_delete_iscsi(self):
+ """Check if logout_iscsi() and delete_iscsi() are called.
+
+ Make sure that logout_iscsi() and delete_iscsi() are called once
+ login_iscsi() is invoked.
+
+ """
+ address = '127.0.0.1'
+ port = 3306
+ iqn = 'iqn.xyz'
+ lun = 1
+ image_path = '/tmp/xyz/image'
+ root_mb = 128
+ swap_mb = 64
+ ephemeral_mb = 256
+ ephemeral_format = 'exttest'
+ node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
+
+ dev = '/dev/fake'
+
+ class TestException(Exception):
+ pass
+
+ name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi',
+ 'logout_iscsi', 'delete_iscsi', 'work_on_disk']
+ patch_list = [mock.patch.object(utils, name,
+ spec_set=types.FunctionType)
+ for name in name_list]
+ mock_list = [patcher.start() for patcher in patch_list]
+ for patcher in patch_list:
+ self.addCleanup(patcher.stop)
+
+ parent_mock = mock.MagicMock(spec=[])
+ for mocker, name in zip(mock_list, name_list):
+ parent_mock.attach_mock(mocker, name)
+
+ parent_mock.get_dev.return_value = dev
+ parent_mock.get_image_mb.return_value = 1
+ parent_mock.work_on_disk.side_effect = TestException
+ calls_expected = [mock.call.get_image_mb(image_path),
+ mock.call.get_dev(address, port, iqn, lun),
+ mock.call.discovery(address, port),
+ mock.call.login_iscsi(address, port, iqn),
+ mock.call.work_on_disk(dev, root_mb, swap_mb,
+ ephemeral_mb,
+ ephemeral_format, image_path,
+ node_uuid, configdrive=None,
+ preserve_ephemeral=False,
+ boot_option="netboot",
+ boot_mode="bios"),
+ mock.call.logout_iscsi(address, port, iqn),
+ mock.call.delete_iscsi(address, port, iqn)]
+
+ self.assertRaises(TestException, utils.deploy_partition_image,
+ address, port, iqn, lun, image_path,
+ root_mb, swap_mb, ephemeral_mb, ephemeral_format,
+ node_uuid)
+
+ self.assertEqual(calls_expected, parent_mock.mock_calls)
+
+
+class SwitchPxeConfigTestCase(tests_base.TestCase):
+
+ def _create_config(self, ipxe=False, boot_mode=None, boot_loader='elilo'):
+ (fd, fname) = tempfile.mkstemp()
+ if boot_mode == 'uefi':
+ if boot_loader == 'grub':
+ pxe_cfg = _UEFI_PXECONF_DEPLOY_GRUB
+ else:
+ pxe_cfg = _UEFI_PXECONF_DEPLOY
+ else:
+ pxe_cfg = _IPXECONF_DEPLOY if ipxe else _PXECONF_DEPLOY
+ os.write(fd, pxe_cfg)
+ os.close(fd)
+ self.addCleanup(os.unlink, fname)
+ return fname
+
+ def test_switch_pxe_config_partition_image(self):
+ boot_mode = 'bios'
+ fname = self._create_config()
+ utils.switch_pxe_config(fname,
+ '12345678-1234-1234-1234-1234567890abcdef',
+ boot_mode,
+ False)
+ with open(fname, 'r') as f:
+ pxeconf = f.read()
+ self.assertEqual(_PXECONF_BOOT_PARTITION, pxeconf)
+
+ def test_switch_pxe_config_whole_disk_image(self):
+ boot_mode = 'bios'
+ fname = self._create_config()
+ utils.switch_pxe_config(fname,
+ '0x12345678',
+ boot_mode,
+ True)
+ with open(fname, 'r') as f:
+ pxeconf = f.read()
+ self.assertEqual(_PXECONF_BOOT_WHOLE_DISK, pxeconf)
+
+ def test_switch_pxe_config_trusted_boot(self):
+ boot_mode = 'bios'
+ fname = self._create_config()
+ utils.switch_pxe_config(fname,
+ '12345678-1234-1234-1234-1234567890abcdef',
+ boot_mode,
+ False, True)
+ with open(fname, 'r') as f:
+ pxeconf = f.read()
+ self.assertEqual(_PXECONF_TRUSTED_BOOT, pxeconf)
+
+ def test_switch_ipxe_config_partition_image(self):
+ boot_mode = 'bios'
+ cfg.CONF.set_override('ipxe_enabled', True, 'pxe')
+ fname = self._create_config(ipxe=True)
+ utils.switch_pxe_config(fname,
+ '12345678-1234-1234-1234-1234567890abcdef',
+ boot_mode,
+ False)
+ with open(fname, 'r') as f:
+ pxeconf = f.read()
+ self.assertEqual(_IPXECONF_BOOT_PARTITION, pxeconf)
+
+ def test_switch_ipxe_config_whole_disk_image(self):
+ boot_mode = 'bios'
+ cfg.CONF.set_override('ipxe_enabled', True, 'pxe')
+ fname = self._create_config(ipxe=True)
+ utils.switch_pxe_config(fname,
+ '0x12345678',
+ boot_mode,
+ True)
+ with open(fname, 'r') as f:
+ pxeconf = f.read()
+ self.assertEqual(_IPXECONF_BOOT_WHOLE_DISK, pxeconf)
+
+ def test_switch_uefi_elilo_pxe_config_partition_image(self):
+ boot_mode = 'uefi'
+ fname = self._create_config(boot_mode=boot_mode)
+ utils.switch_pxe_config(fname,
+ '12345678-1234-1234-1234-1234567890abcdef',
+ boot_mode,
+ False)
+ with open(fname, 'r') as f:
+ pxeconf = f.read()
+ self.assertEqual(_UEFI_PXECONF_BOOT_PARTITION, pxeconf)
+
+ def test_switch_uefi_elilo_config_whole_disk_image(self):
+ boot_mode = 'uefi'
+ fname = self._create_config(boot_mode=boot_mode)
+ utils.switch_pxe_config(fname,
+ '0x12345678',
+ boot_mode,
+ True)
+ with open(fname, 'r') as f:
+ pxeconf = f.read()
+ self.assertEqual(_UEFI_PXECONF_BOOT_WHOLE_DISK, pxeconf)
+
+ def test_switch_uefi_grub_pxe_config_partition_image(self):
+ boot_mode = 'uefi'
+ fname = self._create_config(boot_mode=boot_mode, boot_loader='grub')
+ utils.switch_pxe_config(fname,
+ '12345678-1234-1234-1234-1234567890abcdef',
+ boot_mode,
+ False)
+ with open(fname, 'r') as f:
+ pxeconf = f.read()
+ self.assertEqual(_UEFI_PXECONF_BOOT_PARTITION_GRUB, pxeconf)
+
+ def test_switch_uefi_grub_config_whole_disk_image(self):
+ boot_mode = 'uefi'
+ fname = self._create_config(boot_mode=boot_mode, boot_loader='grub')
+ utils.switch_pxe_config(fname,
+ '0x12345678',
+ boot_mode,
+ True)
+ with open(fname, 'r') as f:
+ pxeconf = f.read()
+ self.assertEqual(_UEFI_PXECONF_BOOT_WHOLE_DISK_GRUB, pxeconf)
+
+
+@mock.patch('time.sleep', lambda sec: None)
+class OtherFunctionTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(OtherFunctionTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="fake_pxe")
+ self.node = obj_utils.create_test_node(self.context, driver='fake_pxe')
+
+ def test_get_dev(self):
+ expected = '/dev/disk/by-path/ip-1.2.3.4:5678-iscsi-iqn.fake-lun-9'
+ actual = utils.get_dev('1.2.3.4', 5678, 'iqn.fake', 9)
+ self.assertEqual(expected, actual)
+
+ @mock.patch.object(os, 'stat', autospec=True)
+ @mock.patch.object(stat, 'S_ISBLK', autospec=True)
+ def test_is_block_device_works(self, mock_is_blk, mock_os):
+ device = '/dev/disk/by-path/ip-1.2.3.4:5678-iscsi-iqn.fake-lun-9'
+ mock_is_blk.return_value = True
+ mock_os().st_mode = 10000
+ self.assertTrue(utils.is_block_device(device))
+ mock_is_blk.assert_called_once_with(mock_os().st_mode)
+
+ @mock.patch.object(os, 'stat', autospec=True)
+ def test_is_block_device_raises(self, mock_os):
+ device = '/dev/disk/by-path/ip-1.2.3.4:5678-iscsi-iqn.fake-lun-9'
+ mock_os.side_effect = OSError
+ self.assertRaises(exception.InstanceDeployFailure,
+ utils.is_block_device, device)
+ mock_os.assert_has_calls([mock.call(device)] * 3)
+
+ @mock.patch.object(os.path, 'getsize', autospec=True)
+ @mock.patch.object(images, 'converted_size', autospec=True)
+ def test_get_image_mb(self, mock_csize, mock_getsize):
+ mb = 1024 * 1024
+
+ mock_getsize.return_value = 0
+ mock_csize.return_value = 0
+ self.assertEqual(0, utils.get_image_mb('x', False))
+ self.assertEqual(0, utils.get_image_mb('x', True))
+ mock_getsize.return_value = 1
+ mock_csize.return_value = 1
+ self.assertEqual(1, utils.get_image_mb('x', False))
+ self.assertEqual(1, utils.get_image_mb('x', True))
+ mock_getsize.return_value = mb
+ mock_csize.return_value = mb
+ self.assertEqual(1, utils.get_image_mb('x', False))
+ self.assertEqual(1, utils.get_image_mb('x', True))
+ mock_getsize.return_value = mb + 1
+ mock_csize.return_value = mb + 1
+ self.assertEqual(2, utils.get_image_mb('x', False))
+ self.assertEqual(2, utils.get_image_mb('x', True))
+
+ def test_parse_root_device_hints(self):
+ self.node.properties['root_device'] = {'wwn': 123456}
+ expected = 'wwn=123456'
+ result = utils.parse_root_device_hints(self.node)
+ self.assertEqual(expected, result)
+
+ def test_parse_root_device_hints_string_space(self):
+ self.node.properties['root_device'] = {'model': 'fake model'}
+ expected = 'model=fake%20model'
+ result = utils.parse_root_device_hints(self.node)
+ self.assertEqual(expected, result)
+
+ def test_parse_root_device_hints_no_hints(self):
+ self.node.properties = {}
+ result = utils.parse_root_device_hints(self.node)
+ self.assertIsNone(result)
+
+ def test_parse_root_device_hints_invalid_hints(self):
+ self.node.properties['root_device'] = {'vehicle': 'Owlship'}
+ self.assertRaises(exception.InvalidParameterValue,
+ utils.parse_root_device_hints, self.node)
+
+ def test_parse_root_device_hints_invalid_size(self):
+ self.node.properties['root_device'] = {'size': 'not-int'}
+ self.assertRaises(exception.InvalidParameterValue,
+ utils.parse_root_device_hints, self.node)
+
+ @mock.patch.object(utils, 'LOG', autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(task_manager.TaskManager, 'process_event',
+ autospec=True)
+ def _test_set_failed_state(self, mock_event, mock_power, mock_log,
+ event_value=None, power_value=None,
+ log_calls=None):
+ err_msg = 'some failure'
+ mock_event.side_effect = event_value
+ mock_power.side_effect = power_value
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ utils.set_failed_state(task, err_msg)
+ mock_event.assert_called_once_with(task, 'fail')
+ mock_power.assert_called_once_with(task, states.POWER_OFF)
+ self.assertEqual(err_msg, task.node.last_error)
+ if log_calls:
+ mock_log.exception.assert_has_calls(log_calls)
+ else:
+ self.assertFalse(mock_log.called)
+
+ def test_set_failed_state(self):
+ exc_state = exception.InvalidState('invalid state')
+ exc_param = exception.InvalidParameterValue('invalid parameter')
+ mock_call = mock.call(mock.ANY)
+ self._test_set_failed_state()
+ calls = [mock_call]
+ self._test_set_failed_state(event_value=iter([exc_state] * len(calls)),
+ log_calls=calls)
+ calls = [mock_call]
+ self._test_set_failed_state(power_value=iter([exc_param] * len(calls)),
+ log_calls=calls)
+ calls = [mock_call, mock_call]
+ self._test_set_failed_state(event_value=iter([exc_state] * len(calls)),
+ power_value=iter([exc_param] * len(calls)),
+ log_calls=calls)
+
+ def test_get_boot_option(self):
+ self.node.instance_info = {'capabilities': '{"boot_option": "local"}'}
+ result = utils.get_boot_option(self.node)
+ self.assertEqual("local", result)
+
+ def test_get_boot_option_default_value(self):
+ self.node.instance_info = {}
+ result = utils.get_boot_option(self.node)
+ self.assertEqual("netboot", result)
+
+
+@mock.patch.object(disk_partitioner.DiskPartitioner, 'commit', lambda _: None)
+class WorkOnDiskTestCase(tests_base.TestCase):
+
+ def setUp(self):
+ super(WorkOnDiskTestCase, self).setUp()
+ self.image_path = '/tmp/xyz/image'
+ self.root_mb = 128
+ self.swap_mb = 64
+ self.ephemeral_mb = 0
+ self.ephemeral_format = None
+ self.configdrive_mb = 0
+ self.dev = '/dev/fake'
+ self.swap_part = '/dev/fake-part1'
+ self.root_part = '/dev/fake-part2'
+
+ self.mock_ibd_obj = mock.patch.object(
+ utils, 'is_block_device', autospec=True)
+ self.mock_ibd = self.mock_ibd_obj.start()
+ self.addCleanup(self.mock_ibd_obj.stop)
+ self.mock_mp_obj = mock.patch.object(
+ utils, 'make_partitions', autospec=True)
+ self.mock_mp = self.mock_mp_obj.start()
+ self.addCleanup(self.mock_mp_obj.stop)
+ self.mock_remlbl_obj = mock.patch.object(
+ utils, 'destroy_disk_metadata', autospec=True)
+ self.mock_remlbl = self.mock_remlbl_obj.start()
+ self.addCleanup(self.mock_remlbl_obj.stop)
+ self.mock_mp.return_value = {'swap': self.swap_part,
+ 'root': self.root_part}
+
+ def test_no_root_partition(self):
+ self.mock_ibd.return_value = False
+ self.assertRaises(exception.InstanceDeployFailure,
+ utils.work_on_disk, self.dev, self.root_mb,
+ self.swap_mb, self.ephemeral_mb,
+ self.ephemeral_format, self.image_path, 'fake-uuid')
+ self.mock_ibd.assert_called_once_with(self.root_part)
+ self.mock_mp.assert_called_once_with(self.dev, self.root_mb,
+ self.swap_mb, self.ephemeral_mb,
+ self.configdrive_mb,
+ 'fake-uuid',
+ commit=True,
+ boot_option="netboot",
+ boot_mode="bios")
+
+ def test_no_swap_partition(self):
+ self.mock_ibd.side_effect = iter([True, False])
+ calls = [mock.call(self.root_part),
+ mock.call(self.swap_part)]
+ self.assertRaises(exception.InstanceDeployFailure,
+ utils.work_on_disk, self.dev, self.root_mb,
+ self.swap_mb, self.ephemeral_mb,
+ self.ephemeral_format, self.image_path, 'fake-uuid')
+ self.assertEqual(self.mock_ibd.call_args_list, calls)
+ self.mock_mp.assert_called_once_with(self.dev, self.root_mb,
+ self.swap_mb, self.ephemeral_mb,
+ self.configdrive_mb,
+ 'fake-uuid',
+ commit=True,
+ boot_option="netboot",
+ boot_mode="bios")
+
+ def test_no_ephemeral_partition(self):
+ ephemeral_part = '/dev/fake-part1'
+ swap_part = '/dev/fake-part2'
+ root_part = '/dev/fake-part3'
+ ephemeral_mb = 256
+ ephemeral_format = 'exttest'
+
+ self.mock_mp.return_value = {'ephemeral': ephemeral_part,
+ 'swap': swap_part,
+ 'root': root_part}
+ self.mock_ibd.side_effect = iter([True, True, False])
+ calls = [mock.call(root_part),
+ mock.call(swap_part),
+ mock.call(ephemeral_part)]
+ self.assertRaises(exception.InstanceDeployFailure,
+ utils.work_on_disk, self.dev, self.root_mb,
+ self.swap_mb, ephemeral_mb, ephemeral_format,
+ self.image_path, 'fake-uuid')
+ self.assertEqual(self.mock_ibd.call_args_list, calls)
+ self.mock_mp.assert_called_once_with(self.dev, self.root_mb,
+ self.swap_mb, ephemeral_mb,
+ self.configdrive_mb,
+ 'fake-uuid',
+ commit=True,
+ boot_option="netboot",
+ boot_mode="bios")
+
+ @mock.patch.object(common_utils, 'unlink_without_raise', autospec=True)
+ @mock.patch.object(utils, '_get_configdrive', autospec=True)
+ def test_no_configdrive_partition(self, mock_configdrive, mock_unlink):
+ mock_configdrive.return_value = (10, 'fake-path')
+ swap_part = '/dev/fake-part1'
+ configdrive_part = '/dev/fake-part2'
+ root_part = '/dev/fake-part3'
+ configdrive_url = 'http://1.2.3.4/cd'
+ configdrive_mb = 10
+
+ self.mock_mp.return_value = {'swap': swap_part,
+ 'configdrive': configdrive_part,
+ 'root': root_part}
+ self.mock_ibd.side_effect = iter([True, True, False])
+ calls = [mock.call(root_part),
+ mock.call(swap_part),
+ mock.call(configdrive_part)]
+ self.assertRaises(exception.InstanceDeployFailure,
+ utils.work_on_disk, self.dev, self.root_mb,
+ self.swap_mb, self.ephemeral_mb,
+ self.ephemeral_format, self.image_path, 'fake-uuid',
+ preserve_ephemeral=False,
+ configdrive=configdrive_url,
+ boot_option="netboot")
+ self.assertEqual(self.mock_ibd.call_args_list, calls)
+ self.mock_mp.assert_called_once_with(self.dev, self.root_mb,
+ self.swap_mb, self.ephemeral_mb,
+ configdrive_mb,
+ 'fake-uuid',
+ commit=True,
+ boot_option="netboot",
+ boot_mode="bios")
+ mock_unlink.assert_called_once_with('fake-path')
+
+
+@mock.patch.object(common_utils, 'execute', autospec=True)
+class MakePartitionsTestCase(tests_base.TestCase):
+
+ def setUp(self):
+ super(MakePartitionsTestCase, self).setUp()
+ self.dev = 'fake-dev'
+ self.root_mb = 1024
+ self.swap_mb = 512
+ self.ephemeral_mb = 0
+ self.configdrive_mb = 0
+ self.parted_static_cmd = ['parted', '-a', 'optimal', '-s', self.dev,
+ '--', 'unit', 'MiB', 'mklabel', 'msdos']
+
+ def _test_make_partitions(self, mock_exc, boot_option):
+ mock_exc.return_value = (None, None)
+ utils.make_partitions(self.dev, self.root_mb, self.swap_mb,
+ self.ephemeral_mb, self.configdrive_mb,
+ '12345678-1234-1234-1234-1234567890abcxyz',
+ boot_option=boot_option)
+
+ expected_mkpart = ['mkpart', 'primary', 'linux-swap', '1', '513',
+ 'mkpart', 'primary', '', '513', '1537']
+ if boot_option == "local":
+ expected_mkpart.extend(['set', '2', 'boot', 'on'])
+ parted_cmd = self.parted_static_cmd + expected_mkpart
+ parted_call = mock.call(*parted_cmd, run_as_root=True,
+ check_exit_code=[0])
+ fuser_cmd = ['fuser', 'fake-dev']
+ fuser_call = mock.call(*fuser_cmd, run_as_root=True,
+ check_exit_code=[0, 1])
+ mock_exc.assert_has_calls([parted_call, fuser_call])
+
+ def test_make_partitions(self, mock_exc):
+ self._test_make_partitions(mock_exc, boot_option="netboot")
+
+ def test_make_partitions_local_boot(self, mock_exc):
+ self._test_make_partitions(mock_exc, boot_option="local")
+
+ def test_make_partitions_with_ephemeral(self, mock_exc):
+ self.ephemeral_mb = 2048
+ expected_mkpart = ['mkpart', 'primary', '', '1', '2049',
+ 'mkpart', 'primary', 'linux-swap', '2049', '2561',
+ 'mkpart', 'primary', '', '2561', '3585']
+ cmd = self.parted_static_cmd + expected_mkpart
+ mock_exc.return_value = (None, None)
+ utils.make_partitions(self.dev, self.root_mb, self.swap_mb,
+ self.ephemeral_mb, self.configdrive_mb,
+ '12345678-1234-1234-1234-1234567890abcxyz')
+
+ parted_call = mock.call(*cmd, run_as_root=True, check_exit_code=[0])
+ mock_exc.assert_has_calls([parted_call])
+
+
+@mock.patch.object(utils, 'get_dev_block_size', autospec=True)
+@mock.patch.object(common_utils, 'execute', autospec=True)
+class DestroyMetaDataTestCase(tests_base.TestCase):
+
+ def setUp(self):
+ super(DestroyMetaDataTestCase, self).setUp()
+ self.dev = 'fake-dev'
+ self.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
+
+ def test_destroy_disk_metadata(self, mock_exec, mock_gz):
+ mock_gz.return_value = 64
+ expected_calls = [mock.call('dd', 'if=/dev/zero', 'of=fake-dev',
+ 'bs=512', 'count=36', run_as_root=True,
+ check_exit_code=[0],
+ use_standard_locale=True),
+ mock.call('dd', 'if=/dev/zero', 'of=fake-dev',
+ 'bs=512', 'count=36', 'seek=28',
+ run_as_root=True,
+ check_exit_code=[0],
+ use_standard_locale=True)]
+ utils.destroy_disk_metadata(self.dev, self.node_uuid)
+ mock_exec.assert_has_calls(expected_calls)
+ self.assertTrue(mock_gz.called)
+
+ def test_destroy_disk_metadata_get_dev_size_fail(self, mock_exec, mock_gz):
+ mock_gz.side_effect = processutils.ProcessExecutionError
+
+ expected_call = [mock.call('dd', 'if=/dev/zero', 'of=fake-dev',
+ 'bs=512', 'count=36', run_as_root=True,
+ check_exit_code=[0],
+ use_standard_locale=True)]
+ self.assertRaises(processutils.ProcessExecutionError,
+ utils.destroy_disk_metadata,
+ self.dev,
+ self.node_uuid)
+ mock_exec.assert_has_calls(expected_call)
+
+ def test_destroy_disk_metadata_dd_fail(self, mock_exec, mock_gz):
+ mock_exec.side_effect = processutils.ProcessExecutionError
+
+ expected_call = [mock.call('dd', 'if=/dev/zero', 'of=fake-dev',
+ 'bs=512', 'count=36', run_as_root=True,
+ check_exit_code=[0],
+ use_standard_locale=True)]
+ self.assertRaises(processutils.ProcessExecutionError,
+ utils.destroy_disk_metadata,
+ self.dev,
+ self.node_uuid)
+ mock_exec.assert_has_calls(expected_call)
+ self.assertFalse(mock_gz.called)
+
+
+@mock.patch.object(common_utils, 'execute', autospec=True)
+class GetDeviceBlockSizeTestCase(tests_base.TestCase):
+
+ def setUp(self):
+ super(GetDeviceBlockSizeTestCase, self).setUp()
+ self.dev = 'fake-dev'
+ self.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
+
+ def test_get_dev_block_size(self, mock_exec):
+ mock_exec.return_value = ("64", "")
+ expected_call = [mock.call('blockdev', '--getsz', self.dev,
+ run_as_root=True, check_exit_code=[0])]
+ utils.get_dev_block_size(self.dev)
+ mock_exec.assert_has_calls(expected_call)
+
+
+@mock.patch.object(utils, 'dd', autospec=True)
+@mock.patch.object(images, 'qemu_img_info', autospec=True)
+@mock.patch.object(images, 'convert_image', autospec=True)
+class PopulateImageTestCase(tests_base.TestCase):
+
+ def setUp(self):
+ super(PopulateImageTestCase, self).setUp()
+
+ def test_populate_raw_image(self, mock_cg, mock_qinfo, mock_dd):
+ type(mock_qinfo.return_value).file_format = mock.PropertyMock(
+ return_value='raw')
+ utils.populate_image('src', 'dst')
+ mock_dd.assert_called_once_with('src', 'dst')
+ self.assertFalse(mock_cg.called)
+
+ def test_populate_qcow2_image(self, mock_cg, mock_qinfo, mock_dd):
+ type(mock_qinfo.return_value).file_format = mock.PropertyMock(
+ return_value='qcow2')
+ utils.populate_image('src', 'dst')
+ mock_cg.assert_called_once_with('src', 'dst', 'raw', True)
+ self.assertFalse(mock_dd.called)
+
+
+@mock.patch.object(utils, 'is_block_device', lambda d: True)
+@mock.patch.object(utils, 'block_uuid', lambda p: 'uuid')
+@mock.patch.object(utils, 'dd', lambda *_: None)
+@mock.patch.object(images, 'convert_image', lambda *_: None)
+@mock.patch.object(common_utils, 'mkfs', lambda *_: None)
+# NOTE(dtantsur): destroy_disk_metadata resets file size, disabling it
+@mock.patch.object(utils, 'destroy_disk_metadata', lambda *_: None)
+class RealFilePartitioningTestCase(tests_base.TestCase):
+ """This test applies some real-world partitioning scenario to a file.
+
+ This test covers the whole partitioning, mocking everything not possible
+ on a file. That helps us assure, that we do all partitioning math properly
+ and also conducts integration testing of DiskPartitioner.
+ """
+
+ def setUp(self):
+ super(RealFilePartitioningTestCase, self).setUp()
+ # NOTE(dtantsur): no parted utility on gate-ironic-python26
+ try:
+ common_utils.execute('parted', '--version')
+ except OSError as exc:
+ self.skipTest('parted utility was not found: %s' % exc)
+ self.file = tempfile.NamedTemporaryFile(delete=False)
+ # NOTE(ifarkas): the file needs to be closed, so fuser won't report
+ # any usage
+ self.file.close()
+ # NOTE(dtantsur): 20 MiB file with zeros
+ common_utils.execute('dd', 'if=/dev/zero', 'of=%s' % self.file.name,
+ 'bs=1', 'count=0', 'seek=20MiB')
+
+ @staticmethod
+ def _run_without_root(func, *args, **kwargs):
+ """Make sure root is not required when using utils.execute."""
+ real_execute = common_utils.execute
+
+ def fake_execute(*cmd, **kwargs):
+ kwargs['run_as_root'] = False
+ return real_execute(*cmd, **kwargs)
+
+ with mock.patch.object(common_utils, 'execute', fake_execute):
+ return func(*args, **kwargs)
+
+ def test_different_sizes(self):
+ # NOTE(dtantsur): Keep this list in order with expected partitioning
+ fields = ['ephemeral_mb', 'swap_mb', 'root_mb']
+ variants = ((0, 0, 12), (4, 2, 8), (0, 4, 10), (5, 0, 10))
+ for variant in variants:
+ kwargs = dict(zip(fields, variant))
+ self._run_without_root(utils.work_on_disk, self.file.name,
+ ephemeral_format='ext4', node_uuid='',
+ image_path='path', **kwargs)
+ part_table = self._run_without_root(
+ disk_partitioner.list_partitions, self.file.name)
+ for part, expected_size in zip(part_table, filter(None, variant)):
+ self.assertEqual(expected_size, part['size'],
+ "comparison failed for %s" % list(variant))
+
+ def test_whole_disk(self):
+ # 6 MiB ephemeral + 3 MiB swap + 9 MiB root + 1 MiB for MBR
+ # + 1 MiB MAGIC == 20 MiB whole disk
+ # TODO(dtantsur): figure out why we need 'magic' 1 more MiB
+ # and why the is different on Ubuntu and Fedora (see below)
+ self._run_without_root(utils.work_on_disk, self.file.name,
+ root_mb=9, ephemeral_mb=6, swap_mb=3,
+ ephemeral_format='ext4', node_uuid='',
+ image_path='path')
+ part_table = self._run_without_root(
+ disk_partitioner.list_partitions, self.file.name)
+ sizes = [part['size'] for part in part_table]
+ # NOTE(dtantsur): parted in Ubuntu 12.04 will occupy the last MiB,
+ # parted in Fedora 20 won't - thus two possible variants for last part
+ self.assertEqual([6, 3], sizes[:2],
+ "unexpected partitioning %s" % part_table)
+ self.assertIn(sizes[2], (9, 10))
+
+ @mock.patch.object(image_cache, 'clean_up_caches', autospec=True)
+ def test_fetch_images(self, mock_clean_up_caches):
+
+ mock_cache = mock.MagicMock(
+ spec_set=['fetch_image', 'master_dir'], master_dir='master_dir')
+ utils.fetch_images(None, mock_cache, [('uuid', 'path')])
+ mock_clean_up_caches.assert_called_once_with(None, 'master_dir',
+ [('uuid', 'path')])
+ mock_cache.fetch_image.assert_called_once_with('uuid', 'path',
+ ctx=None,
+ force_raw=True)
+
+ @mock.patch.object(image_cache, 'clean_up_caches', autospec=True)
+ def test_fetch_images_fail(self, mock_clean_up_caches):
+
+ exc = exception.InsufficientDiskSpace(path='a',
+ required=2,
+ actual=1)
+
+ mock_cache = mock.MagicMock(
+ spec_set=['master_dir'], master_dir='master_dir')
+ mock_clean_up_caches.side_effect = iter([exc])
+ self.assertRaises(exception.InstanceDeployFailure,
+ utils.fetch_images,
+ None,
+ mock_cache,
+ [('uuid', 'path')])
+ mock_clean_up_caches.assert_called_once_with(None, 'master_dir',
+ [('uuid', 'path')])
+
+
+@mock.patch.object(tempfile, 'NamedTemporaryFile', autospec=True)
+@mock.patch.object(shutil, 'copyfileobj', autospec=True)
+@mock.patch.object(requests, 'get', autospec=True)
+class GetConfigdriveTestCase(tests_base.TestCase):
+
+ def setUp(self):
+ super(GetConfigdriveTestCase, self).setUp()
+ # NOTE(lucasagomes): "name" can't be passed to Mock() when
+ # instantiating the object because it's an expected parameter.
+ # https://docs.python.org/3/library/unittest.mock.html
+ self.fake_configdrive_file = mock.Mock(tell=lambda *_: 123)
+ self.fake_configdrive_file.name = '/tmp/foo'
+
+ @mock.patch.object(gzip, 'GzipFile', autospec=True)
+ def test_get_configdrive(self, mock_gzip, mock_requests, mock_copy,
+ mock_file):
+ mock_file.return_value = self.fake_configdrive_file
+ mock_requests.return_value = mock.MagicMock(
+ spec_set=['content'], content='Zm9vYmFy')
+ utils._get_configdrive('http://1.2.3.4/cd', 'fake-node-uuid')
+ mock_requests.assert_called_once_with('http://1.2.3.4/cd')
+ mock_gzip.assert_called_once_with('configdrive', 'rb',
+ fileobj=mock.ANY)
+ mock_copy.assert_called_once_with(mock.ANY, mock.ANY)
+ mock_file.assert_called_once_with(prefix='configdrive',
+ dir=cfg.CONF.tempdir, delete=False)
+
+ @mock.patch.object(gzip, 'GzipFile', autospec=True)
+ def test_get_configdrive_base64_string(self, mock_gzip, mock_requests,
+ mock_copy, mock_file):
+ mock_file.return_value = self.fake_configdrive_file
+ utils._get_configdrive('Zm9vYmFy', 'fake-node-uuid')
+ self.assertFalse(mock_requests.called)
+ mock_gzip.assert_called_once_with('configdrive', 'rb',
+ fileobj=mock.ANY)
+ mock_copy.assert_called_once_with(mock.ANY, mock.ANY)
+ mock_file.assert_called_once_with(prefix='configdrive',
+ dir=cfg.CONF.tempdir, delete=False)
+
+ def test_get_configdrive_bad_url(self, mock_requests, mock_copy,
+ mock_file):
+ mock_requests.side_effect = requests.exceptions.RequestException
+ self.assertRaises(exception.InstanceDeployFailure,
+ utils._get_configdrive, 'http://1.2.3.4/cd',
+ 'fake-node-uuid')
+ self.assertFalse(mock_copy.called)
+ self.assertFalse(mock_file.called)
+
+ @mock.patch.object(base64, 'b64decode', autospec=True)
+ def test_get_configdrive_base64_error(self, mock_b64, mock_requests,
+ mock_copy, mock_file):
+ mock_b64.side_effect = TypeError
+ self.assertRaises(exception.InstanceDeployFailure,
+ utils._get_configdrive,
+ 'malformed', 'fake-node-uuid')
+ mock_b64.assert_called_once_with('malformed')
+ self.assertFalse(mock_copy.called)
+ self.assertFalse(mock_file.called)
+
+ @mock.patch.object(gzip, 'GzipFile', autospec=True)
+ def test_get_configdrive_gzip_error(self, mock_gzip, mock_requests,
+ mock_copy, mock_file):
+ mock_file.return_value = self.fake_configdrive_file
+ mock_requests.return_value = mock.MagicMock(
+ spec_set=['content'], content='Zm9vYmFy')
+ mock_copy.side_effect = IOError
+ self.assertRaises(exception.InstanceDeployFailure,
+ utils._get_configdrive, 'http://1.2.3.4/cd',
+ 'fake-node-uuid')
+ mock_requests.assert_called_once_with('http://1.2.3.4/cd')
+ mock_gzip.assert_called_once_with('configdrive', 'rb',
+ fileobj=mock.ANY)
+ mock_copy.assert_called_once_with(mock.ANY, mock.ANY)
+ mock_file.assert_called_once_with(prefix='configdrive',
+ dir=cfg.CONF.tempdir, delete=False)
+
+
+class VirtualMediaDeployUtilsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(VirtualMediaDeployUtilsTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="iscsi_ilo")
+ info_dict = db_utils.get_test_ilo_info()
+ self.node = obj_utils.create_test_node(
+ self.context, driver='iscsi_ilo', driver_info=info_dict)
+
+ def test_get_single_nic_with_vif_port_id(self):
+ obj_utils.create_test_port(
+ self.context, node_id=self.node.id, address='aa:bb:cc:dd:ee:ff',
+ uuid=uuidutils.generate_uuid(),
+ extra={'vif_port_id': 'test-vif-A'}, driver='iscsi_ilo')
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ address = utils.get_single_nic_with_vif_port_id(task)
+ self.assertEqual('aa:bb:cc:dd:ee:ff', address)
+
+
+class ParseInstanceInfoCapabilitiesTestCase(tests_base.TestCase):
+
+ def setUp(self):
+ super(ParseInstanceInfoCapabilitiesTestCase, self).setUp()
+ self.node = obj_utils.get_test_node(self.context, driver='fake')
+
+ def test_parse_instance_info_capabilities_string(self):
+ self.node.instance_info = {'capabilities': '{"cat": "meow"}'}
+ expected_result = {"cat": "meow"}
+ result = utils.parse_instance_info_capabilities(self.node)
+ self.assertEqual(expected_result, result)
+
+ def test_parse_instance_info_capabilities(self):
+ self.node.instance_info = {'capabilities': {"dog": "wuff"}}
+ expected_result = {"dog": "wuff"}
+ result = utils.parse_instance_info_capabilities(self.node)
+ self.assertEqual(expected_result, result)
+
+ def test_parse_instance_info_invalid_type(self):
+ self.node.instance_info = {'capabilities': 'not-a-dict'}
+ self.assertRaises(exception.InvalidParameterValue,
+ utils.parse_instance_info_capabilities, self.node)
+
+ def test_is_secure_boot_requested_true(self):
+ self.node.instance_info = {'capabilities': {"secure_boot": "tRue"}}
+ self.assertTrue(utils.is_secure_boot_requested(self.node))
+
+ def test_is_secure_boot_requested_false(self):
+ self.node.instance_info = {'capabilities': {"secure_boot": "false"}}
+ self.assertFalse(utils.is_secure_boot_requested(self.node))
+
+ def test_is_secure_boot_requested_invalid(self):
+ self.node.instance_info = {'capabilities': {"secure_boot": "invalid"}}
+ self.assertFalse(utils.is_secure_boot_requested(self.node))
+
+ def test_is_trusted_boot_requested_true(self):
+ self.node.instance_info = {'capabilities': {"trusted_boot": "true"}}
+ self.assertTrue(utils.is_trusted_boot_requested(self.node))
+
+ def test_is_trusted_boot_requested_false(self):
+ self.node.instance_info = {'capabilities': {"trusted_boot": "false"}}
+ self.assertFalse(utils.is_trusted_boot_requested(self.node))
+
+ def test_is_trusted_boot_requested_invalid(self):
+ self.node.instance_info = {'capabilities': {"trusted_boot": "invalid"}}
+ self.assertFalse(utils.is_trusted_boot_requested(self.node))
+
+ def test_get_boot_mode_for_deploy_using_capabilities(self):
+ properties = {'capabilities': 'boot_mode:uefi,cap2:value2'}
+ self.node.properties = properties
+
+ result = utils.get_boot_mode_for_deploy(self.node)
+ self.assertEqual('uefi', result)
+
+ def test_get_boot_mode_for_deploy_using_instance_info_cap(self):
+ instance_info = {'capabilities': {'secure_boot': 'True'}}
+ self.node.instance_info = instance_info
+
+ result = utils.get_boot_mode_for_deploy(self.node)
+ self.assertEqual('uefi', result)
+
+ instance_info = {'capabilities': {'trusted_boot': 'True'}}
+ self.node.instance_info = instance_info
+
+ result = utils.get_boot_mode_for_deploy(self.node)
+ self.assertEqual('bios', result)
+
+ instance_info = {'capabilities': {'trusted_boot': 'True'},
+ 'capabilities': {'secure_boot': 'True'}}
+ self.node.instance_info = instance_info
+
+ result = utils.get_boot_mode_for_deploy(self.node)
+ self.assertEqual('uefi', result)
+
+ def test_get_boot_mode_for_deploy_using_instance_info(self):
+ instance_info = {'deploy_boot_mode': 'bios'}
+ self.node.instance_info = instance_info
+
+ result = utils.get_boot_mode_for_deploy(self.node)
+ self.assertEqual('bios', result)
+
+ def test_validate_boot_mode_capability(self):
+ prop = {'capabilities': 'boot_mode:uefi,cap2:value2'}
+ self.node.properties = prop
+
+ result = utils.validate_capabilities(self.node)
+ self.assertIsNone(result)
+
+ def test_validate_boot_mode_capability_with_exc(self):
+ prop = {'capabilities': 'boot_mode:UEFI,cap2:value2'}
+ self.node.properties = prop
+
+ self.assertRaises(exception.InvalidParameterValue,
+ utils.validate_capabilities, self.node)
+
+ def test_validate_boot_mode_capability_instance_info(self):
+ inst_info = {'capabilities': {"boot_mode": "uefi", "cap2": "value2"}}
+ self.node.instance_info = inst_info
+
+ result = utils.validate_capabilities(self.node)
+ self.assertIsNone(result)
+
+ def test_validate_boot_mode_capability_instance_info_with_exc(self):
+ inst_info = {'capabilities': {"boot_mode": "UEFI", "cap2": "value2"}}
+ self.node.instance_info = inst_info
+
+ self.assertRaises(exception.InvalidParameterValue,
+ utils.validate_capabilities, self.node)
+
+ def test_validate_trusted_boot_capability(self):
+ properties = {'capabilities': 'trusted_boot:value'}
+ self.node.properties = properties
+ self.assertRaises(exception.InvalidParameterValue,
+ utils.validate_capabilities, self.node)
+
+ def test_all_supported_capabilities(self):
+ self.assertEqual(('local', 'netboot'),
+ utils.SUPPORTED_CAPABILITIES['boot_option'])
+ self.assertEqual(('bios', 'uefi'),
+ utils.SUPPORTED_CAPABILITIES['boot_mode'])
+ self.assertEqual(('true', 'false'),
+ utils.SUPPORTED_CAPABILITIES['secure_boot'])
+ self.assertEqual(('true', 'false'),
+ utils.SUPPORTED_CAPABILITIES['trusted_boot'])
+
+
+class TrySetBootDeviceTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(TrySetBootDeviceTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="fake")
+ self.node = obj_utils.create_test_node(self.context, driver="fake")
+
+ @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
+ def test_try_set_boot_device_okay(self, node_set_boot_device_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ utils.try_set_boot_device(task, boot_devices.DISK,
+ persistent=True)
+ node_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK, persistent=True)
+
+ @mock.patch.object(utils, 'LOG', autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
+ def test_try_set_boot_device_ipmifailure_uefi(
+ self, node_set_boot_device_mock, log_mock):
+ self.node.properties = {'capabilities': 'boot_mode:uefi'}
+ self.node.save()
+ node_set_boot_device_mock.side_effect = iter(
+ [exception.IPMIFailure(cmd='a')])
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ utils.try_set_boot_device(task, boot_devices.DISK,
+ persistent=True)
+ node_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK, persistent=True)
+ log_mock.warning.assert_called_once_with(mock.ANY)
+
+ @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
+ def test_try_set_boot_device_ipmifailure_bios(
+ self, node_set_boot_device_mock):
+ node_set_boot_device_mock.side_effect = iter(
+ [exception.IPMIFailure(cmd='a')])
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.IPMIFailure,
+ utils.try_set_boot_device,
+ task, boot_devices.DISK, persistent=True)
+ node_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK, persistent=True)
+
+ @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
+ def test_try_set_boot_device_some_other_exception(
+ self, node_set_boot_device_mock):
+ exc = exception.IloOperationError(operation="qwe", error="error")
+ node_set_boot_device_mock.side_effect = iter([exc])
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.IloOperationError,
+ utils.try_set_boot_device,
+ task, boot_devices.DISK, persistent=True)
+ node_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK, persistent=True)
+
+
+class AgentMethodsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(AgentMethodsTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_agent')
+ n = {'driver': 'fake_agent',
+ 'driver_internal_info': {'agent_url': 'http://127.0.0.1:9999'}}
+
+ self.node = obj_utils.create_test_node(self.context, **n)
+ self.ports = [obj_utils.create_test_port(self.context,
+ node_id=self.node.id)]
+
+ self.clean_steps = {
+ 'hardware_manager_version': '1',
+ 'clean_steps': {
+ 'GenericHardwareManager': [
+ {'interface': 'deploy',
+ 'step': 'erase_devices',
+ 'priority': 20},
+ ],
+ 'SpecificHardwareManager': [
+ {'interface': 'deploy',
+ 'step': 'update_firmware',
+ 'priority': 30},
+ {'interface': 'raid',
+ 'step': 'create_configuration',
+ 'priority': 10},
+ ]
+ }
+ }
+
+ @mock.patch('ironic.objects.Port.list_by_node_id',
+ spec_set=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'get_clean_steps',
+ autospec=True)
+ def test_get_clean_steps(self, client_mock, list_ports_mock):
+ client_mock.return_value = {
+ 'command_result': self.clean_steps}
+ list_ports_mock.return_value = self.ports
+
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ response = utils.agent_get_clean_steps(task)
+ client_mock.assert_called_once_with(mock.ANY, task.node,
+ self.ports)
+ self.assertEqual('1', task.node.driver_internal_info[
+ 'hardware_manager_version'])
+
+ # Since steps are returned in dicts, they have non-deterministic
+ # ordering
+ self.assertEqual(2, len(response))
+ self.assertIn(self.clean_steps['clean_steps'][
+ 'GenericHardwareManager'][0], response)
+ self.assertIn(self.clean_steps['clean_steps'][
+ 'SpecificHardwareManager'][0], response)
+
+ @mock.patch('ironic.objects.Port.list_by_node_id',
+ spec_set=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'get_clean_steps',
+ autospec=True)
+ def test_get_clean_steps_missing_steps(self, client_mock,
+ list_ports_mock):
+ del self.clean_steps['clean_steps']
+ client_mock.return_value = {
+ 'command_result': self.clean_steps}
+ list_ports_mock.return_value = self.ports
+
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ self.assertRaises(exception.NodeCleaningFailure,
+ utils.agent_get_clean_steps,
+ task)
+ client_mock.assert_called_once_with(mock.ANY, task.node,
+ self.ports)
+
+ @mock.patch('ironic.objects.Port.list_by_node_id',
+ spec_set=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'execute_clean_step',
+ autospec=True)
+ def test_execute_clean_step(self, client_mock, list_ports_mock):
+ client_mock.return_value = {
+ 'command_status': 'SUCCEEDED'}
+ list_ports_mock.return_value = self.ports
+
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ response = utils.agent_execute_clean_step(
+ task,
+ self.clean_steps['clean_steps']['GenericHardwareManager'][0])
+ self.assertEqual(states.CLEANWAIT, response)
+
+ @mock.patch('ironic.objects.Port.list_by_node_id',
+ spec_set=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'execute_clean_step',
+ autospec=True)
+ def test_execute_clean_step_running(self, client_mock, list_ports_mock):
+ client_mock.return_value = {
+ 'command_status': 'RUNNING'}
+ list_ports_mock.return_value = self.ports
+
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ response = utils.agent_execute_clean_step(
+ task,
+ self.clean_steps['clean_steps']['GenericHardwareManager'][0])
+ self.assertEqual(states.CLEANWAIT, response)
+
+ @mock.patch('ironic.objects.Port.list_by_node_id',
+ spec_set=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'execute_clean_step',
+ autospec=True)
+ def test_execute_clean_step_version_mismatch(
+ self, client_mock, list_ports_mock):
+ client_mock.return_value = {
+ 'command_status': 'RUNNING'}
+ list_ports_mock.return_value = self.ports
+
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ response = utils.agent_execute_clean_step(
+ task,
+ self.clean_steps['clean_steps']['GenericHardwareManager'][0])
+ self.assertEqual(states.CLEANWAIT, response)
+
+ def test_agent_add_clean_params(self):
+ cfg.CONF.deploy.erase_devices_iterations = 2
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ utils.agent_add_clean_params(task)
+ self.assertEqual(task.node.driver_internal_info.get(
+ 'agent_erase_devices_iterations'), 2)
+
+ @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.delete_cleaning_ports',
+ autospec=True)
+ @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.create_cleaning_ports',
+ autospec=True)
+ def _test_prepare_inband_cleaning_ports(
+ self, create_mock, delete_mock, return_vif_port_id=True):
+ if return_vif_port_id:
+ create_mock.return_value = {self.ports[0].uuid: 'vif-port-id'}
+ else:
+ create_mock.return_value = {}
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ utils.prepare_cleaning_ports(task)
+ create_mock.assert_called_once_with(mock.ANY, task)
+ delete_mock.assert_called_once_with(mock.ANY, task)
+
+ self.ports[0].refresh()
+ self.assertEqual('vif-port-id', self.ports[0].extra['vif_port_id'])
+
+ def test_prepare_inband_cleaning_ports(self):
+ self._test_prepare_inband_cleaning_ports()
+
+ def test_prepare_inband_cleaning_ports_no_vif_port_id(self):
+ self.assertRaises(
+ exception.NodeCleaningFailure,
+ self._test_prepare_inband_cleaning_ports,
+ return_vif_port_id=False)
+
+ @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.delete_cleaning_ports',
+ autospec=True)
+ def test_tear_down_inband_cleaning_ports(self, neutron_mock):
+ extra_dict = self.ports[0].extra
+ extra_dict['vif_port_id'] = 'vif-port-id'
+ self.ports[0].extra = extra_dict
+ self.ports[0].save()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ utils.tear_down_cleaning_ports(task)
+ neutron_mock.assert_called_once_with(mock.ANY, task)
+
+ self.ports[0].refresh()
+ self.assertNotIn('vif_port_id', self.ports[0].extra)
+
+ @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', autospec=True)
+ @mock.patch('ironic.conductor.utils.node_power_action', autospec=True)
+ @mock.patch.object(utils, 'build_agent_options', autospec=True)
+ @mock.patch.object(utils, 'prepare_cleaning_ports', autospec=True)
+ def _test_prepare_inband_cleaning(
+ self, prepare_cleaning_ports_mock,
+ build_options_mock, power_mock, prepare_ramdisk_mock,
+ manage_boot=True):
+ build_options_mock.return_value = {'a': 'b'}
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.assertEqual(
+ states.CLEANWAIT,
+ utils.prepare_inband_cleaning(task, manage_boot=manage_boot))
+ prepare_cleaning_ports_mock.assert_called_once_with(task)
+ power_mock.assert_called_once_with(task, states.REBOOT)
+ self.assertEqual(task.node.driver_internal_info.get(
+ 'agent_erase_devices_iterations'), 1)
+ if manage_boot:
+ prepare_ramdisk_mock.assert_called_once_with(
+ mock.ANY, mock.ANY, {'a': 'b'})
+ build_options_mock.assert_called_once_with(task.node)
+ else:
+ self.assertFalse(prepare_ramdisk_mock.called)
+ self.assertFalse(build_options_mock.called)
+
+ def test_prepare_inband_cleaning(self):
+ self._test_prepare_inband_cleaning()
+
+ def test_prepare_inband_cleaning_manage_boot_false(self):
+ self._test_prepare_inband_cleaning(manage_boot=False)
+
+ @mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True)
+ @mock.patch.object(utils, 'tear_down_cleaning_ports', autospec=True)
+ @mock.patch('ironic.conductor.utils.node_power_action', autospec=True)
+ def _test_tear_down_inband_cleaning(
+ self, power_mock, tear_down_ports_mock,
+ clean_up_ramdisk_mock, manage_boot=True):
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ utils.tear_down_inband_cleaning(task, manage_boot=manage_boot)
+ power_mock.assert_called_once_with(task, states.POWER_OFF)
+ tear_down_ports_mock.assert_called_once_with(task)
+ if manage_boot:
+ clean_up_ramdisk_mock.assert_called_once_with(
+ task.driver.boot, task)
+ else:
+ self.assertFalse(clean_up_ramdisk_mock.called)
+
+ def test_tear_down_inband_cleaning(self):
+ self._test_tear_down_inband_cleaning(manage_boot=True)
+
+ def test_tear_down_inband_cleaning_manage_boot_false(self):
+ self._test_tear_down_inband_cleaning(manage_boot=False)
+
+ def test_build_agent_options_conf(self):
+ self.config(api_url='api-url', group='conductor')
+ options = utils.build_agent_options(self.node)
+ self.assertEqual('api-url', options['ipa-api-url'])
+ self.assertEqual('fake_agent', options['ipa-driver-name'])
+ self.assertEqual(0, options['coreos.configdrive'])
+
+ @mock.patch.object(keystone, 'get_service_url', autospec=True)
+ def test_build_agent_options_keystone(self, get_url_mock):
+
+ self.config(api_url=None, group='conductor')
+ get_url_mock.return_value = 'api-url'
+ options = utils.build_agent_options(self.node)
+ self.assertEqual('api-url', options['ipa-api-url'])
+ self.assertEqual('fake_agent', options['ipa-driver-name'])
+ self.assertEqual(0, options['coreos.configdrive'])
+
+ def test_build_agent_options_root_device_hints(self):
+ self.config(api_url='api-url', group='conductor')
+ self.node.properties['root_device'] = {'model': 'fake_model'}
+ options = utils.build_agent_options(self.node)
+ self.assertEqual('api-url', options['ipa-api-url'])
+ self.assertEqual('fake_agent', options['ipa-driver-name'])
+ self.assertEqual('model=fake_model', options['root_device'])
+
+
+@mock.patch.object(utils, 'is_block_device', autospec=True)
+@mock.patch.object(utils, 'login_iscsi', lambda *_: None)
+@mock.patch.object(utils, 'discovery', lambda *_: None)
+@mock.patch.object(utils, 'logout_iscsi', lambda *_: None)
+@mock.patch.object(utils, 'delete_iscsi', lambda *_: None)
+@mock.patch.object(utils, 'get_dev', lambda *_: '/dev/fake')
+class ISCSISetupAndHandleErrorsTestCase(tests_base.TestCase):
+
+ def test_no_parent_device(self, mock_ibd):
+ address = '127.0.0.1'
+ port = 3306
+ iqn = 'iqn.xyz'
+ lun = 1
+ mock_ibd.return_value = False
+ expected_dev = '/dev/fake'
+ with testtools.ExpectedException(exception.InstanceDeployFailure):
+ with utils._iscsi_setup_and_handle_errors(
+ address, port, iqn, lun) as dev:
+ self.assertEqual(expected_dev, dev)
+
+ mock_ibd.assert_called_once_with(expected_dev)
+
+ def test_parent_device_yield(self, mock_ibd):
+ address = '127.0.0.1'
+ port = 3306
+ iqn = 'iqn.xyz'
+ lun = 1
+ expected_dev = '/dev/fake'
+ mock_ibd.return_value = True
+ with utils._iscsi_setup_and_handle_errors(
+ address, port, iqn, lun) as dev:
+ self.assertEqual(expected_dev, dev)
+
+ mock_ibd.assert_called_once_with(expected_dev)
+
+
+class ValidateImagePropertiesTestCase(db_base.DbTestCase):
+
+ @mock.patch.object(image_service, 'get_image_service', autospec=True)
+ def test_validate_image_properties_glance_image(self, image_service_mock):
+ node = obj_utils.create_test_node(
+ self.context, driver='fake_pxe',
+ instance_info=INST_INFO_DICT,
+ driver_info=DRV_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ d_info = pxe._parse_instance_info(node)
+ image_service_mock.return_value.show.return_value = {
+ 'properties': {'kernel_id': '1111', 'ramdisk_id': '2222'},
+ }
+
+ utils.validate_image_properties(self.context, d_info,
+ ['kernel_id', 'ramdisk_id'])
+ image_service_mock.assert_called_once_with(
+ node.instance_info['image_source'], context=self.context
+ )
+
+ @mock.patch.object(image_service, 'get_image_service', autospec=True)
+ def test_validate_image_properties_glance_image_missing_prop(
+ self, image_service_mock):
+ node = obj_utils.create_test_node(
+ self.context, driver='fake_pxe',
+ instance_info=INST_INFO_DICT,
+ driver_info=DRV_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ d_info = pxe._parse_instance_info(node)
+ image_service_mock.return_value.show.return_value = {
+ 'properties': {'kernel_id': '1111'},
+ }
+
+ self.assertRaises(exception.MissingParameterValue,
+ utils.validate_image_properties,
+ self.context, d_info, ['kernel_id', 'ramdisk_id'])
+ image_service_mock.assert_called_once_with(
+ node.instance_info['image_source'], context=self.context
+ )
+
+ @mock.patch.object(image_service, 'get_image_service', autospec=True)
+ def test_validate_image_properties_glance_image_not_authorized(
+ self, image_service_mock):
+ d_info = {'image_source': 'uuid'}
+ show_mock = image_service_mock.return_value.show
+ show_mock.side_effect = exception.ImageNotAuthorized(image_id='uuid')
+ self.assertRaises(exception.InvalidParameterValue,
+ utils.validate_image_properties, self.context,
+ d_info, [])
+
+ @mock.patch.object(image_service, 'get_image_service', autospec=True)
+ def test_validate_image_properties_glance_image_not_found(
+ self, image_service_mock):
+ d_info = {'image_source': 'uuid'}
+ show_mock = image_service_mock.return_value.show
+ show_mock.side_effect = exception.ImageNotFound(image_id='uuid')
+ self.assertRaises(exception.InvalidParameterValue,
+ utils.validate_image_properties, self.context,
+ d_info, [])
+
+ def test_validate_image_properties_invalid_image_href(self):
+ d_info = {'image_source': 'emule://uuid'}
+ self.assertRaises(exception.InvalidParameterValue,
+ utils.validate_image_properties, self.context,
+ d_info, [])
+
+ @mock.patch.object(image_service.HttpImageService, 'show', autospec=True)
+ def test_validate_image_properties_nonglance_image(
+ self, image_service_show_mock):
+ instance_info = {
+ 'image_source': 'http://ubuntu',
+ 'kernel': 'kernel_uuid',
+ 'ramdisk': 'file://initrd',
+ 'root_gb': 100,
+ }
+ image_service_show_mock.return_value = {'size': 1, 'properties': {}}
+ node = obj_utils.create_test_node(
+ self.context, driver='fake_pxe',
+ instance_info=instance_info,
+ driver_info=DRV_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ d_info = pxe._parse_instance_info(node)
+ utils.validate_image_properties(self.context, d_info,
+ ['kernel', 'ramdisk'])
+ image_service_show_mock.assert_called_once_with(
+ mock.ANY, instance_info['image_source'])
+
+ @mock.patch.object(image_service.HttpImageService, 'show', autospec=True)
+ def test_validate_image_properties_nonglance_image_validation_fail(
+ self, img_service_show_mock):
+ instance_info = {
+ 'image_source': 'http://ubuntu',
+ 'kernel': 'kernel_uuid',
+ 'ramdisk': 'file://initrd',
+ 'root_gb': 100,
+ }
+ img_service_show_mock.side_effect = iter(
+ [exception.ImageRefValidationFailed(
+ image_href='http://ubuntu', reason='HTTPError')])
+ node = obj_utils.create_test_node(
+ self.context, driver='fake_pxe',
+ instance_info=instance_info,
+ driver_info=DRV_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ d_info = pxe._parse_instance_info(node)
+ self.assertRaises(exception.InvalidParameterValue,
+ utils.validate_image_properties, self.context,
+ d_info, ['kernel', 'ramdisk'])
diff --git a/ironic/tests/unit/drivers/modules/test_iboot.py b/ironic/tests/unit/drivers/modules/test_iboot.py
new file mode 100644
index 000000000..2ad9ed09f
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/test_iboot.py
@@ -0,0 +1,384 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2014 Red Hat, 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
+# 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.
+
+"""Test class for iBoot PDU driver module."""
+
+import types
+
+import mock
+
+from ironic.common import driver_factory
+from ironic.common import exception
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.drivers.modules import iboot
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+
+INFO_DICT = db_utils.get_test_iboot_info()
+
+
+class IBootPrivateMethodTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IBootPrivateMethodTestCase, self).setUp()
+ self.config(max_retry=0, group='iboot')
+ self.config(retry_interval=0, group='iboot')
+
+ def test__parse_driver_info_good(self):
+ node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_iboot',
+ driver_info=INFO_DICT)
+ info = iboot._parse_driver_info(node)
+ self.assertIsNotNone(info.get('address'))
+ self.assertIsNotNone(info.get('username'))
+ self.assertIsNotNone(info.get('password'))
+ self.assertIsNotNone(info.get('port'))
+ self.assertIsNotNone(info.get('relay_id'))
+
+ def test__parse_driver_info_good_with_explicit_port(self):
+ info = dict(INFO_DICT)
+ info['iboot_port'] = '1234'
+ node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_iboot',
+ driver_info=info)
+ info = iboot._parse_driver_info(node)
+ self.assertEqual(1234, info.get('port'))
+
+ def test__parse_driver_info_good_with_explicit_relay_id(self):
+ info = dict(INFO_DICT)
+ info['iboot_relay_id'] = '2'
+ node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_iboot',
+ driver_info=info)
+ info = iboot._parse_driver_info(node)
+ self.assertEqual(2, info.get('relay_id'))
+
+ def test__parse_driver_info_missing_address(self):
+ info = dict(INFO_DICT)
+ del info['iboot_address']
+ node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_iboot',
+ driver_info=info)
+ self.assertRaises(exception.MissingParameterValue,
+ iboot._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_missing_username(self):
+ info = dict(INFO_DICT)
+ del info['iboot_username']
+ node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_iboot',
+ driver_info=info)
+ self.assertRaises(exception.MissingParameterValue,
+ iboot._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_missing_password(self):
+ info = dict(INFO_DICT)
+ del info['iboot_password']
+ node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_iboot',
+ driver_info=info)
+ self.assertRaises(exception.MissingParameterValue,
+ iboot._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_bad_port(self):
+ info = dict(INFO_DICT)
+ info['iboot_port'] = 'not-integer'
+ node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_iboot',
+ driver_info=info)
+ self.assertRaises(exception.InvalidParameterValue,
+ iboot._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_bad_relay_id(self):
+ info = dict(INFO_DICT)
+ info['iboot_relay_id'] = 'not-integer'
+ node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_iboot',
+ driver_info=info)
+ self.assertRaises(exception.InvalidParameterValue,
+ iboot._parse_driver_info,
+ node)
+
+ @mock.patch.object(iboot, '_get_connection', autospec=True)
+ def test__power_status_on(self, mock_get_conn):
+ mock_connection = mock.MagicMock(spec_set=['get_relays'])
+ mock_connection.get_relays.return_value = [True]
+ mock_get_conn.return_value = mock_connection
+ node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_iboot',
+ driver_info=INFO_DICT)
+ info = iboot._parse_driver_info(node)
+
+ status = iboot._power_status(info)
+
+ self.assertEqual(states.POWER_ON, status)
+ mock_get_conn.assert_called_once_with(info)
+ mock_connection.get_relays.assert_called_once_with()
+
+ @mock.patch.object(iboot, '_get_connection', autospec=True)
+ def test__power_status_off(self, mock_get_conn):
+ mock_connection = mock.MagicMock(spec_set=['get_relays'])
+ mock_connection.get_relays.return_value = [False]
+ mock_get_conn.return_value = mock_connection
+ node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_iboot',
+ driver_info=INFO_DICT)
+ info = iboot._parse_driver_info(node)
+
+ status = iboot._power_status(info)
+
+ self.assertEqual(states.POWER_OFF, status)
+ mock_get_conn.assert_called_once_with(info)
+ mock_connection.get_relays.assert_called_once_with()
+
+ @mock.patch.object(iboot, '_get_connection', autospec=True)
+ def test__power_status_exception(self, mock_get_conn):
+ mock_connection = mock.MagicMock(spec_set=['get_relays'])
+ mock_connection.get_relays.return_value = None
+ mock_get_conn.return_value = mock_connection
+ node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_iboot',
+ driver_info=INFO_DICT)
+ info = iboot._parse_driver_info(node)
+
+ status = iboot._power_status(info)
+ self.assertEqual(states.ERROR, status)
+ mock_get_conn.assert_called_once_with(info)
+ mock_connection.get_relays.assert_called_once_with()
+
+ @mock.patch.object(iboot, '_get_connection', autospec=True)
+ def test__power_status_exception_type_error(self, mock_get_conn):
+ mock_connection = mock.MagicMock(spec_set=['get_relays'])
+ side_effect = TypeError("Surprise!")
+ mock_connection.get_relays.side_effect = side_effect
+
+ mock_get_conn.return_value = mock_connection
+ node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_iboot',
+ driver_info=INFO_DICT)
+ info = iboot._parse_driver_info(node)
+
+ status = iboot._power_status(info)
+ self.assertEqual(states.ERROR, status)
+ mock_get_conn.assert_called_once_with(info)
+ mock_connection.get_relays.assert_called_once_with()
+
+ @mock.patch.object(iboot, '_get_connection', autospec=True)
+ def test__power_status_exception_index_error(self, mock_get_conn):
+ mock_connection = mock.MagicMock(spec_set=['get_relays'])
+ side_effect = IndexError("Gotcha!")
+ mock_connection.get_relays.side_effect = side_effect
+
+ mock_get_conn.return_value = mock_connection
+ node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_iboot',
+ driver_info=INFO_DICT)
+ info = iboot._parse_driver_info(node)
+ status = iboot._power_status(info)
+ self.assertEqual(states.ERROR, status)
+
+ mock_get_conn.assert_called_once_with(info)
+ mock_connection.get_relays.assert_called_once_with()
+
+ @mock.patch.object(iboot, '_get_connection', autospec=True)
+ def test__power_status_error(self, mock_get_conn):
+ mock_connection = mock.MagicMock(spec_set=['get_relays'])
+ mock_connection.get_relays.return_value = list()
+ mock_get_conn.return_value = mock_connection
+ node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_iboot',
+ driver_info=INFO_DICT)
+ info = iboot._parse_driver_info(node)
+
+ status = iboot._power_status(info)
+
+ self.assertEqual(states.ERROR, status)
+ mock_get_conn.assert_called_once_with(info)
+ mock_connection.get_relays.assert_called_once_with()
+
+ @mock.patch.object(iboot, '_get_connection', autospec=True)
+ def test__power_status_retries(self, mock_get_conn):
+ self.config(max_retry=1, group='iboot')
+
+ mock_connection = mock.MagicMock(spec_set=['get_relays'])
+ side_effect = TypeError("Surprise!")
+ mock_connection.get_relays.side_effect = side_effect
+
+ mock_get_conn.return_value = mock_connection
+ node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_iboot',
+ driver_info=INFO_DICT)
+ info = iboot._parse_driver_info(node)
+
+ status = iboot._power_status(info)
+ self.assertEqual(states.ERROR, status)
+ mock_get_conn.assert_called_once_with(info)
+ self.assertEqual(2, mock_connection.get_relays.call_count)
+
+
+class IBootDriverTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IBootDriverTestCase, self).setUp()
+ self.config(max_retry=0, group='iboot')
+ self.config(retry_interval=0, group='iboot')
+ mgr_utils.mock_the_extension_manager(driver='fake_iboot')
+ self.driver = driver_factory.get_driver('fake_iboot')
+ self.node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_iboot',
+ driver_info=INFO_DICT)
+ self.info = iboot._parse_driver_info(self.node)
+
+ def test_get_properties(self):
+ expected = iboot.COMMON_PROPERTIES
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=True) as task:
+ self.assertEqual(expected, task.driver.get_properties())
+
+ @mock.patch.object(iboot, '_power_status', autospec=True)
+ @mock.patch.object(iboot, '_switch', autospec=True)
+ def test_set_power_state_good(self, mock_switch, mock_power_status):
+ mock_power_status.return_value = states.POWER_ON
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.driver.power.set_power_state(task, states.POWER_ON)
+
+ # ensure functions were called with the valid parameters
+ mock_switch.assert_called_once_with(self.info, True)
+ mock_power_status.assert_called_once_with(self.info)
+
+ @mock.patch.object(iboot, '_power_status', autospec=True)
+ @mock.patch.object(iboot, '_switch', autospec=True)
+ def test_set_power_state_bad(self, mock_switch, mock_power_status):
+ mock_power_status.return_value = states.POWER_OFF
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.PowerStateFailure,
+ task.driver.power.set_power_state,
+ task, states.POWER_ON)
+
+ # ensure functions were called with the valid parameters
+ mock_switch.assert_called_once_with(self.info, True)
+ mock_power_status.assert_called_once_with(self.info)
+
+ @mock.patch.object(iboot, '_power_status', autospec=True)
+ @mock.patch.object(iboot, '_switch', autospec=True)
+ def test_set_power_state_invalid_parameter(self, mock_switch,
+ mock_power_status):
+ mock_power_status.return_value = states.POWER_ON
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.power.set_power_state,
+ task, states.NOSTATE)
+
+ @mock.patch.object(iboot, '_power_status', autospec=True)
+ @mock.patch.object(iboot, '_switch', spec_set=types.FunctionType)
+ def test_reboot_good(self, mock_switch, mock_power_status):
+ manager = mock.MagicMock(spec_set=['switch'])
+ mock_power_status.return_value = states.POWER_ON
+
+ manager.attach_mock(mock_switch, 'switch')
+ expected = [mock.call.switch(self.info, False),
+ mock.call.switch(self.info, True)]
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.driver.power.reboot(task)
+
+ self.assertEqual(manager.mock_calls, expected)
+
+ @mock.patch.object(iboot, '_power_status', autospec=True)
+ @mock.patch.object(iboot, '_switch', spec_set=types.FunctionType)
+ def test_reboot_bad(self, mock_switch, mock_power_status):
+ manager = mock.MagicMock(spec_set=['switch'])
+ mock_power_status.return_value = states.POWER_OFF
+
+ manager.attach_mock(mock_switch, 'switch')
+ expected = [mock.call.switch(self.info, False),
+ mock.call.switch(self.info, True)]
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.PowerStateFailure,
+ task.driver.power.reboot, task)
+
+ self.assertEqual(manager.mock_calls, expected)
+
+ @mock.patch.object(iboot, '_power_status', autospec=True)
+ @mock.patch.object(iboot, '_get_connection', autospec=True)
+ def test__switch_retries(self, mock_get_conn, mock_power_status):
+ self.config(max_retry=1, group='iboot')
+ mock_power_status.return_value = states.POWER_ON
+
+ mock_connection = mock.MagicMock(spec_set=['switch'])
+ side_effect = TypeError("Surprise!")
+ mock_connection.switch.side_effect = side_effect
+ mock_get_conn.return_value = mock_connection
+
+ iboot._switch(self.info, False)
+ self.assertEqual(2, mock_connection.switch.call_count)
+
+ @mock.patch.object(iboot, '_power_status', autospec=True)
+ def test_get_power_state(self, mock_power_status):
+ mock_power_status.return_value = states.POWER_ON
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ state = task.driver.power.get_power_state(task)
+ self.assertEqual(state, states.POWER_ON)
+
+ # ensure functions were called with the valid parameters
+ mock_power_status.assert_called_once_with(self.info)
+
+ @mock.patch.object(iboot, '_parse_driver_info', autospec=True)
+ def test_validate_good(self, parse_drv_info_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.driver.power.validate(task)
+ self.assertEqual(1, parse_drv_info_mock.call_count)
+
+ @mock.patch.object(iboot, '_parse_driver_info', autospec=True)
+ def test_validate_fails(self, parse_drv_info_mock):
+ side_effect = iter([exception.InvalidParameterValue("Bad input")])
+ parse_drv_info_mock.side_effect = side_effect
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.power.validate, task)
+ self.assertEqual(1, parse_drv_info_mock.call_count)
diff --git a/ironic/tests/unit/drivers/modules/test_image_cache.py b/ironic/tests/unit/drivers/modules/test_image_cache.py
new file mode 100644
index 000000000..447db0a99
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/test_image_cache.py
@@ -0,0 +1,697 @@
+# -*- encoding: utf-8 -*-
+#
+# Copyright 2014 Red Hat, Inc.
+#
+# 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.
+
+"""Tests for ImageCache class and helper functions."""
+
+import datetime
+import os
+import tempfile
+import time
+import uuid
+
+import mock
+from oslo_utils import uuidutils
+import six
+
+from ironic.common import exception
+from ironic.common import image_service
+from ironic.common import images
+from ironic.common import utils
+from ironic.drivers.modules import image_cache
+from ironic.tests.unit import base
+
+
+def touch(filename):
+ open(filename, 'w').close()
+
+
+class TestImageCacheFetch(base.TestCase):
+
+ def setUp(self):
+ super(TestImageCacheFetch, self).setUp()
+ self.master_dir = tempfile.mkdtemp()
+ self.cache = image_cache.ImageCache(self.master_dir, None, None)
+ self.dest_dir = tempfile.mkdtemp()
+ self.dest_path = os.path.join(self.dest_dir, 'dest')
+ self.uuid = uuidutils.generate_uuid()
+ self.master_path = os.path.join(self.master_dir, self.uuid)
+
+ @mock.patch.object(image_cache, '_fetch', autospec=True)
+ @mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
+ @mock.patch.object(image_cache.ImageCache, '_download_image',
+ autospec=True)
+ def test_fetch_image_no_master_dir(self, mock_download, mock_clean_up,
+ mock_fetch):
+ self.cache.master_dir = None
+ self.cache.fetch_image(self.uuid, self.dest_path)
+ self.assertFalse(mock_download.called)
+ mock_fetch.assert_called_once_with(
+ None, self.uuid, self.dest_path, True)
+ self.assertFalse(mock_clean_up.called)
+
+ @mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
+ @mock.patch.object(image_cache.ImageCache, '_download_image',
+ autospec=True)
+ @mock.patch.object(os, 'link', autospec=True)
+ @mock.patch.object(image_cache, '_delete_dest_path_if_stale',
+ return_value=True, autospec=True)
+ @mock.patch.object(image_cache, '_delete_master_path_if_stale',
+ return_value=True, autospec=True)
+ def test_fetch_image_dest_and_master_uptodate(
+ self, mock_cache_upd, mock_dest_upd, mock_link, mock_download,
+ mock_clean_up):
+ self.cache.fetch_image(self.uuid, self.dest_path)
+ mock_cache_upd.assert_called_once_with(self.master_path, self.uuid,
+ None)
+ mock_dest_upd.assert_called_once_with(self.master_path, self.dest_path)
+ self.assertFalse(mock_link.called)
+ self.assertFalse(mock_download.called)
+ self.assertFalse(mock_clean_up.called)
+
+ @mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
+ @mock.patch.object(image_cache.ImageCache, '_download_image',
+ autospec=True)
+ @mock.patch.object(os, 'link', autospec=True)
+ @mock.patch.object(image_cache, '_delete_dest_path_if_stale',
+ return_value=False, autospec=True)
+ @mock.patch.object(image_cache, '_delete_master_path_if_stale',
+ return_value=True, autospec=True)
+ def test_fetch_image_dest_out_of_date(
+ self, mock_cache_upd, mock_dest_upd, mock_link, mock_download,
+ mock_clean_up):
+ self.cache.fetch_image(self.uuid, self.dest_path)
+ mock_cache_upd.assert_called_once_with(self.master_path, self.uuid,
+ None)
+ mock_dest_upd.assert_called_once_with(self.master_path, self.dest_path)
+ mock_link.assert_called_once_with(self.master_path, self.dest_path)
+ self.assertFalse(mock_download.called)
+ self.assertFalse(mock_clean_up.called)
+
+ @mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
+ @mock.patch.object(image_cache.ImageCache, '_download_image',
+ autospec=True)
+ @mock.patch.object(os, 'link', autospec=True)
+ @mock.patch.object(image_cache, '_delete_dest_path_if_stale',
+ return_value=True, autospec=True)
+ @mock.patch.object(image_cache, '_delete_master_path_if_stale',
+ return_value=False, autospec=True)
+ def test_fetch_image_master_out_of_date(
+ self, mock_cache_upd, mock_dest_upd, mock_link, mock_download,
+ mock_clean_up):
+ self.cache.fetch_image(self.uuid, self.dest_path)
+ mock_cache_upd.assert_called_once_with(self.master_path, self.uuid,
+ None)
+ mock_dest_upd.assert_called_once_with(self.master_path, self.dest_path)
+ self.assertFalse(mock_link.called)
+ mock_download.assert_called_once_with(
+ self.cache, self.uuid, self.master_path, self.dest_path,
+ ctx=None, force_raw=True)
+ mock_clean_up.assert_called_once_with(self.cache)
+
+ @mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
+ @mock.patch.object(image_cache.ImageCache, '_download_image',
+ autospec=True)
+ @mock.patch.object(os, 'link', autospec=True)
+ @mock.patch.object(image_cache, '_delete_dest_path_if_stale',
+ return_value=True, autospec=True)
+ @mock.patch.object(image_cache, '_delete_master_path_if_stale',
+ return_value=False, autospec=True)
+ def test_fetch_image_both_master_and_dest_out_of_date(
+ self, mock_cache_upd, mock_dest_upd, mock_link, mock_download,
+ mock_clean_up):
+ self.cache.fetch_image(self.uuid, self.dest_path)
+ mock_cache_upd.assert_called_once_with(self.master_path, self.uuid,
+ None)
+ mock_dest_upd.assert_called_once_with(self.master_path, self.dest_path)
+ self.assertFalse(mock_link.called)
+ mock_download.assert_called_once_with(
+ self.cache, self.uuid, self.master_path, self.dest_path,
+ ctx=None, force_raw=True)
+ mock_clean_up.assert_called_once_with(self.cache)
+
+ @mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
+ @mock.patch.object(image_cache.ImageCache, '_download_image',
+ autospec=True)
+ def test_fetch_image_not_uuid(self, mock_download, mock_clean_up):
+ href = u'http://abc.com/ubuntu.qcow2'
+ href_encoded = href.encode('utf-8') if six.PY2 else href
+ href_converted = str(uuid.uuid5(uuid.NAMESPACE_URL, href_encoded))
+ master_path = os.path.join(self.master_dir, href_converted)
+ self.cache.fetch_image(href, self.dest_path)
+ mock_download.assert_called_once_with(
+ self.cache, href, master_path, self.dest_path,
+ ctx=None, force_raw=True)
+ self.assertTrue(mock_clean_up.called)
+
+ @mock.patch.object(image_cache, '_fetch', autospec=True)
+ def test__download_image(self, mock_fetch):
+ def _fake_fetch(ctx, uuid, tmp_path, *args):
+ self.assertEqual(self.uuid, uuid)
+ self.assertNotEqual(self.dest_path, tmp_path)
+ self.assertNotEqual(os.path.dirname(tmp_path), self.master_dir)
+ with open(tmp_path, 'w') as fp:
+ fp.write("TEST")
+
+ mock_fetch.side_effect = _fake_fetch
+ self.cache._download_image(self.uuid, self.master_path, self.dest_path)
+ self.assertTrue(os.path.isfile(self.dest_path))
+ self.assertTrue(os.path.isfile(self.master_path))
+ self.assertEqual(os.stat(self.dest_path).st_ino,
+ os.stat(self.master_path).st_ino)
+ with open(self.dest_path) as fp:
+ self.assertEqual("TEST", fp.read())
+
+
+@mock.patch.object(os, 'unlink', autospec=True)
+class TestUpdateImages(base.TestCase):
+
+ def setUp(self):
+ super(TestUpdateImages, self).setUp()
+ self.master_dir = tempfile.mkdtemp()
+ self.dest_dir = tempfile.mkdtemp()
+ self.dest_path = os.path.join(self.dest_dir, 'dest')
+ self.uuid = uuidutils.generate_uuid()
+ self.master_path = os.path.join(self.master_dir, self.uuid)
+
+ @mock.patch.object(os.path, 'exists', return_value=False, autospec=True)
+ @mock.patch.object(image_service, 'get_image_service', autospec=True)
+ def test__delete_master_path_if_stale_glance_img_not_cached(
+ self, mock_gis, mock_path_exists, mock_unlink):
+ res = image_cache._delete_master_path_if_stale(self.master_path,
+ self.uuid, None)
+ self.assertFalse(mock_gis.called)
+ self.assertFalse(mock_unlink.called)
+ mock_path_exists.assert_called_once_with(self.master_path)
+ self.assertFalse(res)
+
+ @mock.patch.object(os.path, 'exists', return_value=True, autospec=True)
+ @mock.patch.object(image_service, 'get_image_service', autospec=True)
+ def test__delete_master_path_if_stale_glance_img(
+ self, mock_gis, mock_path_exists, mock_unlink):
+ res = image_cache._delete_master_path_if_stale(self.master_path,
+ self.uuid, None)
+ self.assertFalse(mock_gis.called)
+ self.assertFalse(mock_unlink.called)
+ mock_path_exists.assert_called_once_with(self.master_path)
+ self.assertTrue(res)
+
+ @mock.patch.object(image_service, 'get_image_service', autospec=True)
+ def test__delete_master_path_if_stale_no_master(self, mock_gis,
+ mock_unlink):
+ res = image_cache._delete_master_path_if_stale(self.master_path,
+ 'http://11', None)
+ self.assertFalse(mock_gis.called)
+ self.assertFalse(mock_unlink.called)
+ self.assertFalse(res)
+
+ @mock.patch.object(image_service, 'get_image_service', autospec=True)
+ def test__delete_master_path_if_stale_no_updated_at(self, mock_gis,
+ mock_unlink):
+ touch(self.master_path)
+ href = 'http://awesomefreeimages.al/img111'
+ mock_gis.return_value.show.return_value = {}
+ res = image_cache._delete_master_path_if_stale(self.master_path, href,
+ None)
+ mock_gis.assert_called_once_with(href, context=None)
+ self.assertFalse(mock_unlink.called)
+ self.assertTrue(res)
+
+ @mock.patch.object(image_service, 'get_image_service', autospec=True)
+ def test__delete_master_path_if_stale_master_up_to_date(self, mock_gis,
+ mock_unlink):
+ touch(self.master_path)
+ href = 'http://awesomefreeimages.al/img999'
+ mock_gis.return_value.show.return_value = {
+ 'updated_at': datetime.datetime(1999, 11, 15, 8, 12, 31)
+ }
+ res = image_cache._delete_master_path_if_stale(self.master_path, href,
+ None)
+ mock_gis.assert_called_once_with(href, context=None)
+ self.assertFalse(mock_unlink.called)
+ self.assertTrue(res)
+
+ @mock.patch.object(image_service, 'get_image_service', autospec=True)
+ def test__delete_master_path_if_stale_master_same_time(self, mock_gis,
+ mock_unlink):
+ # When times identical should not delete cached file
+ touch(self.master_path)
+ mtime = utils.unix_file_modification_datetime(self.master_path)
+ href = 'http://awesomefreeimages.al/img999'
+ mock_gis.return_value.show.return_value = {
+ 'updated_at': mtime
+ }
+ res = image_cache._delete_master_path_if_stale(self.master_path, href,
+ None)
+ mock_gis.assert_called_once_with(href, context=None)
+ self.assertFalse(mock_unlink.called)
+ self.assertTrue(res)
+
+ @mock.patch.object(image_service, 'get_image_service', autospec=True)
+ def test__delete_master_path_if_stale_out_of_date(self, mock_gis,
+ mock_unlink):
+ touch(self.master_path)
+ href = 'http://awesomefreeimages.al/img999'
+ mock_gis.return_value.show.return_value = {
+ 'updated_at': datetime.datetime((datetime.datetime.utcnow().year
+ + 1), 11, 15, 8, 12, 31)
+ }
+ res = image_cache._delete_master_path_if_stale(self.master_path, href,
+ None)
+ mock_gis.assert_called_once_with(href, context=None)
+ mock_unlink.assert_called_once_with(self.master_path)
+ self.assertFalse(res)
+
+ def test__delete_dest_path_if_stale_no_dest(self, mock_unlink):
+ res = image_cache._delete_dest_path_if_stale(self.master_path,
+ self.dest_path)
+ self.assertFalse(mock_unlink.called)
+ self.assertFalse(res)
+
+ def test__delete_dest_path_if_stale_no_master(self, mock_unlink):
+ touch(self.dest_path)
+ res = image_cache._delete_dest_path_if_stale(self.master_path,
+ self.dest_path)
+ mock_unlink.assert_called_once_with(self.dest_path)
+ self.assertFalse(res)
+
+ def test__delete_dest_path_if_stale_out_of_date(self, mock_unlink):
+ touch(self.master_path)
+ touch(self.dest_path)
+ res = image_cache._delete_dest_path_if_stale(self.master_path,
+ self.dest_path)
+ mock_unlink.assert_called_once_with(self.dest_path)
+ self.assertFalse(res)
+
+ def test__delete_dest_path_if_stale_up_to_date(self, mock_unlink):
+ touch(self.master_path)
+ os.link(self.master_path, self.dest_path)
+ res = image_cache._delete_dest_path_if_stale(self.master_path,
+ self.dest_path)
+ self.assertFalse(mock_unlink.called)
+ self.assertTrue(res)
+
+
+class TestImageCacheCleanUp(base.TestCase):
+
+ def setUp(self):
+ super(TestImageCacheCleanUp, self).setUp()
+ self.master_dir = tempfile.mkdtemp()
+ self.cache = image_cache.ImageCache(self.master_dir,
+ cache_size=10,
+ cache_ttl=600)
+
+ @mock.patch.object(image_cache.ImageCache, '_clean_up_ensure_cache_size',
+ autospec=True)
+ def test_clean_up_old_deleted(self, mock_clean_size):
+ mock_clean_size.return_value = None
+ files = [os.path.join(self.master_dir, str(i))
+ for i in range(2)]
+ for filename in files:
+ touch(filename)
+ # NOTE(dtantsur): Can't alter ctime, have to set mtime to the future
+ new_current_time = time.time() + 900
+ os.utime(files[0], (new_current_time - 100, new_current_time - 100))
+ with mock.patch.object(time, 'time', lambda: new_current_time):
+ self.cache.clean_up()
+
+ mock_clean_size.assert_called_once_with(self.cache, mock.ANY, None)
+ survived = mock_clean_size.call_args[0][1]
+ self.assertEqual(1, len(survived))
+ self.assertEqual(files[0], survived[0][0])
+ # NOTE(dtantsur): do not compare milliseconds
+ self.assertEqual(int(new_current_time - 100), int(survived[0][1]))
+ self.assertEqual(int(new_current_time - 100),
+ int(survived[0][2].st_mtime))
+
+ @mock.patch.object(image_cache.ImageCache, '_clean_up_ensure_cache_size',
+ autospec=True)
+ def test_clean_up_old_with_amount(self, mock_clean_size):
+ files = [os.path.join(self.master_dir, str(i))
+ for i in range(2)]
+ for filename in files:
+ open(filename, 'wb').write(b'X')
+ new_current_time = time.time() + 900
+ with mock.patch.object(time, 'time', lambda: new_current_time):
+ self.cache.clean_up(amount=1)
+
+ self.assertFalse(mock_clean_size.called)
+ # Exactly one file is expected to be deleted
+ self.assertTrue(any(os.path.exists(f) for f in files))
+ self.assertFalse(all(os.path.exists(f) for f in files))
+
+ @mock.patch.object(image_cache.ImageCache, '_clean_up_ensure_cache_size',
+ autospec=True)
+ def test_clean_up_files_with_links_untouched(self, mock_clean_size):
+ mock_clean_size.return_value = None
+ files = [os.path.join(self.master_dir, str(i))
+ for i in range(2)]
+ for filename in files:
+ touch(filename)
+ os.link(filename, filename + 'copy')
+
+ new_current_time = time.time() + 900
+ with mock.patch.object(time, 'time', lambda: new_current_time):
+ self.cache.clean_up()
+
+ for filename in files:
+ self.assertTrue(os.path.exists(filename))
+ mock_clean_size.assert_called_once_with(mock.ANY, [], None)
+
+ @mock.patch.object(image_cache.ImageCache, '_clean_up_too_old',
+ autospec=True)
+ def test_clean_up_ensure_cache_size(self, mock_clean_ttl):
+ mock_clean_ttl.side_effect = lambda *xx: xx[1:]
+ # NOTE(dtantsur): Cache size in test is 10 bytes, we create 6 files
+ # with 3 bytes each and expect 3 to be deleted
+ files = [os.path.join(self.master_dir, str(i))
+ for i in range(6)]
+ for filename in files:
+ with open(filename, 'w') as fp:
+ fp.write('123')
+ # NOTE(dtantsur): Make 3 files 'newer' to check that
+ # old ones are deleted first
+ new_current_time = time.time() + 100
+ for filename in files[:3]:
+ os.utime(filename, (new_current_time, new_current_time))
+
+ with mock.patch.object(time, 'time', lambda: new_current_time):
+ self.cache.clean_up()
+
+ for filename in files[:3]:
+ self.assertTrue(os.path.exists(filename))
+ for filename in files[3:]:
+ self.assertFalse(os.path.exists(filename))
+
+ mock_clean_ttl.assert_called_once_with(mock.ANY, mock.ANY, None)
+
+ @mock.patch.object(image_cache.ImageCache, '_clean_up_too_old',
+ autospec=True)
+ def test_clean_up_ensure_cache_size_with_amount(self, mock_clean_ttl):
+ mock_clean_ttl.side_effect = lambda *xx: xx[1:]
+ # NOTE(dtantsur): Cache size in test is 10 bytes, we create 6 files
+ # with 3 bytes each and set amount to be 15, 5 files are to be deleted
+ files = [os.path.join(self.master_dir, str(i))
+ for i in range(6)]
+ for filename in files:
+ with open(filename, 'w') as fp:
+ fp.write('123')
+ # NOTE(dtantsur): Make 1 file 'newer' to check that
+ # old ones are deleted first
+ new_current_time = time.time() + 100
+ os.utime(files[0], (new_current_time, new_current_time))
+
+ with mock.patch.object(time, 'time', lambda: new_current_time):
+ self.cache.clean_up(amount=15)
+
+ self.assertTrue(os.path.exists(files[0]))
+ for filename in files[5:]:
+ self.assertFalse(os.path.exists(filename))
+
+ mock_clean_ttl.assert_called_once_with(mock.ANY, mock.ANY, 15)
+
+ @mock.patch.object(image_cache.LOG, 'info', autospec=True)
+ @mock.patch.object(image_cache.ImageCache, '_clean_up_too_old',
+ autospec=True)
+ def test_clean_up_cache_still_large(self, mock_clean_ttl, mock_log):
+ mock_clean_ttl.side_effect = lambda *xx: xx[1:]
+ # NOTE(dtantsur): Cache size in test is 10 bytes, we create 2 files
+ # than cannot be deleted and expected this to be logged
+ files = [os.path.join(self.master_dir, str(i))
+ for i in range(2)]
+ for filename in files:
+ with open(filename, 'w') as fp:
+ fp.write('123')
+ os.link(filename, filename + 'copy')
+
+ self.cache.clean_up()
+
+ for filename in files:
+ self.assertTrue(os.path.exists(filename))
+ self.assertTrue(mock_log.called)
+ mock_clean_ttl.assert_called_once_with(mock.ANY, mock.ANY, None)
+
+ @mock.patch.object(utils, 'rmtree_without_raise', autospec=True)
+ @mock.patch.object(image_cache, '_fetch', autospec=True)
+ def test_temp_images_not_cleaned(self, mock_fetch, mock_rmtree):
+ def _fake_fetch(ctx, uuid, tmp_path, *args):
+ with open(tmp_path, 'w') as fp:
+ fp.write("TEST" * 10)
+
+ # assume cleanup from another thread at this moment
+ self.cache.clean_up()
+ self.assertTrue(os.path.exists(tmp_path))
+
+ mock_fetch.side_effect = _fake_fetch
+ master_path = os.path.join(self.master_dir, 'uuid')
+ dest_path = os.path.join(tempfile.mkdtemp(), 'dest')
+ self.cache._download_image('uuid', master_path, dest_path)
+ self.assertTrue(mock_rmtree.called)
+
+ @mock.patch.object(utils, 'rmtree_without_raise', autospec=True)
+ @mock.patch.object(image_cache, '_fetch', autospec=True)
+ def test_temp_dir_exception(self, mock_fetch, mock_rmtree):
+ mock_fetch.side_effect = exception.IronicException
+ self.assertRaises(exception.IronicException,
+ self.cache._download_image,
+ 'uuid', 'fake', 'fake')
+ self.assertTrue(mock_rmtree.called)
+
+ @mock.patch.object(image_cache.LOG, 'warn', autospec=True)
+ @mock.patch.object(image_cache.ImageCache, '_clean_up_too_old',
+ autospec=True)
+ @mock.patch.object(image_cache.ImageCache, '_clean_up_ensure_cache_size',
+ autospec=True)
+ def test_clean_up_amount_not_satisfied(self, mock_clean_size,
+ mock_clean_ttl, mock_log):
+ mock_clean_ttl.side_effect = lambda *xx: xx[1:]
+ mock_clean_size.side_effect = lambda self, listing, amount: amount
+ self.cache.clean_up(amount=15)
+ self.assertTrue(mock_log.called)
+
+ def test_cleanup_ordering(self):
+
+ class ParentCache(image_cache.ImageCache):
+ def __init__(self):
+ super(ParentCache, self).__init__('a', 1, 1, None)
+
+ @image_cache.cleanup(priority=10000)
+ class Cache1(ParentCache):
+ pass
+
+ @image_cache.cleanup(priority=20000)
+ class Cache2(ParentCache):
+ pass
+
+ @image_cache.cleanup(priority=10000)
+ class Cache3(ParentCache):
+ pass
+
+ self.assertEqual(image_cache._cache_cleanup_list[0][1], Cache2)
+
+ # The order of caches with same prioirty is not deterministic.
+ item_possibilities = [Cache1, Cache3]
+ second_item_actual = image_cache._cache_cleanup_list[1][1]
+ self.assertIn(second_item_actual, item_possibilities)
+ item_possibilities.remove(second_item_actual)
+ third_item_actual = image_cache._cache_cleanup_list[2][1]
+ self.assertEqual(item_possibilities[0], third_item_actual)
+
+
+@mock.patch.object(image_cache, '_cache_cleanup_list', autospec=True)
+@mock.patch.object(os, 'statvfs', autospec=True)
+@mock.patch.object(image_service, 'get_image_service', autospec=True)
+class CleanupImageCacheTestCase(base.TestCase):
+
+ def setUp(self):
+ super(CleanupImageCacheTestCase, self).setUp()
+ self.mock_first_cache = mock.MagicMock(spec_set=[])
+ self.mock_second_cache = mock.MagicMock(spec_set=[])
+ self.cache_cleanup_list = [(50, self.mock_first_cache),
+ (20, self.mock_second_cache)]
+ self.mock_first_cache.return_value.master_dir = 'first_cache_dir'
+ self.mock_second_cache.return_value.master_dir = 'second_cache_dir'
+
+ def test_no_clean_up(self, mock_image_service, mock_statvfs,
+ cache_cleanup_list_mock):
+ # Enough space found - no clean up
+ mock_show = mock_image_service.return_value.show
+ mock_show.return_value = dict(size=42)
+ mock_statvfs.return_value = mock.MagicMock(
+ spec_set=['f_frsize', 'f_bavail'], f_frsize=1, f_bavail=1024)
+
+ cache_cleanup_list_mock.__iter__.return_value = self.cache_cleanup_list
+
+ image_cache.clean_up_caches(None, 'master_dir', [('uuid', 'path')])
+
+ mock_show.assert_called_once_with('uuid')
+ mock_statvfs.assert_called_once_with('master_dir')
+ self.assertFalse(self.mock_first_cache.return_value.clean_up.called)
+ self.assertFalse(self.mock_second_cache.return_value.clean_up.called)
+
+ mock_statvfs.assert_called_once_with('master_dir')
+
+ @mock.patch.object(os, 'stat', autospec=True)
+ def test_one_clean_up(self, mock_stat, mock_image_service, mock_statvfs,
+ cache_cleanup_list_mock):
+ # Not enough space, first cache clean up is enough
+ mock_stat.return_value.st_dev = 1
+ mock_show = mock_image_service.return_value.show
+ mock_show.return_value = dict(size=42)
+ mock_statvfs.side_effect = [
+ mock.MagicMock(f_frsize=1, f_bavail=1,
+ spec_set=['f_frsize', 'f_bavail']),
+ mock.MagicMock(f_frsize=1, f_bavail=1024,
+ spec_set=['f_frsize', 'f_bavail'])
+ ]
+ cache_cleanup_list_mock.__iter__.return_value = self.cache_cleanup_list
+ image_cache.clean_up_caches(None, 'master_dir', [('uuid', 'path')])
+
+ mock_show.assert_called_once_with('uuid')
+ mock_statvfs.assert_called_with('master_dir')
+ self.assertEqual(2, mock_statvfs.call_count)
+ self.mock_first_cache.return_value.clean_up.assert_called_once_with(
+ amount=(42 - 1))
+ self.assertFalse(self.mock_second_cache.return_value.clean_up.called)
+
+ # Since we are using generator expression in clean_up_caches, stat on
+ # second cache wouldn't be called if we got enough free space on
+ # cleaning up the first cache.
+ mock_stat_calls_expected = [mock.call('master_dir'),
+ mock.call('first_cache_dir')]
+ mock_statvfs_calls_expected = [mock.call('master_dir'),
+ mock.call('master_dir')]
+ self.assertEqual(mock_stat_calls_expected, mock_stat.mock_calls)
+ self.assertEqual(mock_statvfs_calls_expected, mock_statvfs.mock_calls)
+
+ @mock.patch.object(os, 'stat', autospec=True)
+ def test_clean_up_another_fs(self, mock_stat, mock_image_service,
+ mock_statvfs, cache_cleanup_list_mock):
+ # Not enough space, need to cleanup second cache
+ mock_stat.side_effect = [mock.MagicMock(st_dev=1, spec_set=['st_dev']),
+ mock.MagicMock(st_dev=2, spec_set=['st_dev']),
+ mock.MagicMock(st_dev=1, spec_set=['st_dev'])]
+ mock_show = mock_image_service.return_value.show
+ mock_show.return_value = dict(size=42)
+ mock_statvfs.side_effect = [
+ mock.MagicMock(f_frsize=1, f_bavail=1,
+ spec_set=['f_frsize', 'f_bavail']),
+ mock.MagicMock(f_frsize=1, f_bavail=1024,
+ spec_set=['f_frsize', 'f_bavail'])
+ ]
+
+ cache_cleanup_list_mock.__iter__.return_value = self.cache_cleanup_list
+ image_cache.clean_up_caches(None, 'master_dir', [('uuid', 'path')])
+
+ mock_show.assert_called_once_with('uuid')
+ mock_statvfs.assert_called_with('master_dir')
+ self.assertEqual(2, mock_statvfs.call_count)
+ self.mock_second_cache.return_value.clean_up.assert_called_once_with(
+ amount=(42 - 1))
+ self.assertFalse(self.mock_first_cache.return_value.clean_up.called)
+
+ # Since first cache exists on a different partition, it wouldn't be
+ # considered for cleanup.
+ mock_stat_calls_expected = [mock.call('master_dir'),
+ mock.call('first_cache_dir'),
+ mock.call('second_cache_dir')]
+ mock_statvfs_calls_expected = [mock.call('master_dir'),
+ mock.call('master_dir')]
+ self.assertEqual(mock_stat_calls_expected, mock_stat.mock_calls)
+ self.assertEqual(mock_statvfs_calls_expected, mock_statvfs.mock_calls)
+
+ @mock.patch.object(os, 'stat', autospec=True)
+ def test_both_clean_up(self, mock_stat, mock_image_service, mock_statvfs,
+ cache_cleanup_list_mock):
+ # Not enough space, clean up of both caches required
+ mock_stat.return_value.st_dev = 1
+ mock_show = mock_image_service.return_value.show
+ mock_show.return_value = dict(size=42)
+ mock_statvfs.side_effect = [
+ mock.MagicMock(f_frsize=1, f_bavail=1,
+ spec_set=['f_frsize', 'f_bavail']),
+ mock.MagicMock(f_frsize=1, f_bavail=2,
+ spec_set=['f_frsize', 'f_bavail']),
+ mock.MagicMock(f_frsize=1, f_bavail=1024,
+ spec_set=['f_frsize', 'f_bavail'])
+ ]
+
+ cache_cleanup_list_mock.__iter__.return_value = self.cache_cleanup_list
+ image_cache.clean_up_caches(None, 'master_dir', [('uuid', 'path')])
+
+ mock_show.assert_called_once_with('uuid')
+ mock_statvfs.assert_called_with('master_dir')
+ self.assertEqual(3, mock_statvfs.call_count)
+ self.mock_first_cache.return_value.clean_up.assert_called_once_with(
+ amount=(42 - 1))
+ self.mock_second_cache.return_value.clean_up.assert_called_once_with(
+ amount=(42 - 2))
+
+ mock_stat_calls_expected = [mock.call('master_dir'),
+ mock.call('first_cache_dir'),
+ mock.call('second_cache_dir')]
+ mock_statvfs_calls_expected = [mock.call('master_dir'),
+ mock.call('master_dir'),
+ mock.call('master_dir')]
+ self.assertEqual(mock_stat_calls_expected, mock_stat.mock_calls)
+ self.assertEqual(mock_statvfs_calls_expected, mock_statvfs.mock_calls)
+
+ @mock.patch.object(os, 'stat', autospec=True)
+ def test_clean_up_fail(self, mock_stat, mock_image_service, mock_statvfs,
+ cache_cleanup_list_mock):
+ # Not enough space even after cleaning both caches - failure
+ mock_stat.return_value.st_dev = 1
+ mock_show = mock_image_service.return_value.show
+ mock_show.return_value = dict(size=42)
+ mock_statvfs.return_value = mock.MagicMock(
+ f_frsize=1, f_bavail=1, spec_set=['f_frsize', 'f_bavail'])
+
+ cache_cleanup_list_mock.__iter__.return_value = self.cache_cleanup_list
+ self.assertRaises(exception.InsufficientDiskSpace,
+ image_cache.clean_up_caches,
+ None, 'master_dir', [('uuid', 'path')])
+
+ mock_show.assert_called_once_with('uuid')
+ mock_statvfs.assert_called_with('master_dir')
+ self.assertEqual(3, mock_statvfs.call_count)
+ self.mock_first_cache.return_value.clean_up.assert_called_once_with(
+ amount=(42 - 1))
+ self.mock_second_cache.return_value.clean_up.assert_called_once_with(
+ amount=(42 - 1))
+
+ mock_stat_calls_expected = [mock.call('master_dir'),
+ mock.call('first_cache_dir'),
+ mock.call('second_cache_dir')]
+ mock_statvfs_calls_expected = [mock.call('master_dir'),
+ mock.call('master_dir'),
+ mock.call('master_dir')]
+ self.assertEqual(mock_stat_calls_expected, mock_stat.mock_calls)
+ self.assertEqual(mock_statvfs_calls_expected, mock_statvfs.mock_calls)
+
+
+class TestFetchCleanup(base.TestCase):
+
+ @mock.patch.object(images, 'converted_size', autospec=True)
+ @mock.patch.object(images, 'fetch', autospec=True)
+ @mock.patch.object(images, 'image_to_raw', autospec=True)
+ @mock.patch.object(image_cache, '_clean_up_caches', autospec=True)
+ def test__fetch(self, mock_clean, mock_raw, mock_fetch, mock_size):
+ mock_size.return_value = 100
+ image_cache._fetch('fake', 'fake-uuid', '/foo/bar', force_raw=True)
+ mock_fetch.assert_called_once_with('fake', 'fake-uuid',
+ '/foo/bar.part', force_raw=False)
+ mock_clean.assert_called_once_with('/foo', 100)
+ mock_raw.assert_called_once_with('fake-uuid', '/foo/bar',
+ '/foo/bar.part')
diff --git a/ironic/tests/unit/drivers/modules/test_inspector.py b/ironic/tests/unit/drivers/modules/test_inspector.py
new file mode 100644
index 000000000..349acb4b8
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/test_inspector.py
@@ -0,0 +1,239 @@
+# 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 eventlet
+import ironic_inspector_client as client
+import mock
+
+from ironic.common import driver_factory
+from ironic.common import exception
+from ironic.common import keystone
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.drivers.modules import inspector
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.objects import utils as obj_utils
+
+
+class DisabledTestCase(db_base.DbTestCase):
+ def setUp(self):
+ super(DisabledTestCase, self).setUp()
+
+ def _do_mock(self):
+ # NOTE(dtantsur): fake driver always has inspection, using another one
+ mgr_utils.mock_the_extension_manager("pxe_ssh")
+ self.driver = driver_factory.get_driver("pxe_ssh")
+
+ def test_disabled(self):
+ self.config(enabled=False, group='inspector')
+ self._do_mock()
+ self.assertIsNone(self.driver.inspect)
+ # NOTE(dtantsur): it's expected that fake_inspector fails to load
+ # in this case
+ self.assertRaises(exception.DriverLoadError,
+ mgr_utils.mock_the_extension_manager,
+ "fake_inspector")
+
+ def test_enabled(self):
+ self.config(enabled=True, group='inspector')
+ self._do_mock()
+ self.assertIsNotNone(self.driver.inspect)
+
+ @mock.patch.object(inspector, 'client', None)
+ def test_init_inspector_not_imported(self):
+ self.assertRaises(exception.DriverLoadError,
+ inspector.Inspector)
+
+ def test_init_ok(self):
+ self.config(enabled=True, group='inspector')
+ inspector.Inspector()
+
+
+class BaseTestCase(db_base.DbTestCase):
+ def setUp(self):
+ super(BaseTestCase, self).setUp()
+ self.config(enabled=True, group='inspector')
+ mgr_utils.mock_the_extension_manager("fake_inspector")
+ self.driver = driver_factory.get_driver("fake_inspector")
+ self.node = obj_utils.get_test_node(self.context)
+ self.task = mock.MagicMock(spec=task_manager.TaskManager)
+ self.task.context = mock.MagicMock(spec_set=['auth_token'])
+ self.task.shared = False
+ self.task.node = self.node
+ self.task.driver = self.driver
+ self.api_version = (1, 0)
+
+
+class CommonFunctionsTestCase(BaseTestCase):
+ def test_validate_ok(self):
+ self.driver.inspect.validate(self.task)
+
+ def test_get_properties(self):
+ res = self.driver.inspect.get_properties()
+ self.assertEqual({}, res)
+
+ def test_create_if_enabled(self):
+ res = inspector.Inspector.create_if_enabled('driver')
+ self.assertIsInstance(res, inspector.Inspector)
+
+ @mock.patch.object(inspector.LOG, 'info', autospec=True)
+ def test_create_if_enabled_disabled(self, warn_mock):
+ self.config(enabled=False, group='inspector')
+ res = inspector.Inspector.create_if_enabled('driver')
+ self.assertIsNone(res)
+ self.assertTrue(warn_mock.called)
+
+
+@mock.patch.object(eventlet, 'spawn_n', lambda f, *a, **kw: f(*a, **kw))
+@mock.patch.object(client, 'introspect')
+class InspectHardwareTestCase(BaseTestCase):
+ def test_ok(self, mock_introspect):
+ self.assertEqual(states.INSPECTING,
+ self.driver.inspect.inspect_hardware(self.task))
+ mock_introspect.assert_called_once_with(
+ self.node.uuid,
+ api_version=self.api_version,
+ auth_token=self.task.context.auth_token)
+
+ def test_url(self, mock_introspect):
+ self.config(service_url='meow', group='inspector')
+ self.assertEqual(states.INSPECTING,
+ self.driver.inspect.inspect_hardware(self.task))
+ mock_introspect.assert_called_once_with(
+ self.node.uuid,
+ api_version=self.api_version,
+ auth_token=self.task.context.auth_token,
+ base_url='meow')
+
+ @mock.patch.object(task_manager, 'acquire', autospec=True)
+ def test_error(self, mock_acquire, mock_introspect):
+ mock_introspect.side_effect = RuntimeError('boom')
+ self.driver.inspect.inspect_hardware(self.task)
+ mock_introspect.assert_called_once_with(
+ self.node.uuid,
+ api_version=self.api_version,
+ auth_token=self.task.context.auth_token)
+ task = mock_acquire.return_value.__enter__.return_value
+ self.assertIn('boom', task.node.last_error)
+ task.process_event.assert_called_once_with('fail')
+
+
+@mock.patch.object(keystone, 'get_admin_auth_token', lambda: 'the token')
+@mock.patch.object(client, 'get_status')
+class CheckStatusTestCase(BaseTestCase):
+ def setUp(self):
+ super(CheckStatusTestCase, self).setUp()
+ self.node.provision_state = states.INSPECTING
+
+ def test_not_inspecting(self, mock_get):
+ self.node.provision_state = states.MANAGEABLE
+ inspector._check_status(self.task)
+ self.assertFalse(mock_get.called)
+
+ def test_not_inspector(self, mock_get):
+ self.task.driver.inspect = object()
+ inspector._check_status(self.task)
+ self.assertFalse(mock_get.called)
+
+ def test_not_finished(self, mock_get):
+ mock_get.return_value = {}
+ inspector._check_status(self.task)
+ mock_get.assert_called_once_with(self.node.uuid,
+ api_version=self.api_version,
+ auth_token='the token')
+ self.assertFalse(self.task.process_event.called)
+
+ def test_exception_ignored(self, mock_get):
+ mock_get.side_effect = RuntimeError('boom')
+ inspector._check_status(self.task)
+ mock_get.assert_called_once_with(self.node.uuid,
+ api_version=self.api_version,
+ auth_token='the token')
+ self.assertFalse(self.task.process_event.called)
+
+ def test_status_ok(self, mock_get):
+ mock_get.return_value = {'finished': True}
+ inspector._check_status(self.task)
+ mock_get.assert_called_once_with(self.node.uuid,
+ api_version=self.api_version,
+ auth_token='the token')
+ self.task.process_event.assert_called_once_with('done')
+
+ def test_status_error(self, mock_get):
+ mock_get.return_value = {'error': 'boom'}
+ inspector._check_status(self.task)
+ mock_get.assert_called_once_with(self.node.uuid,
+ api_version=self.api_version,
+ auth_token='the token')
+ self.task.process_event.assert_called_once_with('fail')
+ self.assertIn('boom', self.node.last_error)
+
+ def test_service_url(self, mock_get):
+ self.config(service_url='meow', group='inspector')
+ mock_get.return_value = {'finished': True}
+ inspector._check_status(self.task)
+ mock_get.assert_called_once_with(self.node.uuid,
+ api_version=self.api_version,
+ auth_token='the token',
+ base_url='meow')
+ self.task.process_event.assert_called_once_with('done')
+
+ def test_is_standalone(self, mock_get):
+ self.config(auth_strategy='noauth')
+ mock_get.return_value = {'finished': True}
+ inspector._check_status(self.task)
+ mock_get.assert_called_once_with(
+ self.node.uuid,
+ api_version=self.api_version,
+ auth_token=self.task.context.auth_token)
+ self.task.process_event.assert_called_once_with('done')
+
+ def test_not_standalone(self, mock_get):
+ self.config(auth_strategy='keystone')
+ mock_get.return_value = {'finished': True}
+ inspector._check_status(self.task)
+ mock_get.assert_called_once_with(self.node.uuid,
+ api_version=self.api_version,
+ auth_token='the token')
+ self.task.process_event.assert_called_once_with('done')
+
+
+@mock.patch.object(eventlet.greenthread, 'spawn_n',
+ lambda f, *a, **kw: f(*a, **kw))
+@mock.patch.object(task_manager, 'acquire', autospec=True)
+@mock.patch.object(inspector, '_check_status', autospec=True)
+class PeriodicTaskTestCase(BaseTestCase):
+ def test_ok(self, mock_check, mock_acquire):
+ mgr = mock.MagicMock(spec=['iter_nodes'])
+ mgr.iter_nodes.return_value = [('1', 'd1'), ('2', 'd2')]
+ tasks = [mock.sentinel.task1, mock.sentinel.task2]
+ mock_acquire.side_effect = (
+ mock.MagicMock(__enter__=mock.MagicMock(return_value=task))
+ for task in tasks
+ )
+ inspector.Inspector()._periodic_check_result(
+ mgr, mock.sentinel.context)
+ mock_check.assert_any_call(tasks[0])
+ mock_check.assert_any_call(tasks[1])
+ self.assertEqual(2, mock_acquire.call_count)
+
+ def test_node_locked(self, mock_check, mock_acquire):
+ iter_nodes_ret = [('1', 'd1'), ('2', 'd2')]
+ mock_acquire.side_effect = iter([exception.NodeLocked("boom")] *
+ len(iter_nodes_ret))
+ mgr = mock.MagicMock(spec=['iter_nodes'])
+ mgr.iter_nodes.return_value = iter_nodes_ret
+ inspector.Inspector()._periodic_check_result(
+ mgr, mock.sentinel.context)
+ self.assertFalse(mock_check.called)
+ self.assertEqual(2, mock_acquire.call_count)
diff --git a/ironic/tests/unit/drivers/modules/test_ipminative.py b/ironic/tests/unit/drivers/modules/test_ipminative.py
new file mode 100644
index 000000000..8cc1ecf87
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/test_ipminative.py
@@ -0,0 +1,610 @@
+# coding=utf-8
+
+# Copyright 2013 International Business Machines Corporation
+# 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
+# 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.
+
+"""
+Test class for Native IPMI power driver module.
+"""
+
+import mock
+from oslo_utils import uuidutils
+from pyghmi import exceptions as pyghmi_exception
+
+from ironic.common import boot_devices
+from ironic.common import driver_factory
+from ironic.common import exception
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.drivers.modules import console_utils
+from ironic.drivers.modules import ipminative
+from ironic.drivers import utils as driver_utils
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+INFO_DICT = db_utils.get_test_ipmi_info()
+
+
+class IPMINativePrivateMethodTestCase(db_base.DbTestCase):
+ """Test cases for ipminative private methods."""
+
+ def setUp(self):
+ super(IPMINativePrivateMethodTestCase, self).setUp()
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_ipminative',
+ driver_info=INFO_DICT)
+ self.info = ipminative._parse_driver_info(self.node)
+
+ def test__parse_driver_info(self):
+ # make sure we get back the expected things
+ self.assertIsNotNone(self.info.get('address'))
+ self.assertIsNotNone(self.info.get('username'))
+ self.assertIsNotNone(self.info.get('password'))
+ self.assertIsNotNone(self.info.get('uuid'))
+ self.assertIsNotNone(self.info.get('force_boot_device'))
+
+ # make sure error is raised when info, eg. username, is missing
+ info = dict(INFO_DICT)
+ del info['ipmi_username']
+
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ self.assertRaises(exception.MissingParameterValue,
+ ipminative._parse_driver_info,
+ node)
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test__power_status_on(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.get_power.return_value = {'powerstate': 'on'}
+
+ state = ipminative._power_status(self.info)
+ ipmicmd.get_power.assert_called_once_with()
+ self.assertEqual(states.POWER_ON, state)
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test__power_status_off(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.get_power.return_value = {'powerstate': 'off'}
+
+ state = ipminative._power_status(self.info)
+ ipmicmd.get_power.assert_called_once_with()
+ self.assertEqual(states.POWER_OFF, state)
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test__power_status_error(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.get_power.return_value = {'powerstate': 'Error'}
+
+ state = ipminative._power_status(self.info)
+ ipmicmd.get_power.assert_called_once_with()
+ self.assertEqual(states.ERROR, state)
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test__power_on(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.set_power.return_value = {'powerstate': 'on'}
+
+ self.config(retry_timeout=400, group='ipmi')
+ state = ipminative._power_on(self.info)
+ ipmicmd.set_power.assert_called_once_with('on', 400)
+ self.assertEqual(states.POWER_ON, state)
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test__power_off(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.set_power.return_value = {'powerstate': 'off'}
+
+ self.config(retry_timeout=500, group='ipmi')
+ state = ipminative._power_off(self.info)
+ ipmicmd.set_power.assert_called_once_with('off', 500)
+ self.assertEqual(states.POWER_OFF, state)
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test__reboot(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.set_power.return_value = {'powerstate': 'on'}
+
+ self.config(retry_timeout=600, group='ipmi')
+ state = ipminative._reboot(self.info)
+ ipmicmd.set_power.assert_called_once_with('boot', 600)
+ self.assertEqual(states.POWER_ON, state)
+
+ def _create_sensor_object(self, value, type_, name, states=None,
+ units='fake_units', health=0):
+ if states is None:
+ states = []
+ return type('Reading', (object, ), {
+ 'value': value, 'type': type_, 'name': name,
+ 'states': states, 'units': units, 'health': health})()
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test__get_sensors_data(self, ipmi_mock):
+ reading_1 = self._create_sensor_object('fake_value1',
+ 'fake_type_A',
+ 'fake_name1')
+ reading_2 = self._create_sensor_object('fake_value2',
+ 'fake_type_A',
+ 'fake_name2')
+ reading_3 = self._create_sensor_object('fake_value3',
+ 'fake_type_B',
+ 'fake_name3')
+ readings = [reading_1, reading_2, reading_3]
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.get_sensor_data.return_value = readings
+ expected = {
+ 'fake_type_A': {
+ 'fake_name1': {
+ 'Health': '0',
+ 'Sensor ID': 'fake_name1',
+ 'Sensor Reading': 'fake_value1 fake_units',
+ 'States': '[]',
+ 'Units': 'fake_units'
+ },
+ 'fake_name2': {
+ 'Health': '0',
+ 'Sensor ID': 'fake_name2',
+ 'Sensor Reading': 'fake_value2 fake_units',
+ 'States': '[]',
+ 'Units': 'fake_units'
+ }
+ },
+ 'fake_type_B': {
+ 'fake_name3': {
+ 'Health': '0',
+ 'Sensor ID': 'fake_name3',
+ 'Sensor Reading': 'fake_value3 fake_units',
+ 'States': '[]', 'Units': 'fake_units'
+ }
+ }
+ }
+ ret = ipminative._get_sensors_data(self.info)
+ self.assertEqual(expected, ret)
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test__get_sensors_data_missing_values(self, ipmi_mock):
+ reading_1 = self._create_sensor_object('fake_value1',
+ 'fake_type_A',
+ 'fake_name1')
+ reading_2 = self._create_sensor_object(None,
+ 'fake_type_A',
+ 'fake_name2')
+ reading_3 = self._create_sensor_object(None,
+ 'fake_type_B',
+ 'fake_name3')
+ readings = [reading_1, reading_2, reading_3]
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.get_sensor_data.return_value = readings
+
+ expected = {
+ 'fake_type_A': {
+ 'fake_name1': {
+ 'Health': '0',
+ 'Sensor ID': 'fake_name1',
+ 'Sensor Reading': 'fake_value1 fake_units',
+ 'States': '[]',
+ 'Units': 'fake_units'
+ }
+ }
+ }
+ ret = ipminative._get_sensors_data(self.info)
+ self.assertEqual(expected, ret)
+
+ def test__parse_raw_bytes_ok(self):
+ bytes_string = '0x11 0x12 0x25 0xFF'
+ netfn, cmd, data = ipminative._parse_raw_bytes(bytes_string)
+ self.assertEqual(0x11, netfn)
+ self.assertEqual(0x12, cmd)
+ self.assertEqual([0x25, 0xFF], data)
+
+ def test__parse_raw_bytes_invalid_value(self):
+ bytes_string = '0x11 oops'
+ self.assertRaises(exception.InvalidParameterValue,
+ ipminative._parse_raw_bytes,
+ bytes_string)
+
+ def test__parse_raw_bytes_missing_byte(self):
+ bytes_string = '0x11'
+ self.assertRaises(exception.InvalidParameterValue,
+ ipminative._parse_raw_bytes,
+ bytes_string)
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test__send_raw(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipminative._send_raw(self.info, '0x01 0x02 0x03 0x04')
+ ipmicmd.xraw_command.assert_called_once_with(1, 2, data=[3, 4])
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test__send_raw_fail(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.xraw_command.side_effect = pyghmi_exception.IpmiException()
+ self.assertRaises(exception.IPMIFailure, ipminative._send_raw,
+ self.info, '0x01 0x02')
+
+
+class IPMINativeDriverTestCase(db_base.DbTestCase):
+ """Test cases for ipminative.NativeIPMIPower class functions."""
+
+ def setUp(self):
+ super(IPMINativeDriverTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="fake_ipminative")
+ self.driver = driver_factory.get_driver("fake_ipminative")
+
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_ipminative',
+ driver_info=INFO_DICT)
+ self.info = ipminative._parse_driver_info(self.node)
+
+ def test_get_properties(self):
+ expected = ipminative.COMMON_PROPERTIES
+ self.assertEqual(expected, self.driver.power.get_properties())
+ self.assertEqual(expected, self.driver.management.get_properties())
+ self.assertEqual(expected, self.driver.vendor.get_properties())
+
+ expected = list(ipminative.COMMON_PROPERTIES)
+ expected += list(ipminative.CONSOLE_PROPERTIES)
+ self.assertEqual(sorted(expected),
+ sorted(self.driver.console.get_properties().keys()))
+ self.assertEqual(sorted(expected),
+ sorted(self.driver.get_properties().keys()))
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test_get_power_state(self, ipmi_mock):
+ # Getting the mocked command.
+ cmd_mock = ipmi_mock.return_value
+ # Getting the get power mock.
+ get_power_mock = cmd_mock.get_power
+
+ return_values = [{'powerstate': 'error'},
+ {'powerstate': 'on'},
+ {'powerstate': 'off'}]
+
+ get_power_mock.side_effect = lambda: return_values.pop()
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ pstate = self.driver.power.get_power_state(task)
+ self.assertEqual(states.POWER_OFF, pstate)
+
+ pstate = self.driver.power.get_power_state(task)
+ self.assertEqual(states.POWER_ON, pstate)
+
+ pstate = self.driver.power.get_power_state(task)
+ self.assertEqual(states.ERROR, pstate)
+ self.assertEqual(3, get_power_mock.call_count,
+ "pyghmi.ipmi.command.Command.get_power was not"
+ " called 3 times.")
+
+ @mock.patch.object(ipminative, '_power_on', autospec=True)
+ def test_set_power_on_ok(self, power_on_mock):
+ power_on_mock.return_value = states.POWER_ON
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.driver.power.set_power_state(
+ task, states.POWER_ON)
+ power_on_mock.assert_called_once_with(self.info)
+
+ @mock.patch.object(driver_utils, 'ensure_next_boot_device', autospec=True)
+ @mock.patch.object(ipminative, '_power_on', autospec=True)
+ def test_set_power_on_with_next_boot(self, power_on_mock, mock_next_boot):
+ power_on_mock.return_value = states.POWER_ON
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.driver.power.set_power_state(
+ task, states.POWER_ON)
+ mock_next_boot.assert_called_once_with(task, self.info)
+ power_on_mock.assert_called_once_with(self.info)
+
+ @mock.patch.object(ipminative, '_power_off', autospec=True)
+ def test_set_power_off_ok(self, power_off_mock):
+ power_off_mock.return_value = states.POWER_OFF
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.driver.power.set_power_state(
+ task, states.POWER_OFF)
+ power_off_mock.assert_called_once_with(self.info)
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test_set_power_on_fail(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.set_power.return_value = {'powerstate': 'error'}
+
+ self.config(retry_timeout=500, group='ipmi')
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.assertRaises(exception.PowerStateFailure,
+ self.driver.power.set_power_state,
+ task,
+ states.POWER_ON)
+ ipmicmd.set_power.assert_called_once_with('on', 500)
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test_set_boot_device_ok(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.set_bootdev.return_value = None
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.driver.management.set_boot_device(task, boot_devices.PXE)
+ # PXE is converted to 'network' internally by ipminative
+ ipmicmd.set_bootdev.assert_called_once_with('network', persist=False)
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test_force_set_boot_device_ok(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.set_bootdev.return_value = None
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ task.node.driver_info['ipmi_force_boot_device'] = True
+ self.driver.management.set_boot_device(task, boot_devices.PXE)
+ task.node.refresh()
+ self.assertEqual(
+ False,
+ task.node.driver_internal_info['is_next_boot_persistent']
+ )
+ # PXE is converted to 'network' internally by ipminative
+ ipmicmd.set_bootdev.assert_called_once_with('network', persist=False)
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test_set_boot_device_with_persistent(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.set_bootdev.return_value = None
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ task.node.driver_info['ipmi_force_boot_device'] = True
+ self.driver.management.set_boot_device(task,
+ boot_devices.PXE,
+ True)
+ self.assertEqual(
+ boot_devices.PXE,
+ task.node.driver_internal_info['persistent_boot_device'])
+ # PXE is converted to 'network' internally by ipminative
+ ipmicmd.set_bootdev.assert_called_once_with('network', persist=False)
+
+ def test_set_boot_device_bad_device(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ self.driver.management.set_boot_device,
+ task,
+ 'fake-device')
+
+ @mock.patch.object(driver_utils, 'ensure_next_boot_device', autospec=True)
+ @mock.patch.object(ipminative, '_reboot', autospec=True)
+ def test_reboot_ok(self, reboot_mock, mock_next_boot):
+ reboot_mock.return_value = None
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.driver.power.reboot(task)
+ mock_next_boot.assert_called_once_with(task, self.info)
+ reboot_mock.assert_called_once_with(self.info)
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test_reboot_fail(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.set_power.return_value = {'powerstate': 'error'}
+
+ self.config(retry_timeout=500, group='ipmi')
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.assertRaises(exception.PowerStateFailure,
+ self.driver.power.reboot,
+ task)
+ ipmicmd.set_power.assert_called_once_with('boot', 500)
+
+ def test_management_interface_get_supported_boot_devices(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ expected = [boot_devices.PXE, boot_devices.DISK,
+ boot_devices.CDROM, boot_devices.BIOS]
+ self.assertEqual(sorted(expected), sorted(task.driver.management.
+ get_supported_boot_devices(task)))
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test_management_interface_get_boot_device_good(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.get_bootdev.return_value = {'bootdev': 'hd'}
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ bootdev = self.driver.management.get_boot_device(task)
+ self.assertEqual(boot_devices.DISK, bootdev['boot_device'])
+ self.assertIsNone(bootdev['persistent'])
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test_management_interface_get_boot_device_persistent(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.get_bootdev.return_value = {'bootdev': 'hd',
+ 'persistent': True}
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ bootdev = self.driver.management.get_boot_device(task)
+ self.assertEqual(boot_devices.DISK, bootdev['boot_device'])
+ self.assertTrue(bootdev['persistent'])
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test_management_interface_get_boot_device_fail(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.get_bootdev.side_effect = pyghmi_exception.IpmiException
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.IPMIFailure,
+ self.driver.management.get_boot_device, task)
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test_management_interface_get_boot_device_fail_dict(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.get_bootdev.return_value = {'error': 'boooom'}
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.IPMIFailure,
+ self.driver.management.get_boot_device, task)
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test_management_interface_get_boot_device_unknown(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.get_bootdev.return_value = {'bootdev': 'unknown'}
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ expected = {'boot_device': None, 'persistent': None}
+ self.assertEqual(expected,
+ self.driver.management.get_boot_device(task))
+
+ def test_get_force_boot_device_persistent(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node.driver_info['ipmi_force_boot_device'] = True
+ task.node.driver_internal_info['persistent_boot_device'] = 'pxe'
+ bootdev = self.driver.management.get_boot_device(task)
+ self.assertEqual('pxe', bootdev['boot_device'])
+ self.assertTrue(bootdev['persistent'])
+
+ def test_management_interface_validate_good(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.driver.management.validate(task)
+
+ def test_management_interface_validate_fail(self):
+ # Missing IPMI driver_info information
+ node = obj_utils.create_test_node(self.context,
+ uuid=uuidutils.generate_uuid(),
+ driver='fake_ipminative')
+ with task_manager.acquire(self.context, node.uuid) as task:
+ self.assertRaises(exception.MissingParameterValue,
+ task.driver.management.validate, task)
+
+ @mock.patch('pyghmi.ipmi.command.Command', autospec=True)
+ def test_get_sensors_data(self, ipmi_mock):
+ ipmicmd = ipmi_mock.return_value
+ ipmicmd.get_sensor_data.return_value = None
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.driver.management.get_sensors_data(task)
+ ipmicmd.get_sensor_data.assert_called_once_with()
+
+ @mock.patch.object(console_utils, 'start_shellinabox_console',
+ autospec=True)
+ def test_start_console(self, mock_exec):
+ mock_exec.return_value = None
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.driver.console.start_console(task)
+
+ mock_exec.assert_called_once_with(self.info['uuid'],
+ self.info['port'],
+ mock.ANY)
+ self.assertTrue(mock_exec.called)
+
+ @mock.patch.object(console_utils, 'start_shellinabox_console',
+ autospec=True)
+ def test_start_console_fail(self, mock_exec):
+ mock_exec.side_effect = iter(
+ [exception.ConsoleSubprocessFailed(error='error')])
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.assertRaises(exception.ConsoleSubprocessFailed,
+ self.driver.console.start_console,
+ task)
+
+ @mock.patch.object(console_utils, 'stop_shellinabox_console',
+ autospec=True)
+ def test_stop_console(self, mock_exec):
+ mock_exec.return_value = None
+
+ with task_manager.acquire(self.context,
+ self.node['uuid']) as task:
+ self.driver.console.stop_console(task)
+
+ mock_exec.assert_called_once_with(self.info['uuid'])
+ self.assertTrue(mock_exec.called)
+
+ @mock.patch.object(console_utils, 'stop_shellinabox_console',
+ autospec=True)
+ def test_stop_console_fail(self, mock_stop):
+ mock_stop.side_effect = iter([exception.ConsoleError()])
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.assertRaises(exception.ConsoleError,
+ self.driver.console.stop_console,
+ task)
+
+ mock_stop.assert_called_once_with(self.node.uuid)
+
+ @mock.patch.object(console_utils, 'get_shellinabox_console_url',
+ autospec=True)
+ def test_get_console(self, mock_exec):
+ url = 'http://localhost:4201'
+ mock_exec.return_value = url
+ expected = {'type': 'shellinabox', 'url': url}
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ console_info = self.driver.console.get_console(task)
+
+ self.assertEqual(expected, console_info)
+ mock_exec.assert_called_once_with(self.info['port'])
+ self.assertTrue(mock_exec.called)
+
+ @mock.patch.object(ipminative, '_parse_driver_info', autospec=True)
+ @mock.patch.object(ipminative, '_parse_raw_bytes', autospec=True)
+ def test_vendor_passthru_validate__send_raw_bytes_good(self, mock_raw,
+ mock_driver):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.driver.vendor.validate(task,
+ method='send_raw',
+ http_method='POST',
+ raw_bytes='0x00 0x01')
+ mock_raw.assert_called_once_with('0x00 0x01')
+ mock_driver.assert_called_once_with(task.node)
+
+ def test_vendor_passthru_validate__send_raw_bytes_fail(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.MissingParameterValue,
+ self.driver.vendor.validate,
+ task, method='send_raw')
+
+ def test_vendor_passthru_vendor_routes(self):
+ expected = ['send_raw', 'bmc_reset']
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ vendor_routes = task.driver.vendor.vendor_routes
+ self.assertIsInstance(vendor_routes, dict)
+ self.assertEqual(sorted(expected), sorted(vendor_routes))
+
+ @mock.patch.object(ipminative, '_send_raw', autospec=True)
+ def test_send_raw(self, send_raw_mock):
+ bytes = '0x00 0x01'
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.driver.vendor.send_raw(task, http_method='POST',
+ raw_bytes=bytes)
+
+ send_raw_mock.assert_called_once_with(self.info, bytes)
+
+ @mock.patch.object(ipminative, '_send_raw', autospec=True)
+ def _test_bmc_reset(self, warm, send_raw_mock):
+ expected_bytes = '0x06 0x03' if warm else '0x06 0x02'
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.driver.vendor.bmc_reset(task, http_method='POST', warm=warm)
+
+ send_raw_mock.assert_called_once_with(self.info, expected_bytes)
+
+ def test_bmc_reset_cold(self):
+ self._test_bmc_reset(False)
+
+ def test_bmc_reset_warm(self):
+ self._test_bmc_reset(True)
diff --git a/ironic/tests/unit/drivers/modules/test_ipmitool.py b/ironic/tests/unit/drivers/modules/test_ipmitool.py
new file mode 100644
index 000000000..6a80b7ea9
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/test_ipmitool.py
@@ -0,0 +1,1899 @@
+# coding=utf-8
+
+# Copyright 2012 Hewlett-Packard Development Company, L.P.
+# Copyright (c) 2012 NTT DOCOMO, INC.
+# Copyright 2014 International Business Machines Corporation
+# 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
+# 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.
+#
+
+"""Test class for IPMITool driver module."""
+
+import os
+import stat
+import subprocess
+import tempfile
+import time
+import types
+
+import mock
+from oslo_concurrency import processutils
+from oslo_config import cfg
+from oslo_utils import uuidutils
+import six
+
+from ironic.common import boot_devices
+from ironic.common import driver_factory
+from ironic.common import exception
+from ironic.common import states
+from ironic.common import utils
+from ironic.conductor import task_manager
+from ironic.drivers.modules import console_utils
+from ironic.drivers.modules import ipmitool as ipmi
+from ironic.drivers import utils as driver_utils
+from ironic.tests.unit import base
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+CONF = cfg.CONF
+
+CONF.import_opt('min_command_interval',
+ 'ironic.drivers.modules.ipminative',
+ group='ipmi')
+
+INFO_DICT = db_utils.get_test_ipmi_info()
+
+# BRIDGE_INFO_DICT will have all the bridging parameters appended
+BRIDGE_INFO_DICT = INFO_DICT.copy()
+BRIDGE_INFO_DICT.update(db_utils.get_test_ipmi_bridging_parameters())
+
+
+class IPMIToolCheckInitTestCase(base.TestCase):
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(utils, 'check_dir', autospec=True)
+ def test_power_init_calls(self, mock_check_dir, mock_support):
+ mock_support.return_value = True
+ ipmi.TMP_DIR_CHECKED = None
+ ipmi.IPMIPower()
+ mock_support.assert_called_with(mock.ANY)
+ mock_check_dir.assert_called_once_with()
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(utils, 'check_dir', autospec=True)
+ def test_power_init_calls_raises_1(self, mock_check_dir, mock_support):
+ mock_support.return_value = True
+ ipmi.TMP_DIR_CHECKED = None
+ mock_check_dir.side_effect = iter(
+ [exception.PathNotFound(dir="foo_dir")])
+ self.assertRaises(exception.PathNotFound, ipmi.IPMIPower)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(utils, 'check_dir', autospec=True)
+ def test_power_init_calls_raises_2(self, mock_check_dir, mock_support):
+ mock_support.return_value = True
+ ipmi.TMP_DIR_CHECKED = None
+ mock_check_dir.side_effect = iter(
+ [exception.DirectoryNotWritable(dir="foo_dir")])
+ self.assertRaises(exception.DirectoryNotWritable, ipmi.IPMIPower)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(utils, 'check_dir', autospec=True)
+ def test_power_init_calls_raises_3(self, mock_check_dir, mock_support):
+ mock_support.return_value = True
+ ipmi.TMP_DIR_CHECKED = None
+ mock_check_dir.side_effect = iter([exception.InsufficientDiskSpace(
+ path="foo_dir", required=1, actual=0)])
+ self.assertRaises(exception.InsufficientDiskSpace, ipmi.IPMIPower)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(utils, 'check_dir', autospec=True)
+ def test_power_init_calls_already_checked(self,
+ mock_check_dir,
+ mock_support):
+ mock_support.return_value = True
+ ipmi.TMP_DIR_CHECKED = True
+ ipmi.IPMIPower()
+ mock_support.assert_called_with(mock.ANY)
+ self.assertEqual(0, mock_check_dir.call_count)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(utils, 'check_dir', autospec=True)
+ def test_management_init_calls(self, mock_check_dir, mock_support):
+ mock_support.return_value = True
+ ipmi.TMP_DIR_CHECKED = None
+
+ ipmi.IPMIManagement()
+ mock_support.assert_called_with(mock.ANY)
+ mock_check_dir.assert_called_once_with()
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(utils, 'check_dir', autospec=True)
+ def test_management_init_calls_already_checked(self,
+ mock_check_dir,
+ mock_support):
+ mock_support.return_value = True
+ ipmi.TMP_DIR_CHECKED = False
+
+ ipmi.IPMIManagement()
+ mock_support.assert_called_with(mock.ANY)
+ self.assertEqual(0, mock_check_dir.call_count)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(utils, 'check_dir', autospec=True)
+ def test_vendor_passthru_init_calls(self, mock_check_dir, mock_support):
+ mock_support.return_value = True
+ ipmi.TMP_DIR_CHECKED = None
+ ipmi.VendorPassthru()
+ mock_support.assert_called_with(mock.ANY)
+ mock_check_dir.assert_called_once_with()
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(utils, 'check_dir', autospec=True)
+ def test_vendor_passthru_init_calls_already_checked(self,
+ mock_check_dir,
+ mock_support):
+ mock_support.return_value = True
+ ipmi.TMP_DIR_CHECKED = True
+ ipmi.VendorPassthru()
+ mock_support.assert_called_with(mock.ANY)
+ self.assertEqual(0, mock_check_dir.call_count)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(utils, 'check_dir', autospec=True)
+ def test_console_init_calls(self, mock_check_dir, mock_support):
+ mock_support.return_value = True
+ ipmi.TMP_DIR_CHECKED = None
+ ipmi.IPMIShellinaboxConsole()
+ mock_support.assert_called_with(mock.ANY)
+ mock_check_dir.assert_called_once_with()
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(utils, 'check_dir', autospec=True)
+ def test_console_init_calls_already_checked(self,
+ mock_check_dir,
+ mock_support):
+ mock_support.return_value = True
+ ipmi.TMP_DIR_CHECKED = True
+ ipmi.IPMIShellinaboxConsole()
+ mock_support.assert_called_with(mock.ANY)
+ self.assertEqual(0, mock_check_dir.call_count)
+
+
+@mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+@mock.patch.object(subprocess, 'check_call', autospec=True)
+class IPMIToolCheckOptionSupportedTestCase(base.TestCase):
+
+ def test_check_timing_pass(self, mock_chkcall, mock_support):
+ mock_chkcall.return_value = (None, None)
+ mock_support.return_value = None
+ expected = [mock.call('timing'),
+ mock.call('timing', True)]
+
+ ipmi._check_option_support(['timing'])
+ self.assertTrue(mock_chkcall.called)
+ self.assertEqual(expected, mock_support.call_args_list)
+
+ def test_check_timing_fail(self, mock_chkcall, mock_support):
+ mock_chkcall.side_effect = iter(
+ [subprocess.CalledProcessError(1, 'ipmitool')])
+ mock_support.return_value = None
+ expected = [mock.call('timing'),
+ mock.call('timing', False)]
+
+ ipmi._check_option_support(['timing'])
+ self.assertTrue(mock_chkcall.called)
+ self.assertEqual(expected, mock_support.call_args_list)
+
+ def test_check_timing_no_ipmitool(self, mock_chkcall, mock_support):
+ mock_chkcall.side_effect = iter([OSError()])
+ mock_support.return_value = None
+ expected = [mock.call('timing')]
+
+ self.assertRaises(OSError, ipmi._check_option_support, ['timing'])
+ self.assertTrue(mock_chkcall.called)
+ self.assertEqual(expected, mock_support.call_args_list)
+
+ def test_check_single_bridge_pass(self, mock_chkcall, mock_support):
+ mock_chkcall.return_value = (None, None)
+ mock_support.return_value = None
+ expected = [mock.call('single_bridge'),
+ mock.call('single_bridge', True)]
+
+ ipmi._check_option_support(['single_bridge'])
+ self.assertTrue(mock_chkcall.called)
+ self.assertEqual(expected, mock_support.call_args_list)
+
+ def test_check_single_bridge_fail(self, mock_chkcall, mock_support):
+ mock_chkcall.side_effect = iter(
+ [subprocess.CalledProcessError(1, 'ipmitool')])
+ mock_support.return_value = None
+ expected = [mock.call('single_bridge'),
+ mock.call('single_bridge', False)]
+
+ ipmi._check_option_support(['single_bridge'])
+ self.assertTrue(mock_chkcall.called)
+ self.assertEqual(expected, mock_support.call_args_list)
+
+ def test_check_single_bridge_no_ipmitool(self, mock_chkcall,
+ mock_support):
+ mock_chkcall.side_effect = iter([OSError()])
+ mock_support.return_value = None
+ expected = [mock.call('single_bridge')]
+
+ self.assertRaises(OSError, ipmi._check_option_support,
+ ['single_bridge'])
+ self.assertTrue(mock_chkcall.called)
+ self.assertEqual(expected, mock_support.call_args_list)
+
+ def test_check_dual_bridge_pass(self, mock_chkcall, mock_support):
+ mock_chkcall.return_value = (None, None)
+ mock_support.return_value = None
+ expected = [mock.call('dual_bridge'),
+ mock.call('dual_bridge', True)]
+
+ ipmi._check_option_support(['dual_bridge'])
+ self.assertTrue(mock_chkcall.called)
+ self.assertEqual(expected, mock_support.call_args_list)
+
+ def test_check_dual_bridge_fail(self, mock_chkcall, mock_support):
+ mock_chkcall.side_effect = iter(
+ [subprocess.CalledProcessError(1, 'ipmitool')])
+ mock_support.return_value = None
+ expected = [mock.call('dual_bridge'),
+ mock.call('dual_bridge', False)]
+
+ ipmi._check_option_support(['dual_bridge'])
+ self.assertTrue(mock_chkcall.called)
+ self.assertEqual(expected, mock_support.call_args_list)
+
+ def test_check_dual_bridge_no_ipmitool(self, mock_chkcall, mock_support):
+ mock_chkcall.side_effect = iter([OSError()])
+ mock_support.return_value = None
+ expected = [mock.call('dual_bridge')]
+
+ self.assertRaises(OSError, ipmi._check_option_support,
+ ['dual_bridge'])
+ self.assertTrue(mock_chkcall.called)
+ self.assertEqual(expected, mock_support.call_args_list)
+
+ def test_check_all_options_pass(self, mock_chkcall, mock_support):
+ mock_chkcall.return_value = (None, None)
+ mock_support.return_value = None
+ expected = [
+ mock.call('timing'), mock.call('timing', True),
+ mock.call('single_bridge'),
+ mock.call('single_bridge', True),
+ mock.call('dual_bridge'), mock.call('dual_bridge', True)]
+
+ ipmi._check_option_support(['timing', 'single_bridge', 'dual_bridge'])
+ self.assertTrue(mock_chkcall.called)
+ self.assertEqual(expected, mock_support.call_args_list)
+
+ def test_check_all_options_fail(self, mock_chkcall, mock_support):
+ options = ['timing', 'single_bridge', 'dual_bridge']
+ mock_chkcall.side_effect = iter(
+ [subprocess.CalledProcessError(1, 'ipmitool')] * len(options))
+ mock_support.return_value = None
+ expected = [
+ mock.call('timing'), mock.call('timing', False),
+ mock.call('single_bridge'),
+ mock.call('single_bridge', False),
+ mock.call('dual_bridge'),
+ mock.call('dual_bridge', False)]
+
+ ipmi._check_option_support(options)
+ self.assertTrue(mock_chkcall.called)
+ self.assertEqual(expected, mock_support.call_args_list)
+
+ def test_check_all_options_no_ipmitool(self, mock_chkcall, mock_support):
+ mock_chkcall.side_effect = iter([OSError()])
+ mock_support.return_value = None
+ # exception is raised once ipmitool was not found for an command
+ expected = [mock.call('timing')]
+
+ self.assertRaises(OSError, ipmi._check_option_support,
+ ['timing', 'single_bridge', 'dual_bridge'])
+ self.assertTrue(mock_chkcall.called)
+ self.assertEqual(expected, mock_support.call_args_list)
+
+
+@mock.patch.object(time, 'sleep', autospec=True)
+class IPMIToolPrivateMethodTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IPMIToolPrivateMethodTestCase, self).setUp()
+ self.node = obj_utils.get_test_node(
+ self.context,
+ driver='fake_ipmitool',
+ driver_info=INFO_DICT)
+ self.info = ipmi._parse_driver_info(self.node)
+
+ def _test__make_password_file(self, mock_sleep, input_password,
+ exception_to_raise=None):
+ pw_file = None
+ try:
+ with ipmi._make_password_file(input_password) as pw_file:
+ if exception_to_raise is not None:
+ raise exception_to_raise
+ self.assertTrue(os.path.isfile(pw_file))
+ self.assertEqual(0o600, os.stat(pw_file)[stat.ST_MODE] & 0o777)
+ with open(pw_file, "r") as f:
+ password = f.read()
+ self.assertEqual(str(input_password), password)
+ finally:
+ if pw_file is not None:
+ self.assertFalse(os.path.isfile(pw_file))
+
+ def test__make_password_file_str_password(self, mock_sleep):
+ self._test__make_password_file(mock_sleep, self.info.get('password'))
+
+ def test__make_password_file_with_numeric_password(self, mock_sleep):
+ self._test__make_password_file(mock_sleep, 12345)
+
+ def test__make_password_file_caller_exception(self, mock_sleep):
+ # Test caller raising exception
+ result = self.assertRaises(
+ ValueError,
+ self._test__make_password_file,
+ mock_sleep, 12345, ValueError('we should fail'))
+ self.assertEqual('we should fail', six.text_type(result))
+
+ @mock.patch.object(tempfile, 'NamedTemporaryFile',
+ new=mock.MagicMock(side_effect=OSError('Test Error')))
+ def test__make_password_file_tempfile_known_exception(self, mock_sleep):
+ # Test OSError exception in _make_password_file for
+ # tempfile.NamedTemporaryFile
+ self.assertRaises(
+ exception.PasswordFileFailedToCreate,
+ self._test__make_password_file, mock_sleep, 12345)
+
+ @mock.patch.object(
+ tempfile, 'NamedTemporaryFile',
+ new=mock.MagicMock(side_effect=OverflowError('Test Error')))
+ def test__make_password_file_tempfile_unknown_exception(self, mock_sleep):
+ # Test exception in _make_password_file for tempfile.NamedTemporaryFile
+ result = self.assertRaises(
+ OverflowError,
+ self._test__make_password_file, mock_sleep, 12345)
+ self.assertEqual('Test Error', six.text_type(result))
+
+ def test__make_password_file_write_exception(self, mock_sleep):
+ # Test exception in _make_password_file for write()
+ mock_namedtemp = mock.mock_open(mock.MagicMock(name='JLV'))
+ with mock.patch('tempfile.NamedTemporaryFile', mock_namedtemp):
+ mock_filehandle = mock_namedtemp.return_value
+ mock_write = mock_filehandle.write
+ mock_write.side_effect = OSError('Test 2 Error')
+ self.assertRaises(
+ exception.PasswordFileFailedToCreate,
+ self._test__make_password_file, mock_sleep, 12345)
+
+ def test__parse_driver_info(self, mock_sleep):
+ # make sure we get back the expected things
+ _OPTIONS = ['address', 'username', 'password', 'uuid']
+ for option in _OPTIONS:
+ self.assertIsNotNone(self.info.get(option))
+
+ info = dict(INFO_DICT)
+
+ # test the default value for 'priv_level'
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ ret = ipmi._parse_driver_info(node)
+ self.assertEqual('ADMINISTRATOR', ret['priv_level'])
+
+ # ipmi_username / ipmi_password are not mandatory
+ del info['ipmi_username']
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ ipmi._parse_driver_info(node)
+ del info['ipmi_password']
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ ipmi._parse_driver_info(node)
+
+ # make sure error is raised when ipmi_address is missing
+ info = dict(INFO_DICT)
+ del info['ipmi_address']
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ self.assertRaises(exception.MissingParameterValue,
+ ipmi._parse_driver_info,
+ node)
+
+ # test the invalid priv_level value
+ info = dict(INFO_DICT)
+ info['ipmi_priv_level'] = 'ABCD'
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ self.assertRaises(exception.InvalidParameterValue,
+ ipmi._parse_driver_info,
+ node)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ def test__parse_driver_info_with_invalid_bridging_type(
+ self, mock_support, mock_sleep):
+ info = BRIDGE_INFO_DICT.copy()
+ # make sure error is raised when ipmi_bridging has unexpected value
+ info['ipmi_bridging'] = 'junk'
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ self.assertRaises(exception.InvalidParameterValue,
+ ipmi._parse_driver_info,
+ node)
+ self.assertFalse(mock_support.called)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ def test__parse_driver_info_with_no_bridging(
+ self, mock_support, mock_sleep):
+ _OPTIONS = ['address', 'username', 'password', 'uuid']
+ _BRIDGING_OPTIONS = ['local_address', 'transit_channel',
+ 'transit_address',
+ 'target_channel', 'target_address']
+ info = BRIDGE_INFO_DICT.copy()
+ info['ipmi_bridging'] = 'no'
+ node = obj_utils.get_test_node(self.context, driver='fake_ipmitool',
+ driver_info=info)
+ ret = ipmi._parse_driver_info(node)
+
+ # ensure that _is_option_supported was not called
+ self.assertFalse(mock_support.called)
+ # check if we got all the required options
+ for option in _OPTIONS:
+ self.assertIsNotNone(ret[option])
+ # test the default value for 'priv_level'
+ self.assertEqual('ADMINISTRATOR', ret['priv_level'])
+
+ # check if bridging parameters were set to None
+ for option in _BRIDGING_OPTIONS:
+ self.assertIsNone(ret[option])
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ def test__parse_driver_info_with_dual_bridging_pass(
+ self, mock_support, mock_sleep):
+ _OPTIONS = ['address', 'username', 'password', 'uuid',
+ 'local_address', 'transit_channel', 'transit_address',
+ 'target_channel', 'target_address']
+ node = obj_utils.get_test_node(self.context, driver='fake_ipmitool',
+ driver_info=BRIDGE_INFO_DICT)
+
+ expected = [mock.call('dual_bridge')]
+
+ # test double bridging and make sure we get back expected result
+ mock_support.return_value = True
+ ret = ipmi._parse_driver_info(node)
+ self.assertEqual(expected, mock_support.call_args_list)
+ for option in _OPTIONS:
+ self.assertIsNotNone(ret[option])
+ # test the default value for 'priv_level'
+ self.assertEqual('ADMINISTRATOR', ret['priv_level'])
+
+ info = BRIDGE_INFO_DICT.copy()
+ # ipmi_local_address / ipmi_username / ipmi_password are not mandatory
+ for optional_arg in ['ipmi_local_address', 'ipmi_username',
+ 'ipmi_password']:
+ del info[optional_arg]
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ ipmi._parse_driver_info(node)
+ self.assertEqual(mock.call('dual_bridge'), mock_support.call_args)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ def test__parse_driver_info_with_dual_bridging_not_supported(
+ self, mock_support, mock_sleep):
+ node = obj_utils.get_test_node(self.context, driver='fake_ipmitool',
+ driver_info=BRIDGE_INFO_DICT)
+ # if dual bridge is not supported then check if error is raised
+ mock_support.return_value = False
+ self.assertRaises(exception.InvalidParameterValue,
+ ipmi._parse_driver_info, node)
+ mock_support.assert_called_once_with('dual_bridge')
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ def test__parse_driver_info_with_dual_bridging_missing_parameters(
+ self, mock_support, mock_sleep):
+ info = BRIDGE_INFO_DICT.copy()
+ mock_support.return_value = True
+ # make sure error is raised when dual bridging is selected and the
+ # required parameters for dual bridging are not provided
+ for param in ['ipmi_transit_channel', 'ipmi_target_address',
+ 'ipmi_transit_address', 'ipmi_target_channel']:
+ del info[param]
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ self.assertRaises(exception.MissingParameterValue,
+ ipmi._parse_driver_info, node)
+ self.assertEqual(mock.call('dual_bridge'),
+ mock_support.call_args)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ def test__parse_driver_info_with_single_bridging_pass(
+ self, mock_support, mock_sleep):
+ _OPTIONS = ['address', 'username', 'password', 'uuid',
+ 'local_address', 'target_channel', 'target_address']
+
+ info = BRIDGE_INFO_DICT.copy()
+ info['ipmi_bridging'] = 'single'
+ node = obj_utils.get_test_node(self.context, driver='fake_ipmitool',
+ driver_info=info)
+
+ expected = [mock.call('single_bridge')]
+
+ # test single bridging and make sure we get back expected things
+ mock_support.return_value = True
+ ret = ipmi._parse_driver_info(node)
+ self.assertEqual(expected, mock_support.call_args_list)
+ for option in _OPTIONS:
+ self.assertIsNotNone(ret[option])
+ # test the default value for 'priv_level'
+ self.assertEqual('ADMINISTRATOR', ret['priv_level'])
+
+ # check if dual bridge params are set to None
+ self.assertIsNone(ret['transit_channel'])
+ self.assertIsNone(ret['transit_address'])
+
+ # ipmi_local_address / ipmi_username / ipmi_password are not mandatory
+ for optional_arg in ['ipmi_local_address', 'ipmi_username',
+ 'ipmi_password']:
+ del info[optional_arg]
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ ipmi._parse_driver_info(node)
+ self.assertEqual(mock.call('single_bridge'),
+ mock_support.call_args)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ def test__parse_driver_info_with_single_bridging_not_supported(
+ self, mock_support, mock_sleep):
+ info = BRIDGE_INFO_DICT.copy()
+ info['ipmi_bridging'] = 'single'
+ node = obj_utils.get_test_node(self.context, driver='fake_ipmitool',
+ driver_info=info)
+
+ # if single bridge is not supported then check if error is raised
+ mock_support.return_value = False
+ self.assertRaises(exception.InvalidParameterValue,
+ ipmi._parse_driver_info, node)
+ mock_support.assert_called_once_with('single_bridge')
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ def test__parse_driver_info_with_single_bridging_missing_parameters(
+ self, mock_support, mock_sleep):
+ info = dict(BRIDGE_INFO_DICT)
+ info['ipmi_bridging'] = 'single'
+ mock_support.return_value = True
+ # make sure error is raised when single bridging is selected and the
+ # required parameters for single bridging are not provided
+ for param in ['ipmi_target_channel', 'ipmi_target_address']:
+ del info[param]
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ self.assertRaises(exception.MissingParameterValue,
+ ipmi._parse_driver_info,
+ node)
+ self.assertEqual(mock.call('single_bridge'),
+ mock_support.call_args)
+
+ def test__parse_driver_info_ipmi_prot_version_1_5(self, mock_sleep):
+ info = dict(INFO_DICT)
+ info['ipmi_protocol_version'] = '1.5'
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ ret = ipmi._parse_driver_info(node)
+ self.assertEqual('1.5', ret['protocol_version'])
+
+ def test__parse_driver_info_invalid_ipmi_prot_version(self, mock_sleep):
+ info = dict(INFO_DICT)
+ info['ipmi_protocol_version'] = '9000'
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ self.assertRaises(exception.InvalidParameterValue,
+ ipmi._parse_driver_info, node)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(ipmi, '_make_password_file', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test__exec_ipmitool_first_call_to_address(self, mock_exec, mock_pwf,
+ mock_support, mock_sleep):
+ ipmi.LAST_CMD_TIME = {}
+ pw_file_handle = tempfile.NamedTemporaryFile()
+ pw_file = pw_file_handle.name
+ file_handle = open(pw_file, "w")
+ args = [
+ 'ipmitool',
+ '-I', 'lanplus',
+ '-H', self.info['address'],
+ '-L', self.info['priv_level'],
+ '-U', self.info['username'],
+ '-f', file_handle,
+ 'A', 'B', 'C',
+ ]
+
+ mock_support.return_value = False
+ mock_pwf.return_value = file_handle
+ mock_exec.return_value = (None, None)
+
+ ipmi._exec_ipmitool(self.info, 'A B C')
+
+ mock_support.assert_called_once_with('timing')
+ mock_pwf.assert_called_once_with(self.info['password'])
+ mock_exec.assert_called_once_with(*args)
+ self.assertFalse(mock_sleep.called)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(ipmi, '_make_password_file', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test__exec_ipmitool_second_call_to_address_sleep(
+ self, mock_exec, mock_pwf, mock_support, mock_sleep):
+ ipmi.LAST_CMD_TIME = {}
+ pw_file_handle1 = tempfile.NamedTemporaryFile()
+ pw_file1 = pw_file_handle1.name
+ file_handle1 = open(pw_file1, "w")
+ pw_file_handle2 = tempfile.NamedTemporaryFile()
+ pw_file2 = pw_file_handle2.name
+ file_handle2 = open(pw_file2, "w")
+ args = [[
+ 'ipmitool',
+ '-I', 'lanplus',
+ '-H', self.info['address'],
+ '-L', self.info['priv_level'],
+ '-U', self.info['username'],
+ '-f', file_handle1,
+ 'A', 'B', 'C',
+ ], [
+ 'ipmitool',
+ '-I', 'lanplus',
+ '-H', self.info['address'],
+ '-L', self.info['priv_level'],
+ '-U', self.info['username'],
+ '-f', file_handle2,
+ 'D', 'E', 'F',
+ ]]
+
+ expected = [mock.call('timing'),
+ mock.call('timing')]
+ mock_support.return_value = False
+ mock_pwf.side_effect = iter([file_handle1, file_handle2])
+ mock_exec.side_effect = iter([(None, None), (None, None)])
+
+ ipmi._exec_ipmitool(self.info, 'A B C')
+ mock_exec.assert_called_with(*args[0])
+
+ ipmi._exec_ipmitool(self.info, 'D E F')
+ self.assertTrue(mock_sleep.called)
+ self.assertEqual(expected, mock_support.call_args_list)
+ mock_exec.assert_called_with(*args[1])
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(ipmi, '_make_password_file', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test__exec_ipmitool_second_call_to_address_no_sleep(
+ self, mock_exec, mock_pwf, mock_support, mock_sleep):
+ ipmi.LAST_CMD_TIME = {}
+ pw_file_handle1 = tempfile.NamedTemporaryFile()
+ pw_file1 = pw_file_handle1.name
+ file_handle1 = open(pw_file1, "w")
+ pw_file_handle2 = tempfile.NamedTemporaryFile()
+ pw_file2 = pw_file_handle2.name
+ file_handle2 = open(pw_file2, "w")
+ args = [[
+ 'ipmitool',
+ '-I', 'lanplus',
+ '-H', self.info['address'],
+ '-L', self.info['priv_level'],
+ '-U', self.info['username'],
+ '-f', file_handle1,
+ 'A', 'B', 'C',
+ ], [
+ 'ipmitool',
+ '-I', 'lanplus',
+ '-H', self.info['address'],
+ '-L', self.info['priv_level'],
+ '-U', self.info['username'],
+ '-f', file_handle2,
+ 'D', 'E', 'F',
+ ]]
+
+ expected = [mock.call('timing'),
+ mock.call('timing')]
+ mock_support.return_value = False
+ mock_pwf.side_effect = iter([file_handle1, file_handle2])
+ mock_exec.side_effect = iter([(None, None), (None, None)])
+
+ ipmi._exec_ipmitool(self.info, 'A B C')
+ mock_exec.assert_called_with(*args[0])
+ # act like enough time has passed
+ ipmi.LAST_CMD_TIME[self.info['address']] = (
+ time.time() - CONF.ipmi.min_command_interval)
+ ipmi._exec_ipmitool(self.info, 'D E F')
+ self.assertFalse(mock_sleep.called)
+ self.assertEqual(expected, mock_support.call_args_list)
+ mock_exec.assert_called_with(*args[1])
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(ipmi, '_make_password_file', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test__exec_ipmitool_two_calls_to_diff_address(
+ self, mock_exec, mock_pwf, mock_support, mock_sleep):
+ ipmi.LAST_CMD_TIME = {}
+ pw_file_handle1 = tempfile.NamedTemporaryFile()
+ pw_file1 = pw_file_handle1.name
+ file_handle1 = open(pw_file1, "w")
+ pw_file_handle2 = tempfile.NamedTemporaryFile()
+ pw_file2 = pw_file_handle2.name
+ file_handle2 = open(pw_file2, "w")
+ args = [[
+ 'ipmitool',
+ '-I', 'lanplus',
+ '-H', self.info['address'],
+ '-L', self.info['priv_level'],
+ '-U', self.info['username'],
+ '-f', file_handle1,
+ 'A', 'B', 'C',
+ ], [
+ 'ipmitool',
+ '-I', 'lanplus',
+ '-H', '127.127.127.127',
+ '-L', self.info['priv_level'],
+ '-U', self.info['username'],
+ '-f', file_handle2,
+ 'D', 'E', 'F',
+ ]]
+
+ expected = [mock.call('timing'),
+ mock.call('timing')]
+ mock_support.return_value = False
+ mock_pwf.side_effect = iter([file_handle1, file_handle2])
+ mock_exec.side_effect = iter([(None, None), (None, None)])
+
+ ipmi._exec_ipmitool(self.info, 'A B C')
+ mock_exec.assert_called_with(*args[0])
+ self.info['address'] = '127.127.127.127'
+ ipmi._exec_ipmitool(self.info, 'D E F')
+ self.assertFalse(mock_sleep.called)
+ self.assertEqual(expected, mock_support.call_args_list)
+ mock_exec.assert_called_with(*args[1])
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(ipmi, '_make_password_file', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test__exec_ipmitool_without_timing(
+ self, mock_exec, mock_pwf, mock_support, mock_sleep):
+ pw_file_handle = tempfile.NamedTemporaryFile()
+ pw_file = pw_file_handle.name
+ file_handle = open(pw_file, "w")
+
+ args = [
+ 'ipmitool',
+ '-I', 'lanplus',
+ '-H', self.info['address'],
+ '-L', self.info['priv_level'],
+ '-U', self.info['username'],
+ '-f', file_handle,
+ 'A', 'B', 'C',
+ ]
+
+ mock_support.return_value = False
+ mock_pwf.return_value = file_handle
+ mock_exec.return_value = (None, None)
+
+ ipmi._exec_ipmitool(self.info, 'A B C')
+
+ mock_support.assert_called_once_with('timing')
+ mock_pwf.assert_called_once_with(self.info['password'])
+ mock_exec.assert_called_once_with(*args)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(ipmi, '_make_password_file', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test__exec_ipmitool_with_timing(
+ self, mock_exec, mock_pwf, mock_support, mock_sleep):
+ pw_file_handle = tempfile.NamedTemporaryFile()
+ pw_file = pw_file_handle.name
+ file_handle = open(pw_file, "w")
+ args = [
+ 'ipmitool',
+ '-I', 'lanplus',
+ '-H', self.info['address'],
+ '-L', self.info['priv_level'],
+ '-U', self.info['username'],
+ '-R', '12',
+ '-N', '5',
+ '-f', file_handle,
+ 'A', 'B', 'C',
+ ]
+
+ mock_support.return_value = True
+ mock_pwf.return_value = file_handle
+ mock_exec.return_value = (None, None)
+
+ ipmi._exec_ipmitool(self.info, 'A B C')
+
+ mock_support.assert_called_once_with('timing')
+ mock_pwf.assert_called_once_with(self.info['password'])
+ mock_exec.assert_called_once_with(*args)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(ipmi, '_make_password_file', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test__exec_ipmitool_without_username(
+ self, mock_exec, mock_pwf, mock_support, mock_sleep):
+ self.info['username'] = None
+ pw_file_handle = tempfile.NamedTemporaryFile()
+ pw_file = pw_file_handle.name
+ file_handle = open(pw_file, "w")
+ args = [
+ 'ipmitool',
+ '-I', 'lanplus',
+ '-H', self.info['address'],
+ '-L', self.info['priv_level'],
+ '-f', file_handle,
+ 'A', 'B', 'C',
+ ]
+
+ mock_support.return_value = False
+ mock_pwf.return_value = file_handle
+ mock_exec.return_value = (None, None)
+ ipmi._exec_ipmitool(self.info, 'A B C')
+ mock_support.assert_called_once_with('timing')
+ self.assertTrue(mock_pwf.called)
+ mock_exec.assert_called_once_with(*args)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(ipmi, '_make_password_file', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test__exec_ipmitool_with_dual_bridging(self,
+ mock_exec, mock_pwf,
+ mock_support,
+ mock_sleep):
+
+ node = obj_utils.get_test_node(self.context, driver='fake_ipmitool',
+ driver_info=BRIDGE_INFO_DICT)
+ # when support for dual bridge command is called returns True
+ mock_support.return_value = True
+ info = ipmi._parse_driver_info(node)
+ pw_file_handle = tempfile.NamedTemporaryFile()
+ pw_file = pw_file_handle.name
+ file_handle = open(pw_file, "w")
+ args = [
+ 'ipmitool',
+ '-I', 'lanplus',
+ '-H', info['address'],
+ '-L', info['priv_level'],
+ '-U', info['username'],
+ '-m', info['local_address'],
+ '-B', info['transit_channel'],
+ '-T', info['transit_address'],
+ '-b', info['target_channel'],
+ '-t', info['target_address'],
+ '-f', file_handle,
+ 'A', 'B', 'C',
+ ]
+
+ expected = [mock.call('dual_bridge'),
+ mock.call('timing')]
+ # When support for timing command is called returns False
+ mock_support.return_value = False
+ mock_pwf.return_value = file_handle
+ mock_exec.return_value = (None, None)
+ ipmi._exec_ipmitool(info, 'A B C')
+ self.assertEqual(expected, mock_support.call_args_list)
+ self.assertTrue(mock_pwf.called)
+ mock_exec.assert_called_once_with(*args)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(ipmi, '_make_password_file', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test__exec_ipmitool_with_single_bridging(self,
+ mock_exec, mock_pwf,
+ mock_support,
+ mock_sleep):
+ single_bridge_info = dict(BRIDGE_INFO_DICT)
+ single_bridge_info['ipmi_bridging'] = 'single'
+ node = obj_utils.get_test_node(self.context, driver='fake_ipmitool',
+ driver_info=single_bridge_info)
+ # when support for single bridge command is called returns True
+ mock_support.return_value = True
+ info = ipmi._parse_driver_info(node)
+ info['transit_channel'] = info['transit_address'] = None
+
+ pw_file_handle = tempfile.NamedTemporaryFile()
+ pw_file = pw_file_handle.name
+ file_handle = open(pw_file, "w")
+ args = [
+ 'ipmitool',
+ '-I', 'lanplus',
+ '-H', info['address'],
+ '-L', info['priv_level'],
+ '-U', info['username'],
+ '-m', info['local_address'],
+ '-b', info['target_channel'],
+ '-t', info['target_address'],
+ '-f', file_handle,
+ 'A', 'B', 'C',
+ ]
+
+ expected = [mock.call('single_bridge'),
+ mock.call('timing')]
+ # When support for timing command is called returns False
+ mock_support.return_value = False
+ mock_pwf.return_value = file_handle
+ mock_exec.return_value = (None, None)
+ ipmi._exec_ipmitool(info, 'A B C')
+ self.assertEqual(expected, mock_support.call_args_list)
+ self.assertTrue(mock_pwf.called)
+ mock_exec.assert_called_once_with(*args)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(ipmi, '_make_password_file', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test__exec_ipmitool_exception(
+ self, mock_exec, mock_pwf, mock_support, mock_sleep):
+ pw_file_handle = tempfile.NamedTemporaryFile()
+ pw_file = pw_file_handle.name
+ file_handle = open(pw_file, "w")
+ args = [
+ 'ipmitool',
+ '-I', 'lanplus',
+ '-H', self.info['address'],
+ '-L', self.info['priv_level'],
+ '-U', self.info['username'],
+ '-f', file_handle,
+ 'A', 'B', 'C',
+ ]
+
+ mock_support.return_value = False
+ mock_pwf.return_value = file_handle
+ mock_exec.side_effect = iter([processutils.ProcessExecutionError("x")])
+ self.assertRaises(processutils.ProcessExecutionError,
+ ipmi._exec_ipmitool,
+ self.info, 'A B C')
+ mock_support.assert_called_once_with('timing')
+ mock_pwf.assert_called_once_with(self.info['password'])
+ mock_exec.assert_called_once_with(*args)
+ self.assertEqual(1, mock_exec.call_count)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test__exec_ipmitool_exception_retry(
+ self, mock_exec, mock_support, mock_sleep):
+
+ ipmi.LAST_CMD_TIME = {}
+ mock_support.return_value = False
+ mock_exec.side_effect = iter([
+ processutils.ProcessExecutionError(
+ stderr="insufficient resources for session"
+ ),
+ (None, None)
+ ])
+
+ # Directly set the configuration values such that
+ # the logic will cause _exec_ipmitool to retry twice.
+ self.config(min_command_interval=1, group='ipmi')
+ self.config(retry_timeout=2, group='ipmi')
+
+ ipmi._exec_ipmitool(self.info, 'A B C')
+
+ mock_support.assert_called_once_with('timing')
+ self.assertEqual(2, mock_exec.call_count)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test__exec_ipmitool_exception_retries_exceeded(
+ self, mock_exec, mock_support, mock_sleep):
+
+ ipmi.LAST_CMD_TIME = {}
+ mock_support.return_value = False
+
+ mock_exec.side_effect = iter([processutils.ProcessExecutionError(
+ stderr="insufficient resources for session"
+ )])
+
+ # Directly set the configuration values such that
+ # the logic will cause _exec_ipmitool to timeout.
+ self.config(min_command_interval=1, group='ipmi')
+ self.config(retry_timeout=1, group='ipmi')
+
+ self.assertRaises(processutils.ProcessExecutionError,
+ ipmi._exec_ipmitool,
+ self.info, 'A B C')
+ mock_support.assert_called_once_with('timing')
+ self.assertEqual(1, mock_exec.call_count)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test__exec_ipmitool_exception_non_retryable_failure(
+ self, mock_exec, mock_support, mock_sleep):
+
+ ipmi.LAST_CMD_TIME = {}
+ mock_support.return_value = False
+
+ # Return a retryable error, then an error that cannot
+ # be retried thus resulting in a single retry
+ # attempt by _exec_ipmitool.
+ mock_exec.side_effect = iter([
+ processutils.ProcessExecutionError(
+ stderr="insufficient resources for session"
+ ),
+ processutils.ProcessExecutionError(
+ stderr="Unknown"
+ ),
+ ])
+
+ # Directly set the configuration values such that
+ # the logic will cause _exec_ipmitool to retry up
+ # to 3 times.
+ self.config(min_command_interval=1, group='ipmi')
+ self.config(retry_timeout=3, group='ipmi')
+
+ self.assertRaises(processutils.ProcessExecutionError,
+ ipmi._exec_ipmitool,
+ self.info, 'A B C')
+ mock_support.assert_called_once_with('timing')
+ self.assertEqual(2, mock_exec.call_count)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(ipmi, '_make_password_file', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test__exec_ipmitool_IPMI_version_1_5(
+ self, mock_exec, mock_pwf, mock_support, mock_sleep):
+ self.info['protocol_version'] = '1.5'
+ # Assert it uses "-I lan" (1.5) instead of "-I lanplus" (2.0)
+ args = [
+ 'ipmitool',
+ '-I', 'lan',
+ '-H', self.info['address'],
+ '-L', self.info['priv_level'],
+ '-U', self.info['username'],
+ '-f', mock.ANY,
+ 'A', 'B', 'C',
+ ]
+
+ mock_support.return_value = False
+ mock_exec.return_value = (None, None)
+ ipmi._exec_ipmitool(self.info, 'A B C')
+ mock_support.assert_called_once_with('timing')
+ self.assertTrue(mock_pwf.called)
+ mock_exec.assert_called_once_with(*args)
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test__power_status_on(self, mock_exec, mock_sleep):
+ mock_exec.return_value = ["Chassis Power is on\n", None]
+
+ state = ipmi._power_status(self.info)
+
+ mock_exec.assert_called_once_with(self.info, "power status")
+ self.assertEqual(states.POWER_ON, state)
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test__power_status_off(self, mock_exec, mock_sleep):
+ mock_exec.return_value = ["Chassis Power is off\n", None]
+
+ state = ipmi._power_status(self.info)
+
+ mock_exec.assert_called_once_with(self.info, "power status")
+ self.assertEqual(states.POWER_OFF, state)
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test__power_status_error(self, mock_exec, mock_sleep):
+ mock_exec.return_value = ["Chassis Power is badstate\n", None]
+
+ state = ipmi._power_status(self.info)
+
+ mock_exec.assert_called_once_with(self.info, "power status")
+ self.assertEqual(states.ERROR, state)
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test__power_status_exception(self, mock_exec, mock_sleep):
+ mock_exec.side_effect = iter(
+ [processutils.ProcessExecutionError("error")])
+ self.assertRaises(exception.IPMIFailure,
+ ipmi._power_status,
+ self.info)
+ mock_exec.assert_called_once_with(self.info, "power status")
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ @mock.patch('eventlet.greenthread.sleep', autospec=True)
+ def test__power_on_max_retries(self, sleep_mock, mock_exec, mock_sleep):
+ self.config(retry_timeout=2, group='ipmi')
+
+ def side_effect(driver_info, command):
+ resp_dict = {"power status": ["Chassis Power is off\n", None],
+ "power on": [None, None]}
+ return resp_dict.get(command, ["Bad\n", None])
+
+ mock_exec.side_effect = side_effect
+
+ expected = [mock.call(self.info, "power on"),
+ mock.call(self.info, "power status"),
+ mock.call(self.info, "power status")]
+
+ state = ipmi._power_on(self.info)
+
+ self.assertEqual(mock_exec.call_args_list, expected)
+ self.assertEqual(states.ERROR, state)
+
+
+class IPMIToolDriverTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IPMIToolDriverTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="fake_ipmitool")
+ self.driver = driver_factory.get_driver("fake_ipmitool")
+
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_ipmitool',
+ driver_info=INFO_DICT)
+ self.info = ipmi._parse_driver_info(self.node)
+
+ @mock.patch.object(ipmi, "_parse_driver_info", autospec=True)
+ def test_power_validate(self, mock_parse):
+ node = obj_utils.get_test_node(self.context, driver='fake_ipmitool',
+ driver_info=INFO_DICT)
+ mock_parse.return_value = {}
+
+ with task_manager.acquire(self.context, node.uuid) as task:
+ task.driver.power.validate(task)
+ mock_parse.assert_called_once_with(mock.ANY)
+
+ def test_get_properties(self):
+ expected = ipmi.COMMON_PROPERTIES
+ self.assertEqual(expected, self.driver.power.get_properties())
+
+ expected = list(ipmi.COMMON_PROPERTIES) + list(ipmi.CONSOLE_PROPERTIES)
+ self.assertEqual(sorted(expected),
+ sorted(self.driver.console.get_properties().keys()))
+ self.assertEqual(sorted(expected),
+ sorted(self.driver.get_properties().keys()))
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test_get_power_state(self, mock_exec):
+ returns = iter([["Chassis Power is off\n", None],
+ ["Chassis Power is on\n", None],
+ ["\n", None]])
+ expected = [mock.call(self.info, "power status"),
+ mock.call(self.info, "power status"),
+ mock.call(self.info, "power status")]
+ mock_exec.side_effect = returns
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ pstate = self.driver.power.get_power_state(task)
+ self.assertEqual(states.POWER_OFF, pstate)
+
+ pstate = self.driver.power.get_power_state(task)
+ self.assertEqual(states.POWER_ON, pstate)
+
+ pstate = self.driver.power.get_power_state(task)
+ self.assertEqual(states.ERROR, pstate)
+
+ self.assertEqual(mock_exec.call_args_list, expected)
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test_get_power_state_exception(self, mock_exec):
+ mock_exec.side_effect = iter(
+ [processutils.ProcessExecutionError("error")])
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.IPMIFailure,
+ self.driver.power.get_power_state,
+ task)
+ mock_exec.assert_called_once_with(self.info, "power status")
+
+ @mock.patch.object(ipmi, '_power_on', autospec=True)
+ @mock.patch.object(ipmi, '_power_off', autospec=True)
+ def test_set_power_on_ok(self, mock_off, mock_on):
+ self.config(retry_timeout=0, group='ipmi')
+
+ mock_on.return_value = states.POWER_ON
+ with task_manager.acquire(self.context,
+ self.node['uuid']) as task:
+ self.driver.power.set_power_state(task,
+ states.POWER_ON)
+
+ mock_on.assert_called_once_with(self.info)
+ self.assertFalse(mock_off.called)
+
+ @mock.patch.object(driver_utils, 'ensure_next_boot_device', autospec=True)
+ @mock.patch.object(ipmi, '_power_on', autospec=True)
+ @mock.patch.object(ipmi, '_power_off', autospec=True)
+ def test_set_power_on_with_next_boot(self, mock_off, mock_on,
+ mock_next_boot):
+ self.config(retry_timeout=0, group='ipmi')
+
+ mock_on.return_value = states.POWER_ON
+ with task_manager.acquire(self.context,
+ self.node['uuid']) as task:
+ self.driver.power.set_power_state(task,
+ states.POWER_ON)
+ mock_next_boot.assert_called_once_with(task, self.info)
+
+ mock_on.assert_called_once_with(self.info)
+ self.assertFalse(mock_off.called)
+
+ @mock.patch.object(ipmi, '_power_on', autospec=True)
+ @mock.patch.object(ipmi, '_power_off', autospec=True)
+ def test_set_power_off_ok(self, mock_off, mock_on):
+ self.config(retry_timeout=0, group='ipmi')
+
+ mock_off.return_value = states.POWER_OFF
+
+ with task_manager.acquire(self.context,
+ self.node['uuid']) as task:
+ self.driver.power.set_power_state(task,
+ states.POWER_OFF)
+
+ mock_off.assert_called_once_with(self.info)
+ self.assertFalse(mock_on.called)
+
+ @mock.patch.object(ipmi, '_power_on', autospec=True)
+ @mock.patch.object(ipmi, '_power_off', autospec=True)
+ def test_set_power_on_fail(self, mock_off, mock_on):
+ self.config(retry_timeout=0, group='ipmi')
+
+ mock_on.return_value = states.ERROR
+ with task_manager.acquire(self.context,
+ self.node['uuid']) as task:
+ self.assertRaises(exception.PowerStateFailure,
+ self.driver.power.set_power_state,
+ task,
+ states.POWER_ON)
+
+ mock_on.assert_called_once_with(self.info)
+ self.assertFalse(mock_off.called)
+
+ def test_set_power_invalid_state(self):
+ with task_manager.acquire(self.context, self.node['uuid']) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ self.driver.power.set_power_state,
+ task,
+ "fake state")
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test_send_raw_bytes_ok(self, mock_exec):
+ mock_exec.return_value = [None, None]
+
+ with task_manager.acquire(self.context,
+ self.node['uuid']) as task:
+ self.driver.vendor.send_raw(task, http_method='POST',
+ raw_bytes='0x00 0x01')
+
+ mock_exec.assert_called_once_with(self.info, 'raw 0x00 0x01')
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test_send_raw_bytes_fail(self, mock_exec):
+ mock_exec.side_effect = iter(
+ [exception.PasswordFileFailedToCreate('error')])
+
+ with task_manager.acquire(self.context,
+ self.node['uuid']) as task:
+ self.assertRaises(exception.IPMIFailure,
+ self.driver.vendor.send_raw,
+ task,
+ http_method='POST',
+ raw_bytes='0x00 0x01')
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test__bmc_reset_ok(self, mock_exec):
+ mock_exec.return_value = [None, None]
+
+ with task_manager.acquire(self.context,
+ self.node['uuid']) as task:
+ self.driver.vendor.bmc_reset(task, 'POST')
+
+ mock_exec.assert_called_once_with(self.info, 'bmc reset warm')
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test__bmc_reset_cold(self, mock_exec):
+ mock_exec.return_value = [None, None]
+
+ with task_manager.acquire(self.context,
+ self.node['uuid']) as task:
+ self.driver.vendor.bmc_reset(task, 'POST', warm=False)
+
+ mock_exec.assert_called_once_with(self.info, 'bmc reset cold')
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test__bmc_reset_fail(self, mock_exec):
+ mock_exec.side_effect = iter([processutils.ProcessExecutionError()])
+
+ with task_manager.acquire(self.context,
+ self.node['uuid']) as task:
+ self.assertRaises(exception.IPMIFailure,
+ self.driver.vendor.bmc_reset,
+ task, 'POST')
+
+ @mock.patch.object(driver_utils, 'ensure_next_boot_device', autospec=True)
+ @mock.patch.object(ipmi, '_power_off', spec_set=types.FunctionType)
+ @mock.patch.object(ipmi, '_power_on', spec_set=types.FunctionType)
+ def test_reboot_ok(self, mock_on, mock_off, mock_next_boot):
+ manager = mock.MagicMock()
+ # NOTE(rloo): if autospec is True, then manager.mock_calls is empty
+ mock_on.return_value = states.POWER_ON
+ manager.attach_mock(mock_off, 'power_off')
+ manager.attach_mock(mock_on, 'power_on')
+ expected = [mock.call.power_off(self.info),
+ mock.call.power_on(self.info)]
+
+ with task_manager.acquire(self.context,
+ self.node['uuid']) as task:
+ self.driver.power.reboot(task)
+ mock_next_boot.assert_called_once_with(task, self.info)
+
+ self.assertEqual(manager.mock_calls, expected)
+
+ @mock.patch.object(ipmi, '_power_off', spec_set=types.FunctionType)
+ @mock.patch.object(ipmi, '_power_on', spec_set=types.FunctionType)
+ def test_reboot_fail(self, mock_on, mock_off):
+ manager = mock.MagicMock()
+ # NOTE(rloo): if autospec is True, then manager.mock_calls is empty
+ mock_on.return_value = states.ERROR
+ manager.attach_mock(mock_off, 'power_off')
+ manager.attach_mock(mock_on, 'power_on')
+ expected = [mock.call.power_off(self.info),
+ mock.call.power_on(self.info)]
+
+ with task_manager.acquire(self.context,
+ self.node['uuid']) as task:
+ self.assertRaises(exception.PowerStateFailure,
+ self.driver.power.reboot,
+ task)
+
+ self.assertEqual(manager.mock_calls, expected)
+
+ @mock.patch.object(ipmi, '_parse_driver_info', autospec=True)
+ def test_vendor_passthru_validate__parse_driver_info_fail(self, info_mock):
+ info_mock.side_effect = iter([exception.InvalidParameterValue("bad")])
+ with task_manager.acquire(self.context, self.node['uuid']) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ self.driver.vendor.validate,
+ task, method='send_raw', raw_bytes='0x00 0x01')
+ info_mock.assert_called_once_with(task.node)
+
+ def test_vendor_passthru_validate__send_raw_bytes_good(self):
+ with task_manager.acquire(self.context, self.node['uuid']) as task:
+ self.driver.vendor.validate(task,
+ method='send_raw',
+ http_method='POST',
+ raw_bytes='0x00 0x01')
+
+ def test_vendor_passthru_validate__send_raw_bytes_fail(self):
+ with task_manager.acquire(self.context, self.node['uuid']) as task:
+ self.assertRaises(exception.MissingParameterValue,
+ self.driver.vendor.validate,
+ task, method='send_raw')
+
+ @mock.patch.object(ipmi.VendorPassthru, 'send_raw', autospec=True)
+ def test_vendor_passthru_call_send_raw_bytes(self, raw_bytes_mock):
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.driver.vendor.send_raw(task, http_method='POST',
+ raw_bytes='0x00 0x01')
+ raw_bytes_mock.assert_called_once_with(
+ self.driver.vendor, task, http_method='POST',
+ raw_bytes='0x00 0x01')
+
+ def test_vendor_passthru_validate__bmc_reset_good(self):
+ with task_manager.acquire(self.context, self.node['uuid']) as task:
+ self.driver.vendor.validate(task,
+ method='bmc_reset')
+
+ def test_vendor_passthru_validate__bmc_reset_warm_good(self):
+ with task_manager.acquire(self.context, self.node['uuid']) as task:
+ self.driver.vendor.validate(task,
+ method='bmc_reset',
+ warm=True)
+
+ def test_vendor_passthru_validate__bmc_reset_cold_good(self):
+ with task_manager.acquire(self.context, self.node['uuid']) as task:
+ self.driver.vendor.validate(task,
+ method='bmc_reset',
+ warm=False)
+
+ @mock.patch.object(ipmi.VendorPassthru, 'bmc_reset', autospec=True)
+ def test_vendor_passthru_call_bmc_reset_warm(self, bmc_mock):
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.driver.vendor.bmc_reset(task, 'POST', warm=True)
+ bmc_mock.assert_called_once_with(
+ self.driver.vendor, task, 'POST', warm=True)
+
+ @mock.patch.object(ipmi.VendorPassthru, 'bmc_reset', autospec=True)
+ def test_vendor_passthru_call_bmc_reset_cold(self, bmc_mock):
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.driver.vendor.bmc_reset(task, 'POST', warm=False)
+ bmc_mock.assert_called_once_with(
+ self.driver.vendor, task, 'POST', warm=False)
+
+ def test_vendor_passthru_vendor_routes(self):
+ expected = ['send_raw', 'bmc_reset']
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ vendor_routes = task.driver.vendor.vendor_routes
+ self.assertIsInstance(vendor_routes, dict)
+ self.assertEqual(sorted(expected), sorted(vendor_routes))
+
+ def test_vendor_passthru_driver_routes(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ driver_routes = task.driver.vendor.driver_routes
+ self.assertIsInstance(driver_routes, dict)
+ self.assertEqual({}, driver_routes)
+
+ def test_console_validate(self):
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=True) as task:
+ task.node.driver_info['ipmi_terminal_port'] = 123
+ task.driver.console.validate(task)
+
+ def test_console_validate_missing_port(self):
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=True) as task:
+ task.node.driver_info.pop('ipmi_terminal_port', None)
+ self.assertRaises(exception.MissingParameterValue,
+ task.driver.console.validate, task)
+
+ def test_console_validate_invalid_port(self):
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=True) as task:
+ task.node.driver_info['ipmi_terminal_port'] = ''
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.console.validate, task)
+
+ def test_console_validate_wrong_ipmi_protocol_version(self):
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=True) as task:
+ task.node.driver_info['ipmi_terminal_port'] = 123
+ task.node.driver_info['ipmi_protocol_version'] = '1.5'
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.console.validate, task)
+
+ @mock.patch.object(console_utils, 'start_shellinabox_console',
+ autospec=True)
+ def test_start_console(self, mock_exec):
+ mock_exec.return_value = None
+
+ with task_manager.acquire(self.context,
+ self.node['uuid']) as task:
+ self.driver.console.start_console(task)
+
+ mock_exec.assert_called_once_with(self.info['uuid'],
+ self.info['port'],
+ mock.ANY)
+ self.assertTrue(mock_exec.called)
+
+ @mock.patch.object(console_utils, 'start_shellinabox_console',
+ autospec=True)
+ def test_start_console_fail(self, mock_exec):
+ mock_exec.side_effect = iter(
+ [exception.ConsoleSubprocessFailed(error='error')])
+
+ with task_manager.acquire(self.context,
+ self.node['uuid']) as task:
+ self.assertRaises(exception.ConsoleSubprocessFailed,
+ self.driver.console.start_console,
+ task)
+
+ @mock.patch.object(console_utils, 'start_shellinabox_console',
+ autospec=True)
+ def test_start_console_fail_nodir(self, mock_exec):
+ mock_exec.side_effect = iter([exception.ConsoleError()])
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.assertRaises(exception.ConsoleError,
+ self.driver.console.start_console,
+ task)
+ mock_exec.assert_called_once_with(self.node.uuid, mock.ANY, mock.ANY)
+
+ @mock.patch.object(console_utils, 'stop_shellinabox_console',
+ autospec=True)
+ def test_stop_console(self, mock_exec):
+ mock_exec.return_value = None
+
+ with task_manager.acquire(self.context,
+ self.node['uuid']) as task:
+ self.driver.console.stop_console(task)
+
+ mock_exec.assert_called_once_with(self.info['uuid'])
+ self.assertTrue(mock_exec.called)
+
+ @mock.patch.object(console_utils, 'stop_shellinabox_console',
+ autospec=True)
+ def test_stop_console_fail(self, mock_stop):
+ mock_stop.side_effect = iter([exception.ConsoleError()])
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.assertRaises(exception.ConsoleError,
+ self.driver.console.stop_console,
+ task)
+
+ mock_stop.assert_called_once_with(self.node.uuid)
+
+ @mock.patch.object(console_utils, 'get_shellinabox_console_url',
+ autospec=True)
+ def test_get_console(self, mock_exec):
+ url = 'http://localhost:4201'
+ mock_exec.return_value = url
+ expected = {'type': 'shellinabox', 'url': url}
+
+ with task_manager.acquire(self.context,
+ self.node['uuid']) as task:
+ console_info = self.driver.console.get_console(task)
+
+ self.assertEqual(expected, console_info)
+ mock_exec.assert_called_once_with(self.info['port'])
+ self.assertTrue(mock_exec.called)
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test_management_interface_set_boot_device_ok(self, mock_exec):
+ mock_exec.return_value = [None, None]
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.driver.management.set_boot_device(task, boot_devices.PXE)
+
+ mock_calls = [mock.call(self.info, "raw 0x00 0x08 0x03 0x08"),
+ mock.call(self.info, "chassis bootdev pxe")]
+ mock_exec.assert_has_calls(mock_calls)
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test_management_interface_force_set_boot_device_ok(self, mock_exec):
+ mock_exec.return_value = [None, None]
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node.driver_info['ipmi_force_boot_device'] = True
+ self.info['force_boot_device'] = True
+ self.driver.management.set_boot_device(task, boot_devices.PXE)
+ task.node.refresh()
+ self.assertEqual(
+ False,
+ task.node.driver_internal_info['is_next_boot_persistent']
+ )
+
+ mock_calls = [mock.call(self.info, "raw 0x00 0x08 0x03 0x08"),
+ mock.call(self.info, "chassis bootdev pxe")]
+ mock_exec.assert_has_calls(mock_calls)
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test_management_interface_set_boot_device_persistent(self, mock_exec):
+ mock_exec.return_value = [None, None]
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node.driver_info['ipmi_force_boot_device'] = True
+ self.info['force_boot_device'] = True
+ self.driver.management.set_boot_device(task,
+ boot_devices.PXE,
+ True)
+ self.assertEqual(
+ boot_devices.PXE,
+ task.node.driver_internal_info['persistent_boot_device'])
+
+ mock_calls = [mock.call(self.info, "raw 0x00 0x08 0x03 0x08"),
+ mock.call(self.info, "chassis bootdev pxe")]
+ mock_exec.assert_has_calls(mock_calls)
+
+ def test_management_interface_set_boot_device_bad_device(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ self.driver.management.set_boot_device,
+ task, 'fake-device')
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test_management_interface_set_boot_device_exec_failed(self, mock_exec):
+ mock_exec.side_effect = iter([processutils.ProcessExecutionError()])
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.IPMIFailure,
+ self.driver.management.set_boot_device,
+ task, boot_devices.PXE)
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test_management_interface_set_boot_device_unknown_exception(self,
+ mock_exec):
+
+ class FakeException(Exception):
+ pass
+
+ mock_exec.side_effect = iter([FakeException('boom')])
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(FakeException,
+ self.driver.management.set_boot_device,
+ task, boot_devices.PXE)
+
+ def test_management_interface_get_supported_boot_devices(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ expected = [boot_devices.PXE, boot_devices.DISK,
+ boot_devices.CDROM, boot_devices.BIOS,
+ boot_devices.SAFE]
+ self.assertEqual(sorted(expected), sorted(task.driver.management.
+ get_supported_boot_devices(task)))
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test_management_interface_get_boot_device(self, mock_exec):
+ # output, expected boot device
+ bootdevs = [('Boot Device Selector : '
+ 'Force Boot from default Hard-Drive\n',
+ boot_devices.DISK),
+ ('Boot Device Selector : '
+ 'Force Boot from default Hard-Drive, request Safe-Mode\n',
+ boot_devices.SAFE),
+ ('Boot Device Selector : '
+ 'Force Boot into BIOS Setup\n',
+ boot_devices.BIOS),
+ ('Boot Device Selector : '
+ 'Force PXE\n',
+ boot_devices.PXE),
+ ('Boot Device Selector : '
+ 'Force Boot from CD/DVD\n',
+ boot_devices.CDROM)]
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ for out, expected_device in bootdevs:
+ mock_exec.return_value = (out, '')
+ expected_response = {'boot_device': expected_device,
+ 'persistent': False}
+ self.assertEqual(expected_response,
+ task.driver.management.get_boot_device(task))
+ mock_exec.assert_called_with(mock.ANY,
+ "chassis bootparam get 5")
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test_management_interface_get_boot_device_unknown_dev(self, mock_exec):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ mock_exec.return_value = ('Boot Device Selector : Fake\n', '')
+ response = task.driver.management.get_boot_device(task)
+ self.assertIsNone(response['boot_device'])
+ mock_exec.assert_called_with(mock.ANY, "chassis bootparam get 5")
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test_management_interface_get_boot_device_fail(self, mock_exec):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ mock_exec.side_effect = iter(
+ [processutils.ProcessExecutionError()])
+ self.assertRaises(exception.IPMIFailure,
+ task.driver.management.get_boot_device, task)
+ mock_exec.assert_called_with(mock.ANY, "chassis bootparam get 5")
+
+ @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
+ def test_management_interface_get_boot_device_persistent(self, mock_exec):
+ outputs = [('Options apply to only next boot\n'
+ 'Boot Device Selector : Force PXE\n',
+ False),
+ ('Options apply to all future boots\n'
+ 'Boot Device Selector : Force PXE\n',
+ True)]
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ for out, expected_persistent in outputs:
+ mock_exec.return_value = (out, '')
+ expected_response = {'boot_device': boot_devices.PXE,
+ 'persistent': expected_persistent}
+ self.assertEqual(expected_response,
+ task.driver.management.get_boot_device(task))
+ mock_exec.assert_called_with(mock.ANY,
+ "chassis bootparam get 5")
+
+ def test_get_force_boot_device_persistent(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node.driver_info['ipmi_force_boot_device'] = True
+ task.node.driver_internal_info['persistent_boot_device'] = 'pxe'
+ bootdev = self.driver.management.get_boot_device(task)
+ self.assertEqual('pxe', bootdev['boot_device'])
+ self.assertTrue(bootdev['persistent'])
+
+ def test_management_interface_validate_good(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.driver.management.validate(task)
+
+ def test_management_interface_validate_fail(self):
+ # Missing IPMI driver_info information
+ node = obj_utils.create_test_node(self.context,
+ uuid=uuidutils.generate_uuid(),
+ driver='fake_ipmitool')
+ with task_manager.acquire(self.context, node.uuid) as task:
+ self.assertRaises(exception.MissingParameterValue,
+ task.driver.management.validate, task)
+
+ def test__parse_ipmi_sensor_data_ok(self):
+ fake_sensors_data = """
+ Sensor ID : Temp (0x1)
+ Entity ID : 3.1 (Processor)
+ Sensor Type (Analog) : Temperature
+ Sensor Reading : -58 (+/- 1) degrees C
+ Status : ok
+ Nominal Reading : 50.000
+ Normal Minimum : 11.000
+ Normal Maximum : 69.000
+ Upper critical : 90.000
+ Upper non-critical : 85.000
+ Positive Hysteresis : 1.000
+ Negative Hysteresis : 1.000
+
+ Sensor ID : Temp (0x2)
+ Entity ID : 3.2 (Processor)
+ Sensor Type (Analog) : Temperature
+ Sensor Reading : 50 (+/- 1) degrees C
+ Status : ok
+ Nominal Reading : 50.000
+ Normal Minimum : 11.000
+ Normal Maximum : 69.000
+ Upper critical : 90.000
+ Upper non-critical : 85.000
+ Positive Hysteresis : 1.000
+ Negative Hysteresis : 1.000
+
+ Sensor ID : FAN MOD 1A RPM (0x30)
+ Entity ID : 7.1 (System Board)
+ Sensor Type (Analog) : Fan
+ Sensor Reading : 8400 (+/- 75) RPM
+ Status : ok
+ Nominal Reading : 5325.000
+ Normal Minimum : 10425.000
+ Normal Maximum : 14775.000
+ Lower critical : 4275.000
+ Positive Hysteresis : 375.000
+ Negative Hysteresis : 375.000
+
+ Sensor ID : FAN MOD 1B RPM (0x31)
+ Entity ID : 7.1 (System Board)
+ Sensor Type (Analog) : Fan
+ Sensor Reading : 8550 (+/- 75) RPM
+ Status : ok
+ Nominal Reading : 7800.000
+ Normal Minimum : 10425.000
+ Normal Maximum : 14775.000
+ Lower critical : 4275.000
+ Positive Hysteresis : 375.000
+ Negative Hysteresis : 375.000
+ """
+ expected_return = {
+ 'Fan': {
+ 'FAN MOD 1A RPM (0x30)': {
+ 'Status': 'ok',
+ 'Sensor Reading': '8400 (+/- 75) RPM',
+ 'Entity ID': '7.1 (System Board)',
+ 'Normal Minimum': '10425.000',
+ 'Positive Hysteresis': '375.000',
+ 'Normal Maximum': '14775.000',
+ 'Sensor Type (Analog)': 'Fan',
+ 'Lower critical': '4275.000',
+ 'Negative Hysteresis': '375.000',
+ 'Sensor ID': 'FAN MOD 1A RPM (0x30)',
+ 'Nominal Reading': '5325.000'
+ },
+ 'FAN MOD 1B RPM (0x31)': {
+ 'Status': 'ok',
+ 'Sensor Reading': '8550 (+/- 75) RPM',
+ 'Entity ID': '7.1 (System Board)',
+ 'Normal Minimum': '10425.000',
+ 'Positive Hysteresis': '375.000',
+ 'Normal Maximum': '14775.000',
+ 'Sensor Type (Analog)': 'Fan',
+ 'Lower critical': '4275.000',
+ 'Negative Hysteresis': '375.000',
+ 'Sensor ID': 'FAN MOD 1B RPM (0x31)',
+ 'Nominal Reading': '7800.000'
+ }
+ },
+ 'Temperature': {
+ 'Temp (0x1)': {
+ 'Status': 'ok',
+ 'Sensor Reading': '-58 (+/- 1) degrees C',
+ 'Entity ID': '3.1 (Processor)',
+ 'Normal Minimum': '11.000',
+ 'Positive Hysteresis': '1.000',
+ 'Upper non-critical': '85.000',
+ 'Normal Maximum': '69.000',
+ 'Sensor Type (Analog)': 'Temperature',
+ 'Negative Hysteresis': '1.000',
+ 'Upper critical': '90.000',
+ 'Sensor ID': 'Temp (0x1)',
+ 'Nominal Reading': '50.000'
+ },
+ 'Temp (0x2)': {
+ 'Status': 'ok',
+ 'Sensor Reading': '50 (+/- 1) degrees C',
+ 'Entity ID': '3.2 (Processor)',
+ 'Normal Minimum': '11.000',
+ 'Positive Hysteresis': '1.000',
+ 'Upper non-critical': '85.000',
+ 'Normal Maximum': '69.000',
+ 'Sensor Type (Analog)': 'Temperature',
+ 'Negative Hysteresis': '1.000',
+ 'Upper critical': '90.000',
+ 'Sensor ID': 'Temp (0x2)',
+ 'Nominal Reading': '50.000'
+ }
+ }
+ }
+ ret = ipmi._parse_ipmi_sensors_data(self.node, fake_sensors_data)
+
+ self.assertEqual(expected_return, ret)
+
+ def test__parse_ipmi_sensor_data_missing_sensor_reading(self):
+ fake_sensors_data = """
+ Sensor ID : Temp (0x1)
+ Entity ID : 3.1 (Processor)
+ Sensor Type (Analog) : Temperature
+ Status : ok
+ Nominal Reading : 50.000
+ Normal Minimum : 11.000
+ Normal Maximum : 69.000
+ Upper critical : 90.000
+ Upper non-critical : 85.000
+ Positive Hysteresis : 1.000
+ Negative Hysteresis : 1.000
+
+ Sensor ID : Temp (0x2)
+ Entity ID : 3.2 (Processor)
+ Sensor Type (Analog) : Temperature
+ Sensor Reading : 50 (+/- 1) degrees C
+ Status : ok
+ Nominal Reading : 50.000
+ Normal Minimum : 11.000
+ Normal Maximum : 69.000
+ Upper critical : 90.000
+ Upper non-critical : 85.000
+ Positive Hysteresis : 1.000
+ Negative Hysteresis : 1.000
+
+ Sensor ID : FAN MOD 1A RPM (0x30)
+ Entity ID : 7.1 (System Board)
+ Sensor Type (Analog) : Fan
+ Sensor Reading : 8400 (+/- 75) RPM
+ Status : ok
+ Nominal Reading : 5325.000
+ Normal Minimum : 10425.000
+ Normal Maximum : 14775.000
+ Lower critical : 4275.000
+ Positive Hysteresis : 375.000
+ Negative Hysteresis : 375.000
+ """
+ expected_return = {
+ 'Fan': {
+ 'FAN MOD 1A RPM (0x30)': {
+ 'Status': 'ok',
+ 'Sensor Reading': '8400 (+/- 75) RPM',
+ 'Entity ID': '7.1 (System Board)',
+ 'Normal Minimum': '10425.000',
+ 'Positive Hysteresis': '375.000',
+ 'Normal Maximum': '14775.000',
+ 'Sensor Type (Analog)': 'Fan',
+ 'Lower critical': '4275.000',
+ 'Negative Hysteresis': '375.000',
+ 'Sensor ID': 'FAN MOD 1A RPM (0x30)',
+ 'Nominal Reading': '5325.000'
+ }
+ },
+ 'Temperature': {
+ 'Temp (0x2)': {
+ 'Status': 'ok',
+ 'Sensor Reading': '50 (+/- 1) degrees C',
+ 'Entity ID': '3.2 (Processor)',
+ 'Normal Minimum': '11.000',
+ 'Positive Hysteresis': '1.000',
+ 'Upper non-critical': '85.000',
+ 'Normal Maximum': '69.000',
+ 'Sensor Type (Analog)': 'Temperature',
+ 'Negative Hysteresis': '1.000',
+ 'Upper critical': '90.000',
+ 'Sensor ID': 'Temp (0x2)',
+ 'Nominal Reading': '50.000'
+ }
+ }
+ }
+ ret = ipmi._parse_ipmi_sensors_data(self.node, fake_sensors_data)
+
+ self.assertEqual(expected_return, ret)
+
+ def test__parse_ipmi_sensor_data_failed(self):
+ fake_sensors_data = "abcdef"
+ self.assertRaises(exception.FailedToParseSensorData,
+ ipmi._parse_ipmi_sensors_data,
+ self.node,
+ fake_sensors_data)
+
+ fake_sensors_data = "abc:def:ghi"
+ self.assertRaises(exception.FailedToParseSensorData,
+ ipmi._parse_ipmi_sensors_data,
+ self.node,
+ fake_sensors_data)
diff --git a/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py b/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py
new file mode 100644
index 000000000..261f11766
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py
@@ -0,0 +1,1402 @@
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# 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
+# 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.
+
+"""Test class for iSCSI deploy mechanism."""
+
+import os
+import tempfile
+
+import mock
+from oslo_config import cfg
+from oslo_utils import fileutils
+from oslo_utils import uuidutils
+
+from ironic.common import driver_factory
+from ironic.common import exception
+from ironic.common import keystone
+from ironic.common import pxe_utils
+from ironic.common import states
+from ironic.common import utils
+from ironic.conductor import task_manager
+from ironic.conductor import utils as manager_utils
+from ironic.drivers.modules import agent_base_vendor
+from ironic.drivers.modules import agent_client
+from ironic.drivers.modules import deploy_utils
+from ironic.drivers.modules import fake
+from ironic.drivers.modules import iscsi_deploy
+from ironic.drivers.modules import pxe
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+CONF = cfg.CONF
+
+INST_INFO_DICT = db_utils.get_test_pxe_instance_info()
+DRV_INFO_DICT = db_utils.get_test_pxe_driver_info()
+DRV_INTERNAL_INFO_DICT = db_utils.get_test_pxe_driver_internal_info()
+
+
+class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
+
+ def test_parse_instance_info_good(self):
+ # make sure we get back the expected things
+ node = obj_utils.create_test_node(
+ self.context, driver='fake_pxe',
+ instance_info=INST_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT
+ )
+ info = iscsi_deploy.parse_instance_info(node)
+ self.assertIsNotNone(info.get('image_source'))
+ self.assertIsNotNone(info.get('root_gb'))
+ self.assertEqual(0, info.get('ephemeral_gb'))
+ self.assertIsNone(info.get('configdrive'))
+
+ def test_parse_instance_info_missing_instance_source(self):
+ # make sure error is raised when info is missing
+ info = dict(INST_INFO_DICT)
+ del info['image_source']
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ self.assertRaises(exception.MissingParameterValue,
+ iscsi_deploy.parse_instance_info,
+ node)
+
+ def test_parse_instance_info_missing_root_gb(self):
+ # make sure error is raised when info is missing
+ info = dict(INST_INFO_DICT)
+ del info['root_gb']
+
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ self.assertRaises(exception.MissingParameterValue,
+ iscsi_deploy.parse_instance_info,
+ node)
+
+ def test_parse_instance_info_invalid_root_gb(self):
+ info = dict(INST_INFO_DICT)
+ info['root_gb'] = 'foobar'
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ self.assertRaises(exception.InvalidParameterValue,
+ iscsi_deploy.parse_instance_info,
+ node)
+
+ def test_parse_instance_info_valid_ephemeral_gb(self):
+ ephemeral_gb = 10
+ ephemeral_fmt = 'test-fmt'
+ info = dict(INST_INFO_DICT)
+ info['ephemeral_gb'] = ephemeral_gb
+ info['ephemeral_format'] = ephemeral_fmt
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ data = iscsi_deploy.parse_instance_info(node)
+ self.assertEqual(ephemeral_gb, data.get('ephemeral_gb'))
+ self.assertEqual(ephemeral_fmt, data.get('ephemeral_format'))
+
+ def test_parse_instance_info_invalid_ephemeral_gb(self):
+ info = dict(INST_INFO_DICT)
+ info['ephemeral_gb'] = 'foobar'
+ info['ephemeral_format'] = 'exttest'
+
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ self.assertRaises(exception.InvalidParameterValue,
+ iscsi_deploy.parse_instance_info,
+ node)
+
+ def test_parse_instance_info_valid_ephemeral_missing_format(self):
+ ephemeral_gb = 10
+ ephemeral_fmt = 'test-fmt'
+ info = dict(INST_INFO_DICT)
+ info['ephemeral_gb'] = ephemeral_gb
+ info['ephemeral_format'] = None
+ self.config(default_ephemeral_format=ephemeral_fmt, group='pxe')
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ instance_info = iscsi_deploy.parse_instance_info(node)
+ self.assertEqual(ephemeral_fmt, instance_info['ephemeral_format'])
+
+ def test_parse_instance_info_valid_preserve_ephemeral_true(self):
+ info = dict(INST_INFO_DICT)
+ for opt in ['true', 'TRUE', 'True', 't',
+ 'on', 'yes', 'y', '1']:
+ info['preserve_ephemeral'] = opt
+
+ node = obj_utils.create_test_node(
+ self.context, uuid=uuidutils.generate_uuid(),
+ instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ data = iscsi_deploy.parse_instance_info(node)
+ self.assertTrue(data.get('preserve_ephemeral'))
+
+ def test_parse_instance_info_valid_preserve_ephemeral_false(self):
+ info = dict(INST_INFO_DICT)
+ for opt in ['false', 'FALSE', 'False', 'f',
+ 'off', 'no', 'n', '0']:
+ info['preserve_ephemeral'] = opt
+ node = obj_utils.create_test_node(
+ self.context, uuid=uuidutils.generate_uuid(),
+ instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ data = iscsi_deploy.parse_instance_info(node)
+ self.assertFalse(data.get('preserve_ephemeral'))
+
+ def test_parse_instance_info_invalid_preserve_ephemeral(self):
+ info = dict(INST_INFO_DICT)
+ info['preserve_ephemeral'] = 'foobar'
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ self.assertRaises(exception.InvalidParameterValue,
+ iscsi_deploy.parse_instance_info,
+ node)
+
+ def test_parse_instance_info_invalid_ephemeral_disk(self):
+ info = dict(INST_INFO_DICT)
+ info['ephemeral_gb'] = 10
+ info['swap_mb'] = 0
+ info['root_gb'] = 20
+ info['preserve_ephemeral'] = True
+ drv_internal_dict = {'instance': {'ephemeral_gb': 9,
+ 'swap_mb': 0,
+ 'root_gb': 20}}
+ drv_internal_dict.update(DRV_INTERNAL_INFO_DICT)
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=drv_internal_dict,
+ )
+ self.assertRaises(exception.InvalidParameterValue,
+ iscsi_deploy.parse_instance_info,
+ node)
+
+ def test__check_disk_layout_unchanged_fails(self):
+ info = dict(INST_INFO_DICT)
+ info['ephemeral_gb'] = 10
+ info['swap_mb'] = 0
+ info['root_gb'] = 20
+ info['preserve_ephemeral'] = True
+ drv_internal_dict = {'instance': {'ephemeral_gb': 20,
+ 'swap_mb': 0,
+ 'root_gb': 20}}
+ drv_internal_dict.update(DRV_INTERNAL_INFO_DICT)
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=drv_internal_dict,
+ )
+ self.assertRaises(exception.InvalidParameterValue,
+ iscsi_deploy._check_disk_layout_unchanged,
+ node, info)
+
+ def test__check_disk_layout_unchanged(self):
+ info = dict(INST_INFO_DICT)
+ info['ephemeral_gb'] = 10
+ info['swap_mb'] = 0
+ info['root_gb'] = 20
+ info['preserve_ephemeral'] = True
+ drv_internal_dict = {'instance': {'ephemeral_gb': 10,
+ 'swap_mb': 0,
+ 'root_gb': 20}}
+ drv_internal_dict.update(DRV_INTERNAL_INFO_DICT)
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=drv_internal_dict,
+ )
+ self.assertIsNone(iscsi_deploy._check_disk_layout_unchanged(node,
+ info))
+
+ def test__save_disk_layout(self):
+ info = dict(INST_INFO_DICT)
+ info['ephemeral_gb'] = 10
+ info['swap_mb'] = 0
+ info['root_gb'] = 10
+ info['preserve_ephemeral'] = False
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ iscsi_deploy._save_disk_layout(node, info)
+ node.refresh()
+ for param in ('ephemeral_gb', 'swap_mb', 'root_gb'):
+ self.assertEqual(
+ info[param], node.driver_internal_info['instance'][param]
+ )
+
+ def test_parse_instance_info_configdrive(self):
+ info = dict(INST_INFO_DICT)
+ info['configdrive'] = 'http://1.2.3.4/cd'
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ instance_info = iscsi_deploy.parse_instance_info(node)
+ self.assertEqual('http://1.2.3.4/cd', instance_info['configdrive'])
+
+ def test_parse_instance_info_nonglance_image(self):
+ info = INST_INFO_DICT.copy()
+ info['image_source'] = 'file:///image.qcow2'
+ info['kernel'] = 'file:///image.vmlinuz'
+ info['ramdisk'] = 'file:///image.initrd'
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ iscsi_deploy.parse_instance_info(node)
+
+ def test_parse_instance_info_nonglance_image_no_kernel(self):
+ info = INST_INFO_DICT.copy()
+ info['image_source'] = 'file:///image.qcow2'
+ info['ramdisk'] = 'file:///image.initrd'
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ self.assertRaises(exception.MissingParameterValue,
+ iscsi_deploy.parse_instance_info, node)
+
+ def test_parse_instance_info_whole_disk_image(self):
+ driver_internal_info = dict(DRV_INTERNAL_INFO_DICT)
+ driver_internal_info['is_whole_disk_image'] = True
+ node = obj_utils.create_test_node(
+ self.context, instance_info=INST_INFO_DICT,
+ driver_internal_info=driver_internal_info,
+ )
+ instance_info = iscsi_deploy.parse_instance_info(node)
+ self.assertIsNotNone(instance_info.get('image_source'))
+ self.assertIsNotNone(instance_info.get('root_gb'))
+ self.assertEqual(0, instance_info.get('swap_mb'))
+ self.assertEqual(0, instance_info.get('ephemeral_gb'))
+ self.assertIsNone(instance_info.get('configdrive'))
+
+ def test_parse_instance_info_whole_disk_image_missing_root(self):
+ info = dict(INST_INFO_DICT)
+ del info['root_gb']
+ node = obj_utils.create_test_node(self.context, instance_info=info)
+ self.assertRaises(exception.InvalidParameterValue,
+ iscsi_deploy.parse_instance_info, node)
+
+
+class IscsiDeployPrivateMethodsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IscsiDeployPrivateMethodsTestCase, self).setUp()
+ n = {
+ 'driver': 'fake_pxe',
+ 'instance_info': INST_INFO_DICT,
+ 'driver_info': DRV_INFO_DICT,
+ 'driver_internal_info': DRV_INTERNAL_INFO_DICT,
+ }
+ mgr_utils.mock_the_extension_manager(driver="fake_pxe")
+ self.node = obj_utils.create_test_node(self.context, **n)
+
+ def test__get_image_dir_path(self):
+ self.assertEqual(os.path.join(CONF.pxe.images_path,
+ self.node.uuid),
+ iscsi_deploy._get_image_dir_path(self.node.uuid))
+
+ def test__get_image_file_path(self):
+ self.assertEqual(os.path.join(CONF.pxe.images_path,
+ self.node.uuid,
+ 'disk'),
+ iscsi_deploy._get_image_file_path(self.node.uuid))
+
+
+class IscsiDeployMethodsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IscsiDeployMethodsTestCase, self).setUp()
+ instance_info = dict(INST_INFO_DICT)
+ instance_info['deploy_key'] = 'fake-56789'
+ n = {
+ 'driver': 'fake_pxe',
+ 'instance_info': instance_info,
+ 'driver_info': DRV_INFO_DICT,
+ 'driver_internal_info': DRV_INTERNAL_INFO_DICT,
+ }
+ mgr_utils.mock_the_extension_manager(driver="fake_pxe")
+ self.node = obj_utils.create_test_node(self.context, **n)
+
+ @mock.patch.object(deploy_utils, 'get_image_mb', autospec=True)
+ def test_check_image_size(self, get_image_mb_mock):
+ get_image_mb_mock.return_value = 1000
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.instance_info['root_gb'] = 1
+ iscsi_deploy.check_image_size(task)
+ get_image_mb_mock.assert_called_once_with(
+ iscsi_deploy._get_image_file_path(task.node.uuid))
+
+ @mock.patch.object(deploy_utils, 'get_image_mb', autospec=True)
+ def test_check_image_size_fails(self, get_image_mb_mock):
+ get_image_mb_mock.return_value = 1025
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.instance_info['root_gb'] = 1
+ self.assertRaises(exception.InstanceDeployFailure,
+ iscsi_deploy.check_image_size,
+ task)
+ get_image_mb_mock.assert_called_once_with(
+ iscsi_deploy._get_image_file_path(task.node.uuid))
+
+ @mock.patch.object(deploy_utils, 'fetch_images', autospec=True)
+ def test_cache_instance_images_master_path(self, mock_fetch_image):
+ temp_dir = tempfile.mkdtemp()
+ self.config(images_path=temp_dir, group='pxe')
+ self.config(instance_master_path=os.path.join(temp_dir,
+ 'instance_master_path'),
+ group='pxe')
+ fileutils.ensure_tree(CONF.pxe.instance_master_path)
+
+ (uuid, image_path) = iscsi_deploy.cache_instance_image(None, self.node)
+ mock_fetch_image.assert_called_once_with(None,
+ mock.ANY,
+ [(uuid, image_path)], True)
+ self.assertEqual('glance://image_uuid', uuid)
+ self.assertEqual(os.path.join(temp_dir,
+ self.node.uuid,
+ 'disk'),
+ image_path)
+
+ @mock.patch.object(utils, 'unlink_without_raise', autospec=True)
+ @mock.patch.object(utils, 'rmtree_without_raise', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'InstanceImageCache', autospec=True)
+ def test_destroy_images(self, mock_cache, mock_rmtree, mock_unlink):
+ self.config(images_path='/path', group='pxe')
+
+ iscsi_deploy.destroy_images('uuid')
+
+ mock_cache.return_value.clean_up.assert_called_once_with()
+ mock_unlink.assert_called_once_with('/path/uuid/disk')
+ mock_rmtree.assert_called_once_with('/path/uuid')
+
+ def _test_build_deploy_ramdisk_options(self, mock_alnum, api_url,
+ expected_root_device=None,
+ expected_boot_option='netboot',
+ expected_boot_mode='bios'):
+ fake_key = '0123456789ABCDEFGHIJKLMNOPQRSTUV'
+ fake_disk = 'fake-disk'
+
+ self.config(disk_devices=fake_disk, group='pxe')
+
+ mock_alnum.return_value = fake_key
+
+ expected_iqn = 'iqn.2008-10.org.openstack:%s' % self.node.uuid
+ expected_opts = {
+ 'iscsi_target_iqn': expected_iqn,
+ 'deployment_id': self.node.uuid,
+ 'deployment_key': fake_key,
+ 'disk': fake_disk,
+ 'ironic_api_url': api_url,
+ 'boot_option': expected_boot_option,
+ 'boot_mode': expected_boot_mode,
+ 'coreos.configdrive': 0,
+ }
+
+ if expected_root_device:
+ expected_opts['root_device'] = expected_root_device
+
+ opts = iscsi_deploy.build_deploy_ramdisk_options(self.node)
+
+ self.assertEqual(expected_opts, opts)
+ mock_alnum.assert_called_once_with(32)
+ # assert deploy_key was injected in the node
+ self.assertIn('deploy_key', self.node.instance_info)
+
+ @mock.patch.object(keystone, 'get_service_url', autospec=True)
+ @mock.patch.object(utils, 'random_alnum', autospec=True)
+ def test_build_deploy_ramdisk_options(self, mock_alnum, mock_get_url):
+ fake_api_url = 'http://127.0.0.1:6385'
+ self.config(api_url=fake_api_url, group='conductor')
+ self._test_build_deploy_ramdisk_options(mock_alnum, fake_api_url)
+
+ # As we are getting the Ironic api url from the config file
+ # assert keystone wasn't called
+ self.assertFalse(mock_get_url.called)
+
+ @mock.patch.object(keystone, 'get_service_url', autospec=True)
+ @mock.patch.object(utils, 'random_alnum', autospec=True)
+ def test_build_deploy_ramdisk_options_keystone(self, mock_alnum,
+ mock_get_url):
+ fake_api_url = 'http://127.0.0.1:6385'
+ mock_get_url.return_value = fake_api_url
+ self._test_build_deploy_ramdisk_options(mock_alnum, fake_api_url)
+
+ # As the Ironic api url is not specified in the config file
+ # assert we are getting it from keystone
+ mock_get_url.assert_called_once_with()
+
+ @mock.patch.object(keystone, 'get_service_url', autospec=True)
+ @mock.patch.object(utils, 'random_alnum', autospec=True)
+ def test_build_deploy_ramdisk_options_root_device(self, mock_alnum,
+ mock_get_url):
+ self.node.properties['root_device'] = {'wwn': 123456}
+ expected = 'wwn=123456'
+ fake_api_url = 'http://127.0.0.1:6385'
+ self.config(api_url=fake_api_url, group='conductor')
+ self._test_build_deploy_ramdisk_options(mock_alnum, fake_api_url,
+ expected_root_device=expected)
+
+ @mock.patch.object(keystone, 'get_service_url', autospec=True)
+ @mock.patch.object(utils, 'random_alnum', autospec=True)
+ def test_build_deploy_ramdisk_options_boot_option(self, mock_alnum,
+ mock_get_url):
+ self.node.instance_info = {'capabilities': '{"boot_option": "local"}'}
+ expected = 'local'
+ fake_api_url = 'http://127.0.0.1:6385'
+ self.config(api_url=fake_api_url, group='conductor')
+ self._test_build_deploy_ramdisk_options(mock_alnum, fake_api_url,
+ expected_boot_option=expected)
+
+ @mock.patch.object(keystone, 'get_service_url', autospec=True)
+ @mock.patch.object(utils, 'random_alnum', autospec=True)
+ def test_build_deploy_ramdisk_options_whole_disk_image(self, mock_alnum,
+ mock_get_url):
+ """Tests a hack to boot_option for whole disk images.
+
+ This hack is in place to fix bug #1441556.
+ """
+ self.node.instance_info = {'capabilities': '{"boot_option": "local"}'}
+ dii = self.node.driver_internal_info
+ dii['is_whole_disk_image'] = True
+ self.node.driver_internal_info = dii
+ self.node.save()
+ expected = 'netboot'
+ fake_api_url = 'http://127.0.0.1:6385'
+ self.config(api_url=fake_api_url, group='conductor')
+ self._test_build_deploy_ramdisk_options(mock_alnum, fake_api_url,
+ expected_boot_option=expected)
+
+ @mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'InstanceImageCache', autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(deploy_utils, 'deploy_partition_image', autospec=True)
+ def test_continue_deploy_fail(self, deploy_mock, power_mock,
+ mock_image_cache, mock_disk_layout):
+ kwargs = {'address': '123456', 'iqn': 'aaa-bbb', 'key': 'fake-56789'}
+ deploy_mock.side_effect = iter([
+ exception.InstanceDeployFailure("test deploy error")])
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ params = iscsi_deploy.get_deploy_info(task.node, **kwargs)
+ self.assertRaises(exception.InstanceDeployFailure,
+ iscsi_deploy.continue_deploy,
+ task, **kwargs)
+ self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+ self.assertIsNotNone(task.node.last_error)
+ deploy_mock.assert_called_once_with(**params)
+ power_mock.assert_called_once_with(task, states.POWER_OFF)
+ mock_image_cache.assert_called_once_with()
+ mock_image_cache.return_value.clean_up.assert_called_once_with()
+ self.assertFalse(mock_disk_layout.called)
+
+ @mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'InstanceImageCache', autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(deploy_utils, 'deploy_partition_image', autospec=True)
+ def test_continue_deploy_ramdisk_fails(self, deploy_mock, power_mock,
+ mock_image_cache, mock_disk_layout):
+ kwargs = {'address': '123456', 'iqn': 'aaa-bbb', 'key': 'fake-56789',
+ 'error': 'test ramdisk error'}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.InstanceDeployFailure,
+ iscsi_deploy.continue_deploy,
+ task, **kwargs)
+ self.assertIsNotNone(task.node.last_error)
+ self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+ power_mock.assert_called_once_with(task, states.POWER_OFF)
+ mock_image_cache.assert_called_once_with()
+ mock_image_cache.return_value.clean_up.assert_called_once_with()
+ self.assertFalse(deploy_mock.called)
+ self.assertFalse(mock_disk_layout.called)
+
+ @mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'InstanceImageCache', autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(deploy_utils, 'deploy_partition_image', autospec=True)
+ def test_continue_deploy_fail_no_root_uuid_or_disk_id(
+ self, deploy_mock, power_mock, mock_image_cache, mock_disk_layout):
+ kwargs = {'address': '123456', 'iqn': 'aaa-bbb', 'key': 'fake-56789'}
+ deploy_mock.return_value = {}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ params = iscsi_deploy.get_deploy_info(task.node, **kwargs)
+ self.assertRaises(exception.InstanceDeployFailure,
+ iscsi_deploy.continue_deploy,
+ task, **kwargs)
+ self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+ self.assertIsNotNone(task.node.last_error)
+ deploy_mock.assert_called_once_with(**params)
+ power_mock.assert_called_once_with(task, states.POWER_OFF)
+ mock_image_cache.assert_called_once_with()
+ mock_image_cache.return_value.clean_up.assert_called_once_with()
+ self.assertFalse(mock_disk_layout.called)
+
+ @mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'InstanceImageCache', autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(deploy_utils, 'deploy_partition_image', autospec=True)
+ def test_continue_deploy_fail_empty_root_uuid(
+ self, deploy_mock, power_mock, mock_image_cache, mock_disk_layout):
+ kwargs = {'address': '123456', 'iqn': 'aaa-bbb', 'key': 'fake-56789'}
+ deploy_mock.return_value = {'root uuid': ''}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ params = iscsi_deploy.get_deploy_info(task.node, **kwargs)
+ self.assertRaises(exception.InstanceDeployFailure,
+ iscsi_deploy.continue_deploy,
+ task, **kwargs)
+ self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+ self.assertIsNotNone(task.node.last_error)
+ deploy_mock.assert_called_once_with(**params)
+ power_mock.assert_called_once_with(task, states.POWER_OFF)
+ mock_image_cache.assert_called_once_with()
+ mock_image_cache.return_value.clean_up.assert_called_once_with()
+ self.assertFalse(mock_disk_layout.called)
+
+ @mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'LOG', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'get_deploy_info', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'InstanceImageCache', autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(deploy_utils, 'deploy_partition_image', autospec=True)
+ def test_continue_deploy(self, deploy_mock, power_mock, mock_image_cache,
+ mock_deploy_info, mock_log, mock_disk_layout):
+ kwargs = {'address': '123456', 'iqn': 'aaa-bbb', 'key': 'fake-56789'}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+
+ mock_deploy_info.return_value = {
+ 'address': '123456',
+ 'boot_option': 'netboot',
+ 'configdrive': "I've got the power",
+ 'ephemeral_format': None,
+ 'ephemeral_mb': 0,
+ 'image_path': (u'/var/lib/ironic/images/1be26c0b-03f2-4d2e-ae87-'
+ u'c02d7f33c123/disk'),
+ 'iqn': 'aaa-bbb',
+ 'lun': '1',
+ 'node_uuid': u'1be26c0b-03f2-4d2e-ae87-c02d7f33c123',
+ 'port': '3260',
+ 'preserve_ephemeral': True,
+ 'root_mb': 102400,
+ 'swap_mb': 0,
+ }
+ log_params = mock_deploy_info.return_value.copy()
+ # Make sure we don't log the full content of the configdrive
+ log_params['configdrive'] = '***'
+ expected_dict = {
+ 'node': self.node.uuid,
+ 'params': log_params,
+ }
+ uuid_dict_returned = {'root uuid': '12345678-87654321'}
+ deploy_mock.return_value = uuid_dict_returned
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ mock_log.isEnabledFor.return_value = True
+ retval = iscsi_deploy.continue_deploy(task, **kwargs)
+ mock_log.debug.assert_called_once_with(
+ mock.ANY, expected_dict)
+ self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+ self.assertIsNone(task.node.last_error)
+ mock_image_cache.assert_called_once_with()
+ mock_image_cache.return_value.clean_up.assert_called_once_with()
+ self.assertEqual(uuid_dict_returned, retval)
+ mock_disk_layout.assert_called_once_with(task.node, mock.ANY)
+
+ @mock.patch.object(iscsi_deploy, 'LOG', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'get_deploy_info', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'InstanceImageCache', autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(deploy_utils, 'deploy_disk_image', autospec=True)
+ def test_continue_deploy_whole_disk_image(
+ self, deploy_mock, power_mock, mock_image_cache, mock_deploy_info,
+ mock_log):
+ kwargs = {'address': '123456', 'iqn': 'aaa-bbb', 'key': 'fake-56789'}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+
+ mock_deploy_info.return_value = {
+ 'address': '123456',
+ 'image_path': (u'/var/lib/ironic/images/1be26c0b-03f2-4d2e-ae87-'
+ u'c02d7f33c123/disk'),
+ 'iqn': 'aaa-bbb',
+ 'lun': '1',
+ 'node_uuid': u'1be26c0b-03f2-4d2e-ae87-c02d7f33c123',
+ 'port': '3260',
+ }
+ log_params = mock_deploy_info.return_value.copy()
+ expected_dict = {
+ 'node': self.node.uuid,
+ 'params': log_params,
+ }
+ uuid_dict_returned = {'disk identifier': '87654321'}
+ deploy_mock.return_value = uuid_dict_returned
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = True
+ mock_log.isEnabledFor.return_value = True
+ retval = iscsi_deploy.continue_deploy(task, **kwargs)
+ mock_log.debug.assert_called_once_with(
+ mock.ANY, expected_dict)
+ self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+ self.assertIsNone(task.node.last_error)
+ mock_image_cache.assert_called_once_with()
+ mock_image_cache.return_value.clean_up.assert_called_once_with()
+ self.assertEqual(uuid_dict_returned, retval)
+
+ def test_get_deploy_info_boot_option_default(self):
+ instance_info = self.node.instance_info
+ instance_info['deploy_key'] = 'key'
+ self.node.instance_info = instance_info
+ kwargs = {'address': '1.1.1.1', 'iqn': 'target-iqn', 'key': 'key'}
+ ret_val = iscsi_deploy.get_deploy_info(self.node, **kwargs)
+ self.assertEqual('1.1.1.1', ret_val['address'])
+ self.assertEqual('target-iqn', ret_val['iqn'])
+ self.assertEqual('netboot', ret_val['boot_option'])
+
+ def test_get_deploy_info_netboot_specified(self):
+ instance_info = self.node.instance_info
+ instance_info['deploy_key'] = 'key'
+ instance_info['capabilities'] = {'boot_option': 'netboot'}
+ self.node.instance_info = instance_info
+ kwargs = {'address': '1.1.1.1', 'iqn': 'target-iqn', 'key': 'key'}
+ ret_val = iscsi_deploy.get_deploy_info(self.node, **kwargs)
+ self.assertEqual('1.1.1.1', ret_val['address'])
+ self.assertEqual('target-iqn', ret_val['iqn'])
+ self.assertEqual('netboot', ret_val['boot_option'])
+
+ def test_get_deploy_info_localboot(self):
+ instance_info = self.node.instance_info
+ instance_info['deploy_key'] = 'key'
+ instance_info['capabilities'] = {'boot_option': 'local'}
+ self.node.instance_info = instance_info
+ kwargs = {'address': '1.1.1.1', 'iqn': 'target-iqn', 'key': 'key'}
+ ret_val = iscsi_deploy.get_deploy_info(self.node, **kwargs)
+ self.assertEqual('1.1.1.1', ret_val['address'])
+ self.assertEqual('target-iqn', ret_val['iqn'])
+ self.assertEqual('local', ret_val['boot_option'])
+
+ @mock.patch.object(iscsi_deploy, 'continue_deploy', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options',
+ autospec=True)
+ def test_do_agent_iscsi_deploy_okay(self, build_options_mock,
+ continue_deploy_mock):
+ build_options_mock.return_value = {'deployment_key': 'abcdef',
+ 'iscsi_target_iqn': 'iqn-qweqwe'}
+ agent_client_mock = mock.MagicMock(spec_set=agent_client.AgentClient)
+ agent_client_mock.start_iscsi_target.return_value = {
+ 'command_status': 'SUCCESS', 'command_error': None}
+ driver_internal_info = {'agent_url': 'http://1.2.3.4:1234'}
+ self.node.driver_internal_info = driver_internal_info
+ self.node.save()
+ uuid_dict_returned = {'root uuid': 'some-root-uuid'}
+ continue_deploy_mock.return_value = uuid_dict_returned
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ret_val = iscsi_deploy.do_agent_iscsi_deploy(
+ task, agent_client_mock)
+ build_options_mock.assert_called_once_with(task.node)
+ agent_client_mock.start_iscsi_target.assert_called_once_with(
+ task.node, 'iqn-qweqwe')
+ continue_deploy_mock.assert_called_once_with(
+ task, error=None, iqn='iqn-qweqwe', key='abcdef',
+ address='1.2.3.4')
+ self.assertEqual(
+ 'some-root-uuid',
+ task.node.driver_internal_info['root_uuid_or_disk_id'])
+ self.assertEqual(ret_val, uuid_dict_returned)
+
+ @mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options',
+ autospec=True)
+ def test_do_agent_iscsi_deploy_start_iscsi_failure(self,
+ build_options_mock):
+ build_options_mock.return_value = {'deployment_key': 'abcdef',
+ 'iscsi_target_iqn': 'iqn-qweqwe'}
+ agent_client_mock = mock.MagicMock(spec_set=agent_client.AgentClient)
+ agent_client_mock.start_iscsi_target.return_value = {
+ 'command_status': 'FAILED', 'command_error': 'booom'}
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.InstanceDeployFailure,
+ iscsi_deploy.do_agent_iscsi_deploy,
+ task, agent_client_mock)
+ build_options_mock.assert_called_once_with(task.node)
+ agent_client_mock.start_iscsi_target.assert_called_once_with(
+ task.node, 'iqn-qweqwe')
+ self.node.refresh()
+ self.assertEqual(states.DEPLOYFAIL, self.node.provision_state)
+ self.assertEqual(states.ACTIVE, self.node.target_provision_state)
+ self.assertIsNotNone(self.node.last_error)
+
+ def test_validate_pass_bootloader_info_input(self):
+ params = {'key': 'some-random-key', 'address': '1.2.3.4',
+ 'error': '', 'status': 'SUCCEEDED'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.instance_info['deploy_key'] = 'some-random-key'
+ # Assert that the method doesn't raise
+ iscsi_deploy.validate_pass_bootloader_info_input(task, params)
+
+ def test_validate_pass_bootloader_info_missing_status(self):
+ params = {'key': 'some-random-key', 'address': '1.2.3.4'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.MissingParameterValue,
+ iscsi_deploy.validate_pass_bootloader_info_input,
+ task, params)
+
+ def test_validate_pass_bootloader_info_missing_key(self):
+ params = {'status': 'SUCCEEDED', 'address': '1.2.3.4'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.MissingParameterValue,
+ iscsi_deploy.validate_pass_bootloader_info_input,
+ task, params)
+
+ def test_validate_pass_bootloader_info_missing_address(self):
+ params = {'status': 'SUCCEEDED', 'key': 'some-random-key'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.MissingParameterValue,
+ iscsi_deploy.validate_pass_bootloader_info_input,
+ task, params)
+
+ def test_validate_pass_bootloader_info_input_invalid_key(self):
+ params = {'key': 'some-other-key', 'address': '1.2.3.4',
+ 'status': 'SUCCEEDED'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.instance_info['deploy_key'] = 'some-random-key'
+ self.assertRaises(exception.InvalidParameterValue,
+ iscsi_deploy.validate_pass_bootloader_info_input,
+ task, params)
+
+ def test_validate_bootloader_install_status(self):
+ kwargs = {'key': 'abcdef', 'status': 'SUCCEEDED', 'error': ''}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.instance_info['deploy_key'] = 'abcdef'
+ # Nothing much to assert except that it shouldn't raise.
+ iscsi_deploy.validate_bootloader_install_status(task, kwargs)
+
+ @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
+ def test_validate_bootloader_install_status_install_failed(
+ self, set_fail_state_mock):
+ kwargs = {'key': 'abcdef', 'status': 'FAILED', 'error': 'some-error'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.provision_state = states.DEPLOYING
+ task.node.target_provision_state = states.ACTIVE
+ task.node.instance_info['deploy_key'] = 'abcdef'
+ self.assertRaises(exception.InstanceDeployFailure,
+ iscsi_deploy.validate_bootloader_install_status,
+ task, kwargs)
+ set_fail_state_mock.assert_called_once_with(task, mock.ANY)
+
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed',
+ autospec=True)
+ def test_finish_deploy(self, notify_mock):
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ iscsi_deploy.finish_deploy(task, '1.2.3.4')
+ notify_mock.assert_called_once_with('1.2.3.4')
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+
+ @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed',
+ autospec=True)
+ def test_finish_deploy_notify_fails(self, notify_mock,
+ set_fail_state_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ notify_mock.side_effect = RuntimeError()
+ self.assertRaises(exception.InstanceDeployFailure,
+ iscsi_deploy.finish_deploy, task, '1.2.3.4')
+ set_fail_state_mock.assert_called_once_with(task, mock.ANY)
+
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed',
+ autospec=True)
+ def test_finish_deploy_ssh_with_local_boot(self, notify_mock,
+ node_power_mock):
+ instance_info = dict(INST_INFO_DICT)
+ instance_info['capabilities'] = {'boot_option': 'local'}
+ n = {
+ 'uuid': uuidutils.generate_uuid(),
+ 'driver': 'fake_ssh',
+ 'instance_info': instance_info,
+ 'provision_state': states.DEPLOYING,
+ 'target_provision_state': states.ACTIVE,
+ }
+ mgr_utils.mock_the_extension_manager(driver="fake_ssh")
+ node = obj_utils.create_test_node(self.context, **n)
+
+ with task_manager.acquire(self.context, node.uuid,
+ shared=False) as task:
+ iscsi_deploy.finish_deploy(task, '1.2.3.4')
+ notify_mock.assert_called_once_with('1.2.3.4')
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+ node_power_mock.assert_called_once_with(task, states.REBOOT)
+
+ @mock.patch.object(keystone, 'get_service_url', autospec=True)
+ def test_validate_good_api_url_from_config_file(self, mock_ks):
+ # not present in the keystone catalog
+ mock_ks.side_effect = exception.KeystoneFailure
+ self.config(group='conductor', api_url='http://foo')
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ iscsi_deploy.validate(task)
+ self.assertFalse(mock_ks.called)
+
+ @mock.patch.object(keystone, 'get_service_url', autospec=True)
+ def test_validate_good_api_url_from_keystone(self, mock_ks):
+ # present in the keystone catalog
+ mock_ks.return_value = 'http://127.0.0.1:1234'
+ # not present in the config file
+ self.config(group='conductor', api_url=None)
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ iscsi_deploy.validate(task)
+ mock_ks.assert_called_once_with()
+
+ @mock.patch.object(keystone, 'get_service_url', autospec=True)
+ def test_validate_fail_no_api_url(self, mock_ks):
+ # not present in the keystone catalog
+ mock_ks.side_effect = exception.KeystoneFailure
+ # not present in the config file
+ self.config(group='conductor', api_url=None)
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ iscsi_deploy.validate, task)
+ mock_ks.assert_called_once_with()
+
+ def test_validate_invalid_root_device_hints(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.node.properties['root_device'] = {'size': 'not-int'}
+ self.assertRaises(exception.InvalidParameterValue,
+ iscsi_deploy.validate, task)
+
+
+class ISCSIDeployTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(ISCSIDeployTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="fake_pxe")
+ self.driver = driver_factory.get_driver("fake_pxe")
+ self.driver.vendor = iscsi_deploy.VendorPassthru()
+ self.node = obj_utils.create_test_node(
+ self.context, driver='fake_pxe',
+ instance_info=INST_INFO_DICT,
+ driver_info=DRV_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ self.node.driver_internal_info['agent_url'] = 'http://1.2.3.4:1234'
+ self.task = mock.MagicMock(spec=task_manager.TaskManager)
+ self.task.shared = False
+ self.task.node = self.node
+ self.task.driver = self.driver
+ self.task.context = self.context
+
+ def test_get_properties(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertEqual({}, task.driver.deploy.get_properties())
+
+ @mock.patch.object(iscsi_deploy, 'validate', autospec=True)
+ @mock.patch.object(deploy_utils, 'validate_capabilities', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
+ def test_validate(self, pxe_validate_mock,
+ validate_capabilities_mock, validate_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+
+ task.driver.deploy.validate(task)
+
+ pxe_validate_mock.assert_called_once_with(task.driver.boot, task)
+ validate_capabilities_mock.assert_called_once_with(task.node)
+ validate_mock.assert_called_once_with(task)
+
+ @mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
+ def test_prepare_node_active(self, prepare_instance_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.node.provision_state = states.ACTIVE
+
+ task.driver.deploy.prepare(task)
+
+ prepare_instance_mock.assert_called_once_with(
+ task.driver.boot, task)
+
+ @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options',
+ autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', autospec=True)
+ def test_prepare_node_deploying(self, mock_prepare_ramdisk,
+ mock_iscsi_options, mock_agent_options):
+ mock_iscsi_options.return_value = {'a': 'b'}
+ mock_agent_options.return_value = {'c': 'd'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.node.provision_state = states.DEPLOYWAIT
+
+ task.driver.deploy.prepare(task)
+
+ mock_iscsi_options.assert_called_once_with(task.node)
+ mock_agent_options.assert_called_once_with(task.node)
+ mock_prepare_ramdisk.assert_called_once_with(
+ task.driver.boot, task, {'a': 'b', 'c': 'd'})
+
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'check_image_size', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'cache_instance_image', autospec=True)
+ def test_deploy(self, mock_cache_instance_image,
+ mock_check_image_size, mock_node_power_action):
+ with task_manager.acquire(self.context,
+ self.node.uuid, shared=False) as task:
+ state = task.driver.deploy.deploy(task)
+ self.assertEqual(state, states.DEPLOYWAIT)
+ mock_cache_instance_image.assert_called_once_with(
+ self.context, task.node)
+ mock_check_image_size.assert_called_once_with(task)
+ mock_node_power_action.assert_called_once_with(task, states.REBOOT)
+
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ def test_tear_down(self, node_power_action_mock):
+ with task_manager.acquire(self.context,
+ self.node.uuid, shared=False) as task:
+ state = task.driver.deploy.tear_down(task)
+ self.assertEqual(state, states.DELETED)
+ node_power_action_mock.assert_called_once_with(task,
+ states.POWER_OFF)
+
+ @mock.patch.object(pxe.PXEBoot, 'clean_up_instance', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'destroy_images', autospec=True)
+ def test_clean_up(self, destroy_images_mock, clean_up_ramdisk_mock,
+ clean_up_instance_mock):
+ with task_manager.acquire(self.context,
+ self.node.uuid, shared=False) as task:
+ task.driver.deploy.clean_up(task)
+ destroy_images_mock.assert_called_once_with(task.node.uuid)
+ clean_up_ramdisk_mock.assert_called_once_with(
+ task.driver.boot, task)
+ clean_up_instance_mock.assert_called_once_with(
+ task.driver.boot, task)
+
+
+class TestVendorPassthru(db_base.DbTestCase):
+
+ def setUp(self):
+ super(TestVendorPassthru, self).setUp()
+ mgr_utils.mock_the_extension_manager()
+ self.driver = driver_factory.get_driver("fake")
+ self.driver.vendor = iscsi_deploy.VendorPassthru()
+ self.node = obj_utils.create_test_node(
+ self.context, driver='fake',
+ instance_info=INST_INFO_DICT,
+ driver_info=DRV_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ self.node.driver_internal_info['agent_url'] = 'http://1.2.3.4:1234'
+ self.task = mock.MagicMock(spec=task_manager.TaskManager)
+ self.task.shared = False
+ self.task.node = self.node
+ self.task.driver = self.driver
+ self.task.context = self.context
+
+ def test_validate_good(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.node.instance_info['deploy_key'] = 'fake-56789'
+ task.driver.vendor.validate(task, method='pass_deploy_info',
+ address='123456', iqn='aaa-bbb',
+ key='fake-56789')
+
+ def test_validate_fail(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.vendor.validate,
+ task, method='pass_deploy_info',
+ key='fake-56789')
+
+ def test_validate_key_notmatch(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.vendor.validate,
+ task, method='pass_deploy_info',
+ address='123456', iqn='aaa-bbb',
+ key='fake-12345')
+
+ @mock.patch.object(fake.FakeBoot, 'prepare_instance', autospec=True)
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed',
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'InstanceImageCache', autospec=True)
+ @mock.patch.object(deploy_utils, 'deploy_partition_image', autospec=True)
+ def _test_pass_deploy_info_deploy(self, is_localboot, mock_deploy,
+ mock_image_cache,
+ notify_mock,
+ fakeboot_prepare_instance_mock):
+ # set local boot
+ i_info = self.node.instance_info
+ if is_localboot:
+ i_info['capabilities'] = '{"boot_option": "local"}'
+
+ i_info['deploy_key'] = 'fake-56789'
+ self.node.instance_info = i_info
+
+ self.node.power_state = states.POWER_ON
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+
+ root_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
+ mock_deploy.return_value = {'root uuid': root_uuid}
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.driver.vendor.pass_deploy_info(
+ task, address='123456', iqn='aaa-bbb', key='fake-56789')
+
+ self.node.refresh()
+ self.assertEqual(states.POWER_ON, self.node.power_state)
+ self.assertIn('root_uuid_or_disk_id', self.node.driver_internal_info)
+ self.assertIsNone(self.node.last_error)
+ mock_image_cache.assert_called_once_with()
+ mock_image_cache.return_value.clean_up.assert_called_once_with()
+ notify_mock.assert_called_once_with('123456')
+ fakeboot_prepare_instance_mock.assert_called_once_with(mock.ANY, task)
+
+ @mock.patch.object(fake.FakeBoot, 'prepare_instance', autospec=True)
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed',
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'InstanceImageCache', autospec=True)
+ @mock.patch.object(deploy_utils, 'deploy_disk_image', autospec=True)
+ def _test_pass_deploy_info_whole_disk_image(self, is_localboot,
+ mock_deploy,
+ mock_image_cache,
+ notify_mock,
+ fakeboot_prep_inst_mock):
+ i_info = self.node.instance_info
+ # set local boot
+ if is_localboot:
+ i_info['capabilities'] = '{"boot_option": "local"}'
+
+ i_info['deploy_key'] = 'fake-56789'
+ self.node.instance_info = i_info
+
+ self.node.power_state = states.POWER_ON
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+
+ disk_id = '0x12345678'
+ mock_deploy.return_value = {'disk identifier': disk_id}
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = True
+ task.driver.vendor.pass_deploy_info(task, address='123456',
+ iqn='aaa-bbb',
+ key='fake-56789')
+
+ self.node.refresh()
+ self.assertEqual(states.POWER_ON, self.node.power_state)
+ self.assertIsNone(self.node.last_error)
+ mock_image_cache.assert_called_once_with()
+ mock_image_cache.return_value.clean_up.assert_called_once_with()
+ notify_mock.assert_called_once_with('123456')
+ fakeboot_prep_inst_mock.assert_called_once_with(mock.ANY, task)
+
+ def test_pass_deploy_info_deploy(self):
+ self._test_pass_deploy_info_deploy(False)
+ self.assertEqual(states.ACTIVE, self.node.provision_state)
+ self.assertEqual(states.NOSTATE, self.node.target_provision_state)
+
+ def test_pass_deploy_info_localboot(self):
+ self._test_pass_deploy_info_deploy(True)
+ self.assertEqual(states.DEPLOYWAIT, self.node.provision_state)
+ self.assertEqual(states.ACTIVE, self.node.target_provision_state)
+
+ def test_pass_deploy_info_whole_disk_image(self):
+ self._test_pass_deploy_info_whole_disk_image(False)
+ self.assertEqual(states.ACTIVE, self.node.provision_state)
+ self.assertEqual(states.NOSTATE, self.node.target_provision_state)
+
+ def test_pass_deploy_info_whole_disk_image_localboot(self):
+ self._test_pass_deploy_info_whole_disk_image(True)
+ self.assertEqual(states.ACTIVE, self.node.provision_state)
+ self.assertEqual(states.NOSTATE, self.node.target_provision_state)
+
+ def test_pass_deploy_info_invalid(self):
+ self.node.power_state = states.POWER_ON
+ self.node.provision_state = states.AVAILABLE
+ self.node.target_provision_state = states.NOSTATE
+ self.node.save()
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.InvalidState,
+ task.driver.vendor.pass_deploy_info,
+ task, address='123456', iqn='aaa-bbb',
+ key='fake-56789', error='test ramdisk error')
+
+ self.node.refresh()
+ self.assertEqual(states.AVAILABLE, self.node.provision_state)
+ self.assertEqual(states.NOSTATE, self.node.target_provision_state)
+ self.assertEqual(states.POWER_ON, self.node.power_state)
+
+ @mock.patch.object(iscsi_deploy.VendorPassthru, 'pass_deploy_info')
+ def test_pass_deploy_info_lock_elevated(self, mock_deploy_info):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.driver.vendor.pass_deploy_info(
+ task, address='123456', iqn='aaa-bbb', key='fake-56789')
+
+ # lock elevated w/o exception
+ self.assertEqual(1, mock_deploy_info.call_count,
+ "pass_deploy_info was not called once.")
+
+ def test_vendor_routes(self):
+ expected = ['heartbeat', 'pass_deploy_info',
+ 'pass_bootloader_install_info']
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ vendor_routes = task.driver.vendor.vendor_routes
+ self.assertIsInstance(vendor_routes, dict)
+ self.assertEqual(sorted(expected), sorted(list(vendor_routes)))
+
+ def test_driver_routes(self):
+ expected = ['lookup']
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ driver_routes = task.driver.vendor.driver_routes
+ self.assertIsInstance(driver_routes, dict)
+ self.assertEqual(sorted(expected), sorted(list(driver_routes)))
+
+ @mock.patch.object(iscsi_deploy, 'validate_bootloader_install_status',
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'finish_deploy', autospec=True)
+ def test_pass_bootloader_install_info(self, finish_deploy_mock,
+ validate_input_mock):
+ kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.pass_bootloader_install_info(task, **kwargs)
+ finish_deploy_mock.assert_called_once_with(task, '123456')
+ validate_input_mock.assert_called_once_with(task, kwargs)
+
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'reboot_and_finish_deploy', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy', autospec=True)
+ def test_continue_deploy_netboot(self, do_agent_iscsi_deploy_mock,
+ reboot_and_finish_deploy_mock):
+
+ uuid_dict_returned = {'root uuid': 'some-root-uuid'}
+ do_agent_iscsi_deploy_mock.return_value = uuid_dict_returned
+ self.driver.vendor.continue_deploy(self.task)
+ do_agent_iscsi_deploy_mock.assert_called_once_with(
+ self.task, self.driver.vendor._client)
+ reboot_and_finish_deploy_mock.assert_called_once_with(
+ mock.ANY, self.task)
+
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'reboot_and_finish_deploy', autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'configure_local_boot', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy', autospec=True)
+ def test_continue_deploy_localboot(self, do_agent_iscsi_deploy_mock,
+ configure_local_boot_mock,
+ reboot_and_finish_deploy_mock):
+
+ self.node.instance_info = {
+ 'capabilities': {'boot_option': 'local'}}
+ self.node.save()
+ uuid_dict_returned = {'root uuid': 'some-root-uuid'}
+ do_agent_iscsi_deploy_mock.return_value = uuid_dict_returned
+
+ self.driver.vendor.continue_deploy(self.task)
+ do_agent_iscsi_deploy_mock.assert_called_once_with(
+ self.task, self.driver.vendor._client)
+ configure_local_boot_mock.assert_called_once_with(
+ self.task.driver.vendor, self.task, root_uuid='some-root-uuid',
+ efi_system_part_uuid=None)
+ reboot_and_finish_deploy_mock.assert_called_once_with(
+ self.task.driver.vendor, self.task)
+
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'reboot_and_finish_deploy', autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'configure_local_boot', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy', autospec=True)
+ def test_continue_deploy_localboot_uefi(self, do_agent_iscsi_deploy_mock,
+ configure_local_boot_mock,
+ reboot_and_finish_deploy_mock):
+
+ self.node.instance_info = {
+ 'capabilities': {'boot_option': 'local'}}
+ self.node.save()
+ uuid_dict_returned = {'root uuid': 'some-root-uuid',
+ 'efi system partition uuid': 'efi-part-uuid'}
+ do_agent_iscsi_deploy_mock.return_value = uuid_dict_returned
+
+ self.driver.vendor.continue_deploy(self.task)
+ do_agent_iscsi_deploy_mock.assert_called_once_with(
+ self.task, self.driver.vendor._client)
+ configure_local_boot_mock.assert_called_once_with(
+ self.task.driver.vendor, self.task, root_uuid='some-root-uuid',
+ efi_system_part_uuid='efi-part-uuid')
+ reboot_and_finish_deploy_mock.assert_called_once_with(
+ self.task.driver.vendor, self.task)
+
+
+# Cleanup of iscsi_deploy with pxe boot interface
+class CleanUpFullFlowTestCase(db_base.DbTestCase):
+ def setUp(self):
+ super(CleanUpFullFlowTestCase, self).setUp()
+ self.config(image_cache_size=0, group='pxe')
+
+ # Configure node
+ mgr_utils.mock_the_extension_manager(driver="fake_pxe")
+ instance_info = INST_INFO_DICT
+ instance_info['deploy_key'] = 'fake-56789'
+ self.node = obj_utils.create_test_node(
+ self.context, driver='fake_pxe',
+ instance_info=instance_info,
+ driver_info=DRV_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ self.port = obj_utils.create_test_port(self.context,
+ node_id=self.node.id)
+
+ # Configure temporary directories
+ pxe_temp_dir = tempfile.mkdtemp()
+ self.config(tftp_root=pxe_temp_dir, group='pxe')
+ tftp_master_dir = os.path.join(CONF.pxe.tftp_root,
+ 'tftp_master')
+ self.config(tftp_master_path=tftp_master_dir, group='pxe')
+ os.makedirs(tftp_master_dir)
+
+ instance_temp_dir = tempfile.mkdtemp()
+ self.config(images_path=instance_temp_dir,
+ group='pxe')
+ instance_master_dir = os.path.join(CONF.pxe.images_path,
+ 'instance_master')
+ self.config(instance_master_path=instance_master_dir,
+ group='pxe')
+ os.makedirs(instance_master_dir)
+ self.pxe_config_dir = os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg')
+ os.makedirs(self.pxe_config_dir)
+
+ # Populate some file names
+ self.master_kernel_path = os.path.join(CONF.pxe.tftp_master_path,
+ 'kernel')
+ self.master_instance_path = os.path.join(CONF.pxe.instance_master_path,
+ 'image_uuid')
+ self.node_tftp_dir = os.path.join(CONF.pxe.tftp_root,
+ self.node.uuid)
+ os.makedirs(self.node_tftp_dir)
+ self.kernel_path = os.path.join(self.node_tftp_dir,
+ 'kernel')
+ self.node_image_dir = iscsi_deploy._get_image_dir_path(self.node.uuid)
+ os.makedirs(self.node_image_dir)
+ self.image_path = iscsi_deploy._get_image_file_path(self.node.uuid)
+ self.config_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
+ self.mac_path = pxe_utils._get_pxe_mac_path(self.port.address)
+
+ # Create files
+ self.files = [self.config_path, self.master_kernel_path,
+ self.master_instance_path]
+ for fname in self.files:
+ # NOTE(dtantsur): files with 0 size won't be cleaned up
+ with open(fname, 'w') as fp:
+ fp.write('test')
+
+ os.link(self.config_path, self.mac_path)
+ os.link(self.master_kernel_path, self.kernel_path)
+ os.link(self.master_instance_path, self.image_path)
+
+ @mock.patch.object(pxe, '_get_instance_image_info', autospec=True)
+ @mock.patch.object(pxe, '_get_deploy_image_info', autospec=True)
+ def test_clean_up_with_master(self, mock_get_deploy_image_info,
+ mock_get_instance_image_info):
+ image_info = {'kernel': ('kernel_uuid',
+ self.kernel_path)}
+ mock_get_instance_image_info.return_value = image_info
+ mock_get_deploy_image_info.return_value = {}
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.driver.deploy.clean_up(task)
+ mock_get_instance_image_info.assert_called_with(task.node,
+ task.context)
+ mock_get_deploy_image_info.assert_called_with(task.node)
+ for path in ([self.kernel_path, self.image_path, self.config_path]
+ + self.files):
+ self.assertFalse(os.path.exists(path),
+ '%s is not expected to exist' % path)
diff --git a/ironic/tests/unit/drivers/modules/test_seamicro.py b/ironic/tests/unit/drivers/modules/test_seamicro.py
new file mode 100644
index 000000000..63d816112
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/test_seamicro.py
@@ -0,0 +1,676 @@
+# 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.
+
+"""Test class for Ironic SeaMicro driver."""
+
+import uuid
+
+import mock
+from oslo_utils import uuidutils
+from seamicroclient import client as seamicro_client
+from seamicroclient import exceptions as seamicro_client_exception
+
+from ironic.common import boot_devices
+from ironic.common import driver_factory
+from ironic.common import exception
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.drivers.modules import console_utils
+from ironic.drivers.modules import seamicro
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+INFO_DICT = db_utils.get_test_seamicro_info()
+
+
+class Fake_Server(object):
+ def __init__(self, active=False, *args, **kwargs):
+ self.active = active
+ self.nic = {'0': {'untaggedVlan': ''}}
+
+ def power_on(self):
+ self.active = True
+
+ def power_off(self, force=False):
+ self.active = False
+
+ def reset(self):
+ self.active = True
+
+ def set_untagged_vlan(self, vlan_id):
+ return
+
+ def attach_volume(self, volume_id):
+ return
+
+ def detach_volume(self):
+ return
+
+ def set_boot_order(self, boot_order):
+ return
+
+ def refresh(self, wait=0):
+ return self
+
+
+class Fake_Volume(object):
+ def __init__(self, id=None, *args, **kwargs):
+ if id is None:
+ self.id = "%s/%s/%s" % ("0", "ironic-p6-6", str(uuid.uuid4()))
+ else:
+ self.id = id
+
+
+class Fake_Pool(object):
+ def __init__(self, freeSize=None, *args, **kwargs):
+ self.freeSize = freeSize
+
+
+class SeaMicroValidateParametersTestCase(db_base.DbTestCase):
+
+ def test__parse_driver_info_good(self):
+ # make sure we get back the expected things
+ node = obj_utils.get_test_node(
+ self.context,
+ driver='fake_seamicro',
+ driver_info=INFO_DICT)
+ info = seamicro._parse_driver_info(node)
+ self.assertIsNotNone(info.get('api_endpoint'))
+ self.assertIsNotNone(info.get('username'))
+ self.assertIsNotNone(info.get('password'))
+ self.assertIsNotNone(info.get('server_id'))
+ self.assertIsNotNone(info.get('uuid'))
+
+ def test__parse_driver_info_missing_api_endpoint(self):
+ # make sure error is raised when info is missing
+ info = dict(INFO_DICT)
+ del info['seamicro_api_endpoint']
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ self.assertRaises(exception.MissingParameterValue,
+ seamicro._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_missing_username(self):
+ # make sure error is raised when info is missing
+ info = dict(INFO_DICT)
+ del info['seamicro_username']
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ self.assertRaises(exception.MissingParameterValue,
+ seamicro._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_missing_password(self):
+ # make sure error is raised when info is missing
+ info = dict(INFO_DICT)
+ del info['seamicro_password']
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ self.assertRaises(exception.MissingParameterValue,
+ seamicro._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_missing_server_id(self):
+ # make sure error is raised when info is missing
+ info = dict(INFO_DICT)
+ del info['seamicro_server_id']
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ self.assertRaises(exception.MissingParameterValue,
+ seamicro._parse_driver_info,
+ node)
+
+
+@mock.patch('eventlet.greenthread.sleep', lambda n: None)
+class SeaMicroPrivateMethodsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(SeaMicroPrivateMethodsTestCase, self).setUp()
+ n = {
+ 'driver': 'fake_seamicro',
+ 'driver_info': INFO_DICT
+ }
+ self.node = obj_utils.create_test_node(self.context, **n)
+ self.Server = Fake_Server
+ self.Volume = Fake_Volume
+ self.Pool = Fake_Pool
+ self.config(action_timeout=0, group='seamicro')
+ self.config(max_retry=2, group='seamicro')
+
+ self.info = seamicro._parse_driver_info(self.node)
+
+ @mock.patch.object(seamicro_client, "Client", autospec=True)
+ def test__get_client(self, mock_client):
+ args = {'username': self.info['username'],
+ 'password': self.info['password'],
+ 'auth_url': self.info['api_endpoint']}
+ seamicro._get_client(**self.info)
+ mock_client.assert_called_once_with(self.info['api_version'], **args)
+
+ @mock.patch.object(seamicro_client, "Client", autospec=True)
+ def test__get_client_fail(self, mock_client):
+ args = {'username': self.info['username'],
+ 'password': self.info['password'],
+ 'auth_url': self.info['api_endpoint']}
+ mock_client.side_effect = seamicro_client_exception.UnsupportedVersion
+ self.assertRaises(exception.InvalidParameterValue,
+ seamicro._get_client,
+ **self.info)
+ mock_client.assert_called_once_with(self.info['api_version'], **args)
+
+ @mock.patch.object(seamicro, "_get_server", autospec=True)
+ def test__get_power_status_on(self, mock_get_server):
+ mock_get_server.return_value = self.Server(active=True)
+ pstate = seamicro._get_power_status(self.node)
+ self.assertEqual(states.POWER_ON, pstate)
+
+ @mock.patch.object(seamicro, "_get_server", autospec=True)
+ def test__get_power_status_off(self, mock_get_server):
+ mock_get_server.return_value = self.Server(active=False)
+ pstate = seamicro._get_power_status(self.node)
+ self.assertEqual(states.POWER_OFF, pstate)
+
+ @mock.patch.object(seamicro, "_get_server", autospec=True)
+ def test__get_power_status_error(self, mock_get_server):
+ mock_get_server.return_value = self.Server(active=None)
+ pstate = seamicro._get_power_status(self.node)
+ self.assertEqual(states.ERROR, pstate)
+
+ @mock.patch.object(seamicro, "_get_server", autospec=True)
+ def test__power_on_good(self, mock_get_server):
+ mock_get_server.return_value = self.Server(active=False)
+ pstate = seamicro._power_on(self.node)
+ self.assertEqual(states.POWER_ON, pstate)
+
+ @mock.patch.object(seamicro, "_get_server", autospec=True)
+ def test__power_on_fail(self, mock_get_server):
+ def fake_power_on():
+ return
+
+ server = self.Server(active=False)
+ server.power_on = fake_power_on
+ mock_get_server.return_value = server
+ pstate = seamicro._power_on(self.node)
+ self.assertEqual(states.ERROR, pstate)
+
+ @mock.patch.object(seamicro, "_get_server", autospec=True)
+ def test__power_off_good(self, mock_get_server):
+ mock_get_server.return_value = self.Server(active=True)
+ pstate = seamicro._power_off(self.node)
+ self.assertEqual(states.POWER_OFF, pstate)
+
+ @mock.patch.object(seamicro, "_get_server", autospec=True)
+ def test__power_off_fail(self, mock_get_server):
+ def fake_power_off():
+ return
+ server = self.Server(active=True)
+ server.power_off = fake_power_off
+ mock_get_server.return_value = server
+ pstate = seamicro._power_off(self.node)
+ self.assertEqual(states.ERROR, pstate)
+
+ @mock.patch.object(seamicro, "_get_server", autospec=True)
+ def test__reboot_good(self, mock_get_server):
+ mock_get_server.return_value = self.Server(active=True)
+ pstate = seamicro._reboot(self.node)
+ self.assertEqual(states.POWER_ON, pstate)
+
+ @mock.patch.object(seamicro, "_get_server", autospec=True)
+ def test__reboot_fail(self, mock_get_server):
+ def fake_reboot():
+ return
+ server = self.Server(active=False)
+ server.reset = fake_reboot
+ mock_get_server.return_value = server
+ pstate = seamicro._reboot(self.node)
+ self.assertEqual(states.ERROR, pstate)
+
+ @mock.patch.object(seamicro, "_get_volume", autospec=True)
+ def test__validate_fail(self, mock_get_volume):
+ volume_id = "0/p6-6/vol1"
+ volume = self.Volume()
+ volume.id = volume_id
+ mock_get_volume.return_value = volume
+ self.assertRaises(exception.InvalidParameterValue,
+ seamicro._validate_volume, self.info, volume_id)
+
+ @mock.patch.object(seamicro, "_get_volume", autospec=True)
+ def test__validate_good(self, mock_get_volume):
+ volume = self.Volume()
+ mock_get_volume.return_value = volume
+ valid = seamicro._validate_volume(self.info, volume.id)
+ self.assertEqual(valid, True)
+
+ @mock.patch.object(seamicro, "_get_pools", autospec=True)
+ def test__create_volume_fail(self, mock_get_pools):
+ mock_get_pools.return_value = None
+ self.assertRaises(exception.IronicException,
+ seamicro._create_volume,
+ self.info, 2)
+
+ @mock.patch.object(seamicro, "_get_pools", autospec=True)
+ @mock.patch.object(seamicro, "_get_client", autospec=True)
+ def test__create_volume_good(self, mock_get_client, mock_get_pools):
+ pools = [self.Pool(1), self.Pool(6), self.Pool(5)]
+ mock_seamicro_volumes = mock.MagicMock(spec_set=['create'])
+ mock_get_client.return_value = mock.MagicMock(
+ volumes=mock_seamicro_volumes, spec_set=['volumes'])
+ mock_get_pools.return_value = pools
+ seamicro._create_volume(self.info, 2)
+
+
+class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(SeaMicroPowerDriverTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_seamicro')
+ self.driver = driver_factory.get_driver('fake_seamicro')
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_seamicro',
+ driver_info=INFO_DICT)
+ self.get_server_patcher = mock.patch.object(seamicro, '_get_server',
+ autospec=True)
+
+ self.get_server_mock = None
+ self.Server = Fake_Server
+ self.Volume = Fake_Volume
+ self.info = seamicro._parse_driver_info(self.node)
+
+ def test_get_properties(self):
+ expected = seamicro.COMMON_PROPERTIES
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=True) as task:
+ self.assertEqual(expected, task.driver.power.get_properties())
+
+ expected = (list(seamicro.COMMON_PROPERTIES) +
+ list(seamicro.CONSOLE_PROPERTIES))
+ console_properties = task.driver.console.get_properties().keys()
+ self.assertEqual(sorted(expected), sorted(console_properties))
+ self.assertEqual(sorted(expected),
+ sorted(task.driver.get_properties().keys()))
+
+ def test_vendor_routes(self):
+ expected = ['set_node_vlan_id', 'attach_volume']
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ vendor_routes = task.driver.vendor.vendor_routes
+ self.assertIsInstance(vendor_routes, dict)
+ self.assertEqual(sorted(expected), sorted(vendor_routes))
+
+ def test_driver_routes(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ driver_routes = task.driver.vendor.driver_routes
+ self.assertIsInstance(driver_routes, dict)
+ self.assertEqual({}, driver_routes)
+
+ @mock.patch.object(seamicro, '_parse_driver_info', autospec=True)
+ def test_power_interface_validate_good(self, parse_drv_info_mock):
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=True) as task:
+ task.driver.power.validate(task)
+ self.assertEqual(1, parse_drv_info_mock.call_count)
+
+ @mock.patch.object(seamicro, '_parse_driver_info', autospec=True)
+ def test_power_interface_validate_fails(self, parse_drv_info_mock):
+ side_effect = iter([exception.InvalidParameterValue("Bad input")])
+ parse_drv_info_mock.side_effect = side_effect
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=True) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.power.validate, task)
+ self.assertEqual(1, parse_drv_info_mock.call_count)
+
+ @mock.patch.object(seamicro, '_reboot', autospec=True)
+ def test_reboot(self, mock_reboot):
+ mock_reboot.return_value = states.POWER_ON
+
+ with task_manager.acquire(self.context, self.info['uuid'],
+ shared=False) as task:
+ task.driver.power.reboot(task)
+
+ mock_reboot.assert_called_once_with(task.node)
+
+ def test_set_power_state_bad_state(self):
+ self.get_server_mock = self.get_server_patcher.start()
+ self.get_server_mock.return_value = self.Server()
+
+ with task_manager.acquire(self.context, self.info['uuid'],
+ shared=False) as task:
+ self.assertRaises(exception.IronicException,
+ task.driver.power.set_power_state,
+ task, "BAD_PSTATE")
+ self.get_server_patcher.stop()
+
+ @mock.patch.object(seamicro, '_power_on', autospec=True)
+ def test_set_power_state_on_good(self, mock_power_on):
+ mock_power_on.return_value = states.POWER_ON
+
+ with task_manager.acquire(self.context, self.info['uuid'],
+ shared=False) as task:
+ task.driver.power.set_power_state(task, states.POWER_ON)
+
+ mock_power_on.assert_called_once_with(task.node)
+
+ @mock.patch.object(seamicro, '_power_on', autospec=True)
+ def test_set_power_state_on_fail(self, mock_power_on):
+ mock_power_on.return_value = states.POWER_OFF
+
+ with task_manager.acquire(self.context, self.info['uuid'],
+ shared=False) as task:
+ self.assertRaises(exception.PowerStateFailure,
+ task.driver.power.set_power_state,
+ task, states.POWER_ON)
+
+ mock_power_on.assert_called_once_with(task.node)
+
+ @mock.patch.object(seamicro, '_power_off', autospec=True)
+ def test_set_power_state_off_good(self, mock_power_off):
+ mock_power_off.return_value = states.POWER_OFF
+
+ with task_manager.acquire(self.context, self.info['uuid'],
+ shared=False) as task:
+ task.driver.power.set_power_state(task, states.POWER_OFF)
+
+ mock_power_off.assert_called_once_with(task.node)
+
+ @mock.patch.object(seamicro, '_power_off', autospec=True)
+ def test_set_power_state_off_fail(self, mock_power_off):
+ mock_power_off.return_value = states.POWER_ON
+
+ with task_manager.acquire(self.context, self.info['uuid'],
+ shared=False) as task:
+ self.assertRaises(exception.PowerStateFailure,
+ task.driver.power.set_power_state,
+ task, states.POWER_OFF)
+
+ mock_power_off.assert_called_once_with(task.node)
+
+ @mock.patch.object(seamicro, '_parse_driver_info', autospec=True)
+ def test_vendor_passthru_validate_good(self, mock_info):
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=True) as task:
+ for method in task.driver.vendor.vendor_routes:
+ task.driver.vendor.validate(task, **{'method': method})
+ self.assertEqual(len(task.driver.vendor.vendor_routes),
+ mock_info.call_count)
+
+ @mock.patch.object(seamicro, '_parse_driver_info', autospec=True)
+ def test_vendor_passthru_validate_parse_driver_info_fail(self, mock_info):
+ mock_info.side_effect = iter([exception.InvalidParameterValue("bad")])
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=True) as task:
+ method = list(task.driver.vendor.vendor_routes)[0]
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.vendor.validate,
+ task, **{'method': method})
+ mock_info.assert_called_once_with(task.node)
+
+ @mock.patch.object(seamicro, '_get_server', autospec=True)
+ def test_set_node_vlan_id_good(self, mock_get_server):
+ vlan_id = "12"
+ mock_get_server.return_value = self.Server(active="true")
+ with task_manager.acquire(self.context, self.info['uuid'],
+ shared=False) as task:
+ kwargs = {'vlan_id': vlan_id}
+ task.driver.vendor.set_node_vlan_id(task, **kwargs)
+ mock_get_server.assert_called_once_with(self.info)
+
+ def test_set_node_vlan_id_no_input(self):
+ with task_manager.acquire(self.context, self.info['uuid'],
+ shared=False) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.vendor.set_node_vlan_id,
+ task, **{})
+
+ @mock.patch.object(seamicro, '_get_server', autospec=True)
+ def test_set_node_vlan_id_fail(self, mock_get_server):
+ def fake_set_untagged_vlan(self, **kwargs):
+ raise seamicro_client_exception.ClientException(500)
+
+ vlan_id = "12"
+ server = self.Server(active="true")
+ server.set_untagged_vlan = fake_set_untagged_vlan
+ mock_get_server.return_value = server
+ with task_manager.acquire(self.context, self.info['uuid'],
+ shared=False) as task:
+ kwargs = {'vlan_id': vlan_id}
+ self.assertRaises(exception.IronicException,
+ task.driver.vendor.set_node_vlan_id,
+ task, **kwargs)
+
+ mock_get_server.assert_called_once_with(self.info)
+
+ @mock.patch.object(seamicro, '_get_server', autospec=True)
+ @mock.patch.object(seamicro, '_validate_volume', autospec=True)
+ def test_attach_volume_with_volume_id_good(self, mock_validate_volume,
+ mock_get_server):
+ volume_id = '0/ironic-p6-1/vol1'
+ mock_validate_volume.return_value = True
+ mock_get_server.return_value = self.Server(active="true")
+ with task_manager.acquire(self.context, self.info['uuid'],
+ shared=False) as task:
+ kwargs = {'volume_id': volume_id}
+ task.driver.vendor.attach_volume(task, **kwargs)
+ mock_get_server.assert_called_once_with(self.info)
+
+ @mock.patch.object(seamicro, '_get_server', autospec=True)
+ @mock.patch.object(seamicro, '_get_volume', autospec=True)
+ def test_attach_volume_with_invalid_volume_id_fail(self,
+ mock_get_volume,
+ mock_get_server):
+ volume_id = '0/p6-1/vol1'
+ mock_get_volume.return_value = self.Volume(volume_id)
+ mock_get_server.return_value = self.Server(active="true")
+ with task_manager.acquire(self.context, self.info['uuid'],
+ shared=False) as task:
+ kwargs = {'volume_id': volume_id}
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.vendor.attach_volume,
+ task, **kwargs)
+
+ @mock.patch.object(seamicro, '_get_server', autospec=True)
+ @mock.patch.object(seamicro, '_validate_volume', autospec=True)
+ def test_attach_volume_fail(self, mock_validate_volume,
+ mock_get_server):
+ def fake_attach_volume(self, **kwargs):
+ raise seamicro_client_exception.ClientException(500)
+
+ volume_id = '0/p6-1/vol1'
+ mock_validate_volume.return_value = True
+ server = self.Server(active="true")
+ server.attach_volume = fake_attach_volume
+ mock_get_server.return_value = server
+ with task_manager.acquire(self.context, self.info['uuid'],
+ shared=False) as task:
+ kwargs = {'volume_id': volume_id}
+ self.assertRaises(exception.IronicException,
+ task.driver.vendor.attach_volume,
+ task, **kwargs)
+
+ mock_get_server.assert_called_once_with(self.info)
+
+ @mock.patch.object(seamicro, '_get_server', autospec=True)
+ @mock.patch.object(seamicro, '_validate_volume', autospec=True)
+ @mock.patch.object(seamicro, '_create_volume', autospec=True)
+ def test_attach_volume_with_volume_size_good(self, mock_create_volume,
+ mock_validate_volume,
+ mock_get_server):
+ volume_id = '0/ironic-p6-1/vol1'
+ volume_size = 2
+ mock_create_volume.return_value = volume_id
+ mock_validate_volume.return_value = True
+ mock_get_server.return_value = self.Server(active="true")
+ with task_manager.acquire(self.context, self.info['uuid'],
+ shared=False) as task:
+ kwargs = {'volume_size': volume_size}
+ task.driver.vendor.attach_volume(task, **kwargs)
+ mock_get_server.assert_called_once_with(self.info)
+ mock_create_volume.assert_called_once_with(self.info, volume_size)
+
+ def test_attach_volume_with_no_input_fail(self):
+ with task_manager.acquire(self.context, self.info['uuid'],
+ shared=False) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.vendor.attach_volume, task,
+ **{})
+
+ @mock.patch.object(seamicro, '_get_server', autospec=True)
+ def test_set_boot_device_good(self, mock_get_server):
+ boot_device = "disk"
+ mock_get_server.return_value = self.Server(active="true")
+ with task_manager.acquire(self.context, self.info['uuid'],
+ shared=False) as task:
+ task.driver.management.set_boot_device(task, boot_device)
+ mock_get_server.assert_called_once_with(self.info)
+
+ @mock.patch.object(seamicro, '_get_server', autospec=True)
+ def test_set_boot_device_invalid_device_fail(self, mock_get_server):
+ boot_device = "invalid_device"
+ mock_get_server.return_value = self.Server(active="true")
+ with task_manager.acquire(self.context, self.info['uuid'],
+ shared=False) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.management.set_boot_device,
+ task, boot_device)
+
+ @mock.patch.object(seamicro, '_get_server', autospec=True)
+ def test_set_boot_device_fail(self, mock_get_server):
+ def fake_set_boot_order(self, **kwargs):
+ raise seamicro_client_exception.ClientException(500)
+
+ boot_device = "pxe"
+ server = self.Server(active="true")
+ server.set_boot_order = fake_set_boot_order
+ mock_get_server.return_value = server
+ with task_manager.acquire(self.context, self.info['uuid'],
+ shared=False) as task:
+ self.assertRaises(exception.IronicException,
+ task.driver.management.set_boot_device,
+ task, boot_device)
+
+ mock_get_server.assert_called_once_with(self.info)
+
+ def test_management_interface_get_supported_boot_devices(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ expected = [boot_devices.PXE, boot_devices.DISK]
+ self.assertEqual(sorted(expected), sorted(task.driver.management.
+ get_supported_boot_devices(task)))
+
+ def test_management_interface_get_boot_device(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ expected = {'boot_device': None, 'persistent': None}
+ self.assertEqual(expected,
+ task.driver.management.get_boot_device(task))
+
+ def test_management_interface_validate_good(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.driver.management.validate(task)
+
+ def test_management_interface_validate_fail(self):
+ # Missing SEAMICRO driver_info information
+ node = obj_utils.create_test_node(self.context,
+ uuid=uuidutils.generate_uuid(),
+ driver='fake_seamicro')
+ with task_manager.acquire(self.context, node.uuid) as task:
+ self.assertRaises(exception.MissingParameterValue,
+ task.driver.management.validate, task)
+
+
+class SeaMicroDriverTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(SeaMicroDriverTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_seamicro')
+ self.driver = driver_factory.get_driver('fake_seamicro')
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_seamicro',
+ driver_info=INFO_DICT)
+ self.get_server_patcher = mock.patch.object(seamicro, '_get_server',
+ autospec=True)
+
+ self.get_server_mock = None
+ self.Server = Fake_Server
+ self.Volume = Fake_Volume
+ self.info = seamicro._parse_driver_info(self.node)
+
+ @mock.patch.object(console_utils, 'start_shellinabox_console',
+ autospec=True)
+ def test_start_console(self, mock_exec):
+ mock_exec.return_value = None
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.driver.console.start_console(task)
+
+ mock_exec.assert_called_once_with(self.info['uuid'],
+ self.info['port'],
+ mock.ANY)
+
+ @mock.patch.object(console_utils, 'start_shellinabox_console',
+ autospec=True)
+ def test_start_console_fail(self, mock_exec):
+ mock_exec.side_effect = iter(
+ [exception.ConsoleSubprocessFailed(error='error')])
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.assertRaises(exception.ConsoleSubprocessFailed,
+ self.driver.console.start_console,
+ task)
+
+ @mock.patch.object(console_utils, 'stop_shellinabox_console',
+ autospec=True)
+ def test_stop_console(self, mock_exec):
+ mock_exec.return_value = None
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.driver.console.stop_console(task)
+
+ mock_exec.assert_called_once_with(self.info['uuid'])
+
+ @mock.patch.object(console_utils, 'stop_shellinabox_console',
+ autospec=True)
+ def test_stop_console_fail(self, mock_stop):
+ mock_stop.side_effect = iter([exception.ConsoleError()])
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.assertRaises(exception.ConsoleError,
+ self.driver.console.stop_console,
+ task)
+
+ mock_stop.assert_called_once_with(self.node.uuid)
+
+ @mock.patch.object(console_utils, 'start_shellinabox_console',
+ autospec=True)
+ def test_start_console_fail_nodir(self, mock_exec):
+ mock_exec.side_effect = iter([exception.ConsoleError()])
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ self.assertRaises(exception.ConsoleError,
+ self.driver.console.start_console,
+ task)
+ mock_exec.assert_called_once_with(self.node.uuid, mock.ANY, mock.ANY)
+
+ @mock.patch.object(console_utils, 'get_shellinabox_console_url',
+ autospec=True)
+ def test_get_console(self, mock_exec):
+ url = 'http://localhost:4201'
+ mock_exec.return_value = url
+ expected = {'type': 'shellinabox', 'url': url}
+
+ with task_manager.acquire(self.context,
+ self.node.uuid) as task:
+ console_info = self.driver.console.get_console(task)
+
+ self.assertEqual(expected, console_info)
+ mock_exec.assert_called_once_with(self.info['port'])
diff --git a/ironic/tests/unit/drivers/modules/test_snmp.py b/ironic/tests/unit/drivers/modules/test_snmp.py
new file mode 100644
index 000000000..5842739f0
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/test_snmp.py
@@ -0,0 +1,1263 @@
+# Copyright 2013,2014 Cray Inc
+#
+# Authors: David Hewson <dhewson@cray.com>
+# Stig Telfer <stelfer@cray.com>
+# Mark Goddard <mgoddard@cray.com>
+#
+# 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
+# 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.
+
+"""Test class for SNMP power driver module."""
+
+import mock
+from oslo_config import cfg
+from pysnmp.entity.rfc3413.oneliner import cmdgen
+from pysnmp import error as snmp_error
+
+from ironic.common import exception
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.drivers.modules import snmp as snmp
+from ironic.tests.unit import base
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+CONF = cfg.CONF
+INFO_DICT = db_utils.get_test_snmp_info()
+
+
+@mock.patch.object(cmdgen, 'CommandGenerator', autospec=True)
+class SNMPClientTestCase(base.TestCase):
+ def setUp(self):
+ super(SNMPClientTestCase, self).setUp()
+ self.address = '1.2.3.4'
+ self.port = '6700'
+ self.oid = 'oid'
+ self.value = 'value'
+
+ def test___init__(self, mock_cmdgen):
+ client = snmp.SNMPClient(self.address, self.port, snmp.SNMP_V1)
+ mock_cmdgen.assert_called_once_with()
+ self.assertEqual(self.address, client.address)
+ self.assertEqual(self.port, client.port)
+ self.assertEqual(snmp.SNMP_V1, client.version)
+ self.assertIsNone(client.community)
+ self.assertFalse('security' in client.__dict__)
+ self.assertEqual(mock_cmdgen.return_value, client.cmd_gen)
+
+ @mock.patch.object(cmdgen, 'CommunityData', autospec=True)
+ def test__get_auth_v1(self, mock_community, mock_cmdgen):
+ client = snmp.SNMPClient(self.address, self.port, snmp.SNMP_V1)
+ client._get_auth()
+ mock_cmdgen.assert_called_once_with()
+ mock_community.assert_called_once_with(client.community, mpModel=0)
+
+ @mock.patch.object(cmdgen, 'UsmUserData', autospec=True)
+ def test__get_auth_v3(self, mock_user, mock_cmdgen):
+ client = snmp.SNMPClient(self.address, self.port, snmp.SNMP_V3)
+ client._get_auth()
+ mock_cmdgen.assert_called_once_with()
+ mock_user.assert_called_once_with(client.security)
+
+ @mock.patch.object(cmdgen, 'UdpTransportTarget', autospec=True)
+ def test__get_transport(self, mock_transport, mock_cmdgen):
+ client = snmp.SNMPClient(self.address, self.port, snmp.SNMP_V3)
+ client._get_transport()
+ mock_cmdgen.assert_called_once_with()
+ mock_transport.assert_called_once_with((client.address, client.port))
+
+ @mock.patch.object(cmdgen, 'UdpTransportTarget', autospec=True)
+ def test__get_transport_err(self, mock_transport, mock_cmdgen):
+ mock_transport.side_effect = snmp_error.PySnmpError
+ client = snmp.SNMPClient(self.address, self.port, snmp.SNMP_V3)
+ self.assertRaises(snmp_error.PySnmpError, client._get_transport)
+ mock_cmdgen.assert_called_once_with()
+ mock_transport.assert_called_once_with((client.address, client.port))
+
+ @mock.patch.object(snmp.SNMPClient, '_get_transport', autospec=True)
+ @mock.patch.object(snmp.SNMPClient, '_get_auth', autospec=True)
+ def test_get(self, mock_auth, mock_transport, mock_cmdgen):
+ var_bind = (self.oid, self.value)
+ mock_cmdgenerator = mock_cmdgen.return_value
+ mock_cmdgenerator.getCmd.return_value = ("", None, 0, [var_bind])
+ client = snmp.SNMPClient(self.address, self.port, snmp.SNMP_V3)
+ val = client.get(self.oid)
+ self.assertEqual(var_bind[1], val)
+ mock_cmdgenerator.getCmd.assert_called_once_with(mock.ANY, mock.ANY,
+ self.oid)
+
+ @mock.patch.object(snmp.SNMPClient, '_get_transport', autospec=True)
+ @mock.patch.object(snmp.SNMPClient, '_get_auth', autospec=True)
+ def test_get_err_transport(self, mock_auth, mock_transport, mock_cmdgen):
+ mock_transport.side_effect = snmp_error.PySnmpError
+ var_bind = (self.oid, self.value)
+ mock_cmdgenerator = mock_cmdgen.return_value
+ mock_cmdgenerator.getCmd.return_value = ("engine error", None, 0,
+ [var_bind])
+ client = snmp.SNMPClient(self.address, self.port, snmp.SNMP_V3)
+ self.assertRaises(exception.SNMPFailure, client.get, self.oid)
+ self.assertFalse(mock_cmdgenerator.getCmd.called)
+
+ @mock.patch.object(snmp.SNMPClient, '_get_transport', autospec=True)
+ @mock.patch.object(snmp.SNMPClient, '_get_auth', autospec=True)
+ def test_get_err_engine(self, mock_auth, mock_transport, mock_cmdgen):
+ var_bind = (self.oid, self.value)
+ mock_cmdgenerator = mock_cmdgen.return_value
+ mock_cmdgenerator.getCmd.return_value = ("engine error", None, 0,
+ [var_bind])
+ client = snmp.SNMPClient(self.address, self.port, snmp.SNMP_V3)
+ self.assertRaises(exception.SNMPFailure, client.get, self.oid)
+ mock_cmdgenerator.getCmd.assert_called_once_with(mock.ANY, mock.ANY,
+ self.oid)
+
+ @mock.patch.object(snmp.SNMPClient, '_get_transport', autospec=True)
+ @mock.patch.object(snmp.SNMPClient, '_get_auth', autospec=True)
+ def test_set(self, mock_auth, mock_transport, mock_cmdgen):
+ var_bind = (self.oid, self.value)
+ mock_cmdgenerator = mock_cmdgen.return_value
+ mock_cmdgenerator.setCmd.return_value = ("", None, 0, [var_bind])
+ client = snmp.SNMPClient(self.address, self.port, snmp.SNMP_V3)
+ client.set(self.oid, self.value)
+ mock_cmdgenerator.setCmd.assert_called_once_with(mock.ANY, mock.ANY,
+ var_bind)
+
+ @mock.patch.object(snmp.SNMPClient, '_get_transport', autospec=True)
+ @mock.patch.object(snmp.SNMPClient, '_get_auth', autospec=True)
+ def test_set_err_transport(self, mock_auth, mock_transport, mock_cmdgen):
+ mock_transport.side_effect = snmp_error.PySnmpError
+ var_bind = (self.oid, self.value)
+ mock_cmdgenerator = mock_cmdgen.return_value
+ mock_cmdgenerator.setCmd.return_value = ("engine error", None, 0,
+ [var_bind])
+ client = snmp.SNMPClient(self.address, self.port, snmp.SNMP_V3)
+ self.assertRaises(exception.SNMPFailure,
+ client.set, self.oid, self.value)
+ self.assertFalse(mock_cmdgenerator.setCmd.called)
+
+ @mock.patch.object(snmp.SNMPClient, '_get_transport', autospec=True)
+ @mock.patch.object(snmp.SNMPClient, '_get_auth', autospec=True)
+ def test_set_err_engine(self, mock_auth, mock_transport, mock_cmdgen):
+ var_bind = (self.oid, self.value)
+ mock_cmdgenerator = mock_cmdgen.return_value
+ mock_cmdgenerator.setCmd.return_value = ("engine error", None, 0,
+ [var_bind])
+ client = snmp.SNMPClient(self.address, self.port, snmp.SNMP_V3)
+ self.assertRaises(exception.SNMPFailure,
+ client.set, self.oid, self.value)
+ mock_cmdgenerator.setCmd.assert_called_once_with(mock.ANY, mock.ANY,
+ var_bind)
+
+
+class SNMPValidateParametersTestCase(db_base.DbTestCase):
+
+ def _get_test_node(self, driver_info):
+ return obj_utils.get_test_node(
+ self.context,
+ driver_info=driver_info)
+
+ def test__parse_driver_info_default(self):
+ # Make sure we get back the expected things.
+ node = self._get_test_node(INFO_DICT)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual(INFO_DICT['snmp_driver'], info.get('driver'))
+ self.assertEqual(INFO_DICT['snmp_address'], info.get('address'))
+ self.assertEqual(INFO_DICT['snmp_port'], str(info.get('port')))
+ self.assertEqual(INFO_DICT['snmp_outlet'], info.get('outlet'))
+ self.assertEqual(INFO_DICT['snmp_version'], info.get('version'))
+ self.assertEqual(INFO_DICT.get('snmp_community'),
+ info.get('community'))
+ self.assertEqual(INFO_DICT.get('snmp_security'),
+ info.get('security'))
+
+ def test__parse_driver_info_apc(self):
+ # Make sure the APC driver type is parsed.
+ info = db_utils.get_test_snmp_info(snmp_driver='apc')
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual('apc', info.get('driver'))
+
+ def test__parse_driver_info_apc_masterswitch(self):
+ # Make sure the APC driver type is parsed.
+ info = db_utils.get_test_snmp_info(snmp_driver='apc_masterswitch')
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual('apc_masterswitch', info.get('driver'))
+
+ def test__parse_driver_info_apc_masterswitchplus(self):
+ # Make sure the APC driver type is parsed.
+ info = db_utils.get_test_snmp_info(snmp_driver='apc_masterswitchplus')
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual('apc_masterswitchplus', info.get('driver'))
+
+ def test__parse_driver_info_apc_rackpdu(self):
+ # Make sure the APC driver type is parsed.
+ info = db_utils.get_test_snmp_info(snmp_driver='apc_rackpdu')
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual('apc_rackpdu', info.get('driver'))
+
+ def test__parse_driver_info_aten(self):
+ # Make sure the Aten driver type is parsed.
+ info = db_utils.get_test_snmp_info(snmp_driver='aten')
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual('aten', info.get('driver'))
+
+ def test__parse_driver_info_cyberpower(self):
+ # Make sure the CyberPower driver type is parsed.
+ info = db_utils.get_test_snmp_info(snmp_driver='cyberpower')
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual('cyberpower', info.get('driver'))
+
+ def test__parse_driver_info_eatonpower(self):
+ # Make sure the Eaton Power driver type is parsed.
+ info = db_utils.get_test_snmp_info(snmp_driver='eatonpower')
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual('eatonpower', info.get('driver'))
+
+ def test__parse_driver_info_teltronix(self):
+ # Make sure the Teltronix driver type is parsed.
+ info = db_utils.get_test_snmp_info(snmp_driver='teltronix')
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual('teltronix', info.get('driver'))
+
+ def test__parse_driver_info_snmp_v1(self):
+ # Make sure SNMPv1 is parsed with a community string.
+ info = db_utils.get_test_snmp_info(snmp_version='1',
+ snmp_community='public')
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual('1', info.get('version'))
+ self.assertEqual('public', info.get('community'))
+
+ def test__parse_driver_info_snmp_v2c(self):
+ # Make sure SNMPv2c is parsed with a community string.
+ info = db_utils.get_test_snmp_info(snmp_version='2c',
+ snmp_community='private')
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual('2c', info.get('version'))
+ self.assertEqual('private', info.get('community'))
+
+ def test__parse_driver_info_snmp_v3(self):
+ # Make sure SNMPv3 is parsed with a security string.
+ info = db_utils.get_test_snmp_info(snmp_version='3',
+ snmp_security='pass')
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual('3', info.get('version'))
+ self.assertEqual('pass', info.get('security'))
+
+ def test__parse_driver_info_snmp_port_default(self):
+ # Make sure default SNMP UDP port numbers are correct
+ info = dict(INFO_DICT)
+ del info['snmp_port']
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual(161, info.get('port'))
+
+ def test__parse_driver_info_snmp_port(self):
+ # Make sure non-default SNMP UDP port numbers can be configured
+ info = db_utils.get_test_snmp_info(snmp_port='10161')
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual(10161, info.get('port'))
+
+ def test__parse_driver_info_missing_driver(self):
+ # Make sure exception is raised when the driver type is missing.
+ info = dict(INFO_DICT)
+ del info['snmp_driver']
+ node = self._get_test_node(info)
+ self.assertRaises(exception.MissingParameterValue,
+ snmp._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_invalid_driver(self):
+ # Make sure exception is raised when the driver type is invalid.
+ info = db_utils.get_test_snmp_info(snmp_driver='invalidpower')
+ node = self._get_test_node(info)
+ self.assertRaises(exception.InvalidParameterValue,
+ snmp._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_missing_address(self):
+ # Make sure exception is raised when the address is missing.
+ info = dict(INFO_DICT)
+ del info['snmp_address']
+ node = self._get_test_node(info)
+ self.assertRaises(exception.MissingParameterValue,
+ snmp._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_missing_outlet(self):
+ # Make sure exception is raised when the outlet is missing.
+ info = dict(INFO_DICT)
+ del info['snmp_outlet']
+ node = self._get_test_node(info)
+ self.assertRaises(exception.MissingParameterValue,
+ snmp._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_default_version(self):
+ # Make sure version defaults to 1 when it is missing.
+ info = dict(INFO_DICT)
+ del info['snmp_version']
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual('1', info.get('version'))
+ self.assertEqual(INFO_DICT['snmp_community'], info.get('community'))
+
+ def test__parse_driver_info_invalid_version(self):
+ # Make sure exception is raised when version is invalid.
+ info = db_utils.get_test_snmp_info(snmp_version='42',
+ snmp_community='public',
+ snmp_security='pass')
+ node = self._get_test_node(info)
+ self.assertRaises(exception.InvalidParameterValue,
+ snmp._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_default_version_and_missing_community(self):
+ # Make sure exception is raised when version and community are missing.
+ info = dict(INFO_DICT)
+ del info['snmp_version']
+ del info['snmp_community']
+ node = self._get_test_node(info)
+ self.assertRaises(exception.MissingParameterValue,
+ snmp._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_missing_community_snmp_v1(self):
+ # Make sure exception is raised when community is missing with SNMPv1.
+ info = dict(INFO_DICT)
+ del info['snmp_community']
+ node = self._get_test_node(info)
+ self.assertRaises(exception.MissingParameterValue,
+ snmp._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_missing_community_snmp_v2c(self):
+ # Make sure exception is raised when community is missing with SNMPv2c.
+ info = db_utils.get_test_snmp_info(snmp_version='2c')
+ del info['snmp_community']
+ node = self._get_test_node(info)
+ self.assertRaises(exception.MissingParameterValue,
+ snmp._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_missing_security(self):
+ # Make sure exception is raised when security is missing with SNMPv3.
+ info = db_utils.get_test_snmp_info(snmp_version='3')
+ del info['snmp_security']
+ node = self._get_test_node(info)
+ self.assertRaises(exception.MissingParameterValue,
+ snmp._parse_driver_info,
+ node)
+
+
+@mock.patch.object(snmp, '_get_client', autospec=True)
+class SNMPDeviceDriverTestCase(db_base.DbTestCase):
+ """Tests for the SNMP device-specific driver classes.
+
+ The SNMP client object is mocked to allow various error cases to be tested.
+ """
+
+ def setUp(self):
+ super(SNMPDeviceDriverTestCase, self).setUp()
+ self.node = obj_utils.get_test_node(
+ self.context,
+ driver='fake_snmp',
+ driver_info=INFO_DICT)
+
+ def _update_driver_info(self, **kwargs):
+ self.node["driver_info"].update(**kwargs)
+
+ def _set_snmp_driver(self, snmp_driver):
+ self._update_driver_info(snmp_driver=snmp_driver)
+
+ def _get_snmp_failure(self):
+ return exception.SNMPFailure(operation='test-operation',
+ error='test-error')
+
+ def test_power_state_on(self, mock_get_client):
+ # Ensure the power on state is queried correctly
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = driver.value_power_on
+ pstate = driver.power_state()
+ mock_client.get.assert_called_once_with(driver._snmp_oid())
+ self.assertEqual(states.POWER_ON, pstate)
+
+ def test_power_state_off(self, mock_get_client):
+ # Ensure the power off state is queried correctly
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = driver.value_power_off
+ pstate = driver.power_state()
+ mock_client.get.assert_called_once_with(driver._snmp_oid())
+ self.assertEqual(states.POWER_OFF, pstate)
+
+ def test_power_state_error(self, mock_get_client):
+ # Ensure an unexpected power state returns an error
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = 42
+ pstate = driver.power_state()
+ mock_client.get.assert_called_once_with(driver._snmp_oid())
+ self.assertEqual(states.ERROR, pstate)
+
+ def test_power_state_snmp_failure(self, mock_get_client):
+ # Ensure SNMP failure exceptions raised during a query are propagated
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.side_effect = self._get_snmp_failure()
+ self.assertRaises(exception.SNMPFailure,
+ driver.power_state)
+ mock_client.get.assert_called_once_with(driver._snmp_oid())
+
+ def test_power_on(self, mock_get_client):
+ # Ensure the device is powered on correctly
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = driver.value_power_on
+ pstate = driver.power_on()
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_on)
+ mock_client.get.assert_called_once_with(driver._snmp_oid())
+ self.assertEqual(states.POWER_ON, pstate)
+
+ def test_power_off(self, mock_get_client):
+ # Ensure the device is powered off correctly
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = driver.value_power_off
+ pstate = driver.power_off()
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_off)
+ mock_client.get.assert_called_once_with(driver._snmp_oid())
+ self.assertEqual(states.POWER_OFF, pstate)
+
+ @mock.patch("eventlet.greenthread.sleep", autospec=True)
+ def test_power_on_delay(self, mock_sleep, mock_get_client):
+ # Ensure driver waits for the state to change following a power on
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.side_effect = [driver.value_power_off,
+ driver.value_power_on]
+ pstate = driver.power_on()
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_on)
+ calls = [mock.call(driver._snmp_oid())] * 2
+ mock_client.get.assert_has_calls(calls)
+ self.assertEqual(states.POWER_ON, pstate)
+
+ @mock.patch("eventlet.greenthread.sleep", autospec=True)
+ def test_power_off_delay(self, mock_sleep, mock_get_client):
+ # Ensure driver waits for the state to change following a power off
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.side_effect = [driver.value_power_on,
+ driver.value_power_off]
+ pstate = driver.power_off()
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_off)
+ calls = [mock.call(driver._snmp_oid())] * 2
+ mock_client.get.assert_has_calls(calls)
+ self.assertEqual(states.POWER_OFF, pstate)
+
+ @mock.patch("eventlet.greenthread.sleep", autospec=True)
+ def test_power_on_invalid_state(self, mock_sleep, mock_get_client):
+ # Ensure driver retries when querying unexpected states following a
+ # power on
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = 42
+ pstate = driver.power_on()
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_on)
+ attempts = CONF.snmp.power_timeout // driver.retry_interval
+ calls = [mock.call(driver._snmp_oid())] * attempts
+ mock_client.get.assert_has_calls(calls)
+ self.assertEqual(states.ERROR, pstate)
+
+ @mock.patch("eventlet.greenthread.sleep", autospec=True)
+ def test_power_off_invalid_state(self, mock_sleep, mock_get_client):
+ # Ensure driver retries when querying unexpected states following a
+ # power off
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = 42
+ pstate = driver.power_off()
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_off)
+ attempts = CONF.snmp.power_timeout // driver.retry_interval
+ calls = [mock.call(driver._snmp_oid())] * attempts
+ mock_client.get.assert_has_calls(calls)
+ self.assertEqual(states.ERROR, pstate)
+
+ def test_power_on_snmp_set_failure(self, mock_get_client):
+ # Ensure SNMP failure exceptions raised during a power on set operation
+ # are propagated
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.set.side_effect = self._get_snmp_failure()
+ self.assertRaises(exception.SNMPFailure,
+ driver.power_on)
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_on)
+
+ def test_power_off_snmp_set_failure(self, mock_get_client):
+ # Ensure SNMP failure exceptions raised during a power off set
+ # operation are propagated
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.set.side_effect = self._get_snmp_failure()
+ self.assertRaises(exception.SNMPFailure,
+ driver.power_off)
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_off)
+
+ def test_power_on_snmp_get_failure(self, mock_get_client):
+ # Ensure SNMP failure exceptions raised during a power on get operation
+ # are propagated
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.side_effect = self._get_snmp_failure()
+ self.assertRaises(exception.SNMPFailure,
+ driver.power_on)
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_on)
+ mock_client.get.assert_called_once_with(driver._snmp_oid())
+
+ def test_power_off_snmp_get_failure(self, mock_get_client):
+ # Ensure SNMP failure exceptions raised during a power off get
+ # operation are propagated
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.side_effect = self._get_snmp_failure()
+ self.assertRaises(exception.SNMPFailure,
+ driver.power_off)
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_off)
+ mock_client.get.assert_called_once_with(driver._snmp_oid())
+
+ @mock.patch("eventlet.greenthread.sleep", autospec=True)
+ def test_power_on_timeout(self, mock_sleep, mock_get_client):
+ # Ensure that a power on consistency poll timeout causes an error
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = driver.value_power_off
+ pstate = driver.power_on()
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_on)
+ attempts = CONF.snmp.power_timeout // driver.retry_interval
+ calls = [mock.call(driver._snmp_oid())] * attempts
+ mock_client.get.assert_has_calls(calls)
+ self.assertEqual(states.ERROR, pstate)
+
+ @mock.patch("eventlet.greenthread.sleep", autospec=True)
+ def test_power_off_timeout(self, mock_sleep, mock_get_client):
+ # Ensure that a power off consistency poll timeout causes an error
+ mock_client = mock_get_client.return_value
+ CONF.snmp.power_timeout = 5
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = driver.value_power_on
+ pstate = driver.power_off()
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_off)
+ attempts = CONF.snmp.power_timeout // driver.retry_interval
+ calls = [mock.call(driver._snmp_oid())] * attempts
+ mock_client.get.assert_has_calls(calls)
+ self.assertEqual(states.ERROR, pstate)
+
+ def test_power_reset(self, mock_get_client):
+ # Ensure the device is reset correctly
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.side_effect = [driver.value_power_off,
+ driver.value_power_on]
+ pstate = driver.power_reset()
+ calls = [mock.call(driver._snmp_oid(), driver.value_power_off),
+ mock.call(driver._snmp_oid(), driver.value_power_on)]
+ mock_client.set.assert_has_calls(calls)
+ calls = [mock.call(driver._snmp_oid())] * 2
+ mock_client.get.assert_has_calls(calls)
+ self.assertEqual(states.POWER_ON, pstate)
+
+ @mock.patch("eventlet.greenthread.sleep", autospec=True)
+ def test_power_reset_off_delay(self, mock_sleep, mock_get_client):
+ # Ensure driver waits for the power off state change following a power
+ # reset
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.side_effect = [driver.value_power_on,
+ driver.value_power_off,
+ driver.value_power_on]
+ pstate = driver.power_reset()
+ calls = [mock.call(driver._snmp_oid(), driver.value_power_off),
+ mock.call(driver._snmp_oid(), driver.value_power_on)]
+ mock_client.set.assert_has_calls(calls)
+ calls = [mock.call(driver._snmp_oid())] * 3
+ mock_client.get.assert_has_calls(calls)
+ self.assertEqual(states.POWER_ON, pstate)
+
+ @mock.patch("eventlet.greenthread.sleep", autospec=True)
+ def test_power_reset_on_delay(self, mock_sleep, mock_get_client):
+ # Ensure driver waits for the power on state change following a power
+ # reset
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.side_effect = [driver.value_power_off,
+ driver.value_power_off,
+ driver.value_power_on]
+ pstate = driver.power_reset()
+ calls = [mock.call(driver._snmp_oid(), driver.value_power_off),
+ mock.call(driver._snmp_oid(), driver.value_power_on)]
+ mock_client.set.assert_has_calls(calls)
+ calls = [mock.call(driver._snmp_oid())] * 3
+ mock_client.get.assert_has_calls(calls)
+ self.assertEqual(states.POWER_ON, pstate)
+
+ @mock.patch("eventlet.greenthread.sleep", autospec=True)
+ def test_power_reset_off_delay_on_delay(self, mock_sleep, mock_get_client):
+ # Ensure driver waits for both state changes following a power reset
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.side_effect = [driver.value_power_on,
+ driver.value_power_off,
+ driver.value_power_off,
+ driver.value_power_on]
+ pstate = driver.power_reset()
+ calls = [mock.call(driver._snmp_oid(), driver.value_power_off),
+ mock.call(driver._snmp_oid(), driver.value_power_on)]
+ mock_client.set.assert_has_calls(calls)
+ calls = [mock.call(driver._snmp_oid())] * 4
+ mock_client.get.assert_has_calls(calls)
+ self.assertEqual(states.POWER_ON, pstate)
+
+ @mock.patch("eventlet.greenthread.sleep", autospec=True)
+ def test_power_reset_off_invalid_state(self, mock_sleep, mock_get_client):
+ # Ensure driver retries when querying unexpected states following a
+ # power off during a reset
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = 42
+ pstate = driver.power_reset()
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_off)
+ attempts = CONF.snmp.power_timeout // driver.retry_interval
+ calls = [mock.call(driver._snmp_oid())] * attempts
+ mock_client.get.assert_has_calls(calls)
+ self.assertEqual(states.ERROR, pstate)
+
+ @mock.patch("eventlet.greenthread.sleep", autospec=True)
+ def test_power_reset_on_invalid_state(self, mock_sleep, mock_get_client):
+ # Ensure driver retries when querying unexpected states following a
+ # power on during a reset
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ attempts = CONF.snmp.power_timeout // driver.retry_interval
+ mock_client.get.side_effect = ([driver.value_power_off] +
+ [42] * attempts)
+ pstate = driver.power_reset()
+ calls = [mock.call(driver._snmp_oid(), driver.value_power_off),
+ mock.call(driver._snmp_oid(), driver.value_power_on)]
+ mock_client.set.assert_has_calls(calls)
+ calls = [mock.call(driver._snmp_oid())] * (1 + attempts)
+ mock_client.get.assert_has_calls(calls)
+ self.assertEqual(states.ERROR, pstate)
+
+ @mock.patch("eventlet.greenthread.sleep", autospec=True)
+ def test_power_reset_off_timeout(self, mock_sleep, mock_get_client):
+ # Ensure that a power off consistency poll timeout during a reset
+ # causes an error
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = driver.value_power_on
+ pstate = driver.power_reset()
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_off)
+ attempts = CONF.snmp.power_timeout // driver.retry_interval
+ calls = [mock.call(driver._snmp_oid())] * attempts
+ mock_client.get.assert_has_calls(calls)
+ self.assertEqual(states.ERROR, pstate)
+
+ @mock.patch("eventlet.greenthread.sleep", autospec=True)
+ def test_power_reset_on_timeout(self, mock_sleep, mock_get_client):
+ # Ensure that a power on consistency poll timeout during a reset
+ # causes an error
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ attempts = CONF.snmp.power_timeout // driver.retry_interval
+ mock_client.get.side_effect = ([driver.value_power_off] *
+ (1 + attempts))
+ pstate = driver.power_reset()
+ calls = [mock.call(driver._snmp_oid(), driver.value_power_off),
+ mock.call(driver._snmp_oid(), driver.value_power_on)]
+ mock_client.set.assert_has_calls(calls)
+ calls = [mock.call(driver._snmp_oid())] * (1 + attempts)
+ mock_client.get.assert_has_calls(calls)
+ self.assertEqual(states.ERROR, pstate)
+
+ def test_power_reset_off_snmp_set_failure(self, mock_get_client):
+ # Ensure SNMP failure exceptions raised during a reset power off set
+ # operation are propagated
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.set.side_effect = self._get_snmp_failure()
+ self.assertRaises(exception.SNMPFailure,
+ driver.power_reset)
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_off)
+ self.assertFalse(mock_client.get.called)
+
+ def test_power_reset_off_snmp_get_failure(self, mock_get_client):
+ # Ensure SNMP failure exceptions raised during a reset power off get
+ # operation are propagated
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.side_effect = self._get_snmp_failure()
+ self.assertRaises(exception.SNMPFailure,
+ driver.power_reset)
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_off)
+ mock_client.get.assert_called_once_with(driver._snmp_oid())
+
+ def test_power_reset_on_snmp_set_failure(self, mock_get_client):
+ # Ensure SNMP failure exceptions raised during a reset power on set
+ # operation are propagated
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.set.side_effect = [None, self._get_snmp_failure()]
+ mock_client.get.return_value = driver.value_power_off
+ self.assertRaises(exception.SNMPFailure,
+ driver.power_reset)
+ calls = [mock.call(driver._snmp_oid(), driver.value_power_off),
+ mock.call(driver._snmp_oid(), driver.value_power_on)]
+ mock_client.set.assert_has_calls(calls)
+ mock_client.get.assert_called_once_with(driver._snmp_oid())
+
+ def test_power_reset_on_snmp_get_failure(self, mock_get_client):
+ # Ensure SNMP failure exceptions raised during a reset power on get
+ # operation are propagated
+ mock_client = mock_get_client.return_value
+ driver = snmp._get_driver(self.node)
+ mock_client.get.side_effect = [driver.value_power_off,
+ self._get_snmp_failure()]
+ self.assertRaises(exception.SNMPFailure,
+ driver.power_reset)
+ calls = [mock.call(driver._snmp_oid(), driver.value_power_off),
+ mock.call(driver._snmp_oid(), driver.value_power_on)]
+ mock_client.set.assert_has_calls(calls)
+ calls = [mock.call(driver._snmp_oid()), mock.call(driver._snmp_oid())]
+ mock_client.get.assert_has_calls(calls)
+
+ def _test_simple_device_power_state_on(self, snmp_driver, mock_get_client):
+ # Ensure a simple device driver queries power on correctly
+ mock_client = mock_get_client.return_value
+ self._set_snmp_driver(snmp_driver)
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = driver.value_power_on
+ pstate = driver.power_state()
+ mock_client.get.assert_called_once_with(driver._snmp_oid())
+ self.assertEqual(states.POWER_ON, pstate)
+
+ def _test_simple_device_power_state_off(self, snmp_driver,
+ mock_get_client):
+ # Ensure a simple device driver queries power off correctly
+ mock_client = mock_get_client.return_value
+ self._set_snmp_driver(snmp_driver)
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = driver.value_power_off
+ pstate = driver.power_state()
+ mock_client.get.assert_called_once_with(driver._snmp_oid())
+ self.assertEqual(states.POWER_OFF, pstate)
+
+ def _test_simple_device_power_on(self, snmp_driver, mock_get_client):
+ # Ensure a simple device driver powers on correctly
+ mock_client = mock_get_client.return_value
+ self._set_snmp_driver(snmp_driver)
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = driver.value_power_on
+ pstate = driver.power_on()
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_on)
+ mock_client.get.assert_called_once_with(driver._snmp_oid())
+ self.assertEqual(states.POWER_ON, pstate)
+
+ def _test_simple_device_power_off(self, snmp_driver, mock_get_client):
+ # Ensure a simple device driver powers off correctly
+ mock_client = mock_get_client.return_value
+ self._set_snmp_driver(snmp_driver)
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = driver.value_power_off
+ pstate = driver.power_off()
+ mock_client.set.assert_called_once_with(driver._snmp_oid(),
+ driver.value_power_off)
+ mock_client.get.assert_called_once_with(driver._snmp_oid())
+ self.assertEqual(states.POWER_OFF, pstate)
+
+ def _test_simple_device_power_reset(self, snmp_driver, mock_get_client):
+ # Ensure a simple device driver resets correctly
+ mock_client = mock_get_client.return_value
+ self._set_snmp_driver(snmp_driver)
+ driver = snmp._get_driver(self.node)
+ mock_client.get.side_effect = [driver.value_power_off,
+ driver.value_power_on]
+ pstate = driver.power_reset()
+ calls = [mock.call(driver._snmp_oid(), driver.value_power_off),
+ mock.call(driver._snmp_oid(), driver.value_power_on)]
+ mock_client.set.assert_has_calls(calls)
+ calls = [mock.call(driver._snmp_oid())] * 2
+ mock_client.get.assert_has_calls(calls)
+ self.assertEqual(states.POWER_ON, pstate)
+
+ def test_apc_snmp_objects(self, mock_get_client):
+ # Ensure the correct SNMP object OIDs and values are used by the APC
+ # driver
+ self._update_driver_info(snmp_driver="apc",
+ snmp_outlet="3")
+ driver = snmp._get_driver(self.node)
+ oid = (1, 3, 6, 1, 4, 1, 318, 1, 1, 4, 4, 2, 1, 3, 3)
+ self.assertEqual(oid, driver._snmp_oid())
+ self.assertEqual(1, driver.value_power_on)
+ self.assertEqual(2, driver.value_power_off)
+
+ def test_apc_power_state_on(self, mock_get_client):
+ self._test_simple_device_power_state_on('apc', mock_get_client)
+
+ def test_apc_power_state_off(self, mock_get_client):
+ self._test_simple_device_power_state_off('apc', mock_get_client)
+
+ def test_apc_power_on(self, mock_get_client):
+ self._test_simple_device_power_on('apc', mock_get_client)
+
+ def test_apc_power_off(self, mock_get_client):
+ self._test_simple_device_power_off('apc', mock_get_client)
+
+ def test_apc_power_reset(self, mock_get_client):
+ self._test_simple_device_power_reset('apc', mock_get_client)
+
+ def test_apc_masterswitch_snmp_objects(self, mock_get_client):
+ # Ensure the correct SNMP object OIDs and values are used by the APC
+ # masterswitch driver
+ self._update_driver_info(snmp_driver="apc_masterswitch",
+ snmp_outlet="6")
+ driver = snmp._get_driver(self.node)
+ oid = (1, 3, 6, 1, 4, 1, 318, 1, 1, 4, 4, 2, 1, 3, 6)
+ self.assertEqual(oid, driver._snmp_oid())
+ self.assertEqual(1, driver.value_power_on)
+ self.assertEqual(2, driver.value_power_off)
+
+ def test_apc_masterswitch_power_state_on(self, mock_get_client):
+ self._test_simple_device_power_state_on('apc_masterswitch',
+ mock_get_client)
+
+ def test_apc_masterswitch_power_state_off(self, mock_get_client):
+ self._test_simple_device_power_state_off('apc_masterswitch',
+ mock_get_client)
+
+ def test_apc_masterswitch_power_on(self, mock_get_client):
+ self._test_simple_device_power_on('apc_masterswitch', mock_get_client)
+
+ def test_apc_masterswitch_power_off(self, mock_get_client):
+ self._test_simple_device_power_off('apc_masterswitch', mock_get_client)
+
+ def test_apc_masterswitch_power_reset(self, mock_get_client):
+ self._test_simple_device_power_reset('apc_masterswitch',
+ mock_get_client)
+
+ def test_apc_masterswitchplus_snmp_objects(self, mock_get_client):
+ # Ensure the correct SNMP object OIDs and values are used by the APC
+ # masterswitchplus driver
+ self._update_driver_info(snmp_driver="apc_masterswitchplus",
+ snmp_outlet="6")
+ driver = snmp._get_driver(self.node)
+ oid = (1, 3, 6, 1, 4, 1, 318, 1, 1, 6, 5, 1, 1, 5, 6)
+ self.assertEqual(oid, driver._snmp_oid())
+ self.assertEqual(1, driver.value_power_on)
+ self.assertEqual(3, driver.value_power_off)
+
+ def test_apc_masterswitchplus_power_state_on(self, mock_get_client):
+ self._test_simple_device_power_state_on('apc_masterswitchplus',
+ mock_get_client)
+
+ def test_apc_masterswitchplus_power_state_off(self, mock_get_client):
+ self._test_simple_device_power_state_off('apc_masterswitchplus',
+ mock_get_client)
+
+ def test_apc_masterswitchplus_power_on(self, mock_get_client):
+ self._test_simple_device_power_on('apc_masterswitchplus',
+ mock_get_client)
+
+ def test_apc_masterswitchplus_power_off(self, mock_get_client):
+ self._test_simple_device_power_off('apc_masterswitchplus',
+ mock_get_client)
+
+ def test_apc_masterswitchplus_power_reset(self, mock_get_client):
+ self._test_simple_device_power_reset('apc_masterswitchplus',
+ mock_get_client)
+
+ def test_apc_rackpdu_snmp_objects(self, mock_get_client):
+ # Ensure the correct SNMP object OIDs and values are used by the APC
+ # rackpdu driver
+ self._update_driver_info(snmp_driver="apc_rackpdu",
+ snmp_outlet="6")
+ driver = snmp._get_driver(self.node)
+ oid = (1, 3, 6, 1, 4, 1, 318, 1, 1, 12, 3, 3, 1, 1, 4, 6)
+
+ self.assertEqual(oid, driver._snmp_oid())
+ self.assertEqual(1, driver.value_power_on)
+ self.assertEqual(2, driver.value_power_off)
+
+ def test_apc_rackpdu_power_state_on(self, mock_get_client):
+ self._test_simple_device_power_state_on('apc_rackpdu', mock_get_client)
+
+ def test_apc_rackpdu_power_state_off(self, mock_get_client):
+ self._test_simple_device_power_state_off('apc_rackpdu',
+ mock_get_client)
+
+ def test_apc_rackpdu_power_on(self, mock_get_client):
+ self._test_simple_device_power_on('apc_rackpdu', mock_get_client)
+
+ def test_apc_rackpdu_power_off(self, mock_get_client):
+ self._test_simple_device_power_off('apc_rackpdu', mock_get_client)
+
+ def test_apc_rackpdu_power_reset(self, mock_get_client):
+ self._test_simple_device_power_reset('apc_rackpdu', mock_get_client)
+
+ def test_aten_snmp_objects(self, mock_get_client):
+ # Ensure the correct SNMP object OIDs and values are used by the
+ # Aten driver
+ self._update_driver_info(snmp_driver="aten",
+ snmp_outlet="3")
+ driver = snmp._get_driver(self.node)
+ oid = (1, 3, 6, 1, 4, 1, 21317, 1, 3, 2, 2, 2, 2, 3, 0)
+ self.assertEqual(oid, driver._snmp_oid())
+ self.assertEqual(2, driver.value_power_on)
+ self.assertEqual(1, driver.value_power_off)
+
+ def test_aten_power_state_on(self, mock_get_client):
+ self._test_simple_device_power_state_on('aten', mock_get_client)
+
+ def test_aten_power_state_off(self, mock_get_client):
+ self._test_simple_device_power_state_off('aten', mock_get_client)
+
+ def test_aten_power_on(self, mock_get_client):
+ self._test_simple_device_power_on('aten', mock_get_client)
+
+ def test_aten_power_off(self, mock_get_client):
+ self._test_simple_device_power_off('aten', mock_get_client)
+
+ def test_aten_power_reset(self, mock_get_client):
+ self._test_simple_device_power_reset('aten', mock_get_client)
+
+ def test_cyberpower_snmp_objects(self, mock_get_client):
+ # Ensure the correct SNMP object OIDs and values are used by the
+ # CyberPower driver
+ self._update_driver_info(snmp_driver="cyberpower",
+ snmp_outlet="3")
+ driver = snmp._get_driver(self.node)
+ oid = (1, 3, 6, 1, 4, 1, 3808, 1, 1, 3, 3, 3, 1, 1, 4, 3)
+ self.assertEqual(oid, driver._snmp_oid())
+ self.assertEqual(1, driver.value_power_on)
+ self.assertEqual(2, driver.value_power_off)
+
+ def test_cyberpower_power_state_on(self, mock_get_client):
+ self._test_simple_device_power_state_on('cyberpower', mock_get_client)
+
+ def test_cyberpower_power_state_off(self, mock_get_client):
+ self._test_simple_device_power_state_off('cyberpower', mock_get_client)
+
+ def test_cyberpower_power_on(self, mock_get_client):
+ self._test_simple_device_power_on('cyberpower', mock_get_client)
+
+ def test_cyberpower_power_off(self, mock_get_client):
+ self._test_simple_device_power_off('cyberpower', mock_get_client)
+
+ def test_cyberpower_power_reset(self, mock_get_client):
+ self._test_simple_device_power_reset('cyberpower', mock_get_client)
+
+ def test_teltronix_snmp_objects(self, mock_get_client):
+ # Ensure the correct SNMP object OIDs and values are used by the
+ # Teltronix driver
+ self._update_driver_info(snmp_driver="teltronix",
+ snmp_outlet="3")
+ driver = snmp._get_driver(self.node)
+ oid = (1, 3, 6, 1, 4, 1, 23620, 1, 2, 2, 1, 4, 3)
+ self.assertEqual(oid, driver._snmp_oid())
+ self.assertEqual(2, driver.value_power_on)
+ self.assertEqual(1, driver.value_power_off)
+
+ def test_teltronix_power_state_on(self, mock_get_client):
+ self._test_simple_device_power_state_on('teltronix', mock_get_client)
+
+ def test_teltronix_power_state_off(self, mock_get_client):
+ self._test_simple_device_power_state_off('teltronix', mock_get_client)
+
+ def test_teltronix_power_on(self, mock_get_client):
+ self._test_simple_device_power_on('teltronix', mock_get_client)
+
+ def test_teltronix_power_off(self, mock_get_client):
+ self._test_simple_device_power_off('teltronix', mock_get_client)
+
+ def test_teltronix_power_reset(self, mock_get_client):
+ self._test_simple_device_power_reset('teltronix', mock_get_client)
+
+ def test_eaton_power_snmp_objects(self, mock_get_client):
+ # Ensure the correct SNMP object OIDs and values are used by the Eaton
+ # Power driver
+ self._update_driver_info(snmp_driver="eatonpower",
+ snmp_outlet="3")
+ driver = snmp._get_driver(self.node)
+ status_oid = (1, 3, 6, 1, 4, 1, 534, 6, 6, 7, 6, 6, 1, 2, 3)
+ poweron_oid = (1, 3, 6, 1, 4, 1, 534, 6, 6, 7, 6, 6, 1, 3, 3)
+ poweroff_oid = (1, 3, 6, 1, 4, 1, 534, 6, 6, 7, 6, 6, 1, 4, 3)
+ self.assertEqual(status_oid, driver._snmp_oid(driver.oid_status))
+ self.assertEqual(poweron_oid, driver._snmp_oid(driver.oid_poweron))
+ self.assertEqual(poweroff_oid, driver._snmp_oid(driver.oid_poweroff))
+ self.assertEqual(0, driver.status_off)
+ self.assertEqual(1, driver.status_on)
+ self.assertEqual(2, driver.status_pending_off)
+ self.assertEqual(3, driver.status_pending_on)
+
+ def test_eaton_power_power_state_on(self, mock_get_client):
+ # Ensure the Eaton Power driver queries on correctly
+ mock_client = mock_get_client.return_value
+ self._set_snmp_driver("eatonpower")
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = driver.status_on
+ pstate = driver.power_state()
+ mock_client.get.assert_called_once_with(
+ driver._snmp_oid(driver.oid_status))
+ self.assertEqual(states.POWER_ON, pstate)
+
+ def test_eaton_power_power_state_off(self, mock_get_client):
+ # Ensure the Eaton Power driver queries off correctly
+ mock_client = mock_get_client.return_value
+ self._set_snmp_driver("eatonpower")
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = driver.status_off
+ pstate = driver.power_state()
+ mock_client.get.assert_called_once_with(
+ driver._snmp_oid(driver.oid_status))
+ self.assertEqual(states.POWER_OFF, pstate)
+
+ def test_eaton_power_power_state_pending_off(self, mock_get_client):
+ # Ensure the Eaton Power driver queries pending off correctly
+ mock_client = mock_get_client.return_value
+ self._set_snmp_driver("eatonpower")
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = driver.status_pending_off
+ pstate = driver.power_state()
+ mock_client.get.assert_called_once_with(
+ driver._snmp_oid(driver.oid_status))
+ self.assertEqual(states.POWER_ON, pstate)
+
+ def test_eaton_power_power_state_pending_on(self, mock_get_client):
+ # Ensure the Eaton Power driver queries pending on correctly
+ mock_client = mock_get_client.return_value
+ self._set_snmp_driver("eatonpower")
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = driver.status_pending_on
+ pstate = driver.power_state()
+ mock_client.get.assert_called_once_with(
+ driver._snmp_oid(driver.oid_status))
+ self.assertEqual(states.POWER_OFF, pstate)
+
+ def test_eaton_power_power_on(self, mock_get_client):
+ # Ensure the Eaton Power driver powers on correctly
+ mock_client = mock_get_client.return_value
+ self._set_snmp_driver("eatonpower")
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = driver.status_on
+ pstate = driver.power_on()
+ mock_client.set.assert_called_once_with(
+ driver._snmp_oid(driver.oid_poweron), driver.value_power_on)
+ mock_client.get.assert_called_once_with(
+ driver._snmp_oid(driver.oid_status))
+ self.assertEqual(states.POWER_ON, pstate)
+
+ def test_eaton_power_power_off(self, mock_get_client):
+ # Ensure the Eaton Power driver powers off correctly
+ mock_client = mock_get_client.return_value
+ self._set_snmp_driver("eatonpower")
+ driver = snmp._get_driver(self.node)
+ mock_client.get.return_value = driver.status_off
+ pstate = driver.power_off()
+ mock_client.set.assert_called_once_with(
+ driver._snmp_oid(driver.oid_poweroff), driver.value_power_off)
+ mock_client.get.assert_called_once_with(
+ driver._snmp_oid(driver.oid_status))
+ self.assertEqual(states.POWER_OFF, pstate)
+
+ def test_eaton_power_power_reset(self, mock_get_client):
+ # Ensure the Eaton Power driver resets correctly
+ mock_client = mock_get_client.return_value
+ self._set_snmp_driver("eatonpower")
+ driver = snmp._get_driver(self.node)
+ mock_client.get.side_effect = [driver.status_off, driver.status_on]
+ pstate = driver.power_reset()
+ calls = [mock.call(driver._snmp_oid(driver.oid_poweroff),
+ driver.value_power_off),
+ mock.call(driver._snmp_oid(driver.oid_poweron),
+ driver.value_power_on)]
+ mock_client.set.assert_has_calls(calls)
+ calls = [mock.call(driver._snmp_oid(driver.oid_status))] * 2
+ mock_client.get.assert_has_calls(calls)
+ self.assertEqual(states.POWER_ON, pstate)
+
+
+@mock.patch.object(snmp, '_get_driver', autospec=True)
+class SNMPDriverTestCase(db_base.DbTestCase):
+ """SNMP power driver interface tests.
+
+ In this test case, the SNMP power driver interface is exercised. The
+ device-specific SNMP driver is mocked to allow various error cases to be
+ tested.
+ """
+
+ def setUp(self):
+ super(SNMPDriverTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_snmp')
+
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_snmp',
+ driver_info=INFO_DICT)
+
+ def _get_snmp_failure(self):
+ return exception.SNMPFailure(operation='test-operation',
+ error='test-error')
+
+ def test_get_properties(self, mock_get_driver):
+ expected = snmp.COMMON_PROPERTIES
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertEqual(expected, task.driver.get_properties())
+
+ def test_get_power_state_on(self, mock_get_driver):
+ mock_driver = mock_get_driver.return_value
+ mock_driver.power_state.return_value = states.POWER_ON
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ pstate = task.driver.power.get_power_state(task)
+ mock_driver.power_state.assert_called_once_with()
+ self.assertEqual(states.POWER_ON, pstate)
+
+ def test_get_power_state_off(self, mock_get_driver):
+ mock_driver = mock_get_driver.return_value
+ mock_driver.power_state.return_value = states.POWER_OFF
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ pstate = task.driver.power.get_power_state(task)
+ mock_driver.power_state.assert_called_once_with()
+ self.assertEqual(states.POWER_OFF, pstate)
+
+ def test_get_power_state_error(self, mock_get_driver):
+ mock_driver = mock_get_driver.return_value
+ mock_driver.power_state.return_value = states.ERROR
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ pstate = task.driver.power.get_power_state(task)
+ mock_driver.power_state.assert_called_once_with()
+ self.assertEqual(states.ERROR, pstate)
+
+ def test_get_power_state_snmp_failure(self, mock_get_driver):
+ mock_driver = mock_get_driver.return_value
+ mock_driver.power_state.side_effect = self._get_snmp_failure()
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.SNMPFailure,
+ task.driver.power.get_power_state, task)
+ mock_driver.power_state.assert_called_once_with()
+
+ def test_set_power_state_on(self, mock_get_driver):
+ mock_driver = mock_get_driver.return_value
+ mock_driver.power_on.return_value = states.POWER_ON
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.driver.power.set_power_state(task, states.POWER_ON)
+ mock_driver.power_on.assert_called_once_with()
+
+ def test_set_power_state_off(self, mock_get_driver):
+ mock_driver = mock_get_driver.return_value
+ mock_driver.power_off.return_value = states.POWER_OFF
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.driver.power.set_power_state(task, states.POWER_OFF)
+ mock_driver.power_off.assert_called_once_with()
+
+ def test_set_power_state_error(self, mock_get_driver):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.power.set_power_state,
+ task, states.ERROR)
+
+ def test_set_power_state_on_snmp_failure(self, mock_get_driver):
+ mock_driver = mock_get_driver.return_value
+ mock_driver.power_on.side_effect = self._get_snmp_failure()
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.SNMPFailure,
+ task.driver.power.set_power_state,
+ task, states.POWER_ON)
+ mock_driver.power_on.assert_called_once_with()
+
+ def test_set_power_state_off_snmp_failure(self, mock_get_driver):
+ mock_driver = mock_get_driver.return_value
+ mock_driver.power_off.side_effect = self._get_snmp_failure()
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.SNMPFailure,
+ task.driver.power.set_power_state,
+ task, states.POWER_OFF)
+ mock_driver.power_off.assert_called_once_with()
+
+ def test_set_power_state_on_timeout(self, mock_get_driver):
+ mock_driver = mock_get_driver.return_value
+ mock_driver.power_on.return_value = states.ERROR
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.PowerStateFailure,
+ task.driver.power.set_power_state,
+ task, states.POWER_ON)
+ mock_driver.power_on.assert_called_once_with()
+
+ def test_set_power_state_off_timeout(self, mock_get_driver):
+ mock_driver = mock_get_driver.return_value
+ mock_driver.power_off.return_value = states.ERROR
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.PowerStateFailure,
+ task.driver.power.set_power_state,
+ task, states.POWER_OFF)
+ mock_driver.power_off.assert_called_once_with()
+
+ def test_reboot(self, mock_get_driver):
+ mock_driver = mock_get_driver.return_value
+ mock_driver.power_reset.return_value = states.POWER_ON
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.driver.power.reboot(task)
+ mock_driver.power_reset.assert_called_once_with()
+
+ def test_reboot_snmp_failure(self, mock_get_driver):
+ mock_driver = mock_get_driver.return_value
+ mock_driver.power_reset.side_effect = self._get_snmp_failure()
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.SNMPFailure,
+ task.driver.power.reboot, task)
+ mock_driver.power_reset.assert_called_once_with()
+
+ def test_reboot_timeout(self, mock_get_driver):
+ mock_driver = mock_get_driver.return_value
+ mock_driver.power_reset.return_value = states.ERROR
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.PowerStateFailure,
+ task.driver.power.reboot, task)
+ mock_driver.power_reset.assert_called_once_with()
diff --git a/ironic/tests/unit/drivers/modules/test_ssh.py b/ironic/tests/unit/drivers/modules/test_ssh.py
new file mode 100644
index 000000000..a7c25667c
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/test_ssh.py
@@ -0,0 +1,975 @@
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# 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
+# 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.
+
+"""Test class for Ironic SSH power driver."""
+
+import tempfile
+
+import mock
+from oslo_concurrency import processutils
+from oslo_config import cfg
+from oslo_utils import uuidutils
+import paramiko
+
+from ironic.common import boot_devices
+from ironic.common import driver_factory
+from ironic.common import exception
+from ironic.common import states
+from ironic.common import utils
+from ironic.conductor import task_manager
+from ironic.drivers.modules import ssh
+from ironic.drivers import utils as driver_utils
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+
+CONF = cfg.CONF
+
+
+class SSHValidateParametersTestCase(db_base.DbTestCase):
+
+ def test__parse_driver_info_good_password(self):
+ # make sure we get back the expected things
+ node = obj_utils.get_test_node(
+ self.context,
+ driver='fake_ssh',
+ driver_info=db_utils.get_test_ssh_info('password'))
+ info = ssh._parse_driver_info(node)
+ self.assertIsNotNone(info.get('host'))
+ self.assertIsNotNone(info.get('username'))
+ self.assertIsNotNone(info.get('password'))
+ self.assertIsNotNone(info.get('port'))
+ self.assertIsNotNone(info.get('virt_type'))
+ self.assertIsNotNone(info.get('cmd_set'))
+ self.assertIsNotNone(info.get('uuid'))
+
+ def test__parse_driver_info_good_key(self):
+ # make sure we get back the expected things
+ node = obj_utils.get_test_node(
+ self.context,
+ driver='fake_ssh',
+ driver_info=db_utils.get_test_ssh_info('key'))
+ info = ssh._parse_driver_info(node)
+ self.assertIsNotNone(info.get('host'))
+ self.assertIsNotNone(info.get('username'))
+ self.assertIsNotNone(info.get('key_contents'))
+ self.assertIsNotNone(info.get('port'))
+ self.assertIsNotNone(info.get('virt_type'))
+ self.assertIsNotNone(info.get('cmd_set'))
+ self.assertIsNotNone(info.get('uuid'))
+
+ def test__parse_driver_info_good_file(self):
+ # make sure we get back the expected things
+ d_info = db_utils.get_test_ssh_info('file')
+ tempdir = tempfile.mkdtemp()
+ key_path = tempdir + '/foo'
+ open(key_path, 'wt').close()
+ d_info['ssh_key_filename'] = key_path
+ node = obj_utils.get_test_node(
+ self.context,
+ driver='fake_ssh',
+ driver_info=d_info)
+ info = ssh._parse_driver_info(node)
+ self.assertIsNotNone(info.get('host'))
+ self.assertIsNotNone(info.get('username'))
+ self.assertIsNotNone(info.get('key_filename'))
+ self.assertIsNotNone(info.get('port'))
+ self.assertIsNotNone(info.get('virt_type'))
+ self.assertIsNotNone(info.get('cmd_set'))
+ self.assertIsNotNone(info.get('uuid'))
+
+ def test__parse_driver_info_bad_file(self):
+ # A filename that doesn't exist errors.
+ info = db_utils.get_test_ssh_info('file')
+ node = obj_utils.get_test_node(
+ self.context,
+ driver='fake_ssh',
+ driver_info=info)
+ self.assertRaises(
+ exception.InvalidParameterValue, ssh._parse_driver_info, node)
+
+ def test__parse_driver_info_too_many(self):
+ info = db_utils.get_test_ssh_info('too_many')
+ node = obj_utils.get_test_node(
+ self.context,
+ driver='fake_ssh',
+ driver_info=info)
+ self.assertRaises(
+ exception.InvalidParameterValue, ssh._parse_driver_info, node)
+
+ def test__parse_driver_info_missing_host(self):
+ # make sure error is raised when info is missing
+ info = db_utils.get_test_ssh_info()
+ del info['ssh_address']
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ self.assertRaises(exception.MissingParameterValue,
+ ssh._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_missing_user(self):
+ # make sure error is raised when info is missing
+ info = db_utils.get_test_ssh_info()
+ del info['ssh_username']
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ self.assertRaises(exception.MissingParameterValue,
+ ssh._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_invalid_creds(self):
+ # make sure error is raised when info is missing
+ info = db_utils.get_test_ssh_info('no-creds')
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ self.assertRaises(exception.InvalidParameterValue,
+ ssh._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_missing_virt_type(self):
+ # make sure error is raised when info is missing
+ info = db_utils.get_test_ssh_info()
+ del info['ssh_virt_type']
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ self.assertRaises(exception.MissingParameterValue,
+ ssh._parse_driver_info,
+ node)
+
+ def test__parse_driver_info_ssh_port_wrong_type(self):
+ # make sure error is raised when ssh_port is not integer
+ info = db_utils.get_test_ssh_info()
+ info['ssh_port'] = 'wrong_port_value'
+ node = obj_utils.get_test_node(self.context, driver_info=info)
+ self.assertRaises(exception.InvalidParameterValue,
+ ssh._parse_driver_info,
+ node)
+
+ def test__normalize_mac_string(self):
+ mac_raw = "0A:1B-2C-3D:4F"
+ mac_clean = ssh._normalize_mac(mac_raw)
+ self.assertEqual("0a1b2c3d4f", mac_clean)
+
+ def test__normalize_mac_unicode(self):
+ mac_raw = u"0A:1B-2C-3D:4F"
+ mac_clean = ssh._normalize_mac(mac_raw)
+ self.assertEqual("0a1b2c3d4f", mac_clean)
+
+ def test__parse_driver_info_with_custom_libvirt_uri(self):
+ CONF.set_override('libvirt_uri', 'qemu:///foo', 'ssh')
+ expected_base_cmd = "LC_ALL=C /usr/bin/virsh --connect qemu:///foo"
+
+ node = obj_utils.get_test_node(
+ self.context,
+ driver='fake_ssh',
+ driver_info=db_utils.get_test_ssh_info())
+ node['driver_info']['ssh_virt_type'] = 'virsh'
+ info = ssh._parse_driver_info(node)
+ self.assertEqual(expected_base_cmd, info['cmd_set']['base_cmd'])
+
+ def test__get_boot_device_map_parallels(self):
+ boot_map = ssh._get_boot_device_map('parallels')
+ self.assertEqual('net0', boot_map[boot_devices.PXE])
+
+ def test__get_boot_device_map_vbox(self):
+ boot_map = ssh._get_boot_device_map('vbox')
+ self.assertEqual('net', boot_map[boot_devices.PXE])
+
+ def test__get_boot_device_map_exception(self):
+ self.assertRaises(exception.InvalidParameterValue,
+ ssh._get_boot_device_map,
+ 'this_doesn_t_exist')
+
+
+class SSHPrivateMethodsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(SSHPrivateMethodsTestCase, self).setUp()
+ self.node = obj_utils.get_test_node(
+ self.context,
+ driver='fake_ssh',
+ driver_info=db_utils.get_test_ssh_info())
+ self.sshclient = paramiko.SSHClient()
+
+ @mock.patch.object(utils, 'ssh_connect', autospec=True)
+ def test__get_connection_client(self, ssh_connect_mock):
+ ssh_connect_mock.return_value = self.sshclient
+ client = ssh._get_connection(self.node)
+ self.assertEqual(self.sshclient, client)
+ driver_info = ssh._parse_driver_info(self.node)
+ ssh_connect_mock.assert_called_once_with(driver_info)
+
+ @mock.patch.object(utils, 'ssh_connect', autospec=True)
+ def test__get_connection_exception(self, ssh_connect_mock):
+ ssh_connect_mock.side_effect = iter(
+ [exception.SSHConnectFailed(host='fake')])
+ self.assertRaises(exception.SSHConnectFailed,
+ ssh._get_connection,
+ self.node)
+ driver_info = ssh._parse_driver_info(self.node)
+ ssh_connect_mock.assert_called_once_with(driver_info)
+
+ @mock.patch.object(processutils, 'ssh_execute', autospec=True)
+ def test__ssh_execute(self, exec_ssh_mock):
+ ssh_cmd = "somecmd"
+ expected = ['a', 'b', 'c']
+ exec_ssh_mock.return_value = ('\n'.join(expected), '')
+ lst = ssh._ssh_execute(self.sshclient, ssh_cmd)
+ exec_ssh_mock.assert_called_once_with(self.sshclient, ssh_cmd)
+ self.assertEqual(expected, lst)
+
+ @mock.patch.object(processutils, 'ssh_execute', autospec=True)
+ def test__ssh_execute_exception(self, exec_ssh_mock):
+ ssh_cmd = "somecmd"
+ exec_ssh_mock.side_effect = processutils.ProcessExecutionError
+ self.assertRaises(exception.SSHCommandFailed,
+ ssh._ssh_execute,
+ self.sshclient,
+ ssh_cmd)
+ exec_ssh_mock.assert_called_once_with(self.sshclient, ssh_cmd)
+
+ @mock.patch.object(processutils, 'ssh_execute', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ def test__get_power_status_on_unquoted(self, get_hosts_name_mock,
+ exec_ssh_mock):
+ info = ssh._parse_driver_info(self.node)
+ exec_ssh_mock.return_value = (
+ 'ExactNodeName', '')
+ get_hosts_name_mock.return_value = "ExactNodeName"
+
+ pstate = ssh._get_power_status(self.sshclient, info)
+
+ ssh_cmd = "%s %s" % (info['cmd_set']['base_cmd'],
+ info['cmd_set']['list_running'])
+ self.assertEqual(states.POWER_ON, pstate)
+ exec_ssh_mock.assert_called_once_with(self.sshclient, ssh_cmd)
+ get_hosts_name_mock.assert_called_once_with(self.sshclient, info)
+
+ @mock.patch.object(processutils, 'ssh_execute', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ def test__get_power_status_on(self, get_hosts_name_mock, exec_ssh_mock):
+ info = ssh._parse_driver_info(self.node)
+ exec_ssh_mock.return_value = (
+ '"NodeName" {b43c4982-110c-4c29-9325-d5f41b053513}', '')
+ get_hosts_name_mock.return_value = "NodeName"
+
+ pstate = ssh._get_power_status(self.sshclient, info)
+
+ ssh_cmd = "%s %s" % (info['cmd_set']['base_cmd'],
+ info['cmd_set']['list_running'])
+ self.assertEqual(states.POWER_ON, pstate)
+ exec_ssh_mock.assert_called_once_with(self.sshclient, ssh_cmd)
+ get_hosts_name_mock.assert_called_once_with(self.sshclient, info)
+
+ @mock.patch.object(processutils, 'ssh_execute', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ def test__get_power_status_off(self, get_hosts_name_mock, exec_ssh_mock):
+ info = ssh._parse_driver_info(self.node)
+ exec_ssh_mock.return_value = (
+ '"NodeName" {b43c4982-110c-4c29-9325-d5f41b053513}', '')
+ get_hosts_name_mock.return_value = "NotNodeName"
+
+ pstate = ssh._get_power_status(self.sshclient, info)
+
+ ssh_cmd = "%s %s" % (info['cmd_set']['base_cmd'],
+ info['cmd_set']['list_running'])
+ self.assertEqual(states.POWER_OFF, pstate)
+ exec_ssh_mock.assert_called_once_with(self.sshclient, ssh_cmd)
+ get_hosts_name_mock.assert_called_once_with(self.sshclient, info)
+
+ @mock.patch.object(processutils, 'ssh_execute', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ def test__get_power_status_error(self, get_hosts_name_mock, exec_ssh_mock):
+
+ info = ssh._parse_driver_info(self.node)
+
+ info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
+ get_hosts_name_mock.return_value = None
+ self.assertRaises(exception.NodeNotFound,
+ ssh._get_power_status,
+ self.sshclient,
+ info)
+
+ get_hosts_name_mock.assert_called_once_with(self.sshclient, info)
+ self.assertFalse(exec_ssh_mock.called)
+
+ @mock.patch.object(processutils, 'ssh_execute', autospec=True)
+ def test__get_power_status_exception(self, exec_ssh_mock):
+ info = ssh._parse_driver_info(self.node)
+ exec_ssh_mock.side_effect = processutils.ProcessExecutionError
+
+ self.assertRaises(exception.SSHCommandFailed,
+ ssh._get_power_status,
+ self.sshclient,
+ info)
+ ssh_cmd = "%s %s" % (info['cmd_set']['base_cmd'],
+ info['cmd_set']['list_all'])
+ exec_ssh_mock.assert_called_once_with(
+ self.sshclient, ssh_cmd)
+
+ @mock.patch.object(processutils, 'ssh_execute', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ def test__get_power_status_correct_node(self, get_hosts_name_mock,
+ exec_ssh_mock):
+ # Bug: #1397834 test that get_power_status return status of
+ # baremeta_1 (off) and not baremetal_11 (on)
+ info = ssh._parse_driver_info(self.node)
+ exec_ssh_mock.return_value = ('"baremetal_11"\n"seed"\n', '')
+ get_hosts_name_mock.return_value = "baremetal_1"
+
+ pstate = ssh._get_power_status(self.sshclient, info)
+ self.assertEqual(states.POWER_OFF, pstate)
+
+ @mock.patch.object(processutils, 'ssh_execute', autospec=True)
+ def test__get_hosts_name_for_node_match(self, exec_ssh_mock):
+ info = ssh._parse_driver_info(self.node)
+ info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
+ ssh_cmd = "%s %s" % (info['cmd_set']['base_cmd'],
+ info['cmd_set']['list_all'])
+
+ cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'],
+ info['cmd_set']['get_node_macs'])
+ cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
+ exec_ssh_mock.side_effect = iter([('NodeName', ''),
+ ('52:54:00:cf:2d:31', '')])
+ expected = [mock.call(self.sshclient, ssh_cmd),
+ mock.call(self.sshclient, cmd_to_exec)]
+
+ found_name = ssh._get_hosts_name_for_node(self.sshclient, info)
+
+ self.assertEqual('NodeName', found_name)
+ self.assertEqual(expected, exec_ssh_mock.call_args_list)
+
+ @mock.patch.object(processutils, 'ssh_execute', autospec=True)
+ def test__get_hosts_name_for_node_no_match(self, exec_ssh_mock):
+ info = ssh._parse_driver_info(self.node)
+ info['macs'] = ["11:11:11:11:11:11", "22:22:22:22:22:22"]
+ exec_ssh_mock.side_effect = iter([('NodeName', ''),
+ ('52:54:00:cf:2d:31', '')])
+
+ ssh_cmd = "%s %s" % (info['cmd_set']['base_cmd'],
+ info['cmd_set']['list_all'])
+
+ cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'],
+ info['cmd_set']['get_node_macs'])
+
+ cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
+ expected = [mock.call(self.sshclient, ssh_cmd),
+ mock.call(self.sshclient, cmd_to_exec)]
+
+ found_name = ssh._get_hosts_name_for_node(self.sshclient, info)
+
+ self.assertIsNone(found_name)
+ self.assertEqual(expected, exec_ssh_mock.call_args_list)
+
+ @mock.patch.object(processutils, 'ssh_execute', autospec=True)
+ def test__get_hosts_name_for_node_exception(self, exec_ssh_mock):
+ info = ssh._parse_driver_info(self.node)
+ info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
+ ssh_cmd = "%s %s" % (info['cmd_set']['base_cmd'],
+ info['cmd_set']['list_all'])
+
+ cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'],
+ info['cmd_set']['get_node_macs'])
+ cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
+
+ exec_ssh_mock.side_effect = iter(
+ [('NodeName', ''), processutils.ProcessExecutionError])
+ expected = [mock.call(self.sshclient, ssh_cmd),
+ mock.call(self.sshclient, cmd_to_exec)]
+
+ self.assertRaises(exception.SSHCommandFailed,
+ ssh._get_hosts_name_for_node,
+ self.sshclient,
+ info)
+ self.assertEqual(expected, exec_ssh_mock.call_args_list)
+
+ @mock.patch.object(processutils, 'ssh_execute', autospec=True)
+ @mock.patch.object(ssh, '_get_power_status', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ def test__power_on_good(self, get_hosts_name_mock, get_power_status_mock,
+ exec_ssh_mock):
+ info = ssh._parse_driver_info(self.node)
+ info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
+
+ get_power_status_mock.side_effect = iter([states.POWER_OFF,
+ states.POWER_ON])
+ get_hosts_name_mock.return_value = "NodeName"
+ expected = [mock.call(self.sshclient, info),
+ mock.call(self.sshclient, info)]
+
+ cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'],
+ info['cmd_set']['start_cmd'])
+ cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
+ current_state = ssh._power_on(self.sshclient, info)
+
+ self.assertEqual(states.POWER_ON, current_state)
+ self.assertEqual(expected, get_power_status_mock.call_args_list)
+ get_hosts_name_mock.assert_called_once_with(self.sshclient, info)
+ exec_ssh_mock.assert_called_once_with(self.sshclient, cmd_to_exec)
+
+ @mock.patch.object(processutils, 'ssh_execute', autospec=True)
+ @mock.patch.object(ssh, '_get_power_status', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ def test__power_on_fail(self, get_hosts_name_mock, get_power_status_mock,
+ exec_ssh_mock):
+ info = ssh._parse_driver_info(self.node)
+ info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
+ get_power_status_mock.side_effect = iter([states.POWER_OFF,
+ states.POWER_OFF])
+ get_hosts_name_mock.return_value = "NodeName"
+ expected = [mock.call(self.sshclient, info),
+ mock.call(self.sshclient, info)]
+
+ cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'],
+ info['cmd_set']['start_cmd'])
+ cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
+ current_state = ssh._power_on(self.sshclient, info)
+
+ self.assertEqual(states.ERROR, current_state)
+ self.assertEqual(expected, get_power_status_mock.call_args_list)
+ get_hosts_name_mock.assert_called_once_with(self.sshclient, info)
+ exec_ssh_mock.assert_called_once_with(self.sshclient, cmd_to_exec)
+
+ @mock.patch.object(processutils, 'ssh_execute', autospec=True)
+ @mock.patch.object(ssh, '_get_power_status', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ def test__power_on_exception(self, get_hosts_name_mock,
+ get_power_status_mock, exec_ssh_mock):
+ info = ssh._parse_driver_info(self.node)
+ info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
+
+ exec_ssh_mock.side_effect = processutils.ProcessExecutionError
+ get_power_status_mock.side_effect = iter([states.POWER_OFF,
+ states.POWER_ON])
+ get_hosts_name_mock.return_value = "NodeName"
+
+ cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'],
+ info['cmd_set']['start_cmd'])
+ cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
+
+ self.assertRaises(exception.SSHCommandFailed,
+ ssh._power_on,
+ self.sshclient,
+ info)
+ get_power_status_mock.assert_called_once_with(self.sshclient, info)
+ get_hosts_name_mock.assert_called_once_with(self.sshclient, info)
+ exec_ssh_mock.assert_called_once_with(self.sshclient, cmd_to_exec)
+
+ @mock.patch.object(processutils, 'ssh_execute', autospec=True)
+ @mock.patch.object(ssh, '_get_power_status', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ def test__power_off_good(self, get_hosts_name_mock,
+ get_power_status_mock, exec_ssh_mock):
+ info = ssh._parse_driver_info(self.node)
+ info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
+ get_power_status_mock.side_effect = iter([states.POWER_ON,
+ states.POWER_OFF])
+ get_hosts_name_mock.return_value = "NodeName"
+ expected = [mock.call(self.sshclient, info),
+ mock.call(self.sshclient, info)]
+
+ cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'],
+ info['cmd_set']['stop_cmd'])
+ cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
+ current_state = ssh._power_off(self.sshclient, info)
+
+ self.assertEqual(states.POWER_OFF, current_state)
+ self.assertEqual(expected, get_power_status_mock.call_args_list)
+ get_hosts_name_mock.assert_called_once_with(self.sshclient, info)
+ exec_ssh_mock.assert_called_once_with(self.sshclient, cmd_to_exec)
+
+ @mock.patch.object(processutils, 'ssh_execute', autospec=True)
+ @mock.patch.object(ssh, '_get_power_status', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ def test__power_off_fail(self, get_hosts_name_mock,
+ get_power_status_mock, exec_ssh_mock):
+ info = ssh._parse_driver_info(self.node)
+ info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
+ get_power_status_mock.side_effect = iter([states.POWER_ON,
+ states.POWER_ON])
+ get_hosts_name_mock.return_value = "NodeName"
+ expected = [mock.call(self.sshclient, info),
+ mock.call(self.sshclient, info)]
+
+ cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'],
+ info['cmd_set']['stop_cmd'])
+ cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
+ current_state = ssh._power_off(self.sshclient, info)
+
+ self.assertEqual(states.ERROR, current_state)
+ self.assertEqual(expected, get_power_status_mock.call_args_list)
+ get_hosts_name_mock.assert_called_once_with(self.sshclient, info)
+ exec_ssh_mock.assert_called_once_with(self.sshclient, cmd_to_exec)
+
+ @mock.patch.object(processutils, 'ssh_execute', autospec=True)
+ @mock.patch.object(ssh, '_get_power_status', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ def test__power_off_exception(self, get_hosts_name_mock,
+ get_power_status_mock, exec_ssh_mock):
+ info = ssh._parse_driver_info(self.node)
+ info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
+ exec_ssh_mock.side_effect = processutils.ProcessExecutionError
+ get_power_status_mock.side_effect = iter([states.POWER_ON,
+ states.POWER_OFF])
+ get_hosts_name_mock.return_value = "NodeName"
+
+ cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'],
+ info['cmd_set']['stop_cmd'])
+ cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
+
+ self.assertRaises(exception.SSHCommandFailed, ssh._power_off,
+ self.sshclient, info)
+ get_power_status_mock.assert_called_once_with(self.sshclient, info)
+ get_hosts_name_mock.assert_called_once_with(self.sshclient, info)
+ exec_ssh_mock.assert_called_once_with(self.sshclient, cmd_to_exec)
+
+ def test_exec_ssh_command_good(self):
+ class Channel(object):
+ def recv_exit_status(self):
+ return 0
+
+ class Stream(object):
+ def __init__(self, buffer=''):
+ self.buffer = buffer
+ self.channel = Channel()
+
+ def read(self):
+ return self.buffer
+
+ def close(self):
+ pass
+
+ with mock.patch.object(self.sshclient, 'exec_command',
+ autospec=True) as exec_command_mock:
+ exec_command_mock.return_value = (Stream(),
+ Stream('hello'),
+ Stream())
+ stdout, stderr = processutils.ssh_execute(self.sshclient,
+ "command")
+
+ self.assertEqual('hello', stdout)
+ exec_command_mock.assert_called_once_with("command")
+
+ def test_exec_ssh_command_fail(self):
+ class Channel(object):
+ def recv_exit_status(self):
+ return 127
+
+ class Stream(object):
+ def __init__(self, buffer=''):
+ self.buffer = buffer
+ self.channel = Channel()
+
+ def read(self):
+ return self.buffer
+
+ def close(self):
+ pass
+
+ with mock.patch.object(self.sshclient, 'exec_command',
+ autospec=True) as exec_command_mock:
+ exec_command_mock.return_value = (Stream(),
+ Stream('hello'),
+ Stream())
+ self.assertRaises(processutils.ProcessExecutionError,
+ processutils.ssh_execute,
+ self.sshclient,
+ "command")
+ exec_command_mock.assert_called_once_with("command")
+
+
+class SSHDriverTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(SSHDriverTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="fake_ssh")
+ self.driver = driver_factory.get_driver("fake_ssh")
+ self.node = obj_utils.create_test_node(
+ self.context, driver='fake_ssh',
+ driver_info=db_utils.get_test_ssh_info())
+ self.port = obj_utils.create_test_port(self.context,
+ node_id=self.node.id)
+ self.sshclient = paramiko.SSHClient()
+
+ @mock.patch.object(utils, 'ssh_connect', autospec=True)
+ def test__validate_info_ssh_connect_failed(self, ssh_connect_mock):
+ info = ssh._parse_driver_info(self.node)
+
+ ssh_connect_mock.side_effect = iter(
+ [exception.SSHConnectFailed(host='fake')])
+ with task_manager.acquire(self.context, info['uuid'],
+ shared=False) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.power.validate, task)
+ driver_info = ssh._parse_driver_info(task.node)
+ ssh_connect_mock.assert_called_once_with(driver_info)
+
+ def test_get_properties(self):
+ expected = ssh.COMMON_PROPERTIES
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertEqual(expected, task.driver.power.get_properties())
+ self.assertEqual(expected, task.driver.get_properties())
+ self.assertEqual(expected, task.driver.management.get_properties())
+
+ def test_validate_fail_no_port(self):
+ new_node = obj_utils.create_test_node(
+ self.context,
+ uuid='aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
+ driver='fake_ssh',
+ driver_info=db_utils.get_test_ssh_info())
+ with task_manager.acquire(self.context, new_node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.MissingParameterValue,
+ task.driver.power.validate,
+ task)
+
+ @mock.patch.object(driver_utils, 'get_node_mac_addresses', autospec=True)
+ @mock.patch.object(ssh, '_get_connection', autospec=True)
+ @mock.patch.object(ssh, '_power_on', autospec=True)
+ def test_reboot_good(self, power_on_mock, get_conn_mock,
+ get_mac_addr_mock):
+ info = ssh._parse_driver_info(self.node)
+ info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
+ get_mac_addr_mock.return_value = info['macs']
+ get_conn_mock.return_value = self.sshclient
+ power_on_mock.return_value = states.POWER_ON
+ with mock.patch.object(ssh, '_parse_driver_info',
+ autospec=True) as parse_drv_info_mock:
+ parse_drv_info_mock.return_value = info
+ with task_manager.acquire(self.context, info['uuid'],
+ shared=False) as task:
+ task.driver.power.reboot(task)
+
+ parse_drv_info_mock.assert_called_once_with(task.node)
+ get_mac_addr_mock.assert_called_once_with(mock.ANY)
+ get_conn_mock.assert_called_once_with(task.node)
+ power_on_mock.assert_called_once_with(self.sshclient, info)
+
+ @mock.patch.object(driver_utils, 'get_node_mac_addresses', autospec=True)
+ @mock.patch.object(ssh, '_get_connection', autospec=True)
+ @mock.patch.object(ssh, '_power_on', autospec=True)
+ def test_reboot_fail(self, power_on_mock, get_conn_mock,
+ get_mac_addr_mock):
+ info = ssh._parse_driver_info(self.node)
+ info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
+ get_mac_addr_mock.return_value = info['macs']
+ get_conn_mock.return_value = self.sshclient
+ power_on_mock.return_value = states.POWER_OFF
+ with mock.patch.object(ssh, '_parse_driver_info',
+ autospec=True) as parse_drv_info_mock:
+ parse_drv_info_mock.return_value = info
+ with task_manager.acquire(self.context, info['uuid'],
+ shared=False) as task:
+ self.assertRaises(exception.PowerStateFailure,
+ task.driver.power.reboot, task)
+ parse_drv_info_mock.assert_called_once_with(task.node)
+ get_mac_addr_mock.assert_called_once_with(mock.ANY)
+ get_conn_mock.assert_called_once_with(task.node)
+ power_on_mock.assert_called_once_with(self.sshclient, info)
+
+ @mock.patch.object(driver_utils, 'get_node_mac_addresses', autospec=True)
+ @mock.patch.object(ssh, '_get_connection', autospec=True)
+ def test_set_power_state_bad_state(self, get_conn_mock,
+ get_mac_addr_mock):
+ info = ssh._parse_driver_info(self.node)
+ info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
+ get_mac_addr_mock.return_value = info['macs']
+ get_conn_mock.return_value = self.sshclient
+ with mock.patch.object(ssh, '_parse_driver_info',
+ autospec=True) as parse_drv_info_mock:
+ parse_drv_info_mock.return_value = info
+ with task_manager.acquire(self.context, info['uuid'],
+ shared=False) as task:
+ self.assertRaises(
+ exception.InvalidParameterValue,
+ task.driver.power.set_power_state,
+ task,
+ "BAD_PSTATE")
+
+ parse_drv_info_mock.assert_called_once_with(task.node)
+ get_mac_addr_mock.assert_called_once_with(mock.ANY)
+ get_conn_mock.assert_called_once_with(task.node)
+
+ @mock.patch.object(driver_utils, 'get_node_mac_addresses', autospec=True)
+ @mock.patch.object(ssh, '_get_connection', autospec=True)
+ @mock.patch.object(ssh, '_power_on', autospec=True)
+ def test_set_power_state_on_good(self, power_on_mock, get_conn_mock,
+ get_mac_addr_mock):
+ info = ssh._parse_driver_info(self.node)
+ info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
+ get_mac_addr_mock.return_value = info['macs']
+ get_conn_mock.return_value = self.sshclient
+ power_on_mock.return_value = states.POWER_ON
+ with mock.patch.object(ssh, '_parse_driver_info',
+ autospec=True) as parse_drv_info_mock:
+ parse_drv_info_mock.return_value = info
+ with task_manager.acquire(self.context, info['uuid'],
+ shared=False) as task:
+ task.driver.power.set_power_state(task, states.POWER_ON)
+
+ parse_drv_info_mock.assert_called_once_with(task.node)
+ get_mac_addr_mock.assert_called_once_with(mock.ANY)
+ get_conn_mock.assert_called_once_with(task.node)
+ power_on_mock.assert_called_once_with(self.sshclient, info)
+
+ @mock.patch.object(driver_utils, 'get_node_mac_addresses', autospec=True)
+ @mock.patch.object(ssh, '_get_connection', autospec=True)
+ @mock.patch.object(ssh, '_power_on', autospec=True)
+ def test_set_power_state_on_fail(self, power_on_mock, get_conn_mock,
+ get_mac_addr_mock):
+ info = ssh._parse_driver_info(self.node)
+ info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
+ get_mac_addr_mock.return_value = info['macs']
+ get_conn_mock.return_value = self.sshclient
+ power_on_mock.return_value = states.POWER_OFF
+ with mock.patch.object(ssh, '_parse_driver_info',
+ autospec=True) as parse_drv_info_mock:
+ parse_drv_info_mock.return_value = info
+ with task_manager.acquire(self.context, info['uuid'],
+ shared=False) as task:
+ self.assertRaises(
+ exception.PowerStateFailure,
+ task.driver.power.set_power_state,
+ task,
+ states.POWER_ON)
+
+ parse_drv_info_mock.assert_called_once_with(task.node)
+ get_mac_addr_mock.assert_called_once_with(mock.ANY)
+ get_conn_mock.assert_called_once_with(task.node)
+ power_on_mock.assert_called_once_with(self.sshclient, info)
+
+ @mock.patch.object(driver_utils, 'get_node_mac_addresses', autospec=True)
+ @mock.patch.object(ssh, '_get_connection', autospec=True)
+ @mock.patch.object(ssh, '_power_off', autospec=True)
+ def test_set_power_state_off_good(self, power_off_mock, get_conn_mock,
+ get_mac_addr_mock):
+ info = ssh._parse_driver_info(self.node)
+ info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
+ get_mac_addr_mock.return_value = info['macs']
+ get_conn_mock.return_value = self.sshclient
+ power_off_mock.return_value = states.POWER_OFF
+ with mock.patch.object(ssh, '_parse_driver_info',
+ autospec=True) as parse_drv_info_mock:
+ parse_drv_info_mock.return_value = info
+ with task_manager.acquire(self.context, info['uuid'],
+ shared=False) as task:
+ task.driver.power.set_power_state(task, states.POWER_OFF)
+
+ parse_drv_info_mock.assert_called_once_with(task.node)
+ get_mac_addr_mock.assert_called_once_with(mock.ANY)
+ get_conn_mock.assert_called_once_with(task.node)
+ power_off_mock.assert_called_once_with(self.sshclient, info)
+
+ @mock.patch.object(driver_utils, 'get_node_mac_addresses', autospec=True)
+ @mock.patch.object(ssh, '_get_connection', autospec=True)
+ @mock.patch.object(ssh, '_power_off', autospec=True)
+ def test_set_power_state_off_fail(self, power_off_mock, get_conn_mock,
+ get_mac_addr_mock):
+ info = ssh._parse_driver_info(self.node)
+ info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
+ get_mac_addr_mock.return_value = info['macs']
+ get_conn_mock.return_value = self.sshclient
+ power_off_mock.return_value = states.POWER_ON
+ with mock.patch.object(ssh, '_parse_driver_info',
+ autospec=True) as parse_drv_info_mock:
+ parse_drv_info_mock.return_value = info
+ with task_manager.acquire(self.context, info['uuid'],
+ shared=False) as task:
+ self.assertRaises(
+ exception.PowerStateFailure,
+ task.driver.power.set_power_state,
+ task,
+ states.POWER_OFF)
+
+ parse_drv_info_mock.assert_called_once_with(task.node)
+ get_mac_addr_mock.assert_called_once_with(mock.ANY)
+ get_conn_mock.assert_called_once_with(task.node)
+ power_off_mock.assert_called_once_with(self.sshclient, info)
+
+ @mock.patch.object(ssh, '_get_connection', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ @mock.patch.object(ssh, '_ssh_execute', autospec=True)
+ def test_management_interface_set_boot_device_vbox_ok(self, mock_exc,
+ mock_h,
+ mock_get_conn):
+ fake_name = 'fake-name'
+ mock_h.return_value = fake_name
+ mock_get_conn.return_value = self.sshclient
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node['driver_info']['ssh_virt_type'] = 'vbox'
+ self.driver.management.set_boot_device(task, boot_devices.PXE)
+ expected_cmd = ('LC_ALL=C /usr/bin/VBoxManage modifyvm %s '
+ '--boot1 net') % fake_name
+ mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
+
+ @mock.patch.object(ssh, '_get_connection', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ @mock.patch.object(ssh, '_ssh_execute', autospec=True)
+ def test_management_interface_set_boot_device_parallels_ok(self, mock_exc,
+ mock_h,
+ mock_get_conn):
+ fake_name = 'fake-name'
+ mock_h.return_value = fake_name
+ mock_get_conn.return_value = self.sshclient
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node['driver_info']['ssh_virt_type'] = 'parallels'
+ self.driver.management.set_boot_device(task, boot_devices.PXE)
+ expected_cmd = ('LC_ALL=C /usr/bin/prlctl set %s '
+ '--device-bootorder "net0"') % fake_name
+ mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
+
+ @mock.patch.object(ssh, '_get_connection', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ @mock.patch.object(ssh, '_ssh_execute', autospec=True)
+ def test_management_interface_set_boot_device_virsh_ok(self, mock_exc,
+ mock_h,
+ mock_get_conn):
+ fake_name = 'fake-name'
+ mock_h.return_value = fake_name
+ mock_get_conn.return_value = self.sshclient
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node['driver_info']['ssh_virt_type'] = 'virsh'
+ self.driver.management.set_boot_device(task, boot_devices.PXE)
+ expected_cmd = ('EDITOR="sed -i \'/<boot \\(dev\\|order\\)=*\\>'
+ '/d;/<\\/os>/i\\<boot dev=\\"network\\"/>\'" '
+ 'LC_ALL=C /usr/bin/virsh --connect qemu:///system '
+ 'edit %s') % fake_name
+ mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
+
+ def test_set_boot_device_bad_device(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ self.driver.management.set_boot_device,
+ task, 'invalid-device')
+
+ @mock.patch.object(ssh, '_get_connection', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ def test_set_boot_device_not_supported(self, mock_h, mock_get_conn):
+ mock_h.return_value = 'NodeName'
+ mock_get_conn.return_value = self.sshclient
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ # vmware does not support set_boot_device()
+ task.node['driver_info']['ssh_virt_type'] = 'vmware'
+ self.assertRaises(NotImplementedError,
+ self.driver.management.set_boot_device,
+ task, boot_devices.PXE)
+
+ def test_management_interface_get_supported_boot_devices(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ expected = [boot_devices.PXE, boot_devices.DISK,
+ boot_devices.CDROM]
+ self.assertEqual(sorted(expected), sorted(task.driver.management.
+ get_supported_boot_devices(task)))
+
+ @mock.patch.object(ssh, '_get_connection', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ @mock.patch.object(ssh, '_ssh_execute', autospec=True)
+ def test_management_interface_get_boot_device_vbox(self, mock_exc,
+ mock_h,
+ mock_get_conn):
+ fake_name = 'fake-name'
+ mock_h.return_value = fake_name
+ mock_exc.return_value = ('net', '')
+ mock_get_conn.return_value = self.sshclient
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node['driver_info']['ssh_virt_type'] = 'vbox'
+ result = self.driver.management.get_boot_device(task)
+ self.assertEqual(boot_devices.PXE, result['boot_device'])
+ expected_cmd = ('LC_ALL=C /usr/bin/VBoxManage showvminfo '
+ '--machinereadable %s '
+ '| awk -F \'"\' \'/boot1/{print $2}\'') % fake_name
+ mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
+
+ @mock.patch.object(ssh, '_get_connection', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ @mock.patch.object(ssh, '_ssh_execute', autospec=True)
+ def test_management_interface_get_boot_device_parallels(self, mock_exc,
+ mock_h,
+ mock_get_conn):
+ fake_name = 'fake-name'
+ mock_h.return_value = fake_name
+ mock_exc.return_value = ('net0', '')
+ mock_get_conn.return_value = self.sshclient
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node['driver_info']['ssh_virt_type'] = 'parallels'
+ result = self.driver.management.get_boot_device(task)
+ self.assertEqual(boot_devices.PXE, result['boot_device'])
+ expected_cmd = ('LC_ALL=C /usr/bin/prlctl list -i %s '
+ '| awk \'/^Boot order:/ {print $3}\'') % fake_name
+ mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
+
+ @mock.patch.object(ssh, '_get_connection', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ @mock.patch.object(ssh, '_ssh_execute', autospec=True)
+ def test_management_interface_get_boot_device_virsh(self, mock_exc,
+ mock_h,
+ mock_get_conn):
+ fake_name = 'fake-name'
+ mock_h.return_value = fake_name
+ mock_exc.return_value = ('network', '')
+ mock_get_conn.return_value = self.sshclient
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node['driver_info']['ssh_virt_type'] = 'virsh'
+ result = self.driver.management.get_boot_device(task)
+ self.assertEqual(boot_devices.PXE, result['boot_device'])
+ expected_cmd = ('LC_ALL=C /usr/bin/virsh --connect '
+ 'qemu:///system dumpxml %s | awk \'/boot dev=/ '
+ '{ gsub( ".*dev=" Q, "" ); gsub( Q ".*", "" ); '
+ 'print; }\' Q="\'" RS="[<>]" | head -1') % fake_name
+ mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
+
+ @mock.patch.object(ssh, '_get_connection', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ def test_get_boot_device_not_supported(self, mock_h, mock_get_conn):
+ mock_h.return_value = 'NodeName'
+ mock_get_conn.return_value = self.sshclient
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ # vmware does not support get_boot_device()
+ task.node['driver_info']['ssh_virt_type'] = 'vmware'
+ expected = {'boot_device': None, 'persistent': None}
+ self.assertEqual(expected,
+ self.driver.management.get_boot_device(task))
+
+ @mock.patch.object(ssh, '_get_connection', autospec=True)
+ @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
+ @mock.patch.object(ssh, '_ssh_execute', autospec=True)
+ def test_get_power_state_vmware(self, mock_exc, mock_h, mock_get_conn):
+ # To see replacing {_NodeName_} in vmware's list_running
+ nodename = 'fakevm'
+ mock_h.return_value = nodename
+ mock_get_conn.return_value = self.sshclient
+ # list_running quotes names
+ mock_exc.return_value = ('"%s"' % nodename, '')
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node['driver_info']['ssh_virt_type'] = 'vmware'
+ power_state = self.driver.power.get_power_state(task)
+ self.assertEqual(states.POWER_ON, power_state)
+ expected_cmd = ("LC_ALL=C /bin/vim-cmd vmsvc/power.getstate "
+ "%(node)s | grep 'Powered on' >/dev/null && "
+ "echo '\"%(node)s\"' || true") % {'node': nodename}
+ mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
+
+ def test_management_interface_validate_good(self):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.driver.management.validate(task)
+
+ def test_management_interface_validate_fail(self):
+ # Missing SSH driver_info information
+ node = obj_utils.create_test_node(self.context,
+ uuid=uuidutils.generate_uuid(),
+ driver='fake_ssh')
+ with task_manager.acquire(self.context, node.uuid) as task:
+ self.assertRaises(exception.MissingParameterValue,
+ task.driver.management.validate, task)
diff --git a/ironic/tests/unit/drivers/modules/test_virtualbox.py b/ironic/tests/unit/drivers/modules/test_virtualbox.py
new file mode 100644
index 000000000..a854cb553
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/test_virtualbox.py
@@ -0,0 +1,374 @@
+# 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.
+
+"""Test class for VirtualBox Driver Modules."""
+
+import mock
+from oslo_config import cfg
+from pyremotevbox import exception as pyremotevbox_exc
+from pyremotevbox import vbox as pyremotevbox_vbox
+
+from ironic.common import boot_devices
+from ironic.common import exception
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.drivers.modules import virtualbox
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.objects import utils as obj_utils
+
+INFO_DICT = {
+ 'virtualbox_vmname': 'baremetal1',
+ 'virtualbox_host': '10.0.2.2',
+ 'virtualbox_username': 'username',
+ 'virtualbox_password': 'password',
+ 'virtualbox_port': 12345,
+}
+
+CONF = cfg.CONF
+
+
+class VirtualBoxMethodsTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(VirtualBoxMethodsTestCase, self).setUp()
+ driver_info = INFO_DICT.copy()
+ mgr_utils.mock_the_extension_manager(driver="fake_vbox")
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_vbox',
+ driver_info=driver_info)
+
+ def test__parse_driver_info(self):
+ info = virtualbox._parse_driver_info(self.node)
+ self.assertEqual('baremetal1', info['vmname'])
+ self.assertEqual('10.0.2.2', info['host'])
+ self.assertEqual('username', info['username'])
+ self.assertEqual('password', info['password'])
+ self.assertEqual(12345, info['port'])
+
+ def test__parse_driver_info_missing_vmname(self):
+ del self.node.driver_info['virtualbox_vmname']
+ self.assertRaises(exception.MissingParameterValue,
+ virtualbox._parse_driver_info, self.node)
+
+ def test__parse_driver_info_missing_host(self):
+ del self.node.driver_info['virtualbox_host']
+ self.assertRaises(exception.MissingParameterValue,
+ virtualbox._parse_driver_info, self.node)
+
+ def test__parse_driver_info_invalid_port(self):
+ self.node.driver_info['virtualbox_port'] = 'invalid-port'
+ self.assertRaises(exception.InvalidParameterValue,
+ virtualbox._parse_driver_info, self.node)
+
+ def test__parse_driver_info_missing_port(self):
+ del self.node.driver_info['virtualbox_port']
+ info = virtualbox._parse_driver_info(self.node)
+ self.assertEqual(18083, info['port'])
+
+ @mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost', autospec=True)
+ def test__run_virtualbox_method(self, host_mock):
+ host_object_mock = mock.MagicMock(spec_set=['find_vm'])
+ func_mock = mock.MagicMock(spec_set=[])
+ vm_object_mock = mock.MagicMock(spec_set=['foo'], foo=func_mock)
+ host_mock.return_value = host_object_mock
+ host_object_mock.find_vm.return_value = vm_object_mock
+ func_mock.return_value = 'return-value'
+
+ return_value = virtualbox._run_virtualbox_method(
+ self.node, 'some-ironic-method', 'foo', 'args', kwarg='kwarg')
+
+ host_mock.assert_called_once_with(vmname='baremetal1',
+ host='10.0.2.2',
+ username='username',
+ password='password',
+ port=12345)
+ host_object_mock.find_vm.assert_called_once_with('baremetal1')
+ func_mock.assert_called_once_with('args', kwarg='kwarg')
+ self.assertEqual('return-value', return_value)
+
+ @mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost', autospec=True)
+ def test__run_virtualbox_method_get_host_fails(self, host_mock):
+ host_mock.side_effect = pyremotevbox_exc.PyRemoteVBoxException
+
+ self.assertRaises(exception.VirtualBoxOperationFailed,
+ virtualbox._run_virtualbox_method,
+ self.node, 'some-ironic-method', 'foo',
+ 'args', kwarg='kwarg')
+
+ @mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost', autospec=True)
+ def test__run_virtualbox_method_find_vm_fails(self, host_mock):
+ host_object_mock = mock.MagicMock(spec_set=['find_vm'])
+ host_mock.return_value = host_object_mock
+ exc = pyremotevbox_exc.PyRemoteVBoxException
+ host_object_mock.find_vm.side_effect = exc
+
+ self.assertRaises(exception.VirtualBoxOperationFailed,
+ virtualbox._run_virtualbox_method,
+ self.node, 'some-ironic-method', 'foo', 'args',
+ kwarg='kwarg')
+ host_mock.assert_called_once_with(vmname='baremetal1',
+ host='10.0.2.2',
+ username='username',
+ password='password',
+ port=12345)
+ host_object_mock.find_vm.assert_called_once_with('baremetal1')
+
+ @mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost', autospec=True)
+ def test__run_virtualbox_method_func_fails(self, host_mock):
+ host_object_mock = mock.MagicMock(spec_set=['find_vm'])
+ host_mock.return_value = host_object_mock
+ func_mock = mock.MagicMock()
+ vm_object_mock = mock.MagicMock(spec_set=['foo'], foo=func_mock)
+ host_object_mock.find_vm.return_value = vm_object_mock
+ func_mock.side_effect = pyremotevbox_exc.PyRemoteVBoxException
+
+ self.assertRaises(exception.VirtualBoxOperationFailed,
+ virtualbox._run_virtualbox_method,
+ self.node, 'some-ironic-method', 'foo',
+ 'args', kwarg='kwarg')
+ host_mock.assert_called_once_with(vmname='baremetal1',
+ host='10.0.2.2',
+ username='username',
+ password='password',
+ port=12345)
+ host_object_mock.find_vm.assert_called_once_with('baremetal1')
+ func_mock.assert_called_once_with('args', kwarg='kwarg')
+
+ @mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost', autospec=True)
+ def test__run_virtualbox_method_invalid_method(self, host_mock):
+ host_object_mock = mock.MagicMock(spec_set=['find_vm'])
+ host_mock.return_value = host_object_mock
+ vm_object_mock = mock.MagicMock(spec_set=[])
+ host_object_mock.find_vm.return_value = vm_object_mock
+ del vm_object_mock.foo
+
+ self.assertRaises(exception.InvalidParameterValue,
+ virtualbox._run_virtualbox_method,
+ self.node, 'some-ironic-method', 'foo',
+ 'args', kwarg='kwarg')
+ host_mock.assert_called_once_with(vmname='baremetal1',
+ host='10.0.2.2',
+ username='username',
+ password='password',
+ port=12345)
+ host_object_mock.find_vm.assert_called_once_with('baremetal1')
+
+ @mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost', autospec=True)
+ def test__run_virtualbox_method_vm_wrong_power_state(self, host_mock):
+ host_object_mock = mock.MagicMock(spec_set=['find_vm'])
+ host_mock.return_value = host_object_mock
+ func_mock = mock.MagicMock(spec_set=[])
+ vm_object_mock = mock.MagicMock(spec_set=['foo'], foo=func_mock)
+ host_object_mock.find_vm.return_value = vm_object_mock
+ func_mock.side_effect = pyremotevbox_exc.VmInWrongPowerState
+
+ # _run_virtualbox_method() doesn't catch VmInWrongPowerState and
+ # lets caller handle it.
+ self.assertRaises(pyremotevbox_exc.VmInWrongPowerState,
+ virtualbox._run_virtualbox_method,
+ self.node, 'some-ironic-method', 'foo',
+ 'args', kwarg='kwarg')
+ host_mock.assert_called_once_with(vmname='baremetal1',
+ host='10.0.2.2',
+ username='username',
+ password='password',
+ port=12345)
+ host_object_mock.find_vm.assert_called_once_with('baremetal1')
+ func_mock.assert_called_once_with('args', kwarg='kwarg')
+
+
+class VirtualBoxPowerTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(VirtualBoxPowerTestCase, self).setUp()
+ driver_info = INFO_DICT.copy()
+ mgr_utils.mock_the_extension_manager(driver="fake_vbox")
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_vbox',
+ driver_info=driver_info)
+
+ def test_get_properties(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ properties = task.driver.power.get_properties()
+
+ self.assertIn('virtualbox_vmname', properties)
+ self.assertIn('virtualbox_host', properties)
+
+ @mock.patch.object(virtualbox, '_parse_driver_info', autospec=True)
+ def test_validate(self, parse_info_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.power.validate(task)
+ parse_info_mock.assert_called_once_with(task.node)
+
+ @mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
+ def test_get_power_state(self, run_method_mock):
+ run_method_mock.return_value = 'PoweredOff'
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ power_state = task.driver.power.get_power_state(task)
+ run_method_mock.assert_called_once_with(task.node,
+ 'get_power_state',
+ 'get_power_status')
+ self.assertEqual(states.POWER_OFF, power_state)
+
+ @mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
+ def test_get_power_state_invalid_state(self, run_method_mock):
+ run_method_mock.return_value = 'invalid-state'
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ power_state = task.driver.power.get_power_state(task)
+ run_method_mock.assert_called_once_with(task.node,
+ 'get_power_state',
+ 'get_power_status')
+ self.assertEqual(states.ERROR, power_state)
+
+ @mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
+ def test_set_power_state_off(self, run_method_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.power.set_power_state(task, states.POWER_OFF)
+ run_method_mock.assert_called_once_with(task.node,
+ 'set_power_state',
+ 'stop')
+
+ @mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
+ def test_set_power_state_on(self, run_method_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.power.set_power_state(task, states.POWER_ON)
+ run_method_mock.assert_called_once_with(task.node,
+ 'set_power_state',
+ 'start')
+
+ @mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
+ def test_set_power_state_reboot(self, run_method_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.power.set_power_state(task, states.REBOOT)
+ run_method_mock.assert_any_call(task.node,
+ 'reboot',
+ 'stop')
+ run_method_mock.assert_any_call(task.node,
+ 'reboot',
+ 'start')
+
+ def test_set_power_state_invalid_state(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.power.set_power_state,
+ task, 'invalid-state')
+
+ @mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
+ def test_reboot(self, run_method_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.power.reboot(task)
+ run_method_mock.assert_any_call(task.node,
+ 'reboot',
+ 'stop')
+ run_method_mock.assert_any_call(task.node,
+ 'reboot',
+ 'start')
+
+
+class VirtualBoxManagementTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(VirtualBoxManagementTestCase, self).setUp()
+ driver_info = INFO_DICT.copy()
+ mgr_utils.mock_the_extension_manager(driver="fake_vbox")
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_vbox',
+ driver_info=driver_info)
+
+ def test_get_properties(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ properties = task.driver.management.get_properties()
+
+ self.assertIn('virtualbox_vmname', properties)
+ self.assertIn('virtualbox_host', properties)
+
+ @mock.patch.object(virtualbox, '_parse_driver_info', autospec=True)
+ def test_validate(self, parse_info_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.validate(task)
+ parse_info_mock.assert_called_once_with(task.node)
+
+ def test_get_supported_boot_devices(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ devices = task.driver.management.get_supported_boot_devices(task)
+ self.assertIn(boot_devices.PXE, devices)
+ self.assertIn(boot_devices.DISK, devices)
+ self.assertIn(boot_devices.CDROM, devices)
+
+ @mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
+ def test_get_boot_device_ok(self, run_method_mock):
+ run_method_mock.return_value = 'Network'
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ret_val = task.driver.management.get_boot_device(task)
+ run_method_mock.assert_called_once_with(task.node,
+ 'get_boot_device',
+ 'get_boot_device')
+ self.assertEqual(boot_devices.PXE, ret_val['boot_device'])
+ self.assertTrue(ret_val['persistent'])
+
+ @mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
+ def test_get_boot_device_invalid(self, run_method_mock):
+ run_method_mock.return_value = 'invalid-boot-device'
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ret_val = task.driver.management.get_boot_device(task)
+ self.assertIsNone(ret_val['boot_device'])
+ self.assertIsNone(ret_val['persistent'])
+
+ @mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
+ def test_set_boot_device_ok(self, run_method_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.set_boot_device(task, boot_devices.PXE)
+ run_method_mock.assert_called_once_with(task.node,
+ 'set_boot_device',
+ 'set_boot_device',
+ 'Network')
+
+ @mock.patch.object(virtualbox, 'LOG', autospec=True)
+ @mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
+ def test_set_boot_device_wrong_power_state(self, run_method_mock,
+ log_mock):
+ run_method_mock.side_effect = pyremotevbox_exc.VmInWrongPowerState
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.set_boot_device(task, boot_devices.PXE)
+ log_mock.error.assert_called_once_with(mock.ANY, mock.ANY)
+
+ @mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
+ def test_set_boot_device_invalid(self, run_method_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.management.set_boot_device,
+ task, 'invalid-boot-device')
+
+ def test_get_sensors_data(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(NotImplementedError,
+ task.driver.management.get_sensors_data,
+ task)
diff --git a/ironic/tests/unit/drivers/modules/test_wol.py b/ironic/tests/unit/drivers/modules/test_wol.py
new file mode 100644
index 000000000..f8e02e298
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/test_wol.py
@@ -0,0 +1,194 @@
+# Copyright 2015 Red Hat, 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
+# 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.
+
+"""Test class for Wake-On-Lan driver module."""
+
+import socket
+import time
+
+import mock
+from oslo_utils import uuidutils
+
+from ironic.common import driver_factory
+from ironic.common import exception
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.drivers.modules import wol
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.objects import utils as obj_utils
+
+
+@mock.patch.object(time, 'sleep', lambda *_: None)
+class WakeOnLanPrivateMethodTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(WakeOnLanPrivateMethodTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_wol')
+ self.driver = driver_factory.get_driver('fake_wol')
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_wol')
+ self.port = obj_utils.create_test_port(self.context,
+ node_id=self.node.id)
+
+ def test__parse_parameters(self):
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=True) as task:
+ params = wol._parse_parameters(task)
+ self.assertEqual('255.255.255.255', params['host'])
+ self.assertEqual(9, params['port'])
+
+ def test__parse_parameters_non_default_params(self):
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=True) as task:
+ task.node.driver_info = {'wol_host': '1.2.3.4',
+ 'wol_port': 7}
+ params = wol._parse_parameters(task)
+ self.assertEqual('1.2.3.4', params['host'])
+ self.assertEqual(7, params['port'])
+
+ def test__parse_parameters_no_ports_fail(self):
+ node = obj_utils.create_test_node(
+ self.context,
+ uuid=uuidutils.generate_uuid(),
+ driver='fake_wol')
+ with task_manager.acquire(
+ self.context, node.uuid, shared=True) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ wol._parse_parameters, task)
+
+ @mock.patch.object(socket, 'socket', autospec=True, spec_set=True)
+ def test_send_magic_packets(self, mock_socket):
+ fake_socket = mock.Mock(spec=socket, spec_set=True)
+ mock_socket.return_value = fake_socket()
+ obj_utils.create_test_port(self.context,
+ uuid=uuidutils.generate_uuid(),
+ address='aa:bb:cc:dd:ee:ff',
+ node_id=self.node.id)
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=True) as task:
+ wol._send_magic_packets(task, '255.255.255.255', 9)
+
+ expected_calls = [
+ mock.call(),
+ mock.call().setsockopt(socket.SOL_SOCKET,
+ socket.SO_BROADCAST, 1),
+ mock.call().sendto(mock.ANY, ('255.255.255.255', 9)),
+ mock.call().sendto(mock.ANY, ('255.255.255.255', 9)),
+ mock.call().close()]
+
+ fake_socket.assert_has_calls(expected_calls)
+ self.assertEqual(1, mock_socket.call_count)
+
+ @mock.patch.object(socket, 'socket', autospec=True, spec_set=True)
+ def test_send_magic_packets_network_sendto_error(self, mock_socket):
+ fake_socket = mock.Mock(spec=socket, spec_set=True)
+ fake_socket.return_value.sendto.side_effect = socket.error('boom')
+ mock_socket.return_value = fake_socket()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=True) as task:
+ self.assertRaises(exception.WolOperationError,
+ wol._send_magic_packets,
+ task, '255.255.255.255', 9)
+ self.assertEqual(1, mock_socket.call_count)
+ # assert sendt0() was invoked
+ fake_socket.return_value.sendto.assert_called_once_with(
+ mock.ANY, ('255.255.255.255', 9))
+
+ @mock.patch.object(socket, 'socket', autospec=True, spec_set=True)
+ def test_magic_packet_format(self, mock_socket):
+ fake_socket = mock.Mock(spec=socket, spec_set=True)
+ mock_socket.return_value = fake_socket()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=True) as task:
+ wol._send_magic_packets(task, '255.255.255.255', 9)
+
+ expct_packet = (b'\xff\xff\xff\xff\xff\xffRT\x00\xcf-1RT\x00'
+ b'\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT'
+ b'\x00\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT\x00'
+ b'\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT'
+ b'\x00\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT\x00\xcf-1')
+ mock_socket.return_value.sendto.assert_called_once_with(
+ expct_packet, ('255.255.255.255', 9))
+
+
+@mock.patch.object(time, 'sleep', lambda *_: None)
+class WakeOnLanDriverTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(WakeOnLanDriverTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_wol')
+ self.driver = driver_factory.get_driver('fake_wol')
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_wol')
+ self.port = obj_utils.create_test_port(self.context,
+ node_id=self.node.id)
+
+ def test_get_properties(self):
+ expected = wol.COMMON_PROPERTIES
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=True) as task:
+ self.assertEqual(expected, task.driver.get_properties())
+
+ def test_get_power_state(self):
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=True) as task:
+ task.node.power_state = states.POWER_ON
+ pstate = task.driver.power.get_power_state(task)
+ self.assertEqual(states.POWER_ON, pstate)
+
+ def test_get_power_state_nostate(self):
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=True) as task:
+ task.node.power_state = states.NOSTATE
+ pstate = task.driver.power.get_power_state(task)
+ self.assertEqual(states.POWER_OFF, pstate)
+
+ @mock.patch.object(wol, '_send_magic_packets', autospec=True,
+ spec_set=True)
+ def test_set_power_state_power_on(self, mock_magic):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.driver.power.set_power_state(task, states.POWER_ON)
+ mock_magic.assert_called_once_with(task, '255.255.255.255', 9)
+
+ @mock.patch.object(wol.LOG, 'info', autospec=True, spec_set=True)
+ @mock.patch.object(wol, '_send_magic_packets', autospec=True,
+ spec_set=True)
+ def test_set_power_state_power_off(self, mock_magic, mock_log):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.driver.power.set_power_state(task, states.POWER_OFF)
+ mock_log.assert_called_once_with(mock.ANY, self.node.uuid)
+ # assert magic packets weren't sent
+ self.assertFalse(mock_magic.called)
+
+ @mock.patch.object(wol, '_send_magic_packets', autospec=True,
+ spec_set=True)
+ def test_set_power_state_power_fail(self, mock_magic):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ task.driver.power.set_power_state,
+ task, 'wrong-state')
+ # assert magic packets weren't sent
+ self.assertFalse(mock_magic.called)
+
+ @mock.patch.object(wol.LOG, 'info', autospec=True, spec_set=True)
+ @mock.patch.object(wol.WakeOnLanPower, 'set_power_state', autospec=True,
+ spec_set=True)
+ def test_reboot(self, mock_power, mock_log):
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.driver.power.reboot(task)
+ mock_log.assert_called_once_with(mock.ANY, self.node.uuid)
+ mock_power.assert_called_once_with(task.driver.power, task,
+ states.POWER_ON)
diff --git a/ironic/tests/unit/drivers/modules/ucs/__init__.py b/ironic/tests/unit/drivers/modules/ucs/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ucs/__init__.py
diff --git a/ironic/tests/unit/drivers/modules/ucs/test_helper.py b/ironic/tests/unit/drivers/modules/ucs/test_helper.py
new file mode 100644
index 000000000..c83e82d46
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ucs/test_helper.py
@@ -0,0 +1,161 @@
+# Copyright 2015, Cisco Systems.
+
+# 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.
+
+"""Test class for common methods used by UCS modules."""
+
+import mock
+from oslo_config import cfg
+from oslo_utils import importutils
+
+from ironic.common import exception
+from ironic.conductor import task_manager
+from ironic.db import api as dbapi
+from ironic.drivers.modules.ucs import helper as ucs_helper
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+ucs_error = importutils.try_import('UcsSdk.utils.exception')
+
+INFO_DICT = db_utils.get_test_ucs_info()
+CONF = cfg.CONF
+
+
+class UcsValidateParametersTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(UcsValidateParametersTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver="fake_ucs")
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_ucs',
+ driver_info=INFO_DICT)
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.helper = ucs_helper.CiscoUcsHelper(task)
+
+ def test_parse_driver_info(self):
+ info = ucs_helper.parse_driver_info(self.node)
+
+ self.assertIsNotNone(info.get('ucs_address'))
+ self.assertIsNotNone(info.get('ucs_username'))
+ self.assertIsNotNone(info.get('ucs_password'))
+ self.assertIsNotNone(info.get('ucs_service_profile'))
+
+ def test_parse_driver_info_missing_address(self):
+
+ del self.node.driver_info['ucs_address']
+ self.assertRaises(exception.MissingParameterValue,
+ ucs_helper.parse_driver_info, self.node)
+
+ def test_parse_driver_info_missing_username(self):
+ del self.node.driver_info['ucs_username']
+ self.assertRaises(exception.MissingParameterValue,
+ ucs_helper.parse_driver_info, self.node)
+
+ def test_parse_driver_info_missing_password(self):
+ del self.node.driver_info['ucs_password']
+ self.assertRaises(exception.MissingParameterValue,
+ ucs_helper.parse_driver_info, self.node)
+
+ def test_parse_driver_info_missing_service_profile(self):
+ del self.node.driver_info['ucs_service_profile']
+ self.assertRaises(exception.MissingParameterValue,
+ ucs_helper.parse_driver_info, self.node)
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ def test_connect_ucsm(self, mock_helper):
+ mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.helper.connect_ucsm()
+
+ mock_helper.generate_ucsm_handle.assert_called_once_with(
+ task.node.driver_info['ucs_address'],
+ task.node.driver_info['ucs_username'],
+ task.node.driver_info['ucs_password']
+ )
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ def test_connect_ucsm_fail(self, mock_helper):
+ side_effect = ucs_error.UcsConnectionError(
+ message='connecting to ucsm',
+ error='failed')
+ mock_helper.generate_ucsm_handle.side_effect = side_effect
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.UcsConnectionError,
+ self.helper.connect_ucsm
+ )
+ mock_helper.generate_ucsm_handle.assert_called_once_with(
+ task.node.driver_info['ucs_address'],
+ task.node.driver_info['ucs_username'],
+ task.node.driver_info['ucs_password']
+ )
+
+ @mock.patch('ironic.drivers.modules.ucs.helper',
+ autospec=True)
+ def test_logout(self, mock_helper):
+ self.helper.logout()
+
+
+class UcsCommonMethodsTestcase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(UcsCommonMethodsTestcase, self).setUp()
+ self.dbapi = dbapi.get_instance()
+ mgr_utils.mock_the_extension_manager(driver="fake_ucs")
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_ucs',
+ driver_info=INFO_DICT.copy())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.helper = ucs_helper.CiscoUcsHelper(task)
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper', autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.helper.CiscoUcsHelper',
+ autospec=True)
+ def test_requires_ucs_client_ok_logout(self, mc_helper, mock_ucs_helper):
+ mock_helper = mc_helper.return_value
+ mock_helper.logout.return_value = None
+ mock_working_function = mock.Mock()
+ mock_working_function.__name__ = "Working"
+ mock_working_function.return_valure = "Success"
+ mock_ucs_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ wont_error = ucs_helper.requires_ucs_client(
+ mock_working_function)
+ wont_error(wont_error, task)
+ mock_helper.logout.assert_called_once_with()
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper', autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.helper.CiscoUcsHelper',
+ autospec=True)
+ def test_requires_ucs_client_fail_logout(self, mc_helper, mock_ucs_helper):
+ mock_helper = mc_helper.return_value
+ mock_helper.logout.return_value = None
+ mock_broken_function = mock.Mock()
+ mock_broken_function.__name__ = "Broken"
+ mock_broken_function.side_effect = exception.IronicException()
+ mock_ucs_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+
+ will_error = ucs_helper.requires_ucs_client(mock_broken_function)
+ self.assertRaises(exception.IronicException,
+ will_error, will_error, task)
+ mock_helper.logout.assert_called_once_with()
diff --git a/ironic/tests/unit/drivers/modules/ucs/test_management.py b/ironic/tests/unit/drivers/modules/ucs/test_management.py
new file mode 100644
index 000000000..17101756b
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ucs/test_management.py
@@ -0,0 +1,139 @@
+# Copyright 2015, Cisco Systems.
+
+# 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.
+
+"""
+Test class for UCS ManagementInterface
+"""
+
+import mock
+from oslo_config import cfg
+from oslo_utils import importutils
+
+from ironic.common import boot_devices
+from ironic.common import exception
+from ironic.conductor import task_manager
+from ironic.drivers.modules.ucs import helper as ucs_helper
+from ironic.drivers.modules.ucs import management as ucs_mgmt
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+ucs_error = importutils.try_import('UcsSdk.utils.exception')
+
+INFO_DICT = db_utils.get_test_ucs_info()
+CONF = cfg.CONF
+
+
+class UcsManagementTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(UcsManagementTestCase, self).setUp()
+ mgr_utils.mock_the_extension_manager(driver='fake_ucs')
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_ucs',
+ driver_info=INFO_DICT)
+ self.interface = ucs_mgmt.UcsManagement()
+ self.task = mock.Mock()
+ self.task.node = self.node
+
+ def test_get_properties(self):
+ expected = ucs_helper.COMMON_PROPERTIES
+ self.assertEqual(expected, self.interface.get_properties())
+
+ def test_get_supported_boot_devices(self):
+ expected = [boot_devices.PXE, boot_devices.DISK, boot_devices.CDROM]
+ self.assertEqual(sorted(expected),
+ sorted(self.interface.get_supported_boot_devices()))
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ @mock.patch(
+ 'ironic.drivers.modules.ucs.management.ucs_mgmt.BootDeviceHelper',
+ spec_set=True, autospec=True)
+ def test_get_boot_device(self, mock_ucs_mgmt, mock_helper):
+ mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ mock_mgmt = mock_ucs_mgmt.return_value
+ mock_mgmt.get_boot_device.return_value = {
+ 'boot_device': 'disk',
+ 'persistent': False
+ }
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ expected_device = boot_devices.DISK
+ expected_response = {'boot_device': expected_device,
+ 'persistent': False}
+ self.assertEqual(expected_response,
+ self.interface.get_boot_device(task))
+ mock_mgmt.get_boot_device.assert_called_once_with()
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ @mock.patch(
+ 'ironic.drivers.modules.ucs.management.ucs_mgmt.BootDeviceHelper',
+ spec_set=True, autospec=True)
+ def test_get_boot_device_fail(self, mock_ucs_mgmt, mock_helper):
+ mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ mock_mgmt = mock_ucs_mgmt.return_value
+ side_effect = ucs_error.UcsOperationError(
+ operation='getting boot device',
+ error='failed',
+ node=self.node.uuid
+ )
+ mock_mgmt.get_boot_device.side_effect = side_effect
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.UcsOperationError,
+ self.interface.get_boot_device,
+ task)
+ mock_mgmt.get_boot_device.assert_called_once_with()
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ @mock.patch(
+ 'ironic.drivers.modules.ucs.management.ucs_mgmt.BootDeviceHelper',
+ spec_set=True, autospec=True)
+ def test_set_boot_device(self, mock_mgmt, mock_helper):
+ mc_mgmt = mock_mgmt.return_value
+ mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.interface.set_boot_device(task, boot_devices.CDROM)
+
+ mc_mgmt.set_boot_device.assert_called_once_with('cdrom', False)
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ @mock.patch(
+ 'ironic.drivers.modules.ucs.management.ucs_mgmt.BootDeviceHelper',
+ spec_set=True, autospec=True)
+ def test_set_boot_device_fail(self, mock_mgmt, mock_helper):
+ mc_mgmt = mock_mgmt.return_value
+ mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ side_effect = exception.UcsOperationError(
+ operation='setting boot device',
+ error='failed',
+ node=self.node.uuid)
+ mc_mgmt.set_boot_device.side_effect = side_effect
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.IronicException,
+ self.interface.set_boot_device,
+ task, boot_devices.PXE)
+ mc_mgmt.set_boot_device.assert_called_once_with(
+ boot_devices.PXE, False)
+
+ def test_get_sensors_data(self):
+ self.assertRaises(NotImplementedError,
+ self.interface.get_sensors_data, self.task)
diff --git a/ironic/tests/unit/drivers/modules/ucs/test_power.py b/ironic/tests/unit/drivers/modules/ucs/test_power.py
new file mode 100644
index 000000000..a01a96bbd
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ucs/test_power.py
@@ -0,0 +1,302 @@
+# Copyright 2015, Cisco Systems.
+
+# 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.
+
+"""Test class for UcsPower module."""
+import mock
+from oslo_config import cfg
+from oslo_utils import importutils
+
+from ironic.common import exception
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.drivers.modules.ucs import helper as ucs_helper
+from ironic.drivers.modules.ucs import power as ucs_power
+from ironic.tests.unit.conductor import utils as mgr_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+ucs_error = importutils.try_import('UcsSdk.utils.exception')
+
+INFO_DICT = db_utils.get_test_ucs_info()
+CONF = cfg.CONF
+
+
+class UcsPowerTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(UcsPowerTestCase, self).setUp()
+ driver_info = INFO_DICT
+ mgr_utils.mock_the_extension_manager(driver="fake_ucs")
+ self.node = obj_utils.create_test_node(self.context,
+ driver='fake_ucs',
+ driver_info=driver_info)
+ CONF.set_override('max_retry', 2, 'cisco_ucs')
+ CONF.set_override('action_interval', 0, 'cisco_ucs')
+ self.interface = ucs_power.Power()
+
+ def test_get_properties(self):
+ expected = ucs_helper.COMMON_PROPERTIES
+ expected.update(ucs_helper.COMMON_PROPERTIES)
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertEqual(expected, task.driver.get_properties())
+
+ @mock.patch.object(ucs_helper, 'parse_driver_info',
+ spec_set=True, autospec=True)
+ def test_validate(self, mock_parse_driver_info):
+ mock_parse_driver_info.return_value = {}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.interface.validate(task)
+ mock_parse_driver_info.assert_called_once_with(task.node)
+
+ @mock.patch.object(ucs_helper, 'parse_driver_info',
+ spec_set=True, autospec=True)
+ def test_validate_fail(self, mock_parse_driver_info):
+ side_effect = iter([exception.InvalidParameterValue('Invalid Input')])
+ mock_parse_driver_info.side_effect = side_effect
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ self.interface.validate,
+ task)
+ mock_parse_driver_info.assert_called_once_with(task.node)
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
+ spec_set=True, autospec=True)
+ def test_get_power_state_up(self, mock_power_helper, mock_helper):
+ mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ mock_power = mock_power_helper.return_value
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ mock_power.get_power_state.return_value = 'up'
+ self.assertEqual(states.POWER_ON,
+ self.interface.get_power_state(task))
+ mock_power.get_power_state.assert_called_once_with()
+ mock_power.get_power_state.reset_mock()
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
+ spec_set=True, autospec=True)
+ def test_get_power_state_down(self, mock_power_helper, mock_helper):
+ mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ mock_power = mock_power_helper.return_value
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ mock_power.get_power_state.return_value = 'down'
+ self.assertEqual(states.POWER_OFF,
+ self.interface.get_power_state(task))
+ mock_power.get_power_state.assert_called_once_with()
+ mock_power.get_power_state.reset_mock()
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
+ spec_set=True, autospec=True)
+ def test_get_power_state_error(self, mock_power_helper, mock_helper):
+ mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ mock_power = mock_power_helper.return_value
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ mock_power.get_power_state.return_value = states.ERROR
+ self.assertEqual(states.ERROR,
+ self.interface.get_power_state(task))
+ mock_power.get_power_state.assert_called_once_with()
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
+ spec_set=True, autospec=True)
+ def test_get_power_state_fail(self,
+ mock_ucs_power,
+ mock_helper):
+ mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ power = mock_ucs_power.return_value
+ power.get_power_state.side_effect = (
+ ucs_error.UcsOperationError(operation='getting power state',
+ error='failed'))
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.UcsOperationError,
+ self.interface.get_power_state,
+ task)
+ power.get_power_state.assert_called_with()
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.power._wait_for_state_change',
+ spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
+ spec_set=True, autospec=True)
+ def test_set_power_state(self, mock_power_helper, mock__wait, mock_helper):
+ target_state = states.POWER_ON
+ mock_power = mock_power_helper.return_value
+ mock_power.get_power_state.side_effect = ['down', 'up']
+ mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ mock__wait.return_value = target_state
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertIsNone(self.interface.set_power_state(task,
+ target_state))
+
+ mock_power.set_power_state.assert_called_once_with('up')
+ mock_power.get_power_state.assert_called_once_with()
+ mock__wait.assert_called_once_with(target_state, mock_power)
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
+ spec_set=True, autospec=True)
+ def test_set_power_state_fail(self, mock_power_helper, mock_helper):
+ mock_power = mock_power_helper.return_value
+ mock_power.set_power_state.side_effect = (
+ ucs_error.UcsOperationError(operation='setting power state',
+ error='failed'))
+ mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.UcsOperationError,
+ self.interface.set_power_state,
+ task, states.POWER_OFF)
+ mock_power.set_power_state.assert_called_once_with('down')
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ def test_set_power_state_invalid_state(self, mock_helper):
+ mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.InvalidParameterValue,
+ self.interface.set_power_state,
+ task, states.ERROR)
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
+ spec_set=True, autospec=True)
+ def test__wait_for_state_change_already_target_state(
+ self,
+ mock_ucs_power,
+ mock_helper):
+ mock_power = mock_ucs_power.return_value
+ target_state = states.POWER_ON
+ mock_power.get_power_state.return_value = 'up'
+ mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ self.assertEqual(states.POWER_ON,
+ ucs_power._wait_for_state_change(
+ target_state, mock_power))
+ mock_power.get_power_state.assert_called_with()
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
+ spec_set=True, autospec=True)
+ def test__wait_for_state_change_exceed_iterations(
+ self,
+ mock_power_helper,
+ mock_helper):
+ mock_power = mock_power_helper.return_value
+ target_state = states.POWER_ON
+ mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ mock_power.get_power_state.side_effect = (
+ ['down', 'down', 'down', 'down'])
+ self.assertEqual(states.ERROR,
+ ucs_power._wait_for_state_change(
+ target_state, mock_power)
+ )
+ mock_power.get_power_state.assert_called_with()
+ self.assertEqual(4, mock_power.get_power_state.call_count)
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.power._wait_for_state_change',
+ spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
+ spec_set=True, autospec=True)
+ def test_set_and_wait_for_state_change_fail(
+ self,
+ mock_power_helper,
+ mock__wait,
+ mock_helper):
+ target_state = states.POWER_ON
+ mock_power = mock_power_helper.return_value
+ mock_power.get_power_state.return_value = 'down'
+ mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ mock__wait.return_value = states.POWER_OFF
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.PowerStateFailure,
+ self.interface.set_power_state,
+ task,
+ target_state)
+
+ mock_power.set_power_state.assert_called_once_with('up')
+ mock_power.get_power_state.assert_called_once_with()
+ mock__wait.assert_called_once_with(target_state, mock_power)
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.power._wait_for_state_change',
+ spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
+ spec_set=True, autospec=True)
+ def test_reboot(self, mock_power_helper, mock__wait, mock_helper):
+ mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ mock_power = mock_power_helper.return_value
+ mock__wait.return_value = states.POWER_ON
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertIsNone(self.interface.reboot(task))
+ mock_power.reboot.assert_called_once_with()
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
+ spec_set=True, autospec=True)
+ def test_reboot_fail(self, mock_power_helper,
+ mock_ucs_helper):
+ mock_ucs_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ mock_power = mock_power_helper.return_value
+ mock_power.reboot.side_effect = (
+ ucs_error.UcsOperationError(operation='rebooting', error='failed'))
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.UcsOperationError,
+ self.interface.reboot,
+ task
+ )
+ mock_power.reboot.assert_called_once_with()
+
+ @mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
+ spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.power._wait_for_state_change',
+ spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
+ spec_set=True, autospec=True)
+ def test_reboot__wait_state_change_fail(self, mock_power_helper,
+ mock__wait,
+ mock_ucs_helper):
+ mock_ucs_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
+ mock_power = mock_power_helper.return_value
+ mock__wait.return_value = states.ERROR
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.PowerStateFailure,
+ self.interface.reboot,
+ task)
+ mock_power.reboot.assert_called_once_with()