Arista Drivers decomposition part II

As a part of vendor driver decomposition,
this patch moves the remaining Arista specific
code to openstack/networking-arista

Change-Id: Ie16b5ed936b116043dea36ec967bb5ae9cdacbdf
Partial-Implements: blueprint core-vendor-decomposition
This commit is contained in:
Sukhdev Kapur 2015-07-30 15:23:52 -07:00
parent 94dea10828
commit 50eb8041bd
13 changed files with 4 additions and 1526 deletions

View File

@ -1,100 +0,0 @@
# Defines configuration options specific for Arista ML2 Mechanism driver
[ml2_arista]
# (StrOpt) EOS IP address. This is required field. If not set, all
# communications to Arista EOS will fail
#
# eapi_host =
# Example: eapi_host = 192.168.0.1
#
# (StrOpt) EOS command API username. This is required field.
# if not set, all communications to Arista EOS will fail.
#
# eapi_username =
# Example: arista_eapi_username = admin
#
# (StrOpt) EOS command API password. This is required field.
# if not set, all communications to Arista EOS will fail.
#
# eapi_password =
# Example: eapi_password = my_password
#
# (StrOpt) Defines if hostnames are sent to Arista EOS as FQDNs
# ("node1.domain.com") or as short names ("node1"). This is
# optional. If not set, a value of "True" is assumed.
#
# use_fqdn =
# Example: use_fqdn = True
#
# (IntOpt) Sync interval in seconds between Neutron plugin and EOS.
# This field defines how often the synchronization is performed.
# This is an optional field. If not set, a value of 180 seconds
# is assumed.
#
# sync_interval =
# Example: sync_interval = 60
#
# (StrOpt) Defines Region Name that is assigned to this OpenStack Controller.
# This is useful when multiple OpenStack/Neutron controllers are
# managing the same Arista HW clusters. Note that this name must
# match with the region name registered (or known) to keystone
# service. Authentication with Keysotne is performed by EOS.
# This is optional. If not set, a value of "RegionOne" is assumed.
#
# region_name =
# Example: region_name = RegionOne
[l3_arista]
# (StrOpt) primary host IP address. This is required field. If not set, all
# communications to Arista EOS will fail. This is the host where
# primary router is created.
#
# primary_l3_host =
# Example: primary_l3_host = 192.168.10.10
#
# (StrOpt) Primary host username. This is required field.
# if not set, all communications to Arista EOS will fail.
#
# primary_l3_host_username =
# Example: arista_primary_l3_username = admin
#
# (StrOpt) Primary host password. This is required field.
# if not set, all communications to Arista EOS will fail.
#
# primary_l3_host_password =
# Example: primary_l3_password = my_password
#
# (StrOpt) IP address of the second Arista switch paired as
# MLAG (Multi-chassis Link Aggregation) with the first.
# This is optional field, however, if mlag_config flag is set,
# then this is a required field. If not set, all
# communications to Arista EOS will fail. If mlag_config is set
# to False, then this field is ignored
#
# seconadary_l3_host =
# Example: seconadary_l3_host = 192.168.10.20
#
# (BoolOpt) Defines if Arista switches are configured in MLAG mode
# If yes, all L3 configuration is pushed to both switches
# automatically. If this flag is set, ensure that secondary_l3_host
# is set to the second switch's IP.
# This flag is Optional. If not set, a value of "False" is assumed.
#
# mlag_config =
# Example: mlag_config = True
#
# (BoolOpt) Defines if the router is created in default VRF or a
# a specific VRF. This is optional.
# If not set, a value of "False" is assumed.
#
# Example: use_vrf = True
#
# (IntOpt) Sync interval in seconds between Neutron plugin and EOS.
# This field defines how often the synchronization is performed.
# This is an optional field. If not set, a value of 180 seconds
# is assumed.
#
# l3_sync_interval =
# Example: l3_sync_interval = 60

View File

@ -25,6 +25,10 @@ LBAAS_TABLES = ['vips', 'sessionpersistences', 'pools', 'healthmonitors',
FWAAS_TABLES = ['firewall_rules', 'firewalls', 'firewall_policies']
DRIVER_TABLES = [
# Arista ML2 driver Models moved to openstack/networking-arista
'arista_provisioned_nets',
'arista_provisioned_vms',
'arista_provisioned_tenants',
# Models moved to openstack/networking-cisco
'cisco_ml2_apic_contracts',
'cisco_ml2_apic_names',

View File

@ -52,7 +52,6 @@ from neutron.plugins.brocade.db import models as brocade_models # noqa
from neutron.plugins.cisco.db.l3 import l3_models # noqa
from neutron.plugins.cisco.db import n1kv_models_v2 # noqa
from neutron.plugins.cisco.db import network_models_v2 # noqa
from neutron.plugins.ml2.drivers.arista import db # noqa
from neutron.plugins.ml2.drivers.brocade.db import ( # noqa
models as ml2_brocade_models)
from neutron.plugins.ml2.drivers.cisco.n1kv import n1kv_models # noqa

View File

@ -1,12 +0,0 @@
Arista Neutron ML2 Mechanism Driver
This mechanism driver implements ML2 Driver API and is used to manage the virtual and physical networks using Arista Hardware.
Note: Initial version of this driver support VLANs only.
For more details on use please refer to:
https://wiki.openstack.org/wiki/Arista-neutron-ml2-driver
The back-end of the driver is now moved to:
https://github.com/stackforge/networking-arista

View File

@ -1,128 +0,0 @@
# Copyright (c) 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.
from oslo_config import cfg
# Arista ML2 Mechanism driver specific configuration knobs.
#
# Following are user configurable options for Arista ML2 Mechanism
# driver. The eapi_username, eapi_password, and eapi_host are
# required options. Region Name must be the same that is used by
# Keystone service. This option is available to support multiple
# OpenStack/Neutron controllers.
ARISTA_DRIVER_OPTS = [
cfg.StrOpt('eapi_username',
default='',
help=_('Username for Arista EOS. This is required field. '
'If not set, all communications to Arista EOS '
'will fail.')),
cfg.StrOpt('eapi_password',
default='',
secret=True, # do not expose value in the logs
help=_('Password for Arista EOS. This is required field. '
'If not set, all communications to Arista EOS '
'will fail.')),
cfg.StrOpt('eapi_host',
default='',
help=_('Arista EOS IP address. This is required field. '
'If not set, all communications to Arista EOS '
'will fail.')),
cfg.BoolOpt('use_fqdn',
default=True,
help=_('Defines if hostnames are sent to Arista EOS as FQDNs '
'("node1.domain.com") or as short names ("node1"). '
'This is optional. If not set, a value of "True" '
'is assumed.')),
cfg.IntOpt('sync_interval',
default=180,
help=_('Sync interval in seconds between Neutron plugin and '
'EOS. This interval defines how often the '
'synchronization is performed. This is an optional '
'field. If not set, a value of 180 seconds is '
'assumed.')),
cfg.StrOpt('region_name',
default='RegionOne',
help=_('Defines Region Name that is assigned to this OpenStack '
'Controller. This is useful when multiple '
'OpenStack/Neutron controllers are managing the same '
'Arista HW clusters. Note that this name must match '
'with the region name registered (or known) to keystone '
'service. Authentication with Keysotne is performed by '
'EOS. This is optional. If not set, a value of '
'"RegionOne" is assumed.'))
]
""" Arista L3 Service Plugin specific configuration knobs.
Following are user configurable options for Arista L3 plugin
driver. The eapi_username, eapi_password, and eapi_host are
required options.
"""
ARISTA_L3_PLUGIN = [
cfg.StrOpt('primary_l3_host_username',
default='',
help=_('Username for Arista EOS. This is required field. '
'If not set, all communications to Arista EOS '
'will fail')),
cfg.StrOpt('primary_l3_host_password',
default='',
secret=True, # do not expose value in the logs
help=_('Password for Arista EOS. This is required field. '
'If not set, all communications to Arista EOS '
'will fail')),
cfg.StrOpt('primary_l3_host',
default='',
help=_('Arista EOS IP address. This is required field. '
'If not set, all communications to Arista EOS '
'will fail')),
cfg.StrOpt('secondary_l3_host',
default='',
help=_('Arista EOS IP address for second Switch MLAGed with '
'the first one. This an optional field, however, if '
'mlag_config flag is set, then this is required. '
'If not set, all communications to Arista EOS '
'will fail')),
cfg.BoolOpt('mlag_config',
default=False,
help=_('This flag is used indicate if Arista Switches are '
'configured in MLAG mode. If yes, all L3 config '
'is pushed to both the switches automatically. '
'If this flag is set to True, ensure to specify IP '
'addresses of both switches. '
'This is optional. If not set, a value of "False" '
'is assumed.')),
cfg.BoolOpt('use_vrf',
default=False,
help=_('A "True" value for this flag indicates to create a '
'router in VRF. If not set, all routers are created '
'in default VRF. '
'This is optional. If not set, a value of "False" '
'is assumed.')),
cfg.IntOpt('l3_sync_interval',
default=180,
help=_('Sync interval in seconds between L3 Service plugin '
'and EOS. This interval defines how often the '
'synchronization is performed. This is an optional '
'field. If not set, a value of 180 seconds is assumed'))
]
cfg.CONF.register_opts(ARISTA_L3_PLUGIN, "l3_arista")
cfg.CONF.register_opts(ARISTA_DRIVER_OPTS, "ml2_arista")

View File

@ -1,80 +0,0 @@
# Copyright (c) 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.
import sqlalchemy as sa
from neutron.db import model_base
from neutron.db import models_v2
UUID_LEN = 36
STR_LEN = 255
class AristaProvisionedNets(model_base.BASEV2, models_v2.HasId,
models_v2.HasTenant):
"""Stores networks provisioned on Arista EOS.
Saves the segmentation ID for each network that is provisioned
on EOS. This information is used during synchronization between
Neutron and EOS.
"""
__tablename__ = 'arista_provisioned_nets'
network_id = sa.Column(sa.String(UUID_LEN))
segmentation_id = sa.Column(sa.Integer)
def eos_network_representation(self, segmentation_type):
return {u'networkId': self.network_id,
u'segmentationTypeId': self.segmentation_id,
u'segmentationType': segmentation_type}
class AristaProvisionedVms(model_base.BASEV2, models_v2.HasId,
models_v2.HasTenant):
"""Stores VMs provisioned on Arista EOS.
All VMs launched on physical hosts connected to Arista
Switches are remembered
"""
__tablename__ = 'arista_provisioned_vms'
vm_id = sa.Column(sa.String(STR_LEN))
host_id = sa.Column(sa.String(STR_LEN))
port_id = sa.Column(sa.String(UUID_LEN))
network_id = sa.Column(sa.String(UUID_LEN))
def eos_vm_representation(self):
return {u'vmId': self.vm_id,
u'host': self.host_id,
u'ports': {self.port_id: [{u'portId': self.port_id,
u'networkId': self.network_id}]}}
def eos_port_representation(self):
return {u'vmId': self.vm_id,
u'host': self.host_id,
u'portId': self.port_id,
u'networkId': self.network_id}
class AristaProvisionedTenants(model_base.BASEV2, models_v2.HasId,
models_v2.HasTenant):
"""Stores Tenants provisioned on Arista EOS.
Tenants list is maintained for sync between Neutron and EOS.
"""
__tablename__ = 'arista_provisioned_tenants'
def eos_tenant_representation(self):
return {u'tenantId': self.tenant_id}

View File

@ -1,35 +0,0 @@
# Copyright (c) 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.
"""Exceptions used by Arista ML2 Mechanism Driver."""
from neutron.common import exceptions
class AristaRpcError(exceptions.NeutronException):
message = _('%(msg)s')
class AristaConfigError(exceptions.NeutronException):
message = _('%(msg)s')
class AristaServicePluginRpcError(exceptions.NeutronException):
message = _('%(msg)s')
class AristaServicePluginConfigError(exceptions.NeutronException):
message = _('%(msg)s')

View File

@ -1,470 +0,0 @@
# Copyright (c) 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.
import threading
from networking_arista.common import db_lib
from networking_arista.ml2 import arista_ml2
from oslo_config import cfg
from oslo_log import log as logging
from neutron.common import constants as n_const
from neutron.i18n import _LI
from neutron.plugins.common import constants as p_const
from neutron.plugins.ml2.common import exceptions as ml2_exc
from neutron.plugins.ml2 import driver_api
from neutron.plugins.ml2.drivers.arista import config # noqa
from neutron.plugins.ml2.drivers.arista import db
from neutron.plugins.ml2.drivers.arista import exceptions as arista_exc
LOG = logging.getLogger(__name__)
EOS_UNREACHABLE_MSG = _('Unable to reach EOS')
class AristaDriver(driver_api.MechanismDriver):
"""Ml2 Mechanism driver for Arista networking hardware.
Remembers all networks and VMs that are provisioned on Arista Hardware.
Does not send network provisioning request if the network has already been
provisioned before for the given port.
"""
def __init__(self, rpc=None):
self.rpc = rpc or arista_ml2.AristaRPCWrapper()
self.db_nets = db.AristaProvisionedNets()
self.db_vms = db.AristaProvisionedVms()
self.db_tenants = db.AristaProvisionedTenants()
self.ndb = db_lib.NeutronNets()
confg = cfg.CONF.ml2_arista
self.segmentation_type = db_lib.VLAN_SEGMENTATION
self.timer = None
self.eos = arista_ml2.SyncService(self.rpc, self.ndb)
self.sync_timeout = confg['sync_interval']
self.eos_sync_lock = threading.Lock()
def initialize(self):
self.rpc.register_with_eos()
self._cleanup_db()
self.rpc.check_cli_commands()
# Registering with EOS updates self.rpc.region_updated_time. Clear it
# to force an initial sync
self.rpc.clear_region_updated_time()
self._synchronization_thread()
def create_network_precommit(self, context):
"""Remember the tenant, and network information."""
network = context.current
segments = context.network_segments
if segments[0][driver_api.NETWORK_TYPE] != p_const.TYPE_VLAN:
# If network type is not VLAN, do nothing
return
network_id = network['id']
tenant_id = network['tenant_id']
if not tenant_id:
tenant_id = context._plugin_context.tenant_id
segmentation_id = segments[0]['segmentation_id']
with self.eos_sync_lock:
db_lib.remember_tenant(tenant_id)
db_lib.remember_network(tenant_id,
network_id,
segmentation_id)
def create_network_postcommit(self, context):
"""Provision the network on the Arista Hardware."""
network = context.current
network_id = network['id']
network_name = network['name']
tenant_id = network['tenant_id']
if not tenant_id:
tenant_id = context._plugin_context.tenant_id
segments = context.network_segments
vlan_id = segments[0]['segmentation_id']
shared_net = network['shared']
with self.eos_sync_lock:
if db_lib.is_network_provisioned(tenant_id, network_id):
try:
network_dict = {
'network_id': network_id,
'segmentation_id': vlan_id,
'network_name': network_name,
'shared': shared_net}
self.rpc.create_network(tenant_id, network_dict)
except arista_exc.AristaRpcError:
LOG.info(EOS_UNREACHABLE_MSG)
raise ml2_exc.MechanismDriverError()
else:
LOG.info(_LI('Network %s is not created as it is not found in '
'Arista DB'), network_id)
def update_network_precommit(self, context):
"""At the moment we only support network name change
Any other change in network is not supported at this time.
We do not store the network names, therefore, no DB store
action is performed here.
"""
new_network = context.current
orig_network = context.original
if new_network['name'] != orig_network['name']:
LOG.info(_LI('Network name changed to %s'), new_network['name'])
def update_network_postcommit(self, context):
"""At the moment we only support network name change
If network name is changed, a new network create request is
sent to the Arista Hardware.
"""
new_network = context.current
orig_network = context.original
if ((new_network['name'] != orig_network['name']) or
(new_network['shared'] != orig_network['shared'])):
network_id = new_network['id']
network_name = new_network['name']
tenant_id = new_network['tenant_id']
if not tenant_id:
tenant_id = context._plugin_context.tenant_id
vlan_id = new_network['provider:segmentation_id']
shared_net = new_network['shared']
with self.eos_sync_lock:
if db_lib.is_network_provisioned(tenant_id, network_id):
try:
network_dict = {
'network_id': network_id,
'segmentation_id': vlan_id,
'network_name': network_name,
'shared': shared_net}
self.rpc.create_network(tenant_id, network_dict)
except arista_exc.AristaRpcError:
LOG.info(EOS_UNREACHABLE_MSG)
raise ml2_exc.MechanismDriverError()
else:
LOG.info(_LI('Network %s is not updated as it is not found'
' in Arista DB'), network_id)
def delete_network_precommit(self, context):
"""Delete the network infromation from the DB."""
network = context.current
network_id = network['id']
tenant_id = network['tenant_id']
with self.eos_sync_lock:
if db_lib.is_network_provisioned(tenant_id, network_id):
db_lib.forget_network(tenant_id, network_id)
def delete_network_postcommit(self, context):
"""Send network delete request to Arista HW."""
network = context.current
segments = context.network_segments
if segments[0][driver_api.NETWORK_TYPE] != p_const.TYPE_VLAN:
# If networtk type is not VLAN, do nothing
return
network_id = network['id']
tenant_id = network['tenant_id']
if not tenant_id:
tenant_id = context._plugin_context.tenant_id
with self.eos_sync_lock:
# Succeed deleting network in case EOS is not accessible.
# EOS state will be updated by sync thread once EOS gets
# alive.
try:
self.rpc.delete_network(tenant_id, network_id)
# if necessary, delete tenant as well.
self.delete_tenant(tenant_id)
except arista_exc.AristaRpcError:
LOG.info(EOS_UNREACHABLE_MSG)
raise ml2_exc.MechanismDriverError()
def create_port_precommit(self, context):
"""Remember the infromation about a VM and its ports
A VM information, along with the physical host information
is saved.
"""
port = context.current
device_id = port['device_id']
device_owner = port['device_owner']
host = context.host
# device_id and device_owner are set on VM boot
is_vm_boot = device_id and device_owner
if host and is_vm_boot:
port_id = port['id']
network_id = port['network_id']
tenant_id = port['tenant_id']
if not tenant_id:
tenant_id = context._plugin_context.tenant_id
with self.eos_sync_lock:
if not db_lib.is_network_provisioned(tenant_id, network_id):
# Ignore this request if network is not provisioned
return
db_lib.remember_tenant(tenant_id)
db_lib.remember_vm(device_id, host, port_id,
network_id, tenant_id)
def create_port_postcommit(self, context):
"""Plug a physical host into a network.
Send provisioning request to Arista Hardware to plug a host
into appropriate network.
"""
port = context.current
device_id = port['device_id']
device_owner = port['device_owner']
host = context.host
# device_id and device_owner are set on VM boot
is_vm_boot = device_id and device_owner
if host and is_vm_boot:
port_id = port['id']
port_name = port['name']
network_id = port['network_id']
tenant_id = port['tenant_id']
if not tenant_id:
tenant_id = context._plugin_context.tenant_id
with self.eos_sync_lock:
hostname = self._host_name(host)
vm_provisioned = db_lib.is_vm_provisioned(device_id,
host,
port_id,
network_id,
tenant_id)
# If network does not exist under this tenant,
# it may be a shared network. Get shared network owner Id
net_provisioned = (
db_lib.is_network_provisioned(tenant_id, network_id) or
self.ndb.get_shared_network_owner_id(network_id)
)
if vm_provisioned and net_provisioned:
try:
self.rpc.plug_port_into_network(device_id,
hostname,
port_id,
network_id,
tenant_id,
port_name,
device_owner)
except arista_exc.AristaRpcError:
LOG.info(EOS_UNREACHABLE_MSG)
raise ml2_exc.MechanismDriverError()
else:
LOG.info(_LI('VM %s is not created as it is not found in '
'Arista DB'), device_id)
def update_port_precommit(self, context):
"""Update the name of a given port.
At the moment we only support port name change.
Any other change to port is not supported at this time.
We do not store the port names, therefore, no DB store
action is performed here.
"""
new_port = context.current
orig_port = context.original
if new_port['name'] != orig_port['name']:
LOG.info(_LI('Port name changed to %s'), new_port['name'])
new_port = context.current
device_id = new_port['device_id']
device_owner = new_port['device_owner']
host = context.host
# device_id and device_owner are set on VM boot
is_vm_boot = device_id and device_owner
if host and host != orig_port['binding:host_id'] and is_vm_boot:
port_id = new_port['id']
network_id = new_port['network_id']
tenant_id = new_port['tenant_id']
if not tenant_id:
tenant_id = context._plugin_context.tenant_id
with self.eos_sync_lock:
db_lib.update_vm_host(device_id, host, port_id,
network_id, tenant_id)
def update_port_postcommit(self, context):
"""Update the name of a given port in EOS.
At the moment we only support port name change
Any other change to port is not supported at this time.
"""
port = context.current
orig_port = context.original
device_id = port['device_id']
device_owner = port['device_owner']
host = context.host
is_vm_boot = device_id and device_owner
if host and is_vm_boot:
port_id = port['id']
port_name = port['name']
network_id = port['network_id']
tenant_id = port['tenant_id']
if not tenant_id:
tenant_id = context._plugin_context.tenant_id
with self.eos_sync_lock:
hostname = self._host_name(host)
segmentation_id = db_lib.get_segmentation_id(tenant_id,
network_id)
vm_provisioned = db_lib.is_vm_provisioned(device_id,
host,
port_id,
network_id,
tenant_id)
# If network does not exist under this tenant,
# it may be a shared network. Get shared network owner Id
net_provisioned = (
db_lib.is_network_provisioned(tenant_id, network_id,
segmentation_id) or
self.ndb.get_shared_network_owner_id(network_id)
)
if vm_provisioned and net_provisioned:
try:
orig_host = orig_port['binding:host_id']
if host != orig_host:
# The port moved to a different host. So delete the
# old port on the old host before creating a new
# port on the new host.
self._delete_port(port, orig_host, tenant_id)
self.rpc.plug_port_into_network(device_id,
hostname,
port_id,
network_id,
tenant_id,
port_name,
device_owner)
except arista_exc.AristaRpcError:
LOG.info(EOS_UNREACHABLE_MSG)
raise ml2_exc.MechanismDriverError()
else:
LOG.info(_LI('VM %s is not updated as it is not found in '
'Arista DB'), device_id)
def delete_port_precommit(self, context):
"""Delete information about a VM and host from the DB."""
port = context.current
host_id = context.host
device_id = port['device_id']
tenant_id = port['tenant_id']
if not tenant_id:
tenant_id = context._plugin_context.tenant_id
network_id = port['network_id']
port_id = port['id']
with self.eos_sync_lock:
if db_lib.is_vm_provisioned(device_id, host_id, port_id,
network_id, tenant_id):
db_lib.forget_vm(device_id, host_id, port_id,
network_id, tenant_id)
def delete_port_postcommit(self, context):
"""unPlug a physical host from a network.
Send provisioning request to Arista Hardware to unplug a host
from appropriate network.
"""
port = context.current
host = context.host
tenant_id = port['tenant_id']
if not tenant_id:
tenant_id = context._plugin_context.tenant_id
with self.eos_sync_lock:
self._delete_port(port, host, tenant_id)
def _delete_port(self, port, host, tenant_id):
"""Deletes the port from EOS.
param port: Port which is to be deleted
param host: The host on which the port existed
param tenant_id: The tenant to which the port belongs to. Some times
the tenant id in the port dict is not present (as in
the case of HA router).
"""
device_id = port['device_id']
port_id = port['id']
network_id = port['network_id']
device_owner = port['device_owner']
try:
if not db_lib.is_network_provisioned(tenant_id, network_id):
# If we do not have network associated with this, ignore it
return
hostname = self._host_name(host)
if device_owner == n_const.DEVICE_OWNER_DHCP:
self.rpc.unplug_dhcp_port_from_network(device_id,
hostname,
port_id,
network_id,
tenant_id)
else:
self.rpc.unplug_host_from_network(device_id,
hostname,
port_id,
network_id,
tenant_id)
# if necessary, delete tenant as well.
self.delete_tenant(tenant_id)
except arista_exc.AristaRpcError:
LOG.info(EOS_UNREACHABLE_MSG)
raise ml2_exc.MechanismDriverError()
def delete_tenant(self, tenant_id):
"""delete a tenant from DB.
A tenant is deleted only if there is no network or VM configured
configured for this tenant.
"""
objects_for_tenant = (db_lib.num_nets_provisioned(tenant_id) +
db_lib.num_vms_provisioned(tenant_id))
if not objects_for_tenant:
db_lib.forget_tenant(tenant_id)
try:
self.rpc.delete_tenant(tenant_id)
except arista_exc.AristaRpcError:
LOG.info(EOS_UNREACHABLE_MSG)
raise ml2_exc.MechanismDriverError()
def _host_name(self, hostname):
fqdns_used = cfg.CONF.ml2_arista['use_fqdn']
return hostname if fqdns_used else hostname.split('.')[0]
def _synchronization_thread(self):
with self.eos_sync_lock:
self.eos.do_synchronize()
self.timer = threading.Timer(self.sync_timeout,
self._synchronization_thread)
self.timer.start()
def stop_synchronization_thread(self):
if self.timer:
self.timer.cancel()
self.timer = None
def _cleanup_db(self):
"""Clean up any uncessary entries in our DB."""
db_tenants = db_lib.get_tenants()
for tenant in db_tenants:
neutron_nets = self.ndb.get_all_networks_for_tenant(tenant)
neutron_nets_id = []
for net in neutron_nets:
neutron_nets_id.append(net['id'])
db_nets = db_lib.get_networks(tenant)
for net_id in db_nets.keys():
if net_id not in neutron_nets_id:
db_lib.forget_network(tenant, net_id)

View File

@ -1,280 +0,0 @@
# Copyright 2014 Arista Networks, 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 copy
import threading
from networking_arista.common import db_lib
from networking_arista.l3Plugin import arista_l3_driver
from oslo_config import cfg
from oslo_log import helpers as log_helpers
from oslo_log import log as logging
from oslo_utils import excutils
from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api
from neutron.api.rpc.handlers import l3_rpc
from neutron.common import constants as n_const
from neutron.common import rpc as n_rpc
from neutron.common import topics
from neutron import context as nctx
from neutron.db import db_base_plugin_v2
from neutron.db import extraroute_db
from neutron.db import l3_agentschedulers_db
from neutron.db import l3_gwmode_db
from neutron.i18n import _LE, _LI
from neutron.plugins.common import constants
from neutron.plugins.ml2.driver_context import NetworkContext # noqa
LOG = logging.getLogger(__name__)
class AristaL3ServicePlugin(db_base_plugin_v2.NeutronDbPluginV2,
extraroute_db.ExtraRoute_db_mixin,
l3_gwmode_db.L3_NAT_db_mixin,
l3_agentschedulers_db.L3AgentSchedulerDbMixin):
"""Implements L3 Router service plugin for Arista hardware.
Creates routers in Arista hardware, manages them, adds/deletes interfaces
to the routes.
"""
supported_extension_aliases = ["router", "ext-gw-mode",
"extraroute"]
def __init__(self, driver=None):
self.driver = driver or arista_l3_driver.AristaL3Driver()
self.ndb = db_lib.NeutronNets()
self.setup_rpc()
self.sync_timeout = cfg.CONF.l3_arista.l3_sync_interval
self.sync_lock = threading.Lock()
self._synchronization_thread()
def setup_rpc(self):
# RPC support
self.topic = topics.L3PLUGIN
self.conn = n_rpc.create_connection(new=True)
self.agent_notifiers.update(
{n_const.AGENT_TYPE_L3: l3_rpc_agent_api.L3AgentNotifyAPI()})
self.endpoints = [l3_rpc.L3RpcCallback()]
self.conn.create_consumer(self.topic, self.endpoints,
fanout=False)
self.conn.consume_in_threads()
def get_plugin_type(self):
return constants.L3_ROUTER_NAT
def get_plugin_description(self):
"""Returns string description of the plugin."""
return ("Arista L3 Router Service Plugin for Arista Hardware "
"based routing")
def _synchronization_thread(self):
with self.sync_lock:
self.synchronize()
self.timer = threading.Timer(self.sync_timeout,
self._synchronization_thread)
self.timer.start()
def stop_synchronization_thread(self):
if self.timer:
self.timer.cancel()
self.timer = None
@log_helpers.log_method_call
def create_router(self, context, router):
"""Create a new router entry in DB, and create it Arista HW."""
tenant_id = self._get_tenant_id_for_create(context, router['router'])
# Add router to the DB
with context.session.begin(subtransactions=True):
new_router = super(AristaL3ServicePlugin, self).create_router(
context,
router)
# create router on the Arista Hw
try:
self.driver.create_router(context, tenant_id, new_router)
return new_router
except Exception:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Error creating router on Arista HW router=%s "),
new_router)
super(AristaL3ServicePlugin, self).delete_router(context,
new_router['id'])
@log_helpers.log_method_call
def update_router(self, context, router_id, router):
"""Update an existing router in DB, and update it in Arista HW."""
with context.session.begin(subtransactions=True):
# Read existing router record from DB
original_router = super(AristaL3ServicePlugin, self).get_router(
context, router_id)
# Update router DB
new_router = super(AristaL3ServicePlugin, self).update_router(
context, router_id, router)
# Modify router on the Arista Hw
try:
self.driver.update_router(context, router_id,
original_router, new_router)
return new_router
except Exception:
LOG.error(_LE("Error updating router on Arista HW router=%s "),
new_router)
@log_helpers.log_method_call
def delete_router(self, context, router_id):
"""Delete an existing router from Arista HW as well as from the DB."""
router = super(AristaL3ServicePlugin, self).get_router(context,
router_id)
tenant_id = router['tenant_id']
# Delete router on the Arista Hw
try:
self.driver.delete_router(context, tenant_id, router_id, router)
except Exception as e:
LOG.error(_LE("Error deleting router on Arista HW "
"router %(r)s exception=%(e)s"),
{'r': router, 'e': e})
with context.session.begin(subtransactions=True):
super(AristaL3ServicePlugin, self).delete_router(context,
router_id)
@log_helpers.log_method_call
def add_router_interface(self, context, router_id, interface_info):
"""Add a subnet of a network to an existing router."""
new_router = super(AristaL3ServicePlugin, self).add_router_interface(
context, router_id, interface_info)
# Get network info for the subnet that is being added to the router.
# Check if the interface information is by port-id or subnet-id
add_by_port, add_by_sub = self._validate_interface_info(interface_info)
if add_by_sub:
subnet = self.get_subnet(context, interface_info['subnet_id'])
elif add_by_port:
port = self.get_port(context, interface_info['port_id'])
subnet_id = port['fixed_ips'][0]['subnet_id']
subnet = self.get_subnet(context, subnet_id)
network_id = subnet['network_id']
# To create SVI's in Arista HW, the segmentation Id is required
# for this network.
ml2_db = NetworkContext(self, context, {'id': network_id})
seg_id = ml2_db.network_segments[0]['segmentation_id']
# Package all the info needed for Hw programming
router = super(AristaL3ServicePlugin, self).get_router(context,
router_id)
router_info = copy.deepcopy(new_router)
router_info['seg_id'] = seg_id
router_info['name'] = router['name']
router_info['cidr'] = subnet['cidr']
router_info['gip'] = subnet['gateway_ip']
router_info['ip_version'] = subnet['ip_version']
try:
self.driver.add_router_interface(context, router_info)
return new_router
except Exception:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Error Adding subnet %(subnet)s to "
"router %(router_id)s on Arista HW"),
{'subnet': subnet, 'router_id': router_id})
super(AristaL3ServicePlugin, self).remove_router_interface(
context,
router_id,
interface_info)
@log_helpers.log_method_call
def remove_router_interface(self, context, router_id, interface_info):
"""Remove a subnet of a network from an existing router."""
new_router = (
super(AristaL3ServicePlugin, self).remove_router_interface(
context, router_id, interface_info))
# Get network information of the subnet that is being removed
subnet = self.get_subnet(context, new_router['subnet_id'])
network_id = subnet['network_id']
# For SVI removal from Arista HW, segmentation ID is needed
ml2_db = NetworkContext(self, context, {'id': network_id})
seg_id = ml2_db.network_segments[0]['segmentation_id']
router = super(AristaL3ServicePlugin, self).get_router(context,
router_id)
router_info = copy.deepcopy(new_router)
router_info['seg_id'] = seg_id
router_info['name'] = router['name']
try:
self.driver.remove_router_interface(context, router_info)
return new_router
except Exception as exc:
LOG.error(_LE("Error removing interface %(interface)s from "
"router %(router_id)s on Arista HW"
"Exception =(exc)s"),
{'interface': interface_info, 'router_id': router_id,
'exc': exc})
def synchronize(self):
"""Synchronizes Router DB from Neturon DB with EOS.
Walks through the Neturon Db and ensures that all the routers
created in Netuton DB match with EOS. After creating appropriate
routers, it ensures to add interfaces as well.
Uses idempotent properties of EOS configuration, which means
same commands can be repeated.
"""
LOG.info(_LI('Syncing Neutron Router DB <-> EOS'))
ctx = nctx.get_admin_context()
routers = super(AristaL3ServicePlugin, self).get_routers(ctx)
for r in routers:
tenant_id = r['tenant_id']
ports = self.ndb.get_all_ports_for_tenant(tenant_id)
try:
self.driver.create_router(self, tenant_id, r)
except Exception:
continue
# Figure out which interfaces are added to this router
for p in ports:
if p['device_id'] == r['id']:
net_id = p['network_id']
subnet_id = p['fixed_ips'][0]['subnet_id']
subnet = self.ndb.get_subnet_info(subnet_id)
ml2_db = NetworkContext(self, ctx, {'id': net_id})
seg_id = ml2_db.network_segments[0]['segmentation_id']
r['seg_id'] = seg_id
r['cidr'] = subnet['cidr']
r['gip'] = subnet['gateway_ip']
r['ip_version'] = subnet['ip_version']
try:
self.driver.add_router_interface(self, r)
except Exception:
LOG.error(_LE("Error Adding interface %(subnet_id)s "
"to router %(router_id)s on Arista HW"),
{'subnet_id': subnet_id, 'router_id': r})

View File

@ -1,417 +0,0 @@
# Copyright (c) 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.
import sys
import mock
from neutron.extensions import portbindings
from neutron.tests.unit import testlib_api
with mock.patch.dict(sys.modules,
{'networking_arista': mock.Mock(),
'networking_arista.ml2': mock.Mock(),
'networking_arista.common': mock.Mock()}):
from neutron.plugins.ml2.drivers.arista import mechanism_arista
class AristaDriverTestCase(testlib_api.SqlTestCase):
"""Main test cases for Arista Mechanism driver.
Tests all mechanism driver APIs supported by Arista Driver. It invokes
all the APIs as they would be invoked in real world scenarios and
verifies the functionality.
"""
def setUp(self):
super(AristaDriverTestCase, self).setUp()
self.fake_rpc = mock.MagicMock()
mechanism_arista.db_lib = self.fake_rpc
self.drv = mechanism_arista.AristaDriver(self.fake_rpc)
def tearDown(self):
super(AristaDriverTestCase, self).tearDown()
self.drv.stop_synchronization_thread()
def test_create_network_precommit(self):
tenant_id = 'ten-1'
network_id = 'net1-id'
segmentation_id = 1001
network_context = self._get_network_context(tenant_id,
network_id,
segmentation_id,
False)
self.drv.create_network_precommit(network_context)
expected_calls = [
mock.call.remember_tenant(tenant_id),
mock.call.remember_network(tenant_id,
network_id,
segmentation_id)
]
mechanism_arista.db_lib.assert_has_calls(expected_calls)
def test_create_network_postcommit(self):
tenant_id = 'ten-1'
network_id = 'net1-id'
segmentation_id = 1001
network_context = self._get_network_context(tenant_id,
network_id,
segmentation_id,
False)
mechanism_arista.db_lib.is_network_provisioned.return_value = True
network = network_context.current
segments = network_context.network_segments
net_dict = {
'network_id': network['id'],
'segmentation_id': segments[0]['segmentation_id'],
'network_name': network['name'],
'shared': network['shared']}
self.drv.create_network_postcommit(network_context)
expected_calls = [
mock.call.is_network_provisioned(tenant_id, network_id),
mock.call.create_network(tenant_id, net_dict),
]
mechanism_arista.db_lib.assert_has_calls(expected_calls)
def test_delete_network_precommit(self):
tenant_id = 'ten-1'
network_id = 'net1-id'
segmentation_id = 1001
network_context = self._get_network_context(tenant_id,
network_id,
segmentation_id,
False)
mechanism_arista.db_lib.is_network_provisioned.return_value = True
mechanism_arista.db_lib.num_nets_provisioned.return_value = 0
mechanism_arista.db_lib.num_vms_provisioned.return_value = 0
self.drv.delete_network_precommit(network_context)
expected_calls = [
mock.call.is_network_provisioned(tenant_id, network_id),
mock.call.forget_network(tenant_id, network_id),
]
mechanism_arista.db_lib.assert_has_calls(expected_calls)
def test_delete_network_postcommit(self):
tenant_id = 'ten-1'
network_id = 'net1-id'
segmentation_id = 1001
network_context = self._get_network_context(tenant_id,
network_id,
segmentation_id,
False)
self.drv.delete_network_postcommit(network_context)
expected_calls = [
mock.call.delete_network(tenant_id, network_id),
]
mechanism_arista.db_lib.assert_has_calls(expected_calls)
def test_create_port_precommit(self):
tenant_id = 'ten-1'
network_id = 'net1-id'
segmentation_id = 1001
vm_id = 'vm1'
network_context = self._get_network_context(tenant_id,
network_id,
segmentation_id,
False)
port_context = self._get_port_context(tenant_id,
network_id,
vm_id,
network_context)
host_id = port_context.current['binding:host_id']
port_id = port_context.current['id']
self.drv.create_port_precommit(port_context)
expected_calls = [
mock.call.remember_tenant(tenant_id),
mock.call.remember_vm(vm_id, host_id, port_id,
network_id, tenant_id)
]
mechanism_arista.db_lib.assert_has_calls(expected_calls)
def test_create_port_postcommit(self):
tenant_id = 'ten-1'
network_id = 'net1-id'
segmentation_id = 1001
vm_id = 'vm1'
network_context = self._get_network_context(tenant_id,
network_id,
segmentation_id,
False)
port_context = self._get_port_context(tenant_id,
network_id,
vm_id,
network_context)
mechanism_arista.db_lib.is_vm_provisioned.return_value = True
mechanism_arista.db_lib.is_network_provisioned.return_value = True
mechanism_arista.db_lib.get_shared_network_owner_id.return_value = 1
port = port_context.current
device_id = port['device_id']
device_owner = port['device_owner']
host_id = port['binding:host_id']
port_id = port['id']
port_name = port['name']
self.drv.create_port_postcommit(port_context)
expected_calls = [
mock.call.is_vm_provisioned(device_id, host_id, port_id,
network_id, tenant_id),
mock.call.is_network_provisioned(tenant_id, network_id),
mock.call.plug_port_into_network(device_id, host_id, port_id,
network_id, tenant_id,
port_name, device_owner)
]
mechanism_arista.db_lib.assert_has_calls(expected_calls)
# Now test the delete ports
def test_delete_port_precommit(self):
tenant_id = 'ten-1'
network_id = 'net1-id'
segmentation_id = 1001
vm_id = 'vm1'
network_context = self._get_network_context(tenant_id,
network_id,
segmentation_id,
False)
port_context = self._get_port_context(tenant_id,
network_id,
vm_id,
network_context)
mechanism_arista.db_lib.is_vm_provisioned.return_value = True
mechanism_arista.db_lib.num_nets_provisioned.return_value = 0
mechanism_arista.db_lib.num_vms_provisioned.return_value = 0
self.drv.delete_port_precommit(port_context)
host_id = port_context.current['binding:host_id']
port_id = port_context.current['id']
expected_calls = [
mock.call.is_vm_provisioned(vm_id, host_id, port_id,
network_id, tenant_id),
mock.call.forget_vm(vm_id, host_id, port_id,
network_id, tenant_id),
]
mechanism_arista.db_lib.assert_has_calls(expected_calls)
def test_delete_port_postcommit(self):
tenant_id = 'ten-1'
network_id = 'net1-id'
segmentation_id = 1001
vm_id = 'vm1'
network_context = self._get_network_context(tenant_id,
network_id,
segmentation_id,
False)
port_context = self._get_port_context(tenant_id,
network_id,
vm_id,
network_context)
port = port_context.current
device_id = port['device_id']
host_id = port['binding:host_id']
port_id = port['id']
self.drv.delete_port_postcommit(port_context)
expected_calls = [
mock.call.unplug_host_from_network(device_id, host_id, port_id,
network_id, tenant_id)
]
mechanism_arista.db_lib.assert_has_calls(expected_calls)
def test_update_port_precommit(self):
tenant_id = 'ten-1'
network_id = 'net1-id'
segmentation_id = 1001
vm_id = 'vm1'
network_context = self._get_network_context(tenant_id,
network_id,
segmentation_id,
False)
port_context = self._get_port_context(tenant_id,
network_id,
vm_id,
network_context)
host_id = port_context.current['binding:host_id']
port_context.original['binding:host_id'] = 'ubuntu0'
port_id = port_context.current['id']
self.drv.update_port_precommit(port_context)
expected_calls = [
mock.call.update_vm_host(vm_id, host_id, port_id,
network_id, tenant_id)
]
mechanism_arista.db_lib.assert_has_calls(expected_calls)
def test_update_port_postcommit(self):
tenant_id = 'ten-1'
network_id = 'net1-id'
segmentation_id = 1001
vm_id = 'vm1'
network_context = self._get_network_context(tenant_id,
network_id,
segmentation_id,
False)
port_context = self._get_port_context(tenant_id,
network_id,
vm_id,
network_context)
mechanism_arista.db_lib.is_vm_provisioned.return_value = True
mechanism_arista.db_lib.is_network_provisioned.return_value = True
mechanism_arista.db_lib.get_shared_network_owner_id.return_value = 1
mechanism_arista.db_lib.get_segmentation_id.return_value = 1001
mechanism_arista.db_lib.num_nets_provisioned.return_value = 1
mechanism_arista.db_lib.num_vms_provisioned.return_value = 1
port = port_context.current
device_id = port['device_id']
device_owner = port['device_owner']
host_id = port['binding:host_id']
orig_host_id = 'ubuntu0'
port_context.original['binding:host_id'] = orig_host_id
port_id = port['id']
port_name = port['name']
self.drv.update_port_postcommit(port_context)
expected_calls = [
mock.call.NeutronNets(),
mock.call.get_segmentation_id(tenant_id, network_id),
mock.call.is_vm_provisioned(device_id, host_id, port_id,
network_id, tenant_id),
mock.call.is_network_provisioned(tenant_id, network_id,
segmentation_id),
mock.call.is_network_provisioned(tenant_id, network_id),
mock.call.unplug_host_from_network(device_id, orig_host_id,
port_id, network_id, tenant_id),
mock.call.num_nets_provisioned(tenant_id),
mock.call.num_vms_provisioned(tenant_id),
mock.call.plug_port_into_network(device_id, host_id, port_id,
network_id, tenant_id,
port_name, device_owner)
]
mechanism_arista.db_lib.assert_has_calls(expected_calls)
def _get_network_context(self, tenant_id, net_id, seg_id, shared):
network = {'id': net_id,
'tenant_id': tenant_id,
'name': 'test-net',
'shared': shared}
network_segments = [{'segmentation_id': seg_id,
'network_type': 'vlan'}]
return FakeNetworkContext(network, network_segments, network)
def _get_port_context(self, tenant_id, net_id, vm_id, network):
port = {'device_id': vm_id,
'device_owner': 'compute',
'binding:host_id': 'ubuntu1',
'name': 'test-port',
'tenant_id': tenant_id,
'id': 101,
'network_id': net_id
}
return FakePortContext(port, dict(port), network)
class fake_keystone_info_class(object):
"""To generate fake Keystone Authentification token information
Arista Driver expects Keystone auth info. This fake information
is for testing only
"""
auth_uri = 'abc://host:35357/v2.0/'
identity_uri = 'abc://host:5000'
admin_user = 'neutron'
admin_password = 'fun'
admin_tenant_name = 'tenant_name'
class FakeNetworkContext(object):
"""To generate network context for testing purposes only."""
def __init__(self, network, segments=None, original_network=None):
self._network = network
self._original_network = original_network
self._segments = segments
@property
def current(self):
return self._network
@property
def original(self):
return self._original_network
@property
def network_segments(self):
return self._segments
class FakePortContext(object):
"""To generate port context for testing purposes only."""
def __init__(self, port, original_port, network):
self._port = port
self._original_port = original_port
self._network_context = network
@property
def current(self):
return self._port
@property
def original(self):
return self._original_port
@property
def network(self):
return self._network_context
@property
def host(self):
return self._port.get(portbindings.HOST_ID)
@property
def original_host(self):
return self._original_port.get(portbindings.HOST_ID)

View File

@ -63,7 +63,6 @@ data_files =
etc/neutron/plugins/bigswitch/restproxy.ini
etc/neutron/plugins/ml2/linuxbridge_agent.ini
etc/neutron/plugins/ml2/ml2_conf.ini
etc/neutron/plugins/ml2/ml2_conf_arista.ini
etc/neutron/plugins/ml2/ml2_conf_brocade.ini
etc/neutron/plugins/ml2/ml2_conf_brocade_fi_ni.ini
etc/neutron/plugins/ml2/ml2_conf_cisco.ini
@ -167,7 +166,6 @@ neutron.ml2.mechanism_drivers =
linuxbridge = neutron.plugins.ml2.drivers.linuxbridge.mech_driver.mech_linuxbridge:LinuxbridgeMechanismDriver
openvswitch = neutron.plugins.ml2.drivers.openvswitch.mech_driver.mech_openvswitch:OpenvswitchMechanismDriver
hyperv = neutron.plugins.ml2.drivers.hyperv.mech_hyperv:HypervMechanismDriver
arista = neutron.plugins.ml2.drivers.arista.mechanism_arista:AristaDriver
# Note: ncs and cisco_ncs point to the same driver entrypoint
# TODO: The old name (ncs) can be dropped when it is no longer used
ncs = neutron.plugins.ml2.drivers.cisco.ncs.driver:NCSMechanismDriver

View File

@ -140,7 +140,6 @@ commands = python -m testtools.run \
neutron.tests.unit.plugins.ml2.drivers.test_type_vxlan \
neutron.tests.unit.plugins.ml2.drivers.test_type_gre \
neutron.tests.unit.plugins.ml2.drivers.test_helpers \
neutron.tests.unit.plugins.ml2.drivers.arista.test_mechanism_arista \
neutron.tests.unit.plugins.ml2.drivers.test_type_local \
neutron.tests.unit.plugins.ml2.drivers.mechanism_logger \
neutron.tests.unit.plugins.ml2.drivers.test_type_flat \