Coverage for src/birdplan/bird_config/sections/protocols/ospf/ospf_config_parser.py: 79%
87 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-25 07:38 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-25 07:38 +0000
1#
2# SPDX-License-Identifier: GPL-3.0-or-later
3#
4# Copyright (c) 2019-2025, AllWorldIT
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
19"""BIRD OSPF configuration parser."""
21from typing import Any
23from .....exceptions import BirdPlanConfigError
24from .... import BirdConfig
25from ....config_parser import ConfigParser
27__all__ = ["OSPFConfigParser"]
30class OSPFConfigParser(ConfigParser):
31 """OSPF configuration parser."""
33 _birdconf: BirdConfig
35 def parse(self, config: dict[str, Any]) -> None:
36 """Configure OSPF protocol."""
38 self._config_ospf(config)
40 def _config_ospf(self, config: dict[str, Any]) -> None:
41 """Configure OSPF section."""
43 # If we have no ospf section, just return
44 if "ospf" not in config:
45 return
47 # Check configuration options are supported
48 for config_item in config["ospf"]:
49 if config_item not in ("accept", "redistribute", "areas", "v4version"):
50 raise BirdPlanConfigError(f"The 'ospf' config item '{config_item}' is not supported")
52 # Check what version of OSPF we're using for IPv4
53 if "v4version" in config["ospf"]:
54 if isinstance(config["ospf"]["v4version"], int | str):
55 v4version = f"{config['ospf']['v4version']}"
56 if v4version in ("2", "3"):
57 self.birdconf.protocols.ospf.v4version = v4version
58 else:
59 raise BirdPlanConfigError("The 'ospf' config item 'v4version' has unsupported value")
60 else:
61 raise BirdPlanConfigError("The 'ospf' config item 'v4version' has unsupported type")
63 self._config_ospf_accept(config)
64 self._config_ospf_redistribute(config)
65 self._config_ospf_areas(config)
67 def _config_ospf_accept(self, config: dict[str, Any]) -> None:
68 """Configure ospf:accept section."""
70 # If we don't have an accept section, just return
71 if "accept" not in config["ospf"]:
72 return
74 # Loop with accept items
75 for accept, accept_config in config["ospf"]["accept"].items():
76 # Allow accept of the default route
77 if accept == "default":
78 self.birdconf.protocols.ospf.route_policy_accept.default = accept_config
79 # If we don't understand this 'accept' entry, throw an error
80 else:
81 raise BirdPlanConfigError(f"Configuration item '{accept}' not understood in ospf:accept")
83 def _config_ospf_redistribute(self, config: dict[str, Any]) -> None: # noqa: C901,PLR0912
84 """Configure ospf:redistribute section."""
86 # If we don't have a redistribute section just return
87 if "redistribute" not in config["ospf"]:
88 return
90 # Loop with redistribution items
91 for redistribute, redistribute_config in config["ospf"]["redistribute"].items():
92 # Add static route redistribution
93 if redistribute == "static":
94 self.birdconf.protocols.ospf.route_policy_redistribute.static = redistribute_config
95 # Add connected route redistribution
96 elif redistribute == "connected":
97 # Set type
98 redistribute_connected: bool | list[str]
99 # Check what kind of config we go...
100 if isinstance(redistribute_config, bool):
101 redistribute_connected = redistribute_config
102 # Else if its a dict, we need to treat it a bit differently
103 elif isinstance(redistribute_config, dict):
104 # Check it has an "interfaces" key
105 if "interfaces" not in redistribute_config:
106 raise BirdPlanConfigError(
107 f"Configurion item '{redistribute}' has no 'interfaces' option in ospf:redistribute"
108 )
109 # If it does, check that it is a list
110 if not isinstance(redistribute_config["interfaces"], list):
111 raise BirdPlanConfigError(
112 f"Configurion item '{redistribute}:interfaces' has an invalid type in ospf:redistribute"
113 )
114 # Set redistribute_connected as the interface list
115 redistribute_connected = redistribute_config["interfaces"]
116 else:
117 raise BirdPlanConfigError(f"Configurion item '{redistribute}' has an unsupported value")
118 # Add configuration
119 self.birdconf.protocols.ospf.route_policy_redistribute.connected = redistribute_connected
120 # Add kernel route redistribution
121 elif redistribute == "kernel":
122 self.birdconf.protocols.ospf.route_policy_redistribute.kernel = redistribute_config
123 # Add kernel default route redistribution
124 elif redistribute == "kernel_default":
125 self.birdconf.protocols.ospf.route_policy_redistribute.kernel_default = redistribute_config
126 # Add static route redistribution
127 elif redistribute == "static":
128 self.birdconf.protocols.ospf.route_policy_redistribute.static = redistribute_config
129 # Add static default route redistribution
130 elif redistribute == "static_default":
131 self.birdconf.protocols.ospf.route_policy_redistribute.static_default = redistribute_config
132 # If we don't understand this 'redistribute' entry, throw an error
133 else:
134 raise BirdPlanConfigError(f"Configuration item '{redistribute}' not understood in ospf:redistribute")
136 def _config_ospf_areas(self, config: dict[str, Any]) -> None: # noqa: C901,PLR0912
137 """Configure ospf:interfaces section."""
139 # If we don't have areas in our ospf section, just return
140 if "areas" not in config["ospf"]:
141 return
143 # Loop with each area and its config
144 for area_name, raw_area_config in config["ospf"]["areas"].items():
145 # Make sure we have an interface for the area
146 if "interfaces" not in raw_area_config:
147 raise BirdPlanConfigError(f"OSPF area '{area_name}' must contain 'interfaces'")
148 # Loop with each config item
149 area_config = {}
150 for config_item, raw_config in raw_area_config.items():
151 # Make sure this item is supported
152 if config_item not in ("xxxxxxx", "interfaces"):
153 raise BirdPlanConfigError(
154 f"Configuration item '{config_item}' with value '{raw_config}' not understood in ospf:areas"
155 )
156 # Skip over interfaces
157 if config_item == "interfaces":
158 continue
159 # Check for supported config options
160 if config_item in ("xxxxx", "yyyy"):
161 area_config[config_item] = raw_config
162 # If we don't understand this 'redistribute' entry, throw an error
163 else:
164 raise BirdPlanConfigError(f"Configuration item '{config_item}' not understood in ospf:areas")
166 # Add area
167 area = self.birdconf.protocols.ospf.add_area(area_name, area_config)
169 # Loop with interfaces in area
170 for interface_name, raw_config in raw_area_config["interfaces"].items():
171 # Start with no special interface configuration
172 interface_config: dict[str, Any] = {}
173 # Check what kind of config we've got...
174 add_ospf_interface = False
175 if isinstance(raw_config, bool):
176 add_ospf_interface = raw_config
177 # Else if its a dict, we need to treat it a bit differently
178 elif isinstance(raw_config, dict):
179 add_ospf_interface = True
180 for raw_item, raw_value in raw_config.items():
181 if raw_item in ("cost", "ecmp_weight", "hello", "stub", "wait"):
182 interface_config[raw_item] = raw_value
183 else:
184 raise BirdPlanConfigError(
185 f"Configuration item '{raw_item}' not understood in OSPF interface '{interface_name}'"
186 )
187 else:
188 raise BirdPlanConfigError(
189 f"Configurion for OSPF interface name '{interface_name}' has an unsupported type: '{type(interface_name)}'"
190 )
192 # Add interface to area
193 if add_ospf_interface:
194 area.add_interface(interface_name, interface_config)