summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/api_samples/all_extensions/extensions-get-resp.json8
-rw-r--r--doc/api_samples/all_extensions/extensions-get-resp.xml3
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json5
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml4
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json8
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml2
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.json14
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml12
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json16
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml15
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json25
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml23
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json5
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml4
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json23
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml21
-rw-r--r--etc/nova/policy.json1
-rw-r--r--nova/api/openstack/compute/contrib/baremetal_nodes.py210
-rw-r--r--nova/network/linux_net.py52
-rw-r--r--nova/network/model.py5
-rw-r--r--nova/network/quantumv2/api.py3
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_baremetal_nodes.py197
-rw-r--r--nova/tests/fake_policy.py1
-rw-r--r--nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl8
-rw-r--r--nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl3
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json.tpl5
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml.tpl4
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json.tpl8
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml.tpl7
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-req.json.tpl14
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml.tpl12
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json.tpl16
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml.tpl15
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json.tpl21
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml.tpl19
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json.tpl5
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml.tpl4
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json.tpl21
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml.tpl17
-rw-r--r--nova/tests/integrated/test_api_samples.py73
-rw-r--r--nova/tests/network/test_manager.py1
-rw-r--r--nova/tests/test_libvirt_vif.py87
-rw-r--r--nova/virt/libvirt/vif.py77
43 files changed, 986 insertions, 88 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/network/linux_net.py b/nova/network/linux_net.py
index 4fefb2db4..a9b44e94a 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -1124,6 +1124,40 @@ def _create_veth_pair(dev1_name, dev2_name):
run_as_root=True)
+def create_ovs_vif_port(bridge, dev, iface_id, mac, instance_id):
+ utils.execute('ovs-vsctl', '--', '--may-exist', 'add-port',
+ bridge, dev,
+ '--', 'set', 'Interface', dev,
+ 'external-ids:iface-id=%s' % iface_id,
+ 'external-ids:iface-status=active',
+ 'external-ids:attached-mac=%s' % mac,
+ 'external-ids:vm-uuid=%s' % instance_id,
+ run_as_root=True)
+
+
+def delete_ovs_vif_port(bridge, dev):
+ utils.execute('ovs-vsctl', 'del-port', bridge, dev,
+ run_as_root=True)
+ utils.execute('ip', 'link', 'delete', dev,
+ run_as_root=True)
+
+
+def create_tap_dev(dev, mac_address=None):
+ if not device_exists(dev):
+ try:
+ # First, try with 'ip'
+ utils.execute('ip', 'tuntap', 'add', dev, 'mode', 'tap',
+ run_as_root=True, check_exit_code=[0, 2, 254])
+ except exception.ProcessExecutionError:
+ # Second option: tunctl
+ utils.execute('tunctl', '-b', '-t', dev, run_as_root=True)
+ if mac_address:
+ utils.execute('ip', 'link', 'set', dev, 'address', mac_address,
+ run_as_root=True, check_exit_code=[0, 2, 254])
+ utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True,
+ check_exit_code=[0, 2, 254])
+
+
# Similar to compute virt layers, the Linux network node
# code uses a flexible driver model to support different ways
# of creating ethernet interfaces and attaching them to the network.
@@ -1535,7 +1569,7 @@ class QuantumLinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
iptables_manager.ipv4['filter'].add_rule('FORWARD',
'--out-interface %s -j ACCEPT' % bridge)
- QuantumLinuxBridgeInterfaceDriver.create_tap_dev(dev, mac_address)
+ create_tap_dev(dev, mac_address)
if not device_exists(bridge):
LOG.debug(_("Starting bridge %s "), bridge)
@@ -1570,22 +1604,6 @@ class QuantumLinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
LOG.debug(_("Unplugged gateway interface '%s'"), dev)
return dev
- @classmethod
- def create_tap_dev(_self, dev, mac_address=None):
- if not device_exists(dev):
- try:
- # First, try with 'ip'
- utils.execute('ip', 'tuntap', 'add', dev, 'mode', 'tap',
- run_as_root=True, check_exit_code=[0, 2, 254])
- except exception.ProcessExecutionError:
- # Second option: tunctl
- utils.execute('tunctl', '-b', '-t', dev, run_as_root=True)
- if mac_address:
- utils.execute('ip', 'link', 'set', dev, 'address', mac_address,
- run_as_root=True, check_exit_code=[0, 2, 254])
- utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True,
- check_exit_code=[0, 2, 254])
-
def get_dev(self, network):
dev = self.GATEWAY_INTERFACE_PREFIX + str(network['uuid'][0:11])
return dev
diff --git a/nova/network/model.py b/nova/network/model.py
index 0771156c1..9accb883e 100644
--- a/nova/network/model.py
+++ b/nova/network/model.py
@@ -207,7 +207,7 @@ class Network(Model):
class VIF(Model):
"""Represents a Virtual Interface in Nova."""
def __init__(self, id=None, address=None, network=None, type=None,
- devname=None, **kwargs):
+ devname=None, ovs_interfaceid=None, **kwargs):
super(VIF, self).__init__()
self['id'] = id
@@ -216,6 +216,8 @@ class VIF(Model):
self['type'] = type
self['devname'] = devname
+ self['ovs_interfaceid'] = ovs_interfaceid
+
self._set_meta(kwargs)
def __eq__(self, other):
@@ -381,6 +383,7 @@ class NetworkInfo(list):
'vif_type': vif['type'],
'vif_devname': vif.get('devname'),
'vif_uuid': vif['id'],
+ 'ovs_interfaceid': vif.get('ovs_interfaceid'),
'rxtx_cap': vif.get_meta('rxtx_cap', 0),
'dns': [get_ip(ip) for ip in subnet_v4['dns']],
'ips': [fixed_ip_dict(ip, subnet)
diff --git a/nova/network/quantumv2/api.py b/nova/network/quantumv2/api.py
index 29e5e2f06..704ed5cef 100644
--- a/nova/network/quantumv2/api.py
+++ b/nova/network/quantumv2/api.py
@@ -661,11 +661,13 @@ class API(base.Base):
if fixed_ip.is_in_subnet(subnet)]
bridge = None
+ ovs_interfaceid = None
vif_type = port.get('binding:vif_type')
# TODO(berrange) Quantum should pass the bridge name
# in another binding metadata field
if vif_type == network_model.VIF_TYPE_OVS:
bridge = CONF.quantum_ovs_bridge
+ ovs_interfaceid = port['id']
elif vif_type == network_model.VIF_TYPE_BRIDGE:
bridge = "brq" + port['network_id']
@@ -688,6 +690,7 @@ class API(base.Base):
address=port['mac_address'],
network=network,
type=port.get('binding:vif_type'),
+ ovs_interfaceid=ovs_interfaceid,
devname=devname))
return nw_info
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'
diff --git a/nova/tests/network/test_manager.py b/nova/tests/network/test_manager.py
index b5b3ec107..9ccdffab5 100644
--- a/nova/tests/network/test_manager.py
+++ b/nova/tests/network/test_manager.py
@@ -185,6 +185,7 @@ class FlatNetworkTestCase(test.TestCase):
'vif_devname': None,
'vif_uuid':
'00000000-0000-0000-0000-00000000000000%02d' % nid,
+ 'ovs_interfaceid': None,
'should_create_vlan': False,
'should_create_bridge': False}
self.assertThat(info, matchers.DictMatches(check))
diff --git a/nova/tests/test_libvirt_vif.py b/nova/tests/test_libvirt_vif.py
index 11ffa020f..3861d7dfa 100644
--- a/nova/tests/test_libvirt_vif.py
+++ b/nova/tests/test_libvirt_vif.py
@@ -27,7 +27,7 @@ CONF = cfg.CONF
class LibvirtVifTestCase(test.TestCase):
- net = {
+ net_bridge = {
'cidr': '101.168.1.0/24',
'cidr_v6': '101:1db9::/64',
'gateway_v6': '101:1db9::1',
@@ -42,15 +42,39 @@ class LibvirtVifTestCase(test.TestCase):
'id': 'network-id-xxx-yyy-zzz'
}
- mapping = {
+ mapping_bridge = {
'mac': 'ca:fe:de:ad:be:ef',
- 'gateway_v6': net['gateway_v6'],
+ 'gateway_v6': net_bridge['gateway_v6'],
'ips': [{'ip': '101.168.1.9'}],
'dhcp_server': '191.168.1.1',
'vif_uuid': 'vif-xxx-yyy-zzz',
'vif_devname': 'tap-xxx-yyy-zzz'
}
+ net_ovs = {
+ 'cidr': '101.168.1.0/24',
+ 'cidr_v6': '101:1db9::/64',
+ 'gateway_v6': '101:1db9::1',
+ 'netmask_v6': '64',
+ 'netmask': '255.255.255.0',
+ 'bridge': 'br0',
+ 'vlan': 99,
+ 'gateway': '101.168.1.1',
+ 'broadcast': '101.168.1.255',
+ 'dns1': '8.8.8.8',
+ 'id': 'network-id-xxx-yyy-zzz'
+ }
+
+ mapping_ovs = {
+ 'mac': 'ca:fe:de:ad:be:ef',
+ 'gateway_v6': net_ovs['gateway_v6'],
+ 'ips': [{'ip': '101.168.1.9'}],
+ 'dhcp_server': '191.168.1.1',
+ 'vif_uuid': 'vif-xxx-yyy-zzz',
+ 'vif_devname': 'tap-xxx-yyy-zzz',
+ 'ovs_interfaceid': 'aaa-bbb-ccc',
+ }
+
instance = {
'name': 'instance-name',
'uuid': 'instance-uuid'
@@ -67,7 +91,7 @@ class LibvirtVifTestCase(test.TestCase):
self.stubs.Set(utils, 'execute', fake_execute)
- def _get_instance_xml(self, driver):
+ def _get_instance_xml(self, driver, net, mapping):
conf = vconfig.LibvirtConfigGuest()
conf.virt_type = "qemu"
conf.name = "fake-name"
@@ -75,7 +99,7 @@ class LibvirtVifTestCase(test.TestCase):
conf.memory = 100 * 1024
conf.vcpus = 4
- nic = driver.get_config(self.instance, self.net, self.mapping)
+ nic = driver.get_config(self.instance, net, mapping)
conf.add_device(nic)
return conf.to_xml()
@@ -126,7 +150,9 @@ class LibvirtVifTestCase(test.TestCase):
libvirt_type='kvm')
d = vif.LibvirtBridgeDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_bridge,
+ self.mapping_bridge)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -143,7 +169,9 @@ class LibvirtVifTestCase(test.TestCase):
libvirt_type='kvm')
d = vif.LibvirtBridgeDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_bridge,
+ self.mapping_bridge)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -160,7 +188,9 @@ class LibvirtVifTestCase(test.TestCase):
libvirt_type='qemu')
d = vif.LibvirtBridgeDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_bridge,
+ self.mapping_bridge)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -177,7 +207,9 @@ class LibvirtVifTestCase(test.TestCase):
libvirt_type='xen')
d = vif.LibvirtBridgeDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_bridge,
+ self.mapping_bridge)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -191,7 +223,9 @@ class LibvirtVifTestCase(test.TestCase):
def test_bridge_driver(self):
d = vif.LibvirtBridgeDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_bridge,
+ self.mapping_bridge)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -199,13 +233,15 @@ class LibvirtVifTestCase(test.TestCase):
node = ret[0]
self.assertEqual(node.get("type"), "bridge")
br_name = node.find("source").get("bridge")
- self.assertEqual(br_name, self.net['bridge'])
+ self.assertEqual(br_name, self.net_bridge['bridge'])
mac = node.find("mac").get("address")
- self.assertEqual(mac, self.mapping['mac'])
+ self.assertEqual(mac, self.mapping_bridge['mac'])
def test_ovs_ethernet_driver(self):
d = vif.LibvirtOpenVswitchDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_ovs,
+ self.mapping_ovs)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -215,13 +251,15 @@ class LibvirtVifTestCase(test.TestCase):
dev_name = node.find("target").get("dev")
self.assertTrue(dev_name.startswith("tap"))
mac = node.find("mac").get("address")
- self.assertEqual(mac, self.mapping['mac'])
+ self.assertEqual(mac, self.mapping_ovs['mac'])
script = node.find("script").get("path")
self.assertEquals(script, "")
def test_ovs_virtualport_driver(self):
d = vif.LibvirtOpenVswitchVirtualPortDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_ovs,
+ self.mapping_ovs)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -232,21 +270,24 @@ class LibvirtVifTestCase(test.TestCase):
br_name = node.find("source").get("bridge")
self.assertEqual(br_name, "br0")
mac = node.find("mac").get("address")
- self.assertEqual(mac, self.mapping['mac'])
+ self.assertEqual(mac, self.mapping_ovs['mac'])
vp = node.find("virtualport")
self.assertEqual(vp.get("type"), "openvswitch")
iface_id_found = False
for p_elem in vp.findall("parameters"):
iface_id = p_elem.get("interfaceid", None)
if iface_id:
- self.assertEqual(iface_id, self.mapping['vif_uuid'])
+ self.assertEqual(iface_id,
+ self.mapping_ovs['ovs_interfaceid'])
iface_id_found = True
self.assertTrue(iface_id_found)
def test_quantum_bridge_ethernet_driver(self):
d = vif.QuantumLinuxBridgeVIFDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_bridge,
+ self.mapping_bridge)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -256,13 +297,15 @@ class LibvirtVifTestCase(test.TestCase):
dev_name = node.find("target").get("dev")
self.assertTrue(dev_name.startswith("tap"))
mac = node.find("mac").get("address")
- self.assertEqual(mac, self.mapping['mac'])
+ self.assertEqual(mac, self.mapping_ovs['mac'])
br_name = node.find("source").get("bridge")
self.assertEqual(br_name, "br0")
def test_quantum_hybrid_driver(self):
d = vif.LibvirtHybridOVSBridgeDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_ovs,
+ self.mapping_ovs)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -270,6 +313,6 @@ class LibvirtVifTestCase(test.TestCase):
node = ret[0]
self.assertEqual(node.get("type"), "bridge")
br_name = node.find("source").get("bridge")
- self.assertEqual(br_name, self.net['bridge'])
+ self.assertEqual(br_name, self.net_ovs['bridge'])
mac = node.find("mac").get("address")
- self.assertEqual(mac, self.mapping['mac'])
+ self.assertEqual(mac, self.mapping_ovs['mac'])
diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py
index 83d43a6db..d90a5e295 100644
--- a/nova/virt/libvirt/vif.py
+++ b/nova/virt/libvirt/vif.py
@@ -150,6 +150,9 @@ class LibvirtOpenVswitchDriver(LibvirtBaseVIFDriver):
def get_bridge_name(self, network):
return network.get('bridge') or CONF.libvirt_ovs_bridge
+ def get_ovs_interfaceid(self, mapping):
+ return mapping.get('ovs_interfaceid') or mapping['vif_uuid']
+
def get_config(self, instance, network, mapping):
dev = self.get_vif_devname(mapping)
@@ -162,55 +165,26 @@ class LibvirtOpenVswitchDriver(LibvirtBaseVIFDriver):
return conf
- def create_ovs_vif_port(self, bridge, dev, iface_id, mac, instance_id):
- utils.execute('ovs-vsctl', '--', '--may-exist', 'add-port',
- bridge, dev,
- '--', 'set', 'Interface', dev,
- 'external-ids:iface-id=%s' % iface_id,
- 'external-ids:iface-status=active',
- 'external-ids:attached-mac=%s' % mac,
- 'external-ids:vm-uuid=%s' % instance_id,
- run_as_root=True)
-
- def delete_ovs_vif_port(self, bridge, dev):
- utils.execute('ovs-vsctl', 'del-port', bridge, dev,
- run_as_root=True)
- utils.execute('ip', 'link', 'delete', dev, run_as_root=True)
-
def plug(self, instance, vif):
network, mapping = vif
- iface_id = mapping['vif_uuid']
+ iface_id = self.get_ovs_interfaceid(mapping)
dev = self.get_vif_devname(mapping)
- if not linux_net.device_exists(dev):
- # Older version of the command 'ip' from the iproute2 package
- # don't have support for the tuntap option (lp:882568). If it
- # turns out we're on an old version we work around this by using
- # tunctl.
- try:
- # First, try with 'ip'
- utils.execute('ip', 'tuntap', 'add', dev, 'mode', 'tap',
- run_as_root=True)
- except exception.ProcessExecutionError:
- # Second option: tunctl
- utils.execute('tunctl', '-b', '-t', dev, run_as_root=True)
- utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
-
- self.create_ovs_vif_port(self.get_bridge_name(network),
- dev, iface_id, mapping['mac'],
- instance['uuid'])
+ linux_net.create_tap_dev(dev)
+ linux_net.create_ovs_vif_port(self.get_bridge_name(network),
+ dev, iface_id, mapping['mac'],
+ instance['uuid'])
def unplug(self, instance, vif):
"""Unplug the VIF by deleting the port from the bridge."""
try:
network, mapping = vif
- self.delete_ovs_vif_port(self.get_bridge_name(network),
- self.get_vif_devname(mapping))
+ linux_net.delete_ovs_vif_port(self.get_bridge_name(network),
+ self.get_vif_devname(mapping))
except exception.ProcessExecutionError:
LOG.exception(_("Failed while unplugging vif"), instance=instance)
-class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver,
- LibvirtOpenVswitchDriver):
+class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver):
"""VIF driver that uses OVS + Linux Bridge for iptables compatibility.
Enables the use of OVS-based Quantum plugins while at the same
@@ -229,6 +203,9 @@ class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver,
def get_bridge_name(self, network):
return network.get('bridge') or CONF.libvirt_ovs_bridge
+ def get_ovs_interfaceid(self, mapping):
+ return mapping.get('ovs_interfaceid') or mapping['vif_uuid']
+
def get_config(self, instance, network, mapping):
br_name = self.get_br_name(mapping['vif_uuid'])
network['bridge'] = br_name
@@ -247,9 +224,9 @@ class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver,
"""
network, mapping = vif
- iface_id = mapping['vif_uuid']
- br_name = self.get_br_name(iface_id)
- v1_name, v2_name = self.get_veth_pair_names(iface_id)
+ iface_id = self.get_ovs_interfaceid(mapping)
+ br_name = self.get_br_name(mapping['vif_uuid'])
+ v1_name, v2_name = self.get_veth_pair_names(mapping['vif_uuid'])
if not linux_net.device_exists(br_name):
utils.execute('brctl', 'addbr', br_name, run_as_root=True)
@@ -258,9 +235,9 @@ class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver,
linux_net._create_veth_pair(v1_name, v2_name)
utils.execute('ip', 'link', 'set', br_name, 'up', run_as_root=True)
utils.execute('brctl', 'addif', br_name, v1_name, run_as_root=True)
- self.create_ovs_vif_port(self.get_bridge_name(network),
- v2_name, iface_id, mapping['mac'],
- instance['uuid'])
+ linux_net.create_ovs_vif_port(self.get_bridge_name(network),
+ v2_name, iface_id, mapping['mac'],
+ instance['uuid'])
def unplug(self, instance, vif):
"""UnPlug using hybrid strategy
@@ -270,16 +247,16 @@ class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver,
"""
try:
network, mapping = vif
- iface_id = mapping['vif_uuid']
- br_name = self.get_br_name(iface_id)
- v1_name, v2_name = self.get_veth_pair_names(iface_id)
+ br_name = self.get_br_name(mapping['vif_uuid'])
+ v1_name, v2_name = self.get_veth_pair_names(mapping['vif_uuid'])
utils.execute('brctl', 'delif', br_name, v1_name, run_as_root=True)
utils.execute('ip', 'link', 'set', br_name, 'down',
run_as_root=True)
utils.execute('brctl', 'delbr', br_name, run_as_root=True)
- self.delete_ovs_vif_port(self.get_bridge_name(network), v2_name)
+ linux_net.delete_ovs_vif_port(self.get_bridge_name(network),
+ v2_name)
except exception.ProcessExecutionError:
LOG.exception(_("Failed while unplugging vif"), instance=instance)
@@ -291,6 +268,9 @@ class LibvirtOpenVswitchVirtualPortDriver(LibvirtBaseVIFDriver):
def get_bridge_name(self, network):
return network.get('bridge') or CONF.libvirt_ovs_bridge
+ def get_ovs_interfaceid(self, mapping):
+ return mapping.get('ovs_interfaceid') or mapping['vif_uuid']
+
def get_config(self, instance, network, mapping):
"""Pass data required to create OVS virtual port element."""
conf = super(LibvirtOpenVswitchVirtualPortDriver,
@@ -299,7 +279,8 @@ class LibvirtOpenVswitchVirtualPortDriver(LibvirtBaseVIFDriver):
mapping)
designer.set_vif_host_backend_ovs_config(
- conf, self.get_bridge_name(network), mapping['vif_uuid'],
+ conf, self.get_bridge_name(network),
+ self.get_ovs_interfaceid(mapping),
self.get_vif_devname(mapping))
return conf