Add REST api to manage bare-metal nodes

* create/delete/list/show bare-metal nodes
* add/remove interfaces to/from bare-metal nodes

blueprint general-bare-metal-provisioning-framework

Change-Id: I1e76f7e3f7f74087e844cfb23dc92154f4c3e127
This commit is contained in:
Arata Notsu 2013-01-23 17:32:46 +09:00
parent cd4093e0f2
commit 0a8cc37c0c
37 changed files with 849 additions and 0 deletions

View File

@ -88,6 +88,14 @@
"namespace": "http://docs.openstack.org/compute/ext/availabilityzone/api/v1.1",
"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 ",

View File

@ -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,

View File

@ -0,0 +1,5 @@
{
"add_interface": {
"address": "aa:aa:aa:aa:aa:aa"
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<add_interface
address="aa:aa:aa:aa:aa:aa"
/>

View File

@ -0,0 +1,8 @@
{
"interface": {
"address": "aa:aa:aa:aa:aa:aa",
"datapath_id": null,
"id": 1,
"port_no": null
}
}

View File

@ -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"/>

View File

@ -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
}
}

View File

@ -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"
/>

View File

@ -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
}
}

View File

@ -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>

View File

@ -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
}
]
}

View File

@ -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>

View File

@ -0,0 +1,5 @@
{
"remove_interface": {
"address": "aa:aa:aa:aa:aa:aa"
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<remove_interface
address="aa:aa:aa:aa:aa:aa"
/>

View File

@ -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
}
}

View File

@ -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>

View File

@ -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",

View File

@ -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

View File

@ -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)

View File

@ -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": "",

View File

@ -88,6 +88,14 @@
"namespace": "http://docs.openstack.org/compute/ext/availabilityzone/api/v1.1",
"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",

View File

@ -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>

View File

@ -0,0 +1,5 @@
{
"add_interface": {
"address": "%(address)s"
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<add_interface
address="%(address)s"
/>

View File

@ -0,0 +1,8 @@
{
"interface": {
"id": %(interface_id)s,
"address": "aa:aa:aa:aa:aa:aa",
"datapath_id": null,
"port_no": null
}
}

View File

@ -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"
/>

View File

@ -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
}
}

View File

@ -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"
/>

View File

@ -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": []
}
}

View File

@ -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>

View File

@ -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
}]
}]
}

View File

@ -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>

View File

@ -0,0 +1,5 @@
{
"remove_interface": {
"address": "%(address)s"
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<remove_interface
address="%(address)s"
/>

View File

@ -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
}]
}
}

View File

@ -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>

View File

@ -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
@ -2580,3 +2581,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'