baremetal: Enhance VIF attachment with port and portgroup UUIDs

This change extends the `attach_vif` and `attach_vif_to_node` methods to
accept optional parameters for VIF port UUID and VIF portgroup UUID.
This enhancement allows for more flexible VIF attachment scenarios while
ensuring that only one of these parameters can be set at a time.

- Added parameters `vif_port_uuid` and `vif_portgroup_uuid` to the
  `attach_vif` method in the `Node` class.
- Updated the `attach_vif_to_node` method in the `Proxy` class to pass
  these parameters to the `attach_vif` method.
- Included a check to ensure only one of `vif_port_uuid` and
  `vif_portgroup_uuid` can be set at a time in both methods.
- Modified the request body in the `attach_vif` method to include these
  parameters if provided.

Change-Id: I4b8487b45ae04f387c2f02a9505916072edc96aa
This commit is contained in:
Salman Hajizada 2024-06-12 22:30:24 +03:00 committed by Stephen Finucane
parent 9145dcec64
commit 3467560977
5 changed files with 137 additions and 15 deletions

View File

@ -70,6 +70,9 @@ STATE_VERSIONS = {
VIF_VERSION = '1.28'
"""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'
"""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
# under the License.
import typing as ty
from openstack.baremetal.v1 import _common
from openstack.baremetal.v1 import allocation as _allocation
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(
resource_type=resource_type.__name__, value=value
),
**kwargs
**kwargs,
)
def chassis(self, details=False, **query):
@ -964,7 +966,15 @@ class Proxy(proxy.Proxy):
_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.
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
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.
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
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
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.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):
"""Detach a VIF from the node.

View File

@ -12,6 +12,7 @@
import collections
import enum
import typing as ty
from openstack.baremetal.v1 import _common
from openstack import exceptions
@ -790,7 +791,15 @@ class Node(_common.Resource):
if wait:
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.
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.
: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.
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
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
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)
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(
session,
'commit',
_common.VIF_VERSION,
required_version,
error_message=("Cannot use VIF attachment API"),
)
request = self._prepare_request(requires_id=True)
request.url = utils.urljoin(request.url, 'vifs')
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
if not retry_on_conflict:
retriable_status_codes = list(set(retriable_status_codes) - {409})

View File

@ -558,12 +558,14 @@ class TestNodeVif(base.TestCase):
def setUp(self):
super().setUp()
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.node = node.Node(
id='c29db401-b6a7-4530-af8e-20a720dee946', driver=FAKE['driver']
)
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):
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,
json={'id': self.vif_id},
headers=mock.ANY,
microversion='1.28',
microversion='1.67',
retriable_status_codes=[409, 503],
)
@ -585,16 +587,59 @@ class TestNodeVif(base.TestCase):
'nodes/%s/vifs' % self.node.id,
json={'id': self.vif_id},
headers=mock.ANY,
microversion='1.28',
microversion='1.67',
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):
self.assertTrue(self.node.detach_vif(self.session, self.vif_id))
self.session.delete.assert_called_once_with(
f'nodes/{self.node.id}/vifs/{self.vif_id}',
headers=mock.ANY,
microversion='1.28',
microversion='1.67',
retriable_status_codes=_common.RETRIABLE_STATUS_CODES,
)
@ -604,7 +649,7 @@ class TestNodeVif(base.TestCase):
self.session.delete.assert_called_once_with(
f'nodes/{self.node.id}/vifs/{self.vif_id}',
headers=mock.ANY,
microversion='1.28',
microversion='1.67',
retriable_status_codes=_common.RETRIABLE_STATUS_CODES,
)
@ -620,7 +665,7 @@ class TestNodeVif(base.TestCase):
self.session.get.assert_called_once_with(
'nodes/%s/vifs' % self.node.id,
headers=mock.ANY,
microversion='1.28',
microversion='1.67',
)
def test_incompatible_microversion(self):
@ -641,6 +686,23 @@ class TestNodeVif(base.TestCase):
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(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.