Refactored lb_{health_monitor,listener,member,pool} modules

Change-Id: Iffd6ffb08aae4cbd84e4cade79993d82e8c2b2de
This commit is contained in:
Jakob Meng 2022-12-13 11:07:57 +01:00
parent 407b50c8b2
commit 97c4531d15
6 changed files with 1608 additions and 896 deletions

View File

@ -0,0 +1,322 @@
---
- name: Create external network
openstack.cloud.network:
cloud: "{{ cloud }}"
external: true
name: ansible_external_network
state: present
- name: Create external subnet
openstack.cloud.subnet:
cidr: 10.6.6.0/24
cloud: "{{ cloud }}"
name: ansible_external_subnet
network_name: ansible_external_network
state: present
- name: Create internal network
openstack.cloud.network:
cloud: "{{ cloud }}"
name: ansible_internal_network
state: present
- name: Create internal subnet
openstack.cloud.subnet:
cloud: "{{ cloud }}"
state: present
network_name: ansible_internal_network
name: ansible_internal_subnet
cidr: 10.7.7.0/24
- name: Create router
openstack.cloud.router:
cloud: "{{ cloud }}"
external_fixed_ips:
- subnet: ansible_external_subnet
ip: 10.6.6.10
interfaces:
- net: ansible_internal_network
subnet: ansible_internal_subnet
portip: 10.7.7.1
name: ansible_router
network: ansible_external_network
state: present
- name: Create load-balancer
openstack.cloud.loadbalancer:
assign_floating_ip: true
cloud: "{{ cloud }}"
floating_ip_network: ansible_external_network
name: ansible_lb
state: present
timeout: 450
vip_subnet: ansible_internal_subnet
register: load_balancer
- name: Create load-balancer listener
openstack.cloud.lb_listener:
cloud: "{{ cloud }}"
load_balancer: ansible_lb
name: ansible_listener
protocol: HTTP
protocol_port: 8080
state: present
register: listener
- name: Assert return values of lb_listener module
assert:
that:
# allow new fields to be introduced but prevent fields from being removed
- "['allowed_cidrs', 'alpn_protocols', 'connection_limit', 'created_at', 'default_pool', 'default_pool_id',
'default_tls_container_ref', 'description', 'id', 'insert_headers', 'is_admin_state_up', 'l7_policies',
'load_balancer_id', 'load_balancers', 'name', 'operating_status', 'project_id', 'protocol', 'protocol_port',
'provisioning_status', 'sni_container_refs', 'tags', 'timeout_client_data', 'timeout_member_connect',
'timeout_member_data', 'timeout_tcp_inspect', 'tls_ciphers', 'tls_versions', 'updated_at'
]|difference(listener.listener.keys())|length == 0"
- name: Create load-balancer listener again
openstack.cloud.lb_listener:
cloud: "{{ cloud }}"
load_balancer: ansible_lb
name: ansible_listener
protocol: HTTP
protocol_port: 8080
state: present
register: listener
- name: Assert return values of lb_listener module
assert:
that:
- listener is not changed
- name: Update load-balancer listener description
openstack.cloud.lb_listener:
cloud: "{{ cloud }}"
description: "Ansible load-balancer listener"
load_balancer: ansible_lb
name: ansible_listener
protocol: HTTP
protocol_port: 8080
state: present
register: listener
- name: Assert return values of lb_listener module
assert:
that:
- listener.listener.description == "Ansible load-balancer listener"
- name: Create load-balancer pool
openstack.cloud.lb_pool:
cloud: "{{ cloud }}"
lb_algorithm: ROUND_ROBIN
listener: "{{ listener.listener.id }}"
name: ansible_pool
protocol: HTTP
state: present
register: pool
- name: Assert return values of lb_pool module
assert:
that:
# allow new fields to be introduced but prevent fields from being removed
- "['alpn_protocols', 'created_at', 'description', 'health_monitor_id', 'id', 'is_admin_state_up', 'lb_algorithm',
'listener_id', 'listeners', 'loadbalancer_id', 'loadbalancers', 'members','name', 'operating_status',
'project_id', 'protocol', 'provisioning_status', 'session_persistence', 'tags', 'tls_ciphers', 'tls_enabled',
'tls_versions', 'updated_at'
]|difference(pool.pool.keys())|length == 0"
- name: Create load-balancer pool again
openstack.cloud.lb_pool:
cloud: "{{ cloud }}"
lb_algorithm: ROUND_ROBIN
listener: "{{ listener.listener.id }}"
name: ansible_pool
protocol: HTTP
state: present
register: pool
- name: Assert return values of lb_pool module
assert:
that:
- pool is not changed
- name: Update load-balancer pool description
openstack.cloud.lb_pool:
cloud: "{{ cloud }}"
description: "Ansible load-balancer pool"
lb_algorithm: ROUND_ROBIN
listener: "{{ listener.listener.id }}"
name: ansible_pool
protocol: HTTP
state: present
register: pool
- name: Assert return values of lb_pool module
assert:
that:
- pool.pool.description == "Ansible load-balancer pool"
- name: Create load-balancer pool member
openstack.cloud.lb_member:
address: 10.7.7.42
cloud: "{{ cloud }}"
name: ansible_member
pool: ansible_pool
protocol_port: 8080
state: present
register: member
- name: Assert return values of lb_member module
assert:
that:
# allow new fields to be introduced but prevent fields from being removed
- "['address', 'backup', 'created_at', 'id', 'is_admin_state_up', 'monitor_address', 'monitor_port', 'name',
'operating_status', 'project_id', 'protocol_port', 'provisioning_status', 'subnet_id', 'tags', 'updated_at',
'weight'
]|difference(member.member.keys())|length == 0"
- name: Create load-balancer pool member again
openstack.cloud.lb_member:
address: 10.7.7.42
cloud: "{{ cloud }}"
name: ansible_member
pool: ansible_pool
protocol_port: 8080
state: present
register: member
- name: Assert return values of lb_member module
assert:
that:
- member is not changed
- name: Update load-balancer pool member weight
openstack.cloud.lb_member:
address: 10.7.7.42
cloud: "{{ cloud }}"
name: ansible_member
pool: ansible_pool
protocol_port: 8080
state: present
weight: 42
register: member
- name: Assert return values of lb_member module
assert:
that:
- member.member.weight == 42
- name: Create load-balancer health monitor
openstack.cloud.lb_health_monitor:
cloud: "{{ cloud }}"
delay: 10
health_monitor_timeout: 5
max_retries: 3
name: ansible_health_monitor
pool: ansible_pool
state: present
register: health_monitor
- name: Assert return values of lb_health_monitor module
assert:
that:
# allow new fields to be introduced but prevent fields from being removed
- "['created_at', 'delay', 'expected_codes', 'http_method', 'id', 'is_admin_state_up', 'max_retries',
'max_retries_down', 'name', 'operating_status', 'pool_id', 'pools', 'project_id', 'provisioning_status',
'tags', 'timeout', 'type', 'updated_at', 'url_path'
]|difference(health_monitor.health_monitor.keys())|length == 0"
- name: Create load-balancer health monitor again
openstack.cloud.lb_health_monitor:
cloud: "{{ cloud }}"
delay: 10
health_monitor_timeout: 5
max_retries: 3
name: ansible_health_monitor
pool: ansible_pool
state: present
register: health_monitor
- name: Assert return values of lb_health_monitor module
assert:
that:
- health_monitor is not changed
- name: Update load-balancer health monitor delay
openstack.cloud.lb_health_monitor:
cloud: "{{ cloud }}"
delay: 1337
health_monitor_timeout: 5
max_retries: 3
name: ansible_health_monitor
pool: ansible_pool
state: present
register: health_monitor
- name: Assert return values of lb_health_monitor module
assert:
that:
- health_monitor.health_monitor.delay == 1337
- name: Delete load-balancer health monitor
openstack.cloud.lb_health_monitor:
cloud: "{{ cloud }}"
name: ansible_health_monitor
state: absent
- name: Delete load-balancer pool member
openstack.cloud.lb_member:
cloud: "{{ cloud }}"
name: ansible_member
pool: ansible_pool
state: absent
- name: Delete load-balancer pool
openstack.cloud.lb_pool:
cloud: "{{ cloud }}"
name: ansible_pool
state: absent
- name: Delete load-balancer listener
openstack.cloud.lb_listener:
cloud: "{{ cloud }}"
name: ansible_listener
state: absent
- name: Delete load-balancer
openstack.cloud.loadbalancer:
cloud: "{{ cloud }}"
delete_floating_ip: true
name: ansible_lb
state: absent
timeout: 150
- name: Delete router
openstack.cloud.router:
cloud: "{{ cloud }}"
name: ansible_router
state: absent
- name: Delete internal subnet
openstack.cloud.subnet:
cloud: "{{ cloud }}"
name: ansible_internal_subnet
state: absent
- name: Delete internal network
openstack.cloud.network:
cloud: "{{ cloud }}"
name: ansible_internal_network
state: absent
- name: Delete external subnet
openstack.cloud.subnet:
cloud: "{{ cloud }}"
name: ansible_external_subnet
state: absent
- name: Delete external network
openstack.cloud.network:
cloud: "{{ cloud }}"
name: ansible_external_network
state: absent

View File

@ -282,3 +282,5 @@
cloud: "{{ cloud }}"
name: ansible_external_network
state: absent
- import_tasks: lb_modules.yml

View File

@ -4,287 +4,356 @@
# Copyright (c) 2020 Jesper Schmitz Mouridsen.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
DOCUMENTATION = r'''
---
module: lb_health_monitor
author: OpenStack Ansible SIG
short_description: Add/Delete a health m nonitor to a pool in the load balancing service from OpenStack Cloud
short_description: Manage health monitor in a OpenStack load-balancer pool
description:
- Add or Remove a health monitor to/from a pool in the OpenStack load-balancer service.
- Add, update or remove health monitor from a load-balancer pool in OpenStack
cloud.
options:
name:
type: 'str'
description:
- Name that has to be given to the health monitor
required: true
state:
type: 'str'
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
pool:
required: true
type: 'str'
description:
- The pool name or id to monitor by the health monitor.
type:
type: 'str'
default: HTTP
description:
- One of HTTP, HTTPS, PING, SCTP, TCP, TLS-HELLO, or UDP-CONNECT.
choices: [HTTP, HTTPS, PING, SCTP, TCP, TLS-HELLO, UDP-CONNECT]
delay:
type: 'str'
required: true
description:
- the interval, in seconds, between health checks.
max_retries:
required: true
type: 'str'
description:
- The number of successful checks before changing the operating status of the member to ONLINE.
max_retries_down:
type: 'str'
default: '3'
description:
- The number of allowed check failures before changing the operating status of the member to ERROR. A valid value is from 1 to 10. The default is 3.
resp_timeout:
required: true
description:
- The time, in seconds, after which a health check times out. Must be less than delay
type: int
admin_state_up:
default: True
description:
- The admin state of the helath monitor true for up or false for down
type: bool
expected_codes:
type: 'str'
default: '200'
description:
- The list of HTTP status codes expected in response from the member to declare it healthy. Specify one of the following values
A single value, such as 200
A list, such as 200, 202
A range, such as 200-204
http_method:
type: 'str'
default: GET
choices: ['GET', 'CONNECT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE']
description:
- The HTTP method that the health monitor uses for requests. One of CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, or TRACE. The default is GET.
url_path:
type: 'str'
default: '/'
description:
- The HTTP URL path of the request sent by the monitor to test the health of a backend member.
Must be a string that begins with a forward slash (/). The default URL path is /.
requirements: ["openstacksdk"]
delay:
description:
- The interval, in seconds, between health checks.
- Required when I(state) is C(present).
type: int
expected_codes:
description:
- The list of HTTP status codes expected in response from the member to
declare it healthy. Specify one of the following values.
- For example, I(expected_codes) could be a single value, such as C(200),
a list, such as C(200, 202) or a range, such as C(200-204).
- "Octavia's default for I(expected_codes) is C(200)."
type: str
health_monitor_timeout:
description:
- The time, in seconds, after which a health check times out.
- Must be less than I(delay).
- Required when I(state) is C(present).
type: int
aliases: ['resp_timeout']
http_method:
description:
- The HTTP method that the health monitor uses for requests.
- For example, I(http_method) could be C(CONNECT), C(DELETE), C(GET),
C(HEAD), C(OPTIONS), C(PATCH), C(POST), C(PUT), or C(TRACE).
- "Octavia's default for I(http_method) is C(GET)."
type: str
is_admin_state_up:
description:
- Whether the health monitor is up or down.
type: bool
aliases: ['admin_state_up']
max_retries:
description:
- The number of successful checks before changing the operating status
of the member to ONLINE.
- Required when I(state) is C(present).
type: int
max_retries_down:
description:
- The number of allowed check failures before changing the operating
status of the member to ERROR. A valid value is from 1 to 10.
type: int
name:
description:
- Name that has to be given to the health monitor.
- This attribute cannot be updated.
type: str
required: true
pool:
description:
- The pool name or id to monitor by the health monitor.
- Required when I(state) is C(present).
- This attribute cannot be updated.
type: str
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
type: str
type:
default: HTTP
description:
- The type of health monitor.
- For example, I(type) could be C(HTTP), C(HTTPS), C(PING), C(SCTP),
C(TCP), C(TLS-HELLO) or C(UDP-CONNECT).
- This attribute cannot be updated.
type: str
url_path:
description:
- The HTTP URL path of the request sent by the monitor to test the health
of a backend member.
- Must be a string that begins with a forward slash (C(/)).
- "Octavia's default URL path is C(/)."
type: str
requirements:
- "python >= 3.6"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
- openstack.cloud.openstack
'''
EXAMPLES = '''
#Create a healtmonitor named healthmonitor01 with method HEAD url_path /status and expect code 200
- openstack.cloud.lb_health_monitor:
auth:
auth_url: "{{keystone_url}}"
username: "{{username}}"
password: "{{password}}"
project_domain_name: "{{domain_name}}"
user_domain_name: "{{domain_name}}"
project_name: "{{project_name}}"
wait: true
admin_state_up: True
expected_codes: '200'
max_retries_down: '4'
http_method: GET
url_path: "/status"
pool: '{{pool_id}}'
name: 'healthmonitor01'
delay: '10'
max_retries: '3'
resp_timeout: '5'
state: present
'''
RETURN = '''
health_monitor:
description: Dictionary describing the health monitor.
returned: On success when C(state=present)
type: complex
contains:
id:
description: The health monitor UUID.
returned: On success when C(state=present)
type: str
admin_state_up:
returned: On success when C(state=present)
description: The administrative state of the resource.
type: bool
created_at:
returned: On success when C(state=present)
description: The UTC date and timestamp when the resource was created.
type: str
delay:
returned: On success when C(state=present)
description: The time, in seconds, between sending probes to members.
type: int
expected_codes:
returned: On success when C(state=present)
description: The list of HTTP status codes expected in response from the member to declare it healthy.
type: str
http_method:
returned: On success when C(state=present)
description: The HTTP method that the health monitor uses for requests.
type: str
max_retries:
returned: On success when C(state=present)
description: The number of successful checks before changing the operating status of the member to ONLINE.
type: str
max_retries_down:
returned: On success when C(state=present)
description: The number of allowed check failures before changing the operating status of the member to ERROR.
type: str
name:
returned: On success when C(state=present)
description: Human-readable name of the resource.
type: str
operating_status:
returned: On success when C(state=present)
description: The operating status of the resource.
type: str
pool_id:
returned: On success when C(state=present)
description: The id of the pool.
type: str
project_id:
returned: On success when C(state=present)
description: The ID of the project owning this resource.
type: str
provisioning_status:
returned: On success when C(state=present)
description: The provisioning status of the resource.
type: str
timeout:
returned: On success when C(state=present)
description: The maximum time, in seconds, that a monitor waits to connect before it times out.
type: int
type:
returned: On success when C(state=present)
description: The type of health monitor.
type: str
updated_at:
returned: On success when C(state=present)
description: The UTC date and timestamp when the resource was last updated.
type: str
url_path:
returned: On success when C(state=present)
description: The HTTP URL path of the request sent by the monitor to test the health of a backend member.
type: str
'''
import time
RETURN = r'''
health_monitor:
description: Dictionary describing the load-balancer health monitor.
returned: On success when I(state) is C(present).
type: dict
contains:
created_at:
description: The UTC date and timestamp when the resource was created.
type: str
delay:
description: The time, in seconds, between sending probes to members.
type: int
expected_codes:
description: The list of HTTP status codes expected in response from the
member to declare it healthy.
type: str
http_method:
description: The HTTP method that the health monitor uses for requests.
type: str
id:
description: The health monitor UUID.
type: str
is_admin_state_up:
description: The administrative state of the resource.
type: bool
max_retries:
description: The number of successful checks before changing the
operating status of the member to ONLINE.
type: int
max_retries_down:
description: The number of allowed check failures before changing the
operating status of the member to ERROR.
type: int
name:
description: Human-readable name of the resource.
type: str
operating_status:
description: The operating status of the resource.
type: str
pool_id:
description: The id of the pool.
type: str
pools:
description: List of associated pool ids.
type: list
project_id:
description: The ID of the project owning this resource.
type: str
provisioning_status:
description: The provisioning status of the resource.
type: str
tags:
description: A list of associated tags.
type: list
timeout:
description: The maximum time, in seconds, that a monitor waits to
connect before it times out.
type: int
type:
description: The type of health monitor.
type: str
updated_at:
description: The UTC date and timestamp when the resource was last
updated.
type: str
url_path:
description: The HTTP URL path of the request sent by the monitor to
test the health of a backend member.
type: str
'''
EXAMPLES = r'''
- name: Create a load-balancer health monitor
openstack.cloud.lb_health_monitor:
cloud: devstack
delay: 10
expected_codes: '200'
health_monitor_timeout: 5
http_method: GET
is_admin_state_up: true
max_retries: 3
max_retries_down: 4
name: healthmonitor01
pool: lb_pool
state: present
url_path: '/status'
- name: Delete a load-balancer health monitor
openstack.cloud.lb_health_monitor:
cloud: devstack
name: healthmonitor01
state: absent
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class HealthMonitorModule(OpenStackModule):
def _wait_for_health_monitor_status(self, health_monitor_id, status, failures, interval=5):
timeout = self.params['timeout']
total_sleep = 0
if failures is None:
failures = []
while total_sleep < timeout:
health_monitor = self.conn.load_balancer.get_health_monitor(health_monitor_id)
provisioning_status = health_monitor.provisioning_status
if provisioning_status == status:
return health_monitor
if provisioning_status in failures:
self._fail_json(
msg="health monitor %s transitioned to failure state %s" %
(health_monitor, provisioning_status)
)
time.sleep(interval)
total_sleep += interval
self._fail_json(msg="timeout waiting for health monitor %s to transition to %s" %
(health_monitor_id, status)
)
class LoadBalancerHealthMonitorModule(OpenStackModule):
argument_spec = dict(
delay=dict(type='int'),
expected_codes=dict(),
health_monitor_timeout=dict(type='int', aliases=['resp_timeout']),
http_method=dict(),
is_admin_state_up=dict(type='bool', aliases=['admin_state_up']),
max_retries=dict(type='int'),
max_retries_down=dict(type='int'),
name=dict(required=True),
delay=dict(required=True),
max_retries=dict(required=True),
max_retries_down=dict(default="3"),
resp_timeout=dict(required=True, type='int'),
pool=dict(required=True),
expected_codes=dict(default="200"),
admin_state_up=dict(default=True, type='bool'),
pool=dict(),
state=dict(default='present', choices=['absent', 'present']),
http_method=dict(default="GET", choices=["GET", "CONNECT", "DELETE",
"HEAD", "OPTIONS", "PATCH",
"POST", "PUT", "TRACE"]),
url_path=dict(default="/"),
type=dict(default='HTTP',
choices=['HTTP', 'HTTPS', 'PING', 'SCTP', 'TCP', 'TLS-HELLO', 'UDP-CONNECT']))
type=dict(default='HTTP'),
url_path=dict(),
)
module_kwargs = dict(supports_check_mode=True)
module_kwargs = dict(
required_if=[
('state', 'present', ('delay', 'health_monitor_timeout',
'max_retries', 'pool',)),
],
supports_check_mode=True,
)
def run(self):
state = self.params['state']
try:
changed = False
health_monitor = self.conn.load_balancer.find_health_monitor(name_or_id=self.params['name'])
pool = self.conn.load_balancer.find_pool(name_or_id=self.params['pool'])
if self.params['state'] == 'present':
if not health_monitor:
changed = True
health_attrs = {"pool_id": pool.id,
"type": self.params["type"],
"delay": self.params['delay'],
"max_retries": self.params['max_retries'],
"max_retries_down": self.params['max_retries_down'],
"timeout": self.params['resp_timeout'],
"name": self.params['name'],
"admin_state_up": self.params["admin_state_up"],
}
if self.params["type"] in ["HTTP", "HTTPS"]:
health_attrs["expected_codes"] = self.params["expected_codes"]
health_attrs["http_method"] = self.params["http_method"]
health_attrs["url_path"] = self.params["url_path"]
health_monitor = self._find()
if self.ansible.check_mode:
self.exit_json(changed=True)
if self.ansible.check_mode:
self.exit_json(changed=self._will_change(state, health_monitor))
health_monitor = self.conn.load_balancer.create_health_monitor(**health_attrs)
if not self.params['wait']:
self.exit_json(changed=changed, id=health_monitor.id,
health_monitor=health_monitor.to_dict())
else:
health_monitor = self._wait_for_health_monitor_status(health_monitor.id, "ACTIVE", ["ERROR"])
self.exit_json(changed=changed, id=health_monitor.id,
health_monitor=health_monitor.to_dict())
else:
self.exit_json(changed=changed, id=health_monitor.id,
health_monitor=health_monitor.to_dict()
)
elif self.params['state'] == 'absent':
if health_monitor:
if self.ansible.check_mode:
self.exit_json(changed=True)
self.conn.load_balancer.delete_health_monitor(health_monitor)
changed = True
if state == 'present' and not health_monitor:
# Create health_monitor
health_monitor = self._create()
self.exit_json(
changed=True,
health_monitor=health_monitor.to_dict(computed=False))
self.exit_json(changed=changed)
except Exception as e:
self.fail(msg=str(e))
elif state == 'present' and health_monitor:
# Update health_monitor
update = self._build_update(health_monitor)
if update:
health_monitor = self._update(health_monitor, update)
self.exit_json(
changed=bool(update),
health_monitor=health_monitor.to_dict(computed=False))
elif state == 'absent' and health_monitor:
# Delete health_monitor
self._delete(health_monitor)
self.exit_json(changed=True)
elif state == 'absent' and not health_monitor:
# Do nothing
self.exit_json(changed=False)
def _build_update(self, health_monitor):
update = {}
non_updateable_keys = [k for k in ['type']
if self.params[k] is not None
and self.params[k] != health_monitor[k]]
pool_name_or_id = self.params['pool']
pool = self.conn.load_balancer.find_pool(name_or_id=pool_name_or_id,
ignore_missing=False)
# Field pool_id is not returned from self.conn.load_balancer.\
# find_pool() so use pools instead.
if health_monitor['pools'] != [dict(id=pool.id)]:
non_updateable_keys.append('pool')
if non_updateable_keys:
self.fail_json(msg='Cannot update parameters {0}'
.format(non_updateable_keys))
attributes = dict((k, self.params[k])
for k in ['delay', 'expected_codes', 'http_method',
'is_admin_state_up', 'max_retries',
'max_retries_down', 'type', 'url_path']
if self.params[k] is not None
and self.params[k] != health_monitor[k])
health_monitor_timeout = self.params['health_monitor_timeout']
if health_monitor_timeout is not None \
and health_monitor_timeout != health_monitor['timeout']:
attributes['timeout'] = health_monitor_timeout
if attributes:
update['attributes'] = attributes
return update
def _create(self):
kwargs = dict((k, self.params[k])
for k in ['delay', 'expected_codes', 'http_method',
'is_admin_state_up', 'max_retries',
'max_retries_down', 'name', 'type', 'url_path']
if self.params[k] is not None)
health_monitor_timeout = self.params['health_monitor_timeout']
if health_monitor_timeout is not None:
kwargs['timeout'] = health_monitor_timeout
pool_name_or_id = self.params['pool']
pool = self.conn.load_balancer.find_pool(name_or_id=pool_name_or_id,
ignore_missing=False)
kwargs['pool_id'] = pool.id
health_monitor = \
self.conn.load_balancer.create_health_monitor(**kwargs)
if self.params['wait']:
health_monitor = self.sdk.resource.wait_for_status(
self.conn.load_balancer, health_monitor,
status='active',
failures=['error'],
wait=self.params['timeout'],
attribute='provisioning_status')
return health_monitor
def _delete(self, health_monitor):
self.conn.load_balancer.delete_health_monitor(health_monitor.id)
def _find(self):
name = self.params['name']
return self.conn.load_balancer.find_health_monitor(name_or_id=name)
def _update(self, health_monitor, update):
attributes = update.get('attributes')
if attributes:
health_monitor = self.conn.load_balancer.update_health_monitor(
health_monitor.id, **attributes)
if self.params['wait']:
health_monitor = self.sdk.resource.wait_for_status(
self.conn.load_balancer, health_monitor,
status='active',
failures=['error'],
wait=self.params['timeout'],
attribute='provisioning_status')
return health_monitor
def _will_change(self, state, health_monitor):
if state == 'present' and not health_monitor:
return True
elif state == 'present' and health_monitor:
return bool(self._build_update(health_monitor))
elif state == 'absent' and health_monitor:
return True
else:
# state == 'absent' and not health_monitor:
return False
def main():
module = HealthMonitorModule()
module = LoadBalancerHealthMonitorModule()
module()

View File

@ -4,283 +4,383 @@
# Copyright (c) 2018 Catalyst Cloud Ltd.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
DOCUMENTATION = r'''
---
module: lb_listener
short_description: Add/Delete a listener for a load balancer from OpenStack Cloud
short_description: Manage load-balancer listener in a OpenStack cloud
author: OpenStack Ansible SIG
description:
- Add or Remove a listener for a load balancer from the OpenStack load-balancer service.
- Add, update or remove listener from OpenStack load-balancer.
options:
name:
description:
- Name that has to be given to the listener
required: true
type: str
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
type: str
loadbalancer:
description:
- The name or id of the load balancer that this listener belongs to.
required: true
type: str
protocol:
description:
- The protocol for the listener.
choices: [HTTP, HTTPS, TCP, TERMINATED_HTTPS, UDP, SCTP]
default: HTTP
type: str
protocol_port:
description:
- The protocol port number for the listener.
default: 80
type: int
timeout_client_data:
description:
- Client inactivity timeout in milliseconds.
default: 50000
type: int
timeout_member_data:
description:
- Member inactivity timeout in milliseconds.
default: 50000
type: int
wait:
description:
- If the module should wait for the load balancer to be ACTIVE.
type: bool
default: 'yes'
timeout:
description:
- The amount of time the module should wait for the load balancer to get
into ACTIVE state.
default: 180
type: int
requirements:
- "python >= 3.6"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
RETURN = '''
id:
description: The listener UUID.
returned: On success when I(state) is 'present'
default_tls_container_ref:
description:
- A URI to a key manager service secrets container with TLS secrets.
type: str
sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
listener:
description: Dictionary describing the listener.
returned: On success when I(state) is 'present'
type: complex
contains:
id:
description: Unique UUID.
type: str
sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
name:
description: Name given to the listener.
type: str
sample: "test"
description:
description: The listener description.
type: str
sample: "description"
load_balancer_id:
description: The load balancer UUID this listener belongs to.
type: str
sample: "b32eef7e-d2a6-4ea4-a301-60a873f89b3b"
loadbalancers:
description: A list of load balancer IDs..
type: list
sample: [{"id": "b32eef7e-d2a6-4ea4-a301-60a873f89b3b"}]
provisioning_status:
description: The provisioning status of the listener.
type: str
sample: "ACTIVE"
operating_status:
description: The operating status of the listener.
type: str
sample: "ONLINE"
is_admin_state_up:
description: The administrative state of the listener.
type: bool
sample: true
protocol:
description: The protocol for the listener.
type: str
sample: "HTTP"
protocol_port:
description: The protocol port number for the listener.
type: int
sample: 80
timeout_client_data:
description: Client inactivity timeout in milliseconds.
type: int
sample: 50000
timeout_member_data:
description: Member inactivity timeout in milliseconds.
type: int
sample: 50000
description:
description:
- A human-readable description for the load-balancer listener.
type: str
is_admin_state_up:
description:
- The administrative state of the listener, which is up or down.
type: bool
load_balancer:
description:
- The name or id of the load-balancer that this listener belongs to.
- Required when I(state) is C(present).
- This attribute cannot be updated.
type: str
aliases: ['loadbalancer']
name:
description:
- Name that has to be given to the listener.
- This attribute cannot be updated.
required: true
type: str
protocol:
description:
- The protocol for the listener.
- For example, I(protocol) could be C(HTTP), C(HTTPS), C(TCP),
C(TERMINATED_HTTPS), C(UDP), C(SCTP) or C(PROMETHEUS).
- This attribute cannot be updated.
default: HTTP
type: str
protocol_port:
description:
- The protocol port number for the listener.
- This attribute cannot be updated.
type: int
sni_container_refs:
description:
- A list of URIs to the key manager service secrets containers with TLS
secrets.
type: list
elements: str
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
type: str
timeout_client_data:
description:
- Client inactivity timeout in milliseconds.
type: int
timeout_member_data:
description:
- Member inactivity timeout in milliseconds.
type: int
requirements:
- "python >= 3.6"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a listener, wait for the loadbalancer to be active.
- openstack.cloud.lb_listener:
RETURN = r'''
listener:
description: Dictionary describing the listener.
returned: On success when I(state) is C(present).
type: dict
contains:
allowed_cidrs:
description: List of IPv4 or IPv6 CIDRs.
type: list
alpn_protocols:
description: List of ALPN protocols.
type: list
connection_limit:
description: The maximum number of connections permitted for this load
balancer.
type: str
created_at:
description: Timestamp when the listener was created.
type: str
default_pool:
description: Default pool to which the requests will be routed.
type: str
default_pool_id:
description: ID of default pool. Must have compatible protocol with
listener.
type: str
default_tls_container_ref:
description: A reference to a container of TLS secrets.
type: str
description:
description: The listener description.
type: str
sample: "description"
id:
description: Unique UUID.
type: str
sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
insert_headers:
description: Dictionary of additional headers insertion into HTTP header.
type: dict
is_admin_state_up:
description: The administrative state of the listener.
type: bool
sample: true
l7_policies:
description: A list of L7 policy objects.
type: list
load_balancer_id:
description: The load balancer UUID this listener belongs to.
type: str
sample: "b32eef7e-d2a6-4ea4-a301-60a873f89b3b"
load_balancers:
description: A list of load balancer IDs.
type: list
sample: [{"id": "b32eef7e-d2a6-4ea4-a301-60a873f89b3b"}]
name:
description: Name given to the listener.
type: str
sample: "test"
operating_status:
description: The operating status of the listener.
type: str
sample: "ONLINE"
project_id:
description: The ID of the project owning this resource.
type: str
protocol:
description: The protocol for the listener.
type: str
sample: "HTTP"
protocol_port:
description: The protocol port number for the listener.
type: int
sample: 80
provisioning_status:
description: The provisioning status of the listener.
type: str
sample: "ACTIVE"
sni_container_refs:
description: A list of references to TLS secrets.
type: list
tags:
description: A list of associated tags.
type: list
timeout_client_data:
description: Client inactivity timeout in milliseconds.
type: int
sample: 50000
timeout_member_connect:
description: Backend member connection timeout in milliseconds.
type: int
timeout_member_data:
description: Member inactivity timeout in milliseconds.
type: int
sample: 50000
timeout_tcp_inspect:
description: Time, in milliseconds, to wait for additional TCP packets
for content inspection.
type: int
tls_ciphers:
description: Stores a cipher string in OpenSSL format.
type: str
tls_versions:
description: A list of TLS protocols to be used by the listener.
type: list
updated_at:
description: Timestamp when the listener was last updated.
type: str
'''
EXAMPLES = r'''
- name: Create a listener, wait for the loadbalancer to be active
openstack.cloud.lb_listener:
cloud: mycloud
endpoint_type: admin
state: present
load_balancer: test-loadbalancer
name: test-listener
loadbalancer: test-loadbalancer
protocol: HTTP
protocol_port: 8080
# Create a listener, do not wait for the loadbalancer to be active.
- openstack.cloud.lb_listener:
cloud: mycloud
endpoint_type: admin
state: present
name: test-listener
loadbalancer: test-loadbalancer
protocol: HTTP
protocol_port: 8080
wait: no
# Delete a listener
- openstack.cloud.lb_listener:
- name: Delete a listener
openstack.cloud.lb_listener:
cloud: mycloud
endpoint_type: admin
load_balancer: test-loadbalancer
name: test-listener
state: absent
name: test-listener
loadbalancer: test-loadbalancer
# Create a listener, increase timeouts for connection persistence (for SSH for example).
- openstack.cloud.lb_listener:
- name: Create a listener, increase timeouts for connection persistence
openstack.cloud.lb_listener:
cloud: mycloud
endpoint_type: admin
state: present
load_balancer: test-loadbalancer
name: test-listener
loadbalancer: test-loadbalancer
protocol: TCP
protocol_port: 22
state: present
timeout_client_data: 1800000
timeout_member_data: 1800000
'''
import time
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class LoadbalancerListenerModule(OpenStackModule):
class LoadBalancerListenerModule(OpenStackModule):
argument_spec = dict(
default_tls_container_ref=dict(),
description=dict(),
is_admin_state_up=dict(type='bool'),
load_balancer=dict(aliases=['loadbalancer']),
name=dict(required=True),
protocol=dict(default='HTTP'),
protocol_port=dict(type='int'),
sni_container_refs=dict(type='list', elements='str'),
state=dict(default='present', choices=['absent', 'present']),
loadbalancer=dict(required=True),
protocol=dict(default='HTTP',
choices=['HTTP', 'HTTPS', 'TCP', 'TERMINATED_HTTPS', 'UDP', 'SCTP']),
protocol_port=dict(default=80, type='int'),
timeout_client_data=dict(default=50000, type='int'),
timeout_member_data=dict(default=50000, type='int'),
timeout_client_data=dict(type='int'),
timeout_member_data=dict(type='int'),
)
module_kwargs = dict(
required_if=[
('state', 'present', ('load_balancer',)),
],
supports_check_mode=True,
)
module_kwargs = dict()
def _lb_wait_for_status(self, lb, status, failures, interval=5):
"""Wait for load balancer to be in a particular provisioning status."""
timeout = self.params['timeout']
total_sleep = 0
if failures is None:
failures = []
while total_sleep < timeout:
lb = self.conn.load_balancer.get_load_balancer(lb.id)
if lb.provisioning_status == status:
return None
if lb.provisioning_status in failures:
self.fail_json(
msg="Load Balancer %s transitioned to failure state %s" %
(lb.id, lb.provisioning_status)
)
time.sleep(interval)
total_sleep += interval
self.fail_json(
msg="Timeout waiting for Load Balancer %s to transition to %s" %
(lb.id, status)
)
def run(self):
loadbalancer = self.params['loadbalancer']
loadbalancer_id = None
state = self.params['state']
changed = False
listener = self.conn.load_balancer.find_listener(
name_or_id=self.params['name'])
listener = self._find()
if self.params['state'] == 'present':
if not listener:
lb = self.conn.load_balancer.find_load_balancer(loadbalancer)
if not lb:
self.fail_json(
msg='load balancer %s is not found' % loadbalancer
)
loadbalancer_id = lb.id
if self.ansible.check_mode:
self.exit_json(changed=self._will_change(state, listener))
listener = self.conn.load_balancer.create_listener(
name=self.params['name'],
loadbalancer_id=loadbalancer_id,
protocol=self.params['protocol'],
protocol_port=self.params['protocol_port'],
timeout_client_data=self.params['timeout_client_data'],
timeout_member_data=self.params['timeout_member_data'],
)
changed = True
if state == 'present' and not listener:
# Create listener
listener = self._create()
self.exit_json(changed=True,
rbac_listener=listener.to_dict(computed=False),
listener=listener.to_dict(computed=False))
if not self.params['wait']:
self.exit_json(
changed=changed, listener=listener.to_dict(),
id=listener.id)
elif state == 'present' and listener:
# Update listener
update = self._build_update(listener)
if update:
listener = self._update(listener, update)
if self.params['wait']:
# Check in case the listener already exists.
lb = self.conn.load_balancer.find_load_balancer(loadbalancer)
if not lb:
self.fail_json(
msg='load balancer %s is not found' % loadbalancer
)
self._lb_wait_for_status(lb, "ACTIVE", ["ERROR"])
self.exit_json(changed=bool(update),
rbac_listener=listener.to_dict(computed=False),
listener=listener.to_dict(computed=False))
self.exit_json(
changed=changed, listener=listener.to_dict(), id=listener.id)
elif self.params['state'] == 'absent':
if not listener:
changed = False
else:
self.conn.load_balancer.delete_listener(listener)
changed = True
elif state == 'absent' and listener:
# Delete listener
self._delete(listener)
self.exit_json(changed=True)
if self.params['wait']:
# Wait for the load balancer to be active after deleting
# the listener.
lb = self.conn.load_balancer.find_load_balancer(loadbalancer)
if not lb:
self.fail_json(
msg='load balancer %s is not found' % loadbalancer
)
self._lb_wait_for_status(lb, "ACTIVE", ["ERROR"])
elif state == 'absent' and not listener:
# Do nothing
self.exit_json(changed=False)
self.exit_json(changed=changed)
def _build_update(self, listener):
update = {}
non_updateable_keys = [k for k in ['protocol', 'protocol_port']
if self.params[k] is not None
and self.params[k] != listener[k]]
load_balancer_name_or_id = self.params['load_balancer']
load_balancer = self.conn.load_balancer.find_load_balancer(
load_balancer_name_or_id, ignore_missing=False)
# Field load_balancer_id is not returned from self.conn.load_balancer.\
# find_load_balancer() so use load_balancers instead.
if listener['load_balancers'] != [dict(id=load_balancer.id)]:
non_updateable_keys.append('load_balancer')
if non_updateable_keys:
self.fail_json(msg='Cannot update parameters {0}'
.format(non_updateable_keys))
attributes = dict((k, self.params[k])
for k in ['default_tls_container_ref',
'description',
'is_admin_state_up',
'sni_container_refs',
'timeout_client_data',
'timeout_member_data']
if self.params[k] is not None
and self.params[k] != listener[k])
if attributes:
update['attributes'] = attributes
return update
def _create(self):
kwargs = dict((k, self.params[k])
for k in ['default_tls_container_ref', 'description',
'is_admin_state_up', 'name', 'protocol',
'protocol_port', 'sni_container_refs',
'timeout_client_data', 'timeout_member_data']
if self.params[k] is not None)
load_balancer_name_or_id = self.params['load_balancer']
load_balancer = self.conn.load_balancer.find_load_balancer(
load_balancer_name_or_id, ignore_missing=False)
kwargs['load_balancer_id'] = load_balancer.id
listener = self.conn.load_balancer.create_listener(**kwargs)
if self.params['wait']:
self.conn.load_balancer.wait_for_load_balancer(
listener.load_balancer_id,
wait=self.params['timeout'])
return listener
def _delete(self, listener):
self.conn.load_balancer.delete_listener(listener.id)
if self.params['wait']:
# Field load_balancer_id is not returned from self.conn.\
# load_balancer.find_listener() so use load_balancers instead.
if not listener.load_balancers \
or len(listener.load_balancers) != 1:
raise AssertionError("A single load-balancer is expected")
self.conn.load_balancer.wait_for_load_balancer(
listener.load_balancers[0]['id'],
wait=self.params['timeout'])
def _find(self):
name = self.params['name']
return self.conn.load_balancer.find_listener(name_or_id=name)
def _update(self, listener, update):
attributes = update.get('attributes')
if attributes:
listener = self.conn.load_balancer.update_listener(listener.id,
**attributes)
if self.params['wait']:
# Field load_balancer_id is not returned from self.conn.\
# load_balancer.find_listener() so use load_balancers instead.
if not listener.load_balancers \
or len(listener.load_balancers) != 1:
raise AssertionError("A single load-balancer is expected")
self.conn.load_balancer.wait_for_load_balancer(
listener.load_balancers[0]['id'],
wait=self.params['timeout'])
return listener
def _will_change(self, state, listener):
if state == 'present' and not listener:
return True
elif state == 'present' and listener:
return bool(self._build_update(listener))
elif state == 'absent' and listener:
return True
else:
# state == 'absent' and not listener:
return False
def main():
module = LoadbalancerListenerModule()
module = LoadBalancerListenerModule()
module()

View File

@ -4,231 +4,377 @@
# Copyright (c) 2018 Catalyst Cloud Ltd.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
DOCUMENTATION = r'''
---
module: lb_member
short_description: Add/Delete a member for a pool in load balancer from OpenStack Cloud
short_description: Manage members in a OpenStack load-balancer pool
author: OpenStack Ansible SIG
description:
- Add or Remove a member for a pool from the OpenStack load-balancer service.
- Add, update or remove member from OpenStack load-balancer pool.
options:
name:
description:
- Name that has to be given to the member
required: true
type: str
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
type: str
pool:
description:
- The name or id of the pool that this member belongs to.
required: true
type: str
protocol_port:
description:
- The protocol port number for the member.
default: 80
type: int
address:
description:
- The IP address of the member.
type: str
subnet_id:
description:
- The subnet ID the member service is accessible from.
type: str
wait:
description:
- If the module should wait for the load balancer to be ACTIVE.
type: bool
default: 'yes'
timeout:
description:
- The amount of time the module should wait for the load balancer to get
into ACTIVE state.
default: 180
type: int
monitor_address:
description:
- IP address used to monitor this member
type: str
monitor_port:
description:
- Port used to monitor this member
type: int
requirements:
- "python >= 3.6"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
RETURN = '''
id:
description: The member UUID.
returned: On success when I(state) is 'present'
address:
description:
- The IP address of the member.
- Required when I(state) is C(present).
- This attribute cannot be updated.
type: str
sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
monitor_address:
description:
- IP address used to monitor this member.
type: str
monitor_port:
description:
- Port used to monitor this member.
type: int
name:
description:
- Name that has to be given to the member.
required: true
type: str
pool:
description:
- The name or id of the pool that this member belongs to.
- This attribute cannot be updated.
required: true
type: str
protocol_port:
description:
- The protocol port number for the member.
- Required when I(state) is C(present).
- This attribute cannot be updated.
type: int
state:
description:
- Should the resource be C(present) or C(absent).
choices: [present, absent]
default: present
type: str
subnet_id:
description:
- The subnet ID the member service is accessible from.
- This attribute cannot be updated.
type: str
weight:
description:
- The weight of a member determines the portion of requests or
connections it services compared to the other members of the pool.
- For example, a member with a weight of 10 receives five times as many
requests as a member with a weight of 2. A value of 0 means the member
does not receive new connections but continues to service existing
connections. A valid value is from 0 to 256.
- "Octavia's default for I(weight) is C(1)."
type: int
requirements:
- "python >= 3.6"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
RETURN = r'''
member:
description: Dictionary describing the member.
returned: On success when I(state) is 'present'
type: complex
contains:
id:
description: Unique UUID.
type: str
sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
name:
description: Name given to the member.
type: str
sample: "test"
description:
description: The member description.
type: str
sample: "description"
provisioning_status:
description: The provisioning status of the member.
type: str
sample: "ACTIVE"
operating_status:
description: The operating status of the member.
type: str
sample: "ONLINE"
is_admin_state_up:
description: The administrative state of the member.
type: bool
sample: true
protocol_port:
description: The protocol port number for the member.
type: int
sample: 80
subnet_id:
description: The subnet ID the member service is accessible from.
type: str
sample: "489247fa-9c25-11e8-9679-00224d6b7bc1"
address:
description: The IP address of the backend member server.
type: str
sample: "192.168.2.10"
description: Dictionary describing the load-balancer pool member.
returned: On success when I(state) is C(present).
type: dict
contains:
address:
description: The IP address of the backend member server.
type: str
backup:
description: A bool value that indicates whether the member is a backup
or not. Backup members only receive traffic when all
non-backup members are down.
type: bool
created_at:
description: Timestamp when the member was created.
type: str
id:
description: Unique UUID.
type: str
is_admin_state_up:
description: The administrative state of the member.
type: bool
monitor_address:
description: IP address used to monitor this member.
type: str
monitor_port:
description: Port used to monitor this member.
type: int
name:
description: Name given to the member.
type: str
operating_status:
description: Operating status of the member.
type: str
project_id:
description: The ID of the project this member is associated with.
type: str
protocol_port:
description: The protocol port number for the member.
type: int
provisioning_status:
description: The provisioning status of the member.
type: str
subnet_id:
description: The subnet ID the member service is accessible from.
type: str
tags:
description: A list of associated tags.
type: list
updated_at:
description: Timestamp when the member was last updated.
type: str
weight:
description: A positive integer value that indicates the relative portion
of traffic that this member should receive from the pool.
For example, a member with a weight of 10 receives five
times as much traffic as a member with weight of 2.
type: int
pool:
description: Dictionary describing the load-balancer pool.
returned: On success when I(state) is C(present).
type: dict
contains:
alpn_protocols:
description: List of ALPN protocols.
type: list
created_at:
description: Timestamp when the pool was created.
type: str
description:
description: The pool description.
type: str
health_monitor_id:
description: Health Monitor ID.
type: str
id:
description: Unique UUID.
type: str
is_admin_state_up:
description: The administrative state of the pool.
type: bool
lb_algorithm:
description: The load balancing algorithm for the pool.
type: str
listener_id:
description: The listener ID the pool belongs to.
type: str
listeners:
description: A list of listener IDs.
type: list
loadbalancer_id:
description: The load balancer ID the pool belongs to. This field is set
when the pool does not belong to any listener in the load
balancer.
type: str
loadbalancers:
description: A list of load balancer IDs.
type: list
members:
description: A list of member IDs.
type: list
name:
description: Name given to the pool.
type: str
operating_status:
description: The operating status of the pool.
type: str
project_id:
description: The ID of the project.
type: str
protocol:
description: The protocol for the pool.
type: str
provisioning_status:
description: The provisioning status of the pool.
type: str
session_persistence:
description: A JSON object specifying the session persistence for the
pool.
type: dict
tags:
description: A list of associated tags.
type: list
tls_ciphers:
description: Stores a string of cipher strings in OpenSSL format.
type: str
tls_enabled:
description: Use TLS for connections to backend member servers.
type: bool
tls_versions:
description: A list of TLS protocol versions to be used in by the pool.
type: list
updated_at:
description: Timestamp when the pool was updated.
type: str
'''
EXAMPLES = '''
# Create a member, wait for the member to be created.
- openstack.cloud.lb_member:
cloud: mycloud
endpoint_type: admin
state: present
name: test-member
pool: test-pool
EXAMPLES = r'''
- name: Create member in a load-balancer pool
openstack.cloud.lb_member:
address: 192.168.10.3
protocol_port: 8080
# Delete a listener
- openstack.cloud.lb_member:
cloud: mycloud
endpoint_type: admin
state: absent
name: test-member
pool: test-pool
'''
protocol_port: 8080
state: present
import time
- name: Delete member from a load-balancer pool
openstack.cloud.lb_member:
cloud: mycloud
name: test-member
pool: test-pool
state: absent
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class LoadbalancerMemberModule(OpenStackModule):
class LoadBalancerMemberModule(OpenStackModule):
argument_spec = dict(
name=dict(required=True),
state=dict(default='present', choices=['absent', 'present']),
pool=dict(required=True),
address=dict(),
protocol_port=dict(default=80, type='int'),
subnet_id=dict(),
monitor_address=dict(),
monitor_port=dict(type='int')
monitor_port=dict(type='int'),
name=dict(required=True),
pool=dict(required=True),
protocol_port=dict(type='int'),
state=dict(default='present', choices=['absent', 'present']),
subnet_id=dict(),
weight=dict(type='int'),
)
module_kwargs = dict(
required_if=[
('state', 'present', ('address', 'protocol_port',)),
],
supports_check_mode=True,
)
module_kwargs = dict()
def _wait_for_member_status(self, pool_id, member_id, status,
failures, interval=5):
timeout = self.params['timeout']
total_sleep = 0
if failures is None:
failures = []
while total_sleep < timeout:
member = self.conn.load_balancer.get_member(member_id, pool_id)
provisioning_status = member.provisioning_status
if provisioning_status == status:
return member
if provisioning_status in failures:
self.fail_json(
msg="Member %s transitioned to failure state %s" %
(member_id, provisioning_status)
)
time.sleep(interval)
total_sleep += interval
self.fail_json(
msg="Timeout waiting for member %s to transition to %s" %
(member_id, status)
)
def run(self):
state = self.params['state']
member, pool = self._find()
if self.ansible.check_mode:
self.exit_json(changed=self._will_change(state, member, pool))
if state == 'present' and not member:
# Create member
member = self._create(pool)
self.exit_json(changed=True,
member=member.to_dict(computed=False),
pool=pool.to_dict(computed=False))
elif state == 'present' and member:
# Update member
update = self._build_update(member, pool)
if update:
member = self._update(member, pool, update)
self.exit_json(changed=bool(update),
member=member.to_dict(computed=False),
pool=pool.to_dict(computed=False))
elif state == 'absent' and member:
# Delete member
self._delete(member, pool)
self.exit_json(changed=True)
elif state == 'absent' and not member:
# Do nothing
self.exit_json(changed=False)
def _build_update(self, member, pool):
update = {}
non_updateable_keys = [k for k in ['address', 'name', 'protocol_port',
'subnet_id']
if self.params[k] is not None
and self.params[k] != member[k]]
if non_updateable_keys:
self.fail_json(msg='Cannot update parameters {0}'
.format(non_updateable_keys))
attributes = dict((k, self.params[k])
for k in ['monitor_address', 'monitor_port',
'weight']
if self.params[k] is not None
and self.params[k] != member[k])
if attributes:
update['attributes'] = attributes
return update
def _create(self, pool):
kwargs = dict((k, self.params[k])
for k in ['address', 'monitor_address', 'monitor_port',
'name', 'protocol_port', 'subnet_id', 'weight']
if self.params[k] is not None)
member = self.conn.load_balancer.create_member(pool.id, **kwargs)
if self.params['wait']:
member = self.sdk.resource.wait_for_status(
self.conn.load_balancer, member,
status='active',
failures=['error'],
wait=self.params['timeout'],
attribute='provisioning_status')
return member
def _delete(self, member, pool):
self.conn.load_balancer.delete_member(member.id, pool.id)
if self.params['wait']:
for count in self.sdk.utils.iterate_timeout(
timeout=self.params['timeout'],
message="Timeout waiting for load-balancer member to be absent"
):
if self.conn.load_balancer.\
find_member(member.id, pool.id) is None:
break
def _find(self):
name = self.params['name']
pool = self.params['pool']
pool_name_or_id = self.params['pool']
changed = False
pool = self.conn.load_balancer.find_pool(name_or_id=pool_name_or_id,
ignore_missing=False)
member = self.conn.load_balancer.find_member(name, pool.id)
pool_ret = self.conn.load_balancer.find_pool(name_or_id=pool)
if not pool_ret:
self.fail_json(msg='pool %s is not found' % pool)
return member, pool
pool_id = pool_ret.id
member = self.conn.load_balancer.find_member(name, pool_id)
def _update(self, member, pool, update):
attributes = update.get('attributes')
if attributes:
member = self.conn.load_balancer.update_member(member.id, pool.id,
**attributes)
if self.params['wait']:
member = self.sdk.resource.wait_for_status(
self.conn.load_balancer, member,
status='active',
failures=['error'],
wait=self.params['timeout'],
attribute='provisioning_status')
if self.params['state'] == 'present':
if not member:
member = self.conn.load_balancer.create_member(
pool_ret,
address=self.params['address'],
name=name,
protocol_port=self.params['protocol_port'],
subnet_id=self.params['subnet_id'],
monitor_address=self.params['monitor_address'],
monitor_port=self.params['monitor_port']
)
changed = True
return member
if not self.params['wait']:
self.exit_json(
changed=changed, member=member.to_dict(), id=member.id)
if self.params['wait']:
member = self._wait_for_member_status(
pool_id, member.id, "ACTIVE", ["ERROR"])
self.exit_json(
changed=changed, member=member.to_dict(), id=member.id)
elif self.params['state'] == 'absent':
if member:
self.conn.load_balancer.delete_member(member, pool_ret)
changed = True
self.exit_json(changed=changed)
def _will_change(self, state, member, pool):
if state == 'present' and not member:
return True
elif state == 'present' and member:
return bool(self._build_update(member, pool))
elif state == 'absent' and member:
return True
else:
# state == 'absent' and not member:
return False
def main():
module = LoadbalancerMemberModule()
module = LoadBalancerMemberModule()
module()

View File

@ -4,259 +4,332 @@
# Copyright (c) 2018 Catalyst Cloud Ltd.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
DOCUMENTATION = r'''
---
module: lb_pool
short_description: Add/Delete a pool in the load balancing service from OpenStack Cloud
short_description: Manage load-balancer pool in a OpenStack cloud.
author: OpenStack Ansible SIG
description:
- Add or Remove a pool from the OpenStack load-balancer service.
- Add, update or remove load-balancer pool from OpenStack cloud.
options:
name:
description:
- Name that has to be given to the pool
required: true
type: str
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
type: str
loadbalancer:
description:
- The name or id of the load balancer that this pool belongs to.
Either loadbalancer or listener must be specified for pool creation.
type: str
listener:
description:
- The name or id of the listener that this pool belongs to.
Either loadbalancer or listener must be specified for pool creation.
type: str
protocol:
description:
- The protocol for the pool.
choices: [HTTP, HTTPS, PROXY, TCP, UDP]
default: HTTP
type: str
lb_algorithm:
description:
- The load balancing algorithm for the pool.
choices: [LEAST_CONNECTIONS, ROUND_ROBIN, SOURCE_IP]
default: ROUND_ROBIN
type: str
wait:
description:
- If the module should wait for the pool to be ACTIVE.
type: bool
default: 'yes'
timeout:
description:
- The amount of time the module should wait for the pool to get
into ACTIVE state.
default: 180
type: int
requirements:
- "python >= 3.6"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
RETURN = '''
id:
description: The pool UUID.
returned: On success when I(state) is 'present'
description:
description:
- A human-readable description for the load-balancer pool.
type: str
sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
listener:
description: Dictionary describing the pool.
returned: On success when I(state) is 'present'
type: complex
contains:
id:
description: Unique UUID.
type: str
sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
name:
description: Name given to the pool.
type: str
sample: "test"
description:
description: The pool description.
type: str
sample: "description"
loadbalancers:
description: A list of load balancer IDs.
type: list
sample: [{"id": "b32eef7e-d2a6-4ea4-a301-60a873f89b3b"}]
listeners:
description: A list of listener IDs.
type: list
sample: [{"id": "b32eef7e-d2a6-4ea4-a301-60a873f89b3b"}]
members:
description: A list of member IDs.
type: list
sample: [{"id": "b32eef7e-d2a6-4ea4-a301-60a873f89b3b"}]
loadbalancer_id:
description: The load balancer ID the pool belongs to. This field is set when the pool doesn't belong to any listener in the load balancer.
type: str
sample: "7c4be3f8-9c2f-11e8-83b3-44a8422643a4"
listener_id:
description: The listener ID the pool belongs to.
type: str
sample: "956aa716-9c2f-11e8-83b3-44a8422643a4"
provisioning_status:
description: The provisioning status of the pool.
type: str
sample: "ACTIVE"
operating_status:
description: The operating status of the pool.
type: str
sample: "ONLINE"
is_admin_state_up:
description: The administrative state of the pool.
type: bool
sample: true
protocol:
description: The protocol for the pool.
type: str
sample: "HTTP"
lb_algorithm:
description: The load balancing algorithm for the pool.
type: str
sample: "ROUND_ROBIN"
lb_algorithm:
description:
- The load balancing algorithm for the pool.
- For example, I(lb_algorithm) could be C(LEAST_CONNECTIONS),
C(ROUND_ROBIN), C(SOURCE_IP) or C(SOURCE_IP_PORT).
default: ROUND_ROBIN
type: str
listener:
description:
- The name or id of the listener that this pool belongs to.
- Either I(listener) or I(loadbalancer) must be specified for pool
creation.
- This attribute cannot be updated.
type: str
loadbalancer:
description:
- The name or id of the load balancer that this pool belongs to.
- Either I(listener) or I(loadbalancer) must be specified for pool
creation.
- This attribute cannot be updated.
type: str
name:
description:
- Name that has to be given to the pool.
- This attribute cannot be updated.
required: true
type: str
protocol:
description:
- The protocol for the pool.
- For example, I(protocol) could be C(HTTP), C(HTTPS), C(PROXY),
C(PROXYV2), C(SCTP), C(TCP) and C(UDP).
- This attribute cannot be updated.
default: HTTP
type: str
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
type: str
requirements:
- "python >= 3.6"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a pool, wait for the pool to be active.
- openstack.cloud.lb_pool:
RETURN = r'''
pool:
description: Dictionary describing the load-balancer pool.
returned: On success when I(state) is C(present).
type: dict
contains:
alpn_protocols:
description: List of ALPN protocols.
type: list
created_at:
description: Timestamp when the pool was created.
type: str
description:
description: The pool description.
type: str
health_monitor_id:
description: Health Monitor ID.
type: str
id:
description: Unique UUID.
type: str
is_admin_state_up:
description: The administrative state of the pool.
type: bool
lb_algorithm:
description: The load balancing algorithm for the pool.
type: str
listener_id:
description: The listener ID the pool belongs to.
type: str
listeners:
description: A list of listener IDs.
type: list
loadbalancer_id:
description: The load balancer ID the pool belongs to. This field is set
when the pool does not belong to any listener in the load
balancer.
type: str
loadbalancers:
description: A list of load balancer IDs.
type: list
members:
description: A list of member IDs.
type: list
name:
description: Name given to the pool.
type: str
operating_status:
description: The operating status of the pool.
type: str
project_id:
description: The ID of the project.
type: str
protocol:
description: The protocol for the pool.
type: str
provisioning_status:
description: The provisioning status of the pool.
type: str
session_persistence:
description: A JSON object specifying the session persistence for the
pool.
type: dict
tags:
description: A list of associated tags.
type: list
tls_ciphers:
description: Stores a string of cipher strings in OpenSSL format.
type: str
tls_enabled:
description: Use TLS for connections to backend member servers.
type: bool
tls_versions:
description: A list of TLS protocol versions to be used in by the pool.
type: list
updated_at:
description: Timestamp when the pool was updated.
type: str
'''
EXAMPLES = r'''
- name: Create a load-balander pool
openstack.cloud.lb_pool:
cloud: mycloud
endpoint_type: admin
state: present
name: test-pool
loadbalancer: test-loadbalancer
protocol: HTTP
lb_algorithm: ROUND_ROBIN
# Delete a pool
- openstack.cloud.lb_pool:
cloud: mycloud
endpoint_type: admin
state: absent
loadbalancer: test-loadbalancer
name: test-pool
'''
protocol: HTTP
state: present
import time
- name: Delete a load-balander pool
openstack.cloud.lb_pool:
cloud: mycloud
name: test-pool
state: absent
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class LoadbalancerPoolModule(OpenStackModule):
class LoadBalancerPoolModule(OpenStackModule):
argument_spec = dict(
name=dict(required=True),
state=dict(default='present', choices=['absent', 'present']),
loadbalancer=dict(),
description=dict(),
lb_algorithm=dict(default='ROUND_ROBIN'),
listener=dict(),
protocol=dict(default='HTTP',
choices=['HTTP', 'HTTPS', 'TCP', 'UDP', 'PROXY']),
lb_algorithm=dict(
default='ROUND_ROBIN',
choices=['ROUND_ROBIN', 'LEAST_CONNECTIONS', 'SOURCE_IP']
)
loadbalancer=dict(),
name=dict(required=True),
protocol=dict(default='HTTP'),
state=dict(default='present', choices=['absent', 'present']),
)
module_kwargs = dict(
mutually_exclusive=[['loadbalancer', 'listener']]
required_if=[
('state', 'present', ('listener', 'loadbalancer'), True),
],
mutually_exclusive=[
('listener', 'loadbalancer')
],
supports_check_mode=True,
)
def _wait_for_pool_status(self, pool_id, status, failures,
interval=5):
timeout = self.params['timeout']
total_sleep = 0
if failures is None:
failures = []
while total_sleep < timeout:
pool = self.conn.load_balancer.get_pool(pool_id)
provisioning_status = pool.provisioning_status
if provisioning_status == status:
return pool
if provisioning_status in failures:
self.fail_json(
msg="pool %s transitioned to failure state %s" %
(pool_id, provisioning_status)
)
time.sleep(interval)
total_sleep += interval
self.fail_json(
msg="timeout waiting for pool %s to transition to %s" %
(pool_id, status)
)
def run(self):
loadbalancer = self.params['loadbalancer']
listener = self.params['listener']
state = self.params['state']
changed = False
pool = self.conn.load_balancer.find_pool(name_or_id=self.params['name'])
pool = self._find()
if self.params['state'] == 'present':
if not pool:
loadbalancer_id = None
if not (loadbalancer or listener):
self.fail_json(
msg="either loadbalancer or listener must be provided"
)
if self.ansible.check_mode:
self.exit_json(changed=self._will_change(state, pool))
if loadbalancer:
lb = self.conn.load_balancer.find_load_balancer(loadbalancer)
if not lb:
self.fail_json(
msg='load balancer %s is not found' % loadbalancer)
loadbalancer_id = lb.id
if state == 'present' and not pool:
# Create pool
pool = self._create()
self.exit_json(changed=True,
pool=pool.to_dict(computed=False))
listener_id = None
if listener:
listener_ret = self.conn.load_balancer.find_listener(listener)
if not listener_ret:
self.fail_json(
msg='listener %s is not found' % listener)
listener_id = listener_ret.id
elif state == 'present' and pool:
# Update pool
update = self._build_update(pool)
if update:
pool = self._update(pool, update)
pool = self.conn.load_balancer.create_pool(
name=self.params['name'],
loadbalancer_id=loadbalancer_id,
listener_id=listener_id,
protocol=self.params['protocol'],
lb_algorithm=self.params['lb_algorithm']
)
changed = True
self.exit_json(changed=bool(update),
pool=pool.to_dict(computed=False))
if not self.params['wait']:
self.exit_json(
changed=changed, pool=pool.to_dict(), id=pool.id)
elif state == 'absent' and pool:
# Delete pool
self._delete(pool)
self.exit_json(changed=True)
if self.params['wait']:
pool = self._wait_for_pool_status(
pool.id, "ACTIVE", ["ERROR"])
elif state == 'absent' and not pool:
# Do nothing
self.exit_json(changed=False)
self.exit_json(
changed=changed, pool=pool.to_dict(), id=pool.id)
def _build_update(self, pool):
update = {}
elif self.params['state'] == 'absent':
if pool:
self.conn.load_balancer.delete_pool(pool)
changed = True
non_updateable_keys = [k for k in ['protocol']
if self.params[k] is not None
and self.params[k] != pool[k]]
self.exit_json(changed=changed)
listener_name_or_id = self.params['listener']
if listener_name_or_id:
listener = self.conn.load_balancer.find_listener(
listener_name_or_id, ignore_missing=False)
# Field listener_id is not returned from self.conn.load_balancer.\
# find_listener() so use listeners instead.
if pool['listeners'] != [dict(id=listener.id)]:
non_updateable_keys.append('listener_id')
loadbalancer_name_or_id = self.params['loadbalancer']
if loadbalancer_name_or_id:
loadbalancer = self.conn.load_balancer.find_load_balancer(
loadbalancer_name_or_id, ignore_missing=False)
# Field load_balancer_id is not returned from self.conn.\
# load_balancer.find_load_balancer() so use load_balancers instead.
if listener['load_balancers'] != [dict(id=loadbalancer.id)]:
non_updateable_keys.append('loadbalancer_id')
if non_updateable_keys:
self.fail_json(msg='Cannot update parameters {0}'
.format(non_updateable_keys))
attributes = dict((k, self.params[k])
for k in ['description', 'lb_algorithm']
if self.params[k] is not None
and self.params[k] != pool[k])
if attributes:
update['attributes'] = attributes
return update
def _create(self):
kwargs = dict((k, self.params[k])
for k in ['description', 'name', 'protocol',
'lb_algorithm']
if self.params[k] is not None)
listener_name_or_id = self.params['listener']
if listener_name_or_id:
listener = self.conn.load_balancer.find_listener(
listener_name_or_id, ignore_missing=False)
kwargs['listener_id'] = listener.id
loadbalancer_name_or_id = self.params['loadbalancer']
if loadbalancer_name_or_id:
loadbalancer = self.conn.load_balancer.find_load_balancer(
loadbalancer_name_or_id, ignore_missing=False)
kwargs['loadbalancer_id'] = loadbalancer.id
pool = self.conn.load_balancer.create_pool(**kwargs)
if self.params['wait']:
pool = self.sdk.resource.wait_for_status(
self.conn.load_balancer, pool,
status='active',
failures=['error'],
wait=self.params['timeout'],
attribute='provisioning_status')
return pool
def _delete(self, pool):
self.conn.load_balancer.delete_pool(pool.id)
if self.params['wait']:
for count in self.sdk.utils.iterate_timeout(
timeout=self.params['timeout'],
message="Timeout waiting for load-balancer pool to be absent"
):
if self.conn.load_balancer.\
find_pool(pool.id) is None:
break
def _find(self):
name = self.params['name']
return self.conn.load_balancer.find_pool(name_or_id=name)
def _update(self, pool, update):
attributes = update.get('attributes')
if attributes:
pool = self.conn.load_balancer.update_pool(pool.id, **attributes)
if self.params['wait']:
pool = self.sdk.resource.wait_for_status(
self.conn.load_balancer, pool,
status='active',
failures=['error'],
wait=self.params['timeout'],
attribute='provisioning_status')
return pool
def _will_change(self, state, pool):
if state == 'present' and not pool:
return True
elif state == 'present' and pool:
return bool(self._build_update(pool))
elif state == 'absent' and pool:
return True
else:
# state == 'absent' and not pool:
return False
def main():
module = LoadbalancerPoolModule()
module = LoadBalancerPoolModule()
module()