[ml2_cisco_apic]
# Hostname:port list of APIC controllers
-# apic_hosts=1.1.1.1:80, 1.1.1.2:8080, 1.1.1.3:80
+# apic_hosts = 1.1.1.1:80, 1.1.1.2:8080, 1.1.1.3:80
# Username for the APIC controller
-# apic_username=user
+# apic_username = user
# 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
+# apic_use_ssl = True
# How to map names to APIC: use_uuid or use_name
-# apic_name_mapping=use_name
+# apic_name_mapping = use_name
# Names for APIC objects used by Neutron
# Note: When deploying multiple clouds against one APIC,
# these names must be unique between the clouds.
-# apic_vmm_domain=openstack
-# apic_vlan_ns_name=openstack_ns
-# apic_node_profile=openstack_profile
-# apic_entity_profile=openstack_entity
-# apic_function_profile=openstack_function
-# apic_app_profile_name=openstack_app
+# apic_vmm_domain = openstack
+# apic_vlan_ns_name = openstack_ns
+# apic_node_profile = openstack_profile
+# apic_entity_profile = openstack_entity
+# apic_function_profile = openstack_function
+# apic_app_profile_name = openstack_app
+# Agent timers for State reporting and topology discovery
+# apic_sync_interval = 30
# Specify your network topology.
# This section indicates how your compute nodes are connected to the fabric's
# switches and ports. The format is as follows:
#
# [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
# participating in Openstack. e.g.
#
# [apic_switch:17]
-# ubuntu,ubuntu1=1/10
-# ubuntu2,ubuntu3=1/11
+# ubuntu,ubuntu1 = 1/10
+# ubuntu2,ubuntu3 = 1/11
#
# [apic_switch:18]
-# ubuntu5,ubuntu6=1/1
-# ubuntu7,ubuntu8=1/2
+# ubuntu5,ubuntu6 = 1/1
+# ubuntu7,ubuntu8 = 1/2
# Describe external connectivity.
# In this section you can specify the external network configuration in order
# 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>
+# 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]
--- /dev/null
+# Copyright (c) 2014 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.
+#
+# @author: Ivar Lazzaro (ivar-lazzaro), Cisco Systems Inc.
+
+from neutron.common import constants as n_constants
+from neutron import context
+from neutron import manager
+from neutron.openstack.common.gettextutils import _LW
+from neutron.openstack.common import log
+from neutron.openstack.common import loopingcall
+from neutron.plugins.ml2 import db as l2_db
+from neutron.plugins.ml2 import driver_context
+
+LOG = log.getLogger(__name__)
+
+
+class SynchronizerBase(object):
+
+ def __init__(self, driver, interval=None):
+ self.core_plugin = manager.NeutronManager.get_plugin()
+ self.driver = driver
+ self.interval = interval
+
+ def sync(self, f, *args, **kwargs):
+ """Fire synchronization based on interval.
+
+ Interval can be 0 for 'sync once' >0 for 'sync periodically' and
+ <0 for 'no sync'
+ """
+ if self.interval:
+ if self.interval > 0:
+ loop_call = loopingcall.FixedIntervalLoopingCall(f, *args,
+ **kwargs)
+ loop_call.start(interval=self.interval)
+ return loop_call
+ else:
+ # Fire once
+ f(*args, **kwargs)
+
+
+class ApicBaseSynchronizer(SynchronizerBase):
+
+ def sync_base(self):
+ self.sync(self._sync_base)
+
+ def _sync_base(self):
+ ctx = context.get_admin_context()
+ # Sync Networks
+ for network in self.core_plugin.get_networks(ctx):
+ mech_context = driver_context.NetworkContext(self.core_plugin, ctx,
+ network)
+ try:
+ self.driver.create_network_postcommit(mech_context)
+ except Exception:
+ LOG.warn(_LW("Create network postcommit failed for "
+ "network %s"), network['id'])
+
+ # Sync Subnets
+ for subnet in self.core_plugin.get_subnets(ctx):
+ mech_context = driver_context.SubnetContext(self.core_plugin, ctx,
+ subnet)
+ try:
+ self.driver.create_subnet_postcommit(mech_context)
+ except Exception:
+ LOG.warn(_LW("Create subnet postcommit failed for"
+ " subnet %s"), subnet['id'])
+
+ # Sync Ports (compute/gateway/dhcp)
+ for port in self.core_plugin.get_ports(ctx):
+ _, binding = l2_db.get_locked_port_and_binding(ctx.session,
+ port['id'])
+ network = self.core_plugin.get_network(ctx, port['network_id'])
+ mech_context = driver_context.PortContext(self.core_plugin, ctx,
+ port, network, binding)
+ try:
+ self.driver.create_port_postcommit(mech_context)
+ except Exception:
+ LOG.warn(_LW("Create port postcommit failed for"
+ " port %s"), port['id'])
+
+
+class ApicRouterSynchronizer(SynchronizerBase):
+
+ def sync_router(self):
+ self.sync(self._sync_router)
+
+ def _sync_router(self):
+ ctx = context.get_admin_context()
+ # Sync Router Interfaces
+ filters = {'device_owner': [n_constants.DEVICE_OWNER_ROUTER_INTF]}
+ for interface in self.core_plugin.get_ports(ctx, filters=filters):
+ try:
+ self.driver.add_router_interface_postcommit(
+ ctx, interface['device_id'],
+ {'port_id': interface['id']})
+ except Exception:
+ LOG.warn(_LW("Add interface postcommit failed for "
+ "port %s"), interface['id'])
cfg.StrOpt('root_helper',
default=DEFAULT_ROOT_HELPER,
help=_("Setup root helper as rootwrap or sudo")),
+ cfg.IntOpt('apic_sync_interval',
+ default=0,
+ help=_("Synchronization interval in seconds")),
]
from neutron.plugins.common import constants
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_sync
from neutron.plugins.ml2.drivers.cisco.apic import config
from neutron.plugins.ml2 import models
keyclient_param, keystone_authtoken,
apic_system_id)
+ @staticmethod
+ def get_base_synchronizer(inst):
+ apic_config = cfg.CONF.ml2_cisco_apic
+ return apic_sync.ApicBaseSynchronizer(inst,
+ apic_config.apic_sync_interval)
+
+ @staticmethod
+ def get_router_synchronizer(inst):
+ apic_config = cfg.CONF.ml2_cisco_apic
+ return apic_sync.ApicRouterSynchronizer(inst,
+ apic_config.apic_sync_interval)
+
def initialize(self):
# initialize apic
self.apic_manager = APICMechanismDriver.get_apic_manager()
self.name_mapper = self.apic_manager.apic_mapper
+ self.synchronizer = None
self.apic_manager.ensure_infra_created_on_apic()
self.apic_manager.ensure_bgp_pod_policy_created_on_apic()
+ def sync_init(f):
+ def inner(inst, *args, **kwargs):
+ if not inst.synchronizer:
+ inst.synchronizer = (
+ APICMechanismDriver.get_base_synchronizer(inst))
+ inst.synchronizer.sync_base()
+ return f(inst, *args, **kwargs)
+ return inner
+
@lockutils.synchronized('apic-portlock')
def _perform_path_port_operations(self, context, port):
# Get network
network_id = self.name_mapper.network(context, network_id)
return tenant_id, network_id, gateway_ip
+ @sync_init
def create_port_postcommit(self, context):
self._perform_port_operations(context)
resource='Port', msg='Port device owner and id cannot be '
'changed.')
+ @sync_init
def update_port_postcommit(self, context):
self._perform_port_operations(context)
elif port.get('device_owner') == n_constants.DEVICE_OWNER_DHCP:
self._delete_path_if_last(context)
+ @sync_init
def create_network_postcommit(self, context):
if not context.current.get('router:external'):
tenant_id = context.current['tenant_id']
self.apic_manager.ensure_epg_created(
tenant_id, network_id, transaction=trs)
+ @sync_init
+ def update_network_postcommit(self, context):
+ super(APICMechanismDriver, self).update_network_postcommit(context)
+
def delete_network_postcommit(self, context):
if not context.current.get('router:external'):
tenant_id = context.current['tenant_id']
context.current['id'])
self.apic_manager.delete_external_routed_network(network_id)
+ @sync_init
def create_subnet_postcommit(self, context):
info = self._get_subnet_info(context, context.current)
if info:
self.apic_manager.ensure_subnet_created_on_apic(
tenant_id, network_id, gateway_ip)
+ @sync_init
def update_subnet_postcommit(self, context):
if context.current['gateway_ip'] != context.original['gateway_ip']:
with self.apic_manager.apic.transaction() as trs:
super(ApicL3ServicePlugin, self).__init__()
self.manager = mechanism_apic.APICMechanismDriver.get_apic_manager()
self.name_mapper = self.manager.apic_mapper
+ self.synchronizer = None
self.manager.ensure_infra_created_on_apic()
self.manager.ensure_bgp_pod_policy_created_on_apic()
"""Returns string description of the plugin."""
return _("L3 Router Service Plugin for basic L3 using the APIC")
+ def sync_init(f):
+ def inner(inst, *args, **kwargs):
+ if not inst.synchronizer:
+ inst.synchronizer = (
+ mechanism_apic.APICMechanismDriver.
+ get_router_synchronizer(inst))
+ inst.synchronizer.sync_router()
+ return f(inst, *args, **kwargs)
+ return inner
+
def add_router_interface_postcommit(self, context, router_id,
interface_info):
# Update router's state first
# Router API
+ @sync_init
+ def create_router(self, *args, **kwargs):
+ return super(ApicL3ServicePlugin, self).create_router(*args, **kwargs)
+
+ @sync_init
def update_router(self, context, id, router):
result = super(ApicL3ServicePlugin, self).update_router(context,
id, router)
self.update_router_postcommit(context, result)
return result
+ @sync_init
+ def get_router(self, *args, **kwargs):
+ return super(ApicL3ServicePlugin, self).get_router(*args, **kwargs)
+
+ @sync_init
+ def get_routers(self, *args, **kwargs):
+ return super(ApicL3ServicePlugin, self).get_routers(*args, **kwargs)
+
+ @sync_init
+ def get_routers_count(self, *args, **kwargs):
+ return super(ApicL3ServicePlugin, self).get_routers_count(*args,
+ **kwargs)
+
def delete_router(self, context, router_id):
self.delete_router_precommit(context, router_id)
result = super(ApicL3ServicePlugin, self).delete_router(context,
# Router Interface API
+ @sync_init
def add_router_interface(self, context, router_id, interface_info):
# Create interface in parent
result = super(ApicL3ServicePlugin, self).add_router_interface(
--- /dev/null
+# Copyright (c) 2014 Cisco Systems
+# 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.
+#
+# @author: Ivar Lazzaro (ivarlazzaro@gmail.com), Cisco Systems, Inc.
+
+import sys
+
+import mock
+
+sys.modules["apicapi"] = mock.Mock()
+
+from neutron.plugins.ml2.drivers.cisco.apic import apic_sync
+from neutron.tests import base
+
+LOOPING_CALL = 'neutron.openstack.common.loopingcall.FixedIntervalLoopingCall'
+GET_PLUGIN = 'neutron.manager.NeutronManager.get_plugin'
+GET_ADMIN_CONTEXT = 'neutron.context.get_admin_context'
+L2_DB = 'neutron.plugins.ml2.db.get_locked_port_and_binding'
+NETWORK_CONTEXT = 'neutron.plugins.ml2.driver_context.NetworkContext'
+SUBNET_CONTEXT = 'neutron.plugins.ml2.driver_context.SubnetContext'
+PORT_CONTEXT = 'neutron.plugins.ml2.driver_context.PortContext'
+
+
+class TestCiscoApicSync(base.BaseTestCase):
+
+ def setUp(self):
+ super(TestCiscoApicSync, self).setUp()
+ self.driver = mock.Mock()
+ # Patch looping call
+ loopingcall_c = mock.patch(LOOPING_CALL).start()
+ self.loopingcall = mock.Mock()
+ loopingcall_c.return_value = self.loopingcall
+ # Patch get plugin
+ self.get_plugin = mock.patch(GET_PLUGIN).start()
+ self.get_plugin.return_value = mock.Mock()
+ # Patch get admin context
+ self.get_admin_context = mock.patch(GET_ADMIN_CONTEXT).start()
+ self.get_admin_context.return_value = mock.Mock()
+ # Patch get locked port and binding
+ self.get_locked_port_and_binding = mock.patch(L2_DB).start()
+ self.get_locked_port_and_binding.return_value = [mock.Mock()] * 2
+ # Patch driver context
+ mock.patch(NETWORK_CONTEXT).start()
+ mock.patch(SUBNET_CONTEXT).start()
+ mock.patch(PORT_CONTEXT).start()
+
+ def test_sync_base(self):
+ sync = apic_sync.ApicBaseSynchronizer(self.driver)
+ sync.core_plugin = mock.Mock()
+ sync.core_plugin.get_networks.return_value = [{'id': 'net'}]
+ sync.core_plugin.get_subnets.return_value = [{'id': 'sub'}]
+ sync.core_plugin.get_ports.return_value = [{'id': 'port',
+ 'network_id': 'net'}]
+ sync.sync_base()
+ self.driver.create_network_postcommit.assert_called_once()
+ self.driver.create_subnet_postcommit.assert_called_once()
+ self.get_locked_port_and_binding.assert_called_once()
+ self.driver.create_port_postcommit.assert_called_once()
+
+ def test_sync_router(self):
+ sync = apic_sync.ApicRouterSynchronizer(self.driver)
+ sync.core_plugin = mock.Mock()
+ sync.core_plugin.get_ports.return_value = [{'id': 'port',
+ 'network_id': 'net',
+ 'device_id': 'dev'}]
+ sync.sync_router()
+ self.driver.add_router_interface_postcommit.assert_called_once()
\ No newline at end of file