diff --git a/api-ref/source/mockup/composed-node-create-request.json b/api-ref/source/mockup/composed-node-create-request.json index f2f4c4b..a238741 100644 --- a/api-ref/source/mockup/composed-node-create-request.json +++ b/api-ref/source/mockup/composed-node-create-request.json @@ -1,4 +1,17 @@ { "name": "test_node", "description": "node1", + "properties": { + "memory": { + "capacity_mib": "8000", + "type": "DDR2" + }, + "processor": { + "model": "Multi-Core Intel(R) Xeon(R) processor 7xxx Series", + "total_cores": "2" + }, + "remote_storage": { + "capacity_gib": "100" + } + } } diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 50d1949..0dc6342 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -175,6 +175,12 @@ node_processor_asset: in: body required: true type: array +node_properties: + description: | + Dictionary of properties of a composed node. + in: body + required: false + type: dictionary node_property: description: | Property of composed node including processor , memory , storage info. diff --git a/api-ref/source/valence-api-v1-nodes.inc b/api-ref/source/valence-api-v1-nodes.inc index e5c2787..2357bee 100644 --- a/api-ref/source/valence-api-v1-nodes.inc +++ b/api-ref/source/valence-api-v1-nodes.inc @@ -29,6 +29,8 @@ Request - name: node_request_name - description: node_request_description + - flavor_id: flavor_uuid + - properties: node_properties **Example Node creation request:** diff --git a/valence/controller/flavors.py b/valence/controller/flavors.py index 78119e9..59c4402 100644 --- a/valence/controller/flavors.py +++ b/valence/controller/flavors.py @@ -24,6 +24,11 @@ def list_flavors(): return [flavor.as_dict() for flavor in flavor_models] +def get_flavor(flavorid): + flavor = db_api.Connection.get_flavor_by_uuid(flavorid) + return flavor.as_dict() + + def create_flavor(values): flavor = db_api.Connection.create_flavor(values) return flavor.as_dict() diff --git a/valence/controller/nodes.py b/valence/controller/nodes.py index 9754718..6ce4094 100644 --- a/valence/controller/nodes.py +++ b/valence/controller/nodes.py @@ -15,6 +15,7 @@ import six from valence.common import utils +from valence.controller import flavors from valence.db import api as db_api from valence.redfish import redfish @@ -26,6 +27,32 @@ class Node(object): return {key: node_info[key] for key in six.iterkeys(node_info) if key in ["uuid", "name", "links"]} + @staticmethod + def _create_compose_request(name, description, requirements): + request = {} + + request["Name"] = name + request["Description"] = description + + memory = {} + if "memory" in requirements: + if "capacity_mib" in requirements["memory"]: + memory["CapacityMiB"] = requirements["memory"]["capacity_mib"] + if "type" in requirements["memory"]: + memory["DimmDeviceType"] = requirements["memory"]["type"] + request["Memory"] = [memory] + + processor = {} + if "processor" in requirements: + if "model" in requirements["processor"]: + processor["Model"] = requirements["processor"]["model"] + if "total_cores" in requirements["processor"]: + processor["TotalCores"] = ( + requirements["processor"]["total_cores"]) + request["Processors"] = [processor] + + return request + @classmethod def compose_node(cls, request_body): """Compose new node @@ -34,8 +61,26 @@ class Node(object): return: brief info of this new composed node """ + if "flavor_id" in request_body: + flavor = flavors.get_flavor(request_body["flavor_id"]) + requirements = flavor["properties"] + elif "properties" in request_body: + requirements = request_body["properties"] + else: + requirements = { + "memory": {}, + "processor": {} + } + + name = request_body["name"] + description = request_body["description"] + + compose_request = cls._create_compose_request(name, + description, + requirements) + # Call redfish to compose new node - composed_node = redfish.compose_node(request_body) + composed_node = redfish.compose_node(compose_request) composed_node["uuid"] = utils.generate_uuid() diff --git a/valence/tests/unit/controller/fakes.py b/valence/tests/unit/controller/fakes.py index 2e45132..e0aa656 100644 --- a/valence/tests/unit/controller/fakes.py +++ b/valence/tests/unit/controller/fakes.py @@ -46,7 +46,7 @@ def get_test_composed_node(**kwargs): 'vlans': [{'status': 'Enabled', 'vlanid': 99}]}], 'processor': [{'instruction_set': None, - 'model': None, + 'model': 'None', 'speed_mhz': 3700, - 'total_core': 0}]}) + 'total_core': 2}]}) } diff --git a/valence/tests/unit/controller/test_nodes.py b/valence/tests/unit/controller/test_nodes.py index 2b7ec56..093ee2f 100644 --- a/valence/tests/unit/controller/test_nodes.py +++ b/valence/tests/unit/controller/test_nodes.py @@ -19,6 +19,7 @@ import mock 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 class TestAPINodes(unittest.TestCase): @@ -39,6 +40,37 @@ class TestAPINodes(unittest.TestCase): self.assertEqual(expected, nodes.Node._show_node_brief_info(node_info)) + def test_create_compose_request(self): + name = "test_request" + description = "request for testing purposes" + requirements = { + "memory": { + "capacity_mib": "4000", + "type": "DDR3" + }, + "processor": { + "model": "Intel", + "total_cores": "4" + } + } + + expected = { + "Name": "test_request", + "Description": "request for testing purposes", + "Memory": [{ + "CapacityMiB": "4000", + "DimmDeviceType": "DDR3" + }], + "Processors": [{ + "Model": "Intel", + "TotalCores": "4" + }] + } + result = nodes.Node._create_compose_request(name, + description, + requirements) + self.assertEqual(expected, result) + @mock.patch("valence.db.api.Connection.create_composed_node") @mock.patch("valence.common.utils.generate_uuid") @mock.patch("valence.redfish.redfish.compose_node") @@ -55,12 +87,46 @@ class TestAPINodes(unittest.TestCase): uuid = 'ea8e2a25-2901-438d-8157-de7ffd68d051' mock_generate_uuid.return_value = uuid - result = nodes.Node.compose_node({"name": "test"}) + result = nodes.Node.compose_node( + {"name": node_hw["name"], + "description": node_hw["description"]}) expected = nodes.Node._show_node_brief_info(node_hw) self.assertEqual(expected, result) mock_db_create_composed_node.assert_called_once_with(node_db) + @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.controller.flavors.get_flavor") + def test_compose_node_with_flavor(self, mock_get_flavor, + mock_redfish_compose_node, + mock_generate_uuid, + mock_db_create_composed_node): + """Test node composition using a flavor for requirements""" + node_hw = fakes.get_test_composed_node() + node_db = {"uuid": node_hw["uuid"], + "index": node_hw["index"], + "name": node_hw["name"], + "links": node_hw["links"]} + + mock_redfish_compose_node.return_value = node_hw + uuid = 'ea8e2a25-2901-438d-8157-de7ffd68d051' + mock_generate_uuid.return_value = uuid + + flavor = flavor_fakes.fake_flavor() + mock_get_flavor.return_value = flavor + + result = nodes.Node.compose_node( + {"name": node_hw["name"], + "description": node_hw["description"], + "flavor_id": flavor["uuid"]}) + expected = nodes.Node._show_node_brief_info(node_hw) + + self.assertEqual(expected, result) + 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( diff --git a/valence/tests/unit/fakes/flavor_fakes.py b/valence/tests/unit/fakes/flavor_fakes.py index b0f4653..d0556a4 100644 --- a/valence/tests/unit/fakes/flavor_fakes.py +++ b/valence/tests/unit/fakes/flavor_fakes.py @@ -23,7 +23,7 @@ def fake_flavor(): "type": "DDR2" }, "processor": { - "total_cores": "10", + "total_cores": "2", "model": "Intel" } }