Skip to content

Commit

Permalink
cvlib: Add error checking to device commands run
Browse files Browse the repository at this point in the history
Some users were expecting device command failures to raise an exception
rather than needing to be handled by themselves. Add this check
to the call to bring it more inline with other clients, such as builtin
action error checking

Change-Id: I43ea9de4b92f4268a06240122bbefa8380c5ba30
  • Loading branch information
cianmcgrath committed Apr 15, 2024
1 parent a723f10 commit 4522e04
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 5 deletions.
8 changes: 8 additions & 0 deletions cloudvision/cvlib/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,14 @@ def runDeviceCmds(self, commandsList: List[str], device: Optional[Device] = None
errMsg = resp["errorMessage"]
raise DeviceCommandsFailed((f"Commands failed to run on device \"{device.id}\","
f" returned {errCode}:\"{errMsg}\""), errCode, errMsg)

# Check that none of the commands have outright failed
for i, cmdResp in enumerate(resp):
err = cmdResp.get("error")
if err:
raise DeviceCommandsFailed((f"Command \"{commandsList[i]}\" failed to run on "
f"device \"{device.id}\", returned {err}"))

return resp

@staticmethod
Expand Down
75 changes: 70 additions & 5 deletions test/cvlib/context/test_device_cmds.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
# that can be found in the COPYING file.

import pytest

from unittest import mock
from http import HTTPStatus
from unittest.mock import Mock, patch

from cloudvision.cvlib import (
Action,
ActionContext,
AuthAndEndpoints,
Context,
Device,
User
Expand Down Expand Up @@ -66,7 +67,7 @@ def test_get_host_name_exception(name, device, exception, expected, returnVals):
device=device,
)

ctx.runDeviceCmds = mock.Mock(return_value=returnVals)
ctx.runDeviceCmds = Mock(return_value=returnVals)

with pytest.raises(exception) as excinfo:
ctx.getDeviceHostname(ctx.device)
Expand All @@ -82,7 +83,7 @@ def test_get_host_name_exception(name, device, exception, expected, returnVals):
device = Device(ip="123.456.789", deviceId="JP123456",
deviceMac="00-B0-D0-63-C2-26")

cases_run_device_cmds = [
cases_run_device_cmds_exp = [
(
"no action",
None,
Expand Down Expand Up @@ -111,7 +112,7 @@ def test_get_host_name_exception(name, device, exception, expected, returnVals):
]


@pytest.mark.parametrize('name, device, action, expected', cases_run_device_cmds)
@pytest.mark.parametrize('name, device, action, expected', cases_run_device_cmds_exp)
def test_runDeviceCmds_exception(name, device, action, expected):
ctx = Context(
user=User("test_user", "123"),
Expand All @@ -122,3 +123,67 @@ def test_runDeviceCmds_exception(name, device, action, expected):
with pytest.raises(InvalidContextException) as excinfo:
ctx.runDeviceCmds(["enable", "show hostname"], ctx.device)
assert expected in str(excinfo.value)


cases_run_device_cmds = [
(
"failure in request",
{'errorCode': '341604', 'errorMessage': 'Invalid request'},
True,
f"Commands failed to run on device \"{device.id}\", returned 341604:\"Invalid request\""
),
(
"Command failed",
[
{"response": "", "error": ""},
{"response": "", "error": "Ambiguous command (at token 1)"},
],
True,
(f"Command \"testCommand\" failed to run on device \"{device.id}\","
" returned Ambiguous command (at token 1)")
),
(
"Command passed",
[
{"response": "", "error": ""},
{"response": "Test command success", "error": ""},
],
False,
"Test command success"
),
]


@pytest.mark.parametrize('name, mockedResp, exception, expected', cases_run_device_cmds)
def test_runDeviceCmds(name, mockedResp, exception, expected):

conns = AuthAndEndpoints(
serviceAddr="localhost",
commandEndpoint="commands",
)
ctx = Context(
user=User("test_user", "123"),
connections=conns,
device=device,
action=action,
)

def mocked_request_resp(*args, **kwargs):
class MockResponse:
def __init__(self, data, status_code):
self.json_data = data
self.status_code = status_code

def json(self):
return self.json_data

return MockResponse(mockedResp, HTTPStatus.OK)

with patch('cloudvision.cvlib.context.requests.post', side_effect=mocked_request_resp):
if exception:
with pytest.raises(DeviceCommandsFailed) as excinfo:
ctx.runDeviceCmds(["enable", "testCommand"])
assert expected in str(excinfo.value), "Unexpected exception"
else:
resp = ctx.runDeviceCmds(["enable", "testCommand"])
assert expected == resp[1]["response"], "Response is not expected"

0 comments on commit 4522e04

Please sign in to comment.