Validate portgroup physical network consistency
When creating or updating a port that is a member of a portgroup, we need to validate the consistency of the physical networks of the ports in the portgroup. There are 3 cases we are interested in: - Creating a port which is a member of a portgroup. - Updating the physical network of a port which is a member of a portgroup. - Updating the portgroup of a port. All ports in a portgroup should have the same value (which may be None) for their physical_network field. During creation or update of a port in a portgroup we apply the following validation criteria: - If the portgroup has existing ports with different physical networks, we raise PortgroupPhysnetInconsistent. This shouldn't ever happen. - If the port has a physical network that is inconsistent with other ports in the portgroup, we raise exception.Conflict. If a port's physical network is None, this indicates that ironic's VIF attachment mapping algorithm should operate in a legacy (physical network unaware) mode for this port or portgroup. This allows existing ironic nodes to continue to function after an upgrade to a release including physical network support. Change-Id: I6a6d248155f98109dd36dba5837494f6974846e6 Partial-Bug: #1666009
This commit is contained in:
parent
6084277a51
commit
039225610d
|
@ -750,3 +750,9 @@ class RedfishError(IronicException):
|
|||
|
||||
class RedfishConnectionError(RedfishError):
|
||||
_msg_fmt = _("Redfish connection failed for node %(node)s: %(error)s")
|
||||
|
||||
|
||||
class PortgroupPhysnetInconsistent(IronicException):
|
||||
_msg_fmt = _("Port group %(portgroup)s has member ports with inconsistent "
|
||||
"physical networks (%(physical_networks)s). All ports in a "
|
||||
"port group must have the same physical network.")
|
||||
|
|
|
@ -42,3 +42,25 @@ def get_node_vif_ids(task):
|
|||
port_vifs[port.uuid] = vif
|
||||
vifs['ports'] = port_vifs
|
||||
return vifs
|
||||
|
||||
|
||||
def get_portgroup_by_id(task, portgroup_id):
|
||||
"""Lookup a portgroup by ID on a task object.
|
||||
|
||||
:param task: a TaskManager instance
|
||||
:param portgroup_id: ID of the portgroup.
|
||||
:returns: A Portgroup object or None.
|
||||
"""
|
||||
for portgroup in task.portgroups:
|
||||
if portgroup.id == portgroup_id:
|
||||
return portgroup
|
||||
|
||||
|
||||
def get_ports_by_portgroup_id(task, portgroup_id):
|
||||
"""Lookup ports by their portgroup ID on a task object.
|
||||
|
||||
:param task: a TaskManager instance
|
||||
:param portgroup_id: ID of the portgroup.
|
||||
:returns: A list of Port objects.
|
||||
"""
|
||||
return [port for port in task.ports if port.portgroup_id == portgroup_id]
|
||||
|
|
|
@ -1838,7 +1838,9 @@ class ConductorManager(base_manager.BaseConductorManager):
|
|||
|
||||
@METRICS.timer('ConductorManager.create_port')
|
||||
@messaging.expected_exceptions(exception.NodeLocked,
|
||||
exception.MACAlreadyExists)
|
||||
exception.Conflict,
|
||||
exception.MACAlreadyExists,
|
||||
exception.PortgroupPhysnetInconsistent)
|
||||
def create_port(self, context, port_obj):
|
||||
"""Create a port.
|
||||
|
||||
|
@ -1847,12 +1849,17 @@ class ConductorManager(base_manager.BaseConductorManager):
|
|||
:raises: NodeLocked if node is locked by another conductor
|
||||
:raises: MACAlreadyExists if the port has a MAC which is registered on
|
||||
another port already.
|
||||
:raises: Conflict if the port is a member of a portgroup which is on a
|
||||
different physical network.
|
||||
:raises: PortgroupPhysnetInconsistent if the port's portgroup has
|
||||
ports which are not all assigned the same physical network.
|
||||
"""
|
||||
port_uuid = port_obj.uuid
|
||||
LOG.debug("RPC create_port called for port %s.", port_uuid)
|
||||
|
||||
with task_manager.acquire(context, port_obj.node_id,
|
||||
purpose='port create'):
|
||||
purpose='port create') as task:
|
||||
utils.validate_port_physnet(task, port_obj)
|
||||
port_obj.create()
|
||||
return port_obj
|
||||
|
||||
|
@ -1864,7 +1871,8 @@ class ConductorManager(base_manager.BaseConductorManager):
|
|||
exception.FailedToUpdateDHCPOptOnPort,
|
||||
exception.Conflict,
|
||||
exception.InvalidParameterValue,
|
||||
exception.NetworkError)
|
||||
exception.NetworkError,
|
||||
exception.PortgroupPhysnetInconsistent)
|
||||
def update_port(self, context, port_obj):
|
||||
"""Update a port.
|
||||
|
||||
|
@ -1881,6 +1889,10 @@ class ConductorManager(base_manager.BaseConductorManager):
|
|||
:raises: Conflict if trying to set extra/vif_port_id or
|
||||
pxe_enabled=True on port which is a member of portgroup with
|
||||
standalone_ports_supported=False.
|
||||
:raises: Conflict if the port is a member of a portgroup which is on a
|
||||
different physical network.
|
||||
:raises: PortgroupPhysnetInconsistent if the port's portgroup has
|
||||
ports which are not all assigned the same physical network.
|
||||
"""
|
||||
port_uuid = port_obj.uuid
|
||||
LOG.debug("RPC update_port called for port %s.", port_uuid)
|
||||
|
@ -1900,13 +1912,14 @@ class ConductorManager(base_manager.BaseConductorManager):
|
|||
'port': port_uuid})
|
||||
|
||||
# If port update is modifying the portgroup membership of the port
|
||||
# or modifying the local_link_connection or pxe_enabled flags then
|
||||
# node should be in MANAGEABLE/INSPECTING/ENROLL provisioning state
|
||||
# or in maintenance mode.
|
||||
# Otherwise InvalidState exception is raised.
|
||||
# or modifying the local_link_connection, pxe_enabled or physical
|
||||
# network flags then node should be in MANAGEABLE/INSPECTING/ENROLL
|
||||
# provisioning state or in maintenance mode. Otherwise
|
||||
# InvalidState exception is raised.
|
||||
connectivity_attr = {'portgroup_id',
|
||||
'pxe_enabled',
|
||||
'local_link_connection'}
|
||||
'local_link_connection',
|
||||
'physical_network'}
|
||||
allowed_update_states = [states.ENROLL,
|
||||
states.INSPECTING,
|
||||
states.MANAGEABLE]
|
||||
|
@ -1924,6 +1937,7 @@ class ConductorManager(base_manager.BaseConductorManager):
|
|||
'connect': ', '.join(connectivity_attr),
|
||||
'allowed': ', '.join(allowed_update_states)})
|
||||
|
||||
utils.validate_port_physnet(task, port_obj)
|
||||
task.driver.network.validate(task)
|
||||
# Handle mac_address update and VIF attach/detach stuff.
|
||||
task.driver.network.port_changed(task, port_obj)
|
||||
|
|
|
@ -19,6 +19,7 @@ from oslo_utils import reflection
|
|||
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import network
|
||||
from ironic.common import states
|
||||
from ironic.conductor import notification_utils as notify_utils
|
||||
from ironic.conductor import task_manager
|
||||
|
@ -505,3 +506,73 @@ def _validate_user_clean_steps(task, user_steps):
|
|||
if errors:
|
||||
raise exception.InvalidParameterValue('; '.join(errors))
|
||||
return result
|
||||
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def validate_port_physnet(task, port_obj):
|
||||
"""Validate the consistency of physical networks of ports in a portgroup.
|
||||
|
||||
Validate the consistency of a port's physical network with other ports in
|
||||
the same portgroup. All ports in a portgroup should have the same value
|
||||
(which may be None) for their physical_network field.
|
||||
|
||||
During creation or update of a port in a portgroup we apply the
|
||||
following validation criteria:
|
||||
|
||||
- If the portgroup has existing ports with different physical networks, we
|
||||
raise PortgroupPhysnetInconsistent. This shouldn't ever happen.
|
||||
- If the port has a physical network that is inconsistent with other
|
||||
ports in the portgroup, we raise exception.Conflict.
|
||||
|
||||
If a port's physical network is None, this indicates that ironic's VIF
|
||||
attachment mapping algorithm should operate in a legacy (physical
|
||||
network unaware) mode for this port or portgroup. This allows existing
|
||||
ironic nodes to continue to function after an upgrade to a release
|
||||
including physical network support.
|
||||
|
||||
:param task: a TaskManager instance
|
||||
:param port_obj: a port object to be validated.
|
||||
:raises: Conflict if the port is a member of a portgroup which is on a
|
||||
different physical network.
|
||||
:raises: PortgroupPhysnetInconsistent if the port's portgroup has
|
||||
ports which are not all assigned the same physical network.
|
||||
"""
|
||||
if 'portgroup_id' not in port_obj or not port_obj.portgroup_id:
|
||||
return
|
||||
|
||||
delta = port_obj.obj_what_changed()
|
||||
# We can skip this step if the port's portgroup membership or physical
|
||||
# network assignment is not being changed (during creation these will
|
||||
# appear changed).
|
||||
if not (delta & {'portgroup_id', 'physical_network'}):
|
||||
return
|
||||
|
||||
# Determine the current physical network of the portgroup.
|
||||
pg_ports = network.get_ports_by_portgroup_id(task, port_obj.portgroup_id)
|
||||
port_obj_id = port_obj.id if 'id' in port_obj else None
|
||||
pg_physnets = {port.physical_network
|
||||
for port in pg_ports if port.id != port_obj_id}
|
||||
|
||||
if not pg_physnets:
|
||||
return
|
||||
|
||||
# Sanity check that all existing ports in the group have the same
|
||||
# physical network (should never happen).
|
||||
if len(pg_physnets) > 1:
|
||||
portgroup = network.get_portgroup_by_id(task, port_obj.portgroup_id)
|
||||
raise exception.PortgroupPhysnetInconsistent(
|
||||
portgroup=portgroup.uuid, physical_networks=", ".join(pg_physnets))
|
||||
|
||||
# Check that the port has the same physical network as any existing
|
||||
# member ports.
|
||||
pg_physnet = pg_physnets.pop()
|
||||
port_physnet = (port_obj.physical_network
|
||||
if 'physical_network' in port_obj else None)
|
||||
if port_physnet != pg_physnet:
|
||||
portgroup = network.get_portgroup_by_id(task, port_obj.portgroup_id)
|
||||
msg = _("Port with physical network %(physnet)s cannot become a "
|
||||
"member of port group %(portgroup)s which has ports in "
|
||||
"physical network %(pg_physnet)s.")
|
||||
raise exception.Conflict(
|
||||
msg % {'portgroup': portgroup.uuid, 'physnet': port_physnet,
|
||||
'pg_physnet': pg_physnet})
|
||||
|
|
|
@ -151,3 +151,51 @@ class TestNetwork(db_base.DbTestCase):
|
|||
|
||||
def test_get_node_vif_ids_during_provisioning(self):
|
||||
self._test_get_node_vif_ids_multitenancy('provisioning_vif_port_id')
|
||||
|
||||
|
||||
class GetPortgroupByIdTestCase(db_base.DbTestCase):
|
||||
|
||||
def test_portgroup_by_id(self):
|
||||
node = object_utils.create_test_node(self.context, driver='fake')
|
||||
portgroup = object_utils.create_test_portgroup(self.context,
|
||||
node_id=node.id)
|
||||
object_utils.create_test_portgroup(self.context,
|
||||
node_id=node.id,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
address='00:11:22:33:44:55',
|
||||
name='pg2')
|
||||
with task_manager.acquire(self.context, node.uuid) as task:
|
||||
res = network.get_portgroup_by_id(task, portgroup.id)
|
||||
self.assertEqual(portgroup.id, res.id)
|
||||
|
||||
def test_portgroup_by_id_no_such_portgroup(self):
|
||||
node = object_utils.create_test_node(self.context, driver='fake')
|
||||
object_utils.create_test_portgroup(self.context, node_id=node.id)
|
||||
with task_manager.acquire(self.context, node.uuid) as task:
|
||||
portgroup_id = 'invalid-portgroup-id'
|
||||
res = network.get_portgroup_by_id(task, portgroup_id)
|
||||
self.assertIsNone(res)
|
||||
|
||||
|
||||
class GetPortsByPortgroupIdTestCase(db_base.DbTestCase):
|
||||
|
||||
def test_ports_by_portgroup_id(self):
|
||||
node = object_utils.create_test_node(self.context, driver='fake')
|
||||
portgroup = object_utils.create_test_portgroup(self.context,
|
||||
node_id=node.id)
|
||||
port = object_utils.create_test_port(self.context, node_id=node.id,
|
||||
portgroup_id=portgroup.id)
|
||||
object_utils.create_test_port(self.context, node_id=node.id,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
address='00:11:22:33:44:55')
|
||||
with task_manager.acquire(self.context, node.uuid) as task:
|
||||
res = network.get_ports_by_portgroup_id(task, portgroup.id)
|
||||
self.assertEqual([port.id], [p.id for p in res])
|
||||
|
||||
def test_ports_by_portgroup_id_empty(self):
|
||||
node = object_utils.create_test_node(self.context, driver='fake')
|
||||
portgroup = object_utils.create_test_portgroup(self.context,
|
||||
node_id=node.id)
|
||||
with task_manager.acquire(self.context, node.uuid) as task:
|
||||
res = network.get_ports_by_portgroup_id(task, portgroup.id)
|
||||
self.assertEqual([], res)
|
||||
|
|
|
@ -3160,7 +3160,8 @@ class DestroyNodeTestCase(mgr_utils.ServiceSetUpMixin,
|
|||
class CreatePortTestCase(mgr_utils.ServiceSetUpMixin,
|
||||
tests_db_base.DbTestCase):
|
||||
|
||||
def test_create_port(self):
|
||||
@mock.patch.object(conductor_utils, 'validate_port_physnet')
|
||||
def test_create_port(self, mock_validate):
|
||||
node = obj_utils.create_test_node(self.context, driver='fake')
|
||||
port = obj_utils.get_test_port(self.context, node_id=node.id,
|
||||
extra={'foo': 'bar'})
|
||||
|
@ -3168,6 +3169,7 @@ class CreatePortTestCase(mgr_utils.ServiceSetUpMixin,
|
|||
self.assertEqual({'foo': 'bar'}, res.extra)
|
||||
res = objects.Port.get_by_uuid(self.context, port['uuid'])
|
||||
self.assertEqual({'foo': 'bar'}, res.extra)
|
||||
mock_validate.assert_called_once_with(mock.ANY, port)
|
||||
|
||||
def test_create_port_node_locked(self):
|
||||
node = obj_utils.create_test_node(self.context, driver='fake',
|
||||
|
@ -3181,7 +3183,8 @@ class CreatePortTestCase(mgr_utils.ServiceSetUpMixin,
|
|||
self.assertRaises(exception.PortNotFound, port.get_by_uuid,
|
||||
self.context, port.uuid)
|
||||
|
||||
def test_create_port_mac_exists(self):
|
||||
@mock.patch.object(conductor_utils, 'validate_port_physnet')
|
||||
def test_create_port_mac_exists(self, mock_validate):
|
||||
node = obj_utils.create_test_node(self.context, driver='fake')
|
||||
port = obj_utils.create_test_port(self.context, node_id=node.id)
|
||||
port = obj_utils.get_test_port(self.context, node_id=node.id,
|
||||
|
@ -3194,14 +3197,45 @@ class CreatePortTestCase(mgr_utils.ServiceSetUpMixin,
|
|||
self.assertRaises(exception.PortNotFound, port.get_by_uuid,
|
||||
self.context, port.uuid)
|
||||
|
||||
@mock.patch.object(conductor_utils, 'validate_port_physnet')
|
||||
def test_create_port_physnet_validation_failure_conflict(self,
|
||||
mock_validate):
|
||||
mock_validate.side_effect = exception.Conflict
|
||||
node = obj_utils.create_test_node(self.context, driver='fake')
|
||||
port = obj_utils.get_test_port(self.context, node_id=node.id)
|
||||
exc = self.assertRaises(messaging.rpc.ExpectedException,
|
||||
self.service.create_port,
|
||||
self.context, port)
|
||||
# Compare true exception hidden by @messaging.expected_exceptions
|
||||
self.assertEqual(exception.Conflict, exc.exc_info[0])
|
||||
self.assertRaises(exception.PortNotFound, port.get_by_uuid,
|
||||
self.context, port.uuid)
|
||||
|
||||
@mock.patch.object(conductor_utils, 'validate_port_physnet')
|
||||
def test_create_port_physnet_validation_failure_inconsistent(
|
||||
self, mock_validate):
|
||||
mock_validate.side_effect = exception.PortgroupPhysnetInconsistent(
|
||||
portgroup='pg1', physical_networks='physnet1, physnet2')
|
||||
node = obj_utils.create_test_node(self.context, driver='fake')
|
||||
port = obj_utils.get_test_port(self.context, node_id=node.id)
|
||||
exc = self.assertRaises(messaging.rpc.ExpectedException,
|
||||
self.service.create_port,
|
||||
self.context, port)
|
||||
# Compare true exception hidden by @messaging.expected_exceptions
|
||||
self.assertEqual(exception.PortgroupPhysnetInconsistent,
|
||||
exc.exc_info[0])
|
||||
self.assertRaises(exception.PortNotFound, port.get_by_uuid,
|
||||
self.context, port.uuid)
|
||||
|
||||
|
||||
@mgr_utils.mock_record_keepalive
|
||||
class UpdatePortTestCase(mgr_utils.ServiceSetUpMixin,
|
||||
tests_db_base.DbTestCase):
|
||||
|
||||
@mock.patch.object(conductor_utils, 'validate_port_physnet')
|
||||
@mock.patch.object(n_flat.FlatNetwork, 'port_changed', autospec=True)
|
||||
@mock.patch.object(n_flat.FlatNetwork, 'validate', autospec=True)
|
||||
def test_update_port(self, mock_val, mock_pc):
|
||||
def test_update_port(self, mock_val, mock_pc, mock_vpp):
|
||||
node = obj_utils.create_test_node(self.context, driver='fake')
|
||||
|
||||
port = obj_utils.create_test_port(self.context,
|
||||
|
@ -3213,6 +3247,7 @@ class UpdatePortTestCase(mgr_utils.ServiceSetUpMixin,
|
|||
self.assertEqual(new_extra, res.extra)
|
||||
mock_val.assert_called_once_with(mock.ANY, mock.ANY)
|
||||
mock_pc.assert_called_once_with(mock.ANY, mock.ANY, port)
|
||||
mock_vpp.assert_called_once_with(mock.ANY, port)
|
||||
|
||||
def test_update_port_node_locked(self):
|
||||
node = obj_utils.create_test_node(self.context, driver='fake',
|
||||
|
@ -3375,6 +3410,69 @@ class UpdatePortTestCase(mgr_utils.ServiceSetUpMixin,
|
|||
mock_val.assert_called_once_with(mock.ANY, mock.ANY)
|
||||
mock_pc.assert_called_once_with(mock.ANY, mock.ANY, port)
|
||||
|
||||
@mock.patch.object(n_flat.FlatNetwork, 'port_changed', autospec=True)
|
||||
@mock.patch.object(n_flat.FlatNetwork, 'validate', autospec=True)
|
||||
def test_update_port_physnet_maintenance(self, mock_val, mock_pc):
|
||||
node = obj_utils.create_test_node(
|
||||
self.context, driver='fake', maintenance=True,
|
||||
instance_uuid=uuidutils.generate_uuid(), provision_state='active')
|
||||
port = obj_utils.create_test_port(self.context,
|
||||
node_id=node.id,
|
||||
extra={'vif_port_id': 'fake-id'})
|
||||
new_physnet = 'physnet1'
|
||||
port.physical_network = new_physnet
|
||||
res = self.service.update_port(self.context, port)
|
||||
self.assertEqual(new_physnet, res.physical_network)
|
||||
mock_val.assert_called_once_with(mock.ANY, mock.ANY)
|
||||
mock_pc.assert_called_once_with(mock.ANY, mock.ANY, port)
|
||||
|
||||
def test_update_port_physnet_node_deleting_state(self):
|
||||
node = obj_utils.create_test_node(self.context, driver='fake',
|
||||
provision_state=states.DELETING)
|
||||
port = obj_utils.create_test_port(self.context,
|
||||
node_id=node.id,
|
||||
extra={'foo': 'bar'})
|
||||
old_physnet = port.physical_network
|
||||
port.physical_network = 'physnet1'
|
||||
exc = self.assertRaises(messaging.rpc.ExpectedException,
|
||||
self.service.update_port,
|
||||
self.context, port)
|
||||
self.assertEqual(exception.InvalidState, exc.exc_info[0])
|
||||
port.refresh()
|
||||
self.assertEqual(old_physnet, port.physical_network)
|
||||
|
||||
@mock.patch.object(conductor_utils, 'validate_port_physnet')
|
||||
def test_update_port_physnet_validation_failure_conflict(self,
|
||||
mock_validate):
|
||||
mock_validate.side_effect = exception.Conflict
|
||||
node = obj_utils.create_test_node(self.context, driver='fake')
|
||||
port = obj_utils.create_test_port(self.context, node_id=node.id,
|
||||
uuid=uuidutils.generate_uuid())
|
||||
port.extra = {'foo': 'bar'}
|
||||
exc = self.assertRaises(messaging.rpc.ExpectedException,
|
||||
self.service.update_port,
|
||||
self.context, port)
|
||||
# Compare true exception hidden by @messaging.expected_exceptions
|
||||
self.assertEqual(exception.Conflict, exc.exc_info[0])
|
||||
mock_validate.assert_called_once_with(mock.ANY, port)
|
||||
|
||||
@mock.patch.object(conductor_utils, 'validate_port_physnet')
|
||||
def test_update_port_physnet_validation_failure_inconsistent(
|
||||
self, mock_validate):
|
||||
mock_validate.side_effect = exception.PortgroupPhysnetInconsistent(
|
||||
portgroup='pg1', physical_networks='physnet1, physnet2')
|
||||
node = obj_utils.create_test_node(self.context, driver='fake')
|
||||
port = obj_utils.create_test_port(self.context, node_id=node.id,
|
||||
uuid=uuidutils.generate_uuid())
|
||||
port.extra = {'foo': 'bar'}
|
||||
exc = self.assertRaises(messaging.rpc.ExpectedException,
|
||||
self.service.update_port,
|
||||
self.context, port)
|
||||
# Compare true exception hidden by @messaging.expected_exceptions
|
||||
self.assertEqual(exception.PortgroupPhysnetInconsistent,
|
||||
exc.exc_info[0])
|
||||
mock_validate.assert_called_once_with(mock.ANY, port)
|
||||
|
||||
def test__filter_out_unsupported_types_all(self):
|
||||
self._start_service()
|
||||
CONF.set_override('send_sensor_data_types', ['All'], group='conductor')
|
||||
|
|
|
@ -16,6 +16,7 @@ from oslo_utils import uuidutils
|
|||
|
||||
from ironic.common import driver_factory
|
||||
from ironic.common import exception
|
||||
from ironic.common import network
|
||||
from ironic.common import states
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.conductor import utils as conductor_utils
|
||||
|
@ -990,3 +991,322 @@ class ErrorHandlersTestCase(tests_base.TestCase):
|
|||
conductor_utils.power_state_error_handler(exc, self.node, 'foo')
|
||||
self.assertFalse(self.node.save.called)
|
||||
self.assertFalse(log_mock.warning.called)
|
||||
|
||||
|
||||
class ValidatePortPhysnetTestCase(base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ValidatePortPhysnetTestCase, self).setUp()
|
||||
self.node = obj_utils.create_test_node(self.context, driver='fake')
|
||||
|
||||
@mock.patch.object(objects.Port, 'obj_what_changed')
|
||||
def test_validate_port_physnet_no_portgroup_create(self, mock_owc):
|
||||
port = obj_utils.get_test_port(self.context, node_id=self.node.id)
|
||||
# NOTE(mgoddard): The port object passed to the conductor will not have
|
||||
# a portgroup_id attribute in this case.
|
||||
del port.portgroup_id
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
conductor_utils.validate_port_physnet(task, port)
|
||||
# Verify the early return in the non-portgroup case.
|
||||
self.assertFalse(mock_owc.called)
|
||||
|
||||
@mock.patch.object(network, 'get_ports_by_portgroup_id')
|
||||
def test_validate_port_physnet_no_portgroup_update(self, mock_gpbpi):
|
||||
port = obj_utils.create_test_port(self.context, node_id=self.node.id)
|
||||
port.extra = {'foo': 'bar'}
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
conductor_utils.validate_port_physnet(task, port)
|
||||
# Verify the early return in the no portgroup update case.
|
||||
self.assertFalse(mock_gpbpi.called)
|
||||
|
||||
def test_validate_port_physnet_inconsistent_physnets(self):
|
||||
# NOTE(mgoddard): This *shouldn't* happen, but let's make sure we can
|
||||
# handle it.
|
||||
portgroup = obj_utils.create_test_portgroup(self.context,
|
||||
node_id=self.node.id)
|
||||
obj_utils.create_test_port(self.context, node_id=self.node.id,
|
||||
portgroup_id=portgroup.id,
|
||||
address='00:11:22:33:44:55',
|
||||
physical_network='physnet1',
|
||||
uuid=uuidutils.generate_uuid())
|
||||
obj_utils.create_test_port(self.context, node_id=self.node.id,
|
||||
portgroup_id=portgroup.id,
|
||||
address='00:11:22:33:44:56',
|
||||
physical_network='physnet2',
|
||||
uuid=uuidutils.generate_uuid())
|
||||
port = obj_utils.get_test_port(self.context, node_id=self.node.id,
|
||||
portgroup_id=portgroup.id,
|
||||
address='00:11:22:33:44:57',
|
||||
physical_network='physnet2',
|
||||
uuid=uuidutils.generate_uuid())
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertRaises(exception.PortgroupPhysnetInconsistent,
|
||||
conductor_utils.validate_port_physnet,
|
||||
task, port)
|
||||
|
||||
def test_validate_port_physnet_inconsistent_physnets_fix(self):
|
||||
# NOTE(mgoddard): This *shouldn't* happen, but let's make sure that if
|
||||
# we do get into this state that it is possible to resolve by setting
|
||||
# the physical_network correctly.
|
||||
portgroup = obj_utils.create_test_portgroup(self.context,
|
||||
node_id=self.node.id)
|
||||
obj_utils.create_test_port(self.context, node_id=self.node.id,
|
||||
portgroup_id=portgroup.id,
|
||||
address='00:11:22:33:44:55',
|
||||
physical_network='physnet1',
|
||||
uuid=uuidutils.generate_uuid())
|
||||
port = obj_utils.create_test_port(self.context, node_id=self.node.id,
|
||||
portgroup_id=portgroup.id,
|
||||
address='00:11:22:33:44:56',
|
||||
physical_network='physnet2',
|
||||
uuid=uuidutils.generate_uuid())
|
||||
port.physical_network = 'physnet1'
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
conductor_utils.validate_port_physnet(task, port)
|
||||
|
||||
def _test_validate_port_physnet(self,
|
||||
num_current_ports,
|
||||
current_physnet,
|
||||
new_physnet,
|
||||
operation,
|
||||
valid=True):
|
||||
"""Helper method for testing validate_port_physnet.
|
||||
|
||||
:param num_current_ports: Number of existing ports in the portgroup.
|
||||
:param current_physnet: Physical network of existing ports in the
|
||||
portgroup.
|
||||
:param new_physnet: Physical network to set on the port that is being
|
||||
created or updated.
|
||||
portgroup.
|
||||
:param operation: The operation to perform. One of 'create', 'update',
|
||||
or 'update_add'. 'create' creates a new port and adds
|
||||
it to the portgroup. 'update' updates one of the
|
||||
existing ports. 'update_add' updates a port and adds
|
||||
it to the portgroup.
|
||||
:param valid: Whether the operation is expected to succeed.
|
||||
"""
|
||||
# Prepare existing resources - a node, and a portgroup with optional
|
||||
# existing ports.
|
||||
port = None
|
||||
portgroup = obj_utils.create_test_portgroup(self.context,
|
||||
node_id=self.node.id)
|
||||
macs = ("00:11:22:33:44:%02x" % index
|
||||
for index in range(num_current_ports + 1))
|
||||
for _ in range(num_current_ports):
|
||||
# NOTE: When operation == 'update' we update the last port in the
|
||||
# portgroup.
|
||||
port = obj_utils.create_test_port(
|
||||
self.context, node_id=self.node.id, portgroup_id=portgroup.id,
|
||||
address=next(macs), physical_network=current_physnet,
|
||||
uuid=uuidutils.generate_uuid())
|
||||
|
||||
# Prepare the port on which we are performing the operation.
|
||||
if operation == 'create':
|
||||
# NOTE(mgoddard): We use utils here rather than obj_utils as it
|
||||
# allows us to create a Port without a physical_network field, more
|
||||
# closely matching what happens during creation of a port when a
|
||||
# physical_network is not specified.
|
||||
port = utils.get_test_port(
|
||||
node_id=self.node.id, portgroup_id=portgroup.id,
|
||||
address=next(macs), uuid=uuidutils.generate_uuid(),
|
||||
physical_network=new_physnet)
|
||||
if new_physnet is None:
|
||||
del port["physical_network"]
|
||||
port = objects.Port(self.context, **port)
|
||||
elif operation == 'update_add':
|
||||
port = obj_utils.create_test_port(
|
||||
self.context, node_id=self.node.id, portgroup_id=None,
|
||||
address=next(macs), physical_network=current_physnet,
|
||||
uuid=uuidutils.generate_uuid())
|
||||
port.portgroup_id = portgroup.id
|
||||
|
||||
if operation != 'create' and new_physnet != current_physnet:
|
||||
port.physical_network = new_physnet
|
||||
|
||||
# Perform the validation.
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
if valid:
|
||||
conductor_utils.validate_port_physnet(task, port)
|
||||
else:
|
||||
self.assertRaises(exception.Conflict,
|
||||
conductor_utils.validate_port_physnet,
|
||||
task, port)
|
||||
|
||||
def _test_validate_port_physnet_create(self, **kwargs):
|
||||
self._test_validate_port_physnet(operation='create', **kwargs)
|
||||
|
||||
def _test_validate_port_physnet_update(self, **kwargs):
|
||||
self._test_validate_port_physnet(operation='update', **kwargs)
|
||||
|
||||
def _test_validate_port_physnet_update_add(self, **kwargs):
|
||||
self._test_validate_port_physnet(operation='update_add', **kwargs)
|
||||
|
||||
# Empty portgroup
|
||||
|
||||
def test_validate_port_physnet_empty_portgroup_create_1(self):
|
||||
self._test_validate_port_physnet_create(
|
||||
num_current_ports=0,
|
||||
current_physnet=None,
|
||||
new_physnet=None)
|
||||
|
||||
def test_validate_port_physnet_empty_portgroup_create_2(self):
|
||||
self._test_validate_port_physnet_create(
|
||||
num_current_ports=0,
|
||||
current_physnet=None,
|
||||
new_physnet='physnet1')
|
||||
|
||||
def test_validate_port_physnet_empty_portgroup_update_1(self):
|
||||
self._test_validate_port_physnet_update_add(
|
||||
num_current_ports=0,
|
||||
current_physnet=None,
|
||||
new_physnet=None)
|
||||
|
||||
def test_validate_port_physnet_empty_portgroup_update_2(self):
|
||||
self._test_validate_port_physnet_update_add(
|
||||
num_current_ports=0,
|
||||
current_physnet=None,
|
||||
new_physnet='physnet1')
|
||||
|
||||
# 1-port portgroup, no physnet.
|
||||
|
||||
def test_validate_port_physnet_1_port_portgroup_no_physnet_create_1(self):
|
||||
self._test_validate_port_physnet_create(
|
||||
num_current_ports=1,
|
||||
current_physnet=None,
|
||||
new_physnet=None)
|
||||
|
||||
def test_validate_port_physnet_1_port_portgroup_no_physnet_create_2(self):
|
||||
self._test_validate_port_physnet_create(
|
||||
num_current_ports=1,
|
||||
current_physnet=None,
|
||||
new_physnet='physnet1',
|
||||
valid=False)
|
||||
|
||||
def test_validate_port_physnet_1_port_portgroup_no_physnet_update_1(self):
|
||||
self._test_validate_port_physnet_update(
|
||||
num_current_ports=1,
|
||||
current_physnet=None,
|
||||
new_physnet=None)
|
||||
|
||||
def test_validate_port_physnet_1_port_portgroup_no_physnet_update_2(self):
|
||||
self._test_validate_port_physnet_update(
|
||||
num_current_ports=1,
|
||||
current_physnet=None,
|
||||
new_physnet='physnet1')
|
||||
|
||||
def test_validate_port_physnet_1_port_portgroup_no_physnet_update_add_1(
|
||||
self):
|
||||
self._test_validate_port_physnet_update_add(
|
||||
num_current_ports=1,
|
||||
current_physnet=None,
|
||||
new_physnet=None)
|
||||
|
||||
def test_validate_port_physnet_1_port_portgroup_no_physnet_update_add_2(
|
||||
self):
|
||||
self._test_validate_port_physnet_update_add(
|
||||
num_current_ports=1,
|
||||
current_physnet=None,
|
||||
new_physnet='physnet1',
|
||||
valid=False)
|
||||
|
||||
# 1-port portgroup, with physnet 'physnet1'.
|
||||
|
||||
def test_validate_port_physnet_1_port_portgroup_w_physnet_create_1(self):
|
||||
self._test_validate_port_physnet_create(
|
||||
num_current_ports=1,
|
||||
current_physnet='physnet1',
|
||||
new_physnet='physnet1')
|
||||
|
||||
def test_validate_port_physnet_1_port_portgroup_w_physnet_create_2(self):
|
||||
self._test_validate_port_physnet_create(
|
||||
num_current_ports=1,
|
||||
current_physnet='physnet1',
|
||||
new_physnet='physnet2',
|
||||
valid=False)
|
||||
|
||||
def test_validate_port_physnet_1_port_portgroup_w_physnet_create_3(self):
|
||||
self._test_validate_port_physnet_create(
|
||||
num_current_ports=1,
|
||||
current_physnet='physnet1',
|
||||
new_physnet=None,
|
||||
valid=False)
|
||||
|
||||
def test_validate_port_physnet_1_port_portgroup_w_physnet_update_1(self):
|
||||
self._test_validate_port_physnet_update(
|
||||
num_current_ports=1,
|
||||
current_physnet='physnet1',
|
||||
new_physnet='physnet1')
|
||||
|
||||
def test_validate_port_physnet_1_port_portgroup_w_physnet_update_2(self):
|
||||
self._test_validate_port_physnet_update(
|
||||
num_current_ports=1,
|
||||
current_physnet='physnet1',
|
||||
new_physnet='physnet2')
|
||||
|
||||
def test_validate_port_physnet_1_port_portgroup_w_physnet_update_3(self):
|
||||
self._test_validate_port_physnet_update(
|
||||
num_current_ports=1,
|
||||
current_physnet='physnet1',
|
||||
new_physnet=None)
|
||||
|
||||
def test_validate_port_physnet_1_port_portgroup_w_physnet_update_add_1(
|
||||
self):
|
||||
self._test_validate_port_physnet_update_add(
|
||||
num_current_ports=1,
|
||||
current_physnet='physnet1',
|
||||
new_physnet='physnet1')
|
||||
|
||||
def test_validate_port_physnet_1_port_portgroup_w_physnet_update_add_2(
|
||||
self):
|
||||
self._test_validate_port_physnet_update_add(
|
||||
num_current_ports=1,
|
||||
current_physnet='physnet1',
|
||||
new_physnet='physnet2',
|
||||
valid=False)
|
||||
|
||||
def test_validate_port_physnet_1_port_portgroup_w_physnet_update_add_3(
|
||||
self):
|
||||
self._test_validate_port_physnet_update_add(
|
||||
num_current_ports=1,
|
||||
current_physnet='physnet1',
|
||||
new_physnet=None,
|
||||
valid=False)
|
||||
|
||||
# 2-port portgroup, no physnet
|
||||
|
||||
def test_validate_port_physnet_2_port_portgroup_no_physnet_update_1(self):
|
||||
self._test_validate_port_physnet_update(
|
||||
num_current_ports=2,
|
||||
current_physnet=None,
|
||||
new_physnet=None)
|
||||
|
||||
def test_validate_port_physnet_2_port_portgroup_no_physnet_update_2(self):
|
||||
self._test_validate_port_physnet_update(
|
||||
num_current_ports=2,
|
||||
current_physnet=None,
|
||||
new_physnet='physnet1',
|
||||
valid=False)
|
||||
|
||||
# 2-port portgroup, with physnet 'physnet1'
|
||||
|
||||
def test_validate_port_physnet_2_port_portgroup_w_physnet_update_1(self):
|
||||
self._test_validate_port_physnet_update(
|
||||
num_current_ports=2,
|
||||
current_physnet='physnet1',
|
||||
new_physnet='physnet1')
|
||||
|
||||
def test_validate_port_physnet_2_port_portgroup_w_physnet_update_2(self):
|
||||
self._test_validate_port_physnet_update(
|
||||
num_current_ports=2,
|
||||
current_physnet='physnet1',
|
||||
new_physnet='physnet2',
|
||||
valid=False)
|
||||
|
||||
def test_validate_port_physnet_2_port_portgroup_w_physnet_update_3(self):
|
||||
self._test_validate_port_physnet_update(
|
||||
num_current_ports=2,
|
||||
current_physnet='physnet1',
|
||||
new_physnet=None,
|
||||
valid=False)
|
||||
|
|
Loading…
Reference in New Issue