From 1340d11084768c08b3432e4b5e05a2af6266dcaf Mon Sep 17 00:00:00 2001 From: Maciej Wilk Date: Tue, 22 Sep 2026 13:33:39 +0200 Subject: [PATCH 1/7] Add int tests for networking NAT Gateway endpoints --- .../models/networking/test_networking.py | 219 +++++++++++++++++- 1 file changed, 217 insertions(+), 2 deletions(-) diff --git a/test/integration/models/networking/test_networking.py b/test/integration/models/networking/test_networking.py index 18b6dabb1..02364a904 100644 --- a/test/integration/models/networking/test_networking.py +++ b/test/integration/models/networking/test_networking.py @@ -15,13 +15,26 @@ import pytest import requests -from linode_api4 import ApiError, Instance, LinodeClient +from linode_api4 import ( + ApiError, + Capability, + Instance, + InterfaceGeneration, + LinodeClient, +) from linode_api4.objects import ( Config, ConfigInterfaceIPv4, Firewall, IPAddress, + LinodeInterfaceDefaultRouteOptions, + LinodeInterfaceVPCIPv4AddressOptions, + LinodeInterfaceVPCIPv4Options, + LinodeInterfaceVPCIPv4RangeOptions, + LinodeInterfaceVPCOptions, + NATGateway, ReservedIPAddress, + VPCSubnetNATGatewayOptions, ) from linode_api4.objects.networking import ( FirewallCreateDevicesOptions, @@ -35,7 +48,23 @@ base_url=get_api_url(), ca_path=get_api_ca_file(), ), - {"Linodes", "Cloud Firewall"}, + {Capability.linodes, Capability.firewall}, + site_type="core", +) + +TEST_NAT_REGION = get_region( + LinodeClient( + token=get_token(), + base_url=get_api_url(), + ca_path=get_api_ca_file(), + ), + { + Capability.firewall, + Capability.linodes, + Capability.linode_interfaces, + Capability.natgateway, + Capability.vpcs, + }, site_type="core", ) @@ -127,6 +156,74 @@ def create_firewall_with_device(create_linode_without_firewall): firewall.delete() +@pytest.fixture +def create_nat_vpc(test_linode_client): + client = test_linode_client + label = get_test_label(length=10) + + vpc = client.vpcs.create( + label=label, + region=TEST_NAT_REGION, + description="test NAT Gateway VPC", + ipv6=[{"range": "auto"}], + ) + yield vpc + + vpc.delete() + + +@pytest.fixture +def create_nat_vpc_with_subnet(test_linode_client, create_nat_vpc): + subnet = create_nat_vpc.subnet_create( + label="test-nat-subnet", + ipv4="10.0.0.0/24", + ipv6=[{"range": "auto"}], + ) + + yield create_nat_vpc, subnet + + subnet.delete() + + +@pytest.fixture +def create_nat_vpc_with_subnet_and_linode( + test_linode_client, create_nat_vpc_with_subnet, e2e_test_firewall +): + vpc, subnet = create_nat_vpc_with_subnet + + instance = test_linode_client.linode.instance_create( + "g6-standard-1", + vpc.region, + label=get_test_label(length=8), + firewall=e2e_test_firewall, + interface_generation=InterfaceGeneration.LINODE, + booted=False, + ) + + yield vpc, subnet, instance + + instance.delete() + + +@pytest.fixture +def create_nat_gateway(request, test_linode_client): + client = test_linode_client + label = "nat-gateway-" + get_test_label(length=8) + autoscaling = getattr(request, "param", True) + + natgateway = client.networking.natgateway_create( + label=label, + region=TEST_NAT_REGION, + use_autoscaling=autoscaling, + ) + + yield natgateway + + # Delete only if NAT Gateway exists (some tests may delete it earlier) + if client.load(NATGateway, natgateway.id): + natgateway.delete() + + def test_get_networking_rule_versions(test_linode_client, test_firewall): firewall = test_linode_client.load(Firewall, test_firewall.id) @@ -555,3 +652,121 @@ def test_convert_unassigned_reserved_ip_to_ephemeral( ReservedIPAddress.address == reserved_ip.address ) assert len(reserved_ips_list) == 0 + + +@pytest.mark.smoke +def test_create_nat_gateway(test_linode_client, create_nat_gateway): + client = test_linode_client + gateway = client.load(NATGateway, create_nat_gateway.id) + + assert gateway.id == create_nat_gateway.id + assert gateway.region.id == create_nat_gateway.region.id + assert gateway.label == create_nat_gateway.label + + +def test_update_nat_gateway(test_linode_client, create_nat_gateway): + client = test_linode_client + gateway = create_nat_gateway + + new_label = f"{gateway.label}-updated" + gateway.label = new_label + gateway.save() + + gateway_updated = client.load(NATGateway, gateway.id) + + assert gateway_updated.label == new_label + + +@pytest.mark.parametrize("create_nat_gateway", [False], indirect=True) +def test_update_nat_gateway_with_ip_address( + test_linode_client, create_nat_gateway, create_reserved_ip +): + client = test_linode_client + gateway = client.load(NATGateway, create_nat_gateway.id) + reserved_ip = create_reserved_ip + + gateway.address_assignment_create(reserved_ip.address) + gateway = client.load(NATGateway, gateway.id) + + assert len(gateway.addresses) == 1 + assert gateway.addresses[0].address == reserved_ip.address + + +@pytest.mark.parametrize("create_nat_gateway", [False], indirect=True) +def test_get_nat_gateway_linode_ifaces( + test_linode_client, + e2e_test_firewall, + create_nat_gateway, + create_nat_vpc_with_subnet_and_linode, + create_reserved_ip, +): + client = test_linode_client + gateway = client.load(NATGateway, create_nat_gateway.id) + vpc, subnet, linode = create_nat_vpc_with_subnet_and_linode + reserved_ip = create_reserved_ip + + gateway.address_assignment_create(reserved_ip.address) + subnet.natgateway = VPCSubnetNATGatewayOptions(id=gateway.id) + subnet.save() + + linode.interface_create( + firewall_id=e2e_test_firewall, + default_route=LinodeInterfaceDefaultRouteOptions( + ipv4=True, + ), + vpc=LinodeInterfaceVPCOptions( + subnet_id=subnet.id, + ipv4=LinodeInterfaceVPCIPv4Options( + addresses=[ + LinodeInterfaceVPCIPv4AddressOptions( + address="auto", + primary=True, + nat_1_1_address=None, + ) + ], + ranges=[ + LinodeInterfaceVPCIPv4RangeOptions( + range="/32", + ) + ], + ), + ), + ) + + gateway_ifaces = gateway.interfaces() + linode = client.load(Instance, linode.id) + + assert len(gateway_ifaces) == 1 + assert gateway.vpc_subnet.id == subnet.id + assert gateway_ifaces[0].id == linode.linode_interfaces[0].id + assert gateway_ifaces[0].linode.id == linode.id + assert gateway_ifaces[0].addresses[0] == reserved_ip.address + assert gateway_ifaces[0].portsets[0].address == reserved_ip.address + + +def test_get_nat_gateway_types(test_linode_client): + client = test_linode_client + gateway_types = client.networking.natgateway_types() + + assert len(gateway_types) > 0 + assert gateway_types[0].id == "g1-natgateway" + assert gateway_types[0].label == "NAT Gateway" + assert ( + gateway_types[0].price.hourly >= 0 + or gateway_types[0].price.monthly >= 0 + ) + # region_prices and transfer fields are not in use at the moment + # assert isinstance(gateway_types[0].region_prices, list) + # assert isinstance(gateway_types[0].transfer, int) + + +def test_get_nat_gateway_settings(test_linode_client): + client = test_linode_client + gateway_settings = client.networking.natgateway_settings() + + assert all( + port in [4096, 8192, 16384] + for port in gateway_settings.allowed_ports_per_interface + ) + assert gateway_settings.maximum_autoscaling_addresses_per_natgateway > 0 + assert gateway_settings.maximum_reserved_addresses_per_natgateway > 0 From 1c3565fa7060f7cfbe826df8fafb9b0e42e0bef3 Mon Sep 17 00:00:00 2001 From: Maciej Wilk Date: Tue, 22 Sep 2026 16:00:33 +0200 Subject: [PATCH 2/7] Add int tests for VPC subnet with NAT Gateway --- test/integration/conftest.py | 50 +++++++++++++ .../models/networking/test_networking.py | 74 ++++--------------- test/integration/models/vpc/test_vpc.py | 28 ++++++- 3 files changed, 91 insertions(+), 61 deletions(-) diff --git a/test/integration/conftest.py b/test/integration/conftest.py index 998c9769a..c4b0ad82b 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -17,6 +17,7 @@ from requests.exceptions import ConnectionError, RequestException from linode_api4 import ( + Capability, Instance, InterfaceGeneration, IPAddress, @@ -25,6 +26,7 @@ LinodeInterfacePublicOptions, LinodeInterfaceVLANOptions, LinodeInterfaceVPCOptions, + NATGateway, PlacementGroupPolicy, PlacementGroupType, PostgreSQLDatabase, @@ -815,3 +817,51 @@ def create_reserved_ip_assigned(test_linode_client, create_linode): IPAddress.address == reserved_ip.address ): address[0].delete() + + +@pytest.fixture +def create_nat_gateway(request, test_linode_client): + client = test_linode_client + label = "nat-gateway-" + get_test_label(length=8) + autoscaling = getattr(request, "param", True) + region = get_region( + client, + { + Capability.firewall, + Capability.linodes, + Capability.linode_interfaces, + Capability.natgateway, + Capability.vpcs, + Capability.vpc_dual_stack, + Capability.vpc_ipv6_stack, + }, + ) + + gateway = client.networking.natgateway_create( + label=label, + region=region, + use_autoscaling=autoscaling, + ) + + yield gateway + + # Delete only if NAT Gateway exists (some tests may delete it earlier) + if client.load(NATGateway, gateway.id): + gateway.delete() + + +@pytest.fixture +def create_nat_vpc(test_linode_client, create_nat_gateway): + client = test_linode_client + gateway = create_nat_gateway + label = get_test_label(length=10) + + vpc = client.vpcs.create( + label=label, + region=gateway.region, + description="test NAT Gateway VPC", + ipv6=[{"range": "auto"}], + ) + yield vpc + + vpc.delete() diff --git a/test/integration/models/networking/test_networking.py b/test/integration/models/networking/test_networking.py index 02364a904..f51d1fea8 100644 --- a/test/integration/models/networking/test_networking.py +++ b/test/integration/models/networking/test_networking.py @@ -52,22 +52,6 @@ site_type="core", ) -TEST_NAT_REGION = get_region( - LinodeClient( - token=get_token(), - base_url=get_api_url(), - ca_path=get_api_ca_file(), - ), - { - Capability.firewall, - Capability.linodes, - Capability.linode_interfaces, - Capability.natgateway, - Capability.vpcs, - }, - site_type="core", -) - def create_linode_func(test_linode_client): client = test_linode_client @@ -156,22 +140,6 @@ def create_firewall_with_device(create_linode_without_firewall): firewall.delete() -@pytest.fixture -def create_nat_vpc(test_linode_client): - client = test_linode_client - label = get_test_label(length=10) - - vpc = client.vpcs.create( - label=label, - region=TEST_NAT_REGION, - description="test NAT Gateway VPC", - ipv6=[{"range": "auto"}], - ) - yield vpc - - vpc.delete() - - @pytest.fixture def create_nat_vpc_with_subnet(test_linode_client, create_nat_vpc): subnet = create_nat_vpc.subnet_create( @@ -205,25 +173,6 @@ def create_nat_vpc_with_subnet_and_linode( instance.delete() -@pytest.fixture -def create_nat_gateway(request, test_linode_client): - client = test_linode_client - label = "nat-gateway-" + get_test_label(length=8) - autoscaling = getattr(request, "param", True) - - natgateway = client.networking.natgateway_create( - label=label, - region=TEST_NAT_REGION, - use_autoscaling=autoscaling, - ) - - yield natgateway - - # Delete only if NAT Gateway exists (some tests may delete it earlier) - if client.load(NATGateway, natgateway.id): - natgateway.delete() - - def test_get_networking_rule_versions(test_linode_client, test_firewall): firewall = test_linode_client.load(Firewall, test_firewall.id) @@ -654,6 +603,14 @@ def test_convert_unassigned_reserved_ip_to_ephemeral( assert len(reserved_ips_list) == 0 +def verify_nat_gateway_ifaces(gateway_ifaces, linode, reserved_ip): + assert len(gateway_ifaces) == 1 + assert gateway_ifaces[0].id == linode.linode_interfaces[0].id + assert gateway_ifaces[0].linode.id == linode.id + assert gateway_ifaces[0].addresses[0] == reserved_ip.address + assert gateway_ifaces[0].portsets[0].address == reserved_ip.address + + @pytest.mark.smoke def test_create_nat_gateway(test_linode_client, create_nat_gateway): client = test_linode_client @@ -682,7 +639,7 @@ def test_update_nat_gateway_with_ip_address( test_linode_client, create_nat_gateway, create_reserved_ip ): client = test_linode_client - gateway = client.load(NATGateway, create_nat_gateway.id) + gateway = create_nat_gateway reserved_ip = create_reserved_ip gateway.address_assignment_create(reserved_ip.address) @@ -701,7 +658,7 @@ def test_get_nat_gateway_linode_ifaces( create_reserved_ip, ): client = test_linode_client - gateway = client.load(NATGateway, create_nat_gateway.id) + gateway = create_nat_gateway vpc, subnet, linode = create_nat_vpc_with_subnet_and_linode reserved_ip = create_reserved_ip @@ -732,16 +689,13 @@ def test_get_nat_gateway_linode_ifaces( ), ), ) + linode = client.load(Instance, linode.id) gateway_ifaces = gateway.interfaces() - linode = client.load(Instance, linode.id) + verify_nat_gateway_ifaces(gateway_ifaces, linode, reserved_ip) - assert len(gateway_ifaces) == 1 - assert gateway.vpc_subnet.id == subnet.id - assert gateway_ifaces[0].id == linode.linode_interfaces[0].id - assert gateway_ifaces[0].linode.id == linode.id - assert gateway_ifaces[0].addresses[0] == reserved_ip.address - assert gateway_ifaces[0].portsets[0].address == reserved_ip.address + gateway_ifaces = gateway.address_interfaces(reserved_ip.address) + verify_nat_gateway_ifaces(gateway_ifaces, linode, reserved_ip) def test_get_nat_gateway_types(test_linode_client): diff --git a/test/integration/models/vpc/test_vpc.py b/test/integration/models/vpc/test_vpc.py index 1fca6e712..745646242 100644 --- a/test/integration/models/vpc/test_vpc.py +++ b/test/integration/models/vpc/test_vpc.py @@ -2,7 +2,13 @@ import pytest -from linode_api4 import VPC, ApiError, VPCIPv4DefaultRange, VPCSubnet +from linode_api4 import ( + VPC, + ApiError, + VPCIPv4DefaultRange, + VPCSubnet, +) +from linode_api4.objects import VPCSubnetNATGatewayOptions @pytest.mark.smoke @@ -169,3 +175,23 @@ def test_vpc_with_ipv4(test_linode_client, create_vpc_with_ipv4): updated_vpc = client.load(VPC, vpc.id) assert updated_vpc.ipv4[0].range == "192.168.0.0/17" + + +def test_vpc_subnet_with_nat_gateway( + request, test_linode_client, create_nat_gateway, create_nat_vpc +): + client = test_linode_client + gateway = create_nat_gateway + vpc = create_nat_vpc + + subnet = create_nat_vpc.subnet_create( + label="test-nat-subnet", + ipv4="10.0.0.0/24", + ipv6=[{"range": "auto"}], + natgateway=VPCSubnetNATGatewayOptions(id=gateway.id), + ) + request.addfinalizer(subnet.delete) + + subnet = client.load(VPCSubnet, subnet.id, vpc.id) + + assert subnet.natgateway.id == gateway.id From 1776a6c1bfcbda1b7c65397857ffbd10a1a1929f Mon Sep 17 00:00:00 2001 From: Maciej Wilk Date: Tue, 22 Sep 2026 16:27:05 +0200 Subject: [PATCH 3/7] Remove condition for NAT gateway deletion --- test/integration/conftest.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/integration/conftest.py b/test/integration/conftest.py index c4b0ad82b..75a9dbb2c 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -26,7 +26,6 @@ LinodeInterfacePublicOptions, LinodeInterfaceVLANOptions, LinodeInterfaceVPCOptions, - NATGateway, PlacementGroupPolicy, PlacementGroupType, PostgreSQLDatabase, @@ -845,9 +844,7 @@ def create_nat_gateway(request, test_linode_client): yield gateway - # Delete only if NAT Gateway exists (some tests may delete it earlier) - if client.load(NATGateway, gateway.id): - gateway.delete() + gateway.delete() @pytest.fixture From bbbc4b58e6ef345e3a5682522f2fce0cfa94922a Mon Sep 17 00:00:00 2001 From: Maciej Wilk Date: Wed, 23 Sep 2026 10:58:53 +0200 Subject: [PATCH 4/7] Refactor NAT Gateway integration tests --- .../models/networking/test_networking.py | 77 ++++++++++++++----- test/integration/models/vpc/test_vpc.py | 3 +- 2 files changed, 58 insertions(+), 22 deletions(-) diff --git a/test/integration/models/networking/test_networking.py b/test/integration/models/networking/test_networking.py index f51d1fea8..2e1b6ed35 100644 --- a/test/integration/models/networking/test_networking.py +++ b/test/integration/models/networking/test_networking.py @@ -603,22 +603,47 @@ def test_convert_unassigned_reserved_ip_to_ephemeral( assert len(reserved_ips_list) == 0 -def verify_nat_gateway_ifaces(gateway_ifaces, linode, reserved_ip): - assert len(gateway_ifaces) == 1 - assert gateway_ifaces[0].id == linode.linode_interfaces[0].id - assert gateway_ifaces[0].linode.id == linode.id - assert gateway_ifaces[0].addresses[0] == reserved_ip.address - assert gateway_ifaces[0].portsets[0].address == reserved_ip.address +def verify_nat_gateway(nat_gateway, nat_gateway_read): + assert nat_gateway_read.id == nat_gateway.id + assert nat_gateway_read.region.id == nat_gateway.region.id + assert nat_gateway_read.label == nat_gateway.label + assert len(nat_gateway_read.addresses) == 0 + assert nat_gateway_read.address_autoscale_max > 0 + assert nat_gateway_read.default_ports_per_interface == 4096 + assert nat_gateway_read.portset_assignments == 0 + assert nat_gateway_read.portset_capacity > 0 + + +def verify_nat_gateway_address(nat_address, reserved_ip_address): + assert nat_address.address == reserved_ip_address + assert nat_address.in_use == False + assert nat_address.interface_count == 0 + assert nat_address.interface_url.endswith( + f"/{reserved_ip_address}/interfaces" + ) + assert nat_address.portset_assignments == 0 + assert nat_address.portset_capacity > 0 + + +def verify_nat_gateway_interface(interface, linode, reserved_ip): + assert interface.id == linode.linode_interfaces[0].id + assert interface.linode.id == linode.id + assert interface.addresses[0] == reserved_ip.address + assert interface.portsets[0].address == reserved_ip.address @pytest.mark.smoke def test_create_nat_gateway(test_linode_client, create_nat_gateway): client = test_linode_client + gateway = client.load(NATGateway, create_nat_gateway.id) + verify_nat_gateway(create_nat_gateway, gateway) - assert gateway.id == create_nat_gateway.id - assert gateway.region.id == create_nat_gateway.region.id - assert gateway.label == create_nat_gateway.label + gateways = client.networking.natgateways( + NATGateway.label == create_nat_gateway.label + ) + assert len(gateways) == 1 + verify_nat_gateway(create_nat_gateway, gateways[0]) def test_update_nat_gateway(test_linode_client, create_nat_gateway): @@ -630,23 +655,28 @@ def test_update_nat_gateway(test_linode_client, create_nat_gateway): gateway.save() gateway_updated = client.load(NATGateway, gateway.id) - assert gateway_updated.label == new_label @pytest.mark.parametrize("create_nat_gateway", [False], indirect=True) -def test_update_nat_gateway_with_ip_address( +def test_assign_nat_gateway_ip_address( test_linode_client, create_nat_gateway, create_reserved_ip ): - client = test_linode_client gateway = create_nat_gateway reserved_ip = create_reserved_ip gateway.address_assignment_create(reserved_ip.address) - gateway = client.load(NATGateway, gateway.id) - assert len(gateway.addresses) == 1 - assert gateway.addresses[0].address == reserved_ip.address + addresses = gateway.address_assignments() + assert len(addresses) == 1 + verify_nat_gateway_address(addresses[0], reserved_ip.address) + + address = gateway.address_assignment_view(reserved_ip.address) + verify_nat_gateway_address(address, reserved_ip.address) + + gateway.address_assignment_delete(reserved_ip.address) + addresses = gateway.address_assignments() + assert len(addresses) == 0 @pytest.mark.parametrize("create_nat_gateway", [False], indirect=True) @@ -663,6 +693,7 @@ def test_get_nat_gateway_linode_ifaces( reserved_ip = create_reserved_ip gateway.address_assignment_create(reserved_ip.address) + # Link the NAT Gateway to the VPC Subnet subnet.natgateway = VPCSubnetNATGatewayOptions(id=gateway.id) subnet.save() @@ -692,16 +723,22 @@ def test_get_nat_gateway_linode_ifaces( linode = client.load(Instance, linode.id) gateway_ifaces = gateway.interfaces() - verify_nat_gateway_ifaces(gateway_ifaces, linode, reserved_ip) + assert len(gateway_ifaces) == 1 + verify_nat_gateway_interface(gateway_ifaces[0], linode, reserved_ip) gateway_ifaces = gateway.address_interfaces(reserved_ip.address) - verify_nat_gateway_ifaces(gateway_ifaces, linode, reserved_ip) + verify_nat_gateway_interface(gateway_ifaces[0], linode, reserved_ip) + + address = gateway.address_assignment_view(reserved_ip.address) + assert address.in_use == True + assert address.interface_count == 1 + assert address.portset_assignments == 1 def test_get_nat_gateway_types(test_linode_client): client = test_linode_client - gateway_types = client.networking.natgateway_types() + gateway_types = client.networking.natgateway_types() assert len(gateway_types) > 0 assert gateway_types[0].id == "g1-natgateway" assert gateway_types[0].label == "NAT Gateway" @@ -709,15 +746,15 @@ def test_get_nat_gateway_types(test_linode_client): gateway_types[0].price.hourly >= 0 or gateway_types[0].price.monthly >= 0 ) - # region_prices and transfer fields are not in use at the moment + # TODO region_prices and transfer fields are not in use at the moment # assert isinstance(gateway_types[0].region_prices, list) # assert isinstance(gateway_types[0].transfer, int) def test_get_nat_gateway_settings(test_linode_client): client = test_linode_client - gateway_settings = client.networking.natgateway_settings() + gateway_settings = client.networking.natgateway_settings() assert all( port in [4096, 8192, 16384] for port in gateway_settings.allowed_ports_per_interface diff --git a/test/integration/models/vpc/test_vpc.py b/test/integration/models/vpc/test_vpc.py index 745646242..e2200dfd9 100644 --- a/test/integration/models/vpc/test_vpc.py +++ b/test/integration/models/vpc/test_vpc.py @@ -184,7 +184,7 @@ def test_vpc_subnet_with_nat_gateway( gateway = create_nat_gateway vpc = create_nat_vpc - subnet = create_nat_vpc.subnet_create( + subnet = vpc.subnet_create( label="test-nat-subnet", ipv4="10.0.0.0/24", ipv6=[{"range": "auto"}], @@ -193,5 +193,4 @@ def test_vpc_subnet_with_nat_gateway( request.addfinalizer(subnet.delete) subnet = client.load(VPCSubnet, subnet.id, vpc.id) - assert subnet.natgateway.id == gateway.id From f1074b52ed0ce23f27bd10b261275290ca9fd031 Mon Sep 17 00:00:00 2001 From: Maciej Wilk Date: Wed, 23 Sep 2026 11:34:03 +0200 Subject: [PATCH 5/7] Clean up --- .../models/networking/test_networking.py | 138 +++++++++--------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/test/integration/models/networking/test_networking.py b/test/integration/models/networking/test_networking.py index 2e1b6ed35..598b56bb1 100644 --- a/test/integration/models/networking/test_networking.py +++ b/test/integration/models/networking/test_networking.py @@ -87,18 +87,6 @@ def create_linode_to_be_shared_with_ips(test_linode_client): linode.delete() -@pytest.mark.smoke -def test_get_networking_rules(test_linode_client, test_firewall): - firewall = test_linode_client.load(Firewall, test_firewall.id) - - rules = firewall.get_rules() - - assert "inbound" in str(rules) - assert "inbound_policy" in str(rules) - assert "outbound" in str(rules) - assert "outbound_policy" in str(rules) - - @pytest.fixture def create_linode_without_firewall(test_linode_client): """ @@ -173,6 +161,75 @@ def create_nat_vpc_with_subnet_and_linode( instance.delete() +def verify_reserved_ip(reserved_ip): + assert isinstance( + ipaddress.ip_address(reserved_ip.address), ipaddress.IPv4Address + ) + assert reserved_ip.type == "ipv4" + assert reserved_ip.public == True + assert reserved_ip.reserved == True + assert reserved_ip.linode_id is None + assert reserved_ip.assigned_entity is None + + +def verify_reserved_ip_assigned(reserved_ip, resource): + assert isinstance( + ipaddress.ip_address(reserved_ip.address), ipaddress.IPv4Address + ) + assert reserved_ip.type == "ipv4" + assert reserved_ip.public == True + assert reserved_ip.reserved == True + assert reserved_ip.linode_id == resource.id + assert reserved_ip.region.id == resource.region.id + assert reserved_ip.assigned_entity.id == resource.id + assert reserved_ip.assigned_entity.type == "linode" + assert reserved_ip.assigned_entity.label == resource.label + assert ( + reserved_ip.assigned_entity.url == f"/v4/linode/instances/{resource.id}" + ) + + +def verify_nat_gateway(nat_gateway, nat_gateway_read): + assert nat_gateway_read.id == nat_gateway.id + assert nat_gateway_read.region.id == nat_gateway.region.id + assert nat_gateway_read.label == nat_gateway.label + assert len(nat_gateway_read.addresses) == 0 + assert nat_gateway_read.address_autoscale_max > 0 + assert nat_gateway_read.default_ports_per_interface == 4096 + assert nat_gateway_read.portset_assignments == 0 + assert nat_gateway_read.portset_capacity > 0 + + +def verify_nat_gateway_address(nat_address, reserved_ip_address): + assert nat_address.address == reserved_ip_address + assert nat_address.in_use == False + assert nat_address.interface_count == 0 + assert nat_address.interface_url.endswith( + f"/{reserved_ip_address}/interfaces" + ) + assert nat_address.portset_assignments == 0 + assert nat_address.portset_capacity > 0 + + +def verify_nat_gateway_interface(interface, linode, reserved_ip): + assert interface.id == linode.linode_interfaces[0].id + assert interface.linode.id == linode.id + assert interface.addresses[0] == reserved_ip.address + assert interface.portsets[0].address == reserved_ip.address + + +@pytest.mark.smoke +def test_get_networking_rules(test_linode_client, test_firewall): + firewall = test_linode_client.load(Firewall, test_firewall.id) + + rules = firewall.get_rules() + + assert "inbound" in str(rules) + assert "inbound_policy" in str(rules) + assert "outbound" in str(rules) + assert "outbound_policy" in str(rules) + + def test_get_networking_rule_versions(test_linode_client, test_firewall): firewall = test_linode_client.load(Firewall, test_firewall.id) @@ -408,34 +465,6 @@ def test_ip_info(test_linode_client, create_linode): assert ip_info.vpc_nat_1_1 is None -def verify_reserved_ip(reserved_ip): - assert isinstance( - ipaddress.ip_address(reserved_ip.address), ipaddress.IPv4Address - ) - assert reserved_ip.type == "ipv4" - assert reserved_ip.public == True - assert reserved_ip.reserved == True - assert reserved_ip.linode_id is None - assert reserved_ip.assigned_entity is None - - -def verify_reserved_ip_assigned(reserved_ip, resource): - assert isinstance( - ipaddress.ip_address(reserved_ip.address), ipaddress.IPv4Address - ) - assert reserved_ip.type == "ipv4" - assert reserved_ip.public == True - assert reserved_ip.reserved == True - assert reserved_ip.linode_id == resource.id - assert reserved_ip.region.id == resource.region.id - assert reserved_ip.assigned_entity.id == resource.id - assert reserved_ip.assigned_entity.type == "linode" - assert reserved_ip.assigned_entity.label == resource.label - assert ( - reserved_ip.assigned_entity.url == f"/v4/linode/instances/{resource.id}" - ) - - @pytest.mark.smoke @pytest.mark.parametrize( "region, tags, expected", @@ -603,35 +632,6 @@ def test_convert_unassigned_reserved_ip_to_ephemeral( assert len(reserved_ips_list) == 0 -def verify_nat_gateway(nat_gateway, nat_gateway_read): - assert nat_gateway_read.id == nat_gateway.id - assert nat_gateway_read.region.id == nat_gateway.region.id - assert nat_gateway_read.label == nat_gateway.label - assert len(nat_gateway_read.addresses) == 0 - assert nat_gateway_read.address_autoscale_max > 0 - assert nat_gateway_read.default_ports_per_interface == 4096 - assert nat_gateway_read.portset_assignments == 0 - assert nat_gateway_read.portset_capacity > 0 - - -def verify_nat_gateway_address(nat_address, reserved_ip_address): - assert nat_address.address == reserved_ip_address - assert nat_address.in_use == False - assert nat_address.interface_count == 0 - assert nat_address.interface_url.endswith( - f"/{reserved_ip_address}/interfaces" - ) - assert nat_address.portset_assignments == 0 - assert nat_address.portset_capacity > 0 - - -def verify_nat_gateway_interface(interface, linode, reserved_ip): - assert interface.id == linode.linode_interfaces[0].id - assert interface.linode.id == linode.id - assert interface.addresses[0] == reserved_ip.address - assert interface.portsets[0].address == reserved_ip.address - - @pytest.mark.smoke def test_create_nat_gateway(test_linode_client, create_nat_gateway): client = test_linode_client From 928e1ec96908ecb34b347e3d64e0c26af54bdb08 Mon Sep 17 00:00:00 2001 From: Maciej Wilk Date: Thu, 24 Sep 2026 09:35:05 +0200 Subject: [PATCH 6/7] Add fixture for Reserved IP created in the NAT Gateway region --- .../models/networking/test_networking.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/test/integration/models/networking/test_networking.py b/test/integration/models/networking/test_networking.py index 598b56bb1..8b7999d68 100644 --- a/test/integration/models/networking/test_networking.py +++ b/test/integration/models/networking/test_networking.py @@ -128,6 +128,18 @@ def create_firewall_with_device(create_linode_without_firewall): firewall.delete() +@pytest.fixture +def create_nat_reserved_ip(test_linode_client, create_nat_gateway): + client = test_linode_client + gateway = create_nat_gateway + + reserved_ip = client.networking.reserved_ip_create(region=gateway.region) + + yield reserved_ip + + reserved_ip.delete() + + @pytest.fixture def create_nat_vpc_with_subnet(test_linode_client, create_nat_vpc): subnet = create_nat_vpc.subnet_create( @@ -660,10 +672,10 @@ def test_update_nat_gateway(test_linode_client, create_nat_gateway): @pytest.mark.parametrize("create_nat_gateway", [False], indirect=True) def test_assign_nat_gateway_ip_address( - test_linode_client, create_nat_gateway, create_reserved_ip + test_linode_client, create_nat_gateway, create_nat_reserved_ip ): gateway = create_nat_gateway - reserved_ip = create_reserved_ip + reserved_ip = create_nat_reserved_ip gateway.address_assignment_create(reserved_ip.address) @@ -685,12 +697,12 @@ def test_get_nat_gateway_linode_ifaces( e2e_test_firewall, create_nat_gateway, create_nat_vpc_with_subnet_and_linode, - create_reserved_ip, + create_nat_reserved_ip, ): client = test_linode_client gateway = create_nat_gateway vpc, subnet, linode = create_nat_vpc_with_subnet_and_linode - reserved_ip = create_reserved_ip + reserved_ip = create_nat_reserved_ip gateway.address_assignment_create(reserved_ip.address) # Link the NAT Gateway to the VPC Subnet From 3aee09be10b6a27c3dab71d62aff795beab0387c Mon Sep 17 00:00:00 2001 From: Maciej Wilk Date: Thu, 24 Sep 2026 11:24:01 +0200 Subject: [PATCH 7/7] Add test for NAT Gateway and Reserved IP in diff regions --- .../models/networking/test_networking.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/integration/models/networking/test_networking.py b/test/integration/models/networking/test_networking.py index 8b7999d68..2251566fa 100644 --- a/test/integration/models/networking/test_networking.py +++ b/test/integration/models/networking/test_networking.py @@ -773,3 +773,29 @@ def test_get_nat_gateway_settings(test_linode_client): ) assert gateway_settings.maximum_autoscaling_addresses_per_natgateway > 0 assert gateway_settings.maximum_reserved_addresses_per_natgateway > 0 + + +@pytest.mark.skip(reason="VPC-6253") +@pytest.mark.parametrize("create_nat_gateway", [False], indirect=True) +def test_assign_nat_gateway_ip_address_in_diff_region_fail( + test_linode_client, create_nat_gateway +): + client = test_linode_client + gateway = create_nat_gateway + region = get_region( + client, {Capability.linodes, Capability.firewall}, site_type="core" + ) + + while region == gateway.region: + region = get_region( + client, {Capability.linodes, Capability.firewall}, site_type="core" + ) + + reserved_ip = client.networking.reserved_ip_create(region=region) + + with pytest.raises(RuntimeError) as err: + gateway.address_assignment_create(reserved_ip.address) + assert "[400] ..." in str(err.value) + + addresses = gateway.address_assignments() + assert len(addresses) == 0