Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e5cd701
TPT-4278 python-sdk: Implement support for Reserved IP for IPv4
mgwoj Mar 26, 2026
9b0629f
TPT-4278: python-sdk: Implement support for Reserved IP for IPv4
mgwoj Apr 15, 2026
aeca38a
Create int tests for Reserved IPs networking endpoints
mawilk90 Apr 24, 2026
d87ddc5
Create int tests for Reserved IPs: types, allocate
mawilk90 Apr 24, 2026
d43652b
Create int tests for Reserved IPs: ephemeral
mawilk90 Apr 24, 2026
b2489c2
Create int tests for Reserved IPs: linode instances
mawilk90 Apr 24, 2026
18a8081
Move fixtures for reserved IPs into conftest
mawilk90 Apr 27, 2026
c76a33f
Create int tests for Reserved IPs: linode interfaces
mawilk90 Apr 27, 2026
bb5f435
Create int tests for Reserved IPs: nodebalancers
mawilk90 Apr 27, 2026
1a2cdb5
Create int tests for Reserved IPs: tags
mawilk90 Apr 27, 2026
818ee6c
Refactor
mawilk90 Apr 27, 2026
92cf8c6
Update tests after API changes on DevCloud
mawilk90 Apr 28, 2026
6b0047b
Create int tests for Reserved IPs: tags #2
mawilk90 Apr 28, 2026
e0fe178
Linter fix
mawilk90 Apr 28, 2026
cddf54b
Linter fix
mawilk90 Apr 28, 2026
4af8f10
Remove pytest.ini
mawilk90 Apr 28, 2026
aea1095
Remove unused assertions after API Team clarifications
mawilk90 Apr 28, 2026
ad3d036
Use reservedIP's region for NB
mawilk90 Apr 30, 2026
be8a783
Refactor
mawilk90 Apr 30, 2026
298a0f1
Address Copilot remarks
mawilk90 May 4, 2026
f28059d
Linter fix
mawilk90 May 4, 2026
dad4334
Revert capabilities' changes in get_regions
mawilk90 May 4, 2026
4b0f363
Linter fixes
mawilk90 May 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions test/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
PlacementGroupPolicy,
PlacementGroupType,
PostgreSQLDatabase,
ReservedIPAddress,
)
from linode_api4.errors import ApiError
from linode_api4.linode_client import LinodeClient, MonitorClient
Expand Down Expand Up @@ -727,3 +728,47 @@ def test_monitor_client(get_monitor_token_for_db_entities):
)

return client, entity_ids


@pytest.fixture
def create_reserved_ip(test_linode_client):
client = test_linode_client
region = get_region(client, {"Linodes", "Cloud Firewall"}, site_type="core")
reserved_ip = client.networking.reserved_ip_create(
region=region, tags=["test"]
)

yield reserved_ip

# Delete only if IP exists (some tests delete it earlier)
if client.networking.reserved_ips(
ReservedIPAddress.address == reserved_ip.address
):
reserved_ip.delete()


@pytest.fixture
def create_reserved_ip_assigned(test_linode_client, create_linode):
client = test_linode_client
linode = create_linode
reserved_ip = client.networking.reserved_ip_create(
region=linode.region,
tags=["test", "assigned"],
)

client.networking.ip_addresses_assign(
assignments=[{"address": reserved_ip.address, "linode_id": linode.id}],
Comment thread
mawilk90 marked this conversation as resolved.
region=linode.region,
)

reserved_ip = test_linode_client.load(
ReservedIPAddress, reserved_ip.address
)

yield linode, reserved_ip

# Delete only if IP exists (some tests delete it earlier)
if client.networking.reserved_ips(
ReservedIPAddress.address == reserved_ip.address
):
reserved_ip.delete()
78 changes: 78 additions & 0 deletions test/integration/models/linode/interfaces/test_interfaces.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import copy
import ipaddress
from test.integration.helpers import get_test_label

import pytest

from linode_api4 import (
ApiError,
Instance,
InterfaceGeneration,
LinodeInterface,
LinodeInterfaceDefaultRouteOptions,
LinodeInterfaceOptions,
LinodeInterfacePublicIPv4AddressOptions,
LinodeInterfacePublicIPv4Options,
LinodeInterfacePublicIPv6Options,
Expand All @@ -18,9 +21,28 @@
LinodeInterfaceVPCIPv4Options,
LinodeInterfaceVPCIPv4RangeOptions,
LinodeInterfaceVPCOptions,
ReservedIPAddress,
)


def build_interface_public_ipv4(firewall, ip_address):
return LinodeInterfaceOptions(
firewall_id=firewall,
default_route=LinodeInterfaceDefaultRouteOptions(
ipv4=True,
),
public=LinodeInterfacePublicOptions(
ipv4=LinodeInterfacePublicIPv4Options(
addresses=[
LinodeInterfacePublicIPv4AddressOptions(
address=ip_address, primary=True
)
],
),
),
)


def test_linode_create_with_linode_interfaces(
create_vpc_with_subnet,
linode_with_linode_interfaces,
Expand Down Expand Up @@ -359,3 +381,59 @@ def test_linode_interface_firewalls(e2e_test_firewall, linode_interface_public):
firewall = firewalls[0]
assert firewall.id == e2e_test_firewall.id
assert firewall.label == e2e_test_firewall.label


@pytest.mark.parametrize(
"iface_type",
[InterfaceGeneration.LEGACY_CONFIG, InterfaceGeneration.LINODE],
)
def test_linode_interfaces_with_reserved_ips(
test_linode_client, e2e_test_firewall, create_reserved_ip, iface_type
):
client = test_linode_client
reserved_ip = create_reserved_ip
label = get_test_label(length=8)

if iface_type == InterfaceGeneration.LEGACY_CONFIG:
linode, _ = client.linode.instance_create(
"g6-nanode-1",
reserved_ip.region,
image="linode/debian12",
label=label,
firewall=e2e_test_firewall,
interface_generation=iface_type,
ipv4=[reserved_ip.address],
)
else:
interface = build_interface_public_ipv4(
e2e_test_firewall.id, reserved_ip.address
)
linode, _ = client.linode.instance_create(
"g6-nanode-1",
reserved_ip.region,
image="linode/debian12",
label=label,
interface_generation=iface_type,
interfaces=[interface],
)

linode_ips = linode.ips.ipv4.public
assert len(linode_ips) == 1
assert linode_ips[0].address == reserved_ip.address
assert linode_ips[0].reserved == True
assert linode_ips[0].linode_id == linode.id
assert linode_ips[0].assigned_entity.id == linode.id
assert linode_ips[0].assigned_entity.type == "linode"
assert linode_ips[0].assigned_entity.label == linode.label
assert (
linode_ips[0].assigned_entity.url == f"/v4/linode/instances/{linode.id}"
)

linode.delete()
reserved_ips_list = client.networking.reserved_ips(
ReservedIPAddress.address == reserved_ip.address
)
assert len(reserved_ips_list) == 1
assert reserved_ips_list[0].reserved == True
assert reserved_ips_list[0].linode_id is None
assert reserved_ips_list[0].assigned_entity is None
40 changes: 40 additions & 0 deletions test/integration/models/linode/test_linode.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
Instance,
InterfaceGeneration,
LinodeInterface,
ReservedIPAddress,
Type,
)
from linode_api4.objects.linode import InstanceDiskEncryptionType, MigrationType
Expand Down Expand Up @@ -1156,3 +1157,42 @@ def test_update_linode_maintenance_policy(create_linode, test_linode_client):
linode.invalidate()
assert result
assert linode.maintenance_policy_id == non_default_policy.slug


def test_update_linode_with_reserved_ip_in_address(
test_linode_client, e2e_test_firewall, create_reserved_ip
):
label = get_test_label(length=8)
client = test_linode_client
reserved_ip = create_reserved_ip

linode, _ = client.linode.instance_create(
"g6-nanode-1",
reserved_ip.region,
image="linode/debian12",
label=label,
firewall=e2e_test_firewall,
)

linode_ips = linode.ips.ipv4.public
assert len(linode_ips) == 1
assert linode_ips[0].address != reserved_ip.address

linode.ip_allocate(True, reserved_ip.address)
delattr(linode, "_ips")
linode_ips = linode.ips.ipv4.public
assert len(linode_ips) == 2
assert reserved_ip.address in [ip.address for ip in linode_ips]

reserved_ip = client.networking.reserved_ips(
ReservedIPAddress.address == reserved_ip.address
)[0]
assert reserved_ip.linode_id == linode.id
assert reserved_ip.assigned_entity.id == linode.id
assert reserved_ip.assigned_entity.type == "linode"
assert reserved_ip.assigned_entity.label == linode.label
assert (
reserved_ip.assigned_entity.url == f"/v4/linode/instances/{linode.id}"
)

linode.delete()
Loading
Loading