class IPAllocatorTests(unittest.TestCase): """ Test class for the Mobilityd IP Allocator """ RECYCLING_INTERVAL_SECONDS = 1 def _new_ip_allocator(self, recycling_interval): """ Creates and sets up an IPAllocator with the given recycling interval. """ # NOTE: change below to True to run IP allocator tests locally. We # don't persist to Redis during normal unit tests since they are run # in Sandcastle. config = { 'recycling_interval': recycling_interval, 'persist_to_redis': False, 'redis_port': 6379, } self._allocator = IPAddressManager( recycling_interval=recycling_interval, allocator_type=MobilityD.IP_POOL, config=config) self._allocator.add_ip_block(self._block) def setUp(self): # need to allocate at least 4 bits, as 13 addresses # are either preallocated or not valid for hosts self._block = ipaddress.ip_network('192.168.0.0/28') # 192.168.0.0 is not valid host IP # 192.168.0.1 to 192.168.0.11 are preallocated self._ip0 = ipaddress.ip_address('192.168.0.12') self._ip1 = ipaddress.ip_address('192.168.0.13') self._ip2 = ipaddress.ip_address('192.168.0.14') self._new_ip_allocator(self.RECYCLING_INTERVAL_SECONDS) def test_list_added_ip_blocks(self): """ test list assigned IP blocks """ ip_block_list = self._allocator.list_added_ip_blocks() self.assertEqual(ip_block_list, [self._block]) def test_list_empty_ip_block(self): """ test list empty ip block """ ip_list = self._allocator.list_allocated_ips(self._block) self.assertEqual(len(ip_list), 0) def test_list_unknown_ip_block(self): """ test list unknown ip block """ block = ipaddress.ip_network('10.0.0.0/28') with self.assertRaises(IPBlockNotFoundError): self._allocator.list_allocated_ips(block) def test_alloc_ip_address(self): """ test alloc_ip_address """ ip0 = self._allocator.alloc_ip_address('SID0') self.assertTrue(ip0 in [self._ip0, self._ip1, self._ip2]) self.assertTrue(ip0 in self._allocator.list_allocated_ips(self._block)) self.assertEqual(self._allocator.get_sid_ip_table(), [('SID0', ip0)]) ip1 = self._allocator.alloc_ip_address('SID1') self.assertTrue(ip1 in [self._ip0, self._ip1, self._ip2]) self.assertNotEqual(ip1, ip0) self.assertEqual({ip0, ip1}, set(self._allocator.list_allocated_ips(self._block))) self.assertEqual(set(self._allocator.get_sid_ip_table()), {('SID0', ip0), ('SID1', ip1)}) ip2 = self._allocator.alloc_ip_address('SID2') self.assertTrue(ip2 in [self._ip0, self._ip1, self._ip2]) self.assertNotEqual(ip2, ip0) self.assertNotEqual(ip2, ip1) self.assertEqual({ip0, ip1, ip2}, set(self._allocator.list_allocated_ips(self._block))) self.assertEqual(set(self._allocator.get_sid_ip_table()), {('SID0', ip0), ('SID1', ip1), ('SID2', ip2)}) # allocate from empty free set with self.assertRaises(NoAvailableIPError): self._allocator.alloc_ip_address('SID3') def test_release_ip_address(self): """ test release_ip_address """ ip0 = self._allocator.alloc_ip_address('SID0') ip1 = self._allocator.alloc_ip_address('SID1') ip2 = self._allocator.alloc_ip_address('SID2') # release ip self._allocator.release_ip_address('SID0', ip0) self.assertFalse( ip0 in self._allocator.list_allocated_ips(self._block)) # check not recyled self.assertEqual(set(self._allocator.get_sid_ip_table()), {('SID0', ip0), ('SID1', ip1), ('SID2', ip2)}) with self.assertRaises(NoAvailableIPError): self._allocator.alloc_ip_address('SID3') # double release with self.assertRaises(IPNotInUseError): self._allocator.release_ip_address('SID0', ip0) # ip does not exist with self.assertRaises(MappingNotFoundError): non_existing_ip = ipaddress.ip_address('192.168.1.16') self._allocator.release_ip_address('SID0', non_existing_ip) def test_get_ip_for_subscriber(self): """ test get_ip_for_sid """ ip0 = self._allocator.alloc_ip_address('SID0') ip1 = self._allocator.alloc_ip_address('SID1') ip0_returned = self._allocator.get_ip_for_sid('SID0') ip1_returned = self._allocator.get_ip_for_sid('SID1') # check if retrieved ip is the same as the one allocated self.assertEqual(ip0, ip0_returned) self.assertEqual(ip1, ip1_returned) def test_get_ip_for_unknown_subscriber(self): """ Getting ip for non existent subscriber should return None """ self.assertIsNone(self._allocator.get_ip_for_sid('SID0')) def test_get_sid_for_ip(self): """ test get_sid_for_ip """ ip0 = self._allocator.alloc_ip_address('SID0') ip1 = self._allocator.alloc_ip_address('SID1') sid0_returned = self._allocator.get_sid_for_ip(ip0) sid1_returned = self._allocator.get_sid_for_ip(ip1) self.assertEqual('SID0', sid0_returned) self.assertEqual('SID1', sid1_returned) def test_get_sid_for_unknown_ip(self): """ Getting sid for non allocated ip address should return None """ self.assertIsNone( self._allocator.get_sid_for_ip(ipaddress.ip_address('1.1.1.1'))) def test_allocate_allocate(self): """ Duplicated IP requests for the same UE returns same IP """ ip0 = self._allocator.alloc_ip_address('SID0') ip1 = self._allocator.alloc_ip_address('SID0') self.assertEqual(ip0, ip1) def test_allocated_release_allocate(self): """ Immediate allocation after releasing get the same IP """ ip0 = self._allocator.alloc_ip_address('SID0') self._allocator.release_ip_address('SID0', ip0) ip2 = self._allocator.alloc_ip_address('SID0') self.assertEqual(ip0, ip2) def test_allocate_release_recycle_allocate(self): """ Allocation after recycling should get different IPs """ ip0 = self._allocator.alloc_ip_address('SID0') self._allocator.release_ip_address('SID0', ip0) # Wait for auto-recycler to kick in time.sleep(1.2 * self.RECYCLING_INTERVAL_SECONDS) ip1 = self._allocator.alloc_ip_address('SID1') ip2 = self._allocator.alloc_ip_address('SID0') self.assertNotEqual(ip1, ip2) def test_recycle_tombstone_ip_on_timer(self): """ test recycle tombstone IP on interval loop """ ip0 = self._allocator.alloc_ip_address('SID0') ip1 = self._allocator.alloc_ip_address('SID1') ip2 = self._allocator.alloc_ip_address('SID2') self._allocator.release_ip_address('SID0', ip0) # Wait for auto-recycler to kick in time.sleep(2 * self.RECYCLING_INTERVAL_SECONDS) ip3 = self._allocator.alloc_ip_address('SID3') self.assertEqual(ip0, ip3) self._allocator.release_ip_address('SID1', ip1) # Wait for auto-recycler to kick in time.sleep(2 * self.RECYCLING_INTERVAL_SECONDS) ip4 = self._allocator.alloc_ip_address('SID4') self.assertEqual(ip1, ip4) self._allocator.release_ip_address('SID2', ip2) # Wait for auto-recycler to kick in time.sleep(2 * self.RECYCLING_INTERVAL_SECONDS) ip5 = self._allocator.alloc_ip_address('SID5') self.assertEqual(ip2, ip5) def test_allocate_unrecycled_IP(self): """ Allocation should fail before IP recycling """ ip0 = self._allocator.alloc_ip_address('SID0') self._allocator.alloc_ip_address('SID1') self._allocator.alloc_ip_address('SID2') self._allocator.release_ip_address('SID0', ip0) with self.assertRaises(NoAvailableIPError): self._allocator.alloc_ip_address('SID3') def test_recycle_tombstone_ip(self): """ test recycle tombstone IP """ self._new_ip_allocator(0) ip0 = self._allocator.alloc_ip_address('SID0') ip1 = self._allocator.alloc_ip_address('SID1') ip2 = self._allocator.alloc_ip_address('SID2') self._allocator.release_ip_address('SID0', ip0) ip3 = self._allocator.alloc_ip_address('SID3') self.assertEqual(ip0, ip3) self._allocator.release_ip_address('SID1', ip1) ip4 = self._allocator.alloc_ip_address('SID4') self.assertEqual(ip1, ip4) self._allocator.release_ip_address('SID2', ip2) ip5 = self._allocator.alloc_ip_address('SID5') self.assertEqual(ip2, ip5) def test_remove_unallocated_block(self): """ test removing the allocator for an unallocated block """ self.assertEqual({self._block}, self._allocator.remove_ip_blocks(self._block)) def test_remove_allocated_block_without_force(self): """ test removing the allocator for an allocated block unforcibly """ self._allocator.alloc_ip_address('SID0') self.assertEqual( set(), self._allocator.remove_ip_blocks(self._block, force=False)) def test_remove_unforcible_is_default_behavior(self): """ test that removing by default is unforcible remove """ self._allocator.alloc_ip_address('SID0') self.assertEqual(set(), self._allocator.remove_ip_blocks(self._block)) def test_remove_allocated_block_with_force(self): """ test removing the allocator for an allocated block forcibly """ self._allocator.alloc_ip_address('SID0') self.assertEqual({self._block}, self._allocator.remove_ip_blocks(self._block, force=True)) def test_remove_after_releasing_all_addresses(self): """ removing after releasing all allocated addresses """ self._new_ip_allocator(0) # Immediately recycle ip0 = self._allocator.alloc_ip_address('SID0') ip1 = self._allocator.alloc_ip_address('SID1') self.assertEqual( set(), self._allocator.remove_ip_blocks(self._block, force=False)) self._allocator.release_ip_address('SID0', ip0) self._allocator.release_ip_address('SID1', ip1) self.assertEqual({self._block}, self._allocator.remove_ip_blocks(self._block, force=False)) def test_remove_after_releasing_some_addresses(self): """ removing after releasing all allocated addresses """ self._new_ip_allocator(0) # Immediately recycle ip0 = self._allocator.alloc_ip_address('SID0') ip1 = self._allocator.alloc_ip_address('SID1') self.assertEqual( set(), self._allocator.remove_ip_blocks(self._block, force=False)) self._allocator.release_ip_address('SID0', ip0) self.assertEqual( set(), self._allocator.remove_ip_blocks(self._block, force=False)) self.assertTrue( ip0 not in self._allocator.list_allocated_ips(self._block)) self.assertTrue(ip1 in self._allocator.list_allocated_ips(self._block)) def test_reap_after_forced_remove(self): """ test reaping after a forced remove and readding the reaped ips doesn't free them """ recycling_interval_seconds = 1 # plenty of time to set up self._new_ip_allocator(recycling_interval_seconds) ip0 = self._allocator.alloc_ip_address('SID0') ip1 = self._allocator.alloc_ip_address('SID1') self._allocator.release_ip_address('SID0', ip0) self.assertEqual({self._block}, self._allocator.remove_ip_blocks(self._block, force=True)) self._allocator.add_ip_block(self._block) ip0 = self._allocator.alloc_ip_address('SID0') ip1 = self._allocator.alloc_ip_address('SID1') # Wait for auto-recycler to kick in time.sleep(recycling_interval_seconds) # Ensure that released-then-allocated address doesn't get reaped self.assertTrue(ip0 in self._allocator.list_allocated_ips(self._block)) self.assertTrue(ip1 in self._allocator.list_allocated_ips(self._block))
def main(): """Start mobilityd""" service = MagmaService('mobilityd', mconfigs_pb2.MobilityD()) # Optionally pipe errors to Sentry sentry_init(service_name=service.name) # Load service configs and mconfig config = service.config mconfig = service.mconfig multi_apn = config.get('multi_apn', mconfig.multi_apn_ip_alloc) static_ip_enabled = config.get('static_ip', mconfig.static_ip_enabled) allocator_type = mconfig.ip_allocator_type dhcp_iface = config.get('dhcp_iface', 'dhcp0') dhcp_retry_limit = config.get('retry_limit', RETRY_LIMIT) # TODO: consider adding gateway mconfig to decide whether to # persist to Redis client = get_default_client() store = MobilityStore( client, config.get('persist_to_redis', False), config.get('redis_port', DEFAULT_REDIS_PORT), ) chan = ServiceRegistry.get_rpc_channel( 'subscriberdb', ServiceRegistry.LOCAL, ) ipv4_allocator = _get_ipv4_allocator( store, allocator_type, static_ip_enabled, multi_apn, dhcp_iface, dhcp_retry_limit, SubscriberDBStub(chan), ) # Init IPv6 allocator, for now only IP_POOL mode is supported for IPv6 ipv6_allocator = IPv6AllocatorPool( store=store, session_prefix_alloc_mode=_get_value_or_default( mconfig.ipv6_prefix_allocation_type, DEFAULT_IPV6_PREFIX_ALLOC_MODE, ), ) # Load IPAddressManager ip_address_man = IPAddressManager(ipv4_allocator, ipv6_allocator, store) # Load IPv4 and IPv6 blocks from the configurable mconfig file # No dynamic reloading support for now, assume restart after updates ipv4_block = _get_ip_block(mconfig.ip_block, "IPv4") if ipv4_block is not None: logging.info('Adding IPv4 block') try: allocated_ip_blocks = ip_address_man.list_added_ip_blocks() if ipv4_block not in allocated_ip_blocks: # Cleanup previously allocated IP blocks ip_address_man.remove_ip_blocks(*allocated_ip_blocks, force=True) ip_address_man.add_ip_block(ipv4_block) except OverlappedIPBlocksError: logging.warning("Overlapped IPv4 block: %s", ipv4_block) ipv6_block = _get_ip_block(mconfig.ipv6_block, "IPv6") if ipv6_block is not None: logging.info('Adding IPv6 block') try: allocated_ipv6_block = ip_address_man.get_assigned_ipv6_block() if ipv6_block != allocated_ipv6_block: ip_address_man.add_ip_block(ipv6_block) except OverlappedIPBlocksError: logging.warning("Overlapped IPv6 block: %s", ipv6_block) # Add all servicers to the server mobility_service_servicer = MobilityServiceRpcServicer( ip_address_man, config.get('print_grpc_payload', False), ) mobility_service_servicer.add_to_server(service.rpc_server) service.run() # Cleanup the service service.close()