Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 35 additions & 1 deletion linode_api4/groups/region.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from linode_api4.groups import Group
from linode_api4.objects import Region
from linode_api4.objects.region import RegionAvailabilityEntry
from linode_api4.objects.region import (
RegionAvailabilityEntry,
RegionVPCAvailability,
)


class RegionGroup(Group):
Expand Down Expand Up @@ -43,3 +46,34 @@ def availability(self, *filters):
return self.client._get_and_filter(
RegionAvailabilityEntry, *filters, endpoint="/regions/availability"
)

def vpc_availability(self, *filters):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: vpc_availabilities. Normally we use plural forms to indicate it's an list endpoints.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but then it will be inconsistent with the naming of availability method

"""
Returns VPC availability data for all regions.

NOTE: IPv6 VPCs may not currently be available to all users.

This endpoint supports pagination with the following parameters:
- page: Page number (>= 1)
- page_size: Number of items per page (25-500)

Pagination is handled automatically by PaginatedList. To configure page_size,
set it when creating the LinodeClient:

client = LinodeClient(token, page_size=100)

API Documentation: https://techdocs.akamai.com/linode-api/reference/get-regions-vpc-availability

:param filters: Any number of filters to apply to this query.
See :doc:`Filtering Collections</linode_api4/objects/filtering>`
for more details on filtering.

:returns: A list of VPC availability data for regions.
:rtype: PaginatedList of RegionVPCAvailability
"""

return self.client._get_and_filter(
RegionVPCAvailability,
*filters,
endpoint="/regions/vpc-availability",
)
38 changes: 38 additions & 0 deletions linode_api4/objects/region.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,29 @@ def availability(self) -> List["RegionAvailabilityEntry"]:

return [RegionAvailabilityEntry.from_json(v) for v in result]

@property
def vpc_availability(self) -> "RegionVPCAvailability":
"""
Returns VPC availability data for this region.

NOTE: IPv6 VPCs may not currently be available to all users.

API Documentation: https://techdocs.akamai.com/linode-api/reference/get-region-vpc-availability

:returns: VPC availability data for this region.
:rtype: RegionVPCAvailability
"""
result = self._client.get(
f"{self.api_endpoint}/vpc-availability", model=self
)

if result is None:
raise UnexpectedResponseError(
"Expected VPC availability data, got None."
)

return RegionVPCAvailability.from_json(result)


@dataclass
class RegionAvailabilityEntry(JSONObject):
Expand All @@ -137,3 +160,18 @@ class RegionAvailabilityEntry(JSONObject):
region: Optional[str] = None
plan: Optional[str] = None
available: bool = False


@dataclass
class RegionVPCAvailability(JSONObject):
"""
Represents the VPC availability data for a region.

API Documentation: https://techdocs.akamai.com/linode-api/reference/get-regions-vpc-availability

NOTE: IPv6 VPCs may not currently be available to all users.
"""

region: Optional[str] = None
available: bool = False
available_ipv6_prefix_lengths: Optional[List[int]] = None
5 changes: 5 additions & 0 deletions test/fixtures/regions_us-east_vpc-availability.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"region": "us-east",
"available": true,
"available_ipv6_prefix_lengths": [52, 48]
}
132 changes: 132 additions & 0 deletions test/fixtures/regions_vpc-availability.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
{
"data": [
{
"region": "us-east",
"available": true,
"available_ipv6_prefix_lengths": [52, 48]
},
{
"region": "us-west",
"available": true,
"available_ipv6_prefix_lengths": [56, 52, 48]
},
{
"region": "nl-ams",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "us-ord",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "us-iad",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "fr-par",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "us-sea",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "br-gru",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "se-sto",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "es-mad",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "in-maa",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "jp-osa",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "it-mil",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "us-mia",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "id-cgk",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "us-lax",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "gb-lon",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "au-mel",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "in-bom-2",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "de-fra-2",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "sg-sin-2",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "jp-tyo-3",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "fr-par-2",
"available": true,
"available_ipv6_prefix_lengths": []
},
{
"region": "ca-central",
"available": false,
"available_ipv6_prefix_lengths": []
},
{
"region": "ap-southeast",
"available": false,
"available_ipv6_prefix_lengths": []
}
],
"page": 1,
"pages": 2,
"results": 50
}
62 changes: 62 additions & 0 deletions test/integration/models/region/test_region.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import pytest

from linode_api4.objects import Region


@pytest.mark.smoke
def test_list_regions_vpc_availability(test_linode_client):
"""
Test listing VPC availability for all regions.
"""
client = test_linode_client

vpc_availability = client.regions.vpc_availability()

assert len(vpc_availability) > 0

for entry in vpc_availability:
assert entry.region is not None
assert len(entry.region) > 0
assert entry.available is not None
assert isinstance(entry.available, bool)
# available_ipv6_prefix_lengths may be empty list but should exist
assert entry.available_ipv6_prefix_lengths is not None
assert isinstance(entry.available_ipv6_prefix_lengths, list)


@pytest.mark.smoke
def test_get_region_vpc_availability_via_object(test_linode_client):
"""
Test getting VPC availability via the Region object property.
"""
client = test_linode_client

# Get the first available region
regions = client.regions()
assert len(regions) > 0
test_region_id = regions[0].id

region = Region(client, test_region_id)
vpc_avail = region.vpc_availability

assert vpc_avail is not None
assert vpc_avail.region == test_region_id
assert vpc_avail.available is not None
assert isinstance(vpc_avail.available, bool)
assert vpc_avail.available_ipv6_prefix_lengths is not None
assert isinstance(vpc_avail.available_ipv6_prefix_lengths, list)


def test_vpc_availability_available_regions(test_linode_client):
"""
Test that some regions have VPC availability enabled.
"""
client = test_linode_client

vpc_availability = client.regions.vpc_availability()

# Filter for regions where VPC is available
available_regions = [v for v in vpc_availability if v.available]

# There should be at least some regions with VPC available
assert len(available_regions) > 0
30 changes: 27 additions & 3 deletions test/unit/groups/region_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ def test_list_availability(self):
for entry in avail_entries:
assert entry.region is not None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intended to remove not None checks? It looks the fields are Optional

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but it is redundant with next check:

                assert len(entry.region) > 0

assert len(entry.region) > 0

assert entry.plan is not None
assert len(entry.plan) > 0

assert entry.available is not None

# Ensure all three pages are read
Expand All @@ -49,3 +46,30 @@ def test_list_availability(self):
assert json.loads(call.get("headers").get("X-Filter")) == {
"+and": [{"region": "us-east"}, {"plan": "premium4096.7"}]
}

def test_list_vpc_availability(self):
"""
Tests that region VPC availability can be listed.
"""

with self.mock_get("/regions/vpc-availability") as m:
vpc_entries = self.client.regions.vpc_availability()

assert len(vpc_entries) > 0

for entry in vpc_entries:
assert len(entry.region) > 0
assert entry.available is not None
# available_ipv6_prefix_lengths may be empty list but should exist
assert entry.available_ipv6_prefix_lengths is not None

# Ensure both pages are read
assert m.call_count == 2
assert (
m.mock.call_args_list[0].args[0] == "//regions/vpc-availability"
)

assert (
m.mock.call_args_list[1].args[0]
== "//regions/vpc-availability?page=2&page_size=25"
)
12 changes: 12 additions & 0 deletions test/unit/objects/region_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,15 @@ def test_region_availability(self):
assert len(entry.plan) > 0

assert entry.available is not None

def test_region_vpc_availability(self):
"""
Tests that VPC availability for a specific region can be retrieved.
"""
vpc_avail = Region(self.client, "us-east").vpc_availability

assert vpc_avail is not None
assert vpc_avail.region == "us-east"
assert vpc_avail.available is True
assert vpc_avail.available_ipv6_prefix_lengths is not None
assert isinstance(vpc_avail.available_ipv6_prefix_lengths, list)