Add new features to gtm server module (#48507)
Remove the f5-sdk. Fix unit tests. Add new prober parameters
This commit is contained in:
parent
364ed4e660
commit
4a74c1fec6
2 changed files with 722 additions and 87 deletions
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2017 F5 Networks Inc.
|
||||
# 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
|
||||
|
@ -127,10 +127,88 @@ options:
|
|||
agent.
|
||||
type: bool
|
||||
version_added: 2.7
|
||||
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.8
|
||||
availability_requirements:
|
||||
description:
|
||||
- Specifies, if you activate more than one health monitor, the number of health
|
||||
monitors that must receive successful responses in order for the link to be
|
||||
considered available.
|
||||
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.8
|
||||
prober_preference:
|
||||
description:
|
||||
- Specifies the type of prober to use to monitor this server's resources.
|
||||
- This option is ignored in C(TMOS) version C(12.x).
|
||||
- From C(TMOS) version C(13.x) and up, when prober_preference is set to C(pool)
|
||||
a C(prober_pool) parameter must be specified.
|
||||
choices:
|
||||
- inside-datacenter
|
||||
- outside-datacenter
|
||||
- inherit
|
||||
- pool
|
||||
version_added: 2.8
|
||||
prober_fallback:
|
||||
description:
|
||||
- Specifies the type of prober to use to monitor this server's resources
|
||||
when the preferred prober is not available.
|
||||
- This option is ignored in C(TMOS) version C(12.x).
|
||||
- From C(TMOS) version C(13.x) and up, when prober_preference is set to C(pool)
|
||||
a C(prober_pool) parameter must be specified.
|
||||
- The choices are mutually exclusive with prober_preference parameter,
|
||||
with the exception of C(any-available) or C(none) option.
|
||||
choices:
|
||||
- any
|
||||
- inside-datacenter
|
||||
- outside-datacenter
|
||||
- inherit
|
||||
- pool
|
||||
- none
|
||||
version_added: 2.8
|
||||
prober_pool:
|
||||
description:
|
||||
- Specifies the name of the prober pool to use to monitor this server's resources.
|
||||
- From C(TMOS) version C(13.x) and up, this parameter is mandatory when C(prober_preference) is set to C(pool).
|
||||
- Format of the name can be either be prepended by partition (C(/Common/foo)), or specified
|
||||
just as an object name (C(foo)).
|
||||
- In C(TMOS) version C(12.x) prober_pool can be set to empty string to revert to default setting of inherit.
|
||||
version_added: 2.8
|
||||
extends_documentation_fragment: f5
|
||||
author:
|
||||
- Robert Teller
|
||||
- Tim Rupp (@caphrim007)
|
||||
- Wojciech Wypior (@wojtek0806)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
|
@ -183,6 +261,11 @@ EXAMPLES = r'''
|
|||
'''
|
||||
|
||||
RETURN = r'''
|
||||
monitors:
|
||||
description: The new list of monitors for the resource.
|
||||
returned: changed
|
||||
type: list
|
||||
sample: ['/Common/monitor1', '/Common/monitor2']
|
||||
link_discovery:
|
||||
description: The new C(link_discovery) configured on the remote device.
|
||||
returned: changed
|
||||
|
@ -205,34 +288,38 @@ datacenter:
|
|||
sample: datacenter01
|
||||
'''
|
||||
|
||||
import re
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.basic import env_fallback
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
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.bigip import F5RestClient
|
||||
from library.module_utils.network.f5.common import F5ModuleError
|
||||
from library.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from library.module_utils.network.f5.common import cleanup_tokens
|
||||
from library.module_utils.network.f5.common import f5_argument_spec
|
||||
from library.module_utils.network.f5.common import fq_name
|
||||
try:
|
||||
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
from library.module_utils.network.f5.common import f5_argument_spec
|
||||
from library.module_utils.network.f5.common import transform_name
|
||||
from library.module_utils.network.f5.common import exit_json
|
||||
from library.module_utils.network.f5.common import fail_json
|
||||
from library.module_utils.network.f5.common import is_empty_list
|
||||
from library.module_utils.network.f5.icontrol import tmos_version
|
||||
from library.module_utils.network.f5.icontrol import module_provisioned
|
||||
except ImportError:
|
||||
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 F5RestClient
|
||||
from ansible.module_utils.network.f5.common import F5ModuleError
|
||||
from ansible.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from ansible.module_utils.network.f5.common import cleanup_tokens
|
||||
from ansible.module_utils.network.f5.common import f5_argument_spec
|
||||
from ansible.module_utils.network.f5.common import fq_name
|
||||
try:
|
||||
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
from ansible.module_utils.network.f5.common import f5_argument_spec
|
||||
from ansible.module_utils.network.f5.common import transform_name
|
||||
from ansible.module_utils.network.f5.common import exit_json
|
||||
from ansible.module_utils.network.f5.common import fail_json
|
||||
from ansible.module_utils.network.f5.common import is_empty_list
|
||||
from ansible.module_utils.network.f5.icontrol import tmos_version
|
||||
from ansible.module_utils.network.f5.icontrol import module_provisioned
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
|
@ -252,6 +339,10 @@ class Parameters(AnsibleF5Parameters):
|
|||
'iqAllowPath': 'iquery_allow_path',
|
||||
'iqAllowServiceCheck': 'iquery_allow_service_check',
|
||||
'iqAllowSnmp': 'iquery_allow_snmp',
|
||||
'monitor': 'monitors',
|
||||
'proberPreference': 'prober_preference',
|
||||
'proberPool': 'prober_pool',
|
||||
'proberFallback': 'prober_fallback',
|
||||
}
|
||||
|
||||
api_attributes = [
|
||||
|
@ -265,6 +356,10 @@ class Parameters(AnsibleF5Parameters):
|
|||
'iqAllowPath',
|
||||
'iqAllowServiceCheck',
|
||||
'iqAllowSnmp',
|
||||
'monitor',
|
||||
'proberPreference',
|
||||
'proberPool',
|
||||
'proberFallback',
|
||||
]
|
||||
|
||||
updatables = [
|
||||
|
@ -276,6 +371,10 @@ class Parameters(AnsibleF5Parameters):
|
|||
'iquery_allow_path',
|
||||
'iquery_allow_service_check',
|
||||
'iquery_allow_snmp',
|
||||
'monitors',
|
||||
'prober_preference',
|
||||
'prober_pool',
|
||||
'prober_fallback',
|
||||
]
|
||||
|
||||
returnables = [
|
||||
|
@ -287,6 +386,12 @@ class Parameters(AnsibleF5Parameters):
|
|||
'iquery_allow_path',
|
||||
'iquery_allow_service_check',
|
||||
'iquery_allow_snmp',
|
||||
'devices',
|
||||
'monitors',
|
||||
'availability_requirements',
|
||||
'prober_preference',
|
||||
'prober_pool',
|
||||
'prober_fallback',
|
||||
]
|
||||
|
||||
|
||||
|
@ -348,6 +453,111 @@ class ApiParameters(Parameters):
|
|||
return True
|
||||
return False
|
||||
|
||||
@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'] == '/Common/bigip':
|
||||
return '/Common/bigip'
|
||||
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):
|
||||
@property
|
||||
|
@ -383,11 +593,6 @@ class ModuleParameters(Parameters):
|
|||
})
|
||||
return result
|
||||
|
||||
def devices_list(self):
|
||||
if self._values['devices'] is None:
|
||||
return None
|
||||
return self._values['devices']
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
if self._values['state'] in ['present', 'enabled']:
|
||||
|
@ -435,6 +640,83 @@ class ModuleParameters(Parameters):
|
|||
return None
|
||||
return self._values['iquery_options']['allow_snmp']
|
||||
|
||||
@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 is_empty_list(self._values['monitors']):
|
||||
return '/Common/bigip'
|
||||
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
|
||||
|
||||
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 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')
|
||||
|
||||
@property
|
||||
def prober_pool(self):
|
||||
if self._values['prober_pool'] is None:
|
||||
return None
|
||||
if self._values['prober_pool'] == '':
|
||||
return self._values['prober_pool']
|
||||
result = fq_name(self.partition, self._values['prober_pool'])
|
||||
return result
|
||||
|
||||
@property
|
||||
def prober_fallback(self):
|
||||
if self._values['prober_fallback'] == 'any':
|
||||
return 'any-available'
|
||||
return self._values['prober_fallback']
|
||||
|
||||
|
||||
class Changes(Parameters):
|
||||
def to_return(self):
|
||||
|
@ -446,6 +728,19 @@ class Changes(Parameters):
|
|||
|
||||
|
||||
class UsableChanges(Changes):
|
||||
@property
|
||||
def monitors(self):
|
||||
monitor_string = self._values['monitors']
|
||||
if monitor_string is None:
|
||||
return None
|
||||
|
||||
if '{' in monitor_string and '}':
|
||||
tmp = monitor_string.strip('}').split('{')
|
||||
monitor = ''.join(tmp).rstrip()
|
||||
return monitor
|
||||
|
||||
return monitor_string
|
||||
|
||||
@property
|
||||
def iquery_allow_path(self):
|
||||
if self._values['iquery_allow_path'] is None:
|
||||
|
@ -478,6 +773,111 @@ class ReportableChanges(Changes):
|
|||
return 'bigip'
|
||||
return self._values['server_type']
|
||||
|
||||
@property
|
||||
def monitors(self):
|
||||
if self._values['monitors'] is None:
|
||||
return []
|
||||
try:
|
||||
result = re.findall(r'/\w+/[^\s}]+', self._values['monitors'])
|
||||
result.sort()
|
||||
return result
|
||||
except Exception:
|
||||
return self._values['monitors']
|
||||
|
||||
@property
|
||||
def availability_requirement_type(self):
|
||||
if self._values['monitors'] is None:
|
||||
return None
|
||||
if 'min ' in self._values['monitors']:
|
||||
return 'at_least'
|
||||
elif 'require ' in self._values['monitors']:
|
||||
return 'require'
|
||||
else:
|
||||
return 'all'
|
||||
|
||||
@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 int(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 int(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 int(matches.group('least'))
|
||||
|
||||
@property
|
||||
def availability_requirements(self):
|
||||
if self._values['monitors'] is None:
|
||||
return None
|
||||
result = dict()
|
||||
result['type'] = self.availability_requirement_type
|
||||
result['at_least'] = self.at_least
|
||||
result['number_of_probers'] = self.number_of_probers
|
||||
result['number_of_probes'] = self.number_of_probes
|
||||
return result
|
||||
|
||||
@property
|
||||
def prober_fallback(self):
|
||||
if self._values['prober_fallback'] == 'any-available':
|
||||
return 'any'
|
||||
return self._values['prober_fallback']
|
||||
|
||||
|
||||
class Difference(object):
|
||||
def __init__(self, want, have=None):
|
||||
|
@ -612,8 +1012,8 @@ class Difference(object):
|
|||
server_change = self._server_type_changed()
|
||||
if not devices_change and not server_change:
|
||||
return None
|
||||
tmos_version = self.client.api.tmos_version
|
||||
if LooseVersion(tmos_version) >= LooseVersion('13.0.0'):
|
||||
tmos = tmos_version(self.client)
|
||||
if LooseVersion(tmos) >= LooseVersion('13.0.0'):
|
||||
result = self._handle_current_server_type_and_devices(
|
||||
devices_change, server_change
|
||||
)
|
||||
|
@ -631,6 +1031,95 @@ class Difference(object):
|
|||
elif self.want.state in ['present', 'enabled'] and self.have.disabled:
|
||||
return dict(enabled=True)
|
||||
|
||||
@property
|
||||
def monitors(self):
|
||||
if self.want.monitors is None:
|
||||
return None
|
||||
if self.want.monitors == '/Common/bigip' and self.have.monitors == '/Common/bigip':
|
||||
return None
|
||||
if self.want.monitors == '/Common/bigip' and self.have.monitors is None:
|
||||
return None
|
||||
if self.want.monitors == '/Common/bigip' and len(self.have.monitors) > 0:
|
||||
return '/Common/bigip'
|
||||
if self.have.monitors is None:
|
||||
return self.want.monitors
|
||||
if self.have.monitors != self.want.monitors:
|
||||
return self.want.monitors
|
||||
|
||||
@property
|
||||
def prober_pool(self):
|
||||
if self.want.prober_pool is None:
|
||||
return None
|
||||
if self.have.prober_pool is None:
|
||||
if self.want.prober_pool == '':
|
||||
return None
|
||||
if self.want.prober_pool != self.have.prober_pool:
|
||||
return self.want.prober_pool
|
||||
|
||||
@property
|
||||
def prober_preference(self):
|
||||
if self.want.prober_preference is None:
|
||||
return None
|
||||
if self.want.prober_preference == self.have.prober_preference:
|
||||
return None
|
||||
if self.want.prober_preference == 'pool' and self.want.prober_pool is None:
|
||||
raise F5ModuleError(
|
||||
"A prober_pool needs to be set if prober_preference is set to 'pool'"
|
||||
)
|
||||
if self.want.prober_preference != 'pool' and self.have.prober_preference == 'pool':
|
||||
if self.want.prober_fallback != 'pool' and self.want.prober_pool != '':
|
||||
raise F5ModuleError(
|
||||
"To change prober_preference from {0} to {1}, set prober_pool to an empty string".format(
|
||||
self.have.prober_preference,
|
||||
self.want.prober_preference
|
||||
)
|
||||
)
|
||||
if self.want.prober_preference == self.want.prober_fallback:
|
||||
raise F5ModuleError(
|
||||
"Prober_preference and prober_fallback must not be equal."
|
||||
)
|
||||
if self.want.prober_preference == self.have.prober_fallback:
|
||||
raise F5ModuleError(
|
||||
"Cannot set prober_preference to {0} if prober_fallback on device is set to {1}.".format(
|
||||
self.want.prober_preference,
|
||||
self.have.prober_fallback
|
||||
)
|
||||
)
|
||||
if self.want.prober_preference != self.have.prober_preference:
|
||||
return self.want.prober_preference
|
||||
|
||||
@property
|
||||
def prober_fallback(self):
|
||||
if self.want.prober_fallback is None:
|
||||
return None
|
||||
if self.want.prober_fallback == self.have.prober_fallback:
|
||||
return None
|
||||
if self.want.prober_fallback == 'pool' and self.want.prober_pool is None:
|
||||
raise F5ModuleError(
|
||||
"A prober_pool needs to be set if prober_fallback is set to 'pool'"
|
||||
)
|
||||
if self.want.prober_fallback != 'pool' and self.have.prober_fallback == 'pool':
|
||||
if self.want.prober_preference != 'pool' and self.want.prober_pool != '':
|
||||
raise F5ModuleError(
|
||||
"To change prober_fallback from {0} to {1}, set prober_pool to an empty string".format(
|
||||
self.have.prober_fallback,
|
||||
self.want.prober_fallback
|
||||
)
|
||||
)
|
||||
if self.want.prober_preference == self.want.prober_fallback:
|
||||
raise F5ModuleError(
|
||||
"Prober_preference and prober_fallback must not be equal."
|
||||
)
|
||||
if self.want.prober_fallback == self.have.prober_preference:
|
||||
raise F5ModuleError(
|
||||
"Cannot set prober_fallback to {0} if prober_preference on device is set to {1}.".format(
|
||||
self.want.prober_fallback,
|
||||
self.have.prober_preference
|
||||
)
|
||||
)
|
||||
if self.want.prober_fallback != self.have.prober_fallback:
|
||||
return self.want.prober_fallback
|
||||
|
||||
|
||||
class ModuleManager(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -639,7 +1128,7 @@ class ModuleManager(object):
|
|||
self.kwargs = kwargs
|
||||
|
||||
def exec_module(self):
|
||||
if not self.gtm_provisioned():
|
||||
if not module_provisioned(self.client, 'gtm'):
|
||||
raise F5ModuleError(
|
||||
"GTM must be provisioned to use this module."
|
||||
)
|
||||
|
@ -656,20 +1145,12 @@ class ModuleManager(object):
|
|||
return V2Manager(**self.kwargs)
|
||||
|
||||
def version_is_less_than(self, version):
|
||||
tmos_version = self.client.api.tmos_version
|
||||
if LooseVersion(tmos_version) < LooseVersion(version):
|
||||
tmos = tmos_version(self.client)
|
||||
if LooseVersion(tmos) < LooseVersion(version):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def gtm_provisioned(self):
|
||||
resource = self.client.api.tm.sys.dbs.db.load(
|
||||
name='provisioned.cpu.gtm'
|
||||
)
|
||||
if int(resource.value) == 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class BaseManager(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -712,14 +1193,10 @@ class BaseManager(object):
|
|||
result = dict()
|
||||
state = self.want.state
|
||||
|
||||
try:
|
||||
if state in ['present', 'enabled', 'disabled']:
|
||||
changed = self.present()
|
||||
elif state == "absent":
|
||||
changed = self.absent()
|
||||
except iControlUnexpectedHTTPError as e:
|
||||
raise F5ModuleError(str(e))
|
||||
|
||||
if state in ['present', 'enabled', 'disabled']:
|
||||
changed = self.present()
|
||||
elif state == "absent":
|
||||
changed = self.absent()
|
||||
reportable = ReportableChanges(params=self.changes.to_return())
|
||||
changes = reportable.to_return()
|
||||
result.update(**changes)
|
||||
|
@ -761,7 +1238,8 @@ class BaseManager(object):
|
|||
"You must provide an initial device."
|
||||
)
|
||||
self._assign_creation_defaults()
|
||||
|
||||
self.handle_prober_settings()
|
||||
self._set_changed_options()
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.create_on_device()
|
||||
|
@ -772,28 +1250,43 @@ class BaseManager(object):
|
|||
|
||||
def create_on_device(self):
|
||||
params = self.changes.api_params()
|
||||
self.client.api.tm.gtm.servers.server.create(
|
||||
name=self.want.name,
|
||||
partition=self.want.partition,
|
||||
**params
|
||||
params['name'] = self.want.name
|
||||
params['partition'] = self.want.partition
|
||||
uri = "https://{0}:{1}/mgmt/tm/gtm/server/".format(
|
||||
self.client.provider['server'],
|
||||
self.client.provider['server_port']
|
||||
)
|
||||
resp = self.client.api.post(uri, json=params)
|
||||
try:
|
||||
response = resp.json()
|
||||
except ValueError as ex:
|
||||
raise F5ModuleError(str(ex))
|
||||
|
||||
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
|
||||
if 'code' in response and response['code'] in [400, 403]:
|
||||
if 'message' in response:
|
||||
raise F5ModuleError(response['message'])
|
||||
else:
|
||||
raise F5ModuleError(resp.content)
|
||||
return response['selfLink']
|
||||
|
||||
def read_current_from_device(self):
|
||||
resource = self.client.api.tm.gtm.servers.server.load(
|
||||
name=self.want.name,
|
||||
partition=self.want.partition
|
||||
uri = "https://{0}:{1}/mgmt/tm/gtm/server/{2}".format(
|
||||
self.client.provider['server'],
|
||||
self.client.provider['server_port'],
|
||||
transform_name(self.want.partition, self.want.name)
|
||||
)
|
||||
result = resource.attrs
|
||||
return ApiParameters(params=result)
|
||||
resp = self.client.api.get(uri)
|
||||
try:
|
||||
response = resp.json()
|
||||
except ValueError as ex:
|
||||
raise F5ModuleError(str(ex))
|
||||
|
||||
if 'code' in response and response['code'] == 400:
|
||||
if 'message' in response:
|
||||
raise F5ModuleError(response['message'])
|
||||
else:
|
||||
raise F5ModuleError(resp.content)
|
||||
return ApiParameters(params=response)
|
||||
|
||||
def should_update(self):
|
||||
result = self._update_changed_options()
|
||||
|
@ -803,11 +1296,22 @@ class BaseManager(object):
|
|||
|
||||
def update_on_device(self):
|
||||
params = self.changes.api_params()
|
||||
resource = self.client.api.tm.gtm.servers.server.load(
|
||||
name=self.want.name,
|
||||
partition=self.want.partition
|
||||
uri = "https://{0}:{1}/mgmt/tm/gtm/server/{2}".format(
|
||||
self.client.provider['server'],
|
||||
self.client.provider['server_port'],
|
||||
transform_name(self.want.partition, self.want.name)
|
||||
)
|
||||
resource.modify(**params)
|
||||
resp = self.client.api.patch(uri, json=params)
|
||||
try:
|
||||
response = resp.json()
|
||||
except ValueError as ex:
|
||||
raise F5ModuleError(str(ex))
|
||||
|
||||
if 'code' in response and response['code'] == 400:
|
||||
if 'message' in response:
|
||||
raise F5ModuleError(response['message'])
|
||||
else:
|
||||
raise F5ModuleError(resp.content)
|
||||
|
||||
def absent(self):
|
||||
changed = False
|
||||
|
@ -824,18 +1328,30 @@ class BaseManager(object):
|
|||
return True
|
||||
|
||||
def remove_from_device(self):
|
||||
resource = self.client.api.tm.gtm.servers.server.load(
|
||||
name=self.want.name,
|
||||
partition=self.want.partition
|
||||
uri = "https://{0}:{1}/mgmt/tm/gtm/server/{2}".format(
|
||||
self.client.provider['server'],
|
||||
self.client.provider['server_port'],
|
||||
transform_name(self.want.partition, self.want.name)
|
||||
)
|
||||
resource.delete()
|
||||
response = self.client.api.delete(uri)
|
||||
if response.status == 200:
|
||||
return True
|
||||
raise F5ModuleError(response.content)
|
||||
|
||||
def exists(self):
|
||||
result = self.client.api.tm.gtm.servers.server.exists(
|
||||
name=self.want.name,
|
||||
partition=self.want.partition
|
||||
uri = "https://{0}:{1}/mgmt/tm/gtm/server/{2}".format(
|
||||
self.client.provider['server'],
|
||||
self.client.provider['server_port'],
|
||||
transform_name(self.want.partition, self.want.name)
|
||||
)
|
||||
return result
|
||||
resp = self.client.api.get(uri)
|
||||
try:
|
||||
response = resp.json()
|
||||
except ValueError:
|
||||
return False
|
||||
if resp.status == 404 or 'code' in response and response['code'] == 404:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class V1Manager(BaseManager):
|
||||
|
@ -861,8 +1377,33 @@ class V1Manager(BaseManager):
|
|||
if len(self.want.devices) > 1 and self.want.server_type == 'bigip':
|
||||
self.want.update({'server_type': 'redundant-bigip'})
|
||||
|
||||
def update(self):
|
||||
self.have = self.read_current_from_device()
|
||||
self.handle_prober_settings()
|
||||
if not self.should_update():
|
||||
return False
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.update_on_device()
|
||||
return True
|
||||
|
||||
def handle_prober_settings(self):
|
||||
if self.want.prober_preference is not None:
|
||||
self.want._values.pop('prober_preference')
|
||||
if self.want.prober_fallback is not None:
|
||||
self.want._values.pop('prober_fallback')
|
||||
|
||||
|
||||
class V2Manager(BaseManager):
|
||||
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 _assign_creation_defaults(self):
|
||||
if self.want.server_type is None:
|
||||
self.want.update({'server_type': 'bigip'})
|
||||
|
@ -875,6 +1416,21 @@ class V2Manager(BaseManager):
|
|||
def adjust_server_type_by_version(self):
|
||||
pass
|
||||
|
||||
def handle_prober_settings(self):
|
||||
if self.want.prober_preference == 'pool' and self.want.prober_pool is None:
|
||||
raise F5ModuleError(
|
||||
"A prober_pool needs to be set if prober_preference is set to 'pool'"
|
||||
)
|
||||
if self.want.prober_preference is not None and self.want.prober_fallback is not None:
|
||||
if self.want.prober_preference == self.want.prober_fallback:
|
||||
raise F5ModuleError(
|
||||
"The parameters for prober_preference and prober_fallback must not be the same."
|
||||
)
|
||||
if self.want.prober_fallback == 'pool' and self.want.prober_pool is None:
|
||||
raise F5ModuleError(
|
||||
"A prober_pool needs to be set if prober_fallback is set to 'pool'"
|
||||
)
|
||||
|
||||
|
||||
class ArgumentSpec(object):
|
||||
def __init__(self):
|
||||
|
@ -929,7 +1485,37 @@ class ArgumentSpec(object):
|
|||
allow_service_check=dict(type='bool'),
|
||||
allow_snmp=dict(type='bool')
|
||||
)
|
||||
)
|
||||
),
|
||||
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'),
|
||||
prober_preference=dict(
|
||||
choices=['inside-datacenter', 'outside-datacenter', 'inherit', 'pool']
|
||||
),
|
||||
prober_fallback=dict(
|
||||
choices=['inside-datacenter', 'outside-datacenter',
|
||||
'inherit', 'pool', 'any', 'none']
|
||||
),
|
||||
prober_pool=dict()
|
||||
|
||||
)
|
||||
self.argument_spec = {}
|
||||
self.argument_spec.update(f5_argument_spec)
|
||||
|
@ -941,20 +1527,19 @@ def main():
|
|||
|
||||
module = AnsibleModule(
|
||||
argument_spec=spec.argument_spec,
|
||||
supports_check_mode=spec.supports_check_mode
|
||||
supports_check_mode=spec.supports_check_mode,
|
||||
)
|
||||
if not HAS_F5SDK:
|
||||
module.fail_json(msg="The python f5-sdk module is required")
|
||||
|
||||
client = F5RestClient(**module.params)
|
||||
|
||||
try:
|
||||
client = F5Client(**module.params)
|
||||
mm = ModuleManager(module=module, client=client)
|
||||
results = mm.exec_module()
|
||||
cleanup_tokens(client)
|
||||
module.exit_json(**results)
|
||||
exit_json(module, results, client)
|
||||
except F5ModuleError as ex:
|
||||
cleanup_tokens(client)
|
||||
module.fail_json(msg=str(ex))
|
||||
fail_json(module, ex, client)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -8,16 +8,12 @@ __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 units.compat import unittest
|
||||
from units.compat.mock import Mock
|
||||
from units.compat.mock import patch
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
try:
|
||||
|
@ -27,9 +23,13 @@ try:
|
|||
from library.modules.bigip_gtm_server import V1Manager
|
||||
from library.modules.bigip_gtm_server import V2Manager
|
||||
from library.modules.bigip_gtm_server 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
|
||||
|
||||
# In Ansible 2.8, Ansible changed import paths.
|
||||
from test.units.compat import unittest
|
||||
from test.units.compat.mock import Mock
|
||||
from test.units.compat.mock import patch
|
||||
|
||||
from test.units.modules.utils import set_module_args
|
||||
except ImportError:
|
||||
try:
|
||||
from ansible.modules.network.f5.bigip_gtm_server import ApiParameters
|
||||
|
@ -38,8 +38,12 @@ except ImportError:
|
|||
from ansible.modules.network.f5.bigip_gtm_server import V1Manager
|
||||
from ansible.modules.network.f5.bigip_gtm_server import V2Manager
|
||||
from ansible.modules.network.f5.bigip_gtm_server import ArgumentSpec
|
||||
from ansible.module_utils.network.f5.common import F5ModuleError
|
||||
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
|
||||
# Ansible 2.8 imports
|
||||
from units.compat import unittest
|
||||
from units.compat.mock import Mock
|
||||
from units.compat.mock import patch
|
||||
|
||||
from units.modules.utils import set_module_args
|
||||
except ImportError:
|
||||
raise SkipTest("F5 Ansible modules require the f5-sdk Python library")
|
||||
|
@ -138,6 +142,28 @@ class TestV1Manager(unittest.TestCase):
|
|||
def setUp(self):
|
||||
self.spec = ArgumentSpec()
|
||||
|
||||
try:
|
||||
self.p1 = patch('library.modules.bigip_gtm_server.module_provisioned')
|
||||
self.m1 = self.p1.start()
|
||||
self.m1.return_value = True
|
||||
except Exception:
|
||||
self.p1 = patch('ansible.modules.network.f5.bigip_gtm_server.module_provisioned')
|
||||
self.m1 = self.p1.start()
|
||||
self.m1.return_value = True
|
||||
|
||||
try:
|
||||
self.p2 = patch('library.modules.bigip_gtm_server.tmos_version')
|
||||
self.m2 = self.p2.start()
|
||||
self.m2.return_value = '13.0.0'
|
||||
except Exception:
|
||||
self.p2 = patch('ansible.modules.network.f5.bigip_gtm_server.tmos_version')
|
||||
self.m2 = self.p2.start()
|
||||
self.m2.return_value = '13.0.0'
|
||||
|
||||
def tearDown(self):
|
||||
self.p1.stop()
|
||||
self.p2.stop()
|
||||
|
||||
def test_create(self, *args):
|
||||
set_module_args(dict(
|
||||
server='lb.mydomain.com',
|
||||
|
@ -204,6 +230,7 @@ class TestV1Manager(unittest.TestCase):
|
|||
mm.get_manager = Mock(return_value=m1)
|
||||
mm.version_is_less_than = Mock(return_value=True)
|
||||
mm.gtm_provisioned = Mock(return_value=True)
|
||||
mm.module_provisioned = Mock(return_value=True)
|
||||
|
||||
results = mm.exec_module()
|
||||
|
||||
|
@ -216,6 +243,28 @@ class TestV2Manager(unittest.TestCase):
|
|||
def setUp(self):
|
||||
self.spec = ArgumentSpec()
|
||||
|
||||
try:
|
||||
self.p1 = patch('library.modules.bigip_gtm_server.module_provisioned')
|
||||
self.m1 = self.p1.start()
|
||||
self.m1.return_value = True
|
||||
except Exception:
|
||||
self.p1 = patch('ansible.modules.network.f5.bigip_gtm_server.module_provisioned')
|
||||
self.m1 = self.p1.start()
|
||||
self.m1.return_value = True
|
||||
|
||||
try:
|
||||
self.p2 = patch('library.modules.bigip_gtm_server.tmos_version')
|
||||
self.m2 = self.p2.start()
|
||||
self.m2.return_value = '13.0.0'
|
||||
except Exception:
|
||||
self.p2 = patch('ansible.modules.network.f5.bigip_gtm_server.tmos_version')
|
||||
self.m2 = self.p2.start()
|
||||
self.m2.return_value = '13.0.0'
|
||||
|
||||
def tearDown(self):
|
||||
self.p1.stop()
|
||||
self.p2.stop()
|
||||
|
||||
def test_create(self, *args):
|
||||
set_module_args(dict(
|
||||
server='lb.mydomain.com',
|
||||
|
@ -282,6 +331,7 @@ class TestV2Manager(unittest.TestCase):
|
|||
mm.get_manager = Mock(return_value=m1)
|
||||
mm.version_is_less_than = Mock(return_value=False)
|
||||
mm.gtm_provisioned = Mock(return_value=True)
|
||||
mm.module_provisioned = Mock(return_value=True)
|
||||
|
||||
results = mm.exec_module()
|
||||
|
||||
|
|
Loading…
Reference in a new issue