summaryrefslogtreecommitdiff
path: root/lib/ansible/modules/network/f5/bigip_pool_member.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ansible/modules/network/f5/bigip_pool_member.py')
-rw-r--r--lib/ansible/modules/network/f5/bigip_pool_member.py1658
1 files changed, 0 insertions, 1658 deletions
diff --git a/lib/ansible/modules/network/f5/bigip_pool_member.py b/lib/ansible/modules/network/f5/bigip_pool_member.py
deleted file mode 100644
index fcd9dbe833..0000000000
--- a/lib/ansible/modules/network/f5/bigip_pool_member.py
+++ /dev/null
@@ -1,1658 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-#
-# Copyright: (c) 2017, F5 Networks Inc.
-# Copyright: (c) 2013, Matt Hite <mhite@hotmail.com>
-# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-DOCUMENTATION = r'''
----
-module: bigip_pool_member
-short_description: Manages F5 BIG-IP LTM pool members
-description:
- - Manages F5 BIG-IP LTM pool members via iControl SOAP API.
-version_added: 1.4
-options:
- name:
- description:
- - Name of the node to create, or re-use, when creating a new pool member.
- - This parameter is optional and, if not specified, a node name will be
- created automatically from either the specified C(address) or C(fqdn).
- - The C(enabled) state is an alias of C(present).
- type: str
- version_added: 2.6
- state:
- description:
- - Pool member state.
- type: str
- required: True
- choices:
- - present
- - absent
- - enabled
- - disabled
- - forced_offline
- default: present
- pool:
- description:
- - Pool name. This pool must exist.
- type: str
- required: True
- partition:
- description:
- - Partition to manage resources on.
- type: str
- default: Common
- address:
- description:
- - IP address of the pool member. This can be either IPv4 or IPv6. When creating a
- new pool member, one of either C(address) or C(fqdn) must be provided. This
- parameter cannot be updated after it is set.
- type: str
- aliases:
- - ip
- - host
- version_added: 2.2
- fqdn:
- description:
- - FQDN name of the pool member. This can be any name that is a valid RFC 1123 DNS
- name. Therefore, the only characters that can be used are "A" to "Z",
- "a" to "z", "0" to "9", the hyphen ("-") and the period (".").
- - FQDN names must include at lease one period; delineating the host from
- the domain. ex. C(host.domain).
- - FQDN names must end with a letter or a number.
- - When creating a new pool member, one of either C(address) or C(fqdn) must be
- provided. This parameter cannot be updated after it is set.
- type: str
- aliases:
- - hostname
- version_added: 2.6
- port:
- description:
- - Pool member port.
- - This value cannot be changed after it has been set.
- type: int
- required: True
- connection_limit:
- description:
- - Pool member connection limit. Setting this to 0 disables the limit.
- type: int
- description:
- description:
- - Pool member description.
- type: str
- rate_limit:
- description:
- - Pool member rate limit (connections-per-second). Setting this to 0
- disables the limit.
- type: int
- ratio:
- description:
- - Pool member ratio weight. Valid values range from 1 through 100.
- New pool members -- unless overridden with this value -- default
- to 1.
- type: int
- preserve_node:
- description:
- - When state is C(absent) attempts to remove the node that the pool
- member references.
- - The node will not be removed if it is still referenced by other pool
- members. If this happens, the module will not raise an error.
- - Setting this to C(yes) disables this behavior.
- type: bool
- version_added: 2.1
- priority_group:
- description:
- - Specifies a number representing the priority group for the pool member.
- - When adding a new member, the default is 0, meaning that the member has no priority.
- - To specify a priority, you must activate priority group usage when you
- create a new pool or when adding or removing pool members. When activated,
- the system load balances traffic according to the priority group number
- assigned to the pool member.
- - The higher the number, the higher the priority, so a member with a priority
- of 3 has higher priority than a member with a priority of 1.
- type: int
- version_added: 2.5
- fqdn_auto_populate:
- description:
- - Specifies whether the system automatically creates ephemeral nodes using
- the IP addresses returned by the resolution of a DNS query for a node
- defined by an FQDN.
- - When C(yes), the system generates an ephemeral node for each IP address
- returned in response to a DNS query for the FQDN of the node. Additionally,
- when a DNS response indicates the IP address of an ephemeral node no longer
- exists, the system deletes the ephemeral node.
- - When C(no), the system resolves a DNS query for the FQDN of the node
- with the single IP address associated with the FQDN.
- - When creating a new pool member, the default for this parameter is C(yes).
- - Once set this parameter cannot be changed afterwards.
- - This parameter is ignored when C(reuse_nodes) is C(yes).
- type: bool
- version_added: 2.6
- reuse_nodes:
- description:
- - Reuses node definitions if requested.
- type: bool
- default: yes
- version_added: 2.6
- monitors:
- description:
- - Specifies the health monitors that the system currently uses to monitor
- this resource.
- type: list
- version_added: 2.8
- availability_requirements:
- description:
- - Specifies, if you activate more than one health monitor, the number of health
- monitors that must receive successful responses in order for the link to be
- considered available.
- - Specifying an empty string will remove the monitors and revert to inheriting from pool (default).
- - Specifying C(none) value will remove any health monitoring from the member completely.
- suboptions:
- type:
- description:
- - Monitor rule type when C(monitors) is specified.
- - When creating a new pool, if this value is not specified, the default of
- 'all' will be used.
- type: str
- choices:
- - all
- - at_least
- at_least:
- description:
- - Specifies the minimum number of active health monitors that must be successful
- before the link is considered up.
- - This parameter is only relevant when a C(type) of C(at_least) is used.
- - This parameter will be ignored if a type of C(all) is used.
- type: int
- type: dict
- version_added: 2.8
- ip_encapsulation:
- description:
- - Specifies the IP encapsulation using either IPIP (IP encapsulation within IP,
- RFC 2003) or GRE (Generic Router Encapsulation, RFC 2784) on outbound packets
- (from BIG-IP system to server-pool member).
- - When C(none), disables IP encapsulation.
- - When C(inherit), inherits IP encapsulation setting from the member's pool.
- - When any other value, Options are None, Inherit from Pool, and Member Specific.
- type: str
- version_added: 2.8
- aggregate:
- description:
- - List of pool member definitions to be created, modified or removed.
- - When using C(aggregates) if one of the aggregate definitions is invalid, the aggregate run will fail,
- indicating the error it last encountered.
- - The module will C(NOT) rollback any changes it has made prior to encountering the error.
- - The module also will not indicate what changes were made prior to failure, therefore it is strongly advised
- to run the module in check mode to make basic validation, prior to module execution.
- type: list
- aliases:
- - members
- version_added: 2.8
- replace_all_with:
- description:
- - Remove members not defined in the C(aggregate) parameter.
- - This operation is all or none, meaning that it will stop if there are some pool members
- that cannot be removed.
- type: bool
- default: no
- aliases:
- - purge
- version_added: 2.8
-notes:
- - In previous versions of this module, which used the SDK, the C(name) parameter would act as C(fqdn) if C(address) or
- C(fqdn) were not provided.
-extends_documentation_fragment: f5
-author:
- - Tim Rupp (@caphrim007)
- - Wojciech Wypior (@wojtek0806)
-'''
-
-EXAMPLES = '''
-- name: Add pool member
- bigip_pool_member:
- pool: my-pool
- partition: Common
- host: "{{ ansible_default_ipv4['address'] }}"
- port: 80
- description: web server
- connection_limit: 100
- rate_limit: 50
- ratio: 2
- provider:
- server: lb.mydomain.com
- user: admin
- password: secret
- delegate_to: localhost
-
-- name: Modify pool member ratio and description
- bigip_pool_member:
- pool: my-pool
- partition: Common
- host: "{{ ansible_default_ipv4['address'] }}"
- port: 80
- ratio: 1
- description: nginx server
- provider:
- server: lb.mydomain.com
- user: admin
- password: secret
- delegate_to: localhost
-
-- name: Remove pool member from pool
- bigip_pool_member:
- state: absent
- pool: my-pool
- partition: Common
- host: "{{ ansible_default_ipv4['address'] }}"
- port: 80
- provider:
- server: lb.mydomain.com
- user: admin
- password: secret
- delegate_to: localhost
-
-- name: Force pool member offline
- bigip_pool_member:
- state: forced_offline
- pool: my-pool
- partition: Common
- host: "{{ ansible_default_ipv4['address'] }}"
- port: 80
- provider:
- server: lb.mydomain.com
- user: admin
- password: secret
- delegate_to: localhost
-
-- name: Create members with priority groups
- bigip_pool_member:
- pool: my-pool
- partition: Common
- host: "{{ item.address }}"
- name: "{{ item.name }}"
- priority_group: "{{ item.priority_group }}"
- port: 80
- provider:
- server: lb.mydomain.com
- user: admin
- password: secret
- delegate_to: localhost
- loop:
- - address: 1.1.1.1
- name: web1
- priority_group: 4
- - address: 2.2.2.2
- name: web2
- priority_group: 3
- - address: 3.3.3.3
- name: web3
- priority_group: 2
- - address: 4.4.4.4
- name: web4
- priority_group: 1
-
-- name: Add pool members aggregate
- bigip_pool_member:
- pool: my-pool
- aggregate:
- - host: 192.168.1.1
- partition: Common
- port: 80
- description: web server
- connection_limit: 100
- rate_limit: 50
- ratio: 2
- - host: 192.168.1.2
- partition: Common
- port: 80
- description: web server
- connection_limit: 100
- rate_limit: 50
- ratio: 2
- - host: 192.168.1.3
- partition: Common
- port: 80
- description: web server
- connection_limit: 100
- rate_limit: 50
- ratio: 2
- provider:
- server: lb.mydomain.com
- user: admin
- password: secret
- delegate_to: localhost
-
-- name: Add pool members aggregate, remove non aggregates
- bigip_pool_member:
- pool: my-pool
- aggregate:
- - host: 192.168.1.1
- partition: Common
- port: 80
- description: web server
- connection_limit: 100
- rate_limit: 50
- ratio: 2
- - host: 192.168.1.2
- partition: Common
- port: 80
- description: web server
- connection_limit: 100
- rate_limit: 50
- ratio: 2
- - host: 192.168.1.3
- partition: Common
- port: 80
- description: web server
- connection_limit: 100
- rate_limit: 50
- ratio: 2
- replace_all_with: yes
- provider:
- server: lb.mydomain.com
- user: admin
- password: secret
- delegate_to: localhost
-'''
-
-RETURN = '''
-rate_limit:
- description: The new rate limit, in connections per second, of the pool member.
- returned: changed
- type: int
- sample: 100
-connection_limit:
- description: The new connection limit of the pool member
- returned: changed
- type: int
- sample: 1000
-description:
- description: The new description of pool member.
- returned: changed
- type: str
- sample: My pool member
-ratio:
- description: The new pool member ratio weight.
- returned: changed
- type: int
- sample: 50
-priority_group:
- description: The new priority group.
- returned: changed
- type: int
- sample: 3
-fqdn_auto_populate:
- description: Whether FQDN auto population was set on the member or not.
- returned: changed
- type: bool
- sample: True
-fqdn:
- description: The FQDN of the pool member.
- returned: changed
- type: str
- sample: foo.bar.com
-address:
- description: The address of the pool member.
- returned: changed
- type: str
- sample: 1.2.3.4
-monitors:
- description: The new list of monitors for the resource.
- returned: changed
- type: list
- sample: ['/Common/monitor1', '/Common/monitor2']
-replace_all_with:
- description: Purges all non-aggregate pool members from device
- returned: changed
- type: bool
- sample: yes
-'''
-
-import os
-import re
-
-from copy import deepcopy
-
-from ansible.module_utils.urls import urlparse
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.basic import env_fallback
-from ansible.module_utils.six import iteritems
-from ansible.module_utils.network.common.utils import remove_default_spec
-
-try:
- from library.module_utils.network.f5.bigip import F5RestClient
- from library.module_utils.network.f5.common import F5ModuleError
- from library.module_utils.network.f5.common import AnsibleF5Parameters
- from library.module_utils.network.f5.common import fq_name
- from library.module_utils.network.f5.common import transform_name
- from library.module_utils.network.f5.common import f5_argument_spec
- from library.module_utils.network.f5.common import is_valid_hostname
- from library.module_utils.network.f5.common import flatten_boolean
- from library.module_utils.network.f5.compare import cmp_str_with_none
- from library.module_utils.network.f5.ipaddress import is_valid_ip
- from library.module_utils.network.f5.ipaddress import validate_ip_v6_address
- from library.module_utils.network.f5.icontrol import TransactionContextManager
-except ImportError:
- from ansible.module_utils.network.f5.bigip import F5RestClient
- from ansible.module_utils.network.f5.common import F5ModuleError
- from ansible.module_utils.network.f5.common import AnsibleF5Parameters
- from ansible.module_utils.network.f5.common import fq_name
- from ansible.module_utils.network.f5.common import transform_name
- from ansible.module_utils.network.f5.common import f5_argument_spec
- from ansible.module_utils.network.f5.common import is_valid_hostname
- from ansible.module_utils.network.f5.common import flatten_boolean
- from ansible.module_utils.network.f5.compare import cmp_str_with_none
- from ansible.module_utils.network.f5.ipaddress import is_valid_ip
- from ansible.module_utils.network.f5.ipaddress import validate_ip_v6_address
- from ansible.module_utils.network.f5.icontrol import TransactionContextManager
-
-
-class Parameters(AnsibleF5Parameters):
- api_map = {
- 'rateLimit': 'rate_limit',
- 'connectionLimit': 'connection_limit',
- 'priorityGroup': 'priority_group',
- 'monitor': 'monitors',
- 'inheritProfile': 'inherit_profile',
- 'profiles': 'ip_encapsulation',
- }
-
- api_attributes = [
- 'rateLimit',
- 'connectionLimit',
- 'description',
- 'ratio',
- 'priorityGroup',
- 'address',
- 'fqdn',
- 'session',
- 'state',
- 'monitor',
-
- # These two settings are for IP Encapsulation
- 'inheritProfile',
- 'profiles',
- ]
-
- returnables = [
- 'rate_limit',
- 'connection_limit',
- 'description',
- 'ratio',
- 'priority_group',
- 'fqdn_auto_populate',
- 'session',
- 'state',
- 'fqdn',
- 'address',
- 'monitors',
-
- # IP Encapsulation related
- 'inherit_profile',
- 'ip_encapsulation',
- ]
-
- updatables = [
- 'rate_limit',
- 'connection_limit',
- 'description',
- 'ratio',
- 'priority_group',
- 'fqdn_auto_populate',
- 'state',
- 'monitors',
- 'inherit_profile',
- 'ip_encapsulation',
- ]
-
-
-class ModuleParameters(Parameters):
- @property
- def full_name(self):
- delimiter = ':'
- try:
- if validate_ip_v6_address(self.full_name_dict['name']):
- delimiter = '.'
- except TypeError:
- pass
- return '{0}{1}{2}'.format(self.full_name_dict['name'], delimiter, self.port)
-
- @property
- def full_name_dict(self):
- if self._values['name'] is None:
- name = self._values['address'] if self._values['address'] else self._values['fqdn']
- else:
- name = self._values['name']
- return dict(
- name=name,
- port=self.port
- )
-
- @property
- def node_name(self):
- return self.full_name_dict['name']
-
- @property
- def fqdn_name(self):
- return self._values['fqdn']
-
- @property
- def fqdn(self):
- result = {}
- if self.fqdn_auto_populate:
- result['autopopulate'] = 'enabled'
- else:
- result['autopopulate'] = 'disabled'
- if self._values['fqdn'] is None:
- return result
- if not is_valid_hostname(self._values['fqdn']):
- raise F5ModuleError(
- "The specified 'fqdn' value of: {0} is not a valid hostname.".format(self._values['fqdn'])
- )
- result['tmName'] = self._values['fqdn']
- return result
-
- @property
- def pool(self):
- return fq_name(self.want.partition, self._values['pool'])
-
- @property
- def port(self):
- if self._values['port'] is None:
- raise F5ModuleError(
- "Port value must be specified."
- )
- if 0 > int(self._values['port']) or int(self._values['port']) > 65535:
- raise F5ModuleError(
- "Valid ports must be in range 0 - 65535"
- )
- return int(self._values['port'])
-
- @property
- def address(self):
- if self._values['address'] is None:
- return None
- elif self._values['address'] == 'any6':
- return 'any6'
- address = self._values['address'].split('%')[0]
- if is_valid_ip(address):
- return self._values['address']
- raise F5ModuleError(
- "The specified 'address' value of: {0} is not a valid IP address.".format(address)
- )
-
- @property
- def state(self):
- if self._values['state'] == 'enabled':
- return 'present'
- return self._values['state']
-
- @property
- def monitors_list(self):
- if self._values['monitors'] is None:
- return []
- try:
- result = re.findall(r'/\w+/[^\s}]+', self._values['monitors'])
- result.sort()
- return result
- except Exception:
- return self._values['monitors']
-
- @property
- def monitors(self):
- if self._values['monitors'] is None:
- return None
- if len(self._values['monitors']) == 1 and self._values['monitors'][0] == '':
- return 'default'
- if len(self._values['monitors']) == 1 and self._values['monitors'][0] == 'none':
- return '/Common/none'
- monitors = [fq_name(self.partition, x) for x in self.monitors_list]
- if self.availability_requirement_type == 'at_least':
- if self.at_least > len(self.monitors_list):
- raise F5ModuleError(
- "The 'at_least' value must not exceed the number of 'monitors'."
- )
- monitors = ' '.join(monitors)
- result = 'min {0} of {{ {1} }}'.format(self.at_least, monitors)
- else:
- result = ' and '.join(monitors).strip()
- return result
-
- @property
- def availability_requirement_type(self):
- if self._values['availability_requirements'] is None:
- return None
- return self._values['availability_requirements']['type']
-
- @property
- def at_least(self):
- return self._get_availability_value('at_least')
-
- @property
- def ip_encapsulation(self):
- if self._values['ip_encapsulation'] is None:
- return None
- if self._values['ip_encapsulation'] == 'inherit':
- return 'inherit'
- if self._values['ip_encapsulation'] in ['', 'none']:
- return ''
- return fq_name(self.partition, self._values['ip_encapsulation'])
-
- def _get_availability_value(self, type):
- if self._values['availability_requirements'] is None:
- return None
- if self._values['availability_requirements'][type] is None:
- return None
- return int(self._values['availability_requirements'][type])
-
-
-class ApiParameters(Parameters):
- @property
- def ip_encapsulation(self):
- """Returns a simple name for the tunnel.
-
- The API stores the data like so
-
- "profiles": [
- {
- "name": "gre",
- "partition": "Common",
- "nameReference": {
- "link": "https://localhost/mgmt/tm/net/tunnels/gre/~Common~gre?ver=13.1.0.7"
- }
- }
- ]
-
- This method returns that data as a simple profile name. For instance,
-
- /Common/gre
-
- This allows us to do comparisons of it in the Difference class and then,
- as needed, translate it back to the more complex form in the UsableChanges
- class.
-
- Returns:
- string: The simple form representation of the tunnel
- """
- if self._values['ip_encapsulation'] is None and self.inherit_profile == 'yes':
- return 'inherit'
- if self._values['ip_encapsulation'] is None and self.inherit_profile == 'no':
- return ''
- if self._values['ip_encapsulation'] is None:
- return None
-
- # There can be only one
- tunnel = self._values['ip_encapsulation'][0]
-
- return fq_name(tunnel['partition'], tunnel['name'])
-
- @property
- def inherit_profile(self):
- return flatten_boolean(self._values['inherit_profile'])
-
- @property
- def allow(self):
- if self._values['allow'] is None:
- return ''
- if self._values['allow'][0] == 'All':
- return 'all'
- allow = self._values['allow']
- result = list(set([str(x) for x in allow]))
- result = sorted(result)
- return result
-
- @property
- def rate_limit(self):
- if self._values['rate_limit'] is None:
- return None
- if self._values['rate_limit'] == 'disabled':
- return 0
- return int(self._values['rate_limit'])
-
- @property
- def state(self):
- if self._values['state'] in ['user-up', 'unchecked', 'fqdn-up-no-addr', 'fqdn-up'] and self._values['session'] in ['user-enabled']:
- return 'present'
- elif self._values['state'] in ['down', 'up', 'checking'] and self._values['session'] == 'monitor-enabled':
- # monitor-enabled + checking:
- # Monitor is checking to see state of pool member. For instance,
- # whether it is up or down
- #
- # monitor-enabled + down:
- # Monitor returned and determined that pool member is down.
- #
- # monitor-enabled + up
- # Monitor returned and determined that pool member is up.
- return 'present'
- elif self._values['state'] in ['user-down'] and self._values['session'] in ['user-disabled']:
- return 'forced_offline'
- else:
- return 'disabled'
-
- @property
- def availability_requirement_type(self):
- if self._values['monitors'] is None:
- return None
- if 'min ' in self._values['monitors']:
- return 'at_least'
- else:
- return 'all'
-
- @property
- def monitors_list(self):
- if self._values['monitors'] is None:
- return []
- try:
- result = re.findall(r'/\w+/[^\s}]+', self._values['monitors'])
- result.sort()
- return result
- except Exception:
- return self._values['monitors']
-
- @property
- def monitors(self):
- if self._values['monitors'] is None:
- return None
- if self._values['monitors'] == 'default':
- return 'default'
- monitors = [fq_name(self.partition, x) for x in self.monitors_list]
- if self.availability_requirement_type == 'at_least':
- monitors = ' '.join(monitors)
- result = 'min {0} of {{ {1} }}'.format(self.at_least, monitors)
- else:
- result = ' and '.join(monitors).strip()
-
- return result
-
- @property
- def at_least(self):
- """Returns the 'at least' value from the monitor string.
- The monitor string for a Require monitor looks like this.
- min 1 of { /Common/gateway_icmp }
- This method parses out the first of the numeric values. This values represents
- the "at_least" value that can be updated in the module.
- Returns:
- int: The at_least value if found. None otherwise.
- """
- if self._values['monitors'] is None:
- return None
- pattern = r'min\s+(?P<least>\d+)\s+of\s+'
- matches = re.search(pattern, self._values['monitors'])
- if matches is None:
- return None
- return matches.group('least')
-
- @property
- def fqdn_auto_populate(self):
- if self._values['fqdn'] is None:
- return None
- if 'autopopulate' in self._values['fqdn']:
- if self._values['fqdn']['autopopulate'] == 'enabled':
- return True
- return False
-
- @property
- def fqdn(self):
- if self._values['fqdn'] is None:
- return None
- if 'tmName' in self._values['fqdn']:
- return self._values['fqdn']['tmName']
-
-
-class NodeApiParameters(Parameters):
- pass
-
-
-class Changes(Parameters):
- def to_return(self):
- result = {}
- try:
- for returnable in self.returnables:
- result[returnable] = getattr(self, returnable)
- result = self._filter_params(result)
- except Exception:
- pass
- return result
-
-
-class UsableChanges(Changes):
- @property
- def monitors(self):
- monitor_string = self._values['monitors']
- if monitor_string is None:
- return None
- if '{' in monitor_string and '}':
- tmp = monitor_string.strip('}').split('{')
- monitor = ''.join(tmp).rstrip()
- return monitor
- return monitor_string
-
-
-class ReportableChanges(Changes):
- @property
- def ssl_cipher_suite(self):
- default = ':'.join(sorted(Parameters._ciphers.split(':')))
- if self._values['ssl_cipher_suite'] == default:
- return 'default'
- else:
- return self._values['ssl_cipher_suite']
-
- @property
- def fqdn_auto_populate(self):
- if self._values['fqdn'] is None:
- return None
- if 'autopopulate' in self._values['fqdn']:
- if self._values['fqdn']['autopopulate'] == 'enabled':
- return True
- return False
-
- @property
- def fqdn(self):
- if self._values['fqdn'] is None:
- return None
- if 'tmName' in self._values['fqdn']:
- return self._values['fqdn']['tmName']
-
- @property
- def state(self):
- if self._values['state'] in ['user-up', 'unchecked', 'fqdn-up-no-addr', 'fqdn-up'] and self._values['session'] in ['user-enabled']:
- return 'present'
- elif self._values['state'] in ['down', 'up', 'checking'] and self._values['session'] == 'monitor-enabled':
- return 'present'
- elif self._values['state'] in ['user-down'] and self._values['session'] in ['user-disabled']:
- return 'forced_offline'
- else:
- return 'disabled'
-
- @property
- def monitors(self):
- if self._values['monitors'] is None:
- return []
- try:
- result = re.findall(r'/\w+/[^\s}]+', self._values['monitors'])
- result.sort()
- return result
- except Exception:
- return self._values['monitors']
-
- @property
- def availability_requirement_type(self):
- if self._values['monitors'] is None:
- return None
- if 'min ' in self._values['monitors']:
- return 'at_least'
- else:
- return 'all'
-
- @property
- def at_least(self):
- """Returns the 'at least' value from the monitor string.
- The monitor string for a Require monitor looks like this.
- min 1 of { /Common/gateway_icmp }
- This method parses out the first of the numeric values. This values represents
- the "at_least" value that can be updated in the module.
- Returns:
- int: The at_least value if found. None otherwise.
- """
- if self._values['monitors'] is None:
- return None
- pattern = r'min\s+(?P<least>\d+)\s+of\s+'
- matches = re.search(pattern, self._values['monitors'])
- if matches is None:
- return None
- return int(matches.group('least'))
-
- @property
- def availability_requirements(self):
- if self._values['monitors'] is None:
- return None
- result = dict()
- result['type'] = self.availability_requirement_type
- result['at_least'] = self.at_least
- return result
-
-
-class Difference(object):
- def __init__(self, want, have=None):
- self.want = want
- self.have = have
-
- def compare(self, param):
- try:
- result = getattr(self, param)
- return result
- except AttributeError:
- return self.__default(param)
-
- def __default(self, param):
- attr1 = getattr(self.want, param)
- try:
- attr2 = getattr(self.have, param)
- if attr1 != attr2:
- return attr1
- except AttributeError:
- return attr1
-
- @property
- def state(self):
- if self.want.state == self.have.state:
- return None
- if self.want.state == 'forced_offline':
- return {
- 'state': 'user-down',
- 'session': 'user-disabled'
- }
- elif self.want.state == 'disabled':
- return {
- 'state': 'user-up',
- 'session': 'user-disabled'
- }
- elif self.want.state in ['present', 'enabled']:
- return {
- 'state': 'user-up',
- 'session': 'user-enabled'
- }
-
- @property
- def fqdn_auto_populate(self):
- if self.want.fqdn_auto_populate is not None:
- if self.want.fqdn_auto_populate != self.have.fqdn_auto_populate:
- raise F5ModuleError(
- "The fqdn_auto_populate cannot be changed once it has been set."
- )
-
- @property
- def monitors(self):
- if self.want.monitors is None:
- return None
- if self.want.monitors == 'default' and self.have.monitors == 'default':
- return None
- if self.want.monitors == 'default' and self.have.monitors is None:
- return None
- if self.want.monitors == 'default' and len(self.have.monitors) > 0:
- return 'default'
- # this is necessary as in v12 there is a bug where returned value has a space at the end
- if self.want.monitors == '/Common/none' and self.have.monitors in ['/Common/none', '/Common/none ']:
- return None
- if self.have.monitors is None:
- return self.want.monitors
- if self.have.monitors != self.want.monitors:
- return self.want.monitors
-
- @property
- def ip_encapsulation(self):
- result = cmp_str_with_none(self.want.ip_encapsulation, self.have.ip_encapsulation)
- if result is None:
- return None
- if result == 'inherit':
- return dict(
- inherit_profile='enabled',
- ip_encapsulation=[]
- )
- elif result in ['', 'none']:
- return dict(
- inherit_profile='disabled',
- ip_encapsulation=[]
- )
- else:
- return dict(
- inherit_profile='disabled',
- ip_encapsulation=[
- dict(
- name=os.path.basename(result).strip('/'),
- partition=os.path.dirname(result)
- )
- ]
- )
-
-
-class ModuleManager(object):
- def __init__(self, *args, **kwargs):
- self.module = kwargs.get('module', None)
- self.client = F5RestClient(**self.module.params)
- self.want = None
- self.have = None
- self.changes = None
- self.replace_all_with = False
- self.purge_links = list()
- self.on_device = None
-
- def _set_changed_options(self):
- changed = {}
- for key in Parameters.returnables:
- if getattr(self.want, key) is not None:
- changed[key] = getattr(self.want, key)
- if changed:
- self.changes = UsableChanges(params=changed)
-
- def _update_changed_options(self):
- diff = Difference(self.want, self.have)
- updatables = Parameters.updatables
- changed = dict()
- for k in updatables:
- change = diff.compare(k)
- if change is None:
- continue
- else:
- if isinstance(change, dict):
- changed.update(change)
- else:
- changed[k] = change
- if changed:
- self.changes = UsableChanges(params=changed)
- return True
- return False
-
- def _announce_deprecations(self, result):
- warnings = result.pop('__warnings', [])
- for warning in warnings:
- self.module.deprecate(
- msg=warning['msg'],
- version=warning['version']
- )
-
- def exec_module(self):
- wants = None
- if self.module.params['replace_all_with']:
- self.replace_all_with = True
-
- if self.module.params['aggregate']:
- wants = self.merge_defaults_for_aggregate(self.module.params)
-
- result = dict()
- changed = False
-
- if self.replace_all_with and self.purge_links:
- self.purge()
- changed = True
-
- if self.module.params['aggregate']:
- result['aggregate'] = list()
- for want in wants:
- output = self.execute(want)
- if output['changed']:
- changed = output['changed']
- result['aggregate'].append(output)
- else:
- output = self.execute(self.module.params)
- if output['changed']:
- changed = output['changed']
- result.update(output)
- if changed:
- result['changed'] = True
- return result
-
- def merge_defaults_for_aggregate(self, params):
- defaults = deepcopy(params)
- aggregate = defaults.pop('aggregate')
-
- for i, j in enumerate(aggregate):
- for k, v in iteritems(defaults):
- if k != 'replace_all_with':
- if j.get(k, None) is None and v is not None:
- aggregate[i][k] = v
-
- if self.replace_all_with:
- self.compare_aggregate_names(aggregate)
-
- return aggregate
-
- def _filter_ephemerals(self):
- on_device = self._read_purge_collection()
- if not on_device:
- self.on_device = []
- return
- self.on_device = [member for member in on_device if member['ephemeral'] != "true"]
-
- def compare_fqdns(self, items):
- if any('fqdn' in item for item in items):
- aggregates = [item['fqdn'] for item in items if 'fqdn' in item and item['fqdn']]
- collection = [member['fqdn']['tmName'] for member in self.on_device if 'tmName' in member['fqdn']]
-
- diff = set(collection) - set(aggregates)
-
- if diff:
- fqdns = [
- member['selfLink'] for member in self.on_device if 'tmName' in member['fqdn'] and member['fqdn']['tmName'] in diff]
- self.purge_links.extend(fqdns)
- return True
- return False
- return False
-
- def compare_addresses(self, items):
- if any('address' in item for item in items):
- aggregates = [item['address'] for item in items if 'address' in item and item['address']]
- collection = [member['address'] for member in self.on_device]
- diff = set(collection) - set(aggregates)
-
- if diff:
- addresses = [item['selfLink'] for item in self.on_device if item['address'] in diff]
- self.purge_links.extend(addresses)
- return True
- return False
- return False
-
- def compare_aggregate_names(self, items):
- self._filter_ephemerals()
- if not self.on_device:
- return False
- fqdns = self.compare_fqdns(items)
- addresses = self.compare_addresses(items)
-
- if self.purge_links:
- if fqdns:
- if not addresses:
- self.purge_links.extend([item['selfLink'] for item in self.on_device if 'tmName' not in item['fqdn']])
-
- def execute(self, params=None):
- self.want = ModuleParameters(params=params)
- self.have = ApiParameters()
- self.changes = UsableChanges()
-
- changed = False
- result = dict()
- state = params['state']
-
- if state in ['present', 'enabled', 'disabled', 'forced_offline']:
- changed = self.present()
- elif state == "absent":
- changed = self.absent()
-
- reportable = ReportableChanges(params=self.changes.to_return())
- changes = reportable.to_return()
- result.update(**changes)
- result.update(dict(changed=changed))
- self._announce_deprecations(result)
- return result
-
- def present(self):
- if self.exists():
- return self.update()
- else:
- return self.create()
-
- def absent(self):
- if self.exists():
- return self.remove()
- elif not self.want.preserve_node and self.node_exists():
- return self.remove_node_from_device()
- return False
-
- def update(self):
- self.have = self.read_current_from_device()
- if not self.should_update():
- return False
- if self.module.check_mode:
- return True
- self.update_on_device()
- return True
-
- def should_update(self):
- result = self._update_changed_options()
- if result:
- return True
- return False
-
- def remove(self):
- if self.module.check_mode:
- return True
- self.remove_from_device()
- if not self.want.preserve_node:
- self.remove_node_from_device()
- if self.exists():
- raise F5ModuleError("Failed to delete the resource.")
- return True
-
- def purge(self):
- if self.module.check_mode:
- return True
- if not self.pool_exist():
- raise F5ModuleError('The specified pool does not exist')
- self.purge_from_device()
- return True
-
- def create(self):
- if self.want.reuse_nodes:
- self._update_address_with_existing_nodes()
-
- if self.want.name and not any(x for x in [self.want.address, self.want.fqdn_name]):
- self._set_host_by_name()
-
- if self.want.ip_encapsulation == '':
- self.changes.update({'inherit_profile': 'enabled'})
- self.changes.update({'profiles': []})
- elif self.want.ip_encapsulation:
- # Read the current list of tunnels so that IP encapsulation
- # checking can take place.
- tunnels_gre = self.read_current_tunnels_from_device('gre')
- tunnels_ipip = self.read_current_tunnels_from_device('ipip')
- tunnels = tunnels_gre + tunnels_ipip
- if self.want.ip_encapsulation not in tunnels:
- raise F5ModuleError(
- "The specified 'ip_encapsulation' tunnel was not found on the system."
- )
- self.changes.update({'inherit_profile': 'disabled'})
-
- self._update_api_state_attributes()
- self._set_changed_options()
- if self.module.check_mode:
- return True
- self.create_on_device()
- return True
-
- def exists(self):
- if not self.pool_exist():
- raise F5ModuleError('The specified pool does not exist')
-
- uri = "https://{0}:{1}/mgmt/tm/ltm/pool/{2}/members/{3}".format(
- self.client.provider['server'],
- self.client.provider['server_port'],
- transform_name(name=fq_name(self.want.partition, self.want.pool)),
- transform_name(self.want.partition, self.want.full_name)
- )
- resp = self.client.api.get(uri)
- try:
- response = resp.json()
- except ValueError:
- return False
- if resp.status == 404 or 'code' in response and response['code'] == 404:
- return False
- return True
-
- def pool_exist(self):
- if self.replace_all_with:
- pool_name = transform_name(name=fq_name(self.module.params['partition'], self.module.params['pool']))
- else:
- pool_name = transform_name(name=fq_name(self.want.partition, self.want.pool))
-
- uri = "https://{0}:{1}/mgmt/tm/ltm/pool/{2}".format(
- self.client.provider['server'],
- self.client.provider['server_port'],
- pool_name
-
- )
- resp = self.client.api.get(uri)
- try:
- response = resp.json()
- except ValueError:
- return False
- if resp.status == 404 or 'code' in response and response['code'] == 404:
- return False
- return True
-
- def node_exists(self):
- uri = "https://{0}:{1}/mgmt/tm/ltm/node/{2}".format(
- self.client.provider['server'],
- self.client.provider['server_port'],
- transform_name(self.want.partition, self.want.node_name)
- )
- resp = self.client.api.get(uri)
- try:
- response = resp.json()
- except ValueError:
- return False
- if resp.status == 404 or 'code' in response and response['code'] == 404:
- return False
- return True
-
- def _set_host_by_name(self):
- if is_valid_ip(self.want.name):
- self.want.update({
- 'fqdn': None,
- 'address': self.want.name
- })
- else:
- if not is_valid_hostname(self.want.name):
- raise F5ModuleError(
- "'name' is neither a valid IP address or FQDN name."
- )
- self.want.update({
- 'fqdn': self.want.name,
- 'address': None
- })
-
- def _update_api_state_attributes(self):
- if self.want.state == 'forced_offline':
- self.want.update({
- 'state': 'user-down',
- 'session': 'user-disabled',
- })
- elif self.want.state == 'disabled':
- self.want.update({
- 'state': 'user-up',
- 'session': 'user-disabled',
- })
- elif self.want.state in ['present', 'enabled']:
- self.want.update({
- 'state': 'user-up',
- 'session': 'user-enabled',
- })
-
- def _update_address_with_existing_nodes(self):
- try:
- have = self.read_current_node_from_device(self.want.node_name)
-
- if self.want.fqdn_auto_populate and self.want.reuse_nodes:
- self.module.warn("'fqdn_auto_populate' is discarded in favor of the re-used node's auto-populate setting.")
- self.want.update({
- 'fqdn_auto_populate': True if have.fqdn['autopopulate'] == 'enabled' else False
- })
- if 'tmName' in have.fqdn:
- self.want.update({
- 'fqdn': have.fqdn['tmName'],
- 'address': 'any6'
- })
- else:
- self.want.update({
- 'address': have.address
- })
- except Exception:
- return None
-
- def _read_purge_collection(self):
- uri = "https://{0}:{1}/mgmt/tm/ltm/pool/{2}/members".format(
- self.client.provider['server'],
- self.client.provider['server_port'],
- transform_name(name=fq_name(self.module.params['partition'], self.module.params['pool']))
- )
-
- query = '?$select=name,selfLink,fqdn,address,ephemeral'
- resp = self.client.api.get(uri + query)
-
- try:
- response = resp.json()
- except ValueError as ex:
- raise F5ModuleError(str(ex))
-
- if 'code' in response and response['code'] == 400:
- if 'message' in response:
- raise F5ModuleError(response['message'])
- else:
- raise F5ModuleError(resp.content)
- if 'items' in response:
- return response['items']
- return []
-
- def create_on_device(self):
- params = self.changes.api_params()
- params['name'] = self.want.full_name
- params['partition'] = self.want.partition
- uri = "https://{0}:{1}/mgmt/tm/ltm/pool/{2}/members".format(
- self.client.provider['server'],
- self.client.provider['server_port'],
- transform_name(name=fq_name(self.want.partition, self.want.pool)),
-
- )
- resp = self.client.api.post(uri, json=params)
- try:
- response = resp.json()
- except ValueError as ex:
- raise F5ModuleError(str(ex))
-
- if 'code' in response and response['code'] in [400, 403]:
- if 'message' in response:
- raise F5ModuleError(response['message'])
- else:
- raise F5ModuleError(resp.content)
-
- def update_on_device(self):
- params = self.changes.api_params()
- uri = "https://{0}:{1}/mgmt/tm/ltm/pool/{2}/members/{3}".format(
- self.client.provider['server'],
- self.client.provider['server_port'],
- transform_name(name=fq_name(self.want.partition, self.want.pool)),
- transform_name(self.want.partition, self.want.full_name)
-
- )
- resp = self.client.api.patch(uri, json=params)
- try:
- response = resp.json()
- except ValueError as ex:
- raise F5ModuleError(str(ex))
-
- if 'code' in response and response['code'] == 400:
- if 'message' in response:
- raise F5ModuleError(response['message'])
- else:
- raise F5ModuleError(resp.content)
-
- def remove_from_device(self):
- uri = "https://{0}:{1}/mgmt/tm/ltm/pool/{2}/members/{3}".format(
- self.client.provider['server'],
- self.client.provider['server_port'],
- transform_name(name=fq_name(self.want.partition, self.want.pool)),
- transform_name(self.want.partition, self.want.full_name)
-
- )
- response = self.client.api.delete(uri)
- if response.status == 200:
- return True
- raise F5ModuleError(response.content)
-
- def remove_node_from_device(self):
- uri = "https://{0}:{1}/mgmt/tm/ltm/node/{2}".format(
- self.client.provider['server'],
- self.client.provider['server_port'],
- transform_name(self.want.partition, self.want.node_name)
- )
- response = self.client.api.delete(uri)
- if response.status == 200:
- return True
- raise F5ModuleError(response.content)
-
- def read_current_from_device(self):
- uri = "https://{0}:{1}/mgmt/tm/ltm/pool/{2}/members/{3}".format(
- self.client.provider['server'],
- self.client.provider['server_port'],
- transform_name(name=fq_name(self.want.partition, self.want.pool)),
- transform_name(self.want.partition, self.want.full_name)
-
- )
- resp = self.client.api.get(uri)
- try:
- response = resp.json()
- except ValueError as ex:
- raise F5ModuleError(str(ex))
-
- if 'code' in response and response['code'] == 400:
- if 'message' in response:
- raise F5ModuleError(response['message'])
- else:
- raise F5ModuleError(resp.content)
-
- # Read the current list of tunnels so that IP encapsulation
- # checking can take place.
- tunnels_gre = self.read_current_tunnels_from_device('gre')
- tunnels_ipip = self.read_current_tunnels_from_device('ipip')
- response['tunnels'] = tunnels_gre + tunnels_ipip
-
- return ApiParameters(params=response)
-
- def read_current_node_from_device(self, node):
- uri = "https://{0}:{1}/mgmt/tm/ltm/node/{2}".format(
- self.client.provider['server'],
- self.client.provider['server_port'],
- transform_name(self.want.partition, node)
- )
- resp = self.client.api.get(uri)
- try:
- response = resp.json()
- except ValueError as ex:
- raise F5ModuleError(str(ex))
-
- if 'code' in response and response['code'] == 400:
- if 'message' in response:
- raise F5ModuleError(response['message'])
- else:
- raise F5ModuleError(resp.content)
- return NodeApiParameters(params=response)
-
- def read_current_tunnels_from_device(self, tunnel_type):
- uri = "https://{0}:{1}/mgmt/tm/net/tunnels/{2}".format(
- self.client.provider['server'],
- self.client.provider['server_port'],
- tunnel_type
- )
- resp = self.client.api.get(uri)
- try:
- response = resp.json()
- except ValueError as ex:
- raise F5ModuleError(str(ex))
-
- if 'code' in response and response['code'] == 400:
- if 'message' in response:
- raise F5ModuleError(response['message'])
- else:
- raise F5ModuleError(resp.content)
- if 'items' not in response:
- return []
- return [x['fullPath'] for x in response['items']]
-
- def _prepare_links(self, collection):
- # this is to ensure no duplicates are in the provided collection
- no_dupes = list(set(collection))
- links = list()
- purge_paths = [urlparse(link).path for link in no_dupes]
-
- for path in purge_paths:
- link = "https://{0}:{1}{2}".format(
- self.client.provider['server'],
- self.client.provider['server_port'],
- path
- )
- links.append(link)
- return links
-
- def purge_from_device(self):
- links = self._prepare_links(self.purge_links)
-
- with TransactionContextManager(self.client) as transact:
- for link in links:
- resp = transact.api.delete(link)
-
- try:
- response = resp.json()
- except ValueError as ex:
- raise F5ModuleError(str(ex))
-
- if 'code' in response and response['code'] == 400:
- if 'message' in response:
- raise F5ModuleError(response['message'])
- else:
- raise F5ModuleError(resp.content)
- return True
-
-
-class ArgumentSpec(object):
- def __init__(self):
- self.supports_check_mode = True
- element_spec = dict(
- address=dict(aliases=['host', 'ip']),
- fqdn=dict(
- aliases=['hostname']
- ),
- name=dict(),
- port=dict(type='int'),
- connection_limit=dict(type='int'),
- description=dict(),
- rate_limit=dict(type='int'),
- ratio=dict(type='int'),
- preserve_node=dict(type='bool'),
- priority_group=dict(type='int'),
- state=dict(
- default='present',
- choices=['absent', 'present', 'enabled', 'disabled', 'forced_offline']
- ),
- fqdn_auto_populate=dict(type='bool'),
- reuse_nodes=dict(type='bool', default=True),
- availability_requirements=dict(
- type='dict',
- options=dict(
- type=dict(
- choices=['all', 'at_least'],
- required=True
- ),
- at_least=dict(type='int'),
- ),
- required_if=[
- ['type', 'at_least', ['at_least']],
- ]
- ),
- monitors=dict(type='list'),
- ip_encapsulation=dict(),
- partition=dict(
- default='Common',
- fallback=(env_fallback, ['F5_PARTITION'])
- ),
- )
- aggregate_spec = deepcopy(element_spec)
-
- # remove default in aggregate spec, to handle common arguments
- remove_default_spec(aggregate_spec)
-
- self.argument_spec = dict(
- aggregate=dict(
- type='list',
- elements='dict',
- options=aggregate_spec,
- aliases=['members'],
- mutually_exclusive=[
- ['address', 'fqdn']
- ],
- required_one_of=[
- ['address', 'fqdn']
- ],
- ),
- replace_all_with=dict(
- type='bool',
- aliases=['purge'],
- default='no'
- ),
- pool=dict(required=True),
- partition=dict(
- default='Common',
- fallback=(env_fallback, ['F5_PARTITION'])
- ),
- )
-
- self.argument_spec.update(element_spec)
- self.argument_spec.update(f5_argument_spec)
-
- self.mutually_exclusive = [
- ['address', 'aggregate'],
- ['fqdn', 'aggregate']
- ]
- self.required_one_of = [
- ['address', 'fqdn', 'aggregate'],
- ]
-
-
-def main():
- spec = ArgumentSpec()
-
- module = AnsibleModule(
- argument_spec=spec.argument_spec,
- supports_check_mode=spec.supports_check_mode,
- mutually_exclusive=spec.mutually_exclusive,
- required_one_of=spec.required_one_of,
- )
-
- try:
- mm = ModuleManager(module=module)
- results = mm.exec_module()
- module.exit_json(**results)
- except F5ModuleError as ex:
- module.fail_json(msg=str(ex))
-
-
-if __name__ == '__main__':
- main()