diff --git a/README.md b/README.md index 9331055..6d15c14 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ These tests are run regularly against our public infrastructure as well as our i | **Router** | [test_internet_gateway](./test_router.py#L17) | default | | | [test_reverse_ptr_record_of_router](./test_router.py#L73) | default | | | [test_router_connected_private_networks](./test_router.py#L101) | default | +| | [test_peering_connected_private_networks](./test_router.py#L161) | default | | **Server** | [test_change_flavor_from_flex_to_flex](./test_server.py#L21) | default | | | [test_change_flavor_from_flex_to_plus](./test_server.py#L43) | default | | | [test_change_flavor_from_plus_to_flex](./test_server.py#L65) | default | diff --git a/api.py b/api.py index 8163eeb..4783a65 100644 --- a/api.py +++ b/api.py @@ -136,8 +136,8 @@ def on_response(self, response, *args, **kwargs): try: response.raise_for_status() except requests.exceptions.HTTPError as e: - ignore = e.request.method == 'DELETE' \ - and e.response.status_code == 404 + ignore = (e.request.method == 'DELETE' + and e.response.status_code == 404) if not ignore: raise e @@ -190,6 +190,10 @@ def cleanup(self, limit_to_scope=True, limit_to_process=True): # will be removed when interfaces become a toplevel resource if 'routers' in r['href']: + try: + self.patch(r['href'], json={"extra_routes": []}) + except Exception as e: + exceptions.append(e) for iface in r['interfaces']: href = f'routers/{r["uuid"]}/interfaces/{iface["uuid"]}' try: diff --git a/resources.py b/resources.py index e85adb2..f51ea89 100644 --- a/resources.py +++ b/resources.py @@ -76,6 +76,7 @@ def factory(cls, **defaults): server = new_focal_server(flavor='flex-4-1') """ + def create_resource_factory(**parameters): resource = cls(**{**defaults, **parameters}) resource.create() @@ -1133,13 +1134,13 @@ def network(self): class Volume(CloudscaleResource): def __init__( - self, - request, - api, - size=None, - zone=None, - volume_type='ssd', - volume_snapshot_uuid=None, + self, + request, + api, + size=None, + zone=None, + volume_type='ssd', + volume_snapshot_uuid=None, ): super().__init__(request, api) @@ -1348,7 +1349,6 @@ def wait_for_completion(self, seconds=120): continue if current_status == 'success': - return raise RuntimeError(f"Custom Image Import has unexpected status: " @@ -1509,7 +1509,6 @@ def verify_backend(self, prober, backend, count=1, port=None): class Router(CloudscaleResource): def __init__(self, request, api, name, zone, internet_gateway): - super().__init__(request, api) self.spec = { 'name': generate_server_name(request, name), @@ -1546,6 +1545,7 @@ def remove_interface(self, interface): # will be removed when interfaces become a toplevel resource def delete(self): + self.update(extra_routes=[]) for interface in self.interfaces: self.remove_interface(interface) self.api.delete(self.href) diff --git a/test_router.py b/test_router.py index 284e425..7f063d5 100644 --- a/test_router.py +++ b/test_router.py @@ -156,3 +156,96 @@ def test_router_connected_private_networks( # Each server can ping the other over private IPv4 s1.ping(s2.ip("private", 4), tries=5, wait=1) s2.ping(s1.ip("private", 4), tries=5, wait=1) + + +def test_peering_connected_private_networks( + create_router, + create_server, + create_jumphost, + create_private_network, + image, +): + """ Test to create two routers between three private networks. """ + + # Create a pair of routers + router_a = create_router(name='router-a') + router_b = create_router(name='router-b') + + # Create three private networks + private_network_a = create_private_network() + private_network_b = create_private_network() + private_network_c = create_private_network() # Peering Network + + subnet_a = private_network_a.add_subnet( + cidr="10.10.10.0/24", + gateway_address="10.10.10.1", + ) + subnet_b = private_network_b.add_subnet( + cidr="10.10.20.0/24", + gateway_address="10.10.20.1", + ) + subnet_c = private_network_c.add_subnet( + cidr="10.10.0.0/24", + ) + + # Attach the routers to their respective private and peering subnets + router_a.add_interface( + network=private_network_a.uuid, + subnet=subnet_a.uuid, + address="10.10.10.1" + ) + router_a.add_interface( + network=private_network_c.uuid, + subnet=subnet_c.uuid, + address="10.10.0.10" + ) + router_b.add_interface( + network=private_network_b.uuid, + subnet=subnet_b.uuid, + address="10.10.20.1" + ) + router_b.add_interface( + network=private_network_c.uuid, + subnet=subnet_c.uuid, + address="10.10.0.20" + ) + + # Add extra routes to correctly forward traffic + router_a.update(extra_routes=[ + { + "destination": "10.10.20.0/24", + "next_hop": "10.10.0.20" + } + ]) + router_b.update(extra_routes=[ + { + "destination": "10.10.10.0/24", + "next_hop": "10.10.0.10" + } + ]) + + # Create jumpost + jumphost = create_jumphost(private_networks=[ + private_network_a, + private_network_b + ]) + + # Create servers each connected only to its own private network + server_a, server_b = in_parallel(create_server, instances=( + { + "name": "s1", + "image": image, + "interfaces": [{"network": private_network_a.uuid}], + "jump_host": jumphost, + }, + { + "name": "s2", + "image": image, + "interfaces": [{"network": private_network_b.uuid}], + "jump_host": jumphost, + }, + )) + + # Each server can ping the other over private IPv4 + server_a.ping(server_b.ip("private", 4), tries=5, wait=1) + server_b.ping(server_a.ip("private", 4), tries=5, wait=1)