diff --git a/gbpservice/neutron/db/migration/alembic_migrations/versions/4967af35820f_cisco_apic_nested_domain.py b/gbpservice/neutron/db/migration/alembic_migrations/versions/4967af35820f_cisco_apic_nested_domain.py new file mode 100644 index 000000000..99a3461e3 --- /dev/null +++ b/gbpservice/neutron/db/migration/alembic_migrations/versions/4967af35820f_cisco_apic_nested_domain.py @@ -0,0 +1,59 @@ +# 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. + +"""nested domain attributes in cisco_apic extension + +Revision ID: 4967af35820f +Revises: 1c564e737f9f +Create Date: 2018-04-19 14:18:11.909757 + +""" + +# revision identifiers, used by Alembic. +revision = '4967af35820f' +down_revision = '1c564e737f9f' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + + op.add_column('apic_aim_network_extensions', + sa.Column('nested_domain_name', sa.String(1024), + nullable=True)) + op.add_column('apic_aim_network_extensions', + sa.Column('nested_domain_type', sa.String(1024), + nullable=True)) + op.add_column('apic_aim_network_extensions', + sa.Column('nested_domain_infra_vlan', sa.Integer, + nullable=True)) + op.add_column('apic_aim_network_extensions', + sa.Column('nested_domain_service_vlan', sa.Integer, + nullable=True)) + op.add_column('apic_aim_network_extensions', + sa.Column('nested_domain_node_network_vlan', sa.Integer, + nullable=True)) + + op.create_table( + 'apic_aim_network_nested_domain_allowed_vlans', + sa.Column('vlan', sa.Integer, nullable=False), + sa.PrimaryKeyConstraint('vlan'), + sa.Column('network_id', sa.String(36), nullable=False), + sa.ForeignKeyConstraint(['network_id'], ['networks.id'], + name='apic_aim_network_nested_extn_fk_network', + ondelete='CASCADE') + ) + + +def downgrade(): + pass diff --git a/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD b/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD index 2a1ff5589..ea93675a6 100644 --- a/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD +++ b/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD @@ -1 +1 @@ -1c564e737f9f +4967af35820f diff --git a/gbpservice/neutron/extensions/cisco_apic.py b/gbpservice/neutron/extensions/cisco_apic.py index fdb361017..57030ee27 100644 --- a/gbpservice/neutron/extensions/cisco_apic.py +++ b/gbpservice/neutron/extensions/cisco_apic.py @@ -13,11 +13,17 @@ # License for the specific language governing permissions and limitations # under the License. +import ast +import functools +import six + from neutron_lib.api import converters as conv from neutron_lib.api.definitions import address_scope as as_def from neutron_lib.api.definitions import network as net_def from neutron_lib.api.definitions import subnet as subnet_def from neutron_lib.api import extensions +from neutron_lib.api import validators as valid +from oslo_log import log as logging ALIAS = 'cisco-apic' @@ -30,6 +36,12 @@ SVI = 'apic:svi' BGP = 'apic:bgp_enable' BGP_ASN = 'apic:bgp_asn' BGP_TYPE = 'apic:bgp_type' +NESTED_DOMAIN_NAME = 'apic:nested_domain_name' +NESTED_DOMAIN_TYPE = 'apic:nested_domain_type' +NESTED_DOMAIN_INFRA_VLAN = 'apic:nested_domain_infra_vlan' +NESTED_DOMAIN_ALLOWED_VLANS = 'apic:nested_domain_allowed_vlans' +NESTED_DOMAIN_SERVICE_VLAN = 'apic:nested_domain_service_vlan' +NESTED_DOMAIN_NODE_NETWORK_VLAN = 'apic:nested_domain_node_network_vlan' BD = 'BridgeDomain' EPG = 'EndpointGroup' @@ -43,6 +55,103 @@ SYNC_BUILD = 'build' SYNC_ERROR = 'error' SYNC_NOT_APPLICABLE = 'N/A' +VLANS_LIST = 'vlans_list' +VLAN_RANGES = 'vlan_ranges' +APIC_MAX_VLAN = 4093 +APIC_MIN_VLAN = 1 +VLAN_RANGE_START = 'start' +VLAN_RANGE_END = 'end' + +LOG = logging.getLogger(__name__) + + +def _validate_apic_vlan(data, key_specs=None): + if data is None: + return + try: + val = int(data) + if val >= APIC_MIN_VLAN and val <= APIC_MAX_VLAN: + return + msg = _("Invalid value for VLAN: '%s'") % data + LOG.debug(msg) + return msg + except (ValueError, TypeError): + msg = _("Invalid data format for VLAN: '%s'") % data + LOG.debug(msg) + return msg + + +def _validate_apic_vlan_range(data, key_specs=None): + if data is None: + return + + expected_keys = [VLAN_RANGE_START, VLAN_RANGE_END] + msg = valid._verify_dict_keys(expected_keys, data) + if msg: + return msg + for k in expected_keys: + msg = _validate_apic_vlan(data[k]) + if msg: + return msg + if int(data[VLAN_RANGE_START]) > int(data[VLAN_RANGE_END]): + msg = _("Invalid start, end for VLAN range %s") % data + return msg + + +def _validate_dict_or_string(data, key_specs=None): + if data is None: + return + + if isinstance(data, str) or isinstance(data, six.string_types): + try: + data = ast.literal_eval(data) + except Exception: + msg = _("Allowed VLANs %s cannot be converted to dict") % data + return msg + + return valid.validate_dict_or_none(data, key_specs) + + +def convert_apic_vlan(value): + if value is None: + return + else: + return int(value) + + +def convert_nested_domain_allowed_vlans(value): + if value is None: + return + + if isinstance(value, str) or isinstance(value, six.string_types): + value = ast.literal_eval(value) + + vlans_list = [] + if VLANS_LIST in value: + for vlan in value[VLANS_LIST]: + vlans_list.append(convert_apic_vlan(vlan)) + if VLAN_RANGES in value: + for vlan_range in value[VLAN_RANGES]: + for vrng in [VLAN_RANGE_START, VLAN_RANGE_END]: + vlan_range[vrng] = convert_apic_vlan(vlan_range[vrng]) + vlans_list.extend(range(vlan_range[VLAN_RANGE_START], + vlan_range[VLAN_RANGE_END] + 1)) + # eliminate duplicates + vlans_list = list(set(vlans_list)) + # sort + vlans_list.sort() + value[VLANS_LIST] = vlans_list + return value + + +valid.validators['type:apic_vlan'] = _validate_apic_vlan +valid.validators['type:apic_vlan_list'] = functools.partial( + valid._validate_list_of_items, _validate_apic_vlan) +valid.validators['type:apic_vlan_range_list'] = functools.partial( + valid._validate_list_of_items, _validate_apic_vlan_range) +valid.validators['type:dict_or_string'] = _validate_dict_or_string + + APIC_ATTRIBUTES = { DIST_NAMES: {'allow_post': False, 'allow_put': False, 'is_visible': True}, SYNC_STATE: {'allow_post': False, 'allow_put': False, 'is_visible': True} @@ -69,6 +178,45 @@ NET_ATTRIBUTES = { 'is_visible': True, 'default': "0", 'validate': {'type:non_negative': None}, }, + NESTED_DOMAIN_NAME: { + 'allow_post': True, 'allow_put': True, + 'is_visible': True, 'default': '', + 'validate': {'type:string': None}, + }, + NESTED_DOMAIN_TYPE: { + 'allow_post': True, 'allow_put': True, + 'is_visible': True, 'default': '', + 'validate': {'type:string': None}, + }, + NESTED_DOMAIN_INFRA_VLAN: { + 'allow_post': True, 'allow_put': True, + 'is_visible': True, 'default': None, + 'validate': {'type:apic_vlan': None}, + 'convert_to': convert_apic_vlan, + }, + NESTED_DOMAIN_SERVICE_VLAN: { + 'allow_post': True, 'allow_put': True, + 'is_visible': True, 'default': None, + 'validate': {'type:apic_vlan': None}, + 'convert_to': convert_apic_vlan, + }, + NESTED_DOMAIN_NODE_NETWORK_VLAN: { + 'allow_post': True, 'allow_put': True, + 'is_visible': True, 'default': None, + 'validate': {'type:apic_vlan': None}, + 'convert_to': convert_apic_vlan, + }, + NESTED_DOMAIN_ALLOWED_VLANS: { + 'allow_post': True, 'allow_put': True, + 'is_visible': True, 'default': None, + 'validate': { + 'type:dict_or_string': { + VLANS_LIST: {'type:apic_vlan_list': None}, + VLAN_RANGES: {'type:apic_vlan_range_list': None}, + } + }, + 'convert_to': convert_nested_domain_allowed_vlans, + }, } EXT_NET_ATTRIBUTES = { diff --git a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_db.py b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_db.py index df5a14a10..fd4904562 100644 --- a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_db.py +++ b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_db.py @@ -43,6 +43,11 @@ class NetworkExtensionDb(model_base.BASEV2): backref=orm.backref( 'aim_extension_mapping', lazy='joined', uselist=False, cascade='delete')) + nested_domain_name = sa.Column(sa.String(1024), nullable=True) + nested_domain_type = sa.Column(sa.String(1024), nullable=True) + nested_domain_infra_vlan = sa.Column(sa.Integer, nullable=True) + nested_domain_service_vlan = sa.Column(sa.Integer, nullable=True) + nested_domain_node_network_vlan = sa.Column(sa.Integer, nullable=True) class NetworkExtensionCidrDb(model_base.BASEV2): @@ -55,6 +60,16 @@ class NetworkExtensionCidrDb(model_base.BASEV2): cidr = sa.Column(sa.String(64), primary_key=True) +class NetworkExtNestedDomainAllowedVlansDb(model_base.BASEV2): + + __tablename__ = 'apic_aim_network_nested_domain_allowed_vlans' + + # There is a single pool of VLANs for an APIC + vlan = sa.Column(sa.Integer(), primary_key=True) + network_id = sa.Column( + sa.String(36), sa.ForeignKey('networks.id', ondelete="CASCADE")) + + class SubnetExtensionDb(model_base.BASEV2): __tablename__ = 'apic_aim_subnet_extensions' @@ -87,6 +102,9 @@ class ExtensionDbMixin(object): network_id=network_id).first()) db_cidrs = (session.query(NetworkExtensionCidrDb).filter_by( network_id=network_id).all()) + db_vlans = (session.query( + NetworkExtNestedDomainAllowedVlansDb).filter_by( + network_id=network_id).all()) result = {} if db_obj: self._set_if_not_none(result, cisco_apic.EXTERNAL_NETWORK, @@ -97,6 +115,19 @@ class ExtensionDbMixin(object): result[cisco_apic.BGP] = db_obj['bgp_enable'] result[cisco_apic.BGP_TYPE] = db_obj['bgp_type'] result[cisco_apic.BGP_ASN] = db_obj['bgp_asn'] + result[cisco_apic.NESTED_DOMAIN_NAME] = ( + db_obj['nested_domain_name']) + result[cisco_apic.NESTED_DOMAIN_TYPE] = ( + db_obj['nested_domain_type']) + result[cisco_apic.NESTED_DOMAIN_INFRA_VLAN] = ( + db_obj['nested_domain_infra_vlan']) + result[cisco_apic.NESTED_DOMAIN_SERVICE_VLAN] = ( + db_obj['nested_domain_service_vlan']) + result[cisco_apic.NESTED_DOMAIN_NODE_NETWORK_VLAN] = ( + db_obj['nested_domain_node_network_vlan']) + result[cisco_apic.NESTED_DOMAIN_ALLOWED_VLANS] = ( + [c['vlan'] for c in db_vlans]) + if result.get(cisco_apic.EXTERNAL_NETWORK): result[cisco_apic.EXTERNAL_CIDRS] = [c['cidr'] for c in db_cidrs] @@ -120,6 +151,21 @@ class ExtensionDbMixin(object): db_obj['bgp_type'] = res_dict[cisco_apic.BGP_TYPE] if cisco_apic.BGP_ASN in res_dict: db_obj['bgp_asn'] = res_dict[cisco_apic.BGP_ASN] + if cisco_apic.NESTED_DOMAIN_NAME in res_dict: + db_obj['nested_domain_name'] = res_dict[ + cisco_apic.NESTED_DOMAIN_NAME] + if cisco_apic.NESTED_DOMAIN_TYPE in res_dict: + db_obj['nested_domain_type'] = res_dict[ + cisco_apic.NESTED_DOMAIN_TYPE] + if cisco_apic.NESTED_DOMAIN_INFRA_VLAN in res_dict: + db_obj['nested_domain_infra_vlan'] = res_dict[ + cisco_apic.NESTED_DOMAIN_INFRA_VLAN] + if cisco_apic.NESTED_DOMAIN_SERVICE_VLAN in res_dict: + db_obj['nested_domain_service_vlan'] = res_dict[ + cisco_apic.NESTED_DOMAIN_SERVICE_VLAN] + if cisco_apic.NESTED_DOMAIN_NODE_NETWORK_VLAN in res_dict: + db_obj['nested_domain_node_network_vlan'] = res_dict[ + cisco_apic.NESTED_DOMAIN_NODE_NETWORK_VLAN] session.add(db_obj) if cisco_apic.EXTERNAL_CIDRS in res_dict: @@ -127,6 +173,12 @@ class ExtensionDbMixin(object): res_dict[cisco_apic.EXTERNAL_CIDRS], network_id=network_id) + if cisco_apic.NESTED_DOMAIN_ALLOWED_VLANS in res_dict: + self._update_list_attr( + session, NetworkExtNestedDomainAllowedVlansDb, 'vlan', + res_dict[cisco_apic.NESTED_DOMAIN_ALLOWED_VLANS], + network_id=network_id) + def get_network_ids_by_ext_net_dn(self, session, dn, lock_update=False): ids = session.query(NetworkExtensionDb.network_id).filter_by( external_network_dn=dn) @@ -187,6 +239,8 @@ class ExtensionDbMixin(object): def _update_list_attr(self, session, db_model, column, new_values, **filters): + if new_values is None: + return rows = session.query(db_model).filter_by(**filters).all() new_values = set(new_values) for r in rows: diff --git a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_driver.py b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_driver.py index 1e72e341a..dff0af92b 100644 --- a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_driver.py +++ b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_driver.py @@ -99,7 +99,23 @@ class ApicExtensionDriver(api_plus.ExtensionDriver, res_dict = {cisco_apic.SVI: is_svi, cisco_apic.BGP: is_bgp_enabled, cisco_apic.BGP_TYPE: bgp_type, - cisco_apic.BGP_ASN: asn} + cisco_apic.BGP_ASN: asn, + cisco_apic.NESTED_DOMAIN_NAME: + data.get(cisco_apic.NESTED_DOMAIN_NAME), + cisco_apic.NESTED_DOMAIN_TYPE: + data.get(cisco_apic.NESTED_DOMAIN_TYPE), + cisco_apic.NESTED_DOMAIN_INFRA_VLAN: + data.get(cisco_apic.NESTED_DOMAIN_INFRA_VLAN), + cisco_apic.NESTED_DOMAIN_SERVICE_VLAN: + data.get(cisco_apic.NESTED_DOMAIN_SERVICE_VLAN), + cisco_apic.NESTED_DOMAIN_NODE_NETWORK_VLAN: + data.get(cisco_apic.NESTED_DOMAIN_NODE_NETWORK_VLAN), + } + if cisco_apic.VLANS_LIST in (data.get( + cisco_apic.NESTED_DOMAIN_ALLOWED_VLANS) or {}): + res_dict.update({cisco_apic.NESTED_DOMAIN_ALLOWED_VLANS: + data.get(cisco_apic.NESTED_DOMAIN_ALLOWED_VLANS)[ + cisco_apic.VLANS_LIST]}) self.set_network_extn_db(plugin_context.session, result['id'], res_dict) result.update(res_dict) @@ -129,25 +145,43 @@ class ApicExtensionDriver(api_plus.ExtensionDriver, result.update(res_dict) def process_update_network(self, plugin_context, data, result): - # External_cidr, bgp_enable, bgp_type and bgp_asn can be updated. - if (cisco_apic.EXTERNAL_CIDRS not in data and - cisco_apic.BGP not in data and - cisco_apic.BGP_TYPE not in data and - cisco_apic.BGP_ASN not in data): + # External_cidr, bgp_enable, bgp_type and bgp_asn or + # nested domain attributes could be updated + update_attrs = [ + cisco_apic.EXTERNAL_CIDRS, cisco_apic.BGP, cisco_apic.BGP_TYPE, + cisco_apic.BGP_ASN, + cisco_apic.NESTED_DOMAIN_NAME, cisco_apic.NESTED_DOMAIN_TYPE, + cisco_apic.NESTED_DOMAIN_INFRA_VLAN, + cisco_apic.NESTED_DOMAIN_SERVICE_VLAN, + cisco_apic.NESTED_DOMAIN_NODE_NETWORK_VLAN, + cisco_apic.NESTED_DOMAIN_ALLOWED_VLANS] + + if not(set(update_attrs) & set(data.keys())): return + res_dict = {} if result.get(cisco_apic.DIST_NAMES, {}).get( cisco_apic.EXTERNAL_NETWORK): if cisco_apic.EXTERNAL_CIDRS in data: - res_dict = {cisco_apic.EXTERNAL_CIDRS: - data[cisco_apic.EXTERNAL_CIDRS]} + res_dict.update({cisco_apic.EXTERNAL_CIDRS: + data[cisco_apic.EXTERNAL_CIDRS]}) self.validate_bgp_params(data, result) - if cisco_apic.BGP in data: - res_dict.update({cisco_apic.BGP: data[cisco_apic.BGP]}) - if cisco_apic.BGP_TYPE in data: - res_dict.update({cisco_apic.BGP_TYPE: data[cisco_apic.BGP_TYPE]}) - if cisco_apic.BGP_ASN in data: - res_dict.update({cisco_apic.BGP_ASN: data[cisco_apic.BGP_ASN]}) + + ext_keys = [cisco_apic.BGP, cisco_apic.BGP_TYPE, cisco_apic.BGP_ASN, + cisco_apic.NESTED_DOMAIN_NAME, cisco_apic.NESTED_DOMAIN_TYPE, + cisco_apic.NESTED_DOMAIN_INFRA_VLAN, + cisco_apic.NESTED_DOMAIN_SERVICE_VLAN, + cisco_apic.NESTED_DOMAIN_NODE_NETWORK_VLAN] + for e_k in ext_keys: + if e_k in data: + res_dict.update({e_k: data[e_k]}) + + if cisco_apic.VLANS_LIST in (data.get( + cisco_apic.NESTED_DOMAIN_ALLOWED_VLANS) or {}): + res_dict.update({cisco_apic.NESTED_DOMAIN_ALLOWED_VLANS: + data.get(cisco_apic.NESTED_DOMAIN_ALLOWED_VLANS)[ + cisco_apic.VLANS_LIST]}) + if res_dict: self.set_network_extn_db(plugin_context.session, result['id'], res_dict) diff --git a/gbpservice/neutron/services/grouppolicy/drivers/cisco/apic/aim_mapping.py b/gbpservice/neutron/services/grouppolicy/drivers/cisco/apic/aim_mapping.py index 532b48a7f..2a70e2f0c 100644 --- a/gbpservice/neutron/services/grouppolicy/drivers/cisco/apic/aim_mapping.py +++ b/gbpservice/neutron/services/grouppolicy/drivers/cisco/apic/aim_mapping.py @@ -118,8 +118,19 @@ opts = [ help=_('If True, advertise network MTU values if core plugin ' 'calculates them. MTU is advertised to running ' 'instances via DHCP and RA MTU options.')), + cfg.IntOpt('nested_host_vlan', + default=4094, + help=_("This is a locally siginificant VLAN used to provide " + "connectivity to the OpenStack VM when configured " + "to host the nested domain (Kubernetes/OpenShift). " + "Any traffic originating from the VM and intended " + "to go on the Neutron network, is tagged with this " + "VLAN. The VLAN is stripped by the Opflex installed " + "flows on the integration bridge and the traffic is " + "forwarded on the Neutron network.")), ] + cfg.CONF.register_opts(opts, "aim_mapping") @@ -177,6 +188,8 @@ class AIMMappingDriver(nrd.CommonNeutronBase, aim_rpc.AIMMappingRPCMixin): LOG.info('Implicit AIM contracts will be created ' 'for l3_policies which do not have them.') self._create_per_l3p_implicit_contracts() + self._nested_host_vlan = ( + cfg.CONF.aim_mapping.nested_host_vlan) @log.log_method_call def start_rpc_listeners(self): @@ -2409,6 +2422,16 @@ class AIMMappingDriver(nrd.CommonNeutronBase, aim_rpc.AIMMappingRPCMixin): network = self._get_network(context, port['network_id']) return network.get('dns_domain') + def _get_nested_domain(self, context, port): + network = self._get_network(context, port['network_id']) + return (network.get('apic:nested_domain_name'), + network.get('apic:nested_domain_type'), + network.get('apic:nested_domain_infra_vlan'), + network.get('apic:nested_domain_service_vlan'), + network.get('apic:nested_domain_node_network_vlan'), + network.get('apic:nested_domain_allowed_vlans'), + self._nested_host_vlan) + def _create_per_l3p_implicit_contracts(self): admin_context = n_context.get_admin_context() context = type('', (object,), {})() diff --git a/gbpservice/neutron/services/grouppolicy/drivers/cisco/apic/aim_mapping_rpc.py b/gbpservice/neutron/services/grouppolicy/drivers/cisco/apic/aim_mapping_rpc.py index f6423aa66..32f76de58 100644 --- a/gbpservice/neutron/services/grouppolicy/drivers/cisco/apic/aim_mapping_rpc.py +++ b/gbpservice/neutron/services/grouppolicy/drivers/cisco/apic/aim_mapping_rpc.py @@ -246,6 +246,7 @@ class AIMMappingRPCMixin(ha_ip_db.HAIPOwnerDbMixin): self._add_segmentation_label_details(context, port, details) self._set_dhcp_lease_time(details) details.pop('_cache', None) + self._add_nested_domain_details(context, port, details) LOG.debug("Details for port %s : %s", port['id'], details) return details @@ -324,6 +325,25 @@ class AIMMappingRPCMixin(ha_ip_db.HAIPOwnerDbMixin): details['vrf_subnets'] = self._get_vrf_subnets(context, tenant_name, name, details) + # Child class needs to support: + # - self._get_nested_domain(context, port) + def _add_nested_domain_details(self, context, port, details): + # This method needs to define requirements for this Mixin's child + # classes in order to fill the following result parameters: + # - nested_domain_name; + # - nested_domain_type; + # - nested_domain_infra_vlan; + # - nested_domain_service_vlan; + # - nested_domain_node_network_vlan; + # - nested_domain_allowed_vlans; + (details['nested_domain_name'], details['nested_domain_type'], + details['nested_domain_infra_vlan'], + details['nested_domain_service_vlan'], + details['nested_domain_node_network_vlan'], + details['nested_domain_allowed_vlans'], + details['nested_host_vlan']) = ( + self._get_nested_domain(context, port)) + # Child class needs to support: # - self._get_segmentation_labels(context, port, details) def _add_segmentation_label_details(self, context, port, details): diff --git a/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py index 1fe0e9197..4308509ab 100644 --- a/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py +++ b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py @@ -4401,6 +4401,61 @@ class TestExtensionAttributes(ApicAimTestCase): self._delete('networks', net2['id']) self.assertFalse(extn.get_network_extn_db(session, net2['id'])) + def test_network_with_nested_domain_lifecycle(self): + session = db_api.get_session() + extn = extn_db.ExtensionDbMixin() + vlan_dict = {'vlans_list': ['2', '3', '4', '3'], + 'vlan_ranges': [{'start': '6', 'end': '9'}, + {'start': '11', 'end': '14'}]} + expt_vlans = [2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14] + kwargs = {'apic:nested_domain_name': 'myk8s', + 'apic:nested_domain_type': 'k8s', + 'apic:nested_domain_infra_vlan': '4093', + 'apic:nested_domain_service_vlan': '1000', + 'apic:nested_domain_node_network_vlan': '1001', + 'apic:nested_domain_allowed_vlans': vlan_dict, + } + + net1 = self._make_network(self.fmt, 'net1', True, + arg_list=tuple(kwargs.keys()), + **kwargs)['network'] + self.assertEqual('myk8s', net1['apic:nested_domain_name']) + self.assertEqual('k8s', net1['apic:nested_domain_type']) + self.assertEqual(4093, net1['apic:nested_domain_infra_vlan']) + self.assertEqual(1000, net1['apic:nested_domain_service_vlan']) + self.assertEqual(1001, net1['apic:nested_domain_node_network_vlan']) + self.assertItemsEqual(expt_vlans, + net1['apic:nested_domain_allowed_vlans']) + + vlan_dict = {'vlans_list': ['2', '3', '4', '3']} + expt_vlans = [2, 3, 4] + data = {'network': {'apic:nested_domain_name': 'new-myk8s', + 'apic:nested_domain_type': 'new-k8s', + 'apic:nested_domain_infra_vlan': '1093', + 'apic:nested_domain_service_vlan': '2000', + 'apic:nested_domain_node_network_vlan': '2001', + 'apic:nested_domain_allowed_vlans': vlan_dict}} + req = self.new_update_request('networks', data, net1['id'], + self.fmt) + resp = req.get_response(self.api) + self.assertEqual(resp.status_code, 200) + net1 = self.deserialize(self.fmt, resp)['network'] + self.assertEqual('new-myk8s', net1['apic:nested_domain_name']) + self.assertEqual('new-k8s', net1['apic:nested_domain_type']) + self.assertEqual(1093, net1['apic:nested_domain_infra_vlan']) + self.assertEqual(2000, net1['apic:nested_domain_service_vlan']) + self.assertEqual(2001, net1['apic:nested_domain_node_network_vlan']) + self.assertItemsEqual(expt_vlans, + net1['apic:nested_domain_allowed_vlans']) + + # Test delete. + self._delete('networks', net1['id']) + self.assertFalse(extn.get_network_extn_db(session, net1['id'])) + db_vlans = (session.query( + extn_db.NetworkExtNestedDomainAllowedVlansDb).filter_by( + network_id=net1['id']).all()) + self.assertEqual([], db_vlans) + def test_external_network_lifecycle(self): session = db_api.get_reader_session() extn = extn_db.ExtensionDbMixin() diff --git a/gbpservice/neutron/tests/unit/plugins/ml2plus/test_extension_apic_aim.py b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_extension_apic_aim.py new file mode 100644 index 000000000..fd8da332e --- /dev/null +++ b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_extension_apic_aim.py @@ -0,0 +1,59 @@ +# Copyright (c) 2018 Cisco Systems Inc. +# All Rights Reserved. +# +# 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. + +from neutron.tests import base + +from gbpservice.neutron.extensions import cisco_apic as apic_ext + + +class TestAttributeValidators(base.BaseTestCase): + + def test_validate_apic_vlan(self): + self.assertIsNone(apic_ext._validate_apic_vlan(None)) + self.assertIsNone(apic_ext._validate_apic_vlan('10')) + self.assertIsNone(apic_ext._validate_apic_vlan(10)) + + +class TestAttributeConverters(base.BaseTestCase): + + def test_convert_apic_vlan(self): + self.assertIsInstance(apic_ext.convert_apic_vlan('2'), int) + self.assertIsInstance(apic_ext.convert_apic_vlan(2), int) + self.assertIsNone(apic_ext.convert_apic_vlan(None)) + + def test_convert_nested_domain_allowed_vlans(self): + test_dict_str = "{'vlans_list': [2, 3, 4], " + ( + "'vlan_ranges': [{'start': 6, 'end': 9}, ") + ( + "{'start': 11, 'end': 14}]}") + expt_list = [2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14] + self.assertItemsEqual( + apic_ext.convert_nested_domain_allowed_vlans( + test_dict_str)['vlans_list'], expt_list) + test_dict = {'vlans_list': [2, 3, 4], + 'vlan_ranges': [{'start': 6, 'end': 9}, + {'start': 11, 'end': 14}]} + expt_list = [2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14] + self.assertItemsEqual( + apic_ext.convert_nested_domain_allowed_vlans( + test_dict)['vlans_list'], expt_list) + test_dict = {'vlans_list': ['2', '3', '4', '3'], + 'vlan_ranges': [{'start': '6', 'end': '9'}, + {'start': '11', 'end': '14'}, + {'start': '6', 'end': '9'}]} + expt_list = [2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14] + self.assertItemsEqual( + apic_ext.convert_nested_domain_allowed_vlans( + test_dict)['vlans_list'], expt_list) + self.assertIsNone(apic_ext.convert_nested_domain_allowed_vlans(None)) diff --git a/gbpservice/neutron/tests/unit/services/grouppolicy/test_aim_mapping_driver.py b/gbpservice/neutron/tests/unit/services/grouppolicy/test_aim_mapping_driver.py index 081f8d3ec..d70d7e814 100644 --- a/gbpservice/neutron/tests/unit/services/grouppolicy/test_aim_mapping_driver.py +++ b/gbpservice/neutron/tests/unit/services/grouppolicy/test_aim_mapping_driver.py @@ -5273,6 +5273,54 @@ class TestNetworkServicePolicy(AIMBaseTestCase): netaddr.IPAddress(allocation_pool_after_nsp[0].get('end')) + 1) +class TestNestedDomain(AIMBaseTestCase): + + def setUp(self, **kwargs): + super(TestNestedDomain, self).setUp(**kwargs) + self.plugin = self._plugin + + def _test_get_nested_domain_details(self, vlans_arg): + self._register_agent('host1', test_aim_md.AGENT_CONF_OPFLEX) + expt_vlans = [2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14] + kwargs = {'apic:nested_domain_name': 'myk8s', + 'apic:nested_domain_type': 'k8s', + 'apic:nested_domain_infra_vlan': '4093', + 'apic:nested_domain_service_vlan': '1000', + 'apic:nested_domain_node_network_vlan': '1001', + 'apic:nested_domain_allowed_vlans': vlans_arg, + } + net = self._make_network(self.fmt, 'net1', True, + arg_list=tuple(kwargs.keys()), **kwargs) + self._make_subnet(self.fmt, net, '10.0.1.1', '10.0.1.0/24') + + p1 = self._make_port(self.fmt, net['network']['id'], + device_owner='compute:')['port'] + p1 = self._bind_port_to_host(p1['id'], 'host1')['port'] + details = self.driver.get_gbp_details( + self._neutron_admin_context, device='tap%s' % p1['id'], + host='h1') + self.assertEqual('myk8s', details['nested_domain_name']) + self.assertEqual('k8s', details['nested_domain_type']) + self.assertEqual(4093, details['nested_domain_infra_vlan']) + self.assertEqual(1000, details['nested_domain_service_vlan']) + self.assertEqual(1001, details['nested_domain_node_network_vlan']) + self.assertItemsEqual(expt_vlans, + details['nested_domain_allowed_vlans']) + self.assertEqual(4094, details['nested_host_vlan']) + + def test_get_nested_domain_details_vlan_dict_input(self): + vlan_dict = {'vlans_list': ['2', '3', '4', '3'], + 'vlan_ranges': [{'start': '6', 'end': '9'}, + {'start': '11', 'end': '14'}]} + self._test_get_nested_domain_details(vlan_dict) + + def test_get_nested_domain_details_vlan_string_input(self): + vlan_str = "{'vlans_list': ['2', '3', '4', '3'], " + ( + "'vlan_ranges': [{'start': '6', 'end': '9'}, ") + ( + "{'start': '11', 'end': '14'}]}") + self._test_get_nested_domain_details(vlan_str) + + class TestNeutronPortOperation(AIMBaseTestCase): def setUp(self, **kwargs):