Skip to content

Commit c5f6a76

Browse files
committed
TPT-4656: Support NodeBalancer type and backend connectivity
Expose type and backend_connectivity on NodeBalancer, document create kwargs, and cover IPv6/VPC backends in unit and integration tests.
1 parent 301fad1 commit c5f6a76

8 files changed

Lines changed: 192 additions & 6 deletions

File tree

linode_api4/groups/nodebalancer.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,27 @@ def create(self, region, **kwargs):
3535
:param ipv4: A reserved IPv4 address to assign to this NodeBalancer.
3636
NOTE: Reserved IP feature may not currently be available to all users.
3737
:type ipv4: str
38+
:param type: The NodeBalancer type. Supported values include
39+
``common``, ``basic``, ``premium``, ``premium_40g``,
40+
and ``enterprise``. This cannot be changed after creation.
41+
NOTE: Creating premium or enterprise NodeBalancers may not
42+
currently be available to all users.
43+
:type type: str
44+
:param backend_connectivity: How this NodeBalancer communicates with
45+
backends (``legacy``, ``ipv6``, or ``vpc``). If omitted,
46+
the API infers a value from ``vpcs`` or config nodes, or
47+
returns ``undefined`` until the first node is added.
48+
``undefined`` cannot be sent by clients. This cannot be
49+
changed after creation.
50+
NOTE: This field may not currently be available to all users.
51+
:type backend_connectivity: str
52+
:param vpcs: VPC attachments for this NodeBalancer. Required when
53+
``backend_connectivity`` is ``vpc``.
54+
:type vpcs: list[dict]
55+
:param configs: NodeBalancer configs and optional nodes to create
56+
with this NodeBalancer. Node addresses must match the
57+
selected or inferred backend connectivity.
58+
:type configs: list[dict]
3859
3960
:returns: The new NodeBalancer
4061
:rtype: NodeBalancer

linode_api4/objects/nodebalancer.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,10 @@ def node_create(self, label, address, **kwargs):
162162
163163
API documentation: https://techdocs.akamai.com/linode-api/reference/post-node-balancer-node
164164
165-
:param address: The private IP Address where this backend can be reached.
166-
This must be a private IP address.
165+
:param address: The address where this backend can be reached. This may
166+
be a private IPv4 address, a public IPv6 address in
167+
``[IPv6]:port`` format, or a VPC address. The address
168+
type must match this NodeBalancer's ``backend_connectivity``.
167169
:type address: str
168170
169171
:param label: The label for this node. This is for display purposes only.
@@ -255,6 +257,8 @@ class NodeBalancer(Base):
255257
"tags": Property(mutable=True, unordered=True),
256258
"client_udp_sess_throttle": Property(mutable=True),
257259
"locks": Property(unordered=True),
260+
"type": Property(),
261+
"backend_connectivity": Property(),
258262
}
259263

260264
# create derived objects

test/fixtures/linode_instances_123_nodebalancers.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
"ipv4": "203.0.113.1",
99
"ipv6": null,
1010
"label": "balancer12345",
11+
"type": "premium",
12+
"backend_connectivity": "ipv6",
1113
"region": "us-east",
1214
"tags": [
1315
"example tag",

test/fixtures/nodebalancers.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
"label": "balancer123456",
1212
"client_conn_throttle": 0,
1313
"tags": ["something"],
14-
"locks": ["cannot_delete_with_subresources"]
14+
"locks": ["cannot_delete_with_subresources"],
15+
"type": "premium",
16+
"backend_connectivity": "ipv6"
1517
},
1618
{
1719
"created": "2018-01-01T00:01:01",
@@ -24,7 +26,9 @@
2426
"label": "balancer123457",
2527
"client_conn_throttle": 0,
2628
"tags": [],
27-
"locks": []
29+
"locks": [],
30+
"type": "premium",
31+
"backend_connectivity": "ipv6"
2832
}
2933
],
3034
"results": 2,

test/fixtures/nodebalancers_123456.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,7 @@
1313
],
1414
"locks": [
1515
"cannot_delete_with_subresources"
16-
]
17-
}
16+
],
17+
"type": "premium",
18+
"backend_connectivity": "ipv6"
19+
}

test/integration/models/nodebalancer/test_nodebalancer.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,15 @@ def test_create_nb(test_linode_client, e2e_test_firewall):
106106
label=label,
107107
firewall=e2e_test_firewall.id,
108108
client_udp_sess_throttle=5,
109+
type="premium",
110+
backend_connectivity="ipv6",
109111
)
110112

111113
assert TEST_REGION, nb.region
112114
assert label == nb.label
113115
assert 5 == nb.client_udp_sess_throttle
116+
assert nb.type == "premium"
117+
assert nb.backend_connectivity == "ipv6"
114118

115119
nb.delete()
116120

test/unit/linode_client_test.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1589,6 +1589,78 @@ def test_nodebalancer_types(self):
15891589
self.assertEqual(types[0].region_prices[0].hourly, 0.018)
15901590
self.assertEqual(types[0].region_prices[0].monthly, 12)
15911591

1592+
def test_create_with_type_and_backend_connectivity(self):
1593+
"""
1594+
Tests that creating a NodeBalancer forwards type, backend_connectivity,
1595+
vpcs, and config node addresses.
1596+
"""
1597+
with self.mock_post(
1598+
{
1599+
"id": 1234,
1600+
"label": "my-premium-nb",
1601+
"type": "premium",
1602+
"backend_connectivity": "ipv6",
1603+
"region": "us-east",
1604+
}
1605+
) as m:
1606+
nb = self.client.nodebalancers.create(
1607+
"us-east",
1608+
label="my-premium-nb",
1609+
type="premium",
1610+
backend_connectivity="ipv6",
1611+
configs=[
1612+
{
1613+
"port": 80,
1614+
"nodes": [
1615+
{
1616+
"address": "[2001:db8:abcd:0012::1]:80",
1617+
"label": "node1",
1618+
}
1619+
],
1620+
}
1621+
],
1622+
)
1623+
1624+
self.assertEqual(m.call_url, "/nodebalancers")
1625+
self.assertEqual(m.call_data["region"], "us-east")
1626+
self.assertEqual(m.call_data["type"], "premium")
1627+
self.assertEqual(m.call_data["backend_connectivity"], "ipv6")
1628+
self.assertEqual(
1629+
m.call_data["configs"][0]["nodes"][0]["address"],
1630+
"[2001:db8:abcd:0012::1]:80",
1631+
)
1632+
self.assertEqual(nb.id, 1234)
1633+
self.assertEqual(nb.type, "premium")
1634+
self.assertEqual(nb.backend_connectivity, "ipv6")
1635+
1636+
def test_create_with_vpc_backend_connectivity(self):
1637+
"""
1638+
Tests that creating a NodeBalancer forwards vpc backend connectivity.
1639+
"""
1640+
with self.mock_post(
1641+
{
1642+
"id": 1234,
1643+
"label": "my-nb",
1644+
"type": "common",
1645+
"backend_connectivity": "vpc",
1646+
"region": "us-east",
1647+
}
1648+
) as m:
1649+
nb = self.client.nodebalancers.create(
1650+
"us-east",
1651+
label="my-nb",
1652+
backend_connectivity="vpc",
1653+
vpcs=[{"subnet_id": 123456, "ipv4_range": "10.0.250.4/30"}],
1654+
)
1655+
1656+
self.assertEqual(m.call_url, "/nodebalancers")
1657+
self.assertEqual(m.call_data["backend_connectivity"], "vpc")
1658+
self.assertEqual(
1659+
m.call_data["vpcs"],
1660+
[{"subnet_id": 123456, "ipv4_range": "10.0.250.4/30"}],
1661+
)
1662+
self.assertEqual(nb.backend_connectivity, "vpc")
1663+
15921664

15931665
class VolumeGroupTest(ClientBaseCase):
15941666
"""

test/unit/objects/nodebalancers_test.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,29 @@ def test_create_node(self):
113113
},
114114
)
115115

116+
def test_create_ipv6_node(self):
117+
"""
118+
Tests that a node can be created with a public IPv6 backend address.
119+
"""
120+
with self.mock_post(
121+
"nodebalancers/123456/configs/65432/nodes/54321"
122+
) as m:
123+
config = NodeBalancerConfig(self.client, 65432, 123456)
124+
node = config.node_create(
125+
"node54321",
126+
"[2001:db8:abcd:0012::1]:80",
127+
weight=50,
128+
mode="accept",
129+
)
130+
131+
self.assertIsNotNone(node)
132+
self.assertEqual(
133+
m.call_url, "/nodebalancers/123456/configs/65432/nodes"
134+
)
135+
self.assertEqual(
136+
m.call_data["address"], "[2001:db8:abcd:0012::1]:80"
137+
)
138+
116139
def test_update_node(self):
117140
"""
118141
Tests that a node can be updated
@@ -154,6 +177,18 @@ def test_delete_node(self):
154177

155178

156179
class NodeBalancerTest(ClientBaseCase):
180+
def test_get(self):
181+
"""
182+
Tests that a NodeBalancer is loaded correctly by ID.
183+
"""
184+
nb = NodeBalancer(self.client, 123456)
185+
self.assertEqual(nb._populated, False)
186+
187+
self.assertEqual(nb.label, "balancer123456")
188+
self.assertEqual(nb._populated, True)
189+
self.assertEqual(nb.type, "premium")
190+
self.assertEqual(nb.backend_connectivity, "ipv6")
191+
157192
def test_update(self):
158193
"""
159194
Test that you can update a NodeBalancer.
@@ -193,6 +228,24 @@ def test_locks_not_in_put(self):
193228
self.assertNotIn("locks", m.call_data)
194229
self.assertEqual(m.call_data["label"], "new-label")
195230

231+
def test_type_and_backend_connectivity_not_in_put(self):
232+
"""
233+
Test that type and backend_connectivity are not included in PUT
234+
requests. These fields cannot be changed after creation.
235+
"""
236+
nb = NodeBalancer(self.client, 123456)
237+
self.assertEqual(nb.type, "premium")
238+
self.assertEqual(nb.backend_connectivity, "ipv6")
239+
240+
nb.label = "new-label"
241+
242+
with self.mock_put("nodebalancers/123456") as m:
243+
nb.save()
244+
self.assertEqual(m.call_url, "/nodebalancers/123456")
245+
self.assertNotIn("type", m.call_data)
246+
self.assertNotIn("backend_connectivity", m.call_data)
247+
self.assertEqual(m.call_data["label"], "new-label")
248+
196249
def test_firewalls(self):
197250
"""
198251
Test that you can get the firewalls for the requested NodeBalancer.
@@ -251,6 +304,30 @@ def test_config_rebuild(self):
251304
},
252305
)
253306

307+
def test_config_rebuild_ipv6(self):
308+
"""
309+
Test that a config can be rebuilt with public IPv6 backend addresses.
310+
"""
311+
config_rebuild_url = "/nodebalancers/12345/configs/4567/rebuild"
312+
with self.mock_post(config_rebuild_url) as m:
313+
nb = NodeBalancer(self.client, 12345)
314+
nodes = [
315+
{
316+
"address": "[2001:db8:abcd:0012::1]:80",
317+
"label": "node1",
318+
"weight": 50,
319+
"mode": "accept",
320+
}
321+
]
322+
323+
result = nb.config_rebuild(4567, nodes, port=80, protocol="http")
324+
self.assertIsNotNone(result)
325+
self.assertEqual(m.call_url, config_rebuild_url)
326+
self.assertEqual(
327+
m.call_data["nodes"][0]["address"],
328+
"[2001:db8:abcd:0012::1]:80",
329+
)
330+
254331
def test_statistics(self):
255332
"""
256333
Test that you can get the statistics about the requested NodeBalancer.

0 commit comments

Comments
 (0)