From 9a47137f535e0cdc0755d3f4deb9ebb1de248724 Mon Sep 17 00:00:00 2001 From: Eli Qiao Date: Thu, 11 Sep 2014 14:13:41 +0800 Subject: [PATCH] Port floating_ips extension to v2.1 This patch ports floating_ips extension from v2 to v2.1, and have v2 unit test cases shared between v2.1 and v2. Partially implements blueprint v2-on-v3-api Change-Id: Ib06c0d6c7ff123f09a99ed94f4b0009695897602 --- .../floating-ips-create-nopool-req.json | 0 .../floating-ips-create-req.json | 3 + .../floating-ips-create-resp.json | 9 + .../floating-ips-get-resp.json | 9 + .../floating-ips-list-empty-resp.json | 3 + .../floating-ips-list-resp.json | 18 ++ etc/nova/policy.json | 2 + .../compute/plugins/v3/floating_ips.py | 300 ++++++++++++++++++ .../floating-ips-create-nopool-req.json.tpl | 0 .../floating-ips-create-req.json.tpl | 3 + .../floating-ips-create-resp.json.tpl | 9 + .../floating-ips-get-resp.json.tpl | 9 + .../floating-ips-list-empty-resp.json.tpl | 3 + .../floating-ips-list-resp.json.tpl | 19 ++ nova/tests/functional/v3/test_floating_ips.py | 106 +++++++ .../compute/contrib/test_floating_ips.py | 155 +++++---- nova/tests/unit/fake_policy.py | 1 + setup.cfg | 1 + 18 files changed, 587 insertions(+), 63 deletions(-) create mode 100644 doc/v3/api_samples/os-floating-ips/floating-ips-create-nopool-req.json create mode 100644 doc/v3/api_samples/os-floating-ips/floating-ips-create-req.json create mode 100644 doc/v3/api_samples/os-floating-ips/floating-ips-create-resp.json create mode 100644 doc/v3/api_samples/os-floating-ips/floating-ips-get-resp.json create mode 100644 doc/v3/api_samples/os-floating-ips/floating-ips-list-empty-resp.json create mode 100644 doc/v3/api_samples/os-floating-ips/floating-ips-list-resp.json create mode 100644 nova/api/openstack/compute/plugins/v3/floating_ips.py create mode 100644 nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-create-nopool-req.json.tpl create mode 100644 nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-create-req.json.tpl create mode 100644 nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-create-resp.json.tpl create mode 100644 nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-get-resp.json.tpl create mode 100644 nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-list-empty-resp.json.tpl create mode 100644 nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-list-resp.json.tpl create mode 100644 nova/tests/functional/v3/test_floating_ips.py diff --git a/doc/v3/api_samples/os-floating-ips/floating-ips-create-nopool-req.json b/doc/v3/api_samples/os-floating-ips/floating-ips-create-nopool-req.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/doc/v3/api_samples/os-floating-ips/floating-ips-create-req.json b/doc/v3/api_samples/os-floating-ips/floating-ips-create-req.json new file mode 100644 index 0000000000..511b009bed --- /dev/null +++ b/doc/v3/api_samples/os-floating-ips/floating-ips-create-req.json @@ -0,0 +1,3 @@ +{ + "pool": "nova" +} \ No newline at end of file diff --git a/doc/v3/api_samples/os-floating-ips/floating-ips-create-resp.json b/doc/v3/api_samples/os-floating-ips/floating-ips-create-resp.json new file mode 100644 index 0000000000..fe161a7dd1 --- /dev/null +++ b/doc/v3/api_samples/os-floating-ips/floating-ips-create-resp.json @@ -0,0 +1,9 @@ +{ + "floating_ip": { + "fixed_ip": null, + "id": 1, + "instance_id": null, + "ip": "10.10.10.1", + "pool": "nova" + } +} \ No newline at end of file diff --git a/doc/v3/api_samples/os-floating-ips/floating-ips-get-resp.json b/doc/v3/api_samples/os-floating-ips/floating-ips-get-resp.json new file mode 100644 index 0000000000..fe161a7dd1 --- /dev/null +++ b/doc/v3/api_samples/os-floating-ips/floating-ips-get-resp.json @@ -0,0 +1,9 @@ +{ + "floating_ip": { + "fixed_ip": null, + "id": 1, + "instance_id": null, + "ip": "10.10.10.1", + "pool": "nova" + } +} \ No newline at end of file diff --git a/doc/v3/api_samples/os-floating-ips/floating-ips-list-empty-resp.json b/doc/v3/api_samples/os-floating-ips/floating-ips-list-empty-resp.json new file mode 100644 index 0000000000..121dbd084e --- /dev/null +++ b/doc/v3/api_samples/os-floating-ips/floating-ips-list-empty-resp.json @@ -0,0 +1,3 @@ +{ + "floating_ips": [] +} \ No newline at end of file diff --git a/doc/v3/api_samples/os-floating-ips/floating-ips-list-resp.json b/doc/v3/api_samples/os-floating-ips/floating-ips-list-resp.json new file mode 100644 index 0000000000..4d58e0676a --- /dev/null +++ b/doc/v3/api_samples/os-floating-ips/floating-ips-list-resp.json @@ -0,0 +1,18 @@ +{ + "floating_ips": [ + { + "fixed_ip": null, + "id": 1, + "instance_id": null, + "ip": "10.10.10.1", + "pool": "nova" + }, + { + "fixed_ip": null, + "id": 2, + "instance_id": null, + "ip": "10.10.10.2", + "pool": "nova" + } + ] +} \ No newline at end of file diff --git a/etc/nova/policy.json b/etc/nova/policy.json index 00f071def3..70ef01c273 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -163,6 +163,8 @@ "compute_extension:v3:os-floating-ip-pools": "", "compute_extension:v3:os-floating-ip-pools:discoverable": "", "compute_extension:floating_ips": "", + "compute_extension:v3:os-floating-ips": "", + "compute_extension:v3:os-floating-ips:discoverable": "", "compute_extension:floating_ips_bulk": "rule:admin_api", "compute_extension:v3:os-floating-ips-bulk": "rule:admin_api", "compute_extension:v3:os-floating-ips-bulk:discoverable": "", diff --git a/nova/api/openstack/compute/plugins/v3/floating_ips.py b/nova/api/openstack/compute/plugins/v3/floating_ips.py new file mode 100644 index 0000000000..84f1f91d88 --- /dev/null +++ b/nova/api/openstack/compute/plugins/v3/floating_ips.py @@ -0,0 +1,300 @@ +# Copyright 2011 OpenStack Foundation +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. +# Copyright 2011 Grid Dynamics +# Copyright 2011 Eldar Nugaev, Kirill Shileev, Ilya Alekseyev +# +# 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. + +import webob + +from nova.api.openstack import common +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova import compute +from nova.compute import utils as compute_utils +from nova import exception +from nova.i18n import _ +from nova.i18n import _LW +from nova import network +from nova.openstack.common import log as logging +from nova.openstack.common import uuidutils + + +LOG = logging.getLogger(__name__) +ALIAS = 'os-floating-ips' +authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS) + + +def _translate_floating_ip_view(floating_ip): + result = { + 'id': floating_ip['id'], + 'ip': floating_ip['address'], + 'pool': floating_ip['pool'], + } + try: + result['fixed_ip'] = floating_ip['fixed_ip']['address'] + except (TypeError, KeyError, AttributeError): + result['fixed_ip'] = None + try: + result['instance_id'] = floating_ip['fixed_ip']['instance_uuid'] + except (TypeError, KeyError, AttributeError): + result['instance_id'] = None + return {'floating_ip': result} + + +def _translate_floating_ips_view(floating_ips): + return {'floating_ips': [_translate_floating_ip_view(ip)['floating_ip'] + for ip in floating_ips]} + + +def get_instance_by_floating_ip_addr(self, context, address): + snagiibfa = self.network_api.get_instance_id_by_floating_address + instance_id = snagiibfa(context, address) + if instance_id: + return common.get_instance(self.compute_api, context, instance_id, + want_objects=True) + + +def disassociate_floating_ip(self, context, instance, address): + try: + self.network_api.disassociate_floating_ip(context, instance, address) + except exception.Forbidden: + raise webob.exc.HTTPForbidden() + except exception.CannotDisassociateAutoAssignedFloatingIP: + msg = _('Cannot disassociate auto assigned floating ip') + raise webob.exc.HTTPForbidden(explanation=msg) + + +class FloatingIPController(object): + """The Floating IPs API controller for the OpenStack API.""" + + def __init__(self): + self.compute_api = compute.API() + self.network_api = network.API() + super(FloatingIPController, self).__init__() + + @extensions.expected_errors(404) + def show(self, req, id): + """Return data about the given floating ip.""" + context = req.environ['nova.context'] + authorize(context) + + try: + floating_ip = self.network_api.get_floating_ip(context, id) + except (exception.NotFound, exception.InvalidID): + msg = _("Floating ip not found for id %s") % id + raise webob.exc.HTTPNotFound(explanation=msg) + + return _translate_floating_ip_view(floating_ip) + + @extensions.expected_errors(()) + def index(self, req): + """Return a list of floating ips allocated to a project.""" + context = req.environ['nova.context'] + authorize(context) + + floating_ips = self.network_api.get_floating_ips_by_project(context) + + return _translate_floating_ips_view(floating_ips) + + @extensions.expected_errors((403, 404)) + def create(self, req, body=None): + context = req.environ['nova.context'] + authorize(context) + + pool = None + if body and 'pool' in body: + pool = body['pool'] + try: + address = self.network_api.allocate_floating_ip(context, pool) + ip = self.network_api.get_floating_ip_by_address(context, address) + except exception.NoMoreFloatingIps: + if pool: + msg = _("No more floating ips in pool %s.") % pool + else: + msg = _("No more floating ips available.") + raise webob.exc.HTTPNotFound(explanation=msg) + except exception.FloatingIpLimitExceeded: + if pool: + msg = _("IP allocation over quota in pool %s.") % pool + else: + msg = _("IP allocation over quota.") + raise webob.exc.HTTPForbidden(explanation=msg) + except exception.FloatingIpPoolNotFound as e: + raise webob.exc.HTTPNotFound(explanation=e.format_message()) + + return _translate_floating_ip_view(ip) + + @wsgi.response(202) + @extensions.expected_errors((400, 403, 404)) + def delete(self, req, id): + context = req.environ['nova.context'] + authorize(context) + + # get the floating ip object + try: + floating_ip = self.network_api.get_floating_ip(context, id) + except (exception.NotFound, exception.InvalidID): + msg = _("Floating ip not found for id %s") % id + raise webob.exc.HTTPNotFound(explanation=msg) + address = floating_ip['address'] + + # get the associated instance object (if any) + instance = get_instance_by_floating_ip_addr(self, context, address) + try: + self.network_api.disassociate_and_release_floating_ip( + context, instance, floating_ip) + except exception.Forbidden: + raise webob.exc.HTTPForbidden() + except exception.CannotDisassociateAutoAssignedFloatingIP: + msg = _('Cannot disassociate auto assigned floating ip') + raise webob.exc.HTTPForbidden(explanation=msg) + + +class FloatingIPActionController(wsgi.Controller): + def __init__(self, *args, **kwargs): + super(FloatingIPActionController, self).__init__(*args, **kwargs) + self.compute_api = compute.API() + self.network_api = network.API() + + @extensions.expected_errors((400, 403, 404)) + @wsgi.action('addFloatingIp') + def _add_floating_ip(self, req, id, body): + """Associate floating_ip to an instance.""" + context = req.environ['nova.context'] + authorize(context) + + try: + address = body['addFloatingIp']['address'] + except TypeError: + msg = _("Missing parameter dict") + raise webob.exc.HTTPBadRequest(explanation=msg) + except KeyError: + msg = _("Address not specified") + raise webob.exc.HTTPBadRequest(explanation=msg) + + instance = common.get_instance(self.compute_api, context, id, + want_objects=True) + cached_nwinfo = compute_utils.get_nw_info_for_instance(instance) + if not cached_nwinfo: + msg = _('No nw_info cache associated with instance') + raise webob.exc.HTTPBadRequest(explanation=msg) + + fixed_ips = cached_nwinfo.fixed_ips() + if not fixed_ips: + msg = _('No fixed ips associated to instance') + raise webob.exc.HTTPBadRequest(explanation=msg) + + fixed_address = None + if 'fixed_address' in body['addFloatingIp']: + fixed_address = body['addFloatingIp']['fixed_address'] + for fixed in fixed_ips: + if fixed['address'] == fixed_address: + break + else: + msg = _('Specified fixed address not assigned to instance') + raise webob.exc.HTTPBadRequest(explanation=msg) + + if not fixed_address: + fixed_address = fixed_ips[0]['address'] + if len(fixed_ips) > 1: + LOG.warning(_LW('multiple fixed_ips exist, using the first: ' + '%s'), fixed_address) + + try: + self.network_api.associate_floating_ip(context, instance, + floating_address=address, + fixed_address=fixed_address) + except exception.FloatingIpAssociated: + msg = _('floating ip is already associated') + raise webob.exc.HTTPBadRequest(explanation=msg) + except exception.NoFloatingIpInterface: + msg = _('l3driver call to add floating ip failed') + raise webob.exc.HTTPBadRequest(explanation=msg) + except exception.FloatingIpNotFoundForAddress: + msg = _('floating ip not found') + raise webob.exc.HTTPNotFound(explanation=msg) + except exception.Forbidden as e: + raise webob.exc.HTTPForbidden(explanation=e.format_message()) + except Exception as e: + msg = _('Unable to associate floating ip %(address)s to ' + 'fixed ip %(fixed_address)s for instance %(id)s. ' + 'Error: %(error)s') % ( + {'address': address, 'fixed_address': fixed_address, + 'id': id, 'error': e}) + LOG.exception(msg) + raise webob.exc.HTTPBadRequest(explanation=msg) + + return webob.Response(status_int=202) + + @extensions.expected_errors((400, 403, 404, 422)) + @wsgi.action('removeFloatingIp') + def _remove_floating_ip(self, req, id, body): + """Dissociate floating_ip from an instance.""" + context = req.environ['nova.context'] + authorize(context) + + try: + address = body['removeFloatingIp']['address'] + except TypeError: + msg = _("Missing parameter dict") + raise webob.exc.HTTPBadRequest(explanation=msg) + except KeyError: + msg = _("Address not specified") + raise webob.exc.HTTPBadRequest(explanation=msg) + + # get the floating ip object + try: + floating_ip = self.network_api.get_floating_ip_by_address(context, + address) + except exception.FloatingIpNotFoundForAddress: + msg = _("floating ip not found") + raise webob.exc.HTTPNotFound(explanation=msg) + + # get the associated instance object (if any) + instance = get_instance_by_floating_ip_addr(self, context, address) + + # disassociate if associated + if (instance and + floating_ip.get('fixed_ip_id') and + (uuidutils.is_uuid_like(id) and + [instance['uuid'] == id] or + [instance['id'] == id])[0]): + try: + disassociate_floating_ip(self, context, instance, address) + except exception.FloatingIpNotAssociated: + msg = _('Floating ip is not associated') + raise webob.exc.HTTPBadRequest(explanation=msg) + return webob.Response(status_int=202) + else: + msg = _("Floating ip %(address)s is not associated with instance " + "%(id)s.") % {'address': address, 'id': id} + raise webob.exc.HTTPUnprocessableEntity(explanation=msg) + + +class FloatingIps(extensions.V3APIExtensionBase): + """Floating IPs support.""" + + name = "FloatingIps" + alias = ALIAS + version = 1 + + def get_resources(self): + resource = [extensions.ResourceExtension(ALIAS, + FloatingIPController())] + return resource + + def get_controller_extensions(self): + controller = FloatingIPActionController() + extension = extensions.ControllerExtension(self, 'servers', controller) + return [extension] diff --git a/nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-create-nopool-req.json.tpl b/nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-create-nopool-req.json.tpl new file mode 100644 index 0000000000..e69de29bb2 diff --git a/nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-create-req.json.tpl b/nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-create-req.json.tpl new file mode 100644 index 0000000000..24129f4958 --- /dev/null +++ b/nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-create-req.json.tpl @@ -0,0 +1,3 @@ +{ + "pool": "%(pool)s" +} \ No newline at end of file diff --git a/nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-create-resp.json.tpl b/nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-create-resp.json.tpl new file mode 100644 index 0000000000..10ee8d9bd4 --- /dev/null +++ b/nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-create-resp.json.tpl @@ -0,0 +1,9 @@ +{ + "floating_ip": { + "fixed_ip": null, + "id": 1, + "instance_id": null, + "ip": "10.10.10.1", + "pool": "nova" + } +} diff --git a/nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-get-resp.json.tpl b/nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-get-resp.json.tpl new file mode 100644 index 0000000000..10ee8d9bd4 --- /dev/null +++ b/nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-get-resp.json.tpl @@ -0,0 +1,9 @@ +{ + "floating_ip": { + "fixed_ip": null, + "id": 1, + "instance_id": null, + "ip": "10.10.10.1", + "pool": "nova" + } +} diff --git a/nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-list-empty-resp.json.tpl b/nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-list-empty-resp.json.tpl new file mode 100644 index 0000000000..12f118da50 --- /dev/null +++ b/nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-list-empty-resp.json.tpl @@ -0,0 +1,3 @@ +{ + "floating_ips": [] +} diff --git a/nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-list-resp.json.tpl b/nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-list-resp.json.tpl new file mode 100644 index 0000000000..06f57451c9 --- /dev/null +++ b/nova/tests/functional/v3/api_samples/os-floating-ips/floating-ips-list-resp.json.tpl @@ -0,0 +1,19 @@ +{ + "floating_ips": [ + { + "fixed_ip": null, + "id": 1, + "instance_id": null, + "ip": "10.10.10.1", + "pool": "nova" + }, + { + "fixed_ip": null, + "id": 2, + "instance_id": null, + "ip": "10.10.10.2", + "pool": "nova" + } + ] +} + diff --git a/nova/tests/functional/v3/test_floating_ips.py b/nova/tests/functional/v3/test_floating_ips.py new file mode 100644 index 0000000000..574df2ce34 --- /dev/null +++ b/nova/tests/functional/v3/test_floating_ips.py @@ -0,0 +1,106 @@ +# Copyright 2014 IBM Corp. +# +# 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 oslo.config import cfg + +from nova import context +from nova.tests.functional.v3 import api_sample_base + +CONF = cfg.CONF +CONF.import_opt('default_floating_pool', 'nova.network.floating_ips') +CONF.import_opt('public_interface', 'nova.network.linux_net') + + +class FloatingIpsTest(api_sample_base.ApiSampleTestBaseV3): + extension_name = "os-floating-ips" + + def setUp(self): + super(FloatingIpsTest, self).setUp() + pool = CONF.default_floating_pool + interface = CONF.public_interface + + self.ip_pool = [ + { + 'address': "10.10.10.1", + 'pool': pool, + 'interface': interface + }, + { + 'address': "10.10.10.2", + 'pool': pool, + 'interface': interface + }, + { + 'address': "10.10.10.3", + 'pool': pool, + 'interface': interface + }, + ] + self.compute.db.floating_ip_bulk_create( + context.get_admin_context(), self.ip_pool) + + def tearDown(self): + self.compute.db.floating_ip_bulk_destroy( + context.get_admin_context(), self.ip_pool) + super(FloatingIpsTest, self).tearDown() + + def test_floating_ips_list_empty(self): + response = self._do_get('os-floating-ips') + + subs = self._get_regexes() + self._verify_response('floating-ips-list-empty-resp', + subs, response, 200) + + def test_floating_ips_list(self): + self._do_post('os-floating-ips', + 'floating-ips-create-nopool-req', + {}) + self._do_post('os-floating-ips', + 'floating-ips-create-nopool-req', + {}) + + response = self._do_get('os-floating-ips') + subs = self._get_regexes() + self._verify_response('floating-ips-list-resp', + subs, response, 200) + + def test_floating_ips_create_nopool(self): + response = self._do_post('os-floating-ips', + 'floating-ips-create-nopool-req', + {}) + subs = self._get_regexes() + self._verify_response('floating-ips-create-resp', + subs, response, 200) + + def test_floating_ips_create(self): + response = self._do_post('os-floating-ips', + 'floating-ips-create-req', + {"pool": CONF.default_floating_pool}) + subs = self._get_regexes() + self._verify_response('floating-ips-create-resp', subs, response, 200) + + def test_floating_ips_get(self): + self.test_floating_ips_create() + # NOTE(sdague): the first floating ip will always have 1 as an id, + # but it would be better if we could get this from the create + response = self._do_get('os-floating-ips/%d' % 1) + subs = self._get_regexes() + self._verify_response('floating-ips-get-resp', subs, response, 200) + + def test_floating_ips_delete(self): + self.test_floating_ips_create() + response = self._do_delete('os-floating-ips/%d' % 1) + self.assertEqual(response.status_code, 202) + self.assertEqual(response.content, "") diff --git a/nova/tests/unit/api/openstack/compute/contrib/test_floating_ips.py b/nova/tests/unit/api/openstack/compute/contrib/test_floating_ips.py index b383d1dbc1..081075d276 100644 --- a/nova/tests/unit/api/openstack/compute/contrib/test_floating_ips.py +++ b/nova/tests/unit/api/openstack/compute/contrib/test_floating_ips.py @@ -22,7 +22,8 @@ import mock from oslo.serialization import jsonutils import webob -from nova.api.openstack.compute.contrib import floating_ips +from nova.api.openstack.compute.contrib import floating_ips as fips_v2 +from nova.api.openstack.compute.plugins.v3 import floating_ips as fips_v21 from nova.api.openstack import extensions from nova import compute from nova.compute import utils as compute_utils @@ -105,18 +106,16 @@ def get_instance_by_floating_ip_addr(self, context, address): return None -class FloatingIpTestNeutron(test.NoDBTestCase): +class FloatingIpTestNeutronV21(test.NoDBTestCase): + floating_ips = fips_v21 def setUp(self): - super(FloatingIpTestNeutron, self).setUp() + super(FloatingIpTestNeutronV21, self).setUp() self.flags(network_api_class='nova.network.neutronv2.api.API') - self.controller = floating_ips.FloatingIPController() - - def _get_fake_request(self): - return fakes.HTTPRequest.blank('/v2/fake/os-floating-ips/1') + self.controller = self.floating_ips.FloatingIPController() def test_floatingip_delete(self): - req = self._get_fake_request() + req = fakes.HTTPRequest.blank('/v2/fake/os-floating-ips/1') fip_val = {'address': '1.1.1.1', 'fixed_ip_id': '192.168.1.2'} with contextlib.nested( mock.patch.object(self.controller.network_api, @@ -140,9 +139,15 @@ class FloatingIpTestNeutron(test.NoDBTestCase): self.assertTrue(dis_and_del.called) -class FloatingIpTest(test.TestCase): +class FloatingIpTestNeutronV2(FloatingIpTestNeutronV21): + floating_ips = fips_v2 + + +class FloatingIpTestV21(test.TestCase): floating_ip = "10.10.10.10" floating_ip_2 = "10.10.10.11" + floating_ips = fips_v21 + url = '/v2/fake/servers/test_inst/action' def _create_floating_ips(self, floating_ips=None): """Create a floating ip object.""" @@ -169,11 +174,11 @@ class FloatingIpTest(test.TestCase): def _get_fake_server_request(self): return fakes.HTTPRequest.blank('/v2/fake/servers/test_inst/action') - def _get_fake_response(self, req, init_only): - return req.get_response(fakes.wsgi_app(init_only=(init_only,))) + def _get_fake_app(self): + return fakes.wsgi_app_v21(init_only=('servers', 'os-floating-ips')) def setUp(self): - super(FloatingIpTest, self).setUp() + super(FloatingIpTestV21, self).setUp() self.stubs.Set(compute.api.API, "get", compute_api_get) self.stubs.Set(network.api.API, "get_floating_ip", @@ -200,17 +205,14 @@ class FloatingIpTest(test.TestCase): self.ext_mgr = extensions.ExtensionManager() self.ext_mgr.extensions = {} - self.controller = floating_ips.FloatingIPController() - self.manager = floating_ips.FloatingIPActionController(self.ext_mgr) - - self.flags( - osapi_compute_extension=[ - 'nova.api.openstack.compute.contrib.select_extensions'], - osapi_compute_ext_list=['Floating_ips']) + self.controller = self.floating_ips.FloatingIPController() + self.manager = self.floating_ips.\ + FloatingIPActionController(self.ext_mgr) + self.app = self._get_fake_app() def tearDown(self): self._delete_floating_ip() - super(FloatingIpTest, self).tearDown() + super(FloatingIpTestV21, self).tearDown() def test_floatingip_delete(self): req = self._get_fake_fip_request('1') @@ -237,7 +239,7 @@ class FloatingIpTest(test.TestCase): floating_ip_address) # NOTE(vish): network_get uses the id not the address floating_ip = db.floating_ip_get(self.context, floating_ip['id']) - view = floating_ips._translate_floating_ip_view(floating_ip) + view = self.floating_ips._translate_floating_ip_view(floating_ip) self.assertIn('floating_ip', view) self.assertTrue(view['floating_ip']['id']) self.assertEqual(view['floating_ip']['ip'], self.floating_ip) @@ -247,7 +249,7 @@ class FloatingIpTest(test.TestCase): def test_translate_floating_ip_view_dict(self): floating_ip = {'id': 0, 'address': '10.0.0.10', 'pool': 'nova', 'fixed_ip': None} - view = floating_ips._translate_floating_ip_view(floating_ip) + view = self.floating_ips._translate_floating_ip_view(floating_ip) self.assertIn('floating_ip', view) def test_floating_ips_list(self): @@ -275,7 +277,7 @@ class FloatingIpTest(test.TestCase): req = self._get_fake_fip_request('9876') req.method = 'DELETE' - res = self._get_fake_response(req, 'os-floating-ips') + res = req.get_response(self.app) self.assertEqual(res.status_int, 404) expected_msg = ('{"itemNotFound": {"message": "Floating ip not found ' 'for id 9876", "code": 404}}') @@ -293,14 +295,14 @@ class FloatingIpTest(test.TestCase): self.stubs.Set(network.api.API, "get_floating_ip", fake_get_floating_ip) - self.stubs.Set(floating_ips, "get_instance_by_floating_ip_addr", + self.stubs.Set(self.floating_ips, "get_instance_by_floating_ip_addr", fake_get_instance_by_floating_ip_addr) - self.stubs.Set(floating_ips, "disassociate_floating_ip", + self.stubs.Set(self.floating_ips, "disassociate_floating_ip", fake_disassociate_floating_ip) req = self._get_fake_fip_request('1') req.method = 'DELETE' - res = self._get_fake_response(req, 'os-floating-ips') + res = req.get_response(self.app) self.assertEqual(res.status_int, 202) def test_floating_ip_show(self): @@ -319,7 +321,7 @@ class FloatingIpTest(test.TestCase): fake_get_floating_ip) req = self._get_fake_fip_request('9876') - res = self._get_fake_response(req, 'os-floating-ips') + res = req.get_response(self.app) self.assertEqual(res.status_int, 404) expected_msg = ('{"itemNotFound": {"message": "Floating ip not found ' 'for id 9876", "code": 404}}') @@ -475,25 +477,6 @@ class FloatingIpTest(test.TestCase): self.manager._add_floating_ip, req, 'test_inst', body) - def test_not_extended_floating_ip_associate_fixed(self): - # Check that fixed_address is ignored if os-extended-floating-ips - # is not loaded - fixed_address_requested = '192.168.1.101' - fixed_address_allocated = '192.168.1.100' - - def fake_associate_floating_ip(*args, **kwargs): - self.assertEqual(fixed_address_allocated, - kwargs['fixed_address']) - - self.stubs.Set(network.api.API, "associate_floating_ip", - fake_associate_floating_ip) - body = dict(addFloatingIp=dict(address=self.floating_ip, - fixed_address=fixed_address_requested)) - - req = self._get_fake_server_request() - rsp = self.manager._add_floating_ip(req, 'test_inst', body) - self.assertEqual(202, rsp.status_int) - def test_associate_not_allocated_floating_ip_to_instance(self): def fake_associate_floating_ip(self, context, instance, floating_address, fixed_address, @@ -504,11 +487,11 @@ class FloatingIpTest(test.TestCase): fake_associate_floating_ip) floating_ip = '10.10.10.11' body = dict(addFloatingIp=dict(address=floating_ip)) - req = self._get_fake_server_request() + req = webob.Request.blank(self.url) req.method = "POST" req.body = jsonutils.dumps(body) req.headers["content-type"] = "application/json" - resp = self._get_fake_response(req, 'servers') + resp = req.get_response(self.app) res_dict = jsonutils.loads(resp.body) self.assertEqual(resp.status_int, 404) self.assertEqual(res_dict['itemNotFound']['message'], @@ -701,9 +684,43 @@ class FloatingIpTest(test.TestCase): body) -class ExtendedFloatingIpTest(test.TestCase): +class FloatingIpTestV2(FloatingIpTestV21): + floating_ips = fips_v2 + + def _get_fake_app(self): + return fakes.wsgi_app(init_only=('servers', 'os-floating-ips')) + + def setUp(self): + super(FloatingIpTestV2, self).setUp() + self.flags( + osapi_compute_extension=[ + 'nova.api.openstack.compute.contrib.select_extensions'], + osapi_compute_ext_list=['Floating_ips']) + + def test_not_extended_floating_ip_associate_fixed(self): + # Check that fixed_address is ignored if os-extended-floating-ips + # is not loaded + fixed_address_requested = '192.168.1.101' + fixed_address_allocated = '192.168.1.100' + + def fake_associate_floating_ip(*args, **kwargs): + self.assertEqual(fixed_address_allocated, + kwargs['fixed_address']) + + self.stubs.Set(network.api.API, "associate_floating_ip", + fake_associate_floating_ip) + body = dict(addFloatingIp=dict(address=self.floating_ip, + fixed_address=fixed_address_requested)) + + req = self._get_fake_server_request() + rsp = self.manager._add_floating_ip(req, 'test_inst', body) + self.assertEqual(202, rsp.status_int) + + +class ExtendedFloatingIpTestV21(test.TestCase): floating_ip = "10.10.10.10" floating_ip_2 = "10.10.10.11" + floating_ips = fips_v21 def _create_floating_ips(self, floating_ips=None): """Create a floating ip object.""" @@ -727,11 +744,11 @@ class ExtendedFloatingIpTest(test.TestCase): def _get_fake_request(self): return fakes.HTTPRequest.blank('/v2/fake/servers/test_inst/action') - def _get_fake_response(self, req, init_only): - return req.get_response(fakes.wsgi_app(init_only=(init_only,))) + def _get_fake_app(self): + return fakes.wsgi_app_v21(init_only=('servers', 'os-floating-ips')) def setUp(self): - super(ExtendedFloatingIpTest, self).setUp() + super(ExtendedFloatingIpTestV21, self).setUp() self.stubs.Set(compute.api.API, "get", compute_api_get) self.stubs.Set(network.api.API, "get_floating_ip", @@ -760,16 +777,14 @@ class ExtendedFloatingIpTest(test.TestCase): self.ext_mgr.extensions = {} self.ext_mgr.extensions['os-floating-ips'] = True self.ext_mgr.extensions['os-extended-floating-ips'] = True - self.controller = floating_ips.FloatingIPController() - self.manager = floating_ips.FloatingIPActionController(self.ext_mgr) - self.flags( - osapi_compute_extension=[ - 'nova.api.openstack.compute.contrib.select_extensions'], - osapi_compute_ext_list=['Floating_ips', 'Extended_floating_ips']) + self.controller = self.floating_ips.FloatingIPController() + self.manager = self.floating_ips.\ + FloatingIPActionController(self.ext_mgr) + self.app = self._get_fake_app() def tearDown(self): self._delete_floating_ip() - super(ExtendedFloatingIpTest, self).tearDown() + super(ExtendedFloatingIpTestV21, self).tearDown() def test_extended_floating_ip_associate_fixed(self): fixed_address = '192.168.1.101' @@ -799,16 +814,30 @@ class ExtendedFloatingIpTest(test.TestCase): req.method = "POST" req.body = jsonutils.dumps(body) req.headers["content-type"] = "application/json" - resp = self._get_fake_response(req, 'servers') + resp = req.get_response(self.app) res_dict = jsonutils.loads(resp.body) self.assertEqual(resp.status_int, 400) self.assertEqual(res_dict['badRequest']['message'], "Specified fixed address not assigned to instance") -class FloatingIpSerializerTest(test.TestCase): +class ExtendedFloatingIpTestV2(ExtendedFloatingIpTestV21): + floating_ips = fips_v2 + + def _get_fake_app(self): + return fakes.wsgi_app(init_only=('servers', 'os-floating-ips')) + + def setUp(self): + super(ExtendedFloatingIpTestV2, self).setUp() + self.flags( + osapi_compute_extension=[ + 'nova.api.openstack.compute.contrib.select_extensions'], + osapi_compute_ext_list=['Floating_ips', 'Extended_floating_ips']) + + +class FloatingIpSerializerTestV2(test.TestCase): def test_default_serializer(self): - serializer = floating_ips.FloatingIPTemplate() + serializer = fips_v2.FloatingIPTemplate() text = serializer.serialize(dict( floating_ip=dict( instance_id=1, @@ -825,7 +854,7 @@ class FloatingIpSerializerTest(test.TestCase): self.assertEqual('1', tree.get('id')) def test_index_serializer(self): - serializer = floating_ips.FloatingIPsTemplate() + serializer = fips_v2.FloatingIPsTemplate() text = serializer.serialize(dict( floating_ips=[ dict(instance_id=1, diff --git a/nova/tests/unit/fake_policy.py b/nova/tests/unit/fake_policy.py index d60af3eb33..be0b88cc19 100644 --- a/nova/tests/unit/fake_policy.py +++ b/nova/tests/unit/fake_policy.py @@ -218,6 +218,7 @@ policy_data = """ "compute_extension:floating_ip_pools": "", "compute_extension:v3:os-floating-ip-pools": "", "compute_extension:floating_ips": "", + "compute_extension:v3:os-floating-ips": "", "compute_extension:floating_ips_bulk": "", "compute_extension:v3:os-floating-ips-bulk": "", "compute_extension:fping": "", diff --git a/setup.cfg b/setup.cfg index 8d679b5fa7..e20ecceb4f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -89,6 +89,7 @@ nova.api.v3.extensions = flavor_manage = nova.api.openstack.compute.plugins.v3.flavor_manage:FlavorManage floating_ip_dns = nova.api.openstack.compute.plugins.v3.floating_ip_dns:FloatingIpDns floating_ip_pools = nova.api.openstack.compute.plugins.v3.floating_ip_pools:FloatingIpPools + floating_ips = nova.api.openstack.compute.plugins.v3.floating_ips:FloatingIps floating_ips_bulk = nova.api.openstack.compute.plugins.v3.floating_ips_bulk:FloatingIpsBulk fping = nova.api.openstack.compute.plugins.v3.fping:Fping hide_server_addresses = nova.api.openstack.compute.plugins.v3.hide_server_addresses:HideServerAddresses