Merge "Arista L3 Service Plugin decomposition"
This commit is contained in:
commit
7b54b74bfd
|
@ -398,7 +398,7 @@ The following chart captures the following aspects:
|
||||||
+===============================+=======================+===========+==================+=========+==============+
|
+===============================+=======================+===========+==================+=========+==============+
|
||||||
| freescale-nscs_ | ml2,fw | no | no | [D] | |
|
| freescale-nscs_ | ml2,fw | no | no | [D] | |
|
||||||
+-------------------------------+-----------------------+-----------+------------------+---------+--------------+
|
+-------------------------------+-----------------------+-----------+------------------+---------+--------------+
|
||||||
| networking-arista_ | ml2 | no | yes | [B] | |
|
| networking-arista_ | ml2,l3 | yes | yes | [C] | Kilo |
|
||||||
+-------------------------------+-----------------------+-----------+------------------+---------+--------------+
|
+-------------------------------+-----------------------+-----------+------------------+---------+--------------+
|
||||||
| networking-brocade_ | | | | | |
|
| networking-brocade_ | | | | | |
|
||||||
+-------------------------------+-----------------------+-----------+------------------+---------+--------------+
|
+-------------------------------+-----------------------+-----------+------------------+---------+--------------+
|
||||||
|
@ -437,7 +437,8 @@ Arista
|
||||||
------
|
------
|
||||||
|
|
||||||
* Git: https://github.com/stackforge/networking-arista
|
* Git: https://github.com/stackforge/networking-arista
|
||||||
* PyPI: https://pypi.python.org/pypi/networking-arista
|
* Launchpad: https://launchpad.net/networking-arista
|
||||||
|
* Pypi: https://pypi.python.org/pypi/networking-arista
|
||||||
|
|
||||||
.. _networking-brocade:
|
.. _networking-brocade:
|
||||||
|
|
||||||
|
|
|
@ -7,3 +7,6 @@ Note: Initial version of this driver support VLANs only.
|
||||||
|
|
||||||
For more details on use please refer to:
|
For more details on use please refer to:
|
||||||
https://wiki.openstack.org/wiki/Arista-neutron-ml2-driver
|
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,498 +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 hashlib
|
|
||||||
import socket
|
|
||||||
import struct
|
|
||||||
|
|
||||||
import jsonrpclib
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from neutron import context as nctx
|
|
||||||
from neutron.db import db_base_plugin_v2
|
|
||||||
from neutron.i18n import _LE, _LI
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.plugins.ml2.drivers.arista import exceptions as arista_exc
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
EOS_UNREACHABLE_MSG = _('Unable to reach EOS')
|
|
||||||
DEFAULT_VLAN = 1
|
|
||||||
MLAG_SWITCHES = 2
|
|
||||||
VIRTUAL_ROUTER_MAC = '00:11:22:33:44:55'
|
|
||||||
IPV4_BITS = 32
|
|
||||||
IPV6_BITS = 128
|
|
||||||
|
|
||||||
# This string-format-at-a-distance confuses pylint :(
|
|
||||||
# pylint: disable=too-many-format-args
|
|
||||||
router_in_vrf = {
|
|
||||||
'router': {'create': ['vrf definition {0}',
|
|
||||||
'rd {1}',
|
|
||||||
'exit'],
|
|
||||||
'delete': ['no vrf definition {0}']},
|
|
||||||
|
|
||||||
'interface': {'add': ['ip routing vrf {1}',
|
|
||||||
'vlan {0}',
|
|
||||||
'exit',
|
|
||||||
'interface vlan {0}',
|
|
||||||
'vrf forwarding {1}',
|
|
||||||
'ip address {2}'],
|
|
||||||
'remove': ['no interface vlan {0}']}}
|
|
||||||
|
|
||||||
router_in_default_vrf = {
|
|
||||||
'router': {'create': [], # Place holder for now.
|
|
||||||
'delete': []}, # Place holder for now.
|
|
||||||
|
|
||||||
'interface': {'add': ['ip routing',
|
|
||||||
'vlan {0}',
|
|
||||||
'exit',
|
|
||||||
'interface vlan {0}',
|
|
||||||
'ip address {2}'],
|
|
||||||
'remove': ['no interface vlan {0}']}}
|
|
||||||
|
|
||||||
router_in_default_vrf_v6 = {
|
|
||||||
'router': {'create': [],
|
|
||||||
'delete': []},
|
|
||||||
|
|
||||||
'interface': {'add': ['ipv6 unicast-routing',
|
|
||||||
'vlan {0}',
|
|
||||||
'exit',
|
|
||||||
'interface vlan {0}',
|
|
||||||
'ipv6 enable',
|
|
||||||
'ipv6 address {2}'],
|
|
||||||
'remove': ['no interface vlan {0}']}}
|
|
||||||
|
|
||||||
additional_cmds_for_mlag = {
|
|
||||||
'router': {'create': ['ip virtual-router mac-address {0}'],
|
|
||||||
'delete': ['no ip virtual-router mac-address']},
|
|
||||||
|
|
||||||
'interface': {'add': ['ip virtual-router address {0}'],
|
|
||||||
'remove': []}}
|
|
||||||
|
|
||||||
additional_cmds_for_mlag_v6 = {
|
|
||||||
'router': {'create': [],
|
|
||||||
'delete': []},
|
|
||||||
|
|
||||||
'interface': {'add': ['ipv6 virtual-router address {0}'],
|
|
||||||
'remove': []}}
|
|
||||||
|
|
||||||
|
|
||||||
class AristaL3Driver(object):
|
|
||||||
"""Wraps Arista JSON RPC.
|
|
||||||
|
|
||||||
All communications between Neutron and EOS are over JSON RPC.
|
|
||||||
EOS - operating system used on Arista hardware
|
|
||||||
Command API - JSON RPC API provided by Arista EOS
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self._servers = []
|
|
||||||
self._hosts = []
|
|
||||||
self.interfaceDict = None
|
|
||||||
self._validate_config()
|
|
||||||
host = cfg.CONF.l3_arista.primary_l3_host
|
|
||||||
self._hosts.append(host)
|
|
||||||
self._servers.append(jsonrpclib.Server(self._eapi_host_url(host)))
|
|
||||||
self.mlag_configured = cfg.CONF.l3_arista.mlag_config
|
|
||||||
self.use_vrf = cfg.CONF.l3_arista.use_vrf
|
|
||||||
if self.mlag_configured:
|
|
||||||
host = cfg.CONF.l3_arista.secondary_l3_host
|
|
||||||
self._hosts.append(host)
|
|
||||||
self._servers.append(jsonrpclib.Server(self._eapi_host_url(host)))
|
|
||||||
self._additionalRouterCmdsDict = additional_cmds_for_mlag['router']
|
|
||||||
self._additionalInterfaceCmdsDict = (
|
|
||||||
additional_cmds_for_mlag['interface'])
|
|
||||||
if self.use_vrf:
|
|
||||||
self.routerDict = router_in_vrf['router']
|
|
||||||
self.interfaceDict = router_in_vrf['interface']
|
|
||||||
else:
|
|
||||||
self.routerDict = router_in_default_vrf['router']
|
|
||||||
self.interfaceDict = router_in_default_vrf['interface']
|
|
||||||
|
|
||||||
def _eapi_host_url(self, host):
|
|
||||||
user = cfg.CONF.l3_arista.primary_l3_host_username
|
|
||||||
pwd = cfg.CONF.l3_arista.primary_l3_host_password
|
|
||||||
|
|
||||||
eapi_server_url = ('https://%s:%s@%s/command-api' %
|
|
||||||
(user, pwd, host))
|
|
||||||
return eapi_server_url
|
|
||||||
|
|
||||||
def _validate_config(self):
|
|
||||||
if cfg.CONF.l3_arista.get('primary_l3_host') == '':
|
|
||||||
msg = _('Required option primary_l3_host is not set')
|
|
||||||
LOG.error(msg)
|
|
||||||
raise arista_exc.AristaServicePluginConfigError(msg=msg)
|
|
||||||
if cfg.CONF.l3_arista.get('mlag_config'):
|
|
||||||
if cfg.CONF.l3_arista.get('secondary_l3_host') == '':
|
|
||||||
msg = _('Required option secondary_l3_host is not set')
|
|
||||||
LOG.error(msg)
|
|
||||||
raise arista_exc.AristaServicePluginConfigError(msg=msg)
|
|
||||||
if cfg.CONF.l3_arista.get('primary_l3_host_username') == '':
|
|
||||||
msg = _('Required option primary_l3_host_username is not set')
|
|
||||||
LOG.error(msg)
|
|
||||||
raise arista_exc.AristaServicePluginConfigError(msg=msg)
|
|
||||||
|
|
||||||
def create_router_on_eos(self, router_name, rdm, server):
|
|
||||||
"""Creates a router on Arista HW Device.
|
|
||||||
|
|
||||||
:param router_name: globally unique identifier for router/VRF
|
|
||||||
:param rdm: A value generated by hashing router name
|
|
||||||
:param server: Server endpoint on the Arista switch to be configured
|
|
||||||
"""
|
|
||||||
cmds = []
|
|
||||||
rd = "%s:%s" % (rdm, rdm)
|
|
||||||
|
|
||||||
for c in self.routerDict['create']:
|
|
||||||
cmds.append(c.format(router_name, rd))
|
|
||||||
|
|
||||||
if self.mlag_configured:
|
|
||||||
mac = VIRTUAL_ROUTER_MAC
|
|
||||||
for c in self._additionalRouterCmdsDict['create']:
|
|
||||||
cmds.append(c.format(mac))
|
|
||||||
|
|
||||||
self._run_openstack_l3_cmds(cmds, server)
|
|
||||||
|
|
||||||
def delete_router_from_eos(self, router_name, server):
|
|
||||||
"""Deletes a router from Arista HW Device.
|
|
||||||
|
|
||||||
:param router_name: globally unique identifier for router/VRF
|
|
||||||
:param server: Server endpoint on the Arista switch to be configured
|
|
||||||
"""
|
|
||||||
cmds = []
|
|
||||||
for c in self.routerDict['delete']:
|
|
||||||
cmds.append(c.format(router_name))
|
|
||||||
if self.mlag_configured:
|
|
||||||
for c in self._additionalRouterCmdsDict['delete']:
|
|
||||||
cmds.append(c)
|
|
||||||
|
|
||||||
self._run_openstack_l3_cmds(cmds, server)
|
|
||||||
|
|
||||||
def _select_dicts(self, ipv):
|
|
||||||
if self.use_vrf:
|
|
||||||
self.interfaceDict = router_in_vrf['interface']
|
|
||||||
else:
|
|
||||||
if ipv == 6:
|
|
||||||
#for IPv6 use IPv6 commmands
|
|
||||||
self.interfaceDict = router_in_default_vrf_v6['interface']
|
|
||||||
self._additionalInterfaceCmdsDict = (
|
|
||||||
additional_cmds_for_mlag_v6['interface'])
|
|
||||||
else:
|
|
||||||
self.interfaceDict = router_in_default_vrf['interface']
|
|
||||||
self._additionalInterfaceCmdsDict = (
|
|
||||||
additional_cmds_for_mlag['interface'])
|
|
||||||
|
|
||||||
def add_interface_to_router(self, segment_id,
|
|
||||||
router_name, gip, router_ip, mask, server):
|
|
||||||
"""Adds an interface to existing HW router on Arista HW device.
|
|
||||||
|
|
||||||
:param segment_id: VLAN Id associated with interface that is added
|
|
||||||
:param router_name: globally unique identifier for router/VRF
|
|
||||||
:param gip: Gateway IP associated with the subnet
|
|
||||||
:param router_ip: IP address of the router
|
|
||||||
:param mask: subnet mask to be used
|
|
||||||
:param server: Server endpoint on the Arista switch to be configured
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not segment_id:
|
|
||||||
segment_id = DEFAULT_VLAN
|
|
||||||
cmds = []
|
|
||||||
for c in self.interfaceDict['add']:
|
|
||||||
if self.mlag_configured:
|
|
||||||
ip = router_ip
|
|
||||||
else:
|
|
||||||
ip = gip + '/' + mask
|
|
||||||
cmds.append(c.format(segment_id, router_name, ip))
|
|
||||||
if self.mlag_configured:
|
|
||||||
for c in self._additionalInterfaceCmdsDict['add']:
|
|
||||||
cmds.append(c.format(gip))
|
|
||||||
|
|
||||||
self._run_openstack_l3_cmds(cmds, server)
|
|
||||||
|
|
||||||
def delete_interface_from_router(self, segment_id, router_name, server):
|
|
||||||
"""Deletes an interface from existing HW router on Arista HW device.
|
|
||||||
|
|
||||||
:param segment_id: VLAN Id associated with interface that is added
|
|
||||||
:param router_name: globally unique identifier for router/VRF
|
|
||||||
:param server: Server endpoint on the Arista switch to be configured
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not segment_id:
|
|
||||||
segment_id = DEFAULT_VLAN
|
|
||||||
cmds = []
|
|
||||||
for c in self.interfaceDict['remove']:
|
|
||||||
cmds.append(c.format(segment_id))
|
|
||||||
|
|
||||||
self._run_openstack_l3_cmds(cmds, server)
|
|
||||||
|
|
||||||
def create_router(self, context, tenant_id, router):
|
|
||||||
"""Creates a router on Arista Switch.
|
|
||||||
|
|
||||||
Deals with multiple configurations - such as Router per VRF,
|
|
||||||
a router in default VRF, Virtual Router in MLAG configurations
|
|
||||||
"""
|
|
||||||
if router:
|
|
||||||
router_name = self._arista_router_name(tenant_id, router['name'])
|
|
||||||
|
|
||||||
rdm = str(int(hashlib.sha256(router_name).hexdigest(),
|
|
||||||
16) % 6553)
|
|
||||||
mlag_peer_failed = False
|
|
||||||
for s in self._servers:
|
|
||||||
try:
|
|
||||||
self.create_router_on_eos(router_name, rdm, s)
|
|
||||||
mlag_peer_failed = False
|
|
||||||
except Exception:
|
|
||||||
if self.mlag_configured and not mlag_peer_failed:
|
|
||||||
mlag_peer_failed = True
|
|
||||||
else:
|
|
||||||
msg = (_('Failed to create router %s on EOS') %
|
|
||||||
router_name)
|
|
||||||
LOG.exception(msg)
|
|
||||||
raise arista_exc.AristaServicePluginRpcError(msg=msg)
|
|
||||||
|
|
||||||
def delete_router(self, context, tenant_id, router_id, router):
|
|
||||||
"""Deletes a router from Arista Switch."""
|
|
||||||
|
|
||||||
if router:
|
|
||||||
router_name = self._arista_router_name(tenant_id, router['name'])
|
|
||||||
mlag_peer_failed = False
|
|
||||||
for s in self._servers:
|
|
||||||
try:
|
|
||||||
self.delete_router_from_eos(router_name, s)
|
|
||||||
mlag_peer_failed = False
|
|
||||||
except Exception:
|
|
||||||
if self.mlag_configured and not mlag_peer_failed:
|
|
||||||
mlag_peer_failed = True
|
|
||||||
else:
|
|
||||||
msg = (_LE('Failed to delete router %s from EOS') %
|
|
||||||
router_name)
|
|
||||||
LOG.exception(msg)
|
|
||||||
raise arista_exc.AristaServicePluginRpcError(msg=msg)
|
|
||||||
|
|
||||||
def update_router(self, context, router_id, original_router, new_router):
|
|
||||||
"""Updates a router which is already created on Arista Switch.
|
|
||||||
|
|
||||||
TODO: (Sukhdev) - to be implemented in next release.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def add_router_interface(self, context, router_info):
|
|
||||||
"""Adds an interface to a router created on Arista HW router.
|
|
||||||
|
|
||||||
This deals with both IPv6 and IPv4 configurations.
|
|
||||||
"""
|
|
||||||
if router_info:
|
|
||||||
self._select_dicts(router_info['ip_version'])
|
|
||||||
cidr = router_info['cidr']
|
|
||||||
subnet_mask = cidr.split('/')[1]
|
|
||||||
router_name = self._arista_router_name(router_info['tenant_id'],
|
|
||||||
router_info['name'])
|
|
||||||
if self.mlag_configured:
|
|
||||||
# For MLAG, we send a specific IP address as opposed to cidr
|
|
||||||
# For now, we are using x.x.x.253 and x.x.x.254 as virtual IP
|
|
||||||
mlag_peer_failed = False
|
|
||||||
for i, server in enumerate(self._servers):
|
|
||||||
#get appropriate virtual IP address for this router
|
|
||||||
router_ip = self._get_router_ip(cidr, i,
|
|
||||||
router_info['ip_version'])
|
|
||||||
try:
|
|
||||||
self.add_interface_to_router(router_info['seg_id'],
|
|
||||||
router_name,
|
|
||||||
router_info['gip'],
|
|
||||||
router_ip, subnet_mask,
|
|
||||||
server)
|
|
||||||
mlag_peer_failed = False
|
|
||||||
except Exception:
|
|
||||||
if not mlag_peer_failed:
|
|
||||||
mlag_peer_failed = True
|
|
||||||
else:
|
|
||||||
msg = (_('Failed to add interface to router '
|
|
||||||
'%s on EOS') % router_name)
|
|
||||||
LOG.exception(msg)
|
|
||||||
raise arista_exc.AristaServicePluginRpcError(
|
|
||||||
msg=msg)
|
|
||||||
|
|
||||||
else:
|
|
||||||
for s in self._servers:
|
|
||||||
self.add_interface_to_router(router_info['seg_id'],
|
|
||||||
router_name,
|
|
||||||
router_info['gip'],
|
|
||||||
None, subnet_mask, s)
|
|
||||||
|
|
||||||
def remove_router_interface(self, context, router_info):
|
|
||||||
"""Removes previously configured interface from router on Arista HW.
|
|
||||||
|
|
||||||
This deals with both IPv6 and IPv4 configurations.
|
|
||||||
"""
|
|
||||||
if router_info:
|
|
||||||
router_name = self._arista_router_name(router_info['tenant_id'],
|
|
||||||
router_info['name'])
|
|
||||||
mlag_peer_failed = False
|
|
||||||
for s in self._servers:
|
|
||||||
try:
|
|
||||||
self.delete_interface_from_router(router_info['seg_id'],
|
|
||||||
router_name, s)
|
|
||||||
if self.mlag_configured:
|
|
||||||
mlag_peer_failed = False
|
|
||||||
except Exception:
|
|
||||||
if self.mlag_configured and not mlag_peer_failed:
|
|
||||||
mlag_peer_failed = True
|
|
||||||
else:
|
|
||||||
msg = (_LE('Failed to remove interface from router '
|
|
||||||
'%s on EOS') % router_name)
|
|
||||||
LOG.exception(msg)
|
|
||||||
raise arista_exc.AristaServicePluginRpcError(msg=msg)
|
|
||||||
|
|
||||||
def _run_openstack_l3_cmds(self, commands, server):
|
|
||||||
"""Execute/sends a CAPI (Command API) command to EOS.
|
|
||||||
|
|
||||||
In this method, list of commands is appended with prefix and
|
|
||||||
postfix commands - to make is understandble by EOS.
|
|
||||||
|
|
||||||
:param commands : List of command to be executed on EOS.
|
|
||||||
:param server: Server endpoint on the Arista switch to be configured
|
|
||||||
"""
|
|
||||||
command_start = ['enable', 'configure']
|
|
||||||
command_end = ['exit']
|
|
||||||
full_command = command_start + commands + command_end
|
|
||||||
|
|
||||||
LOG.info(_LI('Executing command on Arista EOS: %s'), full_command)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# this returns array of return values for every command in
|
|
||||||
# full_command list
|
|
||||||
ret = server.runCmds(version=1, cmds=full_command)
|
|
||||||
LOG.info(_LI('Results of execution on Arista EOS: %s'), ret)
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
msg = (_LE("Error occurred while trying to execute "
|
|
||||||
"commands %(cmd)s on EOS %(host)s"),
|
|
||||||
{'cmd': full_command, 'host': server})
|
|
||||||
LOG.exception(msg)
|
|
||||||
raise arista_exc.AristaServicePluginRpcError(msg=msg)
|
|
||||||
|
|
||||||
def _arista_router_name(self, tenant_id, name):
|
|
||||||
# Use a unique name so that OpenStack created routers/SVIs
|
|
||||||
# can be distinguishged from the user created routers/SVIs
|
|
||||||
# on Arista HW.
|
|
||||||
return 'OS' + '-' + tenant_id + '-' + name
|
|
||||||
|
|
||||||
def _get_binary_from_ipv4(self, ip_addr):
|
|
||||||
return struct.unpack("!L", socket.inet_pton(socket.AF_INET,
|
|
||||||
ip_addr))[0]
|
|
||||||
|
|
||||||
def _get_binary_from_ipv6(self, ip_addr):
|
|
||||||
hi, lo = struct.unpack("!QQ", socket.inet_pton(socket.AF_INET6,
|
|
||||||
ip_addr))
|
|
||||||
return (hi << 64) | lo
|
|
||||||
|
|
||||||
def _get_ipv4_from_binary(self, bin_addr):
|
|
||||||
return socket.inet_ntop(socket.AF_INET, struct.pack("!L", bin_addr))
|
|
||||||
|
|
||||||
def _get_ipv6_from_binary(self, bin_addr):
|
|
||||||
hi = bin_addr >> 64
|
|
||||||
lo = bin_addr & 0xFFFFFFFF
|
|
||||||
return socket.inet_ntop(socket.AF_INET6, struct.pack("!QQ", hi, lo))
|
|
||||||
|
|
||||||
def _get_router_ip(self, cidr, ip_count, ip_ver):
|
|
||||||
"""For a given IP subnet and IP version type, generate IP for router.
|
|
||||||
|
|
||||||
This method takes the network address (cidr) and selects an
|
|
||||||
IP address that should be assigned to virtual router running
|
|
||||||
on multiple switches. It uses upper addresses in a subnet address
|
|
||||||
as IP for the router. Each instace of the router, on each switch,
|
|
||||||
requires uniqe IP address. For example in IPv4 case, on a 255
|
|
||||||
subnet, it will pick X.X.X.254 as first addess, X.X.X.253 for next,
|
|
||||||
and so on.
|
|
||||||
"""
|
|
||||||
start_ip = MLAG_SWITCHES + ip_count
|
|
||||||
network_addr, prefix = cidr.split('/')
|
|
||||||
if ip_ver == 4:
|
|
||||||
bits = IPV4_BITS
|
|
||||||
ip = self._get_binary_from_ipv4(network_addr)
|
|
||||||
elif ip_ver == 6:
|
|
||||||
bits = IPV6_BITS
|
|
||||||
ip = self._get_binary_from_ipv6(network_addr)
|
|
||||||
|
|
||||||
mask = (pow(2, bits) - 1) << (bits - int(prefix))
|
|
||||||
|
|
||||||
network_addr = ip & mask
|
|
||||||
|
|
||||||
router_ip = pow(2, bits - int(prefix)) - start_ip
|
|
||||||
|
|
||||||
router_ip = network_addr | router_ip
|
|
||||||
if ip_ver == 4:
|
|
||||||
return self._get_ipv4_from_binary(router_ip) + '/' + prefix
|
|
||||||
else:
|
|
||||||
return self._get_ipv6_from_binary(router_ip) + '/' + prefix
|
|
||||||
|
|
||||||
|
|
||||||
class NeutronNets(db_base_plugin_v2.NeutronDbPluginV2):
|
|
||||||
"""Access to Neutron DB.
|
|
||||||
|
|
||||||
Provides access to the Neutron Data bases for all provisioned
|
|
||||||
networks as well ports. This data is used during the synchronization
|
|
||||||
of DB between ML2 Mechanism Driver and Arista EOS
|
|
||||||
Names of the networks and ports are not stored in Arista repository
|
|
||||||
They are pulled from Neutron DB.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.admin_ctx = nctx.get_admin_context()
|
|
||||||
|
|
||||||
def get_all_networks_for_tenant(self, tenant_id):
|
|
||||||
filters = {'tenant_id': [tenant_id]}
|
|
||||||
return super(NeutronNets,
|
|
||||||
self).get_networks(self.admin_ctx, filters=filters) or []
|
|
||||||
|
|
||||||
def get_all_ports_for_tenant(self, tenant_id):
|
|
||||||
filters = {'tenant_id': [tenant_id]}
|
|
||||||
return super(NeutronNets,
|
|
||||||
self).get_ports(self.admin_ctx, filters=filters) or []
|
|
||||||
|
|
||||||
def _get_network(self, tenant_id, network_id):
|
|
||||||
filters = {'tenant_id': [tenant_id],
|
|
||||||
'id': [network_id]}
|
|
||||||
return super(NeutronNets,
|
|
||||||
self).get_networks(self.admin_ctx, filters=filters) or []
|
|
||||||
|
|
||||||
def get_subnet_info(self, subnet_id):
|
|
||||||
subnet = self.get_subnet(subnet_id)
|
|
||||||
return subnet
|
|
||||||
|
|
||||||
def get_subnet_ip_version(self, subnet_id):
|
|
||||||
subnet = self.get_subnet(subnet_id)
|
|
||||||
return subnet['ip_version']
|
|
||||||
|
|
||||||
def get_subnet_gateway_ip(self, subnet_id):
|
|
||||||
subnet = self.get_subnet(subnet_id)
|
|
||||||
return subnet['gateway_ip']
|
|
||||||
|
|
||||||
def get_subnet_cidr(self, subnet_id):
|
|
||||||
subnet = self.get_subnet(subnet_id)
|
|
||||||
return subnet['cidr']
|
|
||||||
|
|
||||||
def get_network_id(self, subnet_id):
|
|
||||||
subnet = self.get_subnet(subnet_id)
|
|
||||||
return subnet['network_id']
|
|
||||||
|
|
||||||
def get_network_id_from_port_id(self, port_id):
|
|
||||||
port = self.get_port(port_id)
|
|
||||||
return port['network_id']
|
|
||||||
|
|
||||||
def get_subnet(self, subnet_id):
|
|
||||||
return super(NeutronNets,
|
|
||||||
self).get_subnet(self.admin_ctx, subnet_id) or []
|
|
||||||
|
|
||||||
def get_port(self, port_id):
|
|
||||||
return super(NeutronNets,
|
|
||||||
self).get_port(self.admin_ctx, port_id) or []
|
|
|
@ -1 +1 @@
|
||||||
networking_arista
|
networking_arista>=2015.1.1,<2015.2.1
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
import copy
|
import copy
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
from networking_arista.common import db_lib
|
||||||
|
from networking_arista.l3Plugin import arista_l3_driver
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
|
|
||||||
|
@ -33,7 +35,6 @@ from neutron.i18n import _LE, _LI
|
||||||
from neutron.openstack.common import log as logging
|
from neutron.openstack.common import log as logging
|
||||||
from neutron.plugins.common import constants
|
from neutron.plugins.common import constants
|
||||||
from neutron.plugins.ml2.driver_context import NetworkContext # noqa
|
from neutron.plugins.ml2.driver_context import NetworkContext # noqa
|
||||||
from neutron.plugins.ml2.drivers.arista import arista_l3_driver
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -55,7 +56,7 @@ class AristaL3ServicePlugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||||
def __init__(self, driver=None):
|
def __init__(self, driver=None):
|
||||||
|
|
||||||
self.driver = driver or arista_l3_driver.AristaL3Driver()
|
self.driver = driver or arista_l3_driver.AristaL3Driver()
|
||||||
self.ndb = arista_l3_driver.NeutronNets()
|
self.ndb = db_lib.NeutronNets()
|
||||||
self.setup_rpc()
|
self.setup_rpc()
|
||||||
self.sync_timeout = cfg.CONF.l3_arista.l3_sync_interval
|
self.sync_timeout = cfg.CONF.l3_arista.l3_sync_interval
|
||||||
self.sync_lock = threading.Lock()
|
self.sync_lock = threading.Lock()
|
||||||
|
|
|
@ -1,456 +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 mock
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from neutron.plugins.ml2.drivers.arista import arista_l3_driver as arista
|
|
||||||
from neutron.tests import base
|
|
||||||
|
|
||||||
|
|
||||||
def setup_arista_config(value='', vrf=False, mlag=False):
|
|
||||||
cfg.CONF.set_override('primary_l3_host', value, "l3_arista")
|
|
||||||
cfg.CONF.set_override('primary_l3_host_username', value, "l3_arista")
|
|
||||||
if vrf:
|
|
||||||
cfg.CONF.set_override('use_vrf', value, "l3_arista")
|
|
||||||
if mlag:
|
|
||||||
cfg.CONF.set_override('secondary_l3_host', value, "l3_arista")
|
|
||||||
cfg.CONF.set_override('mlag_config', value, "l3_arista")
|
|
||||||
|
|
||||||
|
|
||||||
class AristaL3DriverTestCasesDefaultVrf(base.BaseTestCase):
|
|
||||||
"""Test cases to test the RPC between Arista Driver and EOS.
|
|
||||||
|
|
||||||
Tests all methods used to send commands between Arista L3 Driver and EOS
|
|
||||||
to program routing functions in Default VRF.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(AristaL3DriverTestCasesDefaultVrf, self).setUp()
|
|
||||||
setup_arista_config('value')
|
|
||||||
self.drv = arista.AristaL3Driver()
|
|
||||||
self.drv._servers = []
|
|
||||||
self.drv._servers.append(mock.MagicMock())
|
|
||||||
|
|
||||||
def test_no_exception_on_correct_configuration(self):
|
|
||||||
self.assertIsNotNone(self.drv)
|
|
||||||
|
|
||||||
def test_create_router_on_eos(self):
|
|
||||||
router_name = 'test-router-1'
|
|
||||||
route_domain = '123:123'
|
|
||||||
|
|
||||||
self.drv.create_router_on_eos(router_name, route_domain,
|
|
||||||
self.drv._servers[0])
|
|
||||||
cmds = ['enable', 'configure', 'exit']
|
|
||||||
|
|
||||||
self.drv._servers[0].runCmds.assert_called_once_with(version=1,
|
|
||||||
cmds=cmds)
|
|
||||||
|
|
||||||
def test_delete_router_from_eos(self):
|
|
||||||
router_name = 'test-router-1'
|
|
||||||
|
|
||||||
self.drv.delete_router_from_eos(router_name, self.drv._servers[0])
|
|
||||||
cmds = ['enable', 'configure', 'exit']
|
|
||||||
|
|
||||||
self.drv._servers[0].runCmds.assert_called_once_with(version=1,
|
|
||||||
cmds=cmds)
|
|
||||||
|
|
||||||
def test_add_interface_to_router_on_eos(self):
|
|
||||||
router_name = 'test-router-1'
|
|
||||||
segment_id = '123'
|
|
||||||
router_ip = '10.10.10.10'
|
|
||||||
gw_ip = '10.10.10.1'
|
|
||||||
mask = '255.255.255.0'
|
|
||||||
|
|
||||||
self.drv.add_interface_to_router(segment_id, router_name, gw_ip,
|
|
||||||
router_ip, mask, self.drv._servers[0])
|
|
||||||
cmds = ['enable', 'configure', 'ip routing',
|
|
||||||
'vlan %s' % segment_id, 'exit',
|
|
||||||
'interface vlan %s' % segment_id,
|
|
||||||
'ip address %s/%s' % (gw_ip, mask), 'exit']
|
|
||||||
|
|
||||||
self.drv._servers[0].runCmds.assert_called_once_with(version=1,
|
|
||||||
cmds=cmds)
|
|
||||||
|
|
||||||
def test_delete_interface_from_router_on_eos(self):
|
|
||||||
router_name = 'test-router-1'
|
|
||||||
segment_id = '123'
|
|
||||||
|
|
||||||
self.drv.delete_interface_from_router(segment_id, router_name,
|
|
||||||
self.drv._servers[0])
|
|
||||||
cmds = ['enable', 'configure', 'no interface vlan %s' % segment_id,
|
|
||||||
'exit']
|
|
||||||
|
|
||||||
self.drv._servers[0].runCmds.assert_called_once_with(version=1,
|
|
||||||
cmds=cmds)
|
|
||||||
|
|
||||||
|
|
||||||
class AristaL3DriverTestCasesUsingVRFs(base.BaseTestCase):
|
|
||||||
"""Test cases to test the RPC between Arista Driver and EOS.
|
|
||||||
|
|
||||||
Tests all methods used to send commands between Arista L3 Driver and EOS
|
|
||||||
to program routing functions using multiple VRFs.
|
|
||||||
Note that the configuration commands are different when VRFs are used.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(AristaL3DriverTestCasesUsingVRFs, self).setUp()
|
|
||||||
setup_arista_config('value', vrf=True)
|
|
||||||
self.drv = arista.AristaL3Driver()
|
|
||||||
self.drv._servers = []
|
|
||||||
self.drv._servers.append(mock.MagicMock())
|
|
||||||
|
|
||||||
def test_no_exception_on_correct_configuration(self):
|
|
||||||
self.assertIsNotNone(self.drv)
|
|
||||||
|
|
||||||
def test_create_router_on_eos(self):
|
|
||||||
max_vrfs = 5
|
|
||||||
routers = ['testRouter-%s' % n for n in range(max_vrfs)]
|
|
||||||
domains = ['10%s' % n for n in range(max_vrfs)]
|
|
||||||
|
|
||||||
for (r, d) in zip(routers, domains):
|
|
||||||
self.drv.create_router_on_eos(r, d, self.drv._servers[0])
|
|
||||||
|
|
||||||
cmds = ['enable', 'configure',
|
|
||||||
'vrf definition %s' % r,
|
|
||||||
'rd %(rd)s:%(rd)s' % {'rd': d}, 'exit', 'exit']
|
|
||||||
|
|
||||||
self.drv._servers[0].runCmds.assert_called_with(version=1,
|
|
||||||
cmds=cmds)
|
|
||||||
|
|
||||||
def test_delete_router_from_eos(self):
|
|
||||||
max_vrfs = 5
|
|
||||||
routers = ['testRouter-%s' % n for n in range(max_vrfs)]
|
|
||||||
|
|
||||||
for r in routers:
|
|
||||||
self.drv.delete_router_from_eos(r, self.drv._servers[0])
|
|
||||||
cmds = ['enable', 'configure', 'no vrf definition %s' % r,
|
|
||||||
'exit']
|
|
||||||
|
|
||||||
self.drv._servers[0].runCmds.assert_called_with(version=1,
|
|
||||||
cmds=cmds)
|
|
||||||
|
|
||||||
def test_add_interface_to_router_on_eos(self):
|
|
||||||
router_name = 'test-router-1'
|
|
||||||
segment_id = '123'
|
|
||||||
router_ip = '10.10.10.10'
|
|
||||||
gw_ip = '10.10.10.1'
|
|
||||||
mask = '255.255.255.0'
|
|
||||||
|
|
||||||
self.drv.add_interface_to_router(segment_id, router_name, gw_ip,
|
|
||||||
router_ip, mask, self.drv._servers[0])
|
|
||||||
cmds = ['enable', 'configure',
|
|
||||||
'ip routing vrf %s' % router_name,
|
|
||||||
'vlan %s' % segment_id, 'exit',
|
|
||||||
'interface vlan %s' % segment_id,
|
|
||||||
'vrf forwarding %s' % router_name,
|
|
||||||
'ip address %s/%s' % (gw_ip, mask), 'exit']
|
|
||||||
|
|
||||||
self.drv._servers[0].runCmds.assert_called_once_with(version=1,
|
|
||||||
cmds=cmds)
|
|
||||||
|
|
||||||
def test_delete_interface_from_router_on_eos(self):
|
|
||||||
router_name = 'test-router-1'
|
|
||||||
segment_id = '123'
|
|
||||||
|
|
||||||
self.drv.delete_interface_from_router(segment_id, router_name,
|
|
||||||
self.drv._servers[0])
|
|
||||||
cmds = ['enable', 'configure', 'no interface vlan %s' % segment_id,
|
|
||||||
'exit']
|
|
||||||
|
|
||||||
self.drv._servers[0].runCmds.assert_called_once_with(version=1,
|
|
||||||
cmds=cmds)
|
|
||||||
|
|
||||||
|
|
||||||
class AristaL3DriverTestCasesMlagConfig(base.BaseTestCase):
|
|
||||||
"""Test cases to test the RPC between Arista Driver and EOS.
|
|
||||||
|
|
||||||
Tests all methods used to send commands between Arista L3 Driver and EOS
|
|
||||||
to program routing functions in Default VRF using MLAG configuration.
|
|
||||||
MLAG configuration means that the commands will be sent to both
|
|
||||||
primary and secondary Arista Switches.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(AristaL3DriverTestCasesMlagConfig, self).setUp()
|
|
||||||
setup_arista_config('value', mlag=True)
|
|
||||||
self.drv = arista.AristaL3Driver()
|
|
||||||
self.drv._servers = []
|
|
||||||
self.drv._servers.append(mock.MagicMock())
|
|
||||||
self.drv._servers.append(mock.MagicMock())
|
|
||||||
|
|
||||||
def test_no_exception_on_correct_configuration(self):
|
|
||||||
self.assertIsNotNone(self.drv)
|
|
||||||
|
|
||||||
def test_create_router_on_eos(self):
|
|
||||||
router_name = 'test-router-1'
|
|
||||||
route_domain = '123:123'
|
|
||||||
router_mac = '00:11:22:33:44:55'
|
|
||||||
|
|
||||||
for s in self.drv._servers:
|
|
||||||
self.drv.create_router_on_eos(router_name, route_domain, s)
|
|
||||||
cmds = ['enable', 'configure',
|
|
||||||
'ip virtual-router mac-address %s' % router_mac, 'exit']
|
|
||||||
|
|
||||||
s.runCmds.assert_called_with(version=1, cmds=cmds)
|
|
||||||
|
|
||||||
def test_delete_router_from_eos(self):
|
|
||||||
router_name = 'test-router-1'
|
|
||||||
|
|
||||||
for s in self.drv._servers:
|
|
||||||
self.drv.delete_router_from_eos(router_name, s)
|
|
||||||
cmds = ['enable', 'configure',
|
|
||||||
'no ip virtual-router mac-address', 'exit']
|
|
||||||
|
|
||||||
s.runCmds.assert_called_once_with(version=1, cmds=cmds)
|
|
||||||
|
|
||||||
def test_add_interface_to_router_on_eos(self):
|
|
||||||
router_name = 'test-router-1'
|
|
||||||
segment_id = '123'
|
|
||||||
router_ip = '10.10.10.10'
|
|
||||||
gw_ip = '10.10.10.1'
|
|
||||||
mask = '255.255.255.0'
|
|
||||||
|
|
||||||
for s in self.drv._servers:
|
|
||||||
self.drv.add_interface_to_router(segment_id, router_name, gw_ip,
|
|
||||||
router_ip, mask, s)
|
|
||||||
cmds = ['enable', 'configure', 'ip routing',
|
|
||||||
'vlan %s' % segment_id, 'exit',
|
|
||||||
'interface vlan %s' % segment_id,
|
|
||||||
'ip address %s' % router_ip,
|
|
||||||
'ip virtual-router address %s' % gw_ip, 'exit']
|
|
||||||
|
|
||||||
s.runCmds.assert_called_once_with(version=1, cmds=cmds)
|
|
||||||
|
|
||||||
def test_delete_interface_from_router_on_eos(self):
|
|
||||||
router_name = 'test-router-1'
|
|
||||||
segment_id = '123'
|
|
||||||
|
|
||||||
for s in self.drv._servers:
|
|
||||||
self.drv.delete_interface_from_router(segment_id, router_name, s)
|
|
||||||
|
|
||||||
cmds = ['enable', 'configure', 'no interface vlan %s' % segment_id,
|
|
||||||
'exit']
|
|
||||||
|
|
||||||
s.runCmds.assert_called_once_with(version=1, cmds=cmds)
|
|
||||||
|
|
||||||
|
|
||||||
class AristaL3DriverTestCases_v4(base.BaseTestCase):
|
|
||||||
"""Test cases to test the RPC between Arista Driver and EOS.
|
|
||||||
|
|
||||||
Tests all methods used to send commands between Arista L3 Driver and EOS
|
|
||||||
to program routing functions in Default VRF using IPv4.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(AristaL3DriverTestCases_v4, self).setUp()
|
|
||||||
setup_arista_config('value')
|
|
||||||
self.drv = arista.AristaL3Driver()
|
|
||||||
self.drv._servers = []
|
|
||||||
self.drv._servers.append(mock.MagicMock())
|
|
||||||
|
|
||||||
def test_no_exception_on_correct_configuration(self):
|
|
||||||
self.assertIsNotNone(self.drv)
|
|
||||||
|
|
||||||
def test_add_v4_interface_to_router(self):
|
|
||||||
gateway_ip = '10.10.10.1'
|
|
||||||
cidrs = ['10.10.10.0/24', '10.11.11.0/24']
|
|
||||||
|
|
||||||
# Add couple of IPv4 subnets to router
|
|
||||||
for cidr in cidrs:
|
|
||||||
router = {'name': 'test-router-1',
|
|
||||||
'tenant_id': 'ten-a',
|
|
||||||
'seg_id': '123',
|
|
||||||
'cidr': "%s" % cidr,
|
|
||||||
'gip': "%s" % gateway_ip,
|
|
||||||
'ip_version': 4}
|
|
||||||
|
|
||||||
self.assertFalse(self.drv.add_router_interface(None, router))
|
|
||||||
|
|
||||||
def test_delete_v4_interface_from_router(self):
|
|
||||||
gateway_ip = '10.10.10.1'
|
|
||||||
cidrs = ['10.10.10.0/24', '10.11.11.0/24']
|
|
||||||
|
|
||||||
# remove couple of IPv4 subnets from router
|
|
||||||
for cidr in cidrs:
|
|
||||||
router = {'name': 'test-router-1',
|
|
||||||
'tenant_id': 'ten-a',
|
|
||||||
'seg_id': '123',
|
|
||||||
'cidr': "%s" % cidr,
|
|
||||||
'gip': "%s" % gateway_ip,
|
|
||||||
'ip_version': 4}
|
|
||||||
|
|
||||||
self.assertFalse(self.drv.remove_router_interface(None, router))
|
|
||||||
|
|
||||||
|
|
||||||
class AristaL3DriverTestCases_v6(base.BaseTestCase):
|
|
||||||
"""Test cases to test the RPC between Arista Driver and EOS.
|
|
||||||
|
|
||||||
Tests all methods used to send commands between Arista L3 Driver and EOS
|
|
||||||
to program routing functions in Default VRF using IPv6.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(AristaL3DriverTestCases_v6, self).setUp()
|
|
||||||
setup_arista_config('value')
|
|
||||||
self.drv = arista.AristaL3Driver()
|
|
||||||
self.drv._servers = []
|
|
||||||
self.drv._servers.append(mock.MagicMock())
|
|
||||||
|
|
||||||
def test_no_exception_on_correct_configuration(self):
|
|
||||||
self.assertIsNotNone(self.drv)
|
|
||||||
|
|
||||||
def test_add_v6_interface_to_router(self):
|
|
||||||
gateway_ip = '3FFE::1'
|
|
||||||
cidrs = ['3FFE::/16', '2001::/16']
|
|
||||||
|
|
||||||
# Add couple of IPv6 subnets to router
|
|
||||||
for cidr in cidrs:
|
|
||||||
router = {'name': 'test-router-1',
|
|
||||||
'tenant_id': 'ten-a',
|
|
||||||
'seg_id': '123',
|
|
||||||
'cidr': "%s" % cidr,
|
|
||||||
'gip': "%s" % gateway_ip,
|
|
||||||
'ip_version': 6}
|
|
||||||
|
|
||||||
self.assertFalse(self.drv.add_router_interface(None, router))
|
|
||||||
|
|
||||||
def test_delete_v6_interface_from_router(self):
|
|
||||||
gateway_ip = '3FFE::1'
|
|
||||||
cidrs = ['3FFE::/16', '2001::/16']
|
|
||||||
|
|
||||||
# remove couple of IPv6 subnets from router
|
|
||||||
for cidr in cidrs:
|
|
||||||
router = {'name': 'test-router-1',
|
|
||||||
'tenant_id': 'ten-a',
|
|
||||||
'seg_id': '123',
|
|
||||||
'cidr': "%s" % cidr,
|
|
||||||
'gip': "%s" % gateway_ip,
|
|
||||||
'ip_version': 6}
|
|
||||||
|
|
||||||
self.assertFalse(self.drv.remove_router_interface(None, router))
|
|
||||||
|
|
||||||
|
|
||||||
class AristaL3DriverTestCases_MLAG_v6(base.BaseTestCase):
|
|
||||||
"""Test cases to test the RPC between Arista Driver and EOS.
|
|
||||||
|
|
||||||
Tests all methods used to send commands between Arista L3 Driver and EOS
|
|
||||||
to program routing functions in Default VRF on MLAG'ed switches using IPv6.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(AristaL3DriverTestCases_MLAG_v6, self).setUp()
|
|
||||||
setup_arista_config('value', mlag=True)
|
|
||||||
self.drv = arista.AristaL3Driver()
|
|
||||||
self.drv._servers = []
|
|
||||||
self.drv._servers.append(mock.MagicMock())
|
|
||||||
self.drv._servers.append(mock.MagicMock())
|
|
||||||
|
|
||||||
def test_no_exception_on_correct_configuration(self):
|
|
||||||
self.assertIsNotNone(self.drv)
|
|
||||||
|
|
||||||
def test_add_v6_interface_to_router(self):
|
|
||||||
gateway_ip = '3FFE::1'
|
|
||||||
cidrs = ['3FFE::/16', '2001::/16']
|
|
||||||
|
|
||||||
# Add couple of IPv6 subnets to router
|
|
||||||
for cidr in cidrs:
|
|
||||||
router = {'name': 'test-router-1',
|
|
||||||
'tenant_id': 'ten-a',
|
|
||||||
'seg_id': '123',
|
|
||||||
'cidr': "%s" % cidr,
|
|
||||||
'gip': "%s" % gateway_ip,
|
|
||||||
'ip_version': 6}
|
|
||||||
|
|
||||||
self.assertFalse(self.drv.add_router_interface(None, router))
|
|
||||||
|
|
||||||
def test_delete_v6_interface_from_router(self):
|
|
||||||
gateway_ip = '3FFE::1'
|
|
||||||
cidrs = ['3FFE::/16', '2001::/16']
|
|
||||||
|
|
||||||
# remove couple of IPv6 subnets from router
|
|
||||||
for cidr in cidrs:
|
|
||||||
router = {'name': 'test-router-1',
|
|
||||||
'tenant_id': 'ten-a',
|
|
||||||
'seg_id': '123',
|
|
||||||
'cidr': "%s" % cidr,
|
|
||||||
'gip': "%s" % gateway_ip,
|
|
||||||
'ip_version': 6}
|
|
||||||
|
|
||||||
self.assertFalse(self.drv.remove_router_interface(None, router))
|
|
||||||
|
|
||||||
|
|
||||||
class AristaL3DriverTestCasesMlag_one_switch_failed(base.BaseTestCase):
|
|
||||||
"""Test cases to test with non redundant hardare in redundancy mode.
|
|
||||||
|
|
||||||
In the following test cases, the driver is configured in MLAG (redundancy
|
|
||||||
mode) but, one of the switches is mocked to throw exceptoin to mimic
|
|
||||||
failure of the switch. Ensure that the the operation does not fail when
|
|
||||||
one of the switches fails.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(AristaL3DriverTestCasesMlag_one_switch_failed, self).setUp()
|
|
||||||
setup_arista_config('value', mlag=True)
|
|
||||||
self.drv = arista.AristaL3Driver()
|
|
||||||
self.drv._servers = []
|
|
||||||
self.drv._servers.append(mock.MagicMock())
|
|
||||||
self.drv._servers.append(mock.MagicMock())
|
|
||||||
|
|
||||||
def test_create_router_when_one_switch_fails(self):
|
|
||||||
router = {}
|
|
||||||
router['name'] = 'test-router-1'
|
|
||||||
tenant = '123'
|
|
||||||
|
|
||||||
# Make one of the switches throw an exception - i.e. fail
|
|
||||||
self.drv._servers[0].runCmds = mock.Mock(side_effect=Exception)
|
|
||||||
self.drv.create_router(None, tenant, router)
|
|
||||||
|
|
||||||
def test_delete_router_when_one_switch_fails(self):
|
|
||||||
router = {}
|
|
||||||
router['name'] = 'test-router-1'
|
|
||||||
tenant = '123'
|
|
||||||
router_id = '345'
|
|
||||||
|
|
||||||
# Make one of the switches throw an exception - i.e. fail
|
|
||||||
self.drv._servers[1].runCmds = mock.Mock(side_effect=Exception)
|
|
||||||
self.drv.delete_router(None, tenant, router_id, router)
|
|
||||||
|
|
||||||
def test_add_router_interface_when_one_switch_fails(self):
|
|
||||||
router = {}
|
|
||||||
router['name'] = 'test-router-1'
|
|
||||||
router['tenant_id'] = 'ten-1'
|
|
||||||
router['seg_id'] = '100'
|
|
||||||
router['ip_version'] = 4
|
|
||||||
router['cidr'] = '10.10.10.0/24'
|
|
||||||
router['gip'] = '10.10.10.1'
|
|
||||||
|
|
||||||
# Make one of the switches throw an exception - i.e. fail
|
|
||||||
self.drv._servers[1].runCmds = mock.Mock(side_effect=Exception)
|
|
||||||
self.drv.add_router_interface(None, router)
|
|
||||||
|
|
||||||
def test_remove_router_interface_when_one_switch_fails(self):
|
|
||||||
router = {}
|
|
||||||
router['name'] = 'test-router-1'
|
|
||||||
router['tenant_id'] = 'ten-1'
|
|
||||||
router['seg_id'] = '100'
|
|
||||||
router['ip_version'] = 4
|
|
||||||
router['cidr'] = '10.10.10.0/24'
|
|
||||||
router['gip'] = '10.10.10.1'
|
|
||||||
|
|
||||||
# Make one of the switches throw an exception - i.e. fail
|
|
||||||
self.drv._servers[0].runCmds = mock.Mock(side_effect=Exception)
|
|
||||||
self.drv.remove_router_interface(None, router)
|
|
Loading…
Reference in New Issue