Remove external dependency on jsonrpclib
Remove the external dependency on jsonrpclib to better align with the upstream OpenStack requirements. This change introduces a new EAPIClient class for the L3 agent. In the future, the ML2 driver will be refactored to use shared EAPI code. Change-Id: Ia7cae6a9ae091f2ec306bd64ef1617236cfd6d83
This commit is contained in:
parent
0da4bd1c3e
commit
8d0a6b09a0
@ -118,6 +118,14 @@
|
|||||||
# secondary_l3_host =
|
# secondary_l3_host =
|
||||||
# Example: secondary_l3_host = 192.168.10.20
|
# Example: secondary_l3_host = 192.168.10.20
|
||||||
#
|
#
|
||||||
|
# (IntOpt) Connection timeout interval in seconds. This interval
|
||||||
|
# defines how long an EAPI request from the driver to '
|
||||||
|
# EOS waits before timing out. If not set, a value of 10
|
||||||
|
# seconds is assumed.
|
||||||
|
#
|
||||||
|
# conn_timeout =
|
||||||
|
# Example: conn_timeout = 10
|
||||||
|
#
|
||||||
# (BoolOpt) Defines if Arista switches are configured in MLAG mode
|
# (BoolOpt) Defines if Arista switches are configured in MLAG mode
|
||||||
# If yes, all L3 configuration is pushed to both switches
|
# If yes, all L3 configuration is pushed to both switches
|
||||||
# automatically. If this flag is set, ensure that secondary_l3_host
|
# automatically. If this flag is set, ensure that secondary_l3_host
|
||||||
|
133
networking_arista/common/api.py
Normal file
133
networking_arista/common/api.py
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
# Copyright (c) 2017 Arista Networks, Inc
|
||||||
|
#
|
||||||
|
# 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 json
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import excutils
|
||||||
|
import requests
|
||||||
|
from requests import exceptions as requests_exc
|
||||||
|
from six.moves.urllib import parse
|
||||||
|
|
||||||
|
from networking_arista._i18n import _LI, _LW
|
||||||
|
from networking_arista.common import exceptions as arista_exc
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# EAPI error message
|
||||||
|
ERR_CVX_NOT_LEADER = 'only available on cluster leader'
|
||||||
|
|
||||||
|
|
||||||
|
class EAPIClient(object):
|
||||||
|
def __init__(self, host, username=None, password=None, verify=False,
|
||||||
|
timeout=None):
|
||||||
|
self.host = host
|
||||||
|
self.timeout = timeout
|
||||||
|
self.url = self._make_url(host)
|
||||||
|
self.session = requests.Session()
|
||||||
|
self.session.headers['Content-Type'] = 'application/json'
|
||||||
|
self.session.headers['Accept'] = 'application/json'
|
||||||
|
self.session.verify = verify
|
||||||
|
if username and password:
|
||||||
|
self.session.auth = (username, password)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _make_url(host, scheme='https'):
|
||||||
|
return parse.urlunsplit(
|
||||||
|
(scheme, host, '/command-api', '', '')
|
||||||
|
)
|
||||||
|
|
||||||
|
def execute(self, commands, commands_to_log=None):
|
||||||
|
params = {
|
||||||
|
'timestamps': False,
|
||||||
|
'format': 'json',
|
||||||
|
'version': 1,
|
||||||
|
'cmds': commands
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'id': 'Networking Arista Driver',
|
||||||
|
'method': 'runCmds',
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'params': params
|
||||||
|
}
|
||||||
|
|
||||||
|
if commands_to_log:
|
||||||
|
log_data = dict(data)
|
||||||
|
log_data['params'] = dict(params)
|
||||||
|
log_data['params']['cmds'] = commands_to_log
|
||||||
|
else:
|
||||||
|
log_data = data
|
||||||
|
|
||||||
|
LOG.info(
|
||||||
|
_LI('EAPI request %(ip)s contains %(data)s'),
|
||||||
|
{'ip': self.host, 'data': json.dumps(log_data)}
|
||||||
|
)
|
||||||
|
|
||||||
|
# request handling
|
||||||
|
try:
|
||||||
|
error = None
|
||||||
|
response = self.session.post(
|
||||||
|
self.url,
|
||||||
|
data=json.dumps(data),
|
||||||
|
timeout=self.timeout
|
||||||
|
)
|
||||||
|
except requests_exc.ConnectionError:
|
||||||
|
error = _LW('Error while trying to connect to %(ip)s')
|
||||||
|
except requests_exc.ConnectTimeout:
|
||||||
|
error = _LW('Timed out while trying to connect to %(ip)s')
|
||||||
|
except requests_exc.Timeout:
|
||||||
|
error = _LW('Timed out during an EAPI request to %(ip)s')
|
||||||
|
except requests_exc.InvalidURL:
|
||||||
|
error = _LW('Ingoring attempt to connect to invalid URL at %(ip)s')
|
||||||
|
except Exception as e:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
LOG.warning(
|
||||||
|
_LW('Error during processing the EAPI request %(error)s'),
|
||||||
|
{'error': e}
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
if error:
|
||||||
|
msg = error % {'ip': self.host}
|
||||||
|
# stop processing since we've encountered request error
|
||||||
|
LOG.warning(msg)
|
||||||
|
raise arista_exc.AristaRpcError(msg=msg)
|
||||||
|
|
||||||
|
# response handling
|
||||||
|
try:
|
||||||
|
resp_data = response.json()
|
||||||
|
return resp_data['result']
|
||||||
|
except ValueError as e:
|
||||||
|
LOG.info(_LI('Ignoring invalid JSON response'))
|
||||||
|
except KeyError:
|
||||||
|
if 'error' in resp_data and resp_data['error']['code'] == 1002:
|
||||||
|
for d in resp_data['error']['data']:
|
||||||
|
if not isinstance(d, dict):
|
||||||
|
continue
|
||||||
|
elif ERR_CVX_NOT_LEADER in d.get('errors', {})[0]:
|
||||||
|
LOG.info(
|
||||||
|
_LI('%(ip)s is not the CVX leader'),
|
||||||
|
{'ip': self.host}
|
||||||
|
)
|
||||||
|
return
|
||||||
|
msg = _LI('Unexpected EAPI error')
|
||||||
|
LOG.info(msg)
|
||||||
|
raise arista_exc.AristaRpcError(msg=msg)
|
||||||
|
except Exception as e:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
LOG.warning(
|
||||||
|
_LW('Error during processing the EAPI response %(error)s'),
|
||||||
|
{'error': e}
|
||||||
|
)
|
@ -146,6 +146,12 @@ ARISTA_L3_PLUGIN = [
|
|||||||
'mlag_config flag is set, then this is required. '
|
'mlag_config flag is set, then this is required. '
|
||||||
'If not set, all communications to Arista EOS '
|
'If not set, all communications to Arista EOS '
|
||||||
'will fail')),
|
'will fail')),
|
||||||
|
cfg.IntOpt('conn_timeout',
|
||||||
|
default=10,
|
||||||
|
help=_('Connection timeout interval in seconds. This interval '
|
||||||
|
'defines how long an EAPI request from the driver to '
|
||||||
|
'EOS waits before timing out. If not set, a value of 10 '
|
||||||
|
'seconds is assumed.')),
|
||||||
cfg.BoolOpt('mlag_config',
|
cfg.BoolOpt('mlag_config',
|
||||||
default=False,
|
default=False,
|
||||||
help=_('This flag is used indicate if Arista Switches are '
|
help=_('This flag is used indicate if Arista Switches are '
|
||||||
|
@ -16,11 +16,11 @@ import hashlib
|
|||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
import jsonrpc_requests
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from networking_arista._i18n import _, _LI
|
from networking_arista._i18n import _, _LI
|
||||||
|
from networking_arista.common import api
|
||||||
from networking_arista.common import exceptions as arista_exc
|
from networking_arista.common import exceptions as arista_exc
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -100,23 +100,13 @@ class AristaL3Driver(object):
|
|||||||
self._validate_config()
|
self._validate_config()
|
||||||
host = cfg.CONF.l3_arista.primary_l3_host
|
host = cfg.CONF.l3_arista.primary_l3_host
|
||||||
self._hosts.append(host)
|
self._hosts.append(host)
|
||||||
self._servers.append(
|
self._servers.append(self._make_eapi_client(host))
|
||||||
jsonrpc_requests.Server(
|
|
||||||
self._eapi_host_url(host),
|
|
||||||
verify=False
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self._mlag_configured = cfg.CONF.l3_arista.mlag_config
|
self._mlag_configured = cfg.CONF.l3_arista.mlag_config
|
||||||
self._use_vrf = cfg.CONF.l3_arista.use_vrf
|
self._use_vrf = cfg.CONF.l3_arista.use_vrf
|
||||||
if self._mlag_configured:
|
if self._mlag_configured:
|
||||||
host = cfg.CONF.l3_arista.secondary_l3_host
|
host = cfg.CONF.l3_arista.secondary_l3_host
|
||||||
self._hosts.append(host)
|
self._hosts.append(host)
|
||||||
self._servers.append(
|
self._servers.append(self._make_eapi_client(host))
|
||||||
jsonrpc_requests.Server(
|
|
||||||
self._eapi_host_url(host),
|
|
||||||
verify=False
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self._additionalRouterCmdsDict = additional_cmds_for_mlag['router']
|
self._additionalRouterCmdsDict = additional_cmds_for_mlag['router']
|
||||||
self._additionalInterfaceCmdsDict = (
|
self._additionalInterfaceCmdsDict = (
|
||||||
additional_cmds_for_mlag['interface'])
|
additional_cmds_for_mlag['interface'])
|
||||||
@ -127,13 +117,15 @@ class AristaL3Driver(object):
|
|||||||
self.routerDict = router_in_default_vrf['router']
|
self.routerDict = router_in_default_vrf['router']
|
||||||
self._interfaceDict = router_in_default_vrf['interface']
|
self._interfaceDict = router_in_default_vrf['interface']
|
||||||
|
|
||||||
def _eapi_host_url(self, host):
|
@staticmethod
|
||||||
user = cfg.CONF.l3_arista.primary_l3_host_username
|
def _make_eapi_client(host):
|
||||||
pwd = cfg.CONF.l3_arista.primary_l3_host_password
|
return api.EAPIClient(
|
||||||
|
host,
|
||||||
eapi_server_url = ('https://%s:%s@%s/command-api' %
|
username=cfg.CONF.l3_arista.primary_l3_host_username,
|
||||||
(user, pwd, host))
|
password=cfg.CONF.l3_arista.primary_l3_host_password,
|
||||||
return eapi_server_url
|
verify=False,
|
||||||
|
timeout=cfg.CONF.l3_arista.conn_timeout
|
||||||
|
)
|
||||||
|
|
||||||
def _validate_config(self):
|
def _validate_config(self):
|
||||||
if cfg.CONF.l3_arista.get('primary_l3_host') == '':
|
if cfg.CONF.l3_arista.get('primary_l3_host') == '':
|
||||||
@ -385,7 +377,7 @@ class AristaL3Driver(object):
|
|||||||
try:
|
try:
|
||||||
# this returns array of return values for every command in
|
# this returns array of return values for every command in
|
||||||
# full_command list
|
# full_command list
|
||||||
ret = server.runCmds(version=1, cmds=full_command)
|
ret = server.execute(full_command)
|
||||||
LOG.info(_LI('Results of execution on Arista EOS: %s'), ret)
|
LOG.info(_LI('Results of execution on Arista EOS: %s'), ret)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -14,11 +14,11 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import jsonrpc_requests
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from networking_arista._i18n import _, _LI
|
from networking_arista._i18n import _, _LI
|
||||||
|
from networking_arista.common import api
|
||||||
from networking_arista.common import db_lib
|
from networking_arista.common import db_lib
|
||||||
from networking_arista.common import exceptions as arista_exc
|
from networking_arista.common import exceptions as arista_exc
|
||||||
|
|
||||||
@ -91,22 +91,18 @@ class AristaSecGroupSwitchDriver(object):
|
|||||||
switch_pass = ''
|
switch_pass = ''
|
||||||
self._hosts[switch_ip] = (
|
self._hosts[switch_ip] = (
|
||||||
{'user': switch_user, 'password': switch_pass})
|
{'user': switch_user, 'password': switch_pass})
|
||||||
self._servers.append(
|
self._servers.append(self._make_eapi_client(switch_ip))
|
||||||
jsonrpc_requests.Server(
|
|
||||||
self._eapi_host_url(switch_ip),
|
|
||||||
verify=False
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.aclCreateDict = acl_cmd['acl']
|
self.aclCreateDict = acl_cmd['acl']
|
||||||
self.aclApplyDict = acl_cmd['apply']
|
self.aclApplyDict = acl_cmd['apply']
|
||||||
|
|
||||||
def _eapi_host_url(self, host):
|
def _make_eapi_client(self, host):
|
||||||
user = self._hosts[host]['user']
|
return api.EAPIClient(
|
||||||
pwd = self._hosts[host]['password']
|
host,
|
||||||
|
username=self._hosts[host]['user'],
|
||||||
eapi_server_url = ('https://%s:%s@%s/command-api' %
|
password=self._hosts[host]['password'],
|
||||||
(user, pwd, host))
|
verify=False,
|
||||||
return eapi_server_url
|
timeout=cfg.CONF.ml2_arista.conn_timeout
|
||||||
|
)
|
||||||
|
|
||||||
def _validate_config(self):
|
def _validate_config(self):
|
||||||
if not self.sg_enabled:
|
if not self.sg_enabled:
|
||||||
@ -462,10 +458,8 @@ class AristaSecGroupSwitchDriver(object):
|
|||||||
# Ingress ACL, egress ACL or both
|
# Ingress ACL, egress ACL or both
|
||||||
direction = ['ingress', 'egress']
|
direction = ['ingress', 'egress']
|
||||||
|
|
||||||
server = jsonrpc_requests.Server(
|
server = self._make_eapi_client(switch_info)
|
||||||
self._eapi_host_url(switch_info),
|
|
||||||
verify=False
|
|
||||||
)
|
|
||||||
for d in range(len(direction)):
|
for d in range(len(direction)):
|
||||||
name = self._arista_acl_name(sg['id'], direction[d])
|
name = self._arista_acl_name(sg['id'], direction[d])
|
||||||
try:
|
try:
|
||||||
@ -511,10 +505,7 @@ class AristaSecGroupSwitchDriver(object):
|
|||||||
# THIS IS TOTAL HACK NOW - just for testing
|
# THIS IS TOTAL HACK NOW - just for testing
|
||||||
# Assumes the credential of all switches are same as specified
|
# Assumes the credential of all switches are same as specified
|
||||||
# in the condig file
|
# in the condig file
|
||||||
server = jsonrpc_requests.Server(
|
server = self._make_eapi_client(switch_info)
|
||||||
self._eapi_host_url(switch_info),
|
|
||||||
verify=False
|
|
||||||
)
|
|
||||||
for d in range(len(direction)):
|
for d in range(len(direction)):
|
||||||
name = self._arista_acl_name(sg['id'], direction[d])
|
name = self._arista_acl_name(sg['id'], direction[d])
|
||||||
try:
|
try:
|
||||||
@ -543,7 +534,7 @@ class AristaSecGroupSwitchDriver(object):
|
|||||||
try:
|
try:
|
||||||
# this returns array of return values for every command in
|
# this returns array of return values for every command in
|
||||||
# full_command list
|
# full_command list
|
||||||
ret = server.runCmds(version=1, cmds=full_command)
|
ret = server.execute(full_command)
|
||||||
LOG.info(_LI('Results of execution on Arista EOS: %s'), ret)
|
LOG.info(_LI('Results of execution on Arista EOS: %s'), ret)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
|
0
networking_arista/tests/unit/common/__init__.py
Normal file
0
networking_arista/tests/unit/common/__init__.py
Normal file
257
networking_arista/tests/unit/common/test_api.py
Normal file
257
networking_arista/tests/unit/common/test_api.py
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
# Copyright (c) 2017 Arista Networks, Inc
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
import requests
|
||||||
|
from requests import exceptions as requests_exc
|
||||||
|
import testtools
|
||||||
|
|
||||||
|
from networking_arista.common import api
|
||||||
|
|
||||||
|
|
||||||
|
class TestEAPIClientInit(testtools.TestCase):
|
||||||
|
def test_basic_init(self):
|
||||||
|
host_ip = '10.20.30.40'
|
||||||
|
client = api.EAPIClient(host_ip)
|
||||||
|
self.assertEqual(client.host, host_ip)
|
||||||
|
self.assertEqual(client.url, 'https://10.20.30.40/command-api')
|
||||||
|
self.assertDictContainsSubset(
|
||||||
|
{'Content-Type': 'application/json', 'Accept': 'application/json'},
|
||||||
|
client.session.headers
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_init_enable_verify(self):
|
||||||
|
client = api.EAPIClient('10.0.0.1', verify=True)
|
||||||
|
self.assertTrue(client.session.verify)
|
||||||
|
|
||||||
|
def test_init_auth(self):
|
||||||
|
client = api.EAPIClient('10.0.0.1', username='user', password='pass')
|
||||||
|
self.assertEqual(client.session.auth, ('user', 'pass'))
|
||||||
|
|
||||||
|
def test_init_timeout(self):
|
||||||
|
client = api.EAPIClient('10.0.0.1', timeout=99)
|
||||||
|
self.assertEqual(client.timeout, 99)
|
||||||
|
|
||||||
|
def test_make_url(self):
|
||||||
|
url = api.EAPIClient._make_url('1.2.3.4')
|
||||||
|
self.assertEqual(url, 'https://1.2.3.4/command-api')
|
||||||
|
|
||||||
|
def test_make_url_http(self):
|
||||||
|
url = api.EAPIClient._make_url('5.6.7.8', 'http')
|
||||||
|
self.assertEqual(url, 'http://5.6.7.8/command-api')
|
||||||
|
|
||||||
|
|
||||||
|
class TestEAPIClientExecute(testtools.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestEAPIClientExecute, self).setUp()
|
||||||
|
|
||||||
|
mock.patch('requests.Session.post').start()
|
||||||
|
self.mock_log = mock.patch.object(api, 'LOG').start()
|
||||||
|
self.mock_json_dumps = mock.patch.object(api.json, 'dumps').start()
|
||||||
|
|
||||||
|
self.addCleanup(mock.patch.stopall)
|
||||||
|
|
||||||
|
self.client = api.EAPIClient('10.0.0.1', timeout=99)
|
||||||
|
|
||||||
|
def _test_execute_helper(self, commands, commands_to_log=None):
|
||||||
|
expected_data = {
|
||||||
|
'id': 'Networking Arista Driver',
|
||||||
|
'method': 'runCmds',
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'params': {
|
||||||
|
'timestamps': False,
|
||||||
|
'format': 'json',
|
||||||
|
'version': 1,
|
||||||
|
'cmds': commands
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.session.post.assert_called_once_with(
|
||||||
|
'https://10.0.0.1/command-api',
|
||||||
|
data=self.mock_json_dumps.return_value,
|
||||||
|
timeout=99
|
||||||
|
)
|
||||||
|
|
||||||
|
self.mock_log.info.assert_has_calls(
|
||||||
|
[
|
||||||
|
mock.call(
|
||||||
|
mock.ANY,
|
||||||
|
{
|
||||||
|
'ip': '10.0.0.1',
|
||||||
|
'data': self.mock_json_dumps.return_value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
log_data = dict(expected_data)
|
||||||
|
log_data['params'] = dict(expected_data['params'])
|
||||||
|
log_data['params']['cmds'] = commands_to_log or commands
|
||||||
|
|
||||||
|
self.mock_json_dumps.assert_has_calls(
|
||||||
|
[
|
||||||
|
mock.call(log_data),
|
||||||
|
mock.call(expected_data)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_command_prep(self):
|
||||||
|
commands = ['enable']
|
||||||
|
self.client.execute(commands)
|
||||||
|
self._test_execute_helper(commands)
|
||||||
|
|
||||||
|
def test_commands_to_log(self):
|
||||||
|
commands = ['config', 'secret']
|
||||||
|
commands_to_log = ['config', '******']
|
||||||
|
self.client.execute(commands, commands_to_log)
|
||||||
|
self._test_execute_helper(commands, commands_to_log)
|
||||||
|
|
||||||
|
def _test_execute_error_helper(self, raise_exception, expected_exception,
|
||||||
|
warning_has_params=False):
|
||||||
|
commands = ['config']
|
||||||
|
|
||||||
|
self.client.session.post.side_effect = raise_exception
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
expected_exception,
|
||||||
|
self.client.execute,
|
||||||
|
commands
|
||||||
|
)
|
||||||
|
|
||||||
|
self._test_execute_helper(commands)
|
||||||
|
|
||||||
|
if warning_has_params:
|
||||||
|
args = (mock.ANY, mock.ANY)
|
||||||
|
else:
|
||||||
|
args = (mock.ANY,)
|
||||||
|
self.mock_log.warning.assert_called_once_with(*args)
|
||||||
|
|
||||||
|
def test_request_connection_error(self):
|
||||||
|
self._test_execute_error_helper(
|
||||||
|
requests_exc.ConnectionError,
|
||||||
|
api.arista_exc.AristaRpcError
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_request_connect_timeout(self):
|
||||||
|
self._test_execute_error_helper(
|
||||||
|
requests_exc.ConnectTimeout,
|
||||||
|
api.arista_exc.AristaRpcError
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_request_timeout(self):
|
||||||
|
self._test_execute_error_helper(
|
||||||
|
requests_exc.Timeout,
|
||||||
|
api.arista_exc.AristaRpcError
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_request_connect_InvalidURL(self):
|
||||||
|
self._test_execute_error_helper(
|
||||||
|
requests_exc.InvalidURL,
|
||||||
|
api.arista_exc.AristaRpcError
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_request_other_exception(self):
|
||||||
|
class OtherException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self._test_execute_error_helper(
|
||||||
|
OtherException,
|
||||||
|
OtherException,
|
||||||
|
warning_has_params=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def _test_response_helper(self, response_data):
|
||||||
|
mock_response = mock.MagicMock(requests.Response)
|
||||||
|
mock_response.json.return_value = response_data
|
||||||
|
self.client.session.post.return_value = mock_response
|
||||||
|
|
||||||
|
def test_response_success(self):
|
||||||
|
mock_response = mock.MagicMock(requests.Response)
|
||||||
|
mock_response.json.return_value = {'result': mock.sentinel}
|
||||||
|
self.client.session.post.return_value = mock_response
|
||||||
|
|
||||||
|
retval = self.client.execute(['enable'])
|
||||||
|
self.assertEqual(retval, mock.sentinel)
|
||||||
|
|
||||||
|
def test_response_json_error(self):
|
||||||
|
mock_response = mock.MagicMock(requests.Response)
|
||||||
|
mock_response.json.side_effect = ValueError
|
||||||
|
self.client.session.post.return_value = mock_response
|
||||||
|
|
||||||
|
retval = self.client.execute(['enable'])
|
||||||
|
self.assertIsNone(retval)
|
||||||
|
self.mock_log.info.assert_has_calls([mock.call(mock.ANY)])
|
||||||
|
|
||||||
|
def _test_response_format_error_helper(self, bad_response):
|
||||||
|
mock_response = mock.MagicMock(requests.Response)
|
||||||
|
mock_response.json.return_value = bad_response
|
||||||
|
self.client.session.post.return_value = mock_response
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
api.arista_exc.AristaRpcError,
|
||||||
|
self.client.execute,
|
||||||
|
['enable']
|
||||||
|
)
|
||||||
|
self.mock_log.info.assert_has_calls([mock.call(mock.ANY)])
|
||||||
|
|
||||||
|
def test_response_format_error(self):
|
||||||
|
self._test_response_format_error_helper({})
|
||||||
|
|
||||||
|
def test_response_unknown_error_code(self):
|
||||||
|
self._test_response_format_error_helper(
|
||||||
|
{'error': {'code': 999}}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_response_known_error_code(self):
|
||||||
|
self._test_response_format_error_helper(
|
||||||
|
{'error': {'code': 1002, 'data': []}}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_response_known_error_code_data_is_not_dict(self):
|
||||||
|
self._test_response_format_error_helper(
|
||||||
|
{'error': {'code': 1002, 'data': ['some text']}}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_response_not_cvx_leader(self):
|
||||||
|
mock_response = mock.MagicMock(requests.Response)
|
||||||
|
mock_response.json.return_value = {
|
||||||
|
'error': {
|
||||||
|
'code': 1002,
|
||||||
|
'data': [{'errors': [api.ERR_CVX_NOT_LEADER]}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.client.session.post.return_value = mock_response
|
||||||
|
|
||||||
|
retval = self.client.execute(['enable'])
|
||||||
|
self.assertIsNone(retval)
|
||||||
|
|
||||||
|
def test_response_other_exception(self):
|
||||||
|
class OtherException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
mock_response = mock.MagicMock(requests.Response)
|
||||||
|
mock_response.json.return_value = 'text'
|
||||||
|
self.client.session.post.return_value = mock_response
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
TypeError,
|
||||||
|
self.client.execute,
|
||||||
|
['enable']
|
||||||
|
)
|
||||||
|
self.mock_log.warning.assert_has_calls(
|
||||||
|
[
|
||||||
|
mock.call(mock.ANY, {'error': mock.ANY})
|
||||||
|
]
|
||||||
|
)
|
@ -57,8 +57,7 @@ class AristaL3DriverTestCasesDefaultVrf(base.BaseTestCase):
|
|||||||
self.drv._servers[0])
|
self.drv._servers[0])
|
||||||
cmds = ['enable', 'configure', 'exit']
|
cmds = ['enable', 'configure', 'exit']
|
||||||
|
|
||||||
self.drv._servers[0].runCmds.assert_called_once_with(version=1,
|
self.drv._servers[0].execute.assert_called_once_with(cmds)
|
||||||
cmds=cmds)
|
|
||||||
|
|
||||||
def test_delete_router_from_eos(self):
|
def test_delete_router_from_eos(self):
|
||||||
router_name = 'test-router-1'
|
router_name = 'test-router-1'
|
||||||
@ -66,8 +65,7 @@ class AristaL3DriverTestCasesDefaultVrf(base.BaseTestCase):
|
|||||||
self.drv.delete_router_from_eos(router_name, self.drv._servers[0])
|
self.drv.delete_router_from_eos(router_name, self.drv._servers[0])
|
||||||
cmds = ['enable', 'configure', 'exit']
|
cmds = ['enable', 'configure', 'exit']
|
||||||
|
|
||||||
self.drv._servers[0].runCmds.assert_called_once_with(version=1,
|
self.drv._servers[0].execute.assert_called_once_with(cmds)
|
||||||
cmds=cmds)
|
|
||||||
|
|
||||||
def test_add_interface_to_router_on_eos(self):
|
def test_add_interface_to_router_on_eos(self):
|
||||||
router_name = 'test-router-1'
|
router_name = 'test-router-1'
|
||||||
@ -83,8 +81,7 @@ class AristaL3DriverTestCasesDefaultVrf(base.BaseTestCase):
|
|||||||
'interface vlan %s' % segment_id,
|
'interface vlan %s' % segment_id,
|
||||||
'ip address %s/%s' % (gw_ip, mask), 'exit']
|
'ip address %s/%s' % (gw_ip, mask), 'exit']
|
||||||
|
|
||||||
self.drv._servers[0].runCmds.assert_called_once_with(version=1,
|
self.drv._servers[0].execute.assert_called_once_with(cmds)
|
||||||
cmds=cmds)
|
|
||||||
|
|
||||||
def test_delete_interface_from_router_on_eos(self):
|
def test_delete_interface_from_router_on_eos(self):
|
||||||
router_name = 'test-router-1'
|
router_name = 'test-router-1'
|
||||||
@ -95,8 +92,7 @@ class AristaL3DriverTestCasesDefaultVrf(base.BaseTestCase):
|
|||||||
cmds = ['enable', 'configure', 'no interface vlan %s' % segment_id,
|
cmds = ['enable', 'configure', 'no interface vlan %s' % segment_id,
|
||||||
'exit']
|
'exit']
|
||||||
|
|
||||||
self.drv._servers[0].runCmds.assert_called_once_with(version=1,
|
self.drv._servers[0].execute.assert_called_once_with(cmds)
|
||||||
cmds=cmds)
|
|
||||||
|
|
||||||
|
|
||||||
class AristaL3DriverTestCasesUsingVRFs(base.BaseTestCase):
|
class AristaL3DriverTestCasesUsingVRFs(base.BaseTestCase):
|
||||||
@ -129,8 +125,7 @@ class AristaL3DriverTestCasesUsingVRFs(base.BaseTestCase):
|
|||||||
'vrf definition %s' % r,
|
'vrf definition %s' % r,
|
||||||
'rd %(rd)s:%(rd)s' % {'rd': d}, 'exit', 'exit']
|
'rd %(rd)s:%(rd)s' % {'rd': d}, 'exit', 'exit']
|
||||||
|
|
||||||
self.drv._servers[0].runCmds.assert_called_with(version=1,
|
self.drv._servers[0].execute.assert_called_with(cmds)
|
||||||
cmds=cmds)
|
|
||||||
|
|
||||||
def test_delete_router_from_eos(self):
|
def test_delete_router_from_eos(self):
|
||||||
max_vrfs = 5
|
max_vrfs = 5
|
||||||
@ -141,8 +136,7 @@ class AristaL3DriverTestCasesUsingVRFs(base.BaseTestCase):
|
|||||||
cmds = ['enable', 'configure', 'no vrf definition %s' % r,
|
cmds = ['enable', 'configure', 'no vrf definition %s' % r,
|
||||||
'exit']
|
'exit']
|
||||||
|
|
||||||
self.drv._servers[0].runCmds.assert_called_with(version=1,
|
self.drv._servers[0].execute.assert_called_with(cmds)
|
||||||
cmds=cmds)
|
|
||||||
|
|
||||||
def test_add_interface_to_router_on_eos(self):
|
def test_add_interface_to_router_on_eos(self):
|
||||||
router_name = 'test-router-1'
|
router_name = 'test-router-1'
|
||||||
@ -160,8 +154,7 @@ class AristaL3DriverTestCasesUsingVRFs(base.BaseTestCase):
|
|||||||
'vrf forwarding %s' % router_name,
|
'vrf forwarding %s' % router_name,
|
||||||
'ip address %s/%s' % (gw_ip, mask), 'exit']
|
'ip address %s/%s' % (gw_ip, mask), 'exit']
|
||||||
|
|
||||||
self.drv._servers[0].runCmds.assert_called_once_with(version=1,
|
self.drv._servers[0].execute.assert_called_once_with(cmds)
|
||||||
cmds=cmds)
|
|
||||||
|
|
||||||
def test_delete_interface_from_router_on_eos(self):
|
def test_delete_interface_from_router_on_eos(self):
|
||||||
router_name = 'test-router-1'
|
router_name = 'test-router-1'
|
||||||
@ -172,8 +165,7 @@ class AristaL3DriverTestCasesUsingVRFs(base.BaseTestCase):
|
|||||||
cmds = ['enable', 'configure', 'no interface vlan %s' % segment_id,
|
cmds = ['enable', 'configure', 'no interface vlan %s' % segment_id,
|
||||||
'exit']
|
'exit']
|
||||||
|
|
||||||
self.drv._servers[0].runCmds.assert_called_once_with(version=1,
|
self.drv._servers[0].execute.assert_called_once_with(cmds)
|
||||||
cmds=cmds)
|
|
||||||
|
|
||||||
|
|
||||||
class AristaL3DriverTestCasesMlagConfig(base.BaseTestCase):
|
class AristaL3DriverTestCasesMlagConfig(base.BaseTestCase):
|
||||||
@ -206,7 +198,7 @@ class AristaL3DriverTestCasesMlagConfig(base.BaseTestCase):
|
|||||||
cmds = ['enable', 'configure',
|
cmds = ['enable', 'configure',
|
||||||
'ip virtual-router mac-address %s' % router_mac, 'exit']
|
'ip virtual-router mac-address %s' % router_mac, 'exit']
|
||||||
|
|
||||||
s.runCmds.assert_called_with(version=1, cmds=cmds)
|
s.execute.assert_called_with(cmds)
|
||||||
|
|
||||||
def test_delete_router_from_eos(self):
|
def test_delete_router_from_eos(self):
|
||||||
router_name = 'test-router-1'
|
router_name = 'test-router-1'
|
||||||
@ -215,7 +207,7 @@ class AristaL3DriverTestCasesMlagConfig(base.BaseTestCase):
|
|||||||
self.drv.delete_router_from_eos(router_name, s)
|
self.drv.delete_router_from_eos(router_name, s)
|
||||||
cmds = ['enable', 'configure', 'exit']
|
cmds = ['enable', 'configure', 'exit']
|
||||||
|
|
||||||
s.runCmds.assert_called_once_with(version=1, cmds=cmds)
|
s.execute.assert_called_once_with(cmds)
|
||||||
|
|
||||||
def test_add_interface_to_router_on_eos(self):
|
def test_add_interface_to_router_on_eos(self):
|
||||||
router_name = 'test-router-1'
|
router_name = 'test-router-1'
|
||||||
@ -233,7 +225,7 @@ class AristaL3DriverTestCasesMlagConfig(base.BaseTestCase):
|
|||||||
'ip address %s' % router_ip,
|
'ip address %s' % router_ip,
|
||||||
'ip virtual-router address %s' % gw_ip, 'exit']
|
'ip virtual-router address %s' % gw_ip, 'exit']
|
||||||
|
|
||||||
s.runCmds.assert_called_once_with(version=1, cmds=cmds)
|
s.execute.assert_called_once_with(cmds)
|
||||||
|
|
||||||
def test_delete_interface_from_router_on_eos(self):
|
def test_delete_interface_from_router_on_eos(self):
|
||||||
router_name = 'test-router-1'
|
router_name = 'test-router-1'
|
||||||
@ -245,7 +237,7 @@ class AristaL3DriverTestCasesMlagConfig(base.BaseTestCase):
|
|||||||
cmds = ['enable', 'configure', 'no interface vlan %s' % segment_id,
|
cmds = ['enable', 'configure', 'no interface vlan %s' % segment_id,
|
||||||
'exit']
|
'exit']
|
||||||
|
|
||||||
s.runCmds.assert_called_once_with(version=1, cmds=cmds)
|
s.execute.assert_called_once_with(cmds)
|
||||||
|
|
||||||
|
|
||||||
class AristaL3DriverTestCases_v4(base.BaseTestCase):
|
class AristaL3DriverTestCases_v4(base.BaseTestCase):
|
||||||
@ -416,7 +408,7 @@ class AristaL3DriverTestCasesMlag_one_switch_failed(base.BaseTestCase):
|
|||||||
tenant = '123'
|
tenant = '123'
|
||||||
|
|
||||||
# Make one of the switches throw an exception - i.e. fail
|
# Make one of the switches throw an exception - i.e. fail
|
||||||
self.drv._servers[0].runCmds = mock.Mock(side_effect=Exception)
|
self.drv._servers[0].execute = mock.Mock(side_effect=Exception)
|
||||||
self.drv.create_router(None, tenant, router)
|
self.drv.create_router(None, tenant, router)
|
||||||
|
|
||||||
def test_delete_router_when_one_switch_fails(self):
|
def test_delete_router_when_one_switch_fails(self):
|
||||||
@ -426,7 +418,7 @@ class AristaL3DriverTestCasesMlag_one_switch_failed(base.BaseTestCase):
|
|||||||
router_id = '345'
|
router_id = '345'
|
||||||
|
|
||||||
# Make one of the switches throw an exception - i.e. fail
|
# Make one of the switches throw an exception - i.e. fail
|
||||||
self.drv._servers[1].runCmds = mock.Mock(side_effect=Exception)
|
self.drv._servers[1].execute = mock.Mock(side_effect=Exception)
|
||||||
self.drv.delete_router(None, tenant, router_id, router)
|
self.drv.delete_router(None, tenant, router_id, router)
|
||||||
|
|
||||||
def test_add_router_interface_when_one_switch_fails(self):
|
def test_add_router_interface_when_one_switch_fails(self):
|
||||||
@ -439,7 +431,7 @@ class AristaL3DriverTestCasesMlag_one_switch_failed(base.BaseTestCase):
|
|||||||
router['gip'] = '10.10.10.1'
|
router['gip'] = '10.10.10.1'
|
||||||
|
|
||||||
# Make one of the switches throw an exception - i.e. fail
|
# Make one of the switches throw an exception - i.e. fail
|
||||||
self.drv._servers[1].runCmds = mock.Mock(side_effect=Exception)
|
self.drv._servers[1].execute = mock.Mock(side_effect=Exception)
|
||||||
self.drv.add_router_interface(None, router)
|
self.drv.add_router_interface(None, router)
|
||||||
|
|
||||||
def test_remove_router_interface_when_one_switch_fails(self):
|
def test_remove_router_interface_when_one_switch_fails(self):
|
||||||
@ -452,5 +444,5 @@ class AristaL3DriverTestCasesMlag_one_switch_failed(base.BaseTestCase):
|
|||||||
router['gip'] = '10.10.10.1'
|
router['gip'] = '10.10.10.1'
|
||||||
|
|
||||||
# Make one of the switches throw an exception - i.e. fail
|
# Make one of the switches throw an exception - i.e. fail
|
||||||
self.drv._servers[0].runCmds = mock.Mock(side_effect=Exception)
|
self.drv._servers[0].execute = mock.Mock(side_effect=Exception)
|
||||||
self.drv.remove_router_interface(None, router)
|
self.drv.remove_router_interface(None, router)
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
# process, which may cause wedges in the gate later.
|
# process, which may cause wedges in the gate later.
|
||||||
|
|
||||||
Babel>=1.3
|
Babel>=1.3
|
||||||
jsonrpc-requests
|
|
||||||
neutron-lib>=1.1.0 # Apache-2.0
|
neutron-lib>=1.1.0 # Apache-2.0
|
||||||
oslo.log>=3.11.0 # Apache-2.0
|
oslo.log>=3.11.0 # Apache-2.0
|
||||||
pbr>=1.8
|
pbr>=1.8
|
||||||
|
Loading…
Reference in New Issue
Block a user