1b38b7c500
With "extends_documentation_fragment: ['openstack.cloud.openstack']" it is not necessary to list required Python libraries in section 'requirements' of DOCUMENTATION docstring in modules. Ansible will merge requirements from doc fragments and DOCUMENTATION docstring which previously resulted in duplicates such as in server module [0]: * openstacksdk * openstacksdk >= 0.36, < 0.99.0 * python >= 3.6 When removing the 'requirements' section from server module, then Ansible will list openstacksdk once only: * openstacksdk >= 0.36, < 0.99.0 * python >= 3.6 To see what documentation Ansible will produce for server module run: ansible-doc --type module openstack.cloud.server [0] https://docs.ansible.com/ansible/latest/collections/openstack/\ cloud/server_module.html Change-Id: I727ed95ee480bb644b5a533f6a9526973677064c
380 lines
12 KiB
Python
380 lines
12 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# 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 = r'''
|
|
---
|
|
module: lb_member
|
|
short_description: Manage members in a OpenStack load-balancer pool
|
|
author: OpenStack Ansible SIG
|
|
description:
|
|
- Add, update or remove member from OpenStack load-balancer pool.
|
|
options:
|
|
address:
|
|
description:
|
|
- The IP address of the member.
|
|
- Required when I(state) is C(present).
|
|
- This attribute cannot be updated.
|
|
type: str
|
|
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
|
|
extends_documentation_fragment:
|
|
- openstack.cloud.openstack
|
|
'''
|
|
|
|
RETURN = r'''
|
|
member:
|
|
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 = r'''
|
|
- name: Create member in a load-balancer pool
|
|
openstack.cloud.lb_member:
|
|
address: 192.168.10.3
|
|
cloud: mycloud
|
|
name: test-member
|
|
pool: test-pool
|
|
protocol_port: 8080
|
|
state: present
|
|
|
|
- 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):
|
|
argument_spec = dict(
|
|
address=dict(),
|
|
monitor_address=dict(),
|
|
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,
|
|
)
|
|
|
|
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_name_or_id = self.params['pool']
|
|
|
|
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)
|
|
|
|
return member, pool
|
|
|
|
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')
|
|
|
|
return member
|
|
|
|
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()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|