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)
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)
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)
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)
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)
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)
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)
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)
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
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)
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
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)
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)
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)