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
This commit is contained in:
Nate Potter 2017-02-03 16:40:23 -08:00
parent 36aabb9011
commit 5ce2d68ed7
8 changed files with 166 additions and 10 deletions

View File

@ -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.

View File

@ -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

View File

@ -73,6 +73,8 @@ api.add_resource(v1_nodes.Node,
api.add_resource(v1_nodes.NodeAction,
'/v1/nodes/<string:node_uuid>/action',
endpoint='node_action')
api.add_resource(v1_nodes.NodeManage, '/v1/nodes/manage',
endpoint='node_manage')
api.add_resource(v1_nodes.NodesStorage,
'/v1/nodes/<string:nodeid>/storages',
endpoint='nodes_storages')

View File

@ -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):

View File

@ -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',

View File

@ -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': <Redfish index of node to manage>
}
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

View File

@ -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()

View File

@ -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')
}
]