Skip to content

Commit

Permalink
Merge branch 'release-0.8.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
jerearista committed Feb 8, 2018
2 parents 8984f4b + a25b6b2 commit 5032e25
Show file tree
Hide file tree
Showing 14 changed files with 145 additions and 17 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.8.1
0.8.2
16 changes: 14 additions & 2 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,20 @@ provides some helpful methods and attributes to work with the switch.
# send one or more commands to the node
node.enable('show hostname')
[{'command': 'show hostname', 'result': {u'hostname': u'veos01', u'fqdn':
u'veos01.arista.com'}, 'encoding': 'json'}]
[{'command': 'show hostname',
'encoding': 'json',
'result': {u'hostname': u'veos01',
u'fqdn': u'veos01.arista.com'}}]
# Request a specific revision of a command that has been updated
node.enable({'cmd': 'show cvx', 'revision': 2})
[{'command': {'cmd': 'show cvx', 'revision': 2},
'encoding': 'json',
'result': {u'clusterMode': False,
u'controllerUUID': u'',
u'enabled': False,
u'heartbeatInterval': 20.0,
u'heartbeatTimeout': 60.0}}]
# use the config method to send configuration commands
node.config('hostname veos01')
Expand Down
41 changes: 41 additions & 0 deletions docs/release-notes-0.8.2.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
Release 0.8.2
-------------

2018-02-09

New Modules
^^^^^^^^^^^


Enhancements
^^^^^^^^^^^^

* Support eapi command revision syntax (`158 <https://github.com/arista-eosplus/pyeapi/pull/158>`_) [`jerearista <https://github.com/jerearista>`_]
Support requests for specific revisions of EOS command output

.. code-block:: python
>>> node.enable({'cmd': 'show cvx', 'revision': 2})
[{'command': {'cmd': 'show cvx', 'revision': 2},
'encoding': 'json',
'result': {u'clusterMode': False,
u'controllerUUID': u'',
u'enabled': False,
u'heartbeatInterval': 20.0,
u'heartbeatTimeout': 60.0}}]
* Add clearer error message for bad user credentials. (`152 <https://github.com/arista-eosplus/pyeapi/pull/152>`_) [`mharista <https://github.com/mharista>`_]
.. comment
* Reformat EapiConnection send methods exception handling. (`148 <https://github.com/arista-eosplus/pyeapi/pull/148>`_) [`mharista <https://github.com/mharista>`_]
.. comment
Fixed
^^^^^

* Fix route map getall function to find route maps with a hyphen in the name. (`154 <https://github.com/arista-eosplus/pyeapi/pull/154>`_) [`mharista <https://github.com/mharista>`_]
.. comment
Known Caveats
^^^^^^^^^^^^^


1 change: 1 addition & 0 deletions docs/release-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Release Notes
:maxdepth: 2
:titlesonly:

release-notes-0.8.2.rst
release-notes-0.8.1.rst
release-notes-0.8.0.rst
release-notes-0.7.0.rst
Expand Down
2 changes: 1 addition & 1 deletion pyeapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
__version__ = '0.8.1'
__version__ = '0.8.2'
__author__ = 'Arista EOS+'


Expand Down
2 changes: 1 addition & 1 deletion pyeapi/api/routemaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def get(self, name):

def getall(self):
resources = dict()
routemaps_re = re.compile(r'^route-map\s(\w+)\s\w+\s\d+$', re.M)
routemaps_re = re.compile(r'^route-map\s([\w-]+)\s\w+\s\d+$', re.M)
for name in routemaps_re.findall(self.config):
routemap = self.get(name)
if routemap:
Expand Down
20 changes: 12 additions & 8 deletions pyeapi/eapilib.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,10 @@ def send(self, data):
reason=response.reason))
_LOGGER.debug('Response content: {}'.format(response_content))

if response.status == 401:
raise ConnectionError(str(self), '%s. %s' % (response.reason,
response_content))

# Work around for Python 2.7/3.x compatibility
if not type(response_content) == str:
# For Python 3.x - decode bytes into string
Expand All @@ -416,17 +420,17 @@ def send(self, data):
return decoded

# socket.error is deprecated in python 3 and replaced with OSError.
except (socket.error, OSError, ValueError) as exc:
if isinstance(exc, socket.error) or isinstance(exc, OSError):
self.socket_error = exc
except (socket.error, OSError) as exc:
_LOGGER.exception(exc)
self.socket_error = exc
self.error = exc
error_msg = 'unable to connect to eAPI'
if self.socket_error:
error_msg = ('Socket error during eAPI connection: %s'
% str(exc))
error_msg = 'Socket error during eAPI connection: %s' % str(exc)
raise ConnectionError(str(self), error_msg)

except ValueError as exc:
_LOGGER.exception(exc)
self.socket_error = None
self.error = exc
raise ConnectionError(str(self), 'unable to connect to eAPI')
finally:
self.transport.close()

Expand Down
2 changes: 1 addition & 1 deletion pyeapi/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def make_iterable(value):
# Convert unicode values to strings for Python 2
if isinstance(value, unicode):
value = str(value)
if isinstance(value, str):
if isinstance(value, str) or isinstance(value, dict):
value = [value]

if not isinstance(value, collections.Iterable):
Expand Down
12 changes: 12 additions & 0 deletions test/fixtures/running_config.routemaps
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,15 @@ route-map FOOBAR permit 20
match interface Ethernet2
continue 200
!
route-map FOO-BAR permit 10
match as 2000
match source-protocol ospf
match interface Ethernet2
continue 200
!
route-map FOO-BAR deny 20
match as 2000
match source-protocol ospf
match interface Ethernet2
continue 200
!
6 changes: 3 additions & 3 deletions test/system/test_api_routemaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ def test_getall(self):
dut.config(['no route-map TEST deny 10',
'route-map TEST deny 10',
'set weight 100',
'no route-map TEST2 permit 50',
'route-map TEST2 permit 50',
'no route-map TEST-2 permit 50',
'route-map TEST-2 permit 50',
'match tag 50'])
result = dut.api('routemaps').getall()
self.assertIn(('TEST'), result)
self.assertIn(('TEST2'), result)
self.assertIn(('TEST-2'), result)

def test_create(self):
for dut in self.duts:
Expand Down
26 changes: 26 additions & 0 deletions test/system/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ def setUp(self):
# enable password on the dut and clear it on tearDown
dut.config("enable secret %s" % dut._enablepwd)

def test_unauthorized_user(self):
error_string = ('Unauthorized. Unable to authenticate user: Bad'
' username/password combination')
for dut in self.duts:
temp_node = pyeapi.connect(host=dut.settings['host'],
transport=dut.settings['transport'],
username='wrong', password='nope',
port=dut.settings['port'],
return_node=True)
try:
temp_node.run_commands('show version')
except pyeapi.eapilib.ConnectionError as err:
self.assertEqual(err.message, error_string)

def test_populate_version_properties(self):
for dut in self.duts:
result = dut.run_commands('show version')
Expand All @@ -72,6 +86,18 @@ def test_enable_single_command(self):
self.assertIsInstance(result, list, 'dut=%s' % dut)
self.assertEqual(len(result), 1, 'dut=%s' % dut)

def test_enable_single_extended_command(self):
for dut in self.duts:
result = dut.run_commands({'cmd': 'show cvx', 'revision': 1})
self.assertIsInstance(result, list, 'dut=%s' % dut)
self.assertEqual(len(result), 1, 'dut=%s' % dut)
self.assertTrue('clusterMode' not in result[0].keys())

result2 = dut.run_commands({'cmd': 'show cvx', 'revision': 2})
self.assertIsInstance(result2, list, 'dut=%s' % dut)
self.assertEqual(len(result2), 1, 'dut=%s' % dut)
self.assertTrue('clusterMode' in result2[0].keys())

def test_enable_single_unicode_command(self):
for dut in self.duts:
result = dut.run_commands(u'show version')
Expand Down
3 changes: 3 additions & 0 deletions test/unit/test_api_routemaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,11 @@ def test_get_not_configured(self):
self.assertIsNone(self.instance.get('blah'))

def test_getall(self):
# Review fixtures/running_config.routemaps to see the default
# running-config that is the basis for this test
result = self.instance.getall()
self.assertIsInstance(result, dict)
self.assertEqual(len(result.keys()), 4)

def test_routemaps_functions(self):
for name in ['create', 'delete', 'default']:
Expand Down
10 changes: 10 additions & 0 deletions test/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@ def test_enable_with_single_unicode_command(self):
self.connection.execute.assert_called_once_with(response, 'json')
self.assertEqual(command, result[0]['result'])

def test_enable_with_single_extended_command(self):
command = {'cmd': 'show cvx', 'revision': 2}
response = ['enable', command]

self.connection.execute.return_value = {'result': list(response)}
result = self.node.enable(command)

self.connection.execute.assert_called_once_with(response, 'json')
self.assertEqual(command, result[0]['result'])

def test_no_enable_with_single_command(self):
command = random_string()
response = [command]
Expand Down
19 changes: 19 additions & 0 deletions test/unit/test_eapilib.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,25 @@ def test_send_with_authentication(self):

self.assertTrue(mock_transport.close.called)

def test_send_unauthorized_user(self):
error_string = ('Unauthorized. Unable to authenticate user: Bad'
' username/password combination')
response_str = ('Unable to authenticate user: Bad username/password'
' combination')
mock_transport = Mock(name='transport')
mockcfg = {'getresponse.return_value.read.return_value': response_str,
'getresponse.return_value.status': 401,
'getresponse.return_value.reason': 'Unauthorized'}
mock_transport.configure_mock(**mockcfg)

instance = pyeapi.eapilib.EapiConnection()
instance.authentication('username', 'password')
instance.transport = mock_transport
try:
instance.send('test')
except pyeapi.eapilib.ConnectionError as err:
self.assertEqual(err.message, error_string)

def test_send_raises_connection_error(self):
mock_transport = Mock(name='transport')
mockcfg = {'getresponse.return_value.read.side_effect': ValueError}
Expand Down

0 comments on commit 5032e25

Please sign in to comment.