Merge pull request #25 from JoProvost/power_state_in_core

Move the outlet state responsibility to the core
This commit is contained in:
Mathieu Mitchell 2016-10-20 22:49:27 -04:00 committed by GitHub
commit a7f2d92cd0
12 changed files with 152 additions and 62 deletions

View File

@ -19,32 +19,51 @@ REBOOT = 'REBOOT'
class Core(object):
def __init__(self, driver, mapping):
def __init__(self, driver, mapping, store, default_state):
self.driver = driver
self.mapping = mapping
self.store = store
self.default_state = default_state
self.logger = logging.getLogger(__name__)
def pdu_outlet_state_changed(self, name, outlet_number, state):
def pdu_outlet_state_changed(self, pdu, outlet, state):
self.store[(pdu, outlet)] = state
self.logger.info("PDU '{}', outlet '{}' has new state: '{}'".format(
name, outlet_number, state)
pdu, outlet, state)
)
try:
server_name = self._get_server_name(name, outlet_number)
device = self._get_device(pdu, outlet)
self.logger.debug(
"Found server '{}' on PDU '{}' outlet '{}'".format(
server_name, name, outlet_number)
device, pdu, outlet)
)
except KeyError:
return
if state == POWER_ON:
self.driver.power_on(server_name)
self.driver.power_on(device)
elif state == POWER_OFF:
self.driver.power_off(server_name)
self.driver.power_off(device)
elif state == REBOOT:
self.driver.power_off(server_name)
self.driver.power_on(server_name)
self.driver.power_off(device)
self.driver.power_on(device)
def _get_server_name(self, pdu_name, outlet_number):
return self.mapping[(pdu_name, outlet_number)]
def get_pdu_outlet_state(self, pdu, outlet):
try:
return self.store[(pdu, outlet)]
except KeyError:
pass
try:
device = self._get_device(pdu, outlet)
power_state = self.driver.get_power_state(device)
except KeyError:
power_state = self.default_state
self.store[(pdu, outlet)] = power_state
return power_state
def _get_device(self, pdu, outlet):
return self.mapping[(pdu, outlet)]

View File

@ -40,7 +40,9 @@ def main():
config.read(config_file)
driver = get_driver_from_config(config)
mapping = get_mapping_for_config(config)
core = virtualpdu.core.Core(driver=driver, mapping=mapping)
outlet_default_state = get_default_state_from_config(config)
core = virtualpdu.core.Core(driver=driver, mapping=mapping, store={},
default_state=outlet_default_state)
pdu_threads = []
for pdu in [s for s in config.sections() if s != 'global']:
@ -48,14 +50,7 @@ def main():
port = int(config.get(pdu, 'listen_port'))
community = config.get(pdu, 'community')
try:
default_state = config.get(pdu, 'outlet_default_state')
except configparser.NoOptionError:
default_state = 'ON'
outlet_default_state = parse_default_state_config(default_state)
apc_pdu = apc_rackpdu.APCRackPDU(pdu, core, outlet_default_state)
apc_pdu = apc_rackpdu.APCRackPDU(pdu, core)
pdu_threads.append(pysnmp_handler.SNMPPDUHarness(
apc_pdu,
@ -115,5 +110,13 @@ def get_mapping_for_config(conf):
return mapping
def get_default_state_from_config(conf):
try:
default_state = conf.get('global', 'outlet_default_state')
except (configparser.NoSectionError, configparser.NoOptionError):
default_state = 'ON'
return parse_default_state_config(default_state)
class UnableToParseConfig(Exception):
pass

View File

@ -45,20 +45,18 @@ class PDUOutletStates(BasePDUOutletStates):
class PDUOutlet(object):
states = PDUOutletStates()
def __init__(self, outlet_number, pdu, default_state):
def __init__(self, outlet_number, pdu):
self.outlet_number = outlet_number
self.pdu = pdu
self._state = default_state
self.oid = None
@property
def state(self):
return self._state
return self.pdu.get_outlet_state(self.outlet_number)
@state.setter
def state(self, state):
self._state = state
self.pdu.outlet_state_changed(self.outlet_number, self._state)
self.pdu.set_outlet_state(self.outlet_number, state)
class PDU(object):
@ -66,24 +64,26 @@ class PDU(object):
outlet_index_start = 1
outlet_class = PDUOutlet
def __init__(self, name, core, outlet_default_state=core.POWER_ON):
def __init__(self, name, core):
self.name = name
self.core = core
outlet_native_default_state = \
self.outlet_class.states.from_core(outlet_default_state)
self.oids = [
self.outlet_class(outlet_number=o + self.outlet_index_start,
pdu=self,
default_state=outlet_native_default_state
) for o in range(self.outlet_count)
]
self.oid_mapping = {oid.oid: oid for oid in self.oids}
def outlet_state_changed(self, outlet_number, value):
def set_outlet_state(self, outlet_number, value):
self.core.pdu_outlet_state_changed(
name=self.name,
outlet_number=outlet_number,
pdu=self.name,
outlet=outlet_number,
state=self.outlet_class.states.to_core(value))
def get_outlet_state(self, outlet_number):
return self.outlet_class.states.from_core(
self.core.get_pdu_outlet_state(
pdu=self.name,
outlet=outlet_number))

View File

@ -42,9 +42,9 @@ class APCRackPDUOutletStates(BasePDUOutletStates):
class APCRackPDUOutlet(PDUOutlet):
states = APCRackPDUOutletStates()
def __init__(self, outlet_number, pdu, default_state):
def __init__(self, outlet_number, pdu):
super(APCRackPDUOutlet, self).__init__(
outlet_number, pdu, default_state)
outlet_number, pdu)
self.oid = rPDU_outlet_control_outlet_command + (self.outlet_number, )

View File

@ -40,9 +40,9 @@ class BaytechMRP27PDUOutletStates(BasePDUOutletStates):
class BaytechMRP27PDUOutlet(PDUOutlet):
states = BaytechMRP27PDUOutletStates()
def __init__(self, outlet_number, pdu, default_state):
def __init__(self, outlet_number, pdu):
super(BaytechMRP27PDUOutlet, self).__init__(
outlet_number, pdu, default_state)
outlet_number, pdu)
self.oid = sBTA_modules_RPC_outlet_state + (1, self.outlet_number)

View File

@ -11,7 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from virtualpdu import core
from virtualpdu.pdu.apc_rackpdu import APCRackPDU
from virtualpdu.tests.integration.pdu import PDUTestCase
@ -21,6 +21,8 @@ class TestAPCRackPDU(PDUTestCase):
pdu_class = APCRackPDU
def test_all_ports_are_on_by_default(self):
self.core_mock.get_pdu_outlet_state.return_value = core.POWER_ON
enterprises = (1, 3, 6, 1, 4, 1)
rPDUControl = (318, 1, 1, 12, 3, 3, 1, 1, 4)
@ -40,10 +42,14 @@ class TestAPCRackPDU(PDUTestCase):
rPDUControl = (318, 1, 1, 12, 3, 3, 1, 1, 4)
outlet_1 = enterprises + rPDUControl + (1,)
self.core_mock.get_pdu_outlet_state.return_value = core.POWER_ON
self.assertEqual(self.pdu.outlet_class.states.IMMEDIATE_ON,
self.snmp_get(outlet_1))
self.snmp_set(outlet_1, self.pdu.outlet_class.states.IMMEDIATE_OFF)
self.core_mock.pdu_outlet_state_changed.assert_called_with(
pdu=self.pdu.name, outlet=1, state=core.POWER_OFF)
self.core_mock.get_pdu_outlet_state.return_value = core.POWER_OFF
self.assertEqual(self.pdu.outlet_class.states.IMMEDIATE_OFF,
self.snmp_get(outlet_1))

View File

@ -11,7 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from virtualpdu import core
from virtualpdu.pdu.baytech_mrp27 import BaytechMRP27PDU
from virtualpdu.tests.integration.pdu import PDUTestCase
@ -21,6 +21,8 @@ class TestBaytechMRP27PDU(PDUTestCase):
pdu_class = BaytechMRP27PDU
def test_all_ports_are_on_by_default(self):
self.core_mock.get_pdu_outlet_state.return_value = core.POWER_ON
outlet_state_oid = (1, 3, 6, 1, 4, 1) + (4779, 1, 3, 5, 3, 1, 3)
self.assertEqual(1, self.snmp_get(outlet_state_oid + (1, 1)))
@ -40,10 +42,14 @@ class TestBaytechMRP27PDU(PDUTestCase):
outlet_state_oid = (1, 3, 6, 1, 4, 1) + (4779, 1, 3, 5, 3, 1, 3)
outlet_1 = outlet_state_oid + (1, 1)
self.core_mock.get_pdu_outlet_state.return_value = core.POWER_ON
self.assertEqual(self.pdu.outlet_class.states.ON,
self.snmp_get(outlet_1))
self.snmp_set(outlet_1, self.pdu.outlet_class.states.OFF)
self.core_mock.pdu_outlet_state_changed.assert_called_with(
pdu=self.pdu.name, outlet=1, state=core.POWER_OFF)
self.core_mock.get_pdu_outlet_state.return_value = core.POWER_OFF
self.assertEqual(self.pdu.outlet_class.states.OFF,
self.snmp_get(outlet_1))

View File

@ -15,6 +15,7 @@
from pyasn1.type import univ
from pysnmp.proto.rfc1905 import NoSuchInstance
from virtualpdu import core
from virtualpdu import pdu
from virtualpdu.tests.integration.pdu import PDUTestCase
from virtualpdu.tests.snmp_error_indications import RequestTimedOut
@ -34,11 +35,10 @@ class TestPDU(PDUTestCase):
self.snmp_set(enterprises + (42,), univ.Integer(7)))
def test_get_valid_oid_wrong_community(self):
default_state = self.pdu.outlet_class.states.ON
self.core_mock.get_pdu_outlet_state.return_value = core.POWER_ON
self.pdu.oid_mapping[enterprises + (88, 1)] = \
pdu.PDUOutlet(outlet_number=1,
pdu=self.pdu,
default_state=default_state)
pdu=self.pdu)
self.assertEqual(self.pdu.outlet_class.states.ON,
self.snmp_get(enterprises + (88, 1)))

View File

@ -32,7 +32,9 @@ class TestCoreIntegration(base.TestCase):
self.core = core.Core(driver=self.driver,
mapping={
('my_pdu', 5): 'test'
})
},
store={},
default_state=core.POWER_OFF)
self.outlet_oid = apc_rackpdu.rPDU_outlet_control_outlet_command + (5,)
@ -73,14 +75,22 @@ class TestCoreIntegration(base.TestCase):
self.driver.get_power_state('test'))
def test_initial_outlet_power_state_on(self):
pdu = apc_rackpdu.APCRackPDU('my_pdu', self.core, core.POWER_ON)
my_core = core.Core(driver=self.driver,
mapping={},
store={},
default_state=core.POWER_ON)
pdu = apc_rackpdu.APCRackPDU('my_pdu', my_core)
snmp_client_ = self.get_harness_client(pdu)
self.assertEqual(pdu.outlet_class.states.IMMEDIATE_ON,
snmp_client_.get_one(self.outlet_oid))
def test_initial_outlet_power_state_off(self):
pdu = apc_rackpdu.APCRackPDU('my_pdu', self.core, core.POWER_OFF)
my_core = core.Core(driver=self.driver,
mapping={},
store={},
default_state=core.POWER_OFF)
pdu = apc_rackpdu.APCRackPDU('my_pdu', my_core)
snmp_client_ = self.get_harness_client(pdu)
self.assertEqual(pdu.outlet_class.states.IMMEDIATE_OFF,

View File

@ -128,8 +128,10 @@ class TestEntryPointIntegration(base.TestCase):
def test_entrypoint_raise_on_invalid_mode(self):
with tempfile.NamedTemporaryFile() as f:
test_config2 = TEST_CONFIG + """outlet_default_state = invalid_mode
"""
test_config2 = """[global]
libvirt_uri=test:///default
outlet_default_state = invalid_mode
"""
f.write(bytearray(test_config2, encoding='utf-8'))
f.flush()
try:

View File

@ -21,8 +21,8 @@ class BasePDUTests(object):
self.pdu.outlet_class.states.from_core(core.POWER_ON)
self.core_mock.pdu_outlet_state_changed.assert_called_with(
name='my_pdu',
outlet_number=1,
pdu='my_pdu',
outlet=1,
state=core.POWER_ON)
def test_reboot_notifies_core(self):
@ -30,8 +30,8 @@ class BasePDUTests(object):
self.pdu.outlet_class.states.from_core(core.REBOOT)
self.core_mock.pdu_outlet_state_changed.assert_called_with(
name='my_pdu',
outlet_number=1,
pdu='my_pdu',
outlet=1,
state=core.REBOOT)
def test_power_off_notifies_core(self):
@ -39,6 +39,30 @@ class BasePDUTests(object):
self.pdu.outlet_class.states.from_core(core.POWER_OFF)
self.core_mock.pdu_outlet_state_changed.assert_called_with(
name='my_pdu',
outlet_number=1,
pdu='my_pdu',
outlet=1,
state=core.POWER_OFF)
def test_read_power_on(self):
self.core_mock.get_pdu_outlet_state.return_value = core.POWER_ON
self.assertEqual(
self.pdu.outlet_class.states.from_core(core.POWER_ON),
self.pdu.oids[0].state
)
self.core_mock.get_pdu_outlet_state.assert_called_with(
pdu='my_pdu',
outlet=1)
def test_read_power_off(self):
self.core_mock.get_pdu_outlet_state.return_value = core.POWER_OFF
self.assertEqual(
self.pdu.outlet_class.states.from_core(core.POWER_OFF),
self.pdu.oids[0].state
)
self.core_mock.get_pdu_outlet_state.assert_called_with(
pdu='my_pdu',
outlet=1)

View File

@ -27,34 +27,54 @@ class TestCore(base.TestCase):
('my_pdu', 1): 'server_one'
}
self.core = core.Core(driver=self.driver_mock, mapping=mapping)
self.store = {}
self.core = core.Core(driver=self.driver_mock, mapping=mapping,
store=self.store, default_state=core.POWER_ON)
def test_pdu_outlet_state_changed_on_power_off(self):
self.core.pdu_outlet_state_changed(name='my_pdu',
outlet_number=1,
self.core.pdu_outlet_state_changed(pdu='my_pdu',
outlet=1,
state=core.POWER_OFF)
self.driver_mock.power_off.assert_called_with('server_one')
def test_pdu_outlet_state_changed_machine_not_in_mapping_noop(self):
self.core.pdu_outlet_state_changed(name='my_pdu',
outlet_number=2,
self.core.pdu_outlet_state_changed(pdu='my_pdu',
outlet=2,
state=core.POWER_OFF)
self.assertFalse(self.driver_mock.power_off.called)
self.assertFalse(self.driver_mock.power_on.called)
def test_pdu_outlet_state_changed_on_power_on(self):
self.core.pdu_outlet_state_changed(name='my_pdu',
outlet_number=1,
self.core.pdu_outlet_state_changed(pdu='my_pdu',
outlet=1,
state=core.POWER_ON)
self.driver_mock.power_on.assert_called_with('server_one')
def test_pdu_outlet_state_changed_on_reboot(self):
self.core.pdu_outlet_state_changed(name='my_pdu',
outlet_number=1,
self.core.pdu_outlet_state_changed(pdu='my_pdu',
outlet=1,
state=core.REBOOT)
self.driver_mock.assert_has_calls([mock.call.power_off('server_one'),
mock.call.power_on('server_one')])
def test_pdu_outlet_state_on_cached_state(self):
self.store[('my_pdu', 1)] = core.POWER_OFF
self.assertEqual(
core.POWER_OFF,
self.core.get_pdu_outlet_state(pdu='my_pdu', outlet=1))
def test_pdu_outlet_state_on_connected_device(self):
self.driver_mock.get_power_state.return_value = core.POWER_OFF
self.assertEqual(
core.POWER_OFF,
self.core.get_pdu_outlet_state(pdu='my_pdu', outlet=1))
self.driver_mock.get_power_state.assert_called_with('server_one')
def test_pdu_outlet_state_on_disconnected_outlet(self):
self.assertEqual(
core.POWER_ON,
self.core.get_pdu_outlet_state(pdu='my_pdu', outlet=2))