示例#1
0
def _create_flip(context, flip, port_fixed_ips):
    """Associates the flip with ports and creates it with the flip driver

    :param context: neutron api request context.
    :param flip: quark.db.models.IPAddress object representing a floating IP
    :param port_fixed_ips: dictionary of the structure:
    {"<id of port>": {"port": <quark.db.models.Port>,
     "fixed_ip": "<fixed ip address>"}}
    :return: None
    """
    if port_fixed_ips:
        context.session.begin()
        try:
            ports = [val['port'] for val in port_fixed_ips.values()]
            flip = db_api.port_associate_ip(context, ports, flip,
                                            port_fixed_ips.keys())

            for port_id in port_fixed_ips:
                fixed_ip = port_fixed_ips[port_id]['fixed_ip']
                flip = db_api.floating_ip_associate_fixed_ip(context, flip,
                                                             fixed_ip)

            flip_driver = registry.DRIVER_REGISTRY.get_driver()

            flip_driver.register_floating_ip(flip, port_fixed_ips)
            context.session.commit()
        except Exception:
            context.session.rollback()
            raise

    # alexm: Notify from this method for consistency with _delete_flip
    billing.notify(context, billing.IP_ASSOC, flip)
示例#2
0
def _create_flip(context, flip, port_fixed_ips):
    """Associates the flip with ports and creates it with the flip driver

    :param context: neutron api request context.
    :param flip: quark.db.models.IPAddress object representing a floating IP
    :param port_fixed_ips: dictionary of the structure:
    {"<id of port>": {"port": <quark.db.models.Port>,
     "fixed_ip": "<fixed ip address>"}}
    :return: None
    """
    if port_fixed_ips:
        context.session.begin()
        try:
            ports = [val['port'] for val in port_fixed_ips.values()]
            flip = db_api.port_associate_ip(context, ports, flip,
                                            port_fixed_ips.keys())

            for port_id in port_fixed_ips:
                fixed_ip = port_fixed_ips[port_id]['fixed_ip']
                flip = db_api.floating_ip_associate_fixed_ip(
                    context, flip, fixed_ip)

            flip_driver = registry.DRIVER_REGISTRY.get_driver()

            flip_driver.register_floating_ip(flip, port_fixed_ips)
            context.session.commit()
        except Exception:
            context.session.rollback()
            raise

    # alexm: Notify from this method for consistency with _delete_flip
    billing.notify(context, billing.IP_ASSOC, flip)
示例#3
0
 def test_port_associate_ip(self):
     self.context.session.add = mock.Mock()
     mock_ports = [models.Port(id=str(x), network_id="2", ip_addresses=[])
                   for x in xrange(4)]
     mock_address = models.IPAddress(id="1", address=3232235876,
                                     address_readable="192.168.1.100",
                                     subnet_id="1", network_id="2",
                                     version=4, used_by_tenant_id="1")
     r = db_api.port_associate_ip(self.context, mock_ports, mock_address)
     self.assertEqual(len(r.associations), len(mock_ports))
     for assoc, port in zip(r.associations, mock_ports):
         self.assertEqual(assoc.port, port)
         self.assertEqual(assoc.ip_address, mock_address)
         self.assertEqual(assoc.enabled, False)
示例#4
0
 def test_port_associate_ip_enable_port(self):
     self.context.session.add = mock.Mock()
     mock_port = models.Port(id="1", network_id="2", ip_addresses=[])
     mock_address = models.IPAddress(id="1", address=3232235876,
                                     address_readable="192.168.1.100",
                                     subnet_id="1", network_id="2",
                                     version=4, used_by_tenant_id="1")
     r = db_api.port_associate_ip(self.context, [mock_port], mock_address,
                                  enable_port="1")
     self.assertEqual(len(r.associations), 1)
     assoc = r.associations[0]
     self.assertEqual(assoc.port, mock_port)
     self.assertEqual(assoc.ip_address, mock_address)
     self.assertEqual(assoc.enabled, True)
     self.context.session.add.assert_called_once_with(assoc)
示例#5
0
    def test_ip_address_port_find_service(self):
        net = db_api.network_create(self.context)
        port = db_api.port_create(self.context, network_id=net["id"],
                                  backend_key="", device_id="")
        ip_address = db_api.ip_address_create(
            self.context, address=netaddr.IPAddress("0.0.0.0"))
        self.context.session.flush()

        ip_address = db_api.port_associate_ip(self.context, [port], ip_address)
        ip_address.set_service_for_port(port, "foobar")
        self.context.session.flush()

        ports = ip_api.get_ports_for_ip_address(
            self.context, ip_address["id"],
            filters={"service": "not-foobar"})
        self.assertEqual(len(ports), 0)

        ports = ip_api.get_ports_for_ip_address(
            self.context, ip_address["id"],
            filters={"service": "foobar"})
        self.assertEqual(len(ports), 1)
示例#6
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)
示例#7
0
def create_floatingip(context, content):
    """Allocate or reallocate a floating IP.

    :param context: neutron api request context.
    :param content: dictionary describing the floating ip, with keys
        as listed in the RESOURCE_ATTRIBUTE_MAP object in
        neutron/api/v2/attributes.py.  All keys will be populated.

    :returns: Dictionary containing details for the new floating IP.  If values
        are declared in the fields parameter, then only those keys will be
        present.
    """
    LOG.info('create_floatingip %s for tenant %s and body %s' %
             (id, context.tenant_id, content))
    tenant_id = content.get('tenant_id')
    network_id = content.get('floating_network_id')
    fixed_ip_address = content.get('fixed_ip_address')
    ip_address = content.get('floating_ip_address')
    port_id = content.get('port_id')

    if not tenant_id:
        tenant_id = context.tenant_id

    if not network_id:
        raise exceptions.BadRequest(resource='floating_ip',
                                    msg='floating_network_id is required.')

    network = db_api.network_find(context, id=network_id, scope=db_api.ONE)

    if not network:
        raise exceptions.NetworkNotFound(net_id=network_id)

    fixed_ip = None
    port = None
    if port_id:
        port = db_api.port_find(context, id=port_id, scope=db_api.ONE)

        if not port:
            raise exceptions.PortNotFound(port_id=port_id)

        if not port.ip_addresses or len(port.ip_addresses) == 0:
            raise qex.NoAvailableFixedIpsForPort(port_id=port_id)

        if not fixed_ip_address:
            fixed_ip = _get_next_available_fixed_ip(port)
            if not fixed_ip:
                raise qex.NoAvailableFixedIpsForPort(
                    port_id=port_id)
        else:
            fixed_ip = next((ip for ip in port.ip_addresses
                            if (ip['address_readable'] == fixed_ip_address and
                                ip.get('address_type') == ip_types.FIXED)),
                            None)

            if not fixed_ip:
                raise qex.FixedIpDoesNotExistsForPort(
                    fixed_ip=fixed_ip_address, port_id=port_id)

            if any(ip for ip in port.ip_addresses
                   if (ip.get('address_type') == ip_types.FLOATING and
                       ip.fixed_ip['address_readable'] == fixed_ip_address)):
                raise qex.PortAlreadyContainsFloatingIp(
                    port_id=port_id)

    new_addresses = []
    ip_addresses = []
    if ip_address:
        ip_addresses.append(ip_address)

    seg_name = CONF.QUARK.floating_ip_segment_name
    strategy_name = CONF.QUARK.floating_ip_ipam_strategy

    if strategy_name.upper() == 'NETWORK':
        strategy_name = network.get("ipam_strategy")

    ipam_driver = ipam.IPAM_REGISTRY.get_strategy(strategy_name)
    ipam_driver.allocate_ip_address(context, new_addresses, network_id,
                                    port_id, CONF.QUARK.ipam_reuse_after,
                                    seg_name, version=4,
                                    ip_addresses=ip_addresses,
                                    address_type=ip_types.FLOATING)

    flip = new_addresses[0]

    if fixed_ip and port:
        context.session.begin()
        try:
            flip = db_api.port_associate_ip(context, [port], flip, [port_id])
            flip = db_api.floating_ip_associate_fixed_ip(context, flip,
                                                         fixed_ip)

            flip_driver = registry.DRIVER_REGISTRY.get_driver()

            flip_driver.register_floating_ip(flip, port, fixed_ip)
            context.session.commit()
        except Exception:
            context.session.rollback()
            raise

    return v._make_floating_ip_dict(flip, port_id)
示例#8
0
def update_floatingip(context, id, content):
    """Update an existing floating IP.

    :param context: neutron api request context.
    :param id: id of the floating ip
    :param content: dictionary with keys indicating fields to update.
        valid keys are those that have a value of True for 'allow_put'
        as listed in the RESOURCE_ATTRIBUTE_MAP object in
        neutron/api/v2/attributes.py.

    :returns: Dictionary containing details for the new floating IP.  If values
        are declared in the fields parameter, then only those keys will be
        present.
    """

    LOG.info('update_floatingip %s for tenant %s and body %s' %
             (id, context.tenant_id, content))

    if 'port_id' not in content:
        raise exceptions.BadRequest(resource='floating_ip',
                                    msg='port_id is required.')

    port_id = content.get('port_id')
    port = None
    fixed_ip = None
    current_port = None

    context.session.begin()
    try:
        flip = db_api.floating_ip_find(context, id=id, scope=db_api.ONE)
        if not flip:
            raise qex.FloatingIpNotFound(id=id)

        current_ports = flip.ports

        if current_ports and len(current_ports) > 0:
            current_port = current_ports[0]

        if not port_id and not current_port:
            raise qex.FloatingIpUpdateNoPortIdSupplied()

        if port_id:
            port = db_api.port_find(context, id=port_id, scope=db_api.ONE)
            if not port:
                raise exceptions.PortNotFound(port_id=port_id)

            if any(ip for ip in port.ip_addresses
                   if (ip.get('address_type') == ip_types.FLOATING)):
                raise qex.PortAlreadyContainsFloatingIp(port_id=port_id)

            if current_port and current_port.id == port_id:
                d = dict(flip_id=id, port_id=port_id)
                raise qex.PortAlreadyAssociatedToFloatingIp(**d)

            fixed_ip = _get_next_available_fixed_ip(port)
            LOG.info('new fixed ip: %s' % fixed_ip)
            if not fixed_ip:
                raise qex.NoAvailableFixedIpsForPort(port_id=port_id)

        LOG.info('current ports: %s' % current_ports)

        if current_port:
            flip = db_api.port_disassociate_ip(context, [current_port], flip)

        if flip.fixed_ip:
            flip = db_api.floating_ip_disassociate_fixed_ip(context, flip)

        if port:
            flip = db_api.port_associate_ip(context, [port], flip, [port_id])
            flip = db_api.floating_ip_associate_fixed_ip(context, flip,
                                                         fixed_ip)

        flip_driver = registry.DRIVER_REGISTRY.get_driver()

        if port:
            if current_port:
                flip_driver.update_floating_ip(flip, port, fixed_ip)
            else:
                flip_driver.register_floating_ip(flip, port, fixed_ip)
        else:
            flip_driver.remove_floating_ip(flip)

        context.session.commit()
    except (qex.RegisterFloatingIpFailure, qex.RemoveFloatingIpFailure):
        context.session.rollback()
        raise

    # Note(alanquillin) The ports parameters on the model is not
    # properly getting cleaned up when removed.  Manually cleaning them up.
    # Need to fix the db api to correctly update the model.
    if not port:
        flip.ports = []

    return v._make_floating_ip_dict(flip, port_id)
示例#9
0
def _update_flip(context, flip_id, ip_type, requested_ports):
    """Update a flip based IPAddress

    :param context: neutron api request context.
    :param flip_id: id of the flip or scip
    :param ip_type: ip_types.FLOATING | ip_types.SCALING
    :param requested_ports: dictionary of the structure:
    {"port_id": "<id of port>", "fixed_ip": "<fixed ip address>"}
    :return: quark.models.IPAddress
    """
    # This list will hold flips that require notifications.
    # Using sets to avoid dups, if any.
    notifications = {
        billing.IP_ASSOC: set(),
        billing.IP_DISASSOC: set()
    }

    context.session.begin()
    try:
        flip = db_api.floating_ip_find(context, id=flip_id, scope=db_api.ONE)
        if not flip:
            if ip_type == ip_types.SCALING:
                raise q_exc.ScalingIpNotFound(id=flip_id)
            raise q_exc.FloatingIpNotFound(id=flip_id)
        current_ports = flip.ports

        # Determine what ports are being removed, being added, and remain
        req_port_ids = [request_port.get('port_id')
                        for request_port in requested_ports]
        curr_port_ids = [curr_port.id for curr_port in current_ports]
        added_port_ids = [port_id for port_id in req_port_ids
                          if port_id and port_id not in curr_port_ids]
        removed_port_ids = [port_id for port_id in curr_port_ids
                            if port_id not in req_port_ids]
        remaining_port_ids = set(curr_port_ids) - set(removed_port_ids)

        # Validations just for floating ip types
        if (ip_type == ip_types.FLOATING and curr_port_ids and
                curr_port_ids == req_port_ids):
            d = dict(flip_id=flip_id, port_id=curr_port_ids[0])
            raise q_exc.PortAlreadyAssociatedToFloatingIp(**d)
        if (ip_type == ip_types.FLOATING and
                not curr_port_ids and not req_port_ids):
            raise q_exc.FloatingIpUpdateNoPortIdSupplied()

        # Validate that GW IP is not in use on the NW.
        flip_subnet = v._make_subnet_dict(flip.subnet)
        for added_port_id in added_port_ids:
            port = _get_port(context, added_port_id)
            nw = port.network
            nw_ports = v._make_ports_list(nw.ports)
            fixed_ips = [ip.get('ip_address') for p in nw_ports
                         for ip in p.get('fixed_ips')]

            gw_ip = flip_subnet.get('gateway_ip')
            if gw_ip in fixed_ips:
                port_with_gateway_ip = None
                for port in nw_ports:
                    for ip in port.get('fixed_ips'):
                        if gw_ip in ip.get('ip_address'):
                            port_with_gateway_ip = port
                            break
                port_id = port_with_gateway_ip.get('id')
                network_id = port_with_gateway_ip.get('network_id')
                raise q_exc.FixedIpAllocatedToGatewayIp(port_id=port_id,
                                                        network_id=network_id)
        port_fixed_ips = {}

        # Keep the ports and fixed ips that have not changed
        for port_id in remaining_port_ids:
            port = db_api.port_find(context, id=port_id, scope=db_api.ONE)
            fixed_ip = _get_flip_fixed_ip_by_port_id(flip, port_id)
            port_fixed_ips[port_id] = {'port': port, 'fixed_ip': fixed_ip}

        # Disassociate the ports and fixed ips from the flip that were
        # associated to the flip but are not anymore
        for port_id in removed_port_ids:
            port = db_api.port_find(context, id=port_id, scope=db_api.ONE)
            flip = db_api.port_disassociate_ip(context, [port], flip)
            notifications[billing.IP_DISASSOC].add(flip)
            fixed_ip = _get_flip_fixed_ip_by_port_id(flip, port_id)
            if fixed_ip:
                flip = db_api.floating_ip_disassociate_fixed_ip(
                    context, flip, fixed_ip)

        # Validate the new ports with the flip and associate the new ports
        # and fixed ips with the flip
        for port_id in added_port_ids:
            port = db_api.port_find(context, id=port_id, scope=db_api.ONE)
            if not port:
                raise n_exc.PortNotFound(port_id=port_id)
            if any(ip for ip in port.ip_addresses
                   if (ip.get('address_type') == ip_types.FLOATING)):
                raise q_exc.PortAlreadyContainsFloatingIp(port_id=port_id)
            if any(ip for ip in port.ip_addresses
                   if (ip.get('address_type') == ip_types.SCALING)):
                raise q_exc.PortAlreadyContainsScalingIp(port_id=port_id)
            fixed_ip = _get_next_available_fixed_ip(port)
            LOG.info('new fixed ip: %s' % fixed_ip)
            if not fixed_ip:
                raise q_exc.NoAvailableFixedIpsForPort(port_id=port_id)
            port_fixed_ips[port_id] = {'port': port, 'fixed_ip': fixed_ip}
            flip = db_api.port_associate_ip(context, [port], flip, [port_id])
            notifications[billing.IP_ASSOC].add(flip)
            flip = db_api.floating_ip_associate_fixed_ip(context, flip,
                                                         fixed_ip)

        flip_driver = registry.DRIVER_REGISTRY.get_driver()
        # If there are not any remaining ports and no new ones are being added,
        # remove the floating ip from unicorn
        if not remaining_port_ids and not added_port_ids:
            flip_driver.remove_floating_ip(flip)
        # If new ports are being added but there previously was not any ports,
        # then register a new floating ip with the driver because it is
        # assumed it does not exist
        elif added_port_ids and not curr_port_ids:
            flip_driver.register_floating_ip(flip, port_fixed_ips)
        else:
            flip_driver.update_floating_ip(flip, port_fixed_ips)
        context.session.commit()
    except Exception:
        context.session.rollback()
        raise

    # Send notifications for possible associate/disassociate events
    for notif_type, flip_set in notifications.iteritems():
        for flip in flip_set:
            billing.notify(context, notif_type, flip)

    # NOTE(blogan): ORM does not seem to update the model to the real state
    # of the database, so I'm doing an explicit refresh for now.
    context.session.refresh(flip)
    return flip
示例#10
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)
示例#11
0
def _update_flip(context, flip_id, ip_type, requested_ports):
    """Update a flip based IPAddress

    :param context: neutron api request context.
    :param flip_id: id of the flip or scip
    :param ip_type: ip_types.FLOATING | ip_types.SCALING
    :param requested_ports: dictionary of the structure:
    {"port_id": "<id of port>", "fixed_ip": "<fixed ip address>"}
    :return: quark.models.IPAddress
    """
    # This list will hold flips that require notifications.
    # Using sets to avoid dups, if any.
    notifications = {billing.IP_ASSOC: set(), billing.IP_DISASSOC: set()}

    context.session.begin()
    try:
        flip = db_api.floating_ip_find(context, id=flip_id, scope=db_api.ONE)
        if not flip:
            if ip_type == ip_types.SCALING:
                raise q_exc.ScalingIpNotFound(id=flip_id)
            raise q_exc.FloatingIpNotFound(id=flip_id)
        current_ports = flip.ports

        # Determine what ports are being removed, being added, and remain
        req_port_ids = [
            request_port.get('port_id') for request_port in requested_ports
        ]
        curr_port_ids = [curr_port.id for curr_port in current_ports]
        added_port_ids = [
            port_id for port_id in req_port_ids
            if port_id and port_id not in curr_port_ids
        ]
        removed_port_ids = [
            port_id for port_id in curr_port_ids if port_id not in req_port_ids
        ]
        remaining_port_ids = set(curr_port_ids) - set(removed_port_ids)

        # Validations just for floating ip types
        if (ip_type == ip_types.FLOATING and curr_port_ids
                and curr_port_ids == req_port_ids):
            d = dict(flip_id=flip_id, port_id=curr_port_ids[0])
            raise q_exc.PortAlreadyAssociatedToFloatingIp(**d)
        if (ip_type == ip_types.FLOATING and not curr_port_ids
                and not req_port_ids):
            raise q_exc.FloatingIpUpdateNoPortIdSupplied()

        # Validate that GW IP is not in use on the NW.
        flip_subnet = v._make_subnet_dict(flip.subnet)
        for added_port_id in added_port_ids:
            port = _get_port(context, added_port_id)
            nw = port.network
            nw_ports = v._make_ports_list(nw.ports)
            fixed_ips = [
                ip.get('ip_address') for p in nw_ports
                for ip in p.get('fixed_ips')
            ]

            gw_ip = flip_subnet.get('gateway_ip')
            if gw_ip in fixed_ips:
                port_with_gateway_ip = None
                for port in nw_ports:
                    for ip in port.get('fixed_ips'):
                        if gw_ip in ip.get('ip_address'):
                            port_with_gateway_ip = port
                            break
                port_id = port_with_gateway_ip.get('id')
                network_id = port_with_gateway_ip.get('network_id')
                raise q_exc.FixedIpAllocatedToGatewayIp(port_id=port_id,
                                                        network_id=network_id)
        port_fixed_ips = {}

        # Keep the ports and fixed ips that have not changed
        for port_id in remaining_port_ids:
            port = db_api.port_find(context, id=port_id, scope=db_api.ONE)
            fixed_ip = _get_flip_fixed_ip_by_port_id(flip, port_id)
            port_fixed_ips[port_id] = {'port': port, 'fixed_ip': fixed_ip}

        # Disassociate the ports and fixed ips from the flip that were
        # associated to the flip but are not anymore
        for port_id in removed_port_ids:
            port = db_api.port_find(context, id=port_id, scope=db_api.ONE)
            flip = db_api.port_disassociate_ip(context, [port], flip)
            notifications[billing.IP_DISASSOC].add(flip)
            fixed_ip = _get_flip_fixed_ip_by_port_id(flip, port_id)
            if fixed_ip:
                flip = db_api.floating_ip_disassociate_fixed_ip(
                    context, flip, fixed_ip)

        # Validate the new ports with the flip and associate the new ports
        # and fixed ips with the flip
        for port_id in added_port_ids:
            port = db_api.port_find(context, id=port_id, scope=db_api.ONE)
            if not port:
                raise n_exc.PortNotFound(port_id=port_id)
            if any(ip for ip in port.ip_addresses
                   if (ip.get('address_type') == ip_types.FLOATING)):
                raise q_exc.PortAlreadyContainsFloatingIp(port_id=port_id)
            if any(ip for ip in port.ip_addresses
                   if (ip.get('address_type') == ip_types.SCALING)):
                raise q_exc.PortAlreadyContainsScalingIp(port_id=port_id)
            fixed_ip = _get_next_available_fixed_ip(port)
            LOG.info('new fixed ip: %s' % fixed_ip)
            if not fixed_ip:
                raise q_exc.NoAvailableFixedIpsForPort(port_id=port_id)
            port_fixed_ips[port_id] = {'port': port, 'fixed_ip': fixed_ip}
            flip = db_api.port_associate_ip(context, [port], flip, [port_id])
            notifications[billing.IP_ASSOC].add(flip)
            flip = db_api.floating_ip_associate_fixed_ip(
                context, flip, fixed_ip)

        flip_driver = registry.DRIVER_REGISTRY.get_driver()
        # If there are not any remaining ports and no new ones are being added,
        # remove the floating ip from unicorn
        if not remaining_port_ids and not added_port_ids:
            flip_driver.remove_floating_ip(flip)
        # If new ports are being added but there previously was not any ports,
        # then register a new floating ip with the driver because it is
        # assumed it does not exist
        elif added_port_ids and not curr_port_ids:
            flip_driver.register_floating_ip(flip, port_fixed_ips)
        else:
            flip_driver.update_floating_ip(flip, port_fixed_ips)
        context.session.commit()
    except Exception:
        context.session.rollback()
        raise

    # Send notifications for possible associate/disassociate events
    for notif_type, flip_set in notifications.iteritems():
        for flip in flip_set:
            billing.notify(context, notif_type, flip)

    # NOTE(blogan): ORM does not seem to update the model to the real state
    # of the database, so I'm doing an explicit refresh for now.
    context.session.refresh(flip)
    return flip
示例#12
0
def create_floatingip(context, content):
    """Allocate or reallocate a floating IP.

    :param context: neutron api request context.
    :param content: dictionary describing the floating ip, with keys
        as listed in the RESOURCE_ATTRIBUTE_MAP object in
        neutron/api/v2/attributes.py.  All keys will be populated.

    :returns: Dictionary containing details for the new floating IP.  If values
        are declared in the fields parameter, then only those keys will be
        present.
    """
    LOG.info('create_floatingip %s for tenant %s and body %s' %
             (id, context.tenant_id, content))
    tenant_id = content.get('tenant_id')
    network_id = content.get('floating_network_id')
    fixed_ip_address = content.get('fixed_ip_address')
    ip_address = content.get('floating_ip_address')
    port_id = content.get('port_id')

    if not tenant_id:
        tenant_id = context.tenant_id

    if not network_id:
        raise exceptions.BadRequest(resource='floating_ip',
                                    msg='floating_network_id is required.')

    network = db_api.network_find(context, id=network_id, scope=db_api.ONE)

    if not network:
        raise exceptions.NetworkNotFound(net_id=network_id)

    fixed_ip = None
    port = None
    if port_id:
        port = db_api.port_find(context, id=port_id, scope=db_api.ONE)

        if not port:
            raise exceptions.PortNotFound(port_id=port_id)

        if not port.ip_addresses or len(port.ip_addresses) == 0:
            raise qex.NoAvailableFixedIPsForPort(port_id=port_id)

        if not fixed_ip_address:
            fixed_ip = _get_next_available_fixed_ip(port)
            if not fixed_ip:
                raise qex.NoAvailableFixedIPsForPort(
                    port_id=port_id)
        else:
            fixed_ip = next((ip for ip in port.ip_addresses
                            if (ip['address_readable'] == fixed_ip_address and
                                ip.get('address_type') == ip_types.FIXED)),
                            None)

            if not fixed_ip:
                raise qex.FixedIpDoesNotExistsForPort(
                    fixed_ip=fixed_ip_address, port_id=port_id)

            if any(ip for ip in port.ip_addresses
                   if (ip.get('address_type') == ip_types.FLOATING and
                       ip.fixed_ip['address_readable'] == fixed_ip_address)):
                raise qex.PortAlreadyContainsFloatingIp(
                    port_id=port_id)

    new_addresses = []
    ip_addresses = []
    if ip_address:
        ip_addresses.append(ip_address)

    seg_name = CONF.QUARK.floating_ip_segment_name
    strategy_name = CONF.QUARK.floating_ip_ipam_strategy

    if strategy_name.upper() == 'NETWORK':
        strategy_name = network.get("ipam_strategy")

    ipam_driver = ipam.IPAM_REGISTRY.get_strategy(strategy_name)
    ipam_driver.allocate_ip_address(context, new_addresses, network_id,
                                    port_id, CONF.QUARK.ipam_reuse_after,
                                    seg_name, version=4,
                                    ip_addresses=ip_addresses,
                                    address_type=ip_types.FLOATING)

    flip = new_addresses[0]

    if fixed_ip and port:
        with context.session.begin():
            flip = db_api.port_associate_ip(context, [port], flip, [port_id])
            flip = db_api.floating_ip_associate_fixed_ip(context, flip,
                                                         fixed_ip)

            flip_driver = registry.DRIVER_REGISTRY.get_driver()

            flip_driver.register_floating_ip(flip, port, fixed_ip)

    return v._make_floating_ip_dict(flip, port_id)
示例#13
0
def update_floatingip(context, id, content):
    """Update an existing floating IP.

    :param context: neutron api request context.
    :param id: id of the floating ip
    :param content: dictionary with keys indicating fields to update.
        valid keys are those that have a value of True for 'allow_put'
        as listed in the RESOURCE_ATTRIBUTE_MAP object in
        neutron/api/v2/attributes.py.

    :returns: Dictionary containing details for the new floating IP.  If values
        are declared in the fields parameter, then only those keys will be
        present.
    """

    LOG.info('update_floatingip %s for tenant %s and body %s' %
             (id, context.tenant_id, content))

    if 'port_id' not in content:
        raise exceptions.BadRequest(resource='floating_ip',
                                    msg='port_id is required.')

    port_id = content.get('port_id')
    port = None
    fixed_ip = None
    current_port = None

    with context.session.begin():
        flip = db_api.floating_ip_find(context, id=id, scope=db_api.ONE)
        if not flip:
            raise qex.FloatingIpNotFound(id=id)

        current_ports = flip.ports

        if current_ports and len(current_ports) > 0:
            current_port = current_ports[0]

        if not port_id and not current_port:
            raise qex.FloatingIPUpdateNoPortIdSupplied()

        if port_id:
            port = db_api.port_find(context, id=port_id, scope=db_api.ONE)
            if not port:
                raise exceptions.PortNotFound(port_id=port_id)

            if current_port and current_port.id == port_id:
                d = dict(flip_id=id, port_id=port_id)
                raise qex.PortAlreadyAssociatedToFloatingIP(**d)

            fixed_ip = _get_next_available_fixed_ip(port)
            LOG.info('new fixed ip: %s' % fixed_ip)
            if not fixed_ip:
                raise qex.NoAvailableFixedIPsForPort(port_id=port_id)

        LOG.info('current ports: %s' % current_ports)

        if current_port:
            flip = db_api.port_disassociate_ip(context, [current_port], flip)

        if flip.fixed_ip:
            flip = db_api.floating_ip_disassociate_fixed_ip(context, flip)

        if port:
            flip = db_api.port_associate_ip(context, [port], flip, [port_id])
            flip = db_api.floating_ip_associate_fixed_ip(context, flip,
                                                         fixed_ip)
        flip_driver = registry.DRIVER_REGISTRY.get_driver()

        if port:
            if current_port:
                flip_driver.update_floating_ip(flip, port, fixed_ip)
            else:
                flip_driver.register_floating_ip(flip, port, fixed_ip)
        else:
            flip_driver.remove_floating_ip(flip)

    # Note(alanquillin) The ports parameters on the model is not
    # properly getting cleaned up when removed.  Manually cleaning them up.
    # Need to fix the db api to correctly update the model.
    if not port:
        flip.ports = []

    return v._make_floating_ip_dict(flip)
示例#14
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)