diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 25a2a52..525641f 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 91f96f0..d9b3230 100644 --- a/api-ref/source/valence-api-v1-nodes.inc +++ b/api-ref/source/valence-api-v1-nodes.inc @@ -53,6 +53,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:** @@ -85,6 +86,7 @@ Response - uuid: node_uuid - name: node_name + - index: node_index - node_power_state: node_power_state - links: links @@ -303,8 +305,8 @@ Normal response codes: 200 Error response codes: badRequest(400), unauthorized(401), forbidden(403) -Requet ------- +Request +------- .. rest_parameters:: parameters.yaml @@ -326,3 +328,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 ee7136e..b2b9aca 100644 --- a/valence/api/route.py +++ b/valence/api/route.py @@ -78,6 +78,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') + } + ]