From 2a313062118f4b5d0fd7098e9b01085c2cd2980b Mon Sep 17 00:00:00 2001 From: ezilber-akamai Date: Fri, 17 Jan 2025 15:42:36 -0500 Subject: [PATCH] Added support for NodeBalancers UDP --- docs/inventory/instance.rst | 16 ++-- docs/modules/nodebalancer.md | 10 +- plugins/modules/nodebalancer.py | 41 ++++++-- requirements.txt | 4 +- .../targets/nodebalancer_udp/tasks/main.yaml | 95 +++++++++++++++++++ 5 files changed, 145 insertions(+), 21 deletions(-) create mode 100644 tests/integration/targets/nodebalancer_udp/tasks/main.yaml diff --git a/docs/inventory/instance.rst b/docs/inventory/instance.rst index c4f28a31..18445b50 100644 --- a/docs/inventory/instance.rst +++ b/docs/inventory/instance.rst @@ -76,19 +76,19 @@ Parameters **parent_group (type=str):** - \• parent group for keyed group. + \• parent group for keyed group **prefix (type=str):** - \• A keyed group name will start with this prefix. + \• A keyed group name will start with this prefix **separator (type=str, default=_):** - \• separator used to build the keyed group name. + \• separator used to build the keyed group name **key (type=str):** - \• The key from input dictionary used to generate groups. + \• The key from input dictionary used to generate groups **default_value (type=str):** @@ -98,7 +98,7 @@ Parameters **trailing_separator (type=bool, default=True):** - \• Set this option to :literal:`false` to omit the :literal:`keyed\_groups[].separator` after the host variable when the value is an empty string. + \• Set this option to :literal:`False` to omit the :literal:`keyed\_groups[].separator` after the host variable when the value is an empty string. \• This option is mutually exclusive with :literal:`keyed\_groups[].default\_value`. @@ -109,13 +109,13 @@ Parameters **leading_separator (type=boolean, default=True):** - \• Use in conjunction with :literal:`keyed\_groups`. + \• Use in conjunction with keyed\_groups. \• By default, a keyed group that does not have a prefix or a separator provided will have a name that starts with an underscore. - \• This is because the default prefix is :literal:`""` and the default separator is :literal:`"\_"`. + \• This is because the default prefix is "" and the default separator is "\_". - \• Set this option to :literal:`false` to omit the leading underscore (or other separator) if no prefix is given. + \• Set this option to False to omit the leading underscore (or other separator) if no prefix is given. \• If the group name is derived from a mapping the separator is still used to concatenate the items. diff --git a/docs/modules/nodebalancer.md b/docs/modules/nodebalancer.md index 7f5d487d..b3a58efa 100644 --- a/docs/modules/nodebalancer.md +++ b/docs/modules/nodebalancer.md @@ -46,6 +46,7 @@ Manage a Linode NodeBalancer. | `label` |
`str`
|
**Required**
| The unique label to give this NodeBalancer. | | `state` |
`str`
|
**Required**
| The desired state of the target. **(Choices: `present`, `absent`)** | | `client_conn_throttle` |
`int`
|
Optional
| Throttle connections per second. Set to 0 (zero) to disable throttling. **(Updatable)** | +| `client_udp_sess_throttle` |
`int`
|
Optional
| Throttle UDP sessions per second (0-20). Set to 0 (zero) to disable throttling. **(Updatable)** | | `region` |
`str`
|
Optional
| The ID of the Region to create this NodeBalancer in. | | `firewall_id` |
`int`
|
Optional
| The ID of the Firewall to assign this NodeBalancer to. | | `tags` |
`list`
|
Optional
| Tags to assign to this NodeBalancer. **(Updatable)** | @@ -55,7 +56,7 @@ Manage a Linode NodeBalancer. | Field | Type | Required | Description | |-----------|------|----------|------------------------------------------------------------------------------| -| `algorithm` |
`str`
|
Optional
| What algorithm this NodeBalancer should use for routing traffic to backends. **(Choices: `roundrobin`, `leastconn`, `source`; Updatable)** | +| `algorithm` |
`str`
|
Optional
| What algorithm this NodeBalancer should use for routing traffic to backends. **(Choices: `roundrobin`, `leastconn`, `source`, `ring_hash`; Updatable)** | | `check` |
`str`
|
Optional
| The type of check to perform against backends to ensure they are serving requests. **(Choices: `none`, `connection`, `http`, `http_body`; Updatable)** | | `check_attempts` |
`int`
|
Optional
| How many times to attempt a check before considering a backend to be down. **(Updatable)** | | `check_body` |
`str`
|
Optional
| This value must be present in the response body of the check in order for it to pass. If this value is not present in the response body of a check request, the backend is considered to be down. **(Updatable)** | @@ -63,14 +64,15 @@ Manage a Linode NodeBalancer. | `check_passive` |
`bool`
|
Optional
| If true, any response from this backend with a 5xx status code will be enough for it to be considered unhealthy and taken out of rotation. **(Updatable)** | | `check_path` |
`str`
|
Optional
| The URL path to check on each backend. If the backend does not respond to this request it is considered to be down. **(Updatable)** | | `check_timeout` |
`int`
|
Optional
| How long, in seconds, to wait for a check attempt before considering it failed. **(Updatable)** | -| `cipher_suite` |
`str`
|
Optional
| What ciphers to use for SSL connections served by this NodeBalancer. **(Choices: `recommended`, `legacy`; Default: `recommended`; Updatable)** | +| `udp_check_port` |
`int`
|
Optional
| Specifies the port on the backend node used for active health checks, which may differ from the port serving traffic. **(Updatable)** | +| `cipher_suite` |
`str`
|
Optional
| What ciphers to use for SSL connections served by this NodeBalancer. **(Choices: `recommended`, `legacy`, `none`; Updatable)** | | `port` |
`int`
|
Optional
| The port this Config is for. **(Updatable)** | -| `protocol` |
`str`
|
Optional
| The protocol this port is configured to serve. **(Choices: `http`, `https`, `tcp`; Updatable)** | +| `protocol` |
`str`
|
Optional
| The protocol this port is configured to serve. **(Choices: `http`, `https`, `tcp`, `udp`; Updatable)** | | `proxy_protocol` |
`str`
|
Optional
| ProxyProtocol is a TCP extension that sends initial TCP connection information such as source/destination IPs and ports to backend devices. **(Choices: `none`, `v1`, `v2`; Updatable)** | | `recreate` |
`bool`
|
Optional
| If true, the config will be forcibly recreated on every run. This is useful for updates to redacted fields (`ssl_cert`, `ssl_key`) **(Default: `False`)** | | `ssl_cert` |
`str`
|
Optional
| The PEM-formatted public SSL certificate (or the combined PEM-formatted SSL certificate and Certificate Authority chain) that should be served on this NodeBalancerConfig’s port. **(Updatable)** | | `ssl_key` |
`str`
|
Optional
| The PEM-formatted private key for the SSL certificate set in the ssl_cert field. **(Updatable)** | -| `stickiness` |
`str`
|
Optional
| Controls how session stickiness is handled on this port. **(Choices: `none`, `table`, `http_cookie`; Updatable)** | +| `stickiness` |
`str`
|
Optional
| Controls how session stickiness is handled on this port. **(Choices: `none`, `table`, `http_cookie`, `session`, `source_ip`; Updatable)** | | [`nodes` (sub-options)](#nodes) |
`list`
|
Optional
| A list of nodes to apply to this config. These can alternatively be configured through the nodebalancer_node module. **(Updatable)** | ### nodes diff --git a/plugins/modules/nodebalancer.py b/plugins/modules/nodebalancer.py index 124acdf9..14cbc98f 100644 --- a/plugins/modules/nodebalancer.py +++ b/plugins/modules/nodebalancer.py @@ -76,7 +76,7 @@ "What algorithm this NodeBalancer should use " "for routing traffic to backends." ], - choices=["roundrobin", "leastconn", "source"], + choices=["roundrobin", "leastconn", "source", "ring_hash"], ), "check": SpecField( type=FieldType.string, @@ -143,15 +143,23 @@ "failed." ], ), + "udp_check_port": SpecField( + type=FieldType.integer, + required=False, + editable=True, + description=[ + "Specifies the port on the backend node used for active health checks, which " + "may differ from the port serving traffic." + ], + ), "cipher_suite": SpecField( type=FieldType.string, required=False, - default="recommended", editable=True, description=[ "What ciphers to use for SSL connections served by this NodeBalancer." ], - choices=["recommended", "legacy"], + choices=["recommended", "legacy", "none"], ), "port": SpecField( type=FieldType.integer, @@ -164,7 +172,7 @@ required=False, editable=True, description=["The protocol this port is configured to serve."], - choices=["http", "https", "tcp"], + choices=["http", "https", "tcp", "udp"], ), "proxy_protocol": SpecField( type=FieldType.string, @@ -211,7 +219,7 @@ description=[ "Controls how session stickiness is handled on this port." ], - choices=["none", "table", "http_cookie"], + choices=["none", "table", "http_cookie", "session", "source_ip"], ), "nodes": SpecField( type=FieldType.list, @@ -240,6 +248,14 @@ "Set to 0 (zero) to disable throttling.", ], ), + "client_udp_sess_throttle": SpecField( + type=FieldType.integer, + editable=True, + description=[ + "Throttle UDP sessions per second (0-20).", + "Set to 0 (zero) to disable throttling.", + ], + ), "region": SpecField( type=FieldType.string, description=["The ID of the Region to create this NodeBalancer in."], @@ -305,7 +321,11 @@ }, ) -MUTABLE_FIELDS: Set[str] = {"client_conn_throttle", "tags"} +MUTABLE_FIELDS: Set[str] = { + "client_conn_throttle", + "tags", + "client_udp_sess_throttle", +} DOCUMENTATION = r""" """ @@ -372,7 +392,14 @@ def _create_nodebalancer(self) -> Optional[NodeBalancer]: params = { k: v for k, v in self.module.params.items() - if k in {"client_conn_throttle", "label", "firewall_id", "tags"} + if k + in { + "client_udp_sess_throttle", + "client_conn_throttle", + "label", + "firewall_id", + "tags", + } } try: diff --git a/requirements.txt b/requirements.txt index 3490bb5b..ed9b03c2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -linode-api4>=5.24.0 +git+https://github.com/linode/python-linode-api.git@2d23cefc18bc17d80ca117a6e372ff5ef937ff38#egg=linode-api4 polling==0.3.2 -ansible-specdoc>=0.0.15 +ansible-specdoc>=0.0.15 \ No newline at end of file diff --git a/tests/integration/targets/nodebalancer_udp/tasks/main.yaml b/tests/integration/targets/nodebalancer_udp/tasks/main.yaml new file mode 100644 index 00000000..7c75b7c5 --- /dev/null +++ b/tests/integration/targets/nodebalancer_udp/tasks/main.yaml @@ -0,0 +1,95 @@ +- name: nodebalancer_udp + block: + - set_fact: + r: "{{ 1000000000 | random }}" + + - name: create nodebalancer + linode.cloud.nodebalancer: + label: 'ansible-test-{{ r }}' + region: ap-northeast + client_udp_sess_throttle: 3 + state: present + firewall_id: '{{ firewall_id }}' + register: create_nodebalancer + + - name: Assert NodeBalancer is created + assert: + that: + - create_nodebalancer.changed + - create_nodebalancer.configs|length == 0 + - create_nodebalancer.node_balancer.client_udp_sess_throttle == 3 + + - name: Add NodeBalancer config + linode.cloud.nodebalancer: + label: '{{ create_nodebalancer.node_balancer.label }}' + region: ap-northeast + client_udp_sess_throttle: 3 + state: present + configs: + - port: 80 + protocol: udp + algorithm: roundrobin + udp_check_port: 12345 + register: create_config + + - name: Assert nb config is added + assert: + that: + - create_config.configs|length == 1 + - create_config.configs[0].port == 80 + - create_config.configs[0].udp_check_port == 12345 + + - name: Update NodeBalancer config + linode.cloud.nodebalancer: + label: '{{ create_nodebalancer.node_balancer.label }}' + region: ap-northeast + client_udp_sess_throttle: 3 + state: present + configs: + - port: 80 + protocol: udp + algorithm: roundrobin + udp_check_port: 1234 + register: update_config + + - name: Assert nb config is updated + assert: + that: + - update_config.configs|length == 1 + - update_config.configs[0].udp_check_port == 1234 + - update_config.changed + + - name: Get nodebalancer_info + linode.cloud.nodebalancer_info: + label: '{{ create_nodebalancer.node_balancer.label }}' + register: nb_info + + - name: Assert nb info + assert: + that: + - nb_info.node_balancer.id == create_nodebalancer.node_balancer.id + - nb_info.configs[0].udp_check_port == 1234 + - nb_info.configs[0].udp_session_timeout == 16 + - nb_info.node_balancer.client_udp_sess_throttle == 3 + + always: + - ignore_errors: yes + block: + - name: Delete the NodeBalancer + linode.cloud.nodebalancer: + label: '{{ create_nodebalancer.node_balancer.label }}' + state: absent + register: delete + + - name: Assert NodeBalancer delete + assert: + that: + - delete.changed + - delete.node_balancer.id == create_nodebalancer.node_balancer.id + + environment: + LINODE_UA_PREFIX: '{{ ua_prefix }}' + LINODE_API_TOKEN: '{{ api_token }}' + LINODE_API_URL: '{{ api_url }}' + LINODE_API_VERSION: '{{ api_version }}' + LINODE_CA: '{{ ca_file or "" }}' \ No newline at end of file