Merge "baremetal: Enhance VIF attachment with port and portgroup UUIDs"

This commit is contained in:
Zuul 2024-07-22 21:52:47 +00:00 committed by Gerrit Code Review
commit dfef1d36e8
5 changed files with 137 additions and 15 deletions

View File

@ -70,6 +70,9 @@ STATE_VERSIONS = {
VIF_VERSION = '1.28' VIF_VERSION = '1.28'
"""API version in which the VIF operations were introduced.""" """API version in which the VIF operations were introduced."""
VIF_OPTIONAL_PARAMS_VERSION = '1.67'
"""API version in which the VIF optional parameters were introduced."""
INJECT_NMI_VERSION = '1.29' INJECT_NMI_VERSION = '1.29'
"""API vresion in which support for injecting NMI was introduced.""" """API vresion in which support for injecting NMI was introduced."""

View File

@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import typing as ty
from openstack.baremetal.v1 import _common from openstack.baremetal.v1 import _common
from openstack.baremetal.v1 import allocation as _allocation from openstack.baremetal.v1 import allocation as _allocation
from openstack.baremetal.v1 import chassis as _chassis from openstack.baremetal.v1 import chassis as _chassis
@ -64,7 +66,7 @@ class Proxy(proxy.Proxy):
error_message="No {resource_type} found for {value}".format( error_message="No {resource_type} found for {value}".format(
resource_type=resource_type.__name__, value=value resource_type=resource_type.__name__, value=value
), ),
**kwargs **kwargs,
) )
def chassis(self, details=False, **query): def chassis(self, details=False, **query):
@ -964,7 +966,15 @@ class Proxy(proxy.Proxy):
_portgroup.PortGroup, port_group, ignore_missing=ignore_missing _portgroup.PortGroup, port_group, ignore_missing=ignore_missing
) )
def attach_vif_to_node(self, node, vif_id, retry_on_conflict=True): def attach_vif_to_node(
self,
node: ty.Union[_node.Node, str],
vif_id: str,
retry_on_conflict: bool = True,
*,
port_id: ty.Optional[str] = None,
port_group_id: ty.Optional[str] = None,
) -> None:
"""Attach a VIF to the node. """Attach a VIF to the node.
The exact form of the VIF ID depends on the network interface used by The exact form of the VIF ID depends on the network interface used by
@ -974,17 +984,29 @@ class Proxy(proxy.Proxy):
:param node: The value can be either the name or ID of a node or :param node: The value can be either the name or ID of a node or
a :class:`~openstack.baremetal.v1.node.Node` instance. a :class:`~openstack.baremetal.v1.node.Node` instance.
:param string vif_id: Backend-specific VIF ID. :param vif_id: Backend-specific VIF ID.
:param retry_on_conflict: Whether to retry HTTP CONFLICT errors. :param retry_on_conflict: Whether to retry HTTP CONFLICT errors.
This can happen when either the VIF is already used on a node or This can happen when either the VIF is already used on a node or
the node is locked. Since the latter happens more often, the the node is locked. Since the latter happens more often, the
default value is True. default value is True.
:return: ``None`` :param port_id: The UUID of the port to attach the VIF to. Only one of
port_id or port_group_id can be provided.
:param port_group_id: The UUID of the portgroup to attach to. Only one
of port_group_id or port_id can be provided.
:return: None
:raises: :exc:`~openstack.exceptions.NotSupported` if the server :raises: :exc:`~openstack.exceptions.NotSupported` if the server
does not support the VIF API. does not support the VIF API.
:raises: :exc:`~openstack.exceptions.InvalidRequest` if both port_id
and port_group_id are provided.
""" """
res = self._get_resource(_node.Node, node) res = self._get_resource(_node.Node, node)
res.attach_vif(self, vif_id, retry_on_conflict=retry_on_conflict) res.attach_vif(
self,
vif_id=vif_id,
retry_on_conflict=retry_on_conflict,
port_id=port_id,
port_group_id=port_group_id,
)
def detach_vif_from_node(self, node, vif_id, ignore_missing=True): def detach_vif_from_node(self, node, vif_id, ignore_missing=True):
"""Detach a VIF from the node. """Detach a VIF from the node.

View File

@ -12,6 +12,7 @@
import collections import collections
import enum import enum
import typing as ty
from openstack.baremetal.v1 import _common from openstack.baremetal.v1 import _common
from openstack import exceptions from openstack import exceptions
@ -790,7 +791,15 @@ class Node(_common.Resource):
if wait: if wait:
self.wait_for_power_state(session, expected, timeout=timeout) self.wait_for_power_state(session, expected, timeout=timeout)
def attach_vif(self, session, vif_id, retry_on_conflict=True): def attach_vif(
self,
session,
vif_id: str,
retry_on_conflict: bool = True,
*,
port_id: ty.Optional[str] = None,
port_group_id: ty.Optional[str] = None,
) -> None:
"""Attach a VIF to the node. """Attach a VIF to the node.
The exact form of the VIF ID depends on the network interface used by The exact form of the VIF ID depends on the network interface used by
@ -800,26 +809,46 @@ class Node(_common.Resource):
:param session: The session to use for making this request. :param session: The session to use for making this request.
:type session: :class:`~keystoneauth1.adapter.Adapter` :type session: :class:`~keystoneauth1.adapter.Adapter`
:param string vif_id: Backend-specific VIF ID. :param vif_id: Backend-specific VIF ID.
:param retry_on_conflict: Whether to retry HTTP CONFLICT errors. :param retry_on_conflict: Whether to retry HTTP CONFLICT errors.
This can happen when either the VIF is already used on a node or This can happen when either the VIF is already used on a node or
the node is locked. Since the latter happens more often, the the node is locked. Since the latter happens more often, the
default value is True. default value is True.
:return: ``None`` :param port_id: The UUID of the port to attach the VIF to. Only one of
port_id or port_group_id can be provided.
:param port_group_id: The UUID of the portgroup to attach to. Only one
of port_group_id or port_id can be provided.
:return: None
:raises: :exc:`~openstack.exceptions.NotSupported` if the server :raises: :exc:`~openstack.exceptions.NotSupported` if the server
does not support the VIF API. does not support the VIF API.
:raises: :exc:`~openstack.exceptions.InvalidRequest` if both port_id
and port_group_id are provided.
""" """
if port_id and port_group_id:
msg = (
'Only one of vif_port_id and vif_portgroup_id can be provided'
)
raise exceptions.InvalidRequest(msg)
session = self._get_session(session) session = self._get_session(session)
if port_id or port_group_id:
required_version = _common.VIF_OPTIONAL_PARAMS_VERSION
else:
required_version = _common.VIF_VERSION
version = self._assert_microversion_for( version = self._assert_microversion_for(
session, session,
'commit', 'commit',
_common.VIF_VERSION, required_version,
error_message=("Cannot use VIF attachment API"), error_message=("Cannot use VIF attachment API"),
) )
request = self._prepare_request(requires_id=True) request = self._prepare_request(requires_id=True)
request.url = utils.urljoin(request.url, 'vifs') request.url = utils.urljoin(request.url, 'vifs')
body = {'id': vif_id} body = {'id': vif_id}
if port_id:
body['port_uuid'] = port_id
elif port_group_id:
body['portgroup_uuid'] = port_group_id
retriable_status_codes = _common.RETRIABLE_STATUS_CODES retriable_status_codes = _common.RETRIABLE_STATUS_CODES
if not retry_on_conflict: if not retry_on_conflict:
retriable_status_codes = list(set(retriable_status_codes) - {409}) retriable_status_codes = list(set(retriable_status_codes) - {409})

View File

@ -558,12 +558,14 @@ class TestNodeVif(base.TestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.session = mock.Mock(spec=adapter.Adapter) self.session = mock.Mock(spec=adapter.Adapter)
self.session.default_microversion = '1.28' self.session.default_microversion = '1.67'
self.session.log = mock.Mock() self.session.log = mock.Mock()
self.node = node.Node( self.node = node.Node(
id='c29db401-b6a7-4530-af8e-20a720dee946', driver=FAKE['driver'] id='c29db401-b6a7-4530-af8e-20a720dee946', driver=FAKE['driver']
) )
self.vif_id = '714bdf6d-2386-4b5e-bd0d-bc036f04b1ef' self.vif_id = '714bdf6d-2386-4b5e-bd0d-bc036f04b1ef'
self.vif_port_uuid = 'port-uuid'
self.vif_portgroup_uuid = 'portgroup-uuid'
def test_attach_vif(self): def test_attach_vif(self):
self.assertIsNone(self.node.attach_vif(self.session, self.vif_id)) self.assertIsNone(self.node.attach_vif(self.session, self.vif_id))
@ -571,7 +573,7 @@ class TestNodeVif(base.TestCase):
'nodes/%s/vifs' % self.node.id, 'nodes/%s/vifs' % self.node.id,
json={'id': self.vif_id}, json={'id': self.vif_id},
headers=mock.ANY, headers=mock.ANY,
microversion='1.28', microversion='1.67',
retriable_status_codes=[409, 503], retriable_status_codes=[409, 503],
) )
@ -585,16 +587,59 @@ class TestNodeVif(base.TestCase):
'nodes/%s/vifs' % self.node.id, 'nodes/%s/vifs' % self.node.id,
json={'id': self.vif_id}, json={'id': self.vif_id},
headers=mock.ANY, headers=mock.ANY,
microversion='1.28', microversion='1.67',
retriable_status_codes=[503], retriable_status_codes=[503],
) )
def test_attach_vif_with_port_uuid(self):
self.assertIsNone(
self.node.attach_vif(
self.session, self.vif_id, port_id=self.vif_port_uuid
)
)
self.session.post.assert_called_once_with(
'nodes/%s/vifs' % self.node.id,
json={'id': self.vif_id, 'port_uuid': self.vif_port_uuid},
headers=mock.ANY,
microversion='1.67',
retriable_status_codes=[409, 503],
)
def test_attach_vif_with_portgroup_uuid(self):
self.assertIsNone(
self.node.attach_vif(
self.session,
self.vif_id,
port_group_id=self.vif_portgroup_uuid,
)
)
self.session.post.assert_called_once_with(
'nodes/%s/vifs' % self.node.id,
json={
'id': self.vif_id,
'portgroup_uuid': self.vif_portgroup_uuid,
},
headers=mock.ANY,
microversion='1.67',
retriable_status_codes=[409, 503],
)
def test_attach_vif_with_port_uuid_and_portgroup_uuid(self):
self.assertRaises(
exceptions.InvalidRequest,
self.node.attach_vif,
self.session,
self.vif_id,
port_id=self.vif_port_uuid,
port_group_id=self.vif_portgroup_uuid,
)
def test_detach_vif_existing(self): def test_detach_vif_existing(self):
self.assertTrue(self.node.detach_vif(self.session, self.vif_id)) self.assertTrue(self.node.detach_vif(self.session, self.vif_id))
self.session.delete.assert_called_once_with( self.session.delete.assert_called_once_with(
f'nodes/{self.node.id}/vifs/{self.vif_id}', f'nodes/{self.node.id}/vifs/{self.vif_id}',
headers=mock.ANY, headers=mock.ANY,
microversion='1.28', microversion='1.67',
retriable_status_codes=_common.RETRIABLE_STATUS_CODES, retriable_status_codes=_common.RETRIABLE_STATUS_CODES,
) )
@ -604,7 +649,7 @@ class TestNodeVif(base.TestCase):
self.session.delete.assert_called_once_with( self.session.delete.assert_called_once_with(
f'nodes/{self.node.id}/vifs/{self.vif_id}', f'nodes/{self.node.id}/vifs/{self.vif_id}',
headers=mock.ANY, headers=mock.ANY,
microversion='1.28', microversion='1.67',
retriable_status_codes=_common.RETRIABLE_STATUS_CODES, retriable_status_codes=_common.RETRIABLE_STATUS_CODES,
) )
@ -620,7 +665,7 @@ class TestNodeVif(base.TestCase):
self.session.get.assert_called_once_with( self.session.get.assert_called_once_with(
'nodes/%s/vifs' % self.node.id, 'nodes/%s/vifs' % self.node.id,
headers=mock.ANY, headers=mock.ANY,
microversion='1.28', microversion='1.67',
) )
def test_incompatible_microversion(self): def test_incompatible_microversion(self):
@ -641,6 +686,23 @@ class TestNodeVif(base.TestCase):
exceptions.NotSupported, self.node.list_vifs, self.session exceptions.NotSupported, self.node.list_vifs, self.session
) )
def test_incompatible_microversion_optional_params(self):
self.session.default_microversion = '1.28'
self.assertRaises(
exceptions.NotSupported,
self.node.attach_vif,
self.session,
self.vif_id,
port_id=self.vif_port_uuid,
)
self.assertRaises(
exceptions.NotSupported,
self.node.attach_vif,
self.session,
self.vif_id,
port_group_id=self.vif_portgroup_uuid,
)
@mock.patch.object(exceptions, 'raise_from_response', mock.Mock()) @mock.patch.object(exceptions, 'raise_from_response', mock.Mock())
@mock.patch.object(node.Node, '_get_session', lambda self, x: x) @mock.patch.object(node.Node, '_get_session', lambda self, x: x)

View File

@ -0,0 +1,6 @@
---
features:
- |
Extend the ``attach_vif`` and ``attach_vif_to_node`` methods of the
baremetal proxy to to accept optional parameters for VIF port UUID and
VIF portgroup UUID.