summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Rupp <caphrim007@gmail.com>2018-04-25 07:16:11 -0700
committerGitHub <noreply@github.com>2018-04-25 07:16:11 -0700
commitfb264281ded12a1819338b53dbb9bc33e2a9b591 (patch)
treeb6078706cc06f408b01fba111be1c72ccdd58ee0
parent1c49cc4377459d8848cc11c8fea117cdcffcb4d9 (diff)
downloadansible-fb264281ded12a1819338b53dbb9bc33e2a9b591.tar.gz
Adds various features and fixes (#39271)
* a refactor of pool member and node modules to be inline with current f5 conventions * Added priority_group_activation to pools * various other small convention fixes and bug fixes
-rw-r--r--lib/ansible/modules/network/f5/bigip_monitor_udp.py14
-rw-r--r--lib/ansible/modules/network/f5/bigip_node.py31
-rw-r--r--lib/ansible/modules/network/f5/bigip_partition.py9
-rw-r--r--lib/ansible/modules/network/f5/bigip_policy.py9
-rw-r--r--lib/ansible/modules/network/f5/bigip_policy_rule.py38
-rw-r--r--lib/ansible/modules/network/f5/bigip_pool.py102
-rw-r--r--lib/ansible/modules/network/f5/bigip_pool_member.py1085
-rw-r--r--lib/ansible/modules/network/f5/bigip_profile_client_ssl.py30
-rw-r--r--test/sanity/validate-modules/ignore.txt1
-rw-r--r--test/units/modules/network/f5/fixtures/load_net_node_with_fqdn.json25
-rw-r--r--test/units/modules/network/f5/fixtures/load_net_node_with_ipv4_address.json24
-rw-r--r--test/units/modules/network/f5/test_bigip_monitor_udp.py6
-rw-r--r--test/units/modules/network/f5/test_bigip_node.py6
-rw-r--r--test/units/modules/network/f5/test_bigip_partition.py6
-rw-r--r--test/units/modules/network/f5/test_bigip_policy.py10
-rw-r--r--test/units/modules/network/f5/test_bigip_policy_rule.py10
-rw-r--r--test/units/modules/network/f5/test_bigip_pool.py8
-rw-r--r--test/units/modules/network/f5/test_bigip_pool_member.py338
-rw-r--r--test/units/modules/network/f5/test_bigip_profile_client_ssl.py8
19 files changed, 1230 insertions, 530 deletions
diff --git a/lib/ansible/modules/network/f5/bigip_monitor_udp.py b/lib/ansible/modules/network/f5/bigip_monitor_udp.py
index 1ab9293328..ff5a87ff18 100644
--- a/lib/ansible/modules/network/f5/bigip_monitor_udp.py
+++ b/lib/ansible/modules/network/f5/bigip_monitor_udp.py
@@ -17,7 +17,7 @@ DOCUMENTATION = r'''
module: bigip_monitor_udp
short_description: Manages F5 BIG-IP LTM udp monitors
description: Manages F5 BIG-IP LTM udp monitors.
-version_added: "2.5"
+version_added: 2.5
options:
name:
description:
@@ -150,30 +150,23 @@ import os
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
-HAS_DEVEL_IMPORTS = False
-
try:
- # Sideband repository used for dev
from library.module_utils.network.f5.bigip import HAS_F5SDK
from library.module_utils.network.f5.bigip import F5Client
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 cleanup_tokens
- from library.module_utils.network.f5.common import fqdn_name
from library.module_utils.network.f5.common import f5_argument_spec
try:
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
except ImportError:
HAS_F5SDK = False
- HAS_DEVEL_IMPORTS = True
except ImportError:
- # Upstream Ansible
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
from ansible.module_utils.network.f5.bigip import F5Client
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 cleanup_tokens
- from ansible.module_utils.network.f5.common import fqdn_name
from ansible.module_utils.network.f5.common import f5_argument_spec
try:
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
@@ -208,11 +201,6 @@ class Parameters(AnsibleF5Parameters):
'destination', 'send', 'receive', 'interval', 'timeout', 'time_until_up'
]
- def _fqdn_name(self, value):
- if value is not None and not value.startswith('/'):
- return '/{0}/{1}'.format(self.partition, value)
- return value
-
def to_return(self):
result = {}
try:
diff --git a/lib/ansible/modules/network/f5/bigip_node.py b/lib/ansible/modules/network/f5/bigip_node.py
index 020c7e073a..6a33312398 100644
--- a/lib/ansible/modules/network/f5/bigip_node.py
+++ b/lib/ansible/modules/network/f5/bigip_node.py
@@ -18,7 +18,7 @@ module: bigip_node
short_description: Manages F5 BIG-IP LTM nodes
description:
- Manages F5 BIG-IP LTM nodes.
-version_added: "1.4"
+version_added: 1.4
options:
state:
description:
@@ -59,12 +59,12 @@ options:
quorum:
description:
- Monitor quorum value when C(monitor_type) is C(m_of_n).
- version_added: "2.2"
+ version_added: 2.2
monitors:
description:
- Specifies the health monitors that the system currently uses to
monitor this node.
- version_added: "2.2"
+ version_added: 2.2
address:
description:
- IP address of the node. This can be either IPv4 or IPv6. When creating a
@@ -73,7 +73,7 @@ options:
aliases:
- ip
- host
- version_added: "2.2"
+ version_added: 2.2
fqdn:
description:
- FQDN name of the node. This can be any name that is a valid RFC 1123 DNS
@@ -86,7 +86,7 @@ options:
provided. This parameter cannot be updated after it is set.
aliases:
- hostname
- version_added: "2.5"
+ version_added: 2.5
description:
description:
- Specifies descriptive text that identifies the node.
@@ -97,7 +97,9 @@ options:
version_added: 2.5
notes:
- Requires the netaddr Python package on the host. This is as easy as
- pip install netaddr
+ C(pip install netaddr).
+requirements:
+ - netaddr
extends_documentation_fragment: f5
author:
- Tim Rupp (@caphrim007)
@@ -216,30 +218,25 @@ import time
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
-HAS_DEVEL_IMPORTS = False
-
try:
- # Sideband repository used for dev
from library.module_utils.network.f5.bigip import HAS_F5SDK
from library.module_utils.network.f5.bigip import F5Client
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 cleanup_tokens
- from library.module_utils.network.f5.common import fqdn_name
+ from library.module_utils.network.f5.common import fq_name
from library.module_utils.network.f5.common import f5_argument_spec
try:
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
except ImportError:
HAS_F5SDK = False
- HAS_DEVEL_IMPORTS = True
except ImportError:
- # Upstream Ansible
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
from ansible.module_utils.network.f5.bigip import F5Client
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 cleanup_tokens
- from ansible.module_utils.network.f5.common import fqdn_name
+ from ansible.module_utils.network.f5.common import fq_name
from ansible.module_utils.network.f5.common import f5_argument_spec
try:
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
@@ -291,11 +288,6 @@ class Parameters(AnsibleF5Parameters):
except Exception:
return result
- def _fqdn_name(self, value):
- if value is not None and not value.startswith('/'):
- return '/{0}/{1}'.format(self.partition, value)
- return value
-
@property
def monitors_list(self):
if self._values['monitors'] is None:
@@ -310,13 +302,12 @@ class Parameters(AnsibleF5Parameters):
def monitors(self):
if self._values['monitors'] is None:
return None
- monitors = [self._fqdn_name(x) for x in self.monitors_list]
+ monitors = [fq_name(self.partition, x) for x in self.monitors_list]
if self.monitor_type == 'm_of_n':
monitors = ' '.join(monitors)
result = 'min %s of { %s }' % (self.quorum, monitors)
else:
result = ' and '.join(monitors).strip()
-
return result
@property
diff --git a/lib/ansible/modules/network/f5/bigip_partition.py b/lib/ansible/modules/network/f5/bigip_partition.py
index 452fe245c2..fb2ade5ad9 100644
--- a/lib/ansible/modules/network/f5/bigip_partition.py
+++ b/lib/ansible/modules/network/f5/bigip_partition.py
@@ -18,7 +18,7 @@ module: bigip_partition
short_description: Manage BIG-IP partitions
description:
- Manage BIG-IP partitions.
-version_added: "2.5"
+version_added: 2.5
options:
name:
description:
@@ -107,30 +107,23 @@ description:
from ansible.module_utils.basic import AnsibleModule
-HAS_DEVEL_IMPORTS = False
-
try:
- # Sideband repository used for dev
from library.module_utils.network.f5.bigip import HAS_F5SDK
from library.module_utils.network.f5.bigip import F5Client
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 cleanup_tokens
- from library.module_utils.network.f5.common import fqdn_name
from library.module_utils.network.f5.common import f5_argument_spec
try:
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
except ImportError:
HAS_F5SDK = False
- HAS_DEVEL_IMPORTS = True
except ImportError:
- # Upstream Ansible
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
from ansible.module_utils.network.f5.bigip import F5Client
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 cleanup_tokens
- from ansible.module_utils.network.f5.common import fqdn_name
from ansible.module_utils.network.f5.common import f5_argument_spec
try:
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
diff --git a/lib/ansible/modules/network/f5/bigip_policy.py b/lib/ansible/modules/network/f5/bigip_policy.py
index 20c864b981..bf1af427c9 100644
--- a/lib/ansible/modules/network/f5/bigip_policy.py
+++ b/lib/ansible/modules/network/f5/bigip_policy.py
@@ -23,7 +23,7 @@ description:
the description, and things unrelated to the policy rules themselves.
It is also the first module that should be used when creating rules as
the C(bigip_policy_rule) module requires a policy parameter.
-version_added: "2.5"
+version_added: 2.5
options:
description:
description:
@@ -168,30 +168,23 @@ from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from distutils.version import LooseVersion
-HAS_DEVEL_IMPORTS = False
-
try:
- # Sideband repository used for dev
from library.module_utils.network.f5.bigip import HAS_F5SDK
from library.module_utils.network.f5.bigip import F5Client
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 cleanup_tokens
- from library.module_utils.network.f5.common import fqdn_name
from library.module_utils.network.f5.common import f5_argument_spec
try:
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
except ImportError:
HAS_F5SDK = False
- HAS_DEVEL_IMPORTS = True
except ImportError:
- # Upstream Ansible
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
from ansible.module_utils.network.f5.bigip import F5Client
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 cleanup_tokens
- from ansible.module_utils.network.f5.common import fqdn_name
from ansible.module_utils.network.f5.common import f5_argument_spec
try:
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
diff --git a/lib/ansible/modules/network/f5/bigip_policy_rule.py b/lib/ansible/modules/network/f5/bigip_policy_rule.py
index 0e7c93c609..af09d71832 100644
--- a/lib/ansible/modules/network/f5/bigip_policy_rule.py
+++ b/lib/ansible/modules/network/f5/bigip_policy_rule.py
@@ -41,7 +41,7 @@ options:
- When C(type) is C(ignore), will remove all existing actions from this
rule.
required: true
- choices: [ 'forward', 'enable', 'ignore' ]
+ choices: ['forward', 'enable', 'ignore']
pool:
description:
- Pool that you want to forward traffic to.
@@ -77,7 +77,7 @@ options:
list will provide a match.
- When C(type) is C(all_traffic), will remove all existing conditions from
this rule.
- required: true
+ required: True
choices: [ 'http_uri', 'all_traffic' ]
path_begins_with_any:
description:
@@ -120,6 +120,7 @@ EXAMPLES = r'''
actions:
- type: forward
pool: pool-svrs
+ delegate_to: localhost
- name: Add multiple rules to the new policy
bigip_policy_rule:
@@ -127,6 +128,7 @@ EXAMPLES = r'''
name: "{{ item.name }}"
conditions: "{{ item.conditions }}"
actions: "{{ item.actions }}"
+ delegate_to: localhost
loop:
- name: rule1
actions:
@@ -151,6 +153,7 @@ EXAMPLES = r'''
- type: all_traffic
actions:
- type: ignore
+ delegate_to: localhost
'''
RETURN = r'''
@@ -197,30 +200,25 @@ from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.six import iteritems
-HAS_DEVEL_IMPORTS = False
-
try:
- # Sideband repository used for dev
from library.module_utils.network.f5.bigip import HAS_F5SDK
from library.module_utils.network.f5.bigip import F5Client
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 cleanup_tokens
- from library.module_utils.network.f5.common import fqdn_name
+ from library.module_utils.network.f5.common import fq_name
from library.module_utils.network.f5.common import f5_argument_spec
try:
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
except ImportError:
HAS_F5SDK = False
- HAS_DEVEL_IMPORTS = True
except ImportError:
- # Upstream Ansible
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
from ansible.module_utils.network.f5.bigip import F5Client
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 cleanup_tokens
- from ansible.module_utils.network.f5.common import fqdn_name
+ from ansible.module_utils.network.f5.common import fq_name
from ansible.module_utils.network.f5.common import f5_argument_spec
try:
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
@@ -241,10 +239,9 @@ class Parameters(AnsibleF5Parameters):
'actions', 'conditions', 'description'
]
- def _fqdn_name(self, value):
- if value is not None and not value.startswith('/'):
- return '/{0}/{1}'.format(self.partition, value)
- return value
+ returnable = [
+ 'description'
+ ]
@property
def name(self):
@@ -255,13 +252,6 @@ class Parameters(AnsibleF5Parameters):
return self._values.get('description', None)
@property
- def strategy(self):
- if self._values['strategy'] is None:
- return None
- result = self._fqdn_name(self._values['strategy'])
- return result
-
- @property
def policy(self):
if self._values['policy'] is None:
return None
@@ -407,7 +397,7 @@ class ModuleParameters(Parameters):
raise F5ModuleError(
"A 'pool' must be specified when the 'forward' type is used."
)
- action['pool'] = self._fqdn_name(item['pool'])
+ action['pool'] = fq_name(self.partition, item['pool'])
def _handle_enable_action(self, action, item):
"""Handle the nuances of the enable type
@@ -422,7 +412,7 @@ class ModuleParameters(Parameters):
"An 'asm_policy' must be specified when the 'enable' type is used."
)
action.update(dict(
- policy=self._fqdn_name(item['asm_policy']),
+ policy=fq_name(self.partition, item['asm_policy']),
asm=True
))
@@ -836,9 +826,9 @@ class ArgumentSpec(object):
'all_traffic'
],
required=True
- )
+ ),
+ path_begins_with_any=dict()
),
- path_begins_with_any=dict()
),
name=dict(required=True),
policy=dict(required=True),
diff --git a/lib/ansible/modules/network/f5/bigip_pool.py b/lib/ansible/modules/network/f5/bigip_pool.py
index 1db6899046..22e3737d0d 100644
--- a/lib/ansible/modules/network/f5/bigip_pool.py
+++ b/lib/ansible/modules/network/f5/bigip_pool.py
@@ -23,7 +23,7 @@ options:
description:
description:
- Specifies descriptive text that identifies the pool.
- version_added: "2.3"
+ version_added: 2.3
name:
description:
- Pool name
@@ -34,7 +34,7 @@ options:
description:
- Load balancing method. When creating a new pool, if this value is not
specified, the default of C(round-robin) will be used.
- version_added: "1.3"
+ version_added: 1.3
choices:
- dynamic-ratio-member
- dynamic-ratio-node
@@ -54,7 +54,7 @@ options:
- ratio-session
- round-robin
- weighted-least-connections-member
- - weighted-least-connections-nod
+ - weighted-least-connections-node
monitor_type:
description:
- Monitor rule type when C(monitors) is specified.
@@ -69,32 +69,32 @@ options:
or already existing on the device.
- Both C(single) and C(and_list) are functionally identical since BIG-IP
considers all monitors as "a list".
- version_added: "1.3"
+ version_added: 1.3
choices: ['and_list', 'm_of_n', 'single']
quorum:
description:
- Monitor quorum value when C(monitor_type) is C(m_of_n).
- Quorum must be a value of 1 or greater when C(monitor_type) is C(m_of_n).
- version_added: "1.3"
+ version_added: 1.3
monitors:
description:
- Monitor template name list. If the partition is not provided as part of
the monitor name, then the C(partition) option will be used instead.
- version_added: "1.3"
+ version_added: 1.3
slow_ramp_time:
description:
- Sets the ramp-up time (in seconds) to gradually ramp up the load on
newly added or freshly detected up pool members.
- version_added: "1.3"
+ version_added: 1.3
reselect_tries:
description:
- Sets the number of times the system tries to contact a pool member
after a passive failure.
- version_added: "2.2"
+ version_added: 2.2
service_down_action:
description:
- Sets the action to take when node goes down in pool.
- version_added: "1.3"
+ version_added: 1.3
choices:
- none
- reset
@@ -124,6 +124,25 @@ options:
that are numbers.
- Data will be persisted, not ephemeral.
version_added: 2.5
+ priority_group_activation:
+ description:
+ - Specifies whether the system load balances traffic according to the priority
+ number assigned to the pool member.
+ - When creating a new pool, if this parameter is not specified, the default of
+ C(0) will be used.
+ - To disable this setting, provide the value C(0).
+ - Once you enable this setting, you can specify pool member priority when you
+ create a new pool or on a pool member's properties screen.
+ - The system treats same-priority pool members as a group.
+ - To enable priority group activation, provide a number from C(0) to C(65535)
+ that represents the minimum number of members that must be available in one
+ priority group before the system directs traffic to members in a lower
+ priority group.
+ - When a sufficient number of members become available in the higher priority
+ group, the system again directs traffic to the higher priority group.
+ aliases:
+ - minimum_active_members
+ version_added: 2.6
notes:
- Requires BIG-IP software version >= 12.
- To add members do a pool, use the C(bigip_pool_member) module. Previously, the
@@ -144,7 +163,7 @@ EXAMPLES = r'''
state: present
name: my-pool
partition: Common
- lb_method: least-connection-member
+ lb_method: least-connections-member
slow_ramp_time: 120
delegate_to: localhost
@@ -307,6 +326,11 @@ metadata:
returned: changed
type: dict
sample: {'key1': 'foo', 'key2': 'bar'}
+priority_group_activation:
+ description: The new minimum number of members to activate the priorty group.
+ returned: changed
+ type: int
+ sample: 10
'''
import re
@@ -315,30 +339,25 @@ from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.six import iteritems
-HAS_DEVEL_IMPORTS = False
-
try:
- # Sideband repository used for dev
from library.module_utils.network.f5.bigip import HAS_F5SDK
from library.module_utils.network.f5.bigip import F5Client
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 cleanup_tokens
- from library.module_utils.network.f5.common import fqdn_name
+ from library.module_utils.network.f5.common import fq_name
from library.module_utils.network.f5.common import f5_argument_spec
try:
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
except ImportError:
HAS_F5SDK = False
- HAS_DEVEL_IMPORTS = True
except ImportError:
- # Upstream Ansible
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
from ansible.module_utils.network.f5.bigip import F5Client
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 cleanup_tokens
- from ansible.module_utils.network.f5.common import fqdn_name
+ from ansible.module_utils.network.f5.common import fq_name
from ansible.module_utils.network.f5.common import f5_argument_spec
try:
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
@@ -358,24 +377,26 @@ class Parameters(AnsibleF5Parameters):
'slowRampTime': 'slow_ramp_time',
'reselectTries': 'reselect_tries',
'serviceDownAction': 'service_down_action',
- 'monitor': 'monitors'
+ 'monitor': 'monitors',
+ 'minActiveMembers': 'priority_group_activation'
}
api_attributes = [
'description', 'name', 'loadBalancingMode', 'monitor', 'slowRampTime',
- 'reselectTries', 'serviceDownAction', 'metadata'
+ 'reselectTries', 'serviceDownAction', 'metadata', 'minActiveMembers'
]
returnables = [
'monitor_type', 'quorum', 'monitors', 'service_down_action',
'description', 'lb_method', 'slow_ramp_time',
- 'reselect_tries', 'monitor', 'name', 'partition', 'metadata'
+ 'reselect_tries', 'monitor', 'name', 'partition', 'metadata',
+ 'priority_group_activation'
]
updatables = [
'monitor_type', 'quorum', 'monitors', 'service_down_action',
'description', 'lb_method', 'slow_ramp_time', 'reselect_tries',
- 'metadata'
+ 'metadata', 'priority_group_activation'
]
@property
@@ -389,16 +410,21 @@ class Parameters(AnsibleF5Parameters):
raise F5ModuleError('Provided lb_method is unknown')
return lb_method
- def _fqdn_name(self, value):
- if value is not None and not value.startswith('/'):
- return '/{0}/{1}'.format(self.partition, value)
- return value
+ def _verify_quorum_type(self, quorum):
+ try:
+ if quorum is None:
+ return None
+ return int(quorum)
+ except ValueError:
+ raise F5ModuleError(
+ "The specified 'quorum' must be an integer."
+ )
@property
def monitors(self):
if self._values['monitors'] is None:
return None
- monitors = [self._fqdn_name(x) for x in self.monitors_list]
+ monitors = [fq_name(self.partition, x) for x in self.monitors_list]
if self.monitor_type == 'm_of_n':
monitors = ' '.join(monitors)
result = 'min %s of { %s }' % (self.quorum, monitors)
@@ -406,15 +432,11 @@ class Parameters(AnsibleF5Parameters):
result = ' and '.join(monitors).strip()
return result
- def _verify_quorum_type(self, quorum):
- try:
- if quorum is None:
- return None
- return int(quorum)
- except ValueError:
- raise F5ModuleError(
- "The specified 'quorum' must be an integer."
- )
+ @property
+ def priority_group_activation(self):
+ if self._values['priority_group_activation'] is None:
+ return None
+ return int(self._values['priority_group_activation'])
class ApiParameters(Parameters):
@@ -447,7 +469,7 @@ class ApiParameters(Parameters):
if self._values['monitors'] is None:
return []
try:
- result = re.findall(r'/\w+/[^\s}]+', self._values['monitors'])
+ result = re.findall(r'/[\w-]+/[^\s}]+', self._values['monitors'])
return result
except Exception:
return self._values['monitors']
@@ -534,7 +556,7 @@ class UsableChanges(Changes):
class ReportableChanges(Changes):
@property
def monitors(self):
- result = sorted(re.findall(r'/\w+/[^\s}]+', self._values['monitors']))
+ result = sorted(re.findall(r'/[\w-]+/[^\s}]+', self._values['monitors']))
return result
@property
@@ -794,6 +816,8 @@ class ModuleManager(object):
raise F5ModuleError(
"When using a 'monitor_type' of 'single', only one monitor may be provided"
)
+ if self.want.priority_group_activation is None:
+ self.want.update({'priority_group_activation': 0})
self._set_changed_options()
if self.module.check_mode:
@@ -903,6 +927,10 @@ class ArgumentSpec(object):
partition=dict(
default='Common',
fallback=(env_fallback, ['F5_PARTITION'])
+ ),
+ priority_group_activation=dict(
+ type='int',
+ aliases=['minimum_active_members']
)
)
self.argument_spec = {}
diff --git a/lib/ansible/modules/network/f5/bigip_pool_member.py b/lib/ansible/modules/network/f5/bigip_pool_member.py
index 5e6407c972..58456bae11 100644
--- a/lib/ansible/modules/network/f5/bigip_pool_member.py
+++ b/lib/ansible/modules/network/f5/bigip_pool_member.py
@@ -20,17 +20,13 @@ 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
-author:
- - Matt Hite (@mhite)
- - Tim Rupp (@caphrim007)
-notes:
- - Requires BIG-IP software version >= 11
- - F5 developed module 'bigsuds' required (see http://devcentral.f5.com)
- - Best run as a local_action in your playbook
- - Supersedes bigip_pool for managing pool members
-requirements:
- - bigsuds
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).
+ version_added: 2.6
state:
description:
- Pool member state.
@@ -39,20 +35,9 @@ options:
choices:
- present
- absent
- session_state:
- description:
- - Set new session availability status for pool member.
- version_added: 2.0
- choices:
- - enabled
- - disabled
- monitor_state:
- description:
- - Set monitor availability status for pool member.
- version_added: 2.0
- choices:
- enabled
- disabled
+ - forced_offline
pool:
description:
- Pool name. This pool must exist.
@@ -61,16 +46,32 @@ options:
description:
- Partition
default: Common
- host:
+ address:
description:
- - Pool member IP.
- required: True
+ - 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.
+ 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.
aliases:
- - address
- - name
+ - hostname
+ version_added: 2.6
port:
description:
- Pool member port.
+ - This value cannot be changed after it has been set.
required: True
connection_limit:
description:
@@ -89,10 +90,11 @@ options:
to 1.
preserve_node:
description:
- - When state is absent and the pool member is no longer referenced
- in other pools, the default behavior removes the unused node
- object. Setting this to 'yes' disables this behavior.
- default: no
+ - 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:
@@ -106,7 +108,48 @@ options:
- 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.
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(enabled), 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(disabled), 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).
+ - 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.
+ default: yes
+ type: bool
+ version_added: 2.6
+ session_state:
+ description:
+ - Set new session availability status for pool member.
+ - This parameter is deprecated and will be removed in Ansible 2.7. Use C(state)
+ C(enabled) or C(disabled).
+ version_added: 2.0
+ choices:
+ - enabled
+ - disabled
+ monitor_state:
+ description:
+ - Set monitor availability status for pool member.
+ - This parameter is deprecated and will be removed in Ansible 2.7. Use C(state)
+ C(enabled) or C(disabled).
+ version_added: 2.0
+ choices:
+ - enabled
+ - disabled
extends_documentation_fragment: f5
+author:
+ - Tim Rupp (@caphrim007)
'''
EXAMPLES = '''
@@ -152,398 +195,694 @@ EXAMPLES = '''
port: 80
delegate_to: localhost
-
-# The BIG-IP GUI doesn't map directly to the API calls for "Pool ->
-# Members -> State". The following states map to API monitor
-# and session states.
-#
-# Enabled (all traffic allowed):
-# monitor_state=enabled, session_state=enabled
-# Disabled (only persistent or active connections allowed):
-# monitor_state=enabled, session_state=disabled
-# Forced offline (only active connections allowed):
-# monitor_state=disabled, session_state=disabled
-#
-# See https://devcentral.f5.com/questions/icontrol-equivalent-call-for-b-node-down
-
- name: Force pool member offline
bigip_pool_member:
server: lb.mydomain.com
user: admin
password: secret
- state: present
- session_state: disabled
- monitor_state: disabled
+ state: forced_offline
pool: my-pool
partition: Common
host: "{{ ansible_default_ipv4['address'] }}"
port: 80
delegate_to: localhost
+
+- name: Create members with priority groups
+ bigip_pool_member:
+ server: lb.mydomain.com
+ user: admin
+ password: secret
+ pool: my-pool
+ partition: Common
+ host: "{{ item.address }}"
+ name: "{{ item.name }}"
+ priority_group: "{{ item.priority_group }}"
+ port: 80
+ delegate_to: localhost
+ loop:
+ - host: 1.1.1.1
+ name: web1
+ priority_group: 4
+ - host: 2.2.2.2
+ name: web2
+ priority_group: 3
+ - host: 3.3.3.3
+ name: web3
+ priority_group: 2
+ - host: 4.4.4.4
+ name: web4
+ priority_group: 1
'''
-try:
- import bigsuds
- HAS_BIGSUDS = True
-except ImportError:
- pass # Handled by f5_utils.bigsuds_found
+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: string
+ 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: string
+ sample: foo.bar.com
+address:
+ description: The address of the pool member.
+ returned: changed
+ type: string
+ sample: 1.2.3.4
+'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
-from ansible.module_utils.f5_utils import bigip_api, bigsuds_found
-
-HAS_DEVEL_IMPORTS = False
try:
+ from library.module_utils.network.f5.bigip import HAS_F5SDK
+ from library.module_utils.network.f5.bigip import F5Client
+ 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 cleanup_tokens
+ from library.module_utils.network.f5.common import fq_name
+ from library.module_utils.network.f5.common import is_valid_hostname
from library.module_utils.network.f5.common import f5_argument_spec
- from library.module_utils.network.f5.common import fqdn_name
- HAS_DEVEL_IMPORTS = True
+ try:
+ from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
+ except ImportError:
+ HAS_F5SDK = False
except ImportError:
- from ansible.module_utils.network.f5.common import fqdn_name
+ from ansible.module_utils.network.f5.bigip import HAS_F5SDK
+ from ansible.module_utils.network.f5.bigip import F5Client
+ 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 cleanup_tokens
+ from ansible.module_utils.network.f5.common import fq_name
+ from ansible.module_utils.network.f5.common import is_valid_hostname
from ansible.module_utils.network.f5.common import f5_argument_spec
-
-
-def pool_exists(api, pool):
- # hack to determine if pool exists
- result = False
try:
- api.LocalLB.Pool.get_object_status(pool_names=[pool])
- result = True
- except bigsuds.OperationFailed as e:
- if "was not found" in str(e):
- result = False
- else:
- # genuine exception
- raise
- return result
-
-
-def member_exists(api, pool, address, port):
- # hack to determine if member exists
- result = False
- try:
- members = [{'address': address, 'port': port}]
- api.LocalLB.Pool.get_member_object_status(pool_names=[pool],
- members=[members])
- result = True
- except bigsuds.OperationFailed as e:
- if "was not found" in str(e):
- result = False
- else:
- # genuine exception
- raise
- return result
-
-
-def delete_node_address(api, address):
- result = False
- try:
- api.LocalLB.NodeAddressV2.delete_node_address(nodes=[address])
- result = True
- except bigsuds.OperationFailed as e:
- if "is referenced by a member of pool" in str(e):
- result = False
- else:
- # genuine exception
- raise
- return result
-
-
-def remove_pool_member(api, pool, address, port):
- members = [{'address': address, 'port': port}]
- api.LocalLB.Pool.remove_member_v2(
- pool_names=[pool],
- members=[members]
- )
-
-
-def add_pool_member(api, pool, address, port):
- members = [{'address': address, 'port': port}]
- api.LocalLB.Pool.add_member_v2(
- pool_names=[pool],
- members=[members]
- )
+ from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
+ except ImportError:
+ HAS_F5SDK = False
-
-def get_connection_limit(api, pool, address, port):
- members = [{'address': address, 'port': port}]
- result = api.LocalLB.Pool.get_member_connection_limit(
- pool_names=[pool],
- members=[members]
- )[0][0]
- return result
-
-
-def set_connection_limit(api, pool, address, port, limit):
- members = [{'address': address, 'port': port}]
- api.LocalLB.Pool.set_member_connection_limit(
- pool_names=[pool],
- members=[members],
- limits=[[limit]]
- )
-
-
-def get_description(api, pool, address, port):
- members = [{'address': address, 'port': port}]
- result = api.LocalLB.Pool.get_member_description(
- pool_names=[pool],
- members=[members]
- )[0][0]
- return result
-
-
-def set_description(api, pool, address, port, description):
- members = [{'address': address, 'port': port}]
- api.LocalLB.Pool.set_member_description(
- pool_names=[pool],
- members=[members],
- descriptions=[[description]]
- )
+try:
+ import netaddr
+ HAS_NETADDR = True
+except ImportError:
+ HAS_NETADDR = False
-def get_rate_limit(api, pool, address, port):
- members = [{'address': address, 'port': port}]
- result = api.LocalLB.Pool.get_member_rate_limit(
- pool_names=[pool],
- members=[members]
- )[0][0]
- return result
+class Parameters(AnsibleF5Parameters):
+ api_map = {
+ 'rateLimit': 'rate_limit',
+ 'connectionLimit': 'connection_limit',
+ 'priorityGroup': 'priority_group',
+ }
+ api_attributes = [
+ 'rateLimit', 'connectionLimit', 'description', 'ratio', 'priorityGroup',
+ 'address', 'fqdn', 'session', 'state'
+ ]
-def set_rate_limit(api, pool, address, port, limit):
- members = [{'address': address, 'port': port}]
- api.LocalLB.Pool.set_member_rate_limit(
- pool_names=[pool],
- members=[members],
- limits=[[limit]]
- )
+ returnables = [
+ 'rate_limit', 'connection_limit', 'description', 'ratio', 'priority_group',
+ 'fqdn_auto_populate', 'session', 'state', 'fqdn', 'address'
+ ]
+ updatables = [
+ 'rate_limit', 'connection_limit', 'description', 'ratio', 'priority_group',
+ 'fqdn_auto_populate', 'state'
+ ]
-def get_ratio(api, pool, address, port):
- members = [{'address': address, 'port': port}]
- result = api.LocalLB.Pool.get_member_ratio(
- pool_names=[pool],
- members=[members]
- )[0][0]
- return result
+class ModuleParameters(Parameters):
+ @property
+ def full_name(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 '{0}:{1}'.format(name, self.port)
+
+ @property
+ def node_name(self):
+ return self.full_name.split(':')[0]
+
+ @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' is not a valid hostname."
+ )
+ 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 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 state(self):
+ # TODO(Remove all of this state craziness in 2.7)
+ if self.session_state is not None or self.monitor_state is not None:
+ if self._values['state'] in ['enabled', 'disabled', 'forced_offline']:
+ self._values['__warnings'].append([{
+ 'msg': "'session_state' is deprecated and will be ignored in favor of 'state'.",
+ 'version': '2.7'
+ }])
+ return self._values['state']
+ else:
+ if self.session_state is not None:
+ self._values['__warnings'].append([{
+ 'msg': "'session_state' is deprecated and will be removed in the future. Use 'state'.",
+ 'version': '2.7'
+ }])
+ elif self.monitor_state is not None:
+ self._values['__warnings'].append([{
+ 'msg': "'monitor_state' is deprecated and will be removed in the future. Use 'state'.",
+ 'version': '2.7'
+ }])
+
+ if self.session_state == 'enabled' and self.monitor_state == 'enabled':
+ return 'enabled'
+ elif self.session_state == 'disabled' and self.monitor_state == 'enabled':
+ return 'disabled'
+ else:
+ return 'forced_offline'
+ return self._values['state']
+
+ @property
+ def address(self):
+ if self._values['address'] is None:
+ return None
+ elif self._values['address'] == 'any6':
+ return 'any6'
+ try:
+ addr = netaddr.IPAddress(self._values['address'])
+ return str(addr)
+ except netaddr.AddrFormatError:
+ raise F5ModuleError(
+ "The specified 'address' value is not a valid IP address."
+ )
+
+
+class ApiParameters(Parameters):
+ @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'] and self._values['session'] in ['user-enabled']:
+ return 'present'
+ elif self._values['state'] in ['user-down'] and self._values['session'] in ['user-disabled']:
+ return 'forced_offline'
+ else:
+ return 'disabled'
-def set_ratio(api, pool, address, port, ratio):
- members = [{'address': address, 'port': port}]
- api.LocalLB.Pool.set_member_ratio(
- pool_names=[pool],
- members=[members],
- ratios=[[ratio]]
- )
+class NodeApiParameters(Parameters):
+ pass
-def get_priority_group(api, pool, address, port):
- members = [{'address': address, 'port': port}]
- result = api.LocalLB.Pool.get_member_priority(
- pool_names=[pool],
- members=[members]
- )[0][0]
- return result
+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
-def set_priority_group(api, pool, address, port, priority_group):
- members = [{'address': address, 'port': port}]
- api.LocalLB.Pool.set_member_priority(
- pool_names=[pool],
- members=[members],
- priorities=[[priority_group]]
- )
+class UsableChanges(Changes):
+ pass
-def set_member_session_enabled_state(api, pool, address, port, session_state):
- members = [{'address': address, 'port': port}]
- session_state = ["STATE_%s" % session_state.strip().upper()]
- api.LocalLB.Pool.set_member_session_enabled_state(
- pool_names=[pool],
- members=[members],
- session_states=[session_state]
- )
+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'] and self._values['session'] in ['user-enabled']:
+ return 'present'
+ elif self._values['state'] in ['user-down'] and self._values['session'] in ['user-disabled']:
+ return 'forced_offline'
+ else:
+ return 'disabled'
+
+
+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'
+ }
+
+
+class ModuleManager(object):
+ def __init__(self, *args, **kwargs):
+ self.module = kwargs.get('module', None)
+ self.client = kwargs.get('client', None)
+ self.want = ModuleParameters(params=self.module.params)
+ self.have = ApiParameters()
+ self.changes = UsableChanges()
+
+ 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 should_update(self):
+ result = self._update_changed_options()
+ if result:
+ return True
+ return False
+
+ def exec_module(self):
+ changed = False
+ result = dict()
+ state = self.want.state
+
+ try:
+ if state in ['present', 'present', 'enabled', 'disabled', 'forced_offline']:
+ changed = self.present()
+ elif state == "absent":
+ changed = self.absent()
+ except iControlUnexpectedHTTPError as e:
+ raise F5ModuleError(str(e))
+
+ 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 _announce_deprecations(self, result):
+ warnings = result.pop('__warnings', [])
+ for warning in warnings:
+ self.module.deprecate(
+ msg=warning['msg'],
+ version=warning['version']
+ )
+
+ def present(self):
+ if self.exists():
+ return self.update()
+ else:
+ return self.create()
+
+ def exists(self):
+ try:
+ pool = self.client.api.tm.ltm.pools.pool.load(
+ name=self.want.pool,
+ partition=self.want.partition
+ )
+ except Exception:
+ raise F5ModuleError('The specified pool does not exist')
+ result = pool.members_s.members.exists(
+ name=self.want.full_name,
+ partition=self.want.partition
+ )
+ return result
-def get_member_session_status(api, pool, address, port):
- members = [{'address': address, 'port': port}]
- result = api.LocalLB.Pool.get_member_session_status(
- pool_names=[pool],
- members=[members]
- )[0][0]
- result = result.split("SESSION_STATUS_")[-1].lower()
- return result
+ def node_exists(self):
+ resource = self.client.api.tm.ltm.nodes.node.exists(
+ name=self.want.node_name,
+ partition=self.want.partition
+ )
+ return resource
+
+ 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 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 _set_host_by_name(self):
+ try:
+ netaddr.IPAddress(self.want.name)
+ self.want.update({
+ 'fqdn': None,
+ 'address': self.want.name
+ })
+ except netaddr.AddrFormatError:
+ 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',
+
+ # TODO(Remove in 2.7)
+ 'session_state': None,
+ 'monitor_state': None
+ })
+ elif self.want.state == 'disabled':
+ self.want.update({
+ 'state': 'user-up',
+ 'session': 'user-disabled',
+
+ # TODO(Remove in 2.7)
+ 'session_state': None,
+ 'monitor_state': None
+ })
+ elif self.want.state in ['present', 'enabled']:
+ self.want.update({
+ 'state': 'user-up',
+ 'session': 'user-enabled',
+
+ # TODO(Remove in 2.7)
+ 'session_state': None,
+ 'monitor_state': None
+ })
+
+ 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 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()
+
+ self._update_api_state_attributes()
+ self._set_changed_options()
+ if self.module.check_mode:
+ return True
+ self.create_on_device()
+ return True
+
+ def create_on_device(self):
+ params = self.changes.api_params()
+ pool = self.client.api.tm.ltm.pools.pool.load(
+ name=self.want.pool,
+ partition=self.want.partition
+ )
+ pool.members_s.members.create(
+ name=self.want.full_name,
+ partition=self.want.partition,
+ **params
+ )
+ def update_on_device(self):
+ params = self.changes.api_params()
+ pool = self.client.api.tm.ltm.pools.pool.load(
+ name=self.want.pool,
+ partition=self.want.partition
+ )
+ resource = pool.members_s.members.load(
+ name=self.want.full_name,
+ partition=self.want.partition
+ )
+ resource.modify(**params)
+
+ 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 remove_from_device(self):
+ pool = self.client.api.tm.ltm.pools.pool.load(
+ name=self.want.pool,
+ partition=self.want.partition
+ )
+ resource = pool.members_s.members.load(
+ name=self.want.full_name,
+ partition=self.want.partition
+ )
+ if resource:
+ resource.delete()
-def set_member_monitor_state(api, pool, address, port, monitor_state):
- members = [{'address': address, 'port': port}]
- monitor_state = ["STATE_%s" % monitor_state.strip().upper()]
- api.LocalLB.Pool.set_member_monitor_state(
- pool_names=[pool],
- members=[members],
- monitor_states=[monitor_state]
- )
+ def remove_node_from_device(self):
+ resource = self.client.api.tm.ltm.nodes.node.load(
+ name=self.want.node_name,
+ partition=self.want.partition
+ )
+ if resource:
+ resource.delete()
+ def read_current_from_device(self):
+ pool = self.client.api.tm.ltm.pools.pool.load(
+ name=self.want.pool,
+ partition=self.want.partition
+ )
+ resource = pool.members_s.members.load(
+ name=self.want.full_name,
+ partition=self.want.partition
+ )
+ return ApiParameters(params=resource.attrs)
-def get_member_monitor_status(api, pool, address, port):
- members = [{'address': address, 'port': port}]
- result = api.LocalLB.Pool.get_member_monitor_status(
- pool_names=[pool],
- members=[members]
- )[0][0]
- result = result.split("MONITOR_STATUS_")[-1].lower()
- return result
+ def read_current_node_from_device(self, node):
+ resource = self.client.api.tm.ltm.nodes.node.load(
+ name=node,
+ partition=self.want.partition
+ )
+ return NodeApiParameters(params=resource.attrs)
+
+
+class ArgumentSpec(object):
+ def __init__(self):
+ self.supports_check_mode = True
+ argument_spec = dict(
+ pool=dict(required=True),
+ address=dict(aliases=['host', 'ip']),
+ fqdn=dict(
+ aliases=['hostname']
+ ),
+ name=dict(),
+ port=dict(type='int', required=True),
+ 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']
+ ),
+ partition=dict(
+ default='Common',
+ fallback=(env_fallback, ['F5_PARTITION'])
+ ),
+ fqdn_auto_populate=dict(type='bool'),
+ reuse_nodes=dict(type='bool', default=True),
+
+ # Deprecated params
+ # TODO(Remove in 2.7)
+ session_state=dict(choices=['enabled', 'disabled']),
+ monitor_state=dict(choices=['enabled', 'disabled']),
+ )
+ self.argument_spec = {}
+ self.argument_spec.update(f5_argument_spec)
+ self.argument_spec.update(argument_spec)
+ self.mutually_exclusive = [
+ ['address', 'fqdn']
+ ]
+ self.required_one_of = [
+ ['name', 'address', 'fqdn'],
+ ]
def main():
- result = {}
- argument_spec = f5_argument_spec
-
- meta_args = dict(
- session_state=dict(type='str', choices=['enabled', 'disabled']),
- monitor_state=dict(type='str', choices=['enabled', 'disabled']),
- pool=dict(type='str', required=True),
- host=dict(type='str', required=True, aliases=['address', 'name']),
- port=dict(type='int', required=True),
- connection_limit=dict(type='int'),
- description=dict(type='str'),
- rate_limit=dict(type='int'),
- ratio=dict(type='int'),
- preserve_node=dict(type='bool', default=False),
- priority_group=dict(type='int'),
- state=dict(default='present', choices=['absent', 'present']),
- partition=dict(
- default='Common',
- fallback=(env_fallback, ['F5_PARTITION'])
- )
- )
- argument_spec.update(meta_args)
+ spec = ArgumentSpec()
module = AnsibleModule(
- argument_spec=argument_spec,
- supports_check_mode=True
+ argument_spec=spec.argument_spec,
+ supports_check_mode=spec.supports_check_mode
)
-
- if not bigsuds_found:
- module.fail_json(msg="the python bigsuds module is required")
-
- if module.params['validate_certs']:
- import ssl
- if not hasattr(ssl, 'SSLContext'):
- module.fail_json(
- msg='bigsuds does not support verifying certificates with python < 2.7.9. '
- 'Either update python or set validate_certs=False on the task')
-
- server = module.params['server']
- server_port = module.params['server_port']
- user = module.params['user']
- password = module.params['password']
- state = module.params['state']
- partition = module.params['partition']
- validate_certs = module.params['validate_certs']
-
- session_state = module.params['session_state']
- monitor_state = module.params['monitor_state']
- pool = fqdn_name(partition, module.params['pool'])
- connection_limit = module.params['connection_limit']
- description = module.params['description']
- rate_limit = module.params['rate_limit']
- ratio = module.params['ratio']
- priority_group = module.params['priority_group']
- host = module.params['host']
- address = fqdn_name(partition, host)
- port = module.params['port']
- preserve_node = module.params['preserve_node']
-
- if (host and port is None) or (port is not None and not host):
- module.fail_json(msg="both host and port must be supplied")
-
- if 0 > port or port > 65535:
- module.fail_json(msg="valid ports must be in range 0 - 65535")
+ if not HAS_F5SDK:
+ module.fail_json(msg="The python f5-sdk module is required")
+ if not HAS_NETADDR:
+ module.fail_json(msg="The python netaddr module is required")
try:
- api = bigip_api(server, user, password, validate_certs, port=server_port)
- if not pool_exists(api, pool):
- module.fail_json(msg="pool %s does not exist" % pool)
- result = {'changed': False} # default
-
- if state == 'absent':
- if member_exists(api, pool, address, port):
- if not module.check_mode:
- remove_pool_member(api, pool, address, port)
- if preserve_node:
- result = {'changed': True}
- else:
- deleted = delete_node_address(api, address)
- result = {'changed': True, 'deleted': deleted}
- else:
- result = {'changed': True}
-
- elif state == 'present':
- if not member_exists(api, pool, address, port):
- if not module.check_mode:
- add_pool_member(api, pool, address, port)
- if connection_limit is not None:
- set_connection_limit(api, pool, address, port, connection_limit)
- if description is not None:
- set_description(api, pool, address, port, description)
- if rate_limit is not None:
- set_rate_limit(api, pool, address, port, rate_limit)
- if ratio is not None:
- set_ratio(api, pool, address, port, ratio)
- if session_state is not None:
- set_member_session_enabled_state(api, pool, address, port, session_state)
- if monitor_state is not None:
- set_member_monitor_state(api, pool, address, port, monitor_state)
- if priority_group is not None:
- set_priority_group(api, pool, address, port, priority_group)
- result = {'changed': True}
- else:
- # pool member exists -- potentially modify attributes
- if connection_limit is not None and connection_limit != get_connection_limit(api, pool, address, port):
- if not module.check_mode:
- set_connection_limit(api, pool, address, port, connection_limit)
- result = {'changed': True}
- if description is not None and description != get_description(api, pool, address, port):
- if not module.check_mode:
- set_description(api, pool, address, port, description)
- result = {'changed': True}
- if rate_limit is not None and rate_limit != get_rate_limit(api, pool, address, port):
- if not module.check_mode:
- set_rate_limit(api, pool, address, port, rate_limit)
- result = {'changed': True}
- if ratio is not None and ratio != get_ratio(api, pool, address, port):
- if not module.check_mode:
- set_ratio(api, pool, address, port, ratio)
- result = {'changed': True}
- if session_state is not None:
- session_status = get_member_session_status(api, pool, address, port)
- if session_state == 'enabled' and session_status == 'forced_disabled':
- if not module.check_mode:
- set_member_session_enabled_state(api, pool, address, port, session_state)
- result = {'changed': True}
- elif session_state == 'disabled' and session_status != 'forced_disabled':
- if not module.check_mode:
- set_member_session_enabled_state(api, pool, address, port, session_state)
- result = {'changed': True}
- if monitor_state is not None:
- monitor_status = get_member_monitor_status(api, pool, address, port)
- if monitor_state == 'enabled' and monitor_status == 'forced_down':
- if not module.check_mode:
- set_member_monitor_state(api, pool, address, port, monitor_state)
- result = {'changed': True}
- elif monitor_state == 'disabled' and monitor_status != 'forced_down':
- if not module.check_mode:
- set_member_monitor_state(api, pool, address, port, monitor_state)
- result = {'changed': True}
- if priority_group is not None and priority_group != get_priority_group(api, pool, address, port):
- if not module.check_mode:
- set_priority_group(api, pool, address, port, priority_group)
- result = {'changed': True}
-
- except Exception as e:
- module.fail_json(msg="received exception: %s" % e)
-
- module.exit_json(**result)
+ client = F5Client(**module.params)
+ mm = ModuleManager(module=module, client=client)
+ results = mm.exec_module()
+ cleanup_tokens(client)
+ module.exit_json(**results)
+ except F5ModuleError as ex:
+ cleanup_tokens(client)
+ module.fail_json(msg=str(ex))
if __name__ == '__main__':
diff --git a/lib/ansible/modules/network/f5/bigip_profile_client_ssl.py b/lib/ansible/modules/network/f5/bigip_profile_client_ssl.py
index 3da33084fd..debee4af56 100644
--- a/lib/ansible/modules/network/f5/bigip_profile_client_ssl.py
+++ b/lib/ansible/modules/network/f5/bigip_profile_client_ssl.py
@@ -16,8 +16,9 @@ DOCUMENTATION = r'''
---
module: bigip_profile_client_ssl
short_description: Manages client SSL profiles on a BIG-IP
-description: Manages client SSL profiles on a BIG-IP.
-version_added: "2.5"
+description:
+ - Manages client SSL profiles on a BIG-IP.
+version_added: 2.5
options:
name:
description:
@@ -41,7 +42,8 @@ options:
type of each certificate/key type. This means that you can only have one
RSA, one DSA, and one ECDSA per profile. If you attempt to assign two
RSA, DSA, or ECDSA certificate/key combo, the device will reject this.
- - This list is a complex list that specifies a number of keys. There are several supported keys.
+ - This list is a complex list that specifies a number of keys. There are
+ several supported keys.
suboptions:
cert:
description:
@@ -131,30 +133,25 @@ from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.six import iteritems
-HAS_DEVEL_IMPORTS = False
-
try:
- # Sideband repository used for dev
from library.module_utils.network.f5.bigip import HAS_F5SDK
from library.module_utils.network.f5.bigip import F5Client
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 cleanup_tokens
- from library.module_utils.network.f5.common import fqdn_name
+ from library.module_utils.network.f5.common import fq_name
from library.module_utils.network.f5.common import f5_argument_spec
try:
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
except ImportError:
HAS_F5SDK = False
- HAS_DEVEL_IMPORTS = True
except ImportError:
- # Upstream Ansible
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
from ansible.module_utils.network.f5.bigip import F5Client
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 cleanup_tokens
- from ansible.module_utils.network.f5.common import fqdn_name
+ from ansible.module_utils.network.f5.common import fq_name
from ansible.module_utils.network.f5.common import f5_argument_spec
try:
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
@@ -181,11 +178,6 @@ class Parameters(AnsibleF5Parameters):
class ModuleParameters(Parameters):
- def _fqdn_name(self, value):
- if value is not None and not value.startswith('/'):
- return '/{0}/{1}'.format(self.partition, value)
- return value
-
def _key_filename(self, name):
if name.endswith('.key'):
return name
@@ -202,14 +194,14 @@ class ModuleParameters(Parameters):
if 'chain' not in item or item['chain'] == 'none':
result = 'none'
else:
- result = self._cert_filename(self._fqdn_name(item['chain']))
+ result = self._cert_filename(fq_name(self.partition, item['chain']))
return result
@property
def parent(self):
if self._values['parent'] is None:
return None
- result = self._fqdn_name(self._values['parent'])
+ result = fq_name(self.partition, self._values['parent'])
return result
@property
@@ -233,8 +225,8 @@ class ModuleParameters(Parameters):
filename, ex = os.path.splitext(name)
tmp = {
'name': filename,
- 'cert': self._fqdn_name(cert),
- 'key': self._fqdn_name(key),
+ 'cert': fq_name(self.partition, cert),
+ 'key': fq_name(self.partition, key),
'chain': chain
}
if 'passphrase' in item:
diff --git a/test/sanity/validate-modules/ignore.txt b/test/sanity/validate-modules/ignore.txt
index d72cac92bc..cb7f5c5b9e 100644
--- a/test/sanity/validate-modules/ignore.txt
+++ b/test/sanity/validate-modules/ignore.txt
@@ -1079,7 +1079,6 @@ lib/ansible/modules/network/f5/bigip_iapp_service.py E324
lib/ansible/modules/network/f5/bigip_iapp_service.py E325
lib/ansible/modules/network/f5/bigip_monitor_snmp_dca.py E326
lib/ansible/modules/network/f5/bigip_policy.py E324
-lib/ansible/modules/network/f5/bigip_pool.py E326
lib/ansible/modules/network/f5/bigip_profile_client_ssl.py E324
lib/ansible/modules/network/f5/bigip_selfip.py E324
lib/ansible/modules/network/f5/bigip_sys_global.py E326
diff --git a/test/units/modules/network/f5/fixtures/load_net_node_with_fqdn.json b/test/units/modules/network/f5/fixtures/load_net_node_with_fqdn.json
new file mode 100644
index 0000000000..67c02568d5
--- /dev/null
+++ b/test/units/modules/network/f5/fixtures/load_net_node_with_fqdn.json
@@ -0,0 +1,25 @@
+{
+ "kind": "tm:ltm:node:nodestate",
+ "name": "foo.bar.com",
+ "partition": "Common",
+ "fullPath": "/Common/foo.bar.com",
+ "generation": 157,
+ "selfLink": "https://localhost/mgmt/tm/ltm/node/~Common~foo.bar.com?ver=12.0.0",
+ "address": "any6",
+ "connectionLimit": 0,
+ "dynamicRatio": 1,
+ "ephemeral": "false",
+ "fqdn": {
+ "addressFamily": "ipv4",
+ "autopopulate": "enabled",
+ "downInterval": 5,
+ "interval": "3600",
+ "tmName": "foo.bar.com"
+ },
+ "logging": "disabled",
+ "monitor": "default",
+ "rateLimit": "disabled",
+ "ratio": 1,
+ "session": "user-enabled",
+ "state": "fqdn-up-no-addr"
+}
diff --git a/test/units/modules/network/f5/fixtures/load_net_node_with_ipv4_address.json b/test/units/modules/network/f5/fixtures/load_net_node_with_ipv4_address.json
new file mode 100644
index 0000000000..9e3be88929
--- /dev/null
+++ b/test/units/modules/network/f5/fixtures/load_net_node_with_ipv4_address.json
@@ -0,0 +1,24 @@
+{
+ "kind": "tm:ltm:node:nodestate",
+ "name": "7.3.67.8",
+ "partition": "Common",
+ "fullPath": "/Common/7.3.67.8",
+ "generation": 162,
+ "selfLink": "https://localhost/mgmt/tm/ltm/node/~Common~7.3.67.8?ver=12.0.0",
+ "address": "7.3.67.8",
+ "connectionLimit": 0,
+ "dynamicRatio": 1,
+ "ephemeral": "false",
+ "fqdn": {
+ "addressFamily": "ipv4",
+ "autopopulate": "disabled",
+ "downInterval": 5,
+ "interval": "3600"
+ },
+ "logging": "disabled",
+ "monitor": "default",
+ "rateLimit": "disabled",
+ "ratio": 1,
+ "session": "user-enabled",
+ "state": "unchecked"
+}
diff --git a/test/units/modules/network/f5/test_bigip_monitor_udp.py b/test/units/modules/network/f5/test_bigip_monitor_udp.py
index e22cc57517..c9431d4e3d 100644
--- a/test/units/modules/network/f5/test_bigip_monitor_udp.py
+++ b/test/units/modules/network/f5/test_bigip_monitor_udp.py
@@ -21,9 +21,9 @@ from ansible.compat.tests.mock import patch
from ansible.module_utils.basic import AnsibleModule
try:
- from library.bigip_monitor_udp import Parameters
- from library.bigip_monitor_udp import ModuleManager
- from library.bigip_monitor_udp import ArgumentSpec
+ from library.modules.bigip_monitor_udp import Parameters
+ from library.modules.bigip_monitor_udp import ModuleManager
+ from library.modules.bigip_monitor_udp import ArgumentSpec
from library.module_utils.network.f5.common import F5ModuleError
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
from test.unit.modules.utils import set_module_args
diff --git a/test/units/modules/network/f5/test_bigip_node.py b/test/units/modules/network/f5/test_bigip_node.py
index 384270d307..0729e31526 100644
--- a/test/units/modules/network/f5/test_bigip_node.py
+++ b/test/units/modules/network/f5/test_bigip_node.py
@@ -20,9 +20,9 @@ from ansible.compat.tests.mock import patch
from ansible.module_utils.basic import AnsibleModule
try:
- from library.bigip_node import Parameters
- from library.bigip_node import ModuleManager
- from library.bigip_node import ArgumentSpec
+ from library.modules.bigip_node import Parameters
+ from library.modules.bigip_node import ModuleManager
+ from library.modules.bigip_node import ArgumentSpec
from library.module_utils.network.f5.common import F5ModuleError
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
from test.unit.modules.utils import set_module_args
diff --git a/test/units/modules/network/f5/test_bigip_partition.py b/test/units/modules/network/f5/test_bigip_partition.py
index 795427ee5f..c4aa79f7b2 100644
--- a/test/units/modules/network/f5/test_bigip_partition.py
+++ b/test/units/modules/network/f5/test_bigip_partition.py
@@ -20,9 +20,9 @@ from ansible.compat.tests.mock import patch
from ansible.module_utils.basic import AnsibleModule
try:
- from library.bigip_partition import Parameters
- from library.bigip_partition import ModuleManager
- from library.bigip_partition import ArgumentSpec
+ from library.modules.bigip_partition import Parameters
+ from library.modules.bigip_partition import ModuleManager
+ from library.modules.bigip_partition import ArgumentSpec
from library.module_utils.network.f5.common import F5ModuleError
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
from test.unit.modules.utils import set_module_args
diff --git a/test/units/modules/network/f5/test_bigip_policy.py b/test/units/modules/network/f5/test_bigip_policy.py
index d9e29b0b8a..0441025076 100644
--- a/test/units/modules/network/f5/test_bigip_policy.py
+++ b/test/units/modules/network/f5/test_bigip_policy.py
@@ -20,11 +20,11 @@ from ansible.compat.tests.mock import patch
from ansible.module_utils.basic import AnsibleModule
try:
- from library.bigip_policy import Parameters
- from library.bigip_policy import ModuleManager
- from library.bigip_policy import SimpleManager
- from library.bigip_policy import ComplexManager
- from library.bigip_policy import ArgumentSpec
+ from library.modules.bigip_policy import Parameters
+ from library.modules.bigip_policy import ModuleManager
+ from library.modules.bigip_policy import SimpleManager
+ from library.modules.bigip_policy import ComplexManager
+ from library.modules.bigip_policy import ArgumentSpec
from library.module_utils.network.f5.common import F5ModuleError
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
from test.unit.modules.utils import set_module_args
diff --git a/test/units/modules/network/f5/test_bigip_policy_rule.py b/test/units/modules/network/f5/test_bigip_policy_rule.py
index adde3d72ef..33f82e8a1f 100644
--- a/test/units/modules/network/f5/test_bigip_policy_rule.py
+++ b/test/units/modules/network/f5/test_bigip_policy_rule.py
@@ -20,11 +20,11 @@ from ansible.compat.tests.mock import patch
from ansible.module_utils.basic import AnsibleModule
try:
- from library.bigip_policy_rule import Parameters
- from library.bigip_policy_rule import ModuleParameters
- from library.bigip_policy_rule import ApiParameters
- from library.bigip_policy_rule import ModuleManager
- from library.bigip_policy_rule import ArgumentSpec
+ from library.modules.bigip_policy_rule import Parameters
+ from library.modules.bigip_policy_rule import ModuleParameters
+ from library.modules.bigip_policy_rule import ApiParameters
+ from library.modules.bigip_policy_rule import ModuleManager
+ from library.modules.bigip_policy_rule import ArgumentSpec
from library.module_utils.network.f5.common import F5ModuleError
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
from test.unit.modules.utils import set_module_args
diff --git a/test/units/modules/network/f5/test_bigip_pool.py b/test/units/modules/network/f5/test_bigip_pool.py
index 5c69c09b50..e2b2d788b5 100644
--- a/test/units/modules/network/f5/test_bigip_pool.py
+++ b/test/units/modules/network/f5/test_bigip_pool.py
@@ -21,10 +21,10 @@ from ansible.compat.tests.mock import patch
from ansible.module_utils.basic import AnsibleModule
try:
- from library.bigip_pool import ApiParameters
- from library.bigip_pool import ModuleParameters
- from library.bigip_pool import ModuleManager
- from library.bigip_pool import ArgumentSpec
+ from library.modules.bigip_pool import ApiParameters
+ from library.modules.bigip_pool import ModuleParameters
+ from library.modules.bigip_pool import ModuleManager
+ from library.modules.bigip_pool import ArgumentSpec
from library.module_utils.network.f5.common import F5ModuleError
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
from test.unit.modules.utils import set_module_args
diff --git a/test/units/modules/network/f5/test_bigip_pool_member.py b/test/units/modules/network/f5/test_bigip_pool_member.py
new file mode 100644
index 0000000000..b1f15a6dc8
--- /dev/null
+++ b/test/units/modules/network/f5/test_bigip_pool_member.py
@@ -0,0 +1,338 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 F5 Networks Inc.
+# 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
+
+import os
+import json
+import pytest
+import sys
+
+from nose.plugins.skip import SkipTest
+if sys.version_info < (2, 7):
+ raise SkipTest("F5 Ansible modules require Python >= 2.7")
+
+from ansible.compat.tests import unittest
+from ansible.compat.tests.mock import Mock
+from ansible.compat.tests.mock import patch
+from ansible.module_utils.basic import AnsibleModule
+
+try:
+ from library.modules.bigip_pool_member import ModuleParameters
+ from library.modules.bigip_pool_member import ApiParameters
+ from library.modules.bigip_pool_member import NodeApiParameters
+ from library.modules.bigip_pool_member import ModuleManager
+ from library.modules.bigip_pool_member import ArgumentSpec
+ from library.module_utils.network.f5.common import F5ModuleError
+ from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
+ from test.unit.modules.utils import set_module_args
+except ImportError:
+ try:
+ from ansible.modules.network.f5.bigip_pool_member import ModuleParameters
+ from ansible.modules.network.f5.bigip_pool_member import ApiParameters
+ from ansible.modules.network.f5.bigip_pool_member import NodeApiParameters
+ from ansible.modules.network.f5.bigip_pool_member import ModuleManager
+ from ansible.modules.network.f5.bigip_pool_member import ArgumentSpec
+ from ansible.module_utils.network.f5.common import F5ModuleError
+ from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
+ from units.modules.utils import set_module_args
+ except ImportError:
+ raise SkipTest("F5 Ansible modules require the f5-sdk Python library")
+
+fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures')
+fixture_data = {}
+
+
+def load_fixture(name):
+ path = os.path.join(fixture_path, name)
+
+ if path in fixture_data:
+ return fixture_data[path]
+
+ with open(path) as f:
+ data = f.read()
+
+ try:
+ data = json.loads(data)
+ except Exception:
+ pass
+
+ fixture_data[path] = data
+ return data
+
+
+class TestParameters(unittest.TestCase):
+ def test_module_parameters(self):
+ args = dict(
+ pool='my-pool',
+ address='1.2.3.4',
+ fqdn='fqdn.foo.bar',
+ name='my-name',
+ port=2345,
+ connection_limit=100,
+ description='this is a description',
+ rate_limit=70,
+ ratio=20,
+ preserve_node=False,
+ priority_group=10,
+ state='present',
+ partition='Common',
+ fqdn_auto_populate=False,
+ reuse_nodes=False,
+
+ # Deprecated params
+ # TODO(Remove in 2.7)
+ session_state='disabled',
+ monitor_state='disabled',
+ )
+
+ p = ModuleParameters(params=args)
+ assert p.name == 'my-name'
+
+ def test_api_parameters(self):
+ args = load_fixture('load_net_node_with_fqdn.json')
+ p = ApiParameters(params=args)
+ assert p.state == 'present'
+
+
+class TestManager(unittest.TestCase):
+
+ def setUp(self):
+ self.spec = ArgumentSpec()
+
+ def test_create_reuse_node_with_name(self, *args):
+ # Configure the arguments that would be sent to the Ansible module
+ set_module_args(dict(
+ pool='my-pool',
+ name='my-name',
+ port=2345,
+ state='present',
+ partition='Common',
+ reuse_nodes=True,
+ password='password',
+ server='localhost',
+ user='admin'
+ ))
+
+ current_node = NodeApiParameters(params=load_fixture('load_net_node_with_fqdn.json'))
+ module = AnsibleModule(
+ argument_spec=self.spec.argument_spec,
+ supports_check_mode=self.spec.supports_check_mode
+ )
+ mm = ModuleManager(module=module)
+
+ # Override methods to force specific logic in the module to happen
+ mm.exists = Mock(return_value=False)
+ mm.create_on_device = Mock(return_value=True)
+ mm.read_current_node_from_device = Mock(return_value=current_node)
+
+ results = mm.exec_module()
+
+ assert results['changed'] is True
+ assert results['fqdn_auto_populate'] is True
+ assert results['fqdn'] == 'foo.bar.com'
+ assert results['state'] == 'present'
+
+ def test_create_reuse_node_with_ipv4_address(self, *args):
+ # Configure the arguments that would be sent to the Ansible module
+ set_module_args(dict(
+ pool='my-pool',
+ name='7.3.67.8',
+ port=2345,
+ state='present',
+ partition='Common',
+ reuse_nodes=True,
+ password='password',
+ server='localhost',
+ user='admin'
+ ))
+
+ current_node = NodeApiParameters(params=load_fixture('load_net_node_with_ipv4_address.json'))
+ module = AnsibleModule(
+ argument_spec=self.spec.argument_spec,
+ supports_check_mode=self.spec.supports_check_mode
+ )
+ mm = ModuleManager(module=module)
+
+ # Override methods to force specific logic in the module to happen
+ mm.exists = Mock(return_value=False)
+ mm.create_on_device = Mock(return_value=True)
+ mm.read_current_node_from_device = Mock(return_value=current_node)
+
+ results = mm.exec_module()
+
+ assert results['changed'] is True
+ assert results['fqdn_auto_populate'] is False
+ assert results['address'] == '7.3.67.8'
+ assert results['state'] == 'present'
+
+ def test_create_reuse_node_with_fqdn_auto_populate(self, *args):
+ # Configure the arguments that would be sent to the Ansible module
+ set_module_args(dict(
+ pool='my-pool',
+ name='my-name',
+ port=2345,
+ state='present',
+ partition='Common',
+ reuse_nodes=True,
+ fqdn_auto_populate=False,
+ password='password',
+ server='localhost',
+ user='admin'
+ ))
+
+ current_node = NodeApiParameters(params=load_fixture('load_net_node_with_fqdn.json'))
+ module = AnsibleModule(
+ argument_spec=self.spec.argument_spec,
+ supports_check_mode=self.spec.supports_check_mode
+ )
+ mm = ModuleManager(module=module)
+
+ # Override methods to force specific logic in the module to happen
+ mm.exists = Mock(return_value=False)
+ mm.create_on_device = Mock(return_value=True)
+ mm.read_current_node_from_device = Mock(return_value=current_node)
+
+ results = mm.exec_module()
+
+ assert results['changed'] is True
+ assert results['fqdn_auto_populate'] is True
+ assert results['fqdn'] == 'foo.bar.com'
+ assert results['state'] == 'present'
+
+
+class TestLegacyManager(unittest.TestCase):
+
+ def setUp(self):
+ self.spec = ArgumentSpec()
+
+ def test_create_name_is_hostname_with_session_and_monitor_enabled(self, *args):
+ # Configure the arguments that would be sent to the Ansible module
+ set_module_args(dict(
+ pool='my-pool',
+ name='my-name',
+ port=2345,
+ state='present',
+ session_state='enabled',
+ monitor_state='enabled',
+ partition='Common',
+ password='password',
+ server='localhost',
+ user='admin'
+ ))
+
+ module = AnsibleModule(
+ argument_spec=self.spec.argument_spec,
+ supports_check_mode=self.spec.supports_check_mode
+ )
+ mm = ModuleManager(module=module)
+
+ # Override methods to force specific logic in the module to happen
+ mm.exists = Mock(return_value=False)
+ mm.create_on_device = Mock(return_value=True)
+
+ results = mm.exec_module()
+
+ assert results['changed'] is True
+ assert results['fqdn_auto_populate'] is False
+ assert results['fqdn'] == 'my-name'
+ assert results['state'] == 'present'
+
+ def test_create_name_is_address_with_session_and_monitor_enabled(self, *args):
+ # Configure the arguments that would be sent to the Ansible module
+ set_module_args(dict(
+ pool='my-pool',
+ name='10.10.10.10',
+ port=2345,
+ state='present',
+ session_state='enabled',
+ monitor_state='enabled',
+ partition='Common',
+ password='password',
+ server='localhost',
+ user='admin'
+ ))
+
+ module = AnsibleModule(
+ argument_spec=self.spec.argument_spec,
+ supports_check_mode=self.spec.supports_check_mode
+ )
+ mm = ModuleManager(module=module)
+
+ # Override methods to force specific logic in the module to happen
+ mm.exists = Mock(return_value=False)
+ mm.create_on_device = Mock(return_value=True)
+
+ results = mm.exec_module()
+
+ assert results['changed'] is True
+ assert results['fqdn_auto_populate'] is False
+ assert results['address'] == '10.10.10.10'
+ assert results['state'] == 'present'
+
+ def test_create_name_is_address_with_session_disabled_and_monitor_enabled(self, *args):
+ # Configure the arguments that would be sent to the Ansible module
+ set_module_args(dict(
+ pool='my-pool',
+ name='10.10.10.10',
+ port=2345,
+ state='present',
+ monitor_state='enabled',
+ session_state='disabled',
+ partition='Common',
+ password='password',
+ server='localhost',
+ user='admin'
+ ))
+
+ module = AnsibleModule(
+ argument_spec=self.spec.argument_spec,
+ supports_check_mode=self.spec.supports_check_mode
+ )
+ mm = ModuleManager(module=module)
+
+ # Override methods to force specific logic in the module to happen
+ mm.exists = Mock(return_value=False)
+ mm.create_on_device = Mock(return_value=True)
+
+ results = mm.exec_module()
+
+ assert results['changed'] is True
+ assert results['fqdn_auto_populate'] is False
+ assert results['address'] == '10.10.10.10'
+ assert results['state'] == 'disabled'
+
+ def test_create_name_is_address_with_session_and_monitor_disabled(self, *args):
+ # Configure the arguments that would be sent to the Ansible module
+ set_module_args(dict(
+ pool='my-pool',
+ name='10.10.10.10',
+ port=2345,
+ state='present',
+ monitor_state='disabled',
+ session_state='disabled',
+ partition='Common',
+ password='password',
+ server='localhost',
+ user='admin'
+ ))
+
+ module = AnsibleModule(
+ argument_spec=self.spec.argument_spec,
+ supports_check_mode=self.spec.supports_check_mode
+ )
+ mm = ModuleManager(module=module)
+
+ # Override methods to force specific logic in the module to happen
+ mm.exists = Mock(return_value=False)
+ mm.create_on_device = Mock(return_value=True)
+
+ results = mm.exec_module()
+
+ assert results['changed'] is True
+ assert results['fqdn_auto_populate'] is False
+ assert results['address'] == '10.10.10.10'
+ assert results['state'] == 'forced_offline'
diff --git a/test/units/modules/network/f5/test_bigip_profile_client_ssl.py b/test/units/modules/network/f5/test_bigip_profile_client_ssl.py
index 29b30b45a3..e2a68b3bb0 100644
--- a/test/units/modules/network/f5/test_bigip_profile_client_ssl.py
+++ b/test/units/modules/network/f5/test_bigip_profile_client_ssl.py
@@ -21,10 +21,10 @@ from ansible.compat.tests.mock import patch
from ansible.module_utils.basic import AnsibleModule
try:
- from library.bigip_profile_client_ssl import ModuleParameters
- from library.bigip_profile_client_ssl import ApiParameters
- from library.bigip_profile_client_ssl import ModuleManager
- from library.bigip_profile_client_ssl import ArgumentSpec
+ from library.modules.bigip_profile_client_ssl import ModuleParameters
+ from library.modules.bigip_profile_client_ssl import ApiParameters
+ from library.modules.bigip_profile_client_ssl import ModuleManager
+ from library.modules.bigip_profile_client_ssl import ArgumentSpec
from library.module_utils.network.f5.common import F5ModuleError
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
from test.unit.modules.utils import set_module_args