#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright: (c) 2018, 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': ['preview'], 'supported_by': 'certified'} DOCUMENTATION = r''' --- module: bigip_tunnel short_description: Manage tunnels on a BIG-IP description: - Manages tunnels on a BIG-IP. Tunnels are usually based upon a tunnel profile which defines both default arguments and constraints for the tunnel. - Due to this, this module exposes a number of settings that may or may not be related to the type of tunnel you are working with. It is important that you take this into consideration when declaring your tunnel config. - If a specific tunnel does not support the parameter you are considering, the documentation of the parameter will usually make mention of this. Otherwise, when configuring that parameter on the device, the device will notify you. version_added: 2.7 options: name: description: - Specifies the name of the tunnel. required: True description: description: - Description of the tunnel. profile: description: - Specifies the profile to associate with the tunnel for handling traffic. - Depending on your selection, other settings become available or disappear. - This parameter may not be changed after it is set. key: description: - When applied to a GRE tunnel, this value specifies an optional field in the GRE header, used to authenticate the source of the packet. - When applied to a VXLAN or Geneve tunnel, this value specifies the Virtual Network Identifier (VNI). - When applied to an NVGRE tunnel, this value specifies the Virtual Subnet Identifier (VSID). - When creating a new tunnel, if this parameter is supported by the tunnel profile but not specified, the default value is C(0). local_address: description: - Specifies the IP address of the local endpoint of the tunnel. remote_address: description: - Specifies the IP address of the remote endpoint of the tunnel. - For C(dslite), C(fec) (when configuring the FEC tunnel for receiving traffic only), C(v6rd) (configured as a border relay), or C(map), the tunnel must have an unspecified remote address (any). secondary_address: description: - Specifies a non-floating IP address for the tunnel, to be used with host-initiated traffic. mtu: description: - Specifies the maximum transmission unit (MTU) of the tunnel. - When creating a new tunnel, if this parameter is supported by the tunnel profile but not specified, the default value is C(0). - The valid range is from C(0) to C(65515). use_pmtu: description: - Enables or disables the tunnel to use the PMTU (Path MTU) information provided by ICMP NeedFrag error messages. - If C(yes) and the tunnel C(mtu) is set to C(0), the tunnel will use the PMTU information. - If C(yes) and the tunnel C(mtu) is fixed to a non-zero value, the tunnel will use the minimum of PMTU and MTU. - If C(no), the tunnel will use fixed MTU or calculate its MTU using tunnel encapsulation configurations. type: bool tos: description: - Specifies the Type of Service (TOS) value to insert in the encapsulating header of transmitted packets. - When creating a new tunnel, if this parameter is supported by the tunnel profile but not specified, the default value is C(preserve). - When C(preserve), the system copies the TOS value from the inner header to the outer header. - You may also specify a numeric value. The possible values are from C(0) to C(255). auto_last_hop: description: - Allows you to configure auto last hop on a per-tunnel basis. - When creating a new tunnel, if this parameter is supported by the tunnel profile but not specified, the default is C(default). - When C(default), means that the system uses the global auto-lasthop setting to send back the request. - When C(enabled), allows the system to send return traffic to the MAC address that transmitted the request, even if the routing table points to a different network or interface. As a result, the system can send return traffic to clients even when there is no matching route. choices: - default - enabled - disabled traffic_group: description: - Specifies the traffic group to associate with the tunnel. - This value cannot be changed after it is set. This is a limitation of BIG-IP. mode: description: - Specifies how the tunnel carries traffic. - When creating a new tunnel, if this parameter is supported by the tunnel profile but not specified, the default is C(bidirectional). - When C(bidirectional), specifies that the tunnel carries both inbound and outbound traffic. - When C(inbound), specifies that the tunnel carries only incoming traffic. - When C(outbound), specifies that the tunnel carries only outgoing traffic. choices: - bidirectional - inbound - outbound transparent: description: - Specifies that the tunnel operates in transparent mode. - When C(yes), you can inspect and manipulate the encapsulated traffic flowing through the BIG-IP system. - A transparent tunnel terminates a tunnel while presenting the illusion that the tunnel transits the device unmodified (that is, the BIG-IP system appears as if it were an intermediate router that simply routes IP traffic through the device). type: bool partition: description: - Device partition to manage resources on. default: Common state: description: - When C(present), ensures that the tunnel exists. - When C(absent), ensures the tunnel is removed. default: present choices: - present - absent extends_documentation_fragment: f5 author: - Tim Rupp (@caphrim007) ''' EXAMPLES = r''' - name: Create a VXLAN tunnel bigip_tunnel: name: openshift-tunnel local_address: 192.1681.240 key: 0 secondary_address: 192.168.1.100 mtu: 0 use_pmtu: yes tos: preserve auto_last_hop: default traffic_group: traffic-group-1 state: present provider: password: secret server: lb.mydomain.com user: admin delegate_to: localhost ''' RETURN = r''' param1: description: The new param1 value of the resource. returned: changed type: bool sample: true param2: description: The new param2 value of the resource. returned: changed type: string sample: Foo is bar ''' from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import env_fallback try: 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 fq_name from library.module_utils.network.f5.common import f5_argument_spec 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 flatten_boolean from library.module_utils.network.f5.common import transform_name except ImportError: 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 fq_name from ansible.module_utils.network.f5.common import f5_argument_spec 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 flatten_boolean from ansible.module_utils.network.f5.common import transform_name class Parameters(AnsibleF5Parameters): api_map = { 'autoLasthop': 'auto_last_hop', 'localAddress': 'local_address', 'remoteAddress': 'remote_address', 'secondaryAddress': 'secondary_address', 'usePmtu': 'use_pmtu', 'trafficGroup': 'traffic_group', } api_attributes = [ 'autoLasthop', 'description', 'key', 'mtu', 'profile', 'transparent', 'usePmtu', 'tos', 'secondaryAddress', 'remoteAddress', 'mode', 'localAddress', 'trafficGroup', ] returnables = [ 'auto_last_hop', 'local_address', 'mode', 'remote_address', 'secondary_address', 'description', 'key', 'mtu', 'profile', 'transparent', 'use_pmtu', 'tos', 'traffic_group', ] updatables = [ 'auto_last_hop', 'local_address', 'mode', 'remote_address', 'profile', 'secondary_address', 'description', 'key', 'mtu', 'transparent', 'use_pmtu', 'tos', 'traffic_group', ] class ApiParameters(Parameters): pass class ModuleParameters(Parameters): @property def transparent(self): result = flatten_boolean(self._values['transparent']) if result == 'yes': return 'enabled' elif result == 'no': return 'disabled' @property def use_pmtu(self): result = flatten_boolean(self._values['use_pmtu']) if result == 'yes': return 'enabled' elif result == 'no': return 'disabled' @property def profile(self): if self._values['profile'] is None: return None return fq_name(self.partition, self._values['profile']) @property def traffic_group(self): if self._values['traffic_group'] is None: return None elif self._values['traffic_group'] in ['', 'none']: return '' return fq_name(self.partition, self._values['traffic_group']) class Changes(Parameters): def to_return(self): result = {} try: for returnable in self.returnables: result[returnable] = getattr(self, returnable) result = self._filter_params(result) except Exception: pass return result class UsableChanges(Changes): pass class ReportableChanges(Changes): @property def transparent(self): result = flatten_boolean(self._values['transparent']) return result @property def use_pmtu(self): result = flatten_boolean(self._values['use_pmtu']) 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 @property def profile(self): if self.want.profile is None: return None if self.want.profile != self.have.profile: raise F5ModuleError( "'profile' cannot be changed after it is set." ) @property def traffic_group(self): if self.want.traffic_group is None: return None if self.want.traffic_group in ['', None] and self.have.traffic_group is None: return None if self.want.traffic_group != self.have.traffic_group: raise F5ModuleError( "'traffic_group' cannot be changed after it is set." ) class ModuleManager(object): def __init__(self, *args, **kwargs): self.module = kwargs.get('module', None) self.client = kwargs.get('client', None) self.want = ModuleParameters(params=self.module.params) self.have = ApiParameters() self.changes = UsableChanges() def _set_changed_options(self): changed = {} for key in Parameters.returnables: if getattr(self.want, key) is not None: changed[key] = getattr(self.want, key) if changed: self.changes = UsableChanges(params=changed) def _update_changed_options(self): diff = Difference(self.want, self.have) updatables = Parameters.updatables changed = dict() for k in updatables: change = diff.compare(k) if change is None: continue else: if isinstance(change, dict): changed.update(change) else: changed[k] = change if changed: self.changes = UsableChanges(params=changed) return True return False def should_update(self): result = self._update_changed_options() if result: return True return False def exec_module(self): changed = False result = dict() state = self.want.state if state == "present": 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) return result def _announce_deprecations(self, result): warnings = result.pop('__warnings', []) for warning in warnings: self.client.module.deprecate( msg=warning['msg'], version=warning['version'] ) def present(self): if self.exists(): return self.update() else: return self.create() def exists(self): uri = "https://{0}:{1}/mgmt/tm/net/tunnels/tunnel/{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: return False if resp.status == 404 or 'code' in response and response['code'] == 404: return False return True def update(self): self.have = self.read_current_from_device() if not self.should_update(): return False if self.module.check_mode: return True self.update_on_device() return True def remove(self): if self.module.check_mode: return True self.remove_from_device() if self.exists(): raise F5ModuleError("Failed to delete the resource.") return True def create(self): self._set_changed_options() if self.module.check_mode: return True self.create_on_device() return True def create_on_device(self): params = self.changes.api_params() params['name'] = self.want.name params['partition'] = self.want.partition uri = "https://{0}:{1}/mgmt/tm/net/tunnels/tunnel/".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 '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 update_on_device(self): params = self.changes.api_params() uri = "https://{0}:{1}/mgmt/tm/net/tunnels/tunnel/{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 'code' in response and response['code'] == 400: if 'message' in response: raise F5ModuleError(response['message']) else: raise F5ModuleError(resp.content) def absent(self): if self.exists(): return self.remove() return False def remove_from_device(self): uri = "https://{0}:{1}/mgmt/tm/net/tunnels/tunnel/{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 if 'code' in response and response['code'] == 400: if 'message' in response: raise F5ModuleError(response['message']) else: raise F5ModuleError(response.content) def read_current_from_device(self): uri = "https://{0}:{1}/mgmt/tm/net/tunnels/tunnel/{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 'code' in response and response['code'] == 400: if 'message' in response: raise F5ModuleError(response['message']) else: raise F5ModuleError(resp.content) return ApiParameters(params=response) class ArgumentSpec(object): def __init__(self): self.supports_check_mode = True argument_spec = dict( name=dict(required='true'), profile=dict(), description=dict(), key=dict(type='int'), local_address=dict(), remote_address=dict(), secondary_address=dict(), mtu=dict(type='int'), use_pmtu=dict(type='bool'), tos=dict(), auto_last_hop=dict( choices=['default', 'enabled', 'disabled'] ), traffic_group=dict(), mode=dict( choices=['bidirectional', 'inbound', 'outbound'] ), transparent=dict(type='bool'), state=dict( default='present', choices=['present', 'absent'] ), partition=dict( default='Common', fallback=(env_fallback, ['F5_PARTITION']) ) ) 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: client = F5RestClient(**module.params) mm = ModuleManager(module=module, client=client) results = mm.exec_module() exit_json(module, results, client) except F5ModuleError as ex: fail_json(module, ex, client) if __name__ == '__main__': main()