--- /dev/null
+[DATABASE]
+# (StrOpt) SQLAlchemy database connection string. This MUST be changed
+# to actually run the plugin with persistent storage.
+#
+# Default: sql_connection = sqlite://
+# Example: sql_connection = mysql://root:password@localhost/quantum_ml2?charset=utf8
+
+# (IntOpt) Database reconnection retry limit after database
+# connectivity is lost. Value of -1 specifies infinite retry limit.
+#
+# Default: sql_max_retries = -1
+# Example: sql_max_retries = 10
+
+# (IntOpt) Database reconnection interval in seconds after the initial
+# connection to the database fails.
+#
+# Default: reconnect_interval = 2
+# Example: reconnect_interval = 10
+
+# (BoolOpt) Enable the use of eventlet's db_pool for MySQL. The flags
+# sql_min_pool_size, sql_max_pool_size and sql_idle_timeout are
+# relevant only if this is enabled.
+#
+# Default: sql_dbpool_enable = False
+# Example: sql_dbpool_enable = True
+
+# (IntOpt) Minimum number of MySQL connections to keep open in a pool.
+#
+# Default: sql_min_pool_size = 1
+# Example: sql_min_pool_size = 5
+
+# (IntOpt) Maximum number of MySQL connections to keep open in a pool.
+#
+# Default: sql_max_pool_size = 5
+# Example: sql_max_pool_size = 20
+
+# (IntOpt) Timeout in seconds before idle MySQL connections are
+# reaped.
+#
+# Default: sql_idle_timeout = 3600
+# Example: sql_idle_timeout = 6000
+
+# (IntOpt) Maximum number of SQL connections to keep open in a
+# QueuePool in SQLAlchemy.
+#
+# Default: sqlalchemy_pool_size = 5
+# Example: sqlalchemy_pool_size = 10
+
+[ml2]
+# (ListOpt) List of network type driver entrypoints to be loaded from
+# the quantum.ml2.type_drivers namespace.
+#
+# Default: type_drivers = local,flat,vlan
+# Example: type_drivers = flat,vlan,gre
+
+# (ListOpt) Ordered list of network_types to allocate as tenant
+# networks. The default value 'local' is useful for single-box testing
+# but provides no connectivity between hosts.
+#
+# Default: tenant_network_types = local
+# Example: tenant_network_types = vlan,gre
+
+[ml2_type_flat]
+# (ListOpt) List of physical_network names with which flat networks
+# can be created. Use * to allow flat networks with arbitrary
+# physical_network names.
+#
+# Default:flat_networks =
+# Example:flat_networks = physnet1,physnet2
+# Example:flat_networks = *
+
+[ml2_type_vlan]
+# (ListOpt) List of <physical_network>[:<vlan_min>:<vlan_max>] tuples
+# specifying physical_network names usable for VLAN provider and
+# tenant networks, as well as ranges of VLAN tags on each
+# physical_network available for allocation as tenant networks.
+#
+# Default: network_vlan_ranges =
+# Example: network_vlan_ranges = physnet1:1000:2999,physnet2
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""ml2_initial
+
+Revision ID: 5ac71e65402c
+Revises: 32b517556ec9
+Create Date: 2013-05-27 16:08:40.853821
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '5ac71e65402c'
+down_revision = '32b517556ec9'
+
+# Change to ['*'] if this migration applies to all plugins
+
+migration_for_plugins = [
+ 'quantum.plugins.ml2.plugin.Ml2Plugin'
+]
+
+from alembic import op
+import sqlalchemy as sa
+
+
+from quantum.db import migration
+
+
+def upgrade(active_plugin=None, options=None):
+ if not migration.should_run(active_plugin, migration_for_plugins):
+ return
+
+ ### commands auto generated by Alembic - please adjust! ###
+ op.create_table(
+ 'ml2_network_segments',
+ sa.Column('id', sa.String(length=36), nullable=False),
+ sa.Column('network_id', sa.String(length=36), nullable=False),
+ sa.Column('network_type', sa.String(length=32), nullable=False),
+ sa.Column('physical_network', sa.String(length=64), nullable=True),
+ sa.Column('segmentation_id', sa.Integer(), nullable=True),
+ sa.ForeignKeyConstraint(['network_id'], ['networks.id'],
+ ondelete='CASCADE'),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table(
+ 'ml2_vlan_allocations',
+ sa.Column('physical_network', sa.String(length=64), nullable=False),
+ sa.Column('vlan_id', sa.Integer(), autoincrement=False,
+ nullable=False),
+ sa.Column('allocated', sa.Boolean(), autoincrement=False,
+ nullable=False),
+ sa.PrimaryKeyConstraint('physical_network', 'vlan_id')
+ )
+ op.create_table(
+ 'ml2_flat_allocations',
+ sa.Column('physical_network', sa.String(length=64), nullable=False),
+ sa.PrimaryKeyConstraint('physical_network')
+ )
+ ### end Alembic commands ###
+
+
+def downgrade(active_plugin=None, options=None):
+ if not migration.should_run(active_plugin, migration_for_plugins):
+ return
+
+ ### commands auto generated by Alembic - please adjust! ###
+ op.drop_table('ml2_network_segments')
+ op.drop_table('ml2_flat_allocations')
+ op.drop_table('ml2_vlan_allocations')
+ ### end Alembic commands ###
'cisco': 'quantum.plugins.cisco.network_plugin.PluginV2',
'lbr': 'quantum.plugins.linuxbridge.lb_quantum_plugin.LinuxBridgePluginV2',
'meta': 'quantum.plugins.metaplugin.meta_quantum_plugin.MetaPluginV2',
+ 'ml2': 'quantum.plugins.ml2.ml2_plugin.Ml2Plugin',
'nec': 'quantum.plugins.nec.nec_plugin.NECPluginV2',
'nvp': 'quantum.plugins.nicira.QuantumPlugin.NvpPluginV2',
'ovs': 'quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPluginV2',
L3_CAPABLE = [
PLUGINS['lbr'],
PLUGINS['meta'],
+ PLUGINS['ml2'],
PLUGINS['nec'],
PLUGINS['ovs'],
PLUGINS['ryu'],
FOLSOM_QUOTA = [
PLUGINS['lbr'],
+ PLUGINS['ml2'],
PLUGINS['nvp'],
PLUGINS['ovs'],
]
CAPABILITIES = 'binding:capabilities'
CAP_PORT_FILTER = 'port_filter'
+VIF_TYPE_UNBOUND = 'unbound'
+VIF_TYPE_BINDING_FAILED = 'binding_failed'
VIF_TYPE_OVS = 'ovs'
VIF_TYPE_BRIDGE = 'bridge'
VIF_TYPE_802_QBG = '802.1qbg'
'enforce_policy': True,
'is_visible': True},
PHYSICAL_NETWORK: {'allow_post': True, 'allow_put': True,
+ 'validate': {'type:string': None},
'default': attributes.ATTR_NOT_SPECIFIED,
'enforce_policy': True,
'is_visible': True},
--- /dev/null
+The Modular Layer 2 (ml2) plugin is a framework allowing OpenStack
+Networking to simultaneously utilize the variety of layer 2 networking
+technologies found in complex real-world data centers. It currently
+works with the existing openvswitch, linuxbridge, and hyperv L2
+agents, and is intended to replace and deprecate the monolithic
+plugins associated with those L2 agents. The ml2 framework is also
+intended to greatly simplify adding support for new L2 networking
+technologies, requiring much less initial and ongoing effort than
+would be required to add a new monolithic core plugin.
+
+Drivers within ml2 implement separately extensible sets of network
+types and of mechanisms for accessing networks of those types. Unlike
+with the metaplugin, multiple mechanisms can be used simultaneously to
+access different ports of the same virtual network. Mechanisms can
+utilize L2 agents via RPC and/or use mechanism drivers to interact
+with external devices or controllers. Virtual networks can be composed
+of multiple segments of the same or different types. Type and
+mechanism drivers are loaded as python entrypoints using the stevedore
+library.
+
+Each available network type is managed by an ml2
+TypeDriver. TypeDrivers maintain any needed type-specific network
+state, and perform provider network validation and tenant network
+allocation. The initial ml2 version includes drivers for the local,
+flat, and vlan network types. Additional TypeDrivers for gre and vxlan
+network types are expected before the havana release.
+
+RPC callback and notification interfaces support interaction with L2,
+DHCP, and L3 agents. This version has been tested with the existing
+openvswitch and linuxbridge plugins' L2 agents, and should also work
+with the hyperv L2 agent. A modular agent may be developed as a
+follow-on effort.
+
+Support for mechanism drivers is currently skeletal. The
+MechanismDriver interface is currently a stub, with details to be
+defined in future versions. MechanismDrivers will be called both
+inside and following DB transactions for network and port
+create/update/delete operations. They will also be called to establish
+a port binding, determining the VIF type and network segment to be
+used.
+
+The database schema and driver APIs support multi-segment networks,
+but the client API for multi-segment networks is not yet implemented.
+
+A devstack patch supporting use of the ml2 plugin with either the
+openvswitch or linuxbridge L2 agent for the local, flat and vlan
+network types is under review at
+https://review.openstack.org/#/c/27576/. Note that the gre network
+type and the tunnel-related RPCs are not yet implemented, so use the
+vlan network type for multi-node testing. Also note that ml2 does not
+yet work with nova's GenericVIFDriver, so it is necessary to configure
+nova to use a specific driver compatible with the L2 agent deployed on
+each compute node.
+
+Note that the ml2 plugin is new and should be conidered experimental
+at this point. It is undergoing rapid development, so driver APIs and
+other details are likely to change during the havana development
+cycle.
+
+Follow-on tasks required for full ml2 support in havana, including
+parity with the existing monolithic openvswitch, linuxbridge, and
+hyperv plugins:
+
+- Additional unit tests
+
+- Implement MechanismDriver port binding so that a useful
+ binding:vif_type value is returned for nova's GenericVIFDriver based
+ on the binding:host_id value and information from the agents_db
+
+- Implement TypeDriver for GRE networks
+
+- Implement GRE tunnel endpoint management RPCs
+
+
+Additional follow-on tasks expected for the havana release:
+
+- Extend MechanismDriver API to support integration with external
+ devices such as SDN controllers and top-of-rack switches
+
+- Implement TypeDriver for VXLAN networks
+
+- Extend providernet extension API to support multi-segment networks
--- /dev/null
+# 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.
--- /dev/null
+# 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.
+
+from oslo.config import cfg
+
+from quantum import scheduler
+
+
+ml2_opts = [
+ cfg.ListOpt('type_drivers',
+ default=['local', 'flat', 'vlan'],
+ help=_("List of network type driver entrypoints to be loaded "
+ "from the quantum.ml2.type_drivers namespace.")),
+ cfg.ListOpt('tenant_network_types',
+ default=['local'],
+ help=_("Ordered list of network_types to allocate as tenant "
+ "networks.")),
+ cfg.ListOpt('mechanism_drivers',
+ default=[],
+ help=_("List of networking mechanism driver entrypoints to "
+ "be loaded from the quantum.ml2.mechanism_drivers "
+ "namespace.")),
+]
+
+
+cfg.CONF.register_opts(ml2_opts, "ml2")
+cfg.CONF.register_opts(scheduler.AGENTS_SCHEDULER_OPTS)
--- /dev/null
+# 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.
+
+from sqlalchemy.orm import exc
+
+from quantum.db import api as db_api
+from quantum.db import models_v2
+from quantum.db import securitygroups_db as sg_db
+from quantum import manager
+from quantum.openstack.common import log
+from quantum.openstack.common import uuidutils
+from quantum.plugins.ml2 import driver_api as api
+from quantum.plugins.ml2 import models
+
+LOG = log.getLogger(__name__)
+
+
+def initialize():
+ db_api.configure_db()
+
+
+def add_network_segment(session, network_id, segment):
+ with session.begin(subtransactions=True):
+ record = models.NetworkSegment(
+ id=uuidutils.generate_uuid(),
+ network_id=network_id,
+ network_type=segment.get(api.NETWORK_TYPE),
+ physical_network=segment.get(api.PHYSICAL_NETWORK),
+ segmentation_id=segment.get(api.SEGMENTATION_ID)
+ )
+ session.add(record)
+ LOG.info(_("Added segment %(id)s of type %(network_type)s for network"
+ " %(network_id)s"), record)
+
+
+def get_network_segments(session, network_id):
+ with session.begin(subtransactions=True):
+ records = (session.query(models.NetworkSegment).
+ filter_by(network_id=network_id))
+ return [{api.NETWORK_TYPE: record.network_type,
+ api.PHYSICAL_NETWORK: record.physical_network,
+ api.SEGMENTATION_ID: record.segmentation_id}
+ for record in records]
+
+
+def get_port(session, port_id):
+ """Get port record for update within transcation."""
+
+ with session.begin(subtransactions=True):
+ try:
+ record = (session.query(models_v2.Port).
+ filter(models_v2.Port.id.startswith(port_id)).
+ one())
+ return record
+ except exc.NoResultFound:
+ return
+ except exc.MultipleResultsFound:
+ LOG.error(_("Multiple ports have port_id starting with %s"),
+ port_id)
+ return
+
+
+def get_port_and_sgs(port_id):
+ """Get port from database with security group info."""
+
+ LOG.debug(_("get_port_and_sgs() called for port_id %s"), port_id)
+ session = db_api.get_session()
+ sg_binding_port = sg_db.SecurityGroupPortBinding.port_id
+
+ with session.begin(subtransactions=True):
+ query = session.query(models_v2.Port,
+ sg_db.SecurityGroupPortBinding.security_group_id)
+ query = query.outerjoin(sg_db.SecurityGroupPortBinding,
+ models_v2.Port.id == sg_binding_port)
+ query = query.filter(models_v2.Port.id.startswith(port_id))
+ port_and_sgs = query.all()
+ if not port_and_sgs:
+ return
+ port = port_and_sgs[0][0]
+ plugin = manager.QuantumManager.get_plugin()
+ port_dict = plugin._make_port_dict(port)
+ port_dict['security_groups'] = [
+ sg_id for port_, sg_id in port_and_sgs if sg_id]
+ port_dict['security_group_rules'] = []
+ port_dict['security_group_source_groups'] = []
+ port_dict['fixed_ips'] = [ip['ip_address']
+ for ip in port['fixed_ips']]
+ return port_dict
--- /dev/null
+# 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.
+
+from abc import ABCMeta, abstractmethod
+
+# The following keys are used in the segment dictionaries passed via
+# the driver API. These are defined separately from similar keys in
+# quantum.extensions.providernet so that drivers don't need to change
+# if/when providernet moves to the core API.
+#
+NETWORK_TYPE = 'network_type'
+PHYSICAL_NETWORK = 'physical_network'
+SEGMENTATION_ID = 'segmentation_id'
+
+
+class TypeDriver(object):
+ """Define stable abstract interface for ML2 type drivers.
+
+ ML2 type drivers each support a specific network_type for provider
+ and/or tenant network segments. Type drivers must implement this
+ abstract interface, which defines the API by which the plugin uses
+ the driver to manage the persistent type-specific resource
+ allocation state associated with network segments of that type.
+
+ Network segments are represented by segment dictionaries using the
+ NETWORK_TYPE, PHYSICAL_NETWORK, and SEGMENTATION_ID keys defined
+ above, corresponding to the provider attributes. Future revisions
+ of the TypeDriver API may add additional segment dictionary
+ keys. Attributes not applicable for a particular network_type may
+ either be excluded or stored as None.
+ """
+
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def get_type(self):
+ """Get driver's network type.
+
+ :returns network_type value handled by this driver
+ """
+ pass
+
+ @abstractmethod
+ def initialize(self):
+ """Perform driver initialization.
+
+ Called after all drivers have been loaded and the database has
+ been initialized. No abstract methods defined below will be
+ called prior to this method being called.
+ """
+ pass
+
+ @abstractmethod
+ def validate_provider_segment(self, segment):
+ """Validate attributes of a provider network segment.
+
+ :param segment: segment dictionary using keys defined above
+ :returns: segment dictionary with any defaulted attributes added
+ :raises: quantum.common.exceptions.InvalidInput if invalid
+
+ Called outside transaction context to validate the provider
+ attributes for a provider network segment. Raise InvalidInput
+ if:
+
+ - any required attribute is missing
+ - any prohibited or unrecognized attribute is present
+ - any attribute value is not valid
+
+ The network_type attribute is present in segment, but
+ need not be validated.
+ """
+ pass
+
+ @abstractmethod
+ def reserve_provider_segment(self, session, segment):
+ """Reserve resource associated with a provider network segment.
+
+ :param session: database session
+ :param segment: segment dictionary using keys defined above
+
+ Called inside transaction context on session to reserve the
+ type-specific resource for a provider network segment. The
+ segment dictionary passed in was returned by a previous
+ validate_provider_segment() call.
+ """
+ pass
+
+ @abstractmethod
+ def allocate_tenant_segment(self, session):
+ """Allocate resource for a new tenant network segment.
+
+ :param session: database session
+ :returns: segment dictionary using keys defined above
+
+ Called inside transaction context on session to allocate a new
+ tenant network, typically from a type-specific resource
+ pool. If successful, return a segment dictionary describing
+ the segment. If tenant network segment cannot be allocated
+ (i.e. tenant networks not supported or resource pool is
+ exhausted), return None.
+ """
+ pass
+
+ @abstractmethod
+ def release_segment(self, session, segment):
+ """Release network segment.
+
+ :param session: database session
+ :param segment: segment dictionary using keys defined above
+
+ Called inside transaction context on session to release a
+ tenant or provider network's type-specific resource. Runtime
+ errors are not expected, but raising an exception will result
+ in rollback of the transaction.
+ """
+ pass
+
+
+class MechanismDriver(object):
+ """Define stable abstract interface for ML2 mechanism drivers.
+
+ Note that this is currently a stub class, but it is expected to be
+ functional for the H-2 milestone. It currently serves mainly to
+ help solidify the architectural distinction between TypeDrivers
+ and MechanismDrivers.
+ """
+
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def initialize(self):
+ """Perform driver initialization.
+
+ Called after all drivers have been loaded and the database has
+ been initialized. No abstract methods defined below will be
+ called prior to this method being called.
+ """
+ pass
+
+ # TODO(rkukura): Add methods called inside and after transaction
+ # for create_network, update_network, delete_network, create_port,
+ # update_port, delete_port, and maybe for port binding
+ # changes. Exceptions raised by methods called inside transactions
+ # can rollback, but shouldn't block. Methods called after
+ # transaction commits can block, and exceptions may cause deletion
+ # of resource.
--- /dev/null
+# 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.
--- /dev/null
+# 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.
+
+from oslo.config import cfg
+import sqlalchemy as sa
+
+from quantum.common import exceptions as exc
+from quantum.db import model_base
+from quantum.openstack.common import log
+from quantum.plugins.ml2 import driver_api as api
+
+LOG = log.getLogger(__name__)
+
+TYPE_FLAT = 'flat'
+
+flat_opts = [
+ cfg.ListOpt('flat_networks',
+ default=[],
+ help=_("List of physical_network names with which flat "
+ "networks can be created. Use * to allow flat "
+ "networks with arbitrary physical_network names."))
+]
+
+cfg.CONF.register_opts(flat_opts, "ml2_type_flat")
+
+
+class FlatAllocation(model_base.BASEV2):
+ """Represent persistent allocation state of a physical network.
+
+ If a record exists for a physical network, then that physical
+ network has been allocated as a flat network.
+ """
+
+ __tablename__ = 'ml2_flat_allocations'
+
+ physical_network = sa.Column(sa.String(64), nullable=False,
+ primary_key=True)
+
+
+class FlatTypeDriver(api.TypeDriver):
+ """Manage state for flat networks with ML2.
+
+ The FlatTypeDriver implements the 'flat' network_type. Flat
+ network segments provide connectivity between VMs and other
+ devices using any connected IEEE 802.1D conformant
+ physical_network, without the use of VLAN tags, tunneling, or
+ other segmentation mechanisms. Therefore at most one flat network
+ segment can exist on each available physical_network.
+ """
+
+ def __init__(self):
+ self._parse_networks(cfg.CONF.ml2_type_flat.flat_networks)
+
+ def _parse_networks(self, entries):
+ self.flat_networks = entries
+ if '*' in self.flat_networks:
+ LOG.info(_("Arbitrary flat physical_network names allowed"))
+ self.flat_networks = None
+ else:
+ # TODO(rkukura): Validate that each physical_network name
+ # is neither empty nor too long.
+ LOG.info(_("Allowable flat physical_network names: %s"),
+ self.flat_networks)
+
+ def get_type(self):
+ return TYPE_FLAT
+
+ def initialize(self):
+ LOG.info(_("ML2 FlatTypeDriver initialization complete"))
+
+ def validate_provider_segment(self, segment):
+ physical_network = segment.get(api.PHYSICAL_NETWORK)
+ if not physical_network:
+ msg = _("physical_network required for flat provider network")
+ raise exc.InvalidInput(error_message=msg)
+ if self.flat_networks and physical_network not in self.flat_networks:
+ msg = (_("physical_network '%s' unknown for flat provider network")
+ % physical_network)
+ raise exc.InvalidInput(error_message=msg)
+
+ for key, value in segment.iteritems():
+ if value and key not in [api.NETWORK_TYPE,
+ api.PHYSICAL_NETWORK]:
+ msg = _("%s prohibited for flat provider network") % key
+ raise exc.InvalidInput(error_message=msg)
+
+ return segment
+
+ def reserve_provider_segment(self, session, segment):
+ physical_network = segment[api.PHYSICAL_NETWORK]
+ with session.begin(subtransactions=True):
+ try:
+ alloc = (session.query(FlatAllocation).
+ filter_by(physical_network=physical_network).
+ with_lockmode('update').
+ one())
+ raise exc.FlatNetworkInUse(
+ physical_network=physical_network)
+ except sa.orm.exc.NoResultFound:
+ LOG.debug(_("Reserving flat network on physical "
+ "network %s"), physical_network)
+ alloc = FlatAllocation(physical_network=physical_network)
+ session.add(alloc)
+
+ def allocate_tenant_segment(self, session):
+ # Tenant flat networks are not supported.
+ return
+
+ def release_segment(self, session, segment):
+ physical_network = segment[api.PHYSICAL_NETWORK]
+ with session.begin(subtransactions=True):
+ try:
+ alloc = (session.query(FlatAllocation).
+ filter_by(physical_network=physical_network).
+ with_lockmode('update').
+ one())
+ session.delete(alloc)
+ LOG.debug(_("Releasing flat network on physical "
+ "network %s"), physical_network)
+ except sa.orm.exc.NoResultFound:
+ LOG.warning(_("No flat network found on physical network %s"),
+ physical_network)
--- /dev/null
+# 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.
+
+from quantum.common import exceptions as exc
+from quantum.openstack.common import log
+from quantum.plugins.ml2 import driver_api as api
+
+LOG = log.getLogger(__name__)
+
+TYPE_LOCAL = 'local'
+
+
+class LocalTypeDriver(api.TypeDriver):
+ """Manage state for local networks with ML2.
+
+ The LocalTypeDriver implements the 'local' network_type. Local
+ network segments provide connectivity between VMs and other
+ devices running on the same node, provided that a common local
+ network bridging technology is available to those devices. Local
+ network segments do not provide any connectivity between nodes.
+ """
+
+ def __init__(self):
+ LOG.info(_("ML2 LocalTypeDriver initialization complete"))
+
+ def get_type(self):
+ return TYPE_LOCAL
+
+ def initialize(self):
+ pass
+
+ def validate_provider_segment(self, segment):
+ for key, value in segment.iteritems():
+ if value and key not in [api.NETWORK_TYPE]:
+ msg = _("%s prohibited for local provider network") % key
+ raise exc.InvalidInput(error_message=msg)
+
+ return segment
+
+ def reserve_provider_segment(self, session, segment):
+ # No resources to reserve
+ pass
+
+ def allocate_tenant_segment(self, session):
+ # No resources to allocate
+ return {api.NETWORK_TYPE: TYPE_LOCAL}
+
+ def release_segment(self, session, segment):
+ # No resources to release
+ pass
--- /dev/null
+# 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.
+
+import sys
+
+from oslo.config import cfg
+import sqlalchemy as sa
+
+from quantum.common import constants as q_const
+from quantum.common import exceptions as exc
+from quantum.common import utils
+from quantum.db import api as db_api
+from quantum.db import model_base
+from quantum.openstack.common import log
+from quantum.plugins.common import utils as plugin_utils
+from quantum.plugins.ml2 import driver_api as api
+
+LOG = log.getLogger(__name__)
+
+TYPE_VLAN = 'vlan'
+
+vlan_opts = [
+ cfg.ListOpt('network_vlan_ranges',
+ default=[],
+ help=_("List of <physical_network>:<vlan_min>:<vlan_max> or "
+ "<physical_network> specifying physical_network names "
+ "usable for VLAN provider and tenant networks, as "
+ "well as ranges of VLAN tags on each available for "
+ "allocation to tenant networks."))
+]
+
+cfg.CONF.register_opts(vlan_opts, "ml2_type_vlan")
+
+
+class VlanAllocation(model_base.BASEV2):
+ """Represent allocation state of a vlan_id on a physical network.
+
+ If allocated is False, the vlan_id on the physical_network is
+ available for allocation to a tenant network. If allocated is
+ True, the vlan_id on the physical_network is in use, either as a
+ tenant or provider network.
+
+ When an allocation is released, if the vlan_id for the
+ physical_network is inside the pool described by
+ VlanTypeDriver.network_vlan_ranges, then allocated is set to
+ False. If it is outside the pool, the record is deleted.
+ """
+
+ __tablename__ = 'ml2_vlan_allocations'
+
+ physical_network = sa.Column(sa.String(64), nullable=False,
+ primary_key=True)
+ vlan_id = sa.Column(sa.Integer, nullable=False, primary_key=True,
+ autoincrement=False)
+ allocated = sa.Column(sa.Boolean, nullable=False)
+
+
+class VlanTypeDriver(api.TypeDriver):
+ """Manage state for VLAN networks with ML2.
+
+ The VlanTypeDriver implements the 'vlan' network_type. VLAN
+ network segments provide connectivity between VMs and other
+ devices using any connected IEEE 802.1Q conformant
+ physical_network segmented into virtual networks via IEEE 802.1Q
+ headers. Up to 4094 VLAN network segments can exist on each
+ available physical_network.
+ """
+
+ def __init__(self):
+ self._parse_network_vlan_ranges()
+
+ def _parse_network_vlan_ranges(self):
+ try:
+ self.network_vlan_ranges = plugin_utils.parse_network_vlan_ranges(
+ cfg.CONF.ml2_type_vlan.network_vlan_ranges)
+ # TODO(rkukura): Validate that each physical_network name
+ # is neither empty nor too long.
+ except Exception:
+ LOG.exception(_("Failed to parse network_vlan_ranges. "
+ "Service terminated!"))
+ sys.exit(1)
+ LOG.info(_("Network VLAN ranges: %s"), self.network_vlan_ranges)
+
+ def _sync_vlan_allocations(self):
+ session = db_api.get_session()
+ with session.begin(subtransactions=True):
+ # get existing allocations for all physical networks
+ allocations = dict()
+ allocs = (session.query(VlanAllocation).
+ with_lockmode('update'))
+ for alloc in allocs:
+ if alloc.physical_network not in allocations:
+ allocations[alloc.physical_network] = set()
+ allocations[alloc.physical_network].add(alloc)
+
+ # process vlan ranges for each configured physical network
+ for (physical_network,
+ vlan_ranges) in self.network_vlan_ranges.iteritems():
+ # determine current configured allocatable vlans for
+ # this physical network
+ vlan_ids = set()
+ for vlan_min, vlan_max in vlan_ranges:
+ vlan_ids |= set(xrange(vlan_min, vlan_max + 1))
+
+ # remove from table unallocated vlans not currently
+ # allocatable
+ if physical_network in allocations:
+ for alloc in allocations[physical_network]:
+ try:
+ # see if vlan is allocatable
+ vlan_ids.remove(alloc.vlan_id)
+ except KeyError:
+ # it's not allocatable, so check if its allocated
+ if not alloc.allocated:
+ # it's not, so remove it from table
+ LOG.debug(_("Removing vlan %(vlan_id)s on "
+ "physical network "
+ "%(physical_network)s from pool"),
+ {'vlan_id': alloc.vlan_id,
+ 'physical_network':
+ physical_network})
+ session.delete(alloc)
+ del allocations[physical_network]
+
+ # add missing allocatable vlans to table
+ for vlan_id in sorted(vlan_ids):
+ alloc = VlanAllocation(physical_network=physical_network,
+ vlan_id=vlan_id,
+ allocated=False)
+ session.add(alloc)
+
+ # remove from table unallocated vlans for any unconfigured
+ # physical networks
+ for allocs in allocations.itervalues():
+ for alloc in allocs:
+ if not alloc.allocated:
+ LOG.debug(_("Removing vlan %(vlan_id)s on physical "
+ "network %(physical_network)s from pool"),
+ {'vlan_id': alloc.vlan_id,
+ 'physical_network':
+ alloc.physical_network})
+ session.delete(alloc)
+
+ def get_type(self):
+ return TYPE_VLAN
+
+ def initialize(self):
+ self._sync_vlan_allocations()
+ LOG.info(_("VlanTypeDriver initialization complete"))
+
+ def validate_provider_segment(self, segment):
+ physical_network = segment.get(api.PHYSICAL_NETWORK)
+ if not physical_network:
+ msg = _("physical_network required for VLAN provider network")
+ raise exc.InvalidInput(error_message=msg)
+ if physical_network not in self.network_vlan_ranges:
+ msg = (_("physical_network '%s' unknown for VLAN provider network")
+ % physical_network)
+ raise exc.InvalidInput(error_message=msg)
+
+ segmentation_id = segment.get(api.SEGMENTATION_ID)
+ if segmentation_id is None:
+ msg = _("segmentation_id required for VLAN provider network")
+ raise exc.InvalidInput(error_message=msg)
+ if not utils.is_valid_vlan_tag(segmentation_id):
+ msg = (_("segmentation_id out of range (%(min)s through "
+ "%(max)s)") %
+ {'min': q_const.MIN_VLAN_TAG,
+ 'max': q_const.MAX_VLAN_TAG})
+ raise exc.InvalidInput(error_message=msg)
+
+ for key, value in segment.iteritems():
+ if value and key not in [api.NETWORK_TYPE,
+ api.PHYSICAL_NETWORK,
+ api.SEGMENTATION_ID]:
+ msg = _("%s prohibited for VLAN provider network") % key
+ raise exc.InvalidInput(error_message=msg)
+
+ return segment
+
+ def reserve_provider_segment(self, session, segment):
+ physical_network = segment[api.PHYSICAL_NETWORK]
+ vlan_id = segment[api.SEGMENTATION_ID]
+ with session.begin(subtransactions=True):
+ try:
+ alloc = (session.query(VlanAllocation).
+ filter_by(physical_network=physical_network,
+ vlan_id=vlan_id).
+ with_lockmode('update').
+ one())
+ if alloc.allocated:
+ raise exc.VlanIdInUse(vlan_id=vlan_id,
+ physical_network=physical_network)
+ LOG.debug(_("Reserving specific vlan %(vlan_id)s on physical "
+ "network %(physical_network)s from pool"),
+ {'vlan_id': vlan_id,
+ 'physical_network': physical_network})
+ alloc.allocated = True
+ except sa.orm.exc.NoResultFound:
+ LOG.debug(_("Reserving specific vlan %(vlan_id)s on physical "
+ "network %(physical_network)s outside pool"),
+ {'vlan_id': vlan_id,
+ 'physical_network': physical_network})
+ alloc = VlanAllocation(physical_network=physical_network,
+ vlan_id=vlan_id,
+ allocated=True)
+ session.add(alloc)
+
+ def allocate_tenant_segment(self, session):
+ with session.begin(subtransactions=True):
+ alloc = (session.query(VlanAllocation).
+ filter_by(allocated=False).
+ with_lockmode('update').
+ first())
+ if alloc:
+ LOG.debug(_("Allocating vlan %(vlan_id)s on physical network "
+ "%(physical_network)s from pool"),
+ {'vlan_id': alloc.vlan_id,
+ 'physical_network': alloc.physical_network})
+ alloc.allocated = True
+ return {api.NETWORK_TYPE: TYPE_VLAN,
+ api.PHYSICAL_NETWORK: alloc.physical_network,
+ api.SEGMENTATION_ID: alloc.vlan_id}
+
+ def release_segment(self, session, segment):
+ physical_network = segment[api.PHYSICAL_NETWORK]
+ vlan_id = segment[api.SEGMENTATION_ID]
+ with session.begin(subtransactions=True):
+ try:
+ alloc = (session.query(VlanAllocation).
+ filter_by(physical_network=physical_network,
+ vlan_id=vlan_id).
+ with_lockmode('update').
+ one())
+ alloc.allocated = False
+ inside = False
+ for vlan_min, vlan_max in self.network_vlan_ranges.get(
+ physical_network, []):
+ if vlan_min <= vlan_id <= vlan_max:
+ inside = True
+ break
+ if not inside:
+ session.delete(alloc)
+ LOG.debug(_("Releasing vlan %(vlan_id)s on physical "
+ "network %(physical_network)s outside pool"),
+ {'vlan_id': vlan_id,
+ 'physical_network': physical_network})
+ else:
+ LOG.debug(_("Releasing vlan %(vlan_id)s on physical "
+ "network %(physical_network)s to pool"),
+ {'vlan_id': vlan_id,
+ 'physical_network': physical_network})
+ except sa.orm.exc.NoResultFound:
+ LOG.warning(_("No vlan_id %(vlan_id)s found on physical "
+ "network %(physical_network)s"),
+ {'vlan_id': vlan_id,
+ 'physical_network': physical_network})
--- /dev/null
+# 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.
+
+import sys
+
+from oslo.config import cfg
+import stevedore
+
+from quantum.common import exceptions as exc
+from quantum.openstack.common import log
+from quantum.plugins.ml2 import driver_api as api
+
+
+LOG = log.getLogger(__name__)
+
+
+class TypeManager(stevedore.named.NamedExtensionManager):
+ """Manage network segment types using drivers."""
+
+ # Mapping from type name to DriverManager
+ drivers = {}
+
+ def __init__(self):
+ # REVISIT(rkukura): Need way to make stevedore use our logging
+ # configuration. Currently, nothing is logged if loading a
+ # driver fails.
+
+ LOG.info(_("Configured type driver names: %s"),
+ cfg.CONF.ml2.type_drivers)
+ super(TypeManager, self).__init__('quantum.ml2.type_drivers',
+ cfg.CONF.ml2.type_drivers,
+ invoke_on_load=True)
+ LOG.info(_("Loaded type driver names: %s"), self.names())
+ self._register_types()
+ self._check_tenant_network_types(cfg.CONF.ml2.tenant_network_types)
+
+ def _register_types(self):
+ for ext in self:
+ type = ext.obj.get_type()
+ if type in self.drivers:
+ LOG.error(_("Type driver '%(new_driver)s' ignored because type"
+ " driver '%(old_driver)s' is already registered"
+ " for type '%(type)s'"),
+ {'new_driver': ext.name,
+ 'old_driver': self.drivers[type].name,
+ 'type': type})
+ else:
+ self.drivers[type] = ext
+ LOG.info(_("Registered types: %s"), self.drivers.keys())
+
+ def _check_tenant_network_types(self, types):
+ self.tenant_network_types = []
+ for network_type in types:
+ if network_type in self.drivers:
+ self.tenant_network_types.append(network_type)
+ else:
+ LOG.error(_("No type driver for tenant network_type: %s. "
+ "Service terminated!"),
+ network_type)
+ sys.exit(1)
+ LOG.info(_("Tenant network_types: %s"), self.tenant_network_types)
+
+ def initialize(self):
+ for type, driver in self.drivers.iteritems():
+ LOG.info(_("Initializing driver for type '%s'"), type)
+ driver.obj.initialize()
+
+ def validate_provider_segment(self, segment):
+ network_type = segment[api.NETWORK_TYPE]
+ driver = self.drivers.get(network_type)
+ if driver:
+ return driver.obj.validate_provider_segment(segment)
+ else:
+ msg = _("network_type value '%s' not supported") % network_type
+ raise exc.InvalidInput(error_message=msg)
+
+ def reserve_provider_segment(self, session, segment):
+ network_type = segment.get(api.NETWORK_TYPE)
+ driver = self.drivers.get(network_type)
+ driver.obj.reserve_provider_segment(session, segment)
+
+ def allocate_tenant_segment(self, session):
+ for network_type in self.tenant_network_types:
+ driver = self.drivers.get(network_type)
+ segment = driver.obj.allocate_tenant_segment(session)
+ if segment:
+ return segment
+ raise exc.NoNetworkAvailable()
+
+ def release_segment(self, session, segment):
+ network_type = segment.get(api.NETWORK_TYPE)
+ driver = self.drivers.get(network_type)
+ driver.obj.release_segment(session, segment)
+
+
+class MechanismManager(stevedore.named.NamedExtensionManager):
+ """Manage networking mechanisms using drivers.
+
+ Note that this is currently a stub class, but it is expected to be
+ functional for the H-2 milestone. It currently serves mainly to
+ help solidify the architectural distinction between TypeDrivers
+ and MechanismDrivers.
+ """
+
+ def __init__(self):
+ # REVISIT(rkukura): Need way to make stevedore use our logging
+ # configuration. Currently, nothing is logged if loading a
+ # driver fails.
+
+ LOG.info(_("Configured mechanism driver names: %s"),
+ cfg.CONF.ml2.mechanism_drivers)
+ super(MechanismManager, self).__init__('quantum.ml2.mechanism_drivers',
+ cfg.CONF.ml2.mechanism_drivers,
+ invoke_on_load=True)
+ LOG.info(_("Loaded mechanism driver names: %s"), self.names())
+ # TODO(rkukura): Register mechanisms.
+
+ def initialize(self):
+ pass
+
+ # TODO(rkukura): Define mechanism dispatch methods
--- /dev/null
+# 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.
+
+import sqlalchemy as sa
+
+from quantum.db import model_base
+from quantum.db import models_v2
+
+
+class NetworkSegment(model_base.BASEV2, models_v2.HasId):
+ """Represent persistent state of a network segment.
+
+ A network segment is a portion of a quantum network with a
+ specific physical realization. A quantum network can consist of
+ one or more segments.
+ """
+
+ __tablename__ = 'ml2_network_segments'
+
+ network_id = sa.Column(sa.String(36),
+ sa.ForeignKey('networks.id', ondelete="CASCADE"),
+ nullable=False)
+ network_type = sa.Column(sa.String(32), nullable=False)
+ physical_network = sa.Column(sa.String(64))
+ segmentation_id = sa.Column(sa.Integer)
--- /dev/null
+# 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.
+
+from oslo.config import cfg
+
+from quantum.agent import securitygroups_rpc as sg_rpc
+from quantum.api.rpc.agentnotifiers import dhcp_rpc_agent_api
+from quantum.api.rpc.agentnotifiers import l3_rpc_agent_api
+from quantum.api.v2 import attributes
+from quantum.common import constants as const
+from quantum.common import exceptions as exc
+from quantum.common import topics
+from quantum.db import agentschedulers_db
+from quantum.db import db_base_plugin_v2
+from quantum.db import extraroute_db
+from quantum.db import portbindings_db
+from quantum.db import quota_db # noqa
+from quantum.db import securitygroups_rpc_base as sg_db_rpc
+from quantum.extensions import portbindings
+from quantum.extensions import providernet as provider
+from quantum.openstack.common import importutils
+from quantum.openstack.common import log
+from quantum.openstack.common import rpc as c_rpc
+from quantum.plugins.ml2 import config # noqa
+from quantum.plugins.ml2 import db
+from quantum.plugins.ml2 import driver_api as api
+from quantum.plugins.ml2 import managers
+from quantum.plugins.ml2 import rpc
+
+LOG = log.getLogger(__name__)
+
+# REVISIT(rkukura): Move this and other network_type constants to
+# providernet.py?
+TYPE_MULTI_SEGMENT = 'multi-segment'
+
+
+class Ml2Plugin(db_base_plugin_v2.QuantumDbPluginV2,
+ extraroute_db.ExtraRoute_db_mixin,
+ sg_db_rpc.SecurityGroupServerRpcMixin,
+ agentschedulers_db.AgentSchedulerDbMixin,
+ portbindings_db.PortBindingMixin):
+ """Implement the Quantum L2 abstractions using modules.
+
+ Ml2Plugin is a Quantum plugin based on separately extensible sets
+ of network types and mechanisms for connecting to networks of
+ those types. The network types and mechanisms are implemented as
+ drivers loaded via Python entry points. Networks can be made up of
+ multiple segments (not yet fully implemented).
+ """
+
+ # This attribute specifies whether the plugin supports or not
+ # bulk/pagination/sorting operations. Name mangling is used in
+ # order to ensure it is qualified by class
+ __native_bulk_support = True
+ __native_pagination_support = True
+ __native_sorting_support = True
+
+ # List of supported extensions
+ _supported_extension_aliases = ["provider", "router", "extraroute",
+ "binding", "quotas", "security-group",
+ "agent", "agent_scheduler"]
+
+ @property
+ def supported_extension_aliases(self):
+ if not hasattr(self, '_aliases'):
+ aliases = self._supported_extension_aliases[:]
+ sg_rpc.disable_security_group_extension_if_noop_driver(aliases)
+ self._aliases = aliases
+ return self._aliases
+
+ def __init__(self):
+ # First load drivers, then initialize DB, then initialize drivers
+ self.type_manager = managers.TypeManager()
+ self.mechanism_manager = managers.MechanismManager()
+ db.initialize()
+ self.type_manager.initialize()
+ self.mechanism_manager.initialize()
+
+ self._setup_rpc()
+
+ # REVISIT(rkukura): Use stevedore for these?
+ self.network_scheduler = importutils.import_object(
+ cfg.CONF.network_scheduler_driver)
+ self.router_scheduler = importutils.import_object(
+ cfg.CONF.router_scheduler_driver)
+
+ LOG.info(_("Modular L2 Plugin initialization complete"))
+
+ def _setup_rpc(self):
+ self.notifier = rpc.AgentNotifierApi(topics.AGENT)
+ self.dhcp_agent_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
+ self.l3_agent_notifier = l3_rpc_agent_api.L3AgentNotify
+ self.callbacks = rpc.RpcCallbacks(self.notifier)
+ self.topic = topics.PLUGIN
+ self.conn = c_rpc.create_connection(new=True)
+ self.dispatcher = self.callbacks.create_rpc_dispatcher()
+ self.conn.create_consumer(self.topic, self.dispatcher,
+ fanout=False)
+ self.conn.consume_in_thread()
+
+ def _process_provider_create(self, context, attrs):
+ network_type = self._get_attribute(attrs, provider.NETWORK_TYPE)
+ physical_network = self._get_attribute(attrs,
+ provider.PHYSICAL_NETWORK)
+ segmentation_id = self._get_attribute(attrs, provider.SEGMENTATION_ID)
+
+ if attributes.is_attr_set(network_type):
+ segment = {api.NETWORK_TYPE: network_type,
+ api.PHYSICAL_NETWORK: physical_network,
+ api.SEGMENTATION_ID: segmentation_id}
+ return self.type_manager.validate_provider_segment(segment)
+
+ if (attributes.is_attr_set(attrs.get(provider.PHYSICAL_NETWORK)) or
+ attributes.is_attr_set(attrs.get(provider.SEGMENTATION_ID))):
+ msg = _("network_type required if other provider attributes "
+ "specified")
+ raise exc.InvalidInput(error_message=msg)
+
+ def _get_attribute(self, attrs, key):
+ value = attrs.get(key)
+ if value is attributes.ATTR_NOT_SPECIFIED:
+ value = None
+ return value
+
+ def _check_provider_update(self, context, attrs):
+ if (attributes.is_attr_set(attrs.get(provider.NETWORK_TYPE)) or
+ attributes.is_attr_set(attrs.get(provider.PHYSICAL_NETWORK)) or
+ attributes.is_attr_set(attrs.get(provider.SEGMENTATION_ID))):
+ msg = _("Plugin does not support updating provider attributes")
+ raise exc.InvalidInput(error_message=msg)
+
+ def _extend_network_dict_provider(self, context, network):
+ id = network['id']
+ segments = db.get_network_segments(context.session, id)
+ if not segments:
+ LOG.error(_("Network %s has no segments"), id)
+ network[provider.NETWORK_TYPE] = None
+ network[provider.PHYSICAL_NETWORK] = None
+ network[provider.SEGMENTATION_ID] = None
+ elif len(segments) > 1:
+ network[provider.NETWORK_TYPE] = TYPE_MULTI_SEGMENT
+ network[provider.PHYSICAL_NETWORK] = None
+ network[provider.SEGMENTATION_ID] = None
+ else:
+ segment = segments[0]
+ network[provider.NETWORK_TYPE] = segment[api.NETWORK_TYPE]
+ network[provider.PHYSICAL_NETWORK] = segment[api.PHYSICAL_NETWORK]
+ network[provider.SEGMENTATION_ID] = segment[api.SEGMENTATION_ID]
+
+ def _filter_nets_provider(self, context, nets, filters):
+ # TODO(rkukura): Implement filtering.
+ return nets
+
+ def _extend_port_dict_binding(self, context, port):
+ # TODO(rkukura): Implement based on host_id, agents, and
+ # MechanismDrivers. Also set CAPABILITIES. Use
+ # extra_binding_dict if applicable, or maybe a new hook so
+ # base handles field processing and get_port and get_ports
+ # don't need to be overridden.
+ port[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_UNBOUND
+
+ def _notify_port_updated(self, context, port):
+ session = context.session
+ with session.begin(subtransactions=True):
+ network_id = port['network_id']
+ segments = db.get_network_segments(session, network_id)
+ if not segments:
+ LOG.warning(_("In _notify_port_updated() for port %(port_id), "
+ "network %(network_id) has no segments"),
+ {'port_id': port['id'],
+ 'network_id': network_id})
+ return
+ # TODO(rkukura): Use port binding to select segment.
+ segment = segments[0]
+ self.notifier.port_update(context, port,
+ segment[api.NETWORK_TYPE],
+ segment[api.SEGMENTATION_ID],
+ segment[api.PHYSICAL_NETWORK])
+
+ def create_network(self, context, network):
+ attrs = network['network']
+ segment = self._process_provider_create(context, attrs)
+ tenant_id = self._get_tenant_id_for_create(context, attrs)
+
+ session = context.session
+ with session.begin(subtransactions=True):
+ self._ensure_default_security_group(context, tenant_id)
+ if segment:
+ self.type_manager.reserve_provider_segment(session, segment)
+ else:
+ segment = self.type_manager.allocate_tenant_segment(session)
+ result = super(Ml2Plugin, self).create_network(context, network)
+ id = result['id']
+ self._process_l3_create(context, attrs, id)
+ # REVISIT(rkukura): Consider moving all segment management
+ # to TypeManager.
+ db.add_network_segment(session, id, segment)
+ self._extend_network_dict_provider(context, result)
+ self._extend_network_dict_l3(context, result)
+
+ return result
+
+ def update_network(self, context, id, network):
+ attrs = network['network']
+ self._check_provider_update(context, attrs)
+
+ session = context.session
+ with session.begin(subtransactions=True):
+ result = super(Ml2Plugin, self).update_network(context, id,
+ network)
+ self._process_l3_update(context, attrs, id)
+ self._extend_network_dict_provider(context, result)
+ self._extend_network_dict_l3(context, result)
+
+ return result
+
+ def get_network(self, context, id, fields=None):
+ session = context.session
+ with session.begin(subtransactions=True):
+ result = super(Ml2Plugin, self).get_network(context, id, None)
+ self._extend_network_dict_provider(context, result)
+ self._extend_network_dict_l3(context, result)
+
+ return self._fields(result, fields)
+
+ def get_networks(self, context, filters=None, fields=None,
+ sorts=None, limit=None, marker=None, page_reverse=False):
+ session = context.session
+ with session.begin(subtransactions=True):
+ nets = super(Ml2Plugin,
+ self).get_networks(context, filters, None, sorts,
+ limit, marker, page_reverse)
+ for net in nets:
+ self._extend_network_dict_provider(context, net)
+ self._extend_network_dict_l3(context, net)
+
+ nets = self._filter_nets_provider(context, nets, filters)
+ nets = self._filter_nets_l3(context, nets, filters)
+
+ return [self._fields(net, fields) for net in nets]
+
+ def delete_network(self, context, id):
+ session = context.session
+ with session.begin(subtransactions=True):
+ segments = db.get_network_segments(session, id)
+ super(Ml2Plugin, self).delete_network(context, id)
+ for segment in segments:
+ self.type_manager.release_segment(session, segment)
+ # The segment records are deleted via cascade from the
+ # network record, so explicit removal is not necessary.
+
+ self.notifier.network_delete(context, id)
+
+ def create_port(self, context, port):
+ attrs = port['port']
+ attrs['status'] = const.PORT_STATUS_DOWN
+
+ session = context.session
+ with session.begin(subtransactions=True):
+ self._ensure_default_security_group_on_port(context, port)
+ sgids = self._get_security_groups_on_port(context, port)
+ result = super(Ml2Plugin, self).create_port(context, port)
+ self._process_portbindings_create_and_update(context, attrs,
+ result)
+ self._process_port_create_security_group(context, result, sgids)
+ self._extend_port_dict_binding(context, result)
+
+ self.notify_security_groups_member_updated(context, result)
+ return result
+
+ def update_port(self, context, id, port):
+ attrs = port['port']
+ need_port_update_notify = False
+
+ session = context.session
+ with session.begin(subtransactions=True):
+ original_port = super(Ml2Plugin, self).get_port(context, id)
+ updated_port = super(Ml2Plugin, self).update_port(context, id,
+ port)
+ need_port_update_notify = self.update_security_group_on_port(
+ context, id, port, original_port, updated_port)
+ self._process_portbindings_create_and_update(context,
+ attrs,
+ updated_port)
+ self._extend_port_dict_binding(context, updated_port)
+
+ need_port_update_notify |= self.is_security_group_member_updated(
+ context, original_port, updated_port)
+
+ if original_port['admin_state_up'] != updated_port['admin_state_up']:
+ need_port_update_notify = True
+
+ if need_port_update_notify:
+ self._notify_port_updated(context, updated_port)
+
+ return updated_port
+
+ def get_port(self, context, id, fields=None):
+ session = context.session
+ with session.begin(subtransactions=True):
+ port = super(Ml2Plugin, self).get_port(context, id, fields)
+ self._extend_port_dict_binding(context, port)
+
+ return self._fields(port, fields)
+
+ def get_ports(self, context, filters=None, fields=None,
+ sorts=None, limit=None, marker=None, page_reverse=False):
+ session = context.session
+ with session.begin(subtransactions=True):
+ ports = super(Ml2Plugin,
+ self).get_ports(context, filters, fields, sorts,
+ limit, marker, page_reverse)
+ # TODO(nati): filter by security group
+ for port in ports:
+ self._extend_port_dict_binding(context, port)
+
+ return [self._fields(port, fields) for port in ports]
+
+ def delete_port(self, context, id, l3_port_check=True):
+ if l3_port_check:
+ self.prevent_l3_port_deletion(context, id)
+
+ session = context.session
+ with session.begin(subtransactions=True):
+ self.disassociate_floatingips(context, id)
+ port = self.get_port(context, id)
+ self._delete_port_security_group_bindings(context, id)
+ super(Ml2Plugin, self).delete_port(context, id)
+
+ self.notify_security_groups_member_updated(context, port)
--- /dev/null
+# 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.
+
+from quantum.agent import securitygroups_rpc as sg_rpc
+from quantum.common import constants as q_const
+from quantum.common import rpc as q_rpc
+from quantum.common import topics
+from quantum.db import agents_db
+from quantum.db import api as db_api
+from quantum.db import dhcp_rpc_base
+from quantum.db import l3_rpc_base
+from quantum.db import securitygroups_rpc_base as sg_db_rpc
+from quantum.openstack.common import log
+from quantum.openstack.common.rpc import proxy
+from quantum.plugins.ml2 import db
+from quantum.plugins.ml2 import driver_api as api
+
+LOG = log.getLogger(__name__)
+
+TAP_DEVICE_PREFIX = 'tap'
+TAP_DEVICE_PREFIX_LENGTH = 3
+
+
+class RpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
+ l3_rpc_base.L3RpcCallbackMixin,
+ sg_db_rpc.SecurityGroupServerRpcCallbackMixin):
+
+ RPC_API_VERSION = '1.1'
+ # history
+ # 1.0 Initial version (from openvswitch/linuxbridge)
+ # 1.1 Support Security Group RPC
+
+ def __init__(self, notifier):
+ self.notifier = notifier
+
+ def create_rpc_dispatcher(self):
+ '''Get the rpc dispatcher for this manager.
+
+ If a manager would like to set an rpc API version, or support more than
+ one class as the target of rpc messages, override this method.
+ '''
+ return q_rpc.PluginRpcDispatcher([self,
+ agents_db.AgentExtRpcCallback()])
+
+ @classmethod
+ def _device_to_port_id(cls, device):
+ # REVISIT(rkukura): Consider calling into MechanismDrivers to
+ # process device names, or having MechanismDrivers supply list
+ # of device prefixes to strip.
+ if device.startswith(TAP_DEVICE_PREFIX):
+ return device[TAP_DEVICE_PREFIX_LENGTH:]
+ else:
+ return device
+
+ @classmethod
+ def get_port_from_device(cls, device):
+ port_id = cls._device_to_port_id(device)
+ port = db.get_port_and_sgs(port_id)
+ if port:
+ port['device'] = device
+ return port
+
+ def get_device_details(self, rpc_context, **kwargs):
+ """Agent requests device details."""
+ agent_id = kwargs.get('agent_id')
+ device = kwargs.get('device')
+ LOG.debug(_("Device %(device)s details requested by agent "
+ "%(agent_id)s"),
+ {'device': device, 'agent_id': agent_id})
+ port_id = self._device_to_port_id(device)
+
+ session = db_api.get_session()
+ with session.begin(subtransactions=True):
+ port = db.get_port(session, port_id)
+ if not port:
+ LOG.warning(_("Device %(device)s requested by agent "
+ "%(agent_id)s not found in database"),
+ {'device': device, 'agent_id': agent_id})
+ return {'device': device}
+ segments = db.get_network_segments(session, port.network_id)
+ if not segments:
+ LOG.warning(_("Device %(device)s requested by agent "
+ "%(agent_id)s has network %(network_id) with "
+ "no segments"),
+ {'device': device,
+ 'agent_id': agent_id,
+ 'network_id': port.network_id})
+ return {'device': device}
+ #TODO(rkukura): Use/create port binding
+ segment = segments[0]
+ new_status = (q_const.PORT_STATUS_ACTIVE if port.admin_state_up
+ else q_const.PORT_STATUS_DOWN)
+ if port.status != new_status:
+ port.status = new_status
+ entry = {'device': device,
+ 'network_id': port.network_id,
+ 'port_id': port.id,
+ 'admin_state_up': port.admin_state_up,
+ 'network_type': segment[api.NETWORK_TYPE],
+ 'segmentation_id': segment[api.SEGMENTATION_ID],
+ 'physical_network': segment[api.PHYSICAL_NETWORK]}
+ LOG.debug(_("Returning: %s"), entry)
+ return entry
+
+ def update_device_down(self, rpc_context, **kwargs):
+ """Device no longer exists on agent."""
+ # TODO(garyk) - live migration and port status
+ agent_id = kwargs.get('agent_id')
+ device = kwargs.get('device')
+ LOG.debug(_("Device %(device)s no longer exists at agent "
+ "%(agent_id)s"),
+ {'device': device, 'agent_id': agent_id})
+ port_id = self._device_to_port_id(device)
+
+ session = db_api.get_session()
+ with session.begin(subtransactions=True):
+ port = db.get_port(session, port_id)
+ if not port:
+ LOG.warning(_("Device %(device)s updated down by agent "
+ "%(agent_id)s not found in database"),
+ {'device': device, 'agent_id': agent_id})
+ return {'device': device,
+ 'exists': False}
+ if port.status != q_const.PORT_STATUS_DOWN:
+ port.status = q_const.PORT_STATUS_DOWN
+ return {'device': device,
+ 'exists': True}
+
+ def update_device_up(self, rpc_context, **kwargs):
+ """Device is up on agent."""
+ agent_id = kwargs.get('agent_id')
+ device = kwargs.get('device')
+ LOG.debug(_("Device %(device)s up at agent %(agent_id)s"),
+ {'device': device, 'agent_id': agent_id})
+ port_id = self._device_to_port_id(device)
+
+ session = db_api.get_session()
+ with session.begin(subtransactions=True):
+ port = db.get_port(session, port_id)
+ if not port:
+ LOG.warning(_("Device %(device)s updated up by agent "
+ "%(agent_id)s not found in database"),
+ {'device': device, 'agent_id': agent_id})
+ if port.status != q_const.PORT_STATUS_ACTIVE:
+ port.status = q_const.PORT_STATUS_ACTIVE
+
+ # TODO(rkukura) Add tunnel_sync() here if not implemented via a
+ # driver.
+
+
+class AgentNotifierApi(proxy.RpcProxy,
+ sg_rpc.SecurityGroupAgentRpcApiMixin):
+ """Agent side of the openvswitch rpc API.
+
+ API version history:
+ 1.0 - Initial version.
+ """
+
+ BASE_RPC_API_VERSION = '1.0'
+
+ def __init__(self, topic):
+ super(AgentNotifierApi, self).__init__(
+ topic=topic, default_version=self.BASE_RPC_API_VERSION)
+ self.topic_network_delete = topics.get_topic_name(topic,
+ topics.NETWORK,
+ topics.DELETE)
+ self.topic_port_update = topics.get_topic_name(topic,
+ topics.PORT,
+ topics.UPDATE)
+
+ # TODO(rkukura): Add topic_tunnel_update here if not
+ # implemented via a driver.
+
+ def network_delete(self, context, network_id):
+ self.fanout_cast(context,
+ self.make_msg('network_delete',
+ network_id=network_id),
+ topic=self.topic_network_delete)
+
+ def port_update(self, context, port, network_type, segmentation_id,
+ physical_network):
+ self.fanout_cast(context,
+ self.make_msg('port_update',
+ port=port,
+ network_type=network_type,
+ segmentation_id=segmentation_id,
+ physical_network=physical_network),
+ topic=self.topic_port_update)
+
+ # TODO(rkukura): Add tunnel_update() here if not
+ # implemented via a driver.
--- /dev/null
+# 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.
--- /dev/null
+# 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.
+
+from quantum.tests.unit.ml2 import test_ml2_plugin
+from quantum.tests.unit.openvswitch import test_agent_scheduler
+
+
+class Ml2AgentSchedulerTestCase(
+ test_agent_scheduler.OvsAgentSchedulerTestCase):
+ plugin_str = test_ml2_plugin.PLUGIN_NAME
+
+
+class Ml2L3AgentNotifierTestCase(
+ test_agent_scheduler.OvsL3AgentNotifierTestCase):
+ plugin_str = test_ml2_plugin.PLUGIN_NAME
+
+
+class Ml2DhcpAgentNotifierTestCase(
+ test_agent_scheduler.OvsDhcpAgentNotifierTestCase):
+ plugin_str = test_ml2_plugin.PLUGIN_NAME
--- /dev/null
+# 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.
+
+from quantum.tests.unit import _test_extension_portbindings as test_bindings
+from quantum.tests.unit import test_db_plugin as test_plugin
+
+
+PLUGIN_NAME = 'quantum.plugins.ml2.plugin.Ml2Plugin'
+
+
+class Ml2PluginV2TestCase(test_plugin.QuantumDbPluginV2TestCase):
+
+ _plugin_name = PLUGIN_NAME
+
+ def setUp(self):
+ super(Ml2PluginV2TestCase, self).setUp(PLUGIN_NAME)
+ self.port_create_status = 'DOWN'
+
+
+class TestMl2BasicGet(test_plugin.TestBasicGet,
+ Ml2PluginV2TestCase):
+ pass
+
+
+class TestMl2V2HTTPResponse(test_plugin.TestV2HTTPResponse,
+ Ml2PluginV2TestCase):
+ pass
+
+
+class TestMl2NetworksV2(test_plugin.TestNetworksV2,
+ Ml2PluginV2TestCase):
+ pass
+
+
+class TestMl2PortsV2(test_plugin.TestPortsV2, Ml2PluginV2TestCase):
+
+ def test_update_port_status_build(self):
+ with self.port() as port:
+ self.assertEqual(port['port']['status'], 'DOWN')
+ self.assertEqual(self.port_create_status, 'DOWN')
+
+
+# TODO(rkukura) add TestMl2PortBinding
+
+
+# TODO(rkukura) add TestMl2PortBindingNoSG
+
+
+class TestMl2PortBindingHost(Ml2PluginV2TestCase,
+ test_bindings.PortBindingsHostTestCaseMixin):
+ pass
--- /dev/null
+# 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.
+
+"""
+Unit Tests for ml2 rpc
+"""
+
+import mock
+
+from quantum.agent import rpc as agent_rpc
+from quantum.common import topics
+from quantum.openstack.common import context
+from quantum.openstack.common import rpc
+from quantum.plugins.ml2 import rpc as plugin_rpc
+from quantum.tests import base
+
+
+class RpcApiTestCase(base.BaseTestCase):
+
+ def _test_rpc_api(self, rpcapi, topic, method, rpc_method, **kwargs):
+ ctxt = context.RequestContext('fake_user', 'fake_project')
+ expected_retval = 'foo' if method == 'call' else None
+ expected_msg = rpcapi.make_msg(method, **kwargs)
+ expected_msg['version'] = rpcapi.BASE_RPC_API_VERSION
+ if rpc_method == 'cast' and method == 'run_instance':
+ kwargs['call'] = False
+
+ rpc_method_mock = mock.Mock()
+ rpc_method_mock.return_value = expected_retval
+ setattr(rpc, rpc_method, rpc_method_mock)
+
+ retval = getattr(rpcapi, method)(ctxt, **kwargs)
+
+ self.assertEqual(retval, expected_retval)
+
+ expected_args = [ctxt, topic, expected_msg]
+ for arg, expected_arg in zip(rpc_method_mock.call_args[0],
+ expected_args):
+ self.assertEqual(arg, expected_arg)
+
+ def test_delete_network(self):
+ rpcapi = plugin_rpc.AgentNotifierApi(topics.AGENT)
+ self._test_rpc_api(rpcapi,
+ topics.get_topic_name(topics.AGENT,
+ topics.NETWORK,
+ topics.DELETE),
+ 'network_delete', rpc_method='fanout_cast',
+ network_id='fake_request_spec')
+
+ def test_port_update(self):
+ rpcapi = plugin_rpc.AgentNotifierApi(topics.AGENT)
+ self._test_rpc_api(rpcapi,
+ topics.get_topic_name(topics.AGENT,
+ topics.PORT,
+ topics.UPDATE),
+ 'port_update', rpc_method='fanout_cast',
+ port='fake_port',
+ network_type='fake_network_type',
+ segmentation_id='fake_segmentation_id',
+ physical_network='fake_physical_network')
+
+ # def test_tunnel_update(self):
+ # rpcapi = plugin_rpc.AgentNotifierApi(topics.AGENT)
+ # self._test_rpc_api(rpcapi,
+ # topics.get_topic_name(topics.AGENT,
+ # constants.TUNNEL,
+ # topics.UPDATE),
+ # 'tunnel_update', rpc_method='fanout_cast',
+ # tunnel_ip='fake_ip', tunnel_id='fake_id')
+
+ def test_device_details(self):
+ rpcapi = agent_rpc.PluginApi(topics.PLUGIN)
+ self._test_rpc_api(rpcapi, topics.PLUGIN,
+ 'get_device_details', rpc_method='call',
+ device='fake_device',
+ agent_id='fake_agent_id')
+
+ def test_update_device_down(self):
+ rpcapi = agent_rpc.PluginApi(topics.PLUGIN)
+ self._test_rpc_api(rpcapi, topics.PLUGIN,
+ 'update_device_down', rpc_method='call',
+ device='fake_device',
+ agent_id='fake_agent_id')
+
+ # def test_tunnel_sync(self):
+ # rpcapi = agent_rpc.PluginApi(topics.PLUGIN)
+ # self._test_rpc_api(rpcapi, topics.PLUGIN,
+ # 'tunnel_sync', rpc_method='call',
+ # tunnel_ip='fake_tunnel_ip')
+
+ def test_update_device_up(self):
+ rpcapi = agent_rpc.PluginApi(topics.PLUGIN)
+ self._test_rpc_api(rpcapi, topics.PLUGIN,
+ 'update_device_up', rpc_method='call',
+ device='fake_device',
+ agent_id='fake_agent_id')
--- /dev/null
+# Copyright (c) 2013 OpenStack Foundation
+# Copyright 2013, Nachi Ueno, NTT MCL, 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.
+
+import mock
+
+from quantum.api.v2 import attributes
+from quantum.extensions import securitygroup as ext_sg
+from quantum import manager
+from quantum.tests.unit import test_extension_security_group as test_sg
+from quantum.tests.unit import test_security_groups_rpc as test_sg_rpc
+
+PLUGIN_NAME = 'quantum.plugins.ml2.plugin.Ml2Plugin'
+NOTIFIER = 'quantum.plugins.ml2.rpc.AgentNotifierApi'
+
+
+class Ml2SecurityGroupsTestCase(test_sg.SecurityGroupDBTestCase):
+ _plugin_name = PLUGIN_NAME
+
+ def setUp(self, plugin=None):
+ test_sg_rpc.set_firewall_driver(test_sg_rpc.FIREWALL_HYBRID_DRIVER)
+ self.addCleanup(mock.patch.stopall)
+ notifier_p = mock.patch(NOTIFIER)
+ notifier_cls = notifier_p.start()
+ self.notifier = mock.Mock()
+ notifier_cls.return_value = self.notifier
+ self._attribute_map_bk_ = {}
+ for item in attributes.RESOURCE_ATTRIBUTE_MAP:
+ self._attribute_map_bk_[item] = (attributes.
+ RESOURCE_ATTRIBUTE_MAP[item].
+ copy())
+ super(Ml2SecurityGroupsTestCase, self).setUp(PLUGIN_NAME)
+
+ def tearDown(self):
+ super(Ml2SecurityGroupsTestCase, self).tearDown()
+ attributes.RESOURCE_ATTRIBUTE_MAP = self._attribute_map_bk_
+
+
+class TestMl2SecurityGroups(Ml2SecurityGroupsTestCase,
+ test_sg.TestSecurityGroups,
+ test_sg_rpc.SGNotificationTestMixin):
+ def test_security_group_get_port_from_device(self):
+ with self.network() as n:
+ with self.subnet(n):
+ with self.security_group() as sg:
+ security_group_id = sg['security_group']['id']
+ res = self._create_port(self.fmt, n['network']['id'])
+ port = self.deserialize(self.fmt, res)
+ fixed_ips = port['port']['fixed_ips']
+ data = {'port': {'fixed_ips': fixed_ips,
+ 'name': port['port']['name'],
+ ext_sg.SECURITYGROUPS:
+ [security_group_id]}}
+
+ req = self.new_update_request('ports', data,
+ port['port']['id'])
+ res = self.deserialize(self.fmt,
+ req.get_response(self.api))
+ port_id = res['port']['id']
+ plugin = manager.QuantumManager.get_plugin()
+ port_dict = plugin.callbacks.get_port_from_device(port_id)
+ self.assertEqual(port_id, port_dict['id'])
+ self.assertEqual([security_group_id],
+ port_dict[ext_sg.SECURITYGROUPS])
+ self.assertEqual([], port_dict['security_group_rules'])
+ self.assertEqual([fixed_ips[0]['ip_address']],
+ port_dict['fixed_ips'])
+ self._delete('ports', port_id)
+
+ def test_security_group_get_port_from_device_with_no_port(self):
+ plugin = manager.QuantumManager.get_plugin()
+ port_dict = plugin.callbacks.get_port_from_device('bad_device_id')
+ self.assertEqual(None, port_dict)
+
+
+class TestMl2SecurityGroupsXML(TestMl2SecurityGroups):
+ fmt = 'xml'
etc/quantum/plugins/linuxbridge = etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini
etc/quantum/plugins/metaplugin = etc/quantum/plugins/metaplugin/metaplugin.ini
etc/quantum/plugins/midonet = etc/quantum/plugins/midonet/midonet.ini
+ etc/quantum/plugins/ml2 = etc/quantum/plugins/ml2/ml2_conf.ini
etc/quantum/plugins/mlnx = etc/quantum/plugins/mlnx/mlnx_conf.ini
etc/quantum/plugins/nec = etc/quantum/plugins/nec/nec.ini
etc/quantum/plugins/nicira = etc/quantum/plugins/nicira/nvp.ini
quantum-ovs-cleanup = quantum.agent.ovs_cleanup_util:main
quantum-ryu-agent = quantum.plugins.ryu.agent.ryu_quantum_agent:main
quantum-server = quantum.server:main
+quantum.ml2.type_drivers =
+ flat = quantum.plugins.ml2.drivers.type_flat:FlatTypeDriver
+ local = quantum.plugins.ml2.drivers.type_local:LocalTypeDriver
+ vlan = quantum.plugins.ml2.drivers.type_vlan:VlanTypeDriver
[build_sphinx]
all_files = 1
alembic>=0.4.1
oslo.config>=1.1.0
six
+stevedore>=0.7
# Cisco plugin dependencies
python-novaclient