diff --git a/provides.py b/provides.py index 5a3b6dd..01c5958 100644 --- a/provides.py +++ b/provides.py @@ -54,7 +54,40 @@ class BGPEndpoint(reactive.Endpoint): return asn - def publish_info(self, asn=None, passive=False, bindings=None): + def generate_asn_16(self): + """ + Generate unique 16-bit Private Use [RFC6996] ASn. + + This is useful to automate configuration of BGP routers that is part + of a Clos Network Topology with a Layer 3-Only routed design. [RFC7938] + + Assumption: + - Unit has a IPv4 address and it is unique to the deployment. + + Implementation: + - A private 16-bit ASn has a range of 64512 - 65534 + which leaves us with 1022 possible endpoints. + - The 16-bit ASn space is limited and this implementation + will give you unique ASns for a /23 + + Note: + - This implementation generates ASn in the following range: + 65023 - 65534 + - Leaving the following range for any static configuration needs: + 64512 - 65022 + """ + asn_base = 65023 + mask = netaddr.IPAddress('0.0.1.255') + unit_ip = netaddr.IPAddress( + ch_core.hookenv.unit_get('private-address')) + masked_ip = unit_ip & mask + + asn = asn_base + int(masked_ip) + + return asn + + def publish_info(self, asn=None, passive=False, bindings=None, + use_16bit_asn=False): """ Publish the AS Number and IP address of any extra-bindings of this BGP Endpoint over the relationship. @@ -70,7 +103,10 @@ class BGPEndpoint(reactive.Endpoint): if asn: myasn = asn else: - myasn = self.generate_asn() + if use_16bit_asn: + myasn = self.generate_asn_16() + else: + myasn = self.generate_asn() # network_get will return addresses for bindings regardless of them # being bound to a network space. detect actual space bindings by diff --git a/unit_tests/test_provides.py b/unit_tests/test_provides.py index ff5bfe5..e412887 100644 --- a/unit_tests/test_provides.py +++ b/unit_tests/test_provides.py @@ -33,6 +33,40 @@ class TestBGPProvides(ut_utils.BaseTestCase): asn = endpoint.generate_asn() self.assertEqual(asn, 4294967294) + def test_generate_asn_16_min(self): + self.patch_object(provides, 'ch_core') + self.ch_core.hookenv.unit_get.return_value = '0.0.0.0' + endpoint = provides.BGPEndpoint('bgpserver') + asn = endpoint.generate_asn_16() + self.assertEqual(asn, 65023) + + def test_generate_asn_16_collission(self): + self.patch_object(provides, 'ch_core') + self.ch_core.hookenv.unit_get.return_value = '0.0.0.0' + endpoint = provides.BGPEndpoint('bgpserver') + self.ch_core.hookenv.unit_get.return_value = '10.0.0.0' + asn0 = endpoint.generate_asn_16() + self.ch_core.hookenv.unit_get.return_value = '10.0.0.255' + asn0_255 = endpoint.generate_asn_16() + self.ch_core.hookenv.unit_get.return_value = '10.0.1.0' + asn1 = endpoint.generate_asn_16() + self.ch_core.hookenv.unit_get.return_value = '10.0.1.255' + asn1_255 = endpoint.generate_asn_16() + self.ch_core.hookenv.unit_get.return_value = '10.0.2.0' + asn2 = endpoint.generate_asn_16() + self.assertNotEqual(asn0, asn0_255) + self.assertNotEqual(asn0, asn1) + self.assertNotEqual(asn0_255, asn1_255) + self.assertNotEqual(asn1, asn1_255) + self.assertEqual(asn0, asn2) + + def test_generate_asn_16_max(self): + self.patch_object(provides, 'ch_core') + self.ch_core.hookenv.unit_get.return_value = '255.255.255.255' + endpoint = provides.BGPEndpoint('bgpserver') + asn = endpoint.generate_asn_16() + self.assertEqual(asn, 65534) + _network_get_side_effect = [ yaml.load(''' bind-addresses: @@ -154,6 +188,50 @@ ingress-addresses: } ) + def test_publish_info_16bit_asn(self): + self.maxDiff = None + self.patch_object(provides, 'ch_core') + self.ch_core.hookenv.unit_get.return_value = '172.16.122.251' + self.ch_core.hookenv.network_get.side_effect = \ + self._network_get_side_effect + endpoint = provides.BGPEndpoint('bgpserver') + endpoint._relations = [self._relation] + endpoint.publish_info(bindings=['ptp0', 'ptp1', 'ptp2', 'ptp3', + 'lan0'], use_16bit_asn=True) + self.assertEqual( + endpoint.relations[0].to_publish, + { + 'asn': 65274, + 'bindings': [ + { + 'address': '172.16.100.1', + 'cidr': '172.16.100.0/30' + }, + { + 'address': '2001:db8:100::1:0:0', + 'cidr': '2001:db8:100::/64' + }, + { + 'address': '172.16.110.1', + 'cidr': '172.16.110.0/30' + }, + { + 'address': '2001:db8:110::1:0:0', + 'cidr': '2001:db8:110::/64' + }, + { + 'address': '172.16.120.1', + 'cidr': '172.16.120.0/30' + }, + { + 'address': '2001:db8:120::1:0:0', + 'cidr': '2001:db8:120::/64' + }, + ], + 'passive': False, + } + ) + def test_get_received_info(self): self.maxDiff = None self.patch_object(provides, 'ch_core')