Merge "Enable create and delete segments in ML2"

This commit is contained in:
Jenkins 2016-08-29 14:14:28 +00:00 committed by Gerrit Code Review
commit 69015e790c
10 changed files with 442 additions and 33 deletions

View File

@ -22,13 +22,17 @@ import six
from sqlalchemy import or_ from sqlalchemy import or_
from sqlalchemy.orm import exc from sqlalchemy.orm import exc
from neutron._i18n import _LE from neutron._i18n import _, _LE
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.callbacks import resources
from neutron.db.models import securitygroup as sg_models from neutron.db.models import securitygroup as sg_models
from neutron.db import models_v2 from neutron.db import models_v2
from neutron.db import segments_db from neutron.db import segments_db
from neutron.extensions import portbindings from neutron.extensions import portbindings
from neutron import manager from neutron import manager
from neutron.plugins.ml2 import models from neutron.plugins.ml2 import models
from neutron.services.segments import exceptions as seg_exc
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -305,3 +309,30 @@ def is_dhcp_active_on_any_subnet(context, subnet_ids):
return bool(context.session.query(models_v2.Subnet). return bool(context.session.query(models_v2.Subnet).
enable_eagerloads(False).filter_by(enable_dhcp=True). enable_eagerloads(False).filter_by(enable_dhcp=True).
filter(models_v2.Subnet.id.in_(subnet_ids)).count()) filter(models_v2.Subnet.id.in_(subnet_ids)).count())
def _prevent_segment_delete_with_port_bound(resource, event, trigger,
context, segment):
"""Raise exception if there are any ports bound with segment_id."""
segment_id = segment['id']
query = context.session.query(models_v2.Port)
query = query.join(
models.PortBindingLevel,
models.PortBindingLevel.port_id == models_v2.Port.id)
query = query.filter(models.PortBindingLevel.segment_id == segment_id)
port_ids = [p.id for p in query]
# There are still some ports in the segment, segment should not be deleted
# TODO(xiaohhui): Should we delete the dhcp port automatically here?
if port_ids:
reason = _("The segment is still bound with port(s) "
"%s") % ", ".join(port_ids)
raise seg_exc.SegmentInUse(segment_id=segment_id, reason=reason)
def subscribe():
registry.subscribe(_prevent_segment_delete_with_port_bound,
resources.SEGMENT,
events.BEFORE_DELETE)
subscribe()

View File

@ -208,6 +208,26 @@ class TypeManager(stevedore.named.NamedExtensionManager):
segment = self._allocate_tenant_net_segment(session) segment = self._allocate_tenant_net_segment(session)
self._add_network_segment(context, network_id, segment) self._add_network_segment(context, network_id, segment)
def reserve_network_segment(self, session, segment_data):
"""Call type drivers to reserve a network segment."""
# Validate the data of segment
if not validators.is_attr_set(segment_data[api.NETWORK_TYPE]):
msg = _("network_type required")
raise exc.InvalidInput(error_message=msg)
net_type = self._get_attribute(segment_data, api.NETWORK_TYPE)
phys_net = self._get_attribute(segment_data, api.PHYSICAL_NETWORK)
seg_id = self._get_attribute(segment_data, api.SEGMENTATION_ID)
segment = {api.NETWORK_TYPE: net_type,
api.PHYSICAL_NETWORK: phys_net,
api.SEGMENTATION_ID: seg_id}
self.validate_provider_segment(segment)
# Reserve segment in type driver
with session.begin(subtransactions=True):
return self.reserve_provider_segment(session, segment)
def is_partial_segment(self, segment): def is_partial_segment(self, segment):
network_type = segment[api.NETWORK_TYPE] network_type = segment[api.NETWORK_TYPE]
driver = self.drivers.get(network_type) driver = self.drivers.get(network_type)
@ -254,6 +274,9 @@ class TypeManager(stevedore.named.NamedExtensionManager):
filter_dynamic=None) filter_dynamic=None)
for segment in segments: for segment in segments:
self.release_network_segment(session, segment)
def release_network_segment(self, session, segment):
network_type = segment.get(api.NETWORK_TYPE) network_type = segment.get(api.NETWORK_TYPE)
driver = self.drivers.get(network_type) driver = self.drivers.get(network_type)
if driver: if driver:

View File

@ -84,6 +84,7 @@ from neutron.plugins.ml2 import models
from neutron.plugins.ml2 import rpc from neutron.plugins.ml2 import rpc
from neutron.quota import resource_registry from neutron.quota import resource_registry
from neutron.services.qos import qos_consts from neutron.services.qos import qos_consts
from neutron.services.segments import plugin as segments_plugin
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -163,6 +164,14 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
self.mechanism_manager.initialize() self.mechanism_manager.initialize()
registry.subscribe(self._port_provisioned, resources.PORT, registry.subscribe(self._port_provisioned, resources.PORT,
provisioning_blocks.PROVISIONING_COMPLETE) provisioning_blocks.PROVISIONING_COMPLETE)
registry.subscribe(self._handle_segment_change, resources.SEGMENT,
events.PRECOMMIT_CREATE)
registry.subscribe(self._handle_segment_change, resources.SEGMENT,
events.PRECOMMIT_DELETE)
registry.subscribe(self._handle_segment_change, resources.SEGMENT,
events.AFTER_CREATE)
registry.subscribe(self._handle_segment_change, resources.SEGMENT,
events.AFTER_DELETE)
self._setup_dhcp() self._setup_dhcp()
self._start_rpc_notifiers() self._start_rpc_notifiers()
self.add_agent_status_check_worker(self.agent_health_check) self.add_agent_status_check_worker(self.agent_health_check)
@ -927,7 +936,11 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
self.mechanism_manager.delete_network_precommit( self.mechanism_manager.delete_network_precommit(
mech_context) mech_context)
self.type_manager.release_network_segments(session, id) registry.notify(resources.NETWORK,
events.PRECOMMIT_DELETE,
self,
context=context,
network_id=id)
record = self._get_network(context, id) record = self._get_network(context, id)
LOG.debug("Deleting network record %s", record) LOG.debug("Deleting network record %s", record)
session.delete(record) session.delete(record)
@ -1797,3 +1810,47 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
if mech_driver.obj.check_segment_for_agent(segment, agent): if mech_driver.obj.check_segment_for_agent(segment, agent):
return True return True
return False return False
def _handle_segment_change(self, rtype, event, trigger, context, segment):
if (event == events.PRECOMMIT_CREATE and
not isinstance(trigger, segments_plugin.Plugin)):
# TODO(xiaohhui): Now, when create network, ml2 will reserve
# segment and trigger this event handler. This event handler
# will reserve segment again, which will lead to error as the
# segment has already been reserved. This check could be removed
# by unifying segment creation procedure.
return
session = context.session
network_id = segment.get('network_id')
if event == events.PRECOMMIT_CREATE:
updated_segment = self.type_manager.reserve_network_segment(
session, segment)
# The segmentation id might be from ML2 type driver, update it
# in the original segment.
segment[api.SEGMENTATION_ID] = updated_segment[api.SEGMENTATION_ID]
elif event == events.PRECOMMIT_DELETE:
self.type_manager.release_network_segment(session, segment)
try:
self._notify_mechanism_driver_for_segment_change(
event, context, network_id)
except ml2_exc.MechanismDriverError:
with excutils.save_and_reraise_exception():
LOG.error(_LE("mechanism_manager error occurred when "
"handle event %(event)s for segment "
"'%(segment)s'"),
{'event': event, 'segment': segment['id']})
def _notify_mechanism_driver_for_segment_change(self, event,
context, network_id):
network_with_segments = self.get_network(context, network_id)
mech_context = driver_context.NetworkContext(
self, context, network_with_segments,
original_network=network_with_segments)
if (event == events.PRECOMMIT_CREATE or
event == events.PRECOMMIT_DELETE):
self.mechanism_manager.update_network_precommit(mech_context)
elif event == events.AFTER_CREATE or event == events.AFTER_DELETE:
self.mechanism_manager.update_network_postcommit(mech_context)

View File

@ -69,7 +69,8 @@ class SegmentDbMixin(common_db_mixin.CommonDbMixin):
db.NETWORK_TYPE: segment_db[db.NETWORK_TYPE], db.NETWORK_TYPE: segment_db[db.NETWORK_TYPE],
db.SEGMENTATION_ID: segment_db[db.SEGMENTATION_ID], db.SEGMENTATION_ID: segment_db[db.SEGMENTATION_ID],
'hosts': [mapping.host for mapping in 'hosts': [mapping.host for mapping in
segment_db.segment_host_mapping]} segment_db.segment_host_mapping],
'segment_index': segment_db['segment_index']}
return self._fields(res, fields) return self._fields(res, fields)
def _get_segment(self, context, segment_id): def _get_segment(self, context, segment_id):
@ -98,15 +99,33 @@ class SegmentDbMixin(common_db_mixin.CommonDbMixin):
db.PHYSICAL_NETWORK: physical_network, db.PHYSICAL_NETWORK: physical_network,
db.NETWORK_TYPE: network_type, db.NETWORK_TYPE: network_type,
db.SEGMENTATION_ID: segmentation_id} db.SEGMENTATION_ID: segmentation_id}
# Calculate the index of segment
segment_index = 0
segments = self.get_segments(
context,
filters={'network_id': [network_id]},
fields=['segment_index'],
sorts=[('segment_index', True)])
if segments:
# NOTE(xiaohhui): The new index is the last index + 1, this
# may casue discontinuous segment_index. But segment_index
# can functionally work as the order index for segments.
segment_index = (segments[-1].get('segment_index') + 1)
args['segment_index'] = segment_index
new_segment = db.NetworkSegment(**args) new_segment = db.NetworkSegment(**args)
try: try:
context.session.add(new_segment) context.session.add(new_segment)
context.session.flush([new_segment]) context.session.flush([new_segment])
except db_exc.DBReferenceError: except db_exc.DBReferenceError:
raise n_exc.NetworkNotFound(net_id=network_id) raise n_exc.NetworkNotFound(net_id=network_id)
# Do some preliminary operations before commiting the segment to db
registry.notify(resources.SEGMENT, events.PRECOMMIT_CREATE, self, registry.notify(resources.SEGMENT, events.PRECOMMIT_CREATE, self,
context=context, segment=new_segment) context=context, segment=new_segment)
registry.notify(resources.SEGMENT, events.AFTER_CREATE, self,
context=context, segment=new_segment)
return self._make_segment_dict(new_segment) return self._make_segment_dict(new_segment)
@log_helpers.log_method_call @log_helpers.log_method_call
@ -156,11 +175,26 @@ class SegmentDbMixin(common_db_mixin.CommonDbMixin):
@log_helpers.log_method_call @log_helpers.log_method_call
def delete_segment(self, context, uuid): def delete_segment(self, context, uuid):
"""Delete an existing segment.""" """Delete an existing segment."""
segment = self.get_segment(context, uuid)
# Do some preliminary operations before deleting the segment
registry.notify(resources.SEGMENT, events.BEFORE_DELETE,
self.delete_segment, context=context,
segment=segment)
# Delete segment in DB
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
query = self._model_query(context, db.NetworkSegment) query = self._model_query(context, db.NetworkSegment)
query = query.filter(db.NetworkSegment.id == uuid) query = query.filter(db.NetworkSegment.id == uuid)
if 0 == query.delete(): if 0 == query.delete():
raise exceptions.SegmentNotFound(segment_id=uuid) raise exceptions.SegmentNotFound(segment_id=uuid)
# Do some preliminary operations before deleting segment in db
registry.notify(resources.SEGMENT, events.PRECOMMIT_DELETE,
self.delete_segment, context=context,
segment=segment)
registry.notify(resources.SEGMENT, events.AFTER_DELETE,
self.delete_segment, context=context,
segment=segment)
def update_segment_host_mapping(context, host, current_segment_ids): def update_segment_host_mapping(context, host, current_segment_ids):
@ -199,6 +233,10 @@ def _get_phys_nets(agent):
reported_hosts = set() reported_hosts = set()
# NOTE: Module level variable of segments plugin. It should be removed once
# segments becomes a default plugin.
segments_plugin = None
def get_segments_with_phys_nets(context, phys_nets): def get_segments_with_phys_nets(context, phys_nets):
"""Get segments from physical networks. """Get segments from physical networks.
@ -245,6 +283,12 @@ def _update_segment_host_mapping_for_agent(resource, event, trigger,
def _add_segment_host_mapping_for_segment(resource, event, trigger, def _add_segment_host_mapping_for_segment(resource, event, trigger,
context, segment): context, segment):
if not context.session.is_active:
# The session might be in partial rollback state, due to errors in
# peer callback. In that case, there is no need to add the mapping.
# Just return here.
return
if not segment.physical_network: if not segment.physical_network:
return return
cp = manager.NeutronManager.get_plugin() cp = manager.NeutronManager.get_plugin()
@ -259,6 +303,19 @@ def _add_segment_host_mapping_for_segment(resource, event, trigger,
map_segment_to_hosts(context, segment.id, hosts) map_segment_to_hosts(context, segment.id, hosts)
def _delete_segments_for_network(resource, event, trigger,
context, network_id):
admin_ctx = context.elevated()
global segments_plugin
if not segments_plugin:
segments_plugin = manager.NeutronManager.load_class_for_provider(
'neutron.service_plugins', 'segments')()
segments = segments_plugin.get_segments(
admin_ctx, filters={'network_id': [network_id]})
for segment in segments:
segments_plugin.delete_segment(admin_ctx, segment['id'])
def subscribe(): def subscribe():
registry.subscribe(_update_segment_host_mapping_for_agent, registry.subscribe(_update_segment_host_mapping_for_agent,
resources.AGENT, resources.AGENT,
@ -268,5 +325,8 @@ def subscribe():
events.AFTER_UPDATE) events.AFTER_UPDATE)
registry.subscribe(_add_segment_host_mapping_for_segment, registry.subscribe(_add_segment_host_mapping_for_segment,
resources.SEGMENT, events.PRECOMMIT_CREATE) resources.SEGMENT, events.PRECOMMIT_CREATE)
registry.subscribe(_delete_segments_for_network,
resources.NETWORK,
events.PRECOMMIT_DELETE)
subscribe() subscribe()

View File

@ -54,3 +54,7 @@ class HostNotCompatibleWithFixedIps(exceptions.Conflict):
message = _("Host %(host)s is not connected to a segment where the " message = _("Host %(host)s is not connected to a segment where the "
"existing fixed_ips on port %(port_id)s will function given " "existing fixed_ips on port %(port_id)s will function given "
"the routed network topology.") "the routed network topology.")
class SegmentInUse(exceptions.InUse):
message = _("Segment '%(segment_id)s' cannot be deleted: %(reason)s.")

View File

@ -16,7 +16,11 @@
from sqlalchemy.orm import session from sqlalchemy.orm import session
from neutron._i18n import _
from neutron.api.v2 import attributes from neutron.api.v2 import attributes
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.callbacks import resources
from neutron.db import common_db_mixin from neutron.db import common_db_mixin
from neutron.db import models_v2 from neutron.db import models_v2
from neutron.extensions import ip_allocation from neutron.extensions import ip_allocation
@ -24,6 +28,7 @@ from neutron.extensions import l2_adjacency
from neutron.extensions import segment from neutron.extensions import segment
from neutron import manager from neutron import manager
from neutron.services.segments import db from neutron.services.segments import db
from neutron.services.segments import exceptions
def _extend_network_dict_binding(plugin, network_res, network_db): def _extend_network_dict_binding(plugin, network_res, network_db):
@ -71,8 +76,25 @@ class Plugin(db.SegmentDbMixin, segment.SegmentPluginBase):
common_db_mixin.CommonDbMixin.register_dict_extend_funcs( common_db_mixin.CommonDbMixin.register_dict_extend_funcs(
attributes.PORTS, [_extend_port_dict_binding]) attributes.PORTS, [_extend_port_dict_binding])
registry.subscribe(
self._prevent_segment_delete_with_subnet_associated,
resources.SEGMENT,
events.BEFORE_DELETE)
@classmethod @classmethod
def get_instance(cls): def get_instance(cls):
if cls._instance is None: if cls._instance is None:
cls._instance = cls() cls._instance = cls()
return cls._instance return cls._instance
def _prevent_segment_delete_with_subnet_associated(
self, resource, event, trigger, context, segment):
"""Raise exception if there are any subnets associated with segment."""
segment_id = segment['id']
query = context.session.query(models_v2.Subnet.id)
query = query.filter(models_v2.Subnet.segment_id == segment_id)
subnet_ids = [s[0] for s in query]
if subnet_ids:
reason = _("The segment is still associated with subnet(s) "
"%s") % ", ".join(subnet_ids)
raise exceptions.SegmentInUse(segment_id=segment_id, reason=reason)

View File

@ -21,6 +21,7 @@ import webob.exc
from neutron.api.v2 import attributes from neutron.api.v2 import attributes
from neutron.callbacks import events from neutron.callbacks import events
from neutron.callbacks import exceptions
from neutron.callbacks import registry from neutron.callbacks import registry
from neutron.callbacks import resources from neutron.callbacks import resources
from neutron import context from neutron import context
@ -175,24 +176,82 @@ class TestSegment(SegmentTestCase):
expected=expected_segment) expected=expected_segment)
def test_create_segment_no_segmentation_id(self): def test_create_segment_no_segmentation_id(self):
def _mock_reserve_segmentation_id(rtype, event, trigger,
context, segment):
if not segment.get('segmentation_id'):
segment['segmentation_id'] = 200
with self.network() as network: with self.network() as network:
network = network['network'] network = network['network']
registry.subscribe(_mock_reserve_segmentation_id, resources.SEGMENT,
events.PRECOMMIT_CREATE)
expected_segment = {'network_id': network['id'], expected_segment = {'network_id': network['id'],
'physical_network': 'phys_net', 'physical_network': 'phys_net',
'network_type': 'net_type', 'network_type': 'net_type',
'segmentation_id': None} 'segmentation_id': 200}
self._test_create_segment(network_id=network['id'], self._test_create_segment(network_id=network['id'],
physical_network='phys_net', physical_network='phys_net',
expected=expected_segment) expected=expected_segment)
def test_create_segment_with_exception_in_core_plugin(self):
cxt = context.get_admin_context()
with self.network() as network:
network = network['network']
with mock.patch.object(registry, 'notify') as notify:
notify.side_effect = exceptions.CallbackFailure(errors=Exception)
self.assertRaises(webob.exc.HTTPClientError,
self.segment,
network_id=network['id'],
segmentation_id=200)
network_segments = segments_db.get_network_segments(cxt.session,
network['id'])
self.assertEqual([], network_segments)
def test_create_segments_in_certain_order(self):
cxt = context.get_admin_context()
with self.network() as network:
network = network['network']
segment1 = self.segment(
network_id=network['id'], segmentation_id=200)
segment2 = self.segment(
network_id=network['id'], segmentation_id=201)
segment3 = self.segment(
network_id=network['id'], segmentation_id=202)
network_segments = segments_db.get_network_segments(cxt.session,
network['id'])
self.assertEqual(segment1['segment']['id'],
network_segments[0]['id'])
self.assertEqual(segment2['segment']['id'],
network_segments[1]['id'])
self.assertEqual(segment3['segment']['id'],
network_segments[2]['id'])
def test_delete_segment(self): def test_delete_segment(self):
with self.network() as network: with self.network() as network:
network = network['network'] network = network['network']
segment = self.segment(network_id=network['id']) self.segment(network_id=network['id'], segmentation_id=200)
segment = self.segment(network_id=network['id'], segmentation_id=201)
self._delete('segments', segment['segment']['id']) self._delete('segments', segment['segment']['id'])
self._show('segments', segment['segment']['id'], self._show('segments', segment['segment']['id'],
expected_code=webob.exc.HTTPNotFound.code) expected_code=webob.exc.HTTPNotFound.code)
def test_delete_segment_failed_with_subnet_associated(self):
with self.network() as network:
net = network['network']
segment = self._test_create_segment(network_id=net['id'],
segmentation_id=200)
segment_id = segment['segment']['id']
with self.subnet(network=network, segment_id=segment_id):
self._delete('segments', segment_id,
expected_code=webob.exc.HTTPConflict.code)
exist_segment = self._show('segments', segment_id)
self.assertEqual(segment_id, exist_segment['segment']['id'])
def test_get_segment(self): def test_get_segment(self):
with self.network() as network: with self.network() as network:
network = network['network'] network = network['network']
@ -211,10 +270,19 @@ class TestSegment(SegmentTestCase):
segmentation_id=200) segmentation_id=200)
self._test_create_segment(network_id=network['id'], self._test_create_segment(network_id=network['id'],
physical_network='phys_net2', physical_network='phys_net2',
segmentation_id=200) segmentation_id=201)
res = self._list('segments') res = self._list('segments')
self.assertEqual(2, len(res['segments'])) self.assertEqual(2, len(res['segments']))
def test_update_segments(self):
with self.network() as network:
net = network['network']
segment = self._test_create_segment(network_id=net['id'],
segmentation_id=200)
segment['segment']['segmentation_id'] = '201'
self._update('segments', segment['segment']['id'], segment,
expected_code=webob.exc.HTTPClientError.code)
class TestSegmentML2(SegmentTestCase): class TestSegmentML2(SegmentTestCase):
def setUp(self): def setUp(self):
@ -236,7 +304,8 @@ class TestSegmentSubnetAssociation(SegmentTestCase):
with self.network() as network: with self.network() as network:
net = network['network'] net = network['network']
segment = self._test_create_segment(network_id=net['id']) segment = self._test_create_segment(network_id=net['id'],
segmentation_id=200)
segment_id = segment['segment']['id'] segment_id = segment['segment']['id']
with self.subnet(network=network, segment_id=segment_id) as subnet: with self.subnet(network=network, segment_id=segment_id) as subnet:
@ -253,7 +322,8 @@ class TestSegmentSubnetAssociation(SegmentTestCase):
with self.network() as network2: with self.network() as network2:
net = network1['network'] net = network1['network']
segment = self._test_create_segment(network_id=net['id']) segment = self._test_create_segment(network_id=net['id'],
segmentation_id=200)
res = self._create_subnet(self.fmt, res = self._create_subnet(self.fmt,
net_id=network2['network']['id'], net_id=network2['network']['id'],
@ -282,7 +352,8 @@ class TestSegmentSubnetAssociation(SegmentTestCase):
with self.subnet(network=network): with self.subnet(network=network):
net = network['network'] net = network['network']
segment = self._test_create_segment(network_id=net['id']) segment = self._test_create_segment(network_id=net['id'],
segmentation_id=200)
res = self._create_subnet(self.fmt, res = self._create_subnet(self.fmt,
net_id=net['id'], net_id=net['id'],
@ -322,6 +393,9 @@ class HostSegmentMappingTestCase(SegmentTestCase):
config.cfg.CONF.set_override('mechanism_drivers', config.cfg.CONF.set_override('mechanism_drivers',
self._mechanism_drivers, self._mechanism_drivers,
group='ml2') group='ml2')
config.cfg.CONF.set_override('network_vlan_ranges',
['phys_net1', 'phys_net2'],
group='ml2_type_vlan')
if not plugin: if not plugin:
plugin = 'ml2' plugin = 'ml2'
super(HostSegmentMappingTestCase, self).setUp(plugin=plugin) super(HostSegmentMappingTestCase, self).setUp(plugin=plugin)
@ -511,7 +585,7 @@ class TestMl2HostSegmentMappingOVS(HostSegmentMappingTestCase):
network = network['network'] network = network['network']
segment2 = self._test_create_segment( segment2 = self._test_create_segment(
network_id=network['id'], physical_network=physical_network, network_id=network['id'], physical_network=physical_network,
segmentation_id=200, network_type=p_constants.TYPE_VLAN)['segment'] segmentation_id=201, network_type=p_constants.TYPE_VLAN)['segment']
segments_host_db = self._get_segments_for_host(host1) segments_host_db = self._get_segments_for_host(host1)
self.assertEqual(set((segment['id'], segment2['id'])), self.assertEqual(set((segment['id'], segment2['id'])),
set(segments_host_db)) set(segments_host_db))
@ -663,7 +737,8 @@ class TestSegmentAwareIpam(SegmentTestCase):
segment = self._test_create_segment( segment = self._test_create_segment(
network_id=network['network']['id'], network_id=network['network']['id'],
physical_network=physnet) physical_network=physnet,
network_type=p_constants.TYPE_VLAN)
ip_version = netaddr.IPNetwork(cidr).version if cidr else None ip_version = netaddr.IPNetwork(cidr).version if cidr else None
with self.subnet(network=network, with self.subnet(network=network,
@ -730,7 +805,8 @@ class TestSegmentAwareIpam(SegmentTestCase):
with self.network() as network: with self.network() as network:
segment = self._test_create_segment( segment = self._test_create_segment(
network_id=network['network']['id'], network_id=network['network']['id'],
physical_network='physnet') physical_network='physnet',
network_type=p_constants.TYPE_VLAN)
# Map the host to the segment # Map the host to the segment
self._setup_host_mappings([(segment['segment']['id'], 'fakehost')]) self._setup_host_mappings([(segment['segment']['id'], 'fakehost')])
@ -753,7 +829,8 @@ class TestSegmentAwareIpam(SegmentTestCase):
cidr='2001:db8:0:0::/64') as subnet: cidr='2001:db8:0:0::/64') as subnet:
segment = self._test_create_segment( segment = self._test_create_segment(
network_id=network['network']['id'], network_id=network['network']['id'],
physical_network='physnet') physical_network='physnet',
network_type=p_constants.TYPE_VLAN)
self._validate_l2_adjacency(network['network']['id'], is_adjacent=True) self._validate_l2_adjacency(network['network']['id'], is_adjacent=True)
@ -823,7 +900,8 @@ class TestSegmentAwareIpam(SegmentTestCase):
with self.network() as network: with self.network() as network:
segment = self._test_create_segment( segment = self._test_create_segment(
network_id=network['network']['id'], network_id=network['network']['id'],
physical_network='physnet') physical_network='physnet',
network_type=p_constants.TYPE_VLAN)
# Create a port with no IP address (since there is no subnet) # Create a port with no IP address (since there is no subnet)
port = self._create_deferred_ip_port(network) port = self._create_deferred_ip_port(network)
@ -864,7 +942,8 @@ class TestSegmentAwareIpam(SegmentTestCase):
with self.network() as network: with self.network() as network:
segment = self._test_create_segment( segment = self._test_create_segment(
network_id=network['network']['id'], network_id=network['network']['id'],
physical_network='physnet') physical_network='physnet',
network_type=p_constants.TYPE_VLAN)
# Map the host to the segment # Map the host to the segment
self._setup_host_mappings([(segment['segment']['id'], 'fakehost')]) self._setup_host_mappings([(segment['segment']['id'], 'fakehost')])
@ -1155,6 +1234,10 @@ class TestSegmentAwareIpam(SegmentTestCase):
class TestSegmentAwareIpamML2(TestSegmentAwareIpam): class TestSegmentAwareIpamML2(TestSegmentAwareIpam):
def setUp(self): def setUp(self):
config.cfg.CONF.set_override('network_vlan_ranges',
['physnet:200:209', 'physnet0:200:209',
'physnet1:200:209', 'physnet2:200:209'],
group='ml2_type_vlan')
super(TestSegmentAwareIpamML2, self).setUp(plugin='ml2') super(TestSegmentAwareIpamML2, self).setUp(plugin='ml2')
@ -1174,7 +1257,7 @@ class TestDhcpAgentSegmentScheduling(HostSegmentMappingTestCase):
segment = self._test_create_segment(network_id=network['id'], segment = self._test_create_segment(network_id=network['id'],
physical_network=phys_net, physical_network=phys_net,
segmentation_id=200, segmentation_id=200,
network_type='vxlan') network_type='vlan')
dhcp_agents = self.dhcp_agent_db.get_dhcp_agents_hosting_networks( dhcp_agents = self.dhcp_agent_db.get_dhcp_agents_hosting_networks(
self.ctx, [network['id']]) self.ctx, [network['id']])
self.assertEqual(0, len(dhcp_agents)) self.assertEqual(0, len(dhcp_agents))
@ -1230,7 +1313,7 @@ class TestDhcpAgentSegmentScheduling(HostSegmentMappingTestCase):
segment2 = self._test_create_segment(network_id=network['id'], segment2 = self._test_create_segment(network_id=network['id'],
physical_network=phys_net2, physical_network=phys_net2,
segmentation_id=200, segmentation_id=200,
network_type='vxlan')['segment'] network_type='vlan')['segment']
self._register_agent(DHCP_HOSTA, self._register_agent(DHCP_HOSTA,
mappings={phys_net1: 'br-eth-1'}, mappings={phys_net1: 'br-eth-1'},
plugin=self.plugin) plugin=self.plugin)

View File

@ -34,7 +34,6 @@ class TestMechanismDriver(api.MechanismDriver):
assert(context.current['id'] == context.original['id']) assert(context.current['id'] == context.original['id'])
else: else:
assert(not context.original) assert(not context.original)
assert(context.network_segments)
def create_network_precommit(self, context): def create_network_precommit(self, context):
self._check_network_context(context, False) self._check_network_context(context, False)

View File

@ -30,6 +30,7 @@ from sqlalchemy.orm import exc as sqla_exc
from neutron._i18n import _ from neutron._i18n import _
from neutron.callbacks import events from neutron.callbacks import events
from neutron.callbacks import exceptions as c_exc
from neutron.callbacks import registry from neutron.callbacks import registry
from neutron.callbacks import resources from neutron.callbacks import resources
from neutron.common import utils from neutron.common import utils
@ -58,6 +59,8 @@ from neutron.plugins.ml2 import managers
from neutron.plugins.ml2 import models from neutron.plugins.ml2 import models
from neutron.plugins.ml2 import plugin as ml2_plugin from neutron.plugins.ml2 import plugin as ml2_plugin
from neutron.services.qos import qos_consts from neutron.services.qos import qos_consts
from neutron.services.segments import db as segments_plugin_db
from neutron.services.segments import plugin as segments_plugin
from neutron.tests import base from neutron.tests import base
from neutron.tests.common import helpers from neutron.tests.common import helpers
from neutron.tests.unit import _test_extension_portbindings as test_bindings from neutron.tests.unit import _test_extension_portbindings as test_bindings
@ -1840,6 +1843,7 @@ class TestMultiSegmentNetworks(Ml2PluginV2TestCase):
with mock.patch.object(type_vlan.VlanTypeDriver, with mock.patch.object(type_vlan.VlanTypeDriver,
'release_segment') as rs: 'release_segment') as rs:
segments_plugin_db.subscribe()
req = self.new_delete_request('networks', network_id) req = self.new_delete_request('networks', network_id)
res = req.get_response(self.api) res = req.get_response(self.api)
self.assertEqual(2, rs.call_count) self.assertEqual(2, rs.call_count)
@ -2474,3 +2478,131 @@ class TestTransactionGuard(Ml2PluginV2TestCase):
with ctx.session.begin(subtransactions=True): with ctx.session.begin(subtransactions=True):
with testtools.ExpectedException(RuntimeError): with testtools.ExpectedException(RuntimeError):
plugin.delete_subnet(ctx, 'id') plugin.delete_subnet(ctx, 'id')
class TestML2Segments(Ml2PluginV2TestCase):
def _reserve_segment(self, network, seg_id=None):
segment = {'id': 'fake_id',
'network_id': network['network']['id'],
'tenant_id': network['network']['tenant_id'],
driver_api.NETWORK_TYPE: 'vlan',
driver_api.PHYSICAL_NETWORK: self.physnet}
if seg_id:
segment[driver_api.SEGMENTATION_ID] = seg_id
self.driver._handle_segment_change(
mock.ANY, events.PRECOMMIT_CREATE, segments_plugin.Plugin(),
self.context, segment)
if seg_id:
# Assert it is not changed
self.assertEqual(seg_id, segment[driver_api.SEGMENTATION_ID])
else:
self.assertTrue(segment[driver_api.SEGMENTATION_ID] > 0)
return segment
def test_reserve_segment_success_with_partial_segment(self):
with self.network() as network:
self._reserve_segment(network)
def test_reserve_segment_fail_with_duplicate_param(self):
with self.network() as network:
self._reserve_segment(network, 10)
self.assertRaises(
exc.VlanIdInUse, self._reserve_segment, network, 10)
def test_reserve_segment_update_network_mtu(self):
with self.network() as network:
network_id = network['network']['id']
with mock.patch.object(
self.driver, '_get_network_mtu') as mtu:
mtu.return_value = 100
self._reserve_segment(network)
updated_network = self.driver.get_network(self.context,
network_id)
self.assertEqual(100, updated_network[driver_api.MTU])
mtu.return_value = 200
self._reserve_segment(network)
updated_network = self.driver.get_network(self.context,
network_id)
self.assertEqual(200, updated_network[driver_api.MTU])
def _test_nofity_mechanism_manager(self, event):
seg1 = {driver_api.NETWORK_TYPE: 'vlan',
driver_api.PHYSICAL_NETWORK: self.physnet,
driver_api.SEGMENTATION_ID: 1000}
seg2 = {driver_api.NETWORK_TYPE: 'vlan',
driver_api.PHYSICAL_NETWORK: self.physnet,
driver_api.SEGMENTATION_ID: 1001}
seg3 = {driver_api.NETWORK_TYPE: 'vlan',
driver_api.PHYSICAL_NETWORK: self.physnet,
driver_api.SEGMENTATION_ID: 1002}
with self.network() as network:
network = network['network']
for stale_seg in segments_db.get_network_segments(self.context.session,
network['id']):
segments_db.delete_network_segment(self.context.session,
stale_seg['id'])
for seg in [seg1, seg2, seg3]:
seg['network_id'] = network['id']
segments_db.add_network_segment(self.context, network['id'], seg)
self.net_context = None
def record_network_context(net_context):
self.net_context = net_context
with mock.patch.object(managers.MechanismManager,
'update_network_precommit',
side_effect=record_network_context):
self.driver._handle_segment_change(
mock.ANY, event, segments_plugin.Plugin(), self.context, seg1)
# Make sure the mechanism manager can get the right amount of
# segments of network
self.assertEqual(3, len(self.net_context.current[mpnet.SEGMENTS]))
def test_reserve_segment_nofity_mechanism_manager(self):
self._test_nofity_mechanism_manager(events.PRECOMMIT_CREATE)
def test_release_segment(self):
with self.network() as network:
segment = self._reserve_segment(network, 10)
segment['network_id'] = network['network']['id']
self.driver._handle_segment_change(
mock.ANY, events.PRECOMMIT_DELETE, mock.ANY,
self.context, segment)
# Check that the segment_id is not reserved
segment = self._reserve_segment(
network, segment[driver_api.SEGMENTATION_ID])
def test_release_segment_nofity_mechanism_manager(self):
self._test_nofity_mechanism_manager(events.PRECOMMIT_DELETE)
def test_prevent_delete_segment_with_tenant_port(self):
fake_owner_compute = constants.DEVICE_OWNER_COMPUTE_PREFIX + 'fake'
ml2_db.subscribe()
plugin = manager.NeutronManager.get_plugin()
with self.port(device_owner=fake_owner_compute) as port:
binding = ml2_db.get_locked_port_and_binding(self.context.session,
port['port']['id'])[1]
binding['host'] = 'host-ovs-no_filter'
mech_context = driver_context.PortContext(
plugin, self.context, port['port'],
plugin.get_network(self.context, port['port']['network_id']),
binding, None)
plugin._bind_port_if_needed(mech_context)
segment = segments_db.get_network_segments(
self.context.session, port['port']['network_id'])[0]
segment['network_id'] = port['port']['network_id']
self.assertRaises(c_exc.CallbackFailure, registry.notify,
resources.SEGMENT, events.BEFORE_DELETE,
mock.ANY,
context=self.context, segment=segment)
exist_port = self._show('ports', port['port']['id'])
self.assertEqual(port['port']['id'], exist_port['port']['id'])

View File

@ -298,8 +298,7 @@ class TestAutoScheduleSegments(test_plugin.Ml2PluginV2TestCase,
def setUp(self): def setUp(self):
super(TestAutoScheduleSegments, self).setUp() super(TestAutoScheduleSegments, self).setUp()
self.plugin = importutils.import_object('neutron.plugins.ml2.plugin.' self.plugin = self.driver
'Ml2Plugin')
self.segments_plugin = importutils.import_object( self.segments_plugin = importutils.import_object(
'neutron.services.segments.plugin.Plugin') 'neutron.services.segments.plugin.Plugin')
self.ctx = context.get_admin_context() self.ctx = context.get_admin_context()
@ -317,8 +316,8 @@ class TestAutoScheduleSegments(test_plugin.Ml2PluginV2TestCase,
seg = self.segments_plugin.create_segment( seg = self.segments_plugin.create_segment(
self.ctx, self.ctx,
{'segment': {'network_id': network_id, {'segment': {'network_id': network_id,
'physical_network': constants.ATTR_NOT_SPECIFIED, 'physical_network': 'physnet1',
'network_type': 'meh', 'network_type': 'vlan',
'segmentation_id': constants.ATTR_NOT_SPECIFIED}}) 'segmentation_id': constants.ATTR_NOT_SPECIFIED}})
return seg['id'] return seg['id']
@ -506,8 +505,7 @@ class DHCPAgentWeightSchedulerTestCase(test_plugin.Ml2PluginV2TestCase):
weight_scheduler = ( weight_scheduler = (
'neutron.scheduler.dhcp_agent_scheduler.WeightScheduler') 'neutron.scheduler.dhcp_agent_scheduler.WeightScheduler')
cfg.CONF.set_override('network_scheduler_driver', weight_scheduler) cfg.CONF.set_override('network_scheduler_driver', weight_scheduler)
self.plugin = importutils.import_object('neutron.plugins.ml2.plugin.' self.plugin = self.driver
'Ml2Plugin')
mock.patch.object( mock.patch.object(
self.plugin, 'filter_hosts_with_network_access', self.plugin, 'filter_hosts_with_network_access',
side_effect=lambda context, network_id, hosts: hosts).start() side_effect=lambda context, network_id, hosts: hosts).start()
@ -531,8 +529,8 @@ class DHCPAgentWeightSchedulerTestCase(test_plugin.Ml2PluginV2TestCase):
seg = self.segments_plugin.create_segment( seg = self.segments_plugin.create_segment(
self.ctx, self.ctx,
{'segment': {'network_id': network_id, {'segment': {'network_id': network_id,
'physical_network': constants.ATTR_NOT_SPECIFIED, 'physical_network': 'physnet1',
'network_type': 'meh', 'network_type': 'vlan',
'segmentation_id': constants.ATTR_NOT_SPECIFIED}}) 'segmentation_id': constants.ATTR_NOT_SPECIFIED}})
return seg['id'] return seg['id']