Skip to content

[feature] driver/netio: use netio via json-api #1234

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ Currently available are:
``netio``
Controls a NETIO 4-Port PDU via a simple HTTP API.

``netio_json``
Controls a NETIO n-Port PDU via a simple HTTP-JSON API.

``netio_kshell``
Controls a NETIO 4C PDU via a Telnet interface.

Expand Down
157 changes: 157 additions & 0 deletions labgrid/driver/power/netio_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Add HTTP-JSON-API functions for labgrid-netio power driver.
Set model in YML-CFG to 'netio_json'.
"""
__author__ = "Eugen Wiens, Christian Happ, Raffael Krakau"
__copyright__ = "Copyright 2023, JUMO GmbH & Co. KG"
__email__ = "[email protected], [email protected]"
__date__ = "2023-07-05"
__status__ = "Production"

import json

import requests

PORT = 80


class __NetioControl:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the double underscore?

Copy link
Author

@ActionHOSchT ActionHOSchT Sep 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially dunderscore was added to keep NetioControl private and to prevent it from showing up in the doc.(imo a user/dev does only need this info if he already takes a look in the source-code)

With dunderscore the class will be mangled and show up as power__NetioControl giving a better idea what this exactly is.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be resolved?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would a single underscore still be prefered?

"""
Netio-Class to switch sockets via HTTP-JSON-API
"""
__host: str
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__host and the variables below are defined as class variables, however they are not used anywhere? Every place uses the instance variables instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__host is a private copy of host (added to prevent side-effects)
At line 105 this copy will be used.
__host will be mangled and show up as NetioControl__host which prevents any other operation from accidentally modifying it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be resolved?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any further suggestion on how to handle this preferably?

__port: int
__username: str
__password: str

def __init__(self, host, port=80, username="", password=""):
self.__host = host
self.__port = port
self.__username = username
self.__password = password

def set_state(self, socketID, action):
"""
Set power state by socket port number (e.g. 1 - 8) and an action ('1'-'4').

- actions:
- 0: Turn OFF,
- 1: Turn ON,
- 2: Short OFF delay (restart),
- 3: Short ON delay,
- 4: Toggle (invert the state)
"""
state = None
response = requests.post(self.generate_request_url(), json=self.get_request_json_data(socketID, action))

if response.ok:
responseDict = json.loads(response.text)
outputStates = responseDict['Outputs']

for outputState in outputStates:
if outputState['ID'] == socketID:
state = outputState
break
else:
raise Exception(f"Cannot SET the power state for socket number: {socketID} for action: {action}."
f" Error code: {response.text}")

return state

def get_state(self, socketID):
"""
Get current state of a given socket number as json.
"""
state = None
response = requests.get(self.generate_request_url())

if response.ok:
responseDict = json.loads(response.text)

for outputState in responseDict['Outputs']:
if outputState['ID'] == socketID:
state = outputState['State']
break
else:
raise Exception(f"Cannot get the power state for socket {socketID}. Error code: {response.text}")

return state

def convert_socket_id(self, socketID) -> int:
"""
Cast socketID to int.

raises ValueError
"""
try:
socketID = int(socketID)
except ValueError as e:
raise Exception(f"socketID \"{socketID}\" could not be converted to an integer: {e}!")

return socketID

def generate_request_url(self) -> str:
"""
Generate request URL from given params.
"""
requestUrl = 'http://'

if self.__username and self.__password:
requestUrl += f'{self.__username}:{self.__password}@'

requestUrl += f'{self.__host}:{self.__port}/netio.json'

return requestUrl

def get_request_json_data(self, socketID, action) -> str:
data = f'{{"Outputs":[{{"ID":{socketID},"Action":{self.convert_action(action)}}}]}}'
return json.loads(data)

def to_str(self, toConvert) -> str:
if toConvert == 1 or toConvert is True:
toConvert = "1"
elif toConvert == 0 or toConvert is False:
toConvert = "0"
return toConvert

def convert_action(self, action) -> str:
action = self.to_str(action)

try:
assert 0 <= int(action) <= 4
return action
except AssertionError as ae:
raise Exception(f"{ae}\n Action \"{action}\" seems not right, should be between -> '0'-'4'!")


def power_set(host, port, index, action):
"""
Set netio-state at index(socketID)

- host: netio-device adress
- port: standard is 80
- index: depends on netio-device 1-n (n is the size of netio)
- action:
- 0: Turn OFF,
- 1: Turn ON,
- 2: Short OFF delay (restart),
- 3: Short ON delay,
- 4: Toggle (invert the state)
"""
netio = __NetioControl(host, port)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the class indirection necessary? How are username and password set?

Copy link
Author

@ActionHOSchT ActionHOSchT Sep 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class indirection isn't strictly necessary, sure it would also be possible to implement this classless.

The json-api supports/expects username and password, even if they're empty.
afaik NetworkPowerDriver doesn't support to set username and password because NetworkPowerPort doesn't support it.
We/I couldn't find a way to set username and password at exporter-config.yaml.
Because of this another PR is on it's way (hopefully PR-ready within 2 weeks).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Username and password are prefabricated.
If you want this to be removed I will remove username and password.
If it's ok for you I would keep it for possible "future-use".

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the class is not necessary, we can adjust that. Just as note, we use the NetioControl class in other projects as well, we just added the Labgrid API functions power_set and power_get to reuse that code and get it working with Labgrid.

How should we continue on that ?

print(netio.set_state(netio.convert_socket_id(index), action))


def power_get(host, port, index):
"""
Get power-state as json

- host: netio-device adress
- port: standard is 80
- index: depends on netio-device 1-n (n is the size of netio)
"""
netio = __NetioControl(host, port)
return netio.get_state(netio.convert_socket_id(index))
1 change: 1 addition & 0 deletions tests/test_powerdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ def test_import_backends(self):
import labgrid.driver.power.gude
import labgrid.driver.power.gude24
import labgrid.driver.power.netio
import labgrid.driver.power.netio_json
import labgrid.driver.power.netio_kshell
import labgrid.driver.power.rest
import labgrid.driver.power.sentry
Expand Down
Loading