From 996bb5e218ec8bb938aad972a19594a75194d528 Mon Sep 17 00:00:00 2001 From: Rudrajit Tapadar Date: Fri, 9 Aug 2013 23:42:45 -0700 Subject: [PATCH] Multi-segment and trunk support for the Cisco N1Kv Plugin This patch adds vlan and vxlan trunk support in the Cisco N1Kv plugin. It also adds support for multi-segment networks for bridging vlan networks with vxlan networks. Change-Id: Ibecbbfbce1eb4d18ef6a1bc5250622b9ae87d39c Implements: blueprint multi-segment-and-trunk-support-cisco-nexus1000v --- .../46a0efbd8f0_cisco_n1kv_multisegm.py | 80 +++ .../plugins/cisco/common/cisco_constants.py | 17 + .../plugins/cisco/common/cisco_exceptions.py | 5 + neutron/plugins/cisco/db/n1kv_db_v2.py | 348 +++++++++++-- neutron/plugins/cisco/db/n1kv_models_v2.py | 35 +- .../plugins/cisco/extensions/n1kv_profile.py | 12 + .../cisco/extensions/network_profile.py | 6 +- .../cisco/extensions/policy_profile.py | 2 +- neutron/plugins/cisco/n1kv/n1kv_client.py | 59 ++- .../plugins/cisco/n1kv/n1kv_neutron_plugin.py | 492 +++++++++++++++++- neutron/tests/unit/cisco/n1kv/test_n1kv_db.py | 288 +++++++++- 11 files changed, 1264 insertions(+), 80 deletions(-) create mode 100644 neutron/db/migration/alembic_migrations/versions/46a0efbd8f0_cisco_n1kv_multisegm.py diff --git a/neutron/db/migration/alembic_migrations/versions/46a0efbd8f0_cisco_n1kv_multisegm.py b/neutron/db/migration/alembic_migrations/versions/46a0efbd8f0_cisco_n1kv_multisegm.py new file mode 100644 index 00000000000..317dca35ead --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/46a0efbd8f0_cisco_n1kv_multisegm.py @@ -0,0 +1,80 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""cisco_n1kv_multisegment_trunk + +Revision ID: 46a0efbd8f0 +Revises: 53bbd27ec841 +Create Date: 2013-08-20 20:44:08.711110 + +""" + +revision = '46a0efbd8f0' +down_revision = '53bbd27ec841' + +migration_for_plugins = [ + 'neutron.plugins.cisco.network_plugin.PluginV2' +] + +from alembic import op +import sqlalchemy as sa + +from neutron.db import migration + + +def upgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + + op.create_table( + 'cisco_n1kv_trunk_segments', + sa.Column('trunk_segment_id', sa.String(length=36), nullable=False), + sa.Column('segment_id', sa.String(length=36), nullable=False), + sa.Column('dot1qtag', sa.String(length=36), nullable=False), + sa.ForeignKeyConstraint(['trunk_segment_id'], ['networks.id'], + ondelete='CASCADE'), + sa.PrimaryKeyConstraint('trunk_segment_id', 'segment_id', 'dot1qtag') + ) + op.create_table( + 'cisco_n1kv_multi_segments', + sa.Column('multi_segment_id', sa.String(length=36), nullable=False), + sa.Column('segment1_id', sa.String(length=36), nullable=False), + sa.Column('segment2_id', sa.String(length=36), nullable=False), + sa.Column('encap_profile_name', sa.String(length=36), nullable=True), + sa.ForeignKeyConstraint(['multi_segment_id'], ['networks.id'], + ondelete='CASCADE'), + sa.PrimaryKeyConstraint('multi_segment_id', 'segment1_id', + 'segment2_id') + ) + op.alter_column('cisco_network_profiles', 'segment_type', + existing_type=sa.Enum('vlan', 'vxlan', 'trunk', + 'multi-segment'), + existing_nullable=False) + op.add_column('cisco_network_profiles', + sa.Column('sub_type', sa.String(length=255), nullable=True)) + + +def downgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + + op.drop_table('cisco_n1kv_trunk_segments') + op.drop_table('cisco_n1kv_multi_segments') + op.alter_column('cisco_network_profiles', 'segment_type', + existing_type=sa.Enum('vlan', 'vxlan'), + existing_nullable=False) + op.drop_column('cisco_network_profiles', 'sub_type') diff --git a/neutron/plugins/cisco/common/cisco_constants.py b/neutron/plugins/cisco/common/cisco_constants.py index 9ce2503d4ae..b64c0591b5c 100644 --- a/neutron/plugins/cisco/common/cisco_constants.py +++ b/neutron/plugins/cisco/common/cisco_constants.py @@ -77,6 +77,12 @@ NETWORK_TYPE_VLAN = 'vlan' NETWORK_TYPE_VXLAN = 'vxlan' NETWORK_TYPE_LOCAL = 'local' NETWORK_TYPE_NONE = 'none' +NETWORK_TYPE_TRUNK = 'trunk' +NETWORK_TYPE_MULTI_SEGMENT = 'multi-segment' + +# Values for network sub_type +NETWORK_SUBTYPE_TRUNK_VLAN = NETWORK_TYPE_VLAN +NETWORK_SUBTYPE_TRUNK_VXLAN = NETWORK_TYPE_VXLAN # Prefix for VM Network name VM_NETWORK_NAME_PREFIX = 'vmn_' @@ -88,3 +94,14 @@ NAME = 'name' ID = 'id' POLICY = 'policy' TENANT_ID_NOT_SET = 'TENANT_ID_NOT_SET' +ENCAPSULATIONS = 'encapsulations' +STATE = 'state' +ONLINE = 'online' +MAPPINGS = 'mappings' +MAPPING = 'mapping' +SEGMENTS = 'segments' +SEGMENT = 'segment' +BRIDGE_DOMAIN_SUFFIX = '_bd' +ENCAPSULATION_PROFILE_SUFFIX = '_profile' + +UUID_LENGTH = 36 diff --git a/neutron/plugins/cisco/common/cisco_exceptions.py b/neutron/plugins/cisco/common/cisco_exceptions.py index 12ef93e7a14..a37dd8b382e 100644 --- a/neutron/plugins/cisco/common/cisco_exceptions.py +++ b/neutron/plugins/cisco/common/cisco_exceptions.py @@ -212,3 +212,8 @@ class ProfileTenantBindingNotFound(exceptions.NotFound): """Profile to Tenant binding for given profile ID cannot be found.""" message = _("Profile-Tenant binding for profile %(profile_id)s could " "not be found.") + + +class NoClusterFound(exceptions.NotFound): + """No service cluster found to perform multi-segment bridging.""" + message = _("No service cluster found to perform multi-segment bridging.") diff --git a/neutron/plugins/cisco/db/n1kv_db_v2.py b/neutron/plugins/cisco/db/n1kv_db_v2.py index 51b7c6a77cb..6db40fff80f 100644 --- a/neutron/plugins/cisco/db/n1kv_db_v2.py +++ b/neutron/plugins/cisco/db/n1kv_db_v2.py @@ -40,6 +40,230 @@ def initialize(): db.configure_db() +def del_trunk_segment_binding(db_session, trunk_segment_id, segment_pairs): + """ + Delete a trunk network binding. + + :param db_session: database session + :param trunk_segment_id: UUID representing the trunk network + :param segment_pairs: List of segment UUIDs in pair + representing the segments that are trunked + """ + with db_session.begin(subtransactions=True): + for (segment_id, dot1qtag) in segment_pairs: + (db_session.query(n1kv_models_v2.N1kvTrunkSegmentBinding). + filter_by(trunk_segment_id=trunk_segment_id, + segment_id=segment_id, + dot1qtag=dot1qtag).delete()) + alloc = (db_session.query(n1kv_models_v2. + N1kvTrunkSegmentBinding). + filter_by(trunk_segment_id=trunk_segment_id).first()) + if not alloc: + binding = get_network_binding(db_session, trunk_segment_id) + binding.physical_network = None + + +def del_multi_segment_binding(db_session, multi_segment_id, segment_pairs): + """ + Delete a multi-segment network binding. + + :param db_session: database session + :param multi_segment_id: UUID representing the multi-segment network + :param segment_pairs: List of segment UUIDs in pair + representing the segments that are bridged + """ + with db_session.begin(subtransactions=True): + for (segment1_id, segment2_id) in segment_pairs: + (db_session.query(n1kv_models_v2. + N1kvMultiSegmentNetworkBinding).filter_by( + multi_segment_id=multi_segment_id, + segment1_id=segment1_id, + segment2_id=segment2_id).delete()) + + +def add_trunk_segment_binding(db_session, trunk_segment_id, segment_pairs): + """ + Create a trunk network binding. + + :param db_session: database session + :param trunk_segment_id: UUID representing the multi-segment network + :param segment_pairs: List of segment UUIDs in pair + representing the segments to be trunked + """ + with db_session.begin(subtransactions=True): + binding = get_network_binding(db_session, trunk_segment_id) + for (segment_id, tag) in segment_pairs: + if not binding.physical_network: + member_seg_binding = get_network_binding(db_session, + segment_id) + binding.physical_network = member_seg_binding.physical_network + trunk_segment_binding = ( + n1kv_models_v2.N1kvTrunkSegmentBinding( + trunk_segment_id=trunk_segment_id, + segment_id=segment_id, dot1qtag=tag)) + db_session.add(trunk_segment_binding) + + +def add_multi_segment_binding(db_session, multi_segment_id, segment_pairs): + """ + Create a multi-segment network binding. + + :param db_session: database session + :param multi_segment_id: UUID representing the multi-segment network + :param segment_pairs: List of segment UUIDs in pair + representing the segments to be bridged + """ + with db_session.begin(subtransactions=True): + for (segment1_id, segment2_id) in segment_pairs: + multi_segment_binding = ( + n1kv_models_v2.N1kvMultiSegmentNetworkBinding( + multi_segment_id=multi_segment_id, + segment1_id=segment1_id, + segment2_id=segment2_id)) + db_session.add(multi_segment_binding) + + +def add_multi_segment_encap_profile_name(db_session, multi_segment_id, + segment_pair, profile_name): + """ + Add the encapsulation profile name to the multi-segment network binding. + + :param db_session: database session + :param multi_segment_id: UUID representing the multi-segment network + :param segment_pair: set containing the segment UUIDs that are bridged + """ + with db_session.begin(subtransactions=True): + binding = get_multi_segment_network_binding(db_session, + multi_segment_id, + segment_pair) + binding.encap_profile_name = profile_name + + +def get_multi_segment_network_binding(db_session, + multi_segment_id, segment_pair): + """ + Retrieve multi-segment network binding. + + :param db_session: database session + :param multi_segment_id: UUID representing the trunk network whose binding + is to fetch + :param segment_pair: set containing the segment UUIDs that are bridged + :returns: binding object + """ + try: + (segment1_id, segment2_id) = segment_pair + return (db_session.query( + n1kv_models_v2.N1kvMultiSegmentNetworkBinding). + filter_by(multi_segment_id=multi_segment_id, + segment1_id=segment1_id, + segment2_id=segment2_id)).one() + except exc.NoResultFound: + raise c_exc.NetworkBindingNotFound(network_id=multi_segment_id) + + +def get_multi_segment_members(db_session, multi_segment_id): + """ + Retrieve all the member segments of a multi-segment network. + + :param db_session: database session + :param multi_segment_id: UUID representing the multi-segment network + :returns: a list of tuples representing the mapped segments + """ + with db_session.begin(subtransactions=True): + allocs = (db_session.query( + n1kv_models_v2.N1kvMultiSegmentNetworkBinding). + filter_by(multi_segment_id=multi_segment_id)) + return [(a.segment1_id, a.segment2_id) for a in allocs] + + +def get_multi_segment_encap_dict(db_session, multi_segment_id): + """ + Retrieve the encapsulation profiles for every segment pairs bridged. + + :param db_session: database session + :param multi_segment_id: UUID representing the multi-segment network + :returns: a dictionary of lists containing the segment pairs in sets + """ + with db_session.begin(subtransactions=True): + encap_dict = {} + allocs = (db_session.query( + n1kv_models_v2.N1kvMultiSegmentNetworkBinding). + filter_by(multi_segment_id=multi_segment_id)) + for alloc in allocs: + if alloc.encap_profile_name not in encap_dict: + encap_dict[alloc.encap_profile_name] = [] + seg_pair = (alloc.segment1_id, alloc.segment2_id) + encap_dict[alloc.encap_profile_name].append(seg_pair) + return encap_dict + + +def get_trunk_network_binding(db_session, trunk_segment_id, segment_pair): + """ + Retrieve trunk network binding. + + :param db_session: database session + :param trunk_segment_id: UUID representing the trunk network whose binding + is to fetch + :param segment_pair: set containing the segment_id and dot1qtag + :returns: binding object + """ + try: + (segment_id, dot1qtag) = segment_pair + return (db_session.query(n1kv_models_v2.N1kvTrunkSegmentBinding). + filter_by(trunk_segment_id=trunk_segment_id, + segment_id=segment_id, + dot1qtag=dot1qtag)).one() + except exc.NoResultFound: + raise c_exc.NetworkBindingNotFound(network_id=trunk_segment_id) + + +def get_trunk_members(db_session, trunk_segment_id): + """ + Retrieve all the member segments of a trunk network. + + :param db_session: database session + :param trunk_segment_id: UUID representing the trunk network + :returns: a list of tuples representing the segment and their + corresponding dot1qtag + """ + with db_session.begin(subtransactions=True): + allocs = (db_session.query(n1kv_models_v2.N1kvTrunkSegmentBinding). + filter_by(trunk_segment_id=trunk_segment_id)) + return [(a.segment_id, a.dot1qtag) for a in allocs] + + +def is_trunk_member(db_session, segment_id): + """ + Checks if a segment is a member of a trunk segment. + + :param db_session: database session + :param segment_id: UUID of the segment to be checked + :returns: boolean + """ + with db_session.begin(subtransactions=True): + ret = (db_session.query(n1kv_models_v2.N1kvTrunkSegmentBinding). + filter_by(segment_id=segment_id).first()) + return bool(ret) + + +def is_multi_segment_member(db_session, segment_id): + """ + Checks if a segment is a member of a multi-segment network. + + :param db_session: database session + :param segment_id: UUID of the segment to be checked + :returns: boolean + """ + with db_session.begin(subtransactions=True): + ret1 = (db_session.query( + n1kv_models_v2.N1kvMultiSegmentNetworkBinding). + filter_by(segment1_id=segment_id).first()) + ret2 = (db_session.query( + n1kv_models_v2.N1kvMultiSegmentNetworkBinding). + filter_by(segment2_id=segment_id).first()) + return bool(ret1 or ret2) + + def get_network_binding(db_session, network_id): """ Retrieve network binding. @@ -59,13 +283,14 @@ def get_network_binding(db_session, network_id): def add_network_binding(db_session, network_id, network_type, physical_network, segmentation_id, - multicast_ip, network_profile_id): + multicast_ip, network_profile_id, add_segments): """ Create network binding. :param db_session: database session :param network_id: UUID representing the network - :param network_type: string representing type of network (VLAN or VXLAN) + :param network_type: string representing type of network (VLAN, VXLAN, + MULTI_SEGMENT or TRUNK) :param physical_network: Only applicable for VLAN networks. It represents a L2 Domain :param segmentation_id: integer representing VLAN or VXLAN ID @@ -75,6 +300,8 @@ def add_network_binding(db_session, network_id, network_type, IDs. :param network_profile_id: network profile ID based on which this network is created + :param add_segments: List of segment UUIDs in pairs to be added to either a + multi-segment or trunk network """ with db_session.begin(subtransactions=True): binding = n1kv_models_v2.N1kvNetworkBinding( @@ -85,6 +312,12 @@ def add_network_binding(db_session, network_id, network_type, multicast_ip=multicast_ip, profile_id=network_profile_id) db_session.add(binding) + if add_segments is None: + pass + elif network_type == c_const.NETWORK_TYPE_MULTI_SEGMENT: + add_multi_segment_binding(db_session, network_id, add_segments) + elif network_type == c_const.NETWORK_TYPE_TRUNK: + add_trunk_segment_binding(db_session, network_id, add_segments) def get_segment_range(network_profile): @@ -262,6 +495,8 @@ def reserve_vlan(db_session, network_profile): filter(and_( n1kv_models_v2.N1kvVlanAllocation.vlan_id >= seg_min, n1kv_models_v2.N1kvVlanAllocation.vlan_id <= seg_max, + n1kv_models_v2.N1kvVlanAllocation.physical_network == + network_profile['physical_network'], n1kv_models_v2.N1kvVlanAllocation.allocated == False) )).first() if alloc: @@ -314,8 +549,9 @@ def alloc_network(db_session, network_profile_id): network_profile_id) if network_profile.segment_type == c_const.NETWORK_TYPE_VLAN: return reserve_vlan(db_session, network_profile) - else: + if network_profile.segment_type == c_const.NETWORK_TYPE_VXLAN: return reserve_vxlan(db_session, network_profile) + return (None, network_profile.segment_type, 0, "0.0.0.0") def reserve_specific_vlan(db_session, physical_network, vlan_id): @@ -601,14 +837,17 @@ def create_network_profile(db_session, network_profile): LOG.debug(_("create_network_profile()")) with db_session.begin(subtransactions=True): kwargs = {"name": network_profile["name"], - "segment_type": network_profile["segment_type"], - "segment_range": network_profile["segment_range"]} + "segment_type": network_profile["segment_type"]} if network_profile["segment_type"] == c_const.NETWORK_TYPE_VLAN: kwargs["physical_network"] = network_profile["physical_network"] + kwargs["segment_range"] = network_profile["segment_range"] elif network_profile["segment_type"] == c_const.NETWORK_TYPE_VXLAN: kwargs["multicast_ip_index"] = 0 kwargs["multicast_ip_range"] = network_profile[ "multicast_ip_range"] + kwargs["segment_range"] = network_profile["segment_range"] + elif network_profile["segment_type"] == c_const.NETWORK_TYPE_TRUNK: + kwargs["sub_type"] = network_profile["sub_type"] net_profile = n1kv_models_v2.NetworkProfile(**kwargs) db_session.add(net_profile) return net_profile @@ -659,8 +898,7 @@ def _get_network_profiles(**kwargs): if "physical_network" in kwargs: return (db_session.query(n1kv_models_v2.NetworkProfile). filter_by(physical_network=kwargs["physical_network"])) - else: - return db_session.query(n1kv_models_v2.NetworkProfile) + return db_session.query(n1kv_models_v2.NetworkProfile) def create_policy_profile(policy_profile): @@ -730,9 +968,8 @@ def _profile_binding_exists(tenant_id, profile_id, profile_type): def _get_profile_binding(tenant_id, profile_id): LOG.debug(_("_get_profile_binding")) db_session = db.get_session() - binding = db_session.query(n1kv_models_v2.ProfileBinding).filter_by( - tenant_id=tenant_id, profile_id=profile_id).one() - return binding + return (db_session.query(n1kv_models_v2.ProfileBinding).filter_by( + tenant_id=tenant_id, profile_id=profile_id).one()) def get_profile_binding(tenant_id, profile_id): @@ -773,8 +1010,7 @@ def _get_profile_bindings(profile_type=None): profile_bindings = (db_session.query(n1kv_models_v2.ProfileBinding). filter_by(profile_type=profile_type)) return profile_bindings - else: - return db_session.query(n1kv_models_v2.ProfileBinding) + return db_session.query(n1kv_models_v2.ProfileBinding) class NetworkProfile_db_mixin(object): @@ -815,6 +1051,7 @@ class NetworkProfile_db_mixin(object): res = {"id": network_profile["id"], "name": network_profile["name"], "segment_type": network_profile["segment_type"], + "sub_type": network_profile["sub_type"], "segment_range": network_profile["segment_range"], "multicast_ip_index": network_profile["multicast_ip_index"], "multicast_ip_range": network_profile["multicast_ip_range"], @@ -888,13 +1125,12 @@ class NetworkProfile_db_mixin(object): self.add_network_profile_tenant(id, p["add_tenant"]) return self._make_network_profile_dict(get_network_profile( context.session, id)) - elif context.is_admin and "remove_tenant" in p: + if context.is_admin and "remove_tenant" in p: delete_profile_binding(p["remove_tenant"], id) return self._make_network_profile_dict(get_network_profile( context.session, id)) - else: - return self._make_network_profile_dict( - update_network_profile(context.session, id, p)) + return self._make_network_profile_dict( + update_network_profile(context.session, id, p)) def get_network_profile(self, context, id, fields=None): """ @@ -930,11 +1166,10 @@ class NetworkProfile_db_mixin(object): return self._get_collection(context, n1kv_models_v2.NetworkProfile, self._make_network_profile_dict, filters=filters, fields=fields) - else: - return self._get_network_collection_for_tenant(context.session, - n1kv_models_v2. - NetworkProfile, - context.tenant_id) + return self._get_network_collection_for_tenant(context.session, + n1kv_models_v2. + NetworkProfile, + context.tenant_id) def add_network_profile_tenant(self, network_profile_id, tenant_id): """ @@ -992,19 +1227,41 @@ class NetworkProfile_db_mixin(object): :param net_p: network profile object """ - if any(net_p[arg] == "" for arg in ("segment_type", "segment_range")): - msg = _("arguments segment_type and segment_range missing" + if any(net_p[arg] == "" for arg in ["segment_type"]): + msg = _("arguments segment_type missing" " for network profile") LOG.exception(msg) raise q_exc.InvalidInput(error_message=msg) - _segment_type = net_p["segment_type"].lower() - if _segment_type not in [c_const.NETWORK_TYPE_VLAN, - c_const.NETWORK_TYPE_VXLAN]: - msg = _("segment_type should either be vlan or vxlan") + segment_type = net_p["segment_type"].lower() + if segment_type not in [c_const.NETWORK_TYPE_VLAN, + c_const.NETWORK_TYPE_VXLAN, + c_const.NETWORK_TYPE_TRUNK, + c_const.NETWORK_TYPE_MULTI_SEGMENT]: + msg = _("segment_type should either be vlan, vxlan, " + "multi-segment or trunk") LOG.exception(msg) raise q_exc.InvalidInput(error_message=msg) - self._validate_segment_range(net_p) - if _segment_type == c_const.NETWORK_TYPE_VLAN: + if segment_type == c_const.NETWORK_TYPE_VLAN: + if "physical_network" not in net_p: + msg = _("argument physical_network missing " + "for network profile") + LOG.exception(msg) + raise q_exc.InvalidInput(error_message=msg) + if segment_type == c_const.NETWORK_TYPE_TRUNK: + if "sub_type" not in net_p: + msg = _("argument sub_type missing " + "for trunk network profile") + LOG.exception(msg) + raise q_exc.InvalidInput(error_message=msg) + if segment_type in [c_const.NETWORK_TYPE_VLAN, + c_const.NETWORK_TYPE_VXLAN]: + if "segment_range" not in net_p: + msg = _("argument segment_range missing " + "for network profile") + LOG.exception(msg) + raise q_exc.InvalidInput(error_message=msg) + self._validate_segment_range(net_p) + if segment_type != c_const.NETWORK_TYPE_VXLAN: net_p["multicast_ip_range"] = "0.0.0.0" def _validate_segment_range_uniqueness(self, context, net_p): @@ -1018,28 +1275,30 @@ class NetworkProfile_db_mixin(object): if segment_type == c_const.NETWORK_TYPE_VLAN: profiles = _get_network_profiles( physical_network=net_p["physical_network"]) - elif segment_type == c_const.NETWORK_TYPE_VXLAN: - profiles = _get_network_profiles() else: - # TODO(Abhishek): Handle this when we support other segment types - return + profiles = _get_network_profiles() if profiles: - for prfl in profiles: - name = prfl.name - segment_range = prfl.segment_range + for profile in profiles: + name = profile.name + segment_range = profile.segment_range if net_p["name"] == name: msg = (_("NetworkProfile name %s already exists"), net_p["name"]) LOG.exception(msg) raise q_exc.InvalidInput(error_message=msg) + if (c_const.NETWORK_TYPE_MULTI_SEGMENT in + [profile.segment_type, net_p["segment_type"]] or + c_const.NETWORK_TYPE_TRUNK in + [profile.segment_type, net_p["segment_type"]]): + continue seg_min, seg_max = self._get_segment_range( net_p["segment_range"]) - prfl_seg_min, prfl_seg_max = self._get_segment_range( + profile_seg_min, profile_seg_max = self._get_segment_range( segment_range) - if ((prfl_seg_min <= seg_min <= prfl_seg_max) or - (prfl_seg_min <= seg_max <= prfl_seg_max) or - ((seg_min <= prfl_seg_min) and - (seg_max >= prfl_seg_max))): + if ((profile_seg_min <= seg_min <= profile_seg_max) or + (profile_seg_min <= seg_max <= profile_seg_max) or + ((seg_min <= profile_seg_min) and + (seg_max >= profile_seg_max))): msg = _("segment range overlaps with another profile") LOG.exception(msg) raise q_exc.InvalidInput(error_message=msg) @@ -1149,13 +1408,12 @@ class PolicyProfile_db_mixin(object): self.add_policy_profile_tenant(id, p["add_tenant"]) return self._make_policy_profile_dict(get_policy_profile( context.session, id)) - elif context.is_admin and "remove_tenant" in p: + if context.is_admin and "remove_tenant" in p: delete_profile_binding(p["remove_tenant"], id) return self._make_policy_profile_dict(get_policy_profile( context.session, id)) - else: - return self._make_policy_profile_dict( - update_policy_profile(context.session, id, p)) + return self._make_policy_profile_dict( + update_policy_profile(context.session, id, p)) def add_policy_profile_tenant(self, policy_profile_id, tenant_id): """ diff --git a/neutron/plugins/cisco/db/n1kv_models_v2.py b/neutron/plugins/cisco/db/n1kv_models_v2.py index 75b710a372e..98af2528134 100644 --- a/neutron/plugins/cisco/db/n1kv_models_v2.py +++ b/neutron/plugins/cisco/db/n1kv_models_v2.py @@ -15,6 +15,7 @@ # under the License. # # @author: Abhishek Raut, Cisco Systems Inc. +# @author: Rudrajit Tapadar, Cisco Systems Inc. import sqlalchemy as sa @@ -95,7 +96,8 @@ class NetworkProfile(model_base.BASEV2, models_v2.HasId): """ Nexus1000V Network Profiles - segment_type - VLAN, VXLAN + segment_type - VLAN, VXLAN, TRUNK, MULTI_SEGMENT + sub_type - TRUNK_VLAN, TRUNK_VXLAN segment_range - '-' multicast_ip_index - multicast_ip_range - '-' @@ -106,8 +108,12 @@ class NetworkProfile(model_base.BASEV2, models_v2.HasId): name = sa.Column(sa.String(255)) segment_type = sa.Column(sa.Enum(cisco_constants.NETWORK_TYPE_VLAN, cisco_constants.NETWORK_TYPE_VXLAN, + cisco_constants.NETWORK_TYPE_TRUNK, + cisco_constants. + NETWORK_TYPE_MULTI_SEGMENT, name='segment_type'), nullable=False) + sub_type = sa.Column(sa.String(255)) segment_range = sa.Column(sa.String(255)) multicast_ip_index = sa.Column(sa.Integer, default=0) multicast_ip_range = sa.Column(sa.String(255)) @@ -142,3 +148,30 @@ class ProfileBinding(model_base.BASEV2): primary_key=True, default=cisco_constants.TENANT_ID_NOT_SET) profile_id = sa.Column(sa.String(36), primary_key=True) + + +class N1kvTrunkSegmentBinding(model_base.BASEV2): + + """Represents binding of segments in trunk networks.""" + __tablename__ = 'cisco_n1kv_trunk_segments' + + trunk_segment_id = sa.Column(sa.String(36), + sa.ForeignKey('networks.id', + ondelete="CASCADE"), + primary_key=True) + segment_id = sa.Column(sa.String(36), nullable=False, primary_key=True) + dot1qtag = sa.Column(sa.String(36), nullable=False, primary_key=True) + + +class N1kvMultiSegmentNetworkBinding(model_base.BASEV2): + + """Represents binding of segments in multi-segment networks.""" + __tablename__ = 'cisco_n1kv_multi_segments' + + multi_segment_id = sa.Column(sa.String(36), + sa.ForeignKey('networks.id', + ondelete="CASCADE"), + primary_key=True) + segment1_id = sa.Column(sa.String(36), nullable=False, primary_key=True) + segment2_id = sa.Column(sa.String(36), nullable=False, primary_key=True) + encap_profile_name = sa.Column(sa.String(36)) diff --git a/neutron/plugins/cisco/extensions/n1kv_profile.py b/neutron/plugins/cisco/extensions/n1kv_profile.py index 6fa5b244faf..fdf89e2a386 100644 --- a/neutron/plugins/cisco/extensions/n1kv_profile.py +++ b/neutron/plugins/cisco/extensions/n1kv_profile.py @@ -24,6 +24,9 @@ from neutron.api.v2 import attributes PROFILE_ID = 'n1kv:profile_id' MULTICAST_IP = 'n1kv:multicast_ip' +SEGMENT_ADD = 'n1kv:segment_add' +SEGMENT_DEL = 'n1kv:segment_del' +MEMBER_SEGMENTS = 'n1kv:member_segments' EXTENDED_ATTRIBUTES_2_0 = { 'networks': { @@ -34,6 +37,15 @@ EXTENDED_ATTRIBUTES_2_0 = { MULTICAST_IP: {'allow_post': True, 'allow_put': True, 'default': attributes.ATTR_NOT_SPECIFIED, 'is_visible': True}, + SEGMENT_ADD: {'allow_post': True, 'allow_put': True, + 'default': attributes.ATTR_NOT_SPECIFIED, + 'is_visible': True}, + SEGMENT_DEL: {'allow_post': True, 'allow_put': True, + 'default': attributes.ATTR_NOT_SPECIFIED, + 'is_visible': True}, + MEMBER_SEGMENTS: {'allow_post': True, 'allow_put': True, + 'default': attributes.ATTR_NOT_SPECIFIED, + 'is_visible': True}, }, 'ports': { PROFILE_ID: {'allow_post': True, 'allow_put': True, diff --git a/neutron/plugins/cisco/extensions/network_profile.py b/neutron/plugins/cisco/extensions/network_profile.py index f52b5de5906..e21773f5fdf 100644 --- a/neutron/plugins/cisco/extensions/network_profile.py +++ b/neutron/plugins/cisco/extensions/network_profile.py @@ -16,6 +16,7 @@ # # @author: Abhishek Raut, Cisco Systems, Inc. # @author: Sergey Sudakovich, Cisco Systems, Inc. +# @author: Rudrajit Tapadar, Cisco Systems, Inc. from neutron.api import extensions from neutron.api.v2 import attributes @@ -33,6 +34,9 @@ RESOURCE_ATTRIBUTE_MAP = { 'is_visible': True, 'default': ''}, 'segment_type': {'allow_post': True, 'allow_put': True, 'is_visible': True, 'default': ''}, + 'sub_type': {'allow_post': True, 'allow_put': True, + 'is_visible': True, + 'default': attributes.ATTR_NOT_SPECIFIED}, 'segment_range': {'allow_post': True, 'allow_put': True, 'is_visible': True, 'default': ''}, 'multicast_ip_range': {'allow_post': True, 'allow_put': True, @@ -82,7 +86,7 @@ class Network_profile(extensions.ExtensionDescriptor): @classmethod def get_resources(cls): - """Returns Ext Resources.""" + """Returns Extended Resources.""" exts = [] plugin = manager.NeutronManager.get_plugin() for resource_name in ['network_profile', 'network_profile_binding']: diff --git a/neutron/plugins/cisco/extensions/policy_profile.py b/neutron/plugins/cisco/extensions/policy_profile.py index ceee221372b..af3c2508309 100644 --- a/neutron/plugins/cisco/extensions/policy_profile.py +++ b/neutron/plugins/cisco/extensions/policy_profile.py @@ -69,7 +69,7 @@ class Policy_profile(extensions.ExtensionDescriptor): @classmethod def get_resources(cls): - """Returns Ext Resources.""" + """Returns Extended Resources.""" exts = [] plugin = manager.NeutronManager.get_plugin() for resource_name in ['policy_profile', 'policy_profile_binding']: diff --git a/neutron/plugins/cisco/n1kv/n1kv_client.py b/neutron/plugins/cisco/n1kv/n1kv_client.py index 7ce976bc749..7e25aa86ea5 100644 --- a/neutron/plugins/cisco/n1kv/n1kv_client.py +++ b/neutron/plugins/cisco/n1kv/n1kv_client.py @@ -15,6 +15,7 @@ # under the License. # # @author: Abhishek Raut, Cisco Systems, Inc. +# @author: Rudrajit Tapadar, Cisco Systems, Inc. import base64 import httplib2 @@ -117,7 +118,9 @@ class Client(object): "networks": "network", "ports": "port", "set": "instance", - "subnets": "subnet" + "subnets": "subnet", + "mappings": "mapping", + "segments": "segment" } } @@ -138,6 +141,9 @@ class Client(object): logical_networks_path = "/logical-network" logical_network_path = "/logical-network/%s" events_path = "/kvm/events" + clusters_path = "/cluster" + encap_profiles_path = "/encapsulation-profile" + encap_profile_path = "/encapsulation-profile/%s" def __init__(self, **kwargs): """Initialize a new client for the plugin.""" @@ -171,7 +177,7 @@ class Client(object): :param network: network dict """ - body = {'name': network['name'] + '_bd', + body = {'name': network['name'] + c_const.BRIDGE_DOMAIN_SUFFIX, 'segmentId': network[providernet.SEGMENTATION_ID], 'groupIp': network[n1kv_profile.MULTICAST_IP], } return self._post(self.bridge_domains_path, @@ -199,7 +205,20 @@ class Client(object): if network[providernet.NETWORK_TYPE] == c_const.NETWORK_TYPE_VLAN: body['vlan'] = network[providernet.SEGMENTATION_ID] elif network[providernet.NETWORK_TYPE] == c_const.NETWORK_TYPE_VXLAN: - body['bridgeDomain'] = network['name'] + '_bd' + body['bridgeDomain'] = (network['name'] + + c_const.BRIDGE_DOMAIN_SUFFIX) + if network_profile['segment_type'] == c_const.NETWORK_TYPE_TRUNK: + body['mode'] = c_const.NETWORK_TYPE_TRUNK + body['segmentType'] = network_profile['sub_type'] + if network_profile['sub_type'] == c_const.NETWORK_TYPE_VLAN: + body['addSegments'] = network['add_segment_list'] + body['delSegments'] = network['del_segment_list'] + else: + body['encapProfile'] = (network['name'] + + c_const.ENCAPSULATION_PROFILE_SUFFIX) + else: + body['mode'] = 'access' + body['segmentType'] = network_profile['segment_type'] return self._post(self.network_segments_path, body=body) @@ -498,3 +517,37 @@ class Client(object): auth = base64.encodestring("%s:%s" % (username, password)) header = {"Authorization": "Basic %s" % auth} return header + + def get_clusters(self): + """Fetches a list of all vxlan gateway clusters.""" + return self._get(self.clusters_path) + + def create_encapsulation_profile(self, encap): + """ + Create an encapsulation profile on VSM. + + :param encap: encapsulation dict + """ + body = {'name': encap['name'], + 'addMappings': encap['add_segment_list'], + 'delMappings': encap['del_segment_list']} + return self._post(self.encap_profiles_path, + body=body) + + def update_encapsulation_profile(self, context, profile_name, body): + """ + Adds a vlan to bridge-domain mapping to an encapsulation profile. + + :param profile_name: Name of the encapsulation profile + :param body: mapping dictionary + """ + return self._post(self.encap_profile_path + % (profile_name), body=body) + + def delete_encapsulation_profile(self, name): + """ + Delete an encapsulation profile on VSM. + + :param name: name of the encapsulation profile to be deleted + """ + return self._delete(self.encap_profile_path % (name)) diff --git a/neutron/plugins/cisco/n1kv/n1kv_neutron_plugin.py b/neutron/plugins/cisco/n1kv/n1kv_neutron_plugin.py index 5af9b2ea567..b3cb55f80cb 100644 --- a/neutron/plugins/cisco/n1kv/n1kv_neutron_plugin.py +++ b/neutron/plugins/cisco/n1kv/n1kv_neutron_plugin.py @@ -30,6 +30,7 @@ from neutron.api.v2 import attributes from neutron.common import exceptions as q_exc from neutron.common import rpc as q_rpc from neutron.common import topics +from neutron.common import utils from neutron.db import agents_db from neutron.db import agentschedulers_db from neutron.db import db_base_plugin_v2 @@ -41,6 +42,7 @@ from neutron.extensions import providernet from neutron.openstack.common import log as logging from neutron.openstack.common import rpc from neutron.openstack.common.rpc import proxy +from neutron.openstack.common import uuidutils as uuidutils from neutron.plugins.cisco.common import cisco_constants as c_const from neutron.plugins.cisco.common import cisco_credentials_v2 as c_cred from neutron.plugins.cisco.common import cisco_exceptions @@ -281,6 +283,14 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, elif binding.network_type == c_const.NETWORK_TYPE_VLAN: network[providernet.PHYSICAL_NETWORK] = binding.physical_network network[providernet.SEGMENTATION_ID] = binding.segmentation_id + elif binding.network_type == c_const.NETWORK_TYPE_TRUNK: + network[providernet.PHYSICAL_NETWORK] = binding.physical_network + network[providernet.SEGMENTATION_ID] = None + network[n1kv_profile.MULTICAST_IP] = None + elif binding.network_type == c_const.NETWORK_TYPE_MULTI_SEGMENT: + network[providernet.PHYSICAL_NETWORK] = None + network[providernet.SEGMENTATION_ID] = None + network[n1kv_profile.MULTICAST_IP] = None def _process_provider_create(self, context, attrs): network_type = attrs.get(providernet.NETWORK_TYPE) @@ -356,6 +366,283 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, msg = _("plugin does not support updating provider attributes") raise q_exc.InvalidInput(error_message=msg) + def _get_cluster(self, segment1, segment2, clusters): + """ + Returns a cluster to apply the segment mapping + + :param segment1: UUID of segment to be mapped + :param segment2: UUID of segment to be mapped + :param clusters: List of clusters + """ + for cluster in sorted(clusters, key=lambda k: k['size']): + for mapping in cluster[c_const.MAPPINGS]: + for segment in mapping[c_const.SEGMENTS]: + if segment1 in segment or segment2 in segment: + break + else: + cluster['size'] += 2 + return cluster['encapProfileName'] + break + return + + def _extend_mapping_dict(self, context, mapping_dict, segment): + """ + Extends a mapping dictionary by populating dot1q tag and + bridge-domain name. + + :param context: neutron api request context + :param mapping_dict: dictionary to populate values + :param segment: id of the segment being populated + """ + net = self.get_network(context, segment) + if net[providernet.NETWORK_TYPE] == c_const.NETWORK_TYPE_VLAN: + mapping_dict['dot1q'] = str(net[providernet.SEGMENTATION_ID]) + else: + mapping_dict['bridgeDomain'] = (net['name'] + + c_const.BRIDGE_DOMAIN_SUFFIX) + + def _send_add_multi_segment_request(self, context, net_id, segment_pairs): + """ + Send Add multi-segment network request to VSM. + + :param context: neutron api request context + :param net_id: UUID of the multi-segment network + :param segment_pairs: List of segments in UUID pairs + that need to be bridged + """ + + if not segment_pairs: + return + + session = context.session + n1kvclient = n1kv_client.Client() + clusters = n1kvclient.get_clusters() + online_clusters = [] + encap_dict = {} + for cluster in clusters['body'][c_const.SET]: + cluster = cluster[c_const.PROPERTIES] + if cluster[c_const.STATE] == c_const.ONLINE: + cluster['size'] = 0 + for mapping in cluster[c_const.MAPPINGS]: + cluster['size'] += ( + len(mapping[c_const.SEGMENTS])) + online_clusters.append(cluster) + for (segment1, segment2) in segment_pairs: + encap_profile = self._get_cluster(segment1, segment2, + online_clusters) + if encap_profile is not None: + if encap_profile in encap_dict: + profile_dict = encap_dict[encap_profile] + else: + profile_dict = {'name': encap_profile, + 'addMappings': [], + 'delMappings': []} + encap_dict[encap_profile] = profile_dict + mapping_dict = {} + self._extend_mapping_dict(context, + mapping_dict, segment1) + self._extend_mapping_dict(context, + mapping_dict, segment2) + profile_dict['addMappings'].append(mapping_dict) + n1kv_db_v2.add_multi_segment_encap_profile_name(session, + net_id, + (segment1, + segment2), + encap_profile) + else: + raise cisco_exceptions.NoClusterFound + + for profile in encap_dict: + n1kvclient.update_encapsulation_profile(context, profile, + encap_dict[profile]) + + def _send_del_multi_segment_request(self, context, net_id, segment_pairs): + """ + Send Delete multi-segment network request to VSM. + + :param context: neutron api request context + :param net_id: UUID of the multi-segment network + :param segment_pairs: List of segments in UUID pairs + whose bridging needs to be removed + """ + if not segment_pairs: + return + session = context.session + encap_dict = {} + n1kvclient = n1kv_client.Client() + for (segment1, segment2) in segment_pairs: + binding = ( + n1kv_db_v2.get_multi_segment_network_binding(session, net_id, + (segment1, + segment2))) + encap_profile = binding['encap_profile_name'] + if encap_profile in encap_dict: + profile_dict = encap_dict[encap_profile] + else: + profile_dict = {'name': encap_profile, + 'addMappings': [], + 'delMappings': []} + encap_dict[encap_profile] = profile_dict + mapping_dict = {} + self._extend_mapping_dict(context, + mapping_dict, segment1) + self._extend_mapping_dict(context, + mapping_dict, segment2) + profile_dict['delMappings'].append(mapping_dict) + + for profile in encap_dict: + n1kvclient.update_encapsulation_profile(context, profile, + encap_dict[profile]) + + def _get_encap_segments(self, context, segment_pairs): + """ + Get the list of segments in encapsulation profile format. + + :param context: neutron api request context + :param segment_pairs: List of segments that need to be bridged + """ + member_list = [] + for pair in segment_pairs: + (segment, dot1qtag) = pair + member_dict = {} + net = self.get_network(context, segment) + member_dict['bridgeDomain'] = (net['name'] + + c_const.BRIDGE_DOMAIN_SUFFIX) + member_dict['dot1q'] = dot1qtag + member_list.append(member_dict) + return member_list + + def _populate_member_segments(self, context, network, segment_pairs, oper): + """ + Populate trunk network dict with member segments. + + :param context: neutron api request context + :param network: Dictionary containing the trunk network information + :param segment_pairs: List of segments in UUID pairs + that needs to be trunked + :param oper: Operation to be performed + """ + LOG.debug(_('_populate_member_segments %s'), segment_pairs) + trunk_list = [] + for (segment, dot1qtag) in segment_pairs: + net = self.get_network(context, segment) + member_dict = {'segment': net['name'], + 'dot1qtag': dot1qtag} + trunk_list.append(member_dict) + if oper == n1kv_profile.SEGMENT_ADD: + network['add_segment_list'] = trunk_list + elif oper == n1kv_profile.SEGMENT_DEL: + network['del_segment_list'] = trunk_list + + def _parse_multi_segments(self, context, attrs, param): + """ + Parse the multi-segment network attributes + + :param context: neutron api request context + :param attrs: Attributes of the network + :param param: Additional parameter indicating an add + or del operation + :returns: List of segment UUIDs in set pairs + """ + pair_list = [] + valid_seg_types = [c_const.NETWORK_TYPE_VLAN, + c_const.NETWORK_TYPE_VXLAN] + segments = attrs.get(param) + if not attributes.is_attr_set(segments): + return pair_list + for pair in segments.split(','): + segment1, sep, segment2 = pair.partition(':') + if (uuidutils.is_uuid_like(segment1) and + uuidutils.is_uuid_like(segment2)): + binding1 = n1kv_db_v2.get_network_binding(context.session, + segment1) + binding2 = n1kv_db_v2.get_network_binding(context.session, + segment2) + if (binding1.network_type not in valid_seg_types or + binding2.network_type not in valid_seg_types or + binding1.network_type == binding2.network_type): + msg = _("Invalid pairing supplied") + raise q_exc.InvalidInput(error_message=msg) + else: + pair_list.append((segment1, segment2)) + else: + LOG.debug(_('Invalid UUID supplied in %s'), pair) + msg = _("Invalid UUID supplied") + raise q_exc.InvalidInput(error_message=msg) + return pair_list + + def _parse_trunk_segments(self, context, attrs, param, physical_network, + sub_type): + """ + Parse the trunk network attributes + + :param context: neutron api request context + :param attrs: Attributes of the network + :param param: Additional parameter indicating an add + or del operation + :param physical_network: Physical network of the trunk segment + :param sub_type: Sub-type of the trunk segment + :returns: List of segment UUIDs and dot1qtag (for vxlan) in set pairs + """ + pair_list = [] + segments = attrs.get(param) + if not attributes.is_attr_set(segments): + return pair_list + for pair in segments.split(','): + segment, sep, dot1qtag = pair.partition(':') + if sub_type == c_const.NETWORK_TYPE_VLAN: + dot1qtag = '' + if uuidutils.is_uuid_like(segment): + binding = n1kv_db_v2.get_network_binding(context.session, + segment) + if binding.network_type == c_const.NETWORK_TYPE_TRUNK: + msg = _("Cannot add a trunk segment '%s' as a member of " + "another trunk segment") % segment + raise q_exc.InvalidInput(error_message=msg) + elif binding.network_type == c_const.NETWORK_TYPE_VLAN: + if sub_type == c_const.NETWORK_TYPE_VXLAN: + msg = _("Cannot add vlan segment '%s' as a member of " + "a vxlan trunk segment") % segment + raise q_exc.InvalidInput(error_message=msg) + if not physical_network: + physical_network = binding.physical_network + elif physical_network != binding.physical_network: + msg = _("Network UUID '%s' belongs to a different " + "physical network") % segment + raise q_exc.InvalidInput(error_message=msg) + elif binding.network_type == c_const.NETWORK_TYPE_VXLAN: + if sub_type == c_const.NETWORK_TYPE_VLAN: + msg = _("Cannot add vxlan segment '%s' as a member of " + "a vlan trunk segment") % segment + raise q_exc.InvalidInput(error_message=msg) + try: + if not utils.is_valid_vlan_tag(int(dot1qtag)): + msg = _("Vlan tag '%s' is out of range") % dot1qtag + raise q_exc.InvalidInput(error_message=msg) + except ValueError: + msg = _("Vlan tag '%s' is not an integer " + "value") % dot1qtag + raise q_exc.InvalidInput(error_message=msg) + pair_list.append((segment, dot1qtag)) + else: + LOG.debug(_('%s is not a valid uuid'), segment) + msg = _("'%s' is not a valid UUID") % segment + raise q_exc.InvalidInput(error_message=msg) + return pair_list + + def _extend_network_dict_member_segments(self, context, network): + """Add the extended parameter member segments to the network.""" + members = [] + binding = n1kv_db_v2.get_network_binding(context.session, + network['id']) + if binding.network_type == c_const.NETWORK_TYPE_TRUNK: + members = n1kv_db_v2.get_trunk_members(context.session, + network['id']) + elif binding.network_type == c_const.NETWORK_TYPE_MULTI_SEGMENT: + members = n1kv_db_v2.get_multi_segment_members(context.session, + network['id']) + network[n1kv_profile.MEMBER_SEGMENTS] = members + def _extend_network_dict_profile(self, context, network): """Add the extended parameter network profile to the network.""" binding = n1kv_db_v2.get_network_binding(context.session, @@ -435,13 +722,15 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, n1kvclient = n1kv_client.Client() n1kvclient.delete_network_segment_pool(profile['name']) - def _send_create_network_request(self, context, network): + def _send_create_network_request(self, context, network, segment_pairs): """ Send create network request to VSM. Create a bridge domain for network of type VXLAN. :param context: neutron api request context :param network: network dictionary + :param segment_pairs: List of segments in UUID pairs + that need to be bridged """ LOG.debug(_('_send_create_network_request: %s'), network['id']) profile = self.get_network_profile(context, @@ -449,36 +738,110 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, n1kvclient = n1kv_client.Client() if network[providernet.NETWORK_TYPE] == c_const.NETWORK_TYPE_VXLAN: n1kvclient.create_bridge_domain(network) + if network[providernet.NETWORK_TYPE] == c_const.NETWORK_TYPE_TRUNK: + self._populate_member_segments(context, network, segment_pairs, + n1kv_profile.SEGMENT_ADD) + network['del_segment_list'] = [] + if profile['sub_type'] == c_const.NETWORK_TYPE_VXLAN: + encap_dict = {'name': (network['name'] + + c_const.ENCAPSULATION_PROFILE_SUFFIX), + 'add_segment_list': ( + self._get_encap_segments(context, + segment_pairs)), + 'del_segment_list': []} + n1kvclient.create_encapsulation_profile(encap_dict) n1kvclient.create_network_segment(network, profile) - def _send_update_network_request(self, db_session, network): + def _send_update_network_request(self, context, network, add_segments, + del_segments): """ Send update network request to VSM. + :param context: neutron api request context :param network: network dictionary + :param add_segments: List of segments bindings + that need to be deleted + :param del_segments: List of segments bindings + that need to be deleted """ LOG.debug(_('_send_update_network_request: %s'), network['id']) + db_session = context.session profile = n1kv_db_v2.get_network_profile( db_session, network[n1kv_profile.PROFILE_ID]) + n1kvclient = n1kv_client.Client() body = {'name': network['name'], 'id': network['id'], 'networkDefinition': profile['name'], - 'vlan': network[providernet.SEGMENTATION_ID]} - n1kvclient = n1kv_client.Client() + 'vlan': network[providernet.SEGMENTATION_ID], + 'mode': 'access', + 'segmentType': profile['segment_type'], + 'addSegments': [], + 'delSegments': []} + if network[providernet.NETWORK_TYPE] == c_const.NETWORK_TYPE_TRUNK: + self._populate_member_segments(context, network, add_segments, + n1kv_profile.SEGMENT_ADD) + self._populate_member_segments(context, network, del_segments, + n1kv_profile.SEGMENT_DEL) + body['mode'] = c_const.NETWORK_TYPE_TRUNK + body['segmentType'] = profile['sub_type'] + body['addSegments'] = network['add_segment_list'] + body['delSegments'] = network['del_segment_list'] + LOG.debug(_('add_segments=%s'), body['addSegments']) + LOG.debug(_('del_segments=%s'), body['delSegments']) + if profile['sub_type'] == c_const.NETWORK_TYPE_VXLAN: + encap_profile = (network['name'] + + c_const.ENCAPSULATION_PROFILE_SUFFIX) + encap_dict = {'name': encap_profile, + 'addMappings': ( + self._get_encap_segments(context, + add_segments)), + 'delMappings': ( + self._get_encap_segments(context, + del_segments))} + n1kvclient.update_encapsulation_profile(context, encap_profile, + encap_dict) n1kvclient.update_network_segment(network['name'], body) - def _send_delete_network_request(self, network): + def _send_delete_network_request(self, context, network): """ Send delete network request to VSM. Delete bridge domain if network is of type VXLAN. + Delete encapsulation profile if network is of type VXLAN Trunk. + :param context: neutron api request context :param network: network dictionary """ LOG.debug(_('_send_delete_network_request: %s'), network['id']) n1kvclient = n1kv_client.Client() + session = context.session if network[providernet.NETWORK_TYPE] == c_const.NETWORK_TYPE_VXLAN: - name = network['name'] + '_bd' + name = network['name'] + c_const.BRIDGE_DOMAIN_SUFFIX n1kvclient.delete_bridge_domain(name) + elif network[providernet.NETWORK_TYPE] == c_const.NETWORK_TYPE_TRUNK: + profile = self.get_network_profile( + context, network[n1kv_profile.PROFILE_ID]) + if profile['sub_type'] == c_const.NETWORK_TYPE_VXLAN: + profile_name = (network['name'] + + c_const.ENCAPSULATION_PROFILE_SUFFIX) + n1kvclient.delete_encapsulation_profile(profile_name) + elif (network[providernet.NETWORK_TYPE] == + c_const.NETWORK_TYPE_MULTI_SEGMENT): + encap_dict = n1kv_db_v2.get_multi_segment_encap_dict(session, + network['id']) + for profile in encap_dict: + profile_dict = {'name': profile, + 'addSegments': [], + 'delSegments': []} + for segment_pair in encap_dict[profile]: + mapping_dict = {} + (segment1, segment2) = segment_pair + self._extend_mapping_dict(context, + mapping_dict, segment1) + self._extend_mapping_dict(context, + mapping_dict, segment2) + profile_dict['delSegments'].append(mapping_dict) + n1kvclient.update_encapsulation_profile(context, profile, + profile_dict) n1kvclient.delete_network_segment(network['name']) def _send_create_subnet_request(self, context, subnet): @@ -616,6 +979,7 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, network['network']) self._add_dummy_profile_only_if_testing(network) profile_id = self._process_network_profile(context, network['network']) + segment_pairs = None LOG.debug(_('create network: profile_id=%s'), profile_id) session = context.session with session.begin(subtransactions=True): @@ -632,8 +996,24 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, 'net_type': network_type, 'seg_id': segmentation_id, 'multicast_ip': multicast_ip}) - if not segmentation_id: - raise q_exc.TenantNetworksDisabled() + if network_type == c_const.NETWORK_TYPE_MULTI_SEGMENT: + segment_pairs = ( + self._parse_multi_segments(context, network['network'], + n1kv_profile.SEGMENT_ADD)) + LOG.debug(_('seg list %s '), segment_pairs) + elif network_type == c_const.NETWORK_TYPE_TRUNK: + network_profile = self.get_network_profile(context, + profile_id) + segment_pairs = ( + self._parse_trunk_segments(context, network['network'], + n1kv_profile.SEGMENT_ADD, + physical_network, + network_profile['sub_type'] + )) + LOG.debug(_('seg list %s '), segment_pairs) + else: + if not segmentation_id: + raise q_exc.TenantNetworksDisabled() else: # provider network if network_type == c_const.NETWORK_TYPE_VLAN: @@ -655,13 +1035,19 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, physical_network, segmentation_id, multicast_ip, - profile_id) + profile_id, + segment_pairs) self._process_l3_create(context, net, network['network']) self._extend_network_dict_provider(context, net) self._extend_network_dict_profile(context, net) try: - self._send_create_network_request(context, net) + if network_type == c_const.NETWORK_TYPE_MULTI_SEGMENT: + self._send_add_multi_segment_request(context, net['id'], + segment_pairs) + else: + self._send_create_network_request(context, net, segment_pairs) + # note - exception will rollback entire transaction except(cisco_exceptions.VSMError, cisco_exceptions.VSMConnectionFailed): super(N1kvNeutronPluginV2, self).delete_network(context, net['id']) @@ -679,15 +1065,52 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, :returns: updated network object """ self._check_provider_update(context, network['network']) + add_segments = [] + del_segments = [] session = context.session with session.begin(subtransactions=True): net = super(N1kvNeutronPluginV2, self).update_network(context, id, network) self._process_l3_update(context, net, network['network']) + binding = n1kv_db_v2.get_network_binding(session, id) + if binding.network_type == c_const.NETWORK_TYPE_MULTI_SEGMENT: + add_segments = ( + self._parse_multi_segments(context, network['network'], + n1kv_profile.SEGMENT_ADD)) + n1kv_db_v2.add_multi_segment_binding(session, + net['id'], add_segments) + del_segments = ( + self._parse_multi_segments(context, network['network'], + n1kv_profile.SEGMENT_DEL)) + self._send_add_multi_segment_request(context, net['id'], + add_segments) + self._send_del_multi_segment_request(context, net['id'], + del_segments) + n1kv_db_v2.del_multi_segment_binding(session, + net['id'], del_segments) + elif binding.network_type == c_const.NETWORK_TYPE_TRUNK: + network_profile = self.get_network_profile(context, + binding.profile_id) + add_segments = ( + self._parse_trunk_segments(context, network['network'], + n1kv_profile.SEGMENT_ADD, + binding.physical_network, + network_profile['sub_type'])) + n1kv_db_v2.add_trunk_segment_binding(session, + net['id'], add_segments) + del_segments = ( + self._parse_trunk_segments(context, network['network'], + n1kv_profile.SEGMENT_DEL, + binding.physical_network, + network_profile['sub_type'])) + n1kv_db_v2.del_trunk_segment_binding(session, + net['id'], del_segments) self._extend_network_dict_provider(context, net) self._extend_network_dict_profile(context, net) - self._send_update_network_request(context.session, net) + if binding.network_type not in [c_const.NETWORK_TYPE_MULTI_SEGMENT]: + self._send_update_network_request(context, net, add_segments, + del_segments) LOG.debug(_("Updated network: %s"), net['id']) return net @@ -702,6 +1125,20 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, with session.begin(subtransactions=True): binding = n1kv_db_v2.get_network_binding(session, id) network = self.get_network(context, id) + if n1kv_db_v2.is_trunk_member(session, id): + msg = _("Cannot delete a network " + "that is a member of a trunk segment") + raise q_exc.InvalidInput(error_message=msg) + if n1kv_db_v2.is_multi_segment_member(session, id): + msg = _("Cannot delete a network " + "that is a member of a multi-segment network") + raise q_exc.InvalidInput(error_message=msg) + if self.agent_vsm: + try: + self._send_delete_network_request(context, network) + except(cisco_exceptions.VSMError, + cisco_exceptions.VSMConnectionFailed): + LOG.debug(_('Delete failed in VSM')) super(N1kvNeutronPluginV2, self).delete_network(context, id) if binding.network_type == c_const.NETWORK_TYPE_VXLAN: n1kv_db_v2.release_vxlan(session, binding.segmentation_id, @@ -712,8 +1149,6 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, self.network_vlan_ranges) # the network_binding record is deleted via cascade from # the network record, so explicit removal is not necessary - if self.agent_vsm: - self._send_delete_network_request(network) LOG.debug(_("Deleted network: %s"), id) def get_network(self, context, id, fields=None): @@ -728,6 +1163,7 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, net = super(N1kvNeutronPluginV2, self).get_network(context, id, None) self._extend_network_dict_provider(context, net) self._extend_network_dict_profile(context, net) + self._extend_network_dict_member_segments(context, net) return self._fields(net, fields) def get_networks(self, context, filters=None, fields=None): @@ -969,19 +1405,20 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, _network_profile = super( N1kvNeutronPluginV2, self).create_network_profile(context, network_profile) - seg_min, seg_max = self._get_segment_range( - _network_profile['segment_range']) - if _network_profile['segment_type'] == c_const.NETWORK_TYPE_VLAN: - self._add_network_vlan_range(_network_profile['physical_network'], - int(seg_min), - int(seg_max)) - n1kv_db_v2.sync_vlan_allocations(context.session, - self.network_vlan_ranges) - elif _network_profile['segment_type'] == c_const.NETWORK_TYPE_VXLAN: - self.vxlan_id_ranges = [] - self.vxlan_id_ranges.append((int(seg_min), int(seg_max))) - n1kv_db_v2.sync_vxlan_allocations(context.session, - self.vxlan_id_ranges) + if _network_profile['segment_type'] in [c_const.NETWORK_TYPE_VLAN, + c_const.NETWORK_TYPE_VXLAN]: + seg_min, seg_max = self._get_segment_range( + _network_profile['segment_range']) + if _network_profile['segment_type'] == c_const.NETWORK_TYPE_VLAN: + self._add_network_vlan_range( + _network_profile['physical_network'], int(seg_min), + int(seg_max)) + n1kv_db_v2.sync_vlan_allocations(context.session, + self.network_vlan_ranges) + else: + self.vxlan_id_ranges = [(int(seg_min), int(seg_max))] + n1kv_db_v2.sync_vxlan_allocations(context.session, + self.vxlan_id_ranges) try: self._send_create_logical_network_request(_network_profile) except(cisco_exceptions.VSMError, @@ -1018,8 +1455,7 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, n1kv_db_v2.delete_vlan_allocations(context.session, self.network_vlan_ranges) elif _network_profile['segment_type'] == c_const.NETWORK_TYPE_VXLAN: - self.delete_vxlan_ranges = [] - self.delete_vxlan_ranges.append((int(seg_min), int(seg_max))) + self.delete_vxlan_ranges = [(int(seg_min), int(seg_max))] n1kv_db_v2.delete_vxlan_allocations(context.session, self.delete_vxlan_ranges) self._send_delete_network_profile_request(_network_profile) diff --git a/neutron/tests/unit/cisco/n1kv/test_n1kv_db.py b/neutron/tests/unit/cisco/n1kv/test_n1kv_db.py index a1855a20050..35d291c30f7 100644 --- a/neutron/tests/unit/cisco/n1kv/test_n1kv_db.py +++ b/neutron/tests/unit/cisco/n1kv/test_n1kv_db.py @@ -16,6 +16,7 @@ # # @author: Juergen Brendel, Cisco Systems Inc. # @author: Abhishek Raut, Cisco Systems Inc. +# @author: Rudrajit Tapadar, Cisco Systems Inc. from sqlalchemy.orm import exc as s_exc from testtools import matchers @@ -48,6 +49,8 @@ SEGMENT_RANGE_MIN_OVERLAP = '210-230' SEGMENT_RANGE_MAX_OVERLAP = '190-209' SEGMENT_RANGE_OVERLAP = '190-230' TEST_NETWORK_ID = 'abcdefghijklmnopqrstuvwxyz' +TEST_NETWORK_ID2 = 'abcdefghijklmnopqrstuvwxy2' +TEST_NETWORK_ID3 = 'abcdefghijklmnopqrstuvwxy3' TEST_NETWORK_PROFILE = {'name': 'test_profile', 'segment_type': 'vlan', 'physical_network': 'physnet1', @@ -62,6 +65,14 @@ TEST_NETWORK_PROFILE_VXLAN = {'name': 'test_profile', 'multicast_ip_range': '239.0.0.70-239.0.0.80'} TEST_POLICY_PROFILE = {'id': '4a417990-76fb-11e2-bcfd-0800200c9a66', 'name': 'test_policy_profile'} +TEST_NETWORK_PROFILE_MULTI_SEGMENT = {'name': 'test_profile', + 'segment_type': 'multi-segment'} +TEST_NETWORK_PROFILE_VLAN_TRUNK = {'name': 'test_profile', + 'segment_type': 'trunk', + 'sub_type': 'vlan'} +TEST_NETWORK_PROFILE_VXLAN_TRUNK = {'name': 'test_profile', + 'segment_type': 'trunk', + 'sub_type': 'vxlan'} def _create_test_network_profile_if_not_there(session, @@ -398,7 +409,7 @@ class NetworkBindingsTest(test_plugin.NeutronDbPluginV2TestCase): p = _create_test_network_profile_if_not_there(self.session) n1kv_db_v2.add_network_binding( self.session, TEST_NETWORK_ID, 'vlan', - PHYS_NET, 1234, '0.0.0.0', p.id) + PHYS_NET, 1234, '0.0.0.0', p.id, None) binding = n1kv_db_v2.get_network_binding( self.session, TEST_NETWORK_ID) self.assertIsNotNone(binding) @@ -407,6 +418,224 @@ class NetworkBindingsTest(test_plugin.NeutronDbPluginV2TestCase): self.assertEqual(binding.physical_network, PHYS_NET) self.assertEqual(binding.segmentation_id, 1234) + def test_create_multi_segment_network(self): + with self.network() as network: + TEST_NETWORK_ID = network['network']['id'] + + self.assertRaises(c_exc.NetworkBindingNotFound, + n1kv_db_v2.get_network_binding, + self.session, + TEST_NETWORK_ID) + + p = _create_test_network_profile_if_not_there( + self.session, + TEST_NETWORK_PROFILE_MULTI_SEGMENT) + n1kv_db_v2.add_network_binding( + self.session, TEST_NETWORK_ID, 'multi-segment', + None, 0, '0.0.0.0', p.id, None) + binding = n1kv_db_v2.get_network_binding( + self.session, TEST_NETWORK_ID) + self.assertIsNotNone(binding) + self.assertEqual(binding.network_id, TEST_NETWORK_ID) + self.assertEqual(binding.network_type, 'multi-segment') + self.assertIsNone(binding.physical_network) + self.assertEqual(binding.segmentation_id, 0) + + def test_add_multi_segment_binding(self): + with self.network() as network: + TEST_NETWORK_ID = network['network']['id'] + + self.assertRaises(c_exc.NetworkBindingNotFound, + n1kv_db_v2.get_network_binding, + self.session, + TEST_NETWORK_ID) + + p = _create_test_network_profile_if_not_there( + self.session, + TEST_NETWORK_PROFILE_MULTI_SEGMENT) + n1kv_db_v2.add_network_binding( + self.session, TEST_NETWORK_ID, 'multi-segment', + None, 0, '0.0.0.0', p.id, + [(TEST_NETWORK_ID2, TEST_NETWORK_ID3)]) + binding = n1kv_db_v2.get_network_binding( + self.session, TEST_NETWORK_ID) + self.assertIsNotNone(binding) + self.assertEqual(binding.network_id, TEST_NETWORK_ID) + self.assertEqual(binding.network_type, 'multi-segment') + self.assertIsNone(binding.physical_network) + self.assertEqual(binding.segmentation_id, 0) + ms_binding = (n1kv_db_v2.get_multi_segment_network_binding( + self.session, TEST_NETWORK_ID, + (TEST_NETWORK_ID2, TEST_NETWORK_ID3))) + self.assertIsNotNone(ms_binding) + self.assertEqual(ms_binding.multi_segment_id, TEST_NETWORK_ID) + self.assertEqual(ms_binding.segment1_id, TEST_NETWORK_ID2) + self.assertEqual(ms_binding.segment2_id, TEST_NETWORK_ID3) + ms_members = (n1kv_db_v2.get_multi_segment_members( + self.session, TEST_NETWORK_ID)) + self.assertEqual(ms_members, + [(TEST_NETWORK_ID2, TEST_NETWORK_ID3)]) + self.assertTrue(n1kv_db_v2.is_multi_segment_member( + self.session, TEST_NETWORK_ID2)) + self.assertTrue(n1kv_db_v2.is_multi_segment_member( + self.session, TEST_NETWORK_ID3)) + n1kv_db_v2.del_multi_segment_binding( + self.session, TEST_NETWORK_ID, + [(TEST_NETWORK_ID2, TEST_NETWORK_ID3)]) + ms_members = (n1kv_db_v2.get_multi_segment_members( + self.session, TEST_NETWORK_ID)) + self.assertEqual(ms_members, []) + + def test_create_vlan_trunk_network(self): + with self.network() as network: + TEST_NETWORK_ID = network['network']['id'] + + self.assertRaises(c_exc.NetworkBindingNotFound, + n1kv_db_v2.get_network_binding, + self.session, + TEST_NETWORK_ID) + + p = _create_test_network_profile_if_not_there( + self.session, + TEST_NETWORK_PROFILE_VLAN_TRUNK) + n1kv_db_v2.add_network_binding( + self.session, TEST_NETWORK_ID, 'trunk', + None, 0, '0.0.0.0', p.id, None) + binding = n1kv_db_v2.get_network_binding( + self.session, TEST_NETWORK_ID) + self.assertIsNotNone(binding) + self.assertEqual(binding.network_id, TEST_NETWORK_ID) + self.assertEqual(binding.network_type, 'trunk') + self.assertIsNone(binding.physical_network) + self.assertEqual(binding.segmentation_id, 0) + + def test_create_vxlan_trunk_network(self): + with self.network() as network: + TEST_NETWORK_ID = network['network']['id'] + + self.assertRaises(c_exc.NetworkBindingNotFound, + n1kv_db_v2.get_network_binding, + self.session, + TEST_NETWORK_ID) + + p = _create_test_network_profile_if_not_there( + self.session, + TEST_NETWORK_PROFILE_VXLAN_TRUNK) + n1kv_db_v2.add_network_binding( + self.session, TEST_NETWORK_ID, 'trunk', + None, 0, '0.0.0.0', p.id, None) + binding = n1kv_db_v2.get_network_binding( + self.session, TEST_NETWORK_ID) + self.assertIsNotNone(binding) + self.assertEqual(binding.network_id, TEST_NETWORK_ID) + self.assertEqual(binding.network_type, 'trunk') + self.assertIsNone(binding.physical_network) + self.assertEqual(binding.segmentation_id, 0) + + def test_add_vlan_trunk_binding(self): + with self.network() as network1: + with self.network() as network2: + TEST_NETWORK_ID = network1['network']['id'] + + self.assertRaises(c_exc.NetworkBindingNotFound, + n1kv_db_v2.get_network_binding, + self.session, + TEST_NETWORK_ID) + TEST_NETWORK_ID2 = network2['network']['id'] + self.assertRaises(c_exc.NetworkBindingNotFound, + n1kv_db_v2.get_network_binding, + self.session, + TEST_NETWORK_ID2) + p_v = _create_test_network_profile_if_not_there(self.session) + n1kv_db_v2.add_network_binding( + self.session, TEST_NETWORK_ID2, 'vlan', + PHYS_NET, 1234, '0.0.0.0', p_v.id, None) + p = _create_test_network_profile_if_not_there( + self.session, + TEST_NETWORK_PROFILE_VLAN_TRUNK) + n1kv_db_v2.add_network_binding( + self.session, TEST_NETWORK_ID, 'trunk', + None, 0, '0.0.0.0', p.id, [(TEST_NETWORK_ID2, 0)]) + binding = n1kv_db_v2.get_network_binding( + self.session, TEST_NETWORK_ID) + self.assertIsNotNone(binding) + self.assertEqual(binding.network_id, TEST_NETWORK_ID) + self.assertEqual(binding.network_type, 'trunk') + self.assertEqual(binding.physical_network, PHYS_NET) + self.assertEqual(binding.segmentation_id, 0) + t_binding = (n1kv_db_v2.get_trunk_network_binding( + self.session, TEST_NETWORK_ID, + (TEST_NETWORK_ID2, 0))) + self.assertIsNotNone(t_binding) + self.assertEqual(t_binding.trunk_segment_id, TEST_NETWORK_ID) + self.assertEqual(t_binding.segment_id, TEST_NETWORK_ID2) + self.assertEqual(t_binding.dot1qtag, '0') + t_members = (n1kv_db_v2.get_trunk_members( + self.session, TEST_NETWORK_ID)) + self.assertEqual(t_members, + [(TEST_NETWORK_ID2, '0')]) + self.assertTrue(n1kv_db_v2.is_trunk_member( + self.session, TEST_NETWORK_ID2)) + n1kv_db_v2.del_trunk_segment_binding( + self.session, TEST_NETWORK_ID, + [(TEST_NETWORK_ID2, '0')]) + t_members = (n1kv_db_v2.get_multi_segment_members( + self.session, TEST_NETWORK_ID)) + self.assertEqual(t_members, []) + + def test_add_vxlan_trunk_binding(self): + with self.network() as network1: + with self.network() as network2: + TEST_NETWORK_ID = network1['network']['id'] + + self.assertRaises(c_exc.NetworkBindingNotFound, + n1kv_db_v2.get_network_binding, + self.session, + TEST_NETWORK_ID) + TEST_NETWORK_ID2 = network2['network']['id'] + self.assertRaises(c_exc.NetworkBindingNotFound, + n1kv_db_v2.get_network_binding, + self.session, + TEST_NETWORK_ID2) + p_v = _create_test_network_profile_if_not_there( + self.session, TEST_NETWORK_PROFILE_VXLAN_TRUNK) + n1kv_db_v2.add_network_binding( + self.session, TEST_NETWORK_ID2, 'vxlan', + None, 5100, '224.10.10.10', p_v.id, None) + p = _create_test_network_profile_if_not_there( + self.session, + TEST_NETWORK_PROFILE_VXLAN_TRUNK) + n1kv_db_v2.add_network_binding( + self.session, TEST_NETWORK_ID, 'trunk', + None, 0, '0.0.0.0', p.id, + [(TEST_NETWORK_ID2, 5)]) + binding = n1kv_db_v2.get_network_binding( + self.session, TEST_NETWORK_ID) + self.assertIsNotNone(binding) + self.assertEqual(binding.network_id, TEST_NETWORK_ID) + self.assertEqual(binding.network_type, 'trunk') + self.assertIsNone(binding.physical_network) + self.assertEqual(binding.segmentation_id, 0) + t_binding = (n1kv_db_v2.get_trunk_network_binding( + self.session, TEST_NETWORK_ID, + (TEST_NETWORK_ID2, '5'))) + self.assertIsNotNone(t_binding) + self.assertEqual(t_binding.trunk_segment_id, TEST_NETWORK_ID) + self.assertEqual(t_binding.segment_id, TEST_NETWORK_ID2) + self.assertEqual(t_binding.dot1qtag, '5') + t_members = (n1kv_db_v2.get_trunk_members( + self.session, TEST_NETWORK_ID)) + self.assertEqual(t_members, + [(TEST_NETWORK_ID2, '5')]) + self.assertTrue(n1kv_db_v2.is_trunk_member( + self.session, TEST_NETWORK_ID2)) + n1kv_db_v2.del_trunk_segment_binding( + self.session, TEST_NETWORK_ID, + [(TEST_NETWORK_ID2, '5')]) + t_members = (n1kv_db_v2.get_multi_segment_members( + self.session, TEST_NETWORK_ID)) + self.assertEqual(t_members, []) + class NetworkProfileTests(base.BaseTestCase, n1kv_db_v2.NetworkProfile_db_mixin): @@ -436,6 +665,63 @@ class NetworkProfileTests(base.BaseTestCase, db_profile.multicast_ip_range) n1kv_db_v2.delete_network_profile(self.session, _db_profile.id) + def test_create_multi_segment_network_profile(self): + _db_profile = (n1kv_db_v2.create_network_profile( + self.session, TEST_NETWORK_PROFILE_MULTI_SEGMENT)) + self.assertIsNotNone(_db_profile) + db_profile = (self.session.query(n1kv_models_v2.NetworkProfile). + filter_by( + name=TEST_NETWORK_PROFILE_MULTI_SEGMENT['name']). + one()) + self.assertIsNotNone(db_profile) + self.assertEqual(_db_profile.id, db_profile.id) + self.assertEqual(_db_profile.name, db_profile.name) + self.assertEqual(_db_profile.segment_type, db_profile.segment_type) + self.assertEqual(_db_profile.segment_range, db_profile.segment_range) + self.assertEqual(_db_profile.multicast_ip_index, + db_profile.multicast_ip_index) + self.assertEqual(_db_profile.multicast_ip_range, + db_profile.multicast_ip_range) + n1kv_db_v2.delete_network_profile(self.session, _db_profile.id) + + def test_create_vlan_trunk_network_profile(self): + _db_profile = (n1kv_db_v2.create_network_profile( + self.session, TEST_NETWORK_PROFILE_VLAN_TRUNK)) + self.assertIsNotNone(_db_profile) + db_profile = (self.session.query(n1kv_models_v2.NetworkProfile). + filter_by(name=TEST_NETWORK_PROFILE_VLAN_TRUNK['name']). + one()) + self.assertIsNotNone(db_profile) + self.assertEqual(_db_profile.id, db_profile.id) + self.assertEqual(_db_profile.name, db_profile.name) + self.assertEqual(_db_profile.segment_type, db_profile.segment_type) + self.assertEqual(_db_profile.segment_range, db_profile.segment_range) + self.assertEqual(_db_profile.multicast_ip_index, + db_profile.multicast_ip_index) + self.assertEqual(_db_profile.multicast_ip_range, + db_profile.multicast_ip_range) + self.assertEqual(_db_profile.sub_type, db_profile.sub_type) + n1kv_db_v2.delete_network_profile(self.session, _db_profile.id) + + def test_create_vxlan_trunk_network_profile(self): + _db_profile = (n1kv_db_v2.create_network_profile( + self.session, TEST_NETWORK_PROFILE_VXLAN_TRUNK)) + self.assertIsNotNone(_db_profile) + db_profile = (self.session.query(n1kv_models_v2.NetworkProfile). + filter_by(name=TEST_NETWORK_PROFILE_VXLAN_TRUNK['name']). + one()) + self.assertIsNotNone(db_profile) + self.assertEqual(_db_profile.id, db_profile.id) + self.assertEqual(_db_profile.name, db_profile.name) + self.assertEqual(_db_profile.segment_type, db_profile.segment_type) + self.assertEqual(_db_profile.segment_range, db_profile.segment_range) + self.assertEqual(_db_profile.multicast_ip_index, + db_profile.multicast_ip_index) + self.assertEqual(_db_profile.multicast_ip_range, + db_profile.multicast_ip_range) + self.assertEqual(_db_profile.sub_type, db_profile.sub_type) + n1kv_db_v2.delete_network_profile(self.session, _db_profile.id) + def test_create_network_profile_overlap(self): _db_profile = n1kv_db_v2.create_network_profile(self.session, TEST_NETWORK_PROFILE_2)