Merge "Apic drivers enhancements (second approach): L2 refactor"
This commit is contained in:
commit
1f578d2572
@ -58,6 +58,12 @@
|
|||||||
# Password for the APIC controller
|
# Password for the APIC controller
|
||||||
# apic_password=password
|
# apic_password=password
|
||||||
|
|
||||||
|
# Whether use SSl for connecting to the APIC controller or not
|
||||||
|
# apic_use_ssl=True
|
||||||
|
|
||||||
|
# How to map names to APIC: use_uuid or use_name
|
||||||
|
# apic_name_mapping=use_name
|
||||||
|
|
||||||
# Names for APIC objects used by Neutron
|
# Names for APIC objects used by Neutron
|
||||||
# Note: When deploying multiple clouds against one APIC,
|
# Note: When deploying multiple clouds against one APIC,
|
||||||
# these names must be unique between the clouds.
|
# these names must be unique between the clouds.
|
||||||
@ -66,26 +72,43 @@
|
|||||||
# apic_node_profile=openstack_profile
|
# apic_node_profile=openstack_profile
|
||||||
# apic_entity_profile=openstack_entity
|
# apic_entity_profile=openstack_entity
|
||||||
# apic_function_profile=openstack_function
|
# apic_function_profile=openstack_function
|
||||||
|
# apic_app_profile_name=openstack_app
|
||||||
# The following flag will cause all the node profiles on the APIC to
|
|
||||||
# be cleared when neutron-server starts. This is typically used only
|
|
||||||
# for test environments that require clean-slate startup conditions.
|
|
||||||
# apic_clear_node_profiles=False
|
|
||||||
|
|
||||||
# Specify your network topology.
|
# Specify your network topology.
|
||||||
# This section indicates how your compute nodes are connected to the fabric's
|
# This section indicates how your compute nodes are connected to the fabric's
|
||||||
# switches and ports. The format is as follows:
|
# switches and ports. The format is as follows:
|
||||||
#
|
#
|
||||||
# [switch:<swich_id_from_the_apic>]
|
# [apic_switch:<swich_id_from_the_apic>]
|
||||||
# <compute_host>,<compute_host>=<switchport_the_host(s)_are_connected_to>
|
# <compute_host>,<compute_host>=<switchport_the_host(s)_are_connected_to>
|
||||||
#
|
#
|
||||||
# You can have multiple sections, one for each switch in your fabric that is
|
# You can have multiple sections, one for each switch in your fabric that is
|
||||||
# participating in Openstack. e.g.
|
# participating in Openstack. e.g.
|
||||||
#
|
#
|
||||||
# [switch:17]
|
# [apic_switch:17]
|
||||||
# ubuntu,ubuntu1=1/10
|
# ubuntu,ubuntu1=1/10
|
||||||
# ubuntu2,ubuntu3=1/11
|
# ubuntu2,ubuntu3=1/11
|
||||||
#
|
#
|
||||||
# [switch:18]
|
# [apic_switch:18]
|
||||||
# ubuntu5,ubuntu6=1/1
|
# ubuntu5,ubuntu6=1/1
|
||||||
# ubuntu7,ubuntu8=1/2
|
# ubuntu7,ubuntu8=1/2
|
||||||
|
|
||||||
|
# Describe external connectivity.
|
||||||
|
# In this section you can specify the external network configuration in order
|
||||||
|
# for the plugin to be able to teach the fabric how to route the internal
|
||||||
|
# traffic to the outside world. The external connectivity configuration
|
||||||
|
# format is as follows:
|
||||||
|
#
|
||||||
|
# [apic_external_network:<externalNetworkName>]
|
||||||
|
# switch=<switch_id_from_the_apic>
|
||||||
|
# port=<switchport_the_external_router_is_connected_to>
|
||||||
|
# encap=<encapsulation>
|
||||||
|
# cidr_exposed=<cidr_exposed_to_the_external_router>
|
||||||
|
# gateway_ip=<ip_of_the_external_gateway>
|
||||||
|
#
|
||||||
|
# An example follows:
|
||||||
|
# [apic_external_network:network_ext]
|
||||||
|
# switch=203
|
||||||
|
# port=1/34
|
||||||
|
# encap=vlan-100
|
||||||
|
# cidr_exposed=10.10.40.2/16
|
||||||
|
# gateway_ip=10.10.40.1
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
# Copyright 2014 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_apic_driver_update
|
||||||
|
|
||||||
|
Revision ID: 32f3915891fd
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '32f3915891fd'
|
||||||
|
down_revision = 'aae5706a396'
|
||||||
|
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(active_plugins=None, options=None):
|
||||||
|
|
||||||
|
op.drop_table('cisco_ml2_apic_port_profiles')
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'cisco_ml2_apic_host_links',
|
||||||
|
sa.Column('host', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('ifname', sa.String(length=64), nullable=False),
|
||||||
|
sa.Column('ifmac', sa.String(length=32), nullable=True),
|
||||||
|
sa.Column('swid', sa.String(length=32), nullable=False),
|
||||||
|
sa.Column('module', sa.String(length=32), nullable=False),
|
||||||
|
sa.Column('port', sa.String(length=32), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('host', 'ifname'))
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'cisco_ml2_apic_names',
|
||||||
|
sa.Column('neutron_id', sa.String(length=36), nullable=False),
|
||||||
|
sa.Column('neutron_type', sa.String(length=32), nullable=False),
|
||||||
|
sa.Column('apic_name', sa.String(length=255), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('neutron_id', 'neutron_type'))
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(active_plugins=None, options=None):
|
||||||
|
|
||||||
|
op.drop_table('cisco_ml2_apic_names')
|
||||||
|
op.drop_table('cisco_ml2_apic_host_links')
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'cisco_ml2_apic_port_profiles',
|
||||||
|
sa.Column('node_id', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('profile_id', sa.String(length=64), nullable=False),
|
||||||
|
sa.Column('hpselc_id', sa.String(length=64), nullable=False),
|
||||||
|
sa.Column('module', sa.String(length=10), nullable=False),
|
||||||
|
sa.Column('from_port', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('to_port', sa.Integer(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('node_id'))
|
@ -1 +1 @@
|
|||||||
aae5706a396
|
32f3915891fd
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
# Copyright (c) 2013 OpenStack Foundation
|
|
||||||
# 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.
|
|
@ -16,11 +16,15 @@
|
|||||||
# @author: Arvind Somya (asomya@cisco.com), Cisco Systems Inc.
|
# @author: Arvind Somya (asomya@cisco.com), Cisco Systems Inc.
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import orm
|
||||||
from sqlalchemy import sql
|
from sqlalchemy import sql
|
||||||
|
|
||||||
from neutron.db import api as db_api
|
from neutron.db import api as db_api
|
||||||
from neutron.db import model_base
|
from neutron.db import model_base
|
||||||
|
|
||||||
|
from neutron.db import models_v2
|
||||||
|
from neutron.plugins.ml2 import models as models_ml2
|
||||||
|
|
||||||
|
|
||||||
class NetworkEPG(model_base.BASEV2):
|
class NetworkEPG(model_base.BASEV2):
|
||||||
|
|
||||||
@ -35,20 +39,6 @@ class NetworkEPG(model_base.BASEV2):
|
|||||||
nullable=False)
|
nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class PortProfile(model_base.BASEV2):
|
|
||||||
|
|
||||||
"""Port profiles created on the APIC."""
|
|
||||||
|
|
||||||
__tablename__ = 'cisco_ml2_apic_port_profiles'
|
|
||||||
|
|
||||||
node_id = sa.Column(sa.String(255), nullable=False, primary_key=True)
|
|
||||||
profile_id = sa.Column(sa.String(64), nullable=False)
|
|
||||||
hpselc_id = sa.Column(sa.String(64), nullable=False)
|
|
||||||
module = sa.Column(sa.String(10), nullable=False)
|
|
||||||
from_port = sa.Column(sa.Integer(), nullable=False)
|
|
||||||
to_port = sa.Column(sa.Integer(), nullable=False)
|
|
||||||
|
|
||||||
|
|
||||||
class TenantContract(model_base.BASEV2):
|
class TenantContract(model_base.BASEV2):
|
||||||
|
|
||||||
"""Contracts (and Filters) created on the APIC."""
|
"""Contracts (and Filters) created on the APIC."""
|
||||||
@ -61,6 +51,30 @@ class TenantContract(model_base.BASEV2):
|
|||||||
filter_id = sa.Column(sa.String(64), nullable=False)
|
filter_id = sa.Column(sa.String(64), nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class HostLink(model_base.BASEV2):
|
||||||
|
|
||||||
|
"""Connectivity of host links."""
|
||||||
|
|
||||||
|
__tablename__ = 'cisco_ml2_apic_host_links'
|
||||||
|
|
||||||
|
host = sa.Column(sa.String(255), nullable=False, primary_key=True)
|
||||||
|
ifname = sa.Column(sa.String(64), nullable=False, primary_key=True)
|
||||||
|
ifmac = sa.Column(sa.String(32), nullable=True)
|
||||||
|
swid = sa.Column(sa.String(32), nullable=False)
|
||||||
|
module = sa.Column(sa.String(32), nullable=False)
|
||||||
|
port = sa.Column(sa.String(32), nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class ApicName(model_base.BASEV2):
|
||||||
|
"""Mapping of names created on the APIC."""
|
||||||
|
|
||||||
|
__tablename__ = 'cisco_ml2_apic_names'
|
||||||
|
|
||||||
|
neutron_id = sa.Column(sa.String(36), nullable=False, primary_key=True)
|
||||||
|
neutron_type = sa.Column(sa.String(32), nullable=False, primary_key=True)
|
||||||
|
apic_name = sa.Column(sa.String(255), nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class ApicDbModel(object):
|
class ApicDbModel(object):
|
||||||
|
|
||||||
"""DB Model to manage all APIC DB interactions."""
|
"""DB Model to manage all APIC DB interactions."""
|
||||||
@ -68,42 +82,6 @@ class ApicDbModel(object):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.session = db_api.get_session()
|
self.session = db_api.get_session()
|
||||||
|
|
||||||
def get_port_profile_for_node(self, node_id):
|
|
||||||
"""Returns a port profile for a switch if found in the DB."""
|
|
||||||
return self.session.query(PortProfile).filter_by(
|
|
||||||
node_id=node_id).first()
|
|
||||||
|
|
||||||
def get_profile_for_module_and_ports(self, node_id, profile_id,
|
|
||||||
module, from_port, to_port):
|
|
||||||
"""Returns profile for module and ports.
|
|
||||||
|
|
||||||
Grabs the profile row from the DB for the specified switch,
|
|
||||||
module (linecard) and from/to port combination.
|
|
||||||
"""
|
|
||||||
return self.session.query(PortProfile).filter_by(
|
|
||||||
node_id=node_id,
|
|
||||||
module=module,
|
|
||||||
profile_id=profile_id,
|
|
||||||
from_port=from_port,
|
|
||||||
to_port=to_port).first()
|
|
||||||
|
|
||||||
def get_profile_for_module(self, node_id, profile_id, module):
|
|
||||||
"""Returns the first profile for a switch module from the DB."""
|
|
||||||
return self.session.query(PortProfile).filter_by(
|
|
||||||
node_id=node_id,
|
|
||||||
profile_id=profile_id,
|
|
||||||
module=module).first()
|
|
||||||
|
|
||||||
def add_profile_for_module_and_ports(self, node_id, profile_id,
|
|
||||||
hpselc_id, module,
|
|
||||||
from_port, to_port):
|
|
||||||
"""Adds a profile for switch, module and port range."""
|
|
||||||
row = PortProfile(node_id=node_id, profile_id=profile_id,
|
|
||||||
hpselc_id=hpselc_id, module=module,
|
|
||||||
from_port=from_port, to_port=to_port)
|
|
||||||
self.session.add(row)
|
|
||||||
self.session.flush()
|
|
||||||
|
|
||||||
def get_provider_contract(self):
|
def get_provider_contract(self):
|
||||||
"""Returns provider EPG from the DB if found."""
|
"""Returns provider EPG from the DB if found."""
|
||||||
return self.session.query(NetworkEPG).filter_by(
|
return self.session.query(NetworkEPG).filter_by(
|
||||||
@ -170,10 +148,92 @@ class ApicDbModel(object):
|
|||||||
|
|
||||||
return contract
|
return contract
|
||||||
|
|
||||||
def delete_profile_for_node(self, node_id):
|
def add_hostlink(self, host, ifname, ifmac, swid, module, port):
|
||||||
"""Deletes the port profile for a node."""
|
link = HostLink(host=host, ifname=ifname, ifmac=ifmac,
|
||||||
profile = self.session.query(PortProfile).filter_by(
|
swid=swid, module=module, port=port)
|
||||||
node_id=node_id).first()
|
with self.session.begin(subtransactions=True):
|
||||||
if profile:
|
self.session.merge(link)
|
||||||
self.session.delete(profile)
|
|
||||||
self.session.flush()
|
def get_hostlinks(self):
|
||||||
|
return self.session.query(HostLink).all()
|
||||||
|
|
||||||
|
def get_hostlink(self, host, ifname):
|
||||||
|
return self.session.query(HostLink).filter_by(
|
||||||
|
host=host, ifname=ifname).first()
|
||||||
|
|
||||||
|
def get_hostlinks_for_host(self, host):
|
||||||
|
return self.session.query(HostLink).filter_by(
|
||||||
|
host=host).all()
|
||||||
|
|
||||||
|
def get_hostlinks_for_host_switchport(self, host, swid, module, port):
|
||||||
|
return self.session.query(HostLink).filter_by(
|
||||||
|
host=host, swid=swid, module=module, port=port).all()
|
||||||
|
|
||||||
|
def get_hostlinks_for_switchport(self, swid, module, port):
|
||||||
|
return self.session.query(HostLink).filter_by(
|
||||||
|
swid=swid, module=module, port=port).all()
|
||||||
|
|
||||||
|
def delete_hostlink(self, host, ifname):
|
||||||
|
with self.session.begin(subtransactions=True):
|
||||||
|
try:
|
||||||
|
self.session.query(HostLink).filter_by(host=host,
|
||||||
|
ifname=ifname).delete()
|
||||||
|
except orm.exc.NoResultFound:
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_switches(self):
|
||||||
|
return self.session.query(HostLink.swid).distinct()
|
||||||
|
|
||||||
|
def get_modules_for_switch(self, swid):
|
||||||
|
return self.session.query(
|
||||||
|
HostLink.module).filter_by(swid=swid).distinct()
|
||||||
|
|
||||||
|
def get_ports_for_switch_module(self, swid, module):
|
||||||
|
return self.session.query(
|
||||||
|
HostLink.port).filter_by(swid=swid, module=module).distinct()
|
||||||
|
|
||||||
|
def get_switch_and_port_for_host(self, host):
|
||||||
|
return self.session.query(
|
||||||
|
HostLink.swid, HostLink.module, HostLink.port).filter_by(
|
||||||
|
host=host).distinct()
|
||||||
|
|
||||||
|
def get_tenant_network_vlan_for_host(self, host):
|
||||||
|
pb = models_ml2.PortBinding
|
||||||
|
po = models_v2.Port
|
||||||
|
ns = models_ml2.NetworkSegment
|
||||||
|
return self.session.query(
|
||||||
|
po.tenant_id, ns.network_id, ns.segmentation_id).filter(
|
||||||
|
po.id == pb.port_id).filter(pb.host == host).filter(
|
||||||
|
po.network_id == ns.network_id).distinct()
|
||||||
|
|
||||||
|
def add_apic_name(self, neutron_id, neutron_type, apic_name):
|
||||||
|
name = ApicName(neutron_id=neutron_id,
|
||||||
|
neutron_type=neutron_type,
|
||||||
|
apic_name=apic_name)
|
||||||
|
with self.session.begin(subtransactions=True):
|
||||||
|
self.session.add(name)
|
||||||
|
|
||||||
|
def update_apic_name(self, neutron_id, neutron_type, apic_name):
|
||||||
|
with self.session.begin(subtransactions=True):
|
||||||
|
name = self.session.query(ApicName).filter_by(
|
||||||
|
neutron_id=neutron_id, neutron_type=neutron_type).first()
|
||||||
|
if name:
|
||||||
|
name.apic_name = apic_name
|
||||||
|
self.session.merge(name)
|
||||||
|
else:
|
||||||
|
self.add_apic_name(neutron_id, neutron_type, apic_name)
|
||||||
|
|
||||||
|
def get_apic_names(self):
|
||||||
|
return self.session.query(ApicName).all()
|
||||||
|
|
||||||
|
def get_apic_name(self, neutron_id, neutron_type):
|
||||||
|
return self.session.query(ApicName.apic_name).filter_by(
|
||||||
|
neutron_id=neutron_id, neutron_type=neutron_type).first()
|
||||||
|
|
||||||
|
def delete_apic_name(self, neutron_id):
|
||||||
|
with self.session.begin(subtransactions=True):
|
||||||
|
try:
|
||||||
|
self.session.query(ApicName).filter_by(
|
||||||
|
neutron_id=neutron_id).delete()
|
||||||
|
except orm.exc.NoResultFound:
|
||||||
|
return
|
||||||
|
@ -18,6 +18,22 @@
|
|||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_ROOT_HELPER = ('sudo /usr/local/bin/neutron-rootwrap '
|
||||||
|
'/etc/neutron/rootwrap.conf')
|
||||||
|
|
||||||
|
|
||||||
|
# oslo.config limits ${var} expansion to global variables
|
||||||
|
# That is why apic_system_id as a global variable
|
||||||
|
global_opts = [
|
||||||
|
cfg.StrOpt('apic_system_id',
|
||||||
|
default='openstack',
|
||||||
|
help=_("Prefix for APIC domain/names/profiles created")),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
cfg.CONF.register_opts(global_opts)
|
||||||
|
|
||||||
|
|
||||||
apic_opts = [
|
apic_opts = [
|
||||||
cfg.ListOpt('apic_hosts',
|
cfg.ListOpt('apic_hosts',
|
||||||
default=[],
|
default=[],
|
||||||
@ -27,58 +43,96 @@ apic_opts = [
|
|||||||
help=_("Username for the APIC controller")),
|
help=_("Username for the APIC controller")),
|
||||||
cfg.StrOpt('apic_password',
|
cfg.StrOpt('apic_password',
|
||||||
help=_("Password for the APIC controller"), secret=True),
|
help=_("Password for the APIC controller"), secret=True),
|
||||||
|
cfg.StrOpt('apic_name_mapping',
|
||||||
|
default='use_name',
|
||||||
|
help=_("Name mapping strategy to use: use_uuid | use_name")),
|
||||||
cfg.BoolOpt('apic_use_ssl', default=True,
|
cfg.BoolOpt('apic_use_ssl', default=True,
|
||||||
help=_("Use SSL to connect to the APIC controller")),
|
help=_("Use SSL to connect to the APIC controller")),
|
||||||
cfg.StrOpt('apic_vmm_provider', default='VMware',
|
cfg.StrOpt('apic_domain_name',
|
||||||
help=_("Name for the VMM domain provider")),
|
default='${apic_system_id}',
|
||||||
cfg.StrOpt('apic_vmm_domain', default='openstack',
|
help=_("Name for the domain created on APIC")),
|
||||||
help=_("Name for the VMM domain to be created for Openstack")),
|
cfg.StrOpt('apic_app_profile_name',
|
||||||
cfg.StrOpt('apic_vlan_ns_name', default='openstack_ns',
|
default='${apic_system_id}_app',
|
||||||
help=_("Name for the vlan namespace to be used for openstack")),
|
help=_("Name for the app profile used for Openstack")),
|
||||||
cfg.StrOpt('apic_vlan_range', default='2:4093',
|
cfg.StrOpt('apic_vlan_ns_name',
|
||||||
help=_("Range of VLAN's to be used for Openstack")),
|
default='${apic_system_id}_vlan_ns',
|
||||||
cfg.StrOpt('apic_node_profile', default='openstack_profile',
|
help=_("Name for the vlan namespace to be used for Openstack")),
|
||||||
|
cfg.StrOpt('apic_node_profile',
|
||||||
|
default='${apic_system_id}_node_profile',
|
||||||
help=_("Name of the node profile to be created")),
|
help=_("Name of the node profile to be created")),
|
||||||
cfg.StrOpt('apic_entity_profile', default='openstack_entity',
|
cfg.StrOpt('apic_entity_profile',
|
||||||
|
default='${apic_system_id}_entity_profile',
|
||||||
help=_("Name of the entity profile to be created")),
|
help=_("Name of the entity profile to be created")),
|
||||||
cfg.StrOpt('apic_function_profile', default='openstack_function',
|
cfg.StrOpt('apic_function_profile',
|
||||||
|
default='${apic_system_id}_function_profile',
|
||||||
help=_("Name of the function profile to be created")),
|
help=_("Name of the function profile to be created")),
|
||||||
cfg.BoolOpt('apic_clear_node_profiles', default=False,
|
cfg.StrOpt('apic_lacp_profile',
|
||||||
help=_("Clear the node profiles on the APIC at startup "
|
default='${apic_system_id}_lacp_profile',
|
||||||
"(mainly used for testing)")),
|
help=_("Name of the LACP profile to be created")),
|
||||||
|
cfg.ListOpt('apic_host_uplink_ports',
|
||||||
|
default=[],
|
||||||
|
help=_('The uplink ports to check for ACI connectivity')),
|
||||||
|
cfg.ListOpt('apic_vpc_pairs',
|
||||||
|
default=[],
|
||||||
|
help=_('The switch pairs for VPC connectivity')),
|
||||||
|
cfg.StrOpt('apic_vlan_range',
|
||||||
|
default='2:4093',
|
||||||
|
help=_("Range of VLAN's to be used for Openstack")),
|
||||||
|
cfg.StrOpt('root_helper',
|
||||||
|
default=DEFAULT_ROOT_HELPER,
|
||||||
|
help=_("Setup root helper as rootwrap or sudo")),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
cfg.CONF.register_opts(apic_opts, "ml2_cisco_apic")
|
cfg.CONF.register_opts(apic_opts, "ml2_cisco_apic")
|
||||||
|
|
||||||
|
|
||||||
def get_switch_and_port_for_host(host_id):
|
def _get_specific_config(prefix):
|
||||||
for switch, connected in _switch_dict.items():
|
"""retrieve config in the format [<prefix>:<value>]."""
|
||||||
for port, hosts in connected.items():
|
conf_dict = {}
|
||||||
if host_id in hosts:
|
multi_parser = cfg.MultiConfigParser()
|
||||||
return switch, port
|
multi_parser.read(cfg.CONF.config_file)
|
||||||
|
for parsed_file in multi_parser.parsed:
|
||||||
|
for parsed_item in parsed_file.keys():
|
||||||
_switch_dict = {}
|
if parsed_item.startswith(prefix):
|
||||||
|
switch, switch_id = parsed_item.split(':')
|
||||||
|
if switch.lower() == prefix:
|
||||||
|
conf_dict[switch_id] = parsed_file[parsed_item].items()
|
||||||
|
return conf_dict
|
||||||
|
|
||||||
|
|
||||||
def create_switch_dictionary():
|
def create_switch_dictionary():
|
||||||
multi_parser = cfg.MultiConfigParser()
|
switch_dict = {}
|
||||||
read_ok = multi_parser.read(cfg.CONF.config_file)
|
conf = _get_specific_config('apic_switch')
|
||||||
|
for switch_id in conf:
|
||||||
|
switch_dict[switch_id] = switch_dict.get(switch_id, {})
|
||||||
|
for host_list, port in conf[switch_id]:
|
||||||
|
hosts = host_list.split(',')
|
||||||
|
port = port[0]
|
||||||
|
switch_dict[switch_id][port] = (
|
||||||
|
switch_dict[switch_id].get(port, []) + hosts)
|
||||||
|
return switch_dict
|
||||||
|
|
||||||
if len(read_ok) != len(cfg.CONF.config_file):
|
|
||||||
raise cfg.Error(_("Some config files were not parsed properly"))
|
|
||||||
|
|
||||||
for parsed_file in multi_parser.parsed:
|
def create_vpc_dictionary():
|
||||||
for parsed_item in parsed_file.keys():
|
vpc_dict = {}
|
||||||
if parsed_item.startswith('apic_switch'):
|
for pair in cfg.CONF.ml2_cisco_apic.apic_vpc_pairs:
|
||||||
switch, switch_id = parsed_item.split(':')
|
pair_tuple = pair.split(':')
|
||||||
if switch.lower() == 'apic_switch':
|
if (len(pair_tuple) != 2 or
|
||||||
_switch_dict[switch_id] = {}
|
any(map(lambda x: not x.isdigit(), pair_tuple))):
|
||||||
port_cfg = parsed_file[parsed_item].items()
|
# Validation error, ignore this item
|
||||||
for host_list, port in port_cfg:
|
continue
|
||||||
hosts = host_list.split(',')
|
vpc_dict[pair_tuple[0]] = pair_tuple[1]
|
||||||
port = port[0]
|
vpc_dict[pair_tuple[1]] = pair_tuple[0]
|
||||||
_switch_dict[switch_id][port] = hosts
|
return vpc_dict
|
||||||
|
|
||||||
return _switch_dict
|
|
||||||
|
def create_external_network_dictionary():
|
||||||
|
router_dict = {}
|
||||||
|
conf = _get_specific_config('apic_external_network')
|
||||||
|
for net_id in conf:
|
||||||
|
router_dict[net_id] = router_dict.get(net_id, {})
|
||||||
|
for key, value in conf[net_id]:
|
||||||
|
router_dict[net_id][key] = value[0] if value else None
|
||||||
|
|
||||||
|
return router_dict
|
||||||
|
@ -16,16 +16,19 @@
|
|||||||
# @author: Arvind Somya (asomya@cisco.com), Cisco Systems Inc.
|
# @author: Arvind Somya (asomya@cisco.com), Cisco Systems Inc.
|
||||||
|
|
||||||
from apicapi import apic_manager
|
from apicapi import apic_manager
|
||||||
|
from apicapi import exceptions as exc
|
||||||
|
from keystoneclient.v2_0 import client as keyclient
|
||||||
import netaddr
|
import netaddr
|
||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from neutron.extensions import portbindings
|
from neutron.common import constants as n_constants
|
||||||
|
from neutron.openstack.common import lockutils
|
||||||
from neutron.openstack.common import log
|
from neutron.openstack.common import log
|
||||||
from neutron.plugins.common import constants
|
from neutron.plugins.common import constants
|
||||||
from neutron.plugins.ml2 import driver_api as api
|
from neutron.plugins.ml2 import driver_api as api
|
||||||
from neutron.plugins.ml2.drivers.cisco.apic import apic_model
|
from neutron.plugins.ml2.drivers.cisco.apic import apic_model
|
||||||
from neutron.plugins.ml2.drivers.cisco.apic import config
|
from neutron.plugins.ml2.drivers.cisco.apic import config
|
||||||
|
from neutron.plugins.ml2 import models
|
||||||
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
@ -34,94 +37,232 @@ LOG = log.getLogger(__name__)
|
|||||||
class APICMechanismDriver(api.MechanismDriver):
|
class APICMechanismDriver(api.MechanismDriver):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_apic_manager():
|
def get_apic_manager(client=True):
|
||||||
apic_config = cfg.CONF.ml2_cisco_apic
|
apic_config = cfg.CONF.ml2_cisco_apic
|
||||||
network_config = {
|
network_config = {
|
||||||
'vlan_ranges': cfg.CONF.ml2_type_vlan.network_vlan_ranges,
|
'vlan_ranges': cfg.CONF.ml2_type_vlan.network_vlan_ranges,
|
||||||
'switch_dict': config.create_switch_dictionary(),
|
'switch_dict': config.create_switch_dictionary(),
|
||||||
|
'vpc_dict': config.create_vpc_dictionary(),
|
||||||
|
'external_network_dict':
|
||||||
|
config.create_external_network_dictionary(),
|
||||||
}
|
}
|
||||||
|
apic_system_id = cfg.CONF.apic_system_id
|
||||||
|
keyclient_param = keyclient if client else None
|
||||||
|
keystone_authtoken = cfg.CONF.keystone_authtoken if client else None
|
||||||
return apic_manager.APICManager(apic_model.ApicDbModel(), log,
|
return apic_manager.APICManager(apic_model.ApicDbModel(), log,
|
||||||
network_config, apic_config)
|
network_config, apic_config,
|
||||||
|
keyclient_param, keystone_authtoken,
|
||||||
|
apic_system_id)
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
|
# initialize apic
|
||||||
self.apic_manager = APICMechanismDriver.get_apic_manager()
|
self.apic_manager = APICMechanismDriver.get_apic_manager()
|
||||||
|
self.name_mapper = self.apic_manager.apic_mapper
|
||||||
self.apic_manager.ensure_infra_created_on_apic()
|
self.apic_manager.ensure_infra_created_on_apic()
|
||||||
|
self.apic_manager.ensure_bgp_pod_policy_created_on_apic()
|
||||||
|
|
||||||
def _perform_port_operations(self, context):
|
@lockutils.synchronized('apic-portlock')
|
||||||
|
def _perform_path_port_operations(self, context, port):
|
||||||
|
# Get network
|
||||||
|
network_id = context.network.current['id']
|
||||||
|
anetwork_id = self.name_mapper.network(context, network_id)
|
||||||
# Get tenant details from port context
|
# Get tenant details from port context
|
||||||
tenant_id = context.current['tenant_id']
|
tenant_id = context.current['tenant_id']
|
||||||
|
tenant_id = self.name_mapper.tenant(context, tenant_id)
|
||||||
# Get network
|
|
||||||
network = context.network.current['id']
|
|
||||||
|
|
||||||
# Get port
|
|
||||||
port = context.current
|
|
||||||
|
|
||||||
# Get segmentation id
|
# Get segmentation id
|
||||||
if not context.bound_segment:
|
if not context.bound_segment:
|
||||||
LOG.debug(_("Port %s is not bound to a segment"), port)
|
LOG.debug("Port %s is not bound to a segment", port)
|
||||||
return
|
return
|
||||||
seg = None
|
seg = None
|
||||||
if (context.bound_segment.get(api.NETWORK_TYPE) in
|
if (context.bound_segment.get(api.NETWORK_TYPE)
|
||||||
[constants.TYPE_VLAN]):
|
in [constants.TYPE_VLAN]):
|
||||||
seg = context.bound_segment.get(api.SEGMENTATION_ID)
|
seg = context.bound_segment.get(api.SEGMENTATION_ID)
|
||||||
|
# hosts on which this vlan is provisioned
|
||||||
# Check if a compute port
|
|
||||||
if not port['device_owner'].startswith('compute'):
|
|
||||||
# Not a compute port, return
|
|
||||||
return
|
|
||||||
|
|
||||||
host = context.host
|
host = context.host
|
||||||
# Check host that the dhcp agent is running on
|
# Create a static path attachment for the host/epg/switchport combo
|
||||||
filters = {'device_owner': 'network:dhcp',
|
with self.apic_manager.apic.transaction() as trs:
|
||||||
'network_id': network}
|
self.apic_manager.ensure_path_created_for_port(
|
||||||
dhcp_ports = context._plugin.get_ports(context._plugin_context,
|
tenant_id, anetwork_id, host, seg, transaction=trs)
|
||||||
filters=filters)
|
|
||||||
dhcp_hosts = []
|
|
||||||
for dhcp_port in dhcp_ports:
|
|
||||||
dhcp_hosts.append(dhcp_port.get(portbindings.HOST_ID))
|
|
||||||
|
|
||||||
# Create a static path attachment for this host/epg/switchport combo
|
def _perform_gw_port_operations(self, context, port):
|
||||||
self.apic_manager.ensure_tenant_created_on_apic(tenant_id)
|
router_id = port.get('device_id')
|
||||||
if dhcp_hosts:
|
network = context.network.current
|
||||||
for dhcp_host in dhcp_hosts:
|
anetwork_id = self.name_mapper.network(context, network['id'])
|
||||||
self.apic_manager.ensure_path_created_for_port(tenant_id,
|
router_info = self.apic_manager.ext_net_dict.get(network['name'])
|
||||||
network,
|
|
||||||
dhcp_host, seg)
|
if router_id and router_info:
|
||||||
if host not in dhcp_hosts:
|
address = router_info['cidr_exposed']
|
||||||
self.apic_manager.ensure_path_created_for_port(tenant_id, network,
|
next_hop = router_info['gateway_ip']
|
||||||
host, seg)
|
encap = router_info.get('encap') # No encap if None
|
||||||
|
switch = router_info['switch']
|
||||||
|
module, sport = router_info['port'].split('/')
|
||||||
|
with self.apic_manager.apic.transaction() as trs:
|
||||||
|
# Get/Create contract
|
||||||
|
arouter_id = self.name_mapper.router(context, router_id)
|
||||||
|
cid = self.apic_manager.get_router_contract(arouter_id)
|
||||||
|
# Ensure that the external ctx exists
|
||||||
|
self.apic_manager.ensure_context_enforced()
|
||||||
|
# Create External Routed Network and configure it
|
||||||
|
self.apic_manager.ensure_external_routed_network_created(
|
||||||
|
anetwork_id, transaction=trs)
|
||||||
|
self.apic_manager.ensure_logical_node_profile_created(
|
||||||
|
anetwork_id, switch, module, sport, encap,
|
||||||
|
address, transaction=trs)
|
||||||
|
self.apic_manager.ensure_static_route_created(
|
||||||
|
anetwork_id, switch, next_hop, transaction=trs)
|
||||||
|
self.apic_manager.ensure_external_epg_created(
|
||||||
|
anetwork_id, transaction=trs)
|
||||||
|
self.apic_manager.ensure_external_epg_consumed_contract(
|
||||||
|
anetwork_id, cid, transaction=trs)
|
||||||
|
self.apic_manager.ensure_external_epg_provided_contract(
|
||||||
|
anetwork_id, cid, transaction=trs)
|
||||||
|
|
||||||
|
def _perform_port_operations(self, context):
|
||||||
|
# Get port
|
||||||
|
port = context.current
|
||||||
|
# Check if a compute port
|
||||||
|
if port.get('device_owner', '').startswith('compute'):
|
||||||
|
self._perform_path_port_operations(context, port)
|
||||||
|
elif port.get('device_owner') == n_constants.DEVICE_OWNER_ROUTER_GW:
|
||||||
|
self._perform_gw_port_operations(context, port)
|
||||||
|
elif port.get('device_owner') == n_constants.DEVICE_OWNER_DHCP:
|
||||||
|
self._perform_path_port_operations(context, port)
|
||||||
|
|
||||||
|
def _delete_contract(self, context):
|
||||||
|
port = context.current
|
||||||
|
network_id = self.name_mapper.network(
|
||||||
|
context, context.network.current['id'])
|
||||||
|
arouter_id = self.name_mapper.router(context,
|
||||||
|
port.get('device_id'))
|
||||||
|
self.apic_manager.delete_external_epg_contract(arouter_id,
|
||||||
|
network_id)
|
||||||
|
|
||||||
|
def _get_active_path_count(self, context):
|
||||||
|
return context._plugin_context.session.query(
|
||||||
|
models.PortBinding).filter_by(
|
||||||
|
host=context.host, segment=context._binding.segment).count()
|
||||||
|
|
||||||
|
@lockutils.synchronized('apic-portlock')
|
||||||
|
def _delete_port_path(self, context, atenant_id, anetwork_id):
|
||||||
|
if not self._get_active_path_count(context):
|
||||||
|
self.apic_manager.ensure_path_deleted_for_port(
|
||||||
|
atenant_id, anetwork_id,
|
||||||
|
context.host)
|
||||||
|
|
||||||
|
def _delete_path_if_last(self, context):
|
||||||
|
if not self._get_active_path_count(context):
|
||||||
|
tenant_id = context.current['tenant_id']
|
||||||
|
atenant_id = self.name_mapper.tenant(context, tenant_id)
|
||||||
|
network_id = context.network.current['id']
|
||||||
|
anetwork_id = self.name_mapper.network(context, network_id)
|
||||||
|
self._delete_port_path(context, atenant_id, anetwork_id)
|
||||||
|
|
||||||
|
def _get_subnet_info(self, context, subnet):
|
||||||
|
tenant_id = subnet['tenant_id']
|
||||||
|
network_id = subnet['network_id']
|
||||||
|
network = context._plugin.get_network(context._plugin_context,
|
||||||
|
network_id)
|
||||||
|
if not network.get('router:external'):
|
||||||
|
cidr = netaddr.IPNetwork(subnet['cidr'])
|
||||||
|
gateway_ip = '%s/%s' % (subnet['gateway_ip'], str(cidr.prefixlen))
|
||||||
|
|
||||||
|
# Convert to APIC IDs
|
||||||
|
tenant_id = self.name_mapper.tenant(context, tenant_id)
|
||||||
|
network_id = self.name_mapper.network(context, network_id)
|
||||||
|
return tenant_id, network_id, gateway_ip
|
||||||
|
|
||||||
def create_port_postcommit(self, context):
|
def create_port_postcommit(self, context):
|
||||||
self._perform_port_operations(context)
|
self._perform_port_operations(context)
|
||||||
|
|
||||||
|
def update_port_precommit(self, context):
|
||||||
|
orig = context.original
|
||||||
|
curr = context.current
|
||||||
|
if (orig['device_owner'] != curr['device_owner']
|
||||||
|
or orig['device_id'] != curr['device_id']):
|
||||||
|
raise exc.ApicOperationNotSupported(
|
||||||
|
resource='Port', msg='Port device owner and id cannot be '
|
||||||
|
'changed.')
|
||||||
|
|
||||||
def update_port_postcommit(self, context):
|
def update_port_postcommit(self, context):
|
||||||
self._perform_port_operations(context)
|
self._perform_port_operations(context)
|
||||||
|
|
||||||
def create_network_postcommit(self, context):
|
def delete_port_postcommit(self, context):
|
||||||
net_id = context.current['id']
|
port = context.current
|
||||||
tenant_id = context.current['tenant_id']
|
# Check if a compute port
|
||||||
net_name = context.current['name']
|
if port.get('device_owner', '').startswith('compute'):
|
||||||
|
self._delete_path_if_last(context)
|
||||||
|
elif port.get('device_owner') == n_constants.DEVICE_OWNER_ROUTER_GW:
|
||||||
|
self._delete_contract(context)
|
||||||
|
elif port.get('device_owner') == n_constants.DEVICE_OWNER_DHCP:
|
||||||
|
self._delete_path_if_last(context)
|
||||||
|
|
||||||
self.apic_manager.ensure_bd_created_on_apic(tenant_id, net_id)
|
def create_network_postcommit(self, context):
|
||||||
# Create EPG for this network
|
if not context.current.get('router:external'):
|
||||||
self.apic_manager.ensure_epg_created_for_network(tenant_id, net_id,
|
tenant_id = context.current['tenant_id']
|
||||||
net_name)
|
network_id = context.current['id']
|
||||||
|
|
||||||
|
# Convert to APIC IDs
|
||||||
|
tenant_id = self.name_mapper.tenant(context, tenant_id)
|
||||||
|
network_id = self.name_mapper.network(context, network_id)
|
||||||
|
|
||||||
|
# Create BD and EPG for this network
|
||||||
|
with self.apic_manager.apic.transaction() as trs:
|
||||||
|
self.apic_manager.ensure_bd_created_on_apic(tenant_id,
|
||||||
|
network_id,
|
||||||
|
transaction=trs)
|
||||||
|
self.apic_manager.ensure_epg_created(
|
||||||
|
tenant_id, network_id, transaction=trs)
|
||||||
|
|
||||||
def delete_network_postcommit(self, context):
|
def delete_network_postcommit(self, context):
|
||||||
net_id = context.current['id']
|
if not context.current.get('router:external'):
|
||||||
tenant_id = context.current['tenant_id']
|
tenant_id = context.current['tenant_id']
|
||||||
|
network_id = context.current['id']
|
||||||
|
|
||||||
self.apic_manager.delete_bd_on_apic(tenant_id, net_id)
|
# Convert to APIC IDs
|
||||||
self.apic_manager.delete_epg_for_network(tenant_id, net_id)
|
tenant_id = self.name_mapper.tenant(context, tenant_id)
|
||||||
|
network_id = self.name_mapper.network(context, network_id)
|
||||||
|
|
||||||
|
# Delete BD and EPG for this network
|
||||||
|
with self.apic_manager.apic.transaction() as trs:
|
||||||
|
self.apic_manager.delete_epg_for_network(tenant_id, network_id,
|
||||||
|
transaction=trs)
|
||||||
|
self.apic_manager.delete_bd_on_apic(tenant_id, network_id,
|
||||||
|
transaction=trs)
|
||||||
|
else:
|
||||||
|
network_name = context.current['name']
|
||||||
|
if self.apic_manager.ext_net_dict.get(network_name):
|
||||||
|
network_id = self.name_mapper.network(context,
|
||||||
|
context.current['id'])
|
||||||
|
self.apic_manager.delete_external_routed_network(network_id)
|
||||||
|
|
||||||
def create_subnet_postcommit(self, context):
|
def create_subnet_postcommit(self, context):
|
||||||
tenant_id = context.current['tenant_id']
|
info = self._get_subnet_info(context, context.current)
|
||||||
network_id = context.current['network_id']
|
if info:
|
||||||
gateway_ip = context.current['gateway_ip']
|
tenant_id, network_id, gateway_ip = info
|
||||||
cidr = netaddr.IPNetwork(context.current['cidr'])
|
# Create subnet on BD
|
||||||
netmask = str(cidr.prefixlen)
|
self.apic_manager.ensure_subnet_created_on_apic(
|
||||||
gateway_ip = gateway_ip + '/' + netmask
|
tenant_id, network_id, gateway_ip)
|
||||||
|
|
||||||
self.apic_manager.ensure_subnet_created_on_apic(tenant_id, network_id,
|
def update_subnet_postcommit(self, context):
|
||||||
gateway_ip)
|
if context.current['gateway_ip'] != context.original['gateway_ip']:
|
||||||
|
with self.apic_manager.apic.transaction() as trs:
|
||||||
|
info = self._get_subnet_info(context, context.original)
|
||||||
|
if info:
|
||||||
|
tenant_id, network_id, gateway_ip = info
|
||||||
|
# Delete subnet
|
||||||
|
self.apic_manager.ensure_subnet_deleted_on_apic(
|
||||||
|
tenant_id, network_id, gateway_ip, transaction=trs)
|
||||||
|
info = self._get_subnet_info(context, context.current)
|
||||||
|
if info:
|
||||||
|
tenant_id, network_id, gateway_ip = info
|
||||||
|
# Create subnet
|
||||||
|
self.apic_manager.ensure_subnet_created_on_apic(
|
||||||
|
tenant_id, network_id, gateway_ip, transaction=trs)
|
||||||
|
|
||||||
|
def delete_subnet_postcommit(self, context):
|
||||||
|
info = self._get_subnet_info(context, context.current)
|
||||||
|
if info:
|
||||||
|
tenant_id, network_id, gateway_ip = info
|
||||||
|
self.apic_manager.ensure_subnet_deleted_on_apic(
|
||||||
|
tenant_id, network_id, gateway_ip)
|
||||||
|
@ -15,13 +15,12 @@
|
|||||||
#
|
#
|
||||||
# @author: Henry Gessau, Cisco Systems
|
# @author: Henry Gessau, Cisco Systems
|
||||||
|
|
||||||
import mock
|
import contextlib
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
import mock
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from neutron.common import config as neutron_config
|
|
||||||
from neutron.plugins.ml2 import config as ml2_config
|
|
||||||
from neutron.tests import base
|
from neutron.tests import base
|
||||||
|
|
||||||
|
|
||||||
@ -45,9 +44,8 @@ APIC_SUBJECT = 'testSubject'
|
|||||||
APIC_FILTER = 'carbonFilter'
|
APIC_FILTER = 'carbonFilter'
|
||||||
APIC_ENTRY = 'forcedEntry'
|
APIC_ENTRY = 'forcedEntry'
|
||||||
|
|
||||||
APIC_VMMP = 'OpenStack'
|
APIC_SYSTEM_ID = 'sysid'
|
||||||
APIC_DOMAIN = 'cumuloNimbus'
|
APIC_DOMAIN = 'cumuloNimbus'
|
||||||
APIC_PDOM = 'rainStorm'
|
|
||||||
|
|
||||||
APIC_NODE_PROF = 'red'
|
APIC_NODE_PROF = 'red'
|
||||||
APIC_LEAF = 'green'
|
APIC_LEAF = 'green'
|
||||||
@ -68,6 +66,19 @@ APIC_VLANID_TO = 2999
|
|||||||
APIC_VLAN_FROM = 'vlan-%d' % APIC_VLANID_FROM
|
APIC_VLAN_FROM = 'vlan-%d' % APIC_VLANID_FROM
|
||||||
APIC_VLAN_TO = 'vlan-%d' % APIC_VLANID_TO
|
APIC_VLAN_TO = 'vlan-%d' % APIC_VLANID_TO
|
||||||
|
|
||||||
|
APIC_ROUTER = 'router_id'
|
||||||
|
|
||||||
|
APIC_EXT_SWITCH = '203'
|
||||||
|
APIC_EXT_MODULE = '1'
|
||||||
|
APIC_EXT_PORT = '34'
|
||||||
|
APIC_EXT_ENCAP = 'vlan-100'
|
||||||
|
APIC_EXT_CIDR_EXPOSED = '10.10.40.2/16'
|
||||||
|
APIC_EXT_GATEWAY_IP = '10.10.40.1'
|
||||||
|
|
||||||
|
APIC_KEY = 'key'
|
||||||
|
|
||||||
|
KEYSTONE_TOKEN = '123Token123'
|
||||||
|
|
||||||
|
|
||||||
class ControllerMixin(object):
|
class ControllerMixin(object):
|
||||||
|
|
||||||
@ -114,6 +125,10 @@ class ControllerMixin(object):
|
|||||||
self.mock_response_for_post('aaaLogin', userName=APIC_USR,
|
self.mock_response_for_post('aaaLogin', userName=APIC_USR,
|
||||||
token='ok', refreshTimeoutSeconds=timeout)
|
token='ok', refreshTimeoutSeconds=timeout)
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def fake_transaction(self, *args, **kwargs):
|
||||||
|
yield 'transaction'
|
||||||
|
|
||||||
|
|
||||||
class ConfigMixin(object):
|
class ConfigMixin(object):
|
||||||
|
|
||||||
@ -124,8 +139,13 @@ class ConfigMixin(object):
|
|||||||
|
|
||||||
def set_up_mocks(self):
|
def set_up_mocks(self):
|
||||||
# Mock the configuration file
|
# Mock the configuration file
|
||||||
args = ['--config-file', base.etcdir('neutron.conf.test')]
|
base.BaseTestCase.config_parse()
|
||||||
neutron_config.init(args=args)
|
|
||||||
|
# Configure global option apic_system_id
|
||||||
|
cfg.CONF.set_override('apic_system_id', APIC_SYSTEM_ID)
|
||||||
|
|
||||||
|
# Configure option keystone_authtoken
|
||||||
|
cfg.CONF.keystone_authtoken = KEYSTONE_TOKEN
|
||||||
|
|
||||||
# Configure the ML2 mechanism drivers and network types
|
# Configure the ML2 mechanism drivers and network types
|
||||||
ml2_opts = {
|
ml2_opts = {
|
||||||
@ -133,14 +153,23 @@ class ConfigMixin(object):
|
|||||||
'tenant_network_types': ['vlan'],
|
'tenant_network_types': ['vlan'],
|
||||||
}
|
}
|
||||||
for opt, val in ml2_opts.items():
|
for opt, val in ml2_opts.items():
|
||||||
ml2_config.cfg.CONF.set_override(opt, val, 'ml2')
|
cfg.CONF.set_override(opt, val, 'ml2')
|
||||||
|
|
||||||
|
# Configure the ML2 type_vlan opts
|
||||||
|
ml2_type_vlan_opts = {
|
||||||
|
'vlan_ranges': ['physnet1:100:199'],
|
||||||
|
}
|
||||||
|
cfg.CONF.set_override('network_vlan_ranges',
|
||||||
|
ml2_type_vlan_opts['vlan_ranges'],
|
||||||
|
'ml2_type_vlan')
|
||||||
|
self.vlan_ranges = ml2_type_vlan_opts['vlan_ranges']
|
||||||
|
|
||||||
# Configure the Cisco APIC mechanism driver
|
# Configure the Cisco APIC mechanism driver
|
||||||
apic_test_config = {
|
apic_test_config = {
|
||||||
'apic_hosts': APIC_HOSTS,
|
'apic_hosts': APIC_HOSTS,
|
||||||
'apic_username': APIC_USR,
|
'apic_username': APIC_USR,
|
||||||
'apic_password': APIC_PWD,
|
'apic_password': APIC_PWD,
|
||||||
'apic_vmm_domain': APIC_DOMAIN,
|
'apic_domain_name': APIC_SYSTEM_ID,
|
||||||
'apic_vlan_ns_name': APIC_VLAN_NAME,
|
'apic_vlan_ns_name': APIC_VLAN_NAME,
|
||||||
'apic_vlan_range': '%d:%d' % (APIC_VLANID_FROM, APIC_VLANID_TO),
|
'apic_vlan_range': '%d:%d' % (APIC_VLANID_FROM, APIC_VLANID_TO),
|
||||||
'apic_node_profile': APIC_NODE_PROF,
|
'apic_node_profile': APIC_NODE_PROF,
|
||||||
@ -149,13 +178,43 @@ class ConfigMixin(object):
|
|||||||
}
|
}
|
||||||
for opt, val in apic_test_config.items():
|
for opt, val in apic_test_config.items():
|
||||||
cfg.CONF.set_override(opt, val, 'ml2_cisco_apic')
|
cfg.CONF.set_override(opt, val, 'ml2_cisco_apic')
|
||||||
|
self.apic_config = cfg.CONF.ml2_cisco_apic
|
||||||
|
|
||||||
|
# Configure switch topology
|
||||||
apic_switch_cfg = {
|
apic_switch_cfg = {
|
||||||
'apic_switch:east01': {'ubuntu1,ubuntu2': ['3/11']},
|
'apic_switch:101': {'ubuntu1,ubuntu2': ['3/11']},
|
||||||
'apic_switch:east02': {'rhel01,rhel02': ['4/21'],
|
'apic_switch:102': {'rhel01,rhel02': ['4/21'],
|
||||||
'rhel03': ['4/22']},
|
'rhel03': ['4/22']},
|
||||||
}
|
}
|
||||||
self.mocked_parser = mock.patch.object(cfg,
|
self.switch_dict = {
|
||||||
'MultiConfigParser').start()
|
'101': {
|
||||||
|
'3/11': ['ubuntu1', 'ubuntu2'],
|
||||||
|
},
|
||||||
|
'102': {
|
||||||
|
'4/21': ['rhel01', 'rhel02'],
|
||||||
|
'4/22': ['rhel03'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.vpc_dict = {
|
||||||
|
'201': '202',
|
||||||
|
'202': '201',
|
||||||
|
}
|
||||||
|
self.external_network_dict = {
|
||||||
|
APIC_NETWORK + '-name': {
|
||||||
|
'switch': APIC_EXT_SWITCH,
|
||||||
|
'port': APIC_EXT_MODULE + '/' + APIC_EXT_PORT,
|
||||||
|
'encap': APIC_EXT_ENCAP,
|
||||||
|
'cidr_exposed': APIC_EXT_CIDR_EXPOSED,
|
||||||
|
'gateway_ip': APIC_EXT_GATEWAY_IP,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.mocked_parser = mock.patch.object(
|
||||||
|
cfg, 'MultiConfigParser').start()
|
||||||
self.mocked_parser.return_value.read.return_value = [apic_switch_cfg]
|
self.mocked_parser.return_value.read.return_value = [apic_switch_cfg]
|
||||||
self.mocked_parser.return_value.parsed = [apic_switch_cfg]
|
self.mocked_parser.return_value.parsed = [apic_switch_cfg]
|
||||||
|
|
||||||
|
|
||||||
|
class FakeDbContract(object):
|
||||||
|
|
||||||
|
def __init__(self, contract_id):
|
||||||
|
self.contract_id = contract_id
|
||||||
|
@ -15,13 +15,16 @@
|
|||||||
#
|
#
|
||||||
# @author: Henry Gessau, Cisco Systems
|
# @author: Henry Gessau, Cisco Systems
|
||||||
|
|
||||||
import mock
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
sys.modules["apicapi"] = mock.Mock()
|
sys.modules["apicapi"] = mock.Mock()
|
||||||
|
|
||||||
|
from neutron.common import constants as n_constants
|
||||||
from neutron.extensions import portbindings
|
from neutron.extensions import portbindings
|
||||||
from neutron.plugins.ml2.drivers.cisco.apic import mechanism_apic as md
|
from neutron.plugins.ml2.drivers.cisco.apic import mechanism_apic as md
|
||||||
|
from neutron.plugins.ml2.drivers import type_vlan # noqa
|
||||||
from neutron.tests import base
|
from neutron.tests import base
|
||||||
from neutron.tests.unit.ml2.drivers.cisco.apic import (
|
from neutron.tests.unit.ml2.drivers.cisco.apic import (
|
||||||
test_cisco_apic_common as mocked)
|
test_cisco_apic_common as mocked)
|
||||||
@ -50,13 +53,27 @@ class TestCiscoApicMechDriver(base.BaseTestCase,
|
|||||||
|
|
||||||
self.mock_apic_manager_login_responses()
|
self.mock_apic_manager_login_responses()
|
||||||
self.driver = md.APICMechanismDriver()
|
self.driver = md.APICMechanismDriver()
|
||||||
|
self.driver.synchronizer = None
|
||||||
|
md.APICMechanismDriver.get_base_synchronizer = mock.Mock()
|
||||||
self.driver.vif_type = 'test-vif_type'
|
self.driver.vif_type = 'test-vif_type'
|
||||||
self.driver.cap_port_filter = 'test-cap_port_filter'
|
self.driver.cap_port_filter = 'test-cap_port_filter'
|
||||||
|
self.driver.name_mapper = mock.Mock()
|
||||||
|
self.driver.name_mapper.tenant.return_value = mocked.APIC_TENANT
|
||||||
|
self.driver.name_mapper.network.return_value = mocked.APIC_NETWORK
|
||||||
|
self.driver.name_mapper.subnet.return_value = mocked.APIC_SUBNET
|
||||||
|
self.driver.name_mapper.port.return_value = mocked.APIC_PORT
|
||||||
|
self.driver.name_mapper.router.return_value = mocked.APIC_ROUTER
|
||||||
|
self.driver.name_mapper.app_profile.return_value = mocked.APIC_AP
|
||||||
|
self.driver.apic_manager = mock.Mock(
|
||||||
|
name_mapper=mock.Mock(), ext_net_dict=self.external_network_dict)
|
||||||
|
|
||||||
|
self.driver.apic_manager.apic.transaction = self.fake_transaction
|
||||||
|
|
||||||
def test_initialize(self):
|
def test_initialize(self):
|
||||||
mgr = self.driver.apic_manager = mock.Mock()
|
mgr = self.driver.apic_manager
|
||||||
self.driver.initialize()
|
self.driver.initialize()
|
||||||
mgr.ensure_infra_created_on_apic.assert_called_once()
|
mgr.ensure_infra_created_on_apic.assert_called_once()
|
||||||
|
mgr.ensure_bgp_pod_policy_created_on_apic.assert_called_once()
|
||||||
|
|
||||||
def test_update_port_postcommit(self):
|
def test_update_port_postcommit(self):
|
||||||
net_ctx = self._get_network_context(mocked.APIC_TENANT,
|
net_ctx = self._get_network_context(mocked.APIC_TENANT,
|
||||||
@ -65,36 +82,95 @@ class TestCiscoApicMechDriver(base.BaseTestCase,
|
|||||||
port_ctx = self._get_port_context(mocked.APIC_TENANT,
|
port_ctx = self._get_port_context(mocked.APIC_TENANT,
|
||||||
mocked.APIC_NETWORK,
|
mocked.APIC_NETWORK,
|
||||||
'vm1', net_ctx, HOST_ID1)
|
'vm1', net_ctx, HOST_ID1)
|
||||||
mgr = self.driver.apic_manager = mock.Mock()
|
mgr = self.driver.apic_manager
|
||||||
self.driver.update_port_postcommit(port_ctx)
|
self.driver.update_port_postcommit(port_ctx)
|
||||||
mgr.ensure_tenant_created_on_apic.assert_called_once_with(
|
|
||||||
mocked.APIC_TENANT)
|
|
||||||
mgr.ensure_path_created_for_port.assert_called_once_with(
|
mgr.ensure_path_created_for_port.assert_called_once_with(
|
||||||
mocked.APIC_TENANT, mocked.APIC_NETWORK, HOST_ID1,
|
mocked.APIC_TENANT, mocked.APIC_NETWORK, HOST_ID1,
|
||||||
ENCAP)
|
ENCAP, transaction='transaction')
|
||||||
|
|
||||||
|
def test_update_gw_port_postcommit(self):
|
||||||
|
net_ctx = self._get_network_context(mocked.APIC_TENANT,
|
||||||
|
mocked.APIC_NETWORK,
|
||||||
|
TEST_SEGMENT1, external=True)
|
||||||
|
port_ctx = self._get_port_context(mocked.APIC_TENANT,
|
||||||
|
mocked.APIC_NETWORK,
|
||||||
|
'vm1', net_ctx, HOST_ID1, gw=True)
|
||||||
|
mgr = self.driver.apic_manager
|
||||||
|
mgr.get_router_contract.return_value = mocked.FakeDbContract(
|
||||||
|
mocked.APIC_CONTRACT)
|
||||||
|
self.driver.update_port_postcommit(port_ctx)
|
||||||
|
mgr.get_router_contract.assert_called_once_with(
|
||||||
|
port_ctx.current['device_id'])
|
||||||
|
mgr.ensure_context_enforced.assert_called_once()
|
||||||
|
mgr.ensure_external_routed_network_created.assert_called_once_with(
|
||||||
|
mocked.APIC_NETWORK, transaction='transaction')
|
||||||
|
mgr.ensure_logical_node_profile_created.assert_called_once_with(
|
||||||
|
mocked.APIC_NETWORK, mocked.APIC_EXT_SWITCH,
|
||||||
|
mocked.APIC_EXT_MODULE, mocked.APIC_EXT_PORT,
|
||||||
|
mocked.APIC_EXT_ENCAP, mocked.APIC_EXT_CIDR_EXPOSED,
|
||||||
|
transaction='transaction')
|
||||||
|
mgr.ensure_static_route_created.assert_called_once_with(
|
||||||
|
mocked.APIC_NETWORK, mocked.APIC_EXT_SWITCH,
|
||||||
|
mocked.APIC_EXT_GATEWAY_IP, transaction='transaction')
|
||||||
|
mgr.ensure_external_epg_created.assert_called_once_with(
|
||||||
|
mocked.APIC_NETWORK, transaction='transaction')
|
||||||
|
mgr.ensure_external_epg_consumed_contract.assert_called_once_with(
|
||||||
|
mocked.APIC_NETWORK, mgr.get_router_contract.return_value,
|
||||||
|
transaction='transaction')
|
||||||
|
mgr.ensure_external_epg_provided_contract.assert_called_once_with(
|
||||||
|
mocked.APIC_NETWORK, mgr.get_router_contract.return_value,
|
||||||
|
transaction='transaction')
|
||||||
|
|
||||||
|
def test_update_gw_port_postcommit_fail_contract_create(self):
|
||||||
|
net_ctx = self._get_network_context(mocked.APIC_TENANT,
|
||||||
|
mocked.APIC_NETWORK,
|
||||||
|
TEST_SEGMENT1, external=True)
|
||||||
|
port_ctx = self._get_port_context(mocked.APIC_TENANT,
|
||||||
|
mocked.APIC_NETWORK,
|
||||||
|
'vm1', net_ctx, HOST_ID1, gw=True)
|
||||||
|
mgr = self.driver.apic_manager
|
||||||
|
self.driver.update_port_postcommit(port_ctx)
|
||||||
|
mgr.ensure_external_routed_network_deleted.assert_called_once()
|
||||||
|
|
||||||
def test_create_network_postcommit(self):
|
def test_create_network_postcommit(self):
|
||||||
ctx = self._get_network_context(mocked.APIC_TENANT,
|
ctx = self._get_network_context(mocked.APIC_TENANT,
|
||||||
mocked.APIC_NETWORK,
|
mocked.APIC_NETWORK,
|
||||||
TEST_SEGMENT1)
|
TEST_SEGMENT1)
|
||||||
mgr = self.driver.apic_manager = mock.Mock()
|
mgr = self.driver.apic_manager
|
||||||
self.driver.create_network_postcommit(ctx)
|
self.driver.create_network_postcommit(ctx)
|
||||||
mgr.ensure_bd_created_on_apic.assert_called_once_with(
|
mgr.ensure_bd_created_on_apic.assert_called_once_with(
|
||||||
mocked.APIC_TENANT, mocked.APIC_NETWORK)
|
mocked.APIC_TENANT, mocked.APIC_NETWORK, transaction='transaction')
|
||||||
mgr.ensure_epg_created_for_network.assert_called_once_with(
|
mgr.ensure_epg_created.assert_called_once_with(
|
||||||
mocked.APIC_TENANT, mocked.APIC_NETWORK,
|
mocked.APIC_TENANT, mocked.APIC_NETWORK, transaction='transaction')
|
||||||
mocked.APIC_NETWORK + '-name')
|
|
||||||
|
def test_create_external_network_postcommit(self):
|
||||||
|
ctx = self._get_network_context(mocked.APIC_TENANT,
|
||||||
|
mocked.APIC_NETWORK,
|
||||||
|
TEST_SEGMENT1, external=True)
|
||||||
|
mgr = self.driver.apic_manager
|
||||||
|
self.driver.create_network_postcommit(ctx)
|
||||||
|
self.assertFalse(mgr.ensure_bd_created_on_apic.called)
|
||||||
|
self.assertFalse(mgr.ensure_epg_created.called)
|
||||||
|
|
||||||
def test_delete_network_postcommit(self):
|
def test_delete_network_postcommit(self):
|
||||||
ctx = self._get_network_context(mocked.APIC_TENANT,
|
ctx = self._get_network_context(mocked.APIC_TENANT,
|
||||||
mocked.APIC_NETWORK,
|
mocked.APIC_NETWORK,
|
||||||
TEST_SEGMENT1)
|
TEST_SEGMENT1)
|
||||||
mgr = self.driver.apic_manager = mock.Mock()
|
mgr = self.driver.apic_manager
|
||||||
self.driver.delete_network_postcommit(ctx)
|
self.driver.delete_network_postcommit(ctx)
|
||||||
mgr.delete_bd_on_apic.assert_called_once_with(
|
mgr.delete_bd_on_apic.assert_called_once_with(
|
||||||
mocked.APIC_TENANT, mocked.APIC_NETWORK)
|
mocked.APIC_TENANT, mocked.APIC_NETWORK, transaction='transaction')
|
||||||
mgr.delete_epg_for_network.assert_called_once_with(
|
mgr.delete_epg_for_network.assert_called_once_with(
|
||||||
mocked.APIC_TENANT, mocked.APIC_NETWORK)
|
mocked.APIC_TENANT, mocked.APIC_NETWORK, transaction='transaction')
|
||||||
|
|
||||||
|
def test_delete_external_network_postcommit(self):
|
||||||
|
ctx = self._get_network_context(mocked.APIC_TENANT,
|
||||||
|
mocked.APIC_NETWORK,
|
||||||
|
TEST_SEGMENT1, external=True)
|
||||||
|
mgr = self.driver.apic_manager
|
||||||
|
self.driver.delete_network_postcommit(ctx)
|
||||||
|
mgr.delete_external_routed_network.assert_called_once_with(
|
||||||
|
mocked.APIC_NETWORK)
|
||||||
|
|
||||||
def test_create_subnet_postcommit(self):
|
def test_create_subnet_postcommit(self):
|
||||||
net_ctx = self._get_network_context(mocked.APIC_TENANT,
|
net_ctx = self._get_network_context(mocked.APIC_TENANT,
|
||||||
@ -103,18 +179,20 @@ class TestCiscoApicMechDriver(base.BaseTestCase,
|
|||||||
subnet_ctx = self._get_subnet_context(SUBNET_GATEWAY,
|
subnet_ctx = self._get_subnet_context(SUBNET_GATEWAY,
|
||||||
SUBNET_CIDR,
|
SUBNET_CIDR,
|
||||||
net_ctx)
|
net_ctx)
|
||||||
mgr = self.driver.apic_manager = mock.Mock()
|
mgr = self.driver.apic_manager
|
||||||
self.driver.create_subnet_postcommit(subnet_ctx)
|
self.driver.create_subnet_postcommit(subnet_ctx)
|
||||||
mgr.ensure_subnet_created_on_apic.assert_called_once_with(
|
mgr.ensure_subnet_created_on_apic.assert_called_once_with(
|
||||||
mocked.APIC_TENANT, mocked.APIC_NETWORK,
|
mocked.APIC_TENANT, mocked.APIC_NETWORK,
|
||||||
'%s/%s' % (SUBNET_GATEWAY, SUBNET_NETMASK))
|
'%s/%s' % (SUBNET_GATEWAY, SUBNET_NETMASK))
|
||||||
|
|
||||||
def _get_network_context(self, tenant_id, net_id, seg_id=None,
|
def _get_network_context(self, tenant_id, net_id, seg_id=None,
|
||||||
seg_type='vlan'):
|
seg_type='vlan', external=False):
|
||||||
network = {'id': net_id,
|
network = {'id': net_id,
|
||||||
'name': net_id + '-name',
|
'name': net_id + '-name',
|
||||||
'tenant_id': tenant_id,
|
'tenant_id': tenant_id,
|
||||||
'provider:segmentation_id': seg_id}
|
'provider:segmentation_id': seg_id}
|
||||||
|
if external:
|
||||||
|
network['router:external'] = True
|
||||||
if seg_id:
|
if seg_id:
|
||||||
network_segments = [{'id': seg_id,
|
network_segments = [{'id': seg_id,
|
||||||
'segmentation_id': ENCAP,
|
'segmentation_id': ENCAP,
|
||||||
@ -132,7 +210,8 @@ class TestCiscoApicMechDriver(base.BaseTestCase,
|
|||||||
'cidr': cidr}
|
'cidr': cidr}
|
||||||
return FakeSubnetContext(subnet, network)
|
return FakeSubnetContext(subnet, network)
|
||||||
|
|
||||||
def _get_port_context(self, tenant_id, net_id, vm_id, network, host):
|
def _get_port_context(self, tenant_id, net_id, vm_id, network, host,
|
||||||
|
gw=False):
|
||||||
port = {'device_id': vm_id,
|
port = {'device_id': vm_id,
|
||||||
'device_owner': 'compute',
|
'device_owner': 'compute',
|
||||||
'binding:host_id': host,
|
'binding:host_id': host,
|
||||||
@ -140,6 +219,9 @@ class TestCiscoApicMechDriver(base.BaseTestCase,
|
|||||||
'id': mocked.APIC_PORT,
|
'id': mocked.APIC_PORT,
|
||||||
'name': mocked.APIC_PORT,
|
'name': mocked.APIC_PORT,
|
||||||
'network_id': net_id}
|
'network_id': net_id}
|
||||||
|
if gw:
|
||||||
|
port['device_owner'] = n_constants.DEVICE_OWNER_ROUTER_GW
|
||||||
|
port['device_id'] = mocked.APIC_ROUTER
|
||||||
return FakePortContext(port, network)
|
return FakePortContext(port, network)
|
||||||
|
|
||||||
|
|
||||||
@ -165,6 +247,9 @@ class FakeSubnetContext(object):
|
|||||||
def __init__(self, subnet, network):
|
def __init__(self, subnet, network):
|
||||||
self._subnet = subnet
|
self._subnet = subnet
|
||||||
self._network = network
|
self._network = network
|
||||||
|
self._plugin = mock.Mock()
|
||||||
|
self._plugin_context = mock.Mock()
|
||||||
|
self._plugin.get_network.return_value = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current(self):
|
def current(self):
|
||||||
@ -179,11 +264,11 @@ class FakePortContext(object):
|
|||||||
"""To generate port context for testing purposes only."""
|
"""To generate port context for testing purposes only."""
|
||||||
|
|
||||||
def __init__(self, port, network):
|
def __init__(self, port, network):
|
||||||
self._fake_plugin = mock.Mock()
|
|
||||||
self._fake_plugin.get_ports.return_value = []
|
|
||||||
self._fake_plugin_context = None
|
|
||||||
self._port = port
|
self._port = port
|
||||||
self._network = network
|
self._network = network
|
||||||
|
self._plugin = mock.Mock()
|
||||||
|
self._plugin_context = mock.Mock()
|
||||||
|
self._plugin.get_ports.return_value = []
|
||||||
if network.network_segments:
|
if network.network_segments:
|
||||||
self._bound_segment = network.network_segments[0]
|
self._bound_segment = network.network_segments[0]
|
||||||
else:
|
else:
|
||||||
@ -193,14 +278,6 @@ class FakePortContext(object):
|
|||||||
def current(self):
|
def current(self):
|
||||||
return self._port
|
return self._port
|
||||||
|
|
||||||
@property
|
|
||||||
def _plugin(self):
|
|
||||||
return self._fake_plugin
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _plugin_context(self):
|
|
||||||
return self._fake_plugin_context
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def network(self):
|
def network(self):
|
||||||
return self._network
|
return self._network
|
||||||
|
@ -15,12 +15,15 @@
|
|||||||
#
|
#
|
||||||
# @author: Arvind Somya (asomya@cisco.com), Cisco Systems
|
# @author: Arvind Somya (asomya@cisco.com), Cisco Systems
|
||||||
|
|
||||||
import mock
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
sys.modules["apicapi"] = mock.Mock()
|
sys.modules["apicapi"] = mock.Mock()
|
||||||
|
|
||||||
from neutron.services.l3_router import l3_apic
|
from neutron.services.l3_router import l3_apic
|
||||||
|
from neutron.tests.unit.ml2.drivers.cisco.apic import (
|
||||||
|
test_cisco_apic_common as mocked)
|
||||||
from neutron.tests.unit import testlib_api
|
from neutron.tests.unit import testlib_api
|
||||||
|
|
||||||
TENANT = 'tenant1'
|
TENANT = 'tenant1'
|
||||||
@ -58,10 +61,14 @@ class FakePort(object):
|
|||||||
self.subnet_id = SUBNET
|
self.subnet_id = SUBNET
|
||||||
|
|
||||||
|
|
||||||
class TestCiscoApicL3Plugin(testlib_api.SqlTestCase):
|
class TestCiscoApicL3Plugin(testlib_api.SqlTestCase,
|
||||||
|
mocked.ControllerMixin,
|
||||||
|
mocked.ConfigMixin):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestCiscoApicL3Plugin, self).setUp()
|
super(TestCiscoApicL3Plugin, self).setUp()
|
||||||
|
mocked.ControllerMixin.set_up_mocks(self)
|
||||||
|
mocked.ConfigMixin.set_up_mocks(self)
|
||||||
self.plugin = l3_apic.ApicL3ServicePlugin()
|
self.plugin = l3_apic.ApicL3ServicePlugin()
|
||||||
self.context = FakeContext()
|
self.context = FakeContext()
|
||||||
self.context.tenant_id = TENANT
|
self.context.tenant_id = TENANT
|
||||||
|
Loading…
Reference in New Issue
Block a user