diff options
author | Cenne <cennedee+opendev@protonmail.com> | 2021-07-08 18:37:45 +0200 |
---|---|---|
committer | Cenne <cennedee+opendev@protonmail.com> | 2021-08-23 19:38:58 +0200 |
commit | bc95c92f7c122b1217459a1d7a125fae47749e6e (patch) | |
tree | 6ffd3cc271ac841e2a2a5fc72524ee2f1f625a37 /ironic/tests/unit/conductor | |
parent | 9f32ceda1a87ac83d4ac84faec16c01ba27c549e (diff) | |
download | ironic-bc95c92f7c122b1217459a1d7a125fae47749e6e.tar.gz |
Add api endpoints for changing boot_mode and secure_boot state
Done:
- Node API endpoints expose
- RPC methods
- Conductor Manager methods
- Conductor utils new methods
- RBAC new policies
- Node API tests
- Manager Tests (+ some testing for utils methods)
- RBAC tests
- Docs (api-ref)
- REST API version history
- Releasenotes
Story: 2008567
Task: 41709
Change-Id: I2d72389edf546b99c536c6b130ca85ababf80591
Diffstat (limited to 'ironic/tests/unit/conductor')
-rw-r--r-- | ironic/tests/unit/conductor/test_manager.py | 426 | ||||
-rw-r--r-- | ironic/tests/unit/conductor/test_rpcapi.py | 15 |
2 files changed, 441 insertions, 0 deletions
diff --git a/ironic/tests/unit/conductor/test_manager.py b/ironic/tests/unit/conductor/test_manager.py index 897e19d3e..1603b19be 100644 --- a/ironic/tests/unit/conductor/test_manager.py +++ b/ironic/tests/unit/conductor/test_manager.py @@ -34,6 +34,7 @@ from oslo_versionedobjects import fields import tenacity from ironic.common import boot_devices +from ironic.common import boot_modes from ironic.common import components from ironic.common import driver_factory from ironic.common import exception @@ -448,6 +449,431 @@ class ChangeNodePowerStateTestCase(mgr_utils.ServiceSetUpMixin, @mgr_utils.mock_record_keepalive +class ChangeNodeBootModeTestCase(mgr_utils.ServiceSetUpMixin, + db_base.DbTestCase): + @mock.patch.object(fake.FakeManagement, 'set_boot_mode', autospec=True) + @mock.patch.object(fake.FakeManagement, 'get_boot_mode', autospec=True) + def test_change_node_boot_mode_valid(self, get_boot_mock, set_boot_mock): + # Test change_node_boot_mode including integration with + # conductor.utils.node_change_boot_mode + get_boot_mock.side_effect = [boot_modes.LEGACY_BIOS, # before setting + boot_modes.UEFI] # after setting + node = obj_utils.create_test_node(self.context, + driver='fake-hardware', + boot_mode=boot_modes.LEGACY_BIOS) + self._start_service() + + self.service.change_node_boot_mode(self.context, + node.uuid, + boot_modes.UEFI) + self._stop_service() + + set_boot_mock.assert_called_once_with(mock.ANY, mock.ANY, + mode=boot_modes.UEFI) + self.assertEqual(get_boot_mock.call_count, 1) + # Call once before setting to see if it's required + + node.refresh() + self.assertEqual(boot_modes.UEFI, node.boot_mode) + self.assertIsNone(node.last_error) + # Verify the reservation has been cleared by + # background task's link callback. + self.assertIsNone(node.reservation) + + @mock.patch.object(fake.FakeManagement, 'set_boot_mode', autospec=True) + @mock.patch.object(fake.FakeManagement, 'get_boot_mode', autospec=True) + def test_change_node_boot_mode_existing(self, get_boot_mock, + set_boot_mock): + # Test change_node_boot_mode including integration with + # conductor.utils.node_change_boot_mode when target==current + get_boot_mock.return_value = boot_modes.LEGACY_BIOS + node = obj_utils.create_test_node(self.context, + driver='fake-hardware', + boot_mode=boot_modes.LEGACY_BIOS) + self._start_service() + + self.service.change_node_boot_mode(self.context, + node.uuid, + boot_modes.LEGACY_BIOS) + self._stop_service() + + set_boot_mock.assert_not_called() + self.assertEqual(get_boot_mock.call_count, 1) + # Called once before setting to see if it's even required + + node.refresh() + self.assertEqual(boot_modes.LEGACY_BIOS, node.boot_mode) + self.assertIsNone(node.last_error) + # Verify the reservation has been cleared by + # background task's link callback. + self.assertIsNone(node.reservation) + + @mock.patch.object(conductor_utils, 'node_change_boot_mode', + autospec=True) + def test_change_node_boot_mode_node_already_locked(self, ncbm_mock): + # Test change_node_boot_mode with mocked + # conductor.utils.node_change_boot_mode. + fake_reservation = 'fake-reserv' + node = obj_utils.create_test_node(self.context, + driver='fake-hardware', + boot_mode=boot_modes.LEGACY_BIOS, + reservation=fake_reservation) + self._start_service() + + exc = self.assertRaises(messaging.rpc.ExpectedException, + self.service.change_node_boot_mode, + self.context, + node.uuid, + boot_modes.LEGACY_BIOS) + # Compare true exception hidden by @messaging.expected_exceptions + self.assertEqual(exception.NodeLocked, exc.exc_info[0]) + + # In this test worker should not be spawned, but waiting to make sure + # the below perform_mock assertion is valid. + self._stop_service() + self.assertFalse(ncbm_mock.called, 'node_change_boot_mode has been ' + 'unexpectedly called.') + # Verify existing reservation wasn't broken. + node.refresh() + self.assertEqual(fake_reservation, node.reservation) + + def test_change_node_boot_mode_worker_pool_full(self): + # Test change_node_boot_mode including integration with + # conductor.utils.change_node_boot_mode. + initial_state = boot_modes.LEGACY_BIOS + node = obj_utils.create_test_node(self.context, driver='fake-hardware', + boot_mode=initial_state) + self._start_service() + + with mock.patch.object(self.service, + '_spawn_worker', autospec=True) as spawn_mock: + spawn_mock.side_effect = exception.NoFreeConductorWorker() + + exc = self.assertRaises(messaging.rpc.ExpectedException, + self.service.change_node_boot_mode, + self.context, + node.uuid, + boot_modes.UEFI) + # Compare true exception hidden by @messaging.expected_exceptions + self.assertEqual(exception.NoFreeConductorWorker, exc.exc_info[0]) + + spawn_mock.assert_called_once_with(mock.ANY, mock.ANY, + mock.ANY) + node.refresh() + self.assertEqual(initial_state, node.boot_mode) + self.assertIsNotNone(node.last_error) + # Verify the picked reservation has been cleared due to full pool. + self.assertIsNone(node.reservation) + + @mock.patch.object(fake.FakeManagement, 'set_boot_mode', autospec=True) + @mock.patch.object(fake.FakeManagement, 'get_boot_mode', autospec=True) + def test_change_node_boot_mode_exception_in_background_task( + self, get_boot_mock, set_boot_mock): + # Test change_node_boot_mode including integration with + # conductor.utils.node_change_boot_mode. + initial_state = boot_modes.LEGACY_BIOS + node = obj_utils.create_test_node(self.context, driver='fake-hardware', + boot_mode=initial_state) + self._start_service() + + get_boot_mock.return_value = boot_modes.LEGACY_BIOS + new_state = boot_modes.UEFI + set_boot_mock.side_effect = exception.UnsupportedDriverExtension( + driver=fake, extension='set_boot_mode') + + self.service.change_node_boot_mode(self.context, + node.uuid, + new_state) + self._stop_service() + + # Call once before setting to see if it was required + self.assertEqual(get_boot_mock.call_count, 1) + set_boot_mock.assert_called_once_with(mock.ANY, mock.ANY, + new_state) + node.refresh() + self.assertEqual(initial_state, node.boot_mode) + self.assertIsNotNone(node.last_error) + # Verify the reservation has been cleared by background task's + # link callback despite exception in background task. + self.assertIsNone(node.reservation) + + @mock.patch.object(fake.FakeManagement, 'validate', autospec=True) + def test_change_node_boot_mode_validate_fail(self, validate_mock): + # Test change_node_power_state where task.driver.management.validate + # fails + initial_state = boot_modes.UEFI + node = obj_utils.create_test_node(self.context, + driver='fake-hardware', + boot_mode=initial_state) + self._start_service() + + validate_mock.side_effect = exception.InvalidParameterValue( + 'wrong management driver info') + new_state = boot_modes.LEGACY_BIOS + exc = self.assertRaises(messaging.rpc.ExpectedException, + self.service.change_node_boot_mode, + self.context, + node.uuid, + new_state) + + self.assertEqual(exception.InvalidParameterValue, exc.exc_info[0]) + + node.refresh() + validate_mock.assert_called_once_with(mock.ANY, mock.ANY) + self.assertEqual(initial_state, node.boot_mode) + self.assertIsNone(node.last_error) + + @mock.patch.object(fake.FakeManagement, 'set_boot_mode', autospec=True) + @mock.patch.object(fake.FakeManagement, 'get_boot_mode', autospec=True) + def test_change_node_boot_mode_exception_getting_current(self, + get_boot_mock, + set_boot_mock): + # Test change_node_boot_mode smooth opertion when get_boot_mode mode + # raises an exception + initial_state = boot_modes.LEGACY_BIOS + node = obj_utils.create_test_node(self.context, driver='fake-hardware', + boot_mode=initial_state) + self._start_service() + + get_boot_mock.side_effect = exception.UnsupportedDriverExtension( + driver=fake, extension='get_boot_mode') + new_state = boot_modes.UEFI + + self.service.change_node_boot_mode(self.context, + node.uuid, + new_state) + self._stop_service() + + # Call once before setting to see if it is required + self.assertEqual(get_boot_mock.call_count, 1) + set_boot_mock.assert_called_once_with(mock.ANY, mock.ANY, + new_state) + node.refresh() + self.assertEqual(new_state, node.boot_mode) + self.assertIsNone(node.last_error) + # Verify the reservation has been cleared by + # background task's link callback. + self.assertIsNone(node.reservation) + + +@mgr_utils.mock_record_keepalive +class ChangeNodeSecureBootTestCase(mgr_utils.ServiceSetUpMixin, + db_base.DbTestCase): + @mock.patch.object(fake.FakeManagement, 'set_secure_boot_state', + autospec=True) + @mock.patch.object(fake.FakeManagement, 'get_secure_boot_state', + autospec=True) + def test_change_node_secure_boot_valid(self, get_boot_mock, set_boot_mock): + # Test change_node_secure_boot including integration with + # conductor.utils.node_change_secure_boot + get_boot_mock.side_effect = [False, # before setting + True] # after setting + initial_state = False + node = obj_utils.create_test_node(self.context, + driver='fake-hardware', + secure_boot=initial_state) + self._start_service() + target_state = True + self.service.change_node_secure_boot(self.context, + node.uuid, + target_state) + self._stop_service() + + set_boot_mock.assert_called_once_with(mock.ANY, mock.ANY, + target_state) + self.assertEqual(get_boot_mock.call_count, 1) + # Call once before setting to see if it's required + + node.refresh() + self.assertEqual(target_state, node.secure_boot) + self.assertIsNone(node.last_error) + # Verify the reservation has been cleared by + # background task's link callback. + self.assertIsNone(node.reservation) + + @mock.patch.object(fake.FakeManagement, 'set_secure_boot_state', + autospec=True) + @mock.patch.object(fake.FakeManagement, 'get_secure_boot_state', + autospec=True) + def test_change_node_secure_boot_existing(self, get_boot_mock, + set_boot_mock): + # Test change_node_secure_boot including integration with + # conductor.utils.node_change_secure_boot when target==current + get_boot_mock.return_value = False + node = obj_utils.create_test_node(self.context, + driver='fake-hardware', + secure_boot=False) + self._start_service() + + self.service.change_node_secure_boot(self.context, + node.uuid, + False) + self._stop_service() + + set_boot_mock.assert_not_called() + self.assertEqual(get_boot_mock.call_count, 1) + # Called once before setting to see if it's even required + + node.refresh() + self.assertEqual(False, node.secure_boot) + self.assertIsNone(node.last_error) + # Verify the reservation has been cleared by + # background task's link callback. + self.assertIsNone(node.reservation) + + @mock.patch.object(conductor_utils, 'node_change_secure_boot', + autospec=True) + def test_change_node_secure_boot_node_already_locked(self, ncbm_mock): + # Test change_node_secure_boot with mocked + # conductor.utils.node_change_secure_boot. + fake_reservation = 'fake-reserv' + node = obj_utils.create_test_node(self.context, + driver='fake-hardware', + secure_boot=False, + reservation=fake_reservation) + self._start_service() + + exc = self.assertRaises(messaging.rpc.ExpectedException, + self.service.change_node_secure_boot, + self.context, + node.uuid, + False) + # Compare true exception hidden by @messaging.expected_exceptions + self.assertEqual(exception.NodeLocked, exc.exc_info[0]) + + # In this test worker should not be spawned, but waiting to make sure + # the below perform_mock assertion is valid. + self._stop_service() + self.assertFalse(ncbm_mock.called, 'node_change_secure_boot has been ' + 'unexpectedly called.') + # Verify existing reservation wasn't broken. + node.refresh() + self.assertEqual(fake_reservation, node.reservation) + + def test_change_node_secure_boot_worker_pool_full(self): + # Test change_node_secure_boot including integration with + # conductor.utils.change_node_secure_boot. + initial_state = False + node = obj_utils.create_test_node(self.context, driver='fake-hardware', + secure_boot=initial_state) + self._start_service() + + with mock.patch.object(self.service, + '_spawn_worker', autospec=True) as spawn_mock: + spawn_mock.side_effect = exception.NoFreeConductorWorker() + + exc = self.assertRaises(messaging.rpc.ExpectedException, + self.service.change_node_secure_boot, + self.context, + node.uuid, + True) + # Compare true exception hidden by @messaging.expected_exceptions + self.assertEqual(exception.NoFreeConductorWorker, exc.exc_info[0]) + + spawn_mock.assert_called_once_with(mock.ANY, mock.ANY, + mock.ANY) + node.refresh() + self.assertEqual(initial_state, node.secure_boot) + self.assertIsNotNone(node.last_error) + # Verify the picked reservation has been cleared due to full pool. + self.assertIsNone(node.reservation) + + @mock.patch.object(fake.FakeManagement, 'set_secure_boot_state', + autospec=True) + @mock.patch.object(fake.FakeManagement, 'get_secure_boot_state', + autospec=True) + def test_change_node_secure_boot_exception_in_background_task( + self, get_boot_mock, set_boot_mock): + # Test change_node_secure_boot including integration with + # conductor.utils.node_change_secure_boot. + initial_state = False + node = obj_utils.create_test_node(self.context, driver='fake-hardware', + secure_boot=initial_state) + self._start_service() + + get_boot_mock.return_value = False + new_state = True + set_boot_mock.side_effect = exception.UnsupportedDriverExtension( + driver=fake, extension='set_secure_boot_state') + + self.service.change_node_secure_boot(self.context, + node.uuid, + new_state) + self._stop_service() + + # Call once before setting to see if it was required + self.assertEqual(get_boot_mock.call_count, 1) + set_boot_mock.assert_called_once_with(mock.ANY, mock.ANY, + new_state) + node.refresh() + self.assertEqual(initial_state, node.secure_boot) + self.assertIsNotNone(node.last_error) + # Verify the reservation has been cleared by background task's + # link callback despite exception in background task. + self.assertIsNone(node.reservation) + + @mock.patch.object(fake.FakeManagement, 'validate', autospec=True) + def test_change_node_secure_boot_validate_fail(self, validate_mock): + # Test change_node_power_state where task.driver.management.validate + # fails + initial_state = True + node = obj_utils.create_test_node(self.context, + driver='fake-hardware', + secure_boot=initial_state) + self._start_service() + + validate_mock.side_effect = exception.InvalidParameterValue( + 'wrong management driver info') + new_state = False + exc = self.assertRaises(messaging.rpc.ExpectedException, + self.service.change_node_secure_boot, + self.context, + node.uuid, + new_state) + + self.assertEqual(exception.InvalidParameterValue, exc.exc_info[0]) + + node.refresh() + validate_mock.assert_called_once_with(mock.ANY, mock.ANY) + self.assertEqual(initial_state, node.secure_boot) + self.assertIsNone(node.last_error) + + @mock.patch.object(fake.FakeManagement, 'set_secure_boot_state', + autospec=True) + @mock.patch.object(fake.FakeManagement, 'get_secure_boot_state', + autospec=True) + def test_change_node_secure_boot_exception_getting_current(self, + get_boot_mock, + set_boot_mock): + # Test change_node_secure_boot smooth opertion when + # get_secure_boot_state raises an exception + initial_state = False + node = obj_utils.create_test_node(self.context, driver='fake-hardware', + secure_boot=initial_state) + self._start_service() + + get_boot_mock.side_effect = exception.UnsupportedDriverExtension( + driver=fake, extension='get_secure_boot_state') + new_state = True + + self.service.change_node_secure_boot(self.context, + node.uuid, + new_state) + self._stop_service() + + # Call once before setting to see if it is required + self.assertEqual(get_boot_mock.call_count, 1) + set_boot_mock.assert_called_once_with(mock.ANY, mock.ANY, + new_state) + node.refresh() + self.assertEqual(new_state, node.secure_boot) + self.assertIsNone(node.last_error) + # Verify the reservation has been cleared by + # background task's link callback. + self.assertIsNone(node.reservation) + + +@mgr_utils.mock_record_keepalive class CreateNodeTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): def test_create_node(self): node = obj_utils.get_test_node(self.context, driver='fake-hardware', diff --git a/ironic/tests/unit/conductor/test_rpcapi.py b/ironic/tests/unit/conductor/test_rpcapi.py index 0c7763e83..314199498 100644 --- a/ironic/tests/unit/conductor/test_rpcapi.py +++ b/ironic/tests/unit/conductor/test_rpcapi.py @@ -26,6 +26,7 @@ import oslo_messaging as messaging from oslo_messaging import _utils as messaging_utils from ironic.common import boot_devices +from ironic.common import boot_modes from ironic.common import components from ironic.common import exception from ironic.common import indicator_states @@ -301,6 +302,20 @@ class RPCAPITestCase(db_base.DbTestCase): node_id=self.fake_node['uuid'], new_state=states.POWER_ON) + def test_change_node_boot_mode(self): + self._test_rpcapi('change_node_boot_mode', + 'call', + version='1.55', + node_id=self.fake_node['uuid'], + new_state=boot_modes.LEGACY_BIOS) + + def test_change_node_secure_boot(self): + self._test_rpcapi('change_node_secure_boot', + 'call', + version='1.55', + node_id=self.fake_node['uuid'], + new_state=True) + def test_vendor_passthru(self): self._test_rpcapi('vendor_passthru', 'call', |