Skip to content

Commit 271ef57

Browse files
Merge branch 'dev' into TPT-3447-obj-gen2
2 parents aabc2cd + 9e7b52c commit 271ef57

File tree

11 files changed

+230
-8
lines changed

11 files changed

+230
-8
lines changed

.github/workflows/publish-pypi.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ jobs:
2424
LINODE_SDK_VERSION: ${{ github.event.release.tag_name }}
2525

2626
- name: Publish the release artifacts to PyPI
27-
uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # pin@release/v1.12.3
27+
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # pin@release/v1.12.4
2828
with:
2929
password: ${{ secrets.PYPI_API_TOKEN }}

linode_api4/groups/lke.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def cluster_create(
6666
control_plane: Union[
6767
LKEClusterControlPlaneOptions, Dict[str, Any]
6868
] = None,
69+
apl_enabled: bool = False,
6970
**kwargs,
7071
):
7172
"""
@@ -100,8 +101,12 @@ def cluster_create(
100101
formatted dicts.
101102
:param kube_version: The version of Kubernetes to use
102103
:type kube_version: KubeVersion or str
103-
:param control_plane: Dict[str, Any] or LKEClusterControlPlaneRequest
104-
:type control_plane: The control plane configuration of this LKE cluster.
104+
:param control_plane: The control plane configuration of this LKE cluster.
105+
:type control_plane: Dict[str, Any] or LKEClusterControlPlaneRequest
106+
:param apl_enabled: Whether this cluster should use APL.
107+
NOTE: This endpoint is in beta and may only
108+
function if base_url is set to `https://api.linode.com/v4beta`.
109+
:type apl_enabled: bool
105110
:param kwargs: Any other arguments to pass along to the API. See the API
106111
docs for possible values.
107112
@@ -120,6 +125,10 @@ def cluster_create(
120125
}
121126
params.update(kwargs)
122127

128+
# Prevent errors for users without access to APL
129+
if apl_enabled:
130+
params["apl_enabled"] = apl_enabled
131+
123132
result = self.client.post(
124133
"/lke/clusters",
125134
data=_flatten_request_body_recursive(drop_null_keys(params)),

linode_api4/groups/networking.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,3 +367,30 @@ def transfer_prices(self, *filters):
367367
return self.client._get_and_filter(
368368
NetworkTransferPrice, *filters, endpoint="/network-transfer/prices"
369369
)
370+
371+
def delete_vlan(self, vlan, region):
372+
"""
373+
This operation deletes a VLAN.
374+
You can't delete a VLAN if it's still attached to a Linode.
375+
376+
API Documentation: https://techdocs.akamai.com/linode-api/reference/delete-vlan
377+
378+
:param vlan: The label of the VLAN to be deleted.
379+
:type vlan: str or VLAN
380+
:param region: The VLAN's region.
381+
:type region: str or Region
382+
"""
383+
if isinstance(region, Region):
384+
region = region.id
385+
386+
if isinstance(vlan, VLAN):
387+
vlan = vlan.label
388+
resp = self.client.delete(
389+
"/networking/vlans/{}/{}".format(region, vlan),
390+
model=self,
391+
)
392+
393+
if "error" in resp:
394+
return False
395+
396+
return True

linode_api4/objects/lke.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ class LKECluster(Base):
257257
"k8s_version": Property(slug_relationship=KubeVersion, mutable=True),
258258
"pools": Property(derived_class=LKENodePool),
259259
"control_plane": Property(mutable=True),
260+
"apl_enabled": Property(),
260261
}
261262

262263
def invalidate(self):
@@ -353,6 +354,36 @@ def control_plane_acl(self) -> LKEClusterControlPlaneACL:
353354

354355
return LKEClusterControlPlaneACL.from_json(self._control_plane_acl)
355356

357+
@property
358+
def apl_console_url(self) -> Optional[str]:
359+
"""
360+
Returns the URL of this cluster's APL installation if this cluster
361+
is APL-enabled, else None.
362+
363+
:returns: The URL of the APL console for this cluster.
364+
:rtype: str or None
365+
"""
366+
367+
if not self.apl_enabled:
368+
return None
369+
370+
return f"https://console.lke{self.id}.akamai-apl.net"
371+
372+
@property
373+
def apl_health_check_url(self) -> Optional[str]:
374+
"""
375+
Returns the URL of this cluster's APL health check endpoint if this cluster
376+
is APL-enabled, else None.
377+
378+
:returns: The URL of the APL console for this cluster.
379+
:rtype: str or None
380+
"""
381+
382+
if not self.apl_enabled:
383+
return None
384+
385+
return f"https://auth.lke{self.id}.akamai-apl.net/ready"
386+
356387
def node_pool_create(
357388
self,
358389
node_type: Union[Type, str],

test/fixtures/lke_clusters.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
"label": "example-cluster",
77
"region": "ap-west",
88
"k8s_version": "1.19",
9-
"tags": []
9+
"tags": [],
10+
"apl_enabled": true
1011
}

test/fixtures/lke_clusters_18881.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@
99
"tags": [],
1010
"control_plane": {
1111
"high_availability": true
12-
}
12+
},
13+
"apl_enabled": true
1314
}

test/integration/conftest.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,3 +502,22 @@ def pytest_configure(config):
502502
"markers",
503503
"smoke: mark test as part of smoke test suite",
504504
)
505+
506+
507+
@pytest.fixture(scope="session")
508+
def linode_for_vlan_tests(test_linode_client, e2e_test_firewall):
509+
client = test_linode_client
510+
region = get_region(client, {"Linodes", "Vlans"}, site_type="core")
511+
label = get_test_label(length=8)
512+
513+
linode_instance, password = client.linode.instance_create(
514+
"g6-nanode-1",
515+
region,
516+
image="linode/debian12",
517+
label=label,
518+
firewall=e2e_test_firewall,
519+
)
520+
521+
yield linode_instance
522+
523+
linode_instance.delete()

test/integration/models/lke/test_lke.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,32 @@ def lke_cluster_with_labels_and_taints(test_linode_client):
110110
cluster.delete()
111111

112112

113+
@pytest.fixture(scope="session")
114+
def lke_cluster_with_apl(test_linode_client):
115+
version = test_linode_client.lke.versions()[0]
116+
117+
region = get_region(test_linode_client, {"Kubernetes", "Disk Encryption"})
118+
119+
# NOTE: g6-dedicated-4 is the minimum APL-compatible Linode type
120+
node_pools = test_linode_client.lke.node_pool("g6-dedicated-4", 3)
121+
label = get_test_label() + "_cluster"
122+
123+
cluster = test_linode_client.lke.cluster_create(
124+
region,
125+
label,
126+
node_pools,
127+
version,
128+
control_plane=LKEClusterControlPlaneOptions(
129+
high_availability=True,
130+
),
131+
apl_enabled=True,
132+
)
133+
134+
yield cluster
135+
136+
cluster.delete()
137+
138+
113139
def get_cluster_status(cluster: LKECluster, status: str):
114140
return cluster._raw_json["status"] == status
115141

@@ -328,6 +354,19 @@ def test_lke_cluster_labels_and_taints(lke_cluster_with_labels_and_taints):
328354
assert LKENodePoolTaint.from_json(updated_taints[1]) in pool.taints
329355

330356

357+
@pytest.mark.flaky(reruns=3, reruns_delay=2)
358+
def test_lke_cluster_with_apl(lke_cluster_with_apl):
359+
assert lke_cluster_with_apl.apl_enabled == True
360+
assert (
361+
lke_cluster_with_apl.apl_console_url
362+
== f"https://console.lke{lke_cluster_with_apl.id}.akamai-apl.net"
363+
)
364+
assert (
365+
lke_cluster_with_apl.apl_health_check_url
366+
== f"https://auth.lke{lke_cluster_with_apl.id}.akamai-apl.net/ready"
367+
)
368+
369+
331370
def test_lke_types(test_linode_client):
332371
types = test_linode_client.lke.types()
333372

test/integration/models/networking/test_networking.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44
get_region,
55
get_token,
66
)
7-
from test.integration.helpers import get_test_label
7+
from test.integration.helpers import (
8+
get_test_label,
9+
retry_sending_request,
10+
wait_for_condition,
11+
)
812

913
import pytest
1014

11-
from linode_api4 import LinodeClient
15+
from linode_api4 import Instance, LinodeClient
1216
from linode_api4.objects import Config, ConfigInterfaceIPv4, Firewall, IPAddress
1317
from linode_api4.objects.networking import NetworkTransferPrice, Price
1418

@@ -163,3 +167,45 @@ def test_allocate_and_delete_ip(test_linode_client, create_linode):
163167
is_deleted = ip.delete()
164168

165169
assert is_deleted is True
170+
171+
172+
def get_status(linode: Instance, status: str):
173+
return linode.status == status
174+
175+
176+
def test_create_and_delete_vlan(test_linode_client, linode_for_vlan_tests):
177+
linode = linode_for_vlan_tests
178+
179+
config: Config = linode.configs[0]
180+
181+
config.interfaces = []
182+
config.save()
183+
184+
vlan_label = "testvlan"
185+
interface = config.interface_create_vlan(
186+
label=vlan_label, ipam_address="10.0.0.2/32"
187+
)
188+
189+
config.invalidate()
190+
191+
assert interface.id == config.interfaces[0].id
192+
assert interface.purpose == "vlan"
193+
assert interface.label == vlan_label
194+
195+
# Remove the VLAN interface and reboot Linode
196+
config.interfaces = []
197+
config.save()
198+
199+
wait_for_condition(3, 100, get_status, linode, "running")
200+
201+
retry_sending_request(3, linode.reboot)
202+
203+
wait_for_condition(3, 100, get_status, linode, "rebooting")
204+
assert linode.status == "rebooting"
205+
206+
# Delete the VLAN
207+
is_deleted = test_linode_client.networking.delete_vlan(
208+
vlan_label, linode.region
209+
)
210+
211+
assert is_deleted is True

test/unit/objects/lke_test.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def test_get_cluster(self):
3939
self.assertEqual(cluster.region.id, "ap-west")
4040
self.assertEqual(cluster.k8s_version.id, "1.19")
4141
self.assertTrue(cluster.control_plane.high_availability)
42+
self.assertTrue(cluster.apl_enabled)
4243

4344
def test_get_pool(self):
4445
"""
@@ -352,6 +353,40 @@ def test_cluster_create_with_labels_and_taints(self):
352353
],
353354
}
354355

356+
def test_cluster_create_with_apl(self):
357+
"""
358+
Tests that an LKE cluster can be created with APL enabled.
359+
"""
360+
361+
with self.mock_post("lke/clusters") as m:
362+
cluster = self.client.lke.cluster_create(
363+
"us-mia",
364+
"test-aapl-cluster",
365+
[
366+
self.client.lke.node_pool(
367+
"g6-dedicated-4",
368+
3,
369+
)
370+
],
371+
"1.29",
372+
apl_enabled=True,
373+
control_plane=LKEClusterControlPlaneOptions(
374+
high_availability=True,
375+
),
376+
)
377+
378+
assert m.call_data["apl_enabled"] == True
379+
assert m.call_data["control_plane"]["high_availability"] == True
380+
381+
assert (
382+
cluster.apl_console_url == "https://console.lke18881.akamai-apl.net"
383+
)
384+
385+
assert (
386+
cluster.apl_health_check_url
387+
== "https://auth.lke18881.akamai-apl.net/ready"
388+
)
389+
355390
def test_populate_with_taints(self):
356391
"""
357392
Tests that LKENodePool correctly handles a list of LKENodePoolTaint and Dict objects.

0 commit comments

Comments
 (0)