From a69faf00c44518a886c3a65c9a156ed193c4b0d8 Mon Sep 17 00:00:00 2001 From: Lin Yang Date: Mon, 21 Aug 2017 17:21:25 -0700 Subject: [PATCH] Add show composed node detail command Add new command 'rsd node show' to allow user to get composed node details by specifying node ID. Change-Id: I26e4adbbf18519742cc215371e8f86fbb4b7d93e --- rsdclient/common/base.py | 2 +- rsdclient/common/utils.py | 43 ++++++++++++++++ rsdclient/osc/v1/node.py | 21 ++++++++ rsdclient/tests/common/__init__.py | 0 rsdclient/tests/common/fakes.py | 77 ++++++++++++++++++++++++++++ rsdclient/tests/common/test_utils.py | 28 ++++++++++ rsdclient/tests/v1/test_node.py | 11 +++- rsdclient/v1/node.py | 5 ++ setup.cfg | 1 + 9 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 rsdclient/common/utils.py create mode 100644 rsdclient/tests/common/__init__.py create mode 100644 rsdclient/tests/common/fakes.py create mode 100644 rsdclient/tests/common/test_utils.py diff --git a/rsdclient/common/base.py b/rsdclient/common/base.py index b33d651..c3b7f91 100644 --- a/rsdclient/common/base.py +++ b/rsdclient/common/base.py @@ -1,4 +1,4 @@ -# Copyright 2017 99cloud, Inc. +# Copyright 2017 Intel, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/rsdclient/common/utils.py b/rsdclient/common/utils.py new file mode 100644 index 0000000..ad63e6f --- /dev/null +++ b/rsdclient/common/utils.py @@ -0,0 +1,43 @@ +# Copyright 2017 Intel, Inc. +# All Rights Reserved. +# +# 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 six + + +def extract_attr(redfish_obj): + '''Extract all public attributes of python redfish object + + :param redfish_obj: python redfish object returned by rsd_lib + :returns: python dict of that object + ''' + + if isinstance(redfish_obj, (int, six.string_types)): + return redfish_obj + if isinstance(redfish_obj, list): + return [extract_attr(i) for i in redfish_obj] + if isinstance(redfish_obj, dict): + return {i: extract_attr(redfish_obj[i]) for i in redfish_obj} + + result = {} + try: + for key, value in vars(redfish_obj).items(): + # Skip all private attributes + if key.startswith('_'): + continue + result[key] = extract_attr(value) + except TypeError: + return None + + return result diff --git a/rsdclient/osc/v1/node.py b/rsdclient/osc/v1/node.py index 846b016..18dc5d3 100644 --- a/rsdclient/osc/v1/node.py +++ b/rsdclient/osc/v1/node.py @@ -13,6 +13,8 @@ # under the License. # +import json + from osc_lib.command import command @@ -59,3 +61,22 @@ class DeleteNode(command.Command): for node in parsed_args.node: rsd_client.node.delete(node) print("Node {0} has been deleted.".format(node)) + + +class ShowNode(command.Command): + _description = "Display node details" + + def get_parser(self, prog_name): + parser = super(ShowNode, self).get_parser(prog_name) + parser.add_argument( + 'node', + metavar='', + help='ID of the node.') + + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + rsd_client = self.app.client_manager.rsd + node_detail = rsd_client.node.show(parsed_args.node) + print("{0}".format(json.dumps(node_detail, indent=2))) diff --git a/rsdclient/tests/common/__init__.py b/rsdclient/tests/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rsdclient/tests/common/fakes.py b/rsdclient/tests/common/fakes.py new file mode 100644 index 0000000..1dfe128 --- /dev/null +++ b/rsdclient/tests/common/fakes.py @@ -0,0 +1,77 @@ +# Copyright 2017 Intel, Inc. +# +# 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. +# + + +FAKE_NODE_PYTHON_DICT = { + "description": "Node for testing", + "processor_summary": { + "count": 1, + "model": "fake processor model", + "health": "OK" + }, + "composed_node_state": "allocated", + "boot": { + "mode": "fake boot mode", + "enabled": "once", + "target": "pxe", + "allowed_values": ["pxe", "hdd"] + }, + "uuid": "fd011520-86a2-11e7-b4d4-5d323196a3e4", + "power_state": "on", + "memory_summary": { + "size_gib": 8, + "health": "OK" + }, + "identity": "1", + "name": "Test" +} + + +class FakeProcessorSummary(object): + + def __init__(self): + self.count = 1 + self.model = "fake processor model" + self.health = "OK" + + +class FakeBoot(object): + + def __init__(self): + self.mode = "fake boot mode" + self.enabled = "once" + self.target = "pxe" + self.allowed_values = ["pxe", "hdd"] + + +class FakeMemorySummary(object): + + def __init__(self): + self.size_gib = 8 + self.health = "OK" + + +class FakeNode(object): + + def __init__(self): + self.name = "Test" + self.description = "Node for testing" + self.identity = "1" + self.power_state = "on" + self.composed_node_state = "allocated" + self.boot = FakeBoot() + self.processor_summary = FakeProcessorSummary() + self.memory_summary = FakeMemorySummary() + self.uuid = "fd011520-86a2-11e7-b4d4-5d323196a3e4" diff --git a/rsdclient/tests/common/test_utils.py b/rsdclient/tests/common/test_utils.py new file mode 100644 index 0000000..983e93a --- /dev/null +++ b/rsdclient/tests/common/test_utils.py @@ -0,0 +1,28 @@ +# Copyright 2017 Intel, Inc. +# +# 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 testtools + +from rsdclient.common import utils +from rsdclient.tests.common import fakes + + +class UtilsTest(testtools.TestCase): + + def test_compose_node(self): + fake_node = fakes.FakeNode() + result = utils.extract_attr(fake_node) + expected = fakes.FAKE_NODE_PYTHON_DICT + self.assertEqual(result, expected) diff --git a/rsdclient/tests/v1/test_node.py b/rsdclient/tests/v1/test_node.py index 5c89adc..4778adb 100644 --- a/rsdclient/tests/v1/test_node.py +++ b/rsdclient/tests/v1/test_node.py @@ -16,13 +16,14 @@ import mock import testtools +from rsdclient.tests.common import fakes from rsdclient.v1 import node -class ClusterManagerTest(testtools.TestCase): +class NodeTest(testtools.TestCase): def setUp(self): - super(ClusterManagerTest, self).setUp() + super(NodeTest, self).setUp() self.client = mock.Mock() self.client._nodes_path = '/redfish/v1/Nodes' self.mgr = node.NodeManager(self.client) @@ -44,3 +45,9 @@ class ClusterManagerTest(testtools.TestCase): self.mgr.delete(node_id) self.mgr.client.get_node.assert_called_once_with('/redfish/v1/Nodes/1') mock_node.delete_node.assert_called_once() + + def test_show_node(self): + self.client.get_node.return_value = fakes.FakeNode() + result = self.mgr.show('1') + expected = fakes.FAKE_NODE_PYTHON_DICT + self.assertEqual(result, expected) diff --git a/rsdclient/v1/node.py b/rsdclient/v1/node.py index 84b558d..bbe36ca 100644 --- a/rsdclient/v1/node.py +++ b/rsdclient/v1/node.py @@ -16,6 +16,7 @@ import os from rsdclient.common import base +from rsdclient.common import utils class NodeManager(base.Manager): @@ -35,3 +36,7 @@ class NodeManager(base.Manager): def delete(self, node_id): self.client.get_node(self._get_node_uri(node_id)).delete_node() + + def show(self, node_id): + node = self.client.get_node(self._get_node_uri(node_id)) + return utils.extract_attr(node) diff --git a/setup.cfg b/setup.cfg index 75f6e4e..1eb3553 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,6 +30,7 @@ openstack.cli.extension = openstack.rsd.v1 = rsd_node_compose = rsdclient.osc.v1.node:ComposeNode rsd_node_delete = rsdclient.osc.v1.node:DeleteNode + rsd_node_show = rsdclient.osc.v1.node:ShowNode [build_sphinx] all-files = 1