From a9add7d35e27b90f0c420d2b24b1af88b978fd7b Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Tue, 12 Feb 2013 15:45:24 -0500 Subject: Add support for network adapter hotplug. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch makes it possible to add/del instance interface other than booting time. Implement bp:network-adapter-hotplug Originally from change Ibee003a9ec6cc9b3fd275417caccd0c67f6c871f Co-authored-by: Yaguang Tang Co-authored-by: Édouard Thuleau Change-Id: I4f8f677af58afcb928379e5cf859388d1da45d51 --- .../compute/contrib/test_attach_interfaces.py | 244 +++++++++++++++++++++ nova/tests/compute/test_compute.py | 36 +++ nova/tests/fake_policy.py | 1 + .../all_extensions/extensions-get-resp.json.tpl | 8 + .../all_extensions/extensions-get-resp.xml.tpl | 3 + .../attach-interfaces-create-req.json.tpl | 5 + .../attach-interfaces-create-req.xml.tpl | 4 + .../attach-interfaces-create-resp.json.tpl | 12 + .../attach-interfaces-create-resp.xml.tpl | 12 + .../attach-interfaces-list-resp.json.tpl | 16 ++ .../attach-interfaces-list-resp.xml.tpl | 15 ++ .../attach-interfaces-show-resp.json.tpl | 14 ++ .../attach-interfaces-show-resp.xml.tpl | 13 ++ .../os-attach-interfaces/server-post-req.json.tpl | 16 ++ .../os-attach-interfaces/server-post-req.xml.tpl | 19 ++ .../os-attach-interfaces/server-post-resp.json.tpl | 16 ++ .../os-attach-interfaces/server-post-resp.xml.tpl | 6 + nova/tests/integrated/test_api_samples.py | 173 ++++++++++++++- nova/tests/network/test_quantumv2.py | 61 +++++- 19 files changed, 668 insertions(+), 6 deletions(-) create mode 100644 nova/tests/api/openstack/compute/contrib/test_attach_interfaces.py create mode 100644 nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-create-req.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-create-req.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-create-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-create-resp.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-list-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-list-resp.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-show-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-show-resp.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-attach-interfaces/server-post-req.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-attach-interfaces/server-post-req.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-attach-interfaces/server-post-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-attach-interfaces/server-post-resp.xml.tpl (limited to 'nova/tests') diff --git a/nova/tests/api/openstack/compute/contrib/test_attach_interfaces.py b/nova/tests/api/openstack/compute/contrib/test_attach_interfaces.py new file mode 100644 index 000000000..462e4375c --- /dev/null +++ b/nova/tests/api/openstack/compute/contrib/test_attach_interfaces.py @@ -0,0 +1,244 @@ +# Copyright 2012 SINA 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 nova.api.openstack.compute.contrib import attach_interfaces +from nova.compute import api as compute_api +from nova import context +from nova import exception +from nova.network import api as network_api +from nova.openstack.common import cfg +from nova.openstack.common import jsonutils +from nova import test + +import webob +from webob import exc + + +CONF = cfg.CONF + +FAKE_UUID1 = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' +FAKE_UUID2 = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb' + +FAKE_PORT_ID1 = '11111111-1111-1111-1111-111111111111' +FAKE_PORT_ID2 = '22222222-2222-2222-2222-222222222222' +FAKE_PORT_ID3 = '33333333-3333-3333-3333-333333333333' + +FAKE_NET_ID1 = '44444444-4444-4444-4444-444444444444' +FAKE_NET_ID2 = '55555555-5555-5555-5555-555555555555' +FAKE_NET_ID3 = '66666666-6666-6666-6666-666666666666' + +port_data1 = { + "id": FAKE_PORT_ID1, + "network_id": FAKE_NET_ID1, + "admin_state_up": True, + "status": "ACTIVE", + "mac_address": "aa:aa:aa:aa:aa:aa", + "fixed_ips": ["10.0.1.2"], + "device_id": FAKE_UUID1, +} + +port_data2 = { + "id": FAKE_PORT_ID2, + "network_id": FAKE_NET_ID2, + "admin_state_up": True, + "status": "ACTIVE", + "mac_address": "bb:bb:bb:bb:bb:bb", + "fixed_ips": ["10.0.2.2"], + "device_id": FAKE_UUID1, +} + +port_data3 = { + "id": FAKE_PORT_ID3, + "network_id": FAKE_NET_ID3, + "admin_state_up": True, + "status": "ACTIVE", + "mac_address": "bb:bb:bb:bb:bb:bb", + "fixed_ips": ["10.0.2.2"], + "device_id": '', +} + +fake_networks = [FAKE_NET_ID1, FAKE_NET_ID2] +ports = [port_data1, port_data2, port_data3] + + +def fake_list_ports(self, *args, **kwargs): + result = [] + for port in ports: + if port['device_id'] == kwargs['device_id']: + result.append(port) + return {'ports': result} + + +def fake_show_port(self, context, port_id, **kwargs): + for port in ports: + if port['id'] == port_id: + return {'port': port} + + +def fake_attach_interface(self, context, instance, network_id, port_id, + requested_ip='192.168.1.3'): + if not network_id: + # if no network_id is given when add a port to an instance, use the + # first default network. + network_id = fake_networks[0] + if not port_id: + port_id = ports[fake_networks.index(network_id)]['id'] + network_info = [ + {'bridge': 'br-100', + 'id': network_id, + 'cidr': '192.168.1.0/24', + 'vlan': '101', + 'injected': 'False', + 'multi_host': 'False', + 'bridge_interface': 'bridge_interface' + }, + {'label': 'fake_network', + 'broadcast': '192.168.1.255', + 'mac': '11:22:33:11:22:33', + 'vif_uuid': port_id, + 'rxtx_cap': 0, + 'dns': '8.8.8.8', + 'dhcp_server': '192.168.1.1', + 'ips': {'ip': requested_ip, + 'enabled': 1, + 'netmask': '255.255.255.0', + 'gateway': '192.168.1.254'} + } + ] + return network_info + + +def fake_detach_interface(self, context, instance, port_id): + for port in ports: + if port['id'] == port_id: + return + raise exception.PortNotFound(port_id=port_id) + + +def fake_get_instance(self, context, intance_id): + return {} + + +class InterfaceAttachTests(test.TestCase): + def setUp(self): + super(InterfaceAttachTests, self).setUp() + self.flags(quantum_auth_strategy=None) + self.flags(quantum_url='http://anyhost/') + self.flags(quantum_url_timeout=30) + self.stubs.Set(network_api.API, 'show_port', fake_show_port) + self.stubs.Set(network_api.API, 'list_ports', fake_list_ports) + self.stubs.Set(compute_api.API, 'get', fake_get_instance) + self.context = context.get_admin_context() + self.expected_show = {'interfaceAttachment': + {'net_id': FAKE_NET_ID1, + 'port_id': FAKE_PORT_ID1, + 'mac_addr': port_data1['mac_address'], + 'port_state': port_data1['status'], + 'fixed_ips': port_data1['fixed_ips'], + }} + + def test_show(self): + attachments = attach_interfaces.InterfaceAttachmentController() + req = webob.Request.blank('/v2/fake/os-interfaces/show') + req.method = 'POST' + req.body = jsonutils.dumps({}) + req.headers['content-type'] = 'application/json' + req.environ['nova.context'] = self.context + + result = attachments.show(req, FAKE_UUID1, FAKE_PORT_ID1) + self.assertEqual(self.expected_show, result) + + def test_show_invalid(self): + attachments = attach_interfaces.InterfaceAttachmentController() + req = webob.Request.blank('/v2/fake/os-interfaces/show') + req.method = 'POST' + req.body = jsonutils.dumps({}) + req.headers['content-type'] = 'application/json' + req.environ['nova.context'] = self.context + + self.assertRaises(exc.HTTPNotFound, + attachments.show, req, FAKE_UUID2, FAKE_PORT_ID1) + + def test_delete(self): + self.stubs.Set(compute_api.API, 'detach_interface', + fake_detach_interface) + attachments = attach_interfaces.InterfaceAttachmentController() + req = webob.Request.blank('/v2/fake/os-interfaces/delete') + req.method = 'POST' + req.body = jsonutils.dumps({}) + req.headers['content-type'] = 'application/json' + req.environ['nova.context'] = self.context + + result = attachments.delete(req, FAKE_UUID1, FAKE_PORT_ID1) + self.assertEqual('202 Accepted', result.status) + + def test_delete_interface_not_found(self): + self.stubs.Set(compute_api.API, 'detach_interface', + fake_detach_interface) + attachments = attach_interfaces.InterfaceAttachmentController() + req = webob.Request.blank('/v2/fake/os-interfaces/delete') + req.method = 'POST' + req.body = jsonutils.dumps({}) + req.headers['content-type'] = 'application/json' + req.environ['nova.context'] = self.context + + self.assertRaises(exc.HTTPNotFound, + attachments.delete, + req, + FAKE_UUID1, + 'invaid-port-id') + + def test_attach_interface_without_network_id(self): + self.stubs.Set(compute_api.API, 'attach_interface', + fake_attach_interface) + attachments = attach_interfaces.InterfaceAttachmentController() + req = webob.Request.blank('/v2/fake/os-interfaces/attach') + req.method = 'POST' + body = jsonutils.dumps({'port_id': FAKE_PORT_ID1}) + req.body = jsonutils.dumps({}) + req.headers['content-type'] = 'application/json' + req.environ['nova.context'] = self.context + result = attachments.create(req, FAKE_UUID1, jsonutils.loads(req.body)) + self.assertEqual(result['interfaceAttachment']['net_id'], + FAKE_NET_ID1) + + def test_attach_interface_with_network_id(self): + self.stubs.Set(compute_api.API, 'attach_interface', + fake_attach_interface) + attachments = attach_interfaces.InterfaceAttachmentController() + req = webob.Request.blank('/v2/fake/os-interfaces/attach') + req.method = 'POST' + req.body = jsonutils.dumps({'interfaceAttachment': + {'net_id': FAKE_NET_ID2}}) + req.headers['content-type'] = 'application/json' + req.environ['nova.context'] = self.context + result = attachments.create(req, FAKE_UUID1, jsonutils.loads(req.body)) + self.assertEqual(result['interfaceAttachment']['net_id'], + FAKE_NET_ID2) + + def test_attach_interface_with_port_and_network_id(self): + self.stubs.Set(compute_api.API, 'attach_interface', + fake_attach_interface) + attachments = attach_interfaces.InterfaceAttachmentController() + req = webob.Request.blank('/v2/fake/os-interfaces/attach') + req.method = 'POST' + req.body = jsonutils.dumps({'interfaceAttachment': + {'port_id': FAKE_PORT_ID1, + 'net_id': FAKE_NET_ID2}}) + req.headers['content-type'] = 'application/json' + req.environ['nova.context'] = self.context + self.assertRaises(exc.HTTPBadRequest, + attachments.create, req, FAKE_UUID1, + jsonutils.loads(req.body)) diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py index d1a7952f3..0dfb66218 100644 --- a/nova/tests/compute/test_compute.py +++ b/nova/tests/compute/test_compute.py @@ -62,6 +62,7 @@ from nova.tests.compute import fake_resource_tracker from nova.tests.db import fakes as db_fakes from nova.tests import fake_instance_actions from nova.tests import fake_network +from nova.tests import fake_network_cache_model from nova.tests.image import fake as fake_image from nova.tests import matchers from nova import utils @@ -5828,6 +5829,41 @@ class ComputeAPITestCase(BaseTestCase): db.instance_destroy(self.context, instance['uuid']) + def test_attach_interface(self): + instance = { + 'image_ref': 'foo', + } + self.mox.StubOutWithMock(compute_manager, '_get_image_meta') + self.mox.StubOutWithMock(self.compute.network_api, + 'allocate_port_for_instance') + nwinfo = network_model.NetworkInfo() + nwinfo.append(fake_network_cache_model.new_vif()) + network_id = nwinfo[0]['network']['id'] + port_id = nwinfo[0]['id'] + req_ip = '1.2.3.4' + self.compute.network_api.allocate_port_for_instance( + self.context, instance, port_id, network_id, req_ip, + self.compute.conductor_api).AndReturn(nwinfo) + compute_manager._get_image_meta(self.context, instance['image_ref']) + self.mox.ReplayAll() + network, mapping = self.compute.attach_interface(self.context, + instance, + network_id, + port_id, + req_ip) + self.assertEqual(network['id'], network_id) + return nwinfo, port_id + + def test_detach_interface(self): + nwinfo, port_id = self.test_attach_interface() + self.stubs.Set(self.compute.network_api, 'get_instance_nw_info', + lambda *a, **k: nwinfo) + self.stubs.Set(self.compute.network_api, + 'deallocate_port_for_instance', + lambda a, b, c, d: []) + self.compute.detach_interface(self.context, {}, port_id) + self.assertEqual(self.compute.driver._interfaces, {}) + def test_attach_volume(self): # Ensure instance can be soft rebooted. diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index 3878df531..950520437 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:attach_interfaces": "", "compute_extension:baremetal_nodes": "", "compute_extension:cells": "", "compute_extension:certificates": "", 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 17914de42..910867e8a 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 @@ -96,6 +96,14 @@ "namespace": "http://docs.openstack.org/compute/ext/agents/api/v2", "updated": "%(timestamp)s" }, + { + "alias": "os-attach-interfaces", + "description": "Attach interface support.", + "links": [], + "name": "AttachInterfaces", + "namespace": "http://docs.openstack.org/compute/ext/interfaces/api/v1.1", + "updated": "2012-07-22T00:00:00+00:00" + }, { "alias": "os-availability-zone", "description": "%(text)s", 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 4492ed3aa..cfd85c5bc 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 @@ %(text)s + + Attach interface support. + %(text)s diff --git a/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-create-req.json.tpl b/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-create-req.json.tpl new file mode 100644 index 000000000..11dcf6437 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-create-req.json.tpl @@ -0,0 +1,5 @@ +{ + "interfaceAttachment": { + "port_id": "ce531f90-199f-48c0-816c-13e38010b442" + } +} diff --git a/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-create-req.xml.tpl b/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-create-req.xml.tpl new file mode 100644 index 000000000..75e9b97c8 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-create-req.xml.tpl @@ -0,0 +1,4 @@ + + + %(port_id)s + diff --git a/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-create-resp.json.tpl b/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-create-resp.json.tpl new file mode 100644 index 000000000..d882cdc61 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-create-resp.json.tpl @@ -0,0 +1,12 @@ +{ + "interfaceAttachment": { + "fixed_ips": [{ + "subnet_id": "%(subnet_id)s", + "ip_address": "%(ip_address)s" + }], + "mac_addr": "fa:16:3e:4c:2c:30", + "net_id": "%(net_id)s", + "port_id": "%(port_id)s", + "port_state": "%(port_state)s" + } +} diff --git a/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-create-resp.xml.tpl b/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-create-resp.xml.tpl new file mode 100644 index 000000000..b391e5973 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-create-resp.xml.tpl @@ -0,0 +1,12 @@ + + %(net_id)s + %(port_id)s + + + %(subnet_id)s + %(ip_address)s + + + %(port_state)s + %(mac_addr)s + diff --git a/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-list-resp.json.tpl b/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-list-resp.json.tpl new file mode 100644 index 000000000..47dcf2dc6 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-list-resp.json.tpl @@ -0,0 +1,16 @@ +{ + "interfaceAttachments": [ + { + "port_state": "%(port_state)s", + "fixed_ips": [ + { + "subnet_id": "%(subnet_id)s", + "ip_address": "%(ip_address)s" + } + ], + "net_id": "%(net_id)s", + "port_id": "%(port_id)s", + "mac_addr": "%(mac_addr)s" + } + ] +} diff --git a/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-list-resp.xml.tpl b/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-list-resp.xml.tpl new file mode 100644 index 000000000..f3262e948 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-list-resp.xml.tpl @@ -0,0 +1,15 @@ + + + + %(port_state)s + + + %(subnet_id)s + %(ip_address)s + + + %(port_id)s + %(net_id)s + %(mac_addr)s + + diff --git a/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-show-resp.json.tpl b/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-show-resp.json.tpl new file mode 100644 index 000000000..3333bb499 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-show-resp.json.tpl @@ -0,0 +1,14 @@ +{ + "interfaceAttachment": { + "port_state": "%(port_state)s", + "fixed_ips": [ + { + "subnet_id": "%(subnet_id)s", + "ip_address": "%(ip_address)s" + } + ], + "net_id": "%(net_id)s", + "port_id": "%(port_id)s", + "mac_addr": "%(mac_addr)s" + } +} diff --git a/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-show-resp.xml.tpl b/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-show-resp.xml.tpl new file mode 100644 index 000000000..a3393448d --- /dev/null +++ b/nova/tests/integrated/api_samples/os-attach-interfaces/attach-interfaces-show-resp.xml.tpl @@ -0,0 +1,13 @@ + + + %(port_state)s + + + %(subnet_id)s + %(ip_address)s + + + %(port_id)s + %(net_id)s + %(mac_addr)s + diff --git a/nova/tests/integrated/api_samples/os-attach-interfaces/server-post-req.json.tpl b/nova/tests/integrated/api_samples/os-attach-interfaces/server-post-req.json.tpl new file mode 100644 index 000000000..d3916d1aa --- /dev/null +++ b/nova/tests/integrated/api_samples/os-attach-interfaces/server-post-req.json.tpl @@ -0,0 +1,16 @@ +{ + "server" : { + "name" : "new-server-test", + "imageRef" : "%(host)s/openstack/images/%(image_id)s", + "flavorRef" : "%(host)s/openstack/flavors/1", + "metadata" : { + "My Server Name" : "Apache1" + }, + "personality" : [ + { + "path" : "/etc/banner.txt", + "contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA==" + } + ] + } +} diff --git a/nova/tests/integrated/api_samples/os-attach-interfaces/server-post-req.xml.tpl b/nova/tests/integrated/api_samples/os-attach-interfaces/server-post-req.xml.tpl new file mode 100644 index 000000000..f92614984 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-attach-interfaces/server-post-req.xml.tpl @@ -0,0 +1,19 @@ + + + + Apache1 + + + + ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp + dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k + IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs + c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g + QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo + ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv + dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy + c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 + b25zLiINCg0KLVJpY2hhcmQgQmFjaA== + + + diff --git a/nova/tests/integrated/api_samples/os-attach-interfaces/server-post-resp.json.tpl b/nova/tests/integrated/api_samples/os-attach-interfaces/server-post-resp.json.tpl new file mode 100644 index 000000000..d5f030c87 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-attach-interfaces/server-post-resp.json.tpl @@ -0,0 +1,16 @@ +{ + "server": { + "adminPass": "%(password)s", + "id": "%(id)s", + "links": [ + { + "href": "%(host)s/v2/openstack/servers/%(uuid)s", + "rel": "self" + }, + { + "href": "%(host)s/openstack/servers/%(uuid)s", + "rel": "bookmark" + } + ] + } +} diff --git a/nova/tests/integrated/api_samples/os-attach-interfaces/server-post-resp.xml.tpl b/nova/tests/integrated/api_samples/os-attach-interfaces/server-post-resp.xml.tpl new file mode 100644 index 000000000..3bb13e69b --- /dev/null +++ b/nova/tests/integrated/api_samples/os-attach-interfaces/server-post-resp.xml.tpl @@ -0,0 +1,6 @@ + + + + + + diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py index e179052d6..bc95e8357 100644 --- a/nova/tests/integrated/test_api_samples.py +++ b/nova/tests/integrated/test_api_samples.py @@ -31,6 +31,7 @@ from nova.api.openstack.compute.contrib import coverage_ext from nova.api.openstack.compute.contrib import fping # Import extensions to pull in osapi_compute_extension CONF option used below. from nova.cloudpipe import pipelib +from nova.compute import api as compute_api from nova import context from nova import db from nova.db.sqlalchemy import models @@ -266,7 +267,6 @@ class ApiSampleTestBase(integrated_helpers._IntegratedTestBase): sample_data = "{}" else: sample_data = None - try: response_result = self._verify_something(subs, expected, response_data) @@ -3302,3 +3302,174 @@ class FlavorAccessSampleJsonTests(ApiSampleTestBase): class FlavorAccessSampleXmlTests(FlavorAccessSampleJsonTests): ctype = "xml" + + +class AttachInterfacesSampleJsonTest(ServersSampleBase): + extension_name = ('nova.api.openstack.compute.contrib.attach_interfaces.' + 'Attach_interfaces') + + def setUp(self): + super(AttachInterfacesSampleJsonTest, self).setUp() + + def fake_list_ports(self, *args, **kwargs): + uuid = kwargs.get('device_id', None) + if not uuid: + raise InstanceNotFound(instance_id=None) + port_data = { + "id": "ce531f90-199f-48c0-816c-13e38010b442", + "network_id": "3cb9bc59-5699-4588-a4b1-b87f96708bc6", + "admin_state_up": True, + "status": "ACTIVE", + "mac_address": "fa:16:3e:4c:2c:30", + "fixed_ips": [ + { + "ip_address": "192.168.1.3", + "subnet_id": "f8a6e8f8-c2ec-497c-9f23-da9616de54ef" + } + ], + "device_id": uuid, + } + ports = {'ports': [port_data]} + return ports + + def fake_show_port(self, context, port_id=None): + if not port_id: + raise PortNotFound(port_id=None) + port_data = { + "id": port_id, + "network_id": "3cb9bc59-5699-4588-a4b1-b87f96708bc6", + "admin_state_up": True, + "status": "ACTIVE", + "mac_address": "fa:16:3e:4c:2c:30", + "fixed_ips": [ + { + "ip_address": "192.168.1.3", + "subnet_id": "f8a6e8f8-c2ec-497c-9f23-da9616de54ef" + } + ], + "device_id": 'bece68a3-2f8b-4e66-9092-244493d6aba7', + } + port = {'port': port_data} + return port + + def fake_attach_interface(self, context, instance, + network_id, port_id, + requested_ip='192.168.1.3'): + if not network_id: + network_id = "fake_net_uuid" + if not port_id: + port_id = "fake_port_uuid" + network_info = [ + { + 'bridge': 'br-100', + 'id': network_id, + 'cidr': '192.168.1.0/24', + 'vlan': '101', + 'injected': 'False', + 'multi_host': 'False', + 'bridge_interface': 'bridge_interface' + }, + { + "vif_uuid": port_id, + "network_id": network_id, + "admin_state_up": True, + "status": "ACTIVE", + "mac_address": "fa:16:3e:4c:2c:30", + "fixed_ips": [ + { + "ip_address": requested_ip, + "subnet_id": "f8a6e8f8-c2ec-497c-9f23-da9616de54ef" + } + ], + "device_id": instance['uuid'], + } + ] + return network_info + + def fake_detach_interface(self, context, instance, port_id): + pass + + self.stubs.Set(network_api.API, 'list_ports', fake_list_ports) + self.stubs.Set(network_api.API, 'show_port', fake_show_port) + self.stubs.Set(compute_api.API, 'attach_interface', + fake_attach_interface) + self.stubs.Set(compute_api.API, 'detach_interface', + fake_detach_interface) + self.flags(quantum_auth_strategy=None) + self.flags(quantum_url='http://anyhost/') + self.flags(quantum_url_timeout=30) + + def generalize_subs(self, subs, vanilla_regexes): + subs['subnet_id'] = vanilla_regexes['uuid'] + subs['net_id'] = vanilla_regexes['uuid'] + subs['port_id'] = vanilla_regexes['uuid'] + subs['mac_addr'] = '(?:[a-f0-9]{2}:){5}[a-f0-9]{2}' + subs['ip_address'] = vanilla_regexes['ip'] + return subs + + def test_list_interfaces(self): + instance_uuid = self._post_server() + response = self._do_get('servers/%s/os-interface' % instance_uuid) + self.assertEqual(response.status, 200) + subs = { + 'ip_address': '192.168.1.3', + 'subnet_id': 'f8a6e8f8-c2ec-497c-9f23-da9616de54ef', + 'mac_addr': 'fa:16:3e:4c:2c:30', + 'net_id': '3cb9bc59-5699-4588-a4b1-b87f96708bc6', + 'port_id': 'ce531f90-199f-48c0-816c-13e38010b442', + 'port_state': 'ACTIVE' + } + self._verify_response('attach-interfaces-list-resp', subs, response) + + def _stub_show_for_instance(self, instance_uuid, port_id): + show_port = network_api.API().show_port(None, port_id) + show_port['port']['device_id'] = instance_uuid + self.stubs.Set(network_api.API, 'show_port', lambda *a, **k: show_port) + + def test_show_interfaces(self): + instance_uuid = self._post_server() + port_id = 'ce531f90-199f-48c0-816c-13e38010b442' + self._stub_show_for_instance(instance_uuid, port_id) + response = self._do_get('servers/%s/os-interface/%s' % + (instance_uuid, port_id)) + self.assertEqual(response.status, 200) + subs = { + 'ip_address': '192.168.1.3', + 'subnet_id': 'f8a6e8f8-c2ec-497c-9f23-da9616de54ef', + 'mac_addr': 'fa:16:3e:4c:2c:30', + 'net_id': '3cb9bc59-5699-4588-a4b1-b87f96708bc6', + 'port_id': port_id, + 'port_state': 'ACTIVE' + } + self._verify_response('attach-interfaces-show-resp', subs, response) + + def test_create_interfaces(self, instance_uuid=None): + if instance_uuid is None: + instance_uuid = self._post_server() + subs = { + 'net_id': '3cb9bc59-5699-4588-a4b1-b87f96708bc6', + 'port_id': 'ce531f90-199f-48c0-816c-13e38010b442', + 'subnet_id': 'f8a6e8f8-c2ec-497c-9f23-da9616de54ef', + 'ip_address': '192.168.1.3', + 'port_state': 'ACTIVE', + 'mac_addr': 'fa:16:3e:4c:2c:30', + } + self._stub_show_for_instance(instance_uuid, subs['port_id']) + response = self._do_post('servers/%s/os-interface' % instance_uuid, + 'attach-interfaces-create-req', subs) + self.assertEqual(response.status, 200) + subs.update(self._get_regexes()) + self._verify_response('attach-interfaces-create-resp', + subs, response) + + def test_delete_interfaces(self): + instance_uuid = self._post_server() + port_id = 'ce531f90-199f-48c0-816c-13e38010b442' + response = self._do_delete('servers/%s/os-interface/%s' % + (instance_uuid, port_id)) + self.assertEqual(response.status, 202) + self.assertEqual(response.read(), '') + + +class AttachInterfacesSampleXmlTest(AttachInterfacesSampleJsonTest): + ctype = 'xml' diff --git a/nova/tests/network/test_quantumv2.py b/nova/tests/network/test_quantumv2.py index f3f306694..2e2504c81 100644 --- a/nova/tests/network/test_quantumv2.py +++ b/nova/tests/network/test_quantumv2.py @@ -230,11 +230,10 @@ class TestQuantumv2(test.TestCase): 'router_id': 'router_id1'} def tearDown(self): - try: - self.mox.UnsetStubs() - self.mox.VerifyAll() - finally: - CONF.reset() + self.addCleanup(CONF.reset) + self.addCleanup(self.mox.VerifyAll) + self.addCleanup(self.mox.UnsetStubs) + self.addCleanup(self.stubs.UnsetAll) super(TestQuantumv2, self).tearDown() def _verify_nw_info(self, nw_inf, index=0): @@ -614,7 +613,9 @@ class TestQuantumv2(test.TestCase): {'ports': port_data}) for port in port_data: self.moxed_client.delete_port(port['id']) + self.mox.ReplayAll() + api = quantumapi.API() api.deallocate_for_instance(self.context, self.instance) @@ -626,6 +627,56 @@ class TestQuantumv2(test.TestCase): # Test to deallocate in two ports env. self._deallocate_for_instance(2) + def _test_deallocate_port_for_instance(self, number): + port_data = number == 1 and self.port_data1 or self.port_data2 + self.moxed_client.delete_port(port_data[0]['id']) + + nets = [port_data[0]['network_id']] + quantumv2.get_client(mox.IgnoreArg(), admin=True).AndReturn( + self.moxed_client) + self.moxed_client.list_ports( + tenant_id=self.instance['project_id'], + device_id=self.instance['uuid']).AndReturn( + {'ports': port_data[1:]}) + quantumv2.get_client(mox.IgnoreArg()).MultipleTimes().AndReturn( + self.moxed_client) + self.moxed_client.list_networks( + tenant_id=self.instance['project_id'], + shared=False).AndReturn( + {'networks': [self.nets2[1]]}) + self.moxed_client.list_networks(shared=True).AndReturn( + {'networks': []}) + for port in port_data[1:]: + self.moxed_client.list_subnets(id=['my_subid2']).AndReturn({}) + + self.mox.ReplayAll() + + api = quantumapi.API() + nwinfo = api.deallocate_port_for_instance(self.context, self.instance, + port_data[0]['id']) + self.assertEqual(len(nwinfo), len(port_data[1:])) + if len(port_data) > 1: + self.assertEqual(nwinfo[0]['network']['id'], 'my_netid2') + + def test_deallocate_port_for_instance_1(self): + # Test to deallocate the first and only port + self._test_deallocate_port_for_instance(1) + + def test_deallocate_port_for_instance_2(self): + # Test to deallocate the first port of two + self._test_deallocate_port_for_instance(2) + + def test_list_ports(self): + search_opts = {'parm': 'value'} + self.moxed_client.list_ports(**search_opts) + self.mox.ReplayAll() + quantumapi.API().list_ports(self.context, **search_opts) + + def test_show_port(self): + self.moxed_client.show_port('foo') + self.mox.ReplayAll() + quantumapi.API().show_port(self.context, 'foo') + def test_validate_networks(self): requested_networks = [('my_netid1', 'test', None), ('my_netid2', 'test2', None)] -- cgit