Move floating ip db access to calling side.

Most of the allocation for floating ips can be done on the calling
side, including finding the proper host to send the message to.
This saves us from making an rpc call for allocate/deallocate and
makes sure that we only need 1 call for associate/disassociate by
finding the proper host to send the message to immediately.

Getting exceptions to work properly required pulling in the helper
that was used by the conductor to regenerate exceptions that are
wrapped for rpc. Since this is now a shared class, it was moved
to utils.

Also a few config options were moved to avoid circular imports.

Part of blueprint optimize-nova-network

Change-Id: I6ec65b1f3e8d00cab778b10eec620760886567e0
This commit is contained in:
Vishvananda Ishaya 2013-01-30 11:17:33 -08:00
parent 4c8fc34d73
commit 12fa59dbb2
8 changed files with 87 additions and 56 deletions

View File

@ -14,14 +14,13 @@
"""Handles all requests to the conductor service."""
import functools
from nova.conductor import manager
from nova.conductor import rpcapi
from nova import exception as exc
from nova.openstack.common import cfg
from nova.openstack.common import log as logging
from nova.openstack.common.rpc import common as rpc_common
from nova import utils
conductor_opts = [
cfg.BoolOpt('use_local',
@ -43,25 +42,6 @@ CONF.register_opts(conductor_opts, conductor_group)
LOG = logging.getLogger(__name__)
class ExceptionHelper(object):
"""Class to wrap another and translate the ClientExceptions raised by its
function calls to the actual ones"""
def __init__(self, target):
self._target = target
def __getattr__(self, name):
func = getattr(self._target, name)
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except rpc_common.ClientException, e:
raise (e._exc_info[1], None, e._exc_info[2])
return wrapper
class LocalAPI(object):
"""A local version of the conductor API that does database updates
locally instead of via RPC"""
@ -69,7 +49,7 @@ class LocalAPI(object):
def __init__(self):
# TODO(danms): This needs to be something more generic for
# other/future users of this sort of functionality.
self._manager = ExceptionHelper(manager.ConductorManager())
self._manager = utils.ExceptionHelper(manager.ConductorManager())
def wait_until_ready(self, context, *args, **kwargs):
# nothing to wait for in the local case.

View File

@ -22,10 +22,12 @@ import inspect
from nova.db import base
from nova import exception
from nova.network import floating_ips
from nova.network import model as network_model
from nova.network import rpcapi as network_rpcapi
from nova.openstack.common import log as logging
from nova import policy
from nova import utils
LOG = logging.getLogger(__name__)
@ -103,11 +105,14 @@ class API(base.Base):
This is a pluggable module - other implementations do networking via
other services (such as Quantum).
"""
_sentinel = object()
def __init__(self, **kwargs):
self.network_rpcapi = network_rpcapi.NetworkAPI()
helper = utils.ExceptionHelper
# NOTE(vish): this local version of floating_manager has to convert
# ClientExceptions back since they aren't going over rpc.
self.floating_manager = helper(floating_ips.LocalManager())
super(API, self).__init__(**kwargs)
@wrap_check_policy
@ -190,19 +195,15 @@ class API(base.Base):
@wrap_check_policy
def allocate_floating_ip(self, context, pool=None):
"""Adds (allocates) a floating ip to a project from a pool."""
# NOTE(vish): We don't know which network host should get the ip
# when we allocate, so just send it to any one. This
# will probably need to move into a network supervisor
# at some point.
return self.network_rpcapi.allocate_floating_ip(context,
context.project_id, pool, False)
return self.floating_manager.allocate_floating_ip(context,
context.project_id, False, pool)
@wrap_check_policy
def release_floating_ip(self, context, address,
affect_auto_assigned=False):
"""Removes (deallocates) a floating ip with address from a project."""
return self.network_rpcapi.deallocate_floating_ip(context, address,
affect_auto_assigned)
return self.floating_manager.deallocate_floating_ip(context, address,
affect_auto_assigned)
@wrap_check_policy
@refresh_cache
@ -211,10 +212,13 @@ class API(base.Base):
affect_auto_assigned=False):
"""Associates a floating ip with a fixed ip.
ensures floating ip is allocated to the project in context
Ensures floating ip is allocated to the project in context.
Does not verify ownership of the fixed ip. Caller is assumed to have
checked that the instance is properly owned.
"""
orig_instance_uuid = self.network_rpcapi.associate_floating_ip(context,
floating_address, fixed_address, affect_auto_assigned)
orig_instance_uuid = self.floating_manager.associate_floating_ip(
context, floating_address, fixed_address, affect_auto_assigned)
if orig_instance_uuid:
msg_dict = dict(address=floating_address,
@ -232,7 +236,7 @@ class API(base.Base):
def disassociate_floating_ip(self, context, instance, address,
affect_auto_assigned=False):
"""Disassociates a floating ip from fixed ip it is associated with."""
self.network_rpcapi.disassociate_floating_ip(context, address,
return self.floating_manager.disassociate_floating_ip(context, address,
affect_auto_assigned)
@wrap_check_policy
@ -249,6 +253,10 @@ class API(base.Base):
with requested_networks which is user supplied).
:returns: network info as from get_instance_nw_info() below
"""
# NOTE(vish): We can't do the floating ip allocation here because
# this is called from compute.manager which shouldn't
# have db access so we do it on the other side of the
# rpc.
args = {}
args['vpn'] = vpn
args['requested_networks'] = requested_networks
@ -265,7 +273,10 @@ class API(base.Base):
@wrap_check_policy
def deallocate_for_instance(self, context, instance):
"""Deallocates all network structures related to instance."""
# NOTE(vish): We can't do the floating ip deallocation here because
# this is called from compute.manager which shouldn't
# have db access so we do it on the other side of the
# rpc.
args = {}
args['instance_id'] = instance['id']
args['project_id'] = instance['project_id']

View File

@ -18,14 +18,18 @@
# under the License.
from nova import context
from nova.db import base
from nova import exception
from nova.network import rpcapi as network_rpcapi
from nova.openstack.common import cfg
from nova.openstack.common import excutils
from nova.openstack.common import importutils
from nova.openstack.common import lockutils
from nova.openstack.common import log as logging
from nova.openstack.common.notifier import api as notifier
from nova.openstack.common.rpc import common as rpc_common
from nova import quota
from nova import servicegroup
LOG = logging.getLogger(__name__)
@ -38,6 +42,15 @@ floating_opts = [
cfg.BoolOpt('auto_assign_floating_ip',
default=False,
help='Autoassigning floating ip to VM'),
cfg.StrOpt('floating_ip_dns_manager',
default='nova.network.noop_dns_driver.NoopDNSDriver',
help='full class name for the DNS Manager for floating IPs'),
cfg.StrOpt('instance_dns_manager',
default='nova.network.noop_dns_driver.NoopDNSDriver',
help='full class name for the DNS Manager for instance IPs'),
cfg.StrOpt('instance_dns_domain',
default='',
help='full class name for the DNS Zone for instance IPs'),
]
CONF = cfg.CONF
@ -662,3 +675,17 @@ class FloatingIP(object):
def _get_project_for_domain(self, context, domain):
return self.db.dnsdomain_project(context, domain)
class LocalManager(base.Base, FloatingIP):
def __init__(self):
super(LocalManager, self).__init__()
# NOTE(vish): setting the host to none ensures that the actual
# l3driver commands for l3 are done via rpc.
self.host = None
self.servicegroup_api = servicegroup.API()
self.network_rpcapi = network_rpcapi.NetworkAPI()
self.floating_dns_manager = importutils.import_object(
CONF.floating_ip_dns_manager)
self.instance_dns_manager = importutils.import_object(
CONF.instance_dns_manager)

View File

@ -164,15 +164,6 @@ network_opts = [
cfg.StrOpt('l3_lib',
default='nova.network.l3.LinuxNetL3',
help="Indicates underlying L3 management library"),
cfg.StrOpt('instance_dns_manager',
default='nova.network.noop_dns_driver.NoopDNSDriver',
help='full class name for the DNS Manager for instance IPs'),
cfg.StrOpt('instance_dns_domain',
default='',
help='full class name for the DNS Zone for instance IPs'),
cfg.StrOpt('floating_ip_dns_manager',
default='nova.network.noop_dns_driver.NoopDNSDriver',
help='full class name for the DNS Manager for floating IPs'),
]
CONF = cfg.CONF

View File

@ -27,7 +27,6 @@ from nova import db
from nova import exception
from nova import network
from nova.openstack.common import jsonutils
from nova.openstack.common import rpc
from nova import test
from nova.tests.api.openstack import fakes
from nova.tests import fake_network
@ -275,12 +274,11 @@ class FloatingIpTest(test.TestCase):
self.assertIn(self.floating_ip, ip_list)
self.assertNotIn(self.floating_ip_2, ip_list)
# test floating ip allocate/release(deallocate)
def test_floating_ip_allocate_no_free_ips(self):
def fake_call(*args, **kwargs):
def fake_allocate(*args, **kwargs):
raise exception.NoMoreFloatingIps()
self.stubs.Set(rpc, "call", fake_call)
self.stubs.Set(network.api.API, "allocate_floating_ip", fake_allocate)
req = fakes.HTTPRequest.blank('/v2/fake/os-floating-ips')
self.assertRaises(exception.NoMoreFloatingIps,
@ -316,9 +314,12 @@ class FloatingIpTest(test.TestCase):
req = fakes.HTTPRequest.blank('/v2/fake/os-floating-ips/1')
self.controller.delete(req, 1)
# test floating ip add/remove -> associate/disassociate
def test_floating_ip_associate(self):
def fake_associate_floating_ip(*args, **kwargs):
pass
self.stubs.Set(network.api.API, "associate_floating_ip",
fake_associate_floating_ip)
body = dict(addFloatingIp=dict(address=self.floating_ip))
req = fakes.HTTPRequest.blank('/v2/fake/servers/test_inst/action')

View File

@ -30,8 +30,8 @@ CONF.import_opt('scheduler_driver', 'nova.scheduler.manager')
CONF.import_opt('fake_network', 'nova.network.manager')
CONF.import_opt('network_size', 'nova.network.manager')
CONF.import_opt('num_networks', 'nova.network.manager')
CONF.import_opt('floating_ip_dns_manager', 'nova.network.manager')
CONF.import_opt('instance_dns_manager', 'nova.network.manager')
CONF.import_opt('floating_ip_dns_manager', 'nova.network.floating_ips')
CONF.import_opt('instance_dns_manager', 'nova.network.floating_ips')
CONF.import_opt('policy_file', 'nova.policy')
CONF.import_opt('compute_driver', 'nova.virt.driver')
CONF.import_opt('api_paste_config', 'nova.wsgi')

View File

@ -26,8 +26,8 @@ from nova import context
from nova import exception
from nova import network
from nova.network import api
from nova.network import floating_ips
from nova.network import rpcapi as network_rpcapi
from nova.openstack.common import rpc
from nova import policy
from nova import test
@ -90,10 +90,11 @@ class ApiTestCase(test.TestCase):
new_instance = {'uuid': 'new-uuid'}
def fake_rpc_call(context, topic, msg, timeout=None):
def fake_associate(*args, **kwargs):
return orig_instance_uuid
self.stubs.Set(rpc, 'call', fake_rpc_call)
self.stubs.Set(floating_ips.FloatingIP, 'associate_floating_ip',
fake_associate)
def fake_instance_get_by_uuid(context, instance_uuid):
return {'uuid': instance_uuid}

View File

@ -48,6 +48,7 @@ from nova.openstack.common import cfg
from nova.openstack.common import excutils
from nova.openstack.common import importutils
from nova.openstack.common import log as logging
from nova.openstack.common.rpc import common as rpc_common
from nova.openstack.common import timeutils
notify_decorator = 'nova.openstack.common.notifier.api.notify_decorator'
@ -1321,3 +1322,22 @@ def getcallargs(function, *args, **kwargs):
keyed_args[argname] = value
return keyed_args
class ExceptionHelper(object):
"""Class to wrap another and translate the ClientExceptions raised by its
function calls to the actual ones"""
def __init__(self, target):
self._target = target
def __getattr__(self, name):
func = getattr(self._target, name)
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except rpc_common.ClientException, e:
raise (e._exc_info[1], None, e._exc_info[2])
return wrapper