Skip to content
Draft
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,5 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Run tests
run: |
pip install ".[test]"
tox
pip install ".[unit-test]"
make test-unit
3 changes: 1 addition & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
## Source of Truth

- This is the official synchronous Python SDK for the Linode API. Supported
Python versions come from [pyproject.toml](pyproject.toml) (`>=3.10`); do not
infer support from the older environment names still present in [tox.ini](tox.ini).
Python versions come from [pyproject.toml](pyproject.toml) (`>=3.10`).
- Make changes only in canonical source trees such as `linode_api4/`, `test/`,
`docs/`, `examples/`, and `.github/`. Do not edit generated copies or output in
`build/`, `dist/`, `docs/build/`, `docs/_build/`, or `linode_api4.egg-info/`.
Expand Down
11 changes: 4 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,13 @@ Tests
-----

Tests live in the ``test`` directory. When invoking tests, make sure you are
in the root directory of this project. To run the full suite across all
supported python versions, use tox_:
in the root directory of this project. Install unit-test dependencies and run
the mocked unit suite with:

.. code-block:: shell

tox

Running tox also runs pylint and coverage reports.
python3 -m pip install -e ".[unit-test]"
make test-unit

The test suite uses fixtures stored as JSON in ``test/fixtures``. These files
contain sanitized JSON responses from the API - the file name is the URL called
Expand All @@ -131,8 +130,6 @@ from the api base url that should be returned, for example::
linode = self.client.linode.instance_create('g6-standard-2', 'us-east')
self.assertEqual(linode.id, 123) # passes

.. _tox: http://tox.readthedocs.io


Integration Tests
-----------------
Expand Down
21 changes: 21 additions & 0 deletions linode_api4/groups/nodebalancer.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,27 @@ def create(self, region, **kwargs):
:param ipv4: A reserved IPv4 address to assign to this NodeBalancer.
NOTE: Reserved IP feature may not currently be available to all users.
:type ipv4: str
:param type: The NodeBalancer type. Supported values include
``common``, ``premium``, ``premium_40gb``, and
``enterprise``. This cannot be changed after creation.
NOTE: Creating premium or enterprise NodeBalancers may not
currently be available to all users.
:type type: str
:param backend_connectivity: How this NodeBalancer communicates with
backends (``legacy``, ``ipv6``, or ``vpc``). If omitted,
the API infers a value from ``vpcs`` or config nodes, or
returns ``undefined`` until the first node is added.
``undefined`` cannot be sent by clients. This cannot be
changed after creation.
NOTE: This field may not currently be available to all users.
:type backend_connectivity: NodeBalancerBackendConnectivity | str
:param vpcs: VPC attachments for this NodeBalancer. Required when
``backend_connectivity`` is ``vpc``.
:type vpcs: list[dict]
:param configs: NodeBalancer configs and optional nodes to create
with this NodeBalancer. Node addresses must match the
selected or inferred backend connectivity.
:type configs: list[dict]

:returns: The new NodeBalancer
:rtype: NodeBalancer
Expand Down
19 changes: 17 additions & 2 deletions linode_api4/objects/nodebalancer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@
from linode_api4.objects.dbase import DerivedBase
from linode_api4.objects.networking import Firewall, IPAddress
from linode_api4.objects.region import Region
from linode_api4.objects.serializable import StrEnum


class NodeBalancerBackendConnectivity(StrEnum):
"""Backend connectivity modes accepted when creating a NodeBalancer."""

IPV6 = "ipv6"
LEGACY = "legacy"
UNDEFINED = "undefined"
VPC = "vpc"


class NodeBalancerType(Base):
Expand Down Expand Up @@ -162,8 +172,10 @@ def node_create(self, label, address, **kwargs):

API documentation: https://techdocs.akamai.com/linode-api/reference/post-node-balancer-node

:param address: The private IP Address where this backend can be reached.
This must be a private IP address.
:param address: The address and port where this backend can be reached.
The address may be a private IPv4 address, a public
IPv6 address, or a VPC address. The address type must
match this NodeBalancer's ``backend_connectivity``.
:type address: str

:param label: The label for this node. This is for display purposes only.
Expand Down Expand Up @@ -255,6 +267,9 @@ class NodeBalancer(Base):
"tags": Property(mutable=True, unordered=True),
"client_udp_sess_throttle": Property(mutable=True),
"locks": Property(unordered=True),
"type": Property(),
"backend_connectivity": Property(),
"backend_ipv6_prefix": Property(),
}

# create derived objects
Expand Down
29 changes: 17 additions & 12 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,32 +35,37 @@ dependencies = ["requests", "polling", "deprecated"]
dynamic = ["version"]

[project.optional-dependencies]
test = ["tox>=4.4.0"]

dev = [
"tox>=4.4.0",
"mock>=5.0.0",
test-shared = [
"pytest>=7.3.1",
"httpretty>=1.1.4",
"pytest-rerunfailures",
]
unit-test = [
"linode_api4[test-shared]",
"mock>=5.0.0",
]
int-test = [
"linode_api4[test-shared]",
]
lint = [
"black>=23.1.0",
"isort>=5.12.0",
"autoflake>=2.0.1",
"pylint",
"twine>=4.0.2",
"build>=0.10.0",
"Sphinx>=6.0.0",
"sphinx-autobuild>=2021.3.14",
"sphinxcontrib-fulltoc>=1.2.0",
"build>=0.10.0",
"twine>=4.0.2",
"pytest-rerunfailures",
]

doc = [
"Sphinx>=6.0.0",
"sphinx-autobuild>=2021.3.14",
"sphinxcontrib-fulltoc>=1.2.0",
]
dev = [
"linode_api4[unit-test]",
"linode_api4[int-test]",
"linode_api4[lint]",
"linode_api4[doc]",
]

[project.urls]
Homepage = "https://github.com/linode/linode_api4-python"
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/linode_instances_123_nodebalancers.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
"ipv4": "203.0.113.1",
"ipv6": null,
"label": "balancer12345",
"type": "premium",
"backend_connectivity": "ipv6",
"backend_ipv6_prefix": "2600:3c22:1:20:0:3039::/96",
"region": "us-east",
"tags": [
"example tag",
Expand Down
10 changes: 8 additions & 2 deletions test/fixtures/nodebalancers.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
"label": "balancer123456",
"client_conn_throttle": 0,
"tags": ["something"],
"locks": ["cannot_delete_with_subresources"]
"locks": ["cannot_delete_with_subresources"],
"type": "premium",
"backend_connectivity": "ipv6",
"backend_ipv6_prefix": "2600:3c22:1:20:0:3039::/96"
},
{
"created": "2018-01-01T00:01:01",
Expand All @@ -24,7 +27,10 @@
"label": "balancer123457",
"client_conn_throttle": 0,
"tags": [],
"locks": []
"locks": [],
"type": "premium",
"backend_connectivity": "ipv6",
"backend_ipv6_prefix": null
}
],
"results": 2,
Expand Down
7 changes: 5 additions & 2 deletions test/fixtures/nodebalancers_123456.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@
],
"locks": [
"cannot_delete_with_subresources"
]
}
],
"type": "premium",
"backend_connectivity": "ipv6",
"backend_ipv6_prefix": "2600:3c22:1:20:0:3039::/96"
}
7 changes: 7 additions & 0 deletions test/integration/models/nodebalancer/test_nodebalancer.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,18 @@ def test_create_nb(test_linode_client, e2e_test_firewall):
label=label,
firewall=e2e_test_firewall.id,
client_udp_sess_throttle=5,
type="premium",
backend_connectivity="ipv6",
)

assert TEST_REGION, nb.region
assert label == nb.label
assert 5 == nb.client_udp_sess_throttle
assert nb.type == "premium"
assert nb.backend_connectivity == "ipv6"
assert nb.backend_ipv6_prefix is None or isinstance(
nb.backend_ipv6_prefix, str
)

nb.delete()

Expand Down
79 changes: 79 additions & 0 deletions test/unit/linode_client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from linode_api4.objects.beta import BetaProgram
from linode_api4.objects.linode import Instance
from linode_api4.objects.networking import IPAddress
from linode_api4.objects.nodebalancer import NodeBalancerBackendConnectivity
from linode_api4.objects.object_storage import (
ObjectStorageACL,
ObjectStorageCluster,
Expand Down Expand Up @@ -1589,6 +1590,84 @@ def test_nodebalancer_types(self):
self.assertEqual(types[0].region_prices[0].hourly, 0.018)
self.assertEqual(types[0].region_prices[0].monthly, 12)

def test_create_with_type_and_backend_connectivity(self):
"""
Tests that creating a NodeBalancer forwards type, backend_connectivity,
vpcs, and config node addresses.
"""
with self.mock_post(
{
"id": 1234,
"label": "my-premium-nb",
"type": "premium",
"backend_connectivity": "ipv6",
"backend_ipv6_prefix": "2600:3c22:1:20:0:3039::/96",
"region": "us-east",
}
) as m:
nb = self.client.nodebalancers.create(
"us-east",
label="my-premium-nb",
type="premium",
backend_connectivity=NodeBalancerBackendConnectivity.IPV6,
configs=[
{
"port": 80,
"nodes": [
{
"address": "[2001:db8:abcd:0012::1]:80",
"label": "node1",
}
],
}
],
)

self.assertEqual(m.call_url, "/nodebalancers")
self.assertEqual(m.call_data["region"], "us-east")
self.assertEqual(m.call_data["type"], "premium")
self.assertEqual(m.call_data["backend_connectivity"], "ipv6")
self.assertIs(type(m.call_data["backend_connectivity"]), str)
self.assertEqual(
m.call_data["configs"][0]["nodes"][0]["address"],
"[2001:db8:abcd:0012::1]:80",
)
self.assertEqual(nb.id, 1234)
self.assertEqual(nb.type, "premium")
self.assertEqual(nb.backend_connectivity, "ipv6")
self.assertEqual(
nb.backend_ipv6_prefix, "2600:3c22:1:20:0:3039::/96"
)
self.assertNotIn("backend_ipv6_prefix", m.call_data)

def test_create_with_vpc_backend_connectivity(self):
"""
Tests that creating a NodeBalancer forwards vpc backend connectivity.
"""
with self.mock_post(
{
"id": 1234,
"label": "my-nb",
"type": "common",
"backend_connectivity": "vpc",
"region": "us-east",
}
) as m:
nb = self.client.nodebalancers.create(
"us-east",
label="my-nb",
backend_connectivity="vpc",
vpcs=[{"subnet_id": 123456, "ipv4_range": "10.0.250.4/30"}],
)

self.assertEqual(m.call_url, "/nodebalancers")
self.assertEqual(m.call_data["backend_connectivity"], "vpc")
self.assertEqual(
m.call_data["vpcs"],
[{"subnet_id": 123456, "ipv4_range": "10.0.250.4/30"}],
)
self.assertEqual(nb.backend_connectivity, "vpc")


class VolumeGroupTest(ClientBaseCase):
"""
Expand Down
Loading
Loading