From 82599c77346bbefd550ea4ee6c0b13a3df4950af Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 15 Jun 2012 16:35:31 -0500 Subject: moved update cache functionality to the network api previously the network manager get_instance_nw_info was responsible for updating the cache. This is to prevent calling that function in a confusing way. part 2 of this patch was fixing bug997763 floating_ip_associate was removed from the compute api. network api associate is now called directly. network api floating_ip functions now require instance as an argument in order to update cache. Change-Id: Ie8daa017b99e48769afbac4862696ef0a8eb1067 --- nova/api/ec2/cloud.py | 45 +++++++++++-- nova/api/ec2/ec2utils.py | 20 ++---- nova/api/openstack/compute/contrib/floating_ips.py | 75 ++++++++++++++++++---- nova/compute/api.py | 38 ----------- nova/network/api.py | 67 +++++++++++++++---- nova/network/manager.py | 18 ++++-- nova/tests/api/ec2/test_cloud.py | 9 ++- .../openstack/compute/contrib/test_floating_ips.py | 16 +++-- nova/tests/compute/test_compute.py | 43 ------------- nova/tests/network/test_manager.py | 4 +- 10 files changed, 197 insertions(+), 138 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 037a84783..fb6768d4c 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -1111,17 +1111,48 @@ class CloudController(object): " instance %(instance_id)s") % locals(), context=context) instance_id = ec2utils.ec2_id_to_id(instance_id) instance = self.compute_api.get(context, instance_id) + + cached_ipinfo = ec2utils.get_ip_info_for_instance(context, instance) + fixed_ips = cached_ipinfo['fixed_ips'] + cached_ipinfo['fixed_ip6s'] + if not fixed_ips: + msg = _('Unable to associate IP Address, no fixed_ips.') + raise exception.EC2APIError(msg) + + # TODO(tr3buchet): this will associate the floating IP with the + # first fixed_ip an instance has. This should be + # changed to support specifying a particular fixed_ip if + # multiple exist but this may not apply to ec2.. + if len(fixed_ips) > 1: + msg = _('multiple fixed_ips exist, using the first: %s') + LOG.warning(msg, fixed_ips[0]) + try: - self.compute_api.associate_floating_ip(context, - instance, - address=public_ip) - return {'return': "true"} - except exception.FloatingIpNotFound: - raise exception.EC2APIError(_('Unable to associate IP Address.')) + self.network_api.associate_floating_ip(context, instance, + floating_address=public_ip, + fixed_address=fixed_ips[0]) + return {'return': 'true'} + except exception.FloatingIpAssociated: + msg = _('Floating ip is already associated.') + raise exception.EC2APIError(msg) + except exception.NoFloatingIpInterface: + msg = _('l3driver call to add floating ip failed.') + raise exception.EC2APIError(msg) + except: + msg = _('Error, unable to associate floating ip.') + raise exception.EC2APIError(msg) def disassociate_address(self, context, public_ip, **kwargs): + instance_id = self.network_api.get_instance_id_by_floating_address( + context, public_ip) + instance = self.compute_api.get(context, instance_id) LOG.audit(_("Disassociate address %s"), public_ip, context=context) - self.network_api.disassociate_floating_ip(context, address=public_ip) + try: + self.network_api.disassociate_floating_ip(context, instance, + address=public_ip) + except exception.FloatingIpNotAssociated: + msg = _('Floating ip is not associated.') + raise exception.EC2APIError(msg) + return {'return': "true"} def run_instances(self, context, **kwargs): diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py index ceb695a5e..c12e65dd3 100644 --- a/nova/api/ec2/ec2utils.py +++ b/nova/api/ec2/ec2utils.py @@ -93,19 +93,13 @@ def image_ec2_id(image_id, image_type='ami'): def get_ip_info_for_instance_from_nw_info(nw_info): - ip_info = dict(fixed_ips=[], fixed_ip6s=[], floating_ips=[]) - for vif in nw_info: - vif_fixed_ips = vif.fixed_ips() - - fixed_ips = [ip['address'] - for ip in vif_fixed_ips if ip['version'] == 4] - fixed_ip6s = [ip['address'] - for ip in vif_fixed_ips if ip['version'] == 6] - floating_ips = [ip['address'] - for ip in vif.floating_ips()] - ip_info['fixed_ips'].extend(fixed_ips) - ip_info['fixed_ip6s'].extend(fixed_ip6s) - ip_info['floating_ips'].extend(floating_ips) + ip_info = {} + fixed_ips = nw_info.fixed_ips() + ip_info['fixed_ips'] = [ip['address'] for ip in fixed_ips + if ip['version'] == 4] + ip_info['fixed_ip6s'] = [ip['address'] for ip in fixed_ips + if ip['version'] == 6] + ip_info['floating_ips'] = [ip['address'] for ip in nw_info.floating_ips()] return ip_info diff --git a/nova/api/openstack/compute/contrib/floating_ips.py b/nova/api/openstack/compute/contrib/floating_ips.py index 11da0d770..a3dac71ae 100644 --- a/nova/api/openstack/compute/contrib/floating_ips.py +++ b/nova/api/openstack/compute/contrib/floating_ips.py @@ -23,6 +23,7 @@ from nova.api.openstack import extensions from nova.api.openstack import wsgi from nova.api.openstack import xmlutil from nova import compute +from nova.compute import utils as compute_utils from nova import exception from nova import log as logging from nova import network @@ -79,6 +80,23 @@ def _translate_floating_ips_view(floating_ips): 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 self.compute_api.get(context, instance_id) + + +def disassociate_floating_ip(self, context, instance, address): + try: + self.network_api.disassociate_floating_ip(context, instance, address) + except exception.NotAuthorized: + raise webob.exc.HTTPUnauthorized() + except exception.FloatingIpNotAssociated: + msg = _('Floating ip is not associated') + raise webob.exc.HTTPBadRequest(explanation=msg) + + class FloatingIPController(object): """The Floating IPs API controller for the OpenStack API.""" @@ -163,14 +181,20 @@ class FloatingIPController(object): def delete(self, req, id): context = req.environ['nova.context'] authorize(context) + + # get the floating ip object floating_ip = self.network_api.get_floating_ip(context, id) + address = floating_ip['address'] + + # get the associated instance object (if any) + instance = get_instance_by_floating_ip_addr(self, context, address) + # disassociate if associated if floating_ip.get('fixed_ip_id'): - self.network_api.disassociate_floating_ip(context, - floating_ip['address']) + disassociate_floating_ip(self, context, instance, address) - self.network_api.release_floating_ip(context, - address=floating_ip['address']) + # release ip from project + self.network_api.release_floating_ip(context, address) return webob.Response(status_int=202) def _get_ip_by_id(self, context, value): @@ -201,11 +225,36 @@ class FloatingIPActionController(wsgi.Controller): instance = self.compute_api.get(context, id) + 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) + + # TODO(tr3buchet): this will associate the floating IP with the + # first fixed_ip an instance has. This should be + # changed to support specifying a particular fixed_ip if + # multiple exist. + if len(fixed_ips) > 1: + msg = _('multiple fixed_ips exist, using the first: %s') + LOG.warning(msg, fixed_ips[0]['address']) + try: - self.compute_api.associate_floating_ip(context, instance, - address) - except exception.FixedIpNotFoundForInstance: - msg = _("No fixed ips associated to instance") + self.network_api.associate_floating_ip(context, instance, + floating_address=address, + fixed_address=fixed_ips[0]['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: + msg = _('Error. Unable to associate floating ip') raise webob.exc.HTTPBadRequest(explanation=msg) return webob.Response(status_int=202) @@ -225,13 +274,15 @@ class FloatingIPActionController(wsgi.Controller): msg = _("Address not specified") raise webob.exc.HTTPBadRequest(explanation=msg) + # get the floating ip object floating_ip = self.network_api.get_floating_ip_by_address(context, address) + # get the associated instance object (if any) + instance = get_instance_by_floating_ip_addr(self, context, address) + + # disassociate if associated if floating_ip.get('fixed_ip_id'): - try: - self.network_api.disassociate_floating_ip(context, address) - except exception.NotAuthorized: - raise webob.exc.HTTPUnauthorized() + disassociate_floating_ip(self, context, instance, address) return webob.Response(status_int=202) diff --git a/nova/compute/api.py b/nova/compute/api.py index 1e28f0190..fc6a48725 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1588,44 +1588,6 @@ class API(base.Base): volume_id=volume_id) return instance - @wrap_check_policy - def associate_floating_ip(self, context, instance, address): - """Makes calls to network_api to associate_floating_ip. - - :param address: is a string floating ip address - """ - instance_uuid = instance['uuid'] - - # TODO(tr3buchet): currently network_info doesn't contain floating IPs - # in its info, if this changes, the next few lines will need to - # accommodate the info containing floating as well as fixed ip - # addresses - nw_info = self.network_api.get_instance_nw_info(context.elevated(), - instance) - - if not nw_info: - raise exception.FixedIpNotFoundForInstance( - instance_id=instance_uuid) - - ips = [ip for ip in nw_info[0].fixed_ips()] - - if not ips: - raise exception.FixedIpNotFoundForInstance( - instance_id=instance_uuid) - - # TODO(tr3buchet): this will associate the floating IP with the - # first fixed_ip (lowest id) an instance has. This should be - # changed to support specifying a particular fixed_ip if - # multiple exist. - if len(ips) > 1: - msg = _('multiple fixedips exist, using the first: %s') - LOG.warning(msg, ips[0]['address']) - - self.network_api.associate_floating_ip(context, - floating_address=address, fixed_address=ips[0]['address']) - self.network_api.invalidate_instance_cache(context.elevated(), - instance) - @wrap_check_policy def get_instance_metadata(self, context, instance): """Get all metadata associated with an instance.""" diff --git a/nova/network/api.py b/nova/network/api.py index 715d054d5..70ad5d4c2 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -17,6 +17,8 @@ # License for the specific language governing permissions and limitations # under the License. +import functools + from nova.db import base from nova import flags from nova import log as logging @@ -28,6 +30,43 @@ FLAGS = flags.FLAGS LOG = logging.getLogger(__name__) +def refresh_cache(f): + """ + Decorator to update the instance_info_cache + """ + @functools.wraps(f) + def wrapper(self, context, *args, **kwargs): + res = f(self, context, *args, **kwargs) + try: + # get the instance from arguments + instance = kwargs.get('instance') + if not instance and len(args) > 0: + instance = args[0] + + # if no instance, nothing to do + if instance is None: + return res + + # get nw_info from return if possible, otherwise call for it + nw_info = res + if not nw_info or \ + not isinstance(nw_info, network_model.NetworkInfo): + nw_info = self.get_instance_nw_info(context, instance) + + # update cache + cache = {'network_info': nw_info.json()} + self.db.instance_info_cache_update(context, instance['uuid'], + cache) + except Exception: + LOG.exception('Failed storing info cache', instance=instance) + LOG.debug(_('args: %s') % args) + LOG.debug(_('kwargs: %s') % kwargs) + + # return the original function's return value + return res + return wrapper + + class API(base.Base): """API for interacting with the network manager.""" @@ -95,6 +134,13 @@ class API(base.Base): {'method': 'get_floating_ips_by_fixed_address', 'args': {'fixed_address': fixed_address}}) + def get_instance_id_by_floating_address(self, context, address): + # NOTE(tr3buchet): i hate this + return rpc.call(context, + FLAGS.network_topic, + {'method': 'get_instance_id_by_floating_address', + 'args': {'address': address}}) + def get_vifs_by_instance(self, context, instance): # NOTE(vish): When the db calls are converted to store network # data by instance_uuid, this should pass uuid instead. @@ -130,8 +176,10 @@ class API(base.Base): 'args': {'address': address, 'affect_auto_assigned': affect_auto_assigned}}) - def associate_floating_ip(self, context, floating_address, fixed_address, - affect_auto_assigned=False): + @refresh_cache + def associate_floating_ip(self, context, instance, + floating_address, fixed_address, + affect_auto_assigned=False): """Associates a floating ip with a fixed ip. ensures floating ip is allocated to the project in context @@ -143,22 +191,16 @@ class API(base.Base): 'fixed_address': fixed_address, 'affect_auto_assigned': affect_auto_assigned}}) - def disassociate_floating_ip(self, context, address, + @refresh_cache + def disassociate_floating_ip(self, context, instance, address, affect_auto_assigned=False): """Disassociates a floating ip from fixed ip it is associated with.""" - floating_ip = self.db.floating_ip_get_by_address(context, address) - fixed_ip = self.db.fixed_ip_get(context, floating_ip['fixed_ip_id']) - instance = self.db.instance_get(context, fixed_ip['instance_id']) - rpc.cast(context, + rpc.call(context, FLAGS.network_topic, {'method': 'disassociate_floating_ip', 'args': {'address': address}}) - self.invalidate_instance_cache(context, instance) - - def invalidate_instance_cache(self, context, instance): - # NOTE(vish): get_instance_nw_info will recreate the cache for us - self.get_instance_nw_info(context, instance) + @refresh_cache def allocate_for_instance(self, context, instance, **kwargs): """Allocates all network structures for an instance. @@ -222,6 +264,7 @@ class API(base.Base): nw_info = rpc.call(context, FLAGS.network_topic, {'method': 'get_instance_nw_info', 'args': args}) + return network_model.NetworkInfo.hydrate(nw_info) def validate_networks(self, context, requested_networks): diff --git a/nova/network/manager.py b/nova/network/manager.py index 72b41b81f..b46bcd648 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -510,7 +510,7 @@ class FloatingIP(object): fixed_address, interface) else: # send to correct host - rpc.cast(context, + rpc.call(context, rpc.queue_get_for(context, FLAGS.network_topic, host), {'method': '_associate_floating_ip', 'args': {'floating_address': floating_address, @@ -580,7 +580,7 @@ class FloatingIP(object): self._disassociate_floating_ip(context, address, interface) else: # send to correct host - rpc.cast(context, + rpc.call(context, rpc.queue_get_for(context, FLAGS.network_topic, host), {'method': '_disassociate_floating_ip', 'args': {'address': address, @@ -1008,11 +1008,8 @@ class NetworkManager(manager.SchedulerDependentManager): network = self._get_network_by_id(context, vif['network_id']) networks[vif['uuid']] = network - # update instance network cache and return network_info nw_info = self.build_network_info_model(context, vifs, networks, rxtx_factor, host) - self.db.instance_info_cache_update(context, instance_uuid, - {'network_info': nw_info.json()}) return nw_info def build_network_info_model(self, context, vifs, networks, @@ -1613,6 +1610,17 @@ class NetworkManager(manager.SchedulerDependentManager): vifs = self.db.virtual_interface_get_by_instance(context, instance_id) return [dict(vif.iteritems()) for vif in vifs] + def get_instance_id_by_floating_address(self, context, address): + """Returns the instance id a floating ip's fixed ip is allocated to""" + floating_ip = self.db.floating_ip_get_by_address(context, address) + if floating_ip['fixed_ip_id'] is None: + return None + + fixed_ip = self.db.fixed_ip_get(context, floating_ip['fixed_ip_id']) + + # NOTE(tr3buchet): this can be None + return fixed_ip['instance_id'] + @wrap_check_policy def get_network(self, context, network_uuid): network = self.db.network_get_by_uuid(context.elevated(), network_uuid) diff --git a/nova/tests/api/ec2/test_cloud.py b/nova/tests/api/ec2/test_cloud.py index afb906f45..41818de1b 100644 --- a/nova/tests/api/ec2/test_cloud.py +++ b/nova/tests/api/ec2/test_cloud.py @@ -39,6 +39,7 @@ from nova import flags from nova.image import fake from nova.image import s3 from nova import log as logging +from nova.network import api as network_api from nova import rpc from nova import test from nova import utils @@ -233,8 +234,14 @@ class CloudTestCase(test.TestCase): project_id=project_id) fixed_ips = nw_info.fixed_ips() - ec2_id = ec2utils.id_to_ec2_id(inst['id']) + + self.stubs.Set(ec2utils, 'get_ip_info_for_instance', + lambda *args: {'fixed_ips': ['10.0.0.1'], + 'fixed_ip6s': [], + 'floating_ips': []}) + self.stubs.Set(network_api.API, 'get_instance_id_by_floating_address', + lambda *args: 1) self.cloud.associate_address(self.context, instance_id=ec2_id, public_ip=address) diff --git a/nova/tests/api/openstack/compute/contrib/test_floating_ips.py b/nova/tests/api/openstack/compute/contrib/test_floating_ips.py index 9a3a72d55..b1f87c6df 100644 --- a/nova/tests/api/openstack/compute/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/compute/contrib/test_floating_ips.py @@ -19,6 +19,7 @@ import webob from nova.api.openstack.compute.contrib import floating_ips from nova import compute +from nova.compute import utils as compute_utils from nova import context from nova import db from nova import exception @@ -56,7 +57,7 @@ def network_api_get_floating_ips_by_project(self, context): {'id': 2, 'pool': 'nova', 'interface': 'eth0', 'address': '10.10.10.11', - 'fixed_ip_id': None}] + 'fixed_ip_id': None}] def compute_api_get(self, context, instance_id): @@ -79,7 +80,7 @@ def network_api_associate(self, context, floating_address, fixed_address): pass -def network_api_disassociate(self, context, floating_address): +def network_api_disassociate(self, context, instance, floating_address): pass @@ -92,6 +93,12 @@ def fake_instance_get(context, instance_id): "project_id": '123'} +def stub_nw_info(stubs): + def get_nw_info_for_instance(instance): + return fake_network.fake_get_instance_nw_info(stubs, spectacular=True) + return get_nw_info_for_instance + + class FloatingIpTest(test.TestCase): floating_ip = "10.10.10.10" @@ -122,9 +129,8 @@ class FloatingIpTest(test.TestCase): network_api_release) self.stubs.Set(network.api.API, "disassociate_floating_ip", network_api_disassociate) - - fake_network.fake_get_instance_nw_info(self.stubs, 1, 1, - spectacular=True) + self.stubs.Set(compute_utils, "get_nw_info_for_instance", + stub_nw_info(self.stubs)) fake_network.stub_out_nw_api_get_instance_nw_info(self.stubs, spectacular=True) diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py index 92e5d193c..35fa6de23 100644 --- a/nova/tests/compute/test_compute.py +++ b/nova/tests/compute/test_compute.py @@ -2875,49 +2875,6 @@ class ComputeAPITestCase(BaseTestCase): finally: self.compute.terminate_instance(context, instance['uuid']) - def test_associate_floating_ip(self): - """Ensure we can associate a floating ip with an instance""" - called = {'associate': False} - - def fake_associate_ip_network_api(self, ctxt, floating_address, - fixed_address): - called['associate'] = True - - self.stubs.Set(nova.network.API, 'associate_floating_ip', - fake_associate_ip_network_api) - - instance = self._create_fake_instance() - instance_uuid = instance['uuid'] - address = '0.1.2.3' - - self.compute.run_instance(self.context, instance_uuid) - self.compute_api.associate_floating_ip(self.context, - instance, - address) - self.assertTrue(called['associate']) - self.compute.terminate_instance(self.context, instance_uuid) - - def test_associate_floating_ip_no_fixed_ip(self): - """Should fail if instance has no fixed ip.""" - - def fake_get_nw_info(self, ctxt, instance): - return [] - - self.stubs.Set(nova.network.API, 'get_instance_nw_info', - fake_get_nw_info) - - instance = self._create_fake_instance() - instance_uuid = instance['uuid'] - address = '0.1.2.3' - - self.compute.run_instance(self.context, instance_uuid) - self.assertRaises(exception.FixedIpNotFoundForInstance, - self.compute_api.associate_floating_ip, - self.context, - instance, - address) - self.compute.terminate_instance(self.context, instance_uuid) - def test_get(self): """Test get instance""" c = context.get_admin_context() diff --git a/nova/tests/network/test_manager.py b/nova/tests/network/test_manager.py index 15dc68396..817b7271d 100644 --- a/nova/tests/network/test_manager.py +++ b/nova/tests/network/test_manager.py @@ -699,7 +699,7 @@ class VlanNetworkTestCase(test.TestCase): self.local = True self.stubs.Set(self.network.db, 'fixed_ip_get_by_address', fake4) self.stubs.Set(self.network.db, 'network_get', fake4_network) - self.stubs.Set(rpc, 'cast', fake6) + self.stubs.Set(rpc, 'call', fake6) self.network.associate_floating_ip(ctxt, mox.IgnoreArg(), mox.IgnoreArg()) self.assertFalse(self.local) @@ -817,7 +817,7 @@ class VlanNetworkTestCase(test.TestCase): self.local = True self.stubs.Set(self.network.db, 'fixed_ip_get', fake4) self.stubs.Set(self.network.db, 'network_get', fake4_network) - self.stubs.Set(rpc, 'cast', fake6) + self.stubs.Set(rpc, 'call', fake6) self.network.disassociate_floating_ip(ctxt, mox.IgnoreArg()) self.assertFalse(self.local) -- cgit