ansible-later/env_27/lib/python2.7/site-packages/ansible/modules/network/f5/bigip_gtm_wide_ip.py
2019-04-11 13:00:36 +02:00

833 lines
24 KiB
Python

#!/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
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['stableinterface'],
'supported_by': 'certified'}
DOCUMENTATION = r'''
---
module: bigip_gtm_wide_ip
short_description: Manages F5 BIG-IP GTM wide ip
description:
- Manages F5 BIG-IP GTM wide ip.
version_added: 2.0
options:
pool_lb_method:
description:
- Specifies the load balancing method used to select a pool in this wide
IP. This setting is relevant only when multiple pools are configured
for a wide IP.
- The C(round_robin) value is deprecated and will be removed in Ansible 2.9.
- The C(global_availability) value is deprecated and will be removed in Ansible 2.9.
required: True
aliases: ['lb_method']
choices:
- round-robin
- ratio
- topology
- global-availability
- global_availability
- round_robin
version_added: 2.5
name:
description:
- Wide IP name. This name must be formatted as a fully qualified
domain name (FQDN). You can also use the alias C(wide_ip) but this
is deprecated and will be removed in a future Ansible version.
required: True
aliases:
- wide_ip
type:
description:
- Specifies the type of wide IP. GTM wide IPs need to be keyed by query
type in addition to name, since pool members need different attributes
depending on the response RDATA they are meant to supply. This value
is required if you are using BIG-IP versions >= 12.0.0.
choices:
- a
- aaaa
- cname
- mx
- naptr
- srv
version_added: 2.4
state:
description:
- When C(present) or C(enabled), ensures that the Wide IP exists and
is enabled.
- When C(absent), ensures that the Wide IP has been removed.
- When C(disabled), ensures that the Wide IP exists and is disabled.
default: present
choices:
- present
- absent
- disabled
- enabled
version_added: 2.4
partition:
description:
- Device partition to manage resources on.
default: Common
version_added: 2.5
pools:
description:
- The pools that you want associated with the Wide IP.
- If C(ratio) is not provided when creating a new Wide IP, it will default
to 1.
suboptions:
name:
description:
- The name of the pool to include.
required: True
ratio:
description:
- Ratio for the pool.
- The system uses this number with the Ratio load balancing method.
version_added: 2.5
irules:
description:
- List of rules to be applied.
- If you want to remove all existing iRules, specify a single empty value; C("").
See the documentation for an example.
version_added: 2.6
aliases:
description:
- Specifies alternate domain names for the web site content you are load
balancing.
- You can use the same wildcard characters for aliases as you can for actual
wide IP names.
version_added: 2.7
extends_documentation_fragment: f5
author:
- Tim Rupp (@caphrim007)
'''
EXAMPLES = r'''
- name: Set lb method
bigip_gtm_wide_ip:
server: lb.mydomain.com
user: admin
password: secret
pool_lb_method: round-robin
name: my-wide-ip.example.com
delegate_to: localhost
- name: Add iRules to the Wide IP
bigip_gtm_wide_ip:
server: lb.mydomain.com
user: admin
password: secret
pool_lb_method: round-robin
name: my-wide-ip.example.com
irules:
- irule1
- irule2
delegate_to: localhost
- name: Remove one iRule from the Virtual Server
bigip_gtm_wide_ip:
server: lb.mydomain.com
user: admin
password: secret
pool_lb_method: round-robin
name: my-wide-ip.example.com
irules:
- irule1
delegate_to: localhost
- name: Remove all iRules from the Virtual Server
bigip_gtm_wide_ip:
server: lb.mydomain.com
user: admin
password: secret
pool_lb_method: round-robin
name: my-wide-ip.example.com
irules: ""
delegate_to: localhost
- name: Assign a pool with ratio to the Wide IP
bigip_gtm_wide_ip:
server: lb.mydomain.com
user: admin
password: secret
pool_lb_method: round-robin
name: my-wide-ip.example.com
pools:
- name: pool1
ratio: 100
delegate_to: localhost
'''
RETURN = r'''
lb_method:
description: The new load balancing method used by the wide IP.
returned: changed
type: string
sample: topology
state:
description: The new state of the wide IP.
returned: changed
type: string
sample: disabled
irules:
description: iRules set on the Wide IP.
returned: changed
type: list
sample: ['/Common/irule1', '/Common/irule2']
aliases:
description: Aliases set on the Wide IP.
returned: changed
type: list
sample: ['alias1.foo.com', '*.wildcard.domain']
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.six import iteritems
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.common import F5ModuleError
from library.module_utils.network.f5.common import AnsibleF5Parameters
from library.module_utils.network.f5.common import cleanup_tokens
from library.module_utils.network.f5.common import fq_name
from library.module_utils.network.f5.common import is_valid_fqdn
from library.module_utils.network.f5.common import f5_argument_spec
try:
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
except ImportError:
HAS_F5SDK = False
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.common import F5ModuleError
from ansible.module_utils.network.f5.common import AnsibleF5Parameters
from ansible.module_utils.network.f5.common import cleanup_tokens
from ansible.module_utils.network.f5.common import fq_name
from ansible.module_utils.network.f5.common import is_valid_fqdn
from ansible.module_utils.network.f5.common import f5_argument_spec
try:
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
except ImportError:
HAS_F5SDK = False
class Parameters(AnsibleF5Parameters):
api_map = {
'poolLbMode': 'pool_lb_method',
'rules': 'irules',
}
updatables = [
'pool_lb_method',
'state',
'pools',
'irules',
'enabled',
'disabled',
'aliases',
]
returnables = [
'name',
'pool_lb_method',
'state',
'pools',
'irules',
'aliases',
]
api_attributes = [
'poolLbMode',
'enabled',
'disabled',
'pools',
'rules',
'aliases',
]
class ApiParameters(Parameters):
@property
def disabled(self):
if self._values['disabled'] is True:
return True
return False
@property
def enabled(self):
if self._values['enabled'] is True:
return True
return False
@property
def pools(self):
result = []
if self._values['pools'] is None:
return None
pools = sorted(self._values['pools'], key=lambda x: x['order'])
for item in pools:
pool = dict()
pool.update(item)
name = '/{0}/{1}'.format(item['partition'], item['name'])
del pool['nameReference']
del pool['order']
del pool['name']
del pool['partition']
pool['name'] = name
result.append(pool)
return result
class ModuleParameters(Parameters):
@property
def pool_lb_method(self):
if self._values['pool_lb_method'] is None:
return None
lb_method = str(self._values['pool_lb_method'])
if lb_method == 'global_availability':
if self._values['__warnings'] is None:
self._values['__warnings'] = []
self._values['__warnings'].append(
dict(
msg='The provided pool_lb_method is deprecated',
version='2.4'
)
)
lb_method = 'global-availability'
elif lb_method == 'round_robin':
if self._values['__warnings'] is None:
self._values['__warnings'] = []
self._values['__warnings'].append(
dict(
msg='The provided pool_lb_method is deprecated',
version='2.4'
)
)
lb_method = 'round-robin'
return lb_method
@property
def type(self):
if self._values['type'] is None:
return None
return str(self._values['type'])
@property
def name(self):
if self._values['name'] is None:
return None
if not is_valid_fqdn(self._values['name']):
raise F5ModuleError(
"The provided name must be a valid FQDN"
)
return self._values['name']
@property
def state(self):
if self._values['state'] == 'enabled':
return 'present'
return self._values['state']
@property
def enabled(self):
if self._values['state'] == 'disabled':
return False
elif self._values['state'] in ['present', 'enabled']:
return True
else:
return None
@property
def disabled(self):
if self._values['state'] == 'disabled':
return True
elif self._values['state'] in ['present', 'enabled']:
return False
else:
return None
@property
def pools(self):
result = []
if self._values['pools'] is None:
return None
for item in self._values['pools']:
pool = dict()
if 'name' not in item:
raise F5ModuleError(
"'name' is a required key for items in the list of pools."
)
if 'ratio' in item:
pool['ratio'] = item['ratio']
pool['name'] = fq_name(self.partition, item['name'])
result.append(pool)
return result
@property
def irules(self):
results = []
if self._values['irules'] is None:
return None
if len(self._values['irules']) == 1 and self._values['irules'][0] == '':
return ''
for irule in self._values['irules']:
result = fq_name(self.partition, irule)
results.append(result)
return results
@property
def aliases(self):
if self._values['aliases'] is None:
return None
if len(self._values['aliases']) == 1 and self._values['aliases'][0] == '':
return ''
self._values['aliases'].sort()
return self._values['aliases']
class Changes(Parameters):
def to_return(self):
result = {}
try:
for returnable in self.returnables:
change = getattr(self, returnable)
if isinstance(change, dict):
result.update(change)
else:
result[returnable] = change
result = self._filter_params(result)
except Exception:
pass
return result
class UsableChanges(Changes):
@property
def irules(self):
if self._values['irules'] is None:
return None
if self._values['irules'] == '':
return []
return self._values['irules']
class ReportableChanges(Changes):
@property
def pool_lb_method(self):
result = dict(
lb_method=self._values['pool_lb_method'],
pool_lb_method=self._values['pool_lb_method'],
)
return result
class Difference(object):
def __init__(self, want, have=None):
self.want = want
self.have = have
def compare(self, param):
try:
result = getattr(self, param)
return result
except AttributeError:
return self.__default(param)
def __default(self, param):
attr1 = getattr(self.want, param)
try:
attr2 = getattr(self.have, param)
if attr1 != attr2:
return attr1
except AttributeError:
return attr1
def to_tuple(self, items):
result = []
for x in items:
tmp = [(str(k), str(v)) for k, v in iteritems(x)]
result += tmp
return result
def _diff_complex_items(self, want, have):
if want == [] and have is None:
return None
if want is None:
return None
w = self.to_tuple(want)
h = self.to_tuple(have)
if set(w).issubset(set(h)):
return None
else:
return want
@property
def state(self):
if self.want.state == 'disabled' and self.have.enabled:
return self.want.state
elif self.want.state in ['present', 'enabled'] and self.have.disabled:
return self.want.state
@property
def pools(self):
result = self._diff_complex_items(self.want.pools, self.have.pools)
return result
@property
def irules(self):
if self.want.irules is None:
return None
if self.want.irules == '' and self.have.irules is None:
return None
if self.want.irules == '' and len(self.have.irules) > 0:
return []
if sorted(set(self.want.irules)) != sorted(set(self.have.irules)):
return self.want.irules
@property
def aliases(self):
if self.want.aliases is None:
return None
if self.want.aliases == '' and self.have.aliases is None:
return None
if self.want.aliases == '' and len(self.have.aliases) > 0:
return []
if self.have.aliases is None:
return self.want.aliases
if set(self.want.aliases) != set(self.have.aliases):
return self.want.aliases
class ModuleManager(object):
def __init__(self, *args, **kwargs):
self.module = kwargs.get('module', None)
self.client = kwargs.get('client', None)
self.kwargs = kwargs
def exec_module(self):
if self.version_is_less_than_12():
manager = self.get_manager('untyped')
else:
manager = self.get_manager('typed')
return manager.exec_module()
def get_manager(self, type):
if type == 'typed':
return TypedManager(**self.kwargs)
elif type == 'untyped':
return UntypedManager(**self.kwargs)
def version_is_less_than_12(self):
version = self.client.api.tmos_version
if LooseVersion(version) < LooseVersion('12.0.0'):
return True
else:
return False
class BaseManager(object):
def __init__(self, *args, **kwargs):
self.module = kwargs.get('module', None)
self.client = kwargs.get('client', None)
self.want = ModuleParameters(params=self.module.params)
self.have = ApiParameters()
self.changes = UsableChanges()
def _set_changed_options(self):
changed = {}
for key in Parameters.returnables:
if getattr(self.want, key) is not None:
changed[key] = getattr(self.want, key)
if changed:
self.changes = UsableChanges(params=changed)
def _update_changed_options(self):
diff = Difference(self.want, self.have)
updatables = Parameters.updatables
changed = dict()
for k in updatables:
change = diff.compare(k)
if change is None:
continue
else:
if isinstance(change, dict):
changed.update(change)
else:
changed[k] = change
if changed:
self.changes = UsableChanges(params=changed)
return True
return False
def exec_module(self):
changed = False
result = dict()
state = self.want.state
try:
if state in ["present", "disabled"]:
changed = self.present()
elif state == "absent":
changed = self.absent()
except iControlUnexpectedHTTPError as e:
raise F5ModuleError(str(e))
reportable = ReportableChanges(params=self.changes.to_return())
changes = reportable.to_return()
result.update(**changes)
result.update(dict(changed=changed))
self._announce_deprecations(result)
return result
def _announce_deprecations(self, result):
warnings = result.pop('__warnings', [])
for warning in warnings:
self.module.deprecate(
msg=warning['msg'],
version=warning['version']
)
def present(self):
if self.exists():
return self.update()
else:
return self.create()
def create(self):
if self.want.pool_lb_method is None:
raise F5ModuleError(
"The 'pool_lb_method' option is required when state is 'present'"
)
self._set_changed_options()
if self.module.check_mode:
return True
self.create_on_device()
return True
def should_update(self):
result = self._update_changed_options()
if result:
return True
return False
def update(self):
self.have = self.read_current_from_device()
if not self.should_update():
return False
if self.module.check_mode:
return True
self.update_on_device()
return True
def absent(self):
if self.exists():
return self.remove()
return False
def remove(self):
if self.module.check_mode:
return True
self.remove_from_device()
if self.exists():
raise F5ModuleError("Failed to delete the Wide IP")
return True
class UntypedManager(BaseManager):
def exists(self):
return self.client.api.tm.gtm.wideips.wideip.exists(
name=self.want.name,
partition=self.want.partition
)
def update_on_device(self):
params = self.changes.api_params()
result = self.client.api.tm.gtm.wideips.wipeip.load(
name=self.want.name,
partition=self.want.partition
)
result.modify(**params)
def read_current_from_device(self):
resource = self.client.api.tm.gtm.wideips.wideip.load(
name=self.want.name,
partition=self.want.partition
)
result = resource.attrs
return ApiParameters(params=result)
def create_on_device(self):
params = self.changes.api_params()
self.client.api.tm.gtm.wideips.wideip.create(
name=self.want.name,
partition=self.want.partition,
**params
)
def remove_from_device(self):
result = self.client.api.tm.gtm.wideips.wideip.load(
name=self.want.name,
partition=self.want.partition
)
if result:
result.delete()
class TypedManager(BaseManager):
def __init__(self, *args, **kwargs):
super(TypedManager, self).__init__(**kwargs)
if self.want.type is None:
raise F5ModuleError(
"The 'type' option is required for BIG-IP instances "
"greater than or equal to 12.x"
)
type_map = dict(
a='a_s',
aaaa='aaaas',
cname='cnames',
mx='mxs',
naptr='naptrs',
srv='srvs'
)
self.collection = type_map[self.want.type]
def exists(self):
wideips = self.client.api.tm.gtm.wideips
collection = getattr(wideips, self.collection)
resource = getattr(collection, self.want.type)
result = resource.exists(
name=self.want.name,
partition=self.want.partition
)
return result
def update_on_device(self):
params = self.changes.api_params()
wideips = self.client.api.tm.gtm.wideips
collection = getattr(wideips, self.collection)
resource = getattr(collection, self.want.type)
result = resource.load(
name=self.want.name,
partition=self.want.partition
)
result.modify(**params)
def read_current_from_device(self):
wideips = self.client.api.tm.gtm.wideips
collection = getattr(wideips, self.collection)
resource = getattr(collection, self.want.type)
result = resource.load(
name=self.want.name,
partition=self.want.partition
)
result = result.attrs
return ApiParameters(params=result)
def create_on_device(self):
params = self.changes.api_params()
wideips = self.client.api.tm.gtm.wideips
collection = getattr(wideips, self.collection)
resource = getattr(collection, self.want.type)
resource.create(
name=self.want.name,
partition=self.want.partition,
**params
)
def remove_from_device(self):
wideips = self.client.api.tm.gtm.wideips
collection = getattr(wideips, self.collection)
resource = getattr(collection, self.want.type)
result = resource.load(
name=self.want.name,
partition=self.want.partition
)
if result:
result.delete()
class ArgumentSpec(object):
def __init__(self):
lb_method_choices = [
'round-robin', 'topology', 'ratio', 'global-availability',
# TODO(Remove in Ansible 2.9)
'round_robin', 'global_availability'
]
self.supports_check_mode = True
argument_spec = dict(
pool_lb_method=dict(
choices=lb_method_choices,
aliases=['lb_method']
),
name=dict(
required=True,
aliases=['wide_ip']
),
type=dict(
choices=[
'a', 'aaaa', 'cname', 'mx', 'naptr', 'srv'
]
),
state=dict(
default='present',
choices=['absent', 'present', 'enabled', 'disabled']
),
pools=dict(
type='list',
options=dict(
name=dict(required=True),
ratio=dict(type='int')
)
),
partition=dict(
default='Common',
fallback=(env_fallback, ['F5_PARTITION'])
),
irules=dict(
type='list',
),
aliases=dict(
type='list'
)
)
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
)
if not HAS_F5SDK:
module.fail_json(msg="The python f5-sdk module is required")
try:
client = F5Client(**module.params)
mm = ModuleManager(module=module, client=client)
results = mm.exec_module()
cleanup_tokens(client)
module.exit_json(**results)
except F5ModuleError as e:
cleanup_tokens(client)
module.fail_json(msg=str(e))
if __name__ == '__main__':
main()