docs: def _allocate_address(self, instance, network_ids):
          """
          Allocates a floating/public ip address to the given instance.
  
          :param instance: instance to assign address to
  
          :param list network_id: List of IDs (as strings) of networks where to
          request allocation the floating IP.
  
          :return: public ip address
          """
          with OpenStackCloudProvider.__node_start_lock:
              try:
                  # Use the `novaclient` API (works with python-novaclient <8.0.0)
                  free_ips = [ip for ip in self.nova_client.floating_ips.list() if not ip.fixed_ip]
                  if not free_ips:
                      free_ips.append(self.nova_client.floating_ips.create())
              except AttributeError:
                  # Use the `neutronclient` API
                  #
                  # for some obscure reason, using `fixed_ip_address=None` in the
                  # call to `list_floatingips()` returns *no* results (not even,
                  # in fact, those with `fixed_ip_address: None`) whereas
                  # `fixed_ip_address=''` acts as a wildcard and lists *all* the
                  # addresses... so filter them out with a list comprehension
                  free_ips = [ip for ip in
                              self.neutron_client.list_floatingips(fixed_ip_address='')['floatingips']
                              if ip['fixed_ip_address'] is None]
                  if not free_ips:
                      # FIXME: OpenStack Network API v2 requires that we specify
                      # a network ID along with the request for a floating IP.
                      # However, ElastiCluster configuration allows for multiple
                      # networks to be connected to a VM, but does not give any
                      # hint as to which one(s) should be used for such requests.
                      # So we try them all, ignoring errors until one request
                      # succeeds and hope that it's the OK. One can imagine
                      # scenarios where this is *not* correct, but: (1) these
                      # scenarios are unlikely, and (2) the old novaclient code
                      # above has not even had the concept of multiple networks
                      # for floating IPs and no-one has complained in 5 years...
                      allocated_ip = None
                      for network_id in network_ids:
                          log.debug(
                              "Trying to allocate floating IP on network %s ...", network_id)
                          try:
                              allocated_ip = self.neutron_client.create_floatingip({
                                  'floatingip': {'floating_network_id':network_id}})
                          except BadNeutronRequest as err:
                              log.debug(
                                  "Failed allocating floating IP on network %s: %s",
                                  network_id, err)
                          if allocated_ip:
                              free_ips.append(allocated_ip)
                              break
                          else:
                              continue  # try next network
              if free_ips:
                  ip = free_ips.pop()
              else:
                  raise RuntimeError(
                      "Could not allocate floating IP for VM {0}"
                      .format(vm.id))
              instance.add_floating_ip(ip)
          return ip.ip