summaryrefslogtreecommitdiff
path: root/ironic/tests
diff options
context:
space:
mode:
authorQianbiao NG <iampurse@vip.qq.com>2019-02-20 10:56:04 +0800
committerQianbiao NG <iampurse@vip.qq.com>2019-03-14 11:04:29 +0800
commitf1f4f892fe95f95128f8b872d4a3d32dc9cdb5fd (patch)
tree3b8773179673dc873a5764f241489676393dd0d0 /ironic/tests
parentec2f7f992e1cca141df26441bdbf9cd9c682541c (diff)
downloadironic-f1f4f892fe95f95128f8b872d4a3d32dc9cdb5fd.tar.gz
Add Huawei iBMC driver support
This patch proposes to adding iBMC driver for deploying the Huawei 2288H V5, CH121 V5 series servers. The driver aims to add management and power interfaces using Huawei iBMC RESTful APIs for those series servers. Change-Id: Ic5e920e4e58811c6a6dfe927732595950aea64e7 Story: 2004635 Task: 28566
Diffstat (limited to 'ironic/tests')
-rw-r--r--ironic/tests/unit/db/utils.py9
-rw-r--r--ironic/tests/unit/drivers/modules/ibmc/__init__.py0
-rw-r--r--ironic/tests/unit/drivers/modules/ibmc/base.py42
-rw-r--r--ironic/tests/unit/drivers/modules/ibmc/test_management.py276
-rw-r--r--ironic/tests/unit/drivers/modules/ibmc/test_power.py284
-rw-r--r--ironic/tests/unit/drivers/modules/ibmc/test_utils.py172
-rw-r--r--ironic/tests/unit/drivers/modules/ibmc/test_vendor.py60
-rw-r--r--ironic/tests/unit/drivers/test_ibmc.py47
-rw-r--r--ironic/tests/unit/drivers/third_party_driver_mock_specs.py7
-rw-r--r--ironic/tests/unit/drivers/third_party_driver_mocks.py43
10 files changed, 940 insertions, 0 deletions
diff --git a/ironic/tests/unit/db/utils.py b/ironic/tests/unit/db/utils.py
index 2d44efa1a..4c7354ef2 100644
--- a/ironic/tests/unit/db/utils.py
+++ b/ironic/tests/unit/db/utils.py
@@ -671,3 +671,12 @@ def create_test_deploy_template(**kw):
if 'id' not in kw_step:
del template_step['id']
return dbapi.create_deploy_template(template)
+
+
+def get_test_ibmc_info():
+ return {
+ "ibmc_address": "https://example.com",
+ "ibmc_username": "username",
+ "ibmc_password": "password",
+ "verify_ca": False,
+ }
diff --git a/ironic/tests/unit/drivers/modules/ibmc/__init__.py b/ironic/tests/unit/drivers/modules/ibmc/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ibmc/__init__.py
diff --git a/ironic/tests/unit/drivers/modules/ibmc/base.py b/ironic/tests/unit/drivers/modules/ibmc/base.py
new file mode 100644
index 000000000..cb337207e
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ibmc/base.py
@@ -0,0 +1,42 @@
+#
+# 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 base class for iBMC Driver."""
+
+import mock
+
+from ironic.drivers.modules.ibmc import 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
+
+
+class IBMCTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IBMCTestCase, self).setUp()
+ self.driver_info = db_utils.get_test_ibmc_info()
+ self.config(enabled_hardware_types=['ibmc'],
+ enabled_power_interfaces=['ibmc'],
+ enabled_management_interfaces=['ibmc'],
+ enabled_vendor_interfaces=['ibmc'])
+ self.node = obj_utils.create_test_node(
+ self.context, driver='ibmc', driver_info=self.driver_info)
+ self.ibmc = utils.parse_driver_info(self.node)
+
+ @staticmethod
+ def mock_ibmc_conn(ibmc_client_connect):
+ conn = mock.Mock(system=mock.PropertyMock())
+ conn.__enter__ = mock.Mock(return_value=conn)
+ conn.__exit__ = mock.Mock(return_value=None)
+ ibmc_client_connect.return_value = conn
+ return conn
diff --git a/ironic/tests/unit/drivers/modules/ibmc/test_management.py b/ironic/tests/unit/drivers/modules/ibmc/test_management.py
new file mode 100644
index 000000000..d45a23304
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ibmc/test_management.py
@@ -0,0 +1,276 @@
+#
+# 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 iBMC Management interface."""
+
+import itertools
+
+import mock
+from oslo_utils import importutils
+
+from ironic.common import boot_devices
+from ironic.common import boot_modes
+from ironic.common import exception
+from ironic.conductor import task_manager
+from ironic.drivers.modules.ibmc import mappings
+from ironic.drivers.modules.ibmc import utils
+from ironic.tests.unit.drivers.modules.ibmc import base
+
+constants = importutils.try_import('ibmc_client.constants')
+ibmc_client = importutils.try_import('ibmc_client')
+ibmc_error = importutils.try_import('ibmc_client.exceptions')
+
+
+class IBMCManagementTestCase(base.IBMCTestCase):
+
+ 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 utils.COMMON_PROPERTIES:
+ self.assertIn(prop, properties)
+
+ @mock.patch.object(utils, 'parse_driver_info', autospec=True)
+ def test_validate(self, mock_parse_driver_info):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.driver.management.validate(task)
+ mock_parse_driver_info.assert_called_once_with(task.node)
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_get_supported_boot_devices(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ # mock return value
+ _supported_boot_devices = list(mappings.GET_BOOT_DEVICE_MAP)
+ conn.system.get.return_value = mock.Mock(
+ boot_source_override=mock.Mock(
+ supported_boot_devices=_supported_boot_devices
+ )
+ )
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ supported_boot_devices = (
+ task.driver.management.get_supported_boot_devices(task))
+ connect_ibmc.assert_called_once_with(**self.ibmc)
+ expect = sorted(list(mappings.GET_BOOT_DEVICE_MAP.values()))
+ self.assertEqual(expect, sorted(supported_boot_devices))
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_set_boot_device(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ # mock return value
+ conn.system.set_boot_source.return_value = None
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ device_mapping = [
+ (boot_devices.PXE, constants.BOOT_SOURCE_TARGET_PXE),
+ (boot_devices.DISK, constants.BOOT_SOURCE_TARGET_HDD),
+ (boot_devices.CDROM, constants.BOOT_SOURCE_TARGET_CD),
+ (boot_devices.BIOS,
+ constants.BOOT_SOURCE_TARGET_BIOS_SETUP),
+ ('floppy', constants.BOOT_SOURCE_TARGET_FLOPPY),
+ ]
+
+ persistent_mapping = [
+ (True, constants.BOOT_SOURCE_ENABLED_CONTINUOUS),
+ (False, constants.BOOT_SOURCE_ENABLED_ONCE)
+ ]
+
+ data_source = list(itertools.product(device_mapping,
+ persistent_mapping))
+ for (device, persistent) in data_source:
+ task.driver.management.set_boot_device(
+ task, device[0], persistent=persistent[0])
+ connect_ibmc.assert_called_once_with(**self.ibmc)
+ conn.system.set_boot_source.assert_called_once_with(
+ device[1],
+ enabled=persistent[1])
+ # Reset mocks
+ connect_ibmc.reset_mock()
+ conn.system.set_boot_source.reset_mock()
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_set_boot_device_fail(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ # mock return value
+ conn.system.set_boot_source.side_effect = (
+ ibmc_error.IBMCClientError
+ )
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaisesRegex(
+ exception.IBMCError, 'set iBMC boot device',
+ task.driver.management.set_boot_device, task,
+ boot_devices.PXE)
+ connect_ibmc.assert_called_once_with(**self.ibmc)
+ conn.system.set_boot_source.assert_called_once_with(
+ constants.BOOT_SOURCE_TARGET_PXE,
+ enabled=constants.BOOT_SOURCE_ENABLED_ONCE)
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_get_boot_device(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ # mock return value
+ conn.system.get.return_value = mock.Mock(
+ boot_source_override=mock.Mock(
+ target=constants.BOOT_SOURCE_TARGET_PXE,
+ enabled=constants.BOOT_SOURCE_ENABLED_CONTINUOUS
+ )
+ )
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ result_boot_device = task.driver.management.get_boot_device(task)
+ conn.system.get.assert_called_once()
+ connect_ibmc.assert_called_once_with(**self.ibmc)
+ expected = {'boot_device': boot_devices.PXE,
+ 'persistent': True}
+ self.assertEqual(expected, result_boot_device)
+
+ def test_get_supported_boot_modes(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ supported_boot_modes = (
+ task.driver.management.get_supported_boot_modes(task))
+ self.assertEqual(list(mappings.SET_BOOT_MODE_MAP),
+ supported_boot_modes)
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_set_boot_mode(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ # mock system boot source override return value
+ conn.system.get.return_value = mock.Mock(
+ boot_source_override=mock.Mock(
+ target=constants.BOOT_SOURCE_TARGET_PXE,
+ enabled=constants.BOOT_SOURCE_ENABLED_CONTINUOUS
+ )
+ )
+ conn.system.set_boot_source.return_value = None
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ expected_values = [
+ (boot_modes.LEGACY_BIOS, constants.BOOT_SOURCE_MODE_BIOS),
+ (boot_modes.UEFI, constants.BOOT_SOURCE_MODE_UEFI)
+ ]
+
+ for ironic_boot_mode, ibmc_boot_mode in expected_values:
+ task.driver.management.set_boot_mode(task,
+ mode=ironic_boot_mode)
+
+ conn.system.get.assert_called_once()
+ connect_ibmc.assert_called_once_with(**self.ibmc)
+
+ conn.system.set_boot_source.assert_called_once_with(
+ constants.BOOT_SOURCE_TARGET_PXE,
+ enabled=constants.BOOT_SOURCE_ENABLED_CONTINUOUS,
+ mode=ibmc_boot_mode)
+
+ # Reset
+ connect_ibmc.reset_mock()
+ conn.system.set_boot_source.reset_mock()
+ conn.system.get.reset_mock()
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_set_boot_mode_fail(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ # mock system boot source override return value
+ conn.system.get.return_value = mock.Mock(
+ boot_source_override=mock.Mock(
+ target=constants.BOOT_SOURCE_TARGET_PXE,
+ enabled=constants.BOOT_SOURCE_ENABLED_CONTINUOUS
+ )
+ )
+ conn.system.set_boot_source.side_effect = (
+ ibmc_error.IBMCClientError
+ )
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ expected_values = [
+ (boot_modes.LEGACY_BIOS, constants.BOOT_SOURCE_MODE_BIOS),
+ (boot_modes.UEFI, constants.BOOT_SOURCE_MODE_UEFI)
+ ]
+
+ for ironic_boot_mode, ibmc_boot_mode in expected_values:
+ self.assertRaisesRegex(
+ exception.IBMCError, 'set iBMC boot mode',
+ task.driver.management.set_boot_mode, task,
+ ironic_boot_mode)
+
+ conn.system.set_boot_source.assert_called_once_with(
+ constants.BOOT_SOURCE_TARGET_PXE,
+ enabled=constants.BOOT_SOURCE_ENABLED_CONTINUOUS,
+ mode=ibmc_boot_mode)
+
+ conn.system.get.assert_called_once()
+ connect_ibmc.assert_called_once_with(**self.ibmc)
+
+ # Reset
+ connect_ibmc.reset_mock()
+ conn.system.set_boot_source.reset_mock()
+ conn.system.get.reset_mock()
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_get_boot_mode(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ # mock system boot source override return value
+ conn.system.get.return_value = mock.Mock(
+ boot_source_override=mock.Mock(
+ target=constants.BOOT_SOURCE_TARGET_PXE,
+ enabled=constants.BOOT_SOURCE_ENABLED_CONTINUOUS,
+ mode=constants.BOOT_SOURCE_MODE_BIOS,
+ )
+ )
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ response = task.driver.management.get_boot_mode(task)
+
+ conn.system.get.assert_called_once()
+ connect_ibmc.assert_called_once_with(**self.ibmc)
+
+ expected = boot_modes.LEGACY_BIOS
+ self.assertEqual(expected, response)
+
+ def test_get_sensors_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)
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_inject_nmi(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ # mock system boot source override return value
+ conn.system.reset.return_value = None
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.inject_nmi(task)
+
+ connect_ibmc.assert_called_once_with(**self.ibmc)
+ conn.system.reset.assert_called_once_with(constants.RESET_NMI)
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_inject_nmi_fail(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ # mock system boot source override return value
+ conn.system.reset.side_effect = (
+ ibmc_error.IBMCClientError
+ )
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaisesRegex(
+ exception.IBMCError, 'inject iBMC NMI',
+ task.driver.management.inject_nmi, task)
+
+ connect_ibmc.assert_called_once_with(**self.ibmc)
+ conn.system.reset.assert_called_once_with(constants.RESET_NMI)
diff --git a/ironic/tests/unit/drivers/modules/ibmc/test_power.py b/ironic/tests/unit/drivers/modules/ibmc/test_power.py
new file mode 100644
index 000000000..39c5c78d9
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ibmc/test_power.py
@@ -0,0 +1,284 @@
+#
+# 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 iBMC Power interface."""
+
+import mock
+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.ibmc import mappings
+from ironic.drivers.modules.ibmc import utils
+from ironic.tests.unit.drivers.modules.ibmc import base
+
+constants = importutils.try_import('ibmc_client.constants')
+ibmc_client = importutils.try_import('ibmc_client')
+ibmc_error = importutils.try_import('ibmc_client.exceptions')
+
+
+@mock.patch('eventlet.greenthread.sleep', lambda _t: None)
+class IBMCPowerTestCase(base.IBMCTestCase):
+
+ 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 utils.COMMON_PROPERTIES:
+ self.assertIn(prop, properties)
+
+ @mock.patch.object(utils, 'parse_driver_info', autospec=True)
+ def test_validate(self, mock_parse_driver_info):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.driver.power.validate(task)
+ mock_parse_driver_info.assert_called_once_with(task.node)
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_get_power_state(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ expected_values = mappings.GET_POWER_STATE_MAP
+ for current, expected in expected_values.items():
+ # Mock
+ conn.system.get.return_value = mock.Mock(
+ power_state=current
+ )
+
+ # Asserts
+ self.assertEqual(expected,
+ task.driver.power.get_power_state(task))
+
+ conn.system.get.assert_called_once()
+ connect_ibmc.assert_called_once_with(**self.ibmc)
+
+ # Reset Mock
+ conn.system.get.reset_mock()
+ connect_ibmc.reset_mock()
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_set_power_state(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ state_mapping = mappings.SET_POWER_STATE_MAP
+ for (expect_state, reset_type) in state_mapping.items():
+ if expect_state in (states.POWER_OFF, states.SOFT_POWER_OFF):
+ final = constants.SYSTEM_POWER_STATE_OFF
+ transient = constants.SYSTEM_POWER_STATE_ON
+ else:
+ final = constants.SYSTEM_POWER_STATE_ON
+ transient = constants.SYSTEM_POWER_STATE_OFF
+
+ # Mocks
+ mock_system_get_results = (
+ [mock.Mock(power_state=transient)] * 3 +
+ [mock.Mock(power_state=final)])
+ conn.system.get.side_effect = mock_system_get_results
+
+ task.driver.power.set_power_state(task, expect_state)
+
+ # Asserts
+ connect_ibmc.assert_called_with(**self.ibmc)
+ conn.system.reset.assert_called_once_with(reset_type)
+ self.assertEqual(4, conn.system.get.call_count)
+
+ # Reset Mocks
+ # TODO(Qianbiao.NG) why reset_mock does not reset call_count
+ connect_ibmc.reset_mock()
+ conn.system.get.reset_mock()
+ conn.system.reset.reset_mock()
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_set_power_state_not_reached(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.config(power_state_change_timeout=2, group='conductor')
+
+ state_mapping = mappings.SET_POWER_STATE_MAP
+ for (expect_state, reset_type) in state_mapping.items():
+ if expect_state in (states.POWER_OFF, states.SOFT_POWER_OFF):
+ final = constants.SYSTEM_POWER_STATE_OFF
+ transient = constants.SYSTEM_POWER_STATE_ON
+ else:
+ final = constants.SYSTEM_POWER_STATE_ON
+ transient = constants.SYSTEM_POWER_STATE_OFF
+
+ # Mocks
+ mock_system_get_results = (
+ [mock.Mock(power_state=transient)] * 5 +
+ [mock.Mock(power_state=final)])
+ conn.system.get.side_effect = mock_system_get_results
+
+ self.assertRaises(exception.PowerStateFailure,
+ task.driver.power.set_power_state,
+ task, expect_state)
+
+ # Asserts
+ connect_ibmc.assert_called_with(**self.ibmc)
+ conn.system.reset.assert_called_once_with(reset_type)
+
+ # Reset Mocks
+ connect_ibmc.reset_mock()
+ conn.system.get.reset_mock()
+ conn.system.reset.reset_mock()
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_set_power_state_fail(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+
+ # Mocks
+ conn.system.reset.side_effect = (
+ ibmc_error.IBMCClientError
+ )
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ # Asserts
+ self.assertRaisesRegex(
+ exception.IBMCError, 'set iBMC power state',
+ task.driver.power.set_power_state, task, states.POWER_ON)
+ connect_ibmc.assert_called_with(**self.ibmc)
+ conn.system.reset.assert_called_once_with(constants.RESET_ON)
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_set_power_state_timeout(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.config(power_state_change_timeout=2, group='conductor')
+
+ # Mocks
+ conn.system.get.side_effect = (
+ [mock.Mock(power_state=constants.SYSTEM_POWER_STATE_OFF)] * 3
+ )
+
+ # Asserts
+ self.assertRaisesRegex(
+ exception.PowerStateFailure,
+ 'Failed to set node power state to power on',
+ task.driver.power.set_power_state, task, states.POWER_ON)
+
+ connect_ibmc.assert_called_with(**self.ibmc)
+ conn.system.reset.assert_called_once_with(constants.RESET_ON)
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_reboot(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.config(power_state_change_timeout=2, group='conductor')
+ expected_values = [
+ (constants.SYSTEM_POWER_STATE_OFF, constants.RESET_ON),
+ (constants.SYSTEM_POWER_STATE_ON,
+ constants.RESET_FORCE_RESTART)
+ ]
+
+ # for (expect_state, reset_type) in state_mapping.items():
+ for current, reset_type in expected_values:
+ mock_system_get_results = [
+ # Initial state
+ mock.Mock(power_state=current),
+ # Transient state - powering off
+ mock.Mock(power_state=constants.SYSTEM_POWER_STATE_OFF),
+ # Final state - down powering off
+ mock.Mock(power_state=constants.SYSTEM_POWER_STATE_ON)
+ ]
+ conn.system.get.side_effect = mock_system_get_results
+
+ task.driver.power.reboot(task)
+
+ # Asserts
+ connect_ibmc.assert_called_with(**self.ibmc)
+ conn.system.reset.assert_called_once_with(reset_type)
+
+ # Reset Mocks
+ connect_ibmc.reset_mock()
+ conn.system.get.reset_mock()
+ conn.system.reset.reset_mock()
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_reboot_not_reached(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.config(power_state_change_timeout=2, group='conductor')
+
+ # Mocks
+ conn.system.get.return_value = mock.Mock(
+ power_state=constants.SYSTEM_POWER_STATE_OFF)
+ self.assertRaisesRegex(
+ exception.PowerStateFailure,
+ 'Failed to set node power state to power on',
+ task.driver.power.reboot, task)
+
+ # Asserts
+ connect_ibmc.assert_called_with(**self.ibmc)
+ conn.system.reset.assert_called_once_with(constants.RESET_ON)
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_reboot_fail(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+
+ # Mocks
+ conn.system.reset.side_effect = (
+ ibmc_error.IBMCClientError
+ )
+ conn.system.get.return_value = mock.Mock(
+ power_state=constants.SYSTEM_POWER_STATE_ON
+ )
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ # Asserts
+ self.assertRaisesRegex(
+ exception.IBMCError, 'reboot iBMC',
+ task.driver.power.reboot, task)
+ connect_ibmc.assert_called_with(**self.ibmc)
+ conn.system.get.assert_called_once()
+ conn.system.reset.assert_called_once_with(
+ constants.RESET_FORCE_RESTART)
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_reboot_timeout(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+
+ # Mocks
+ conn.system.get.side_effect = [mock.Mock(
+ power_state=constants.SYSTEM_POWER_STATE_OFF
+ )] * 5
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.config(power_state_change_timeout=2, group='conductor')
+
+ # Asserts
+ self.assertRaisesRegex(
+ exception.PowerStateFailure,
+ 'Failed to set node power state to power on',
+ task.driver.power.reboot, task)
+
+ # Asserts
+ connect_ibmc.assert_called_with(**self.ibmc)
+ conn.system.reset.assert_called_once_with(
+ constants.RESET_ON)
+
+ def test_get_supported_power_states(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ supported_power_states = (
+ task.driver.power.get_supported_power_states(task))
+ self.assertEqual(sorted(list(mappings.SET_POWER_STATE_MAP)),
+ sorted(supported_power_states))
diff --git a/ironic/tests/unit/drivers/modules/ibmc/test_utils.py b/ironic/tests/unit/drivers/modules/ibmc/test_utils.py
new file mode 100644
index 000000000..7863ced5b
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ibmc/test_utils.py
@@ -0,0 +1,172 @@
+#
+# 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 iBMC Driver common utils."""
+
+import copy
+import os
+
+import mock
+from oslo_utils import importutils
+
+from ironic.common import exception
+from ironic.conductor import task_manager
+from ironic.drivers.modules.ibmc import utils
+from ironic.tests.unit.drivers.modules.ibmc import base
+
+constants = importutils.try_import('ibmc_client.constants')
+ibmc_client = importutils.try_import('ibmc_client')
+ibmc_error = importutils.try_import('ibmc_client.exceptions')
+
+
+class IBMCUtilsTestCase(base.IBMCTestCase):
+
+ def setUp(self):
+ super(IBMCUtilsTestCase, self).setUp()
+ # Redfish specific configurations
+ self.config(connection_attempts=2, group='ibmc')
+ self.parsed_driver_info = {
+ 'address': 'https://example.com',
+ 'username': 'username',
+ 'password': 'password',
+ 'verify_ca': True,
+ }
+
+ def test_parse_driver_info(self):
+ response = utils.parse_driver_info(self.node)
+ self.assertEqual(self.parsed_driver_info, response)
+
+ def test_parse_driver_info_default_scheme(self):
+ self.node.driver_info['ibmc_address'] = 'example.com'
+ response = utils.parse_driver_info(self.node)
+ self.assertEqual(self.parsed_driver_info, response)
+
+ def test_parse_driver_info_default_scheme_with_port(self):
+ self.node.driver_info['ibmc_address'] = 'example.com:42'
+ self.parsed_driver_info['address'] = 'https://example.com:42'
+ response = utils.parse_driver_info(self.node)
+ self.assertEqual(self.parsed_driver_info, response)
+
+ def test_parse_driver_info_missing_info(self):
+ for prop in utils.REQUIRED_PROPERTIES:
+ self.node.driver_info = self.driver_info.copy()
+ self.node.driver_info.pop(prop)
+ self.assertRaises(exception.MissingParameterValue,
+ utils.parse_driver_info, self.node)
+
+ def test_parse_driver_info_invalid_address(self):
+ for value in ['/banana!', '#location', '?search=hello']:
+ self.node.driver_info['ibmc_address'] = value
+ self.assertRaisesRegex(exception.InvalidParameterValue,
+ 'Invalid iBMC address',
+ utils.parse_driver_info, self.node)
+
+ @mock.patch.object(os.path, 'isdir', autospec=True)
+ def test_parse_driver_info_path_verify_ca(self,
+ mock_isdir):
+ mock_isdir.return_value = True
+ fake_path = '/path/to/a/valid/CA'
+ self.node.driver_info['ibmc_verify_ca'] = fake_path
+ self.parsed_driver_info['verify_ca'] = fake_path
+
+ response = utils.parse_driver_info(self.node)
+ self.assertEqual(self.parsed_driver_info, response)
+ mock_isdir.assert_called_once_with(fake_path)
+
+ @mock.patch.object(os.path, 'isfile', autospec=True)
+ def test_parse_driver_info_valid_capath(self, mock_isfile):
+ mock_isfile.return_value = True
+ fake_path = '/path/to/a/valid/CA.pem'
+ self.node.driver_info['ibmc_verify_ca'] = fake_path
+ self.parsed_driver_info['verify_ca'] = fake_path
+
+ response = utils.parse_driver_info(self.node)
+ self.assertEqual(self.parsed_driver_info, response)
+ mock_isfile.assert_called_once_with(fake_path)
+
+ def test_parse_driver_info_invalid_value_verify_ca(self):
+ # Integers are not supported
+ self.node.driver_info['ibmc_verify_ca'] = 123456
+ self.assertRaisesRegex(exception.InvalidParameterValue,
+ 'Invalid value type',
+ utils.parse_driver_info, self.node)
+
+ def test_parse_driver_info_valid_string_value_verify_ca(self):
+ for value in ('0', 'f', 'false', 'off', 'n', 'no'):
+ self.node.driver_info['ibmc_verify_ca'] = value
+ response = utils.parse_driver_info(self.node)
+ parsed_driver_info = copy.deepcopy(self.parsed_driver_info)
+ parsed_driver_info['verify_ca'] = False
+ self.assertEqual(parsed_driver_info, response)
+
+ for value in ('1', 't', 'true', 'on', 'y', 'yes'):
+ self.node.driver_info['ibmc_verify_ca'] = value
+ response = utils.parse_driver_info(self.node)
+ self.assertEqual(self.parsed_driver_info, response)
+
+ def test_parse_driver_info_invalid_string_value_verify_ca(self):
+ for value in ('xyz', '*', '!123', '123'):
+ self.node.driver_info['ibmc_verify_ca'] = value
+ self.assertRaisesRegex(exception.InvalidParameterValue,
+ 'The value should be a Boolean',
+ utils.parse_driver_info, self.node)
+
+ def test_revert_dictionary(self):
+ data = {
+ "key1": "value1",
+ "key2": "value2"
+ }
+
+ revert = utils.revert_dictionary(data)
+ self.assertEqual({
+ "value1": "key1",
+ "value2": "key2"
+ }, revert)
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_handle_ibmc_exception_retry(self, connect_ibmc):
+
+ @utils.handle_ibmc_exception('get IBMC system')
+ def get_ibmc_system(_task):
+ driver_info = utils.parse_driver_info(_task.node)
+ with ibmc_client.connect(**driver_info) as _conn:
+ return _conn.system.get()
+
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ # Mocks
+ conn.system.get.side_effect = [
+ ibmc_error.ConnectionError(url=self.ibmc['address'],
+ error='Failed to connect to host'),
+ mock.PropertyMock(
+ boot_source_override=mock.PropertyMock(
+ target=constants.BOOT_SOURCE_TARGET_PXE,
+ enabled=constants.BOOT_SOURCE_ENABLED_CONTINUOUS
+ )
+ )
+ ]
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ system = get_ibmc_system(task)
+
+ # Asserts
+ self.assertEqual(constants.BOOT_SOURCE_TARGET_PXE,
+ system.boot_source_override.target)
+ self.assertEqual(constants.BOOT_SOURCE_ENABLED_CONTINUOUS,
+ system.boot_source_override.enabled)
+
+ # 1 failed, 1 succeed
+ connect_ibmc.assert_called_with(**self.ibmc)
+ self.assertEqual(2, connect_ibmc.call_count)
+
+ # 1 failed, 1 succeed
+ self.assertEqual(2, conn.system.get.call_count)
diff --git a/ironic/tests/unit/drivers/modules/ibmc/test_vendor.py b/ironic/tests/unit/drivers/modules/ibmc/test_vendor.py
new file mode 100644
index 000000000..3a316e0d8
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ibmc/test_vendor.py
@@ -0,0 +1,60 @@
+#
+# 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 iBMC vendor interface."""
+
+import mock
+from oslo_utils import importutils
+
+from ironic.conductor import task_manager
+from ironic.drivers.modules.ibmc import utils
+from ironic.tests.unit.drivers.modules.ibmc import base
+
+ibmc_client = importutils.try_import('ibmc_client')
+
+
+@mock.patch('eventlet.greenthread.sleep', lambda _t: None)
+class IBMCVendorTestCase(base.IBMCTestCase):
+
+ def setUp(self):
+ super(IBMCVendorTestCase, self).setUp()
+
+ 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 utils.COMMON_PROPERTIES:
+ self.assertIn(prop, properties)
+
+ @mock.patch.object(utils, 'parse_driver_info', autospec=True)
+ def test_validate(self, mock_parse_driver_info):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.driver.power.validate(task)
+ mock_parse_driver_info.assert_called_once_with(task.node)
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_list_boot_type_order(self, connect_ibmc):
+ # Mocks
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ boot_up_seq = ['Pxe', 'Hdd', 'Others', 'Cd']
+ conn.system.get.return_value = mock.Mock(
+ boot_sequence=['Pxe', 'Hdd', 'Others', 'Cd']
+ )
+
+ expected = {'boot_up_sequence': boot_up_seq}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ seq = task.driver.vendor.boot_up_seq(task)
+ conn.system.get.assert_called_once()
+ connect_ibmc.assert_called_once_with(**self.ibmc)
+ self.assertEqual(expected, seq)
diff --git a/ironic/tests/unit/drivers/test_ibmc.py b/ironic/tests/unit/drivers/test_ibmc.py
new file mode 100644
index 000000000..731311b54
--- /dev/null
+++ b/ironic/tests/unit/drivers/test_ibmc.py
@@ -0,0 +1,47 @@
+#
+# 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.
+
+# Version 1.0.0
+
+from ironic.conductor import task_manager
+from ironic.drivers.modules.ibmc import management as ibmc_mgmt
+from ironic.drivers.modules.ibmc import power as ibmc_power
+from ironic.drivers.modules.ibmc import vendor as ibmc_vendor
+from ironic.drivers.modules import iscsi_deploy
+from ironic.drivers.modules import noop
+from ironic.drivers.modules import pxe
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.objects import utils as obj_utils
+
+
+class IBMCHardwareTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(IBMCHardwareTestCase, self).setUp()
+ self.config(enabled_hardware_types=['ibmc'],
+ enabled_power_interfaces=['ibmc'],
+ enabled_management_interfaces=['ibmc'],
+ enabled_vendor_interfaces=['ibmc'])
+
+ def test_default_interfaces(self):
+ node = obj_utils.create_test_node(self.context, driver='ibmc')
+ with task_manager.acquire(self.context, node.id) as task:
+ self.assertIsInstance(task.driver.management,
+ ibmc_mgmt.IBMCManagement)
+ self.assertIsInstance(task.driver.power,
+ ibmc_power.IBMCPower)
+ self.assertIsInstance(task.driver.boot, pxe.PXEBoot)
+ self.assertIsInstance(task.driver.deploy, iscsi_deploy.ISCSIDeploy)
+ self.assertIsInstance(task.driver.console, noop.NoConsole)
+ self.assertIsInstance(task.driver.raid, noop.NoRAID)
+ self.assertIsInstance(task.driver.vendor, ibmc_vendor.IBMCVendor)
diff --git a/ironic/tests/unit/drivers/third_party_driver_mock_specs.py b/ironic/tests/unit/drivers/third_party_driver_mock_specs.py
index 6a5f906af..49660c4ad 100644
--- a/ironic/tests/unit/drivers/third_party_driver_mock_specs.py
+++ b/ironic/tests/unit/drivers/third_party_driver_mock_specs.py
@@ -170,3 +170,10 @@ XCLARITY_STATES_SPEC = (
'STATE_POWER_OFF',
'STATE_POWER_ON',
)
+
+# python-ibmcclient
+IBMCCLIENT_SPEC = (
+ 'connect',
+ 'exceptions',
+ 'constants',
+)
diff --git a/ironic/tests/unit/drivers/third_party_driver_mocks.py b/ironic/tests/unit/drivers/third_party_driver_mocks.py
index db82dfe7b..6e6b188de 100644
--- a/ironic/tests/unit/drivers/third_party_driver_mocks.py
+++ b/ironic/tests/unit/drivers/third_party_driver_mocks.py
@@ -26,6 +26,7 @@ Current list of mocked libraries:
- pysnmp
- scciclient
- python-dracclient
+- python-ibmcclient
"""
import sys
@@ -263,3 +264,45 @@ if not xclarity_client:
xclarity_client.exceptions.XClarityException = type('XClarityException',
(Exception,), {})
sys.modules['xclarity_client.models'] = xclarity_client.models
+
+
+# python-ibmcclient mocks for HUAWEI rack server driver
+ibmc_client = importutils.try_import('ibmc_client')
+if not ibmc_client:
+ ibmc_client = mock.MagicMock(spec_set=mock_specs.IBMCCLIENT_SPEC)
+ sys.modules['ibmc_client'] = ibmc_client
+
+ # Mock iBMC client exceptions
+ exceptions = mock.MagicMock()
+ exceptions.ConnectionError = (
+ type('ConnectionError', (MockKwargsException,), {}))
+ exceptions.IBMCClientError = (
+ type('IBMCClientError', (MockKwargsException,), {}))
+ sys.modules['ibmc_client.exceptions'] = exceptions
+
+ # Mock iIBMC client constants
+ constants = mock.MagicMock(
+ SYSTEM_POWER_STATE_ON='On',
+ SYSTEM_POWER_STATE_OFF='Off',
+ BOOT_SOURCE_TARGET_NONE='None',
+ BOOT_SOURCE_TARGET_PXE='Pxe',
+ BOOT_SOURCE_TARGET_FLOPPY='Floppy',
+ BOOT_SOURCE_TARGET_CD='Cd',
+ BOOT_SOURCE_TARGET_HDD='Hdd',
+ BOOT_SOURCE_TARGET_BIOS_SETUP='BiosSetup',
+ BOOT_SOURCE_MODE_BIOS='Legacy',
+ BOOT_SOURCE_MODE_UEFI='UEFI',
+ BOOT_SOURCE_ENABLED_ONCE='Once',
+ BOOT_SOURCE_ENABLED_CONTINUOUS='Continuous',
+ BOOT_SOURCE_ENABLED_DISABLED='Disabled',
+ RESET_NMI='Nmi',
+ RESET_ON='On',
+ RESET_FORCE_OFF='ForceOff',
+ RESET_GRACEFUL_SHUTDOWN='GracefulShutdown',
+ RESET_FORCE_RESTART='ForceRestart',
+ RESET_FORCE_POWER_CYCLE='ForcePowerCycle')
+ sys.modules['ibmc_client.constants'] = constants
+
+ if 'ironic.drivers.modules.ibmc' in sys.modules:
+ six.moves.reload_module(
+ sys.modules['ironic.drivers.modules.ibmc'])