diff --git a/README.rst b/README.rst index 054dd19b9..20807a1bc 100644 --- a/README.rst +++ b/README.rst @@ -189,6 +189,10 @@ HTTP API consist of 2 endpoints: * 403 - node is not on discovery * 404 - node cannot be found or multiple nodes found + Successful response body is a JSON dictionary with keys: + + * ``node`` node as returned by Ironic + .. _bug #1391866: https://bugs.launchpad.net/ironic-discoverd/+bug/1391866 Change Log @@ -198,6 +202,8 @@ v1.0.0 ~~~~~~ * ``/v1/continue`` is now sync and errors are returned. +* Option ``power_off_after_discovery`` controls whether to force power off + after the successful discovery, and is ``False`` by default. * Discovery now times out by default. * Add support for plugins that hook into data processing pipeline, see `plugin-architecture blueprint`_ for details. diff --git a/example.conf b/example.conf index 24fd40af4..df38feab8 100644 --- a/example.conf +++ b/example.conf @@ -28,6 +28,8 @@ ;timeout = 3600 ; Amount of time in seconds, after which repeat clean up of timed out nodes. ;firewall_update_period = 60 +; Whether to power off the ramdisk immediately after the successful discovery. +;power_off_after_discovery = false ; IP to listen on. ;listen_address = 0.0.0.0 diff --git a/ironic_discoverd/conf.py b/ironic_discoverd/conf.py index 63fbafe47..962e6aa6e 100644 --- a/ironic_discoverd/conf.py +++ b/ironic_discoverd/conf.py @@ -28,6 +28,7 @@ DEFAULTS = { 'processing_hooks': 'scheduler', 'timeout': '3600', 'clean_up_period': '60', + 'power_off_after_discovery': 'false', } diff --git a/ironic_discoverd/discoverd.py b/ironic_discoverd/discoverd.py index 6b545a252..f13acf37e 100644 --- a/ironic_discoverd/discoverd.py +++ b/ironic_discoverd/discoverd.py @@ -97,9 +97,6 @@ def _process_node(ironic, node, node_info, valid_macs): 'database - skipping', {'mac': mac, 'node': node.uuid}) - patch = [{'op': 'add', 'path': '/extra/newly_discovered', 'value': 'true'}, - {'op': 'remove', 'path': '/extra/on_discovery'}] - node_patches = [] port_patches = {} for hook_ext in hooks: @@ -114,22 +111,28 @@ def _process_node(ironic, node, node_info, valid_macs): port_patches = {mac: patch for (mac, patch) in port_patches.items() if mac in ports and patch} - ironic.node.update(node.uuid, patch + node_patches) + ironic.node.update(node.uuid, node_patches) for mac, patches in port_patches.items(): ironic.port.update(ports[mac].uuid, patches) - LOG.info('Node %s was updated with data from discovery process, forcing ' - 'power off', node.uuid) + LOG.info('Node %s was updated with data from discovery process', node.uuid) firewall.update_filters(ironic) - try: - ironic.node.set_power_state(node.uuid, 'off') - except Exception as exc: - LOG.error('Failed to power off node %s, check it\'s power ' - 'management configuration:\n%s', node.uuid, exc) - raise utils.DiscoveryFailed('Failed to power off node %s' % node.uuid) + if conf.getboolean('discoverd', 'power_off_after_discovery'): + LOG.info('Forcing power off of node %s', node.uuid) + try: + ironic.node.set_power_state(node.uuid, 'off') + except Exception as exc: + LOG.error('Failed to power off node %s, check it\'s power ' + 'management configuration:\n%s', node.uuid, exc) + raise utils.DiscoveryFailed('Failed to power off node %s' % + node.uuid) + + patch = [{'op': 'add', 'path': '/extra/newly_discovered', 'value': 'true'}, + {'op': 'remove', 'path': '/extra/on_discovery'}] + ironic.node.update(node.uuid, patch) def discover(uuids): diff --git a/ironic_discoverd/test.py b/ironic_discoverd/test.py index d577ef84a..a25cf976b 100644 --- a/ironic_discoverd/test.py +++ b/ironic_discoverd/test.py @@ -51,16 +51,19 @@ class BaseTest(unittest.TestCase): @patch.object(utils, 'get_client', autospec=True) class TestProcess(BaseTest): def setUp(self): + super(TestProcess, self).setUp() self.node = Mock(driver_info={'ipmi_address': '1.2.3.4'}, properties={'cpu_arch': 'i386', 'local_gb': 40}, uuid='uuid', extra={'on_discovery': 'true'}) - self.patch = [ - {'op': 'add', 'path': '/extra/newly_discovered', 'value': 'true'}, - {'op': 'remove', 'path': '/extra/on_discovery'}, + self.patch1 = [ {'op': 'add', 'path': '/properties/cpus', 'value': '2'}, {'op': 'add', 'path': '/properties/memory_mb', 'value': '1024'}, ] + self.patch2 = [ + {'op': 'add', 'path': '/extra/newly_discovered', 'value': 'true'}, + {'op': 'remove', 'path': '/extra/on_discovery'}, + ] self.data = { 'ipmi_address': '1.2.3.4', 'cpus': 2, @@ -105,16 +108,17 @@ class TestProcess(BaseTest): self.assertEqual(['11:22:33:44:55:66', '66:55:44:33:22:11'], sorted(pop_mock.call_args[1]['mac'])) - cli.node.update.assert_called_once_with(self.node.uuid, - self.patch + ['fake patch', - 'fake patch 2']) + cli.node.update.assert_any_call(self.node.uuid, + self.patch1 + ['fake patch', + 'fake patch 2']) + cli.node.update.assert_any_call(self.node.uuid, self.patch2) + self.assertEqual(2, cli.node.update.call_count) cli.port.create.assert_any_call(node_uuid=self.node.uuid, address='11:22:33:44:55:66') cli.port.create.assert_any_call(node_uuid=self.node.uuid, address='66:55:44:33:22:11') self.assertEqual(2, cli.port.create.call_count) filters_mock.assert_called_once_with(cli) - cli.node.set_power_state.assert_called_once_with(self.node.uuid, 'off') cli.port.update.assert_called_once_with(self.port.uuid, ['port patch']) pre_mock.assert_called_once_with(self.data) @@ -123,6 +127,14 @@ class TestProcess(BaseTest): def test_ok(self, client_mock, pop_mock, filters_mock, pre_mock, post_mock): self._do_test(client_mock, pop_mock, filters_mock, pre_mock, post_mock) + self.assertFalse(client_mock.return_value.node.set_power_state.called) + + def test_force_off(self, client_mock, pop_mock, filters_mock, pre_mock, + post_mock): + conf.CONF.set('discoverd', 'power_off_after_discovery', 'true') + self._do_test(client_mock, pop_mock, filters_mock, pre_mock, post_mock) + client_mock.return_value.node.set_power_state.assert_called_once_with( + self.node.uuid, 'off') def test_deprecated_macs(self, client_mock, pop_mock, filters_mock, pre_mock, post_mock):