[harness-automation] add another PDU Controller for EATON by SNMP (#4688)

Another PDU Controller used to power cycle between tests. Based by
SNMP protocol. To use EATON_PDU_CONTROLLER is needed PySNMP module
(added to requirements.txt). Also extend settings_sample.py by
comments with needed parameters to create EATON_PDU_CONTROLLER.
This commit is contained in:
doublemis1
2020-05-04 14:43:34 -07:00
committed by GitHub
parent e08f5aebd9
commit 2153f5a4f4
4 changed files with 229 additions and 4 deletions
@@ -42,6 +42,13 @@ except ImportError:
logger = logging.getLogger(__name__)
try:
from pysnmp.hlapi import SnmpEngine, CommunityData, UdpTransportTarget, ContextData, getCmd, setCmd, ObjectType, ObjectIdentity, Integer32
except ImportError:
logger.warning(
'PySNMP module is not installed. Install if EATON_PDU_CONTROLLER is used'
)
class PduController(object):
@@ -91,10 +98,10 @@ class ApcPduController(PduController):
def open(self, **params):
"""Open telnet connection
Args:
params (dict), must contain two parameters "ip" - ip address or hostname and "port" - port number
Example:
params = {'port': 23, 'ip': 'localhost'}
"""
@@ -124,10 +131,10 @@ class ApcPduController(PduController):
def reboot(self, **params):
"""Reboot outlet
Args:
params (dict), must contain parameter "outlet" - outlet number
Example:
params = {'outlet': 1}
"""
@@ -189,6 +196,218 @@ class NordicBoardPduController(PduController):
pass
class EatonPduController(PduController):
outlet_oid_cmd_get_state_base = '1.3.6.1.4.1.534.6.6.7.6.6.1.2.0.'
outlet_oid_cmd_set_on_base = '1.3.6.1.4.1.534.6.6.7.6.6.1.4.0.'
outlet_oid_cmd_set_off_base = '1.3.6.1.4.1.534.6.6.7.6.6.1.3.0.'
outlet_oid_cmd_reboot_base = '1.3.6.1.4.1.534.6.6.7.6.6.1.5.0.'
outlet_oid_cmd_set_reboot_delay_seconds_base = '1.3.6.1.4.1.534.6.6.7.6.6.1.8.0.'
PDU_COMMAND_TIMEOUT = 5
def open(self, **params):
missing_fields = ['ip', 'port']
missing_fields = [
field for field in missing_fields if field not in params.keys()
]
if missing_fields:
raise KeyError(
'Missing keys in PDU params: {}'.format(missing_fields))
self.params = params
self.type = 'pdu'
self.ip = self.params['ip']
self.snmp_agent_port = int(self.params['port'])
self._community = 'public'
self._snmp_engine = SnmpEngine()
self._community_data = CommunityData(self._community, mpModel=0)
self._udp_transport_target = UdpTransportTarget(
(self.ip, self.snmp_agent_port))
self._context = ContextData()
def _outlet_oid_get(self, param, socket):
"""
Translates command to the OID number representing a command for the specific power socket.
Args:
param (str), command string
socket (int), socket index
Return:
full OID identifying the SNMP object (str)
"""
parameters = {
'get_state':
self.outlet_oid_cmd_get_state_base,
'set_on':
self.outlet_oid_cmd_set_on_base,
'set_off':
self.outlet_oid_cmd_set_off_base,
'set_reboot_delay':
self.outlet_oid_cmd_set_reboot_delay_seconds_base,
'reboot':
self.outlet_oid_cmd_reboot_base
}
return parameters[param.lower()] + str(socket)
# Performs set command to specific OID with a given value by sending a SNMP Set message.
def _oid_set(self, oid, value):
"""
Performs set command to specific OID with a given value by sending a SNMP Set message.
Args:
oid (str): Full OID identifying the object to be read.
value (int): Value to be written to the OID as Integer32.
"""
errorIndication, errorStatus, errorIndex, varBinds = next(
setCmd(self._snmp_engine, self._community_data,
self._udp_transport_target, self._context,
ObjectType(ObjectIdentity(oid), Integer32(value))))
if errorIndication:
msg = 'Found PDU errorIndication: {}'.format(errorIndication)
logger.exception(msg)
raise RuntimeError(msg)
elif errorStatus:
msg = 'Found PDU errorStatus: {}'.format(errorStatus)
logger.exception(msg)
raise RuntimeError(msg)
def _oid_get(self, oid):
"""
Performs SNMP get command and returns OID value by sending a SNMP Get message.
Args:
oid (str): Full OID identifying the object to be read.
Return:
OID value (int)
"""
errorIndication, errorStatus, errorIndex, varBinds = next(
getCmd(self._snmp_engine, self._community_data,
self._udp_transport_target, self._context,
ObjectType(ObjectIdentity(oid))))
if errorIndication:
msg = 'Found PDU errorIndication: {}'.format(errorIndication)
logger.exception(msg)
raise RuntimeError(msg)
elif errorStatus:
msg = 'Found PDU errorStatus: {}'.format(errorStatus)
logger.exception(msg)
raise RuntimeError(msg)
return int(str(varBinds[-1]).partition('= ')[-1])
def _outlet_value_set(self, cmd, socket, value=1):
"""
Sets outlet parameter value.
Args:
cmd (str): OID base
socket (int): socket index (last OID number)
value (int): value to be set
"""
oid = self._outlet_oid_get(cmd, socket)
# Values other than 1 does not make sense with commands other than "set_reboot_delay".
if cmd != 'set_reboot_delay':
value = 1
self._oid_set(oid, value)
def _outlet_value_get(self, cmd, socket):
"""
Read outlet parameter value.
Args:
cmd (str): OID base
socket (int): socket index (last OID number)
Return:
parameter value (int)
"""
oid = self._outlet_oid_get(cmd, socket)
return self._oid_get(oid)
def validate_state(self, socket, state):
return (self._outlet_value_get('get_state', socket) == state)
def turn_off(self, sockets):
"""
Turns the specified socket off.
Args:
sockets (list(int)): sockets to be turned off
"""
logger.info('Executing turn OFF for: {}'.format(sockets))
for socket in sockets:
self._outlet_value_set('set_off', socket)
time.sleep(2)
timeout = time.time() + self.PDU_COMMAND_TIMEOUT
while ((time.time() < timeout) and
not self.validate_state(socket, 0)):
time.sleep(0.1)
if self.validate_state(socket, 0):
logger.debug('Turned OFF socket {} at {}'.format(
socket, self.ip))
else:
logger.error('Failed to turn OFF socket {} at {}'.format(
socket, self.ip))
def turn_on(self, sockets):
"""
Turns the specified socket on.
Args:
sockets (list(int)): sockets to be turned on
"""
logger.info('Executing turn ON for: {}'.format(sockets))
for socket in sockets:
self._outlet_value_set('set_on', socket)
time.sleep(2)
timeout = time.time() + self.PDU_COMMAND_TIMEOUT
while ((time.time() < timeout) and
not self.validate_state(socket, 1)):
time.sleep(0.1)
if self.validate_state(socket, 1):
logger.debug('Turned ON socket {} at {}'.format(
socket, self.ip))
else:
logger.error('Failed to turn ON socket {} at {}'.format(
socket, self.ip))
def close(self):
self._community = None
self._snmp_engine = None
self._community_data = None
self._udp_transport_target = None
self._context = None
def reboot(self, **params):
"""
Reboots the sockets specified in the constructor with off and on delays.
Args:
sockets (list(int)): sockets to reboot
"""
logger.info('Executing power cycle for: {}'.format(params['sockets']))
self.turn_off(params['sockets'])
time.sleep(10)
self.turn_on(params['sockets'])
time.sleep(5)
class IpPowerSocketPduController(PduController):
def open(self, **params):
@@ -40,5 +40,7 @@ class PduControllerFactory(object):
return pdu_controller.IpPowerSocketPduController()
elif _type == 'MANUAL_PDU_CONTROLLER':
return pdu_controller.ManualPduController()
elif _type == 'EATON_PDU_CONTROLLER':
return pdu_controller.EatonPduController()
else:
return pdu_controller.DummyPduController()
@@ -122,6 +122,7 @@ Types of supported PDU controllers:
- 'APC_PDU_CONTROLLER' - when APC PDU controller connected
- 'NORDIC_BOARD_PDU_CONTOLLER' - when Nordic boards PDU controller connected
- 'IP_POWER_SOCKET_PDU_CONTROLLER' - when IP Power Socket 5G10A connected
- 'EATON_PDU_CONTROLLER' - when EATON PDU controller connected
"""
PDU_CONTROLLER_OPEN_PARAMS = {'port': 23, 'ip': '127.0.0.1'}
@@ -148,6 +149,8 @@ Example parameters for the 'NORDIC_BOARD_PDU_CONTOLLER':
Example parameters for the 'IP_POWER_SOCKET_PDU_CONTROLLER':
{'sockets': [0, 1]}
Example parameters for the 'EATON_PDU_CONTROLLER':
{'ip': '127.0.0.1'}
"""
SHIELD_CONTROLLER_TYPE = None
@@ -1,2 +1,3 @@
pyserial
selenium
pysnmp