Add node traits support to baremetal

This patch adds add-trait, remove-trait and set-traits
methods to the baremetal module.

Change-Id: I9c2dd35c1c5f823d8c482f0177e87d49024242a4
This commit is contained in:
Riccardo Pittau 2019-07-23 11:04:43 +02:00
parent a88328e90e
commit f162b71cb8
5 changed files with 220 additions and 0 deletions

View File

@ -917,3 +917,44 @@ class Proxy(proxy.Proxy):
"""
res = self._get_resource(_allocation.Allocation, allocation)
return res.wait(self, timeout=timeout, ignore_error=ignore_error)
def add_node_trait(self, node, trait):
"""Add a trait to a node.
:param node: The value can be the name or ID of a node or a
:class:`~openstack.baremetal.v1.node.Node` instance.
:param trait: trait to remove from the node.
:returns: The updated node
"""
res = self._get_resource(_node.Node, node)
return res.add_trait(self, trait)
def remove_node_trait(self, node, trait, ignore_missing=True):
"""Remove a trait from a node.
:param node: The value can be the name or ID of a node or a
:class:`~openstack.baremetal.v1.node.Node` instance.
:param trait: trait to remove from the node.
:param bool ignore_missing: When set to ``False``, an exception
:class:`~openstack.exceptions.ResourceNotFound` will be raised
when the trait could not be found. When set to ``True``, no
exception will be raised when attempting to delete a non-existent
trait.
:returns: The updated :class:`~openstack.baremetal.v1.node.Node`
"""
res = self._get_resource(_node.Node, node)
return res.remove_trait(self, trait, ignore_missing=ignore_missing)
def set_node_traits(self, node, traits):
"""Set traits for a node.
Removes any existing traits and adds the traits passed in to this
method.
:param node: The value can be the name or ID of a node or a
:class:`~openstack.baremetal.v1.node.Node` instance.
:param traits: list of traits to add to the node.
:returns: The updated :class:`~openstack.baremetal.v1.node.Node`
"""
res = self._get_resource(_node.Node, node)
return res.set_traits(self, traits)

View File

@ -745,5 +745,89 @@ class Node(_common.ListMixin, resource.Resource):
.format(node=self.id))
exceptions.raise_from_response(response, error_message=msg)
def add_trait(self, session, trait):
"""Add a trait to a node.
:param session: The session to use for making this request.
:param trait: The trait to add to the node.
:returns: The updated :class:`~openstack.baremetal.v1.node.Node`
"""
session = self._get_session(session)
version = utils.pick_microversion(session, '1.37')
request = self._prepare_request(requires_id=True)
request.url = utils.urljoin(request.url, 'traits', trait)
response = session.put(
request.url, json=None,
headers=request.headers, microversion=version,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
msg = ("Failed to add trait {trait} for node {node}"
.format(trait=trait, node=self.id))
exceptions.raise_from_response(response, error_message=msg)
self.traits = list(set(self.traits or ()) | {trait})
def remove_trait(self, session, trait, ignore_missing=True):
"""Remove a trait from a node.
:param session: The session to use for making this request.
:param trait: The trait to remove from the node.
:param bool ignore_missing: When set to ``False``
:class:`~openstack.exceptions.ResourceNotFound` will be
raised when the trait does not exist.
Otherwise, ``False`` is returned.
:returns: The updated :class:`~openstack.baremetal.v1.node.Node`
"""
session = self._get_session(session)
version = utils.pick_microversion(session, '1.37')
request = self._prepare_request(requires_id=True)
request.url = utils.urljoin(request.url, 'traits', trait)
response = session.delete(
request.url, headers=request.headers, microversion=version,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
if ignore_missing or response.status_code == 400:
session.log.debug(
'Trait %(trait)s was already removed from node %(node)s',
{'trait': trait, 'node': self.id})
return False
msg = ("Failed to remove trait {trait} from bare metal node {node}"
.format(node=self.id, trait=trait))
exceptions.raise_from_response(response, error_message=msg)
self.traits = list(set(self.traits) - {trait})
return True
def set_traits(self, session, traits):
"""Set traits for a node.
Removes any existing traits and adds the traits passed in to this
method.
:param session: The session to use for making this request.
:param traits: list of traits to add to the node.
:returns: The updated :class:`~openstack.baremetal.v1.node.Node`
"""
session = self._get_session(session)
version = utils.pick_microversion(session, '1.37')
request = self._prepare_request(requires_id=True)
request.url = utils.urljoin(request.url, 'traits')
body = {'traits': traits}
response = session.put(
request.url, json=body,
headers=request.headers, microversion=version,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
msg = ("Failed to set traits for node {node}"
.format(node=self.id))
exceptions.raise_from_response(response, error_message=msg)
self.traits = traits
NodeDetail = Node

View File

@ -303,3 +303,49 @@ class TestBareMetalVif(base.BaseBaremetalTest):
self.assertRaises(exceptions.ResourceNotFound,
self.conn.baremetal.detach_vif_from_node,
uuid, self.vif_id, ignore_missing=False)
class TestTraits(base.BaseBaremetalTest):
min_microversion = '1.37'
def setUp(self):
super(TestTraits, self).setUp()
self.node = self.create_node()
def test_add_remove_node_trait(self):
node = self.conn.baremetal.get_node(self.node)
self.assertEqual([], node.traits)
self.conn.baremetal.add_node_trait(self.node, 'CUSTOM_FAKE')
self.assertEqual(['CUSTOM_FAKE'], self.node.traits)
node = self.conn.baremetal.get_node(self.node)
self.assertEqual(['CUSTOM_FAKE'], node.traits)
self.conn.baremetal.add_node_trait(self.node, 'CUSTOM_REAL')
self.assertEqual(['CUSTOM_FAKE', 'CUSTOM_REAL'], self.node.traits)
node = self.conn.baremetal.get_node(self.node)
self.assertEqual(['CUSTOM_FAKE', 'CUSTOM_REAL'], node.traits)
self.conn.baremetal.remove_node_trait(node, 'CUSTOM_FAKE',
ignore_missing=False)
self.assertEqual(['CUSTOM_REAL'], self.node.traits)
node = self.conn.baremetal.get_node(self.node)
self.assertEqual(['CUSTOM_REAL'], node.traits)
def test_set_node_traits(self):
node = self.conn.baremetal.get_node(self.node)
self.assertEqual([], node.traits)
traits1 = ['CUSTOM_FAKE', 'CUSTOM_REAL']
traits2 = ['CUSTOM_FOOBAR']
self.conn.baremetal.set_node_traits(self.node, traits1)
self.assertEqual(['CUSTOM_FAKE', 'CUSTOM_REAL'], self.node.traits)
node = self.conn.baremetal.get_node(self.node)
self.assertEqual(['CUSTOM_FAKE', 'CUSTOM_REAL'], node.traits)
self.conn.baremetal.set_node_traits(self.node, traits2)
self.assertEqual(['CUSTOM_FOOBAR'], self.node.traits)
node = self.conn.baremetal.get_node(self.node)
self.assertEqual(['CUSTOM_FOOBAR'], node.traits)

View File

@ -702,3 +702,48 @@ class TestNodeSetBootDevice(base.TestCase):
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)

View File

@ -0,0 +1,4 @@
---
features:
- |
Implements add/remove/set traits API for bare metal nodes.