def __init__(self, network_dict, datastore): '''Python representation of a _network block Raises: ValueError - if a _network block has invalid information''' self.datastore = datastore self._network_block_utilization = {} self._network_id = network_dict['id'] self.network_name = network_dict['name'] self.family = network_dict['family'] self.location = network_dict['location'] self._network = ipaddress.ip_network(network_dict['network'], strict=True) self.allocation_size = network_dict['allocation_size'] self.reserved_blocks = network_dict['reserved_blocks'] # Sanity check ourselves check.is_valid_ip_family(self.family) # Work out some values based on family size, and make sure our allocation is sane total_length_of_an_ip = None if self.family == AF_INET: total_length_of_an_ip = 32 if self.allocation_size > 32 or self.allocation_size < self._network.prefixlen: raise ValueError('Allocation prefix size is too large!') if self.family == AF_INET6: total_length_of_an_ip = 128 if self.allocation_size > 128 or self.allocation_size < self._network.prefixlen: raise ValueError('Allocation prefix size is too large!') # We can calculate the number of hosts by doing powers of 2 math self._total_number_of_allocations = 2**(self.allocation_size-self._network.prefixlen) # Now the tricky bit. We need to know (in binary), how much we need to add to get # the next IP range we're allocation. # Clarity note, if we're allocating single IPs, the following equation will be 0. Anything # raised to 0 becomes 1. self._block_seperator = 2**(total_length_of_an_ip-self.allocation_size) # Depending on the size of the block, and our family, there are some allocations # that aren't valid. If we're handing out /32 addresses, we need to take in account # that the _network address and broadcast address of a block are unusable. Allocation # handles this case for >/32 blocks, but we need to handle it here otherwise. # FIXME: Confirm this logic is sane ... # IP ranges are 0-255. Powers of two math gets us 256, so drop it by one so everything # else ends up in the right ranges self._total_number_of_allocations -= 1 # In all cases, we need to handle the _network address self._mark_network_address() # If we're IPv4, we need handle the broadcast address if self.family == AF_INET: self._mark_broadcast_address()
def add_ip(self, ip_dict): '''Adds an IP to an interface. Lower-level function to add an IP address Args: ip_dict - takes an IP dictionary (see get_ips) and adds it to an interface directorly Raises: ValueError IP address invalid. See message for more info DuplicateIPError This IP is already configured InterfaceConfigurationError The ip_dict was valid, but the IP failed add ''' check.validate_and_normalize_ip_dict(ip_dict) # Throw an error if we try to add an existing address. existing_ip_check = None try: existing_ip_check = self.get_full_ip_info(ip_dict['ip_address']) except IPNotFound: pass if existing_ip_check: raise DuplicateIPError("This IP has already been assigned!") # We call add slightly differently based on socket family if ip_dict['family'] == AF_INET: self.iproute_api.addr('add', index=self.interface_index, family=AF_INET, address=ip_dict['ip_address'], broadcast=ip_dict['broadcast'], prefixlen=ip_dict['prefix_length']) if ip_dict['family'] == AF_INET6: self.iproute_api.addr('add', index=self.interface_index, family=AF_INET6, address=ip_dict['ip_address'], prefixlen=ip_dict['prefix_length']) # Do a sanity check and make sure the IP actually got added ip_check = self.get_full_ip_info(ip_dict['ip_address']) if not (ip_check['ip_address'] == ip_dict['ip_address'] and ip_check['prefix_length'] == ip_dict['prefix_length']): raise InterfaceConfigurationError("IP failed to add!")
def __init__(self, ip_range): '''Create a new _allocation based on this range''' self.allocation_id = None self._allocation_utilization = {} # _allocation_status refers to the status of a block as a whole. It is equal to the # highest status of any IP within a block self._allocation_status = 'UNALLOCATED' # Do the usual validation and sanity check self._allocation = check.confirm_valid_network(ipaddress.ip_network(ip_range, strict=True)) self._allocation_start = self._allocation.network_address address_size = None if self._allocation.version == 4: address_size = 32 self.family = AF_INET if self._allocation.version == 6: address_size = 128 self.family = AF_INET6 # Power of 2 math to the rescue; work out how many IPs we represent self._total_number_of_ip = 2**(address_size-self._allocation.prefixlen) self._available_ips = self._total_number_of_ip # The network address is unusable in IPv4, and for our sanity, mark it # unusable in IPv6 (dealing with b1oc:: as a valid IP is bleh) # # If we're IPv4, the broadcast addresses is unusable within a block. if self._total_number_of_ip != 1: self._mark_network_address() if self.family == AF_INET: self._mark_broadcast_address()
def create_network(self, name, location, family, network, allocation_size, reserved_blocks): # pylint: disable=too-many-arguments """Creates a network in the database""" # Argument validation check.is_valid_ip_family(family) network = check.validate_and_normalize_ip_network(network) check.is_valid_prefix_size(allocation_size, family) # FIXME: make sure we're not trying to add ourselves twice query = """INSERT INTO network_topology (name, location, family, network, allocation_size, reserved_blocks) VALUES (%s, %s, %s,%s, %s, %s)""" self._do_insert(query, (name, location, int(family), network, allocation_size, reserved_blocks)) # Update our state information to see the new network self.refresh_network_topogoly()
def _get_allocation_offset(self, cidr_block): '''Gets the offset within the dict for a given allocation''' # Validate our input ip_network = check.validate_ip_network(cidr_block) if not check.do_cidr_blocks_overlap(self._network, cidr_block): raise ValueError('Allocation block not within NetworkBlock') if ip_network.prefixlen != self.allocation_size: raise ValueError('Allocation block has wrong allocation size') # Offset is calculated by the difference in network addresses offset = int(ip_network.network_address)-int(self._network.network_address) import pprint pprint.pprint(offset) pprint.pprint(self._network_block_utilization) # Confirm it exists, or throw a ValueError if offset in self._network_block_utilization: return offset raise AllocationNotFound("Allocation doesn't exist within NetworkBlock")
def remove_ip(self, ip_address): '''Removes an IP from an interface. Full details are looked up via get_full_ip_info for removal Args: ip_address - IP address to remove Raises: ValueError The IP address provided was invalid IPNotFound The IP is not configured on this interface InterfaceConfigurationError The IP address was valid, but the IP was not successfully removed ''' # San check ip_address = check.validate_and_normalize_ip(ip_address) # Get the full set of IP information ip_info = self.get_full_ip_info(ip_address) # Attempt to delete if ip_info['family'] == AF_INET: self.iproute_api.addr('delete', index=self.interface_index, address=ip_info['ip_address'], broadcast=ip_info['broadcast'], prefixlen=ip_info['prefix_length']) if ip_info['family'] == AF_INET6: self.iproute_api.addr('delete', index=self.interface_index, address=ip_info['ip_address'], prefixlen=ip_info['prefix_length']) # Confirm the delete. get_full_ip_info will throw an exception if it can't find it try: self.get_full_ip_info(ip_address) except IPNotFound: # We got it! return # Didn't get it. Throw an exception and bail raise InterfaceConfigurationError("IP deletion failure")
def set_ip_status(self, ip_address, status, allocation, machine): """Sets an IP status to reserved in the database""" # Sanity check our input ip_address = check.validate_and_normalize_ip(ip_address) if not isinstance(allocation, AllocationServerSide): raise ValueError("Invalid Allocation object") if not isinstance(machine, Machine): raise ValueError("Invalid Machine object") if not (status == "UNMANAGED" or status == "RESERVED" or status == "STANDBY" or status == "ACTIVE_UTILIZATION"): raise ValueError("Invalid status for IP") # We use REPLACE to make sure statuses are always accurate to the server. In case # of conflict, the server is always the correct source of information query = """REPLACE INTO ip_allocations (from_allocation, allocated_to, ip_address, status, reservation_expires) VALUES (%s, %s, %s, %s, %s)""" # reservation status is null unless we're going to/from RESERVED reservation_status = "NULL" self._do_insert(query, (allocation.get_id(), machine.get_id(), ip_address, status, reservation_status))
def mark_ip_as_reserved(self, ip_to_reserve): '''Moves an IP from unused to reserved''' # Check that this is a valid IP for this allocation ip_address = ipaddress.ip_address(ip_to_reserve) if not check.is_ip_within_block(ip_address, self._allocation): raise ValueError('ip_address not within allocation') if not self._confirm_ip_is_unused(ip_address): raise ValueError(('%s is not UNALLOCATED' % (str(ip_address),) )) # We're good, create the allocation offset = self._calculate_offset(ip_address) ip_status = {} ip_status['status'] = 'RESERVED' ip_status['reserved_until'] = None self._allocation_utilization.update({offset: ip_status}) # We return the validated ip_address for postprocessing by the parent class return ip_address
def get_full_ip_info(self, wanted_address): '''Returns an ip_dict for an individual IP address. Format identical to get_ips() Args: wanted_address - a valid v4 or v6 address to search for. Value is normalized by ipaddress.ip_address when returning Raises: ValueError - wanted_address is not a valid IP address IPNotFound - this interface doesn't have this IP address ''' wanted_address = check.validate_and_normalize_ip(wanted_address) ips = self.get_ips() # Walk the IP table and find the specific IP we want for ip_address in ips: if ip_address['ip_address'] == wanted_address: return ip_address # If we get here, the IP wasn't found raise IPNotFound("IP not found on interface")