From 5ce2d68ed7aae55cffd90f88fa72010ddf9c45ca Mon Sep 17 00:00:00 2001 From: Nate Potter Date: Fri, 3 Feb 2017 16:40:23 -0800 Subject: [PATCH] Implement Node Manage This commit implements the ability for an admin to manage existing RSD composed nodes with Valence. In the event that an RSD pod manager contains composed nodes that were created without Valence, it's possible to use this function to add those nodes to the Valence database. Change-Id: I13e8076fb718ebd356cb7b4839cfb4b83c798672 Implements-blueprint: manage-node --- api-ref/source/parameters.yaml | 6 +++ api-ref/source/valence-api-v1-nodes.inc | 36 ++++++++++++- valence/api/route.py | 2 + valence/api/v1/nodes.py | 7 +++ valence/common/exception.py | 9 ++++ valence/controller/nodes.py | 41 ++++++++++++++- valence/tests/unit/controller/test_nodes.py | 52 +++++++++++++++++-- .../fakes.py => fakes/node_fakes.py} | 23 +++++++- 8 files changed, 166 insertions(+), 10 deletions(-) rename valence/tests/unit/{controller/fakes.py => fakes/node_fakes.py} (76%) diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index b8087df..2a6aa66 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -35,6 +35,12 @@ pod_uuid: in: path required: true type: string +node_index: + description: | + The redfish index of composed node. + in: path + required: true + type: string system_ident: description: | The UUID or name of Compute System. diff --git a/api-ref/source/valence-api-v1-nodes.inc b/api-ref/source/valence-api-v1-nodes.inc index 88cafbe..79503a4 100644 --- a/api-ref/source/valence-api-v1-nodes.inc +++ b/api-ref/source/valence-api-v1-nodes.inc @@ -48,6 +48,7 @@ The list and example below are representative of the response as of API - uuid: node_uuid - name: node_name + - index: node_index - links: links **Example JSON representation of a Node:** @@ -80,6 +81,7 @@ Response - uuid: node_uuid - name: node_name + - index: node_index - node_power_state: node_power_state - links: links @@ -298,8 +300,8 @@ Normal response codes: 200 Error response codes: badRequest(400), unauthorized(401), forbidden(403) -Requet ------- +Request +------- .. rest_parameters:: parameters.yaml @@ -321,3 +323,33 @@ Response .. literalinclude:: mockup/composed-node-get-asset.json :language: javascript + +Manage Node +=========== + +.. rest_method:: POST /v1/nodes/managed + +Manage a composed node already existing in the RSD rack by creating a +Valence database entry for it, allowing Valence to perform all operations +on it. + +Normal response codes: 200 + +Error response codes: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404), conflict(409) + +Request +------- + +.. rest_parameters:: parameters.yaml + + - node_index: node_index + +Response +-------- + +.. rest_parameters:: parameters.yaml + + - uuid: node_uuid + - name: node_name + - index: node_index + - links: links diff --git a/valence/api/route.py b/valence/api/route.py index 587b0c6..1d2de2b 100644 --- a/valence/api/route.py +++ b/valence/api/route.py @@ -73,6 +73,8 @@ api.add_resource(v1_nodes.Node, api.add_resource(v1_nodes.NodeAction, '/v1/nodes//action', endpoint='node_action') +api.add_resource(v1_nodes.NodeManage, '/v1/nodes/manage', + endpoint='node_manage') api.add_resource(v1_nodes.NodesStorage, '/v1/nodes//storages', endpoint='nodes_storages') diff --git a/valence/api/v1/nodes.py b/valence/api/v1/nodes.py index 79bdb4a..12b37c1 100644 --- a/valence/api/v1/nodes.py +++ b/valence/api/v1/nodes.py @@ -52,6 +52,13 @@ class NodeAction(Resource): nodes.Node.node_action(node_uuid, request.get_json())) +class NodeManage(Resource): + + def post(self): + return utils.make_response( + http_client.OK, nodes.Node.manage_node(request.get_json())) + + class NodesStorage(Resource): def get(self, nodeid): diff --git a/valence/common/exception.py b/valence/common/exception.py index 0e5f6fb..148a4ed 100644 --- a/valence/common/exception.py +++ b/valence/common/exception.py @@ -86,6 +86,15 @@ class RedfishException(ValenceError): self.detail = message_detail +class ResourceExists(ValenceError): + def __init__(self, detail='resource already exists', request_id=None): + self.request_id = request_id + self.status_code = http_client.METHOD_NOT_ALLOWED + self.code = http_client.METHOD_NOT_ALLOWED + self.title = "Resource already exists" + self.detail = detail + + class NotFound(ValenceError): def __init__(self, detail='resource not found', diff --git a/valence/controller/nodes.py b/valence/controller/nodes.py index 6ce4094..31d7f5b 100644 --- a/valence/controller/nodes.py +++ b/valence/controller/nodes.py @@ -12,20 +12,25 @@ # License for the specific language governing permissions and limitations # under the License. +import logging + import six +from valence.common import exception from valence.common import utils from valence.controller import flavors from valence.db import api as db_api from valence.redfish import redfish +LOG = logging.getLogger(__name__) + class Node(object): @staticmethod def _show_node_brief_info(node_info): return {key: node_info[key] for key in six.iterkeys(node_info) - if key in ["uuid", "name", "links"]} + if key in ["uuid", "name", "index", "links"]} @staticmethod def _create_compose_request(name, description, requirements): @@ -95,6 +100,40 @@ class Node(object): return cls._show_node_brief_info(composed_node) + @classmethod + def manage_node(cls, request_body): + """Manage existing RSD node. + + param request_body: Parameters for node to manage. + + Required JSON body: + + { + 'node_index': + } + + return: Info on managed node. + """ + + composed_node = redfish.get_node_by_id(request_body["node_index"]) + # Check to see that the node to manage doesn't already exist in the + # Valence database. + current_nodes = cls.list_composed_nodes() + for node in current_nodes: + if node['index'] == composed_node['index']: + raise exception.ResourceExists( + detail="Node already managed by Valence.") + + composed_node["uuid"] = utils.generate_uuid() + + node_db = {"uuid": composed_node["uuid"], + "name": composed_node["name"], + "index": composed_node["index"], + "links": composed_node["links"]} + db_api.Connection.create_composed_node(node_db) + + return cls._show_node_brief_info(composed_node) + @classmethod def get_composed_node_by_uuid(cls, node_uuid): """Get composed node details diff --git a/valence/tests/unit/controller/test_nodes.py b/valence/tests/unit/controller/test_nodes.py index 093ee2f..62ccbae 100644 --- a/valence/tests/unit/controller/test_nodes.py +++ b/valence/tests/unit/controller/test_nodes.py @@ -16,18 +16,20 @@ import unittest import mock +from valence.common import exception from valence.controller import nodes -from valence.tests.unit.controller import fakes from valence.tests.unit.db import utils as test_utils from valence.tests.unit.fakes import flavor_fakes +from valence.tests.unit.fakes import node_fakes class TestAPINodes(unittest.TestCase): def test_show_node_brief_info(self): """Test only show node brief info""" - node_info = fakes.get_test_composed_node() + node_info = node_fakes.get_test_composed_node() expected = { + "index": "1", "name": "fake_name", "uuid": "ea8e2a25-2901-438d-8157-de7ffd68d051", "links": [{'href': 'http://127.0.0.1:8181/v1/nodes/' @@ -71,13 +73,53 @@ class TestAPINodes(unittest.TestCase): requirements) self.assertEqual(expected, result) + @mock.patch("valence.db.api.Connection.create_composed_node") + @mock.patch("valence.common.utils.generate_uuid") + @mock.patch("valence.controller.nodes.Node.list_composed_nodes") + @mock.patch("valence.redfish.redfish.get_node_by_id") + def test_manage_node(self, mock_get_node, mock_list_nodes, + mock_generate_uuid, mock_db_create_composed_node): + manage_node = node_fakes.get_test_composed_node() + mock_get_node.return_value = manage_node + node_list = node_fakes.get_test_node_list() + # Change the index of node 1 so that the node to manage + # doesn't appear in the list of nodes already managed by Valence. + node_list[0]["index"] = '4' + mock_list_nodes.return_value = node_list + + uuid = "ea8e2a25-2901-438d-8157-de7ffd68d051" + mock_generate_uuid.return_value = uuid + + node_db = {"uuid": manage_node["uuid"], + "index": manage_node["index"], + "name": manage_node["name"], + "links": manage_node["links"]} + + nodes.Node.manage_node({"node_index": "1"}) + mock_db_create_composed_node.assert_called_once_with(node_db) + + @mock.patch("valence.controller.nodes.Node.list_composed_nodes") + @mock.patch("valence.redfish.redfish.get_node_by_id") + def test_manage_already_managed_node(self, mock_get_node, mock_list_nodes): + manage_node = node_fakes.get_test_composed_node() + mock_get_node.return_value = manage_node + # Leave the index of node 1 as '1' so that it conflicts with the node + # being managed, meaning we're trying to manage a node that already + # exists in the Valence DB. + node_list = node_fakes.get_test_node_list() + mock_list_nodes.return_value = node_list + + self.assertRaises(exception.ResourceExists, + nodes.Node.manage_node, + {"node_index": "1"}) + @mock.patch("valence.db.api.Connection.create_composed_node") @mock.patch("valence.common.utils.generate_uuid") @mock.patch("valence.redfish.redfish.compose_node") def test_compose_node(self, mock_redfish_compose_node, mock_generate_uuid, mock_db_create_composed_node): """Test compose node successfully""" - node_hw = fakes.get_test_composed_node() + node_hw = node_fakes.get_test_composed_node() node_db = {"uuid": node_hw["uuid"], "index": node_hw["index"], "name": node_hw["name"], @@ -104,7 +146,7 @@ class TestAPINodes(unittest.TestCase): mock_generate_uuid, mock_db_create_composed_node): """Test node composition using a flavor for requirements""" - node_hw = fakes.get_test_composed_node() + node_hw = node_fakes.get_test_composed_node() node_db = {"uuid": node_hw["uuid"], "index": node_hw["index"], "name": node_hw["name"], @@ -132,7 +174,7 @@ class TestAPINodes(unittest.TestCase): def test_get_composed_node_by_uuid( self, mock_db_get_composed_node, mock_redfish_get_node): """Test get composed node detail""" - node_hw = fakes.get_test_composed_node() + node_hw = node_fakes.get_test_composed_node() node_db = test_utils.get_test_composed_node_db_info() mock_db_model = mock.MagicMock() diff --git a/valence/tests/unit/controller/fakes.py b/valence/tests/unit/fakes/node_fakes.py similarity index 76% rename from valence/tests/unit/controller/fakes.py rename to valence/tests/unit/fakes/node_fakes.py index e0aa656..2966282 100644 --- a/valence/tests/unit/controller/fakes.py +++ b/valence/tests/unit/fakes/node_fakes.py @@ -1,5 +1,4 @@ -# copyright (c) 2016 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 # @@ -50,3 +49,23 @@ def get_test_composed_node(**kwargs): 'speed_mhz': 3700, 'total_core': 2}]}) } + + +def get_test_node_list(**kwargs): + return [ + { + 'uuid': kwargs.get('uuid', '11111111-1111-1111-1111-111111111111'), + 'name': kwargs.get('name', 'node_1'), + 'index': kwargs.get('index', '1') + }, + { + 'uuid': kwargs.get('uuid', '22222222-2222-2222-2222-222222222222'), + 'name': kwargs.get('name', 'node_2'), + 'index': kwargs.get('index', '2') + }, + { + 'uuid': kwargs.get('uuid', '33333333-3333-3333-3333-333333333333'), + 'name': kwargs.get('name', 'node_3'), + 'index': kwargs.get('index', '3') + } + ]