Skip to content

Commit 9a4ef97

Browse files
lgarber-akamaizliang-akamaivshanthe
authored
project: VPC Dual Stack (#601)
* Enhanced Interfaces: Add support for Firewall templates (#529) * Add support for Firewall Templates * oops * Add LA notices * Enhanced Interfaces: Add account-related fields (#525) * Enhanced Interfaces: Add account-related fields * Add setting enum * Add LA notice * Drop residual print * Enhanced Interfaces: Implement endpoints & fields related to VPCs and non-interface networking (#526) * Implement endpoints & fields related to VPCs and non-interface networking * Add LA notices * Implement support for VPC Dual Stack (#524) * Enhanced Interfaces: Add support for Linode-related endpoints and fields (#533) * Add support for Linode-related endpoints and fields * oops * tiny fixes * fix docsa * Add docs examples * Docs fixes * oops * Remove irrelevant test * Add LA notices * Fill in API documentation URLs * Add return types * Enable `include_none_values` in FirewallSettingsDefaultFirewallIDs (#558) * VPC Dual Stack: Support changes related to Linode Interfaces (#559) * Implementation; needs tests * Add integration tests * vpctest * removeprint * test * Fix conflicts * Fix missed conflict --------- Co-authored-by: Zhiwei Liang <zliang@akamai.com> Co-authored-by: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Co-authored-by: vshanthe <vshanthe@akamai.com> Co-authored-by: Vinay <143587840+vshanthe@users.noreply.github.com>
1 parent 7e420db commit 9a4ef97

25 files changed

+853
-230
lines changed

linode_api4/groups/vpc.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
from linode_api4.errors import UnexpectedResponseError
44
from linode_api4.groups import Group
5-
from linode_api4.objects import VPC, Region, VPCIPAddress
5+
from linode_api4.objects import VPC, Region, VPCIPAddress, VPCIPv6RangeOptions
6+
from linode_api4.objects.base import _flatten_request_body_recursive
67
from linode_api4.paginated_list import PaginatedList
8+
from linode_api4.util import drop_null_keys
79

810

911
class VPCGroup(Group):
@@ -33,6 +35,7 @@ def create(
3335
region: Union[Region, str],
3436
description: Optional[str] = None,
3537
subnets: Optional[List[Dict[str, Any]]] = None,
38+
ipv6: Optional[List[Union[VPCIPv6RangeOptions, Dict[str, Any]]]] = None,
3639
**kwargs,
3740
) -> VPC:
3841
"""
@@ -48,30 +51,33 @@ def create(
4851
:type description: Optional[str]
4952
:param subnets: A list of subnets to create under this VPC.
5053
:type subnets: List[Dict[str, Any]]
54+
:param ipv6: The IPv6 address ranges for this VPC.
55+
:type ipv6: List[Union[VPCIPv6RangeOptions, Dict[str, Any]]]
5156
5257
:returns: The new VPC object.
5358
:rtype: VPC
5459
"""
5560
params = {
5661
"label": label,
5762
"region": region.id if isinstance(region, Region) else region,
63+
"description": description,
64+
"ipv6": ipv6,
65+
"subnets": subnets,
5866
}
5967

60-
if description is not None:
61-
params["description"] = description
62-
6368
if subnets is not None and len(subnets) > 0:
6469
for subnet in subnets:
6570
if not isinstance(subnet, dict):
6671
raise ValueError(
6772
f"Unsupported type for subnet: {type(subnet)}"
6873
)
6974

70-
params["subnets"] = subnets
71-
7275
params.update(kwargs)
7376

74-
result = self.client.post("/vpcs", data=params)
77+
result = self.client.post(
78+
"/vpcs",
79+
data=drop_null_keys(_flatten_request_body_recursive(params)),
80+
)
7581

7682
if not "id" in result:
7783
raise UnexpectedResponseError(

linode_api4/objects/linode.py

Lines changed: 99 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,83 @@ def _populate(self, json):
300300

301301
@dataclass
302302
class ConfigInterfaceIPv4(JSONObject):
303+
"""
304+
ConfigInterfaceIPv4 represents the IPv4 configuration of a VPC interface.
305+
"""
306+
303307
vpc: str = ""
304308
nat_1_1: str = ""
305309

306310

311+
@dataclass
312+
class ConfigInterfaceIPv6SLAACOptions(JSONObject):
313+
"""
314+
ConfigInterfaceIPv6SLAACOptions is used to set a single IPv6 SLAAC configuration of a VPC interface.
315+
"""
316+
317+
range: str = ""
318+
319+
320+
@dataclass
321+
class ConfigInterfaceIPv6RangeOptions(JSONObject):
322+
"""
323+
ConfigInterfaceIPv6RangeOptions is used to set a single IPv6 range configuration of a VPC interface.
324+
"""
325+
326+
range: str = ""
327+
328+
329+
@dataclass
330+
class ConfigInterfaceIPv6Options(JSONObject):
331+
"""
332+
ConfigInterfaceIPv6Options is used to set the IPv6 configuration of a VPC interface.
333+
"""
334+
335+
slaac: List[ConfigInterfaceIPv6SLAACOptions] = field(
336+
default_factory=lambda: []
337+
)
338+
ranges: List[ConfigInterfaceIPv6RangeOptions] = field(
339+
default_factory=lambda: []
340+
)
341+
is_public: bool = False
342+
343+
344+
@dataclass
345+
class ConfigInterfaceIPv6SLAAC(JSONObject):
346+
"""
347+
ConfigInterfaceIPv6SLAAC represents a single SLAAC address under a VPC interface's IPv6 configuration.
348+
"""
349+
350+
put_class = ConfigInterfaceIPv6SLAACOptions
351+
352+
range: str = ""
353+
address: str = ""
354+
355+
356+
@dataclass
357+
class ConfigInterfaceIPv6Range(JSONObject):
358+
"""
359+
ConfigInterfaceIPv6Range represents a single IPv6 address under a VPC interface's IPv6 configuration.
360+
"""
361+
362+
put_class = ConfigInterfaceIPv6RangeOptions
363+
364+
range: str = ""
365+
366+
367+
@dataclass
368+
class ConfigInterfaceIPv6(JSONObject):
369+
"""
370+
ConfigInterfaceIPv6 represents the IPv6 configuration of a VPC interface.
371+
"""
372+
373+
put_class = ConfigInterfaceIPv6Options
374+
375+
slaac: List[ConfigInterfaceIPv6SLAAC] = field(default_factory=lambda: [])
376+
ranges: List[ConfigInterfaceIPv6Range] = field(default_factory=lambda: [])
377+
is_public: bool = False
378+
379+
307380
class NetworkInterface(DerivedBase):
308381
"""
309382
This class represents a Configuration Profile's network interface object.
@@ -329,6 +402,7 @@ class NetworkInterface(DerivedBase):
329402
"vpc_id": Property(id_relationship=VPC),
330403
"subnet_id": Property(),
331404
"ipv4": Property(mutable=True, json_object=ConfigInterfaceIPv4),
405+
"ipv6": Property(mutable=True, json_object=ConfigInterfaceIPv6),
332406
"ip_ranges": Property(mutable=True),
333407
}
334408

@@ -400,7 +474,10 @@ class ConfigInterface(JSONObject):
400474
# VPC-specific
401475
vpc_id: Optional[int] = None
402476
subnet_id: Optional[int] = None
477+
403478
ipv4: Optional[Union[ConfigInterfaceIPv4, Dict[str, Any]]] = None
479+
ipv6: Optional[Union[ConfigInterfaceIPv6, Dict[str, Any]]] = None
480+
404481
ip_ranges: Optional[List[str]] = None
405482

406483
# Computed
@@ -409,7 +486,7 @@ class ConfigInterface(JSONObject):
409486
def __repr__(self):
410487
return f"Interface: {self.purpose}"
411488

412-
def _serialize(self, *args, **kwargs):
489+
def _serialize(self, is_put: bool = False):
413490
purpose_formats = {
414491
"public": {"purpose": "public", "primary": self.primary},
415492
"vlan": {
@@ -421,11 +498,8 @@ def _serialize(self, *args, **kwargs):
421498
"purpose": "vpc",
422499
"primary": self.primary,
423500
"subnet_id": self.subnet_id,
424-
"ipv4": (
425-
self.ipv4.dict
426-
if isinstance(self.ipv4, ConfigInterfaceIPv4)
427-
else self.ipv4
428-
),
501+
"ipv4": self.ipv4,
502+
"ipv6": self.ipv6,
429503
"ip_ranges": self.ip_ranges,
430504
},
431505
}
@@ -435,11 +509,14 @@ def _serialize(self, *args, **kwargs):
435509
f"Unknown interface purpose: {self.purpose}",
436510
)
437511

438-
return {
439-
k: v
440-
for k, v in purpose_formats[self.purpose].items()
441-
if v is not None
442-
}
512+
return _flatten_request_body_recursive(
513+
{
514+
k: v
515+
for k, v in purpose_formats[self.purpose].items()
516+
if v is not None
517+
},
518+
is_put=is_put,
519+
)
443520

444521

445522
class Config(DerivedBase):
@@ -580,6 +657,7 @@ def interface_create_vpc(
580657
subnet: Union[int, VPCSubnet],
581658
primary=False,
582659
ipv4: Union[Dict[str, Any], ConfigInterfaceIPv4] = None,
660+
ipv6: Union[Dict[str, Any], ConfigInterfaceIPv6Options] = None,
583661
ip_ranges: Optional[List[str]] = None,
584662
) -> NetworkInterface:
585663
"""
@@ -593,6 +671,8 @@ def interface_create_vpc(
593671
:type primary: bool
594672
:param ipv4: The IPv4 configuration of the interface for the associated subnet.
595673
:type ipv4: Dict or ConfigInterfaceIPv4
674+
:param ipv6: The IPv6 configuration of the interface for the associated subnet.
675+
:type ipv6: Dict or ConfigInterfaceIPv6Options
596676
:param ip_ranges: A list of IPs or IP ranges in the VPC subnet.
597677
Packets to these CIDRs are routed through the
598678
VPC network interface.
@@ -603,19 +683,16 @@ def interface_create_vpc(
603683
"""
604684
params = {
605685
"purpose": "vpc",
606-
"subnet_id": subnet.id if isinstance(subnet, VPCSubnet) else subnet,
686+
"subnet_id": subnet,
607687
"primary": primary,
688+
"ipv4": ipv4,
689+
"ipv6": ipv6,
690+
"ip_ranges": ip_ranges,
608691
}
609692

610-
if ipv4 is not None:
611-
params["ipv4"] = (
612-
ipv4.dict if isinstance(ipv4, ConfigInterfaceIPv4) else ipv4
613-
)
614-
615-
if ip_ranges is not None:
616-
params["ip_ranges"] = ip_ranges
617-
618-
return self._interface_create(params)
693+
return self._interface_create(
694+
drop_null_keys(_flatten_request_body_recursive(params))
695+
)
619696

620697
def interface_reorder(self, interfaces: List[Union[int, NetworkInterface]]):
621698
"""
@@ -2018,6 +2095,7 @@ def linode_interfaces(self) -> Optional[list[LinodeInterface]]:
20182095

20192096
if self.interface_generation != InterfaceGeneration.LINODE:
20202097
return None
2098+
20212099
if not hasattr(self, "_interfaces"):
20222100
result = self._client.get(
20232101
"{}/interfaces".format(Instance.api_endpoint),

linode_api4/objects/linode_interfaces.py

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from dataclasses import dataclass, field
2-
from typing import List, Optional, Union
2+
from typing import List, Optional
33

4-
from linode_api4.objects.base import Base, ExplicitNullValue, Property
4+
from linode_api4.objects.base import Base, Property
55
from linode_api4.objects.dbase import DerivedBase
66
from linode_api4.objects.networking import Firewall
77
from linode_api4.objects.serializable import JSONObject
@@ -104,6 +104,41 @@ class LinodeInterfaceVPCIPv4Options(JSONObject):
104104
ranges: Optional[List[LinodeInterfaceVPCIPv4RangeOptions]] = None
105105

106106

107+
@dataclass
108+
class LinodeInterfaceVPCIPv6SLAACOptions(JSONObject):
109+
"""
110+
Options accepted for a single SLAAC when creating or updating the IPv6 configuration of a VPC Linode Interface.
111+
112+
NOTE: Linode interfaces may not currently be available to all users.
113+
"""
114+
115+
range: Optional[str] = None
116+
117+
118+
@dataclass
119+
class LinodeInterfaceVPCIPv6RangeOptions(JSONObject):
120+
"""
121+
Options accepted for a single range when creating or updating the IPv6 configuration of a VPC Linode Interface.
122+
123+
NOTE: Linode interfaces may not currently be available to all users.
124+
"""
125+
126+
range: Optional[str] = None
127+
128+
129+
@dataclass
130+
class LinodeInterfaceVPCIPv6Options(JSONObject):
131+
"""
132+
Options accepted when creating or updating the IPv6 configuration of a VPC Linode Interface.
133+
134+
NOTE: Linode interfaces may not currently be available to all users.
135+
"""
136+
137+
is_public: Optional[bool] = None
138+
slaac: Optional[List[LinodeInterfaceVPCIPv6SLAACOptions]] = None
139+
ranges: Optional[List[LinodeInterfaceVPCIPv6RangeOptions]] = None
140+
141+
107142
@dataclass
108143
class LinodeInterfaceVPCOptions(JSONObject):
109144
"""
@@ -114,6 +149,7 @@ class LinodeInterfaceVPCOptions(JSONObject):
114149

115150
subnet_id: int = 0
116151
ipv4: Optional[LinodeInterfaceVPCIPv4Options] = None
152+
ipv6: Optional[LinodeInterfaceVPCIPv6Options] = None
117153

118154

119155
@dataclass
@@ -193,13 +229,13 @@ class LinodeInterfaceOptions(JSONObject):
193229
NOTE: Linode interfaces may not currently be available to all users.
194230
"""
195231

196-
# If a default firewall_id isn't configured, the API requires that
197-
# firewall_id is defined in the LinodeInterface POST body.
198-
#
199-
# To create a Linode Interface without a firewall, this field should
200-
# be set to `ExplicitNullValue()`.
201-
firewall_id: Union[int, ExplicitNullValue, None] = None
232+
always_include = {
233+
# If a default firewall_id isn't configured, the API requires that
234+
# firewall_id is defined in the LinodeInterface POST body.
235+
"firewall_id"
236+
}
202237

238+
firewall_id: Optional[int] = None
203239
default_route: Optional[LinodeInterfaceDefaultRouteOptions] = None
204240
vpc: Optional[LinodeInterfaceVPCOptions] = None
205241
public: Optional[LinodeInterfacePublicOptions] = None
@@ -265,6 +301,44 @@ class LinodeInterfaceVPCIPv4(JSONObject):
265301
ranges: List[LinodeInterfaceVPCIPv4Range] = field(default_factory=list)
266302

267303

304+
@dataclass
305+
class LinodeInterfaceVPCIPv6SLAAC(JSONObject):
306+
"""
307+
A single SLAAC entry under the IPv6 configuration of a VPC Linode Interface.
308+
309+
NOTE: Linode interfaces may not currently be available to all users.
310+
"""
311+
312+
range: str = ""
313+
address: str = ""
314+
315+
316+
@dataclass
317+
class LinodeInterfaceVPCIPv6Range(JSONObject):
318+
"""
319+
A single range under the IPv6 configuration of a VPC Linode Interface.
320+
321+
NOTE: Linode interfaces may not currently be available to all users.
322+
"""
323+
324+
range: str = ""
325+
326+
327+
@dataclass
328+
class LinodeInterfaceVPCIPv6(JSONObject):
329+
"""
330+
A single address under the IPv6 configuration of a VPC Linode Interface.
331+
332+
NOTE: Linode interfaces may not currently be available to all users.
333+
"""
334+
335+
put_class = LinodeInterfaceVPCIPv6Options
336+
337+
is_public: bool = False
338+
slaac: List[LinodeInterfaceVPCIPv6SLAAC] = field(default_factory=list)
339+
ranges: List[LinodeInterfaceVPCIPv6Range] = field(default_factory=list)
340+
341+
268342
@dataclass
269343
class LinodeInterfaceVPC(JSONObject):
270344
"""
@@ -279,6 +353,7 @@ class LinodeInterfaceVPC(JSONObject):
279353
subnet_id: int = 0
280354

281355
ipv4: Optional[LinodeInterfaceVPCIPv4] = None
356+
ipv6: Optional[LinodeInterfaceVPCIPv6] = None
282357

283358

284359
@dataclass

0 commit comments

Comments
 (0)