summaryrefslogtreecommitdiffstats
path: root/ansible_collections/openstack/cloud/plugins/modules/loadbalancer.py
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/openstack/cloud/plugins/modules/loadbalancer.py')
-rw-r--r--ansible_collections/openstack/cloud/plugins/modules/loadbalancer.py1196
1 files changed, 576 insertions, 620 deletions
diff --git a/ansible_collections/openstack/cloud/plugins/modules/loadbalancer.py b/ansible_collections/openstack/cloud/plugins/modules/loadbalancer.py
index 336da966c..d2addb731 100644
--- a/ansible_collections/openstack/cloud/plugins/modules/loadbalancer.py
+++ b/ansible_collections/openstack/cloud/plugins/modules/loadbalancer.py
@@ -1,4 +1,5 @@
#!/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)
@@ -6,15 +7,55 @@
DOCUMENTATION = '''
---
module: loadbalancer
-short_description: Add/Delete load balancer from OpenStack Cloud
+short_description: Manage Octavia load-balancer in an OpenStack cloud
author: OpenStack Ansible SIG
description:
- - Add or Remove load balancer from the OpenStack load-balancer
- service(Octavia). Load balancer update is not supported for now.
+ - Add, update or remove Octavia load-balancer from OpenStack cloud.
options:
+ assign_floating_ip:
+ description:
+ - Allocate floating ip address and associate with the VIP automatically.
+ - Deprecated, use M(openstack.cloud.floating_ip) instead.
+ type: bool
+ default: false
+ aliases: ['auto_public_ip']
+ delete_floating_ip:
+ description:
+ - When I(state) is C(present) and I(delete_floating_ip) is C(true), then
+ any floating ip address associated with the VIP will be deleted.
+ - When I(state) is C(absent) and I(delete_floating_ip) is C(true), then
+ any floating ip address associated with the VIP will be deleted along
+ with the load balancer.
+ - Deprecated, use M(openstack.cloud.floating_ip) instead.
+ type: bool
+ default: false
+ aliases: ['delete_public_ip']
+ description:
+ description:
+ - A human-readable description for the load-balancer.
+ type: str
+ flavor:
+ description:
+ - The flavor of the load balancer.
+ - This attribute cannot be updated.
+ type: str
+ floating_ip_address:
+ description:
+ - Floating ip address aka public ip address associated with the VIP.
+ - Deprecated, use M(openstack.cloud.floating_ip) instead.
+ type: str
+ aliases: ['public_ip_address']
+ floating_ip_network:
+ description:
+ - Name or ID of a Neutron external network where floating ip address will
+ be created on.
+ - Deprecated, use M(openstack.cloud.floating_ip) instead.
+ type: str
+ aliases: ['public_network']
name:
description:
- The name of the load balancer.
+ - This attribute cannot be updated.
required: true
type: str
state:
@@ -23,663 +64,578 @@ options:
choices: [present, absent]
default: present
type: str
- flavor:
+ vip_address:
description:
- - The flavor of the load balancer.
+ - IP address of the load balancer virtual IP.
+ - This attribute cannot be updated.
type: str
vip_network:
description:
- The name or id of the network for the virtual IP of the load balancer.
- One of I(vip_network), I(vip_subnet), or I(vip_port) must be specified
- for creation.
- type: str
- vip_subnet:
- description:
- - The name or id of the subnet for the virtual IP of the load balancer.
- One of I(vip_network), I(vip_subnet), or I(vip_port) must be specified
+ - One of I(vip_network), I(vip_subnet), or I(vip_port) must be specified
for creation.
+ - This attribute cannot be updated.
type: str
vip_port:
description:
- The name or id of the load balancer virtual IP port. One of
- I(vip_network), I(vip_subnet), or I(vip_port) must be specified for
- creation.
- type: str
- vip_address:
- description:
- - IP address of the load balancer virtual IP.
- type: str
- public_ip_address:
- description:
- - Public IP address associated with the VIP.
+ - One of I(vip_network), I(vip_subnet), or I(vip_port) must be specified
+ for creation.
+ - This attribute cannot be updated.
type: str
- auto_public_ip:
- description:
- - Allocate a public IP address and associate with the VIP automatically.
- type: bool
- default: 'no'
- public_network:
+ vip_subnet:
description:
- - The name or ID of a Neutron external network.
+ - The name or id of the subnet for the virtual IP of the load balancer.
+ - One of I(vip_network), I(vip_subnet), or I(vip_port) must be specified
+ for creation.
+ - This attribute cannot be updated.
type: str
- delete_public_ip:
- description:
- - When C(state=absent) and this option is true, any public IP address
- associated with the VIP will be deleted along with the load balancer.
- type: bool
- default: 'no'
- listeners:
- description:
- - A list of listeners that attached to the load balancer.
- suboptions:
- name:
- description:
- - The listener name or ID.
- protocol:
- description:
- - The protocol for the listener.
- default: HTTP
- protocol_port:
- description:
- - The protocol port number for the listener.
- default: 80
- allowed_cidrs:
- description:
- - A list of IPv4, IPv6 or mix of both CIDRs to be allowed access to the listener. The default is all allowed.
- When a list of CIDRs is provided, the default switches to deny all.
- Ignored on unsupported Octavia versions (less than 2.12)
- default: []
- pool:
- description:
- - The pool attached to the listener.
- suboptions:
- name:
- description:
- - The pool name or ID.
- protocol:
- description:
- - The protocol for the pool.
- default: HTTP
- lb_algorithm:
- description:
- - The load balancing algorithm for the pool.
- default: ROUND_ROBIN
- members:
- description:
- - A list of members that added to the pool.
- suboptions:
- name:
- description:
- - The member name or ID.
- address:
- description:
- - The IP address of the member.
- protocol_port:
- description:
- - The protocol port number for the member.
- default: 80
- subnet:
- description:
- - The name or ID of the subnet the member service is
- accessible from.
- elements: dict
- type: list
- wait:
- description:
- - If the module should wait for the load balancer to be created or
- deleted.
- type: bool
- default: 'yes'
- timeout:
- description:
- - The amount of time the module should wait.
- default: 180
- type: int
-requirements:
- - "python >= 3.6"
- - "openstacksdk"
-
extends_documentation_fragment:
-- openstack.cloud.openstack
+ - openstack.cloud.openstack
'''
-RETURN = '''
-id:
- description: The load balancer UUID.
- returned: On success when C(state=present)
- type: str
- sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
-loadbalancer:
- description: Dictionary describing the load balancer.
- returned: On success when C(state=present)
- type: complex
- contains:
- id:
- description: Unique UUID.
- type: str
- sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
- name:
- description: Name given to the load balancer.
- type: str
- sample: "lingxian_test"
- vip_network_id:
- description: Network ID the load balancer virtual IP port belongs in.
- type: str
- sample: "f171db43-56fd-41cf-82d7-4e91d741762e"
- vip_subnet_id:
- description: Subnet ID the load balancer virtual IP port belongs in.
- type: str
- sample: "c53e3c70-9d62-409a-9f71-db148e7aa853"
- vip_port_id:
- description: The load balancer virtual IP port ID.
- type: str
- sample: "2061395c-1c01-47ab-b925-c91b93df9c1d"
- vip_address:
- description: The load balancer virtual IP address.
- type: str
- sample: "192.168.2.88"
- public_vip_address:
- description: The load balancer public VIP address.
- type: str
- sample: "10.17.8.254"
- provisioning_status:
- description: The provisioning status of the load balancer.
- type: str
- sample: "ACTIVE"
- operating_status:
- description: The operating status of the load balancer.
- type: str
- sample: "ONLINE"
- is_admin_state_up:
- description: The administrative state of the load balancer.
- type: bool
- sample: true
- listeners:
- description: The associated listener IDs, if any.
- type: list
- sample: [{"id": "7aa1b380-beec-459c-a8a7-3a4fb6d30645"}, {"id": "692d06b8-c4f8-4bdb-b2a3-5a263cc23ba6"}]
- pools:
- description: The associated pool IDs, if any.
- type: list
- sample: [{"id": "27b78d92-cee1-4646-b831-e3b90a7fa714"}, {"id": "befc1fb5-1992-4697-bdb9-eee330989344"}]
+RETURN = r'''
+floating_ip:
+ description: Dictionary describing the floating ip address attached to the
+ load-balancer.
+ type: dict
+ returned: On success when I(state) is C(present) and I(assign_floating_ip) is
+ C(true).
+ contains:
+ created_at:
+ description: Timestamp at which the floating IP was assigned.
+ type: str
+ description:
+ description: The description of a floating IP.
+ type: str
+ dns_domain:
+ description: The DNS domain.
+ type: str
+ dns_name:
+ description: The DNS name.
+ type: str
+ fixed_ip_address:
+ description: The fixed IP address associated with a floating IP address.
+ type: str
+ floating_ip_address:
+ description: The IP address of a floating IP.
+ type: str
+ floating_network_id:
+ description: The id of the network associated with a floating IP.
+ type: str
+ id:
+ description: Id of the floating ip.
+ type: str
+ name:
+ description: Name of the floating ip.
+ type: str
+ port_details:
+ description: |
+ The details of the port that this floating IP associates
+ with. Present if C(fip-port-details) extension is loaded.
+ type: dict
+ port_id:
+ description: The port ID floating ip associated with.
+ type: str
+ project_id:
+ description: The ID of the project this floating IP is associated with.
+ type: str
+ qos_policy_id:
+ description: The ID of the QoS policy attached to the floating IP.
+ type: str
+ revision_number:
+ description: Revision number.
+ type: str
+ router_id:
+ description: The id of the router floating ip associated with.
+ type: str
+ status:
+ description: |
+ The status of a floating IP, which can be 'ACTIVE' or 'DOWN'.
+ type: str
+ subnet_id:
+ description: The id of the subnet the floating ip associated with.
+ type: str
+ tags:
+ description: List of tags.
+ type: list
+ elements: str
+ updated_at:
+ description: Timestamp at which the floating IP was last updated.
+ type: str
+load_balancer:
+ description: Dictionary describing the load-balancer.
+ returned: On success when I(state) is C(present).
+ type: dict
+ contains:
+ additional_vips:
+ description: Additional VIPs.
+ type: str
+ availability_zone:
+ description: Name of the target Octavia availability zone.
+ type: str
+ created_at:
+ description: Timestamp when the load balancer was created.
+ type: str
+ description:
+ description: The load balancer description.
+ type: str
+ flavor_id:
+ description: The load balancer flavor ID.
+ type: str
+ id:
+ description: Unique UUID.
+ type: str
+ is_admin_state_up:
+ description: The administrative state of the load balancer.
+ type: bool
+ listeners:
+ description: The associated listener IDs, if any.
+ type: list
+ name:
+ description: Name given to the load balancer.
+ type: str
+ operating_status:
+ description: The operating status of the load balancer.
+ type: str
+ pools:
+ description: The associated pool IDs, if any.
+ type: list
+ project_id:
+ description: The ID of the project this load balancer is associated with.
+ type: str
+ provider:
+ description: Provider name for the load balancer.
+ type: str
+ provisioning_status:
+ description: The provisioning status of the load balancer.
+ type: str
+ tags:
+ description: A list of associated tags.
+ type: str
+ updated_at:
+ description: Timestamp when the load balancer was last updated.
+ type: str
+ vip_address:
+ description: The load balancer virtual IP address.
+ type: str
+ vip_network_id:
+ description: Network ID the load balancer virtual IP port belongs in.
+ type: str
+ vip_port_id:
+ description: The load balancer virtual IP port ID.
+ type: str
+ vip_qos_policy_id:
+ description: VIP qos policy id.
+ type: str
+ vip_subnet_id:
+ description: Subnet ID the load balancer virtual IP port belongs in.
+ type: str
'''
-EXAMPLES = '''
-# Create a load balancer by specifying the VIP subnet.
-- openstack.cloud.loadbalancer:
- auth:
- auth_url: https://identity.example.com
- username: admin
- password: passme
- project_name: admin
- state: present
+EXAMPLES = r'''
+- name: Create a load balancer
+ openstack.cloud.loadbalancer:
+ cloud: devstack
name: my_lb
- vip_subnet: my_subnet
- timeout: 150
-
-# Create a load balancer by specifying the VIP network and the IP address.
-- openstack.cloud.loadbalancer:
- auth:
- auth_url: https://identity.example.com
- username: admin
- password: passme
- project_name: admin
state: present
+ vip_subnet: my_subnet
+
+- name: Create another load balancer
+ openstack.cloud.loadbalancer:
+ cloud: devstack
name: my_lb
- vip_network: my_network
+ state: present
vip_address: 192.168.0.11
+ vip_network: my_network
-# Create a load balancer together with its sub-resources in the 'all in one'
-# way. A public IP address is also allocated to the load balancer VIP.
-- openstack.cloud.loadbalancer:
- auth:
- auth_url: https://identity.example.com
- username: admin
- password: passme
- project_name: admin
- name: lingxian_test
- state: present
- vip_subnet: kong_subnet
- auto_public_ip: yes
- public_network: public
- listeners:
- - name: lingxian_80
- protocol: TCP
- protocol_port: 80
- pool:
- name: lingxian_80_pool
- protocol: TCP
- members:
- - name: mywebserver1
- address: 192.168.2.81
- protocol_port: 80
- subnet: webserver_subnet
- - name: lingxian_8080
- protocol: TCP
- protocol_port: 8080
- pool:
- name: lingxian_8080-pool
- protocol: TCP
- members:
- - name: mywebserver2
- address: 192.168.2.82
- protocol_port: 8080
- wait: yes
- timeout: 600
-
-# Delete a load balancer(and all its related resources)
-- openstack.cloud.loadbalancer:
- auth:
- auth_url: https://identity.example.com
- username: admin
- password: passme
- project_name: admin
- state: absent
+- name: Delete a load balancer and all its related resources
+ openstack.cloud.loadbalancer:
+ cloud: devstack
name: my_lb
-
-# Delete a load balancer(and all its related resources) together with the
-# public IP address(if any) attached to it.
-- openstack.cloud.loadbalancer:
- auth:
- auth_url: https://identity.example.com
- username: admin
- password: passme
- project_name: admin
state: absent
+
+- name: Delete a load balancer, its related resources and its floating ip
+ openstack.cloud.loadbalancer:
+ cloud: devstack
+ delete_floating_ip: true
name: my_lb
- delete_public_ip: yes
+ state: absent
'''
-import time
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class LoadBalancerModule(OpenStackModule):
- def _wait_for_pool(self, pool, provisioning_status, operating_status, failures, interval=5):
- """Wait for pool to be in a particular provisioning and operating status."""
- timeout = self.params['timeout'] # reuse loadbalancer timeout
-
- total_sleep = 0
- if failures is None:
- failures = []
+ argument_spec = dict(
+ assign_floating_ip=dict(default=False, type='bool',
+ aliases=['auto_public_ip']),
+ delete_floating_ip=dict(default=False, type='bool',
+ aliases=['delete_public_ip']),
+ description=dict(),
+ flavor=dict(),
+ floating_ip_address=dict(aliases=['public_ip_address']),
+ floating_ip_network=dict(aliases=['public_network']),
+ name=dict(required=True),
+ state=dict(default='present', choices=['absent', 'present']),
+ vip_address=dict(),
+ vip_network=dict(),
+ vip_port=dict(),
+ vip_subnet=dict(),
+ )
+ module_kwargs = dict(
+ required_if=[
+ ('state', 'present', ('vip_network', 'vip_subnet', 'vip_port'),
+ True)
+ ],
+ mutually_exclusive=[
+ ('assign_floating_ip', 'delete_floating_ip'),
+ ],
+ supports_check_mode=True,
+ )
- while total_sleep < timeout:
- pool = self.conn.load_balancer.find_pool(name_or_id=pool.id)
- if pool:
- if pool.provisioning_status == provisioning_status and pool.operating_status == operating_status:
- return None
- if pool.provisioning_status in failures:
- self.fail_json(
- msg="Pool %s transitioned to failure state %s" %
- (pool.id, pool.provisioning_status)
- )
+ def run(self):
+ state = self.params['state']
+
+ load_balancer = self._find()
+
+ if self.ansible.check_mode:
+ self.exit_json(changed=self._will_change(state, load_balancer))
+
+ if state == 'present' and not load_balancer:
+ # Create load_balancer
+ load_balancer, floating_ip = self._create()
+ self.exit_json(
+ changed=True,
+ load_balancer=load_balancer.to_dict(computed=False),
+ **(dict(floating_ip=floating_ip.to_dict(computed=False))
+ if floating_ip is not None else dict()))
+
+ elif state == 'present' and load_balancer:
+ # Update load_balancer
+ update, floating_ip = self._build_update(load_balancer)
+ if update:
+ load_balancer, floating_ip = self._update(load_balancer,
+ update)
+
+ self.exit_json(
+ changed=bool(update),
+ load_balancer=load_balancer.to_dict(computed=False),
+ **(dict(floating_ip=floating_ip.to_dict(computed=False))
+ if floating_ip is not None else dict()))
+
+ elif state == 'absent' and load_balancer:
+ # Delete load_balancer
+ self._delete(load_balancer)
+ self.exit_json(changed=True)
+
+ elif state == 'absent' and not load_balancer:
+ # Do nothing
+ self.exit_json(changed=False)
+
+ def _build_update(self, load_balancer):
+ update = {}
+
+ non_updateable_keys = [k for k in ['name', 'vip_address']
+ if self.params[k] is not None
+ and self.params[k] != load_balancer[k]]
+
+ flavor_name_or_id = self.params['flavor']
+ if flavor_name_or_id is not None:
+ flavor = self.conn.load_balancer.find_flavor(
+ flavor_name_or_id, ignore_missing=False)
+ if load_balancer['flavor_id'] != flavor.id:
+ non_updateable_keys.append('flavor_id')
+
+ vip_network_name_or_id = self.params['vip_network']
+ if vip_network_name_or_id is not None:
+ network = self.conn.network.find_network(
+ vip_network_name_or_id, ignore_missing=False)
+ if load_balancer['vip_network_id'] != network.id:
+ non_updateable_keys.append('vip_network_id')
+
+ vip_subnet_name_or_id = self.params['vip_subnet']
+ if vip_subnet_name_or_id is not None:
+ subnet = self.conn.network.find_subnet(
+ vip_subnet_name_or_id, ignore_missing=False)
+ if load_balancer['vip_subnet_id'] != subnet.id:
+ non_updateable_keys.append('vip_subnet_id')
+
+ vip_port_name_or_id = self.params['vip_port']
+ if vip_port_name_or_id is not None:
+ port = self.conn.network.find_port(
+ vip_port_name_or_id, ignore_missing=False)
+ if load_balancer['vip_port_id'] != port.id:
+ non_updateable_keys.append('vip_port_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']
+ if self.params[k] is not None
+ and self.params[k] != load_balancer[k])
+
+ if attributes:
+ update['attributes'] = attributes
+
+ floating_ip, floating_ip_update = \
+ self._build_update_floating_ip(load_balancer)
+
+ return {**update, **floating_ip_update}, floating_ip
+
+ def _build_update_floating_ip(self, load_balancer):
+ assign_floating_ip = self.params['assign_floating_ip']
+ delete_floating_ip = self.params['delete_floating_ip']
+
+ floating_ip_address = self.params['floating_ip_address']
+ if floating_ip_address is not None \
+ and (not assign_floating_ip and not delete_floating_ip):
+ self.fail_json(msg="assign_floating_ip or delete_floating_ip must"
+ " be true when floating_ip_address is set")
+
+ floating_ip_network = self.params['floating_ip_network']
+ if floating_ip_network is not None \
+ and (not assign_floating_ip and not delete_floating_ip):
+ self.fail_json(msg="assign_floating_ip or delete_floating_ip must"
+ " be true when floating_ip_network is set")
+
+ ips = list(self.conn.network.ips(
+ port_id=load_balancer.vip_port_id,
+ fixed_ip_address=load_balancer.vip_address))
+
+ if len(ips) > 1:
+ self.fail_json(msg="Only a single floating ip address"
+ " per load-balancer is supported")
+
+ if delete_floating_ip or not assign_floating_ip:
+ if not ips:
+ return None, {}
+
+ if len(ips) != 1:
+ raise AssertionError("A single floating ip is expected")
+
+ ip = ips[0]
+
+ return ip, {'delete_floating_ip': ip}
+
+ # else assign_floating_ip
+
+ if not ips:
+ return None, dict(
+ assign_floating_ip=dict(
+ floating_ip_address=floating_ip_address,
+ floating_ip_network=floating_ip_network))
+
+ if len(ips) != 1:
+ raise AssertionError("A single floating ip is expected")
+
+ ip = ips[0]
+
+ if floating_ip_network is not None:
+ network = self.conn.network.find_network(floating_ip_network,
+ ignore_missing=False)
+ if ip.floating_network_id != network.id:
+ return ip, dict(
+ assign_floating_ip=dict(
+ floating_ip_address=floating_ip_address,
+ floating_ip_network=floating_ip_network),
+ delete_floating_ip=ip)
+
+ if floating_ip_address is not None \
+ and floating_ip_address != ip.floating_ip_address:
+ return ip, dict(
+ assign_floating_ip=dict(
+ floating_ip_address=floating_ip_address,
+ floating_ip_network=floating_ip_network),
+ delete_floating_ip=ip)
+
+ return ip, {}
+
+ def _create(self):
+ kwargs = dict((k, self.params[k])
+ for k in ['description', 'name', 'vip_address']
+ if self.params[k] is not None)
+
+ flavor_name_or_id = self.params['flavor']
+ if flavor_name_or_id is not None:
+ flavor = self.conn.load_balancer.find_flavor(
+ flavor_name_or_id, ignore_missing=False)
+ kwargs['flavor_id'] = flavor.id
+
+ vip_network_name_or_id = self.params['vip_network']
+ if vip_network_name_or_id is not None:
+ network = self.conn.network.find_network(
+ vip_network_name_or_id, ignore_missing=False)
+ kwargs['vip_network_id'] = network.id
+
+ vip_subnet_name_or_id = self.params['vip_subnet']
+ if vip_subnet_name_or_id is not None:
+ subnet = self.conn.network.find_subnet(
+ vip_subnet_name_or_id, ignore_missing=False)
+ kwargs['vip_subnet_id'] = subnet.id
+
+ vip_port_name_or_id = self.params['vip_port']
+ if vip_port_name_or_id is not None:
+ port = self.conn.network.find_port(
+ vip_port_name_or_id, ignore_missing=False)
+ kwargs['vip_port_id'] = port.id
+
+ load_balancer = self.conn.load_balancer.create_load_balancer(**kwargs)
+
+ if self.params['wait']:
+ load_balancer = self.conn.load_balancer.wait_for_load_balancer(
+ load_balancer.id,
+ wait=self.params['timeout'])
+
+ floating_ip, update = self._build_update_floating_ip(load_balancer)
+ if update:
+ load_balancer, floating_ip = \
+ self._update_floating_ip(load_balancer, update)
+
+ return load_balancer, floating_ip
+
+ def _delete(self, load_balancer):
+ if self.params['delete_floating_ip']:
+ ips = list(self.conn.network.ips(
+ port_id=load_balancer.vip_port_id,
+ fixed_ip_address=load_balancer.vip_address))
+ else:
+ ips = []
+
+ # With cascade=False the deletion of load-balancer
+ # would always fail if there are sub-resources.
+ self.conn.load_balancer.delete_load_balancer(load_balancer.id,
+ cascade=True)
+
+ if self.params['wait']:
+ for count in self.sdk.utils.iterate_timeout(
+ timeout=self.params['timeout'],
+ message="Timeout waiting for load-balancer to be absent"
+ ):
+ if self.conn.load_balancer.\
+ find_load_balancer(load_balancer.id) is None:
+ break
+
+ for ip in ips:
+ self.conn.network.delete_ip(ip)
+
+ def _find(self):
+ name = self.params['name']
+ return self.conn.load_balancer.find_load_balancer(name_or_id=name)
+
+ def _update(self, load_balancer, update):
+ attributes = update.get('attributes')
+ if attributes:
+ load_balancer = \
+ self.conn.load_balancer.update_load_balancer(load_balancer.id,
+ **attributes)
+
+ if self.params['wait']:
+ load_balancer = self.conn.load_balancer.wait_for_load_balancer(
+ load_balancer.id,
+ wait=self.params['timeout'])
+
+ load_balancer, floating_ip = \
+ self._update_floating_ip(load_balancer, update)
+
+ return load_balancer, floating_ip
+
+ def _update_floating_ip(self, load_balancer, update):
+ floating_ip = None
+ delete_floating_ip = update.get('delete_floating_ip')
+ if delete_floating_ip:
+ self.conn.network.delete_ip(delete_floating_ip.id)
+
+ assign_floating_ip = update.get('assign_floating_ip')
+ if assign_floating_ip:
+ floating_ip_address = assign_floating_ip['floating_ip_address']
+ floating_ip_network = assign_floating_ip['floating_ip_network']
+
+ if floating_ip_network is not None:
+ network = self.conn.network.find_network(floating_ip_network,
+ ignore_missing=False)
else:
- if provisioning_status == "DELETED":
- return None
- else:
- self.fail_json(
- msg="Pool %s transitioned to DELETED" % pool.id
- )
-
- time.sleep(interval)
- total_sleep += interval
+ network = None
- def _wait_for_lb(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.find_load_balancer(lb.id)
+ if floating_ip_address is not None:
+ kwargs = ({'floating_network_id': network.id}
+ if network is not None else {})
+ ip = self.conn.network.find_ip(floating_ip_address, **kwargs)
+ else:
+ ip = None
- if lb:
- if lb.provisioning_status == status:
- return None
- if lb.provisioning_status in failures:
+ if ip:
+ if ip['port_id'] is not None:
self.fail_json(
- msg="Load Balancer %s transitioned to failure state %s" %
- (lb.id, lb.provisioning_status)
- )
- else:
- if status == "DELETED":
- return None
+ msg="Floating ip {0} is associated to another fixed ip"
+ " address {1} already".format(
+ ip.floating_ip_address, ip.fixed_ip_address))
+
+ # Associate floating ip
+ floating_ip = self.conn.network.update_ip(
+ ip.id, fixed_ip_address=load_balancer.vip_address,
+ port_id=load_balancer.vip_port_id)
+
+ elif floating_ip_address: # and not ip
+ # Create new floating ip
+ kwargs = ({'floating_network_id': network.id}
+ if network is not None else {})
+ floating_ip = self.conn.network.create_ip(
+ fixed_ip_address=load_balancer.vip_address,
+ floating_ip_address=floating_ip_address,
+ port_id=load_balancer.vip_port_id,
+ **kwargs)
+
+ elif network:
+ # List disassociated floating ips on network
+ ips = [ip
+ for ip in
+ self.conn.network.ips(floating_network_id=network.id)
+ if ip['port_id'] is None]
+ if ips:
+ # Associate first disassociated floating ip
+ ip = ips[0]
+ floating_ip = self.conn.network.update_ip(
+ ip.id, fixed_ip_address=load_balancer.vip_address,
+ port_id=load_balancer.vip_port_id)
else:
- self.fail_json(
- msg="Load Balancer %s transitioned to DELETED" % lb.id
- )
+ # No disassociated floating ips
+ # Create new floating ip on network
+ floating_ip = self.conn.network.create_ip(
+ fixed_ip_address=load_balancer.vip_address,
+ floating_network_id=network.id,
+ port_id=load_balancer.vip_port_id)
- time.sleep(interval)
- total_sleep += interval
-
- self.fail_json(
- msg="Timeout waiting for Load Balancer %s to transition to %s" %
- (lb.id, status)
- )
-
- argument_spec = dict(
- name=dict(required=True),
- flavor=dict(required=False),
- state=dict(default='present', choices=['absent', 'present']),
- vip_network=dict(required=False),
- vip_subnet=dict(required=False),
- vip_port=dict(required=False),
- vip_address=dict(required=False),
- listeners=dict(type='list', default=[], elements='dict'),
- public_ip_address=dict(required=False, default=None),
- auto_public_ip=dict(required=False, default=False, type='bool'),
- public_network=dict(required=False),
- delete_public_ip=dict(required=False, default=False, type='bool'),
- )
- module_kwargs = dict(supports_check_mode=True)
-
- def run(self):
- flavor = self.params['flavor']
- vip_network = self.params['vip_network']
- vip_subnet = self.params['vip_subnet']
- vip_port = self.params['vip_port']
- listeners = self.params['listeners']
- public_vip_address = self.params['public_ip_address']
- allocate_fip = self.params['auto_public_ip']
- delete_fip = self.params['delete_public_ip']
- public_network = self.params['public_network']
-
- vip_network_id = None
- vip_subnet_id = None
- vip_port_id = None
- flavor_id = None
-
- try:
- max_microversion = 1
- max_majorversion = 2
- changed = False
- lb = self.conn.load_balancer.find_load_balancer(
- name_or_id=self.params['name'])
-
- if self.params['state'] == 'present':
- if lb and self.ansible.check_mode:
- self.exit_json(changed=False)
- if lb:
- self.exit_json(changed=False)
- ver_data = self.conn.load_balancer.get_all_version_data()
- region = list(ver_data.keys())[0]
- interface_type = list(ver_data[region].keys())[0]
- versions = ver_data[region][interface_type]['load-balancer']
- for ver in versions:
- if ver['status'] == 'CURRENT':
- curversion = ver['version'].split(".")
- max_majorversion = int(curversion[0])
- max_microversion = int(curversion[1])
-
- if not lb:
- if self.ansible.check_mode:
- self.exit_json(changed=True)
-
- if not (vip_network or vip_subnet or vip_port):
- self.fail_json(
- msg="One of vip_network, vip_subnet, or vip_port must "
- "be specified for load balancer creation"
- )
-
- if flavor:
- _flavor = self.conn.load_balancer.find_flavor(flavor)
- if not _flavor:
- self.fail_json(
- msg='flavor %s not found' % flavor
- )
- flavor_id = _flavor.id
-
- if vip_network:
- network = self.conn.get_network(vip_network)
- if not network:
- self.fail_json(
- msg='network %s is not found' % vip_network
- )
- vip_network_id = network.id
- if vip_subnet:
- subnet = self.conn.get_subnet(vip_subnet)
- if not subnet:
- self.fail_json(
- msg='subnet %s is not found' % vip_subnet
- )
- vip_subnet_id = subnet.id
- if vip_port:
- port = self.conn.get_port(vip_port)
-
- if not port:
- self.fail_json(
- msg='port %s is not found' % vip_port
- )
- vip_port_id = port.id
- lbargs = {"name": self.params['name'],
- "vip_network_id": vip_network_id,
- "vip_subnet_id": vip_subnet_id,
- "vip_port_id": vip_port_id,
- "vip_address": self.params['vip_address']
- }
- if flavor_id is not None:
- lbargs["flavor_id"] = flavor_id
-
- lb = self.conn.load_balancer.create_load_balancer(**lbargs)
-
- changed = True
-
- if not listeners and not self.params['wait']:
- self.exit_json(
- changed=changed,
- loadbalancer=lb.to_dict(),
- id=lb.id
- )
-
- self._wait_for_lb(lb, "ACTIVE", ["ERROR"])
-
- for listener_def in listeners:
- listener_name = listener_def.get("name")
- pool_def = listener_def.get("pool")
-
- if not listener_name:
- self.fail_json(msg='listener name is required')
-
- listener = self.conn.load_balancer.find_listener(
- name_or_id=listener_name
- )
-
- if not listener:
- self._wait_for_lb(lb, "ACTIVE", ["ERROR"])
-
- protocol = listener_def.get("protocol", "HTTP")
- protocol_port = listener_def.get("protocol_port", 80)
- allowed_cidrs = listener_def.get("allowed_cidrs", [])
- listenerargs = {"name": listener_name,
- "loadbalancer_id": lb.id,
- "protocol": protocol,
- "protocol_port": protocol_port
- }
- if max_microversion >= 12 and max_majorversion >= 2:
- listenerargs['allowed_cidrs'] = allowed_cidrs
- listener = self.conn.load_balancer.create_listener(**listenerargs)
- changed = True
-
- # Ensure pool in the listener.
- if pool_def:
- pool_name = pool_def.get("name")
- members = pool_def.get('members', [])
-
- if not pool_name:
- self.fail_json(msg='pool name is required')
-
- pool = self.conn.load_balancer.find_pool(name_or_id=pool_name)
-
- if not pool:
- self._wait_for_lb(lb, "ACTIVE", ["ERROR"])
-
- protocol = pool_def.get("protocol", "HTTP")
- lb_algorithm = pool_def.get("lb_algorithm",
- "ROUND_ROBIN")
-
- pool = self.conn.load_balancer.create_pool(
- name=pool_name,
- listener_id=listener.id,
- protocol=protocol,
- lb_algorithm=lb_algorithm
- )
- self._wait_for_pool(pool, "ACTIVE", "ONLINE", ["ERROR"])
- changed = True
-
- # Ensure members in the pool
- for member_def in members:
- member_name = member_def.get("name")
- if not member_name:
- self.fail_json(msg='member name is required')
-
- member = self.conn.load_balancer.find_member(member_name,
- pool.id
- )
-
- if not member:
- self._wait_for_lb(lb, "ACTIVE", ["ERROR"])
-
- address = member_def.get("address")
- if not address:
- self.fail_json(
- msg='member address for member %s is '
- 'required' % member_name
- )
-
- subnet_id = member_def.get("subnet")
- if subnet_id:
- subnet = self.conn.get_subnet(subnet_id)
- if not subnet:
- self.fail_json(
- msg='subnet %s for member %s is not '
- 'found' % (subnet_id, member_name)
- )
- subnet_id = subnet.id
-
- protocol_port = member_def.get("protocol_port", 80)
-
- member = self.conn.load_balancer.create_member(
- pool,
- name=member_name,
- address=address,
- protocol_port=protocol_port,
- subnet_id=subnet_id
- )
- self._wait_for_pool(pool, "ACTIVE", "ONLINE", ["ERROR"])
- changed = True
-
- # Associate public ip to the load balancer VIP. If
- # public_vip_address is provided, use that IP, otherwise, either
- # find an available public ip or create a new one.
- fip = None
- orig_public_ip = None
- new_public_ip = None
- if public_vip_address or allocate_fip:
- ips = self.conn.network.ips(
- port_id=lb.vip_port_id,
- fixed_ip_address=lb.vip_address
- )
- ips = list(ips)
- if ips:
- orig_public_ip = ips[0]
- new_public_ip = orig_public_ip.floating_ip_address
-
- if public_vip_address and public_vip_address != orig_public_ip:
- fip = self.conn.network.find_ip(public_vip_address)
-
- if not fip:
- self.fail_json(
- msg='Public IP %s is unavailable' % public_vip_address
- )
-
- # Release origin public ip first
- self.conn.network.update_ip(
- orig_public_ip,
- fixed_ip_address=None,
- port_id=None
- )
-
- # Associate new public ip
- self.conn.network.update_ip(
- fip,
- fixed_ip_address=lb.vip_address,
- port_id=lb.vip_port_id
- )
-
- new_public_ip = public_vip_address
- changed = True
- elif allocate_fip and not orig_public_ip:
- fip = self.conn.network.find_available_ip()
- if not fip:
- if not public_network:
- self.fail_json(msg="Public network is not provided")
-
- pub_net = self.conn.network.find_network(public_network)
- if not pub_net:
- self.fail_json(
- msg='Public network %s not found' %
- public_network
- )
- fip = self.conn.network.create_ip(
- floating_network_id=pub_net.id
- )
-
- self.conn.network.update_ip(
- fip,
- fixed_ip_address=lb.vip_address,
- port_id=lb.vip_port_id
- )
-
- new_public_ip = fip.floating_ip_address
- changed = True
-
- # Include public_vip_address in the result.
- lb = self.conn.load_balancer.find_load_balancer(name_or_id=lb.id)
- lb_dict = lb.to_dict()
- lb_dict.update({"public_vip_address": new_public_ip})
-
- self.exit_json(
- changed=changed,
- loadbalancer=lb_dict,
- id=lb.id
- )
- elif self.params['state'] == 'absent':
- changed = False
- public_vip_address = None
-
- if lb:
- if self.ansible.check_mode:
- self.exit_json(changed=True)
- if delete_fip:
- ips = self.conn.network.ips(
- port_id=lb.vip_port_id,
- fixed_ip_address=lb.vip_address
- )
- ips = list(ips)
- if ips:
- public_vip_address = ips[0]
-
- # Deleting load balancer with `cascade=False` does not make
- # sense because the deletion will always fail if there are
- # sub-resources.
- self.conn.load_balancer.delete_load_balancer(lb, cascade=True)
- changed = True
-
- if self.params['wait']:
- self._wait_for_lb(lb, "DELETED", ["ERROR"])
-
- if delete_fip and public_vip_address:
- self.conn.network.delete_ip(public_vip_address)
- changed = True
- elif self.ansible.check_mode:
- self.exit_json(changed=False)
-
- self.exit_json(changed=changed)
- except Exception as e:
- self.fail_json(msg=str(e))
+ else:
+ # Find disassociated floating ip
+ ip = self.conn.network.find_available_ip()
+
+ if ip:
+ # Associate disassociated floating ip
+ floating_ip = self.conn.network.update_ip(
+ ip.id, fixed_ip_address=load_balancer.vip_address,
+ port_id=load_balancer.vip_port_id)
+ else:
+ # Create new floating ip
+ floating_ip = self.conn.network.create_ip(
+ fixed_ip_address=load_balancer.vip_address,
+ port_id=load_balancer.vip_port_id)
+
+ return load_balancer, floating_ip
+
+ def _will_change(self, state, load_balancer):
+ if state == 'present' and not load_balancer:
+ return True
+ elif state == 'present' and load_balancer:
+ return bool(self._build_update(load_balancer)[0])
+ elif state == 'absent' and load_balancer:
+ return True
+ else:
+ # state == 'absent' and not load_balancer:
+ return False
def main():