Beispiel #1
0
def update_ip_address(context, id, ip_address):
    LOG.info("update_ip_address %s for tenant %s" %
            (id, context.tenant_id))

    address = db_api.ip_address_find(
        context, id=id, tenant_id=context.tenant_id, scope=db_api.ONE)

    if not address:
        raise exceptions.NotFound(
            message="No IP address found with id=%s" % id)

    old_ports = address['ports']
    port_ids = ip_address['ip_address'].get('port_ids')
    if port_ids is None:
        return v._make_ip_dict(address)

    for port in old_ports:
        port['ip_addresses'].remove(address)

    if port_ids:
        ports = db_api.port_find(
            context, tenant_id=context.tenant_id, id=port_ids,
            scope=db_api.ALL)

        # NOTE: could be considered inefficient because we're converting
        #       to a list to check length. Maybe revisit
        if len(ports) != len(port_ids):
            raise exceptions.NotFound(
                message="No ports not found with ids=%s" % port_ids)
        for port in ports:
            port['ip_addresses'].extend([address])
    else:
        address["deallocated"] = 1

    return v._make_ip_dict(address)
Beispiel #2
0
def update_ip_address(context, id, ip_address):
    LOG.info("update_ip_address %s for tenant %s" %
             (id, context.tenant_id))
    ports = []
    with context.session.begin():
        address = db_api.ip_address_find(
            context, id=id, tenant_id=context.tenant_id, scope=db_api.ONE)
        if not address:
            raise exceptions.NotFound(
                message="No IP address found with id=%s" % id)

        reset = ip_address['ip_address'].get('reset_allocation_time', False)
        if reset and address['deallocated'] == 1:
            if context.is_admin:
                LOG.info("IP's deallocated time being manually reset")
                address['deallocated_at'] = _get_deallocated_override()
            else:
                msg = "Modification of reset_allocation_time requires admin"
                raise webob.exc.HTTPForbidden(detail=msg)

        port_ids = ip_address['ip_address'].get('port_ids')

        if port_ids:
            _raise_if_shared_and_enabled(ip_address, address)
            ports = db_api.port_find(context, tenant_id=context.tenant_id,
                                     id=port_ids, scope=db_api.ALL)
            # NOTE(name): could be considered inefficient because we're
            # converting to a list to check length. Maybe revisit
            if len(ports) != len(port_ids):
                raise exceptions.NotFound(
                    message="No ports not found with ids=%s" % port_ids)

            validate_ports_on_network_and_same_segment(ports,
                                                       address["network_id"])
            validate_port_ip_quotas(context, ports)

            LOG.info("Updating IP address, %s, to only be used by the"
                     "following ports:  %s" % (address.address_readable,
                                               [p.id for p in ports]))
            new_address = db_api.update_port_associations_for_ip(context,
                                                                 ports,
                                                                 address)
        else:
            if port_ids is not None:
                ipam_driver.deallocate_ip_address(
                    context, address)
            return v._make_ip_dict(address)
    return v._make_ip_dict(new_address)
Beispiel #3
0
def get_ip_addresses(context, **filters):
    LOG.info("get_ip_addresses for tenant %s" % context.tenant_id)
    if not filters:
        filters = {}
    if 'type' in filters:
        filters['address_type'] = filters['type']
    if context.is_admin:
        if 'deallocated' in filters:
            if filters['deallocated'] == 'True':
                filters["_deallocated"] = True
            elif filters['deallocated'] == 'False':
                filters["_deallocated"] = False
            elif filters['deallocated'].lower() == 'both':
                pass
            else:
                filters['_deallocated'] = False
        else:
            filters['_deallocated'] = False
    else:
        filters["_deallocated"] = False

    if 'deallocated' in filters:
        # this needs to be removed or it corrupts the filters passed to the db
        # model. Only _deallocated needs to be present
        del filters['deallocated']

    addrs = db_api.ip_address_find(context, scope=db_api.ALL, **filters)
    return [v._make_ip_dict(ip, context.is_admin) for ip in addrs]
Beispiel #4
0
def get_ip_addresses(context, **filters):
    LOG.info("get_ip_addresses for tenant %s" % context.tenant_id)
    if not filters:
        filters = {}
    if 'type' in filters:
        filters['address_type'] = filters['type']
    if context.is_admin:
        if 'deallocated' in filters:
            if filters['deallocated'] == 'True':
                filters["_deallocated"] = True
            elif filters['deallocated'] == 'False':
                filters["_deallocated"] = False
            elif filters['deallocated'].lower() == 'both':
                pass
            else:
                filters['_deallocated'] = False
        else:
            filters['_deallocated'] = False
    else:
        filters["_deallocated"] = False

    if 'deallocated' in filters:
        # this needs to be removed or it corrupts the filters passed to the db
        # model. Only _deallocated needs to be present
        del filters['deallocated']

    addrs = db_api.ip_address_find(context, scope=db_api.ALL, **filters)
    return [v._make_ip_dict(ip, context.is_admin) for ip in addrs]
Beispiel #5
0
def get_ip_address(context, id):
    LOG.info("get_ip_address %s for tenant %s" %
             (id, context.tenant_id))
    addr = db_api.ip_address_find(context, id=id, scope=db_api.ONE)
    if not addr:
        raise quark_exceptions.IpAddressNotFound(addr_id=id)
    return v._make_ip_dict(addr)
Beispiel #6
0
def update_ip_address(context, id, ip_address):
    LOG.info("update_ip_address %s for tenant %s" % (id, context.tenant_id))

    with context.session.begin():
        address = db_api.ip_address_find(context,
                                         id=id,
                                         tenant_id=context.tenant_id,
                                         scope=db_api.ONE)

        if not address:
            raise exceptions.NotFound(
                message="No IP address found with id=%s" % id)

        reset = ip_address['ip_address'].get('reset_allocation_time', False)
        if reset and address['deallocated'] == 1:
            if context.is_admin:
                LOG.info("IP's deallocated time being manually reset")
                address['deallocated_at'] = _get_deallocated_override()
            else:
                msg = "Modification of reset_allocation_time requires admin"
                raise webob.exc.HTTPForbidden(detail=msg)

        old_ports = address['ports']
        port_ids = ip_address['ip_address'].get('port_ids')
        if port_ids is None:
            return v._make_ip_dict(address)

        for port in old_ports:
            port['ip_addresses'].remove(address)

        if port_ids:
            ports = db_api.port_find(context,
                                     tenant_id=context.tenant_id,
                                     id=port_ids,
                                     scope=db_api.ALL)

            # NOTE: could be considered inefficient because we're converting
            #       to a list to check length. Maybe revisit
            if len(ports) != len(port_ids):
                raise exceptions.NotFound(
                    message="No ports not found with ids=%s" % port_ids)
            for port in ports:
                port['ip_addresses'].extend([address])
        else:
            address["deallocated"] = 1

    return v._make_ip_dict(address)
Beispiel #7
0
def get_ip_address(context, id):
    LOG.info("get_ip_address %s for tenant %s" % (id, context.tenant_id))
    filters = {}
    filters["_deallocated"] = False
    addr = db_api.ip_address_find(context, id=id, scope=db_api.ONE, **filters)
    if not addr:
        raise q_exc.IpAddressNotFound(addr_id=id)
    return v._make_ip_dict(addr)
Beispiel #8
0
def update_ip_address(context, id, ip_address):
    LOG.info("update_ip_address %s for tenant %s" %
             (id, context.tenant_id))

    with context.session.begin():
        address = db_api.ip_address_find(
            context, id=id, tenant_id=context.tenant_id, scope=db_api.ONE)

        if not address:
            raise exceptions.NotFound(
                message="No IP address found with id=%s" % id)

        reset = ip_address['ip_address'].get('reset_allocation_time',
                                             False)
        if reset and address['deallocated'] == 1:
            if context.is_admin:
                LOG.info("IP's deallocated time being manually reset")
                address['deallocated_at'] = _get_deallocated_override()
            else:
                msg = "Modification of reset_allocation_time requires admin"
                raise webob.exc.HTTPForbidden(detail=msg)

        old_ports = address['ports']
        port_ids = ip_address['ip_address'].get('port_ids')
        if port_ids is None:
            return v._make_ip_dict(address)

        for port in old_ports:
            port['ip_addresses'].remove(address)

        if port_ids:
            ports = db_api.port_find(
                context, tenant_id=context.tenant_id, id=port_ids,
                scope=db_api.ALL)

            # NOTE: could be considered inefficient because we're converting
            #       to a list to check length. Maybe revisit
            if len(ports) != len(port_ids):
                raise exceptions.NotFound(
                    message="No ports not found with ids=%s" % port_ids)
            for port in ports:
                port['ip_addresses'].extend([address])
        else:
            address["deallocated"] = 1

    return v._make_ip_dict(address)
Beispiel #9
0
def get_ip_addresses(context, **filters):
    LOG.info("get_ip_addresses for tenant %s" % context.tenant_id)
    if not filters:
        filters = {}
    if 'type' in filters:
        filters['address_type'] = filters['type']
    filters["_deallocated"] = False
    addrs = db_api.ip_address_find(context, scope=db_api.ALL, **filters)
    return [v._make_ip_dict(ip) for ip in addrs]
Beispiel #10
0
def get_ip_address(context, id):
    LOG.info("get_ip_address %s for tenant %s" %
             (id, context.tenant_id))
    filters = {}
    filters["_deallocated"] = False
    addr = db_api.ip_address_find(context, id=id, scope=db_api.ONE, **filters)
    if not addr:
        raise q_exc.IpAddressNotFound(addr_id=id)
    return v._make_ip_dict(addr)
Beispiel #11
0
def get_ip_addresses(context, **filters):
    LOG.info("get_ip_addresses for tenant %s" % context.tenant_id)
    if not filters:
        filters = {}
    if 'type' in filters:
        filters['address_type'] = filters['type']
    filters["_deallocated"] = False
    addrs = db_api.ip_address_find(context, scope=db_api.ALL, **filters)
    return [v._make_ip_dict(ip) for ip in addrs]
Beispiel #12
0
def create_ip_address(context, ip_address):
    LOG.info("create_ip_address for tenant %s" % context.tenant_id)

    port = None
    ip_dict = ip_address["ip_address"]
    port_ids = ip_dict.get('port_ids')
    network_id = ip_dict.get('network_id')
    device_ids = ip_dict.get('device_ids')
    ip_version = ip_dict.get('version')
    ip_address = ip_dict.get('ip_address')

    ports = []
    if device_ids and not network_id:
        raise exceptions.BadRequest(
            resource="ip_addresses",
            msg="network_id is required if device_ids are supplied.")
    with context.session.begin():
        if network_id and device_ids:
            for device_id in device_ids:
                port = db_api.port_find(
                    context, network_id=network_id, device_id=device_id,
                    tenant_id=context.tenant_id, scope=db_api.ONE)
                ports.append(port)
        elif port_ids:
            for port_id in port_ids:
                port = db_api.port_find(context, id=port_id,
                                        tenant_id=context.tenant_id,
                                        scope=db_api.ONE)
                ports.append(port)

        if not ports:
            raise exceptions.PortNotFound(port_id=port_ids,
                                          net_id=network_id)

        address = ipam_driver.allocate_ip_address(
            context,
            port['network_id'],
            port['id'],
            CONF.QUARK.ipam_reuse_after,
            ip_version,
            ip_addresses=[ip_address])

        for port in ports:
            port["ip_addresses"].append(address)

    return v._make_ip_dict(address)
Beispiel #13
0
def update_ip_address(context, id, ip_address):
    """Due to NCP-1592 ensure that address_type cannot change after update."""
    LOG.info("update_ip_address %s for tenant %s" % (id, context.tenant_id))
    ports = []
    if 'ip_address' not in ip_address:
        raise n_exc.BadRequest(resource="ip_addresses",
                               msg="Invalid request body.")
    with context.session.begin():
        address = db_api.ip_address_find(context, id=id, scope=db_api.ONE)
        if not address:
            raise q_exc.IpAddressNotFound(addr_id=id)
        iptype = address.address_type
        if iptype == ip_types.FIXED and not CONF.QUARK.ipaddr_allow_fixed_ip:
            raise n_exc.BadRequest(
                resource="ip_addresses",
                msg="Fixed ips cannot be updated using this interface.")

        reset = ip_address['ip_address'].get('reset_allocation_time', False)
        if reset and address['deallocated'] == 1:
            if context.is_admin:
                LOG.info("IP's deallocated time being manually reset")
                address['deallocated_at'] = _get_deallocated_override()
            else:
                msg = "Modification of reset_allocation_time requires admin"
                raise webob.exc.HTTPForbidden(detail=msg)

        port_ids = ip_address['ip_address'].get('port_ids', None)

        if port_ids is not None and not port_ids:
            raise n_exc.BadRequest(
                resource="ip_addresses",
                msg="Cannot be updated with empty port_id list")

        if iptype == ip_types.SHARED:
            has_owner = address.has_any_shared_owner()

        if port_ids:
            if iptype == ip_types.FIXED and len(port_ids) > 1:
                raise n_exc.BadRequest(
                    resource="ip_addresses",
                    msg="Fixed ips cannot be updated with more than one port.")

            _raise_if_shared_and_enabled(ip_address, address)
            ports = db_api.port_find(context,
                                     tenant_id=context.tenant_id,
                                     id=port_ids,
                                     scope=db_api.ALL)
            # NOTE(name): could be considered inefficient because we're
            # converting to a list to check length. Maybe revisit
            if len(ports) != len(port_ids):
                raise n_exc.PortNotFound(port_id=port_ids)

            validate_and_fetch_segment(ports, address["network_id"])
            validate_port_ip_quotas(context, address.network_id, ports)

            if iptype == ip_types.SHARED and has_owner:
                for assoc in address.associations:
                    pid = assoc.port_id
                    if pid not in port_ids and 'none' != assoc.service:
                        raise q_exc.PortRequiresDisassociation()

            LOG.info("Updating IP address, %s, to only be used by the"
                     "following ports:  %s" %
                     (address.address_readable, [p.id for p in ports]))
            new_address = db_api.update_port_associations_for_ip(
                context, ports, address)
        elif iptype == ip_types.SHARED and has_owner:
            raise q_exc.PortRequiresDisassociation()
        else:
            ipam_driver.deallocate_ip_address(context, address)
            return v._make_ip_dict(address)
    return v._make_ip_dict(new_address)
Beispiel #14
0
def create_ip_address(context, body):
    LOG.info("create_ip_address for tenant %s" % context.tenant_id)
    iptype = (ip_types.SHARED if _shared_ip_request(body) else ip_types.FIXED)
    if 'ip_address' not in body:
        raise n_exc.BadRequest(resource="ip_addresses",
                               msg="Invalid request body.")
    if iptype == ip_types.FIXED and not CONF.QUARK.ipaddr_allow_fixed_ip:
        raise n_exc.BadRequest(resource="ip_addresses",
                               msg="Only shared IPs may be made with "
                               "this resource.")
    ip_dict = body.get("ip_address")
    port_ids = ip_dict.get('port_ids', [])
    network_id = ip_dict.get('network_id')
    device_ids = ip_dict.get('device_ids')
    ip_version = ip_dict.get('version')
    ip_address = ip_dict.get('ip_address')
    # If no version is passed, you would get what the network provides,
    # which could be both v4 and v6 addresses. Rather than allow for such
    # an ambiguous outcome, we'll raise instead
    if not ip_version:
        raise n_exc.BadRequest(resource="ip_addresses",
                               msg="version is required.")
    if network_id is None:
        raise n_exc.BadRequest(resource="ip_addresses",
                               msg="network_id is required.")
    if network_id == "":
        raise n_exc.NetworkNotFound(net_id=network_id)
    net = db_api.network_find(context,
                              None,
                              None,
                              None,
                              False,
                              id=network_id,
                              scope=db_api.ONE)
    if not net:
        raise n_exc.NetworkNotFound(net_id=network_id)
    if not port_ids and not device_ids:
        raise n_exc.BadRequest(resource="ip_addresses",
                               msg="port_ids or device_ids required.")

    new_addresses = []
    ports = []
    by_device = False
    with context.session.begin():
        if network_id and device_ids:
            by_device = True
            for device_id in device_ids:
                port = db_api.port_find(context,
                                        network_id=network_id,
                                        device_id=device_id,
                                        tenant_id=context.tenant_id,
                                        scope=db_api.ONE)
                if port is not None:
                    ports.append(port)
        elif port_ids:
            for port_id in port_ids:

                port = db_api.port_find(context,
                                        id=port_id,
                                        tenant_id=context.tenant_id,
                                        scope=db_api.ONE)
                if port is not None:
                    ports.append(port)

        if not ports:
            raise n_exc.PortNotFoundOnNetwork(port_id=port_ids,
                                              net_id=network_id)

    if ((by_device and len(device_ids) != len(ports))
            or (not by_device and len(port_ids) != len(ports))):
        raise q_exc.NotAllPortOrDeviceFound()

    segment_id = validate_and_fetch_segment(ports, network_id)
    if iptype == ip_types.SHARED:
        old_addresses = db_api.ip_address_find(context,
                                               network_id=network_id,
                                               address_type=ip_types.SHARED,
                                               scope=db_api.ALL)
        validate_shared_ips_quotas(context, network_id, old_addresses)
    validate_port_ip_quotas(context, network_id, ports)

    # Shared Ips are only new IPs. Two use cases: if we got device_id
    # or if we got port_ids. We should check the case where we got port_ids
    # and device_ids. The device_id must have a port on the network,
    # and any port_ids must also be on that network already. If we have
    # more than one port by this step, it's considered a shared IP,
    # and therefore will be marked as unconfigured (enabled=False)
    # for all ports.
    ipam_driver.allocate_ip_address(
        context,
        new_addresses,
        network_id,
        None,
        CONF.QUARK.ipam_reuse_after,
        version=ip_version,
        ip_addresses=[ip_address] if ip_address else [],
        segment_id=segment_id,
        address_type=iptype)
    with context.session.begin():
        address = new_addresses[0]
        new_address = db_api.port_associate_ip(context, ports, address)
    return v._make_ip_dict(new_address)
Beispiel #15
0
def update_ip_address(context, id, ip_address):
    """Due to NCP-1592 ensure that address_type cannot change after update."""
    LOG.info("update_ip_address %s for tenant %s" % (id, context.tenant_id))
    ports = []
    if 'ip_address' not in ip_address:
        raise n_exc.BadRequest(resource="ip_addresses",
                               msg="Invalid request body.")
    with context.session.begin():
        address = db_api.ip_address_find(context, id=id, scope=db_api.ONE)
        if not address:
            raise q_exc.IpAddressNotFound(addr_id=id)
        iptype = address.address_type
        if iptype == ip_types.FIXED and not CONF.QUARK.ipaddr_allow_fixed_ip:
            raise n_exc.BadRequest(
                resource="ip_addresses",
                msg="Fixed ips cannot be updated using this interface.")

        reset = ip_address['ip_address'].get('reset_allocation_time', False)
        if reset and address['deallocated'] == 1:
            if context.is_admin:
                LOG.info("IP's deallocated time being manually reset")
                address['deallocated_at'] = _get_deallocated_override()
            else:
                msg = "Modification of reset_allocation_time requires admin"
                raise webob.exc.HTTPForbidden(detail=msg)

        port_ids = ip_address['ip_address'].get('port_ids', None)

        if port_ids is not None and not port_ids:
            raise n_exc.BadRequest(
                resource="ip_addresses",
                msg="Cannot be updated with empty port_id list")

        if iptype == ip_types.SHARED:
            has_owner = address.has_any_shared_owner()

        if port_ids:
            if iptype == ip_types.FIXED and len(port_ids) > 1:
                raise n_exc.BadRequest(
                    resource="ip_addresses",
                    msg="Fixed ips cannot be updated with more than one port.")

            _raise_if_shared_and_enabled(ip_address, address)
            ports = db_api.port_find(context, tenant_id=context.tenant_id,
                                     id=port_ids, scope=db_api.ALL)
            # NOTE(name): could be considered inefficient because we're
            # converting to a list to check length. Maybe revisit
            if len(ports) != len(port_ids):
                raise n_exc.PortNotFound(port_id=port_ids)

            validate_and_fetch_segment(ports, address["network_id"])
            validate_port_ip_quotas(context, address.network_id, ports)

            if iptype == ip_types.SHARED and has_owner:
                for assoc in address.associations:
                    pid = assoc.port_id
                    if pid not in port_ids and 'none' != assoc.service:
                        raise q_exc.PortRequiresDisassociation()

            LOG.info("Updating IP address, %s, to only be used by the"
                     "following ports:  %s" % (address.address_readable,
                                               [p.id for p in ports]))
            new_address = db_api.update_port_associations_for_ip(context,
                                                                 ports,
                                                                 address)
        elif iptype == ip_types.SHARED and has_owner:
            raise q_exc.PortRequiresDisassociation()
        else:
            ipam_driver.deallocate_ip_address(context, address)
            return v._make_ip_dict(address)
    return v._make_ip_dict(new_address)
Beispiel #16
0
def create_ip_address(context, body):
    LOG.info("create_ip_address for tenant %s" % context.tenant_id)
    iptype = (ip_types.SHARED if _shared_ip_request(body)
              else ip_types.FIXED)
    if 'ip_address' not in body:
        raise n_exc.BadRequest(resource="ip_addresses",
                               msg="Invalid request body.")
    if iptype == ip_types.FIXED and not CONF.QUARK.ipaddr_allow_fixed_ip:
        raise n_exc.BadRequest(resource="ip_addresses",
                               msg="Only shared IPs may be made with "
                                   "this resource.")
    ip_dict = body.get("ip_address")
    port_ids = ip_dict.get('port_ids', [])
    network_id = ip_dict.get('network_id')
    tenant_id = ip_dict.get('tenant_id')
    device_ids = ip_dict.get('device_ids')
    ip_version = ip_dict.get('version')
    ip_address = ip_dict.get('ip_address')
    # If no version is passed, you would get what the network provides,
    # which could be both v4 and v6 addresses. Rather than allow for such
    # an ambiguous outcome, we'll raise instead
    if not ip_version:
        raise n_exc.BadRequest(resource="ip_addresses",
                               msg="version is required.")
    if network_id is None:
        raise n_exc.BadRequest(resource="ip_addresses",
                               msg="network_id is required.")
    if network_id == "":
        raise n_exc.NetworkNotFound(net_id=network_id)
    net = db_api.network_find(context, None, None, None, False,
                              id=network_id, scope=db_api.ONE)
    if not net:
        raise n_exc.NetworkNotFound(net_id=network_id)
    if not port_ids and not device_ids:
        raise n_exc.BadRequest(resource="ip_addresses",
                               msg="port_ids or device_ids required.")

    new_addresses = []
    ports = []
    by_device = False
    with context.session.begin():
        if network_id and device_ids:
            by_device = True
            for device_id in device_ids:
                port = db_api.port_find(
                    context, network_id=network_id, device_id=device_id,
                    tenant_id=tenant_id, scope=db_api.ONE)
                if port is not None:
                    ports.append(port)
        elif port_ids:
            for port_id in port_ids:

                port = db_api.port_find(context, id=port_id,
                                        tenant_id=tenant_id,
                                        scope=db_api.ONE)
                if port is not None:
                    ports.append(port)

        if not ports:
            raise n_exc.PortNotFoundOnNetwork(port_id=port_ids,
                                              net_id=network_id)

    if ((by_device and len(device_ids) != len(ports)) or
            (not by_device and len(port_ids) != len(ports))):
        raise q_exc.NotAllPortOrDeviceFound()

    segment_id = validate_and_fetch_segment(ports, network_id)
    if iptype == ip_types.SHARED:
        old_addresses = db_api.ip_address_find(context,
                                               network_id=network_id,
                                               tenant_id=tenant_id,
                                               address_type=ip_types.SHARED,
                                               scope=db_api.ALL)
        validate_shared_ips_quotas(context, network_id, old_addresses)
    validate_port_ip_quotas(context, network_id, ports)

    # Shared Ips are only new IPs. Two use cases: if we got device_id
    # or if we got port_ids. We should check the case where we got port_ids
    # and device_ids. The device_id must have a port on the network,
    # and any port_ids must also be on that network already. If we have
    # more than one port by this step, it's considered a shared IP,
    # and therefore will be marked as unconfigured (enabled=False)
    # for all ports.
    ipam_driver.allocate_ip_address(context, new_addresses, network_id,
                                    None, CONF.QUARK.ipam_reuse_after,
                                    version=ip_version,
                                    ip_addresses=[ip_address]
                                    if ip_address else [],
                                    segment_id=segment_id,
                                    address_type=iptype)
    with context.session.begin():
        address = new_addresses[0]
        new_address = db_api.port_associate_ip(context, ports, address)
    return v._make_ip_dict(new_address)
Beispiel #17
0
def create_ip_address(context, body):
    LOG.info("create_ip_address for tenant %s" % context.tenant_id)
    address_type = (ip_types.SHARED if _shared_ip_request(body)
                    else ip_types.FIXED)
    ip_dict = body.get("ip_address")
    port_ids = ip_dict.get('port_ids', [])
    network_id = ip_dict.get('network_id')
    device_ids = ip_dict.get('device_ids')
    ip_version = ip_dict.get('version')
    ip_address = ip_dict.get('ip_address')
    # If no version is passed, you would get what the network provides,
    # which could be both v4 and v6 addresses. Rather than allow for such
    # an ambiguous outcome, we'll raise instead
    if not ip_version:
        raise exceptions.BadRequest(resource="ip_addresses",
                                    msg="version is required.")
    if not network_id:
        raise exceptions.BadRequest(resource="ip_addresses",
                                    msg="network_id is required.")

    new_addresses = []
    ports = []
    with context.session.begin():
        if network_id and device_ids:
            for device_id in device_ids:
                port = db_api.port_find(
                    context, network_id=network_id, device_id=device_id,
                    tenant_id=context.tenant_id, scope=db_api.ONE)
                ports.append(port)
        elif port_ids:
            for port_id in port_ids:

                port = db_api.port_find(context, id=port_id,
                                        tenant_id=context.tenant_id,
                                        scope=db_api.ONE)
                ports.append(port)

        if not ports:
            raise exceptions.PortNotFound(port_id=port_ids,
                                          net_id=network_id)

    validate_ports_on_network_and_same_segment(ports, network_id)
    validate_port_ip_quotas(context, ports)

    # Shared Ips are only new IPs. Two use cases: if we got device_id
    # or if we got port_ids. We should check the case where we got port_ids
    # and device_ids. The device_id must have a port on the network,
    # and any port_ids must also be on that network already. If we have
    # more than one port by this step, it's considered a shared IP,
    # and therefore will be marked as unconfigured (enabled=False)
    # for all ports.
    ipam_driver.allocate_ip_address(context, new_addresses, network_id,
                                    None, CONF.QUARK.ipam_reuse_after,
                                    version=ip_version,
                                    ip_addresses=[ip_address]
                                    if ip_address else [],
                                    address_type=address_type)
    with context.session.begin():
        new_address = db_api.port_associate_ip(context, ports,
                                               new_addresses[0])
    return v._make_ip_dict(new_address)