Merge "Implement Node Manage"
This commit is contained in:
commit
3a1b4e6d7b
@ -35,6 +35,12 @@ pod_uuid:
|
|||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
node_index:
|
||||||
|
description: |
|
||||||
|
The redfish index of composed node.
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
system_ident:
|
system_ident:
|
||||||
description: |
|
description: |
|
||||||
The UUID or name of Compute System.
|
The UUID or name of Compute System.
|
||||||
|
@ -53,6 +53,7 @@ The list and example below are representative of the response as of API
|
|||||||
|
|
||||||
- uuid: node_uuid
|
- uuid: node_uuid
|
||||||
- name: node_name
|
- name: node_name
|
||||||
|
- index: node_index
|
||||||
- links: links
|
- links: links
|
||||||
|
|
||||||
**Example JSON representation of a Node:**
|
**Example JSON representation of a Node:**
|
||||||
@ -85,6 +86,7 @@ Response
|
|||||||
|
|
||||||
- uuid: node_uuid
|
- uuid: node_uuid
|
||||||
- name: node_name
|
- name: node_name
|
||||||
|
- index: node_index
|
||||||
- node_power_state: node_power_state
|
- node_power_state: node_power_state
|
||||||
- links: links
|
- links: links
|
||||||
|
|
||||||
@ -303,8 +305,8 @@ Normal response codes: 200
|
|||||||
|
|
||||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403)
|
Error response codes: badRequest(400), unauthorized(401), forbidden(403)
|
||||||
|
|
||||||
Requet
|
Request
|
||||||
------
|
-------
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
@ -326,3 +328,33 @@ Response
|
|||||||
.. literalinclude:: mockup/composed-node-get-asset.json
|
.. literalinclude:: mockup/composed-node-get-asset.json
|
||||||
:language: javascript
|
: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
|
||||||
|
@ -78,6 +78,8 @@ api.add_resource(v1_nodes.Node,
|
|||||||
api.add_resource(v1_nodes.NodeAction,
|
api.add_resource(v1_nodes.NodeAction,
|
||||||
'/v1/nodes/<string:node_uuid>/action',
|
'/v1/nodes/<string:node_uuid>/action',
|
||||||
endpoint='node_action')
|
endpoint='node_action')
|
||||||
|
api.add_resource(v1_nodes.NodeManage, '/v1/nodes/manage',
|
||||||
|
endpoint='node_manage')
|
||||||
api.add_resource(v1_nodes.NodesStorage,
|
api.add_resource(v1_nodes.NodesStorage,
|
||||||
'/v1/nodes/<string:nodeid>/storages',
|
'/v1/nodes/<string:nodeid>/storages',
|
||||||
endpoint='nodes_storages')
|
endpoint='nodes_storages')
|
||||||
|
@ -52,6 +52,13 @@ class NodeAction(Resource):
|
|||||||
nodes.Node.node_action(node_uuid, request.get_json()))
|
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):
|
class NodesStorage(Resource):
|
||||||
|
|
||||||
def get(self, nodeid):
|
def get(self, nodeid):
|
||||||
|
@ -86,6 +86,15 @@ class RedfishException(ValenceError):
|
|||||||
self.detail = message_detail
|
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):
|
class NotFound(ValenceError):
|
||||||
|
|
||||||
def __init__(self, detail='resource not found',
|
def __init__(self, detail='resource not found',
|
||||||
|
@ -12,20 +12,25 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from valence.common import exception
|
||||||
from valence.common import utils
|
from valence.common import utils
|
||||||
from valence.controller import flavors
|
from valence.controller import flavors
|
||||||
from valence.db import api as db_api
|
from valence.db import api as db_api
|
||||||
from valence.redfish import redfish
|
from valence.redfish import redfish
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Node(object):
|
class Node(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _show_node_brief_info(node_info):
|
def _show_node_brief_info(node_info):
|
||||||
return {key: node_info[key] for key in six.iterkeys(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
|
@staticmethod
|
||||||
def _create_compose_request(name, description, requirements):
|
def _create_compose_request(name, description, requirements):
|
||||||
@ -95,6 +100,40 @@ class Node(object):
|
|||||||
|
|
||||||
return cls._show_node_brief_info(composed_node)
|
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
|
@classmethod
|
||||||
def get_composed_node_by_uuid(cls, node_uuid):
|
def get_composed_node_by_uuid(cls, node_uuid):
|
||||||
"""Get composed node details
|
"""Get composed node details
|
||||||
|
@ -16,18 +16,20 @@ import unittest
|
|||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
|
from valence.common import exception
|
||||||
from valence.controller import nodes
|
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.db import utils as test_utils
|
||||||
from valence.tests.unit.fakes import flavor_fakes
|
from valence.tests.unit.fakes import flavor_fakes
|
||||||
|
from valence.tests.unit.fakes import node_fakes
|
||||||
|
|
||||||
|
|
||||||
class TestAPINodes(unittest.TestCase):
|
class TestAPINodes(unittest.TestCase):
|
||||||
|
|
||||||
def test_show_node_brief_info(self):
|
def test_show_node_brief_info(self):
|
||||||
"""Test only show node brief info"""
|
"""Test only show node brief info"""
|
||||||
node_info = fakes.get_test_composed_node()
|
node_info = node_fakes.get_test_composed_node()
|
||||||
expected = {
|
expected = {
|
||||||
|
"index": "1",
|
||||||
"name": "fake_name",
|
"name": "fake_name",
|
||||||
"uuid": "ea8e2a25-2901-438d-8157-de7ffd68d051",
|
"uuid": "ea8e2a25-2901-438d-8157-de7ffd68d051",
|
||||||
"links": [{'href': 'http://127.0.0.1:8181/v1/nodes/'
|
"links": [{'href': 'http://127.0.0.1:8181/v1/nodes/'
|
||||||
@ -71,13 +73,53 @@ class TestAPINodes(unittest.TestCase):
|
|||||||
requirements)
|
requirements)
|
||||||
self.assertEqual(expected, result)
|
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.db.api.Connection.create_composed_node")
|
||||||
@mock.patch("valence.common.utils.generate_uuid")
|
@mock.patch("valence.common.utils.generate_uuid")
|
||||||
@mock.patch("valence.redfish.redfish.compose_node")
|
@mock.patch("valence.redfish.redfish.compose_node")
|
||||||
def test_compose_node(self, mock_redfish_compose_node, mock_generate_uuid,
|
def test_compose_node(self, mock_redfish_compose_node, mock_generate_uuid,
|
||||||
mock_db_create_composed_node):
|
mock_db_create_composed_node):
|
||||||
"""Test compose node successfully"""
|
"""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"],
|
node_db = {"uuid": node_hw["uuid"],
|
||||||
"index": node_hw["index"],
|
"index": node_hw["index"],
|
||||||
"name": node_hw["name"],
|
"name": node_hw["name"],
|
||||||
@ -104,7 +146,7 @@ class TestAPINodes(unittest.TestCase):
|
|||||||
mock_generate_uuid,
|
mock_generate_uuid,
|
||||||
mock_db_create_composed_node):
|
mock_db_create_composed_node):
|
||||||
"""Test node composition using a flavor for requirements"""
|
"""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"],
|
node_db = {"uuid": node_hw["uuid"],
|
||||||
"index": node_hw["index"],
|
"index": node_hw["index"],
|
||||||
"name": node_hw["name"],
|
"name": node_hw["name"],
|
||||||
@ -132,7 +174,7 @@ class TestAPINodes(unittest.TestCase):
|
|||||||
def test_get_composed_node_by_uuid(
|
def test_get_composed_node_by_uuid(
|
||||||
self, mock_db_get_composed_node, mock_redfish_get_node):
|
self, mock_db_get_composed_node, mock_redfish_get_node):
|
||||||
"""Test get composed node detail"""
|
"""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()
|
node_db = test_utils.get_test_composed_node_db_info()
|
||||||
|
|
||||||
mock_db_model = mock.MagicMock()
|
mock_db_model = mock.MagicMock()
|
||||||
|
@ -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
|
# not use this file except in compliance with the License. You may obtain
|
||||||
# a copy of the License at
|
# a copy of the License at
|
||||||
#
|
#
|
||||||
@ -50,3 +49,23 @@ def get_test_composed_node(**kwargs):
|
|||||||
'speed_mhz': 3700,
|
'speed_mhz': 3700,
|
||||||
'total_core': 2}]})
|
'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')
|
||||||
|
}
|
||||||
|
]
|
Loading…
x
Reference in New Issue
Block a user