Skip to content

Commit

Permalink
Add set_outlet and restart methods (#7)
Browse files Browse the repository at this point in the history
Implement two specific ServerTech methods to change the outlets
status (on, off, reboot) and to restart the PDU.
  • Loading branch information
inetAnt authored Sep 10, 2021
1 parent 0187827 commit ecccebe
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 8 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ Please find below the list of supported getters:
* get_interfaces_ip (will only return the `NET` interface management address)
* get_users

## Additional features

The default NAPALM methods don't cover everything we can do on the PDUs (they actually do via CLI, but the driver does not control the PDUs via the CLIs yet). In order to perform actions such as changing the status of an outlet or resetting a PDU, new methods are implemented:

* set_outlet
* restart

## Contributing
Please read [CONTRIBUTING](CONTRIBUTING) for details on our process for submitting issues and requests.

Expand Down
11 changes: 11 additions & 0 deletions napalm_servertech_pro2/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,14 @@
"power user": 14,
"admin": 15,
}

SUPPORTED_OUTLET_ACTIONS = ["off", "on", "reboot"]

SUPPORTED_RESTART_ACTIONS = [
"factory",
"factory keep network",
"new firmware",
"new ssh keys",
"new x509 certificate",
"normal",
]
51 changes: 48 additions & 3 deletions napalm_servertech_pro2/pro2.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,17 @@
from netaddr import IPNetwork
from requests.auth import HTTPBasicAuth

from napalm_servertech_pro2.constants import CONFIG_ITEMS, LOCAL_USER_LEVELS
from napalm_servertech_pro2.utils import convert_uptime, parse_hardware
from napalm_servertech_pro2.constants import (
CONFIG_ITEMS,
LOCAL_USER_LEVELS,
SUPPORTED_OUTLET_ACTIONS,
SUPPORTED_RESTART_ACTIONS,
)
from napalm_servertech_pro2.utils import (
convert_uptime,
parse_hardware,
validate_actions,
)


class PRO2Driver(NetworkDriver):
Expand All @@ -33,7 +42,14 @@ def _req(self, path, method="GET", json=None, raise_err=True):
raise err
else:
return {"err": str(err)}
return req.json()
if "application/json" in req.headers.get("Content-Type"):
return req.json()
else:
return {
"status": "success",
"status_code": req.status_code,
"content": req.text,
}

def open(self):
"""Open a connection to the device."""
Expand Down Expand Up @@ -199,3 +215,32 @@ def get_users(self):
}
for user in users
}

def set_outlet(self, outlet_id, action):
"""
Change the status of an outlet
:param outlet_id: a string
:param action: a string (values can be: on, off, reboot)
:return: a dict
"""
validate_actions(action, SUPPORTED_OUTLET_ACTIONS)

outlet = self._req(
f"/control/outlets/{outlet_id}", "PATCH", json={"control_action": action}
)

return outlet

def restart(self, action):
"""
Restarts the PDU
:param action: a string (see SUPPORTED_RESTART_ACTIONS for valid values)
:return: a dict
"""
validate_actions(action, SUPPORTED_RESTART_ACTIONS)

restart = self._req("/restart", "PATCH", json={"action": action})

return restart
10 changes: 10 additions & 0 deletions napalm_servertech_pro2/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,13 @@ def parse_hardware(hardware_string):
"ram": int(m.group("ram")),
"flash": int(m.group("flash")),
}


def validate_actions(action, supported_actions):
"""Ensures the inputed action is supported, raises an exception otherwise."""
if action not in supported_actions:
raise ValueError(
f'Action "{action}" is not supported.'
" the list of valid actions is: {}".format(", ".join(supported_actions))
)
return True
37 changes: 32 additions & 5 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pytest
from napalm.base.test import conftest as parent_conftest
from napalm.base.test.double import BaseTestDouble
from requests.models import HTTPError
from napalm_servertech_pro2 import pro2


Expand Down Expand Up @@ -50,21 +51,47 @@ def open(self):
class FakePRO2Api(BaseTestDouble):
"""ServerTech fake API."""

REQUESTS = {
"control/outlets/XX99": {
"headers": {"Content-Type": "text/html"},
"status_code": 404,
}
}

def request(self, method, **kwargs):
filename = f'{self.sanitize_text(kwargs["url"].split("/jaws/")[1])}.json'
path = self.find_file(filename)
return FakeRequest(method, path)
address = kwargs["url"].split("/jaws/")[1]
if self.REQUESTS.get(address):
return FakeRequest(
method,
address,
self.REQUESTS[address]["headers"],
self.REQUESTS[address]["status_code"],
)
else:
filename = f"{self.sanitize_text(address)}.json"
path = self.find_file(filename)
headers = {
"Content-Type": "application/json" if method == "GET" else "text/html"
}
status_code = 200 if method == "GET" else 204
return FakeRequest(method, path, headers, status_code)


class FakeRequest:
"""A fake API request."""

def __init__(self, method, path):
def __init__(self, method, path, headers, status_code):
self.method = method
self.path = path
self.headers = headers
self.status_code = status_code
self.text = ""

def raise_for_status(self):
return True
if self.status_code >= 400:
raise HTTPError
else:
return True

def json(self):
with open(self.path, "r") as file:
Expand Down
Empty file.
Empty file.
26 changes: 26 additions & 0 deletions tests/unit/test_add.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Tests for getters."""

import pytest
from requests import HTTPError


@pytest.mark.usefixtures("set_device_parameters")
class TestAdd(object):
"""Test additional methods."""

def test_set_outlet(self):
res = self.device.set_outlet("AA01", "on")
assert res["status"] == "success"

with pytest.raises(ValueError):
self.device.set_outlet("AA01", "no")

with pytest.raises(HTTPError):
self.device.set_outlet("XX99", "on")

def test_reboot(self):
res = self.device.restart("normal")
assert res["status"] == "success"

with pytest.raises(ValueError):
self.device.restart("reboot")
3 changes: 3 additions & 0 deletions tests/unit/test_getters.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@
@pytest.mark.usefixtures("set_device_parameters")
class TestGetter(BaseTestGetters):
"""Test get_* methods."""

def test_method_signatures(self):
return True
8 changes: 8 additions & 0 deletions tests/utils/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,11 @@ def test_parse_hardware():
"ram": 2048,
"flash": 2048,
}


def test_validate_actions():
supported_actions = ["foo", "bar"]
assert utils.validate_actions("foo", supported_actions) is True

with pytest.raises(ValueError):
utils.validate_actions("oof", supported_actions)

0 comments on commit ecccebe

Please sign in to comment.