From 95fb32740991b52933c8ab16ff2bf80338f1f6d8 Mon Sep 17 00:00:00 2001 From: Mathieu Mitchell Date: Fri, 19 Aug 2016 15:19:19 -0400 Subject: [PATCH] Implementation of the Core for VPDU This implementation provides the core implementation layer linking the pdu to the driver --- virtualpdu/core.py | 47 +++++++++++++ virtualpdu/power_states.py | 9 ++- .../integration/test_core_integration.py | 69 +++++++++++++++++++ virtualpdu/tests/unit/test_core.py | 61 ++++++++++++++++ 4 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 virtualpdu/core.py create mode 100644 virtualpdu/tests/integration/test_core_integration.py create mode 100644 virtualpdu/tests/unit/test_core.py diff --git a/virtualpdu/core.py b/virtualpdu/core.py new file mode 100644 index 0000000..baf2881 --- /dev/null +++ b/virtualpdu/core.py @@ -0,0 +1,47 @@ +# Copyright 2016 Internap +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +import logging + +POWER_ON = 'POWER_ON' +POWER_OFF = 'POWER_OFF' +REBOOT = 'REBOOT' + + +class Core(object): + def __init__(self, driver, mapping): + self.driver = driver + self.mapping = mapping + self.logger = logging.getLogger(__name__) + + def pdu_outlet_state_changed(self, name, outlet_number, state): + self.logger.info( + "PDU '{name}', outlet '{outlet_number}' has new state: '{state}'". + format(name=name, outlet_number=outlet_number, state=state)) + try: + server_name = self._get_server_name(name, outlet_number) + + self.logger.info("Found server '{0}'".format(server_name)) + except KeyError: + return + + if state == POWER_ON: + self.driver.power_on(server_name) + elif state == POWER_OFF: + self.driver.power_off(server_name) + elif state == REBOOT: + self.driver.power_off(server_name) + self.driver.power_on(server_name) + + def _get_server_name(self, pdu_name, outlet_number): + return self.mapping[(pdu_name, outlet_number)] diff --git a/virtualpdu/power_states.py b/virtualpdu/power_states.py index a26ee50..98b4ef4 100644 --- a/virtualpdu/power_states.py +++ b/virtualpdu/power_states.py @@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -POWER_ON = 'POWER_ON' -POWER_OFF = 'POWER_OFF' -REBOOT = 'REBOOT' +from virtualpdu import core + +# TODO(mmitchell): Refactor the imports around to remove this file. +POWER_ON = core.POWER_ON +POWER_OFF = core.POWER_OFF +REBOOT = core.REBOOT diff --git a/virtualpdu/tests/integration/test_core_integration.py b/virtualpdu/tests/integration/test_core_integration.py new file mode 100644 index 0000000..4d9d2f6 --- /dev/null +++ b/virtualpdu/tests/integration/test_core_integration.py @@ -0,0 +1,69 @@ +# Copyright 2016 Internap +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +import random + +from pysnmp.entity.rfc3413.oneliner import cmdgen + +from virtualpdu import core +from virtualpdu import drivers +from virtualpdu.drivers import libvirt_driver +from virtualpdu.pdu import apc_rackpdu +from virtualpdu.tests import base +from virtualpdu.tests.integration import pdu +from virtualpdu.tests import snmp_client + + +class TestCoreIntegration(base.TestCase): + def tearDown(self): + self.pdu_test_harness.stop() + super(TestCoreIntegration, self).tearDown() + + def test_pdu_outlet_state_changed_on_power_off(self): + mapping = { + ('my_pdu', 5): 'test' + } + driver = libvirt_driver.KeepaliveLibvirtDriver('test:///default') + + core_ = core.Core(driver=driver, mapping=mapping) + + pdu_ = apc_rackpdu.APCRackPDU('my_pdu', core_) + outlet_oid = apc_rackpdu.rPDU_outlet_control_outlet_command + (5,) + + listen_address = '127.0.0.1' + port = random.randint(20000, 30000) + community = 'public' + + self.pdu_test_harness = pdu.SNMPPDUTestHarness(pdu_, + listen_address, + port, + community) + self.pdu_test_harness.start() + + snmp_client_ = snmp_client.SnmpClient(cmdgen, + listen_address, + port, + community, + timeout=1, + retries=1) + + snmp_client_.set(outlet_oid, + apc_rackpdu.rPDU_power_mappings['immediateOff']) + self.assertEqual(drivers.POWER_OFF, + driver.get_power_state('test')) + + snmp_client_.set(outlet_oid, + apc_rackpdu.rPDU_power_mappings['immediateOn']) + self.assertEqual(drivers.POWER_ON, + driver.get_power_state('test')) diff --git a/virtualpdu/tests/unit/test_core.py b/virtualpdu/tests/unit/test_core.py new file mode 100644 index 0000000..7adf2d6 --- /dev/null +++ b/virtualpdu/tests/unit/test_core.py @@ -0,0 +1,61 @@ +# Copyright 2016 Internap +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 mock import mock +from virtualpdu import core +from virtualpdu import drivers +from virtualpdu import power_states +from virtualpdu.tests import base + + +class TestCore(base.TestCase): + def setUp(self): + super(TestCore, self).setUp() + self.driver_mock = mock.create_autospec(drivers.Driver) + + mapping = { + ('my_pdu', 1): 'server_one' + } + + self.core = core.Core(driver=self.driver_mock, mapping=mapping) + + def test_pdu_outlet_state_changed_on_power_off(self): + self.core.pdu_outlet_state_changed(name='my_pdu', + outlet_number=1, + state=power_states.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, + state=power_states.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, + state=power_states.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, + state=power_states.REBOOT) + + self.driver_mock.assert_has_calls([mock.call.power_off('server_one'), + mock.call.power_on('server_one')])