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