diff --git a/bin/nova-manage b/bin/nova-manage index 90d191eca7a2..82edf738939c 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -98,7 +98,7 @@ CONF.import_opt('multi_host', 'nova.network.manager') CONF.import_opt('network_size', 'nova.network.manager') CONF.import_opt('vlan_start', 'nova.network.manager') CONF.import_opt('vpn_start', 'nova.network.manager') -CONF.import_opt('default_floating_pool', 'nova.network.manager') +CONF.import_opt('default_floating_pool', 'nova.network.floating_ips') CONF.import_opt('public_interface', 'nova.network.linux_net') QUOTAS = quota.QUOTAS diff --git a/nova/api/openstack/compute/contrib/floating_ips_bulk.py b/nova/api/openstack/compute/contrib/floating_ips_bulk.py index f5b8d24ddc06..6ba60daf8983 100644 --- a/nova/api/openstack/compute/contrib/floating_ips_bulk.py +++ b/nova/api/openstack/compute/contrib/floating_ips_bulk.py @@ -25,7 +25,7 @@ from nova.openstack.common import cfg from nova.openstack.common import log as logging CONF = cfg.CONF -CONF.import_opt('default_floating_pool', 'nova.network.manager') +CONF.import_opt('default_floating_pool', 'nova.network.floating_ips') CONF.import_opt('public_interface', 'nova.network.linux_net') diff --git a/nova/network/floating_ips.py b/nova/network/floating_ips.py new file mode 100644 index 000000000000..6d8606113c78 --- /dev/null +++ b/nova/network/floating_ips.py @@ -0,0 +1,662 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# 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 import context +from nova import exception +from nova.openstack.common import cfg +from nova.openstack.common import excutils +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 + +LOG = logging.getLogger(__name__) + +QUOTAS = quota.QUOTAS + +floating_opts = [ + cfg.StrOpt('default_floating_pool', + default='nova', + help='Default pool for floating ips'), + cfg.BoolOpt('auto_assign_floating_ip', + default=False, + help='Autoassigning floating ip to VM'), +] + +CONF = cfg.CONF +CONF.register_opts(floating_opts) +CONF.import_opt('public_interface', 'nova.network.linux_net') + + +class FloatingIP(object): + """Mixin class for adding floating IP functionality to a manager.""" + + servicegroup_api = None + + def init_host_floating_ips(self): + """Configures floating ips owned by host.""" + + admin_context = context.get_admin_context() + try: + floating_ips = self.db.floating_ip_get_all_by_host(admin_context, + self.host) + except exception.NotFound: + return + + for floating_ip in floating_ips: + fixed_ip_id = floating_ip.get('fixed_ip_id') + if fixed_ip_id: + try: + fixed_ip = self.db.fixed_ip_get(admin_context, + fixed_ip_id, + get_network=True) + except exception.FixedIpNotFound: + msg = _('Fixed ip %(fixed_ip_id)s not found') % locals() + LOG.debug(msg) + continue + interface = CONF.public_interface or floating_ip['interface'] + try: + self.l3driver.add_floating_ip(floating_ip['address'], + fixed_ip['address'], + interface, + fixed_ip['network']) + except exception.ProcessExecutionError: + LOG.debug(_('Interface %(interface)s not found'), locals()) + raise exception.NoFloatingIpInterface(interface=interface) + + def allocate_for_instance(self, context, **kwargs): + """Handles allocating the floating IP resources for an instance. + + calls super class allocate_for_instance() as well + + rpc.called by network_api + """ + instance_id = kwargs.get('instance_id') + instance_uuid = kwargs.get('instance_uuid') + project_id = kwargs.get('project_id') + requested_networks = kwargs.get('requested_networks') + LOG.debug(_("floating IP allocation for instance |%s|"), + instance_uuid=instance_uuid, context=context) + # call the next inherited class's allocate_for_instance() + # which is currently the NetworkManager version + # do this first so fixed ip is already allocated + nw_info = super(FloatingIP, self).allocate_for_instance(context, + **kwargs) + if CONF.auto_assign_floating_ip: + # allocate a floating ip + floating_address = self.allocate_floating_ip(context, project_id, + True) + # set auto_assigned column to true for the floating ip + self.db.floating_ip_set_auto_assigned(context, floating_address) + + # get the first fixed address belonging to the instance + fixed_ips = nw_info.fixed_ips() + fixed_address = fixed_ips[0]['address'] + + # associate the floating ip to fixed_ip + self.associate_floating_ip(context, + floating_address, + fixed_address, + affect_auto_assigned=True) + + # create a fresh set of network info that contains the floating ip + nw_info = self.get_instance_nw_info(context, **kwargs) + + return nw_info + + def deallocate_for_instance(self, context, **kwargs): + """Handles deallocating floating IP resources for an instance. + + calls super class deallocate_for_instance() as well. + + rpc.called by network_api + """ + instance_id = kwargs.get('instance_id') + + # NOTE(francois.charlier): in some cases the instance might be + # deleted before the IPs are released, so we need to get deleted + # instances too + instance = self.db.instance_get( + context.elevated(read_deleted='yes'), instance_id) + + try: + fixed_ips = self.db.fixed_ip_get_by_instance(context, + instance['uuid']) + except exception.FixedIpNotFoundForInstance: + fixed_ips = [] + # add to kwargs so we can pass to super to save a db lookup there + kwargs['fixed_ips'] = fixed_ips + for fixed_ip in fixed_ips: + fixed_id = fixed_ip['id'] + floating_ips = self.db.floating_ip_get_by_fixed_ip_id(context, + fixed_id) + # disassociate floating ips related to fixed_ip + for floating_ip in floating_ips: + address = floating_ip['address'] + try: + self.disassociate_floating_ip(context, + address, + affect_auto_assigned=True) + except exception.FloatingIpNotAssociated: + LOG.exception(_("Floating IP is not associated. Ignore.")) + # deallocate if auto_assigned + if floating_ip['auto_assigned']: + self.deallocate_floating_ip(context, address, + affect_auto_assigned=True) + + # call the next inherited class's deallocate_for_instance() + # which is currently the NetworkManager version + # call this after so floating IPs are handled first + super(FloatingIP, self).deallocate_for_instance(context, **kwargs) + + def _floating_ip_owned_by_project(self, context, floating_ip): + """Raises if floating ip does not belong to project.""" + if context.is_admin: + return + + if floating_ip['project_id'] != context.project_id: + if floating_ip['project_id'] is None: + LOG.warn(_('Address |%(address)s| is not allocated'), + {'address': floating_ip['address']}) + raise exception.NotAuthorized() + else: + LOG.warn(_('Address |%(address)s| is not allocated to your ' + 'project |%(project)s|'), + {'address': floating_ip['address'], + 'project': context.project_id}) + raise exception.NotAuthorized() + + def allocate_floating_ip(self, context, project_id, auto_assigned=False, + pool=None): + """Gets a floating ip from the pool.""" + # NOTE(tr3buchet): all network hosts in zone now use the same pool + pool = pool or CONF.default_floating_pool + use_quota = not auto_assigned + + # Check the quota; can't put this in the API because we get + # called into from other places + try: + if use_quota: + reservations = QUOTAS.reserve(context, floating_ips=1) + except exception.OverQuota: + pid = context.project_id + LOG.warn(_("Quota exceeded for %(pid)s, tried to allocate " + "floating IP") % locals()) + raise exception.FloatingIpLimitExceeded() + + try: + floating_ip = self.db.floating_ip_allocate_address(context, + project_id, + pool) + payload = dict(project_id=project_id, floating_ip=floating_ip) + notifier.notify(context, + notifier.publisher_id("network"), + 'network.floating_ip.allocate', + notifier.INFO, payload) + + # Commit the reservations + if use_quota: + QUOTAS.commit(context, reservations) + except Exception: + with excutils.save_and_reraise_exception(): + if use_quota: + QUOTAS.rollback(context, reservations) + + return floating_ip + + @rpc_common.client_exceptions(exception.FloatingIpNotFoundForAddress) + def deallocate_floating_ip(self, context, address, + affect_auto_assigned=False): + """Returns a floating ip to the pool.""" + floating_ip = self.db.floating_ip_get_by_address(context, address) + + # handle auto_assigned + if not affect_auto_assigned and floating_ip.get('auto_assigned'): + return + use_quota = not floating_ip.get('auto_assigned') + + # make sure project owns this floating ip (allocated) + self._floating_ip_owned_by_project(context, floating_ip) + + # make sure floating ip is not associated + if floating_ip['fixed_ip_id']: + floating_address = floating_ip['address'] + raise exception.FloatingIpAssociated(address=floating_address) + + # clean up any associated DNS entries + self._delete_all_entries_for_ip(context, + floating_ip['address']) + payload = dict(project_id=floating_ip['project_id'], + floating_ip=floating_ip['address']) + notifier.notify(context, + notifier.publisher_id("network"), + 'network.floating_ip.deallocate', + notifier.INFO, payload=payload) + + # Get reservations... + try: + if use_quota: + reservations = QUOTAS.reserve(context, floating_ips=-1) + else: + reservations = None + except Exception: + reservations = None + LOG.exception(_("Failed to update usages deallocating " + "floating IP")) + + self.db.floating_ip_deallocate(context, address) + + # Commit the reservations + if reservations: + QUOTAS.commit(context, reservations) + + @rpc_common.client_exceptions(exception.FloatingIpNotFoundForAddress) + def associate_floating_ip(self, context, floating_address, fixed_address, + affect_auto_assigned=False): + """Associates a floating ip with a fixed ip. + + Makes sure everything makes sense then calls _associate_floating_ip, + rpc'ing to correct host if i'm not it. + + Access to the floating_address is verified but access to the + fixed_address is not verified. This assumes that that the calling + side has already verified that the fixed_address is legal by + checking access to the instance. + """ + floating_ip = self.db.floating_ip_get_by_address(context, + floating_address) + # handle auto_assigned + if not affect_auto_assigned and floating_ip.get('auto_assigned'): + return + + # make sure project owns this floating ip (allocated) + self._floating_ip_owned_by_project(context, floating_ip) + + # disassociate any already associated + orig_instance_uuid = None + if floating_ip['fixed_ip_id']: + # find previously associated instance + fixed_ip = self.db.fixed_ip_get(context, + floating_ip['fixed_ip_id']) + if fixed_ip['address'] == fixed_address: + # NOTE(vish): already associated to this address + return + orig_instance_uuid = fixed_ip['instance_uuid'] + + self.disassociate_floating_ip(context, floating_address) + + fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_address) + + # send to correct host, unless i'm the correct host + network = self.db.network_get(context.elevated(), + fixed_ip['network_id']) + if network['multi_host']: + instance = self.db.instance_get_by_uuid(context, + fixed_ip['instance_uuid']) + host = instance['host'] + else: + host = network['host'] + + interface = floating_ip.get('interface') + if host == self.host: + # i'm the correct host + self._associate_floating_ip(context, floating_address, + fixed_address, interface, + fixed_ip['instance_uuid']) + else: + # send to correct host + self.network_rpcapi._associate_floating_ip(context, + floating_address, fixed_address, interface, host, + fixed_ip['instance_uuid']) + + return orig_instance_uuid + + def _associate_floating_ip(self, context, floating_address, fixed_address, + interface, instance_uuid): + """Performs db and driver calls to associate floating ip & fixed ip.""" + interface = CONF.public_interface or interface + + @lockutils.synchronized(unicode(floating_address), 'nova-') + def do_associate(): + # associate floating ip + fixed = self.db.floating_ip_fixed_ip_associate(context, + floating_address, + fixed_address, + self.host) + if not fixed: + # NOTE(vish): ip was already associated + return + try: + # gogo driver time + self.l3driver.add_floating_ip(floating_address, fixed_address, + interface, fixed['network']) + except exception.ProcessExecutionError as e: + self.db.floating_ip_disassociate(context, floating_address) + if "Cannot find device" in str(e): + LOG.error(_('Interface %(interface)s not found'), locals()) + raise exception.NoFloatingIpInterface(interface=interface) + + payload = dict(project_id=context.project_id, + instance_id=instance_uuid, + floating_ip=floating_address) + notifier.notify(context, + notifier.publisher_id("network"), + 'network.floating_ip.associate', + notifier.INFO, payload=payload) + do_associate() + + @rpc_common.client_exceptions(exception.FloatingIpNotFoundForAddress) + def disassociate_floating_ip(self, context, address, + affect_auto_assigned=False): + """Disassociates a floating ip from its fixed ip. + + Makes sure everything makes sense then calls _disassociate_floating_ip, + rpc'ing to correct host if i'm not it. + """ + floating_ip = self.db.floating_ip_get_by_address(context, address) + + # handle auto assigned + if not affect_auto_assigned and floating_ip.get('auto_assigned'): + raise exception.CannotDisassociateAutoAssignedFloatingIP() + + # make sure project owns this floating ip (allocated) + self._floating_ip_owned_by_project(context, floating_ip) + + # make sure floating ip is associated + if not floating_ip.get('fixed_ip_id'): + floating_address = floating_ip['address'] + raise exception.FloatingIpNotAssociated(address=floating_address) + + fixed_ip = self.db.fixed_ip_get(context, floating_ip['fixed_ip_id']) + + # send to correct host, unless i'm the correct host + network = self.db.network_get(context.elevated(), + fixed_ip['network_id']) + interface = floating_ip.get('interface') + if network['multi_host']: + instance = self.db.instance_get_by_uuid(context, + fixed_ip['instance_uuid']) + service = self.db.service_get_by_host_and_topic( + context.elevated(), instance['host'], 'network') + if service and self.servicegroup_api.service_is_up(service): + host = instance['host'] + else: + # NOTE(vish): if the service is down just deallocate the data + # locally. Set the host to local so the call will + # not go over rpc and set interface to None so the + # teardown in the driver does not happen. + host = self.host + interface = None + else: + host = network['host'] + + if host == self.host: + # i'm the correct host + self._disassociate_floating_ip(context, address, interface, + fixed_ip['instance_uuid']) + else: + # send to correct host + self.network_rpcapi._disassociate_floating_ip(context, address, + interface, host, fixed_ip['instance_uuid']) + + def _disassociate_floating_ip(self, context, address, interface, + instance_uuid): + """Performs db and driver calls to disassociate floating ip.""" + interface = CONF.public_interface or interface + + @lockutils.synchronized(unicode(address), 'nova-') + def do_disassociate(): + # NOTE(vish): Note that we are disassociating in the db before we + # actually remove the ip address on the host. We are + # safe from races on this host due to the decorator, + # but another host might grab the ip right away. We + # don't worry about this case because the minuscule + # window where the ip is on both hosts shouldn't cause + # any problems. + fixed = self.db.floating_ip_disassociate(context, address) + + if not fixed: + # NOTE(vish): ip was already disassociated + return + if interface: + # go go driver time + self.l3driver.remove_floating_ip(address, fixed['address'], + interface, fixed['network']) + payload = dict(project_id=context.project_id, + instance_id=instance_uuid, + floating_ip=address) + notifier.notify(context, + notifier.publisher_id("network"), + 'network.floating_ip.disassociate', + notifier.INFO, payload=payload) + do_disassociate() + + @rpc_common.client_exceptions(exception.FloatingIpNotFound) + def get_floating_ip(self, context, id): + """Returns a floating IP as a dict.""" + # NOTE(vish): This is no longer used but can't be removed until + # we major version the network_rpcapi. + return dict(self.db.floating_ip_get(context, id).iteritems()) + + def get_floating_pools(self, context): + """Returns list of floating pools.""" + # NOTE(maurosr) This method should be removed in future, replaced by + # get_floating_ip_pools. See bug #1091668 + return self.get_floating_ip_pools(context) + + def get_floating_ip_pools(self, context): + """Returns list of floating ip pools.""" + # NOTE(vish): This is no longer used but can't be removed until + # we major version the network_rpcapi. + pools = self.db.floating_ip_get_pools(context) + return [dict(pool.iteritems()) for pool in pools] + + def get_floating_ip_by_address(self, context, address): + """Returns a floating IP as a dict.""" + # NOTE(vish): This is no longer used but can't be removed until + # we major version the network_rpcapi. + return dict(self.db.floating_ip_get_by_address(context, + address).iteritems()) + + def get_floating_ips_by_project(self, context): + """Returns the floating IPs allocated to a project.""" + # NOTE(vish): This is no longer used but can't be removed until + # we major version the network_rpcapi. + ips = self.db.floating_ip_get_all_by_project(context, + context.project_id) + return [dict(ip.iteritems()) for ip in ips] + + def get_floating_ips_by_fixed_address(self, context, fixed_address): + """Returns the floating IPs associated with a fixed_address.""" + # NOTE(vish): This is no longer used but can't be removed until + # we major version the network_rpcapi. + floating_ips = self.db.floating_ip_get_by_fixed_address(context, + fixed_address) + return [floating_ip['address'] for floating_ip in floating_ips] + + def _is_stale_floating_ip_address(self, context, floating_ip): + try: + self._floating_ip_owned_by_project(context, floating_ip) + except exception.NotAuthorized: + return True + return False if floating_ip.get('fixed_ip_id') else True + + def migrate_instance_start(self, context, instance_uuid, + floating_addresses, + rxtx_factor=None, project_id=None, + source=None, dest=None): + # We only care if floating_addresses are provided and we're + # switching hosts + if not floating_addresses or (source and source == dest): + return + + LOG.info(_("Starting migration network for instance" + " %(instance_uuid)s"), locals()) + for address in floating_addresses: + floating_ip = self.db.floating_ip_get_by_address(context, + address) + + if self._is_stale_floating_ip_address(context, floating_ip): + LOG.warn(_("Floating ip address |%(address)s| no longer " + "belongs to instance %(instance_uuid)s. Will not" + "migrate it "), locals()) + continue + + interface = CONF.public_interface or floating_ip['interface'] + fixed_ip = self.db.fixed_ip_get(context, + floating_ip['fixed_ip_id'], + get_network=True) + self.l3driver.remove_floating_ip(floating_ip['address'], + fixed_ip['address'], + interface, + fixed_ip['network']) + + # NOTE(wenjianhn): Make this address will not be bound to public + # interface when restarts nova-network on dest compute node + self.db.floating_ip_update(context, + floating_ip['address'], + {'host': None}) + + def migrate_instance_finish(self, context, instance_uuid, + floating_addresses, host=None, + rxtx_factor=None, project_id=None, + source=None, dest=None): + # We only care if floating_addresses are provided and we're + # switching hosts + if host and not dest: + dest = host + if not floating_addresses or (source and source == dest): + return + + LOG.info(_("Finishing migration network for instance" + " %(instance_uuid)s"), locals()) + + for address in floating_addresses: + floating_ip = self.db.floating_ip_get_by_address(context, + address) + + if self._is_stale_floating_ip_address(context, floating_ip): + LOG.warn(_("Floating ip address |%(address)s| no longer " + "belongs to instance %(instance_uuid)s. Will not" + "setup it."), locals()) + continue + + self.db.floating_ip_update(context, + floating_ip['address'], + {'host': dest}) + + interface = CONF.public_interface or floating_ip['interface'] + fixed_ip = self.db.fixed_ip_get(context, + floating_ip['fixed_ip_id'], + get_network=True) + self.l3driver.add_floating_ip(floating_ip['address'], + fixed_ip['address'], + interface, + fixed_ip['network']) + + def _prepare_domain_entry(self, context, domain): + domainref = self.db.dnsdomain_get(context, domain) + scope = domainref['scope'] + if scope == 'private': + av_zone = domainref['availability_zone'] + this_domain = {'domain': domain, + 'scope': scope, + 'availability_zone': av_zone} + else: + project = domainref['project_id'] + this_domain = {'domain': domain, + 'scope': scope, + 'project': project} + return this_domain + + def get_dns_domains(self, context): + domains = [] + + db_domain_list = self.db.dnsdomain_list(context) + floating_driver_domain_list = self.floating_dns_manager.get_domains() + instance_driver_domain_list = self.instance_dns_manager.get_domains() + + for db_domain in db_domain_list: + if (db_domain in floating_driver_domain_list or + db_domain in instance_driver_domain_list): + domain_entry = self._prepare_domain_entry(context, + db_domain) + if domain_entry: + domains.append(domain_entry) + else: + LOG.warn(_('Database inconsistency: DNS domain |%s| is ' + 'registered in the Nova db but not visible to ' + 'either the floating or instance DNS driver. It ' + 'will be ignored.'), db_domain) + + return domains + + def add_dns_entry(self, context, address, name, dns_type, domain): + self.floating_dns_manager.create_entry(name, address, + dns_type, domain) + + def modify_dns_entry(self, context, address, name, domain): + self.floating_dns_manager.modify_address(name, address, + domain) + + def delete_dns_entry(self, context, name, domain): + self.floating_dns_manager.delete_entry(name, domain) + + def _delete_all_entries_for_ip(self, context, address): + domain_list = self.get_dns_domains(context) + for domain in domain_list: + names = self.get_dns_entries_by_address(context, + address, + domain['domain']) + for name in names: + self.delete_dns_entry(context, name, domain['domain']) + + def get_dns_entries_by_address(self, context, address, domain): + return self.floating_dns_manager.get_entries_by_address(address, + domain) + + def get_dns_entries_by_name(self, context, name, domain): + return self.floating_dns_manager.get_entries_by_name(name, + domain) + + def create_private_dns_domain(self, context, domain, av_zone): + self.db.dnsdomain_register_for_zone(context, domain, av_zone) + try: + self.instance_dns_manager.create_domain(domain) + except exception.FloatingIpDNSExists: + LOG.warn(_('Domain |%(domain)s| already exists, ' + 'changing zone to |%(av_zone)s|.'), + {'domain': domain, 'av_zone': av_zone}) + + def create_public_dns_domain(self, context, domain, project): + self.db.dnsdomain_register_for_project(context, domain, project) + try: + self.floating_dns_manager.create_domain(domain) + except exception.FloatingIpDNSExists: + LOG.warn(_('Domain |%(domain)s| already exists, ' + 'changing project to |%(project)s|.'), + {'domain': domain, 'project': project}) + + def delete_dns_domain(self, context, domain): + self.db.dnsdomain_unregister(context, domain) + self.floating_dns_manager.delete_domain(domain) + + def _get_project_for_domain(self, context, domain): + return self.db.dnsdomain_project(context, domain) diff --git a/nova/network/manager.py b/nova/network/manager.py index f57bb197b374..161bf421bb85 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -34,7 +34,6 @@ topologies. All of the network commands are issued to a subclass of :vpn_start: First Vpn port for private networks :cnt_vpn_clients: Number of addresses reserved for vpn clients :network_size: Number of addresses in each private subnet -:floating_range: Floating IP address block :fixed_range: Fixed IP address block :fixed_ip_disassociate_timeout: Seconds after which a deallocated ip is disassociated @@ -59,26 +58,22 @@ from nova import ipv6 from nova import manager from nova.network import api as network_api from nova.network import driver +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 cfg -from nova.openstack.common import excutils from nova.openstack.common import importutils from nova.openstack.common import jsonutils 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.openstack.common import timeutils from nova.openstack.common import uuidutils -from nova import quota from nova import servicegroup from nova import utils LOG = logging.getLogger(__name__) -QUOTAS = quota.QUOTAS network_opts = [ cfg.StrOpt('flat_network_bridge', @@ -114,12 +109,6 @@ network_opts = [ cfg.IntOpt('network_size', default=256, help='Number of addresses in each private subnet'), - cfg.StrOpt('floating_range', - default='4.4.4.0/24', - help='Floating IP address block'), - cfg.StrOpt('default_floating_pool', - default='nova', - help='Default pool for floating ips'), cfg.StrOpt('fixed_range', default='10.0.0.0/8', help='Fixed IP address block'), @@ -141,9 +130,6 @@ network_opts = [ cfg.IntOpt('create_unique_mac_address_attempts', default=5, help='Number of attempts to create unique mac address'), - cfg.BoolOpt('auto_assign_floating_ip', - default=False, - help='Autoassigning floating ip to VM'), cfg.BoolOpt('fake_network', default=False, help='If passed, use fake network devices and addresses'), @@ -274,617 +260,6 @@ class RPCAllocateFixedIP(object): self.network_rpcapi.deallocate_fixed_ip(context, address, host) -class FloatingIP(object): - """Mixin class for adding floating IP functionality to a manager.""" - - servicegroup_api = None - - def init_host_floating_ips(self): - """Configures floating ips owned by host.""" - - admin_context = context.get_admin_context() - try: - floating_ips = self.db.floating_ip_get_all_by_host(admin_context, - self.host) - except exception.NotFound: - return - - for floating_ip in floating_ips: - fixed_ip_id = floating_ip.get('fixed_ip_id') - if fixed_ip_id: - try: - fixed_ip = self.db.fixed_ip_get(admin_context, - fixed_ip_id, - get_network=True) - except exception.FixedIpNotFound: - msg = _('Fixed ip %(fixed_ip_id)s not found') % locals() - LOG.debug(msg) - continue - interface = CONF.public_interface or floating_ip['interface'] - try: - self.l3driver.add_floating_ip(floating_ip['address'], - fixed_ip['address'], - interface, - fixed_ip['network']) - except exception.ProcessExecutionError: - LOG.debug(_('Interface %(interface)s not found'), locals()) - raise exception.NoFloatingIpInterface(interface=interface) - - def allocate_for_instance(self, context, **kwargs): - """Handles allocating the floating IP resources for an instance. - - calls super class allocate_for_instance() as well - - rpc.called by network_api - """ - instance_id = kwargs.get('instance_id') - instance_uuid = kwargs.get('instance_uuid') - project_id = kwargs.get('project_id') - requested_networks = kwargs.get('requested_networks') - LOG.debug(_("floating IP allocation for instance |%s|"), - instance_uuid=instance_uuid, context=context) - # call the next inherited class's allocate_for_instance() - # which is currently the NetworkManager version - # do this first so fixed ip is already allocated - nw_info = super(FloatingIP, self).allocate_for_instance(context, - **kwargs) - if CONF.auto_assign_floating_ip: - # allocate a floating ip - floating_address = self.allocate_floating_ip(context, project_id, - True) - # set auto_assigned column to true for the floating ip - self.db.floating_ip_set_auto_assigned(context, floating_address) - - # get the first fixed address belonging to the instance - fixed_ips = nw_info.fixed_ips() - fixed_address = fixed_ips[0]['address'] - - # associate the floating ip to fixed_ip - self.associate_floating_ip(context, - floating_address, - fixed_address, - affect_auto_assigned=True) - - # create a fresh set of network info that contains the floating ip - nw_info = self.get_instance_nw_info(context, **kwargs) - - return nw_info - - def deallocate_for_instance(self, context, **kwargs): - """Handles deallocating floating IP resources for an instance. - - calls super class deallocate_for_instance() as well. - - rpc.called by network_api - """ - instance_id = kwargs.get('instance_id') - - # NOTE(francois.charlier): in some cases the instance might be - # deleted before the IPs are released, so we need to get deleted - # instances too - instance = self.db.instance_get( - context.elevated(read_deleted='yes'), instance_id) - - try: - fixed_ips = self.db.fixed_ip_get_by_instance(context, - instance['uuid']) - except exception.FixedIpNotFoundForInstance: - fixed_ips = [] - # add to kwargs so we can pass to super to save a db lookup there - kwargs['fixed_ips'] = fixed_ips - for fixed_ip in fixed_ips: - fixed_id = fixed_ip['id'] - floating_ips = self.db.floating_ip_get_by_fixed_ip_id(context, - fixed_id) - # disassociate floating ips related to fixed_ip - for floating_ip in floating_ips: - address = floating_ip['address'] - try: - self.disassociate_floating_ip(context, - address, - affect_auto_assigned=True) - except exception.FloatingIpNotAssociated: - LOG.exception(_("Floating IP is not associated. Ignore.")) - # deallocate if auto_assigned - if floating_ip['auto_assigned']: - self.deallocate_floating_ip(context, address, - affect_auto_assigned=True) - - # call the next inherited class's deallocate_for_instance() - # which is currently the NetworkManager version - # call this after so floating IPs are handled first - super(FloatingIP, self).deallocate_for_instance(context, **kwargs) - - def _floating_ip_owned_by_project(self, context, floating_ip): - """Raises if floating ip does not belong to project.""" - if context.is_admin: - return - - if floating_ip['project_id'] != context.project_id: - if floating_ip['project_id'] is None: - LOG.warn(_('Address |%(address)s| is not allocated'), - {'address': floating_ip['address']}) - raise exception.NotAuthorized() - else: - LOG.warn(_('Address |%(address)s| is not allocated to your ' - 'project |%(project)s|'), - {'address': floating_ip['address'], - 'project': context.project_id}) - raise exception.NotAuthorized() - - def allocate_floating_ip(self, context, project_id, auto_assigned=False, - pool=None): - """Gets a floating ip from the pool.""" - # NOTE(tr3buchet): all network hosts in zone now use the same pool - pool = pool or CONF.default_floating_pool - use_quota = not auto_assigned - - # Check the quota; can't put this in the API because we get - # called into from other places - try: - if use_quota: - reservations = QUOTAS.reserve(context, floating_ips=1) - except exception.OverQuota: - pid = context.project_id - LOG.warn(_("Quota exceeded for %(pid)s, tried to allocate " - "floating IP") % locals()) - raise exception.FloatingIpLimitExceeded() - - try: - floating_ip = self.db.floating_ip_allocate_address(context, - project_id, - pool) - payload = dict(project_id=project_id, floating_ip=floating_ip) - notifier.notify(context, - notifier.publisher_id("network"), - 'network.floating_ip.allocate', - notifier.INFO, payload) - - # Commit the reservations - if use_quota: - QUOTAS.commit(context, reservations) - except Exception: - with excutils.save_and_reraise_exception(): - if use_quota: - QUOTAS.rollback(context, reservations) - - return floating_ip - - @rpc_common.client_exceptions(exception.FloatingIpNotFoundForAddress) - def deallocate_floating_ip(self, context, address, - affect_auto_assigned=False): - """Returns a floating ip to the pool.""" - floating_ip = self.db.floating_ip_get_by_address(context, address) - - # handle auto_assigned - if not affect_auto_assigned and floating_ip.get('auto_assigned'): - return - use_quota = not floating_ip.get('auto_assigned') - - # make sure project owns this floating ip (allocated) - self._floating_ip_owned_by_project(context, floating_ip) - - # make sure floating ip is not associated - if floating_ip['fixed_ip_id']: - floating_address = floating_ip['address'] - raise exception.FloatingIpAssociated(address=floating_address) - - # clean up any associated DNS entries - self._delete_all_entries_for_ip(context, - floating_ip['address']) - payload = dict(project_id=floating_ip['project_id'], - floating_ip=floating_ip['address']) - notifier.notify(context, - notifier.publisher_id("network"), - 'network.floating_ip.deallocate', - notifier.INFO, payload=payload) - - # Get reservations... - try: - if use_quota: - reservations = QUOTAS.reserve(context, floating_ips=-1) - else: - reservations = None - except Exception: - reservations = None - LOG.exception(_("Failed to update usages deallocating " - "floating IP")) - - self.db.floating_ip_deallocate(context, address) - - # Commit the reservations - if reservations: - QUOTAS.commit(context, reservations) - - @rpc_common.client_exceptions(exception.FloatingIpNotFoundForAddress) - def associate_floating_ip(self, context, floating_address, fixed_address, - affect_auto_assigned=False): - """Associates a floating ip with a fixed ip. - - Makes sure everything makes sense then calls _associate_floating_ip, - rpc'ing to correct host if i'm not it. - """ - floating_ip = self.db.floating_ip_get_by_address(context, - floating_address) - # handle auto_assigned - if not affect_auto_assigned and floating_ip.get('auto_assigned'): - return - - # make sure project owns this floating ip (allocated) - self._floating_ip_owned_by_project(context, floating_ip) - - # disassociate any already associated - orig_instance_uuid = None - if floating_ip['fixed_ip_id']: - # find previously associated instance - fixed_ip = self.db.fixed_ip_get(context, - floating_ip['fixed_ip_id']) - if fixed_ip['address'] == fixed_address: - # NOTE(vish): already associated to this address - return - orig_instance_uuid = fixed_ip['instance_uuid'] - - self.disassociate_floating_ip(context, floating_address) - - fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_address) - - # send to correct host, unless i'm the correct host - network = self._get_network_by_id(context.elevated(), - fixed_ip['network_id']) - if network['multi_host']: - instance = self.db.instance_get_by_uuid(context, - fixed_ip['instance_uuid']) - host = instance['host'] - else: - host = network['host'] - - interface = floating_ip.get('interface') - if host == self.host: - # i'm the correct host - self._associate_floating_ip(context, floating_address, - fixed_address, interface, - fixed_ip['instance_uuid']) - else: - # send to correct host - self.network_rpcapi._associate_floating_ip(context, - floating_address, fixed_address, interface, host, - fixed_ip['instance_uuid']) - - return orig_instance_uuid - - def _associate_floating_ip(self, context, floating_address, fixed_address, - interface, instance_uuid): - """Performs db and driver calls to associate floating ip & fixed ip.""" - interface = CONF.public_interface or interface - - @lockutils.synchronized(unicode(floating_address), 'nova-') - def do_associate(): - # associate floating ip - fixed = self.db.floating_ip_fixed_ip_associate(context, - floating_address, - fixed_address, - self.host) - if not fixed: - # NOTE(vish): ip was already associated - return - try: - # gogo driver time - self.l3driver.add_floating_ip(floating_address, fixed_address, - interface, fixed['network']) - except exception.ProcessExecutionError as e: - self.db.floating_ip_disassociate(context, floating_address) - if "Cannot find device" in str(e): - LOG.error(_('Interface %(interface)s not found'), locals()) - raise exception.NoFloatingIpInterface(interface=interface) - - payload = dict(project_id=context.project_id, - instance_id=instance_uuid, - floating_ip=floating_address) - notifier.notify(context, - notifier.publisher_id("network"), - 'network.floating_ip.associate', - notifier.INFO, payload=payload) - do_associate() - - @rpc_common.client_exceptions(exception.FloatingIpNotFoundForAddress) - def disassociate_floating_ip(self, context, address, - affect_auto_assigned=False): - """Disassociates a floating ip from its fixed ip. - - Makes sure everything makes sense then calls _disassociate_floating_ip, - rpc'ing to correct host if i'm not it. - """ - floating_ip = self.db.floating_ip_get_by_address(context, address) - - # handle auto assigned - if not affect_auto_assigned and floating_ip.get('auto_assigned'): - raise exception.CannotDisassociateAutoAssignedFloatingIP() - - # make sure project owns this floating ip (allocated) - self._floating_ip_owned_by_project(context, floating_ip) - - # make sure floating ip is associated - if not floating_ip.get('fixed_ip_id'): - floating_address = floating_ip['address'] - raise exception.FloatingIpNotAssociated(address=floating_address) - - fixed_ip = self.db.fixed_ip_get(context, floating_ip['fixed_ip_id']) - - # send to correct host, unless i'm the correct host - network = self._get_network_by_id(context, fixed_ip['network_id']) - interface = floating_ip.get('interface') - if network['multi_host']: - instance = self.db.instance_get_by_uuid(context, - fixed_ip['instance_uuid']) - service = self.db.service_get_by_host_and_topic( - context.elevated(), instance['host'], 'network') - if service and self.servicegroup_api.service_is_up(service): - host = instance['host'] - else: - # NOTE(vish): if the service is down just deallocate the data - # locally. Set the host to local so the call will - # not go over rpc and set interface to None so the - # teardown in the driver does not happen. - host = self.host - interface = None - else: - host = network['host'] - - if host == self.host: - # i'm the correct host - self._disassociate_floating_ip(context, address, interface, - fixed_ip['instance_uuid']) - else: - # send to correct host - self.network_rpcapi._disassociate_floating_ip(context, address, - interface, host, fixed_ip['instance_uuid']) - - def _disassociate_floating_ip(self, context, address, interface, - instance_uuid): - """Performs db and driver calls to disassociate floating ip.""" - interface = CONF.public_interface or interface - - @lockutils.synchronized(unicode(address), 'nova-') - def do_disassociate(): - # NOTE(vish): Note that we are disassociating in the db before we - # actually remove the ip address on the host. We are - # safe from races on this host due to the decorator, - # but another host might grab the ip right away. We - # don't worry about this case because the minuscule - # window where the ip is on both hosts shouldn't cause - # any problems. - fixed = self.db.floating_ip_disassociate(context, address) - - if not fixed: - # NOTE(vish): ip was already disassociated - return - if interface: - # go go driver time - self.l3driver.remove_floating_ip(address, fixed['address'], - interface, fixed['network']) - payload = dict(project_id=context.project_id, - instance_id=instance_uuid, - floating_ip=address) - notifier.notify(context, - notifier.publisher_id("network"), - 'network.floating_ip.disassociate', - notifier.INFO, payload=payload) - do_disassociate() - - @rpc_common.client_exceptions(exception.FloatingIpNotFound) - def get_floating_ip(self, context, id): - """Returns a floating IP as a dict.""" - # NOTE(vish): This is no longer used but can't be removed until - # we major version the network_rpcapi. - return dict(self.db.floating_ip_get(context, id).iteritems()) - - def get_floating_pools(self, context): - """Returns list of floating pools.""" - # NOTE(maurosr) This method should be removed in future, replaced by - # get_floating_ip_pools. See bug #1091668 - return self.get_floating_ip_pools(context) - - def get_floating_ip_pools(self, context): - """Returns list of floating ip pools.""" - # NOTE(vish): This is no longer used but can't be removed until - # we major version the network_rpcapi. - pools = self.db.floating_ip_get_pools(context) - return [dict(pool.iteritems()) for pool in pools] - - def get_floating_ip_by_address(self, context, address): - """Returns a floating IP as a dict.""" - # NOTE(vish): This is no longer used but can't be removed until - # we major version the network_rpcapi. - return dict(self.db.floating_ip_get_by_address(context, - address).iteritems()) - - def get_floating_ips_by_project(self, context): - """Returns the floating IPs allocated to a project.""" - # NOTE(vish): This is no longer used but can't be removed until - # we major version the network_rpcapi. - ips = self.db.floating_ip_get_all_by_project(context, - context.project_id) - return [dict(ip.iteritems()) for ip in ips] - - def get_floating_ips_by_fixed_address(self, context, fixed_address): - """Returns the floating IPs associated with a fixed_address.""" - # NOTE(vish): This is no longer used but can't be removed until - # we major version the network_rpcapi. - floating_ips = self.db.floating_ip_get_by_fixed_address(context, - fixed_address) - return [floating_ip['address'] for floating_ip in floating_ips] - - def _is_stale_floating_ip_address(self, context, floating_ip): - try: - self._floating_ip_owned_by_project(context, floating_ip) - except exception.NotAuthorized: - return True - return False if floating_ip.get('fixed_ip_id') else True - - def migrate_instance_start(self, context, instance_uuid, - floating_addresses, - rxtx_factor=None, project_id=None, - source=None, dest=None): - # We only care if floating_addresses are provided and we're - # switching hosts - if not floating_addresses or (source and source == dest): - return - - LOG.info(_("Starting migration network for instance" - " %(instance_uuid)s"), locals()) - for address in floating_addresses: - floating_ip = self.db.floating_ip_get_by_address(context, - address) - - if self._is_stale_floating_ip_address(context, floating_ip): - LOG.warn(_("Floating ip address |%(address)s| no longer " - "belongs to instance %(instance_uuid)s. Will not" - "migrate it "), locals()) - continue - - interface = CONF.public_interface or floating_ip['interface'] - fixed_ip = self.db.fixed_ip_get(context, - floating_ip['fixed_ip_id'], - get_network=True) - self.l3driver.remove_floating_ip(floating_ip['address'], - fixed_ip['address'], - interface, - fixed_ip['network']) - - # NOTE(wenjianhn): Make this address will not be bound to public - # interface when restarts nova-network on dest compute node - self.db.floating_ip_update(context, - floating_ip['address'], - {'host': None}) - - def migrate_instance_finish(self, context, instance_uuid, - floating_addresses, host=None, - rxtx_factor=None, project_id=None, - source=None, dest=None): - # We only care if floating_addresses are provided and we're - # switching hosts - if host and not dest: - dest = host - if not floating_addresses or (source and source == dest): - return - - LOG.info(_("Finishing migration network for instance" - " %(instance_uuid)s"), locals()) - - for address in floating_addresses: - floating_ip = self.db.floating_ip_get_by_address(context, - address) - - if self._is_stale_floating_ip_address(context, floating_ip): - LOG.warn(_("Floating ip address |%(address)s| no longer " - "belongs to instance %(instance_uuid)s. Will not" - "setup it."), locals()) - continue - - self.db.floating_ip_update(context, - floating_ip['address'], - {'host': dest}) - - interface = CONF.public_interface or floating_ip['interface'] - fixed_ip = self.db.fixed_ip_get(context, - floating_ip['fixed_ip_id'], - get_network=True) - self.l3driver.add_floating_ip(floating_ip['address'], - fixed_ip['address'], - interface, - fixed_ip['network']) - - def _prepare_domain_entry(self, context, domain): - domainref = self.db.dnsdomain_get(context, domain) - scope = domainref['scope'] - if scope == 'private': - av_zone = domainref['availability_zone'] - this_domain = {'domain': domain, - 'scope': scope, - 'availability_zone': av_zone} - else: - project = domainref['project_id'] - this_domain = {'domain': domain, - 'scope': scope, - 'project': project} - return this_domain - - def get_dns_domains(self, context): - domains = [] - - db_domain_list = self.db.dnsdomain_list(context) - floating_driver_domain_list = self.floating_dns_manager.get_domains() - instance_driver_domain_list = self.instance_dns_manager.get_domains() - - for db_domain in db_domain_list: - if (db_domain in floating_driver_domain_list or - db_domain in instance_driver_domain_list): - domain_entry = self._prepare_domain_entry(context, - db_domain) - if domain_entry: - domains.append(domain_entry) - else: - LOG.warn(_('Database inconsistency: DNS domain |%s| is ' - 'registered in the Nova db but not visible to ' - 'either the floating or instance DNS driver. It ' - 'will be ignored.'), db_domain) - - return domains - - def add_dns_entry(self, context, address, name, dns_type, domain): - self.floating_dns_manager.create_entry(name, address, - dns_type, domain) - - def modify_dns_entry(self, context, address, name, domain): - self.floating_dns_manager.modify_address(name, address, - domain) - - def delete_dns_entry(self, context, name, domain): - self.floating_dns_manager.delete_entry(name, domain) - - def _delete_all_entries_for_ip(self, context, address): - domain_list = self.get_dns_domains(context) - for domain in domain_list: - names = self.get_dns_entries_by_address(context, - address, - domain['domain']) - for name in names: - self.delete_dns_entry(context, name, domain['domain']) - - def get_dns_entries_by_address(self, context, address, domain): - return self.floating_dns_manager.get_entries_by_address(address, - domain) - - def get_dns_entries_by_name(self, context, name, domain): - return self.floating_dns_manager.get_entries_by_name(name, - domain) - - def create_private_dns_domain(self, context, domain, av_zone): - self.db.dnsdomain_register_for_zone(context, domain, av_zone) - try: - self.instance_dns_manager.create_domain(domain) - except exception.FloatingIpDNSExists: - LOG.warn(_('Domain |%(domain)s| already exists, ' - 'changing zone to |%(av_zone)s|.'), - {'domain': domain, 'av_zone': av_zone}) - - def create_public_dns_domain(self, context, domain, project): - self.db.dnsdomain_register_for_project(context, domain, project) - try: - self.floating_dns_manager.create_domain(domain) - except exception.FloatingIpDNSExists: - LOG.warn(_('Domain |%(domain)s| already exists, ' - 'changing project to |%(project)s|.'), - {'domain': domain, 'project': project}) - - def delete_dns_domain(self, context, domain): - self.db.dnsdomain_unregister(context, domain) - self.floating_dns_manager.delete_domain(domain) - - def _get_project_for_domain(self, context, domain): - return self.db.dnsdomain_project(context, domain) - - class NetworkManager(manager.SchedulerDependentManager): """Implements common network manager functionality. @@ -2088,7 +1463,8 @@ class FlatManager(NetworkManager): pass -class FlatDHCPManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): +class FlatDHCPManager(RPCAllocateFixedIP, floating_ips.FloatingIP, + NetworkManager): """Flat networking with dhcp. FlatDHCPManager will start up one dhcp server to give out addresses. @@ -2151,7 +1527,7 @@ class FlatDHCPManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): return network_dict -class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): +class VlanManager(RPCAllocateFixedIP, floating_ips.FloatingIP, NetworkManager): """Vlan network with dhcp. VlanManager is the most complicated. It will create a host-managed diff --git a/nova/network/quantumv2/api.py b/nova/network/quantumv2/api.py index b2c20e225aa0..a59ea1c92676 100644 --- a/nova/network/quantumv2/api.py +++ b/nova/network/quantumv2/api.py @@ -59,7 +59,7 @@ quantum_opts = [ CONF = cfg.CONF CONF.register_opts(quantum_opts) -CONF.import_opt('default_floating_pool', 'nova.network.manager') +CONF.import_opt('default_floating_pool', 'nova.network.floating_ips') LOG = logging.getLogger(__name__) NET_EXTERNAL = 'router:external' diff --git a/nova/tests/network/test_manager.py b/nova/tests/network/test_manager.py index 2cc19bbb801e..48183010f781 100644 --- a/nova/tests/network/test_manager.py +++ b/nova/tests/network/test_manager.py @@ -24,6 +24,7 @@ from nova import db from nova.db.sqlalchemy import models from nova import exception from nova import ipv6 +from nova.network import floating_ips from nova.network import linux_net from nova.network import manager as network_manager from nova.network import model as net_model @@ -1577,7 +1578,7 @@ class BackdoorPortTestCase(test.TestCase): self.assertEqual(port, self.manager.backdoor_port) -class TestFloatingIPManager(network_manager.FloatingIP, +class TestFloatingIPManager(floating_ips.FloatingIP, network_manager.NetworkManager): """Dummy manager that implements FloatingIP.""" @@ -1667,8 +1668,8 @@ class FloatingIPTestCase(test.TestCase): 'fixed_ip_get', lambda _x, _y: fixed_ip) - self.stubs.Set(self.network, - '_get_network_by_id', + self.stubs.Set(self.network.db, + 'network_get', lambda _x, _y: network) self.stubs.Set(self.network.db, @@ -1725,8 +1726,8 @@ class FloatingIPTestCase(test.TestCase): 'fixed_ip_get_by_address', lambda _x, _y: fixed_ip) - self.stubs.Set(self.network, - '_get_network_by_id', + self.stubs.Set(self.network.db, + 'network_get', lambda _x, _y: network) self.stubs.Set(self.network.db,