diff options
author | Jenkins <jenkins@review.openstack.org> | 2013-01-23 19:32:17 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2013-01-23 19:32:17 +0000 |
commit | e6907236a4792b7718ac16588523bb0ebf864c96 (patch) | |
tree | b491920bd59c1648e587949adf85afb1d5364961 | |
parent | 01f1f39c18c24366622c0160dd63d5b04e0edd18 (diff) | |
parent | 0a8cc37c0c99bca25e0e02688598597aeeece7a4 (diff) | |
download | nova-e6907236a4792b7718ac16588523bb0ebf864c96.tar.gz nova-e6907236a4792b7718ac16588523bb0ebf864c96.tar.xz nova-e6907236a4792b7718ac16588523bb0ebf864c96.zip |
Merge "Add REST api to manage bare-metal nodes"
37 files changed, 849 insertions, 0 deletions
diff --git a/doc/api_samples/all_extensions/extensions-get-resp.json b/doc/api_samples/all_extensions/extensions-get-resp.json index bd002c080..604ad6763 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.json +++ b/doc/api_samples/all_extensions/extensions-get-resp.json @@ -89,6 +89,14 @@ "updated": "2012-08-09T00:00:00+00:00" }, { + "alias": "os-baremetal-nodes", + "description": "Admin-only bare-metal node administration.", + "links": [], + "name": "BareMetalNodes", + "namespace": "http://docs.openstack.org/compute/ext/baremetal_nodes/api/v2", + "updated": "2013-01-04T00:00:00+00:00" + }, + { "alias": "os-cells", "description": "Enables cells-related functionality such as adding neighbor cells,\n listing neighbor cells, and getting the capabilities of the local cell.\n ", "links": [], diff --git a/doc/api_samples/all_extensions/extensions-get-resp.xml b/doc/api_samples/all_extensions/extensions-get-resp.xml index ebb1c4302..d7f483745 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.xml +++ b/doc/api_samples/all_extensions/extensions-get-resp.xml @@ -37,6 +37,9 @@ <extension alias="os-availability-zone" updated="2012-08-09T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/availabilityzone/api/v1.1" name="AvailabilityZone"> <description>Add availability_zone to the Create Server v1.1 API.</description> </extension> + <extension alias="os-baremetal-nodes" updated="2013-01-04T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/baremetal_nodes/api/v2" name="BareMetalNodes"> + <description>Admin-only bare-metal node administration.</description> + </extension> <extension alias="os-cells" updated="2011-09-21T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/cells/api/v1.1" name="Cells"> <description>Enables cells-related functionality such as adding child cells, listing child cells, getting the capabilities of the local cell, diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json new file mode 100644 index 000000000..2e795e483 --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json @@ -0,0 +1,5 @@ +{ + "add_interface": { + "address": "aa:aa:aa:aa:aa:aa" + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml new file mode 100644 index 000000000..63ca9c21e --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<add_interface + address="aa:aa:aa:aa:aa:aa" +/>
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json new file mode 100644 index 000000000..d0b9cc3fb --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json @@ -0,0 +1,8 @@ +{ + "interface": { + "address": "aa:aa:aa:aa:aa:aa", + "datapath_id": null, + "id": 1, + "port_no": null + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml new file mode 100644 index 000000000..1da1dd284 --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml @@ -0,0 +1,2 @@ +<?xml version='1.0' encoding='UTF-8'?> +<interface datapath_id="None" id="1" port_no="None" address="aa:aa:aa:aa:aa:aa"/>
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.json new file mode 100644 index 000000000..d8b9eb452 --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.json @@ -0,0 +1,14 @@ +{ + "node": { + "service_host": "host", + "cpus": 8, + "memory_mb": 8192, + "local_gb": 128, + "pm_address": "10.1.2.3", + "pm_user": "pm_user", + "pm_password": "pm_pass", + "prov_mac_address": "12:34:56:78:90:ab", + "prov_vlan_id": 1234, + "terminal_port": 8000 + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml new file mode 100644 index 000000000..85c863a97 --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node + service_host="host" + cpus="8" + memory_mb="8192" + local_gb="128" + pm_address="10.1.2.3" + pm_user="pm_user" + prov_mac_address="12:34:56:78:90:ab" + prov_vlan_id="1234" + terminal_port="8000" +/>
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json new file mode 100644 index 000000000..b62a9e663 --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json @@ -0,0 +1,16 @@ +{ + "node": { + "cpus": 8, + "id": 1, + "instance_uuid": null, + "interfaces": [], + "local_gb": 128, + "memory_mb": 8192, + "pm_address": "10.1.2.3", + "pm_user": "pm_user", + "prov_mac_address": "12:34:56:78:90:ab", + "prov_vlan_id": 1234, + "service_host": "host", + "terminal_port": 8000 + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml new file mode 100644 index 000000000..9b8421f0f --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml @@ -0,0 +1,15 @@ +<?xml version='1.0' encoding='UTF-8'?> +<node + instance_uuid="None" + pm_address="10.1.2.3" + cpus="8" + prov_vlan_id="1234" + memory_mb="8192" + prov_mac_address="12:34:56:78:90:ab" + service_host="host" + local_gb="128" + id="1" + pm_user="pm_user" + terminal_port="8000"> + <interfaces/> +</node>
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json new file mode 100644 index 000000000..d43d580ed --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json @@ -0,0 +1,25 @@ +{ + "nodes": [ + { + "cpus": 8, + "id": 1, + "instance_uuid": null, + "interfaces": [ + { + "address": "aa:aa:aa:aa:aa:aa", + "datapath_id": null, + "id": 1, + "port_no": null + } + ], + "local_gb": 128, + "memory_mb": 8192, + "pm_address": "10.1.2.3", + "pm_user": "pm_user", + "prov_mac_address": "12:34:56:78:90:ab", + "prov_vlan_id": 1234, + "service_host": "host", + "terminal_port": 8000 + } + ] +}
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml new file mode 100644 index 000000000..7cd1b5d8a --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml @@ -0,0 +1,23 @@ +<?xml version='1.0' encoding='UTF-8'?> +<nodes> + <node + instance_uuid="None" + pm_address="10.1.2.3" + cpus="8" + prov_vlan_id="1234" + memory_mb="8192" + prov_mac_address="12:34:56:78:90:ab" + service_host="host" + local_gb="128" + id="1" + pm_user="pm_user" + terminal_port="8000"> + <interfaces> + <interface + datapath_id="None" + id="1" + port_no="None" + address="aa:aa:aa:aa:aa:aa"/> + </interfaces> + </node> +</nodes>
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json new file mode 100644 index 000000000..0ce85577d --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json @@ -0,0 +1,5 @@ +{ + "remove_interface": { + "address": "aa:aa:aa:aa:aa:aa" + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml new file mode 100644 index 000000000..6457b059b --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<remove_interface + address="aa:aa:aa:aa:aa:aa" +/>
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json new file mode 100644 index 000000000..d42365752 --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json @@ -0,0 +1,23 @@ +{ + "node": { + "cpus": 8, + "id": 1, + "instance_uuid": null, + "interfaces": [ + { + "address": "aa:aa:aa:aa:aa:aa", + "datapath_id": null, + "id": 1, + "port_no": null + } + ], + "local_gb": 128, + "memory_mb": 8192, + "pm_address": "10.1.2.3", + "pm_user": "pm_user", + "prov_mac_address": "12:34:56:78:90:ab", + "prov_vlan_id": 1234, + "service_host": "host", + "terminal_port": 8000 + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml new file mode 100644 index 000000000..6d5f9719f --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml @@ -0,0 +1,21 @@ +<?xml version='1.0' encoding='UTF-8'?> +<node + instance_uuid="None" + pm_address="10.1.2.3" + cpus="8" + prov_vlan_id="1234" + memory_mb="8192" + prov_mac_address="12:34:56:78:90:ab" + service_host="host" + local_gb="128" + id="1" + pm_user="pm_user" + terminal_port="8000"> + <interfaces> + <interface + datapath_id="None" + id="1" + port_no="None" + address="aa:aa:aa:aa:aa:aa"/> + </interfaces> +</node>
\ No newline at end of file diff --git a/etc/nova/policy.json b/etc/nova/policy.json index f85ab9758..d94ec7192 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -29,6 +29,7 @@ "compute_extension:admin_actions:migrate": "rule:admin_api", "compute_extension:aggregates": "rule:admin_api", "compute_extension:agents": "rule:admin_api", + "compute_extension:baremetal_nodes": "rule:admin_api", "compute_extension:cells": "rule:admin_api", "compute_extension:certificates": "", "compute_extension:cloudpipe": "rule:admin_api", diff --git a/nova/api/openstack/compute/contrib/baremetal_nodes.py b/nova/api/openstack/compute/contrib/baremetal_nodes.py new file mode 100644 index 000000000..38d66d2ae --- /dev/null +++ b/nova/api/openstack/compute/contrib/baremetal_nodes.py @@ -0,0 +1,210 @@ +# Copyright (c) 2013 NTT DOCOMO, INC. +# All Rights Reserved. +# +# 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 +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""The bare-metal admin extension.""" + +import webob + +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil +from nova import exception +from nova.openstack.common import log as logging +from nova.virt.baremetal import db + +LOG = logging.getLogger(__name__) +authorize = extensions.extension_authorizer('compute', 'baremetal_nodes') + +node_fields = ['id', 'cpus', 'local_gb', 'memory_mb', 'pm_address', + 'pm_user', 'prov_mac_address', 'prov_vlan_id', + 'service_host', 'terminal_port', 'instance_uuid', + ] + +interface_fields = ['id', 'address', 'datapath_id', 'port_no'] + + +def _node_dict(node_ref): + d = {} + for f in node_fields: + d[f] = node_ref.get(f) + return d + + +def _interface_dict(interface_ref): + d = {} + for f in interface_fields: + d[f] = interface_ref.get(f) + return d + + +def _make_node_elem(elem): + for f in node_fields: + elem.set(f) + + +def _make_interface_elem(elem): + for f in interface_fields: + elem.set(f) + + +class NodeTemplate(xmlutil.TemplateBuilder): + def construct(self): + node_elem = xmlutil.TemplateElement('node', selector='node') + _make_node_elem(node_elem) + ifs_elem = xmlutil.TemplateElement('interfaces') + if_elem = xmlutil.SubTemplateElement(ifs_elem, 'interface', + selector='interfaces') + _make_interface_elem(if_elem) + node_elem.append(ifs_elem) + return xmlutil.MasterTemplate(node_elem, 1) + + +class NodesTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('nodes') + node_elem = xmlutil.SubTemplateElement(root, 'node', selector='nodes') + _make_node_elem(node_elem) + ifs_elem = xmlutil.TemplateElement('interfaces') + if_elem = xmlutil.SubTemplateElement(ifs_elem, 'interface', + selector='interfaces') + _make_interface_elem(if_elem) + node_elem.append(ifs_elem) + return xmlutil.MasterTemplate(root, 1) + + +class InterfaceTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('interface', selector='interface') + _make_interface_elem(root) + return xmlutil.MasterTemplate(root, 1) + + +class BareMetalNodeController(wsgi.Controller): + """The Bare-Metal Node API controller for the OpenStack API.""" + + @wsgi.serializers(xml=NodesTemplate) + def index(self, req): + context = req.environ['nova.context'] + authorize(context) + nodes_from_db = db.bm_node_get_all(context) + nodes = [] + for node_from_db in nodes_from_db: + try: + ifs = db.bm_interface_get_all_by_bm_node_id( + context, node_from_db['id']) + except exception.InstanceNotFound: + ifs = [] + node = _node_dict(node_from_db) + node['interfaces'] = [_interface_dict(i) for i in ifs] + nodes.append(node) + return {'nodes': nodes} + + @wsgi.serializers(xml=NodeTemplate) + def show(self, req, id): + context = req.environ['nova.context'] + authorize(context) + try: + node = db.bm_node_get(context, id) + except exception.InstanceNotFound: + raise webob.exc.HTTPNotFound + try: + ifs = db.bm_interface_get_all_by_bm_node_id(context, id) + except exception.InstanceNotFound: + ifs = [] + node = _node_dict(node) + node['interfaces'] = [_interface_dict(i) for i in ifs] + return {'node': node} + + @wsgi.serializers(xml=NodeTemplate) + def create(self, req, body): + context = req.environ['nova.context'] + authorize(context) + node = db.bm_node_create(context, body['node']) + node = _node_dict(node) + node['interfaces'] = [] + return {'node': node} + + def delete(self, req, id): + context = req.environ['nova.context'] + authorize(context) + try: + db.bm_node_destroy(context, id) + except exception.InstanceNotFound: + raise webob.exc.HTTPNotFound + return webob.Response(status_int=202) + + def _check_node_exists(self, context, node_id): + try: + db.bm_node_get(context, node_id) + except exception.InstanceNotFound: + raise webob.exc.HTTPNotFound + + @wsgi.serializers(xml=InterfaceTemplate) + @wsgi.action('add_interface') + def _add_interface(self, req, id, body): + context = req.environ['nova.context'] + authorize(context) + self._check_node_exists(context, id) + body = body['add_interface'] + address = body['address'] + datapath_id = body.get('datapath_id') + port_no = body.get('port_no') + if_id = db.bm_interface_create(context, + bm_node_id=id, + address=address, + datapath_id=datapath_id, + port_no=port_no) + if_ref = db.bm_interface_get(context, if_id) + return {'interface': _interface_dict(if_ref)} + + @wsgi.response(202) + @wsgi.action('remove_interface') + def _remove_interface(self, req, id, body): + context = req.environ['nova.context'] + authorize(context) + self._check_node_exists(context, id) + body = body['remove_interface'] + print "body(%s)" % body + if_id = body.get('id') + address = body.get('address') + if not if_id and not address: + raise webob.exc.HTTPBadRequest( + explanation=_("Must specify id or address")) + ifs = db.bm_interface_get_all_by_bm_node_id(context, id) + for i in ifs: + if if_id and if_id != i['id']: + continue + if address and address != i['address']: + continue + db.bm_interface_destroy(context, i['id']) + return webob.Response(status_int=202) + raise webob.exc.HTTPNotFound + + +class Baremetal_nodes(extensions.ExtensionDescriptor): + """Admin-only bare-metal node administration.""" + + name = "BareMetalNodes" + alias = "os-baremetal-nodes" + namespace = "http://docs.openstack.org/compute/ext/baremetal_nodes/api/v2" + updated = "2013-01-04T00:00:00+00:00" + + def get_resources(self): + resources = [] + res = extensions.ResourceExtension('os-baremetal-nodes', + BareMetalNodeController(), + member_actions={"action": "POST", }) + resources.append(res) + return resources diff --git a/nova/tests/api/openstack/compute/contrib/test_baremetal_nodes.py b/nova/tests/api/openstack/compute/contrib/test_baremetal_nodes.py new file mode 100644 index 000000000..381d452a7 --- /dev/null +++ b/nova/tests/api/openstack/compute/contrib/test_baremetal_nodes.py @@ -0,0 +1,197 @@ +# Copyright (c) 2013 NTT DOCOMO, INC. +# All Rights Reserved. +# +# 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 +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from webob import exc + +from nova.api.openstack.compute.contrib import baremetal_nodes +from nova import context +from nova import exception +from nova import test +from nova.virt.baremetal import db + + +class FakeRequest(object): + + def __init__(self, context): + self.environ = {"nova.context": context} + + +class BareMetalNodesTest(test.TestCase): + + def setUp(self): + super(BareMetalNodesTest, self).setUp() + + self.context = context.get_admin_context() + self.controller = baremetal_nodes.BareMetalNodeController() + self.request = FakeRequest(self.context) + + def test_create(self): + node = { + 'service_host': "host", + 'cpus': 8, + 'memory_mb': 8192, + 'local_gb': 128, + 'pm_address': "10.1.2.3", + 'pm_user': "pm_user", + 'pm_password': "pm_pass", + 'prov_mac_address': "12:34:56:78:90:ab", + 'prov_vlan_id': 1234, + 'terminal_port': 8000, + 'interfaces': [], + } + response = node.copy() + response['id'] = 100 + del response['pm_password'] + response['instance_uuid'] = None + self.mox.StubOutWithMock(db, 'bm_node_create') + db.bm_node_create(self.context, node).AndReturn(response) + self.mox.ReplayAll() + res_dict = self.controller.create(self.request, {'node': node}) + self.assertEqual({'node': response}, res_dict) + + def test_delete(self): + self.mox.StubOutWithMock(db, 'bm_node_destroy') + db.bm_node_destroy(self.context, 1) + self.mox.ReplayAll() + self.controller.delete(self.request, 1) + + def test_index(self): + nodes = [{'id': 1}, + {'id': 2}, + ] + interfaces = [{'id': 1, 'address': '11:11:11:11:11:11'}, + {'id': 2, 'address': '22:22:22:22:22:22'}, + ] + self.mox.StubOutWithMock(db, 'bm_node_get_all') + self.mox.StubOutWithMock(db, 'bm_interface_get_all_by_bm_node_id') + db.bm_node_get_all(self.context).AndReturn(nodes) + db.bm_interface_get_all_by_bm_node_id(self.context, 1).\ + AndRaise(exception.InstanceNotFound(instance_id=1)) + db.bm_interface_get_all_by_bm_node_id(self.context, 2).\ + AndReturn(interfaces) + self.mox.ReplayAll() + res_dict = self.controller.index(self.request) + self.assertEqual(2, len(res_dict['nodes'])) + self.assertEqual([], res_dict['nodes'][0]['interfaces']) + self.assertEqual(2, len(res_dict['nodes'][1]['interfaces'])) + + def test_show(self): + node_id = 1 + node = {'id': node_id} + interfaces = [{'id': 1, 'address': '11:11:11:11:11:11'}, + {'id': 2, 'address': '22:22:22:22:22:22'}, + ] + self.mox.StubOutWithMock(db, 'bm_node_get') + self.mox.StubOutWithMock(db, 'bm_interface_get_all_by_bm_node_id') + db.bm_node_get(self.context, node_id).AndReturn(node) + db.bm_interface_get_all_by_bm_node_id(self.context, node_id).\ + AndReturn(interfaces) + self.mox.ReplayAll() + res_dict = self.controller.show(self.request, node_id) + self.assertEqual(node_id, res_dict['node']['id']) + self.assertEqual(2, len(res_dict['node']['interfaces'])) + + def test_add_interface(self): + node_id = 1 + address = '11:22:33:44:55:66' + body = {'add_interface': {'address': address}} + self.mox.StubOutWithMock(db, 'bm_node_get') + self.mox.StubOutWithMock(db, 'bm_interface_create') + self.mox.StubOutWithMock(db, 'bm_interface_get') + db.bm_node_get(self.context, node_id) + db.bm_interface_create(self.context, + bm_node_id=node_id, + address=address, + datapath_id=None, + port_no=None).\ + AndReturn(12345) + db.bm_interface_get(self.context, 12345).\ + AndReturn({'id': 12345, 'address': address}) + self.mox.ReplayAll() + res_dict = self.controller._add_interface(self.request, node_id, body) + self.assertEqual(12345, res_dict['interface']['id']) + self.assertEqual(address, res_dict['interface']['address']) + + def test_remove_interface(self): + node_id = 1 + interfaces = [{'id': 1}, + {'id': 2}, + {'id': 3}, + ] + body = {'remove_interface': {'id': 2}} + self.mox.StubOutWithMock(db, 'bm_node_get') + self.mox.StubOutWithMock(db, 'bm_interface_get_all_by_bm_node_id') + self.mox.StubOutWithMock(db, 'bm_interface_destroy') + db.bm_node_get(self.context, node_id) + db.bm_interface_get_all_by_bm_node_id(self.context, node_id).\ + AndReturn(interfaces) + db.bm_interface_destroy(self.context, 2) + self.mox.ReplayAll() + self.controller._remove_interface(self.request, node_id, body) + + def test_remove_interface_by_address(self): + node_id = 1 + interfaces = [{'id': 1, 'address': '11:11:11:11:11:11'}, + {'id': 2, 'address': '22:22:22:22:22:22'}, + {'id': 3, 'address': '33:33:33:33:33:33'}, + ] + self.mox.StubOutWithMock(db, 'bm_node_get') + self.mox.StubOutWithMock(db, 'bm_interface_get_all_by_bm_node_id') + self.mox.StubOutWithMock(db, 'bm_interface_destroy') + db.bm_node_get(self.context, node_id) + db.bm_interface_get_all_by_bm_node_id(self.context, node_id).\ + AndReturn(interfaces) + db.bm_interface_destroy(self.context, 2) + self.mox.ReplayAll() + body = {'remove_interface': {'address': '22:22:22:22:22:22'}} + self.controller._remove_interface(self.request, node_id, body) + + def test_remove_interface_no_id_no_address(self): + node_id = 1 + self.mox.StubOutWithMock(db, 'bm_node_get') + db.bm_node_get(self.context, node_id) + self.mox.ReplayAll() + body = {'remove_interface': {}} + self.assertRaises(exc.HTTPBadRequest, + self.controller._remove_interface, + self.request, + node_id, + body) + + def test_add_interface_node_not_found(self): + node_id = 1 + self.mox.StubOutWithMock(db, 'bm_node_get') + db.bm_node_get(self.context, node_id).\ + AndRaise(exception.InstanceNotFound(instance_id=node_id)) + self.mox.ReplayAll() + body = {'add_interface': {'address': '11:11:11:11:11:11'}} + self.assertRaises(exc.HTTPNotFound, + self.controller._add_interface, + self.request, + node_id, + body) + + def test_remove_interface_node_not_found(self): + node_id = 1 + self.mox.StubOutWithMock(db, 'bm_node_get') + db.bm_node_get(self.context, node_id).\ + AndRaise(exception.InstanceNotFound(instance_id=node_id)) + self.mox.ReplayAll() + body = {'remove_interface': {'address': '11:11:11:11:11:11'}} + self.assertRaises(exc.HTTPNotFound, + self.controller._remove_interface, + self.request, + node_id, + body) diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index 04e4adbbd..d8780744d 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -105,6 +105,7 @@ policy_data = """ "compute_extension:admin_actions:migrate": "", "compute_extension:aggregates": "", "compute_extension:agents": "", + "compute_extension:baremetal_nodes": "", "compute_extension:cells": "", "compute_extension:certificates": "", "compute_extension:cloudpipe": "", diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl index fe0613646..be2fabec4 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl @@ -89,6 +89,14 @@ "updated": "%(timestamp)s" }, { + "alias": "os-baremetal-nodes", + "description": "%(text)s", + "links": [], + "name": "BareMetalNodes", + "namespace": "http://docs.openstack.org/compute/ext/baremetal_nodes/api/v2", + "updated": "%(timestamp)s" + }, + { "alias": "os-cells", "description": "%(text)s", "links": [], diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl index 2051d891a..ae2e9ff9e 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl @@ -33,6 +33,9 @@ <extension alias="os-agents" name="Agents" namespace="http://docs.openstack.org/compute/ext/agents/api/v2" updated="%(timestamp)s"> <description>%(text)s</description> </extension> + <extension alias="os-baremetal-nodes" name="BareMetalNodes" namespace="http://docs.openstack.org/compute/ext/baremetal_nodes/api/v2" updated="%(timestamp)s"> + <description>%(text)s</description> + </extension> <extension alias="os-cells" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/cells/api/v1.1" name="Cells"> <description>%(text)s</description> </extension> diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json.tpl new file mode 100644 index 000000000..fbc9e5b8d --- /dev/null +++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json.tpl @@ -0,0 +1,5 @@ +{ + "add_interface": { + "address": "%(address)s" + } +} diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml.tpl new file mode 100644 index 000000000..abbbe895b --- /dev/null +++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml.tpl @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<add_interface + address="%(address)s" +/> diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json.tpl new file mode 100644 index 000000000..268b41f08 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json.tpl @@ -0,0 +1,8 @@ +{ + "interface": { + "id": %(interface_id)s, + "address": "aa:aa:aa:aa:aa:aa", + "datapath_id": null, + "port_no": null + } +} diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml.tpl new file mode 100644 index 000000000..e5d34f92b --- /dev/null +++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml.tpl @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface + id="%(interface_id)s" + address="aa:aa:aa:aa:aa:aa" + datapath_id="None" + port_no="None" +/> diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-req.json.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-req.json.tpl new file mode 100644 index 000000000..fd2ae101f --- /dev/null +++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-req.json.tpl @@ -0,0 +1,14 @@ +{ + "node": { + "service_host": "host", + "cpus": 8, + "memory_mb": 8192, + "local_gb": 128, + "pm_address": "10.1.2.3", + "pm_user": "pm_user", + "pm_password": "pm_pass", + "prov_mac_address": "12:34:56:78:90:ab", + "prov_vlan_id": 1234, + "terminal_port": 8000 + } +} diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml.tpl new file mode 100644 index 000000000..78a2c1c74 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml.tpl @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node + service_host="host" + cpus="8" + memory_mb="8192" + local_gb="128" + pm_address="10.1.2.3" + pm_user="pm_user" + prov_mac_address="12:34:56:78:90:ab" + prov_vlan_id="1234" + terminal_port="8000" +/> diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json.tpl new file mode 100644 index 000000000..d3911b49d --- /dev/null +++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json.tpl @@ -0,0 +1,16 @@ +{ + "node": { + "service_host": "host", + "cpus": 8, + "memory_mb": 8192, + "local_gb": 128, + "pm_address": "10.1.2.3", + "pm_user": "pm_user", + "prov_mac_address": "12:34:56:78:90:ab", + "prov_vlan_id": 1234, + "terminal_port": 8000, + "instance_uuid": null, + "id": %(node_id)s, + "interfaces": [] + } +} diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml.tpl new file mode 100644 index 000000000..f21d16a11 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml.tpl @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node + service_host="host" + cpus="8" + memory_mb="8192" + local_gb="128" + pm_address="10.1.2.3" + pm_user="pm_user" + prov_mac_address="12:34:56:78:90:ab" + prov_vlan_id="1234" + terminal_port="8000" + instance_uuid="None" + id="%(node_id)s"> + <interfaces/> +</node> diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json.tpl new file mode 100644 index 000000000..9b04a9cea --- /dev/null +++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json.tpl @@ -0,0 +1,21 @@ +{ + "nodes": [{ + "service_host": "host", + "cpus": 8, + "memory_mb": 8192, + "local_gb": 128, + "pm_address": "10.1.2.3", + "pm_user": "pm_user", + "prov_mac_address": "12:34:56:78:90:ab", + "prov_vlan_id": 1234, + "terminal_port": 8000, + "instance_uuid": null, + "id": %(node_id)s, + "interfaces": [{ + "id": %(interface_id)s, + "address": "%(address)s", + "datapath_id": null, + "port_no": null + }] + }] +} diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml.tpl new file mode 100644 index 000000000..f17b6cc20 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml.tpl @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<nodes> +<node + service_host="host" + cpus="8" + memory_mb="8192" + local_gb="128" + pm_address="10.1.2.3" + pm_user="pm_user" + prov_mac_address="12:34:56:78:90:ab" + prov_vlan_id="1234" + terminal_port="8000" + instance_uuid="None" + id="%(node_id)s"> + <interfaces> + <interface id="%(interface_id)s" address="%(address)s" datapath_id="None" port_no="None"/> + </interfaces> +</node> +</nodes> diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json.tpl new file mode 100644 index 000000000..eb76a9140 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json.tpl @@ -0,0 +1,5 @@ +{ + "remove_interface": { + "address": "%(address)s" + } +} diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml.tpl new file mode 100644 index 000000000..089c94e86 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml.tpl @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<remove_interface + address="%(address)s" +/> diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json.tpl new file mode 100644 index 000000000..701b33d24 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json.tpl @@ -0,0 +1,21 @@ +{ + "node": { + "service_host": "host", + "cpus": 8, + "memory_mb": 8192, + "local_gb": 128, + "pm_address": "10.1.2.3", + "pm_user": "pm_user", + "prov_mac_address": "12:34:56:78:90:ab", + "prov_vlan_id": 1234, + "terminal_port": 8000, + "instance_uuid": null, + "id": %(node_id)s, + "interfaces": [{ + "id": %(interface_id)s, + "address": "%(address)s", + "datapath_id": null, + "port_no": null + }] + } +} diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml.tpl new file mode 100644 index 000000000..36e5568e5 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml.tpl @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node + service_host="host" + cpus="8" + memory_mb="8192" + local_gb="128" + pm_address="10.1.2.3" + pm_user="pm_user" + prov_mac_address="12:34:56:78:90:ab" + prov_vlan_id="1234" + terminal_port="8000" + instance_uuid="None" + id="%(node_id)s"> + <interfaces> + <interface id="%(interface_id)s" address="%(address)s" datapath_id="None" port_no="None"/> + </interfaces> +</node> diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py index f101da243..887ca206f 100644 --- a/nova/tests/integrated/test_api_samples.py +++ b/nova/tests/integrated/test_api_samples.py @@ -43,6 +43,7 @@ from nova.openstack.common import timeutils import nova.quota from nova.scheduler import driver from nova import test +from nova.tests.baremetal.db import base as bm_db_base from nova.tests import fake_network from nova.tests.image import fake from nova.tests.integrated import integrated_helpers @@ -2589,3 +2590,75 @@ class CellsSampleJsonTest(ApiSampleTestBase): class CellsSampleXmlTest(CellsSampleJsonTest): ctype = 'xml' + + +class BareMetalNodesJsonTest(ApiSampleTestBase, bm_db_base.BMDBTestCase): + extension_name = ('nova.api.openstack.compute.contrib.baremetal_nodes.' + 'Baremetal_nodes') + + def _create_node(self): + response = self._do_post("os-baremetal-nodes", + "baremetal-node-create-req", + {}) + self.assertEqual(response.status, 200) + subs = {'node_id': '(?P<id>\d+)'} + return self._verify_response("baremetal-node-create-resp", + subs, response) + + def test_create_node(self): + self._create_node() + + def test_list_nodes(self): + node_id = self._create_node() + interface_id = self._add_interface(node_id) + response = self._do_get('os-baremetal-nodes') + self.assertEqual(response.status, 200) + subs = {'node_id': node_id, + 'interface_id': interface_id, + 'address': 'aa:aa:aa:aa:aa:aa', + } + return self._verify_response('baremetal-node-list-resp', + subs, response) + + def test_show_node(self): + node_id = self._create_node() + interface_id = self._add_interface(node_id) + response = self._do_get('os-baremetal-nodes/%s' % node_id) + self.assertEqual(response.status, 200) + subs = {'node_id': node_id, + 'interface_id': interface_id, + 'address': 'aa:aa:aa:aa:aa:aa', + } + return self._verify_response('baremetal-node-show-resp', + subs, response) + + def test_delete_node(self): + node_id = self._create_node() + response = self._do_delete("os-baremetal-nodes/%s" % node_id) + self.assertEqual(response.status, 202) + + def _add_interface(self, node_id): + response = self._do_post("os-baremetal-nodes/%s/action" % node_id, + "baremetal-node-add-interface-req", + {'address': 'aa:aa:aa:aa:aa:aa'}) + self.assertEqual(response.status, 200) + subs = {'interface_id': r'(?P<id>\d+)'} + return self._verify_response("baremetal-node-add-interface-resp", + subs, response) + + def test_add_interface(self): + node_id = self._create_node() + self._add_interface(node_id) + + def test_remove_interface(self): + node_id = self._create_node() + self._add_interface(node_id) + response = self._do_post("os-baremetal-nodes/%s/action" % node_id, + "baremetal-node-remove-interface-req", + {'address': 'aa:aa:aa:aa:aa:aa'}) + self.assertEqual(response.status, 202) + self.assertEqual(response.read(), "") + + +class BareMetalNodesXmlTest(BareMetalNodesJsonTest): + ctype = 'xml' |