Repository URL to install this package:
|
Version:
6.0.0 ▾
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2017, F5 Networks Inc.
# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: bigip_gtm_server
short_description: Manages F5 BIG-IP GTM servers
description:
- Manage BIG-IP GTM (now BIG-IP DNS) server configuration. This module is able to manipulate the server
definitions in a BIG-IP.
version_added: "1.0.0"
options:
name:
description:
- The name of the server.
- If the virtual server is auto-discovered from the LTM,then the partition name needs to be included as
part of the virtual server name when referencing from the module e.g. "/Common/vsname".
type: str
required: True
state:
description:
- The server state. If C(absent), the module attempts to delete the server.
This will only succeed if this server is not in use by a virtual server.
C(present) creates the server and enables it. If C(enabled), enables the server
if it exists. If C(disabled), creates the server if needed, and sets state to
C(disabled).
type: str
choices:
- present
- absent
- enabled
- disabled
default: present
datacenter:
description:
- Data center to which the server belongs. When creating a new GTM server, this value
is required.
type: str
devices:
description:
- Lists the self IP addresses and translations for each device. When creating a
new GTM server, this value is required. This list is a complex list that
specifies a number of keys.
- The C(name) key specifies a name for the device. The device name must
be unique per server. This key is required.
- The C(address) key contains an IP address, or list of IP addresses, for the
destination server. This key is required.
- The C(translation) key contains an IP address to translate the C(address)
value above to. This key is optional.
- Specifying duplicate C(name) fields is a supported means of providing device
addresses. In this scenario, the addresses will be assigned to the C(name)'s list
of addresses.
type: raw
server_type:
description:
- Specifies the server type. The server type determines the metrics the
system can collect from the server. When creating a new GTM server, the default
value C(bigip) is used.
type: str
choices:
- alteon-ace-director
- cisco-css
- cisco-server-load-balancer
- generic-host
- radware-wsd
- windows-nt-4.0
- bigip
- cisco-local-director-v2
- extreme
- generic-load-balancer
- sun-solaris
- cacheflow
- cisco-local-director-v3
- foundry-server-iron
- netapp
- windows-2000-server
aliases:
- product
link_discovery:
description:
- Specifies whether the system auto-discovers the links for this server. When
creating a new GTM server, if this parameter is not specified, the default
value C(disabled) is used.
- If you set this parameter to C(enabled) or C(enabled-no-delete), you must
also ensure the C(virtual_server_discovery) parameter is also set to
C(enabled) or C(enabled-no-delete).
type: str
choices:
- enabled
- disabled
- enabled-no-delete
virtual_server_discovery:
description:
- Specifies whether the system auto-discovers the virtual servers for this server.
When creating a new GTM server, if this parameter is not specified, the default
value C(disabled) is used.
type: str
choices:
- enabled
- disabled
- enabled-no-delete
partition:
description:
- Device partition to manage resources on.
type: str
default: Common
iquery_options:
description:
- Specifies whether the Global Traffic Manager uses this BIG-IP
system to conduct a variety of probes before delegating traffic to it.
suboptions:
allow_path:
description:
- Specifies the system verifies the logical network route between a data
center server and a local DNS server.
type: bool
allow_service_check:
description:
- Specifies the system verifies that an application on a server is running,
by remotely running the application using an external service checker program.
type: bool
allow_snmp:
description:
- Specifies the system checks the performance of a server running an SNMP
agent.
type: bool
type: dict
monitors:
description:
- Specifies the health monitors 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.
type: list
elements: str
availability_requirements:
description:
- If you activate more than one health monitor, specifies the number of health
monitors that must receive successful responses in order for the link to be
considered available.
type: dict
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 B(all) will be used.
type: str
required: True
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.
type: int
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.
type: int
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, 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.
type: int
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.
type: str
choices:
- inside-datacenter
- outside-datacenter
- inherit
- pool
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 the C(any-available) or C(none) options.
type: str
choices:
- any
- inside-datacenter
- outside-datacenter
- inherit
- pool
- none
prober_pool:
description:
- Specifies the name of the prober pool to use to monitor this server's resources.
- In C(TMOS) version C(13.x) and later, this parameter is mandatory when C(prober_preference) is set to C(pool).
- The 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 an empty string to revert to default setting of C(inherit).
type: str
limits:
description:
- Specifies resource thresholds or limit requirements at the pool member level.
- When you enable one or more limit settings, the system then uses that data to take
members in and out of service.
- You can define limits for any or all of the limit settings. However, when a
member does not meet the resource threshold limit requirement, the system marks
the member as unavailable and directs load balancing traffic to another resource.
suboptions:
bits_enabled:
description:
- Whether the bits limit it enabled or not.
- This parameter allows you to switch on or off the effect of the limit.
type: bool
packets_enabled:
description:
- Whether the packets limit it enabled or not.
- This parameter allows you to switch on or off the effect of the limit.
type: bool
connections_enabled:
description:
- Whether the current connections limit it enabled or not.
- This parameter allows you to switch on or off the effect of the limit.
type: bool
cpu_enabled:
description:
- Whether the CPU limit it enabled or not.
- This parameter allows you to switch on or off the effect of the limit.
type: bool
memory_enabled:
description:
- Whether the memory limit it enabled or not.
- This parameter allows you to switch on or off the effect of the limit.
type: bool
bits_limit:
description:
- Specifies the maximum allowable data throughput rate for the member,
in bits per second.
- If the network traffic volume exceeds this limit, the system marks the
member as unavailable.
type: int
packets_limit:
description:
- Specifies the maximum allowable data transfer rate for the member,
in packets per second.
- If the network traffic volume exceeds this limit, the system marks the
member as unavailable.
type: int
connections_limit:
description:
- Specifies the maximum number of concurrent connections, combined, for all of
the members.
- If the connections exceed this limit, the system marks the server as
unavailable.
type: int
cpu_limit:
description:
- Specifies the percent of CPU usage.
- If percent of CPU usage goes above the limit, the system marks the server as unavailable.
type: int
memory_limit:
description:
- Specifies the available memory required by the virtual servers on the server.
- If available memory falls below this limit, the system marks the server as unavailable.
type: int
type: dict
extends_documentation_fragment: f5networks.f5_modules.f5
author:
- Robert Teller (@r-teller)
- Tim Rupp (@caphrim007)
- Wojciech Wypior (@wojtek0806)
'''
EXAMPLES = r'''
- name: Create server "GTM_Server"
bigip_gtm_server:
name: GTM_Server
datacenter: /Common/New York
server_type: bigip
link_discovery: disabled
virtual_server_discovery: disabled
devices:
- name: server_1
address: 1.1.1.1
- name: server_2
address: 2.2.2.1
translation: 192.168.2.1
- name: server_2
address: 2.2.2.2
- name: server_3
addresses:
- address: 3.3.3.1
- address: 3.3.3.2
- name: server_4
addresses:
- address: 4.4.4.1
translation: 192.168.14.1
- address: 4.4.4.2
provider:
user: admin
password: secret
server: lb.mydomain.com
delegate_to: localhost
- name: Create server "GTM_Server" with expanded keys
bigip_gtm_server:
server: lb.mydomain.com
user: admin
password: secret
name: GTM_Server
datacenter: /Common/New York
server_type: bigip
link_discovery: disabled
virtual_server_discovery: disabled
devices:
- name: server_1
address: 1.1.1.1
- name: server_2
address: 2.2.2.1
translation: 192.168.2.1
- name: server_2
address: 2.2.2.2
- name: server_3
addresses:
- address: 3.3.3.1
- address: 3.3.3.2
- name: server_4
addresses:
- address: 4.4.4.1
translation: 192.168.14.1
- address: 4.4.4.2
provider:
user: admin
password: secret
server: lb.mydomain.com
delegate_to: localhost
'''
RETURN = r'''
bits_enabled:
description: Whether the bits limit is enabled.
returned: changed
type: bool
sample: yes
bits_limit:
description: The new bits_enabled limit.
returned: changed
type: int
sample: 100
connections_enabled:
description: Whether the connections limit is enabled.
returned: changed
type: bool
sample: yes
connections_limit:
description: The new connections_limit limit.
returned: changed
type: int
sample: 100
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
type: str
sample: enabled
virtual_server_discovery:
description: The new C(virtual_server_discovery) name for the trap destination.
returned: changed
type: str
sample: disabled
server_type:
description: The new type of the server.
returned: changed
type: str
sample: bigip
datacenter:
description: The new C(datacenter) which the server is a part of.
returned: changed
type: str
sample: datacenter01
packets_enabled:
description: Whether the packets limit is enabled.
returned: changed
type: bool
sample: yes
packets_limit:
description: The new packets_limit limit.
returned: changed
type: int
sample: 100
'''
import re
from datetime import datetime
from ansible.module_utils.basic import (
AnsibleModule, env_fallback
)
from distutils.version import LooseVersion
from ..module_utils.bigip import F5RestClient
from ..module_utils.common import (
F5ModuleError, AnsibleF5Parameters, transform_name, f5_argument_spec, is_empty_list, fq_name
)
from ..module_utils.icontrol import (
module_provisioned, tmos_version
)
from ..module_utils.teem import send_teem
try:
from collections import OrderedDict
except ImportError:
try:
from ordereddict import OrderedDict
except ImportError:
pass
class Parameters(AnsibleF5Parameters):
api_map = {
'product': 'server_type',
'virtualServerDiscovery': 'virtual_server_discovery',
'linkDiscovery': 'link_discovery',
'addresses': 'devices',
'iqAllowPath': 'iquery_allow_path',
'iqAllowServiceCheck': 'iquery_allow_service_check',
'iqAllowSnmp': 'iquery_allow_snmp',
'monitor': 'monitors',
'proberPreference': 'prober_preference',
'proberPool': 'prober_pool',
'proberFallback': 'prober_fallback',
'limitMaxBps': 'bits_limit',
'limitMaxBpsStatus': 'bits_enabled',
'limitMaxConnections': 'connections_limit',
'limitMaxConnectionsStatus': 'connections_enabled',
'limitMaxPps': 'packets_limit',
'limitMaxPpsStatus': 'packets_enabled',
'limitCpuUsage': 'cpu_limit',
'limitCpuUsageStatus': 'cpu_enabled',
'limitMemAvail': 'memory_limit',
'limitMemAvailStatus': 'memory_enabled',
}
api_attributes = [
'linkDiscovery',
'virtualServerDiscovery',
'product',
'addresses',
'datacenter',
'enabled',
'disabled',
'iqAllowPath',
'iqAllowServiceCheck',
'iqAllowSnmp',
'monitor',
'proberPreference',
'proberPool',
'proberFallback',
'limitMaxBps',
'limitMaxBpsStatus',
'limitMaxConnections',
'limitMaxConnectionsStatus',
'limitMaxPps',
'limitMaxPpsStatus',
'limitCpuUsage',
'limitCpuUsageStatus',
'limitMemAvail',
'limitMemAvailStatus',
]
updatables = [
'link_discovery',
'virtual_server_discovery',
'server_type_and_devices',
'datacenter',
'state',
'iquery_allow_path',
'iquery_allow_service_check',
'iquery_allow_snmp',
'monitors',
'prober_preference',
'prober_pool',
'prober_fallback',
'bits_enabled',
'bits_limit',
'connections_enabled',
'connections_limit',
'packets_enabled',
'packets_limit',
'cpu_enabled',
'cpu_limit',
'memory_enabled',
'memory_limit',
]
returnables = [
'link_discovery',
'virtual_server_discovery',
'server_type',
'datacenter',
'enabled',
'iquery_allow_path',
'iquery_allow_service_check',
'iquery_allow_snmp',
'devices',
'monitors',
'availability_requirements',
'prober_preference',
'prober_pool',
'prober_fallback',
'bits_enabled',
'bits_limit',
'connections_enabled',
'connections_limit',
'packets_enabled',
'packets_limit',
'cpu_enabled',
'cpu_limit',
'memory_enabled',
'memory_limit',
]
class ApiParameters(Parameters):
@property
def devices(self):
if self._values['devices'] is None:
return None
return self._values['devices']
@property
def server_type(self):
if self._values['server_type'] is None:
return None
elif self._values['server_type'] in ['single-bigip', 'redundant-bigip']:
return 'bigip'
else:
return self._values['server_type']
@property
def raw_server_type(self):
if self._values['server_type'] is None:
return None
return self._values['server_type']
@property
def enabled(self):
if self._values['enabled'] is None:
return None
return True
@property
def disabled(self):
if self._values['disabled'] is None:
return None
return True
@property
def iquery_allow_path(self):
if self._values['iquery_allow_path'] is None:
return None
elif self._values['iquery_allow_path'] == 'yes':
return True
return False
@property
def iquery_allow_service_check(self):
if self._values['iquery_allow_service_check'] is None:
return None
elif self._values['iquery_allow_service_check'] == 'yes':
return True
return False
@property
def iquery_allow_snmp(self):
if self._values['iquery_allow_snmp'] is None:
return None
elif self._values['iquery_allow_snmp'] == 'yes':
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):
def _get_limit_value(self, type):
if self._values['limits'] is None:
return None
if self._values['limits'][type] is None:
return None
return int(self._values['limits'][type])
def _get_limit_status(self, type):
if self._values['limits'] is None:
return None
if self._values['limits'][type] is None:
return None
if self._values['limits'][type]:
return 'enabled'
return 'disabled'
@property
def devices(self):
if self._values['devices'] is None:
return None
result = []
for device in self._values['devices']:
if not any(x for x in ['address', 'addresses'] if x in device):
raise F5ModuleError(
"The specified device list must contain an 'address' or 'addresses' key"
)
if 'address' in device:
translation = self._determine_translation(device)
name = device['address']
device_name = device['name']
result.append({
'name': name,
'deviceName': device_name,
'translation': translation
})
elif 'addresses' in device:
for address in device['addresses']:
translation = self._determine_translation(address)
name = address['address']
device_name = device['name']
result.append({
'name': name,
'deviceName': device_name,
'translation': translation
})
return result
@property
def enabled(self):
if self._values['state'] in ['present', 'enabled']:
return True
return False
@property
def datacenter(self):
if self._values['datacenter'] is None:
return None
return fq_name(self.partition, self._values['datacenter'])
def _determine_translation(self, device):
if 'translation' not in device:
return 'none'
return device['translation']
@property
def state(self):
if self._values['state'] == 'enabled':
return 'present'
return self._values['state']
@property
def iquery_allow_path(self):
if self._values['iquery_options'] is None:
return None
elif self._values['iquery_options']['allow_path'] is None:
return None
return self._values['iquery_options']['allow_path']
@property
def iquery_allow_service_check(self):
if self._values['iquery_options'] is None:
return None
elif self._values['iquery_options']['allow_service_check'] is None:
return None
return self._values['iquery_options']['allow_service_check']
@property
def iquery_allow_snmp(self):
if self._values['iquery_options'] is None:
return None
elif self._values['iquery_options']['allow_snmp'] is None:
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']
@property
def bits_limit(self):
return self._get_limit_value('bits_limit')
@property
def packets_limit(self):
return self._get_limit_value('packets_limit')
@property
def connections_limit(self):
return self._get_limit_value('connections_limit')
@property
def cpu_limit(self):
return self._get_limit_value('cpu_limit')
@property
def memory_limit(self):
return self._get_limit_value('memory_limit')
@property
def bits_enabled(self):
return self._get_limit_status('bits_enabled')
@property
def packets_enabled(self):
return self._get_limit_status('packets_enabled')
@property
def connections_enabled(self):
return self._get_limit_status('connections_enabled')
@property
def cpu_enabled(self):
return self._get_limit_status('cpu_enabled')
@property
def memory_enabled(self):
return self._get_limit_status('memory_enabled')
class Changes(Parameters):
def to_return(self):
result = {}
try:
for returnable in self.returnables:
result[returnable] = getattr(self, returnable)
result = self._filter_params(result)
except Exception:
raise
return result
class UsableChanges(Changes):
@property
def monitors(self):
monitor_string = self._values['monitors']
if monitor_string is None:
return None
if '{' in monitor_string and '}' in monitor_string:
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:
return None
elif self._values['iquery_allow_path']:
return 'yes'
return 'no'
@property
def iquery_allow_service_check(self):
if self._values['iquery_allow_service_check'] is None:
return None
elif self._values['iquery_allow_service_check']:
return 'yes'
return 'no'
@property
def iquery_allow_snmp(self):
if self._values['iquery_allow_snmp'] is None:
return None
elif self._values['iquery_allow_snmp']:
return 'yes'
return 'no'
class ReportableChanges(Changes):
@property
def server_type(self):
if self._values['server_type'] in ['single-bigip', 'redundant-bigip']:
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):
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):
want = getattr(self.want, param)
try:
have = getattr(self.have, param)
if want != have:
return want
except AttributeError:
return want
def _discovery_constraints(self):
if self.want.virtual_server_discovery is None:
virtual_server_discovery = self.have.virtual_server_discovery
else:
virtual_server_discovery = self.want.virtual_server_discovery
if self.want.link_discovery is None:
link_discovery = self.have.link_discovery
else:
link_discovery = self.want.link_discovery
if link_discovery in ['enabled', 'enabled-no-delete'] and virtual_server_discovery == 'disabled':
raise F5ModuleError(
"Virtual server discovery must be enabled if link discovery is enabled"
)
def _devices_changed(self):
if self.want.devices is None and self.want.server_type is None:
return None
if self.want.devices is None:
devices = self.have.devices
else:
devices = self.want.devices
if self.have.devices is None:
have_devices = []
else:
have_devices = self.have.devices
if len(devices) == 0:
raise F5ModuleError(
"A GTM server must have at least one device associated with it."
)
want = [OrderedDict(sorted(d.items())) for d in devices]
have = [OrderedDict(sorted(d.items())) for d in have_devices]
if len(have_devices) > 0:
if self._false_positive(devices, have_devices):
return False
if want != have:
return True
return False
def _false_positive(self, devices, have_devices):
match = 0
for w in devices:
for h in have_devices:
if w.items() == h.items():
match = match + 1
if match == len(devices):
return True
def _server_type_changed(self):
if self.want.server_type is None:
self.want.update({'server_type': self.have.server_type})
if self.want.server_type != self.have.server_type:
return True
return False
@property
def link_discovery(self):
self._discovery_constraints()
if self.want.link_discovery != self.have.link_discovery:
return self.want.link_discovery
@property
def virtual_server_discovery(self):
self._discovery_constraints()
if self.want.virtual_server_discovery != self.have.virtual_server_discovery:
return self.want.virtual_server_discovery
def _handle_current_server_type_and_devices(self, devices_change, server_change):
result = {}
if devices_change:
result['devices'] = self.want.devices
if server_change:
result['server_type'] = self.want.server_type
return result
def _handle_legacy_server_type_and_devices(self, devices_change, server_change):
result = {}
if server_change and devices_change:
result['devices'] = self.want.devices
if len(self.want.devices) > 1 and self.want.server_type == 'bigip':
if self.have.raw_server_type != 'redundant-bigip':
result['server_type'] = 'redundant-bigip'
elif self.want.server_type == 'bigip':
if self.have.raw_server_type != 'single-bigip':
result['server_type'] = 'single-bigip'
else:
result['server_type'] = self.want.server_type
elif devices_change:
result['devices'] = self.want.devices
if len(self.want.devices) > 1 and self.have.server_type == 'bigip':
if self.have.raw_server_type != 'redundant-bigip':
result['server_type'] = 'redundant-bigip'
elif self.have.server_type == 'bigip':
if self.have.raw_server_type != 'single-bigip':
result['server_type'] = 'single-bigip'
else:
result['server_type'] = self.want.server_type
elif server_change:
if len(self.have.devices) > 1 and self.want.server_type == 'bigip':
if self.have.raw_server_type != 'redundant-bigip':
result['server_type'] = 'redundant-bigip'
elif self.want.server_type == 'bigip':
if self.have.raw_server_type != 'single-bigip':
result['server_type'] = 'single-bigip'
else:
result['server_type'] = self.want.server_type
return result
@property
def server_type_and_devices(self):
"""Compares difference between server type and devices list
These two parameters are linked with each other and, therefore, must be
compared together to ensure that the correct setting is sent to BIG-IP
:return:
"""
devices_change = self._devices_changed()
server_change = self._server_type_changed()
if not devices_change and not server_change:
return None
tmos = tmos_version(self.client)
if LooseVersion(tmos) >= LooseVersion('13.0.0'):
result = self._handle_current_server_type_and_devices(
devices_change, server_change
)
return result
else:
result = self._handle_legacy_server_type_and_devices(
devices_change, server_change
)
return result
@property
def state(self):
if self.want.state == 'disabled' and self.have.enabled:
return dict(disabled=True)
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):
self.module = kwargs.get('module', None)
self.client = F5RestClient(**self.module.params)
self.kwargs = kwargs
def exec_module(self):
if not module_provisioned(self.client, 'gtm'):
raise F5ModuleError(
"GTM must be provisioned to use this module."
)
if self.version_is_less_than('13.0.0'):
manager = self.get_manager('v1')
else:
manager = self.get_manager('v2')
return manager.exec_module()
def get_manager(self, type):
if type == 'v1':
return V1Manager(**self.kwargs)
elif type == 'v2':
return V2Manager(**self.kwargs)
def version_is_less_than(self, version):
tmos = tmos_version(self.client)
if LooseVersion(tmos) < LooseVersion(version):
return True
else:
return False
class BaseManager(object):
def __init__(self, *args, **kwargs):
self.module = kwargs.get('module', None)
self.client = F5RestClient(**self.module.params)
self.want = ModuleParameters(params=self.module.params)
self.want.update(dict(client=self.client))
self.have = ApiParameters()
self.changes = UsableChanges()
def _set_changed_options(self):
changed = {}
for key in Parameters.returnables:
if getattr(self.want, key) is not None:
changed[key] = getattr(self.want, key)
if changed:
self.changes = UsableChanges(params=changed)
def _update_changed_options(self):
diff = Difference(self.want, self.have)
diff.client = self.client
updatables = Parameters.updatables
changed = dict()
for k in updatables:
change = diff.compare(k)
if change is None:
continue
else:
if isinstance(change, dict):
changed.update(change)
else:
changed[k] = change
if changed:
self.changes = UsableChanges(params=changed)
return True
return False
def exec_module(self):
start = datetime.now().isoformat()
version = tmos_version(self.client)
changed = False
result = dict()
state = self.want.state
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)
result.update(dict(changed=changed))
self._announce_deprecations(result)
send_teem(start, self.client, self.module, version)
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 _check_link_discovery_requirements(self):
if (self.want.link_discovery in ['enabled', 'enabled-no-delete'] and
self.want.virtual_server_discovery == 'disabled'):
raise F5ModuleError(
"Virtual server discovery must be enabled if link discovery is enabled"
)
def present(self):
if self.exists():
return self.update()
else:
return self.create()
def create(self):
if self.want.state == 'disabled':
self.want.update({'disabled': True})
elif self.want.state in ['present', 'enabled']:
self.want.update({'enabled': True})
self.adjust_server_type_by_version()
self.should_update()
if self.want.devices is None:
raise F5ModuleError(
"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()
if self.exists():
return True
else:
raise F5ModuleError("Failed to create the server")
def create_on_device(self):
params = self.changes.api_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))
if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
return True
raise F5ModuleError(resp.content)
def read_current_from_device(self):
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)
)
resp = self.client.api.get(uri)
try:
response = resp.json()
except ValueError as ex:
raise F5ModuleError(str(ex))
if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
return ApiParameters(params=response)
raise F5ModuleError(resp.content)
def should_update(self):
result = self._update_changed_options()
if result:
return True
return False
def update_on_device(self):
params = self.changes.api_params()
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)
)
resp = self.client.api.patch(uri, json=params)
try:
response = resp.json()
except ValueError as ex:
raise F5ModuleError(str(ex))
if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
return True
raise F5ModuleError(resp.content)
def absent(self):
changed = False
if self.exists():
changed = self.remove()
return changed
def remove(self):
if self.module.check_mode:
return True
self.remove_from_device()
if self.exists():
raise F5ModuleError("Failed to delete the server")
return True
def remove_from_device(self):
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)
)
response = self.client.api.delete(uri)
if response.status == 200:
return True
raise F5ModuleError(response.content)
def exists(self):
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)
)
resp = self.client.api.get(uri)
try:
response = resp.json()
except ValueError as ex:
raise F5ModuleError(str(ex))
if resp.status == 404 or 'code' in response and response['code'] == 404:
return False
if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
return True
errors = [401, 403, 409, 500, 501, 502, 503, 504]
if resp.status in errors or 'code' in response and response['code'] in errors:
if 'message' in response:
raise F5ModuleError(response['message'])
else:
raise F5ModuleError(resp.content)
class V1Manager(BaseManager):
def _assign_creation_defaults(self):
if self.want.server_type is None:
if len(self.want.devices) == 0:
raise F5ModuleError(
"You must provide at least one device."
)
elif len(self.want.devices) == 1:
self.want.update({'server_type': 'single-bigip'})
else:
self.want.update({'server_type': 'redundant-bigip'})
if self.want.link_discovery is None:
self.want.update({'link_discovery': 'disabled'})
if self.want.virtual_server_discovery is None:
self.want.update({'virtual_server_discovery': 'disabled'})
self._check_link_discovery_requirements()
def adjust_server_type_by_version(self):
if len(self.want.devices) == 1 and self.want.server_type == 'bigip':
self.want.update({'server_type': 'single-bigip'})
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'})
if self.want.link_discovery is None:
self.want.update({'link_discovery': 'disabled'})
if self.want.virtual_server_discovery is None:
self.want.update({'virtual_server_discovery': 'disabled'})
self._check_link_discovery_requirements()
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):
self.states = ['absent', 'present', 'enabled', 'disabled']
self.server_types = [
'alteon-ace-director',
'cisco-css',
'cisco-server-load-balancer',
'generic-host',
'radware-wsd',
'windows-nt-4.0',
'bigip',
'cisco-local-director-v2',
'extreme',
'generic-load-balancer',
'sun-solaris',
'cacheflow',
'cisco-local-director-v3',
'foundry-server-iron',
'netapp',
'windows-2000-server'
]
self.supports_check_mode = True
argument_spec = dict(
state=dict(
default='present',
choices=self.states,
),
name=dict(required=True),
server_type=dict(
choices=self.server_types,
aliases=['product']
),
datacenter=dict(),
link_discovery=dict(
choices=['enabled', 'disabled', 'enabled-no-delete']
),
virtual_server_discovery=dict(
choices=['enabled', 'disabled', 'enabled-no-delete']
),
devices=dict(
type='raw',
),
partition=dict(
default='Common',
fallback=(env_fallback, ['F5_PARTITION'])
),
iquery_options=dict(
type='dict',
options=dict(
allow_path=dict(type='bool'),
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']]
]
),
limits=dict(
type='dict',
options=dict(
bits_enabled=dict(type='bool'),
packets_enabled=dict(type='bool'),
connections_enabled=dict(type='bool'),
cpu_enabled=dict(type='bool'),
memory_enabled=dict(type='bool'),
bits_limit=dict(type='int'),
packets_limit=dict(type='int'),
connections_limit=dict(type='int'),
cpu_limit=dict(type='int'),
memory_limit=dict(type='int'),
)
),
monitors=dict(
type='list',
elements='str',
),
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)
self.argument_spec.update(argument_spec)
def main():
spec = ArgumentSpec()
module = AnsibleModule(
argument_spec=spec.argument_spec,
supports_check_mode=spec.supports_check_mode,
)
try:
mm = ModuleManager(module=module)
results = mm.exec_module()
module.exit_json(**results)
except F5ModuleError as ex:
module.fail_json(msg=str(ex))
if __name__ == '__main__':
main()