From 13c6bc2bd913b4a3b8ea048b95158ed9560fbf6a Mon Sep 17 00:00:00 2001 From: Riccardo Pittau Date: Fri, 18 Oct 2019 12:24:33 +0200 Subject: [PATCH] Add reset_interfaces argument to patch_node Ironic uses reset_interfaces option when patching a node to reset all the hardware interfaces of a node. This patch adds that argument to the patch_node method in the baremetal module and introduces a patch method under the node module to be able to evaluate the reset_interfaces parameter. Also, it modifies the _prepare_request method in resource to accept query parameters in urls and build the request uri keeping those into account. Increasing minimum version of mock to 3.0.0 to be able to use testing features not supported before that version, e.g. assert_called_once Change-Id: I8bca403df7d38a7ac1d066c5f1d7e2bff1deb054 --- lower-constraints.txt | 2 +- openstack/baremetal/v1/_proxy.py | 9 ++++- openstack/baremetal/v1/node.py | 34 +++++++++++++++++ openstack/resource.py | 6 ++- .../tests/unit/baremetal/v1/test_node.py | 37 +++++++++++++++++++ openstack/tests/unit/test_resource.py | 21 +++++++++++ test-requirements.txt | 2 +- 7 files changed, 106 insertions(+), 5 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 7cdec6680..c4e6ec4d7 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -17,7 +17,7 @@ jsonpointer==1.13 jsonschema==2.6.0 keystoneauth1==3.18.0 linecache2==1.0.0 -mock==2.0.0 +mock==3.0.0 mox3==0.20.0 munch==2.1.0 netifaces==0.10.4 diff --git a/openstack/baremetal/v1/_proxy.py b/openstack/baremetal/v1/_proxy.py index eea434227..0e00b3914 100644 --- a/openstack/baremetal/v1/_proxy.py +++ b/openstack/baremetal/v1/_proxy.py @@ -294,12 +294,16 @@ class Proxy(proxy.Proxy): res = self._get_resource(_node.Node, node, **attrs) return res.commit(self, retry_on_conflict=retry_on_conflict) - def patch_node(self, node, patch, retry_on_conflict=True): + def patch_node(self, node, patch, reset_interfaces=None, + retry_on_conflict=True): """Apply a JSON patch to the node. :param node: The value can be the name or ID of a node or a :class:`~openstack.baremetal.v1.node.Node` instance. :param patch: JSON patch to apply. + :param bool reset_interfaces: whether to reset the node hardware + interfaces to their defaults. This works only when changing + drivers. Added in API microversion 1.45. :param bool retry_on_conflict: Whether to retry HTTP CONFLICT error. Most of the time it can be retried, since it is caused by the node being locked. However, when setting ``instance_id``, this is @@ -313,7 +317,8 @@ class Proxy(proxy.Proxy): :rtype: :class:`~openstack.baremetal.v1.node.Node` """ res = self._get_resource(_node.Node, node) - return res.patch(self, patch, retry_on_conflict=retry_on_conflict) + return res.patch(self, patch, retry_on_conflict=retry_on_conflict, + reset_interfaces=reset_interfaces) def set_node_provision_state(self, node, target, config_drive=None, clean_steps=None, rescue_password=None, diff --git a/openstack/baremetal/v1/node.py b/openstack/baremetal/v1/node.py index 2f4ff1fe1..6bb099c3a 100644 --- a/openstack/baremetal/v1/node.py +++ b/openstack/baremetal/v1/node.py @@ -833,5 +833,39 @@ class Node(_common.ListMixin, resource.Resource): self.traits = traits + def patch(self, session, patch=None, prepend_key=True, has_body=True, + retry_on_conflict=None, base_path=None, reset_interfaces=None): + + if reset_interfaces is not None: + # The id cannot be dirty for an commit + self._body._dirty.discard("id") + + # Only try to update if we actually have anything to commit. + if not patch and not self.requires_commit: + return self + + if not self.allow_patch: + raise exceptions.MethodNotSupported(self, "patch") + + session = self._get_session(session) + microversion = utils.pick_microversion(session, '1.45') + params = [('reset_interfaces', reset_interfaces)] + + request = self._prepare_request(requires_id=True, + prepend_key=prepend_key, + base_path=base_path, patch=True, + params=params) + + if patch: + request.body += self._convert_patch(patch) + + return self._commit(session, request, 'PATCH', microversion, + has_body=has_body, + retry_on_conflict=retry_on_conflict) + + else: + return super(Node, self).patch(session, patch=patch, + retry_on_conflict=retry_on_conflict) + NodeDetail = Node diff --git a/openstack/resource.py b/openstack/resource.py index 59f38a4d2..fe266c6f5 100644 --- a/openstack/resource.py +++ b/openstack/resource.py @@ -1053,7 +1053,7 @@ class Resource(dict): return body def _prepare_request(self, requires_id=None, prepend_key=False, - patch=False, base_path=None): + patch=False, base_path=None, params=None): """Prepare a request to be sent to the server Create operations don't require an ID, but all others do, @@ -1091,6 +1091,10 @@ class Resource(dict): uri = utils.urljoin(uri, self.id) + if params: + query_params = six.moves.urllib.parse.urlencode(params) + uri += '?' + query_params + return _Request(uri, body, headers) def _translate_response(self, response, has_body=None, error_message=None): diff --git a/openstack/tests/unit/baremetal/v1/test_node.py b/openstack/tests/unit/baremetal/v1/test_node.py index 2669eaf30..a1a4dc04f 100644 --- a/openstack/tests/unit/baremetal/v1/test_node.py +++ b/openstack/tests/unit/baremetal/v1/test_node.py @@ -16,6 +16,7 @@ import mock 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 @@ -766,3 +767,39 @@ class TestNodeTraits(base.TestCase): json={'traits': ['CUSTOM_FAKE', 'CUSTOM_REAL', 'CUSTOM_MISSING']}, headers=mock.ANY, microversion='1.37', retriable_status_codes=_common.RETRIABLE_STATUS_CODES) + + +@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.kwargs + 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.kwargs + self.assertEqual(prepreq_kwargs['params'], + [('reset_interfaces', True)]) + mock__commit.assert_called_once() + commit_args = mock__commit.call_args.args + commit_kwargs = mock__commit.call_args.kwargs + self.assertIn('1.45', commit_args) + self.assertEqual(commit_kwargs['retry_on_conflict'], True) + mock_patch.assert_not_called() diff --git a/openstack/tests/unit/test_resource.py b/openstack/tests/unit/test_resource.py index 43e47e91b..ff29ba83d 100644 --- a/openstack/tests/unit/test_resource.py +++ b/openstack/tests/unit/test_resource.py @@ -1104,6 +1104,27 @@ class TestResource(base.TestCase): self.assertEqual([{'op': 'add', 'path': '/x', 'value': 1}], result.body) + def test__prepare_request_with_patch_params(self): + class Test(resource.Resource): + commit_jsonpatch = True + base_path = "/something" + x = resource.Body("x") + y = resource.Body("y") + + the_id = "id" + sot = Test.existing(id=the_id, x=1, y=2) + sot.x = 3 + + params = [('foo', 'bar'), + ('life', 42)] + + result = sot._prepare_request(requires_id=True, patch=True, + params=params) + + self.assertEqual("something/id?foo=bar&life=42", result.url) + self.assertEqual([{'op': 'replace', 'path': '/x', 'value': 3}], + result.body) + def test__translate_response_no_body(self): class Test(resource.Resource): attr = resource.Header("attr") diff --git a/test-requirements.txt b/test-requirements.txt index ce8b81d83..bbd290487 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -8,7 +8,7 @@ ddt>=1.0.1 # MIT extras>=1.0.0 # MIT fixtures>=3.0.0 # Apache-2.0/BSD jsonschema>=2.6.0 # MIT -mock>=2.0.0 # BSD +mock>=3.0.0 # BSD prometheus-client>=0.4.2 # Apache-2.0 python-subunit>=1.0.0 # Apache-2.0/BSD oslo.config>=6.1.0 # Apache-2.0