diff options
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 |