Merge "Arista Drivers decomposition part II"
This commit is contained in:
commit
7b3abffc2c
@ -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
|
@ -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',
|
||||
|
@ -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.nexus import ( # noqa
|
||||
|
@ -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
|
@ -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")
|
@ -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}
|
@ -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')
|
@ -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)
|
@ -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})
|
@ -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)
|
@ -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
|
||||
|
1
tox.ini
1
tox.ini
@ -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 \
|
||||
|
Loading…
Reference in New Issue
Block a user