Update Node controller to make it generic

This commit updates node controller code to use generic manager
interface instead of redfish directly

Partially-Implements blueprint add-vendor-extensible-framework
Change-Id: Ic551167798306a1ed6b5c609117e6d19e6de6574
This commit is contained in:
Anusha Ramineni 2017-08-22 16:04:39 +05:30
parent 570086d34a
commit 1afb9f0cbf
8 changed files with 136 additions and 113 deletions

View File

@ -30,8 +30,12 @@ class Nodes(Resource):
@validator.check_input('compose_node_schema')
def post(self):
# TODO(): podm_id should be passed in request body, if not passed
# scheduler will decide on podm_id
req = request.get_json()
return utils.make_response(
http_client.OK, nodes.Node.compose_node(request.get_json()))
http_client.OK,
nodes.Node(podm_id=req['podm_id']).compose_node(req))
class Node(Resource):
@ -39,11 +43,12 @@ class Node(Resource):
def get(self, node_uuid):
return utils.make_response(
http_client.OK,
nodes.Node.get_composed_node_by_uuid(node_uuid))
nodes.Node(node_id=node_uuid).get_composed_node_by_uuid(node_uuid))
def delete(self, node_uuid):
return utils.make_response(
http_client.OK, nodes.Node.delete_composed_node(node_uuid))
http_client.OK,
nodes.Node(node_id=node_uuid).delete_composed_node(node_uuid))
class NodeAction(Resource):
@ -52,15 +57,18 @@ class NodeAction(Resource):
def post(self, node_uuid):
return utils.make_response(
http_client.NO_CONTENT,
nodes.Node.node_action(node_uuid, request.get_json()))
nodes.Node(node_id=node_uuid).node_action(node_uuid,
request.get_json()))
class NodeManage(Resource):
@validator.check_input('node_manage_schema')
def post(self):
req = request.get_json()
return utils.make_response(
http_client.OK, nodes.Node.manage_node(request.get_json()))
http_client.OK,
nodes.Node(podm_id=req['podm_id']).manage_node(req))
class NodesStorage(Resource):

View File

@ -18,18 +18,36 @@ 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.podmanagers import manager
from valence.provision import driver
from valence.redfish import redfish
LOG = logging.getLogger(__name__)
class Node(object):
def __init__(self, node_id=None, podm_id=None):
"""Create node object
node uuid or podmanager uuid is required to create the
podmanager connection object.
:param node_id Node uuid
:param podm_id podmanager id
"""
self.podm_id = podm_id
if node_id:
self.node = db_api.Connection.get_composed_node_by_uuid(node_id).\
as_dict()
self.podm_id = self.node['podm_id']
self.connection = manager.get_connection(self.podm_id)
@staticmethod
def _show_node_brief_info(node_info):
return {key: node_info[key] for key in node_info.keys()
if key in ["uuid", "name", "index", "uri"]}
if key in ["uuid", "name", "podm_id", "index", "resource_uri"]}
@staticmethod
def _create_compose_request(name, description, requirements):
@ -57,8 +75,7 @@ class Node(object):
return request
@classmethod
def compose_node(cls, request_body):
def compose_node(self, request_body):
"""Compose new node
param request_body: parameter for node composition
@ -80,28 +97,25 @@ class Node(object):
# "description" is optional
description = request_body.get("description", "")
compose_request = cls._create_compose_request(name,
description,
compose_request = self._create_compose_request(name, description,
requirements)
# Call redfish to compose new node
composed_node = redfish.compose_node(compose_request)
composed_node = self.connection.compose_node(compose_request)
composed_node["uuid"] = utils.generate_uuid()
# Only store the minimum set of composed node info into backend db,
# since other fields like power status may be changed and valence is
# not aware.
node_db = {"uuid": composed_node["uuid"],
"podm_id": self.podm_id,
"name": composed_node["name"],
"index": composed_node["index"],
"uri": composed_node["resource_uri"]}
"resource_uri": composed_node["resource_uri"]}
db_api.Connection.create_composed_node(node_db)
return cls._show_node_brief_info(composed_node)
return self._show_node_brief_info(composed_node)
@classmethod
def manage_node(cls, request_body):
def manage_node(self, request_body):
"""Manage existing RSD node.
param request_body: Parameters for node to manage.
@ -110,32 +124,34 @@ class Node(object):
{
'node_index': <Redfish index of node to manage>
'podm_id': <podmanager id with which node is managed>
}
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.
node_index = request_body["node_index"]
error_msg = ("Node '%s' already managed by Valence")
current_nodes = cls.list_composed_nodes()
current_nodes = self.list_composed_nodes()
for node in current_nodes:
if node['index'] == composed_node['index']:
raise exception.ResourceExists(
error_msg % node['index'])
if node['index'] == node_index:
raise exception.ResourceExists(error_msg % node_index)
# Get podm connection with which node should be managed
composed_node = self.connection.get_node_info(node_index)
composed_node["uuid"] = utils.generate_uuid()
node_db = {"uuid": composed_node["uuid"],
"name": composed_node["name"],
"podm_id": self.podm_id,
"index": composed_node["index"],
"uri": composed_node["resource_uri"]}
"resource_uri": composed_node["resource_uri"]}
db_api.Connection.create_composed_node(node_db)
return cls._show_node_brief_info(composed_node)
return self._show_node_brief_info(composed_node)
@classmethod
def get_composed_node_by_uuid(cls, node_uuid):
def get_composed_node_by_uuid(self, node_uuid):
"""Get composed node details
Get the detail of specific composed node. In some cases db data may be
@ -147,28 +163,20 @@ class Node(object):
return: detail of this composed node
"""
node_db = db_api.Connection.get_composed_node_by_uuid(node_uuid)\
.as_dict()
node_hw = redfish.get_node_by_id(node_db["index"])
# Get podm connection to retrieve details
node_hw = self.connection.get_node_info(self.node['index'])
# Add those fields of composed node from db
node_hw.update(node_db)
node_hw.update(self.node)
return node_hw
@classmethod
def delete_composed_node(cls, node_uuid):
def delete_composed_node(self, node_uuid):
"""Delete a composed node
param node_uuid: uuid of composed node
return: message of this deletion
"""
# Get node detail from db, and map node uuid to index
index = db_api.Connection.get_composed_node_by_uuid(node_uuid).index
# Call redfish to delete node, and delete corresponding entry in db
message = redfish.delete_composed_node(index)
# Call podmanager to delete node, and delete corresponding entry in db
message = self.connection.delete_composed_node(self.node['index'])
db_api.Connection.delete_composed_node(node_uuid)
return message
@ -182,17 +190,15 @@ class Node(object):
return [cls._show_node_brief_info(node_info.as_dict())
for node_info in db_api.Connection.list_composed_nodes()]
@classmethod
def node_action(cls, node_uuid, request_body):
def node_action(self, node_uuid, request_body):
"""Post action to a composed node
param node_uuid: uuid of composed node
param request_body: parameter of node action
return: message of this deletion
"""
# Get node detail from db, and map node uuid to index
index = db_api.Connection.get_composed_node_by_uuid(node_uuid).index
return redfish.node_action(index, request_body)
node_index = self.node['index']
return self.connection.node_action(node_index, request_body)
@classmethod
def node_register(cls, node_uuid, request_body):

View File

@ -17,7 +17,7 @@ podm_connections = {}
podm_modules = {}
def get_podm_connection(podm_id):
def get_connection(podm_id):
podm_connection = podm_connections.get(podm_id, None)
if podm_connection:
return podm_connection
@ -25,7 +25,7 @@ def get_podm_connection(podm_id):
username, password = utils.get_basic_auth_credentials(
podm_db['authentication'])
podm_connection = Manager(podm_db['url'], username, password,
podm_db['driver'])
podm_db['driver']).podm
podm_connections[podm_id] = podm_connection
return podm_connection

View File

@ -26,6 +26,18 @@ class PodManagerBase(object):
def get_podm_info(self):
return self.get_resource_info_by_url(self.podm_url)
# TODO(): use rsd_lib here
def compose_node(self, request_body):
pass
# TODO(): use rsd_lib here
def delete_composed_node(self, node_id):
pass
# TODO(): use rsd_lib here
def node_action(self, index, request_body):
pass
def get_resource_info_by_url(self, resource_url):
return self.driver.get_resources_by_url(resource_url)

View File

@ -18,6 +18,7 @@ import mock
from valence.common import exception
from valence.controller import nodes
from valence.podmanagers import podm_base
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
@ -25,15 +26,22 @@ from valence.tests.unit.fakes import node_fakes
class TestAPINodes(unittest.TestCase):
@mock.patch('valence.podmanagers.manager.get_connection')
@mock.patch('valence.redfish.sushy.sushy_instance.RedfishInstance')
def setUp(self, mock_redfish, mock_connection):
self.node_controller = nodes.Node(podm_id='test-podm-1')
self.node_controller.connection = podm_base.PodManagerBase(
'fake', 'fake-pass', 'http://fake-url')
def test_show_node_brief_info(self):
"""Test only show node brief info"""
node_info = node_fakes.get_test_composed_node()
node_info['uri'] = 'nodes/7be5bc10-dcdf-11e6-bd86-934bc6947c55/'
expected = {
"index": "1",
"name": "fake_name",
"podm_id": "78e2a25-2901-438d-8157-de7ffd68d05",
"uuid": "ea8e2a25-2901-438d-8157-de7ffd68d051",
"uri": 'nodes/7be5bc10-dcdf-11e6-bd86-934bc6947c55/',
"resource_uri": 'nodes/7be5bc10-dcdf-11e6-bd86-934bc6947c55/',
}
self.assertEqual(expected,
nodes.Node._show_node_brief_info(node_info))
@ -72,10 +80,11 @@ class TestAPINodes(unittest.TestCase):
@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")
@mock.patch("valence.podmanagers.podm_base.PodManagerBase.get_node_info")
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()
manage_node['podm_id'] = 'test-podm-1'
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
@ -85,47 +94,45 @@ class TestAPINodes(unittest.TestCase):
uuid = "ea8e2a25-2901-438d-8157-de7ffd68d051"
mock_generate_uuid.return_value = uuid
node_db = {"uuid": manage_node["uuid"],
"index": manage_node["index"],
"podm_id": manage_node["podm_id"],
"name": manage_node["name"],
"uri": manage_node["resource_uri"]}
"resource_uri": manage_node["resource_uri"]}
nodes.Node.manage_node({"node_index": "1"})
req = {"node_index": "1"}
self.node_controller.manage_node(req)
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
def test_manage_already_managed_node(self, mock_list_nodes):
# 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,
self.node_controller.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")
@mock.patch("valence.podmanagers.podm_base.PodManagerBase.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 = node_fakes.get_test_composed_node()
node_db = {"uuid": node_hw["uuid"],
"podm_id": 'test-podm-1',
"index": node_hw["index"],
"name": node_hw["name"],
"uri": node_hw["resource_uri"]}
"resource_uri": node_hw["resource_uri"]}
mock_redfish_compose_node.return_value = node_hw
uuid = 'ea8e2a25-2901-438d-8157-de7ffd68d051'
mock_generate_uuid.return_value = uuid
result = nodes.Node.compose_node(
result = self.node_controller.compose_node(
{"name": node_hw["name"],
"description": node_hw["description"]})
expected = nodes.Node._show_node_brief_info(node_hw)
@ -135,7 +142,7 @@ class TestAPINodes(unittest.TestCase):
@mock.patch("valence.db.api.Connection.create_composed_node")
@mock.patch("valence.common.utils.generate_uuid")
@mock.patch("valence.redfish.redfish.compose_node")
@mock.patch("valence.podmanagers.podm_base.PodManagerBase.compose_node")
@mock.patch("valence.controller.flavors.get_flavor")
def test_compose_node_with_flavor(self, mock_get_flavor,
mock_redfish_compose_node,
@ -144,9 +151,10 @@ class TestAPINodes(unittest.TestCase):
"""Test node composition using a flavor for requirements"""
node_hw = node_fakes.get_test_composed_node()
node_db = {"uuid": node_hw["uuid"],
"podm_id": 'test-podm-1',
"index": node_hw["index"],
"name": node_hw["name"],
"uri": node_hw["resource_uri"]}
"resource_uri": node_hw["resource_uri"]}
mock_redfish_compose_node.return_value = node_hw
uuid = 'ea8e2a25-2901-438d-8157-de7ffd68d051'
@ -155,7 +163,7 @@ class TestAPINodes(unittest.TestCase):
flavor = flavor_fakes.fake_flavor()
mock_get_flavor.return_value = flavor
result = nodes.Node.compose_node(
result = self.node_controller.compose_node(
{"name": node_hw["name"],
"description": node_hw["description"],
"flavor_id": flavor["uuid"]})
@ -165,43 +173,26 @@ class TestAPINodes(unittest.TestCase):
mock_db_create_composed_node.assert_called_once_with(node_db)
mock_get_flavor.assert_called_once_with(flavor["uuid"])
@mock.patch("valence.redfish.redfish.get_node_by_id")
@mock.patch("valence.db.api.Connection.get_composed_node_by_uuid")
def test_get_composed_node_by_uuid(
self, mock_db_get_composed_node, mock_redfish_get_node):
@mock.patch("valence.podmanagers.podm_base.PodManagerBase.get_node_info")
def test_get_composed_node_by_uuid(self, mock_redfish_get_node):
"""Test get composed node detail"""
node_hw = node_fakes.get_test_composed_node()
node_db = test_utils.get_test_composed_node_db_info()
mock_db_model = mock.MagicMock()
mock_db_model.as_dict.return_value = node_db
mock_db_get_composed_node.return_value = mock_db_model
self.node_controller.node = node_db
mock_redfish_get_node.return_value = node_hw
result = nodes.Node.get_composed_node_by_uuid("fake_uuid")
result = self.node_controller.get_composed_node_by_uuid("fake_uuid")
expected = copy.deepcopy(node_hw)
expected.update(node_db)
self.assertEqual(expected, result)
@mock.patch("valence.db.api.Connection.delete_composed_node")
@mock.patch("valence.redfish.redfish.delete_composed_node")
@mock.patch("valence.db.api.Connection.get_composed_node_by_uuid")
def test_delete_composed_node(
self, mock_db_get_composed_node, mock_redfish_delete_composed_node,
mock_db_delete_composed_node):
def test_delete_composed_node(self, mock_db_delete_composed_node):
"""Test delete composed node"""
node_db = test_utils.get_test_composed_node_db_info()
mock_db_model = mock.MagicMock()
mock_db_model.index = node_db["index"]
mock_db_get_composed_node.return_value = mock_db_model
nodes.Node.delete_composed_node(node_db["uuid"])
mock_redfish_delete_composed_node.assert_called_once_with(
node_db["index"])
self.node_controller.node = node_db
self.node_controller.delete_composed_node(node_db["uuid"])
mock_db_delete_composed_node.assert_called_once_with(
node_db["uuid"])
@ -220,17 +211,12 @@ class TestAPINodes(unittest.TestCase):
self.assertEqual(expected, result)
@mock.patch("valence.redfish.redfish.node_action")
@mock.patch("valence.db.api.Connection.get_composed_node_by_uuid")
def test_node_action(
self, mock_db_get_composed_node, mock_node_action):
@mock.patch("valence.podmanagers.podm_base.PodManagerBase.node_action")
def test_node_action(self, mock_node_action):
"""Test reset composed node status"""
action = {"Reset": {"Type": "On"}}
mock_db_model = mock.MagicMock()
mock_db_model.index = "1"
mock_db_get_composed_node.return_value = mock_db_model
nodes.Node.node_action("fake_uuid", action)
self.node_controller.node = {'index': '1', 'name': 'test-node'}
self.node_controller.node_action("fake_uuid", action)
mock_node_action.assert_called_once_with("1", action)
@mock.patch("valence.provision.driver.node_register")

View File

@ -15,6 +15,7 @@ def get_test_composed_node(**kwargs):
return {
'uuid': kwargs.get('uuid', 'ea8e2a25-2901-438d-8157-de7ffd68d051'),
'name': kwargs.get('name', 'fake_name'),
'podm_id': kwargs.get('podm_id', '78e2a25-2901-438d-8157-de7ffd68d05'),
'description': kwargs.get('description', 'fake_description'),
'boot_source': kwargs.get('boot_source', 'Hdd'),
'health_status': kwargs.get('health_status', 'OK'),

View File

@ -43,7 +43,6 @@ class TestManager(base.BaseTestCase):
@mock.patch("valence.redfish.sushy.sushy_instance.RedfishInstance")
def test_get_podm_connection(self, redfish_mock, get_podm_mock):
get_podm_mock.return_value = podmanager_fakes.fake_podm_object()
inst = manager.get_podm_connection("fake-id")
self.assertTrue(isinstance(inst, manager.Manager))
self.assertTrue(isinstance(inst.podm, podm_base.PodManagerBase))
inst = manager.get_connection("fake-id")
self.assertTrue(isinstance(inst, podm_base.PodManagerBase))
self.assertTrue(manager.podm_connections['fake-id'])

View File

@ -130,10 +130,11 @@ class TestPodmanagerApi(TestApiValidation):
self.assertEqual('Validation Error', response['title'])
@mock.patch('valence.podmanagers.manager.get_connection')
class TestNodeApi(TestApiValidation):
@mock.patch('valence.controller.nodes.Node.compose_node')
def test_compose_request_using_properties(self, mock_compose):
def test_compose_request_using_properties(self, mock_compose, mock_conn):
req = {
"name": "test_request",
"podm_id": "test-podm",
@ -152,21 +153,24 @@ class TestNodeApi(TestApiValidation):
resp = self.app.post('/v1/nodes',
content_type='application/json',
data=json.dumps(req))
mock_conn.assert_called_once_with('test-podm')
self.assertEqual(http_client.OK, resp.status_code)
@mock.patch('valence.controller.nodes.Node.compose_node')
def test_compose_request_using_flavor(self, mock_compose):
def test_compose_request_using_flavor(self, mock_compose, mock_connection):
req = {
"name": "test_request1",
"flavor_id": "test_flavor"
"flavor_id": "test_flavor",
"podm_id": "test-podm-1"
}
mock_compose.return_value = req
resp = self.app.post('/v1/nodes',
content_type='application/json',
data=json.dumps(req))
mock_connection.assert_called_once_with('test-podm-1')
self.assertEqual(http_client.OK, resp.status_code)
def test_compose_request_invalid_params(self):
def test_compose_request_invalid_params(self, mock_conn):
req = {
"name": "test_request1",
"properties": {"invalid_key": "invalid_value"}
@ -179,7 +183,7 @@ class TestNodeApi(TestApiValidation):
self.assertEqual('Validation Error', response['title'])
@mock.patch('valence.controller.nodes.Node.manage_node')
def test_node_manage_request(self, mock_manage):
def test_node_manage_request(self, mock_manage, mock_connection):
req = {"node_index": "fake-index",
"podm_id": "test-podm-id"}
mock_manage.return_value = {"uuid": "ea8e2a25-2901-438d-8157-de7ffd",
@ -190,10 +194,11 @@ class TestNodeApi(TestApiValidation):
resp = self.app.post('/v1/nodes/manage',
content_type='application/json',
data=json.dumps(req))
mock_connection.assert_called_once_with('test-podm-id')
mock_manage.assert_called_once_with(req)
self.assertEqual(http_client.OK, resp.status_code)
def test_node_manage_request_invalid(self):
def test_node_manage_request_invalid(self, mock_conn):
req = {"node_id": "fake-index"}
resp = self.app.post('/v1/nodes/manage',
content_type='application/json',
@ -202,8 +207,9 @@ class TestNodeApi(TestApiValidation):
self.assertEqual(http_client.BAD_REQUEST, response['status'])
self.assertEqual('Validation Error', response['title'])
@mock.patch('valence.db.api.Connection.get_composed_node_by_uuid')
@mock.patch('valence.controller.nodes.Node.node_action')
def test_node_action_request(self, mock_action):
def test_node_action_request(self, mock_action, m_node, mock_connection):
req = {
"Reset": {
"Type": "On"
@ -217,7 +223,9 @@ class TestNodeApi(TestApiValidation):
self.assertEqual(http_client.NO_CONTENT, resp.status_code)
@mock.patch('valence.controller.nodes.Node.node_action')
def test_node_action_attach_request(self, mock_action):
@mock.patch('valence.db.api.Connection.get_composed_node_by_uuid')
def test_node_action_attach_request(self, mock_node, mock_action,
mock_connection):
req = {
"attach": {
"resource_id": "test-device-1"
@ -231,7 +239,9 @@ class TestNodeApi(TestApiValidation):
self.assertEqual(http_client.NO_CONTENT, resp.status_code)
@mock.patch('valence.controller.nodes.Node.node_action')
def test_node_action_detach_request(self, mock_action):
@mock.patch('valence.db.api.Connection.get_composed_node_by_uuid')
def test_node_action_detach_request(self, mock_node, mock_action,
mock_connection):
req = {
"detach": {
"resource_id": "test-device-1"
@ -244,7 +254,8 @@ class TestNodeApi(TestApiValidation):
mock_action.assert_called_once_with('fake-node', req)
self.assertEqual(http_client.NO_CONTENT, resp.status_code)
def test_node_action_request_invalid(self):
@mock.patch('valence.db.api.Connection.get_composed_node_by_uuid')
def test_node_action_request_invalid(self, mock_node, mock_connection):
req = {
"Boot": {
"Type": "On"