Merge "Initial Modular L2 plugin implementation."
This commit is contained in:
commit
be5530de49
79
etc/quantum/plugins/ml2/ml2_conf.ini
Normal file
79
etc/quantum/plugins/ml2/ml2_conf.ini
Normal file
@ -0,0 +1,79 @@
|
||||
[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
|
@ -0,0 +1,84 @@
|
||||
# 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 ###
|
@ -29,6 +29,7 @@ PLUGINS = {
|
||||
'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',
|
||||
@ -38,6 +39,7 @@ PLUGINS = {
|
||||
L3_CAPABLE = [
|
||||
PLUGINS['lbr'],
|
||||
PLUGINS['meta'],
|
||||
PLUGINS['ml2'],
|
||||
PLUGINS['nec'],
|
||||
PLUGINS['ovs'],
|
||||
PLUGINS['ryu'],
|
||||
@ -45,6 +47,7 @@ L3_CAPABLE = [
|
||||
|
||||
FOLSOM_QUOTA = [
|
||||
PLUGINS['lbr'],
|
||||
PLUGINS['ml2'],
|
||||
PLUGINS['nvp'],
|
||||
PLUGINS['ovs'],
|
||||
]
|
||||
|
@ -35,6 +35,8 @@ PROFILE = 'binding:profile'
|
||||
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'
|
||||
|
@ -30,6 +30,7 @@ EXTENDED_ATTRIBUTES_2_0 = {
|
||||
'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},
|
||||
|
82
quantum/plugins/ml2/README
Normal file
82
quantum/plugins/ml2/README
Normal file
@ -0,0 +1,82 @@
|
||||
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
|
14
quantum/plugins/ml2/__init__.py
Normal file
14
quantum/plugins/ml2/__init__.py
Normal file
@ -0,0 +1,14 @@
|
||||
# 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.
|
39
quantum/plugins/ml2/config.py
Normal file
39
quantum/plugins/ml2/config.py
Normal file
@ -0,0 +1,39 @@
|
||||
# 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)
|
100
quantum/plugins/ml2/db.py
Normal file
100
quantum/plugins/ml2/db.py
Normal file
@ -0,0 +1,100 @@
|
||||
# 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
|
158
quantum/plugins/ml2/driver_api.py
Normal file
158
quantum/plugins/ml2/driver_api.py
Normal file
@ -0,0 +1,158 @@
|
||||
# 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.
|
14
quantum/plugins/ml2/drivers/__init__.py
Normal file
14
quantum/plugins/ml2/drivers/__init__.py
Normal file
@ -0,0 +1,14 @@
|
||||
# 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.
|
134
quantum/plugins/ml2/drivers/type_flat.py
Normal file
134
quantum/plugins/ml2/drivers/type_flat.py
Normal file
@ -0,0 +1,134 @@
|
||||
# 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)
|
62
quantum/plugins/ml2/drivers/type_local.py
Normal file
62
quantum/plugins/ml2/drivers/type_local.py
Normal file
@ -0,0 +1,62 @@
|
||||
# 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
|
269
quantum/plugins/ml2/drivers/type_vlan.py
Normal file
269
quantum/plugins/ml2/drivers/type_vlan.py
Normal file
@ -0,0 +1,269 @@
|
||||
# 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})
|
133
quantum/plugins/ml2/managers.py
Normal file
133
quantum/plugins/ml2/managers.py
Normal file
@ -0,0 +1,133 @@
|
||||
# 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
|
37
quantum/plugins/ml2/models.py
Normal file
37
quantum/plugins/ml2/models.py
Normal file
@ -0,0 +1,37 @@
|
||||
# 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)
|
342
quantum/plugins/ml2/plugin.py
Normal file
342
quantum/plugins/ml2/plugin.py
Normal file
@ -0,0 +1,342 @@
|
||||
# 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)
|
203
quantum/plugins/ml2/rpc.py
Normal file
203
quantum/plugins/ml2/rpc.py
Normal file
@ -0,0 +1,203 @@
|
||||
# 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.
|
14
quantum/tests/unit/ml2/__init__.py
Normal file
14
quantum/tests/unit/ml2/__init__.py
Normal file
@ -0,0 +1,14 @@
|
||||
# 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.
|
32
quantum/tests/unit/ml2/test_agent_scheduler.py
Normal file
32
quantum/tests/unit/ml2/test_agent_scheduler.py
Normal file
@ -0,0 +1,32 @@
|
||||
# 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
|
63
quantum/tests/unit/ml2/test_ml2_plugin.py
Normal file
63
quantum/tests/unit/ml2/test_ml2_plugin.py
Normal file
@ -0,0 +1,63 @@
|
||||
# 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
|
108
quantum/tests/unit/ml2/test_rpcapi.py
Normal file
108
quantum/tests/unit/ml2/test_rpcapi.py
Normal file
@ -0,0 +1,108 @@
|
||||
# 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')
|
89
quantum/tests/unit/ml2/test_security_group.py
Normal file
89
quantum/tests/unit/ml2/test_security_group.py
Normal file
@ -0,0 +1,89 @@
|
||||
# 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'
|
@ -48,6 +48,7 @@ data_files =
|
||||
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
|
||||
@ -85,6 +86,10 @@ console_scripts =
|
||||
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
|
||||
|
@ -20,6 +20,7 @@ python-keystoneclient>=0.2.0
|
||||
alembic>=0.4.1
|
||||
oslo.config>=1.1.0
|
||||
six
|
||||
stevedore>=0.7
|
||||
|
||||
# Cisco plugin dependencies
|
||||
python-novaclient
|
||||
|
Loading…
x
Reference in New Issue
Block a user