Merge "Implement Node Manage"

This commit is contained in:
Jenkins 2017-04-19 20:41:07 +00:00 committed by Gerrit Code Review
commit 3a1b4e6d7b
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

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

View File

@ -78,6 +78,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')
}
]