# 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 unittest import mock from keystoneauth1 import adapter from openstack.baremetal.v1 import _common from openstack.baremetal.v1 import node from openstack import exceptions from openstack import resource from openstack.tests.unit import base # NOTE: Sample data from api-ref doc FAKE = { "chassis_uuid": "1", # NOTE: missed in api-ref sample "clean_step": {}, "console_enabled": False, "created_at": "2016-08-18T22:28:48.643434+00:00", "driver": "agent_ipmitool", "driver_info": { "ipmi_password": "******", "ipmi_username": "ADMIN" }, "driver_internal_info": {}, "extra": {}, "inspection_finished_at": None, "inspection_started_at": None, "instance_info": {}, "instance_uuid": None, "last_error": None, "links": [ { "href": "http://127.0.0.1:6385/v1/nodes/", "rel": "self" }, { "href": "http://127.0.0.1:6385/nodes/", "rel": "bookmark" } ], "maintenance": False, "maintenance_reason": None, "name": "test_node", "network_interface": "flat", "owner": "4b7ed919-e4a6-4017-a081-43205c5b0b73", "portgroups": [ { "href": "http://127.0.0.1:6385/v1/nodes//portgroups", "rel": "self" }, { "href": "http://127.0.0.1:6385/nodes//portgroups", "rel": "bookmark" } ], "ports": [ { "href": "http://127.0.0.1:6385/v1/nodes//ports", "rel": "self" }, { "href": "http://127.0.0.1:6385/nodes//ports", "rel": "bookmark" } ], "power_state": None, "properties": {}, "provision_state": "enroll", "provision_updated_at": None, "raid_config": {}, "reservation": None, "resource_class": None, "states": [ { "href": "http://127.0.0.1:6385/v1/nodes//states", "rel": "self" }, { "href": "http://127.0.0.1:6385/nodes//states", "rel": "bookmark" } ], "target_power_state": None, "target_provision_state": None, "target_raid_config": {}, "updated_at": None, "uuid": "6d85703a-565d-469a-96ce-30b6de53079d" } class TestNode(base.TestCase): def test_basic(self): sot = node.Node() self.assertIsNone(sot.resource_key) self.assertEqual('nodes', sot.resources_key) self.assertEqual('/nodes', sot.base_path) self.assertTrue(sot.allow_create) self.assertTrue(sot.allow_fetch) self.assertTrue(sot.allow_commit) self.assertTrue(sot.allow_delete) self.assertTrue(sot.allow_list) self.assertEqual('PATCH', sot.commit_method) def test_instantiate(self): sot = node.Node(**FAKE) self.assertEqual(FAKE['uuid'], sot.id) self.assertEqual(FAKE['name'], sot.name) self.assertEqual(FAKE['chassis_uuid'], sot.chassis_id) self.assertEqual(FAKE['clean_step'], sot.clean_step) self.assertEqual(FAKE['created_at'], sot.created_at) self.assertEqual(FAKE['driver'], sot.driver) self.assertEqual(FAKE['driver_info'], sot.driver_info) self.assertEqual(FAKE['driver_internal_info'], sot.driver_internal_info) self.assertEqual(FAKE['extra'], sot.extra) self.assertEqual(FAKE['instance_info'], sot.instance_info) self.assertEqual(FAKE['instance_uuid'], sot.instance_id) self.assertEqual(FAKE['console_enabled'], sot.is_console_enabled) self.assertEqual(FAKE['maintenance'], sot.is_maintenance) self.assertEqual(FAKE['last_error'], sot.last_error) self.assertEqual(FAKE['links'], sot.links) self.assertEqual(FAKE['maintenance_reason'], sot.maintenance_reason) self.assertEqual(FAKE['name'], sot.name) self.assertEqual(FAKE['network_interface'], sot.network_interface) self.assertEqual(FAKE['owner'], sot.owner) self.assertEqual(FAKE['ports'], sot.ports) self.assertEqual(FAKE['portgroups'], sot.port_groups) self.assertEqual(FAKE['power_state'], sot.power_state) self.assertEqual(FAKE['properties'], sot.properties) self.assertEqual(FAKE['provision_state'], sot.provision_state) self.assertEqual(FAKE['raid_config'], sot.raid_config) self.assertEqual(FAKE['reservation'], sot.reservation) self.assertEqual(FAKE['resource_class'], sot.resource_class) self.assertEqual(FAKE['states'], sot.states) self.assertEqual(FAKE['target_provision_state'], sot.target_provision_state) self.assertEqual(FAKE['target_power_state'], sot.target_power_state) self.assertEqual(FAKE['target_raid_config'], sot.target_raid_config) self.assertEqual(FAKE['updated_at'], sot.updated_at) def test_normalize_provision_state(self): attrs = dict(FAKE, provision_state=None) sot = node.Node(**attrs) self.assertEqual('available', sot.provision_state) @mock.patch('time.sleep', lambda _t: None) @mock.patch.object(node.Node, 'fetch', autospec=True) class TestNodeWaitForProvisionState(base.TestCase): def setUp(self): super(TestNodeWaitForProvisionState, self).setUp() self.node = node.Node(**FAKE) self.session = mock.Mock() def test_success(self, mock_fetch): def _get_side_effect(_self, session): self.node.provision_state = 'manageable' self.assertIs(session, self.session) mock_fetch.side_effect = _get_side_effect node = self.node.wait_for_provision_state(self.session, 'manageable') self.assertIs(node, self.node) def test_failure(self, mock_fetch): def _get_side_effect(_self, session): self.node.provision_state = 'deploy failed' self.assertIs(session, self.session) mock_fetch.side_effect = _get_side_effect self.assertRaisesRegex(exceptions.ResourceFailure, 'failure state "deploy failed"', self.node.wait_for_provision_state, self.session, 'manageable') def test_failure_error(self, mock_fetch): def _get_side_effect(_self, session): self.node.provision_state = 'error' self.assertIs(session, self.session) mock_fetch.side_effect = _get_side_effect self.assertRaisesRegex(exceptions.ResourceFailure, 'failure state "error"', self.node.wait_for_provision_state, self.session, 'manageable') def test_enroll_as_failure(self, mock_fetch): def _get_side_effect(_self, session): self.node.provision_state = 'enroll' self.node.last_error = 'power failure' self.assertIs(session, self.session) mock_fetch.side_effect = _get_side_effect self.assertRaisesRegex(exceptions.ResourceFailure, 'failed to verify management credentials', self.node.wait_for_provision_state, self.session, 'manageable') def test_timeout(self, mock_fetch): self.assertRaises(exceptions.ResourceTimeout, self.node.wait_for_provision_state, self.session, 'manageable', timeout=0.001) def test_not_abort_on_failed_state(self, mock_fetch): def _get_side_effect(_self, session): self.node.provision_state = 'deploy failed' self.assertIs(session, self.session) mock_fetch.side_effect = _get_side_effect self.assertRaises(exceptions.ResourceTimeout, self.node.wait_for_provision_state, self.session, 'manageable', timeout=0.001, abort_on_failed_state=False) def _fake_assert(self, session, action, expected, error_message=None): return expected @mock.patch.object(node.Node, '_assert_microversion_for', _fake_assert) @mock.patch.object(node.Node, 'fetch', lambda self, session: self) @mock.patch.object(exceptions, 'raise_from_response', mock.Mock()) class TestNodeSetProvisionState(base.TestCase): def setUp(self): super(TestNodeSetProvisionState, self).setUp() self.node = node.Node(**FAKE) self.session = mock.Mock(spec=adapter.Adapter, default_microversion=None) def test_no_arguments(self): result = self.node.set_provision_state(self.session, 'active') self.assertIs(result, self.node) self.session.put.assert_called_once_with( 'nodes/%s/states/provision' % self.node.id, json={'target': 'active'}, headers=mock.ANY, microversion=None, retriable_status_codes=_common.RETRIABLE_STATUS_CODES) def test_manage(self): result = self.node.set_provision_state(self.session, 'manage') self.assertIs(result, self.node) self.session.put.assert_called_once_with( 'nodes/%s/states/provision' % self.node.id, json={'target': 'manage'}, headers=mock.ANY, microversion='1.4', retriable_status_codes=_common.RETRIABLE_STATUS_CODES) def test_deploy_with_configdrive(self): result = self.node.set_provision_state(self.session, 'active', config_drive='abcd') self.assertIs(result, self.node) self.session.put.assert_called_once_with( 'nodes/%s/states/provision' % self.node.id, json={'target': 'active', 'configdrive': 'abcd'}, headers=mock.ANY, microversion=None, retriable_status_codes=_common.RETRIABLE_STATUS_CODES) def test_rebuild_with_configdrive(self): result = self.node.set_provision_state(self.session, 'rebuild', config_drive='abcd') self.assertIs(result, self.node) self.session.put.assert_called_once_with( 'nodes/%s/states/provision' % self.node.id, json={'target': 'rebuild', 'configdrive': 'abcd'}, headers=mock.ANY, microversion='1.35', retriable_status_codes=_common.RETRIABLE_STATUS_CODES) def test_configdrive_as_dict(self): for target in ('rebuild', 'active'): self.session.put.reset_mock() result = self.node.set_provision_state( self.session, target, config_drive={'user_data': 'abcd'}) self.assertIs(result, self.node) self.session.put.assert_called_once_with( 'nodes/%s/states/provision' % self.node.id, json={'target': target, 'configdrive': {'user_data': 'abcd'}}, headers=mock.ANY, microversion='1.56', retriable_status_codes=_common.RETRIABLE_STATUS_CODES) @mock.patch.object(node.Node, '_translate_response', mock.Mock()) @mock.patch.object(node.Node, '_get_session', lambda self, x: x) @mock.patch.object(node.Node, 'set_provision_state', autospec=True) class TestNodeCreate(base.TestCase): def setUp(self): super(TestNodeCreate, self).setUp() self.new_state = None self.session = mock.Mock(spec=adapter.Adapter) self.session.default_microversion = '1.1' self.node = node.Node(driver=FAKE['driver']) def _change_state(*args, **kwargs): self.node.provision_state = self.new_state self.session.post.side_effect = _change_state def test_available_old_version(self, mock_prov): result = self.node.create(self.session) self.assertIs(result, self.node) self.session.post.assert_called_once_with( mock.ANY, json={'driver': FAKE['driver']}, headers=mock.ANY, microversion=self.session.default_microversion, params={}) self.assertFalse(mock_prov.called) def test_available_new_version(self, mock_prov): def _change_state(*args, **kwargs): self.node.provision_state = 'manageable' self.session.default_microversion = '1.11' self.node.provision_state = 'available' self.new_state = 'enroll' mock_prov.side_effect = _change_state result = self.node.create(self.session) self.assertIs(result, self.node) self.session.post.assert_called_once_with( mock.ANY, json={'driver': FAKE['driver']}, headers=mock.ANY, microversion=self.session.default_microversion, params={}) mock_prov.assert_has_calls([ mock.call(self.node, self.session, 'manage', wait=True), mock.call(self.node, self.session, 'provide', wait=True) ]) def test_no_enroll_in_old_version(self, mock_prov): self.node.provision_state = 'enroll' self.assertRaises(exceptions.NotSupported, self.node.create, self.session) self.assertFalse(self.session.post.called) self.assertFalse(mock_prov.called) def test_enroll_new_version(self, mock_prov): self.session.default_microversion = '1.11' self.node.provision_state = 'enroll' self.new_state = 'enroll' result = self.node.create(self.session) self.assertIs(result, self.node) self.session.post.assert_called_once_with( mock.ANY, json={'driver': FAKE['driver']}, headers=mock.ANY, microversion=self.session.default_microversion, params={}) self.assertFalse(mock_prov.called) def test_no_manageable_in_old_version(self, mock_prov): self.node.provision_state = 'manageable' self.assertRaises(exceptions.NotSupported, self.node.create, self.session) self.assertFalse(self.session.post.called) self.assertFalse(mock_prov.called) def test_manageable_old_version(self, mock_prov): self.session.default_microversion = '1.4' self.node.provision_state = 'manageable' self.new_state = 'available' result = self.node.create(self.session) self.assertIs(result, self.node) self.session.post.assert_called_once_with( mock.ANY, json={'driver': FAKE['driver']}, headers=mock.ANY, microversion=self.session.default_microversion, params={}) mock_prov.assert_called_once_with(self.node, self.session, 'manage', wait=True) def test_manageable_new_version(self, mock_prov): self.session.default_microversion = '1.11' self.node.provision_state = 'manageable' self.new_state = 'enroll' result = self.node.create(self.session) self.assertIs(result, self.node) self.session.post.assert_called_once_with( mock.ANY, json={'driver': FAKE['driver']}, headers=mock.ANY, microversion=self.session.default_microversion, params={}) mock_prov.assert_called_once_with(self.node, self.session, 'manage', wait=True) @mock.patch.object(exceptions, 'raise_from_response', mock.Mock()) @mock.patch.object(node.Node, '_get_session', lambda self, x: x) class TestNodeVif(base.TestCase): def setUp(self): super(TestNodeVif, self).setUp() self.session = mock.Mock(spec=adapter.Adapter) self.session.default_microversion = '1.28' self.session.log = mock.Mock() self.node = node.Node(id='c29db401-b6a7-4530-af8e-20a720dee946', driver=FAKE['driver']) self.vif_id = '714bdf6d-2386-4b5e-bd0d-bc036f04b1ef' def test_attach_vif(self): self.assertIsNone(self.node.attach_vif(self.session, self.vif_id)) self.session.post.assert_called_once_with( 'nodes/%s/vifs' % self.node.id, json={'id': self.vif_id}, headers=mock.ANY, microversion='1.28', retriable_status_codes=[409, 503]) def test_attach_vif_no_retries(self): self.assertIsNone(self.node.attach_vif(self.session, self.vif_id, retry_on_conflict=False)) self.session.post.assert_called_once_with( 'nodes/%s/vifs' % self.node.id, json={'id': self.vif_id}, headers=mock.ANY, microversion='1.28', retriable_status_codes={503}) def test_detach_vif_existing(self): self.assertTrue(self.node.detach_vif(self.session, self.vif_id)) self.session.delete.assert_called_once_with( 'nodes/%s/vifs/%s' % (self.node.id, self.vif_id), headers=mock.ANY, microversion='1.28', retriable_status_codes=_common.RETRIABLE_STATUS_CODES) def test_detach_vif_missing(self): self.session.delete.return_value.status_code = 400 self.assertFalse(self.node.detach_vif(self.session, self.vif_id)) self.session.delete.assert_called_once_with( 'nodes/%s/vifs/%s' % (self.node.id, self.vif_id), headers=mock.ANY, microversion='1.28', retriable_status_codes=_common.RETRIABLE_STATUS_CODES) def test_list_vifs(self): self.session.get.return_value.json.return_value = { 'vifs': [ {'id': '1234'}, {'id': '5678'}, ] } res = self.node.list_vifs(self.session) self.assertEqual(['1234', '5678'], res) self.session.get.assert_called_once_with( 'nodes/%s/vifs' % self.node.id, headers=mock.ANY, microversion='1.28') def test_incompatible_microversion(self): self.session.default_microversion = '1.1' self.assertRaises(exceptions.NotSupported, self.node.attach_vif, self.session, self.vif_id) self.assertRaises(exceptions.NotSupported, self.node.detach_vif, self.session, self.vif_id) self.assertRaises(exceptions.NotSupported, self.node.list_vifs, self.session) @mock.patch.object(exceptions, 'raise_from_response', mock.Mock()) @mock.patch.object(node.Node, '_get_session', lambda self, x: x) class TestNodeValidate(base.TestCase): def setUp(self): super(TestNodeValidate, self).setUp() self.session = mock.Mock(spec=adapter.Adapter) self.session.default_microversion = '1.28' self.node = node.Node(**FAKE) def test_validate_ok(self): self.session.get.return_value.json.return_value = { 'boot': {'result': True}, 'console': {'result': False, 'reason': 'Not configured'}, 'deploy': {'result': True}, 'inspect': {'result': None, 'reason': 'Not supported'}, 'power': {'result': True} } result = self.node.validate(self.session) for iface in ('boot', 'deploy', 'power'): self.assertTrue(result[iface].result) self.assertFalse(result[iface].reason) for iface in ('console', 'inspect'): self.assertIsNot(True, result[iface].result) self.assertTrue(result[iface].reason) def test_validate_failed(self): self.session.get.return_value.json.return_value = { 'boot': {'result': False}, 'console': {'result': False, 'reason': 'Not configured'}, 'deploy': {'result': False, 'reason': 'No deploy for you'}, 'inspect': {'result': None, 'reason': 'Not supported'}, 'power': {'result': True} } self.assertRaisesRegex(exceptions.ValidationException, 'No deploy for you', self.node.validate, self.session) def test_validate_no_failure(self): self.session.get.return_value.json.return_value = { 'boot': {'result': False}, 'console': {'result': False, 'reason': 'Not configured'}, 'deploy': {'result': False, 'reason': 'No deploy for you'}, 'inspect': {'result': None, 'reason': 'Not supported'}, 'power': {'result': True} } result = self.node.validate(self.session, required=None) self.assertTrue(result['power'].result) self.assertFalse(result['power'].reason) for iface in ('deploy', 'console', 'inspect'): self.assertIsNot(True, result[iface].result) self.assertTrue(result[iface].reason) # Reason can be empty self.assertFalse(result['boot'].result) self.assertIsNone(result['boot'].reason) @mock.patch('time.sleep', lambda _t: None) @mock.patch.object(node.Node, 'fetch', autospec=True) class TestNodeWaitForReservation(base.TestCase): def setUp(self): super(TestNodeWaitForReservation, self).setUp() self.session = mock.Mock(spec=adapter.Adapter) self.session.default_microversion = '1.6' self.session.log = mock.Mock() self.node = node.Node(**FAKE) def test_no_reservation(self, mock_fetch): self.node.reservation = None node = self.node.wait_for_reservation(None) self.assertIs(node, self.node) self.assertFalse(mock_fetch.called) def test_reservation(self, mock_fetch): self.node.reservation = 'example.com' def _side_effect(node, session): if self.node.reservation == 'example.com': self.node.reservation = 'example2.com' else: self.node.reservation = None mock_fetch.side_effect = _side_effect node = self.node.wait_for_reservation(self.session) self.assertIs(node, self.node) self.assertEqual(2, mock_fetch.call_count) def test_timeout(self, mock_fetch): self.node.reservation = 'example.com' self.assertRaises(exceptions.ResourceTimeout, self.node.wait_for_reservation, self.session, timeout=0.001) mock_fetch.assert_called_with(self.node, self.session) @mock.patch.object(node.Node, '_assert_microversion_for', _fake_assert) @mock.patch.object(exceptions, 'raise_from_response', mock.Mock()) class TestNodeSetPowerState(base.TestCase): def setUp(self): super(TestNodeSetPowerState, self).setUp() self.node = node.Node(**FAKE) self.session = mock.Mock(spec=adapter.Adapter, default_microversion=None) def test_power_on(self): self.node.set_power_state(self.session, 'power on') self.session.put.assert_called_once_with( 'nodes/%s/states/power' % FAKE['uuid'], json={'target': 'power on'}, headers=mock.ANY, microversion=None, retriable_status_codes=_common.RETRIABLE_STATUS_CODES) def test_soft_power_on(self): self.node.set_power_state(self.session, 'soft power off') self.session.put.assert_called_once_with( 'nodes/%s/states/power' % FAKE['uuid'], json={'target': 'soft power off'}, headers=mock.ANY, microversion='1.27', retriable_status_codes=_common.RETRIABLE_STATUS_CODES) @mock.patch.object(exceptions, 'raise_from_response', mock.Mock()) @mock.patch.object(node.Node, '_translate_response', mock.Mock()) @mock.patch.object(node.Node, '_get_session', lambda self, x: x) class TestNodeMaintenance(base.TestCase): def setUp(self): super(TestNodeMaintenance, self).setUp() self.node = node.Node.existing(**FAKE) self.session = mock.Mock(spec=adapter.Adapter, default_microversion='1.1', retriable_status_codes=None) def test_set(self): self.node.set_maintenance(self.session) self.session.put.assert_called_once_with( 'nodes/%s/maintenance' % self.node.id, json={'reason': None}, headers=mock.ANY, microversion=mock.ANY) def test_set_with_reason(self): self.node.set_maintenance(self.session, 'No work on Monday') self.session.put.assert_called_once_with( 'nodes/%s/maintenance' % self.node.id, json={'reason': 'No work on Monday'}, headers=mock.ANY, microversion=mock.ANY) def test_unset(self): self.node.unset_maintenance(self.session) self.session.delete.assert_called_once_with( 'nodes/%s/maintenance' % self.node.id, json=None, headers=mock.ANY, microversion=mock.ANY) def test_set_via_update(self): self.node.is_maintenance = True self.node.commit(self.session) self.session.put.assert_called_once_with( 'nodes/%s/maintenance' % self.node.id, json={'reason': None}, headers=mock.ANY, microversion=mock.ANY) self.assertFalse(self.session.patch.called) def test_set_with_reason_via_update(self): self.node.is_maintenance = True self.node.maintenance_reason = 'No work on Monday' self.node.commit(self.session) self.session.put.assert_called_once_with( 'nodes/%s/maintenance' % self.node.id, json={'reason': 'No work on Monday'}, headers=mock.ANY, microversion=mock.ANY) self.assertFalse(self.session.patch.called) def test_set_with_other_fields(self): self.node.is_maintenance = True self.node.name = 'lazy-3000' self.node.commit(self.session) self.session.put.assert_called_once_with( 'nodes/%s/maintenance' % self.node.id, json={'reason': None}, headers=mock.ANY, microversion=mock.ANY) self.session.patch.assert_called_once_with( 'nodes/%s' % self.node.id, json=[{'path': '/name', 'op': 'replace', 'value': 'lazy-3000'}], headers=mock.ANY, microversion=mock.ANY) def test_set_with_reason_and_other_fields(self): self.node.is_maintenance = True self.node.maintenance_reason = 'No work on Monday' self.node.name = 'lazy-3000' self.node.commit(self.session) self.session.put.assert_called_once_with( 'nodes/%s/maintenance' % self.node.id, json={'reason': 'No work on Monday'}, headers=mock.ANY, microversion=mock.ANY) self.session.patch.assert_called_once_with( 'nodes/%s' % self.node.id, json=[{'path': '/name', 'op': 'replace', 'value': 'lazy-3000'}], headers=mock.ANY, microversion=mock.ANY) def test_no_reason_without_maintenance(self): self.node.maintenance_reason = 'Can I?' self.assertRaises(ValueError, self.node.commit, self.session) self.assertFalse(self.session.put.called) self.assertFalse(self.session.patch.called) def test_set_unset_maintenance(self): self.node.is_maintenance = True self.node.maintenance_reason = 'No work on Monday' self.node.commit(self.session) self.session.put.assert_called_once_with( 'nodes/%s/maintenance' % self.node.id, json={'reason': 'No work on Monday'}, headers=mock.ANY, microversion=mock.ANY) self.node.is_maintenance = False self.node.commit(self.session) self.assertIsNone(self.node.maintenance_reason) self.session.delete.assert_called_once_with( 'nodes/%s/maintenance' % self.node.id, json=None, headers=mock.ANY, microversion=mock.ANY) @mock.patch.object(node.Node, 'fetch', lambda self, session: self) @mock.patch.object(exceptions, 'raise_from_response', mock.Mock()) class TestNodeSetBootDevice(base.TestCase): def setUp(self): super(TestNodeSetBootDevice, self).setUp() self.node = node.Node(**FAKE) self.session = mock.Mock(spec=adapter.Adapter, default_microversion='1.1') def test_node_set_boot_device(self): self.node.set_boot_device(self.session, 'pxe', persistent=False) self.session.put.assert_called_once_with( 'nodes/%s/management/boot_device' % self.node.id, json={'boot_device': 'pxe', 'persistent': False}, headers=mock.ANY, microversion=mock.ANY, retriable_status_codes=_common.RETRIABLE_STATUS_CODES) @mock.patch.object(node.Node, 'fetch', lambda self, session: self) @mock.patch.object(exceptions, 'raise_from_response', mock.Mock()) class TestNodeTraits(base.TestCase): def setUp(self): super(TestNodeTraits, self).setUp() self.node = node.Node(**FAKE) self.session = mock.Mock(spec=adapter.Adapter, default_microversion='1.37') self.session.log = mock.Mock() def test_node_add_trait(self): self.node.add_trait(self.session, 'CUSTOM_FAKE') self.session.put.assert_called_once_with( 'nodes/%s/traits/%s' % (self.node.id, 'CUSTOM_FAKE'), json=None, headers=mock.ANY, microversion='1.37', retriable_status_codes=_common.RETRIABLE_STATUS_CODES) def test_remove_trait(self): self.node.remove_trait(self.session, 'CUSTOM_FAKE') self.session.delete.assert_called_once_with( 'nodes/%s/traits/%s' % (self.node.id, 'CUSTOM_FAKE'), headers=mock.ANY, microversion='1.37', retriable_status_codes=_common.RETRIABLE_STATUS_CODES) def test_remove_trait_missing(self): self.session.delete.return_value.status_code = 400 self.assertFalse(self.node.remove_trait(self.session, 'CUSTOM_MISSING')) self.session.delete.assert_called_once_with( 'nodes/%s/traits/%s' % (self.node.id, 'CUSTOM_MISSING'), headers=mock.ANY, microversion='1.37', retriable_status_codes=_common.RETRIABLE_STATUS_CODES) def test_set_traits(self): traits = ['CUSTOM_FAKE', 'CUSTOM_REAL', 'CUSTOM_MISSING'] self.node.set_traits(self.session, traits) self.session.put.assert_called_once_with( 'nodes/%s/traits' % self.node.id, json={'traits': ['CUSTOM_FAKE', 'CUSTOM_REAL', 'CUSTOM_MISSING']}, headers=mock.ANY, microversion='1.37', retriable_status_codes=_common.RETRIABLE_STATUS_CODES) @mock.patch.object(node.Node, '_assert_microversion_for', _fake_assert) @mock.patch.object(resource.Resource, 'patch', autospec=True) class TestNodePatch(base.TestCase): def setUp(self): super(TestNodePatch, self).setUp() self.node = node.Node(**FAKE) self.session = mock.Mock(spec=adapter.Adapter, default_microversion=None) self.session.log = mock.Mock() def test_node_patch(self, mock_patch): patch = {'path': 'test'} self.node.patch(self.session, patch=patch) mock_patch.assert_called_once() kwargs = mock_patch.call_args[1] self.assertEqual(kwargs['patch'], {'path': 'test'}) @mock.patch.object(resource.Resource, '_prepare_request', autospec=True) @mock.patch.object(resource.Resource, '_commit', autospec=True) def test_node_patch_reset_interfaces(self, mock__commit, mock_prepreq, mock_patch): patch = {'path': 'test'} self.node.patch(self.session, patch=patch, retry_on_conflict=True, reset_interfaces=True) mock_prepreq.assert_called_once() prepreq_kwargs = mock_prepreq.call_args[1] self.assertEqual(prepreq_kwargs['params'], [('reset_interfaces', True)]) mock__commit.assert_called_once() commit_args = mock__commit.call_args[0] commit_kwargs = mock__commit.call_args[1] self.assertIn('1.45', commit_args) self.assertEqual(commit_kwargs['retry_on_conflict'], True) mock_patch.assert_not_called()