"Fossies" - the Fresh Open Source Software Archive

Member "neutron-14.0.3/neutron/db/ipam_pluggable_backend.py" (22 Oct 2019, 28921 Bytes) of package /linux/misc/openstack/neutron-14.0.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "ipam_pluggable_backend.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.0.2_vs_14.0.3.

    1 # Copyright (c) 2015 Infoblox Inc.
    2 # All Rights Reserved.
    3 #
    4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    5 #    not use this file except in compliance with the License. You may obtain
    6 #    a copy of the License at
    7 #
    8 #         http://www.apache.org/licenses/LICENSE-2.0
    9 #
   10 #    Unless required by applicable law or agreed to in writing, software
   11 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   13 #    License for the specific language governing permissions and limitations
   14 #    under the License.
   15 
   16 import copy
   17 
   18 import netaddr
   19 from neutron_lib.api.definitions import portbindings
   20 from neutron_lib import constants
   21 from neutron_lib.db import api as db_api
   22 from neutron_lib import exceptions as n_exc
   23 from neutron_lib.objects import utils as obj_utils
   24 from neutron_lib.plugins import constants as plugin_consts
   25 from neutron_lib.plugins import directory
   26 
   27 from oslo_db import exception as db_exc
   28 from oslo_log import log as logging
   29 from oslo_utils import excutils
   30 
   31 from neutron.common import ipv6_utils
   32 from neutron.db import ipam_backend_mixin
   33 from neutron.ipam import driver
   34 from neutron.ipam import exceptions as ipam_exc
   35 from neutron.objects import ports as port_obj
   36 from neutron.objects import subnet as obj_subnet
   37 
   38 
   39 LOG = logging.getLogger(__name__)
   40 
   41 
   42 def get_ip_update_not_allowed_device_owner_list():
   43     l3plugin = directory.get_plugin(plugin_consts.L3)
   44     # The following list is for IPAM to prevent direct update of port
   45     # IP address. Currently it only has some L3 related types.
   46     # L2 plugin can add the same list here, but for now it is not required.
   47     return getattr(l3plugin, 'IP_UPDATE_NOT_ALLOWED_LIST', [])
   48 
   49 
   50 def is_neutron_built_in_router(context, router_id):
   51     l3plugin = directory.get_plugin(plugin_consts.L3)
   52     return bool(l3plugin and
   53                 l3plugin.router_supports_scheduling(context, router_id))
   54 
   55 
   56 class IpamPluggableBackend(ipam_backend_mixin.IpamBackendMixin):
   57 
   58     def _get_failed_ips(self, all_ips, success_ips):
   59         ips_list = (ip_dict['ip_address'] for ip_dict in success_ips)
   60         return (ip_dict['ip_address'] for ip_dict in all_ips
   61                 if ip_dict['ip_address'] not in ips_list)
   62 
   63     def _safe_rollback(self, func, *args, **kwargs):
   64         """Calls rollback actions and catch all exceptions.
   65 
   66         All exceptions are catched and logged here to prevent rewriting
   67         original exception that triggered rollback action.
   68         """
   69         try:
   70             func(*args, **kwargs)
   71         except Exception as e:
   72             LOG.warning("Revert failed with: %s", e)
   73 
   74     def _ipam_deallocate_ips(self, context, ipam_driver, port, ips,
   75                              revert_on_fail=True):
   76         """Deallocate set of ips over IPAM.
   77 
   78         If any single ip deallocation fails, tries to allocate deallocated
   79         ip addresses with fixed ip request
   80         """
   81         deallocated = []
   82 
   83         try:
   84             for ip in ips:
   85                 try:
   86                     ipam_subnet = ipam_driver.get_subnet(ip['subnet_id'])
   87                     ipam_subnet.deallocate(ip['ip_address'])
   88                     deallocated.append(ip)
   89                 except n_exc.SubnetNotFound:
   90                     LOG.debug("Subnet was not found on ip deallocation: %s",
   91                               ip)
   92         except Exception:
   93             with excutils.save_and_reraise_exception():
   94                 if not ipam_driver.needs_rollback():
   95                     return
   96 
   97                 LOG.debug("An exception occurred during IP deallocation.")
   98                 if revert_on_fail and deallocated:
   99                     LOG.debug("Reverting deallocation")
  100                     # In case of deadlock allocate fails with db error
  101                     # and rewrites original exception preventing db_retry
  102                     # wrappers from restarting entire api request.
  103                     self._safe_rollback(self._ipam_allocate_ips, context,
  104                                         ipam_driver, port, deallocated,
  105                                         revert_on_fail=False)
  106                 elif not revert_on_fail and ips:
  107                     addresses = ', '.join(self._get_failed_ips(ips,
  108                                                                deallocated))
  109                     LOG.error("IP deallocation failed on "
  110                               "external system for %s", addresses)
  111         return deallocated
  112 
  113     def _ipam_allocate_ips(self, context, ipam_driver, port, ips,
  114                            revert_on_fail=True):
  115         """Allocate set of ips over IPAM.
  116 
  117         If any single ip allocation fails, tries to deallocate all
  118         allocated ip addresses.
  119         """
  120         allocated = []
  121 
  122         # we need to start with entries that asked for a specific IP in case
  123         # those IPs happen to be next in the line for allocation for ones that
  124         # didn't ask for a specific IP
  125         ips.sort(key=lambda x: 'ip_address' not in x)
  126         try:
  127             for ip in ips:
  128                 # By default IP info is dict, used to allocate single ip
  129                 # from single subnet.
  130                 # IP info can be list, used to allocate single ip from
  131                 # multiple subnets
  132                 ip_list = [ip] if isinstance(ip, dict) else ip
  133                 subnets = [ip_dict['subnet_id'] for ip_dict in ip_list]
  134                 try:
  135                     factory = ipam_driver.get_address_request_factory()
  136                     ip_request = factory.get_request(context, port, ip_list[0])
  137                     ipam_allocator = ipam_driver.get_allocator(subnets)
  138                     ip_address, subnet_id = ipam_allocator.allocate(ip_request)
  139                 except ipam_exc.IpAddressGenerationFailureAllSubnets:
  140                     raise n_exc.IpAddressGenerationFailure(
  141                         net_id=port['network_id'])
  142 
  143                 allocated.append({'ip_address': ip_address,
  144                                   'subnet_id': subnet_id})
  145         except Exception:
  146             with excutils.save_and_reraise_exception():
  147                 if not ipam_driver.needs_rollback():
  148                     return
  149 
  150                 LOG.debug("An exception occurred during IP allocation.")
  151 
  152                 if revert_on_fail and allocated:
  153                     LOG.debug("Reverting allocation")
  154                     # In case of deadlock deallocation fails with db error
  155                     # and rewrites original exception preventing db_retry
  156                     # wrappers from restarting entire api request.
  157                     self._safe_rollback(self._ipam_deallocate_ips, context,
  158                                         ipam_driver, port, allocated,
  159                                         revert_on_fail=False)
  160                 elif not revert_on_fail and ips:
  161                     addresses = ', '.join(self._get_failed_ips(ips,
  162                                                                allocated))
  163                     LOG.error("IP allocation failed on "
  164                               "external system for %s", addresses)
  165 
  166         return allocated
  167 
  168     def _ipam_update_allocation_pools(self, context, ipam_driver, subnet):
  169         factory = ipam_driver.get_subnet_request_factory()
  170         subnet_request = factory.get_request(context, subnet, None)
  171 
  172         ipam_driver.update_subnet(subnet_request)
  173 
  174     def delete_subnet(self, context, subnet_id):
  175         ipam_driver = driver.Pool.get_instance(None, context)
  176         ipam_driver.remove_subnet(subnet_id)
  177 
  178     def get_subnet(self, context, subnet_id):
  179         ipam_driver = driver.Pool.get_instance(None, context)
  180         return ipam_driver.get_subnet(subnet_id)
  181 
  182     def allocate_ips_for_port_and_store(self, context, port, port_id):
  183         # Make a copy of port dict to prevent changing
  184         # incoming dict by adding 'id' to it.
  185         # Deepcopy doesn't work correctly in this case, because copy of
  186         # ATTR_NOT_SPECIFIED object happens. Address of copied object doesn't
  187         # match original object, so 'is' check fails
  188         # TODO(njohnston): Different behavior is required depending on whether
  189         # a Port object is used or not; once conversion to OVO is complete only
  190         # the first 'if' will be needed
  191         if isinstance(port, port_obj.Port):
  192             port_copy = {"port": self._make_port_dict(
  193                 port, process_extensions=False)}
  194         elif 'port' in port:
  195             port_copy = {'port': port['port'].copy()}
  196         else:
  197             port_copy = {'port': port.copy()}
  198 
  199         port_copy['port']['id'] = port_id
  200         network_id = port_copy['port']['network_id']
  201         ips = []
  202         try:
  203             ips = self._allocate_ips_for_port(context, port_copy)
  204             for ip in ips:
  205                 ip_address = ip['ip_address']
  206                 subnet_id = ip['subnet_id']
  207                 IpamPluggableBackend._store_ip_allocation(
  208                     context, ip_address, network_id,
  209                     subnet_id, port_id)
  210             return ips
  211         except Exception:
  212             with excutils.save_and_reraise_exception():
  213                 if ips:
  214                     ipam_driver = driver.Pool.get_instance(None, context)
  215                     if not ipam_driver.needs_rollback():
  216                         return
  217 
  218                     LOG.debug("An exception occurred during port creation. "
  219                               "Reverting IP allocation")
  220                     self._safe_rollback(self._ipam_deallocate_ips, context,
  221                                         ipam_driver, port_copy['port'], ips,
  222                                         revert_on_fail=False)
  223 
  224     def _allocate_ips_for_port(self, context, port):
  225         """Allocate IP addresses for the port. IPAM version.
  226 
  227         If port['fixed_ips'] is set to 'ATTR_NOT_SPECIFIED', allocate IP
  228         addresses for the port. If port['fixed_ips'] contains an IP address or
  229         a subnet_id then allocate an IP address accordingly.
  230         """
  231         p = port['port']
  232         fixed_configured = p['fixed_ips'] is not constants.ATTR_NOT_SPECIFIED
  233         subnets = self._ipam_get_subnets(context,
  234                                          network_id=p['network_id'],
  235                                          host=p.get(portbindings.HOST_ID),
  236                                          service_type=p.get('device_owner'),
  237                                          fixed_configured=fixed_configured)
  238 
  239         v4, v6_stateful, v6_stateless = self._classify_subnets(
  240             context, subnets)
  241 
  242         if fixed_configured:
  243             ips = self._test_fixed_ips_for_port(context,
  244                                                 p["network_id"],
  245                                                 p['fixed_ips'],
  246                                                 p['device_owner'],
  247                                                 subnets)
  248         else:
  249             ips = []
  250             version_subnets = [v4, v6_stateful]
  251             for subnets in version_subnets:
  252                 if subnets:
  253                     ips.append([{'subnet_id': s['id']}
  254                                 for s in subnets])
  255 
  256         ips.extend(self._get_auto_address_ips(v6_stateless, p))
  257 
  258         ipam_driver = driver.Pool.get_instance(None, context)
  259         return self._ipam_allocate_ips(context, ipam_driver, p, ips)
  260 
  261     def _get_auto_address_ips(self, v6_stateless_subnets, port,
  262                               exclude_subnet_ids=None):
  263         exclude_subnet_ids = exclude_subnet_ids or []
  264         ips = []
  265         is_router_port = (
  266             port['device_owner'] in constants.ROUTER_INTERFACE_OWNERS_SNAT)
  267         if not is_router_port:
  268             for subnet in v6_stateless_subnets:
  269                 if subnet['id'] not in exclude_subnet_ids:
  270                     # IP addresses for IPv6 SLAAC and DHCPv6-stateless subnets
  271                     # are implicitly included.
  272                     ips.append({'subnet_id': subnet['id'],
  273                                 'subnet_cidr': subnet['cidr'],
  274                                 'eui64_address': True,
  275                                 'mac': port['mac_address']})
  276         return ips
  277 
  278     def _test_fixed_ips_for_port(self, context, network_id, fixed_ips,
  279                                  device_owner, subnets):
  280         """Test fixed IPs for port.
  281 
  282         Check that configured subnets are valid prior to allocating any
  283         IPs. Include the subnet_id in the result if only an IP address is
  284         configured.
  285 
  286         :raises: InvalidInput, IpAddressInUse, InvalidIpForNetwork,
  287                  InvalidIpForSubnet
  288         """
  289         fixed_ip_list = []
  290         for fixed in fixed_ips:
  291             fixed['device_owner'] = device_owner
  292             subnet = self._get_subnet_for_fixed_ip(context, fixed, subnets)
  293 
  294             is_auto_addr_subnet = ipv6_utils.is_auto_address_subnet(subnet)
  295             if ('ip_address' in fixed and
  296                     subnet['cidr'] != constants.PROVISIONAL_IPV6_PD_PREFIX):
  297                 if (is_auto_addr_subnet and device_owner not in
  298                         constants.ROUTER_INTERFACE_OWNERS):
  299                     raise ipam_exc.AllocationOnAutoAddressSubnet(
  300                         ip=fixed['ip_address'], subnet_id=subnet['id'])
  301                 fixed_ip_list.append({'subnet_id': subnet['id'],
  302                                       'ip_address': fixed['ip_address']})
  303             else:
  304                 # A scan for auto-address subnets on the network is done
  305                 # separately so that all such subnets (not just those
  306                 # listed explicitly here by subnet ID) are associated
  307                 # with the port.
  308                 if (device_owner in constants.ROUTER_INTERFACE_OWNERS_SNAT or
  309                         not is_auto_addr_subnet):
  310                     fixed_ip_list.append({'subnet_id': subnet['id']})
  311 
  312         return fixed_ip_list
  313 
  314     def _check_ip_changed_by_version(self, context, ip_list, version):
  315         for ip in ip_list:
  316             ip_address = ip.get('ip_address')
  317             subnet_id = ip.get('subnet_id')
  318             if ip_address:
  319                 ip_addr = netaddr.IPAddress(ip_address)
  320                 if ip_addr.version == version:
  321                     return True
  322             elif subnet_id:
  323                 subnet = obj_subnet.Subnet.get_object(context, id=subnet_id)
  324                 if subnet and subnet.ip_version == version:
  325                     return True
  326         return False
  327 
  328     def _update_ips_for_port(self, context, port, host,
  329                              original_ips, new_ips, mac):
  330         """Add or remove IPs from the port. IPAM version"""
  331         added = []
  332         removed = []
  333         changes = self._get_changed_ips_for_port(
  334             context, original_ips, new_ips, port['device_owner'])
  335 
  336         not_allowed_list = get_ip_update_not_allowed_device_owner_list()
  337         if (port['device_owner'] in not_allowed_list and
  338                 is_neutron_built_in_router(context, port['device_id'])):
  339             ip_v4_changed = self._check_ip_changed_by_version(
  340                 context, changes.remove + changes.add,
  341                 constants.IP_VERSION_4)
  342             if ip_v4_changed:
  343                 raise ipam_exc.IPAddressChangeNotAllowed(port_id=port['id'])
  344 
  345         try:
  346             subnets = self._ipam_get_subnets(
  347                 context, network_id=port['network_id'], host=host,
  348                 service_type=port.get('device_owner'), fixed_configured=True)
  349         except ipam_exc.DeferIpam:
  350             subnets = []
  351 
  352         # Check if the IP's to add are OK
  353         to_add = self._test_fixed_ips_for_port(
  354             context, port['network_id'], changes.add,
  355             port['device_owner'], subnets)
  356 
  357         if port['device_owner'] not in constants.ROUTER_INTERFACE_OWNERS:
  358             to_add += self._update_ips_for_pd_subnet(
  359                 context, subnets, changes.add, mac)
  360 
  361         ipam_driver = driver.Pool.get_instance(None, context)
  362         if changes.remove:
  363             removed = self._ipam_deallocate_ips(context, ipam_driver, port,
  364                                                 changes.remove)
  365 
  366         v6_stateless = self._classify_subnets(
  367             context, subnets)[2]
  368         handled_subnet_ids = [ip['subnet_id'] for ip in
  369                               to_add + changes.original + changes.remove]
  370         to_add.extend(self._get_auto_address_ips(
  371             v6_stateless, port, handled_subnet_ids))
  372 
  373         if to_add:
  374             added = self._ipam_allocate_ips(context, ipam_driver,
  375                                             port, to_add)
  376         return self.Changes(add=added,
  377                             original=changes.original,
  378                             remove=removed)
  379 
  380     @db_api.CONTEXT_WRITER
  381     def save_allocation_pools(self, context, subnet, allocation_pools):
  382         for pool in allocation_pools:
  383             first_ip = str(netaddr.IPAddress(pool.first, pool.version))
  384             last_ip = str(netaddr.IPAddress(pool.last, pool.version))
  385             obj_subnet.IPAllocationPool(
  386                 context, subnet_id=subnet['id'], start=first_ip,
  387                 end=last_ip).create()
  388 
  389     def update_port_with_ips(self, context, host, db_port, new_port, new_mac):
  390         changes = self.Changes(add=[], original=[], remove=[])
  391 
  392         auto_assign_subnets = []
  393         if new_mac:
  394             original = self._make_port_dict(db_port, process_extensions=False)
  395             if original.get('mac_address') != new_mac:
  396                 original_ips = original.get('fixed_ips', [])
  397                 # NOTE(hjensas): Only set the default for 'fixed_ips' in
  398                 # new_port if the original port or new_port actually have IPs.
  399                 # Setting the default to [] breaks deferred IP allocation.
  400                 # See Bug: https://bugs.launchpad.net/neutron/+bug/1811905
  401                 if original_ips or new_port.get('fixed_ips'):
  402                     new_ips = new_port.setdefault('fixed_ips', original_ips)
  403                     new_ips_subnets = [new_ip['subnet_id']
  404                                        for new_ip in new_ips]
  405                 for orig_ip in original_ips:
  406                     if ipv6_utils.is_eui64_address(orig_ip.get('ip_address')):
  407                         subnet_to_delete = {}
  408                         subnet_to_delete['subnet_id'] = orig_ip['subnet_id']
  409                         subnet_to_delete['delete_subnet'] = True
  410                         auto_assign_subnets.append(subnet_to_delete)
  411                         try:
  412                             i = new_ips_subnets.index(orig_ip['subnet_id'])
  413                             new_ips[i] = subnet_to_delete
  414                         except ValueError:
  415                             new_ips.append(subnet_to_delete)
  416 
  417         if 'fixed_ips' in new_port:
  418             original = self._make_port_dict(db_port,
  419                                             process_extensions=False)
  420             changes = self._update_ips_for_port(context,
  421                                                 db_port,
  422                                                 host,
  423                                                 original["fixed_ips"],
  424                                                 new_port['fixed_ips'],
  425                                                 new_mac)
  426         try:
  427             # Expire the fixed_ips of db_port in current transaction, because
  428             # it will be changed in the following operation and the latest
  429             # data is expected.
  430             context.session.expire(db_port, ['fixed_ips'])
  431 
  432             # Check if the IPs need to be updated
  433             network_id = db_port['network_id']
  434             for ip in changes.remove:
  435                 self._delete_ip_allocation(context, network_id,
  436                                            ip['subnet_id'], ip['ip_address'])
  437             for ip in changes.add:
  438                 self._store_ip_allocation(
  439                     context, ip['ip_address'], network_id,
  440                     ip['subnet_id'], db_port.id)
  441             self._update_db_port(context, db_port, new_port, network_id,
  442                                  new_mac)
  443 
  444             if auto_assign_subnets:
  445                 port_copy = copy.deepcopy(original)
  446                 port_copy.update(new_port)
  447                 port_copy['fixed_ips'] = auto_assign_subnets
  448                 self.allocate_ips_for_port_and_store(
  449                     context, {'port': port_copy}, port_copy['id'])
  450 
  451             getattr(db_port, 'fixed_ips')  # refresh relationship before return
  452 
  453         except Exception:
  454             with excutils.save_and_reraise_exception():
  455                 if 'fixed_ips' in new_port:
  456                     ipam_driver = driver.Pool.get_instance(None, context)
  457                     if not ipam_driver.needs_rollback():
  458                         return
  459 
  460                     LOG.debug("An exception occurred during port update.")
  461                     if changes.add:
  462                         LOG.debug("Reverting IP allocation.")
  463                         self._safe_rollback(self._ipam_deallocate_ips,
  464                                             context,
  465                                             ipam_driver,
  466                                             db_port,
  467                                             changes.add,
  468                                             revert_on_fail=False)
  469                     if changes.remove:
  470                         LOG.debug("Reverting IP deallocation.")
  471                         self._safe_rollback(self._ipam_allocate_ips,
  472                                             context,
  473                                             ipam_driver,
  474                                             db_port,
  475                                             changes.remove,
  476                                             revert_on_fail=False)
  477         return changes
  478 
  479     def delete_port(self, context, id):
  480         # Get fixed_ips list before port deletion
  481         port = self._get_port(context, id)
  482         ipam_driver = driver.Pool.get_instance(None, context)
  483 
  484         super(IpamPluggableBackend, self).delete_port(context, id)
  485         # Deallocating ips via IPAM after port is deleted locally.
  486         # So no need to do rollback actions on remote server
  487         # in case of fail to delete port locally
  488         self._ipam_deallocate_ips(context, ipam_driver, port,
  489                                   port['fixed_ips'])
  490 
  491     def update_db_subnet(self, context, id, s, old_pools):
  492         subnet = obj_subnet.Subnet.get_object(context, id=id)
  493         old_segment_id = subnet.segment_id if subnet else None
  494         if 'segment_id' in s:
  495             self._validate_segment(
  496                 context, s['network_id'], s['segment_id'], action='update',
  497                 old_segment_id=old_segment_id)
  498         # 'allocation_pools' is removed from 's' in
  499         # _update_subnet_allocation_pools (ipam_backend_mixin),
  500         # so create unchanged copy for ipam driver
  501         subnet_copy = copy.deepcopy(s)
  502         subnet, changes = super(IpamPluggableBackend, self).update_db_subnet(
  503             context, id, s, old_pools)
  504         ipam_driver = driver.Pool.get_instance(None, context)
  505 
  506         # Set old allocation pools if no new pools are provided by user.
  507         # Passing old pools allows to call ipam driver on each subnet update
  508         # even if allocation pools are not changed. So custom ipam drivers
  509         # are able to track other fields changes on subnet update.
  510         if 'allocation_pools' not in subnet_copy:
  511             subnet_copy['allocation_pools'] = old_pools
  512         self._ipam_update_allocation_pools(context, ipam_driver, subnet_copy)
  513 
  514         return subnet, changes
  515 
  516     def add_auto_addrs_on_network_ports(self, context, subnet, ipam_subnet):
  517         """For an auto-address subnet, add addrs for ports on the net."""
  518         # TODO(ataraday): switched for writer when flush_on_subtransaction
  519         # will be available for neutron
  520         with context.session.begin(subtransactions=True):
  521             network_id = subnet['network_id']
  522             ports = port_obj.Port.get_objects(
  523                 context, network_id=network_id,
  524                 device_owner=obj_utils.NotIn(
  525                     constants.ROUTER_INTERFACE_OWNERS_SNAT))
  526             updated_ports = []
  527             ipam_driver = driver.Pool.get_instance(None, context)
  528             factory = ipam_driver.get_address_request_factory()
  529             for port in ports:
  530                 ip = {'subnet_id': subnet['id'],
  531                       'subnet_cidr': subnet['cidr'],
  532                       'eui64_address': True,
  533                       'mac': port.mac_address}
  534                 ip_request = factory.get_request(context, port, ip)
  535                 try:
  536                     ip_address = ipam_subnet.allocate(ip_request)
  537                     allocated = port_obj.IPAllocation(
  538                         context, network_id=network_id, port_id=port.id,
  539                         ip_address=ip_address, subnet_id=subnet['id'])
  540                     # Do the insertion of each IP allocation entry within
  541                     # the context of a nested transaction, so that the entry
  542                     # is rolled back independently of other entries whenever
  543                     # the corresponding port has been deleted; since OVO
  544                     # already opens a nested transaction, we don't need to do
  545                     # it explicitly here.
  546                     allocated.create()
  547                     updated_ports.append(port.id)
  548                 except db_exc.DBReferenceError:
  549                     LOG.debug("Port %s was deleted while updating it with an "
  550                               "IPv6 auto-address. Ignoring.", port.id)
  551                     LOG.debug("Reverting IP allocation for %s", ip_address)
  552                     # Do not fail if reverting allocation was unsuccessful
  553                     try:
  554                         ipam_subnet.deallocate(ip_address)
  555                     except Exception:
  556                         LOG.debug("Reverting IP allocation failed for %s",
  557                                   ip_address)
  558                 except ipam_exc.IpAddressAlreadyAllocated:
  559                     LOG.debug("Port %s got IPv6 auto-address in a concurrent "
  560                               "create or update port request. Ignoring.",
  561                               port.id)
  562             return updated_ports
  563 
  564     def allocate_subnet(self, context, network, subnet, subnetpool_id):
  565         subnetpool = None
  566 
  567         if subnetpool_id and not subnetpool_id == constants.IPV6_PD_POOL_ID:
  568             subnetpool = self._get_subnetpool(context, id=subnetpool_id)
  569             self._validate_ip_version_with_subnetpool(subnet, subnetpool)
  570 
  571         # gateway_ip and allocation pools should be validated or generated
  572         # only for specific request
  573         if subnet['cidr'] is not constants.ATTR_NOT_SPECIFIED:
  574             subnet['gateway_ip'] = self._gateway_ip_str(subnet,
  575                                                         subnet['cidr'])
  576             subnet['allocation_pools'] = self._prepare_allocation_pools(
  577                 subnet['allocation_pools'],
  578                 subnet['cidr'],
  579                 subnet['gateway_ip'])
  580 
  581         ipam_driver = driver.Pool.get_instance(subnetpool, context)
  582         subnet_factory = ipam_driver.get_subnet_request_factory()
  583         subnet_request = subnet_factory.get_request(context, subnet,
  584                                                     subnetpool)
  585         ipam_subnet = ipam_driver.allocate_subnet(subnet_request)
  586         # get updated details with actually allocated subnet
  587         subnet_request = ipam_subnet.get_details()
  588 
  589         try:
  590             subnet = self._save_subnet(context,
  591                                        network,
  592                                        self._make_subnet_args(
  593                                            subnet_request,
  594                                            subnet,
  595                                            subnetpool_id),
  596                                        subnet['dns_nameservers'],
  597                                        subnet['host_routes'],
  598                                        subnet_request)
  599         except Exception:
  600             # Note(pbondar): Third-party ipam servers can't rely
  601             # on transaction rollback, so explicit rollback call needed.
  602             # IPAM part rolled back in exception handling
  603             # and subnet part is rolled back by transaction rollback.
  604             with excutils.save_and_reraise_exception():
  605                 if not ipam_driver.needs_rollback():
  606                     return
  607 
  608                 LOG.debug("An exception occurred during subnet creation. "
  609                           "Reverting subnet allocation.")
  610                 self._safe_rollback(self.delete_subnet,
  611                                     context,
  612                                     subnet_request.subnet_id)
  613         return subnet, ipam_subnet