def test_find_reallocatable_ips_does_not_raise(self): """Regression testing A patch recently introduced a bug wherein addressses could not be returned to the ip_address_find call in attempt_to_reallocate_ip. Adding this test to prevent a future regression. """ network = dict(name="public", tenant_id="fake") ipnet = netaddr.IPNetwork("0.0.0.0/24") next_ip = ipnet.ipv6().first + 2 subnet = dict(id=1, cidr="0.0.0.0/24", next_auto_assign_ip=next_ip, ip_policy=None, tenant_id="fake") with self._stubs(network, subnet) as net: ip_kwargs = { "network_id": net["id"], "reuse_after": 14400, "deallocated": True, "scope": db_api.ONE, "lock_mode": True, "version": 4, "order_by": "address"} try: db_api.ip_address_find(self.context, **ip_kwargs) except Exception: self.fail("This should not have raised")
def test_ip_address_find_ip_address_list(self): ip_address = netaddr.IPAddress("192.168.10.1") try: db_api.ip_address_find(self.context, ip_address=[ip_address], scope=db_api.ONE) except Exception as e: self.fail("Expected no exceptions: %s" % e)
def test_find_reallocatable_ips_does_not_raise(self): """Regression testing A patch recently introduced a bug wherein addressses could not be returned to the ip_address_find call in attempt_to_reallocate_ip. Adding this test to prevent a future regression. """ network = dict(name="public", tenant_id="fake") ipnet = netaddr.IPNetwork("0.0.0.0/24") next_ip = ipnet.ipv6().first + 2 subnet = dict(id=1, cidr="0.0.0.0/24", next_auto_assign_ip=next_ip, ip_policy=None, tenant_id="fake") with self._stubs(network, subnet) as net: ip_kwargs = { "network_id": net["id"], "reuse_after": 14400, "deallocated": True, "scope": db_api.ONE, "lock_mode": True, "version": 4, "order_by": "address" } try: db_api.ip_address_find(self.context, **ip_kwargs) except Exception: self.fail("This should not have raised")
def allocate_ip_address(self, context, net_id, port_id, reuse_after, version=None, ip_address=None): elevated = context.elevated() if ip_address: ip_address = netaddr.IPAddress(ip_address) address = db_api.ip_address_find( elevated, network_id=net_id, reuse_after=reuse_after, deallocated=True, scope=db_api.ONE, ip_address=ip_address) if address: return db_api.ip_address_update( elevated, address, deallocated=False, deallocated_at=None) subnet = self._choose_available_subnet( elevated, net_id, ip_address=ip_address, version=version) # Creating this IP for the first time next_ip = None if ip_address: next_ip = ip_address else: address = True while address: next_ip_int = int(subnet["next_auto_assign_ip"]) next_ip = netaddr.IPAddress(next_ip_int) if subnet["ip_version"] == 4: next_ip = next_ip.ipv4() subnet["next_auto_assign_ip"] = next_ip_int + 1 address = db_api.ip_address_find( elevated, network_id=net_id, ip_address=next_ip, tenant_id=elevated.tenant_id, scope=db_api.ONE) # TODO(mdietz): this is a hack until we have IP policies ip_int = int(next_ip) first_ip = netaddr.IPAddress(int(subnet["first_ip"])) last_ip = netaddr.IPAddress(int(subnet["last_ip"])) if subnet["ip_version"] == 4: first_ip = first_ip.ipv4() last_ip = last_ip.ipv4() first_ip = int(first_ip) last_ip = int(last_ip) diff = ip_int - first_ip if diff < 2: next_ip = netaddr.IPAddress(ip_int + (2 - diff)) if ip_int == last_ip: raise exceptions.IpAddressGenerationFailure(net_id=net_id) if next_ip not in netaddr.IPNetwork(subnet["cidr"]): raise exceptions.IpAddressGenerationFailure(net_id=net_id) address = db_api.ip_address_create( elevated, address=next_ip, subnet_id=subnet["id"], version=subnet["ip_version"], network_id=net_id) return address
def test_ip_address_find_device_id(self): query_mock = mock.Mock() filter_mock = mock.Mock() self.context.session.query = query_mock query_mock.return_value = filter_mock db_api.ip_address_find(self.context, device_id="foo") self.assertEqual(filter_mock.filter.call_count, 1)
def post_update_port(context, id, port): LOG.info("post_update_port %s for tenant %s" % (id, context.tenant_id)) if not port.get("port"): raise exceptions.BadRequest(resource="ports", msg="Port body required") port_db = db_api.port_find(context, id=id, scope=db_api.ONE) if not port_db: raise exceptions.PortNotFound(port_id=id, net_id="") port = port["port"] if "fixed_ips" in port and port["fixed_ips"]: for ip in port["fixed_ips"]: address = None ipam_driver = ipam.IPAM_REGISTRY.get_strategy( port_db["network"]["ipam_strategy"]) if ip: if "ip_id" in ip: ip_id = ip["ip_id"] address = db_api.ip_address_find( context, id=ip_id, tenant_id=context.tenant_id, scope=db_api.ONE) elif "ip_address" in ip: ip_address = ip["ip_address"] net_address = netaddr.IPAddress(ip_address) address = db_api.ip_address_find( context, ip_address=net_address, network_id=port_db["network_id"], tenant_id=context.tenant_id, scope=db_api.ONE) if not address: address = ipam_driver.allocate_ip_address( context, port_db["network_id"], id, CONF.QUARK.ipam_reuse_after, ip_address=ip_address) else: address = ipam_driver.allocate_ip_address( context, port_db["network_id"], id, CONF.QUARK.ipam_reuse_after) address["deallocated"] = 0 already_contained = False for port_address in port_db["ip_addresses"]: if address["id"] == port_address["id"]: already_contained = True break if not already_contained: port_db["ip_addresses"].append(address) return v._make_port_dict(port_db)
def post_update_port(self, context, id, port): LOG.info("post_update_port %s for tenant %s" % (id, context.tenant_id)) port_db = db_api.port_find(context, id=id, scope=db_api.ONE) if not port_db: raise exceptions.PortNotFound(port_id=id, net_id="") if "port" not in port or not port["port"]: raise exceptions.BadRequest() port = port["port"] if "fixed_ips" in port and port["fixed_ips"]: for ip in port["fixed_ips"]: address = None if ip: if "ip_id" in ip: ip_id = ip["ip_id"] address = db_api.ip_address_find( context, id=ip_id, tenant_id=context.tenant_id, scope=db_api.ONE) elif "ip_address" in ip: ip_address = ip["ip_address"] net_address = netaddr.IPAddress(ip_address) address = db_api.ip_address_find( context, ip_address=net_address, network_id=port_db["network_id"], tenant_id=context.tenant_id, scope=db_api.ONE) if not address: address = self.ipam_driver.allocate_ip_address( context, port_db["network_id"], id, self.ipam_reuse_after, ip_address=ip_address) else: address = self.ipam_driver.allocate_ip_address( context, port_db["network_id"], id, self.ipam_reuse_after) address["deallocated"] = 0 already_contained = False for port_address in port_db["ip_addresses"]: if address["id"] == port_address["id"]: already_contained = True break if not already_contained: port_db["ip_addresses"].append(address) return self._make_port_dict(port_db)
def test_ip_address_find_port_id_as_admin(self): self.context.session.query = mock.MagicMock() final_query_mock = self.context.session.query.return_value db_api.ip_address_find(self.context.elevated(), port_id="foo") # NOTE(thomasem): Creates sqlalchemy.sql.elements.BinaryExpression # when using SQLAlchemy models in expressions. expected_filter = models.IPAddress.ports.any(models.Port.id == "foo") self.assertEqual(len(final_query_mock.filter.call_args[0]), 1) self.assertEqual(str(expected_filter), str( final_query_mock.filter.call_args[0][0]))
def post_update_port(context, id, port): LOG.info("post_update_port %s for tenant %s" % (id, context.tenant_id)) if not port.get("port"): raise exceptions.BadRequest(resource="ports", msg="Port body required") with context.session.begin(): port_db = db_api.port_find(context, id=id, scope=db_api.ONE) if not port_db: raise exceptions.PortNotFound(port_id=id, net_id="") port = port["port"] if "fixed_ips" in port and port["fixed_ips"]: for ip in port["fixed_ips"]: address = None ipam_driver = ipam.IPAM_REGISTRY.get_strategy(port_db["network"]["ipam_strategy"]) if ip: if "ip_id" in ip: ip_id = ip["ip_id"] address = db_api.ip_address_find( context, id=ip_id, tenant_id=context.tenant_id, scope=db_api.ONE ) elif "ip_address" in ip: ip_address = ip["ip_address"] net_address = netaddr.IPAddress(ip_address) address = db_api.ip_address_find( context, ip_address=net_address, network_id=port_db["network_id"], tenant_id=context.tenant_id, scope=db_api.ONE, ) if not address: address = ipam_driver.allocate_ip_address( context, port_db["network_id"], id, CONF.QUARK.ipam_reuse_after, ip_address=ip_address ) else: address = ipam_driver.allocate_ip_address( context, port_db["network_id"], id, CONF.QUARK.ipam_reuse_after ) address["deallocated"] = 0 already_contained = False for port_address in port_db["ip_addresses"]: if address["id"] == port_address["id"]: already_contained = True break if not already_contained: port_db["ip_addresses"].append(address) return v._make_port_dict(port_db)
def test_ip_address_find_address_type_as_admin(self): self.context.session.query = mock.MagicMock() filter_mock = self.context.session.query.return_value db_api.ip_address_find(self.context.elevated(), address_type="foo") # NOTE(thomasem): Creates sqlalchemy.sql.elements.BinaryExpression # when using SQLAlchemy models in expressions. expected_filter = models.IPAddress.address_type == "foo" self.assertEqual(len(filter_mock.filter.call_args[0]), 1) # NOTE(thomasem): Unfortunately BinaryExpression.compare isn't # showing to be a reliable comparison, so using the string # representation which dumps the associated SQL for the filter. self.assertEqual(str(expected_filter), str( filter_mock.filter.call_args[0][0]))
def test_reserve_ip_non_admin(self): with self._stubs() as ip: deallocated_ip = ip_addr.update_ip_address(self.context, ip["id"], self.ip_address_dealloc) ip_address = db_api.ip_address_find(self.context, id=deallocated_ip["id"], scope=db_api.ONE) self.assertEqual(ip_address["deallocated"], True) deallocated_ip = ip_addr.update_ip_address(self.context, ip["id"], self.ip_address_reserve) ip_address = db_api.ip_address_find(self.context, id=deallocated_ip["id"], scope=db_api.ONE) self.assertEqual(ip_address["deallocated"], True)
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]
def _allocate_ips_from_subnets(self, context, net_id, subnets, ip_address=None): new_addresses = [] for subnet in subnets: ip_policy_cidrs = models.IPPolicy.get_ip_policy_cidrs(subnet) # Creating this IP for the first time next_ip = None if ip_address: next_ip = ip_address address = db_api.ip_address_find( context, network_id=net_id, ip_address=next_ip, used_by_tenant_id=context.tenant_id, scope=db_api.ONE) if address: raise exceptions.IpAddressGenerationFailure( net_id=net_id) else: next_ip = self._iterate_until_available_ip( context, subnet, net_id, ip_policy_cidrs) context.session.add(subnet) address = db_api.ip_address_create( context, address=next_ip, subnet_id=subnet["id"], version=subnet["ip_version"], network_id=net_id) address["deallocated"] = 0 new_addresses.append(address) return new_addresses
def test_ip_address_find_ip_address_object_list_none(self): with self._stubs(): ip_addresses = db_api.ip_address_find( self.context, ip_address=[netaddr.IPAddress("192.168.10.2")], scope=db_api.ALL) self.assertEqual(len(ip_addresses), 0)
def test_get_deallocated_ips_non_admin_both(self): with self._stubs() as (ip1, ip2): reserved_ip = ip_addr.update_ip_address(self.context, ip2["id"], self.ip_address_dealloc) self.assertNotIn('_deallocated', reserved_ip) ip_addresses = db_api.ip_address_find(self.context, scope=db_api.ALL) self.assertEqual(len(ip_addresses), 2) for ip in ip_addresses: if ip["id"] == ip1["id"]: self.assertEqual(ip["_deallocated"], False) elif ip["id"] == ip2["id"]: self.assertEqual(ip["_deallocated"], True) deallocated_ips = ip_addr.get_ip_addresses(self.context) self.assertEqual(len(deallocated_ips), 1) self.assertEqual(ip1['id'], deallocated_ips[0]['id']) self.assertNotIn('_deallocated', deallocated_ips[0]) filters = {'deallocated': 'True'} deallocated_ips1 = ip_addr.get_ip_addresses( self.context, **filters) self.assertEqual(len(deallocated_ips1), 1) filters = {'deallocated': 'False'} deallocated_ips1 = ip_addr.get_ip_addresses( self.context, **filters) self.assertEqual(len(deallocated_ips1), 1) filters = {'deallocated': 'both'} deallocated_ips = ip_addr.get_ip_addresses(self.context, **filters) self.assertEqual(len(deallocated_ips), 1) self.assertEqual(ip1['id'], deallocated_ips[0]['id']) self.assertNotIn('_deallocated', deallocated_ips[0])
def delete_ip_address(context, id): """Delete an ip address. : param context: neutron api request context : param id: UUID representing the ip address to delete. """ LOG.info("delete_ip_address %s for tenant %s" % (id, context.tenant_id)) with context.session.begin(): ip_address = db_api.ip_address_find( context, id=id, scope=db_api.ONE) if not ip_address or ip_address.deallocated: raise q_exc.IpAddressNotFound(addr_id=id) iptype = ip_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.") if ip_address.has_any_shared_owner(): raise q_exc.PortRequiresDisassociation() db_api.update_port_associations_for_ip(context, [], ip_address) ipam_driver.deallocate_ip_address(context, ip_address)
def update_port_for_ip_address(context, ip_id, id, port): """Update values of a port. : param context: neutron api request context : param ip_id: UUID representing the ip associated with port to update : param id: UUID representing the port to update. : param port: 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. """ LOG.info("update_port %s for tenant %s" % (id, context.tenant_id)) sanitize_list = ['service'] with context.session.begin(): addr = db_api.ip_address_find(context, id=ip_id, scope=db_api.ONE) if not addr: raise q_exc.IpAddressNotFound(addr_id=ip_id) port_db = db_api.port_find(context, id=id, scope=db_api.ONE) if not port_db: raise q_exc.PortNotFound(port_id=id) port_dict = {k: port['port'][k] for k in sanitize_list} require_da = False service = port_dict.get('service') if require_da and _shared_ip_and_active(addr, except_port=id): raise q_exc.PortRequiresDisassociation() addr.set_service_for_port(port_db, service) context.session.add(addr) return v._make_port_for_ip_dict(addr, port_db)
def get_port_for_ip_address(context, ip_id, id, fields=None): """Retrieve a port. : param context: neutron api request context : param id: UUID representing the port to fetch. : param fields: a list of strings that are valid keys in a port dictionary as listed in the RESOURCE_ATTRIBUTE_MAP object in neutron/api/v2/attributes.py. Only these fields will be returned. """ LOG.info("get_port %s for tenant %s fields %s" % (id, context.tenant_id, fields)) addr = db_api.ip_address_find(context, id=ip_id, scope=db_api.ONE) if not addr: raise q_exc.IpAddressNotFound(addr_id=ip_id) filters = {'ip_address_id': [ip_id]} results = db_api.port_find(context, id=id, fields=fields, scope=db_api.ONE, **filters) if not results: raise n_exc.PortNotFound(port_id=id) return v._make_port_for_ip_dict(addr, results)
def attempt_to_reallocate_ip(self, context, net_id, port_id, reuse_after, version=None, ip_address=None): version = version or [4, 6] elevated = context.elevated() # We never want to take the chance of an infinite loop here. Instead, # we'll clean up multiple bad IPs if we find them (assuming something # is really wrong) for times in xrange(3): with context.session.begin(subtransactions=True): address = db_api.ip_address_find( elevated, network_id=net_id, reuse_after=reuse_after, deallocated=True, scope=db_api.ONE, ip_address=ip_address, lock_mode=True, version=version, order_by="address") if address: #NOTE(mdietz): We should always be in the CIDR but we've # also said that before :-/ if address.get("subnet"): cidr = netaddr.IPNetwork(address["subnet"]["cidr"]) addr = netaddr.IPAddress(int(address["address"]), version=int(cidr.version)) if addr in cidr: updated_address = db_api.ip_address_update( elevated, address, deallocated=False, deallocated_at=None, allocated_at=timeutils.utcnow()) return [updated_address] else: # Make sure we never find it again context.session.delete(address) continue break return []
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)
def test_reserve_ip_non_admin(self): with self._stubs() as ip: deallocated_ip = ip_addr.update_ip_address(self.context, ip["id"], self.ip_address_dealloc) ip_address = db_api.ip_address_find( self.context, id=deallocated_ip["id"], scope=db_api.ONE) self.assertEqual(ip_address["deallocated"], True) deallocated_ip = ip_addr.update_ip_address(self.context, ip["id"], self.ip_address_reserve) ip_address = db_api.ip_address_find( self.context, id=deallocated_ip["id"], scope=db_api.ONE) self.assertEqual(ip_address["deallocated"], True)
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)
def test_address_not_in_cidr(self): self.network_db = self.insert_network() self.subnet_v4_db = self.insert_subnet( self.network_db, "192.168.0.0/24") self.ip_address_v4 = netaddr.IPAddress("192.168.1.1") ip_address_db = self.insert_ip_address(self.ip_address_v4, self.network_db, self.subnet_v4_db) self.transaction = self.insert_transaction() ip_kwargs = { "network_id": self.network_db["id"], "reuse_after": self.REUSE_AFTER, "deallocated": True, "version": 4, } reallocated = db_api.ip_address_reallocate( self.context, {"transaction_id": self.transaction.id}, **ip_kwargs) self.assertTrue(reallocated) updated_address = db_api.ip_address_reallocate_find( self.context, self.transaction.id) self.assertIsNone(updated_address) self.context.session.flush() self.assertIsNone(db_api.ip_address_find(self.context, id=ip_address_db.id, scope=db_api.ONE))
def test_ip_address_find_ip_address_list_filter(self): with self._stubs(): ip_addresses = db_api.ip_address_find(self.context, ip_address=[self.addr], scope=db_api.ALL) self.assertEqual(len(ip_addresses), 1) self.assertEqual(ip_addresses[0]["address"], self.addr.ipv6().value)
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)
def allocate_ip_address(self, context, net_id, port_id, reuse_after, version=None, ip_address=None): elevated = context.elevated() if ip_address: ip_address = netaddr.IPAddress(ip_address) address = db_api.ip_address_find( elevated, network_id=net_id, reuse_after=reuse_after, deallocated=True, scope=db_api.ONE, ip_address=ip_address) if address: return db_api.ip_address_update( elevated, address, deallocated=False, deallocated_at=None) subnet = self._choose_available_subnet( elevated, net_id, ip_address=ip_address, version=version) ip_policy_rules = self.get_ip_policy_rule_set(subnet) # Creating this IP for the first time next_ip = None if ip_address: next_ip = ip_address address = db_api.ip_address_find( elevated, network_id=net_id, ip_address=next_ip, tenant_id=elevated.tenant_id, scope=db_api.ONE) if address: raise exceptions.IpAddressGenerationFailure(net_id=net_id) else: address = True while address: next_ip_int = int(subnet["next_auto_assign_ip"]) next_ip = netaddr.IPAddress(next_ip_int) if subnet["ip_version"] == 4: next_ip = next_ip.ipv4() subnet["next_auto_assign_ip"] = next_ip_int + 1 if ip_policy_rules and next_ip in ip_policy_rules: continue address = db_api.ip_address_find( elevated, network_id=net_id, ip_address=next_ip, tenant_id=elevated.tenant_id, scope=db_api.ONE) address = db_api.ip_address_create( elevated, address=next_ip, subnet_id=subnet["id"], version=subnet["ip_version"], network_id=net_id) address["deallocated"] = 0 return address
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]
def test_ip_address_find_ip_address_list_filter(self): with self._stubs(): ip_addresses = db_api.ip_address_find( self.context, ip_address=[self.addr], scope=db_api.ALL) self.assertEqual(len(ip_addresses), 1) self.assertEqual(ip_addresses[0]["address"], self.addr.ipv6().value)
def attempt_to_reallocate_ip(self, context, net_id, port_id, reuse_after, version=None, ip_address=None, segment_id=None, subnets=None): version = version or [4, 6] elevated = context.elevated() # We never want to take the chance of an infinite loop here. Instead, # we'll clean up multiple bad IPs if we find them (assuming something # is really wrong) #TODO(mdietz & mpath): Perhaps remove, select for update might quash for times in xrange(3): with context.session.begin(subtransactions=True): sub_ids = [] if subnets: sub_ids = subnets else: if segment_id: subnets = db_api.subnet_find(elevated, network_id=net_id, segment_id=segment_id) sub_ids = [s["id"] for s in subnets] if not sub_ids: raise exceptions.IpAddressGenerationFailure( net_id=net_id) ip_kwargs = { "network_id": net_id, "reuse_after": reuse_after, "deallocated": True, "scope": db_api.ONE, "ip_address": ip_address, "lock_mode": True, "version": version, "order_by": "address"} if sub_ids: ip_kwargs["subnet_id"] = sub_ids address = db_api.ip_address_find(elevated, **ip_kwargs) if address: #NOTE(mdietz): We should always be in the CIDR but we've # also said that before :-/ if address.get("subnet"): cidr = netaddr.IPNetwork(address["subnet"]["cidr"]) addr = netaddr.IPAddress(int(address["address"]), version=int(cidr.version)) if addr in cidr: updated_address = db_api.ip_address_update( elevated, address, deallocated=False, deallocated_at=None, allocated_at=timeutils.utcnow()) return [updated_address] else: # Make sure we never find it again context.session.delete(address) continue break return []
def test_get_single_deallocated_ip_non_admin_raises(self): with self._stubs() as ip: reserved_ip = ip_addr.update_ip_address(self.context, ip["id"], self.ip_address_dealloc) ip_address = db_api.ip_address_find(self.context, id=reserved_ip["id"], scope=db_api.ONE) self.assertEqual(ip_address["deallocated"], True) with self.assertRaises(q_exc.IpAddressNotFound): ip_addr.get_ip_address(self.context, ip_address['id'])
def test_get_deallocated_ips_non_admin_empty(self): with self._stubs() as ip: reserved_ip = ip_addr.update_ip_address(self.context, ip["id"], self.ip_address_dealloc) ip_address = db_api.ip_address_find(self.context, id=reserved_ip["id"], scope=db_api.ONE) self.assertEqual(ip_address["deallocated"], True) deallocated_ips = ip_addr.get_ip_addresses(self.context) self.assertEqual(len(deallocated_ips), 0)
def test_ip_address_find_port_id(self): self.context.session.query = mock.MagicMock() final_query_mock = self.context.session.query.return_value db_api.ip_address_find(self.context, port_id="foo") # NOTE(thomasem): Creates sqlalchemy.sql.elements.BinaryExpression # when using SQLAlchemy models in expressions. tenant_filter = (models.IPAddress.used_by_tenant_id.in_( [self.context.tenant_id])) port_filter = models.IPAddress.ports.any(models.Port.id == "foo") self.assertEqual(len(final_query_mock.filter.call_args[0]), 2) port_found = False tenant_found = False for model_filter in list(final_query_mock.filter.call_args[0]): if str(port_filter) == str(model_filter): port_found = True elif str(tenant_filter) == str(model_filter): tenant_found = True self.assertTrue(tenant_found) self.assertTrue(port_found)
def _allocate_from_v6_subnet(self, context, net_id, subnet, port_id, ip_address=None, **kwargs): """This attempts to allocate v6 addresses as per RFC2462 and RFC3041. To accomodate this, we effectively treat all v6 assignment as a first time allocation utilizing the MAC address of the VIF. Because we recycle MACs, we will eventually attempt to recreate a previously generated v6 address. Instead of failing, we've opted to handle reallocating that address in this method. This should provide a performance boost over attempting to check each and every subnet in the existing reallocate logic, as we'd have to iterate over each and every subnet returned """ if not (ip_address is None and "mac_address" in kwargs and kwargs["mac_address"]): return self._allocate_from_subnet(context, net_id, subnet, ip_address, **kwargs) else: ip_policy_cidrs = models.IPPolicy.get_ip_policy_cidrs(subnet) for tries, ip_address in enumerate( generate_v6(kwargs["mac_address"]["address"], port_id, subnet["cidr"])): if tries > CONF.QUARK.v6_allocation_attempts - 1: raise exceptions.IpAddressGenerationFailure( net_id=net_id) ip_address = netaddr.IPAddress(ip_address) # NOTE(mdietz): treating the IPSet as a boolean caused netaddr # to attempt to enumerate the entire set! if (ip_policy_cidrs is not None and ip_address in ip_policy_cidrs): continue with context.session.begin(): address = db_api.ip_address_find( context, network_id=net_id, ip_address=ip_address, used_by_tenant_id=context.tenant_id, scope=db_api.ONE, lock_mode=True) if address: return db_api.ip_address_update( context, address, deallocated=False, deallocated_at=None, used_by_tenant_id=context.tenant_id, allocated_at=timeutils.utcnow()) with context.session.begin(): return db_api.ip_address_create( context, address=ip_address, subnet_id=subnet["id"], version=subnet["ip_version"], network_id=net_id)
def allocate_ip_address(self, context, net_id, port_id, reuse_after, version=None, ip_address=None): elevated = context.elevated() if ip_address: ip_address = netaddr.IPAddress(ip_address) new_addresses = [] realloc_ips = self.attempt_to_reallocate_ip(context, net_id, port_id, reuse_after, version=None, ip_address=None) if self.is_strategy_satisfied(realloc_ips): return realloc_ips new_addresses.extend(realloc_ips) with context.session.begin(subtransactions=True): subnets = self._choose_available_subnet( elevated, net_id, version, ip_address=ip_address, reallocated_ips=realloc_ips) for subnet in subnets: ip_policy_rules = models.IPPolicy.get_ip_policy_rule_set( subnet) # Creating this IP for the first time next_ip = None if ip_address: next_ip = ip_address address = db_api.ip_address_find( elevated, network_id=net_id, ip_address=next_ip, used_by_tenant_id=elevated.tenant_id, scope=db_api.ONE) if address: raise exceptions.IpAddressGenerationFailure( net_id=net_id) else: next_ip = self._iterate_until_available_ip( elevated, subnet, net_id, ip_policy_rules) context.session.add(subnet) address = db_api.ip_address_create( elevated, address=next_ip, subnet_id=subnet["id"], version=subnet["ip_version"], network_id=net_id) address["deallocated"] = 0 new_addresses.append(address) for addr in new_addresses: payload = dict(used_by_tenant_id=addr["used_by_tenant_id"], ip_block_id=addr["subnet_id"], ip_address=addr["address_readable"], device_ids=[p["device_id"] for p in addr["ports"]], created_at=addr["created_at"]) notifier_api.notify(context, notifier_api.publisher_id("network"), "ip_block.address.create", notifier_api.CONF.default_notification_level, payload) return new_addresses
def test_get_deallocated_ips_non_admin_empty(self): with self._stubs() as ip: reserved_ip = ip_addr.update_ip_address(self.context, ip["id"], self.ip_address_dealloc) ip_address = db_api.ip_address_find( self.context, id=reserved_ip["id"], scope=db_api.ONE) self.assertEqual(ip_address["deallocated"], True) deallocated_ips = ip_addr.get_ip_addresses(self.context) self.assertEqual(len(deallocated_ips), 0)
def test_get_single_deallocated_ip_non_admin_raises(self): with self._stubs() as ip: reserved_ip = ip_addr.update_ip_address(self.context, ip["id"], self.ip_address_dealloc) ip_address = db_api.ip_address_find( self.context, id=reserved_ip["id"], scope=db_api.ONE) self.assertEqual(ip_address["deallocated"], True) with self.assertRaises(q_exc.IpAddressNotFound): ip_addr.get_ip_address(self.context, ip_address['id'])
def test_get_single_deallocated_ip_admin(self): self.context.is_admin = True with self._stubs() as ip: reserved_ip = ip_addr.update_ip_address(self.context, ip["id"], self.ip_address_dealloc) ip_address = db_api.ip_address_find(self.context, id=reserved_ip["id"], scope=db_api.ONE) self.assertEqual(ip_address["deallocated"], True) deallocated_ip = ip_addr.get_ip_address(self.context, ip_address['id']) self.assertEqual(reserved_ip['id'], deallocated_ip['id']) self.assertEqual(deallocated_ip['_deallocated'], True)
def test_get_single_deallocated_ip_admin(self): self.context.is_admin = True with self._stubs() as ip: reserved_ip = ip_addr.update_ip_address(self.context, ip["id"], self.ip_address_dealloc) ip_address = db_api.ip_address_find( self.context, id=reserved_ip["id"], scope=db_api.ONE) self.assertEqual(ip_address["deallocated"], True) deallocated_ip = ip_addr.get_ip_address(self.context, ip_address['id']) self.assertEqual(reserved_ip['id'], deallocated_ip['id']) self.assertEqual(deallocated_ip['_deallocated'], True)
def test_get_deallocated_ips_admin(self): self.context.is_admin = True with self._stubs() as ip: reserved_ip = ip_addr.update_ip_address(self.context, ip["id"], self.ip_address_dealloc) ip_address = db_api.ip_address_find(self.context, id=reserved_ip["id"], scope=db_api.ONE) self.assertEqual(ip_address["deallocated"], True) filters = {'deallocated': 'True'} deallocated_ips = ip_addr.get_ip_addresses(self.context, **filters) self.assertEqual(len(deallocated_ips), 1) self.assertEqual(reserved_ip['id'], deallocated_ips[0]['id']) self.assertEqual(deallocated_ips[0]['_deallocated'], True)
def test_ip_address_find_address_type(self): self.context.session.query = mock.MagicMock() filter_mock = self.context.session.query.return_value db_api.ip_address_find(self.context, address_type="foo") # NOTE(thomasem): Creates sqlalchemy.sql.elements.BinaryExpression # when using SQLAlchemy models in expressions. tenant_filter = (models.IPAddress.used_by_tenant_id.in_( [self.context.tenant_id])) type_filter = models.IPAddress.address_type == "foo" self.assertEqual(len(filter_mock.filter.call_args[0]), 2) # NOTE(thomasem): Unfortunately BinaryExpression.compare isn't # showing to be a reliable comparison, so using the string # representation which dumps the associated SQL for the filter. type_found = False tenant_found = False for model_filter in list(filter_mock.filter.call_args[0]): if str(type_filter) == str(model_filter): type_found = True elif str(tenant_filter) == str(model_filter): tenant_found = True self.assertTrue(tenant_found) self.assertTrue(type_found)
def test_create_locks_address_doesnt_exist(self): network = db_api.network_create(self.context) subnet = db_api.subnet_create( self.context, network=network, cidr=self.cidr, ip_version=4) self.context.session.flush() addresses = netaddr.IPSet(netaddr.IPNetwork(self.sub_cidr)) null_routes.create_locks(self.context, [network.id], addresses) address = db_api.ip_address_find( self.context, subnet_id=subnet.id, scope=db_api.ONE) self.assertIsNotNone(address) self.assertIsNotNone(address.lock_id)
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)
def get_ports_for_ip_address(context, ip_id, limit=None, sorts=None, marker=None, page_reverse=False, filters=None, fields=None): """Retrieve a list of ports. The contents of the list depends on the identity of the user making the request (as indicated by the context) as well as any filters. : param context: neutron api request context : param filters: a dictionary with keys that are valid keys for a port as listed in the RESOURCE_ATTRIBUTE_MAP object in neutron/api/v2/attributes.py. Values in this dictionary are an iterable containing values that will be used for an exact match comparison for that value. Each result returned by this function will have matched one of the values for each key in filters. : param fields: a list of strings that are valid keys in a port dictionary as listed in the RESOURCE_ATTRIBUTE_MAP object in neutron/api/v2/attributes.py. Only these fields will be returned. """ LOG.info("get_ports for tenant %s filters %s fields %s" % (context.tenant_id, filters, fields)) addr = db_api.ip_address_find(context, id=ip_id, scope=db_api.ONE) if not addr: raise q_exc.IpAddressNotFound(addr_id=ip_id) if filters is None: filters = {} filters['ip_address_id'] = [ip_id] ports = db_api.port_find(context, limit, sorts, marker, fields=fields, join_security_groups=True, **filters) return v._make_ip_ports_list(addr, ports, fields)
def _find_or_create_address(context, network_ids, address): address_model = db_api.ip_address_find(context, network_id=network_ids, address=_to_int(address), scope=db_api.ONE) if not address_model: query = context.session.query(models.Subnet) query = query.filter(models.Subnet.network_id.in_(network_ids)) query = query.filter(models.Subnet.ip_version == address.version) query = query.filter(_to_int(address) >= models.Subnet.first_ip) query = query.filter(_to_int(address) <= models.Subnet.last_ip) subnet = query.one() address_model = db_api.ip_address_create( context, address=address, subnet_id=subnet["id"], version=subnet["ip_version"], network_id=subnet["network_id"], address_type=ip_types.FIXED) address_model["deallocated"] = 1 context.session.add(address_model) return address_model
def test_has_shared_owner_detection(self): with self._stubs(self.network, self.subnet, self.ports_info4) as (net, sub, ports): port_ids = [ports[0]['id'], ports[1]['id']] p_id = ports[0]['id'] shared_ip = { 'ip_address': dict(port_ids=port_ids, network_id=net['id'], version=4) } ip = ip_api.create_ip_address(self.context, shared_ip) self.assertEqual(ip_types.SHARED, ip['type']) ip_db = db_api.ip_address_find(self.context, id=ip['id'], scope=db_api.ONE) self.assertFalse(ip_db.has_any_shared_owner()) port_ip_update = ip_api.update_port_for_ip_address port_ip_update(self.context, ip['id'], p_id, self._make_port_body('derp')) self.assertTrue(ip_db.has_any_shared_owner())
def delete_ip_address(context, id): """Delete an ip address. : param context: neutron api request context : param id: UUID representing the ip address to delete. """ LOG.info("delete_ip_address %s for tenant %s" % (id, context.tenant_id)) with context.session.begin(): ip_address = db_api.ip_address_find(context, id=id, scope=db_api.ONE) if not ip_address or ip_address.deallocated: raise q_exc.IpAddressNotFound(addr_id=id) iptype = ip_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.") if ip_address.has_any_shared_owner(): raise q_exc.PortRequiresDisassociation() db_api.update_port_associations_for_ip(context, [], ip_address) ipam_driver.deallocate_ip_address(context, ip_address)
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)