Change provider network segmentation ID

In the ML2 plugin, allow to update the segmentation ID of a single
provider network.

A new method in the "SimpleAgentMechanismDriverBase" is added:
"provider_network_attribute_updates_supported". This method returns,
if implemented in the specific agent, which network attributes can
be updated on a live network with ports bound to this network back-end.
By default, an empty list is returned.

Partial-Bug: #1806052

Change-Id: I2595335d6fbc51562b070f14eaeaadf49cf7c418
This commit is contained in:
Rodolfo Alonso Hernandez 2019-01-25 09:04:29 +00:00
parent c70a935ce7
commit 6f26f4026b
10 changed files with 261 additions and 18 deletions

View File

@ -18,6 +18,7 @@ import functools
import netaddr
from neutron_lib.api.definitions import ip_allocation as ipalloc_apidef
from neutron_lib.api.definitions import port as port_def
from neutron_lib.api.definitions import portbindings as portbindings_def
from neutron_lib.api.definitions import subnetpool as subnetpool_def
from neutron_lib.api import validators
from neutron_lib.callbacks import events
@ -1476,11 +1477,14 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
filters = filters or {}
fixed_ips = filters.pop('fixed_ips', {})
vif_type = filters.pop(portbindings_def.VIF_TYPE, None)
query = model_query.get_collection_query(context, Port,
filters=filters,
*args, **kwargs)
ip_addresses = fixed_ips.get('ip_address')
subnet_ids = fixed_ips.get('subnet_id')
if vif_type is not None:
query = query.filter(Port.port_bindings.any(vif_type=vif_type))
if ip_addresses:
query = query.filter(
Port.fixed_ips.any(IPAllocation.ip_address.in_(ip_addresses)))

View File

@ -15,11 +15,13 @@ from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib.db import api as db_api
from neutron_lib.plugins.ml2 import api as ml2_api
from oslo_log import log as logging
from oslo_utils import uuidutils
from neutron.objects import base as base_obj
from neutron.objects import network as network_obj
from neutron.services.segments import exceptions as segments_exceptions
LOG = logging.getLogger(__name__)
@ -61,6 +63,19 @@ def add_network_segment(context, network_id, segment, segment_index=0,
'network_id': netseg_obj.network_id})
def update_network_segment(context, segment_id, segmentation_id):
with db_api.CONTEXT_WRITER.using(context):
netseg_obj = network_obj.NetworkSegment.get_object(context,
id=segment_id)
if not netseg_obj:
raise segments_exceptions.SegmentNotFound(segment_id=segment_id)
netseg_obj[ml2_api.SEGMENTATION_ID] = segmentation_id
netseg_obj.update()
LOG.info("Updated segment %(id)s, segmentation_id: %(segmentation_id)s)",
{'id': segment_id, 'segmentation_id': segmentation_id})
def get_network_segments(context, network_id, filter_dynamic=False):
return get_networks_segments(
context, [network_id], filter_dynamic)[network_id]

View File

@ -15,22 +15,6 @@
from neutron_lib.api.definitions import provider_net
from neutron_lib.api import extensions
from neutron_lib.api import validators
from neutron_lib import exceptions as n_exc
from neutron._i18n import _
def _raise_if_updates_provider_attributes(attrs):
"""Raise exception if provider attributes are present.
This method is used for plugins that do not support
updating provider networks.
"""
if any(validators.is_attr_set(attrs.get(a))
for a in provider_net.ATTRIBUTES):
msg = _("Plugin does not support updating provider attributes")
raise n_exc.InvalidInput(error_message=msg)
class Providernet(extensions.APIExtensionDescriptor):

View File

@ -363,3 +363,14 @@ class SimpleAgentMechanismDriverBase(AgentMechanismDriverBase):
return False
return True
@staticmethod
def provider_network_attribute_updates_supported():
"""Returns the provider network attributes that can be updated
Possible values: neutron_lib.api.definitions.provider_net.ATTRIBUTES
:returns: (list) provider network attributes that can be updated in a
live network using this driver.
"""
return []

View File

@ -200,6 +200,10 @@ class TypeManager(stevedore.named.NamedExtensionManager):
segments_db.add_network_segment(
context, network_id, segment, segment_index)
def _update_network_segment(self, context, network_id, segmentation_id):
segments_db.update_network_segment(
context, network_id, segmentation_id)
def create_network_segments(self, context, network, tenant_id):
"""Call type drivers to create network segments."""
segments = self._process_provider_create(network)
@ -222,6 +226,30 @@ class TypeManager(stevedore.named.NamedExtensionManager):
context, filters=filters)
self._add_network_segment(context, network_id, segment)
def update_network_segment(self, context, network, net_data, segment):
"""Call type drivers to update a network segment.
Update operation is currently only supported for VLAN type segments,
and only the SEGMENTATION_ID field can be changed.
"""
segmentation_id = net_data.get(provider.SEGMENTATION_ID)
network_type = segment[api.NETWORK_TYPE]
if network_type != constants.TYPE_VLAN:
msg = (_('Only VLAN type networks can be updated.'))
raise exc.InvalidInput(error_message=msg)
elif not segmentation_id:
msg = (_('Only %s field can be updated in VLAN type networks') %
api.SEGMENTATION_ID)
raise exc.InvalidInput(error_message=msg)
new_segment = {api.NETWORK_TYPE: segment[api.NETWORK_TYPE],
api.PHYSICAL_NETWORK: segment[api.PHYSICAL_NETWORK],
api.SEGMENTATION_ID: segmentation_id}
self.validate_provider_segment(new_segment)
self.reserve_provider_segment(context, new_segment)
self._update_network_segment(context, segment['id'], segmentation_id)
self.release_network_segment(context, segment)
def reserve_network_segment(self, context, segment_data):
"""Call type drivers to reserve a network segment."""
# Validate the data of segment

View File

@ -62,6 +62,7 @@ from neutron_lib.db import utils as db_utils
from neutron_lib import exceptions as exc
from neutron_lib.exceptions import allowedaddresspairs as addr_exc
from neutron_lib.exceptions import port_security as psec_exc
from neutron_lib.objects import utils as obj_utils
from neutron_lib.plugins import constants as plugin_constants
from neutron_lib.plugins import directory
from neutron_lib.plugins.ml2 import api
@ -107,7 +108,6 @@ from neutron.db import segments_db
from neutron.db import subnet_service_type_mixin
from neutron.db import vlantransparent_db
from neutron.extensions import filter_validation
from neutron.extensions import providernet as provider
from neutron.extensions import vlantransparent
from neutron.ipam import exceptions as ipam_exc
from neutron.objects import base as base_obj
@ -797,6 +797,61 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
segment[api.SEGMENTATION_ID],
segment[api.PHYSICAL_NETWORK])
def _update_segmentation_id(self, context, network, net_data):
"""Update segmentation ID in a single provider network"""
segments = segments_db.get_networks_segments(
context, [network['id']])[network['id']]
if len(segments) > 1:
msg = _('Provider network attributes can be updated only in '
'provider networks with a single segment.')
raise exc.InvalidInput(error_message=msg)
vif_types = [portbindings.VIF_TYPE_UNBOUND,
portbindings.VIF_TYPE_BINDING_FAILED]
for mech_driver in self.mechanism_manager.ordered_mech_drivers:
if (provider_net.SEGMENTATION_ID in mech_driver.obj.
provider_network_attribute_updates_supported()):
agent_type = mech_driver.obj.agent_type
agents = self.get_agents(context,
filters={'agent_type': [agent_type]})
for agent in agents:
vif_types.append(mech_driver.obj.get_vif_type(
context, agent, segments[0]))
filter_obj = obj_utils.NotIn(vif_types)
filters = {portbindings.VIF_TYPE:
filter_obj.filter(models.PortBinding.vif_type),
'network_id': [network['id']]}
if super(Ml2Plugin, self).get_ports_count(context,
filters=filters):
msg = (_('Provider network attribute %(attr)s cannot be updated '
'if any port in the network has not the following '
'%(vif_field)s: %(vif_types)s') %
{'attr': provider_net.SEGMENTATION_ID,
'vif_field': portbindings.VIF_TYPE,
'vif_types': ', '.join(vif_types)})
raise exc.InvalidInput(error_message=msg)
self.type_manager.update_network_segment(context, network,
net_data, segments[0])
def _update_provider_network_attributes(self, context, network, net_data):
"""Raise exception if provider network attrs update are not supported.
This function will raise an exception if the provider network attribute
update is not supported.
"""
if net_data.get(provider_net.SEGMENTATION_ID):
self._update_segmentation_id(context, network, net_data)
provider_net_attrs = (set(provider_net.ATTRIBUTES) -
{provider_net.SEGMENTATION_ID})
if any(validators.is_attr_set(net_data.get(a))
for a in provider_net_attrs):
msg = (_('Plugin does not support updating the following provider '
'network attributes: %s') % ', '.join(provider_net_attrs))
raise exc.InvalidInput(error_message=msg)
def _delete_objects(self, context, resource, objects):
delete_op = getattr(self, 'delete_%s' % resource)
for obj in objects:
@ -985,11 +1040,13 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
@db_api.retry_if_session_inactive()
def update_network(self, context, id, network):
net_data = network[net_def.RESOURCE_NAME]
provider._raise_if_updates_provider_attributes(net_data)
need_network_update_notify = False
with db_api.CONTEXT_WRITER.using(context):
original_network = super(Ml2Plugin, self).get_network(context, id)
self._update_provider_network_attributes(
context, original_network, net_data)
updated_network = super(Ml2Plugin, self).update_network(context,
id,
network)

View File

@ -140,3 +140,7 @@ class LoggerMechanismDriver(api.MechanismDriver):
"%(segments)s, candidate hosts %(hosts)s ",
{'segments': segments, 'hosts': candidate_hosts})
return set()
@staticmethod
def provider_network_attribute_updates_supported():
return []

View File

@ -20,6 +20,9 @@ from neutron_lib import constants as const
from neutron_lib.plugins.ml2 import api
VIF_TYPE_TEST = 'vif_type_test'
class TestMechanismDriver(api.MechanismDriver):
"""Test mechanism driver for testing mechanism driver api."""
@ -246,6 +249,9 @@ class TestMechanismDriver(api.MechanismDriver):
self, context, segments, candidate_hosts, agent_getter):
return set()
def get_vif_type(self, context, agent, segment):
return VIF_TYPE_TEST
@property
def resource_provider_uuid5_namespace(self):
return uuid.UUID('7f0ce65c-1f13-11e9-8921-3c6aa7b21d17')
@ -260,3 +266,7 @@ class TestMechanismDriver(api.MechanismDriver):
def get_standard_device_mappings(self, agent):
return {}
@staticmethod
def provider_network_attribute_updates_supported():
return []

View File

@ -16,11 +16,15 @@
import mock
from neutron_lib.api.definitions import provider_net as provider
from neutron_lib import exceptions as exc
from neutron_lib.exceptions import placement as place_exc
from neutron_lib.plugins.ml2 import api
from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_utils import uuidutils
from neutron.db import segments_db
from neutron.plugins.ml2.common import exceptions as ml2_exc
from neutron.plugins.ml2 import managers
from neutron.tests import base
@ -180,3 +184,50 @@ class TestMechManager(base.BaseTestCase):
def test_port_precommit(self):
self._check_resource('port')
class TypeManagerTestCase(base.BaseTestCase):
def setUp(self):
super(TypeManagerTestCase, self).setUp()
self.type_manager = managers.TypeManager()
self.ctx = mock.Mock()
self.network = {'id': uuidutils.generate_uuid()}
def test_update_network_segment_no_vlan_no_segmentation_id(self):
net_data = {}
segment = {api.NETWORK_TYPE: 'vlan'}
self.assertRaises(
exc.InvalidInput, self.type_manager.update_network_segment,
self.ctx, self.network, net_data, segment)
net_data = {provider.SEGMENTATION_ID: 1000}
segment = {api.NETWORK_TYPE: 'no_vlan'}
self.assertRaises(
exc.InvalidInput, self.type_manager.update_network_segment,
self.ctx, self.network, net_data, segment)
def test_update_network_segment(self):
segmentation_id = 1000
net_data = {provider.SEGMENTATION_ID: segmentation_id}
segment = {'id': uuidutils.generate_uuid(),
api.NETWORK_TYPE: 'vlan',
api.PHYSICAL_NETWORK: 'default_network'}
new_segment = {api.NETWORK_TYPE: 'vlan',
api.PHYSICAL_NETWORK: 'default_network',
api.SEGMENTATION_ID: segmentation_id}
with mock.patch.object(self.type_manager,
'validate_provider_segment') as mock_validate, \
mock.patch.object(self.type_manager,
'reserve_provider_segment') as mock_reserve,\
mock.patch.object(self.type_manager,
'release_network_segment') as mock_release, \
mock.patch.object(segments_db, 'update_network_segment') as \
mock_update_network_segment:
self.type_manager.update_network_segment(self.ctx, self.network,
net_data, segment)
mock_validate.assert_called_once_with(new_segment)
mock_reserve.assert_called_once_with(self.ctx, new_segment)
mock_update_network_segment.assert_called_once_with(
self.ctx, segment['id'], segmentation_id)
mock_release.assert_called_once_with(self.ctx, segment)

View File

@ -36,6 +36,7 @@ from neutron_lib import context
from neutron_lib.db import api as db_api
from neutron_lib import exceptions as exc
from neutron_lib import fixture
from neutron_lib.objects import utils as obj_utils
from neutron_lib.plugins import constants as plugin_constants
from neutron_lib.plugins import directory
from neutron_lib.plugins.ml2 import api as driver_api
@ -49,6 +50,7 @@ import webob
from neutron._i18n import _
from neutron.common import utils
from neutron.db import agents_db
from neutron.db import db_base_plugin_v2
from neutron.db import provisioning_blocks
from neutron.db import securitygroups_db as sg_db
from neutron.db import segments_db
@ -419,6 +421,83 @@ class TestMl2NetworksV2(test_plugin.TestNetworksV2,
self.assertEqual(3, f.call_count)
retry_fixture.cleanUp()
def test__update_provider_network_attributes_no_segmentation_id(self):
plugin = directory.get_plugin()
with self.network() as net:
for attribute in set(pnet.ATTRIBUTES) - {pnet.SEGMENTATION_ID}:
net_data = {attribute: 'attribute_value'}
self.assertRaises(
exc.InvalidInput,
plugin._update_provider_network_attributes,
self.context, net['network'], net_data)
def test__update_provider_network_attributes_segmentation_id(self):
plugin = directory.get_plugin()
with self.network() as net:
with mock.patch.object(plugin, '_update_segmentation_id') as \
mock_update_segmentation_id:
net_data = {pnet.SEGMENTATION_ID: 1000}
plugin._update_provider_network_attributes(
self.context, net['network'], net_data)
mock_update_segmentation_id.assert_called_once_with(
self.context, net['network'], net_data)
def test__update_segmentation_id_multisegment_network(self):
plugin = directory.get_plugin()
segments = [{pnet.NETWORK_TYPE: 'vlan',
pnet.PHYSICAL_NETWORK: 'physnet1',
pnet.SEGMENTATION_ID: 1},
{pnet.NETWORK_TYPE: 'vlan',
pnet.PHYSICAL_NETWORK: 'physnet1',
pnet.SEGMENTATION_ID: 2}]
with self.network(**{'arg_list': (mpnet_apidef.SEGMENTS, ),
mpnet_apidef.SEGMENTS: segments}) as net:
self.assertRaises(
exc.InvalidInput, plugin._update_segmentation_id, self.context,
net['network'], {})
def test__update_segmentation_id_ports_wrong_vif_type(self):
plugin = directory.get_plugin()
with self.network() as net:
with mock.patch.object(db_base_plugin_v2.NeutronDbPluginV2,
'get_ports_count') as mock_get_ports_count:
mock_get_ports_count.return_value = 1
self.assertRaises(
exc.InvalidInput, plugin._update_segmentation_id,
self.context, net['network'], {})
def test__update_segmentation_id_ports(self):
plugin = directory.get_plugin()
segments = [{pnet.NETWORK_TYPE: 'vlan',
pnet.PHYSICAL_NETWORK: 'physnet1',
pnet.SEGMENTATION_ID: 1}]
with self.network(**{'arg_list': (mpnet_apidef.SEGMENTS, ),
mpnet_apidef.SEGMENTS: segments}) as net, \
mock.patch.object(db_base_plugin_v2.NeutronDbPluginV2,
'get_ports_count') as mock_get_ports_count, \
mock.patch.object(plugin.type_manager,
'update_network_segment'), \
mock.patch.object(
mech_test.TestMechanismDriver,
'provider_network_attribute_updates_supported',
return_value=[pnet.SEGMENTATION_ID]), \
mock.patch.object(plugin, 'get_agents',
return_value=[mock.ANY]), \
mock.patch.object(obj_utils, 'NotIn') as mock_not_in:
mock_get_ports_count.return_value = 0
net_data = {pnet.SEGMENTATION_ID: 1000}
plugin._update_segmentation_id(self.context, net['network'],
net_data)
mock_not_in.assert_called_once_with([
portbindings.VIF_TYPE_UNBOUND,
portbindings.VIF_TYPE_BINDING_FAILED,
mech_test.VIF_TYPE_TEST])
filters = {portbindings.VIF_TYPE: mock.ANY,
'network_id': [net['network']['id']]}
mock_get_ports_count.assert_called_once_with(
self.context, filters=filters)
class TestExternalNetwork(Ml2PluginV2TestCase):