diff --git a/doc/v3/api_samples/os-virtual-interfaces/server-post-req.json b/doc/v3/api_samples/os-virtual-interfaces/server-post-req.json new file mode 100644 index 000000000000..f0bd48648250 --- /dev/null +++ b/doc/v3/api_samples/os-virtual-interfaces/server-post-req.json @@ -0,0 +1,16 @@ +{ + "server": { + "name": "new-server-test", + "imageRef": "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b", + "flavorRef": "http://openstack.example.com/openstack/flavors/1", + "metadata": { + "My Server Name": "Apache1" + }, + "personality": [ + { + "path": "/etc/banner.txt", + "contents": "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA==" + } + ] + } +} diff --git a/doc/v3/api_samples/os-virtual-interfaces/server-post-resp.json b/doc/v3/api_samples/os-virtual-interfaces/server-post-resp.json new file mode 100644 index 000000000000..f8379cbb3f5e --- /dev/null +++ b/doc/v3/api_samples/os-virtual-interfaces/server-post-resp.json @@ -0,0 +1,16 @@ +{ + "server": { + "adminPass": "m62Pu3gkXXV2", + "id": "a98dd3ae-5feb-4b4b-afa4-25e830ad3305", + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/a98dd3ae-5feb-4b4b-afa4-25e830ad3305", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/a98dd3ae-5feb-4b4b-afa4-25e830ad3305", + "rel": "bookmark" + } + ] + } +} \ No newline at end of file diff --git a/doc/v3/api_samples/os-virtual-interfaces/vifs-list-resp.json b/doc/v3/api_samples/os-virtual-interfaces/vifs-list-resp.json new file mode 100644 index 000000000000..c2e62b6ebd06 --- /dev/null +++ b/doc/v3/api_samples/os-virtual-interfaces/vifs-list-resp.json @@ -0,0 +1,8 @@ +{ + "virtual_interfaces": [ + { + "id": "cec8b9bb-5d22-4104-b3c8-4c35db3210a6", + "mac_address": "fa:16:3e:3c:ce:6f" + } + ] +} \ No newline at end of file diff --git a/etc/nova/policy.json b/etc/nova/policy.json index d8f63b514fff..f00c622d0f01 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -291,6 +291,8 @@ "compute_extension:users": "rule:admin_api", "compute_extension:v3:os-user-data:discoverable": "", "compute_extension:virtual_interfaces": "", + "compute_extension:v3:os-virtual-interfaces": "", + "compute_extension:v3:os-virtual-interfaces:discoverable": "", "compute_extension:virtual_storage_arrays": "", "compute_extension:volumes": "", "compute_extension:volume_attachments:index": "", diff --git a/nova/api/openstack/compute/plugins/v3/virtual_interfaces.py b/nova/api/openstack/compute/plugins/v3/virtual_interfaces.py new file mode 100644 index 000000000000..dbdde428b080 --- /dev/null +++ b/nova/api/openstack/compute/plugins/v3/virtual_interfaces.py @@ -0,0 +1,84 @@ +# Copyright (C) 2011 Midokura KK +# 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 virtual interfaces extension.""" + +from nova.api.openstack import common +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova import compute +from nova import network + + +ALIAS = 'os-virtual-interfaces' +authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS) + + +def _translate_vif_summary_view(_context, vif): + """Maps keys for VIF summary view.""" + d = {} + d['id'] = vif['uuid'] + d['mac_address'] = vif['address'] + return d + + +class ServerVirtualInterfaceController(wsgi.Controller): + """The instance VIF API controller for the OpenStack API. + """ + + def __init__(self): + self.compute_api = compute.API() + self.network_api = network.API() + super(ServerVirtualInterfaceController, self).__init__() + + def _items(self, req, server_id, entity_maker): + """Returns a list of VIFs, transformed through entity_maker.""" + context = req.environ['nova.context'] + instance = common.get_instance(self.compute_api, context, server_id, + want_objects=True) + + vifs = self.network_api.get_vifs_by_instance(context, instance) + limited_list = common.limited(vifs, req) + res = [entity_maker(context, vif) for vif in limited_list] + return {'virtual_interfaces': res} + + @extensions.expected_errors((404)) + def index(self, req, server_id): + """Returns the list of VIFs for a given instance.""" + authorize(req.environ['nova.context']) + return self._items(req, server_id, + entity_maker=_translate_vif_summary_view) + + +class VirtualInterfaces(extensions.V3APIExtensionBase): + """Virtual interface support.""" + + name = "VirtualInterfaces" + alias = ALIAS + version = 1 + + def get_resources(self): + resources = [] + + res = extensions.ResourceExtension( + ALIAS, + controller=ServerVirtualInterfaceController(), + parent=dict(member_name='server', collection_name='servers')) + resources.append(res) + + return resources + + def get_controller_extensions(self): + return [] diff --git a/nova/tests/functional/v3/api_samples/os-virtual-interfaces/server-post-req.json.tpl b/nova/tests/functional/v3/api_samples/os-virtual-interfaces/server-post-req.json.tpl new file mode 100644 index 000000000000..3271a58a7d42 --- /dev/null +++ b/nova/tests/functional/v3/api_samples/os-virtual-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/functional/v3/api_samples/os-virtual-interfaces/server-post-resp.json.tpl b/nova/tests/functional/v3/api_samples/os-virtual-interfaces/server-post-resp.json.tpl new file mode 100644 index 000000000000..d5f030c8730b --- /dev/null +++ b/nova/tests/functional/v3/api_samples/os-virtual-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/functional/v3/api_samples/os-virtual-interfaces/vifs-list-resp.json.tpl b/nova/tests/functional/v3/api_samples/os-virtual-interfaces/vifs-list-resp.json.tpl new file mode 100644 index 000000000000..af0b7e05a795 --- /dev/null +++ b/nova/tests/functional/v3/api_samples/os-virtual-interfaces/vifs-list-resp.json.tpl @@ -0,0 +1,8 @@ +{ + "virtual_interfaces": [ + { + "id": "%(id)s", + "mac_address": "%(mac_addr)s" + } + ] +} \ No newline at end of file diff --git a/nova/tests/unit/api/openstack/compute/contrib/test_virtual_interfaces.py b/nova/tests/unit/api/openstack/compute/contrib/test_virtual_interfaces.py index e8484d61b9dc..6d2b1810cacd 100644 --- a/nova/tests/unit/api/openstack/compute/contrib/test_virtual_interfaces.py +++ b/nova/tests/unit/api/openstack/compute/contrib/test_virtual_interfaces.py @@ -17,7 +17,8 @@ from lxml import etree from oslo.serialization import jsonutils import webob -from nova.api.openstack.compute.contrib import virtual_interfaces +from nova.api.openstack.compute.contrib import virtual_interfaces as vi20 +from nova.api.openstack.compute.plugins.v3 import virtual_interfaces as vi21 from nova.api.openstack import wsgi from nova import compute from nova.compute import api as compute_api @@ -48,24 +49,38 @@ class FakeRequest(object): self.environ = {'nova.context': context} -class ServerVirtualInterfaceTest(test.NoDBTestCase): +class ServerVirtualInterfaceTestV21(test.NoDBTestCase): def setUp(self): - super(ServerVirtualInterfaceTest, self).setUp() - self.stubs.Set(compute.api.API, "get", - compute_api_get) - self.stubs.Set(network.api.API, "get_vifs_by_instance", - get_vifs_by_instance) + super(ServerVirtualInterfaceTestV21, self).setUp() self.flags( osapi_compute_extension=[ 'nova.api.openstack.compute.contrib.select_extensions'], osapi_compute_ext_list=['Virtual_interfaces']) + self.stubs.Set(compute.api.API, "get", + compute_api_get) + self.stubs.Set(network.api.API, "get_vifs_by_instance", + get_vifs_by_instance) + self.app = self._get_app() + self._set_controller() + + def _set_controller(self): + self.controller = vi21.ServerVirtualInterfaceController() + + def _get_app(self): + return fakes.wsgi_app_v21(init_only=('os-virtual-interfaces', + 'servers')) + + def _get_response(self, req): + return req.get_response(self.app) + + def _get_url(self): + return '/v2/fake/servers/abcd/os-virtual-interfaces' def test_get_virtual_interfaces_list(self): - url = '/v2/fake/servers/abcd/os-virtual-interfaces' + url = self._get_url() req = webob.Request.blank(url) - res = req.get_response(fakes.wsgi_app( - init_only=('os-virtual-interfaces',))) + res = self._get_response(req) self.assertEqual(res.status_int, 200) res_dict = jsonutils.loads(res.body) response = {'virtual_interfaces': [ @@ -88,15 +103,24 @@ class ServerVirtualInterfaceTest(test.NoDBTestCase): self.mox.ReplayAll() self.assertRaises( webob.exc.HTTPNotFound, - virtual_interfaces.ServerVirtualInterfaceController().index, + self.controller.index, fake_req, 'fake_uuid') -class ServerVirtualInterfaceSerializerTest(test.NoDBTestCase): +class ServerVirtualInterfaceTestV20(ServerVirtualInterfaceTestV21): + + def _set_controller(self): + self.controller = vi20.ServerVirtualInterfaceController() + + def _get_app(self): + return fakes.wsgi_app(init_only=('os-virtual-interfaces',)) + + +class ServerVirtualInterfaceSerializerTestV20(test.NoDBTestCase): def setUp(self): - super(ServerVirtualInterfaceSerializerTest, self).setUp() + super(ServerVirtualInterfaceSerializerTestV20, self).setUp() self.namespace = wsgi.XMLNS_V11 - self.serializer = virtual_interfaces.VirtualInterfaceTemplate() + self.serializer = vi20.VirtualInterfaceTemplate() def _tag(self, elem): tagname = elem.tag diff --git a/nova/tests/unit/fake_policy.py b/nova/tests/unit/fake_policy.py index be0b88cc19e2..c1b3d808f97f 100644 --- a/nova/tests/unit/fake_policy.py +++ b/nova/tests/unit/fake_policy.py @@ -309,6 +309,7 @@ policy_data = """ "compute_extension:v3:os-suspend-server:resume": "", "compute_extension:users": "", "compute_extension:virtual_interfaces": "", + "compute_extension:v3:os-virtual-interfaces": "", "compute_extension:virtual_storage_arrays": "", "compute_extension:volumes": "", "compute_extension:volume_attachments:index": "", diff --git a/setup.cfg b/setup.cfg index 11d3359abc24..c6be5d1b5304 100644 --- a/setup.cfg +++ b/setup.cfg @@ -134,6 +134,7 @@ nova.api.v3.extensions = tenant_networks = nova.api.openstack.compute.plugins.v3.tenant_networks:TenantNetworks used_limits = nova.api.openstack.compute.plugins.v3.used_limits:UsedLimits versions = nova.api.openstack.compute.plugins.v3.versions:Versions + virtual_interfaces = nova.api.openstack.compute.plugins.v3.virtual_interfaces:VirtualInterfaces volumes = nova.api.openstack.compute.plugins.v3.volumes:Volumes nova.api.v3.extensions.server.create =