diff --git a/virtualpdu/core.py b/virtualpdu/core.py index 2c9d8ce..645853a 100644 --- a/virtualpdu/core.py +++ b/virtualpdu/core.py @@ -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)] diff --git a/virtualpdu/main.py b/virtualpdu/main.py index 2dbe365..b38a4ac 100644 --- a/virtualpdu/main.py +++ b/virtualpdu/main.py @@ -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 diff --git a/virtualpdu/pdu/__init__.py b/virtualpdu/pdu/__init__.py index 8e8e627..8a34ff3 100644 --- a/virtualpdu/pdu/__init__.py +++ b/virtualpdu/pdu/__init__.py @@ -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)) diff --git a/virtualpdu/pdu/apc_rackpdu.py b/virtualpdu/pdu/apc_rackpdu.py index 67db816..51681a1 100644 --- a/virtualpdu/pdu/apc_rackpdu.py +++ b/virtualpdu/pdu/apc_rackpdu.py @@ -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, ) diff --git a/virtualpdu/pdu/baytech_mrp27.py b/virtualpdu/pdu/baytech_mrp27.py index f71762f..2ac86ab 100644 --- a/virtualpdu/pdu/baytech_mrp27.py +++ b/virtualpdu/pdu/baytech_mrp27.py @@ -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) diff --git a/virtualpdu/tests/integration/pdu/test_apc_rackpdu.py b/virtualpdu/tests/integration/pdu/test_apc_rackpdu.py index 9b92a52..89b40c4 100644 --- a/virtualpdu/tests/integration/pdu/test_apc_rackpdu.py +++ b/virtualpdu/tests/integration/pdu/test_apc_rackpdu.py @@ -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)) diff --git a/virtualpdu/tests/integration/pdu/test_baytech_mrp27.py b/virtualpdu/tests/integration/pdu/test_baytech_mrp27.py index 5900e24..b2c47a3 100644 --- a/virtualpdu/tests/integration/pdu/test_baytech_mrp27.py +++ b/virtualpdu/tests/integration/pdu/test_baytech_mrp27.py @@ -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)) diff --git a/virtualpdu/tests/integration/pdu/test_pdu.py b/virtualpdu/tests/integration/pdu/test_pdu.py index 3982bb1..3ced144 100644 --- a/virtualpdu/tests/integration/pdu/test_pdu.py +++ b/virtualpdu/tests/integration/pdu/test_pdu.py @@ -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))) diff --git a/virtualpdu/tests/integration/test_core_integration.py b/virtualpdu/tests/integration/test_core_integration.py index 9297889..1569579 100644 --- a/virtualpdu/tests/integration/test_core_integration.py +++ b/virtualpdu/tests/integration/test_core_integration.py @@ -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, diff --git a/virtualpdu/tests/integration/test_entrypoint.py b/virtualpdu/tests/integration/test_entrypoint.py index 2189ece..1fc49c1 100644 --- a/virtualpdu/tests/integration/test_entrypoint.py +++ b/virtualpdu/tests/integration/test_entrypoint.py @@ -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: diff --git a/virtualpdu/tests/unit/pdu/base_pdu_test_cases.py b/virtualpdu/tests/unit/pdu/base_pdu_test_cases.py index 5c34ecc..0fd4914 100644 --- a/virtualpdu/tests/unit/pdu/base_pdu_test_cases.py +++ b/virtualpdu/tests/unit/pdu/base_pdu_test_cases.py @@ -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) diff --git a/virtualpdu/tests/unit/test_core.py b/virtualpdu/tests/unit/test_core.py index ec9d6c1..55f68d7 100644 --- a/virtualpdu/tests/unit/test_core.py +++ b/virtualpdu/tests/unit/test_core.py @@ -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))