diff --git a/docs/CONFIG.rst b/docs/CONFIG.rst index 4ff34424..b20b17ab 100644 --- a/docs/CONFIG.rst +++ b/docs/CONFIG.rst @@ -136,17 +136,26 @@ For each community, values can be set for any of the three *formats*: standard ( Site-specific custom configuration files **************************************** -Local configuration files can be used to load static site-specific options into the BGP speaker, bypassing ARouteServer configuration building logic. These files can be used to configure, for example, neighborship with peers which are not route server members or that require custom settings. +Local configuration files can be used to load static site-specific options into the BGP speaker, bypassing the dynamic ARouteServer configuration building mechanisms. These files can be used to configure, for example, neighborship with peers which are not route server members or that require custom settings. -In BIRD, an *include* statement is used to add local files at the end of the main configuration file: +In BIRD, *include* statements are used to add local files at the end of the main configuration file. +Depending on the IP version that is in use to build the current configuration, an address family specific *include* statement is also added: .. code:: + # include statements for IPv4 configuration files include "*.local"; + include "*.local4"; -Every file that is put into the same directory of the BIRD main configuration file and whose name matches the "\*.local" pattern is added to the end of the configuration. These files are not processed by ARouteServer but only by BIRD. +.. code:: + + # include statements for IPv6 configuration files + include "*.local"; + include "*.local6"; + +Every file that is put into the same directory of the BIRD main configuration file and whose name matches the "\*.local" or "\*.local[4|6]" pattern is added to the end of the configuration. These files are not processed by ARouteServer but only by BIRD. Configuration options given in .local files must be IP version agnostic and must be supported by both the IPv4 and IPv6 processes; address family specific options must be set in .local4 or .local6 files. -Example: file name "01-route_collector.local" in "/etc/bird" directory: +Example: file name "01-route_collector.local4" in "/etc/bird" directory: .. code:: diff --git a/examples/default/bird4.conf b/examples/default/bird4.conf index 31caa0fc..d7f22011 100644 --- a/examples/default/bird4.conf +++ b/examples/default/bird4.conf @@ -429,4 +429,5 @@ protocol bgp AS10745_1 { -include "*.local"; \ No newline at end of file +include "*.local"; +include "*.local4"; diff --git a/examples/default/bird6.conf b/examples/default/bird6.conf index b7e5a81d..d30f065c 100644 --- a/examples/default/bird6.conf +++ b/examples/default/bird6.conf @@ -359,4 +359,5 @@ protocol bgp AS10745_2 { -include "*.local"; \ No newline at end of file +include "*.local"; +include "*.local6"; diff --git a/examples/rich/bird4.conf b/examples/rich/bird4.conf index 8d8ca7ff..0eae5259 100644 --- a/examples/rich/bird4.conf +++ b/examples/rich/bird4.conf @@ -669,4 +669,5 @@ protocol bgp AS10745_1 { -include "*.local"; \ No newline at end of file +include "*.local"; +include "*.local4"; diff --git a/examples/rich/bird6.conf b/examples/rich/bird6.conf index 5271ab47..a864e053 100644 --- a/examples/rich/bird6.conf +++ b/examples/rich/bird6.conf @@ -587,4 +587,5 @@ protocol bgp AS10745_2 { -include "*.local"; \ No newline at end of file +include "*.local"; +include "*.local6"; diff --git a/pierky/arouteserver/tests/live_tests/base.py b/pierky/arouteserver/tests/live_tests/base.py index b3c79be3..8e3390ee 100644 --- a/pierky/arouteserver/tests/live_tests/base.py +++ b/pierky/arouteserver/tests/live_tests/base.py @@ -133,6 +133,10 @@ class (the one where the ``DATA`` dictionary is set) must have def _get_module_dir(cls): return os.path.dirname(cls.MODULE_PATH) + @classmethod + def _get_local_file_path(cls, filename): + return os.path.join(cls._get_module_dir(), filename) + @classmethod def _get_instance_by_name(cls, name): for instance in cls.INSTANCES: @@ -251,6 +255,26 @@ def build_rs_cfg(cls, tpl_dir_name, tpl_name, out_file_name, return cfg_file_path + @classmethod + def use_static_file(cls, local_filename): + """Prepare the local file in order to use it later. + + Args: + filename (str): the name of the local file, + relative to the scenario directory. + + Returns: + the path of the file to be used + """ + + var_dir = cls._create_var_dir() + var_path = os.path.join(var_dir, local_filename) + local_path = os.path.join(cls._get_module_dir(), local_filename) + with open(local_path, "r") as src: + with open(var_path, "w") as dst: + dst.write(src.read()) + return var_path + @classmethod def mock_irrdb(cls): def _mock_load_data_from_cache(*args, **kwargs): @@ -574,6 +598,27 @@ def log_contains(self, inst, msg, instances={}): if not inst.log_contains(expanded_msg): self.fail("Expected message not found on {} logs:\n\t{}".format(inst.name, expanded_msg)) + def session_exists(self, inst_a, inst_b_or_ip): + """Test if a BGP session between the two instances exists. + + Args: + inst_a: the :class:`BGPSpeakerInstance` instance where the + BGP session is looked for. + + inst_b_or_ip: the :class:`BGPSpeakerInstance` instance or an + IP address that *inst_a* is expected to peer with. + """ + + if inst_a.get_bgp_session(inst_b_or_ip) is None: + self.fail("A BGP session between '{}' ({}) and '{}' " + "does not exist.".format( + inst_a.name, inst_a.ip, + "{} ({})".format( + inst_b_or_ip.name, inst_b_or_ip.ip + ) if isinstance(inst_b_or_ip, BGPSpeakerInstance) + else inst_b_or_ip + )) + def session_is_up(self, inst_a, inst_b): """Test if a BGP session between the two instances is up. @@ -588,6 +633,8 @@ def session_is_up(self, inst_a, inst_b): expected to peer with. """ + self.session_exists(inst_a, inst_b) + if inst_a.bgp_session_is_up(inst_b): return time.sleep(5) diff --git a/pierky/arouteserver/tests/live_tests/bird.py b/pierky/arouteserver/tests/live_tests/bird.py index 25dfc27d..bcd23cbf 100644 --- a/pierky/arouteserver/tests/live_tests/bird.py +++ b/pierky/arouteserver/tests/live_tests/bird.py @@ -16,7 +16,7 @@ import re from docker import DockerInstance -from instances import Route +from instances import Route, BGPSpeakerInstance class BIRDInstance(DockerInstance): @@ -106,11 +106,21 @@ def _get_protocols_status(self, force_update=False): "{}: can't build protocols status map".format(self.name) ) - def bgp_session_is_up(self, other_inst, force_update=False): + def get_bgp_session(self, other_inst_or_ip, force_update=False): + if isinstance(other_inst_or_ip, BGPSpeakerInstance): + other_inst_ip = other_inst_or_ip.ip + else: + other_inst_ip = other_inst_or_ip self._get_protocols_status(force_update=force_update) for proto in self.protocols_status: - if self.protocols_status[proto]["ip"] == other_inst.ip: - return self.protocols_status[proto]["is_up"] + if self.protocols_status[proto]["ip"] == other_inst_ip: + return self.protocols_status[proto] + return None + + def bgp_session_is_up(self, other_inst, force_update=False): + bgp_session_info = self.get_bgp_session(other_inst, force_update) + if bgp_session_info: + return bgp_session_info["is_up"] raise Exception( "Can't get BGP session status for {} on {} " "(looking for {})".format( diff --git a/pierky/arouteserver/tests/live_tests/instances.py b/pierky/arouteserver/tests/live_tests/instances.py index c1ac4b79..85ec05d7 100644 --- a/pierky/arouteserver/tests/live_tests/instances.py +++ b/pierky/arouteserver/tests/live_tests/instances.py @@ -57,6 +57,24 @@ def reload_config(self): def run_cmd(self, args): raise NotImplementedError() + def get_bgp_session(self, other_inst, force_update=False): + """Get information about the BGP session with ``other_inst``. + + Args: + other_inst: the + :class:`BGPSpeakerInstance` + instance that the current instance is expected to have a + running BGP session with. + + force_update (bool): if True, the instance must bypass + any caching mechanism used to keep the BGP sessions status. + + Returns: + a dictionary containing information about the BGP session; + None if the BGP session is not found. + """ + raise NotImplementedError() + def bgp_session_is_up(self, other_inst, force_update=False): """Check if a BGP session with ``other_inst`` is up. diff --git a/templates/bird/main.j2 b/templates/bird/main.j2 index 1b913810..0a4d7f6b 100644 --- a/templates/bird/main.j2 +++ b/templates/bird/main.j2 @@ -20,3 +20,8 @@ include "*.local"; +{% if ip_ver == 4 %} +include "*.local4"; +{% else %} +include "*.local6"; +{% endif %} diff --git a/tests/live_tests/scenarios/global/base.py b/tests/live_tests/scenarios/global/base.py index 9dcc1bc3..d5c1f4f3 100644 --- a/tests/live_tests/scenarios/global/base.py +++ b/tests/live_tests/scenarios/global/base.py @@ -56,6 +56,10 @@ def _setup_instances(cls): cls.build_rs_cfg("bird", "main.j2", "rs.conf", cfg_roas="roas{}.yml".format(cls.IP_VER)), "/etc/bird/bird.conf" + ), + ( + cls.use_static_file("local_file.local{}".format(cls.IP_VER)), + "/etc/bird/local_file.local{}".format(cls.IP_VER) ) ], ), @@ -137,6 +141,10 @@ def test_020_sessions_up(self): self.session_is_up(self.AS101, self.AS1_2) self.session_is_up(self.AS101, self.AS2) + # A dummy session is configured using local include files. + # The following tests if those files are really included. + self.session_exists(self.rs, self.DATA["Dummy"]) + def test_030_good_prefixes_received_by_rs(self): """{}: good prefixes received by rs""" self.receive_route(self.rs, self.DATA["AS1_good1"], self.AS1_1) diff --git a/tests/live_tests/scenarios/global/local_file.local4 b/tests/live_tests/scenarios/global/local_file.local4 new file mode 100644 index 00000000..902fdff1 --- /dev/null +++ b/tests/live_tests/scenarios/global/local_file.local4 @@ -0,0 +1,7 @@ +# This is only used to verify that local files are included properly. +protocol bgp Dummy { + local as 999; + neighbor 192.0.2.99 as 65535; + import none; + export all; +} diff --git a/tests/live_tests/scenarios/global/local_file.local6 b/tests/live_tests/scenarios/global/local_file.local6 new file mode 100644 index 00000000..ae3d265e --- /dev/null +++ b/tests/live_tests/scenarios/global/local_file.local6 @@ -0,0 +1,7 @@ +# This is only used to verify that local files are included properly. +protocol bgp Dummy { + local as 999; + neighbor 2001:db8:1:1::999 as 65535; + import none; + export all; +} diff --git a/tests/live_tests/scenarios/global/test_bird4.py b/tests/live_tests/scenarios/global/test_bird4.py index fe127c50..38ff57ab 100644 --- a/tests/live_tests/scenarios/global/test_bird4.py +++ b/tests/live_tests/scenarios/global/test_bird4.py @@ -33,6 +33,7 @@ class BasicScenario_BIRDIPv4(BasicScenario): "AS1_2_IPAddress": "192.0.2.12", "AS2_1_IPAddress": "192.0.2.21", "AS101_IPAddress": "192.0.2.101", + "Dummy": "192.0.2.99", "AS1_allowed_prefixes": "1.0.0.0/8", "AS1_good1": "1.0.1.0/24", diff --git a/tests/live_tests/scenarios/global/test_bird6.py b/tests/live_tests/scenarios/global/test_bird6.py index 475a3c3f..b22c20cf 100644 --- a/tests/live_tests/scenarios/global/test_bird6.py +++ b/tests/live_tests/scenarios/global/test_bird6.py @@ -33,6 +33,7 @@ class BasicScenario_BIRDIPv6(BasicScenario): "AS1_2_IPAddress": "2001:db8:1:1::12", "AS2_1_IPAddress": "2001:db8:1:1::21", "AS101_IPAddress": "2001:db8:1:1::101", + "Dummy": "2001:db8:1:1::999", "AS1_allowed_prefixes": "2a01:0::/32", "AS1_good1": "2a01:0:1::/48",