diff --git a/valence/api/v1/nodes.py b/valence/api/v1/nodes.py index b0a9475..abeb71c 100644 --- a/valence/api/v1/nodes.py +++ b/valence/api/v1/nodes.py @@ -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): diff --git a/valence/controller/nodes.py b/valence/controller/nodes.py index d58b794..07f857c 100644 --- a/valence/controller/nodes.py +++ b/valence/controller/nodes.py @@ -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, - requirements) - - # Call redfish to compose new node - composed_node = redfish.compose_node(compose_request) + compose_request = self._create_compose_request(name, description, + requirements) + 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': + 'podm_id': } 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): diff --git a/valence/podmanagers/manager.py b/valence/podmanagers/manager.py index a1ab21e..c32f781 100644 --- a/valence/podmanagers/manager.py +++ b/valence/podmanagers/manager.py @@ -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 diff --git a/valence/podmanagers/podm_base.py b/valence/podmanagers/podm_base.py index 30b4491..1544cc3 100644 --- a/valence/podmanagers/podm_base.py +++ b/valence/podmanagers/podm_base.py @@ -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) diff --git a/valence/tests/unit/controller/test_nodes.py b/valence/tests/unit/controller/test_nodes.py index 10683c0..aff71a3 100644 --- a/valence/tests/unit/controller/test_nodes.py +++ b/valence/tests/unit/controller/test_nodes.py @@ -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") diff --git a/valence/tests/unit/fakes/node_fakes.py b/valence/tests/unit/fakes/node_fakes.py index f6088ab..3c77253 100644 --- a/valence/tests/unit/fakes/node_fakes.py +++ b/valence/tests/unit/fakes/node_fakes.py @@ -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'), diff --git a/valence/tests/unit/podmanagers/test_manager.py b/valence/tests/unit/podmanagers/test_manager.py index 4d38c5c..044a35b 100644 --- a/valence/tests/unit/podmanagers/test_manager.py +++ b/valence/tests/unit/podmanagers/test_manager.py @@ -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']) diff --git a/valence/tests/unit/validation/test_validation.py b/valence/tests/unit/validation/test_validation.py index b2dfd70..f863e71 100644 --- a/valence/tests/unit/validation/test_validation.py +++ b/valence/tests/unit/validation/test_validation.py @@ -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"