Various f5 modules fixes (#40037)

* Add members to bigip_gtm_pool
* Add monitors to bigip_gtm_pool
* Add availability_requirements to bigip_gtm_pool
* Refactor bigip_gtm_pool
* Normalize the product value returned by gtm facts
* Corrected various documentation
* Updated various F5 coding conventions
* Add partition to bigip_static_route
* Added more unit tests
This commit is contained in:
Tim Rupp 2018-05-11 14:07:28 -07:00 committed by GitHub
parent 61e7c77dec
commit 383a4f026e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 746 additions and 126 deletions

View file

@ -582,6 +582,14 @@ class ServerParameters(BaseParameters):
'virtual_server_discovery', 'addresses', 'devices', 'virtual_servers' 'virtual_server_discovery', 'addresses', 'devices', 'virtual_servers'
] ]
@property
def product(self):
if self._values['product'] is None:
return None
if self._values['product'] in ['single-bigip', 'redundant-bigip']:
return 'bigip'
return self._values['product']
@property @property
def devices(self): def devices(self):
result = [] result = []

View file

@ -18,15 +18,14 @@ module: bigip_gtm_pool
short_description: Manages F5 BIG-IP GTM pools short_description: Manages F5 BIG-IP GTM pools
description: description:
- Manages F5 BIG-IP GTM pools. - Manages F5 BIG-IP GTM pools.
version_added: "2.4" version_added: 2.4
options: options:
state: state:
description: description:
- Pool member state. When C(present), ensures that the pool is - Pool state. When C(present), ensures that the pool is created and enabled.
created and enabled. When C(absent), ensures that the pool is When C(absent), ensures that the pool is removed from the system. When
removed from the system. When C(enabled) or C(disabled), ensures C(enabled) or C(disabled), ensures that the pool is enabled or disabled
that the pool is enabled or disabled (respectively) on the remote (respectively) on the remote device.
device.
choices: choices:
- present - present
- absent - absent
@ -122,6 +121,60 @@ options:
- Device partition to manage resources on. - Device partition to manage resources on.
default: Common default: Common
version_added: 2.5 version_added: 2.5
members:
description:
- Members to assign to the pool.
- The order of the members in this list is the order that they will be listed in the pool.
suboptions:
server:
description:
- Name of the server which the pool member is a part of.
required: True
virtual_server:
description:
- Name of the virtual server, associated with the server, that the pool member is a part of.
required: True
version_added: 2.6
monitors:
description:
- Specifies the health monitors that the system currently uses to monitor this resource.
- When C(availability_requirements.type) is C(require), you may only have a single monitor in the
C(monitors) list.
version_added: 2.6
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.
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.
choices: ['all', 'at_least', 'require']
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 either C(all) or C(require) is used.
number_of_probes:
description:
- Specifies the minimum number of probes that must succeed for this server to be declared up.
- When creating a new virtual server, if this parameter is specified, then the C(number_of_probers)
parameter must also be specified.
- The value of this parameter should always be B(lower) than, or B(equal to), the value of C(number_of_probers).
- This parameter is only relevant when a C(type) of C(require) is used.
- This parameter will be ignored if a type of either C(all) or C(at_least) is used.
number_of_probers:
description:
- Specifies the number of probers that should be used when running probes.
- When creating a new virtual server, if this parameter is specified, then the C(number_of_probes)
parameter must also be specified.
- The value of this parameter should always be B(higher) than, or B(equal to), the value of C(number_of_probers).
- This parameter is only relevant when a C(type) of C(require) is used.
- This parameter will be ignored if a type of either C(all) or C(at_least) is used.
version_added: 2.6
notes: notes:
- Requires the netaddr Python package on the host. This is as easy as - Requires the netaddr Python package on the host. This is as easy as
pip install netaddr. pip install netaddr.
@ -153,6 +206,24 @@ fallback_ip:
returned: changed returned: changed
type: string type: string
sample: 10.10.10.10 sample: 10.10.10.10
monitors:
description: The new list of monitors for the resource.
returned: changed
type: list
sample: ['/Common/monitor1', '/Common/monitor2']
members:
description: List of members in the pool.
returned: changed
type: complex
contains:
server:
description: The name of the server portion of the member.
returned: changed
type: string
virtual_server:
description: The name of the virtual server portion of the member.
returned: changed
type: string
''' '''
EXAMPLES = r''' EXAMPLES = r'''
@ -174,37 +245,37 @@ EXAMPLES = r'''
delegate_to: localhost delegate_to: localhost
''' '''
from distutils.version import LooseVersion import copy
import re
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback from ansible.module_utils.basic import env_fallback
from distutils.version import LooseVersion
HAS_DEVEL_IMPORTS = False
try: try:
# Sideband repository used for dev
from library.module_utils.network.f5.bigip import HAS_F5SDK 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.bigip import F5Client
from library.module_utils.network.f5.common import F5ModuleError 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 AnsibleF5Parameters
from library.module_utils.network.f5.common import cleanup_tokens 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 from library.module_utils.network.f5.common import f5_argument_spec
try: try:
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
from f5.sdk_exception import LazyAttributesRequired
except ImportError: except ImportError:
HAS_F5SDK = False HAS_F5SDK = False
HAS_DEVEL_IMPORTS = True
except ImportError: except ImportError:
# Upstream Ansible
from ansible.module_utils.network.f5.bigip import HAS_F5SDK 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.bigip import F5Client
from ansible.module_utils.network.f5.common import F5ModuleError 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 AnsibleF5Parameters
from ansible.module_utils.network.f5.common import cleanup_tokens 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 from ansible.module_utils.network.f5.common import f5_argument_spec
try: try:
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
from f5.sdk_exception import LazyAttributesRequired
except ImportError: except ImportError:
HAS_F5SDK = False HAS_F5SDK = False
@ -214,8 +285,6 @@ try:
except ImportError: except ImportError:
HAS_NETADDR = False HAS_NETADDR = False
import copy
class Parameters(AnsibleF5Parameters): class Parameters(AnsibleF5Parameters):
api_map = { api_map = {
@ -225,19 +294,47 @@ class Parameters(AnsibleF5Parameters):
'verifyMemberAvailability': 'verify_member_availability', 'verifyMemberAvailability': 'verify_member_availability',
'fallbackIpv4': 'fallback_ip', 'fallbackIpv4': 'fallback_ip',
'fallbackIpv6': 'fallback_ip', 'fallbackIpv6': 'fallback_ip',
'fallbackIp': 'fallback_ip' 'fallbackIp': 'fallback_ip',
'membersReference': 'members',
'monitor': 'monitors'
} }
updatables = [ updatables = [
'preferred_lb_method', 'alternate_lb_method', 'fallback_lb_method', 'alternate_lb_method',
'fallback_ip', 'state' 'fallback_ip',
'fallback_lb_method',
'members',
'monitors',
'preferred_lb_method',
'state',
] ]
returnables = [ returnables = [
'preferred_lb_method', 'alternate_lb_method', 'fallback_lb_method', 'alternate_lb_method',
'fallback_ip' 'fallback_ip',
'fallback_lb_method',
'members',
'monitors',
'preferred_lb_method',
'enabled',
'disabled'
] ]
api_attributes = [ api_attributes = [
'loadBalancingMode', 'alternateMode', 'fallbackMode', 'verifyMemberAvailability', 'alternateMode',
'fallbackIpv4', 'fallbackIpv6', 'fallbackIp', 'enabled', 'disabled' 'disabled',
'enabled',
'fallbackIp',
'fallbackIpv4',
'fallbackIpv6',
'fallbackMode',
'loadBalancingMode',
'members',
'verifyMemberAvailability',
# The monitor attribute is not included here, because it can break the
# API calls to the device. If this bug is ever fixed, uncomment this code.
#
# monitor
] ]
def to_return(self): def to_return(self):
@ -316,10 +413,252 @@ class Parameters(AnsibleF5Parameters):
return True return True
class ApiParameters(Parameters):
@property
def members(self):
result = []
if self._values['members'] is None or 'items' not in self._values['members']:
return []
for item in self._values['members']['items']:
result.append(dict(item=item['fullPath'], order=item['memberOrder']))
result = [x['item'] for x in sorted(result, key=lambda k: k['order'])]
return result
@property
def availability_requirement_type(self):
if self._values['monitors'] is None:
return None
if 'min ' in self._values['monitors']:
return 'at_least'
elif 'require ' in self._values['monitors']:
return 'require'
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)
elif self.availability_requirement_type == 'require':
monitors = ' '.join(monitors)
result = 'require {0} from {1} {{ {2} }}'.format(self.number_of_probes, self.number_of_probers, monitors)
else:
result = ' and '.join(monitors).strip()
return result
@property
def number_of_probes(self):
"""Returns the probes value from the monitor string.
The monitor string for a Require monitor looks like this.
require 1 from 2 { /Common/tcp }
This method parses out the first of the numeric values. This values represents
the "probes" value that can be updated in the module.
Returns:
int: The probes value if found. None otherwise.
"""
if self._values['monitors'] is None:
return None
pattern = r'require\s+(?P<probes>\d+)\s+from'
matches = re.search(pattern, self._values['monitors'])
if matches is None:
return None
return matches.group('probes')
@property
def number_of_probers(self):
"""Returns the probers value from the monitor string.
The monitor string for a Require monitor looks like this.
require 1 from 2 { /Common/tcp }
This method parses out the first of the numeric values. This values represents
the "probers" value that can be updated in the module.
Returns:
int: The probers value if found. None otherwise.
"""
if self._values['monitors'] is None:
return None
pattern = r'require\s+\d+\s+from\s+(?P<probers>\d+)\s+'
matches = re.search(pattern, self._values['monitors'])
if matches is None:
return None
return matches.group('probers')
@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')
class ModuleParameters(Parameters):
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])
@property
def members(self):
if self._values['members'] is None:
return None
if len(self._values['members']) == 1 and self._values['members'][0] == '':
return []
result = []
for member in self._values['members']:
if 'server' not in member:
raise F5ModuleError(
"One of the provided members is missing a 'server' sub-option."
)
if 'virtual_server' not in member:
raise F5ModuleError(
"One of the provided members is missing a 'virtual_server' sub-option."
)
name = '{0}:{1}'.format(member['server'], member['virtual_server'])
name = fq_name(self.partition, name)
if name in result:
continue
result.append(name)
result = list(result)
return result
@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'
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)
elif self.availability_requirement_type == 'require':
monitors = ' '.join(monitors)
if self.number_of_probes > self.number_of_probers:
raise F5ModuleError(
"The 'number_of_probes' must not exceed the 'number_of_probers'."
)
result = 'require {0} from {1} {{ {2} }}'.format(self.number_of_probes, self.number_of_probers, 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 number_of_probes(self):
return self._get_availability_value('number_of_probes')
@property
def number_of_probers(self):
return self._get_availability_value('number_of_probers')
@property
def at_least(self):
return self._get_availability_value('at_least')
class Changes(Parameters): class Changes(Parameters):
pass pass
class UsableChanges(Changes):
@property
def monitors(self):
if self._values['monitors'] is None:
return None
return self._values['monitors']
@property
def members(self):
results = []
if self._values['members'] is None:
return None
for idx, member in enumerate(self._values['members']):
result = dict(
name=member,
memberOrder=idx
)
results.append(result)
return results
class ReportableChanges(Changes):
@property
def members(self):
results = []
if self._values['members'] is None:
return None
for member in self._values['members']:
parts = member.split(':')
results.append(dict(
server=fq_name(self.partition, parts[0]),
virtual_server=fq_name(self.partition, parts[1])
))
return results
class Difference(object): class Difference(object):
def __init__(self, want, have=None): def __init__(self, want, have=None):
self.want = want self.want = want
@ -352,6 +691,21 @@ class Difference(object):
enabled=True enabled=True
) )
@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'
if self.have.monitors is None:
return self.want.monitors
if self.have.monitors != self.want.monitors:
return self.want.monitors
class ModuleManager(object): class ModuleManager(object):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -396,8 +750,8 @@ class BaseManager(object):
self.module = kwargs.get('module', None) self.module = kwargs.get('module', None)
self.client = kwargs.get('client', None) self.client = kwargs.get('client', None)
self.have = None self.have = None
self.want = Parameters(params=self.module.params) self.want = ModuleParameters(params=self.module.params)
self.changes = Changes() self.changes = UsableChanges()
def _set_changed_options(self): def _set_changed_options(self):
changed = {} changed = {}
@ -405,7 +759,7 @@ class BaseManager(object):
if getattr(self.want, key) is not None: if getattr(self.want, key) is not None:
changed[key] = getattr(self.want, key) changed[key] = getattr(self.want, key)
if changed: if changed:
self.changes = Changes(params=changed) self.changes = UsableChanges(params=changed)
def _update_changed_options(self): def _update_changed_options(self):
diff = Difference(self.want, self.have) diff = Difference(self.want, self.have)
@ -421,7 +775,7 @@ class BaseManager(object):
else: else:
changed[k] = change changed[k] = change
if changed: if changed:
self.changes = Changes(params=changed) self.changes = UsableChanges(params=changed)
return True return True
return False return False
@ -438,11 +792,21 @@ class BaseManager(object):
except iControlUnexpectedHTTPError as e: except iControlUnexpectedHTTPError as e:
raise F5ModuleError(str(e)) raise F5ModuleError(str(e))
changes = self.changes.to_return() reportable = ReportableChanges(params=self.changes.to_return())
changes = reportable.to_return()
result.update(**changes) result.update(**changes)
result.update(dict(changed=changed)) result.update(dict(changed=changed))
self._announce_deprecations(result)
return 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): def present(self):
if self.exists(): if self.exists():
return self.update() return self.update()
@ -474,7 +838,14 @@ class BaseManager(object):
self.want.update({'disabled': True}) self.want.update({'disabled': True})
elif self.want.state in ['present', 'enabled']: elif self.want.state in ['present', 'enabled']:
self.want.update({'enabled': True}) self.want.update({'enabled': True})
self._set_changed_options() self._set_changed_options()
if self.want.availability_requirement_type == 'require' and len(self.want.monitors_list) > 1:
raise F5ModuleError(
"Only one monitor may be specified when using an availability_requirement type of 'require'"
)
if self.module.check_mode: if self.module.check_mode:
return True return True
self.create_on_device() self.create_on_device()
@ -491,6 +862,36 @@ class BaseManager(object):
raise F5ModuleError("Failed to delete the GTM pool") raise F5ModuleError("Failed to delete the GTM pool")
return True return True
def update_monitors_on_device(self):
"""Updates the monitors string on a virtual server
There is a long-standing bug in GTM virtual servers where the monitor value
is a string that includes braces. These braces cause the REST API to panic and
fail to update or create any resources that have an "at_least" or "require"
set of availability_requirements.
This method exists to do a tmsh command to cause the update to take place on
the device.
Preferably, this method can be removed and the bug be fixed. The API should
be working, obviously, but the more concerning issue is if tmsh commands change
over time, breaking this method.
"""
command = 'tmsh modify gtm pool {0} /{1}/{2} monitor {3}'.format(
self.want.type, self.want.partition, self.want.name, self.want.monitors
)
output = self.client.api.tm.util.bash.exec_cmd(
'run',
utilCmdArgs='-c "{0}"'.format(command)
)
try:
if hasattr(output, 'commandResult'):
if len(output.commandResult.strip()) > 0:
raise F5ModuleError(output.commandResult)
except (AttributeError, NameError, LazyAttributesRequired):
pass
return True
class TypedManager(BaseManager): class TypedManager(BaseManager):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -535,7 +936,10 @@ class TypedManager(BaseManager):
name=self.want.name, name=self.want.name,
partition=self.want.partition partition=self.want.partition
) )
if params:
result.modify(**params) result.modify(**params)
if self.want.monitors:
self.update_monitors_on_device()
def read_current_from_device(self): def read_current_from_device(self):
pools = self.client.api.tm.gtm.pools pools = self.client.api.tm.gtm.pools
@ -543,13 +947,18 @@ class TypedManager(BaseManager):
resource = getattr(collection, self.want.type) resource = getattr(collection, self.want.type)
result = resource.load( result = resource.load(
name=self.want.name, name=self.want.name,
partition=self.want.partition partition=self.want.partition,
requests_params=dict(
params=dict(
expandSubcollections='true'
)
)
) )
result = result.attrs result = result.attrs
return Parameters(params=result) return ApiParameters(params=result)
def create_on_device(self): def create_on_device(self):
params = self.want.api_params() params = self.changes.api_params()
pools = self.client.api.tm.gtm.pools pools = self.client.api.tm.gtm.pools
collection = getattr(pools, self.want.collection) collection = getattr(pools, self.want.collection)
resource = getattr(collection, self.want.type) resource = getattr(collection, self.want.type)
@ -558,6 +967,8 @@ class TypedManager(BaseManager):
partition=self.want.partition, partition=self.want.partition,
**params **params
) )
if self.want.monitors:
self.update_monitors_on_device()
def remove_from_device(self): def remove_from_device(self):
pools = self.client.api.tm.gtm.pools pools = self.client.api.tm.gtm.pools
@ -586,6 +997,8 @@ class UntypedManager(BaseManager):
partition=self.want.partition partition=self.want.partition
) )
resource.modify(**params) resource.modify(**params)
if self.want.monitors:
self.update_monitors_on_device()
def read_current_from_device(self): def read_current_from_device(self):
resource = self.client.api.tm.gtm.pools.pool.load( resource = self.client.api.tm.gtm.pools.pool.load(
@ -593,15 +1006,17 @@ class UntypedManager(BaseManager):
partition=self.want.partition partition=self.want.partition
) )
result = resource.attrs result = resource.attrs
return Parameters(params=result) return ApiParameters(params=result)
def create_on_device(self): def create_on_device(self):
params = self.want.api_params() params = self.changes.api_params()
self.client.api.tm.gtm.pools.pool.create( self.client.api.tm.gtm.pools.pool.create(
name=self.want.name, name=self.want.name,
partition=self.want.partition, partition=self.want.partition,
**params **params
) )
if self.want.monitors:
self.update_monitors_on_device()
def remove_from_device(self): def remove_from_device(self):
resource = self.client.api.tm.gtm.pools.pool.load( resource = self.client.api.tm.gtm.pools.pool.load(
@ -656,7 +1071,35 @@ class ArgumentSpec(object):
partition=dict( partition=dict(
default='Common', default='Common',
fallback=(env_fallback, ['F5_PARTITION']) fallback=(env_fallback, ['F5_PARTITION'])
),
members=dict(
type='list',
options=dict(
server=dict(required=True),
virtual_server=dict(required=True)
) )
),
availability_requirements=dict(
type='dict',
options=dict(
type=dict(
choices=['all', 'at_least', 'require'],
required=True
),
at_least=dict(type='int'),
number_of_probes=dict(type='int'),
number_of_probers=dict(type='int')
),
mutually_exclusive=[
['at_least', 'number_of_probes'],
['at_least', 'number_of_probers'],
],
required_if=[
['type', 'at_least', ['at_least']],
['type', 'require', ['number_of_probes', 'number_of_probers']]
]
),
monitors=dict(type='list'),
) )
self.argument_spec = {} self.argument_spec = {}
self.argument_spec.update(f5_argument_spec) self.argument_spec.update(f5_argument_spec)

View file

@ -18,7 +18,7 @@ module: bigip_hostname
short_description: Manage the hostname of a BIG-IP short_description: Manage the hostname of a BIG-IP
description: description:
- Manage the hostname of a BIG-IP. - Manage the hostname of a BIG-IP.
version_added: "2.3" version_added: 2.3
options: options:
hostname: hostname:
description: description:
@ -50,30 +50,23 @@ hostname:
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
HAS_DEVEL_IMPORTS = False
try: try:
# Sideband repository used for dev
from library.module_utils.network.f5.bigip import HAS_F5SDK 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.bigip import F5Client
from library.module_utils.network.f5.common import F5ModuleError 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 AnsibleF5Parameters
from library.module_utils.network.f5.common import cleanup_tokens 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 from library.module_utils.network.f5.common import f5_argument_spec
try: try:
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
except ImportError: except ImportError:
HAS_F5SDK = False HAS_F5SDK = False
HAS_DEVEL_IMPORTS = True
except ImportError: except ImportError:
# Upstream Ansible
from ansible.module_utils.network.f5.bigip import HAS_F5SDK 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.bigip import F5Client
from ansible.module_utils.network.f5.common import F5ModuleError 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 AnsibleF5Parameters
from ansible.module_utils.network.f5.common import cleanup_tokens 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 from ansible.module_utils.network.f5.common import f5_argument_spec
try: try:
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
@ -100,13 +93,55 @@ class Parameters(AnsibleF5Parameters):
return str(self._values['hostname']) return str(self._values['hostname'])
class ApiParameters(Parameters):
pass
class ModuleParameters(Parameters):
pass
class Changes(Parameters):
pass
class UsableChanges(Changes):
pass
class ReportableChanges(Changes):
pass
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
class ModuleManager(object): class ModuleManager(object):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.module = kwargs.get('module', None) self.module = kwargs.get('module', None)
self.client = kwargs.get('client', None) self.client = kwargs.get('client', None)
self.have = None self.have = ApiParameters()
self.want = Parameters(params=self.module.params) self.want = ModuleParameters(params=self.module.params)
self.changes = Parameters() self.changes = UsableChanges()
def _set_changed_options(self): def _set_changed_options(self):
changed = {} changed = {}
@ -114,18 +149,20 @@ class ModuleManager(object):
if getattr(self.want, key) is not None: if getattr(self.want, key) is not None:
changed[key] = getattr(self.want, key) changed[key] = getattr(self.want, key)
if changed: if changed:
self.changes = Parameters(params=changed) self.changes = UsableChanges(params=changed)
def _update_changed_options(self): def _update_changed_options(self):
changed = {} diff = Difference(self.want, self.have)
for key in Parameters.updatables: updatables = Parameters.updatables
if getattr(self.want, key) is not None: changed = dict()
attr1 = getattr(self.want, key) for k in updatables:
attr2 = getattr(self.have, key) change = diff.compare(k)
if attr1 != attr2: if change is None:
changed[key] = attr1 continue
self.changes = Parameters(params=changed) else:
changed[k] = change
if changed: if changed:
self.changes = UsableChanges(params=changed)
return True return True
return False return False
@ -137,15 +174,29 @@ class ModuleManager(object):
except iControlUnexpectedHTTPError as e: except iControlUnexpectedHTTPError as e:
raise F5ModuleError(str(e)) raise F5ModuleError(str(e))
changes = self.changes.to_return() reportable = ReportableChanges(params=self.changes.to_return())
changes = reportable.to_return()
result.update(**changes) result.update(**changes)
result.update(dict(changed=changed)) result.update(dict(changed=changed))
self._announce_deprecations(result)
return 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 read_current_from_device(self): def read_current_from_device(self):
resource = self.client.api.tm.sys.global_settings.load() resource = self.client.api.tm.sys.global_settings.load()
result = resource.attrs result = resource.attrs
return Parameters(params=result)
collection = self.client.api.tm.cm.devices.get_collection()
self_device = next((x.name for x in collection if x.selfDevice == "true"), None)
result['self_device'] = self_device
return ApiParameters(params=result)
def update(self): def update(self):
self.have = self.read_current_from_device() self.have = self.read_current_from_device()
@ -166,8 +217,9 @@ class ModuleManager(object):
params = self.want.api_params() params = self.want.api_params()
resource = self.client.api.tm.sys.global_settings.load() resource = self.client.api.tm.sys.global_settings.load()
resource.modify(**params) resource.modify(**params)
if self.have.self_device:
self.client.api.tm.cm.devices.exec_cmd( self.client.api.tm.cm.devices.exec_cmd(
'mv', name=self.have.hostname, target=self.want.hostname 'mv', name=self.have.self_device, target=self.want.hostname
) )

View file

@ -18,7 +18,11 @@ module: bigip_iapp_service
short_description: Manages TCL iApp services on a BIG-IP short_description: Manages TCL iApp services on a BIG-IP
description: description:
- Manages TCL iApp services on a BIG-IP. - Manages TCL iApp services on a BIG-IP.
version_added: "2.4" - If you are looking for the API that is communicated with on the BIG-IP,
the one the is used is C(/mgmt/tm/sys/application/service/). There are a
couple of APIs in a BIG-IP that might seem like they are relevant to iApp
Services, but the API mentioned here is the one that is used.
version_added: 2.4
options: options:
name: name:
description: description:
@ -46,6 +50,7 @@ options:
This option is equivalent to re-configuring the iApp if that template This option is equivalent to re-configuring the iApp if that template
has changed. has changed.
default: no default: no
type: bool
state: state:
description: description:
- When C(present), ensures that the iApp service is created and running. - When C(present), ensures that the iApp service is created and running.
@ -214,30 +219,25 @@ from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback from ansible.module_utils.basic import env_fallback
from ansible.module_utils.six import iteritems from ansible.module_utils.six import iteritems
HAS_DEVEL_IMPORTS = False
try: try:
# Sideband repository used for dev
from library.module_utils.network.f5.bigip import HAS_F5SDK 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.bigip import F5Client
from library.module_utils.network.f5.common import F5ModuleError 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 AnsibleF5Parameters
from library.module_utils.network.f5.common import cleanup_tokens 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 from library.module_utils.network.f5.common import f5_argument_spec
try: try:
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
except ImportError: except ImportError:
HAS_F5SDK = False HAS_F5SDK = False
HAS_DEVEL_IMPORTS = True
except ImportError: except ImportError:
# Upstream Ansible
from ansible.module_utils.network.f5.bigip import HAS_F5SDK 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.bigip import F5Client
from ansible.module_utils.network.f5.common import F5ModuleError 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 AnsibleF5Parameters
from ansible.module_utils.network.f5.common import cleanup_tokens 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 from ansible.module_utils.network.f5.common import f5_argument_spec
try: try:
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
@ -250,18 +250,16 @@ class Parameters(AnsibleF5Parameters):
'strictUpdates': 'strict_updates', 'strictUpdates': 'strict_updates',
'trafficGroup': 'traffic_group', 'trafficGroup': 'traffic_group',
} }
returnables = [] returnables = []
api_attributes = [ api_attributes = [
'tables', 'variables', 'template', 'lists', 'deviceGroup', 'tables', 'variables', 'template', 'lists', 'deviceGroup',
'inheritedDevicegroup', 'inheritedTrafficGroup', 'trafficGroup', 'inheritedDevicegroup', 'inheritedTrafficGroup', 'trafficGroup',
'strictUpdates' 'strictUpdates'
] ]
updatables = ['tables', 'variables', 'lists', 'strict_updates', 'traffic_group']
def _fqdn_name(self, value): updatables = ['tables', 'variables', 'lists', 'strict_updates', 'traffic_group']
if value is not None and not value.startswith('/'):
return '/{0}/{1}'.format(self.partition, value)
return value
def to_return(self): def to_return(self):
result = {} result = {}
@ -388,7 +386,7 @@ class Parameters(AnsibleF5Parameters):
def template(self): def template(self):
if self._values['template'] is None: if self._values['template'] is None:
return None return None
return self._fqdn_name(self._values['template']) return fq_name(self.partition, self._values['template'])
@template.setter @template.setter
def template(self, value): def template(self, value):
@ -419,14 +417,14 @@ class Parameters(AnsibleF5Parameters):
# Specifying the value overrides any associated value in the payload # Specifying the value overrides any associated value in the payload
elif self._values['traffic_group']: elif self._values['traffic_group']:
result = self._fqdn_name(self._values['traffic_group']) result = fq_name(self.partition, self._values['traffic_group'])
# This will be automatically `None` if it was not set by the # This will be automatically `None` if it was not set by the
# `parameters` setter # `parameters` setter
elif self.trafficGroup: elif self.trafficGroup:
result = self._fqdn_name(self.trafficGroup) result = fq_name(self.partition, self.trafficGroup)
else: else:
result = self._fqdn_name(self._values['traffic_group']) result = fq_name(self.partition, self._values['traffic_group'])
if result.startswith('/Common/'): if result.startswith('/Common/'):
return result return result
else: else:

View file

@ -30,7 +30,7 @@ description:
existing services are changed to consume that new template. As such, existing services are changed to consume that new template. As such,
the ability to update templates in-place requires the C(force) option the ability to update templates in-place requires the C(force) option
to be used. to be used.
version_added: "2.4" version_added: 2.4
options: options:
force: force:
description: description:
@ -108,39 +108,29 @@ import uuid
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback from ansible.module_utils.basic import env_fallback
HAS_DEVEL_IMPORTS = False
try: try:
# Sideband repository used for dev
from library.module_utils.network.f5.bigip import HAS_F5SDK 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.bigip import F5Client
from library.module_utils.network.f5.common import F5ModuleError 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 AnsibleF5Parameters
from library.module_utils.network.f5.common import cleanup_tokens 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 from library.module_utils.network.f5.common import f5_argument_spec
try: try:
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
from f5.utils.iapp_parser import NonextantTemplateNameException
except ImportError: except ImportError:
HAS_F5SDK = False HAS_F5SDK = False
HAS_DEVEL_IMPORTS = True
except ImportError: except ImportError:
# Upstream Ansible
from ansible.module_utils.network.f5.bigip import HAS_F5SDK 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.bigip import F5Client
from ansible.module_utils.network.f5.common import F5ModuleError 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 AnsibleF5Parameters
from ansible.module_utils.network.f5.common import cleanup_tokens 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 from ansible.module_utils.network.f5.common import f5_argument_spec
try: try:
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
except ImportError:
HAS_F5SDK = False
try:
from f5.utils.iapp_parser import NonextantTemplateNameException from f5.utils.iapp_parser import NonextantTemplateNameException
except ImportError: except ImportError:
HAS_F5SDK = False HAS_F5SDK = False
try: try:
@ -151,6 +141,7 @@ except ImportError:
class Parameters(AnsibleF5Parameters): class Parameters(AnsibleF5Parameters):
api_attributes = [] api_attributes = []
returnables = [] returnables = []
@property @property

View file

@ -19,7 +19,7 @@ short_description: Manages Javascript iApp packages on a BIG-IP
description: description:
- Manages Javascript iApp packages on a BIG-IP. This module will allow - Manages Javascript iApp packages on a BIG-IP. This module will allow
you to deploy iAppLX packages to the BIG-IP and manage their lifecycle. you to deploy iAppLX packages to the BIG-IP and manage their lifecycle.
version_added: "2.5" version_added: 2.5
options: options:
package: package:
description: description:
@ -88,33 +88,26 @@ import os
import subprocess import subprocess
import time import time
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from distutils.version import LooseVersion
HAS_DEVEL_IMPORTS = False
try: try:
# Sideband repository used for dev
from library.module_utils.network.f5.bigip import HAS_F5SDK 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.bigip import F5Client
from library.module_utils.network.f5.common import F5ModuleError 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 AnsibleF5Parameters
from library.module_utils.network.f5.common import cleanup_tokens 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 from library.module_utils.network.f5.common import f5_argument_spec
try: try:
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
except ImportError: except ImportError:
HAS_F5SDK = False HAS_F5SDK = False
HAS_DEVEL_IMPORTS = True
except ImportError: except ImportError:
# Upstream Ansible
from ansible.module_utils.network.f5.bigip import HAS_F5SDK 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.bigip import F5Client
from ansible.module_utils.network.f5.common import F5ModuleError 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 AnsibleF5Parameters
from ansible.module_utils.network.f5.common import cleanup_tokens 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 from ansible.module_utils.network.f5.common import f5_argument_spec
try: try:
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError

View file

@ -179,7 +179,7 @@ conditions:
type: complex type: complex
contains: contains:
type: type:
description: The condition type description: The condition type.
returned: changed returned: changed
type: string type: string
sample: http_uri sample: http_uri

View file

@ -66,6 +66,11 @@ options:
- The route domain id of the system. When creating a new static route, if - The route domain id of the system. When creating a new static route, if
this value is not specified, a default value of C(0) will be used. this value is not specified, a default value of C(0) will be used.
- This value cannot be changed once it is set. - This value cannot be changed once it is set.
partition:
description:
- Device partition to manage resources on.
default: Common
version_added: 2.6
state: state:
description: description:
- When C(present), ensures that the static route exists. - When C(present), ensures that the static route exists.
@ -129,6 +134,11 @@ pool:
returned: changed returned: changed
type: string type: string
sample: true sample: true
partition:
description: The partition that the static route was created on.
returned: changed
type: string
sample: Common
description: description:
description: Whether the banner is enabled or not. description: Whether the banner is enabled or not.
returned: changed returned: changed
@ -144,6 +154,7 @@ reject:
import re import re
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE
try: try:
@ -575,6 +586,10 @@ class ArgumentSpec(object):
default='present', default='present',
choices=['absent', 'present'] choices=['absent', 'present']
), ),
partition=dict(
default='Common',
fallback=(env_fallback, ['F5_PARTITION'])
),
route_domain=dict(type='int') route_domain=dict(type='int')
) )
self.argument_spec = {} self.argument_spec = {}

View file

@ -0,0 +1,94 @@
{
"kind": "tm:gtm:pool:a:astate",
"name": "foo",
"partition": "Common",
"fullPath": "/Common/foo",
"generation": 142,
"selfLink": "https://localhost/mgmt/tm/gtm/pool/a/~Common~foo?expandSubcollections=true&ver=12.0.0",
"alternateMode": "round-robin",
"dynamicRatio": "disabled",
"enabled": true,
"fallbackIp": "any",
"fallbackMode": "return-to-dns",
"limitMaxBps": 0,
"limitMaxBpsStatus": "disabled",
"limitMaxConnections": 0,
"limitMaxConnectionsStatus": "disabled",
"limitMaxPps": 0,
"limitMaxPpsStatus": "disabled",
"loadBalancingMode": "round-robin",
"manualResume": "disabled",
"maxAnswersReturned": 1,
"monitor": "default",
"qosHitRatio": 5,
"qosHops": 0,
"qosKilobytesSecond": 3,
"qosLcs": 30,
"qosPacketRate": 1,
"qosRtt": 50,
"qosTopology": 0,
"qosVsCapacity": 0,
"qosVsScore": 0,
"ttl": 30,
"verifyMemberAvailability": "enabled",
"membersReference": {
"link": "https://localhost/mgmt/tm/gtm/pool/a/~Common~foo/members?ver=12.0.0",
"isSubcollection": true,
"items": [
{
"kind": "tm:gtm:pool:a:members:membersstate",
"name": "server1:vs1",
"partition": "Common",
"fullPath": "/Common/server1:vs1",
"generation": 141,
"selfLink": "https://localhost/mgmt/tm/gtm/pool/a/~Common~foo/members/~Common~server1:vs1?ver=12.0.0",
"enabled": true,
"limitMaxBps": 0,
"limitMaxBpsStatus": "disabled",
"limitMaxConnections": 0,
"limitMaxConnectionsStatus": "disabled",
"limitMaxPps": 0,
"limitMaxPpsStatus": "disabled",
"memberOrder": 0,
"monitor": "default",
"ratio": 1
},
{
"kind": "tm:gtm:pool:a:members:membersstate",
"name": "server1:vs2",
"partition": "Common",
"fullPath": "/Common/server1:vs2",
"generation": 142,
"selfLink": "https://localhost/mgmt/tm/gtm/pool/a/~Common~foo/members/~Common~server1:vs1?ver=12.0.0",
"enabled": true,
"limitMaxBps": 0,
"limitMaxBpsStatus": "disabled",
"limitMaxConnections": 0,
"limitMaxConnectionsStatus": "disabled",
"limitMaxPps": 0,
"limitMaxPpsStatus": "disabled",
"memberOrder": 1,
"monitor": "/Common/tcp ",
"ratio": 1
},
{
"kind": "tm:gtm:pool:a:members:membersstate",
"name": "server1:vs3",
"partition": "Common",
"fullPath": "/Common/server1:vs3",
"generation": 141,
"selfLink": "https://localhost/mgmt/tm/gtm/pool/a/~Common~foo/members/~Common~server1:vs3?ver=12.0.0",
"enabled": true,
"limitMaxBps": 0,
"limitMaxBpsStatus": "disabled",
"limitMaxConnections": 0,
"limitMaxConnectionsStatus": "disabled",
"limitMaxPps": 0,
"limitMaxPpsStatus": "disabled",
"memberOrder": 2,
"monitor": "default",
"ratio": 1
}
]
}
}

View file

@ -20,17 +20,19 @@ from ansible.compat.tests.mock import patch
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
try: try:
from library.bigip_gtm_pool import Parameters from library.modules.bigip_gtm_pool import ApiParameters
from library.bigip_gtm_pool import ModuleManager from library.modules.bigip_gtm_pool import ModuleParameters
from library.bigip_gtm_pool import ArgumentSpec from library.modules.bigip_gtm_pool import ModuleManager
from library.bigip_gtm_pool import UntypedManager from library.modules.bigip_gtm_pool import ArgumentSpec
from library.bigip_gtm_pool import TypedManager from library.modules.bigip_gtm_pool import UntypedManager
from library.modules.bigip_gtm_pool import TypedManager
from library.module_utils.network.f5.common import F5ModuleError from library.module_utils.network.f5.common import F5ModuleError
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
from test.unit.modules.utils import set_module_args from test.unit.modules.utils import set_module_args
except ImportError: except ImportError:
try: try:
from ansible.modules.network.f5.bigip_gtm_pool import Parameters from ansible.modules.network.f5.bigip_gtm_pool import ApiParameters
from ansible.modules.network.f5.bigip_gtm_pool import ModuleParameters
from ansible.modules.network.f5.bigip_gtm_pool import ModuleManager from ansible.modules.network.f5.bigip_gtm_pool import ModuleManager
from ansible.modules.network.f5.bigip_gtm_pool import ArgumentSpec from ansible.modules.network.f5.bigip_gtm_pool import ArgumentSpec
from ansible.modules.network.f5.bigip_gtm_pool import UntypedManager from ansible.modules.network.f5.bigip_gtm_pool import UntypedManager
@ -73,7 +75,7 @@ class TestParameters(unittest.TestCase):
fallback_ip='10.10.10.10', fallback_ip='10.10.10.10',
type='a' type='a'
) )
p = Parameters(params=args) p = ModuleParameters(params=args)
assert p.name == 'foo' assert p.name == 'foo'
assert p.preferred_lb_method == 'topology' assert p.preferred_lb_method == 'topology'
assert p.alternate_lb_method == 'ratio' assert p.alternate_lb_method == 'ratio'
@ -81,6 +83,20 @@ class TestParameters(unittest.TestCase):
assert p.fallback_ip == '10.10.10.10' assert p.fallback_ip == '10.10.10.10'
assert p.type == 'a' assert p.type == 'a'
def test_module_parameters_members(self):
args = dict(
partition='Common',
members=[
dict(
server='foo',
virtual_server='bar'
)
]
)
p = ModuleParameters(params=args)
assert len(p.members) == 1
assert p.members[0] == '/Common/foo:bar'
def test_api_parameters(self): def test_api_parameters(self):
args = dict( args = dict(
name='foo', name='foo',
@ -89,13 +105,21 @@ class TestParameters(unittest.TestCase):
fallbackMode='fewest-hops', fallbackMode='fewest-hops',
fallbackIp='10.10.10.10' fallbackIp='10.10.10.10'
) )
p = Parameters(params=args) p = ApiParameters(params=args)
assert p.name == 'foo' assert p.name == 'foo'
assert p.preferred_lb_method == 'topology' assert p.preferred_lb_method == 'topology'
assert p.alternate_lb_method == 'ratio' assert p.alternate_lb_method == 'ratio'
assert p.fallback_lb_method == 'fewest-hops' assert p.fallback_lb_method == 'fewest-hops'
assert p.fallback_ip == '10.10.10.10' assert p.fallback_ip == '10.10.10.10'
def test_api_parameters_members(self):
args = load_fixture('load_gtm_pool_a_with_members_1.json')
p = ApiParameters(params=args)
assert len(p.members) == 3
assert p.members[0] == '/Common/server1:vs1'
assert p.members[1] == '/Common/server1:vs2'
assert p.members[2] == '/Common/server1:vs3'
class TestUntypedManager(unittest.TestCase): class TestUntypedManager(unittest.TestCase):
@ -148,7 +172,7 @@ class TestUntypedManager(unittest.TestCase):
supports_check_mode=self.spec.supports_check_mode supports_check_mode=self.spec.supports_check_mode
) )
current = Parameters(params=load_fixture('load_gtm_pool_untyped_default.json')) current = ApiParameters(params=load_fixture('load_gtm_pool_untyped_default.json'))
# Override methods in the specific type of manager # Override methods in the specific type of manager
tm = UntypedManager(module=module) tm = UntypedManager(module=module)
@ -252,7 +276,7 @@ class TestTypedManager(unittest.TestCase):
supports_check_mode=self.spec.supports_check_mode supports_check_mode=self.spec.supports_check_mode
) )
current = Parameters(params=load_fixture('load_gtm_pool_a_default.json')) current = ApiParameters(params=load_fixture('load_gtm_pool_a_default.json'))
# Override methods in the specific type of manager # Override methods in the specific type of manager
tm = TypedManager(module=module) tm = TypedManager(module=module)

View file

@ -20,15 +20,17 @@ from ansible.compat.tests.mock import patch
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
try: try:
from library.bigip_hostname import Parameters from library.modules.bigip_hostname import ApiParameters
from library.bigip_hostname import ModuleManager from library.modules.bigip_hostname import ModuleParameters
from library.bigip_hostname import ArgumentSpec from library.modules.bigip_hostname import ModuleManager
from library.modules.bigip_hostname import ArgumentSpec
from library.module_utils.network.f5.common import F5ModuleError from library.module_utils.network.f5.common import F5ModuleError
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
from test.unit.modules.utils import set_module_args from test.unit.modules.utils import set_module_args
except ImportError: except ImportError:
try: try:
from ansible.modules.network.f5.bigip_hostname import Parameters from ansible.modules.network.f5.bigip_hostname import ApiParameters
from ansible.modules.network.f5.bigip_hostname import ModuleParameters
from ansible.modules.network.f5.bigip_hostname import ModuleManager from ansible.modules.network.f5.bigip_hostname import ModuleManager
from ansible.modules.network.f5.bigip_hostname import ArgumentSpec from ansible.modules.network.f5.bigip_hostname import ArgumentSpec
from ansible.module_utils.network.f5.common import F5ModuleError from ansible.module_utils.network.f5.common import F5ModuleError
@ -64,7 +66,7 @@ class TestParameters(unittest.TestCase):
args = dict( args = dict(
hostname='foo.internal.com' hostname='foo.internal.com'
) )
p = Parameters(params=args) p = ModuleParameters(params=args)
assert p.hostname == 'foo.internal.com' assert p.hostname == 'foo.internal.com'
@ -83,8 +85,8 @@ class TestManager(unittest.TestCase):
# Configure the parameters that would be returned by querying the # Configure the parameters that would be returned by querying the
# remote device # remote device
current = Parameters( current = ApiParameters(
dict( params=dict(
hostname='foo.internal.com' hostname='foo.internal.com'
) )
) )

View file

@ -20,9 +20,9 @@ from ansible.compat.tests.mock import patch
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
try: try:
from library.bigip_iapp_service import Parameters from library.modules.bigip_iapp_service import Parameters
from library.bigip_iapp_service import ModuleManager from library.modules.bigip_iapp_service import ModuleManager
from library.bigip_iapp_service import ArgumentSpec from library.modules.bigip_iapp_service import ArgumentSpec
from library.module_utils.network.f5.common import F5ModuleError from library.module_utils.network.f5.common import F5ModuleError
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
from test.unit.modules.utils import set_module_args from test.unit.modules.utils import set_module_args

View file

@ -20,9 +20,9 @@ from ansible.compat.tests.mock import patch
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
try: try:
from library.bigip_iapp_template import Parameters from library.modules.bigip_iapp_template import Parameters
from library.bigip_iapp_template import ModuleManager from library.modules.bigip_iapp_template import ModuleManager
from library.bigip_iapp_template import ArgumentSpec from library.modules.bigip_iapp_template import ArgumentSpec
from library.module_utils.network.f5.common import F5ModuleError from library.module_utils.network.f5.common import F5ModuleError
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
from test.unit.modules.utils import set_module_args from test.unit.modules.utils import set_module_args

View file

@ -20,9 +20,9 @@ from ansible.compat.tests.mock import patch
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
try: try:
from library.bigip_iapp_template import Parameters from library.modules.bigip_iapp_template import Parameters
from library.bigip_iapp_template import ModuleManager from library.modules.bigip_iapp_template import ModuleManager
from library.bigip_iapp_template import ArgumentSpec from library.modules.bigip_iapp_template import ArgumentSpec
from library.module_utils.network.f5.common import F5ModuleError from library.module_utils.network.f5.common import F5ModuleError
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
from test.unit.modules.utils import set_module_args from test.unit.modules.utils import set_module_args