|
6 | 6 | from neutron_understack.routers import fetch_or_create_router_segment |
7 | 7 | from neutron_understack.routers import handle_router_interface_removal |
8 | 8 | from neutron_understack.routers import handle_subport_removal |
| 9 | +from neutron_understack.routers import link_vxlan_network_ha_chassis_group |
9 | 10 |
|
10 | 11 |
|
11 | 12 | class TestFetchOrCreateRouterSegment: |
@@ -202,3 +203,94 @@ def test_no_router_on_network(self, mocker, port_context): |
202 | 203 | create_uplink_port.assert_called_once_with( |
203 | 204 | fake_segment, port_context.current["network_id"] |
204 | 205 | ) |
| 206 | + |
| 207 | + |
| 208 | +class TestLinkVxlanNetworkHaChassisGroup: |
| 209 | + @staticmethod |
| 210 | + def _payload(mocker, router_id="router-1", port_id="port-1", network_id="net-1"): |
| 211 | + router = mocker.Mock() |
| 212 | + router.id = router_id |
| 213 | + return mocker.Mock( |
| 214 | + states=[router], |
| 215 | + metadata={"port": {"id": port_id, "network_id": network_id}}, |
| 216 | + ) |
| 217 | + |
| 218 | + @staticmethod |
| 219 | + def _client(mocker, router_hcg, lrp): |
| 220 | + nb_idl = mocker.MagicMock() |
| 221 | + |
| 222 | + def lookup(table, _name, default=None): |
| 223 | + if table == "HA_Chassis_Group": |
| 224 | + return router_hcg |
| 225 | + if table == "Logical_Router_Port": |
| 226 | + return lrp |
| 227 | + return default |
| 228 | + |
| 229 | + nb_idl.lookup.side_effect = lookup |
| 230 | + client = mocker.Mock(_nb_idl=nb_idl) |
| 231 | + return client, nb_idl |
| 232 | + |
| 233 | + def _patch_sync(self, mocker, hcg="net-hcg-uuid"): |
| 234 | + mocker.patch( |
| 235 | + "neutron_understack.routers.n_context.get_admin_context", |
| 236 | + return_value="ctx", |
| 237 | + ) |
| 238 | + return mocker.patch( |
| 239 | + "neutron_understack.routers.ovn_utils.sync_ha_chassis_group_network_unified", |
| 240 | + return_value=(hcg, "chassis-1"), |
| 241 | + ) |
| 242 | + |
| 243 | + def test_populates_network_hcg_and_anchors_lrp(self, mocker): |
| 244 | + hc = mocker.Mock(chassis_name="chassis-1", priority=10) |
| 245 | + router_hcg = mocker.Mock(ha_chassis=[hc], name="neutron-router-1") |
| 246 | + lrp = mocker.Mock(ha_chassis_group=[]) |
| 247 | + client, nb_idl = self._client(mocker, router_hcg, lrp) |
| 248 | + sync = self._patch_sync(mocker) |
| 249 | + mocker.patch("neutron_understack.routers.ovn_client", return_value=client) |
| 250 | + |
| 251 | + link_vxlan_network_ha_chassis_group(None, None, None, self._payload(mocker)) |
| 252 | + |
| 253 | + # Network HCG is populated from the router's chassis. |
| 254 | + assert sync.call_args.args[3] == "net-1" # network_id |
| 255 | + assert sync.call_args.args[4] == "router-1" # router_id |
| 256 | + assert sync.call_args.args[5] == {"chassis-1": 10} # chassis_prio |
| 257 | + # Internal LRP is anchored to the unified network HCG. |
| 258 | + nb_idl.db_set.assert_called_once_with( |
| 259 | + "Logical_Router_Port", |
| 260 | + "lrp-port-1", |
| 261 | + ("ha_chassis_group", "net-hcg-uuid"), |
| 262 | + ) |
| 263 | + |
| 264 | + def test_no_router_hcg(self, mocker): |
| 265 | + client, nb_idl = self._client(mocker, router_hcg=None, lrp=None) |
| 266 | + sync = self._patch_sync(mocker) |
| 267 | + mocker.patch("neutron_understack.routers.ovn_client", return_value=client) |
| 268 | + |
| 269 | + link_vxlan_network_ha_chassis_group(None, None, None, self._payload(mocker)) |
| 270 | + |
| 271 | + sync.assert_not_called() |
| 272 | + nb_idl.db_set.assert_not_called() |
| 273 | + |
| 274 | + def test_router_hcg_without_chassis(self, mocker): |
| 275 | + router_hcg = mocker.Mock(ha_chassis=[]) |
| 276 | + client, nb_idl = self._client(mocker, router_hcg, lrp=None) |
| 277 | + sync = self._patch_sync(mocker) |
| 278 | + mocker.patch("neutron_understack.routers.ovn_client", return_value=client) |
| 279 | + |
| 280 | + link_vxlan_network_ha_chassis_group(None, None, None, self._payload(mocker)) |
| 281 | + |
| 282 | + sync.assert_not_called() |
| 283 | + nb_idl.db_set.assert_not_called() |
| 284 | + |
| 285 | + def test_lrp_missing_still_populates_network_hcg(self, mocker): |
| 286 | + hc = mocker.Mock(chassis_name="chassis-1", priority=10) |
| 287 | + router_hcg = mocker.Mock(ha_chassis=[hc], name="neutron-router-1") |
| 288 | + client, nb_idl = self._client(mocker, router_hcg, lrp=None) |
| 289 | + sync = self._patch_sync(mocker) |
| 290 | + mocker.patch("neutron_understack.routers.ovn_client", return_value=client) |
| 291 | + |
| 292 | + link_vxlan_network_ha_chassis_group(None, None, None, self._payload(mocker)) |
| 293 | + |
| 294 | + # The network HCG is still populated even if the LRP is not found yet. |
| 295 | + sync.assert_called_once() |
| 296 | + nb_idl.db_set.assert_not_called() |
0 commit comments