Arista L3 Service Plugin decomposition
Moving back-end drivers for Arista's L3 Service Plugin from Neutron tree to networing_arist. Change-Id: Ie394c31ae317a71f00be7195e15e05dc56df0ad0 Closes-bug: 1428904changes/69/161969/5
parent
7ed3edfe63
commit
836ab188ae
|
@ -336,3 +336,34 @@ class NeutronNets(db_base_plugin_v2.NeutronDbPluginV2):
|
|||
'id': [network_id]}
|
||||
return super(NeutronNets,
|
||||
self).get_networks(self.admin_ctx, filters=filters) or []
|
||||
|
||||
def get_subnet_info(self, subnet_id):
|
||||
return self.get_subnet(subnet_id)
|
||||
|
||||
def get_subnet_ip_version(self, subnet_id):
|
||||
subnet = self.get_subnet(subnet_id)
|
||||
return subnet['ip_version'] if 'ip_version' in subnet else None
|
||||
|
||||
def get_subnet_gateway_ip(self, subnet_id):
|
||||
subnet = self.get_subnet(subnet_id)
|
||||
return subnet['gateway_ip'] if 'gateway_ip' in subnet else None
|
||||
|
||||
def get_subnet_cidr(self, subnet_id):
|
||||
subnet = self.get_subnet(subnet_id)
|
||||
return subnet['cidr'] if 'cidr' in subnet else None
|
||||
|
||||
def get_network_id(self, subnet_id):
|
||||
subnet = self.get_subnet(subnet_id)
|
||||
return subnet['network_id'] if 'network_id' in subnet else None
|
||||
|
||||
def get_network_id_from_port_id(self, port_id):
|
||||
port = self.get_port(port_id)
|
||||
return port['network_id'] if 'network_id' in port else None
|
||||
|
||||
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 {}
|
||||
|
|
|
@ -0,0 +1,452 @@
|
|||
# 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 oslo_log import log as logging
|
||||
|
||||
from neutron.i18n import _LI
|
||||
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': []},
|
||||
|
||||
'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.AristaSevicePluginConfigError(msg=msg)
|
||||
if cfg.CONF.l3_arista.get('mlag_config'):
|
||||
if cfg.CONF.l3_arista.get('use_vrf'):
|
||||
# This is invalid/unsupported configuration
|
||||
msg = _('VRFs are not supported MLAG config mode')
|
||||
LOG.error(msg)
|
||||
raise arista_exc.AristaSevicePluginConfigError(msg=msg)
|
||||
if cfg.CONF.l3_arista.get('secondary_l3_host') == '':
|
||||
msg = _('Required option secondary_l3_host is not set')
|
||||
LOG.error(msg)
|
||||
raise arista_exc.AristaSevicePluginConfigError(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.AristaSevicePluginConfigError(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:
|
||||
# In VARP config, use router ID else, use gateway IP address.
|
||||
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) % 65536)
|
||||
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:
|
||||
# In paied switch, it is OK to fail on one switch
|
||||
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:
|
||||
# In paied switch, it is OK to fail on one switch
|
||||
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 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 = (_('Failed to add interface to 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 = (_('Error occured 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):
|
||||
"""Generate an arista specific name for this router.
|
||||
|
||||
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):
|
||||
"""Converts IPv4 address to binary form."""
|
||||
|
||||
return struct.unpack("!L", socket.inet_pton(socket.AF_INET,
|
||||
ip_addr))[0]
|
||||
|
||||
def _get_binary_from_ipv6(self, ip_addr):
|
||||
"""Converts IPv6 address to binary form."""
|
||||
|
||||
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):
|
||||
"""Converts binary address to Ipv4 format."""
|
||||
|
||||
return socket.inet_ntop(socket.AF_INET, struct.pack("!L", bin_addr))
|
||||
|
||||
def _get_ipv6_from_binary(self, bin_addr):
|
||||
"""Converts binary address to Ipv6 format."""
|
||||
|
||||
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
|
|
@ -0,0 +1,19 @@
|
|||
# Copyright 2011 OpenStack Foundation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
|
||||
cfg.CONF.use_stderr = False
|
|
@ -0,0 +1,456 @@
|
|||
# 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.tests import base
|
||||
|
||||
from networking_arista.l3Plugin import arista_l3_driver as arista
|
||||
|
||||
|
||||
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', '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