Skip to content
66 changes: 65 additions & 1 deletion linode_api4/objects/nodebalancer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from linode_api4.common import Price, RegionPrice
from linode_api4.errors import UnexpectedResponseError
from linode_api4.objects.base import Base, MappedObject, Property
from linode_api4.objects.base import Base, MappedObject, Property, ExplicitNullValue, _flatten_request_body_recursive
from linode_api4.objects.dbase import DerivedBase
from linode_api4.objects.networking import Firewall, IPAddress
from linode_api4.objects.region import Region
Expand Down Expand Up @@ -97,6 +97,8 @@ class NodeBalancerConfig(DerivedBase):
"check_path": Property(mutable=True),
"check_body": Property(mutable=True),
"check_passive": Property(mutable=True),
"udp_check_port": Property(mutable=True),
"udp_session_timeout": Property(),
"ssl_cert": Property(mutable=True),
"ssl_key": Property(mutable=True),
"ssl_commonname": Property(),
Expand All @@ -106,6 +108,67 @@ class NodeBalancerConfig(DerivedBase):
"proxy_protocol": Property(mutable=True),
}

def _serialize(self):
"""
A helper method to build a dict of all mutable Properties of
this NodeBalancerConfig
"""

result = {}

# Aggregate mutable values into a dict
for k, v in NodeBalancerConfig.properties.items():
if not v.mutable:
continue

value = getattr(self, k)

if not v.nullable and (value is None or value == ""):
continue

# Exclude cipher_suite if protocol is udp
if k == "cipher_suite" and getattr(self, "protocol", None) == "udp":
continue

# Allow explicit null values
if (
isinstance(value, ExplicitNullValue)
or value == ExplicitNullValue
):
value = None

result[k] = value

# Resolve the underlying IDs of results
for k, v in result.items():
result[k] = _flatten_request_body_recursive(v)

return result

def save(self, force=True) -> bool:
Comment thread
ezilber-akamai marked this conversation as resolved.
"""
Send this NodeBalancerConfig's mutable values to the server in a PUT request.
:param force: If true, this method will always send a PUT request regardless of
whether the field has been explicitly updated. For optimization
purposes, this field should be set to false for typical update
operations. (Defaults to True)
:type force: bool
"""

if not force and not self._changed:
return False

data = self._serialize()

result = self._client.put(NodeBalancerConfig.api_endpoint, model=self, data=data)

if "error" in result:
return False

self._populate(result)

return True

@property
def nodes(self):
"""
Expand Down Expand Up @@ -233,6 +296,7 @@ class NodeBalancer(Base):
"configs": Property(derived_class=NodeBalancerConfig),
"transfer": Property(),
"tags": Property(mutable=True, unordered=True),
"client_udp_sess_throttle": Property(mutable=True),
}

# create derived objects
Expand Down
28 changes: 27 additions & 1 deletion test/fixtures/nodebalancers_123456_configs.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,35 @@
"protocol": "http",
"ssl_fingerprint": "",
"proxy_protocol": "none"
},
{
"check": "connection",
"check_attempts": 2,
"stickiness": "table",
"check_interval": 5,
"check_body": "",
"id": 65431,
"check_passive": true,
"algorithm": "roundrobin",
"check_timeout": 3,
"check_path": "/",
"ssl_cert": null,
"ssl_commonname": "",
"port": 80,
"nodebalancer_id": 123456,
"cipher_suite": "recommended",
"ssl_key": null,
"nodes_status": {
"up": 0,
"down": 0
},
"protocol": "udp",
"ssl_fingerprint": "",
"proxy_protocol": "none",
"udp_check_port": 12345
}
],
"results": 1,
"results": 2,
"page": 1,
"pages": 1
}
12 changes: 11 additions & 1 deletion test/fixtures/nodebalancers_123456_configs_65432_nodes.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,19 @@
"mode": "accept",
"config_id": 54321,
"nodebalancer_id": 123456
},
{
"id": 12345,
"address": "192.168.210.120",
"label": "node12345",
"status": "UP",
"weight": 50,
"mode": "none",
"config_id": 123456,
"nodebalancer_id": 123456
}
],
"pages": 1,
"page": 1,
"results": 1
"results": 2
}
110 changes: 109 additions & 1 deletion test/integration/models/nodebalancer/test_nodebalancer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import pytest

from linode_api4 import ApiError, LinodeClient
from linode_api4 import ApiError, ExplicitNullValue, LinodeClient, NodeBalancer
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
from linode_api4.objects import (
NodeBalancerConfig,
NodeBalancerNode,
Expand Down Expand Up @@ -64,6 +64,55 @@ def create_nb_config(test_linode_client, e2e_test_firewall):
nb.delete()


@pytest.fixture(scope="session")
def create_nb_config_with_udp(test_linode_client, e2e_test_firewall):
client = test_linode_client
label = get_test_label(8)

nb = client.nodebalancer_create(
region=TEST_REGION, label=label, firewall=e2e_test_firewall.id
)

config = nb.config_create(protocol="udp", udp_check_port=1234)

yield config

config.delete()
nb.delete()


@pytest.fixture(scope="session")
def create_nb(test_linode_client, e2e_test_firewall):
client = test_linode_client
label = get_test_label(8)

nb = client.nodebalancer_create(
region=TEST_REGION, label=label, firewall=e2e_test_firewall.id
)

yield nb

nb.delete()


def test_create_nb(test_linode_client, e2e_test_firewall):
client = test_linode_client
label = get_test_label(8)

nb = client.nodebalancer_create(
region=TEST_REGION,
label=label,
firewall=e2e_test_firewall.id,
client_udp_sess_throttle=5,
)

assert TEST_REGION, nb.region
assert label == nb.label
assert 5 == nb.client_udp_sess_throttle

nb.delete()


def test_get_nodebalancer_config(test_linode_client, create_nb_config):
config = test_linode_client.load(
NodeBalancerConfig,
Expand All @@ -72,6 +121,65 @@ def test_get_nodebalancer_config(test_linode_client, create_nb_config):
)


def test_get_nb_config_with_udp(test_linode_client, create_nb_config_with_udp):
config = test_linode_client.load(
NodeBalancerConfig,
create_nb_config_with_udp.id,
create_nb_config_with_udp.nodebalancer_id,
)

assert "udp" == config.protocol
assert 1234 == config.udp_check_port
assert 16 == config.udp_session_timeout


def test_update_nb_config(test_linode_client, create_nb_config_with_udp):
config = test_linode_client.load(
NodeBalancerConfig,
create_nb_config_with_udp.id,
create_nb_config_with_udp.nodebalancer_id,
)

config.udp_check_port = 4321
config.save()

config_updated = test_linode_client.load(
NodeBalancerConfig,
create_nb_config_with_udp.id,
create_nb_config_with_udp.nodebalancer_id,
)

assert 4321 == config_updated.udp_check_port


def test_get_nb(test_linode_client, create_nb):
nb = test_linode_client.load(
Comment thread Fixed
NodeBalancer,
create_nb.id,
)

assert nb.id == create_nb.id


def test_update_nb(test_linode_client, create_nb):
nb = test_linode_client.load(
NodeBalancer,
create_nb.id,
)

nb.label = "ThisNewLabel"
nb.client_udp_sess_throttle = 5
nb.save()

nb_updated = test_linode_client.load(
NodeBalancer,
create_nb.id,
)

assert "ThisNewLabel" == nb_updated.label
assert 5 == nb_updated.client_udp_sess_throttle


@pytest.mark.smoke
def test_create_nb_node(
test_linode_client, create_nb_config, linode_with_private_ip
Expand Down
7 changes: 7 additions & 0 deletions test/unit/objects/nodebalancers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ def test_get_config(self):
self.assertEqual(config.ssl_fingerprint, "")
self.assertEqual(config.proxy_protocol, "none")

config_udp = NodeBalancerConfig(self.client, 65431, 123456)
self.assertEqual(config_udp.protocol, "udp")
self.assertEqual(config_udp.udp_check_port, 12345)


class NodeBalancerNodeTest(ClientBaseCase):
"""
Expand All @@ -66,6 +70,9 @@ def test_get_node(self):
self.assertEqual(node.config_id, 65432)
self.assertEqual(node.nodebalancer_id, 123456)

node_udp = NodeBalancerNode(self.client, 12345, (65432, 123456))
self.assertEqual(node_udp.mode, "none")

def test_create_node(self):
"""
Tests that a node can be created
Comment thread
ezilber-akamai marked this conversation as resolved.
Expand Down