Cleanup submitted SNMP driver code for additional PDUs
* Resolved PEP8 issues * Trimmed comments to remove extraneous information * Changed rfc1902.Integer() calls to the correct snmp.Integer() calls * Fixed power state logic checking for new PDUs that don't have transitional states (e.g., 'pendingOn') * Removed redundant warning messages * Added unit tests for Raritan PD2, ServerTech Sentry 3/4, and Vertiv Geist drivers * Updated documentation to list tested PDUs for the new drivers * Updated release notes Change-Id: I9da7b9042b817c346f75a44cd8287e1f63efcb56
This commit is contained in:
parent
b796d7b833
commit
4415c55028
@ -22,9 +22,9 @@ this table could possibly work using a similar driver.
|
||||
|
||||
Please report any device status.
|
||||
|
||||
============== ========== ========== =====================
|
||||
============== ============== ========== =====================
|
||||
Manufacturer Model Supported? Driver name
|
||||
============== ========== ========== =====================
|
||||
============== ============== ========== =====================
|
||||
APC AP7920 Yes apc_masterswitch
|
||||
APC AP9606 Yes apc_masterswitch
|
||||
APC AP9225 Yes apc_masterswitchplus
|
||||
@ -54,7 +54,15 @@ CyberPower all? Untested cyberpower
|
||||
EatonPower all? Untested eatonpower
|
||||
Teltronix all? Yes teltronix
|
||||
BayTech MRP27 Yes baytech_mrp27
|
||||
============== ========== ========== =====================
|
||||
Raritan PX3-5547V-V2 Yes raritan_pdu2
|
||||
Raritan PX3-5726V Yes raritan_pdu2
|
||||
Raritan PX3-5776U-N2 Yes raritan_pdu2
|
||||
Raritan PX3-5969U-V2 Yes raritan_pdu2
|
||||
Raritan PX3-5961I2U-V2 Yes raritan_pdu2
|
||||
Vertiv NU30212 Yes vertivgeist_pdu
|
||||
ServerTech CW-16VE-P32M Yes servertech_sentry3
|
||||
ServerTech C2WG24SN Yes servertech_sentry4
|
||||
============== ============== ========== =====================
|
||||
|
||||
|
||||
Software Requirements
|
||||
|
@ -799,6 +799,341 @@ class SNMPDriverBaytechMRP27(SNMPDriverSimple):
|
||||
value_power_on = 1
|
||||
|
||||
|
||||
class SNMPDriverServerTechSentry3(SNMPDriverBase):
|
||||
"""SNMP driver class for Server Technology Sentry 3 PDUs.
|
||||
|
||||
ftp://ftp.servertech.com/Pub/SNMP/sentry3/Sentry3.mib
|
||||
|
||||
SNMP objects for Server Technology Power PDU.
|
||||
1.3.6.1.4.1.1718.3.2.3.1.5.1.1.<outlet ID> outletStatus
|
||||
Read 0=off, 1=on, 2=off wait, 3=on wait, [...more options follow]
|
||||
1.3.6.1.4.1.1718.3.2.3.1.11.1.1.<outlet ID> outletControlAction
|
||||
Write 0=no action, 1=on, 2=off, 3=reboot
|
||||
"""
|
||||
|
||||
oid_device = (1718, 3, 2, 3, 1)
|
||||
oid_tower_infeed_idx = (1, 1, )
|
||||
oid_power_status = (5,)
|
||||
oid_power_action = (11,)
|
||||
|
||||
status_off = 0
|
||||
status_on = 1
|
||||
status_off_wait = 2
|
||||
status_on_wait = 3
|
||||
|
||||
value_power_on = 1
|
||||
value_power_off = 2
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SNMPDriverServerTechSentry3, self).__init__(*args, **kwargs)
|
||||
# Due to its use of different OIDs for different actions, we only form
|
||||
# an OID that holds the common substring of the OIDs for power
|
||||
# operations.
|
||||
self.oid_base = self.oid_enterprise + self.oid_device
|
||||
|
||||
def _snmp_oid(self, oid):
|
||||
"""Return the OID for one of the outlet control objects.
|
||||
|
||||
:param oid: The action-dependent portion of the OID, as a tuple of
|
||||
integers.
|
||||
:returns: The full OID as a tuple of integers.
|
||||
"""
|
||||
|
||||
outlet = self.snmp_info['outlet']
|
||||
full_oid = self.oid_base + oid + self.oid_tower_infeed_idx + (outlet,)
|
||||
return full_oid
|
||||
|
||||
def _snmp_power_state(self):
|
||||
oid = self._snmp_oid(self.oid_power_status)
|
||||
state = self.client.get(oid)
|
||||
|
||||
# Translate the state to an Ironic power state.
|
||||
if state in (self.status_on, self.status_off_wait):
|
||||
power_state = states.POWER_ON
|
||||
elif state in (self.status_off, self.status_on_wait):
|
||||
power_state = states.POWER_OFF
|
||||
else:
|
||||
LOG.warning("SeverTech Sentry3 PDU %(addr)s oid %(oid) outlet "
|
||||
"%(outlet)s: unrecognised power state %(state)s.",
|
||||
{'addr': self.snmp_info['address'],
|
||||
'oid': oid,
|
||||
'outlet': self.snmp_info['outlet'],
|
||||
'state': state})
|
||||
power_state = states.ERROR
|
||||
|
||||
return power_state
|
||||
|
||||
def _snmp_power_on(self):
|
||||
oid = self._snmp_oid(self.oid_power_action)
|
||||
value = snmp.Integer(self.value_power_on)
|
||||
self.client.set(oid, value)
|
||||
|
||||
def _snmp_power_off(self):
|
||||
oid = self._snmp_oid(self.oid_power_action)
|
||||
value = snmp.Integer(self.value_power_off)
|
||||
self.client.set(oid, value)
|
||||
|
||||
|
||||
class SNMPDriverServerTechSentry4(SNMPDriverBase):
|
||||
"""SNMP driver class for Server Technology Sentry 4 PDUs.
|
||||
|
||||
https://www.servertech.com/support/sentry-mib-oid-tree-downloads
|
||||
|
||||
SNMP objects for Server Technology Power PDU.
|
||||
1.3.6.1.4.1.1718.4.1.8.5.1.1<outlet ID> outletStatus
|
||||
notSet (0) fixedOn (1) idleOff (2) idleOn (3) [...more options follow]
|
||||
pendOn (8) pendOff (9) off (10) on (11) [...more options follow]
|
||||
eventOff (16) eventOn (17) eventReboot (18) eventShutdown (19)
|
||||
1.3.6.1.4.1.1718.4.1.8.5.1.2.<outlet ID> outletControlAction
|
||||
Write 0=no action, 1=on, 2=off, 3=reboot
|
||||
"""
|
||||
|
||||
oid_device = (1718, 4, 1, 8, 5, 1)
|
||||
oid_tower_infeed_idx = (1, 1, )
|
||||
oid_power_status = (1,)
|
||||
oid_power_action = (2,)
|
||||
|
||||
notSet = 0
|
||||
fixedOn = 1
|
||||
idleOff = 2
|
||||
idleOn = 3
|
||||
wakeOff = 4
|
||||
wakeOn = 5
|
||||
ocpOff = 6
|
||||
ocpOn = 7
|
||||
status_pendOn = 8
|
||||
status_pendOff = 9
|
||||
status_off = 10
|
||||
status_on = 11
|
||||
reboot = 12
|
||||
shutdown = 13
|
||||
lockedOff = 14
|
||||
lockedOn = 15
|
||||
|
||||
value_power_on = 1
|
||||
value_power_off = 2
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SNMPDriverServerTechSentry4, self).__init__(*args, **kwargs)
|
||||
# Due to its use of different OIDs for different actions, we only form
|
||||
# an OID that holds the common substring of the OIDs for power
|
||||
# operations.
|
||||
self.oid_base = self.oid_enterprise + self.oid_device
|
||||
|
||||
def _snmp_oid(self, oid):
|
||||
"""Return the OID for one of the outlet control objects.
|
||||
|
||||
:param oid: The action-dependent portion of the OID, as a tuple of
|
||||
integers.
|
||||
:returns: The full OID as a tuple of integers.
|
||||
"""
|
||||
|
||||
outlet = self.snmp_info['outlet']
|
||||
full_oid = self.oid_base + oid + self.oid_tower_infeed_idx + (outlet,)
|
||||
return full_oid
|
||||
|
||||
def _snmp_power_state(self):
|
||||
oid = self._snmp_oid(self.oid_power_status)
|
||||
state = self.client.get(oid)
|
||||
|
||||
# Translate the state to an Ironic power state.
|
||||
if state in (self.status_on, self.status_pendOn, self.idleOn):
|
||||
power_state = states.POWER_ON
|
||||
elif state in (self.status_off, self.status_pendOff):
|
||||
power_state = states.POWER_OFF
|
||||
else:
|
||||
LOG.warning("ServerTech Sentry4 PDU %(addr)s oid %(oid)s outlet "
|
||||
"%(outlet)s: unrecognised power state %(state)s.",
|
||||
{'addr': self.snmp_info['address'],
|
||||
'oid': oid,
|
||||
'outlet': self.snmp_info['outlet'],
|
||||
'state': state})
|
||||
power_state = states.ERROR
|
||||
|
||||
return power_state
|
||||
|
||||
def _snmp_power_on(self):
|
||||
oid = self._snmp_oid(self.oid_power_action)
|
||||
value = snmp.Integer(self.value_power_on)
|
||||
self.client.set(oid, value)
|
||||
|
||||
def _snmp_power_off(self):
|
||||
oid = self._snmp_oid(self.oid_power_action)
|
||||
value = snmp.Integer(self.value_power_off)
|
||||
self.client.set(oid, value)
|
||||
|
||||
|
||||
class SNMPDriverRaritanPDU2(SNMPDriverBase):
|
||||
"""SNMP driver class for Raritan PDU2 PDUs.
|
||||
|
||||
http://support.raritan.com/px2/version-2.4.1/mibs/pdu2-mib-020400-39592.txt
|
||||
http://cdn.raritan.com/download/PX/v1.5.20/PDU-MIB.txt
|
||||
|
||||
Command:
|
||||
snmpset -v2c -c private -m+PDU2-MIB <pdu IP address> \
|
||||
PDU2-MIB::switchingOperation.1.4 = cycle
|
||||
snmpset -v2c -c private <pdu IP address> \
|
||||
.1.3.6.1.4.1.13742.6.4.1.2.1.2.1.4 i 2
|
||||
Output:
|
||||
PDU2-MIB::switchingOperation.1.4 = INTEGER: cycle(2)
|
||||
"""
|
||||
|
||||
oid_device = (13742, 6, 4, 1, 2, 1)
|
||||
oid_power_action = (2, )
|
||||
oid_power_status = (3, )
|
||||
oid_tower_infeed_idx = (1, )
|
||||
|
||||
unavailable = -1
|
||||
status_open = 0
|
||||
status_closed = 1
|
||||
belowLowerCritical = 2
|
||||
belowLowerWarning = 3
|
||||
status_normal = 4
|
||||
aboveUpperWarning = 5
|
||||
aboveUpperCritical = 6
|
||||
status_on = 7
|
||||
status_off = 8
|
||||
detected = 9
|
||||
notDetected = 10
|
||||
alarmed = 11
|
||||
ok = 12
|
||||
marginal = 13
|
||||
fail = 14
|
||||
yes = 15
|
||||
no = 16
|
||||
standby = 17
|
||||
one = 18
|
||||
two = 19
|
||||
inSync = 20
|
||||
outOfSync = 21
|
||||
|
||||
value_power_on = 1
|
||||
value_power_off = 0
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SNMPDriverRaritanPDU2, self).__init__(*args, **kwargs)
|
||||
# Due to its use of different OIDs for different actions, we only form
|
||||
# an OID that holds the common substring of the OIDs for power
|
||||
# operations.
|
||||
self.oid_base = self.oid_enterprise + self.oid_device
|
||||
|
||||
def _snmp_oid(self, oid):
|
||||
"""Return the OID for one of the outlet control objects.
|
||||
|
||||
:param oid: The action-dependent portion of the OID, as a tuple of
|
||||
integers.
|
||||
:returns: The full OID as a tuple of integers.
|
||||
"""
|
||||
|
||||
outlet = self.snmp_info['outlet']
|
||||
full_oid = self.oid_base + oid + self.oid_tower_infeed_idx + (outlet,)
|
||||
return full_oid
|
||||
|
||||
def _snmp_power_state(self):
|
||||
oid = self._snmp_oid(self.oid_power_status)
|
||||
state = self.client.get(oid)
|
||||
|
||||
# Translate the state to an Ironic power state.
|
||||
if state == self.status_on:
|
||||
power_state = states.POWER_ON
|
||||
elif state == self.status_off:
|
||||
power_state = states.POWER_OFF
|
||||
else:
|
||||
LOG.warning("Raritan PDU2 PDU %(addr)s oid %(oid)s outlet "
|
||||
"%(outlet)s: unrecognised power state %(state)s.",
|
||||
{'addr': self.snmp_info['address'],
|
||||
'oid': oid,
|
||||
'outlet': self.snmp_info['outlet'],
|
||||
'state': state})
|
||||
power_state = states.ERROR
|
||||
|
||||
return power_state
|
||||
|
||||
def _snmp_power_on(self):
|
||||
oid = self._snmp_oid(self.oid_power_action)
|
||||
value = snmp.Integer(self.value_power_on)
|
||||
self.client.set(oid, value)
|
||||
|
||||
def _snmp_power_off(self):
|
||||
oid = self._snmp_oid(self.oid_power_action)
|
||||
value = snmp.Integer(self.value_power_off)
|
||||
self.client.set(oid, value)
|
||||
|
||||
|
||||
class SNMPDriverVertivGeistPDU(SNMPDriverBase):
|
||||
"""SNMP driver class for VertivGeist NU30017L/NU30019L PDU.
|
||||
|
||||
https://mibs.observium.org/mib/GEIST-V5-MIB/
|
||||
|
||||
"""
|
||||
|
||||
oid_device = (21239, 5, 2, 3, 5, 1)
|
||||
oid_power_action = (6, )
|
||||
oid_power_status = (4, )
|
||||
oid_tower_infeed_idx = (1, )
|
||||
|
||||
on = 1
|
||||
off = 2
|
||||
on2off = 3
|
||||
off2on = 4
|
||||
rebootOn = 5
|
||||
rebootOff = 5
|
||||
unavailable = 7
|
||||
|
||||
value_power_on = 2
|
||||
value_power_off = 4
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SNMPDriverVertivGeistPDU, self).__init__(*args, **kwargs)
|
||||
# Due to its use of different OIDs for different actions, we only form
|
||||
# an OID that holds the common substring of the OIDs for power
|
||||
# operations.
|
||||
self.oid_base = self.oid_enterprise + self.oid_device
|
||||
|
||||
def _snmp_oid(self, oid):
|
||||
"""Return the OID for one of the outlet control objects.
|
||||
|
||||
:param oid: The action-dependent portion of the OID, as a tuple of
|
||||
integers.
|
||||
|
||||
:returns: The full OID as a tuple of integers.
|
||||
"""
|
||||
|
||||
outlet = self.snmp_info['outlet']
|
||||
full_oid = self.oid_base + oid + (outlet,)
|
||||
return full_oid
|
||||
|
||||
def _snmp_power_state(self):
|
||||
oid = self._snmp_oid(self.oid_power_status)
|
||||
state = self.client.get(oid)
|
||||
|
||||
# Translate the state to an Ironic power state.
|
||||
if state in (self.on, self.on2off):
|
||||
power_state = states.POWER_ON
|
||||
elif state in (self.off, self.off2on):
|
||||
power_state = states.POWER_OFF
|
||||
else:
|
||||
LOG.warning("Vertiv Geist PDU %(addr)s oid %(oid)s outlet "
|
||||
"%(outlet)s: unrecognised power state %(state)s.",
|
||||
{'addr': self.snmp_info['address'],
|
||||
'oid': oid,
|
||||
'outlet': self.snmp_info['outlet'],
|
||||
'state': state})
|
||||
power_state = states.ERROR
|
||||
|
||||
return power_state
|
||||
|
||||
def _snmp_power_on(self):
|
||||
oid = self._snmp_oid(self.oid_power_action)
|
||||
value = snmp.Integer(self.value_power_on)
|
||||
self.client.set(oid, value)
|
||||
|
||||
def _snmp_power_off(self):
|
||||
oid = self._snmp_oid(self.oid_power_action)
|
||||
value = snmp.Integer(self.value_power_off)
|
||||
self.client.set(oid, value)
|
||||
|
||||
|
||||
class SNMPDriverAuto(SNMPDriverBase):
|
||||
|
||||
SYS_OBJ_OID = (1, 3, 6, 1, 2, 1, 1, 2)
|
||||
@ -878,6 +1213,10 @@ DRIVER_CLASSES = {
|
||||
'eatonpower': SNMPDriverEatonPower,
|
||||
'teltronix': SNMPDriverTeltronix,
|
||||
'baytech_mrp27': SNMPDriverBaytechMRP27,
|
||||
'servertech_sentry3': SNMPDriverServerTechSentry3,
|
||||
'servertech_sentry4': SNMPDriverServerTechSentry4,
|
||||
'raritan_pdu2': SNMPDriverRaritanPDU2,
|
||||
'vertivgeist_pdu': SNMPDriverVertivGeistPDU,
|
||||
'auto': SNMPDriverAuto,
|
||||
}
|
||||
|
||||
|
@ -327,6 +327,34 @@ class SNMPValidateParametersTestCase(db_base.DbTestCase):
|
||||
info = snmp._parse_driver_info(node)
|
||||
self.assertEqual('teltronix', info['driver'])
|
||||
|
||||
def test__parse_driver_info_servertech_sentry3(self):
|
||||
# Make sure the servertech_sentry3 driver type is parsed.
|
||||
info = db_utils.get_test_snmp_info(snmp_driver='servertech_sentry3')
|
||||
node = self._get_test_node(info)
|
||||
info = snmp._parse_driver_info(node)
|
||||
self.assertEqual('servertech_sentry3', info['driver'])
|
||||
|
||||
def test__parse_driver_info_servertech_sentry4(self):
|
||||
# Make sure the servertech_sentry4 driver type is parsed.
|
||||
info = db_utils.get_test_snmp_info(snmp_driver='servertech_sentry4')
|
||||
node = self._get_test_node(info)
|
||||
info = snmp._parse_driver_info(node)
|
||||
self.assertEqual('servertech_sentry4', info['driver'])
|
||||
|
||||
def test__parse_driver_info_raritan_pdu2(self):
|
||||
# Make sure the raritan_pdu2 driver type is parsed.
|
||||
info = db_utils.get_test_snmp_info(snmp_driver='raritan_pdu2')
|
||||
node = self._get_test_node(info)
|
||||
info = snmp._parse_driver_info(node)
|
||||
self.assertEqual('raritan_pdu2', info['driver'])
|
||||
|
||||
def test__parse_driver_info_vertivgeist_pdu(self):
|
||||
# Make sure the vertivgeist_pdu driver type is parsed.
|
||||
info = db_utils.get_test_snmp_info(snmp_driver='vertivgeist_pdu')
|
||||
node = self._get_test_node(info)
|
||||
info = snmp._parse_driver_info(node)
|
||||
self.assertEqual('vertivgeist_pdu', info['driver'])
|
||||
|
||||
def test__parse_driver_info_snmp_v1(self):
|
||||
# Make sure SNMPv1 is parsed with a community string.
|
||||
info = db_utils.get_test_snmp_info(snmp_version='1',
|
||||
@ -1260,6 +1288,58 @@ class SNMPDeviceDriverTestCase(db_base.DbTestCase):
|
||||
def test_apc_rackpdu_power_reset(self, mock_get_client):
|
||||
self._test_simple_device_power_reset('apc_rackpdu', mock_get_client)
|
||||
|
||||
def test_raritan_pdu2_snmp_objects(self, mock_get_client):
|
||||
# Ensure the correct SNMP object OIDs and values are used by the
|
||||
# Raritan PDU2 driver
|
||||
self._update_driver_info(snmp_driver="raritan_pdu2",
|
||||
snmp_outlet="6")
|
||||
driver = snmp._get_driver(self.node)
|
||||
oid = (1, 3, 6, 1, 4, 1, 13742, 6, 4, 1, 2, 1, 2, 1, 6)
|
||||
action = (2,)
|
||||
|
||||
self.assertEqual(oid, driver._snmp_oid(action))
|
||||
self.assertEqual(1, driver.value_power_on)
|
||||
self.assertEqual(0, driver.value_power_off)
|
||||
|
||||
def test_servertech_sentry3_snmp_objects(self, mock_get_client):
|
||||
# Ensure the correct SNMP object OIDs and values are used by the
|
||||
# ServerTech Sentry3 driver
|
||||
self._update_driver_info(snmp_driver="servertech_sentry3",
|
||||
snmp_outlet="6")
|
||||
driver = snmp._get_driver(self.node)
|
||||
oid = (1, 3, 6, 1, 4, 1, 1718, 3, 2, 3, 1, 5, 1, 1, 6)
|
||||
action = (5,)
|
||||
|
||||
self.assertEqual(oid, driver._snmp_oid(action))
|
||||
self.assertEqual(1, driver.value_power_on)
|
||||
self.assertEqual(2, driver.value_power_off)
|
||||
|
||||
def test_servertech_sentry4_snmp_objects(self, mock_get_client):
|
||||
# Ensure the correct SNMP object OIDs and values are used by the
|
||||
# ServerTech Sentry4 driver
|
||||
self._update_driver_info(snmp_driver="servertech_sentry4",
|
||||
snmp_outlet="6")
|
||||
driver = snmp._get_driver(self.node)
|
||||
oid = (1, 3, 6, 1, 4, 1, 1718, 4, 1, 8, 5, 1, 2, 1, 1, 6)
|
||||
action = (2,)
|
||||
|
||||
self.assertEqual(oid, driver._snmp_oid(action))
|
||||
self.assertEqual(1, driver.value_power_on)
|
||||
self.assertEqual(2, driver.value_power_off)
|
||||
|
||||
def test_vertivgeist_pdu_snmp_objects(self, mock_get_client):
|
||||
# Ensure the correct SNMP object OIDs and values are used by the
|
||||
# Vertiv Geist PDU driver
|
||||
self._update_driver_info(snmp_driver="vertivgeist_pdu",
|
||||
snmp_outlet="6")
|
||||
driver = snmp._get_driver(self.node)
|
||||
oid = (1, 3, 6, 1, 4, 1, 21239, 5, 2, 3, 5, 1, 4, 6)
|
||||
action = (4,)
|
||||
|
||||
self.assertEqual(oid, driver._snmp_oid(action))
|
||||
self.assertEqual(2, driver.value_power_on)
|
||||
self.assertEqual(4, driver.value_power_off)
|
||||
|
||||
def test_aten_snmp_objects(self, mock_get_client):
|
||||
# Ensure the correct SNMP object OIDs and values are used by the
|
||||
# Aten driver
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds ``raritan_pdu2``, ``servertech_sentry3``, ``servertech_sentry4``,
|
||||
and ``vertivgest_pdu`` snmp drivers to support additional PDU models.
|
Loading…
Reference in New Issue
Block a user