diff options
Diffstat (limited to 'lib/ansible/modules/extras/network/f5')
20 files changed, 9301 insertions, 0 deletions
diff --git a/lib/ansible/modules/extras/network/f5/__init__.py b/lib/ansible/modules/extras/network/f5/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/__init__.py diff --git a/lib/ansible/modules/extras/network/f5/bigip_device_dns.py b/lib/ansible/modules/extras/network/f5/bigip_device_dns.py new file mode 100644 index 0000000000..c469fc4bff --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_device_dns.py @@ -0,0 +1,397 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: bigip_device_dns +short_description: Manage BIG-IP device DNS settings +description: + - Manage BIG-IP device DNS settings +version_added: "2.2" +options: + cache: + description: + - Specifies whether the system caches DNS lookups or performs the + operation each time a lookup is needed. Please note that this applies + only to Access Policy Manager features, such as ACLs, web application + rewrites, and authentication. + required: false + default: disable + choices: + - enable + - disable + name_servers: + description: + - A list of name serverz that the system uses to validate DNS lookups + forwarders: + description: + - A list of BIND servers that the system can use to perform DNS lookups + search: + description: + - A list of domains that the system searches for local domain lookups, + to resolve local host names. + ip_version: + description: + - Specifies whether the DNS specifies IP addresses using IPv4 or IPv6. + required: false + choices: + - 4 + - 6 + state: + description: + - The state of the variable on the system. When C(present), guarantees + that an existing variable is set to C(value). + required: false + default: present + choices: + - absent + - present +notes: + - Requires the f5-sdk Python package on the host. This is as easy as pip + install requests +extends_documentation_fragment: f5 +requirements: + - f5-sdk +author: + - Tim Rupp (@caphrim007) +''' + +EXAMPLES = ''' +- name: Set the DNS settings on the BIG-IP + bigip_device_dns: + name_servers: + - 208.67.222.222 + - 208.67.220.220 + search: + - localdomain + - lab.local + state: present + password: "secret" + server: "lb.mydomain.com" + user: "admin" + validate_certs: "no" + delegate_to: localhost +''' + +RETURN = ''' +cache: + description: The new value of the DNS caching + returned: changed + type: string + sample: "enabled" +name_servers: + description: List of name servers that were added or removed + returned: changed + type: list + sample: "['192.0.2.10', '172.17.12.10']" +forwarders: + description: List of forwarders that were added or removed + returned: changed + type: list + sample: "['192.0.2.10', '172.17.12.10']" +search: + description: List of search domains that were added or removed + returned: changed + type: list + sample: "['192.0.2.10', '172.17.12.10']" +ip_version: + description: IP version that was set that DNS will specify IP addresses in + returned: changed + type: int + sample: 4 +''' + +try: + from f5.bigip.contexts import TransactionContextManager + from f5.bigip import ManagementRoot + HAS_F5SDK = True +except ImportError: + HAS_F5SDK = False + + +REQUIRED = ['name_servers', 'search', 'forwarders', 'ip_version', 'cache'] +CACHE = ['disable', 'enable'] +IP = [4, 6] + + +class BigIpDeviceDns(object): + def __init__(self, *args, **kwargs): + if not HAS_F5SDK: + raise F5ModuleError("The python f5-sdk module is required") + + # The params that change in the module + self.cparams = dict() + + # Stores the params that are sent to the module + self.params = kwargs + self.api = ManagementRoot(kwargs['server'], + kwargs['user'], + kwargs['password'], + port=kwargs['server_port']) + + def flush(self): + result = dict() + changed = False + state = self.params['state'] + + if self.dhcp_enabled(): + raise F5ModuleError( + "DHCP on the mgmt interface must be disabled to make use of " + + "this module" + ) + + if state == 'absent': + changed = self.absent() + else: + changed = self.present() + + result.update(**self.cparams) + result.update(dict(changed=changed)) + return result + + def dhcp_enabled(self): + r = self.api.tm.sys.dbs.db.load(name='dhclient.mgmt') + if r.value == 'enable': + return True + else: + return False + + def read(self): + result = dict() + + cache = self.api.tm.sys.dbs.db.load(name='dns.cache') + proxy = self.api.tm.sys.dbs.db.load(name='dns.proxy.__iter__') + dns = self.api.tm.sys.dns.load() + + result['cache'] = str(cache.value) + result['forwarders'] = str(proxy.value).split(' ') + + if hasattr(dns, 'nameServers'): + result['name_servers'] = dns.nameServers + if hasattr(dns, 'search'): + result['search'] = dns.search + if hasattr(dns, 'include') and 'options inet6' in dns.include: + result['ip_version'] = 6 + else: + result['ip_version'] = 4 + return result + + def present(self): + params = dict() + current = self.read() + + # Temporary locations to hold the changed params + update = dict( + dns=None, + forwarders=None, + cache=None + ) + + nameservers = self.params['name_servers'] + search_domains = self.params['search'] + ip_version = self.params['ip_version'] + forwarders = self.params['forwarders'] + cache = self.params['cache'] + check_mode = self.params['check_mode'] + + if nameservers: + if 'name_servers' in current: + if nameservers != current['name_servers']: + params['nameServers'] = nameservers + else: + params['nameServers'] = nameservers + + if search_domains: + if 'search' in current: + if search_domains != current['search']: + params['search'] = search_domains + else: + params['search'] = search_domains + + if ip_version: + if 'ip_version' in current: + if ip_version != int(current['ip_version']): + if ip_version == 6: + params['include'] = 'options inet6' + elif ip_version == 4: + params['include'] = '' + else: + if ip_version == 6: + params['include'] = 'options inet6' + elif ip_version == 4: + params['include'] = '' + + if params: + self.cparams.update(camel_dict_to_snake_dict(params)) + + if 'include' in params: + del self.cparams['include'] + if params['include'] == '': + self.cparams['ip_version'] = 4 + else: + self.cparams['ip_version'] = 6 + + update['dns'] = params.copy() + params = dict() + + if forwarders: + if 'forwarders' in current: + if forwarders != current['forwarders']: + params['forwarders'] = forwarders + else: + params['forwarders'] = forwarders + + if params: + self.cparams.update(camel_dict_to_snake_dict(params)) + update['forwarders'] = ' '.join(params['forwarders']) + params = dict() + + if cache: + if 'cache' in current: + if cache != current['cache']: + params['cache'] = cache + + if params: + self.cparams.update(camel_dict_to_snake_dict(params)) + update['cache'] = params['cache'] + params = dict() + + if self.cparams: + changed = True + if check_mode: + return changed + else: + return False + + tx = self.api.tm.transactions.transaction + with TransactionContextManager(tx) as api: + cache = api.tm.sys.dbs.db.load(name='dns.cache') + proxy = api.tm.sys.dbs.db.load(name='dns.proxy.__iter__') + dns = api.tm.sys.dns.load() + + # Empty values can be supplied, but you cannot supply the + # None value, so we check for that specifically + if update['cache'] is not None: + cache.update(value=update['cache']) + if update['forwarders'] is not None: + proxy.update(value=update['forwarders']) + if update['dns'] is not None: + dns.update(**update['dns']) + return changed + + def absent(self): + params = dict() + current = self.read() + + # Temporary locations to hold the changed params + update = dict( + dns=None, + forwarders=None + ) + + nameservers = self.params['name_servers'] + search_domains = self.params['search'] + forwarders = self.params['forwarders'] + check_mode = self.params['check_mode'] + + if forwarders and 'forwarders' in current: + set_current = set(current['forwarders']) + set_new = set(forwarders) + + forwarders = set_current - set_new + if forwarders != set_current: + forwarders = list(forwarders) + params['forwarders'] = ' '.join(forwarders) + + if params: + changed = True + self.cparams.update(camel_dict_to_snake_dict(params)) + update['forwarders'] = params['forwarders'] + params = dict() + + if nameservers and 'name_servers' in current: + set_current = set(current['name_servers']) + set_new = set(nameservers) + + nameservers = set_current - set_new + if nameservers != set_current: + params['nameServers'] = list(nameservers) + + if search_domains and 'search' in current: + set_current = set(current['search']) + set_new = set(search_domains) + + search_domains = set_current - set_new + if search_domains != set_current: + params['search'] = list(search_domains) + + if params: + changed = True + self.cparams.update(camel_dict_to_snake_dict(params)) + update['dns'] = params.copy() + params = dict() + + if not self.cparams: + return False + + if check_mode: + return changed + + tx = self.api.tm.transactions.transaction + with TransactionContextManager(tx) as api: + proxy = api.tm.sys.dbs.db.load(name='dns.proxy.__iter__') + dns = api.tm.sys.dns.load() + + if update['forwarders'] is not None: + proxy.update(value=update['forwarders']) + if update['dns'] is not None: + dns.update(**update['dns']) + return changed + + +def main(): + argument_spec = f5_argument_spec() + + meta_args = dict( + cache=dict(required=False, choices=CACHE, default=None), + name_servers=dict(required=False, default=None, type='list'), + forwarders=dict(required=False, default=None, type='list'), + search=dict(required=False, default=None, type='list'), + ip_version=dict(required=False, default=None, choices=IP, type='int') + ) + argument_spec.update(meta_args) + module = AnsibleModule( + argument_spec=argument_spec, + required_one_of=[REQUIRED], + supports_check_mode=True + ) + + try: + obj = BigIpDeviceDns(check_mode=module.check_mode, **module.params) + result = obj.flush() + + module.exit_json(**result) + except F5ModuleError as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.basic import * +from ansible.module_utils.ec2 import camel_dict_to_snake_dict +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_device_ntp.py b/lib/ansible/modules/extras/network/f5/bigip_device_ntp.py new file mode 100644 index 0000000000..6dab16a3cb --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_device_ntp.py @@ -0,0 +1,257 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: bigip_device_ntp +short_description: Manage NTP servers on a BIG-IP +description: + - Manage NTP servers on a BIG-IP +version_added: "2.2" +options: + ntp_servers: + description: + - A list of NTP servers to set on the device. At least one of C(ntp_servers) + or C(timezone) is required. + required: false + default: [] + state: + description: + - The state of the NTP servers on the system. When C(present), guarantees + that the NTP servers are set on the system. When C(absent), removes the + specified NTP servers from the device configuration. + required: false + default: present + choices: + - absent + - present + timezone: + description: + - The timezone to set for NTP lookups. At least one of C(ntp_servers) or + C(timezone) is required. + default: UTC + required: false +notes: + - Requires the f5-sdk Python package on the host. This is as easy as pip + install f5-sdk. +extends_documentation_fragment: f5 +requirements: + - f5-sdk +author: + - Tim Rupp (@caphrim007) +''' + +EXAMPLES = ''' +- name: Set NTP server + bigip_device_ntp: + ntp_servers: + - "192.0.2.23" + password: "secret" + server: "lb.mydomain.com" + user: "admin" + validate_certs: "no" + delegate_to: localhost + +- name: Set timezone + bigip_device_ntp: + password: "secret" + server: "lb.mydomain.com" + timezone: "America/Los_Angeles" + user: "admin" + validate_certs: "no" + delegate_to: localhost +''' + +RETURN = ''' +ntp_servers: + description: The NTP servers that were set on the device + returned: changed + type: list + sample: ["192.0.2.23", "192.0.2.42"] +timezone: + description: The timezone that was set on the device + returned: changed + type: string + sample: "true" +''' + +try: + from f5.bigip import ManagementRoot + from icontrol.session import iControlUnexpectedHTTPError + HAS_F5SDK = True +except ImportError: + HAS_F5SDK = False + + +class BigIpDeviceNtp(object): + def __init__(self, *args, **kwargs): + if not HAS_F5SDK: + raise F5ModuleError("The python f5-sdk module is required") + + # The params that change in the module + self.cparams = dict() + + # Stores the params that are sent to the module + self.params = kwargs + self.api = ManagementRoot(kwargs['server'], + kwargs['user'], + kwargs['password'], + port=kwargs['server_port']) + + def flush(self): + result = dict() + changed = False + state = self.params['state'] + + try: + if state == "present": + changed = self.present() + elif state == "absent": + changed = self.absent() + except iControlUnexpectedHTTPError as e: + raise F5ModuleError(str(e)) + + if 'servers' in self.cparams: + self.cparams['ntp_servers'] = self.cparams.pop('servers') + + result.update(**self.cparams) + result.update(dict(changed=changed)) + return result + + def read(self): + """Read information and transform it + + The values that are returned by BIG-IP in the f5-sdk can have encoding + attached to them as well as be completely missing in some cases. + + Therefore, this method will transform the data from the BIG-IP into a + format that is more easily consumable by the rest of the class and the + parameters that are supported by the module. + """ + p = dict() + r = self.api.tm.sys.ntp.load() + + if hasattr(r, 'servers'): + # Deliberately using sets to supress duplicates + p['servers'] = set([str(x) for x in r.servers]) + if hasattr(r, 'timezone'): + p['timezone'] = str(r.timezone) + return p + + def present(self): + changed = False + params = dict() + current = self.read() + + check_mode = self.params['check_mode'] + ntp_servers = self.params['ntp_servers'] + timezone = self.params['timezone'] + + # NTP servers can be set independently + if ntp_servers is not None: + if 'servers' in current: + items = set(ntp_servers) + if items != current['servers']: + params['servers'] = list(ntp_servers) + else: + params['servers'] = ntp_servers + + # Timezone can be set independently + if timezone is not None: + if 'timezone' in current and current['timezone'] != timezone: + params['timezone'] = timezone + + if params: + changed = True + self.cparams = camel_dict_to_snake_dict(params) + if check_mode: + return changed + else: + return changed + + r = self.api.tm.sys.ntp.load() + r.update(**params) + r.refresh() + + return changed + + def absent(self): + changed = False + params = dict() + current = self.read() + + check_mode = self.params['check_mode'] + ntp_servers = self.params['ntp_servers'] + + if not ntp_servers: + raise F5ModuleError( + "Absent can only be used when removing NTP servers" + ) + + if ntp_servers and 'servers' in current: + servers = current['servers'] + new_servers = [x for x in servers if x not in ntp_servers] + + if servers != new_servers: + params['servers'] = new_servers + + if params: + changed = True + self.cparams = camel_dict_to_snake_dict(params) + if check_mode: + return changed + else: + return changed + + r = self.api.tm.sys.ntp.load() + r.update(**params) + r.refresh() + return changed + + +def main(): + argument_spec = f5_argument_spec() + + meta_args = dict( + ntp_servers=dict(required=False, type='list', default=None), + timezone=dict(default=None, required=False) + ) + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + required_one_of=[ + ['ntp_servers', 'timezone'] + ], + supports_check_mode=True + ) + + try: + obj = BigIpDeviceNtp(check_mode=module.check_mode, **module.params) + result = obj.flush() + + module.exit_json(**result) + except F5ModuleError as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.basic import * +from ansible.module_utils.ec2 import camel_dict_to_snake_dict +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_device_sshd.py b/lib/ansible/modules/extras/network/f5/bigip_device_sshd.py new file mode 100644 index 0000000000..e7a87a4e08 --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_device_sshd.py @@ -0,0 +1,344 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: bigip_device_sshd +short_description: Manage the SSHD settings of a BIG-IP +description: + - Manage the SSHD settings of a BIG-IP +version_added: "2.2" +options: + allow: + description: + - Specifies, if you have enabled SSH access, the IP address or address + range for other systems that can use SSH to communicate with this + system. + choices: + - all + - IP address, such as 172.27.1.10 + - IP range, such as 172.27.*.* or 172.27.0.0/255.255.0.0 + banner: + description: + - Whether to enable the banner or not. + required: false + choices: + - enabled + - disabled + banner_text: + description: + - Specifies the text to include on the pre-login banner that displays + when a user attempts to login to the system using SSH. + required: false + inactivity_timeout: + description: + - Specifies the number of seconds before inactivity causes an SSH + session to log out. + required: false + log_level: + description: + - Specifies the minimum SSHD message level to include in the system log. + choices: + - debug + - debug1 + - debug2 + - debug3 + - error + - fatal + - info + - quiet + - verbose + login: + description: + - Specifies, when checked C(enabled), that the system accepts SSH + communications. + choices: + - enabled + - disabled + required: false + port: + description: + - Port that you want the SSH daemon to run on. + required: false +notes: + - Requires the f5-sdk Python package on the host This is as easy as pip + install f5-sdk. + - Requires BIG-IP version 12.0.0 or greater +extends_documentation_fragment: f5 +requirements: + - f5-sdk +author: + - Tim Rupp (@caphrim007) +''' + +EXAMPLES = ''' +- name: Set the banner for the SSHD service from a string + bigip_device_sshd: + banner: "enabled" + banner_text: "banner text goes here" + password: "secret" + server: "lb.mydomain.com" + user: "admin" + delegate_to: localhost + +- name: Set the banner for the SSHD service from a file + bigip_device_sshd: + banner: "enabled" + banner_text: "{{ lookup('file', '/path/to/file') }}" + password: "secret" + server: "lb.mydomain.com" + user: "admin" + delegate_to: localhost + +- name: Set the SSHD service to run on port 2222 + bigip_device_sshd: + password: "secret" + port: 2222 + server: "lb.mydomain.com" + user: "admin" + delegate_to: localhost +''' + +RETURN = ''' +allow: + description: | + Specifies, if you have enabled SSH access, the IP address or address + range for other systems that can use SSH to communicate with this + system. + returned: changed + type: list + sample: "192.0.2.*" +banner: + description: Whether the banner is enabled or not. + returned: changed + type: string + sample: "true" +banner_text: + description: | + Specifies the text included on the pre-login banner that + displays when a user attempts to login to the system using SSH. + returned: changed and success + type: string + sample: "This is a corporate device. Connecting to it without..." +inactivity_timeout: + description: | + The number of seconds before inactivity causes an SSH. + session to log out + returned: changed + type: int + sample: "10" +log_level: + description: The minimum SSHD message level to include in the system log. + returned: changed + type: string + sample: "debug" +login: + description: Specifies that the system accepts SSH communications or not. + return: changed + type: bool + sample: true +port: + description: Port that you want the SSH daemon to run on. + return: changed + type: int + sample: 22 +''' + +try: + from f5.bigip import ManagementRoot + from icontrol.session import iControlUnexpectedHTTPError + HAS_F5SDK = True +except ImportError: + HAS_F5SDK = False + +CHOICES = ['enabled', 'disabled'] +LEVELS = ['debug', 'debug1', 'debug2', 'debug3', 'error', 'fatal', 'info', + 'quiet', 'verbose'] + + +class BigIpDeviceSshd(object): + def __init__(self, *args, **kwargs): + if not HAS_F5SDK: + raise F5ModuleError("The python f5-sdk module is required") + + # The params that change in the module + self.cparams = dict() + + # Stores the params that are sent to the module + self.params = kwargs + self.api = ManagementRoot(kwargs['server'], + kwargs['user'], + kwargs['password'], + port=kwargs['server_port']) + + def update(self): + changed = False + current = self.read() + params = dict() + + allow = self.params['allow'] + banner = self.params['banner'] + banner_text = self.params['banner_text'] + timeout = self.params['inactivity_timeout'] + log_level = self.params['log_level'] + login = self.params['login'] + port = self.params['port'] + check_mode = self.params['check_mode'] + + if allow: + if 'allow' in current: + items = set(allow) + if items != current['allow']: + params['allow'] = list(items) + else: + params['allow'] = allow + + if banner: + if 'banner' in current: + if banner != current['banner']: + params['banner'] = banner + else: + params['banner'] = banner + + if banner_text: + if 'banner_text' in current: + if banner_text != current['banner_text']: + params['bannerText'] = banner_text + else: + params['bannerText'] = banner_text + + if timeout: + if 'inactivity_timeout' in current: + if timeout != current['inactivity_timeout']: + params['inactivityTimeout'] = timeout + else: + params['inactivityTimeout'] = timeout + + if log_level: + if 'log_level' in current: + if log_level != current['log_level']: + params['logLevel'] = log_level + else: + params['logLevel'] = log_level + + if login: + if 'login' in current: + if login != current['login']: + params['login'] = login + else: + params['login'] = login + + if port: + if 'port' in current: + if port != current['port']: + params['port'] = port + else: + params['port'] = port + + if params: + changed = True + if check_mode: + return changed + self.cparams = camel_dict_to_snake_dict(params) + else: + return changed + + r = self.api.tm.sys.sshd.load() + r.update(**params) + r.refresh() + + return changed + + def read(self): + """Read information and transform it + + The values that are returned by BIG-IP in the f5-sdk can have encoding + attached to them as well as be completely missing in some cases. + + Therefore, this method will transform the data from the BIG-IP into a + format that is more easily consumable by the rest of the class and the + parameters that are supported by the module. + """ + p = dict() + r = self.api.tm.sys.sshd.load() + + if hasattr(r, 'allow'): + # Deliberately using sets to supress duplicates + p['allow'] = set([str(x) for x in r.allow]) + if hasattr(r, 'banner'): + p['banner'] = str(r.banner) + if hasattr(r, 'bannerText'): + p['banner_text'] = str(r.bannerText) + if hasattr(r, 'inactivityTimeout'): + p['inactivity_timeout'] = str(r.inactivityTimeout) + if hasattr(r, 'logLevel'): + p['log_level'] = str(r.logLevel) + if hasattr(r, 'login'): + p['login'] = str(r.login) + if hasattr(r, 'port'): + p['port'] = int(r.port) + return p + + def flush(self): + result = dict() + changed = False + + try: + changed = self.update() + except iControlUnexpectedHTTPError as e: + raise F5ModuleError(str(e)) + + result.update(**self.cparams) + result.update(dict(changed=changed)) + return result + + +def main(): + argument_spec = f5_argument_spec() + + meta_args = dict( + allow=dict(required=False, default=None, type='list'), + banner=dict(required=False, default=None, choices=CHOICES), + banner_text=dict(required=False, default=None), + inactivity_timeout=dict(required=False, default=None, type='int'), + log_level=dict(required=False, default=None, choices=LEVELS), + login=dict(required=False, default=None, choices=CHOICES), + port=dict(required=False, default=None, type='int'), + state=dict(default='present', choices=['present']) + ) + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + try: + obj = BigIpDeviceSshd(check_mode=module.check_mode, **module.params) + result = obj.flush() + + module.exit_json(**result) + except F5ModuleError as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.basic import * +from ansible.module_utils.ec2 import camel_dict_to_snake_dict +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_facts.py b/lib/ansible/modules/extras/network/f5/bigip_facts.py new file mode 100644 index 0000000000..dc6c6b7d1d --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_facts.py @@ -0,0 +1,1724 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (c) 2013, Matt Hite <mhite@hotmail.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: bigip_facts +short_description: Collect facts from F5 BIG-IP devices +description: + - Collect facts from F5 BIG-IP devices via iControl SOAP API +version_added: "1.6" +author: + - Matt Hite (@mhite) + - Tim Rupp (@caphrim007) +notes: + - Requires BIG-IP software version >= 11.4 + - F5 developed module 'bigsuds' required (see http://devcentral.f5.com) + - Best run as a local_action in your playbook + - Tested with manager and above account privilege level + - C(provision) facts were added in 2.2 +requirements: + - bigsuds +options: + session: + description: + - BIG-IP session support; may be useful to avoid concurrency + issues in certain circumstances. + required: false + default: true + choices: [] + aliases: [] + include: + description: + - Fact category or list of categories to collect + required: true + default: null + choices: + - address_class + - certificate + - client_ssl_profile + - device + - device_group + - interface + - key + - node + - pool + - provision + - rule + - self_ip + - software + - system_info + - traffic_group + - trunk + - virtual_address + - virtual_server + - vlan + aliases: [] + filter: + description: + - Shell-style glob matching string used to filter fact keys. Not + applicable for software, provision, and system_info fact categories. + required: false + default: null + choices: [] + aliases: [] +extends_documentation_fragment: f5 +''' + +EXAMPLES = ''' +- name: Collect BIG-IP facts + bigip_facts: + server: "lb.mydomain.com" + user: "admin" + password: "secret" + include: "interface,vlan" + delegate_to: localhost +''' + +try: + from suds import MethodNotFound, WebFault +except ImportError: + bigsuds_found = False +else: + bigsuds_found = True + +import fnmatch +import re +import traceback + + +class F5(object): + """F5 iControl class. + + F5 BIG-IP iControl API class. + + Attributes: + api: iControl API instance. + """ + + def __init__(self, host, user, password, session=False, validate_certs=True, port=443): + self.api = bigip_api(host, user, password, validate_certs, port) + if session: + self.start_session() + + def start_session(self): + self.api = self.api.with_session_id() + + def get_api(self): + return self.api + + def set_recursive_query_state(self, state): + self.api.System.Session.set_recursive_query_state(state) + + def get_recursive_query_state(self): + return self.api.System.Session.get_recursive_query_state() + + def enable_recursive_query_state(self): + self.set_recursive_query_state('STATE_ENABLED') + + def disable_recursive_query_state(self): + self.set_recursive_query_state('STATE_DISABLED') + + def set_active_folder(self, folder): + self.api.System.Session.set_active_folder(folder=folder) + + def get_active_folder(self): + return self.api.System.Session.get_active_folder() + + +class Interfaces(object): + """Interfaces class. + + F5 BIG-IP interfaces class. + + Attributes: + api: iControl API instance. + interfaces: A list of BIG-IP interface names. + """ + + def __init__(self, api, regex=None): + self.api = api + self.interfaces = api.Networking.Interfaces.get_list() + if regex: + re_filter = re.compile(regex) + self.interfaces = filter(re_filter.search, self.interfaces) + + def get_list(self): + return self.interfaces + + def get_active_media(self): + return self.api.Networking.Interfaces.get_active_media(self.interfaces) + + def get_actual_flow_control(self): + return self.api.Networking.Interfaces.get_actual_flow_control(self.interfaces) + + def get_bundle_state(self): + return self.api.Networking.Interfaces.get_bundle_state(self.interfaces) + + def get_description(self): + return self.api.Networking.Interfaces.get_description(self.interfaces) + + def get_dual_media_state(self): + return self.api.Networking.Interfaces.get_dual_media_state(self.interfaces) + + def get_enabled_state(self): + return self.api.Networking.Interfaces.get_enabled_state(self.interfaces) + + def get_if_index(self): + return self.api.Networking.Interfaces.get_if_index(self.interfaces) + + def get_learning_mode(self): + return self.api.Networking.Interfaces.get_learning_mode(self.interfaces) + + def get_lldp_admin_status(self): + return self.api.Networking.Interfaces.get_lldp_admin_status(self.interfaces) + + def get_lldp_tlvmap(self): + return self.api.Networking.Interfaces.get_lldp_tlvmap(self.interfaces) + + def get_mac_address(self): + return self.api.Networking.Interfaces.get_mac_address(self.interfaces) + + def get_media(self): + return self.api.Networking.Interfaces.get_media(self.interfaces) + + def get_media_option(self): + return self.api.Networking.Interfaces.get_media_option(self.interfaces) + + def get_media_option_sfp(self): + return self.api.Networking.Interfaces.get_media_option_sfp(self.interfaces) + + def get_media_sfp(self): + return self.api.Networking.Interfaces.get_media_sfp(self.interfaces) + + def get_media_speed(self): + return self.api.Networking.Interfaces.get_media_speed(self.interfaces) + + def get_media_status(self): + return self.api.Networking.Interfaces.get_media_status(self.interfaces) + + def get_mtu(self): + return self.api.Networking.Interfaces.get_mtu(self.interfaces) + + def get_phy_master_slave_mode(self): + return self.api.Networking.Interfaces.get_phy_master_slave_mode(self.interfaces) + + def get_prefer_sfp_state(self): + return self.api.Networking.Interfaces.get_prefer_sfp_state(self.interfaces) + + def get_flow_control(self): + return self.api.Networking.Interfaces.get_requested_flow_control(self.interfaces) + + def get_sflow_poll_interval(self): + return self.api.Networking.Interfaces.get_sflow_poll_interval(self.interfaces) + + def get_sflow_poll_interval_global(self): + return self.api.Networking.Interfaces.get_sflow_poll_interval_global(self.interfaces) + + def get_sfp_media_state(self): + return self.api.Networking.Interfaces.get_sfp_media_state(self.interfaces) + + def get_stp_active_edge_port_state(self): + return self.api.Networking.Interfaces.get_stp_active_edge_port_state(self.interfaces) + + def get_stp_enabled_state(self): + return self.api.Networking.Interfaces.get_stp_enabled_state(self.interfaces) + + def get_stp_link_type(self): + return self.api.Networking.Interfaces.get_stp_link_type(self.interfaces) + + def get_stp_protocol_detection_reset_state(self): + return self.api.Networking.Interfaces.get_stp_protocol_detection_reset_state(self.interfaces) + + +class SelfIPs(object): + """Self IPs class. + + F5 BIG-IP Self IPs class. + + Attributes: + api: iControl API instance. + self_ips: List of self IPs. + """ + + def __init__(self, api, regex=None): + self.api = api + self.self_ips = api.Networking.SelfIPV2.get_list() + if regex: + re_filter = re.compile(regex) + self.self_ips = filter(re_filter.search, self.self_ips) + + def get_list(self): + return self.self_ips + + def get_address(self): + return self.api.Networking.SelfIPV2.get_address(self.self_ips) + + def get_allow_access_list(self): + return self.api.Networking.SelfIPV2.get_allow_access_list(self.self_ips) + + def get_description(self): + return self.api.Networking.SelfIPV2.get_description(self.self_ips) + + def get_enforced_firewall_policy(self): + return self.api.Networking.SelfIPV2.get_enforced_firewall_policy(self.self_ips) + + def get_floating_state(self): + return self.api.Networking.SelfIPV2.get_floating_state(self.self_ips) + + def get_fw_rule(self): + return self.api.Networking.SelfIPV2.get_fw_rule(self.self_ips) + + def get_netmask(self): + return self.api.Networking.SelfIPV2.get_netmask(self.self_ips) + + def get_staged_firewall_policy(self): + return self.api.Networking.SelfIPV2.get_staged_firewall_policy(self.self_ips) + + def get_traffic_group(self): + return self.api.Networking.SelfIPV2.get_traffic_group(self.self_ips) + + def get_vlan(self): + return self.api.Networking.SelfIPV2.get_vlan(self.self_ips) + + def get_is_traffic_group_inherited(self): + return self.api.Networking.SelfIPV2.is_traffic_group_inherited(self.self_ips) + + +class Trunks(object): + """Trunks class. + + F5 BIG-IP trunks class. + + Attributes: + api: iControl API instance. + trunks: List of trunks. + """ + + def __init__(self, api, regex=None): + self.api = api + self.trunks = api.Networking.Trunk.get_list() + if regex: + re_filter = re.compile(regex) + self.trunks = filter(re_filter.search, self.trunks) + + def get_list(self): + return self.trunks + + def get_active_lacp_state(self): + return self.api.Networking.Trunk.get_active_lacp_state(self.trunks) + + def get_configured_member_count(self): + return self.api.Networking.Trunk.get_configured_member_count(self.trunks) + + def get_description(self): + return self.api.Networking.Trunk.get_description(self.trunks) + + def get_distribution_hash_option(self): + return self.api.Networking.Trunk.get_distribution_hash_option(self.trunks) + + def get_interface(self): + return self.api.Networking.Trunk.get_interface(self.trunks) + + def get_lacp_enabled_state(self): + return self.api.Networking.Trunk.get_lacp_enabled_state(self.trunks) + + def get_lacp_timeout_option(self): + return self.api.Networking.Trunk.get_lacp_timeout_option(self.trunks) + + def get_link_selection_policy(self): + return self.api.Networking.Trunk.get_link_selection_policy(self.trunks) + + def get_media_speed(self): + return self.api.Networking.Trunk.get_media_speed(self.trunks) + + def get_media_status(self): + return self.api.Networking.Trunk.get_media_status(self.trunks) + + def get_operational_member_count(self): + return self.api.Networking.Trunk.get_operational_member_count(self.trunks) + + def get_stp_enabled_state(self): + return self.api.Networking.Trunk.get_stp_enabled_state(self.trunks) + + def get_stp_protocol_detection_reset_state(self): + return self.api.Networking.Trunk.get_stp_protocol_detection_reset_state(self.trunks) + + +class Vlans(object): + """Vlans class. + + F5 BIG-IP Vlans class. + + Attributes: + api: iControl API instance. + vlans: List of VLANs. + """ + + def __init__(self, api, regex=None): + self.api = api + self.vlans = api.Networking.VLAN.get_list() + if regex: + re_filter = re.compile(regex) + self.vlans = filter(re_filter.search, self.vlans) + + def get_list(self): + return self.vlans + + def get_auto_lasthop(self): + return self.api.Networking.VLAN.get_auto_lasthop(self.vlans) + + def get_cmp_hash_algorithm(self): + return self.api.Networking.VLAN.get_cmp_hash_algorithm(self.vlans) + + def get_description(self): + return self.api.Networking.VLAN.get_description(self.vlans) + + def get_dynamic_forwarding(self): + return self.api.Networking.VLAN.get_dynamic_forwarding(self.vlans) + + def get_failsafe_action(self): + return self.api.Networking.VLAN.get_failsafe_action(self.vlans) + + def get_failsafe_state(self): + return self.api.Networking.VLAN.get_failsafe_state(self.vlans) + + def get_failsafe_timeout(self): + return self.api.Networking.VLAN.get_failsafe_timeout(self.vlans) + + def get_if_index(self): + return self.api.Networking.VLAN.get_if_index(self.vlans) + + def get_learning_mode(self): + return self.api.Networking.VLAN.get_learning_mode(self.vlans) + + def get_mac_masquerade_address(self): + return self.api.Networking.VLAN.get_mac_masquerade_address(self.vlans) + + def get_member(self): + return self.api.Networking.VLAN.get_member(self.vlans) + + def get_mtu(self): + return self.api.Networking.VLAN.get_mtu(self.vlans) + + def get_sflow_poll_interval(self): + return self.api.Networking.VLAN.get_sflow_poll_interval(self.vlans) + + def get_sflow_poll_interval_global(self): + return self.api.Networking.VLAN.get_sflow_poll_interval_global(self.vlans) + + def get_sflow_sampling_rate(self): + return self.api.Networking.VLAN.get_sflow_sampling_rate(self.vlans) + + def get_sflow_sampling_rate_global(self): + return self.api.Networking.VLAN.get_sflow_sampling_rate_global(self.vlans) + + def get_source_check_state(self): + return self.api.Networking.VLAN.get_source_check_state(self.vlans) + + def get_true_mac_address(self): + return self.api.Networking.VLAN.get_true_mac_address(self.vlans) + + def get_vlan_id(self): + return self.api.Networking.VLAN.get_vlan_id(self.vlans) + + +class Software(object): + """Software class. + + F5 BIG-IP software class. + + Attributes: + api: iControl API instance. + """ + + def __init__(self, api): + self.api = api + + def get_all_software_status(self): + return self.api.System.SoftwareManagement.get_all_software_status() + + +class VirtualServers(object): + """Virtual servers class. + + F5 BIG-IP virtual servers class. + + Attributes: + api: iControl API instance. + virtual_servers: List of virtual servers. + """ + + def __init__(self, api, regex=None): + self.api = api + self.virtual_servers = api.LocalLB.VirtualServer.get_list() + if regex: + re_filter = re.compile(regex) + self.virtual_servers = filter(re_filter.search, self.virtual_servers) + + def get_list(self): + return self.virtual_servers + + def get_actual_hardware_acceleration(self): + return self.api.LocalLB.VirtualServer.get_actual_hardware_acceleration(self.virtual_servers) + + def get_authentication_profile(self): + return self.api.LocalLB.VirtualServer.get_authentication_profile(self.virtual_servers) + + def get_auto_lasthop(self): + return self.api.LocalLB.VirtualServer.get_auto_lasthop(self.virtual_servers) + + def get_bw_controller_policy(self): + return self.api.LocalLB.VirtualServer.get_bw_controller_policy(self.virtual_servers) + + def get_clone_pool(self): + return self.api.LocalLB.VirtualServer.get_clone_pool(self.virtual_servers) + + def get_cmp_enable_mode(self): + return self.api.LocalLB.VirtualServer.get_cmp_enable_mode(self.virtual_servers) + + def get_connection_limit(self): + return self.api.LocalLB.VirtualServer.get_connection_limit(self.virtual_servers) + + def get_connection_mirror_state(self): + return self.api.LocalLB.VirtualServer.get_connection_mirror_state(self.virtual_servers) + + def get_default_pool_name(self): + return self.api.LocalLB.VirtualServer.get_default_pool_name(self.virtual_servers) + + def get_description(self): + return self.api.LocalLB.VirtualServer.get_description(self.virtual_servers) + + def get_destination(self): + return self.api.LocalLB.VirtualServer.get_destination_v2(self.virtual_servers) + + def get_enabled_state(self): + return self.api.LocalLB.VirtualServer.get_enabled_state(self.virtual_servers) + + def get_enforced_firewall_policy(self): + return self.api.LocalLB.VirtualServer.get_enforced_firewall_policy(self.virtual_servers) + + def get_fallback_persistence_profile(self): + return self.api.LocalLB.VirtualServer.get_fallback_persistence_profile(self.virtual_servers) + + def get_fw_rule(self): + return self.api.LocalLB.VirtualServer.get_fw_rule(self.virtual_servers) + + def get_gtm_score(self): + return self.api.LocalLB.VirtualServer.get_gtm_score(self.virtual_servers) + + def get_last_hop_pool(self): + return self.api.LocalLB.VirtualServer.get_last_hop_pool(self.virtual_servers) + + def get_nat64_state(self): + return self.api.LocalLB.VirtualServer.get_nat64_state(self.virtual_servers) + + def get_object_status(self): + return self.api.LocalLB.VirtualServer.get_object_status(self.virtual_servers) + + def get_persistence_profile(self): + return self.api.LocalLB.VirtualServer.get_persistence_profile(self.virtual_servers) + + def get_profile(self): + return self.api.LocalLB.VirtualServer.get_profile(self.virtual_servers) + + def get_protocol(self): + return self.api.LocalLB.VirtualServer.get_protocol(self.virtual_servers) + + def get_rate_class(self): + return self.api.LocalLB.VirtualServer.get_rate_class(self.virtual_servers) + + def get_rate_limit(self): + return self.api.LocalLB.VirtualServer.get_rate_limit(self.virtual_servers) + + def get_rate_limit_destination_mask(self): + return self.api.LocalLB.VirtualServer.get_rate_limit_destination_mask(self.virtual_servers) + + def get_rate_limit_mode(self): + return self.api.LocalLB.VirtualServer.get_rate_limit_mode(self.virtual_servers) + + def get_rate_limit_source_mask(self): + return self.api.LocalLB.VirtualServer.get_rate_limit_source_mask(self.virtual_servers) + + def get_related_rule(self): + return self.api.LocalLB.VirtualServer.get_related_rule(self.virtual_servers) + + def get_rule(self): + return self.api.LocalLB.VirtualServer.get_rule(self.virtual_servers) + + def get_security_log_profile(self): + return self.api.LocalLB.VirtualServer.get_security_log_profile(self.virtual_servers) + + def get_snat_pool(self): + return self.api.LocalLB.VirtualServer.get_snat_pool(self.virtual_servers) + + def get_snat_type(self): + return self.api.LocalLB.VirtualServer.get_snat_type(self.virtual_servers) + + def get_source_address(self): + return self.api.LocalLB.VirtualServer.get_source_address(self.virtual_servers) + + def get_source_address_translation_lsn_pool(self): + return self.api.LocalLB.VirtualServer.get_source_address_translation_lsn_pool(self.virtual_servers) + + def get_source_address_translation_snat_pool(self): + return self.api.LocalLB.VirtualServer.get_source_address_translation_snat_pool(self.virtual_servers) + + def get_source_address_translation_type(self): + return self.api.LocalLB.VirtualServer.get_source_address_translation_type(self.virtual_servers) + + def get_source_port_behavior(self): + return self.api.LocalLB.VirtualServer.get_source_port_behavior(self.virtual_servers) + + def get_staged_firewall_policy(self): + return self.api.LocalLB.VirtualServer.get_staged_firewall_policy(self.virtual_servers) + + def get_translate_address_state(self): + return self.api.LocalLB.VirtualServer.get_translate_address_state(self.virtual_servers) + + def get_translate_port_state(self): + return self.api.LocalLB.VirtualServer.get_translate_port_state(self.virtual_servers) + + def get_type(self): + return self.api.LocalLB.VirtualServer.get_type(self.virtual_servers) + + def get_vlan(self): + return self.api.LocalLB.VirtualServer.get_vlan(self.virtual_servers) + + def get_wildmask(self): + return self.api.LocalLB.VirtualServer.get_wildmask(self.virtual_servers) + + +class Pools(object): + """Pools class. + + F5 BIG-IP pools class. + + Attributes: + api: iControl API instance. + pool_names: List of pool names. + """ + + def __init__(self, api, regex=None): + self.api = api + self.pool_names = api.LocalLB.Pool.get_list() + if regex: + re_filter = re.compile(regex) + self.pool_names = filter(re_filter.search, self.pool_names) + + def get_list(self): + return self.pool_names + + def get_action_on_service_down(self): + return self.api.LocalLB.Pool.get_action_on_service_down(self.pool_names) + + def get_active_member_count(self): + return self.api.LocalLB.Pool.get_active_member_count(self.pool_names) + + def get_aggregate_dynamic_ratio(self): + return self.api.LocalLB.Pool.get_aggregate_dynamic_ratio(self.pool_names) + + def get_allow_nat_state(self): + return self.api.LocalLB.Pool.get_allow_nat_state(self.pool_names) + + def get_allow_snat_state(self): + return self.api.LocalLB.Pool.get_allow_snat_state(self.pool_names) + + def get_client_ip_tos(self): + return self.api.LocalLB.Pool.get_client_ip_tos(self.pool_names) + + def get_client_link_qos(self): + return self.api.LocalLB.Pool.get_client_link_qos(self.pool_names) + + def get_description(self): + return self.api.LocalLB.Pool.get_description(self.pool_names) + + def get_gateway_failsafe_device(self): + return self.api.LocalLB.Pool.get_gateway_failsafe_device(self.pool_names) + + def get_ignore_persisted_weight_state(self): + return self.api.LocalLB.Pool.get_ignore_persisted_weight_state(self.pool_names) + + def get_lb_method(self): + return self.api.LocalLB.Pool.get_lb_method(self.pool_names) + + def get_member(self): + return self.api.LocalLB.Pool.get_member_v2(self.pool_names) + + def get_minimum_active_member(self): + return self.api.LocalLB.Pool.get_minimum_active_member(self.pool_names) + + def get_minimum_up_member(self): + return self.api.LocalLB.Pool.get_minimum_up_member(self.pool_names) + + def get_minimum_up_member_action(self): + return self.api.LocalLB.Pool.get_minimum_up_member_action(self.pool_names) + + def get_minimum_up_member_enabled_state(self): + return self.api.LocalLB.Pool.get_minimum_up_member_enabled_state(self.pool_names) + + def get_monitor_association(self): + return self.api.LocalLB.Pool.get_monitor_association(self.pool_names) + + def get_monitor_instance(self): + return self.api.LocalLB.Pool.get_monitor_instance(self.pool_names) + + def get_object_status(self): + return self.api.LocalLB.Pool.get_object_status(self.pool_names) + + def get_profile(self): + return self.api.LocalLB.Pool.get_profile(self.pool_names) + + def get_queue_depth_limit(self): + return self.api.LocalLB.Pool.get_queue_depth_limit(self.pool_names) + + def get_queue_on_connection_limit_state(self): + return self.api.LocalLB.Pool.get_queue_on_connection_limit_state(self.pool_names) + + def get_queue_time_limit(self): + return self.api.LocalLB.Pool.get_queue_time_limit(self.pool_names) + + def get_reselect_tries(self): + return self.api.LocalLB.Pool.get_reselect_tries(self.pool_names) + + def get_server_ip_tos(self): + return self.api.LocalLB.Pool.get_server_ip_tos(self.pool_names) + + def get_server_link_qos(self): + return self.api.LocalLB.Pool.get_server_link_qos(self.pool_names) + + def get_simple_timeout(self): + return self.api.LocalLB.Pool.get_simple_timeout(self.pool_names) + + def get_slow_ramp_time(self): + return self.api.LocalLB.Pool.get_slow_ramp_time(self.pool_names) + + +class Devices(object): + """Devices class. + + F5 BIG-IP devices class. + + Attributes: + api: iControl API instance. + devices: List of devices. + """ + + def __init__(self, api, regex=None): + self.api = api + self.devices = api.Management.Device.get_list() + if regex: + re_filter = re.compile(regex) + self.devices = filter(re_filter.search, self.devices) + + def get_list(self): + return self.devices + + def get_active_modules(self): + return self.api.Management.Device.get_active_modules(self.devices) + + def get_base_mac_address(self): + return self.api.Management.Device.get_base_mac_address(self.devices) + + def get_blade_addresses(self): + return self.api.Management.Device.get_blade_addresses(self.devices) + + def get_build(self): + return self.api.Management.Device.get_build(self.devices) + + def get_chassis_id(self): + return self.api.Management.Device.get_chassis_id(self.devices) + + def get_chassis_type(self): + return self.api.Management.Device.get_chassis_type(self.devices) + + def get_comment(self): + return self.api.Management.Device.get_comment(self.devices) + + def get_configsync_address(self): + return self.api.Management.Device.get_configsync_address(self.devices) + + def get_contact(self): + return self.api.Management.Device.get_contact(self.devices) + + def get_description(self): + return self.api.Management.Device.get_description(self.devices) + + def get_edition(self): + return self.api.Management.Device.get_edition(self.devices) + + def get_failover_state(self): + return self.api.Management.Device.get_failover_state(self.devices) + + def get_local_device(self): + return self.api.Management.Device.get_local_device() + + def get_hostname(self): + return self.api.Management.Device.get_hostname(self.devices) + + def get_inactive_modules(self): + return self.api.Management.Device.get_inactive_modules(self.devices) + + def get_location(self): + return self.api.Management.Device.get_location(self.devices) + + def get_management_address(self): + return self.api.Management.Device.get_management_address(self.devices) + + def get_marketing_name(self): + return self.api.Management.Device.get_marketing_name(self.devices) + + def get_multicast_address(self): + return self.api.Management.Device.get_multicast_address(self.devices) + + def get_optional_modules(self): + return self.api.Management.Device.get_optional_modules(self.devices) + + def get_platform_id(self): + return self.api.Management.Device.get_platform_id(self.devices) + + def get_primary_mirror_address(self): + return self.api.Management.Device.get_primary_mirror_address(self.devices) + + def get_product(self): + return self.api.Management.Device.get_product(self.devices) + + def get_secondary_mirror_address(self): + return self.api.Management.Device.get_secondary_mirror_address(self.devices) + + def get_software_version(self): + return self.api.Management.Device.get_software_version(self.devices) + + def get_timelimited_modules(self): + return self.api.Management.Device.get_timelimited_modules(self.devices) + + def get_timezone(self): + return self.api.Management.Device.get_timezone(self.devices) + + def get_unicast_addresses(self): + return self.api.Management.Device.get_unicast_addresses(self.devices) + + +class DeviceGroups(object): + """Device groups class. + + F5 BIG-IP device groups class. + + Attributes: + api: iControl API instance. + device_groups: List of device groups. + """ + + def __init__(self, api, regex=None): + self.api = api + self.device_groups = api.Management.DeviceGroup.get_list() + if regex: + re_filter = re.compile(regex) + self.device_groups = filter(re_filter.search, self.device_groups) + + def get_list(self): + return self.device_groups + + def get_all_preferred_active(self): + return self.api.Management.DeviceGroup.get_all_preferred_active(self.device_groups) + + def get_autosync_enabled_state(self): + return self.api.Management.DeviceGroup.get_autosync_enabled_state(self.device_groups) + + def get_description(self): + return self.api.Management.DeviceGroup.get_description(self.device_groups) + + def get_device(self): + return self.api.Management.DeviceGroup.get_device(self.device_groups) + + def get_full_load_on_sync_state(self): + return self.api.Management.DeviceGroup.get_full_load_on_sync_state(self.device_groups) + + def get_incremental_config_sync_size_maximum(self): + return self.api.Management.DeviceGroup.get_incremental_config_sync_size_maximum(self.device_groups) + + def get_network_failover_enabled_state(self): + return self.api.Management.DeviceGroup.get_network_failover_enabled_state(self.device_groups) + + def get_sync_status(self): + return self.api.Management.DeviceGroup.get_sync_status(self.device_groups) + + def get_type(self): + return self.api.Management.DeviceGroup.get_type(self.device_groups) + + +class TrafficGroups(object): + """Traffic groups class. + + F5 BIG-IP traffic groups class. + + Attributes: + api: iControl API instance. + traffic_groups: List of traffic groups. + """ + + def __init__(self, api, regex=None): + self.api = api + self.traffic_groups = api.Management.TrafficGroup.get_list() + if regex: + re_filter = re.compile(regex) + self.traffic_groups = filter(re_filter.search, self.traffic_groups) + + def get_list(self): + return self.traffic_groups + + def get_auto_failback_enabled_state(self): + return self.api.Management.TrafficGroup.get_auto_failback_enabled_state(self.traffic_groups) + + def get_auto_failback_time(self): + return self.api.Management.TrafficGroup.get_auto_failback_time(self.traffic_groups) + + def get_default_device(self): + return self.api.Management.TrafficGroup.get_default_device(self.traffic_groups) + + def get_description(self): + return self.api.Management.TrafficGroup.get_description(self.traffic_groups) + + def get_ha_load_factor(self): + return self.api.Management.TrafficGroup.get_ha_load_factor(self.traffic_groups) + + def get_ha_order(self): + return self.api.Management.TrafficGroup.get_ha_order(self.traffic_groups) + + def get_is_floating(self): + return self.api.Management.TrafficGroup.get_is_floating(self.traffic_groups) + + def get_mac_masquerade_address(self): + return self.api.Management.TrafficGroup.get_mac_masquerade_address(self.traffic_groups) + + def get_unit_id(self): + return self.api.Management.TrafficGroup.get_unit_id(self.traffic_groups) + + +class Rules(object): + """Rules class. + + F5 BIG-IP iRules class. + + Attributes: + api: iControl API instance. + rules: List of iRules. + """ + + def __init__(self, api, regex=None): + self.api = api + self.rules = api.LocalLB.Rule.get_list() + if regex: + re_filter = re.compile(regex) + self.traffic_groups = filter(re_filter.search, self.rules) + + def get_list(self): + return self.rules + + def get_description(self): + return self.api.LocalLB.Rule.get_description(rule_names=self.rules) + + def get_ignore_vertification(self): + return self.api.LocalLB.Rule.get_ignore_vertification(rule_names=self.rules) + + def get_verification_status(self): + return self.api.LocalLB.Rule.get_verification_status_v2(rule_names=self.rules) + + def get_definition(self): + return [x['rule_definition'] for x in self.api.LocalLB.Rule.query_rule(rule_names=self.rules)] + + +class Nodes(object): + """Nodes class. + + F5 BIG-IP nodes class. + + Attributes: + api: iControl API instance. + nodes: List of nodes. + """ + + def __init__(self, api, regex=None): + self.api = api + self.nodes = api.LocalLB.NodeAddressV2.get_list() + if regex: + re_filter = re.compile(regex) + self.nodes = filter(re_filter.search, self.nodes) + + def get_list(self): + return self.nodes + + def get_address(self): + return self.api.LocalLB.NodeAddressV2.get_address(nodes=self.nodes) + + def get_connection_limit(self): + return self.api.LocalLB.NodeAddressV2.get_connection_limit(nodes=self.nodes) + + def get_description(self): + return self.api.LocalLB.NodeAddressV2.get_description(nodes=self.nodes) + + def get_dynamic_ratio(self): + return self.api.LocalLB.NodeAddressV2.get_dynamic_ratio_v2(nodes=self.nodes) + + def get_monitor_instance(self): + return self.api.LocalLB.NodeAddressV2.get_monitor_instance(nodes=self.nodes) + + def get_monitor_rule(self): + return self.api.LocalLB.NodeAddressV2.get_monitor_rule(nodes=self.nodes) + + def get_monitor_status(self): + return self.api.LocalLB.NodeAddressV2.get_monitor_status(nodes=self.nodes) + + def get_object_status(self): + return self.api.LocalLB.NodeAddressV2.get_object_status(nodes=self.nodes) + + def get_rate_limit(self): + return self.api.LocalLB.NodeAddressV2.get_rate_limit(nodes=self.nodes) + + def get_ratio(self): + return self.api.LocalLB.NodeAddressV2.get_ratio(nodes=self.nodes) + + def get_session_status(self): + return self.api.LocalLB.NodeAddressV2.get_session_status(nodes=self.nodes) + + +class VirtualAddresses(object): + """Virtual addresses class. + + F5 BIG-IP virtual addresses class. + + Attributes: + api: iControl API instance. + virtual_addresses: List of virtual addresses. + """ + + def __init__(self, api, regex=None): + self.api = api + self.virtual_addresses = api.LocalLB.VirtualAddressV2.get_list() + if regex: + re_filter = re.compile(regex) + self.virtual_addresses = filter(re_filter.search, self.virtual_addresses) + + def get_list(self): + return self.virtual_addresses + + def get_address(self): + return self.api.LocalLB.VirtualAddressV2.get_address(self.virtual_addresses) + + def get_arp_state(self): + return self.api.LocalLB.VirtualAddressV2.get_arp_state(self.virtual_addresses) + + def get_auto_delete_state(self): + return self.api.LocalLB.VirtualAddressV2.get_auto_delete_state(self.virtual_addresses) + + def get_connection_limit(self): + return self.api.LocalLB.VirtualAddressV2.get_connection_limit(self.virtual_addresses) + + def get_description(self): + return self.api.LocalLB.VirtualAddressV2.get_description(self.virtual_addresses) + + def get_enabled_state(self): + return self.api.LocalLB.VirtualAddressV2.get_enabled_state(self.virtual_addresses) + + def get_icmp_echo_state(self): + return self.api.LocalLB.VirtualAddressV2.get_icmp_echo_state(self.virtual_addresses) + + def get_is_floating_state(self): + return self.api.LocalLB.VirtualAddressV2.get_is_floating_state(self.virtual_addresses) + + def get_netmask(self): + return self.api.LocalLB.VirtualAddressV2.get_netmask(self.virtual_addresses) + + def get_object_status(self): + return self.api.LocalLB.VirtualAddressV2.get_object_status(self.virtual_addresses) + + def get_route_advertisement_state(self): + return self.api.LocalLB.VirtualAddressV2.get_route_advertisement_state(self.virtual_addresses) + + def get_traffic_group(self): + return self.api.LocalLB.VirtualAddressV2.get_traffic_group(self.virtual_addresses) + + +class AddressClasses(object): + """Address group/class class. + + F5 BIG-IP address group/class class. + + Attributes: + api: iControl API instance. + address_classes: List of address classes. + """ + + def __init__(self, api, regex=None): + self.api = api + self.address_classes = api.LocalLB.Class.get_address_class_list() + if regex: + re_filter = re.compile(regex) + self.address_classes = filter(re_filter.search, self.address_classes) + + def get_list(self): + return self.address_classes + + def get_address_class(self): + key = self.api.LocalLB.Class.get_address_class(self.address_classes) + value = self.api.LocalLB.Class.get_address_class_member_data_value(key) + result = list(map(zip, [x['members'] for x in key], value)) + return result + + def get_description(self): + return self.api.LocalLB.Class.get_description(self.address_classes) + + +class Certificates(object): + """Certificates class. + + F5 BIG-IP certificates class. + + Attributes: + api: iControl API instance. + certificates: List of certificate identifiers. + certificate_list: List of certificate information structures. + """ + + def __init__(self, api, regex=None, mode="MANAGEMENT_MODE_DEFAULT"): + self.api = api + self.certificate_list = api.Management.KeyCertificate.get_certificate_list(mode=mode) + self.certificates = [x['certificate']['cert_info']['id'] for x in self.certificate_list] + if regex: + re_filter = re.compile(regex) + self.certificates = filter(re_filter.search, self.certificates) + self.certificate_list = [x for x in self.certificate_list if x['certificate']['cert_info']['id'] in self.certificates] + + def get_list(self): + return self.certificates + + def get_certificate_list(self): + return self.certificate_list + + +class Keys(object): + """Keys class. + + F5 BIG-IP keys class. + + Attributes: + api: iControl API instance. + keys: List of key identifiers. + key_list: List of key information structures. + """ + + def __init__(self, api, regex=None, mode="MANAGEMENT_MODE_DEFAULT"): + self.api = api + self.key_list = api.Management.KeyCertificate.get_key_list(mode=mode) + self.keys = [x['key_info']['id'] for x in self.key_list] + if regex: + re_filter = re.compile(regex) + self.keys = filter(re_filter.search, self.keys) + self.key_list = [x for x in self.key_list if x['key_info']['id'] in self.keys] + + def get_list(self): + return self.keys + + def get_key_list(self): + return self.key_list + + +class ProfileClientSSL(object): + """Client SSL profiles class. + + F5 BIG-IP client SSL profiles class. + + Attributes: + api: iControl API instance. + profiles: List of client SSL profiles. + """ + + def __init__(self, api, regex=None): + self.api = api + self.profiles = api.LocalLB.ProfileClientSSL.get_list() + if regex: + re_filter = re.compile(regex) + self.profiles = filter(re_filter.search, self.profiles) + + def get_list(self): + return self.profiles + + def get_alert_timeout(self): + return self.api.LocalLB.ProfileClientSSL.get_alert_timeout(self.profiles) + + def get_allow_nonssl_state(self): + return self.api.LocalLB.ProfileClientSSL.get_allow_nonssl_state(self.profiles) + + def get_authenticate_depth(self): + return self.api.LocalLB.ProfileClientSSL.get_authenticate_depth(self.profiles) + + def get_authenticate_once_state(self): + return self.api.LocalLB.ProfileClientSSL.get_authenticate_once_state(self.profiles) + + def get_ca_file(self): + return self.api.LocalLB.ProfileClientSSL.get_ca_file_v2(self.profiles) + + def get_cache_size(self): + return self.api.LocalLB.ProfileClientSSL.get_cache_size(self.profiles) + + def get_cache_timeout(self): + return self.api.LocalLB.ProfileClientSSL.get_cache_timeout(self.profiles) + + def get_certificate_file(self): + return self.api.LocalLB.ProfileClientSSL.get_certificate_file_v2(self.profiles) + + def get_chain_file(self): + return self.api.LocalLB.ProfileClientSSL.get_chain_file_v2(self.profiles) + + def get_cipher_list(self): + return self.api.LocalLB.ProfileClientSSL.get_cipher_list(self.profiles) + + def get_client_certificate_ca_file(self): + return self.api.LocalLB.ProfileClientSSL.get_client_certificate_ca_file_v2(self.profiles) + + def get_crl_file(self): + return self.api.LocalLB.ProfileClientSSL.get_crl_file_v2(self.profiles) + + def get_default_profile(self): + return self.api.LocalLB.ProfileClientSSL.get_default_profile(self.profiles) + + def get_description(self): + return self.api.LocalLB.ProfileClientSSL.get_description(self.profiles) + + def get_forward_proxy_ca_certificate_file(self): + return self.api.LocalLB.ProfileClientSSL.get_forward_proxy_ca_certificate_file(self.profiles) + + def get_forward_proxy_ca_key_file(self): + return self.api.LocalLB.ProfileClientSSL.get_forward_proxy_ca_key_file(self.profiles) + + def get_forward_proxy_ca_passphrase(self): + return self.api.LocalLB.ProfileClientSSL.get_forward_proxy_ca_passphrase(self.profiles) + + def get_forward_proxy_certificate_extension_include(self): + return self.api.LocalLB.ProfileClientSSL.get_forward_proxy_certificate_extension_include(self.profiles) + + def get_forward_proxy_certificate_lifespan(self): + return self.api.LocalLB.ProfileClientSSL.get_forward_proxy_certificate_lifespan(self.profiles) + + def get_forward_proxy_enabled_state(self): + return self.api.LocalLB.ProfileClientSSL.get_forward_proxy_enabled_state(self.profiles) + + def get_forward_proxy_lookup_by_ipaddr_port_state(self): + return self.api.LocalLB.ProfileClientSSL.get_forward_proxy_lookup_by_ipaddr_port_state(self.profiles) + + def get_handshake_timeout(self): + return self.api.LocalLB.ProfileClientSSL.get_handshake_timeout(self.profiles) + + def get_key_file(self): + return self.api.LocalLB.ProfileClientSSL.get_key_file_v2(self.profiles) + + def get_modssl_emulation_state(self): + return self.api.LocalLB.ProfileClientSSL.get_modssl_emulation_state(self.profiles) + + def get_passphrase(self): + return self.api.LocalLB.ProfileClientSSL.get_passphrase(self.profiles) + + def get_peer_certification_mode(self): + return self.api.LocalLB.ProfileClientSSL.get_peer_certification_mode(self.profiles) + + def get_profile_mode(self): + return self.api.LocalLB.ProfileClientSSL.get_profile_mode(self.profiles) + + def get_renegotiation_maximum_record_delay(self): + return self.api.LocalLB.ProfileClientSSL.get_renegotiation_maximum_record_delay(self.profiles) + + def get_renegotiation_period(self): + return self.api.LocalLB.ProfileClientSSL.get_renegotiation_period(self.profiles) + + def get_renegotiation_state(self): + return self.api.LocalLB.ProfileClientSSL.get_renegotiation_state(self.profiles) + + def get_renegotiation_throughput(self): + return self.api.LocalLB.ProfileClientSSL.get_renegotiation_throughput(self.profiles) + + def get_retain_certificate_state(self): + return self.api.LocalLB.ProfileClientSSL.get_retain_certificate_state(self.profiles) + + def get_secure_renegotiation_mode(self): + return self.api.LocalLB.ProfileClientSSL.get_secure_renegotiation_mode(self.profiles) + + def get_server_name(self): + return self.api.LocalLB.ProfileClientSSL.get_server_name(self.profiles) + + def get_session_ticket_state(self): + return self.api.LocalLB.ProfileClientSSL.get_session_ticket_state(self.profiles) + + def get_sni_default_state(self): + return self.api.LocalLB.ProfileClientSSL.get_sni_default_state(self.profiles) + + def get_sni_require_state(self): + return self.api.LocalLB.ProfileClientSSL.get_sni_require_state(self.profiles) + + def get_ssl_option(self): + return self.api.LocalLB.ProfileClientSSL.get_ssl_option(self.profiles) + + def get_strict_resume_state(self): + return self.api.LocalLB.ProfileClientSSL.get_strict_resume_state(self.profiles) + + def get_unclean_shutdown_state(self): + return self.api.LocalLB.ProfileClientSSL.get_unclean_shutdown_state(self.profiles) + + def get_is_base_profile(self): + return self.api.LocalLB.ProfileClientSSL.is_base_profile(self.profiles) + + def get_is_system_profile(self): + return self.api.LocalLB.ProfileClientSSL.is_system_profile(self.profiles) + + +class SystemInfo(object): + """System information class. + + F5 BIG-IP system information class. + + Attributes: + api: iControl API instance. + """ + + def __init__(self, api): + self.api = api + + def get_base_mac_address(self): + return self.api.System.SystemInfo.get_base_mac_address() + + def get_blade_temperature(self): + return self.api.System.SystemInfo.get_blade_temperature() + + def get_chassis_slot_information(self): + return self.api.System.SystemInfo.get_chassis_slot_information() + + def get_globally_unique_identifier(self): + return self.api.System.SystemInfo.get_globally_unique_identifier() + + def get_group_id(self): + return self.api.System.SystemInfo.get_group_id() + + def get_hardware_information(self): + return self.api.System.SystemInfo.get_hardware_information() + + def get_marketing_name(self): + return self.api.System.SystemInfo.get_marketing_name() + + def get_product_information(self): + return self.api.System.SystemInfo.get_product_information() + + def get_pva_version(self): + return self.api.System.SystemInfo.get_pva_version() + + def get_system_id(self): + return self.api.System.SystemInfo.get_system_id() + + def get_system_information(self): + return self.api.System.SystemInfo.get_system_information() + + def get_time(self): + return self.api.System.SystemInfo.get_time() + + def get_time_zone(self): + return self.api.System.SystemInfo.get_time_zone() + + def get_uptime(self): + return self.api.System.SystemInfo.get_uptime() + + +class ProvisionInfo(object): + """Provision information class. + + F5 BIG-IP provision information class. + + Attributes: + api: iControl API instance. + """ + + def __init__(self, api): + self.api = api + + def get_list(self): + result = [] + list = self.api.Management.Provision.get_list() + for item in list: + item = item.lower().replace('tmos_module_', '') + result.append(item) + return result + + def get_provisioned_list(self): + result = [] + list = self.api.Management.Provision.get_provisioned_list() + for item in list: + item = item.lower().replace('tmos_module_', '') + result.append(item) + return result + + +def generate_dict(api_obj, fields): + result_dict = {} + lists = [] + supported_fields = [] + if api_obj.get_list(): + for field in fields: + try: + api_response = getattr(api_obj, "get_" + field)() + except (MethodNotFound, WebFault): + pass + else: + lists.append(api_response) + supported_fields.append(field) + for i, j in enumerate(api_obj.get_list()): + temp = {} + temp.update([(item[0], item[1][i]) for item in zip(supported_fields, lists)]) + result_dict[j] = temp + return result_dict + + +def generate_simple_dict(api_obj, fields): + result_dict = {} + for field in fields: + try: + api_response = getattr(api_obj, "get_" + field)() + except (MethodNotFound, WebFault): + pass + else: + result_dict[field] = api_response + return result_dict + + +def generate_interface_dict(f5, regex): + interfaces = Interfaces(f5.get_api(), regex) + fields = ['active_media', 'actual_flow_control', 'bundle_state', + 'description', 'dual_media_state', 'enabled_state', 'if_index', + 'learning_mode', 'lldp_admin_status', 'lldp_tlvmap', + 'mac_address', 'media', 'media_option', 'media_option_sfp', + 'media_sfp', 'media_speed', 'media_status', 'mtu', + 'phy_master_slave_mode', 'prefer_sfp_state', 'flow_control', + 'sflow_poll_interval', 'sflow_poll_interval_global', + 'sfp_media_state', 'stp_active_edge_port_state', + 'stp_enabled_state', 'stp_link_type', + 'stp_protocol_detection_reset_state'] + return generate_dict(interfaces, fields) + + +def generate_self_ip_dict(f5, regex): + self_ips = SelfIPs(f5.get_api(), regex) + fields = ['address', 'allow_access_list', 'description', + 'enforced_firewall_policy', 'floating_state', 'fw_rule', + 'netmask', 'staged_firewall_policy', 'traffic_group', + 'vlan', 'is_traffic_group_inherited'] + return generate_dict(self_ips, fields) + + +def generate_trunk_dict(f5, regex): + trunks = Trunks(f5.get_api(), regex) + fields = ['active_lacp_state', 'configured_member_count', 'description', + 'distribution_hash_option', 'interface', 'lacp_enabled_state', + 'lacp_timeout_option', 'link_selection_policy', 'media_speed', + 'media_status', 'operational_member_count', 'stp_enabled_state', + 'stp_protocol_detection_reset_state'] + return generate_dict(trunks, fields) + + +def generate_vlan_dict(f5, regex): + vlans = Vlans(f5.get_api(), regex) + fields = ['auto_lasthop', 'cmp_hash_algorithm', 'description', + 'dynamic_forwarding', 'failsafe_action', 'failsafe_state', + 'failsafe_timeout', 'if_index', 'learning_mode', + 'mac_masquerade_address', 'member', 'mtu', + 'sflow_poll_interval', 'sflow_poll_interval_global', + 'sflow_sampling_rate', 'sflow_sampling_rate_global', + 'source_check_state', 'true_mac_address', 'vlan_id'] + return generate_dict(vlans, fields) + + +def generate_vs_dict(f5, regex): + virtual_servers = VirtualServers(f5.get_api(), regex) + fields = ['actual_hardware_acceleration', 'authentication_profile', + 'auto_lasthop', 'bw_controller_policy', 'clone_pool', + 'cmp_enable_mode', 'connection_limit', 'connection_mirror_state', + 'default_pool_name', 'description', 'destination', + 'enabled_state', 'enforced_firewall_policy', + 'fallback_persistence_profile', 'fw_rule', 'gtm_score', + 'last_hop_pool', 'nat64_state', 'object_status', + 'persistence_profile', 'profile', 'protocol', + 'rate_class', 'rate_limit', 'rate_limit_destination_mask', + 'rate_limit_mode', 'rate_limit_source_mask', 'related_rule', + 'rule', 'security_log_profile', 'snat_pool', 'snat_type', + 'source_address', 'source_address_translation_lsn_pool', + 'source_address_translation_snat_pool', + 'source_address_translation_type', 'source_port_behavior', + 'staged_firewall_policy', 'translate_address_state', + 'translate_port_state', 'type', 'vlan', 'wildmask'] + return generate_dict(virtual_servers, fields) + + +def generate_pool_dict(f5, regex): + pools = Pools(f5.get_api(), regex) + fields = ['action_on_service_down', 'active_member_count', + 'aggregate_dynamic_ratio', 'allow_nat_state', + 'allow_snat_state', 'client_ip_tos', 'client_link_qos', + 'description', 'gateway_failsafe_device', + 'ignore_persisted_weight_state', 'lb_method', 'member', + 'minimum_active_member', 'minimum_up_member', + 'minimum_up_member_action', 'minimum_up_member_enabled_state', + 'monitor_association', 'monitor_instance', 'object_status', + 'profile', 'queue_depth_limit', + 'queue_on_connection_limit_state', 'queue_time_limit', + 'reselect_tries', 'server_ip_tos', 'server_link_qos', + 'simple_timeout', 'slow_ramp_time'] + return generate_dict(pools, fields) + + +def generate_device_dict(f5, regex): + devices = Devices(f5.get_api(), regex) + fields = ['active_modules', 'base_mac_address', 'blade_addresses', + 'build', 'chassis_id', 'chassis_type', 'comment', + 'configsync_address', 'contact', 'description', 'edition', + 'failover_state', 'hostname', 'inactive_modules', 'location', + 'management_address', 'marketing_name', 'multicast_address', + 'optional_modules', 'platform_id', 'primary_mirror_address', + 'product', 'secondary_mirror_address', 'software_version', + 'timelimited_modules', 'timezone', 'unicast_addresses'] + return generate_dict(devices, fields) + + +def generate_device_group_dict(f5, regex): + device_groups = DeviceGroups(f5.get_api(), regex) + fields = ['all_preferred_active', 'autosync_enabled_state', 'description', + 'device', 'full_load_on_sync_state', + 'incremental_config_sync_size_maximum', + 'network_failover_enabled_state', 'sync_status', 'type'] + return generate_dict(device_groups, fields) + + +def generate_traffic_group_dict(f5, regex): + traffic_groups = TrafficGroups(f5.get_api(), regex) + fields = ['auto_failback_enabled_state', 'auto_failback_time', + 'default_device', 'description', 'ha_load_factor', + 'ha_order', 'is_floating', 'mac_masquerade_address', + 'unit_id'] + return generate_dict(traffic_groups, fields) + + +def generate_rule_dict(f5, regex): + rules = Rules(f5.get_api(), regex) + fields = ['definition', 'description', 'ignore_vertification', + 'verification_status'] + return generate_dict(rules, fields) + + +def generate_node_dict(f5, regex): + nodes = Nodes(f5.get_api(), regex) + fields = ['address', 'connection_limit', 'description', 'dynamic_ratio', + 'monitor_instance', 'monitor_rule', 'monitor_status', + 'object_status', 'rate_limit', 'ratio', 'session_status'] + return generate_dict(nodes, fields) + + +def generate_virtual_address_dict(f5, regex): + virtual_addresses = VirtualAddresses(f5.get_api(), regex) + fields = ['address', 'arp_state', 'auto_delete_state', 'connection_limit', + 'description', 'enabled_state', 'icmp_echo_state', + 'is_floating_state', 'netmask', 'object_status', + 'route_advertisement_state', 'traffic_group'] + return generate_dict(virtual_addresses, fields) + + +def generate_address_class_dict(f5, regex): + address_classes = AddressClasses(f5.get_api(), regex) + fields = ['address_class', 'description'] + return generate_dict(address_classes, fields) + + +def generate_certificate_dict(f5, regex): + certificates = Certificates(f5.get_api(), regex) + return dict(zip(certificates.get_list(), certificates.get_certificate_list())) + + +def generate_key_dict(f5, regex): + keys = Keys(f5.get_api(), regex) + return dict(zip(keys.get_list(), keys.get_key_list())) + + +def generate_client_ssl_profile_dict(f5, regex): + profiles = ProfileClientSSL(f5.get_api(), regex) + fields = ['alert_timeout', 'allow_nonssl_state', 'authenticate_depth', + 'authenticate_once_state', 'ca_file', 'cache_size', + 'cache_timeout', 'certificate_file', 'chain_file', + 'cipher_list', 'client_certificate_ca_file', 'crl_file', + 'default_profile', 'description', + 'forward_proxy_ca_certificate_file', 'forward_proxy_ca_key_file', + 'forward_proxy_ca_passphrase', + 'forward_proxy_certificate_extension_include', + 'forward_proxy_certificate_lifespan', + 'forward_proxy_enabled_state', + 'forward_proxy_lookup_by_ipaddr_port_state', 'handshake_timeout', + 'key_file', 'modssl_emulation_state', 'passphrase', + 'peer_certification_mode', 'profile_mode', + 'renegotiation_maximum_record_delay', 'renegotiation_period', + 'renegotiation_state', 'renegotiation_throughput', + 'retain_certificate_state', 'secure_renegotiation_mode', + 'server_name', 'session_ticket_state', 'sni_default_state', + 'sni_require_state', 'ssl_option', 'strict_resume_state', + 'unclean_shutdown_state', 'is_base_profile', 'is_system_profile'] + return generate_dict(profiles, fields) + + +def generate_system_info_dict(f5): + system_info = SystemInfo(f5.get_api()) + fields = ['base_mac_address', + 'blade_temperature', 'chassis_slot_information', + 'globally_unique_identifier', 'group_id', + 'hardware_information', + 'marketing_name', + 'product_information', 'pva_version', 'system_id', + 'system_information', 'time', + 'time_zone', 'uptime'] + return generate_simple_dict(system_info, fields) + + +def generate_software_list(f5): + software = Software(f5.get_api()) + software_list = software.get_all_software_status() + return software_list + + +def generate_provision_dict(f5): + provisioned = ProvisionInfo(f5.get_api()) + fields = ['list', 'provisioned_list'] + return generate_simple_dict(provisioned, fields) + + +def main(): + argument_spec = f5_argument_spec() + + meta_args = dict( + session=dict(type='bool', default=False), + include=dict(type='list', required=True), + filter=dict(type='str', required=False), + ) + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec + ) + + if not bigsuds_found: + module.fail_json(msg="the python suds and bigsuds modules are required") + + server = module.params['server'] + server_port = module.params['server_port'] + user = module.params['user'] + password = module.params['password'] + validate_certs = module.params['validate_certs'] + session = module.params['session'] + fact_filter = module.params['filter'] + + if validate_certs: + import ssl + if not hasattr(ssl, 'SSLContext'): + module.fail_json(msg='bigsuds does not support verifying certificates with python < 2.7.9. Either update python or set validate_certs=False on the task') + + if fact_filter: + regex = fnmatch.translate(fact_filter) + else: + regex = None + include = [x.lower() for x in module.params['include']] + valid_includes = ('address_class', 'certificate', 'client_ssl_profile', + 'device', 'device_group', 'interface', 'key', 'node', + 'pool', 'provision', 'rule', 'self_ip', 'software', + 'system_info', 'traffic_group', 'trunk', + 'virtual_address', 'virtual_server', 'vlan') + include_test = map(lambda x: x in valid_includes, include) + if not all(include_test): + module.fail_json(msg="value of include must be one or more of: %s, got: %s" % (",".join(valid_includes), ",".join(include))) + + try: + facts = {} + + if len(include) > 0: + f5 = F5(server, user, password, session, validate_certs, server_port) + saved_active_folder = f5.get_active_folder() + saved_recursive_query_state = f5.get_recursive_query_state() + if saved_active_folder != "/": + f5.set_active_folder("/") + if saved_recursive_query_state != "STATE_ENABLED": + f5.enable_recursive_query_state() + + if 'interface' in include: + facts['interface'] = generate_interface_dict(f5, regex) + if 'self_ip' in include: + facts['self_ip'] = generate_self_ip_dict(f5, regex) + if 'trunk' in include: + facts['trunk'] = generate_trunk_dict(f5, regex) + if 'vlan' in include: + facts['vlan'] = generate_vlan_dict(f5, regex) + if 'virtual_server' in include: + facts['virtual_server'] = generate_vs_dict(f5, regex) + if 'pool' in include: + facts['pool'] = generate_pool_dict(f5, regex) + if 'provision' in include: + facts['provision'] = generate_provision_dict(f5) + if 'device' in include: + facts['device'] = generate_device_dict(f5, regex) + if 'device_group' in include: + facts['device_group'] = generate_device_group_dict(f5, regex) + if 'traffic_group' in include: + facts['traffic_group'] = generate_traffic_group_dict(f5, regex) + if 'rule' in include: + facts['rule'] = generate_rule_dict(f5, regex) + if 'node' in include: + facts['node'] = generate_node_dict(f5, regex) + if 'virtual_address' in include: + facts['virtual_address'] = generate_virtual_address_dict(f5, regex) + if 'address_class' in include: + facts['address_class'] = generate_address_class_dict(f5, regex) + if 'software' in include: + facts['software'] = generate_software_list(f5) + if 'certificate' in include: + facts['certificate'] = generate_certificate_dict(f5, regex) + if 'key' in include: + facts['key'] = generate_key_dict(f5, regex) + if 'client_ssl_profile' in include: + facts['client_ssl_profile'] = generate_client_ssl_profile_dict(f5, regex) + if 'system_info' in include: + facts['system_info'] = generate_system_info_dict(f5) + + # restore saved state + if saved_active_folder and saved_active_folder != "/": + f5.set_active_folder(saved_active_folder) + if saved_recursive_query_state and \ + saved_recursive_query_state != "STATE_ENABLED": + f5.set_recursive_query_state(saved_recursive_query_state) + + result = {'ansible_facts': facts} + + except Exception as e: + module.fail_json(msg="received exception: %s\ntraceback: %s" % (e, traceback.format_exc())) + + module.exit_json(**result) + +# include magic from lib/ansible/module_common.py +from ansible.module_utils.basic import * +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_gtm_datacenter.py b/lib/ansible/modules/extras/network/f5/bigip_gtm_datacenter.py new file mode 100644 index 0000000000..90882b6f64 --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_gtm_datacenter.py @@ -0,0 +1,366 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: bigip_gtm_datacenter +short_description: Manage Datacenter configuration in BIG-IP +description: + - Manage BIG-IP data center configuration. A data center defines the location + where the physical network components reside, such as the server and link + objects that share the same subnet on the network. This module is able to + manipulate the data center definitions in a BIG-IP +version_added: "2.2" +options: + contact: + description: + - The name of the contact for the data center. + description: + description: + - The description of the data center. + enabled: + description: + - Whether the data center should be enabled. At least one of C(state) and + C(enabled) are required. + choices: + - yes + - no + location: + description: + - The location of the data center. + name: + description: + - The name of the data center. + required: true + state: + description: + - The state of the datacenter on the BIG-IP. When C(present), guarantees + that the data center exists. When C(absent) removes the data center + from the BIG-IP. C(enabled) will enable the data center and C(disabled) + will ensure the data center is disabled. At least one of state and + enabled are required. + choices: + - present + - absent +notes: + - Requires the f5-sdk Python package on the host. This is as easy as + pip install f5-sdk. +extends_documentation_fragment: f5 +requirements: + - f5-sdk +author: + - Tim Rupp (@caphrim007) +''' + +EXAMPLES = ''' +- name: Create data center "New York" + bigip_gtm_datacenter: + server: "big-ip" + name: "New York" + location: "222 West 23rd" + delegate_to: localhost +''' + +RETURN = ''' +contact: + description: The contact that was set on the datacenter + returned: changed + type: string + sample: "admin@root.local" +description: + description: The description that was set for the datacenter + returned: changed + type: string + sample: "Datacenter in NYC" +enabled: + description: Whether the datacenter is enabled or not + returned: changed + type: bool + sample: true +location: + description: The location that is set for the datacenter + returned: changed + type: string + sample: "222 West 23rd" +name: + description: Name of the datacenter being manipulated + returned: changed + type: string + sample: "foo" +''' + +try: + from f5.bigip import ManagementRoot + from icontrol.session import iControlUnexpectedHTTPError + HAS_F5SDK = True +except ImportError: + HAS_F5SDK = False + + +class BigIpGtmDatacenter(object): + def __init__(self, *args, **kwargs): + if not HAS_F5SDK: + raise F5ModuleError("The python f5-sdk module is required") + + # The params that change in the module + self.cparams = dict() + + # Stores the params that are sent to the module + self.params = kwargs + self.api = ManagementRoot(kwargs['server'], + kwargs['user'], + kwargs['password'], + port=kwargs['server_port']) + + def create(self): + params = dict() + + check_mode = self.params['check_mode'] + contact = self.params['contact'] + description = self.params['description'] + location = self.params['location'] + name = self.params['name'] + partition = self.params['partition'] + enabled = self.params['enabled'] + + # Specifically check for None because a person could supply empty + # values which would technically still be valid + if contact is not None: + params['contact'] = contact + + if description is not None: + params['description'] = description + + if location is not None: + params['location'] = location + + if enabled is not None: + params['enabled'] = True + else: + params['disabled'] = False + + params['name'] = name + params['partition'] = partition + + self.cparams = camel_dict_to_snake_dict(params) + if check_mode: + return True + + d = self.api.tm.gtm.datacenters.datacenter + d.create(**params) + + if not self.exists(): + raise F5ModuleError("Failed to create the datacenter") + return True + + def read(self): + """Read information and transform it + + The values that are returned by BIG-IP in the f5-sdk can have encoding + attached to them as well as be completely missing in some cases. + + Therefore, this method will transform the data from the BIG-IP into a + format that is more easily consumable by the rest of the class and the + parameters that are supported by the module. + """ + p = dict() + name = self.params['name'] + partition = self.params['partition'] + r = self.api.tm.gtm.datacenters.datacenter.load( + name=name, + partition=partition + ) + + if hasattr(r, 'servers'): + # Deliberately using sets to supress duplicates + p['servers'] = set([str(x) for x in r.servers]) + if hasattr(r, 'contact'): + p['contact'] = str(r.contact) + if hasattr(r, 'location'): + p['location'] = str(r.location) + if hasattr(r, 'description'): + p['description'] = str(r.description) + if r.enabled: + p['enabled'] = True + else: + p['enabled'] = False + p['name'] = name + return p + + def update(self): + changed = False + params = dict() + current = self.read() + + check_mode = self.params['check_mode'] + contact = self.params['contact'] + description = self.params['description'] + location = self.params['location'] + name = self.params['name'] + partition = self.params['partition'] + enabled = self.params['enabled'] + + if contact is not None: + if 'contact' in current: + if contact != current['contact']: + params['contact'] = contact + else: + params['contact'] = contact + + if description is not None: + if 'description' in current: + if description != current['description']: + params['description'] = description + else: + params['description'] = description + + if location is not None: + if 'location' in current: + if location != current['location']: + params['location'] = location + else: + params['location'] = location + + if enabled is not None: + if current['enabled'] != enabled: + if enabled is True: + params['enabled'] = True + params['disabled'] = False + else: + params['disabled'] = True + params['enabled'] = False + + if params: + changed = True + if check_mode: + return changed + self.cparams = camel_dict_to_snake_dict(params) + else: + return changed + + r = self.api.tm.gtm.datacenters.datacenter.load( + name=name, + partition=partition + ) + r.update(**params) + r.refresh() + + return True + + def delete(self): + params = dict() + check_mode = self.params['check_mode'] + + params['name'] = self.params['name'] + params['partition'] = self.params['partition'] + + self.cparams = camel_dict_to_snake_dict(params) + if check_mode: + return True + + dc = self.api.tm.gtm.datacenters.datacenter.load(**params) + dc.delete() + + if self.exists(): + raise F5ModuleError("Failed to delete the datacenter") + return True + + def present(self): + changed = False + + if self.exists(): + changed = self.update() + else: + changed = self.create() + + return changed + + def absent(self): + changed = False + + if self.exists(): + changed = self.delete() + + return changed + + def exists(self): + name = self.params['name'] + partition = self.params['partition'] + + return self.api.tm.gtm.datacenters.datacenter.exists( + name=name, + partition=partition + ) + + def flush(self): + result = dict() + state = self.params['state'] + enabled = self.params['enabled'] + + if state is None and enabled is None: + module.fail_json(msg="Neither 'state' nor 'enabled' set") + + try: + if state == "present": + changed = self.present() + + # Ensure that this field is not returned to the user since it + # is not a valid parameter to the module. + if 'disabled' in self.cparams: + del self.cparams['disabled'] + elif state == "absent": + changed = self.absent() + except iControlUnexpectedHTTPError as e: + raise F5ModuleError(str(e)) + + result.update(**self.cparams) + result.update(dict(changed=changed)) + return result + + +def main(): + argument_spec = f5_argument_spec() + + meta_args = dict( + contact=dict(required=False, default=None), + description=dict(required=False, default=None), + enabled=dict(required=False, type='bool', default=None, choices=BOOLEANS), + location=dict(required=False, default=None), + name=dict(required=True) + ) + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + try: + obj = BigIpGtmDatacenter(check_mode=module.check_mode, **module.params) + result = obj.flush() + + module.exit_json(**result) + except F5ModuleError as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.basic import * +from ansible.module_utils.ec2 import camel_dict_to_snake_dict +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_gtm_virtual_server.py b/lib/ansible/modules/extras/network/f5/bigip_gtm_virtual_server.py new file mode 100644 index 0000000000..079709c1b3 --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_gtm_virtual_server.py @@ -0,0 +1,235 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (c) 2015, Michael Perzel +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: bigip_gtm_virtual_server +short_description: "Manages F5 BIG-IP GTM virtual servers" +description: + - "Manages F5 BIG-IP GTM virtual servers" +version_added: "2.2" +author: + - Michael Perzel (@perzizzle) + - Tim Rupp (@caphrim007) +notes: + - "Requires BIG-IP software version >= 11.4" + - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)" + - "Best run as a local_action in your playbook" + - "Tested with manager and above account privilege level" + +requirements: + - bigsuds +options: + state: + description: + - Virtual server state + required: false + default: present + choices: ['present', 'absent','enabled','disabled'] + virtual_server_name: + description: + - Virtual server name + required: True + virtual_server_server: + description: + - Virtual server server + required: true + host: + description: + - Virtual server host + required: false + default: None + aliases: ['address'] + port: + description: + - Virtual server port + required: false + default: None +extends_documentation_fragment: f5 +''' + +EXAMPLES = ''' + - name: Enable virtual server + local_action: > + bigip_gtm_virtual_server + server=192.0.2.1 + user=admin + password=mysecret + virtual_server_name=myname + virtual_server_server=myserver + state=enabled +''' + +RETURN = '''# ''' + +try: + import bigsuds +except ImportError: + bigsuds_found = False +else: + bigsuds_found = True + + +def server_exists(api, server): + # hack to determine if virtual server exists + result = False + try: + api.GlobalLB.Server.get_object_status([server]) + result = True + except bigsuds.OperationFailed, e: + if "was not found" in str(e): + result = False + else: + # genuine exception + raise + return result + + +def virtual_server_exists(api, name, server): + # hack to determine if virtual server exists + result = False + try: + virtual_server_id = {'name': name, 'server': server} + api.GlobalLB.VirtualServerV2.get_object_status([virtual_server_id]) + result = True + except bigsuds.OperationFailed, e: + if "was not found" in str(e): + result = False + else: + # genuine exception + raise + return result + + +def add_virtual_server(api, virtual_server_name, virtual_server_server, address, port): + addresses = {'address': address, 'port': port} + virtual_server_id = {'name': virtual_server_name, 'server': virtual_server_server} + api.GlobalLB.VirtualServerV2.create([virtual_server_id], [addresses]) + + +def remove_virtual_server(api, virtual_server_name, virtual_server_server): + virtual_server_id = {'name': virtual_server_name, 'server': virtual_server_server} + api.GlobalLB.VirtualServerV2.delete_virtual_server([virtual_server_id]) + + +def get_virtual_server_state(api, name, server): + virtual_server_id = {'name': name, 'server': server} + state = api.GlobalLB.VirtualServerV2.get_enabled_state([virtual_server_id]) + state = state[0].split('STATE_')[1].lower() + return state + + +def set_virtual_server_state(api, name, server, state): + virtual_server_id = {'name': name, 'server': server} + state = "STATE_%s" % state.strip().upper() + api.GlobalLB.VirtualServerV2.set_enabled_state([virtual_server_id], [state]) + + +def main(): + argument_spec = f5_argument_spec() + + meta_args = dict( + state=dict(type='str', default='present', choices=['present', 'absent', 'enabled', 'disabled']), + host=dict(type='str', default=None, aliases=['address']), + port=dict(type='int', default=None), + virtual_server_name=dict(type='str', required=True), + virtual_server_server=dict(type='str', required=True) + ) + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + if not bigsuds_found: + module.fail_json(msg="the python bigsuds module is required") + + server = module.params['server'] + server_port = module.params['server_port'] + validate_certs = module.params['validate_certs'] + user = module.params['user'] + password = module.params['password'] + virtual_server_name = module.params['virtual_server_name'] + virtual_server_server = module.params['virtual_server_server'] + state = module.params['state'] + address = module.params['host'] + port = module.params['port'] + + result = {'changed': False} # default + + try: + api = bigip_api(server, user, password, validate_certs, port=server_port) + + if state == 'absent': + if virtual_server_exists(api, virtual_server_name, virtual_server_server): + if not module.check_mode: + remove_virtual_server(api, virtual_server_name, virtual_server_server) + result = {'changed': True} + else: + # check-mode return value + result = {'changed': True} + elif state == 'present': + if virtual_server_name and virtual_server_server and address and port: + if not virtual_server_exists(api, virtual_server_name, virtual_server_server): + if not module.check_mode: + if server_exists(api, virtual_server_server): + add_virtual_server(api, virtual_server_name, virtual_server_server, address, port) + result = {'changed': True} + else: + module.fail_json(msg="server does not exist") + else: + # check-mode return value + result = {'changed': True} + else: + # virtual server exists -- potentially modify attributes --future feature + result = {'changed': False} + else: + module.fail_json(msg="Address and port are required to create virtual server") + elif state == 'enabled': + if not virtual_server_exists(api, virtual_server_name, virtual_server_server): + module.fail_json(msg="virtual server does not exist") + if state != get_virtual_server_state(api, virtual_server_name, virtual_server_server): + if not module.check_mode: + set_virtual_server_state(api, virtual_server_name, virtual_server_server, state) + result = {'changed': True} + else: + result = {'changed': True} + elif state == 'disabled': + if not virtual_server_exists(api, virtual_server_name, virtual_server_server): + module.fail_json(msg="virtual server does not exist") + if state != get_virtual_server_state(api, virtual_server_name, virtual_server_server): + if not module.check_mode: + set_virtual_server_state(api, virtual_server_name, virtual_server_server, state) + result = {'changed': True} + else: + result = {'changed': True} + + except Exception, e: + module.fail_json(msg="received exception: %s" % e) + + module.exit_json(**result) + +# import module snippets +from ansible.module_utils.basic import * +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_gtm_wide_ip.py b/lib/ansible/modules/extras/network/f5/bigip_gtm_wide_ip.py new file mode 100644 index 0000000000..19292783bc --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_gtm_wide_ip.py @@ -0,0 +1,158 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (c) 2015, Michael Perzel +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +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" +author: + - Michael Perzel (@perzizzle) + - Tim Rupp (@caphrim007) +notes: + - "Requires BIG-IP software version >= 11.4" + - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)" + - "Best run as a local_action in your playbook" + - "Tested with manager and above account privilege level" + +requirements: + - bigsuds +options: + lb_method: + description: + - LB method of wide ip + required: true + choices: ['return_to_dns', 'null', 'round_robin', + 'ratio', 'topology', 'static_persist', 'global_availability', + 'vs_capacity', 'least_conn', 'lowest_rtt', 'lowest_hops', + 'packet_rate', 'cpu', 'hit_ratio', 'qos', 'bps', + 'drop_packet', 'explicit_ip', 'connection_rate', 'vs_score'] + wide_ip: + description: + - Wide IP name + required: true +extends_documentation_fragment: f5 +''' + +EXAMPLES = ''' + - name: Set lb method + local_action: > + bigip_gtm_wide_ip + server=192.0.2.1 + user=admin + password=mysecret + lb_method=round_robin + wide_ip=my-wide-ip.example.com +''' + +try: + import bigsuds +except ImportError: + bigsuds_found = False +else: + bigsuds_found = True + +def get_wide_ip_lb_method(api, wide_ip): + lb_method = api.GlobalLB.WideIP.get_lb_method(wide_ips=[wide_ip])[0] + lb_method = lb_method.strip().replace('LB_METHOD_', '').lower() + return lb_method + +def get_wide_ip_pools(api, wide_ip): + try: + return api.GlobalLB.WideIP.get_wideip_pool([wide_ip]) + except Exception, e: + print e + +def wide_ip_exists(api, wide_ip): + # hack to determine if wide_ip exists + result = False + try: + api.GlobalLB.WideIP.get_object_status(wide_ips=[wide_ip]) + result = True + except bigsuds.OperationFailed, e: + if "was not found" in str(e): + result = False + else: + # genuine exception + raise + return result + +def set_wide_ip_lb_method(api, wide_ip, lb_method): + lb_method = "LB_METHOD_%s" % lb_method.strip().upper() + api.GlobalLB.WideIP.set_lb_method(wide_ips=[wide_ip], lb_methods=[lb_method]) + +def main(): + argument_spec = f5_argument_spec() + + lb_method_choices = ['return_to_dns', 'null', 'round_robin', + 'ratio', 'topology', 'static_persist', 'global_availability', + 'vs_capacity', 'least_conn', 'lowest_rtt', 'lowest_hops', + 'packet_rate', 'cpu', 'hit_ratio', 'qos', 'bps', + 'drop_packet', 'explicit_ip', 'connection_rate', 'vs_score'] + meta_args = dict( + lb_method = dict(type='str', required=True, choices=lb_method_choices), + wide_ip = dict(type='str', required=True) + ) + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + if not bigsuds_found: + module.fail_json(msg="the python bigsuds module is required") + + server = module.params['server'] + server_port = module.params['server_port'] + user = module.params['user'] + password = module.params['password'] + wide_ip = module.params['wide_ip'] + lb_method = module.params['lb_method'] + validate_certs = module.params['validate_certs'] + + result = {'changed': False} # default + + try: + api = bigip_api(server, user, password, validate_certs, port=server_port) + + if not wide_ip_exists(api, wide_ip): + module.fail_json(msg="wide ip %s does not exist" % wide_ip) + + if lb_method is not None and lb_method != get_wide_ip_lb_method(api, wide_ip): + if not module.check_mode: + set_wide_ip_lb_method(api, wide_ip, lb_method) + result = {'changed': True} + else: + result = {'changed': True} + + except Exception, e: + module.fail_json(msg="received exception: %s" % e) + + module.exit_json(**result) + +# import module snippets +from ansible.module_utils.basic import * +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_irule.py b/lib/ansible/modules/extras/network/f5/bigip_irule.py new file mode 100644 index 0000000000..5e99ec34fa --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_irule.py @@ -0,0 +1,385 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: bigip_irule +short_description: Manage iRules across different modules on a BIG-IP. +description: + - Manage iRules across different modules on a BIG-IP. +version_added: "2.2" +options: + content: + description: + - When used instead of 'src', sets the contents of an iRule directly to + the specified value. This is for simple values, but can be used with + lookup plugins for anything complex or with formatting. Either one + of C(src) or C(content) must be provided. + module: + description: + - The BIG-IP module to add the iRule to. + required: true + choices: + - ltm + - gtm + partition: + description: + - The partition to create the iRule on. + required: false + default: Common + name: + description: + - The name of the iRule. + required: true + src: + description: + - The iRule file to interpret and upload to the BIG-IP. Either one + of C(src) or C(content) must be provided. + required: true + state: + description: + - Whether the iRule should exist or not. + required: false + default: present + choices: + - present + - absent +notes: + - Requires the f5-sdk Python package on the host. This is as easy as + pip install f5-sdk. +extends_documentation_fragment: f5 +requirements: + - f5-sdk +author: + - Tim Rupp (@caphrim007) +''' + +EXAMPLES = ''' +- name: Add the iRule contained in templated irule.tcl to the LTM module + bigip_irule: + content: "{{ lookup('template', 'irule-template.tcl') }}" + module: "ltm" + name: "MyiRule" + password: "secret" + server: "lb.mydomain.com" + state: "present" + user: "admin" + delegate_to: localhost + +- name: Add the iRule contained in static file irule.tcl to the LTM module + bigip_irule: + module: "ltm" + name: "MyiRule" + password: "secret" + server: "lb.mydomain.com" + src: "irule-static.tcl" + state: "present" + user: "admin" + delegate_to: localhost +''' + +RETURN = ''' +module: + description: The module that the iRule was added to + returned: changed and success + type: string + sample: "gtm" +src: + description: The filename that included the iRule source + returned: changed and success, when provided + type: string + sample: "/opt/src/irules/example1.tcl" +name: + description: The name of the iRule that was managed + returned: changed and success + type: string + sample: "my-irule" +content: + description: The content of the iRule that was managed + returned: changed and success + type: string + sample: "when LB_FAILED { set wipHost [LB::server addr] }" +partition: + description: The partition in which the iRule was managed + returned: changed and success + type: string + sample: "Common" +''' + +try: + from f5.bigip import ManagementRoot + from icontrol.session import iControlUnexpectedHTTPError + HAS_F5SDK = True +except ImportError: + HAS_F5SDK = False + +MODULES = ['gtm', 'ltm'] + + +class BigIpiRule(object): + def __init__(self, *args, **kwargs): + if not HAS_F5SDK: + raise F5ModuleError("The python f5-sdk module is required") + + if kwargs['state'] != 'absent': + if not kwargs['content'] and not kwargs['src']: + raise F5ModuleError( + "Either 'content' or 'src' must be provided" + ) + + source = kwargs['src'] + if source: + with open(source) as f: + kwargs['content'] = f.read() + + # The params that change in the module + self.cparams = dict() + + # Stores the params that are sent to the module + self.params = kwargs + self.api = ManagementRoot(kwargs['server'], + kwargs['user'], + kwargs['password'], + port=kwargs['server_port']) + + def flush(self): + result = dict() + state = self.params['state'] + + try: + if state == "present": + changed = self.present() + elif state == "absent": + changed = self.absent() + except iControlUnexpectedHTTPError as e: + raise F5ModuleError(str(e)) + + result.update(**self.cparams) + result.update(dict(changed=changed)) + return result + + def read(self): + """Read information and transform it + + The values that are returned by BIG-IP in the f5-sdk can have encoding + attached to them as well as be completely missing in some cases. + + Therefore, this method will transform the data from the BIG-IP into a + format that is more easily consumable by the rest of the class and the + parameters that are supported by the module. + """ + p = dict() + name = self.params['name'] + partition = self.params['partition'] + module = self.params['module'] + + if module == 'ltm': + r = self.api.tm.ltm.rules.rule.load( + name=name, + partition=partition + ) + elif module == 'gtm': + r = self.api.tm.gtm.rules.rule.load( + name=name, + partition=partition + ) + + if hasattr(r, 'apiAnonymous'): + p['content'] = str(r.apiAnonymous) + p['name'] = name + return p + + def delete(self): + params = dict() + check_mode = self.params['check_mode'] + module = self.params['module'] + + params['name'] = self.params['name'] + params['partition'] = self.params['partition'] + + self.cparams = camel_dict_to_snake_dict(params) + if check_mode: + return True + + if module == 'ltm': + r = self.api.tm.ltm.rules.rule.load(**params) + r.delete() + elif module == 'gtm': + r = self.api.tm.gtm.rules.rule.load(**params) + r.delete() + + if self.exists(): + raise F5ModuleError("Failed to delete the iRule") + return True + + def exists(self): + name = self.params['name'] + partition = self.params['partition'] + module = self.params['module'] + + if module == 'ltm': + return self.api.tm.ltm.rules.rule.exists( + name=name, + partition=partition + ) + elif module == 'gtm': + return self.api.tm.gtm.rules.rule.exists( + name=name, + partition=partition + ) + + def present(self): + changed = False + + if self.exists(): + changed = self.update() + else: + changed = self.create() + + return changed + + def update(self): + params = dict() + current = self.read() + changed = False + + check_mode = self.params['check_mode'] + content = self.params['content'] + name = self.params['name'] + partition = self.params['partition'] + module = self.params['module'] + + if content is not None: + if 'content' in current: + if content != current['content']: + params['apiAnonymous'] = content + else: + params['apiAnonymous'] = content + + if params: + changed = True + params['name'] = name + params['partition'] = partition + self.cparams = camel_dict_to_snake_dict(params) + if 'api_anonymous' in self.cparams: + self.cparams['content'] = self.cparams.pop('api_anonymous') + if self.params['src']: + self.cparams['src'] = self.params['src'] + + if check_mode: + return changed + else: + return changed + + if module == 'ltm': + d = self.api.tm.ltm.rules.rule.load( + name=name, + partition=partition + ) + d.update(**params) + d.refresh() + elif module == 'gtm': + d = self.api.tm.gtm.rules.rule.load( + name=name, + partition=partition + ) + d.update(**params) + d.refresh() + + return True + + def create(self): + params = dict() + + check_mode = self.params['check_mode'] + content = self.params['content'] + name = self.params['name'] + partition = self.params['partition'] + module = self.params['module'] + + if check_mode: + return True + + if content is not None: + params['apiAnonymous'] = content + + params['name'] = name + params['partition'] = partition + + self.cparams = camel_dict_to_snake_dict(params) + if 'api_anonymous' in self.cparams: + self.cparams['content'] = self.cparams.pop('api_anonymous') + if self.params['src']: + self.cparams['src'] = self.params['src'] + + if check_mode: + return True + + if module == 'ltm': + d = self.api.tm.ltm.rules.rule + d.create(**params) + elif module == 'gtm': + d = self.api.tm.gtm.rules.rule + d.create(**params) + + if not self.exists(): + raise F5ModuleError("Failed to create the iRule") + return True + + def absent(self): + changed = False + + if self.exists(): + changed = self.delete() + + return changed + + +def main(): + argument_spec = f5_argument_spec() + + meta_args = dict( + content=dict(required=False, default=None), + src=dict(required=False, default=None), + name=dict(required=True), + module=dict(required=True, choices=MODULES) + ) + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + mutually_exclusive=[ + ['content', 'src'] + ] + ) + + try: + obj = BigIpiRule(check_mode=module.check_mode, **module.params) + result = obj.flush() + + module.exit_json(**result) + except F5ModuleError as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.basic import * +from ansible.module_utils.ec2 import camel_dict_to_snake_dict +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_monitor_http.py b/lib/ansible/modules/extras/network/f5/bigip_monitor_http.py new file mode 100644 index 0000000000..3c303c3ce5 --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_monitor_http.py @@ -0,0 +1,443 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (c) 2013, serge van Ginderachter <serge@vanginderachter.be> +# based on Matt Hite's bigip_pool module +# (c) 2013, Matt Hite <mhite@hotmail.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: bigip_monitor_http +short_description: "Manages F5 BIG-IP LTM http monitors" +description: + - Manages F5 BIG-IP LTM monitors via iControl SOAP API +version_added: "1.4" +author: + - Serge van Ginderachter (@srvg) + - Tim Rupp (@caphrim007) +notes: + - "Requires BIG-IP software version >= 11" + - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)" + - "Best run as a local_action in your playbook" + - "Monitor API documentation: https://devcentral.f5.com/wiki/iControl.LocalLB__Monitor.ashx" +requirements: + - bigsuds +options: + state: + description: + - Monitor state + required: false + default: 'present' + choices: + - present + - absent + name: + description: + - Monitor name + required: true + default: null + aliases: + - monitor + partition: + description: + - Partition for the monitor + required: false + default: 'Common' + parent: + description: + - The parent template of this monitor template + required: false + default: 'http' + parent_partition: + description: + - Partition for the parent monitor + required: false + default: 'Common' + send: + description: + - The send string for the monitor call + required: true + default: none + receive: + description: + - The receive string for the monitor call + required: true + default: none + receive_disable: + description: + - The receive disable string for the monitor call + required: true + default: none + ip: + description: + - IP address part of the ipport definition. The default API setting + is "0.0.0.0". + required: false + default: none + port: + description: + - Port address part of the ip/port definition. The default API + setting is 0. + required: false + default: none + interval: + description: + - The interval specifying how frequently the monitor instance + of this template will run. By default, this interval is used for up and + down states. The default API setting is 5. + required: false + default: none + timeout: + description: + - The number of seconds in which the node or service must respond to + the monitor request. If the target responds within the set time + period, it is considered up. If the target does not respond within + the set time period, it is considered down. You can change this + number to any number you want, however, it should be 3 times the + interval number of seconds plus 1 second. The default API setting + is 16. + required: false + default: none + time_until_up: + description: + - Specifies the amount of time in seconds after the first successful + response before a node will be marked up. A value of 0 will cause a + node to be marked up immediately after a valid response is received + from the node. The default API setting is 0. + required: false + default: none +extends_documentation_fragment: f5 +''' + +EXAMPLES = ''' +- name: BIGIP F5 | Create HTTP Monitor + bigip_monitor_http: + state: "present" + server: "lb.mydomain.com" + user: "admin" + password: "secret" + name: "my_http_monitor" + send: "http string to send" + receive: "http string to receive" + delegate_to: localhost + +- name: BIGIP F5 | Remove HTTP Monitor + bigip_monitor_http: + state: "absent" + server: "lb.mydomain.com" + user: "admin" + password: "secret" + name: "my_http_monitor" + delegate_to: localhost +''' + +TEMPLATE_TYPE = 'TTYPE_HTTP' +DEFAULT_PARENT_TYPE = 'http' + + +def check_monitor_exists(module, api, monitor, parent): + # hack to determine if monitor exists + result = False + try: + ttype = api.LocalLB.Monitor.get_template_type(template_names=[monitor])[0] + parent2 = api.LocalLB.Monitor.get_parent_template(template_names=[monitor])[0] + if ttype == TEMPLATE_TYPE and parent == parent2: + result = True + else: + module.fail_json(msg='Monitor already exists, but has a different type (%s) or parent(%s)' % (ttype, parent)) + except bigsuds.OperationFailed as e: + if "was not found" in str(e): + result = False + else: + # genuine exception + raise + return result + + +def create_monitor(api, monitor, template_attributes): + try: + api.LocalLB.Monitor.create_template( + templates=[{ + 'template_name': monitor, + 'template_type': TEMPLATE_TYPE + }], + template_attributes=[template_attributes] + ) + except bigsuds.OperationFailed as e: + if "already exists" in str(e): + return False + else: + # genuine exception + raise + return True + + +def delete_monitor(api, monitor): + try: + api.LocalLB.Monitor.delete_template(template_names=[monitor]) + except bigsuds.OperationFailed as e: + # maybe it was deleted since we checked + if "was not found" in str(e): + return False + else: + # genuine exception + raise + return True + + +def check_string_property(api, monitor, str_property): + try: + template_prop = api.LocalLB.Monitor.get_template_string_property( + [monitor], [str_property['type']] + )[0] + return str_property == template_prop + except bigsuds.OperationFailed as e: + # happens in check mode if not created yet + if "was not found" in str(e): + return True + else: + # genuine exception + raise + + +def set_string_property(api, monitor, str_property): + api.LocalLB.Monitor.set_template_string_property( + template_names=[monitor], + values=[str_property] + ) + + +def check_integer_property(api, monitor, int_property): + try: + template_prop = api.LocalLB.Monitor.get_template_integer_property( + [monitor], [int_property['type']] + )[0] + return int_property == template_prop + except bigsuds.OperationFailed as e: + # happens in check mode if not created yet + if "was not found" in str(e): + return True + else: + # genuine exception + raise + + +def set_integer_property(api, monitor, int_property): + api.LocalLB.Monitor.set_template_integer_property( + template_names=[monitor], + values=[int_property] + ) + + +def update_monitor_properties(api, module, monitor, template_string_properties, template_integer_properties): + changed = False + for str_property in template_string_properties: + if str_property['value'] is not None and not check_string_property(api, monitor, str_property): + if not module.check_mode: + set_string_property(api, monitor, str_property) + changed = True + for int_property in template_integer_properties: + if int_property['value'] is not None and not check_integer_property(api, monitor, int_property): + if not module.check_mode: + set_integer_property(api, monitor, int_property) + changed = True + + return changed + + +def get_ipport(api, monitor): + return api.LocalLB.Monitor.get_template_destination(template_names=[monitor])[0] + + +def set_ipport(api, monitor, ipport): + try: + api.LocalLB.Monitor.set_template_destination( + template_names=[monitor], destinations=[ipport] + ) + return True, "" + except bigsuds.OperationFailed as e: + if "Cannot modify the address type of monitor" in str(e): + return False, "Cannot modify the address type of monitor if already assigned to a pool." + else: + # genuine exception + raise + + +def main(): + argument_spec = f5_argument_spec() + + meta_args = dict( + name=dict(required=True), + parent=dict(default=DEFAULT_PARENT_TYPE), + parent_partition=dict(default='Common'), + send=dict(required=False), + receive=dict(required=False), + receive_disable=dict(required=False), + ip=dict(required=False), + port=dict(required=False, type='int'), + interval=dict(required=False, type='int'), + timeout=dict(required=False, type='int'), + time_until_up=dict(required=False, type='int', default=0) + ) + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + server = module.params['server'] + server_port = module.params['server_port'] + user = module.params['user'] + password = module.params['password'] + state = module.params['state'] + partition = module.params['partition'] + validate_certs = module.params['validate_certs'] + + parent_partition = module.params['parent_partition'] + name = module.params['name'] + parent = fq_name(parent_partition, module.params['parent']) + monitor = fq_name(partition, name) + send = module.params['send'] + receive = module.params['receive'] + receive_disable = module.params['receive_disable'] + ip = module.params['ip'] + port = module.params['port'] + interval = module.params['interval'] + timeout = module.params['timeout'] + time_until_up = module.params['time_until_up'] + + # end monitor specific stuff + + api = bigip_api(server, user, password, validate_certs, port=server_port) + monitor_exists = check_monitor_exists(module, api, monitor, parent) + + # ipport is a special setting + if monitor_exists: + cur_ipport = get_ipport(api, monitor) + if ip is None: + ip = cur_ipport['ipport']['address'] + if port is None: + port = cur_ipport['ipport']['port'] + else: + if interval is None: + interval = 5 + if timeout is None: + timeout = 16 + if ip is None: + ip = '0.0.0.0' + if port is None: + port = 0 + if send is None: + send = '' + if receive is None: + receive = '' + if receive_disable is None: + receive_disable = '' + + # define and set address type + if ip == '0.0.0.0' and port == 0: + address_type = 'ATYPE_STAR_ADDRESS_STAR_PORT' + elif ip == '0.0.0.0' and port != 0: + address_type = 'ATYPE_STAR_ADDRESS_EXPLICIT_PORT' + elif ip != '0.0.0.0' and port != 0: + address_type = 'ATYPE_EXPLICIT_ADDRESS_EXPLICIT_PORT' + else: + address_type = 'ATYPE_UNSET' + + ipport = {'address_type': address_type, + 'ipport': {'address': ip, + 'port': port}} + + template_attributes = {'parent_template': parent, + 'interval': interval, + 'timeout': timeout, + 'dest_ipport': ipport, + 'is_read_only': False, + 'is_directly_usable': True} + + # monitor specific stuff + template_string_properties = [{'type': 'STYPE_SEND', + 'value': send}, + {'type': 'STYPE_RECEIVE', + 'value': receive}, + {'type': 'STYPE_RECEIVE_DRAIN', + 'value': receive_disable}] + + template_integer_properties = [ + { + 'type': 'ITYPE_INTERVAL', + 'value': interval + }, + { + 'type': 'ITYPE_TIMEOUT', + 'value': timeout + }, + { + 'type': 'ITYPE_TIME_UNTIL_UP', + 'value': time_until_up + } + ] + + # main logic, monitor generic + + try: + result = {'changed': False} # default + + if state == 'absent': + if monitor_exists: + if not module.check_mode: + # possible race condition if same task + # on other node deleted it first + result['changed'] |= delete_monitor(api, monitor) + else: + result['changed'] |= True + else: + # check for monitor itself + if not monitor_exists: + if not module.check_mode: + # again, check changed status here b/c race conditions + # if other task already created it + result['changed'] |= create_monitor(api, monitor, template_attributes) + else: + result['changed'] |= True + + # check for monitor parameters + # whether it already existed, or was just created, now update + # the update functions need to check for check mode but + # cannot update settings if it doesn't exist which happens in check mode + result['changed'] |= update_monitor_properties(api, module, monitor, + template_string_properties, + template_integer_properties) + + # we just have to update the ipport if monitor already exists and it's different + if monitor_exists and cur_ipport != ipport: + set_ipport(api, monitor, ipport) + result['changed'] |= True + # else: monitor doesn't exist (check mode) or ipport is already ok + except Exception as e: + module.fail_json(msg="received exception: %s" % e) + + module.exit_json(**result) + +# import module snippets +from ansible.module_utils.basic import * +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_monitor_tcp.py b/lib/ansible/modules/extras/network/f5/bigip_monitor_tcp.py new file mode 100644 index 0000000000..45756b1ba2 --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_monitor_tcp.py @@ -0,0 +1,485 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2013, serge van Ginderachter <serge@vanginderachter.be> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: bigip_monitor_tcp +short_description: "Manages F5 BIG-IP LTM tcp monitors" +description: + - "Manages F5 BIG-IP LTM tcp monitors via iControl SOAP API" +version_added: "1.4" +author: + - Serge van Ginderachter (@srvg) + - Tim Rupp (@caphrim007) +notes: + - "Requires BIG-IP software version >= 11" + - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)" + - "Best run as a local_action in your playbook" + - "Monitor API documentation: https://devcentral.f5.com/wiki/iControl.LocalLB__Monitor.ashx" +requirements: + - bigsuds +options: + state: + description: + - Monitor state + required: false + default: 'present' + choices: + - present + - absent + name: + description: + - Monitor name + required: true + default: null + aliases: + - monitor + partition: + description: + - Partition for the monitor + required: false + default: 'Common' + type: + description: + - The template type of this monitor template + required: false + default: 'tcp' + choices: + - TTYPE_TCP + - TTYPE_TCP_ECHO + - TTYPE_TCP_HALF_OPEN + parent: + description: + - The parent template of this monitor template + required: false + default: 'tcp' + choices: + - tcp + - tcp_echo + - tcp_half_open + parent_partition: + description: + - Partition for the parent monitor + required: false + default: 'Common' + send: + description: + - The send string for the monitor call + required: true + default: none + receive: + description: + - The receive string for the monitor call + required: true + default: none + ip: + description: + - IP address part of the ipport definition. The default API setting + is "0.0.0.0". + required: false + default: none + port: + description: + - Port address part op the ipport definition. The default API + setting is 0. + required: false + default: none + interval: + description: + - The interval specifying how frequently the monitor instance + of this template will run. By default, this interval is used for up and + down states. The default API setting is 5. + required: false + default: none + timeout: + description: + - The number of seconds in which the node or service must respond to + the monitor request. If the target responds within the set time + period, it is considered up. If the target does not respond within + the set time period, it is considered down. You can change this + number to any number you want, however, it should be 3 times the + interval number of seconds plus 1 second. The default API setting + is 16. + required: false + default: none + time_until_up: + description: + - Specifies the amount of time in seconds after the first successful + response before a node will be marked up. A value of 0 will cause a + node to be marked up immediately after a valid response is received + from the node. The default API setting is 0. + required: false + default: none +extends_documentation_fragment: f5 +''' + +EXAMPLES = ''' +- name: Create TCP Monitor + bigip_monitor_tcp: + state: "present" + server: "lb.mydomain.com" + user: "admin" + password: "secret" + name: "my_tcp_monitor" + type: "tcp" + send: "tcp string to send" + receive: "tcp string to receive" + delegate_to: localhost + +- name: Create TCP half open Monitor + bigip_monitor_tcp: + state: "present" + server: "lb.mydomain.com" + user: "admin" + password: "secret" + name: "my_tcp_monitor" + type: "tcp" + send: "tcp string to send" + receive: "http string to receive" + delegate_to: localhost + +- name: Remove TCP Monitor + bigip_monitor_tcp: + state: "absent" + server: "lb.mydomain.com" + user: "admin" + password: "secret" + name: "my_tcp_monitor" +''' + +TEMPLATE_TYPE = DEFAULT_TEMPLATE_TYPE = 'TTYPE_TCP' +TEMPLATE_TYPE_CHOICES = ['tcp', 'tcp_echo', 'tcp_half_open'] +DEFAULT_PARENT = DEFAULT_TEMPLATE_TYPE_CHOICE = DEFAULT_TEMPLATE_TYPE.replace('TTYPE_', '').lower() + + +def check_monitor_exists(module, api, monitor, parent): + # hack to determine if monitor exists + result = False + try: + ttype = api.LocalLB.Monitor.get_template_type(template_names=[monitor])[0] + parent2 = api.LocalLB.Monitor.get_parent_template(template_names=[monitor])[0] + if ttype == TEMPLATE_TYPE and parent == parent2: + result = True + else: + module.fail_json(msg='Monitor already exists, but has a different type (%s) or parent(%s)' % (ttype, parent)) + except bigsuds.OperationFailed as e: + if "was not found" in str(e): + result = False + else: + # genuine exception + raise + return result + + +def create_monitor(api, monitor, template_attributes): + try: + api.LocalLB.Monitor.create_template( + templates=[{ + 'template_name': monitor, + 'template_type': TEMPLATE_TYPE + }], + template_attributes=[template_attributes] + ) + except bigsuds.OperationFailed as e: + if "already exists" in str(e): + return False + else: + # genuine exception + raise + return True + + +def delete_monitor(api, monitor): + try: + api.LocalLB.Monitor.delete_template(template_names=[monitor]) + except bigsuds.OperationFailed as e: + # maybe it was deleted since we checked + if "was not found" in str(e): + return False + else: + # genuine exception + raise + return True + + +def check_string_property(api, monitor, str_property): + try: + template_prop = api.LocalLB.Monitor.get_template_string_property( + [monitor], [str_property['type']] + )[0] + return str_property == template_prop + except bigsuds.OperationFailed as e: + # happens in check mode if not created yet + if "was not found" in str(e): + return True + else: + # genuine exception + raise + + +def set_string_property(api, monitor, str_property): + api.LocalLB.Monitor.set_template_string_property( + template_names=[monitor], + values=[str_property] + ) + + +def check_integer_property(api, monitor, int_property): + try: + return int_property == api.LocalLB.Monitor.get_template_integer_property( + [monitor], [int_property['type']] + )[0] + except bigsuds.OperationFailed as e: + # happens in check mode if not created yet + if "was not found" in str(e): + return True + else: + # genuine exception + raise + + +def set_integer_property(api, monitor, int_property): + api.LocalLB.Monitor.set_template_integer_property( + template_names=[monitor], + values=[int_property] + ) + + +def update_monitor_properties(api, module, monitor, template_string_properties, template_integer_properties): + changed = False + for str_property in template_string_properties: + if str_property['value'] is not None and not check_string_property(api, monitor, str_property): + if not module.check_mode: + set_string_property(api, monitor, str_property) + changed = True + + for int_property in template_integer_properties: + if int_property['value'] is not None and not check_integer_property(api, monitor, int_property): + if not module.check_mode: + set_integer_property(api, monitor, int_property) + changed = True + + return changed + + +def get_ipport(api, monitor): + return api.LocalLB.Monitor.get_template_destination(template_names=[monitor])[0] + + +def set_ipport(api, monitor, ipport): + try: + api.LocalLB.Monitor.set_template_destination( + template_names=[monitor], destinations=[ipport] + ) + return True, "" + + except bigsuds.OperationFailed as e: + if "Cannot modify the address type of monitor" in str(e): + return False, "Cannot modify the address type of monitor if already assigned to a pool." + else: + # genuine exception + raise + + +def main(): + argument_spec = f5_argument_spec() + + meta_args = dict( + name=dict(required=True), + type=dict(default=DEFAULT_TEMPLATE_TYPE_CHOICE, choices=TEMPLATE_TYPE_CHOICES), + parent=dict(default=DEFAULT_PARENT), + parent_partition=dict(default='Common'), + send=dict(required=False), + receive=dict(required=False), + ip=dict(required=False), + port=dict(required=False, type='int'), + interval=dict(required=False, type='int'), + timeout=dict(required=False, type='int'), + time_until_up=dict(required=False, type='int', default=0) + ) + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + if module.params['validate_certs']: + import ssl + if not hasattr(ssl, 'SSLContext'): + module.fail_json(msg='bigsuds does not support verifying certificates with python < 2.7.9. Either update python or set validate_certs=False on the task') + + server = module.params['server'] + server_port = module.params['server_port'] + user = module.params['user'] + password = module.params['password'] + state = module.params['state'] + partition = module.params['partition'] + validate_certs = module.params['validate_certs'] + + parent_partition = module.params['parent_partition'] + name = module.params['name'] + type = 'TTYPE_' + module.params['type'].upper() + parent = fq_name(parent_partition, module.params['parent']) + monitor = fq_name(partition, name) + send = module.params['send'] + receive = module.params['receive'] + ip = module.params['ip'] + port = module.params['port'] + interval = module.params['interval'] + timeout = module.params['timeout'] + time_until_up = module.params['time_until_up'] + + # tcp monitor has multiple types, so overrule + global TEMPLATE_TYPE + TEMPLATE_TYPE = type + + # end monitor specific stuff + + api = bigip_api(server, user, password, validate_certs, port=server_port) + monitor_exists = check_monitor_exists(module, api, monitor, parent) + + # ipport is a special setting + if monitor_exists: + # make sure to not update current settings if not asked + cur_ipport = get_ipport(api, monitor) + if ip is None: + ip = cur_ipport['ipport']['address'] + if port is None: + port = cur_ipport['ipport']['port'] + else: + # use API defaults if not defined to create it + if interval is None: + interval = 5 + if timeout is None: + timeout = 16 + if ip is None: + ip = '0.0.0.0' + if port is None: + port = 0 + if send is None: + send = '' + if receive is None: + receive = '' + + # define and set address type + if ip == '0.0.0.0' and port == 0: + address_type = 'ATYPE_STAR_ADDRESS_STAR_PORT' + elif ip == '0.0.0.0' and port != 0: + address_type = 'ATYPE_STAR_ADDRESS_EXPLICIT_PORT' + elif ip != '0.0.0.0' and port != 0: + address_type = 'ATYPE_EXPLICIT_ADDRESS_EXPLICIT_PORT' + else: + address_type = 'ATYPE_UNSET' + + ipport = { + 'address_type': address_type, + 'ipport': { + 'address': ip, + 'port': port + } + } + + template_attributes = { + 'parent_template': parent, + 'interval': interval, + 'timeout': timeout, + 'dest_ipport': ipport, + 'is_read_only': False, + 'is_directly_usable': True + } + + # monitor specific stuff + if type == 'TTYPE_TCP': + template_string_properties = [ + { + 'type': 'STYPE_SEND', + 'value': send + }, + { + 'type': 'STYPE_RECEIVE', + 'value': receive + } + ] + else: + template_string_properties = [] + + template_integer_properties = [ + { + 'type': 'ITYPE_INTERVAL', + 'value': interval + }, + { + 'type': 'ITYPE_TIMEOUT', + 'value': timeout + }, + { + 'type': 'ITYPE_TIME_UNTIL_UP', + 'value': time_until_up + } + ] + + # main logic, monitor generic + + try: + result = {'changed': False} # default + + if state == 'absent': + if monitor_exists: + if not module.check_mode: + # possible race condition if same task + # on other node deleted it first + result['changed'] |= delete_monitor(api, monitor) + else: + result['changed'] |= True + else: + # check for monitor itself + if not monitor_exists: + if not module.check_mode: + # again, check changed status here b/c race conditions + # if other task already created it + result['changed'] |= create_monitor(api, monitor, template_attributes) + else: + result['changed'] |= True + + # check for monitor parameters + # whether it already existed, or was just created, now update + # the update functions need to check for check mode but + # cannot update settings if it doesn't exist which happens in check mode + result['changed'] |= update_monitor_properties(api, module, monitor, + template_string_properties, + template_integer_properties) + + # we just have to update the ipport if monitor already exists and it's different + if monitor_exists and cur_ipport != ipport: + set_ipport(api, monitor, ipport) + result['changed'] |= True + # else: monitor doesn't exist (check mode) or ipport is already ok + except Exception as e: + module.fail_json(msg="received exception: %s" % e) + + module.exit_json(**result) + +# import module snippets +from ansible.module_utils.basic import * +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_node.py b/lib/ansible/modules/extras/network/f5/bigip_node.py new file mode 100644 index 0000000000..53a0b1973f --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_node.py @@ -0,0 +1,463 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (c) 2013, Matt Hite <mhite@hotmail.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: bigip_node +short_description: "Manages F5 BIG-IP LTM nodes" +description: + - "Manages F5 BIG-IP LTM nodes via iControl SOAP API" +version_added: "1.4" +author: + - Matt Hite (@mhite) + - Tim Rupp (@caphrim007) +notes: + - "Requires BIG-IP software version >= 11" + - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)" + - "Best run as a local_action in your playbook" +requirements: + - bigsuds +options: + state: + description: + - Pool member state + required: true + default: present + choices: ['present', 'absent'] + aliases: [] + session_state: + description: + - Set new session availability status for node + version_added: "1.9" + required: false + default: null + choices: ['enabled', 'disabled'] + aliases: [] + monitor_state: + description: + - Set monitor availability status for node + version_added: "1.9" + required: false + default: null + choices: ['enabled', 'disabled'] + aliases: [] + partition: + description: + - Partition + required: false + default: 'Common' + choices: [] + aliases: [] + name: + description: + - "Node name" + required: false + default: null + choices: [] + monitor_type: + description: + - Monitor rule type when monitors > 1 + version_added: "2.2" + required: False + default: null + choices: ['and_list', 'm_of_n'] + aliases: [] + quorum: + description: + - Monitor quorum value when monitor_type is m_of_n + version_added: "2.2" + required: False + default: null + choices: [] + aliases: [] + monitors: + description: + - Monitor template name list. Always use the full path to the monitor. + version_added: "2.2" + required: False + default: null + choices: [] + aliases: [] + host: + description: + - "Node IP. Required when state=present and node does not exist. Error when state=absent." + required: true + default: null + choices: [] + aliases: ['address', 'ip'] + description: + description: + - "Node description." + required: false + default: null + choices: [] +extends_documentation_fragment: f5 +''' + +EXAMPLES = ''' +- name: Add node + bigip_node: + server: "lb.mydomain.com" + user: "admin" + password: "secret" + state: "present" + partition: "Common" + host: "10.20.30.40" + name: "10.20.30.40" + +# Note that the BIG-IP automatically names the node using the +# IP address specified in previous play's host parameter. +# Future plays referencing this node no longer use the host +# parameter but instead use the name parameter. +# Alternatively, you could have specified a name with the +# name parameter when state=present. + +- name: Add node with a single 'ping' monitor + bigip_node: + server: "lb.mydomain.com" + user: "admin" + password: "secret" + state: "present" + partition: "Common" + host: "10.20.30.40" + name: "mytestserver" + monitors: + - /Common/icmp + delegate_to: localhost + +- name: Modify node description + bigip_node: + server: "lb.mydomain.com" + user: "admin" + password: "secret" + state: "present" + partition: "Common" + name: "10.20.30.40" + description: "Our best server yet" + delegate_to: localhost + +- name: Delete node + bigip_node: + server: "lb.mydomain.com" + user: "admin" + password: "secret" + state: "absent" + partition: "Common" + name: "10.20.30.40" + +# The BIG-IP GUI doesn't map directly to the API calls for "Node -> +# General Properties -> State". The following states map to API monitor +# and session states. +# +# Enabled (all traffic allowed): +# monitor_state=enabled, session_state=enabled +# Disabled (only persistent or active connections allowed): +# monitor_state=enabled, session_state=disabled +# Forced offline (only active connections allowed): +# monitor_state=disabled, session_state=disabled +# +# See https://devcentral.f5.com/questions/icontrol-equivalent-call-for-b-node-down + +- name: Force node offline + bigip_node: + server: "lb.mydomain.com" + user: "admin" + password: "mysecret" + state: "present" + session_state: "disabled" + monitor_state: "disabled" + partition: "Common" + name: "10.20.30.40" +''' + + +def node_exists(api, address): + # hack to determine if node exists + result = False + try: + api.LocalLB.NodeAddressV2.get_object_status(nodes=[address]) + result = True + except bigsuds.OperationFailed as e: + if "was not found" in str(e): + result = False + else: + # genuine exception + raise + return result + + +def create_node_address(api, address, name): + try: + api.LocalLB.NodeAddressV2.create( + nodes=[name], + addresses=[address], + limits=[0] + ) + result = True + desc = "" + except bigsuds.OperationFailed as e: + if "already exists" in str(e): + result = False + desc = "referenced name or IP already in use" + else: + # genuine exception + raise + return (result, desc) + + +def get_node_address(api, name): + return api.LocalLB.NodeAddressV2.get_address(nodes=[name])[0] + + +def delete_node_address(api, address): + try: + api.LocalLB.NodeAddressV2.delete_node_address(nodes=[address]) + result = True + desc = "" + except bigsuds.OperationFailed as e: + if "is referenced by a member of pool" in str(e): + result = False + desc = "node referenced by pool" + else: + # genuine exception + raise + return (result, desc) + + +def set_node_description(api, name, description): + api.LocalLB.NodeAddressV2.set_description(nodes=[name], + descriptions=[description]) + + +def get_node_description(api, name): + return api.LocalLB.NodeAddressV2.get_description(nodes=[name])[0] + + +def set_node_session_enabled_state(api, name, session_state): + session_state = "STATE_%s" % session_state.strip().upper() + api.LocalLB.NodeAddressV2.set_session_enabled_state(nodes=[name], + states=[session_state]) + + +def get_node_session_status(api, name): + result = api.LocalLB.NodeAddressV2.get_session_status(nodes=[name])[0] + result = result.split("SESSION_STATUS_")[-1].lower() + return result + + +def set_node_monitor_state(api, name, monitor_state): + monitor_state = "STATE_%s" % monitor_state.strip().upper() + api.LocalLB.NodeAddressV2.set_monitor_state(nodes=[name], + states=[monitor_state]) + + +def get_node_monitor_status(api, name): + result = api.LocalLB.NodeAddressV2.get_monitor_status(nodes=[name])[0] + result = result.split("MONITOR_STATUS_")[-1].lower() + return result + + +def get_monitors(api, name): + result = api.LocalLB.NodeAddressV2.get_monitor_rule(nodes=[name])[0] + monitor_type = result['type'].split("MONITOR_RULE_TYPE_")[-1].lower() + quorum = result['quorum'] + monitor_templates = result['monitor_templates'] + return (monitor_type, quorum, monitor_templates) + + +def set_monitors(api, name, monitor_type, quorum, monitor_templates): + monitor_type = "MONITOR_RULE_TYPE_%s" % monitor_type.strip().upper() + monitor_rule = {'type': monitor_type, 'quorum': quorum, 'monitor_templates': monitor_templates} + api.LocalLB.NodeAddressV2.set_monitor_rule(nodes=[name], + monitor_rules=[monitor_rule]) + + +def main(): + monitor_type_choices = ['and_list', 'm_of_n'] + + argument_spec = f5_argument_spec() + + meta_args = dict( + session_state=dict(type='str', choices=['enabled', 'disabled']), + monitor_state=dict(type='str', choices=['enabled', 'disabled']), + name=dict(type='str', required=True), + host=dict(type='str', aliases=['address', 'ip']), + description=dict(type='str'), + monitor_type=dict(type='str', choices=monitor_type_choices), + quorum=dict(type='int'), + monitors=dict(type='list') + ) + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + if module.params['validate_certs']: + import ssl + if not hasattr(ssl, 'SSLContext'): + module.fail_json(msg='bigsuds does not support verifying certificates with python < 2.7.9. Either update python or set validate_certs=False on the task') + + server = module.params['server'] + server_port = module.params['server_port'] + user = module.params['user'] + password = module.params['password'] + state = module.params['state'] + partition = module.params['partition'] + validate_certs = module.params['validate_certs'] + + session_state = module.params['session_state'] + monitor_state = module.params['monitor_state'] + host = module.params['host'] + name = module.params['name'] + address = fq_name(partition, name) + description = module.params['description'] + monitor_type = module.params['monitor_type'] + if monitor_type: + monitor_type = monitor_type.lower() + quorum = module.params['quorum'] + monitors = module.params['monitors'] + if monitors: + monitors = [] + for monitor in module.params['monitors']: + monitors.append(fq_name(partition, monitor)) + + # sanity check user supplied values + if state == 'absent' and host is not None: + module.fail_json(msg="host parameter invalid when state=absent") + + if monitors: + if len(monitors) == 1: + # set default required values for single monitor + quorum = 0 + monitor_type = 'single' + elif len(monitors) > 1: + if not monitor_type: + module.fail_json(msg="monitor_type required for monitors > 1") + if monitor_type == 'm_of_n' and not quorum: + module.fail_json(msg="quorum value required for monitor_type m_of_n") + if monitor_type != 'm_of_n': + quorum = 0 + elif monitor_type: + # no monitors specified but monitor_type exists + module.fail_json(msg="monitor_type require monitors parameter") + elif quorum is not None: + # no monitors specified but quorum exists + module.fail_json(msg="quorum requires monitors parameter") + + try: + api = bigip_api(server, user, password, validate_certs, port=server_port) + result = {'changed': False} # default + + if state == 'absent': + if node_exists(api, address): + if not module.check_mode: + deleted, desc = delete_node_address(api, address) + if not deleted: + module.fail_json(msg="unable to delete: %s" % desc) + else: + result = {'changed': True} + else: + # check-mode return value + result = {'changed': True} + + elif state == 'present': + if not node_exists(api, address): + if host is None: + module.fail_json(msg="host parameter required when " + "state=present and node does not exist") + if not module.check_mode: + created, desc = create_node_address(api, address=host, name=address) + if not created: + module.fail_json(msg="unable to create: %s" % desc) + else: + result = {'changed': True} + if session_state is not None: + set_node_session_enabled_state(api, address, + session_state) + result = {'changed': True} + if monitor_state is not None: + set_node_monitor_state(api, address, monitor_state) + result = {'changed': True} + if description is not None: + set_node_description(api, address, description) + result = {'changed': True} + if monitors: + set_monitors(api, address, monitor_type, quorum, monitors) + else: + # check-mode return value + result = {'changed': True} + else: + # node exists -- potentially modify attributes + if host is not None: + if get_node_address(api, address) != host: + module.fail_json(msg="Changing the node address is " + "not supported by the API; " + "delete and recreate the node.") + if session_state is not None: + session_status = get_node_session_status(api, address) + if session_state == 'enabled' and \ + session_status == 'forced_disabled': + if not module.check_mode: + set_node_session_enabled_state(api, address, + session_state) + result = {'changed': True} + elif session_state == 'disabled' and \ + session_status != 'force_disabled': + if not module.check_mode: + set_node_session_enabled_state(api, address, + session_state) + result = {'changed': True} + if monitor_state is not None: + monitor_status = get_node_monitor_status(api, address) + if monitor_state == 'enabled' and \ + monitor_status == 'forced_down': + if not module.check_mode: + set_node_monitor_state(api, address, + monitor_state) + result = {'changed': True} + elif monitor_state == 'disabled' and \ + monitor_status != 'forced_down': + if not module.check_mode: + set_node_monitor_state(api, address, + monitor_state) + result = {'changed': True} + if description is not None: + if get_node_description(api, address) != description: + if not module.check_mode: + set_node_description(api, address, description) + result = {'changed': True} + if monitors: + t_monitor_type, t_quorum, t_monitor_templates = get_monitors(api, address) + if (t_monitor_type != monitor_type) or (t_quorum != quorum) or (set(t_monitor_templates) != set(monitors)): + if not module.check_mode: + set_monitors(api, address, monitor_type, quorum, monitors) + result = {'changed': True} + except Exception as e: + module.fail_json(msg="received exception: %s" % e) + + module.exit_json(**result) + +from ansible.module_utils.basic import * +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_pool.py b/lib/ansible/modules/extras/network/f5/bigip_pool.py new file mode 100644 index 0000000000..69ee1c0750 --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_pool.py @@ -0,0 +1,561 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2013, Matt Hite <mhite@hotmail.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: bigip_pool +short_description: "Manages F5 BIG-IP LTM pools" +description: + - Manages F5 BIG-IP LTM pools via iControl SOAP API +version_added: 1.2 +author: + - Matt Hite (@mhite) + - Tim Rupp (@caphrim007) +notes: + - Requires BIG-IP software version >= 11 + - F5 developed module 'bigsuds' required (see http://devcentral.f5.com) + - Best run as a local_action in your playbook +requirements: + - bigsuds +options: + state: + description: + - Pool/pool member state + required: false + default: present + choices: + - present + - absent + aliases: [] + name: + description: + - Pool name + required: true + default: null + choices: [] + aliases: + - pool + partition: + description: + - Partition of pool/pool member + required: false + default: 'Common' + choices: [] + aliases: [] + lb_method: + description: + - Load balancing method + version_added: "1.3" + required: False + default: 'round_robin' + choices: + - round_robin + - ratio_member + - least_connection_member + - observed_member + - predictive_member + - ratio_node_address + - least_connection_node_address + - fastest_node_address + - observed_node_address + - predictive_node_address + - dynamic_ratio + - fastest_app_response + - least_sessions + - dynamic_ratio_member + - l3_addr + - weighted_least_connection_member + - weighted_least_connection_node_address + - ratio_session + - ratio_least_connection_member + - ratio_least_connection_node_address + aliases: [] + monitor_type: + description: + - Monitor rule type when monitors > 1 + version_added: "1.3" + required: False + default: null + choices: ['and_list', 'm_of_n'] + aliases: [] + quorum: + description: + - Monitor quorum value when monitor_type is m_of_n + version_added: "1.3" + required: False + default: null + choices: [] + aliases: [] + monitors: + description: + - Monitor template name list. Always use the full path to the monitor. + version_added: "1.3" + required: False + default: null + choices: [] + aliases: [] + slow_ramp_time: + description: + - Sets the ramp-up time (in seconds) to gradually ramp up the load on + newly added or freshly detected up pool members + version_added: "1.3" + required: False + default: null + choices: [] + aliases: [] + reselect_tries: + description: + - Sets the number of times the system tries to contact a pool member + after a passive failure + version_added: "2.2" + required: False + default: null + choices: [] + aliases: [] + service_down_action: + description: + - Sets the action to take when node goes down in pool + version_added: "1.3" + required: False + default: null + choices: + - none + - reset + - drop + - reselect + aliases: [] + host: + description: + - "Pool member IP" + required: False + default: null + choices: [] + aliases: + - address + port: + description: + - Pool member port + required: False + default: null + choices: [] + aliases: [] +extends_documentation_fragment: f5 +''' + +EXAMPLES = ''' +- name: Create pool + bigip_pool: + server: "lb.mydomain.com" + user: "admin" + password: "secret" + state: "present" + name: "my-pool" + partition: "Common" + lb_method: "least_connection_member" + slow_ramp_time: 120 + delegate_to: localhost + +- name: Modify load balancer method + bigip_pool: + server: "lb.mydomain.com" + user: "admin" + password: "secret" + state: "present" + name: "my-pool" + partition: "Common" + lb_method: "round_robin" + +- name: Add pool member + bigip_pool: + server: "lb.mydomain.com" + user: "admin" + password: "secret" + state: "present" + name: "my-pool" + partition: "Common" + host: "{{ ansible_default_ipv4["address"] }}" + port: 80 + +- name: Remove pool member from pool + bigip_pool: + server: "lb.mydomain.com" + user: "admin" + password: "secret" + state: "absent" + name: "my-pool" + partition: "Common" + host: "{{ ansible_default_ipv4["address"] }}" + port: 80 + +- name: Delete pool + bigip_pool: + server: "lb.mydomain.com" + user: "admin" + password: "secret" + state: "absent" + name: "my-pool" + partition: "Common" +''' + +RETURN = ''' +''' + + +def pool_exists(api, pool): + # hack to determine if pool exists + result = False + try: + api.LocalLB.Pool.get_object_status(pool_names=[pool]) + result = True + except bigsuds.OperationFailed as e: + if "was not found" in str(e): + result = False + else: + # genuine exception + raise + return result + + +def create_pool(api, pool, lb_method): + # create requires lb_method but we don't want to default + # to a value on subsequent runs + if not lb_method: + lb_method = 'round_robin' + lb_method = "LB_METHOD_%s" % lb_method.strip().upper() + api.LocalLB.Pool.create_v2(pool_names=[pool], lb_methods=[lb_method], + members=[[]]) + + +def remove_pool(api, pool): + api.LocalLB.Pool.delete_pool(pool_names=[pool]) + + +def get_lb_method(api, pool): + lb_method = api.LocalLB.Pool.get_lb_method(pool_names=[pool])[0] + lb_method = lb_method.strip().replace('LB_METHOD_', '').lower() + return lb_method + + +def set_lb_method(api, pool, lb_method): + lb_method = "LB_METHOD_%s" % lb_method.strip().upper() + api.LocalLB.Pool.set_lb_method(pool_names=[pool], lb_methods=[lb_method]) + + +def get_monitors(api, pool): + result = api.LocalLB.Pool.get_monitor_association(pool_names=[pool])[0]['monitor_rule'] + monitor_type = result['type'].split("MONITOR_RULE_TYPE_")[-1].lower() + quorum = result['quorum'] + monitor_templates = result['monitor_templates'] + return (monitor_type, quorum, monitor_templates) + + +def set_monitors(api, pool, monitor_type, quorum, monitor_templates): + monitor_type = "MONITOR_RULE_TYPE_%s" % monitor_type.strip().upper() + monitor_rule = {'type': monitor_type, 'quorum': quorum, 'monitor_templates': monitor_templates} + monitor_association = {'pool_name': pool, 'monitor_rule': monitor_rule} + api.LocalLB.Pool.set_monitor_association(monitor_associations=[monitor_association]) + + +def get_slow_ramp_time(api, pool): + result = api.LocalLB.Pool.get_slow_ramp_time(pool_names=[pool])[0] + return result + + +def set_slow_ramp_time(api, pool, seconds): + api.LocalLB.Pool.set_slow_ramp_time(pool_names=[pool], values=[seconds]) + + +def get_reselect_tries(api, pool): + result = api.LocalLB.Pool.get_reselect_tries(pool_names=[pool])[0] + return result + + +def set_reselect_tries(api, pool, tries): + api.LocalLB.Pool.set_reselect_tries(pool_names=[pool], values=[tries]) + + +def get_action_on_service_down(api, pool): + result = api.LocalLB.Pool.get_action_on_service_down(pool_names=[pool])[0] + result = result.split("SERVICE_DOWN_ACTION_")[-1].lower() + return result + + +def set_action_on_service_down(api, pool, action): + action = "SERVICE_DOWN_ACTION_%s" % action.strip().upper() + api.LocalLB.Pool.set_action_on_service_down(pool_names=[pool], actions=[action]) + + +def member_exists(api, pool, address, port): + # hack to determine if member exists + result = False + try: + members = [{'address': address, 'port': port}] + api.LocalLB.Pool.get_member_object_status(pool_names=[pool], + members=[members]) + result = True + except bigsuds.OperationFailed as e: + if "was not found" in str(e): + result = False + else: + # genuine exception + raise + return result + + +def delete_node_address(api, address): + result = False + try: + api.LocalLB.NodeAddressV2.delete_node_address(nodes=[address]) + result = True + except bigsuds.OperationFailed as e: + if "is referenced by a member of pool" in str(e): + result = False + else: + # genuine exception + raise + return result + + +def remove_pool_member(api, pool, address, port): + members = [{'address': address, 'port': port}] + api.LocalLB.Pool.remove_member_v2(pool_names=[pool], members=[members]) + + +def add_pool_member(api, pool, address, port): + members = [{'address': address, 'port': port}] + api.LocalLB.Pool.add_member_v2(pool_names=[pool], members=[members]) + + +def main(): + lb_method_choices = ['round_robin', 'ratio_member', + 'least_connection_member', 'observed_member', + 'predictive_member', 'ratio_node_address', + 'least_connection_node_address', + 'fastest_node_address', 'observed_node_address', + 'predictive_node_address', 'dynamic_ratio', + 'fastest_app_response', 'least_sessions', + 'dynamic_ratio_member', 'l3_addr', + 'weighted_least_connection_member', + 'weighted_least_connection_node_address', + 'ratio_session', 'ratio_least_connection_member', + 'ratio_least_connection_node_address'] + + monitor_type_choices = ['and_list', 'm_of_n'] + + service_down_choices = ['none', 'reset', 'drop', 'reselect'] + + argument_spec = f5_argument_spec() + + meta_args = dict( + name=dict(type='str', required=True, aliases=['pool']), + lb_method=dict(type='str', choices=lb_method_choices), + monitor_type=dict(type='str', choices=monitor_type_choices), + quorum=dict(type='int'), + monitors=dict(type='list'), + slow_ramp_time=dict(type='int'), + reselect_tries=dict(type='int'), + service_down_action=dict(type='str', choices=service_down_choices), + host=dict(type='str', aliases=['address']), + port=dict(type='int') + ) + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + if not bigsuds_found: + module.fail_json(msg="the python bigsuds module is required") + + if module.params['validate_certs']: + import ssl + if not hasattr(ssl, 'SSLContext'): + module.fail_json(msg='bigsuds does not support verifying certificates with python < 2.7.9. Either update python or set validate_certs=False on the task') + + server = module.params['server'] + server_port = module.params['server_port'] + user = module.params['user'] + password = module.params['password'] + state = module.params['state'] + partition = module.params['partition'] + validate_certs = module.params['validate_certs'] + + name = module.params['name'] + pool = fq_name(partition, name) + lb_method = module.params['lb_method'] + if lb_method: + lb_method = lb_method.lower() + monitor_type = module.params['monitor_type'] + if monitor_type: + monitor_type = monitor_type.lower() + quorum = module.params['quorum'] + monitors = module.params['monitors'] + if monitors: + monitors = [] + for monitor in module.params['monitors']: + monitors.append(fq_name(partition, monitor)) + slow_ramp_time = module.params['slow_ramp_time'] + reselect_tries = module.params['reselect_tries'] + service_down_action = module.params['service_down_action'] + if service_down_action: + service_down_action = service_down_action.lower() + host = module.params['host'] + address = fq_name(partition, host) + port = module.params['port'] + + # sanity check user supplied values + + if (host and port is None) or (port is not None and not host): + module.fail_json(msg="both host and port must be supplied") + + if port is not None and (0 > port or port > 65535): + module.fail_json(msg="valid ports must be in range 0 - 65535") + + if monitors: + if len(monitors) == 1: + # set default required values for single monitor + quorum = 0 + monitor_type = 'single' + elif len(monitors) > 1: + if not monitor_type: + module.fail_json(msg="monitor_type required for monitors > 1") + if monitor_type == 'm_of_n' and not quorum: + module.fail_json(msg="quorum value required for monitor_type m_of_n") + if monitor_type != 'm_of_n': + quorum = 0 + elif monitor_type: + # no monitors specified but monitor_type exists + module.fail_json(msg="monitor_type require monitors parameter") + elif quorum is not None: + # no monitors specified but quorum exists + module.fail_json(msg="quorum requires monitors parameter") + + try: + api = bigip_api(server, user, password, validate_certs, port=server_port) + result = {'changed': False} # default + + if state == 'absent': + if host and port and pool: + # member removal takes precedent + if pool_exists(api, pool) and member_exists(api, pool, address, port): + if not module.check_mode: + remove_pool_member(api, pool, address, port) + deleted = delete_node_address(api, address) + result = {'changed': True, 'deleted': deleted} + else: + result = {'changed': True} + elif pool_exists(api, pool): + # no host/port supplied, must be pool removal + if not module.check_mode: + # hack to handle concurrent runs of module + # pool might be gone before we actually remove it + try: + remove_pool(api, pool) + result = {'changed': True} + except bigsuds.OperationFailed as e: + if "was not found" in str(e): + result = {'changed': False} + else: + # genuine exception + raise + else: + # check-mode return value + result = {'changed': True} + + elif state == 'present': + update = False + if not pool_exists(api, pool): + # pool does not exist -- need to create it + if not module.check_mode: + # a bit of a hack to handle concurrent runs of this module. + # even though we've checked the pool doesn't exist, + # it may exist by the time we run create_pool(). + # this catches the exception and does something smart + # about it! + try: + create_pool(api, pool, lb_method) + result = {'changed': True} + except bigsuds.OperationFailed as e: + if "already exists" in str(e): + update = True + else: + # genuine exception + raise + else: + if monitors: + set_monitors(api, pool, monitor_type, quorum, monitors) + if slow_ramp_time: + set_slow_ramp_time(api, pool, slow_ramp_time) + if reselect_tries: + set_reselect_tries(api, pool, reselect_tries) + if service_down_action: + set_action_on_service_down(api, pool, service_down_action) + if host and port: + add_pool_member(api, pool, address, port) + else: + # check-mode return value + result = {'changed': True} + else: + # pool exists -- potentially modify attributes + update = True + + if update: + if lb_method and lb_method != get_lb_method(api, pool): + if not module.check_mode: + set_lb_method(api, pool, lb_method) + result = {'changed': True} + if monitors: + t_monitor_type, t_quorum, t_monitor_templates = get_monitors(api, pool) + if (t_monitor_type != monitor_type) or (t_quorum != quorum) or (set(t_monitor_templates) != set(monitors)): + if not module.check_mode: + set_monitors(api, pool, monitor_type, quorum, monitors) + result = {'changed': True} + if slow_ramp_time and slow_ramp_time != get_slow_ramp_time(api, pool): + if not module.check_mode: + set_slow_ramp_time(api, pool, slow_ramp_time) + result = {'changed': True} + if reselect_tries and reselect_tries != get_reselect_tries(api, pool): + if not module.check_mode: + set_reselect_tries(api, pool, reselect_tries) + result = {'changed': True} + if service_down_action and service_down_action != get_action_on_service_down(api, pool): + if not module.check_mode: + set_action_on_service_down(api, pool, service_down_action) + result = {'changed': True} + if (host and port) and not member_exists(api, pool, address, port): + if not module.check_mode: + add_pool_member(api, pool, address, port) + result = {'changed': True} + if (host and port == 0) and not member_exists(api, pool, address, port): + if not module.check_mode: + add_pool_member(api, pool, address, port) + result = {'changed': True} + + except Exception as e: + module.fail_json(msg="received exception: %s" % e) + + module.exit_json(**result) + +from ansible.module_utils.basic import * +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_pool_member.py b/lib/ansible/modules/extras/network/f5/bigip_pool_member.py new file mode 100644 index 0000000000..f93ac271ec --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_pool_member.py @@ -0,0 +1,505 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (c) 2013, Matt Hite <mhite@hotmail.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: bigip_pool_member +short_description: Manages F5 BIG-IP LTM pool members +description: + - Manages F5 BIG-IP LTM pool members via iControl SOAP API +version_added: 1.4 +author: + - Matt Hite (@mhite) + - Tim Rupp (@caphrim007) +notes: + - Requires BIG-IP software version >= 11 + - F5 developed module 'bigsuds' required (see http://devcentral.f5.com) + - Best run as a local_action in your playbook + - Supersedes bigip_pool for managing pool members +requirements: + - bigsuds +options: + state: + description: + - Pool member state + required: true + default: present + choices: + - present + - absent + session_state: + description: + - Set new session availability status for pool member + version_added: 2.0 + required: false + default: null + choices: + - enabled + - disabled + monitor_state: + description: + - Set monitor availability status for pool member + version_added: 2.0 + required: false + default: null + choices: + - enabled + - disabled + pool: + description: + - Pool name. This pool must exist. + required: true + partition: + description: + - Partition + required: false + default: 'Common' + host: + description: + - Pool member IP + required: true + aliases: + - address + - name + port: + description: + - Pool member port + required: true + connection_limit: + description: + - Pool member connection limit. Setting this to 0 disables the limit. + required: false + default: null + description: + description: + - Pool member description + required: false + default: null + rate_limit: + description: + - Pool member rate limit (connections-per-second). Setting this to 0 + disables the limit. + required: false + default: null + ratio: + description: + - Pool member ratio weight. Valid values range from 1 through 100. + New pool members -- unless overriden with this value -- default + to 1. + required: false + default: null + preserve_node: + description: + - When state is absent and the pool member is no longer referenced + in other pools, the default behavior removes the unused node + o bject. Setting this to 'yes' disables this behavior. + required: false + default: 'no' + choices: + - yes + - no + version_added: 2.1 +extends_documentation_fragment: f5 +''' + +EXAMPLES = ''' +- name: Add pool member + bigip_pool_member: + server: "lb.mydomain.com" + user: "admin" + password: "secret" + state: "present" + pool: "my-pool" + partition: "Common" + host: "{{ ansible_default_ipv4["address"] }}" + port: 80 + description: "web server" + connection_limit: 100 + rate_limit: 50 + ratio: 2 + delegate_to: localhost + +- name: Modify pool member ratio and description + bigip_pool_member: + server: "lb.mydomain.com" + user: "admin" + password: "secret" + state: "present" + pool: "my-pool" + partition: "Common" + host: "{{ ansible_default_ipv4["address"] }}" + port: 80 + ratio: 1 + description: "nginx server" + delegate_to: localhost + +- name: Remove pool member from pool + bigip_pool_member: + server: "lb.mydomain.com" + user: "admin" + password: "secret" + state: "absent" + pool: "my-pool" + partition: "Common" + host: "{{ ansible_default_ipv4["address"] }}" + port: 80 + delegate_to: localhost + + +# The BIG-IP GUI doesn't map directly to the API calls for "Pool -> +# Members -> State". The following states map to API monitor +# and session states. +# +# Enabled (all traffic allowed): +# monitor_state=enabled, session_state=enabled +# Disabled (only persistent or active connections allowed): +# monitor_state=enabled, session_state=disabled +# Forced offline (only active connections allowed): +# monitor_state=disabled, session_state=disabled +# +# See https://devcentral.f5.com/questions/icontrol-equivalent-call-for-b-node-down + +- name: Force pool member offline + bigip_pool_member: + server: "lb.mydomain.com" + user: "admin" + password: "secret" + state: "present" + session_state: "disabled" + monitor_state: "disabled" + pool: "my-pool" + partition: "Common" + host: "{{ ansible_default_ipv4["address"] }}" + port: 80 + delegate_to: localhost +''' + + +def pool_exists(api, pool): + # hack to determine if pool exists + result = False + try: + api.LocalLB.Pool.get_object_status(pool_names=[pool]) + result = True + except bigsuds.OperationFailed as e: + if "was not found" in str(e): + result = False + else: + # genuine exception + raise + return result + + +def member_exists(api, pool, address, port): + # hack to determine if member exists + result = False + try: + members = [{'address': address, 'port': port}] + api.LocalLB.Pool.get_member_object_status(pool_names=[pool], + members=[members]) + result = True + except bigsuds.OperationFailed as e: + if "was not found" in str(e): + result = False + else: + # genuine exception + raise + return result + + +def delete_node_address(api, address): + result = False + try: + api.LocalLB.NodeAddressV2.delete_node_address(nodes=[address]) + result = True + except bigsuds.OperationFailed as e: + if "is referenced by a member of pool" in str(e): + result = False + else: + # genuine exception + raise + return result + + +def remove_pool_member(api, pool, address, port): + members = [{'address': address, 'port': port}] + api.LocalLB.Pool.remove_member_v2( + pool_names=[pool], + members=[members] + ) + + +def add_pool_member(api, pool, address, port): + members = [{'address': address, 'port': port}] + api.LocalLB.Pool.add_member_v2( + pool_names=[pool], + members=[members] + ) + + +def get_connection_limit(api, pool, address, port): + members = [{'address': address, 'port': port}] + result = api.LocalLB.Pool.get_member_connection_limit( + pool_names=[pool], + members=[members] + )[0][0] + return result + + +def set_connection_limit(api, pool, address, port, limit): + members = [{'address': address, 'port': port}] + api.LocalLB.Pool.set_member_connection_limit( + pool_names=[pool], + members=[members], + limits=[[limit]] + ) + + +def get_description(api, pool, address, port): + members = [{'address': address, 'port': port}] + result = api.LocalLB.Pool.get_member_description( + pool_names=[pool], + members=[members] + )[0][0] + return result + + +def set_description(api, pool, address, port, description): + members = [{'address': address, 'port': port}] + api.LocalLB.Pool.set_member_description( + pool_names=[pool], + members=[members], + descriptions=[[description]] + ) + + +def get_rate_limit(api, pool, address, port): + members = [{'address': address, 'port': port}] + result = api.LocalLB.Pool.get_member_rate_limit( + pool_names=[pool], + members=[members] + )[0][0] + return result + + +def set_rate_limit(api, pool, address, port, limit): + members = [{'address': address, 'port': port}] + api.LocalLB.Pool.set_member_rate_limit( + pool_names=[pool], + members=[members], + limits=[[limit]] + ) + + +def get_ratio(api, pool, address, port): + members = [{'address': address, 'port': port}] + result = api.LocalLB.Pool.get_member_ratio( + pool_names=[pool], + members=[members] + )[0][0] + return result + + +def set_ratio(api, pool, address, port, ratio): + members = [{'address': address, 'port': port}] + api.LocalLB.Pool.set_member_ratio( + pool_names=[pool], + members=[members], + ratios=[[ratio]] + ) + + +def set_member_session_enabled_state(api, pool, address, port, session_state): + members = [{'address': address, 'port': port}] + session_state = ["STATE_%s" % session_state.strip().upper()] + api.LocalLB.Pool.set_member_session_enabled_state( + pool_names=[pool], + members=[members], + session_states=[session_state] + ) + + +def get_member_session_status(api, pool, address, port): + members = [{'address': address, 'port': port}] + result = api.LocalLB.Pool.get_member_session_status( + pool_names=[pool], + members=[members] + )[0][0] + result = result.split("SESSION_STATUS_")[-1].lower() + return result + + +def set_member_monitor_state(api, pool, address, port, monitor_state): + members = [{'address': address, 'port': port}] + monitor_state = ["STATE_%s" % monitor_state.strip().upper()] + api.LocalLB.Pool.set_member_monitor_state( + pool_names=[pool], + members=[members], + monitor_states=[monitor_state] + ) + + +def get_member_monitor_status(api, pool, address, port): + members = [{'address': address, 'port': port}] + result = api.LocalLB.Pool.get_member_monitor_status( + pool_names=[pool], + members=[members] + )[0][0] + result = result.split("MONITOR_STATUS_")[-1].lower() + return result + + +def main(): + argument_spec = f5_argument_spec() + + meta_args = dict( + session_state=dict(type='str', choices=['enabled', 'disabled']), + monitor_state=dict(type='str', choices=['enabled', 'disabled']), + pool=dict(type='str', required=True), + host=dict(type='str', required=True, aliases=['address', 'name']), + port=dict(type='int', required=True), + connection_limit=dict(type='int'), + description=dict(type='str'), + rate_limit=dict(type='int'), + ratio=dict(type='int'), + preserve_node=dict(type='bool', default=False) + ) + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + if module.params['validate_certs']: + import ssl + if not hasattr(ssl, 'SSLContext'): + module.fail_json(msg='bigsuds does not support verifying certificates with python < 2.7.9. Either update python or set validate_certs=False on the task') + + server = module.params['server'] + server_port = module.params['server_port'] + user = module.params['user'] + password = module.params['password'] + state = module.params['state'] + partition = module.params['partition'] + validate_certs = module.params['validate_certs'] + + session_state = module.params['session_state'] + monitor_state = module.params['monitor_state'] + pool = fq_name(partition, module.params['pool']) + connection_limit = module.params['connection_limit'] + description = module.params['description'] + rate_limit = module.params['rate_limit'] + ratio = module.params['ratio'] + host = module.params['host'] + address = fq_name(partition, host) + port = module.params['port'] + preserve_node = module.params['preserve_node'] + + if (host and port is None) or (port is not None and not host): + module.fail_json(msg="both host and port must be supplied") + + if 0 > port or port > 65535: + module.fail_json(msg="valid ports must be in range 0 - 65535") + + try: + api = bigip_api(server, user, password, validate_certs, port=server_port) + if not pool_exists(api, pool): + module.fail_json(msg="pool %s does not exist" % pool) + result = {'changed': False} # default + + if state == 'absent': + if member_exists(api, pool, address, port): + if not module.check_mode: + remove_pool_member(api, pool, address, port) + if preserve_node: + result = {'changed': True} + else: + deleted = delete_node_address(api, address) + result = {'changed': True, 'deleted': deleted} + else: + result = {'changed': True} + + elif state == 'present': + if not member_exists(api, pool, address, port): + if not module.check_mode: + add_pool_member(api, pool, address, port) + if connection_limit is not None: + set_connection_limit(api, pool, address, port, connection_limit) + if description is not None: + set_description(api, pool, address, port, description) + if rate_limit is not None: + set_rate_limit(api, pool, address, port, rate_limit) + if ratio is not None: + set_ratio(api, pool, address, port, ratio) + if session_state is not None: + set_member_session_enabled_state(api, pool, address, port, session_state) + if monitor_state is not None: + set_member_monitor_state(api, pool, address, port, monitor_state) + result = {'changed': True} + else: + # pool member exists -- potentially modify attributes + if connection_limit is not None and connection_limit != get_connection_limit(api, pool, address, port): + if not module.check_mode: + set_connection_limit(api, pool, address, port, connection_limit) + result = {'changed': True} + if description is not None and description != get_description(api, pool, address, port): + if not module.check_mode: + set_description(api, pool, address, port, description) + result = {'changed': True} + if rate_limit is not None and rate_limit != get_rate_limit(api, pool, address, port): + if not module.check_mode: + set_rate_limit(api, pool, address, port, rate_limit) + result = {'changed': True} + if ratio is not None and ratio != get_ratio(api, pool, address, port): + if not module.check_mode: + set_ratio(api, pool, address, port, ratio) + result = {'changed': True} + if session_state is not None: + session_status = get_member_session_status(api, pool, address, port) + if session_state == 'enabled' and session_status == 'forced_disabled': + if not module.check_mode: + set_member_session_enabled_state(api, pool, address, port, session_state) + result = {'changed': True} + elif session_state == 'disabled' and session_status != 'forced_disabled': + if not module.check_mode: + set_member_session_enabled_state(api, pool, address, port, session_state) + result = {'changed': True} + if monitor_state is not None: + monitor_status = get_member_monitor_status(api, pool, address, port) + if monitor_state == 'enabled' and monitor_status == 'forced_down': + if not module.check_mode: + set_member_monitor_state(api, pool, address, port, monitor_state) + result = {'changed': True} + elif monitor_state == 'disabled' and monitor_status != 'forced_down': + if not module.check_mode: + set_member_monitor_state(api, pool, address, port, monitor_state) + result = {'changed': True} + + except Exception as e: + module.fail_json(msg="received exception: %s" % e) + + module.exit_json(**result) + +from ansible.module_utils.basic import * +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_routedomain.py b/lib/ansible/modules/extras/network/f5/bigip_routedomain.py new file mode 100644 index 0000000000..552b20231c --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_routedomain.py @@ -0,0 +1,523 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: bigip_routedomain +short_description: Manage route domains on a BIG-IP +description: + - Manage route domains on a BIG-IP +version_added: "2.2" +options: + bwc_policy: + description: + - The bandwidth controller for the route domain. + connection_limit: + description: + - The maximum number of concurrent connections allowed for the + route domain. Setting this to C(0) turns off connection limits. + description: + description: + - Specifies descriptive text that identifies the route domain. + flow_eviction_policy: + description: + - The eviction policy to use with this route domain. Apply an eviction + policy to provide customized responses to flow overflows and slow + flows on the route domain. + id: + description: + - The unique identifying integer representing the route domain. + required: true + parent: + description: | + Specifies the route domain the system searches when it cannot + find a route in the configured domain. + routing_protocol: + description: + - Dynamic routing protocols for the system to use in the route domain. + choices: + - BFD + - BGP + - IS-IS + - OSPFv2 + - OSPFv3 + - PIM + - RIP + - RIPng + service_policy: + description: + - Service policy to associate with the route domain. + state: + description: + - Whether the route domain should exist or not. + required: false + default: present + choices: + - present + - absent + strict: + description: + - Specifies whether the system enforces cross-routing restrictions + or not. + choices: + - enabled + - disabled + vlans: + description: + - VLANs for the system to use in the route domain +notes: + - Requires the f5-sdk Python package on the host. This is as easy as + pip install f5-sdk. +extends_documentation_fragment: f5 +requirements: + - f5-sdk +author: + - Tim Rupp (@caphrim007) +''' + +EXAMPLES = ''' +- name: Create a route domain + bigip_routedomain: + id: "1234" + password: "secret" + server: "lb.mydomain.com" + state: "present" + user: "admin" + delegate_to: localhost + +- name: Set VLANs on the route domain + bigip_routedomain: + id: "1234" + password: "secret" + server: "lb.mydomain.com" + state: "present" + user: "admin" + vlans: + - net1 + - foo + delegate_to: localhost +''' + +RETURN = ''' +id: + description: The ID of the route domain that was changed + returned: changed + type: int + sample: 2 +description: + description: The description of the route domain + returned: changed + type: string + sample: "route domain foo" +strict: + description: The new strict isolation setting + returned: changed + type: string + sample: "enabled" +parent: + description: The new parent route domain + returned: changed + type: int + sample: 0 +vlans: + description: List of new VLANs the route domain is applied to + returned: changed + type: list + sample: ['/Common/http-tunnel', '/Common/socks-tunnel'] +routing_protocol: + description: List of routing protocols applied to the route domain + returned: changed + type: list + sample: ['bfd', 'bgp'] +bwc_policy: + description: The new bandwidth controller + returned: changed + type: string + sample: /Common/foo +connection_limit: + description: The new connection limit for the route domain + returned: changed + type: integer + sample: 100 +flow_eviction_policy: + description: The new eviction policy to use with this route domain + returned: changed + type: string + sample: /Common/default-eviction-policy +service_policy: + description: The new service policy to use with this route domain + returned: changed + type: string + sample: /Common-my-service-policy +''' + +try: + from f5.bigip import ManagementRoot + from icontrol.session import iControlUnexpectedHTTPError + HAS_F5SDK = True +except ImportError: + HAS_F5SDK = False + +PROTOCOLS = [ + 'BFD', 'BGP', 'IS-IS', 'OSPFv2', 'OSPFv3', 'PIM', 'RIP', 'RIPng' +] + +STRICTS = ['enabled', 'disabled'] + + +class BigIpRouteDomain(object): + def __init__(self, *args, **kwargs): + if not HAS_F5SDK: + raise F5ModuleError("The python f5-sdk module is required") + + # The params that change in the module + self.cparams = dict() + + kwargs['name'] = str(kwargs['id']) + + # Stores the params that are sent to the module + self.params = kwargs + self.api = ManagementRoot(kwargs['server'], + kwargs['user'], + kwargs['password'], + port=kwargs['server_port']) + + def absent(self): + if not self.exists(): + return False + + if self.params['check_mode']: + return True + + rd = self.api.tm.net.route_domains.route_domain.load( + name=self.params['name'] + ) + rd.delete() + + if self.exists(): + raise F5ModuleError("Failed to delete the route domain") + else: + return True + + def present(self): + if self.exists(): + return self.update() + else: + if self.params['check_mode']: + return True + return self.create() + + def read(self): + """Read information and transform it + + The values that are returned by BIG-IP in the f5-sdk can have encoding + attached to them as well as be completely missing in some cases. + + Therefore, this method will transform the data from the BIG-IP into a + format that is more easily consumable by the rest of the class and the + parameters that are supported by the module. + """ + p = dict() + r = self.api.tm.net.route_domains.route_domain.load( + name=self.params['name'] + ) + + p['id'] = int(r.id) + p['name'] = str(r.name) + + if hasattr(r, 'connectionLimit'): + p['connection_limit'] = int(r.connectionLimit) + if hasattr(r, 'description'): + p['description'] = str(r.description) + if hasattr(r, 'strict'): + p['strict'] = str(r.strict) + if hasattr(r, 'parent'): + p['parent'] = r.parent + if hasattr(r, 'vlans'): + p['vlans'] = list(set([str(x) for x in r.vlans])) + if hasattr(r, 'routingProtocol'): + p['routing_protocol'] = list(set([str(x) for x in r.routingProtocol])) + if hasattr(r, 'flowEvictionPolicy'): + p['flow_eviction_policy'] = str(r.flowEvictionPolicy) + if hasattr(r, 'bwcPolicy'): + p['bwc_policy'] = str(r.bwcPolicy) + if hasattr(r, 'servicePolicy'): + p['service_policy'] = str(r.servicePolicy) + return p + + def domains(self): + result = [] + + domains = self.api.tm.net.route_domains.get_collection() + for domain in domains: + # Just checking for the addition of the partition here for + # different versions of BIG-IP + if '/' + self.params['partition'] + '/' in domain.name: + result.append(domain.name) + else: + full_name = '/%s/%s' % (self.params['partition'], domain.name) + result.append(full_name) + return result + + def create(self): + params = dict() + params['id'] = self.params['id'] + params['name'] = self.params['name'] + + partition = self.params['partition'] + description = self.params['description'] + strict = self.params['strict'] + parent = self.params['parent'] + bwc_policy = self.params['bwc_policy'] + vlans = self.params['vlans'] + routing_protocol = self.params['routing_protocol'] + connection_limit = self.params['connection_limit'] + flow_eviction_policy = self.params['flow_eviction_policy'] + service_policy = self.params['service_policy'] + + if description is not None: + params['description'] = description + + if strict is not None: + params['strict'] = strict + + if parent is not None: + parent = '/%s/%s' % (partition, parent) + if parent in self.domains(): + params['parent'] = parent + else: + raise F5ModuleError( + "The parent route domain was not found" + ) + + if bwc_policy is not None: + policy = '/%s/%s' % (partition, bwc_policy) + params['bwcPolicy'] = policy + + if vlans is not None: + params['vlans'] = [] + for vlan in vlans: + vname = '/%s/%s' % (partition, vlan) + params['vlans'].append(vname) + + if routing_protocol is not None: + params['routingProtocol'] = [] + for protocol in routing_protocol: + if protocol in PROTOCOLS: + params['routingProtocol'].append(protocol) + else: + raise F5ModuleError( + "routing_protocol must be one of: %s" % (PROTOCOLS) + ) + + if connection_limit is not None: + params['connectionLimit'] = connection_limit + + if flow_eviction_policy is not None: + policy = '/%s/%s' % (partition, flow_eviction_policy) + params['flowEvictionPolicy'] = policy + + if service_policy is not None: + policy = '/%s/%s' % (partition, service_policy) + params['servicePolicy'] = policy + + self.api.tm.net.route_domains.route_domain.create(**params) + exists = self.api.tm.net.route_domains.route_domain.exists( + name=self.params['name'] + ) + + if exists: + return True + else: + raise F5ModuleError( + "An error occurred while creating the route domain" + ) + + def update(self): + changed = False + params = dict() + current = self.read() + + check_mode = self.params['check_mode'] + partition = self.params['partition'] + description = self.params['description'] + strict = self.params['strict'] + parent = self.params['parent'] + bwc_policy = self.params['bwc_policy'] + vlans = self.params['vlans'] + routing_protocol = self.params['routing_protocol'] + connection_limit = self.params['connection_limit'] + flow_eviction_policy = self.params['flow_eviction_policy'] + service_policy = self.params['service_policy'] + + if description is not None: + if 'description' in current: + if description != current['description']: + params['description'] = description + else: + params['description'] = description + + if strict is not None: + if strict != current['strict']: + params['strict'] = strict + + if parent is not None: + parent = '/%s/%s' % (partition, parent) + if 'parent' in current: + if parent != current['parent']: + params['parent'] = parent + else: + params['parent'] = parent + + if bwc_policy is not None: + policy = '/%s/%s' % (partition, bwc_policy) + if 'bwc_policy' in current: + if policy != current['bwc_policy']: + params['bwcPolicy'] = policy + else: + params['bwcPolicy'] = policy + + if vlans is not None: + tmp = set() + for vlan in vlans: + vname = '/%s/%s' % (partition, vlan) + tmp.add(vname) + tmp = list(tmp) + if 'vlans' in current: + if tmp != current['vlans']: + params['vlans'] = tmp + else: + params['vlans'] = tmp + + if routing_protocol is not None: + tmp = set() + for protocol in routing_protocol: + if protocol in PROTOCOLS: + tmp.add(protocol) + else: + raise F5ModuleError( + "routing_protocol must be one of: %s" % (PROTOCOLS) + ) + tmp = list(tmp) + if 'routing_protocol' in current: + if tmp != current['routing_protocol']: + params['routingProtocol'] = tmp + else: + params['routingProtocol'] = tmp + + if connection_limit is not None: + if connection_limit != current['connection_limit']: + params['connectionLimit'] = connection_limit + + if flow_eviction_policy is not None: + policy = '/%s/%s' % (partition, flow_eviction_policy) + if 'flow_eviction_policy' in current: + if policy != current['flow_eviction_policy']: + params['flowEvictionPolicy'] = policy + else: + params['flowEvictionPolicy'] = policy + + if service_policy is not None: + policy = '/%s/%s' % (partition, service_policy) + if 'service_policy' in current: + if policy != current['service_policy']: + params['servicePolicy'] = policy + else: + params['servicePolicy'] = policy + + if params: + changed = True + self.cparams = camel_dict_to_snake_dict(params) + if check_mode: + return changed + else: + return changed + + try: + rd = self.api.tm.net.route_domains.route_domain.load( + name=self.params['name'] + ) + rd.update(**params) + rd.refresh() + except iControlUnexpectedHTTPError as e: + raise F5ModuleError(e) + + return True + + def exists(self): + return self.api.tm.net.route_domains.route_domain.exists( + name=self.params['name'] + ) + + def flush(self): + result = dict() + state = self.params['state'] + + if self.params['check_mode']: + if value == current: + changed = False + else: + changed = True + else: + if state == "present": + changed = self.present() + current = self.read() + result.update(current) + elif state == "absent": + changed = self.absent() + + result.update(dict(changed=changed)) + return result + + +def main(): + argument_spec = f5_argument_spec() + + meta_args = dict( + id=dict(required=True, type='int'), + description=dict(required=False, default=None), + strict=dict(required=False, default=None, choices=STRICTS), + parent=dict(required=False, type='int', default=None), + vlans=dict(required=False, default=None, type='list'), + routing_protocol=dict(required=False, default=None, type='list'), + bwc_policy=dict(required=False, type='str', default=None), + connection_limit=dict(required=False, type='int', default=None), + flow_eviction_policy=dict(required=False, type='str', default=None), + service_policy=dict(required=False, type='str', default=None) + ) + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + try: + obj = BigIpRouteDomain(check_mode=module.check_mode, **module.params) + result = obj.flush() + + module.exit_json(**result) + except F5ModuleError as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.basic import * +from ansible.module_utils.ec2 import camel_dict_to_snake_dict +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_selfip.py b/lib/ansible/modules/extras/network/f5/bigip_selfip.py new file mode 100644 index 0000000000..6cbf7badb5 --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_selfip.py @@ -0,0 +1,659 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: bigip_selfip +short_description: Manage Self-IPs on a BIG-IP system +description: + - Manage Self-IPs on a BIG-IP system +version_added: "2.2" +options: + address: + description: + - The IP addresses for the new self IP. This value is ignored upon update + as addresses themselves cannot be changed after they are created. + allow_service: + description: + - Configure port lockdown for the Self IP. By default, the Self IP has a + "default deny" policy. This can be changed to allow TCP and UDP ports + as well as specific protocols. This list should contain C(protocol):C(port) + values. + name: + description: + - The self IP to create. + required: true + default: Value of C(address) + netmask: + description: + - The netmasks for the self IP. + required: true + state: + description: + - The state of the variable on the system. When C(present), guarantees + that the Self-IP exists with the provided attributes. When C(absent), + removes the Self-IP from the system. + required: false + default: present + choices: + - absent + - present + traffic_group: + description: + - The traffic group for the self IP addresses in an active-active, + redundant load balancer configuration. + required: false + vlan: + description: + - The VLAN that the new self IPs will be on. + required: true +notes: + - Requires the f5-sdk Python package on the host. This is as easy as pip + install f5-sdk. + - Requires the netaddr Python package on the host. +extends_documentation_fragment: f5 +requirements: + - netaddr + - f5-sdk +author: + - Tim Rupp (@caphrim007) +''' + +EXAMPLES = ''' +- name: Create Self IP + bigip_selfip: + address: "10.10.10.10" + name: "self1" + netmask: "255.255.255.0" + password: "secret" + server: "lb.mydomain.com" + user: "admin" + validate_certs: "no" + vlan: "vlan1" + delegate_to: localhost + +- name: Delete Self IP + bigip_selfip: + name: "self1" + password: "secret" + server: "lb.mydomain.com" + state: "absent" + user: "admin" + validate_certs: "no" + delegate_to: localhost + +- name: Allow management web UI to be accessed on this Self IP + bigip_selfip: + name: "self1" + password: "secret" + server: "lb.mydomain.com" + state: "absent" + user: "admin" + validate_certs: "no" + allow_service: + - "tcp:443" + delegate_to: localhost + +- name: Allow HTTPS and SSH access to this Self IP + bigip_selfip: + name: "self1" + password: "secret" + server: "lb.mydomain.com" + state: "absent" + user: "admin" + validate_certs: "no" + allow_service: + - "tcp:443" + - "tpc:22" + delegate_to: localhost + +- name: Allow all services access to this Self IP + bigip_selfip: + name: "self1" + password: "secret" + server: "lb.mydomain.com" + state: "absent" + user: "admin" + validate_certs: "no" + allow_service: + - all + delegate_to: localhost + +- name: Allow only GRE and IGMP protocols access to this Self IP + bigip_selfip: + name: "self1" + password: "secret" + server: "lb.mydomain.com" + state: "absent" + user: "admin" + validate_certs: "no" + allow_service: + - gre:0 + - igmp:0 + delegate_to: localhost + +- name: Allow all TCP, but no other protocols access to this Self IP + bigip_selfip: + name: "self1" + password: "secret" + server: "lb.mydomain.com" + state: "absent" + user: "admin" + validate_certs: "no" + allow_service: + - tcp:0 + delegate_to: localhost +''' + +RETURN = ''' +allow_service: + description: Services that allowed via this Self IP + returned: changed + type: list + sample: ['igmp:0','tcp:22','udp:53'] +address: + description: The address for the Self IP + returned: created + type: string + sample: "192.0.2.10" +name: + description: The name of the Self IP + returned: + - created + - changed + - deleted + type: string + sample: "self1" +netmask: + description: The netmask of the Self IP + returned: + - changed + - created + type: string + sample: "255.255.255.0" +traffic_group: + description: The traffic group that the Self IP is a member of + return: + - changed + - created + type: string + sample: "traffic-group-local-only" +vlan: + description: The VLAN set on the Self IP + return: + - changed + - created + type: string + sample: "vlan1" +''' + +try: + from f5.bigip import ManagementRoot + from icontrol.session import iControlUnexpectedHTTPError + HAS_F5SDK = True +except ImportError: + HAS_F5SDK = False + +try: + from netaddr import IPNetwork, AddrFormatError + HAS_NETADDR = True +except ImportError: + HAS_NETADDR = False + +FLOAT = ['enabled', 'disabled'] +DEFAULT_TG = 'traffic-group-local-only' +ALLOWED_PROTOCOLS = ['eigrp', 'egp', 'gre', 'icmp', 'igmp', 'igp', 'ipip', + 'l2tp', 'ospf', 'pim', 'tcp', 'udp'] + + +class BigIpSelfIp(object): + def __init__(self, *args, **kwargs): + if not HAS_F5SDK: + raise F5ModuleError("The python f5-sdk module is required") + + # The params that change in the module + self.cparams = dict() + + # Stores the params that are sent to the module + self.params = kwargs + self.api = ManagementRoot(kwargs['server'], + kwargs['user'], + kwargs['password'], + port=kwargs['server_port']) + + def present(self): + changed = False + + if self.exists(): + changed = self.update() + else: + changed = self.create() + + return changed + + def absent(self): + changed = False + + if self.exists(): + changed = self.delete() + + return changed + + def read(self): + """Read information and transform it + + The values that are returned by BIG-IP in the f5-sdk can have encoding + attached to them as well as be completely missing in some cases. + + Therefore, this method will transform the data from the BIG-IP into a + format that is more easily consumable by the rest of the class and the + parameters that are supported by the module. + + :return: List of values currently stored in BIG-IP, formatted for use + in this class. + """ + p = dict() + name = self.params['name'] + partition = self.params['partition'] + r = self.api.tm.net.selfips.selfip.load( + name=name, + partition=partition + ) + + if hasattr(r, 'address'): + ipnet = IPNetwork(r.address) + p['address'] = str(ipnet.ip) + if hasattr(r, 'address'): + ipnet = IPNetwork(r.address) + p['netmask'] = str(ipnet.netmask) + if hasattr(r, 'trafficGroup'): + p['traffic_group'] = str(r.trafficGroup) + if hasattr(r, 'vlan'): + p['vlan'] = str(r.vlan) + if hasattr(r, 'allowService'): + if r.allowService == 'all': + p['allow_service'] = set(['all']) + else: + p['allow_service'] = set([str(x) for x in r.allowService]) + else: + p['allow_service'] = set(['none']) + p['name'] = name + return p + + def verify_services(self): + """Verifies that a supplied service string has correct format + + The string format for port lockdown is PROTOCOL:PORT. This method + will verify that the provided input matches the allowed protocols + and the port ranges before submitting to BIG-IP. + + The only allowed exceptions to this rule are the following values + + * all + * default + * none + + These are special cases that are handled differently in the API. + "all" is set as a string, "default" is set as a one item list, and + "none" removes the key entirely from the REST API. + + :raises F5ModuleError: + """ + result = [] + for svc in self.params['allow_service']: + if svc in ['all', 'none', 'default']: + result = [svc] + break + + tmp = svc.split(':') + if tmp[0] not in ALLOWED_PROTOCOLS: + raise F5ModuleError( + "The provided protocol '%s' is invalid" % (tmp[0]) + ) + try: + port = int(tmp[1]) + except Exception: + raise F5ModuleError( + "The provided port '%s' is not a number" % (tmp[1]) + ) + + if port < 0 or port > 65535: + raise F5ModuleError( + "The provided port '%s' must be between 0 and 65535" + % (port) + ) + else: + result.append(svc) + return set(result) + + def fmt_services(self, services): + """Returns services formatted for consumption by f5-sdk update + + The BIG-IP endpoint for services takes different values depending on + what you want the "allowed services" to be. It can be any of the + following + + - a list containing "protocol:port" values + - the string "all" + - a null value, or None + + This is a convenience function to massage the values the user has + supplied so that they are formatted in such a way that BIG-IP will + accept them and apply the specified policy. + + :param services: The services to format. This is always a Python set + :return: + """ + result = list(services) + if result[0] == 'all': + return 'all' + elif result[0] == 'none': + return None + else: + return list(services) + + def traffic_groups(self): + result = [] + + groups = self.api.tm.cm.traffic_groups.get_collection() + for group in groups: + # Just checking for the addition of the partition here for + # different versions of BIG-IP + if '/' + self.params['partition'] + '/' in group.name: + result.append(group.name) + else: + full_name = '/%s/%s' % (self.params['partition'], group.name) + result.append(str(full_name)) + return result + + def update(self): + changed = False + svcs = [] + params = dict() + current = self.read() + + check_mode = self.params['check_mode'] + address = self.params['address'] + allow_service = self.params['allow_service'] + name = self.params['name'] + netmask = self.params['netmask'] + partition = self.params['partition'] + traffic_group = self.params['traffic_group'] + vlan = self.params['vlan'] + + if address is not None and address != current['address']: + raise F5ModuleError( + 'Self IP addresses cannot be updated' + ) + + if netmask is not None: + # I ignore the address value here even if they provide it because + # you are not allowed to change it. + try: + address = IPNetwork(current['address']) + + new_addr = "%s/%s" % (address.ip, netmask) + nipnet = IPNetwork(new_addr) + + cur_addr = "%s/%s" % (current['address'], current['netmask']) + cipnet = IPNetwork(cur_addr) + + if nipnet != cipnet: + address = "%s/%s" % (nipnet.ip, nipnet.prefixlen) + params['address'] = address + except AddrFormatError: + raise F5ModuleError( + 'The provided address/netmask value was invalid' + ) + + if traffic_group is not None: + traffic_group = "/%s/%s" % (partition, traffic_group) + if traffic_group not in self.traffic_groups(): + raise F5ModuleError( + 'The specified traffic group was not found' + ) + + if 'traffic_group' in current: + if traffic_group != current['traffic_group']: + params['trafficGroup'] = traffic_group + else: + params['trafficGroup'] = traffic_group + + if vlan is not None: + vlans = self.get_vlans() + vlan = "/%s/%s" % (partition, vlan) + + if 'vlan' in current: + if vlan != current['vlan']: + params['vlan'] = vlan + else: + params['vlan'] = vlan + + if vlan not in vlans: + raise F5ModuleError( + 'The specified VLAN was not found' + ) + + if allow_service is not None: + svcs = self.verify_services() + if 'allow_service' in current: + if svcs != current['allow_service']: + params['allowService'] = self.fmt_services(svcs) + else: + params['allowService'] = self.fmt_services(svcs) + + if params: + changed = True + params['name'] = name + params['partition'] = partition + if check_mode: + return changed + self.cparams = camel_dict_to_snake_dict(params) + if svcs: + self.cparams['allow_service'] = list(svcs) + else: + return changed + + r = self.api.tm.net.selfips.selfip.load( + name=name, + partition=partition + ) + r.update(**params) + r.refresh() + + return True + + def get_vlans(self): + """Returns formatted list of VLANs + + The VLAN values stored in BIG-IP are done so using their fully + qualified name which includes the partition. Therefore, "correct" + values according to BIG-IP look like this + + /Common/vlan1 + + This is in contrast to the formats that most users think of VLANs + as being stored as + + vlan1 + + To provide for the consistent user experience while not turfing + BIG-IP, we need to massage the values that are provided by the + user so that they include the partition. + + :return: List of vlans formatted with preceeding partition + """ + partition = self.params['partition'] + vlans = self.api.tm.net.vlans.get_collection() + return [str("/" + partition + "/" + x.name) for x in vlans] + + def create(self): + params = dict() + + svcs = [] + check_mode = self.params['check_mode'] + address = self.params['address'] + allow_service = self.params['allow_service'] + name = self.params['name'] + netmask = self.params['netmask'] + partition = self.params['partition'] + traffic_group = self.params['traffic_group'] + vlan = self.params['vlan'] + + if address is None or netmask is None: + raise F5ModuleError( + 'An address and a netmask must be specififed' + ) + + if vlan is None: + raise F5ModuleError( + 'A VLAN name must be specified' + ) + else: + vlan = "/%s/%s" % (partition, vlan) + + try: + ipin = "%s/%s" % (address, netmask) + ipnet = IPNetwork(ipin) + params['address'] = "%s/%s" % (ipnet.ip, ipnet.prefixlen) + except AddrFormatError: + raise F5ModuleError( + 'The provided address/netmask value was invalid' + ) + + if traffic_group is None: + params['trafficGroup'] = "/%s/%s" % (partition, DEFAULT_TG) + else: + traffic_group = "/%s/%s" % (partition, traffic_group) + if traffic_group in self.traffic_groups(): + params['trafficGroup'] = traffic_group + else: + raise F5ModuleError( + 'The specified traffic group was not found' + ) + + vlans = self.get_vlans() + if vlan in vlans: + params['vlan'] = vlan + else: + raise F5ModuleError( + 'The specified VLAN was not found' + ) + + if allow_service is not None: + svcs = self.verify_services() + params['allowService'] = self.fmt_services(svcs) + + params['name'] = name + params['partition'] = partition + + self.cparams = camel_dict_to_snake_dict(params) + if svcs: + self.cparams['allow_service'] = list(svcs) + + if check_mode: + return True + + d = self.api.tm.net.selfips.selfip + d.create(**params) + + if self.exists(): + return True + else: + raise F5ModuleError("Failed to create the self IP") + + def delete(self): + params = dict() + check_mode = self.params['check_mode'] + + params['name'] = self.params['name'] + params['partition'] = self.params['partition'] + + self.cparams = camel_dict_to_snake_dict(params) + if check_mode: + return True + + dc = self.api.tm.net.selfips.selfip.load(**params) + dc.delete() + + if self.exists(): + raise F5ModuleError("Failed to delete the self IP") + return True + + def exists(self): + name = self.params['name'] + partition = self.params['partition'] + return self.api.tm.net.selfips.selfip.exists( + name=name, + partition=partition + ) + + def flush(self): + result = dict() + state = self.params['state'] + + try: + if state == "present": + changed = self.present() + elif state == "absent": + changed = self.absent() + except iControlUnexpectedHTTPError as e: + raise F5ModuleError(str(e)) + + result.update(**self.cparams) + result.update(dict(changed=changed)) + return result + + +def main(): + argument_spec = f5_argument_spec() + + meta_args = dict( + address=dict(required=False, default=None), + allow_service=dict(type='list', default=None), + name=dict(required=True), + netmask=dict(required=False, default=None), + traffic_group=dict(required=False, default=None), + vlan=dict(required=False, default=None) + ) + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + try: + if not HAS_NETADDR: + raise F5ModuleError( + "The netaddr python module is required." + ) + + obj = BigIpSelfIp(check_mode=module.check_mode, **module.params) + result = obj.flush() + + module.exit_json(**result) + except F5ModuleError as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.basic import * +from ansible.module_utils.ec2 import camel_dict_to_snake_dict +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_ssl_certificate.py b/lib/ansible/modules/extras/network/f5/bigip_ssl_certificate.py new file mode 100644 index 0000000000..076caba9f5 --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_ssl_certificate.py @@ -0,0 +1,516 @@ +#!/usr/bin/python +# +# (c) 2016, Kevin Coming (@waffie1) +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +module: bigip_ssl_certificate +short_description: Import/Delete certificates from BIG-IP +description: + - This module will import/delete SSL certificates on BIG-IP LTM. + Certificates can be imported from certificate and key files on the local + disk, in PEM format. +version_added: 2.2 +options: + cert_content: + description: + - When used instead of 'cert_src', sets the contents of a certificate directly + to the specified value. This is used with lookup plugins or for anything + with formatting or templating. Either one of C(key_src), + C(key_content), C(cert_src) or C(cert_content) must be provided when + C(state) is C(present). + required: false + key_content: + description: + - When used instead of 'key_src', sets the contents of a certificate key + directly to the specified value. This is used with lookup plugins or for + anything with formatting or templating. Either one of C(key_src), + C(key_content), C(cert_src) or C(cert_content) must be provided when + C(state) is C(present). + required: false + state: + description: + - Certificate and key state. This determines if the provided certificate + and key is to be made C(present) on the device or C(absent). + required: true + default: present + choices: + - present + - absent + partition: + description: + - BIG-IP partition to use when adding/deleting certificate. + required: false + default: Common + name: + description: + - SSL Certificate Name. This is the cert/key pair name used + when importing a certificate/key into the F5. It also + determines the filenames of the objects on the LTM + (:Partition:name.cer_11111_1 and :Partition_name.key_11111_1). + required: true + cert_src: + description: + - This is the local filename of the certificate. Either one of C(key_src), + C(key_content), C(cert_src) or C(cert_content) must be provided when + C(state) is C(present). + required: false + key_src: + description: + - This is the local filename of the private key. Either one of C(key_src), + C(key_content), C(cert_src) or C(cert_content) must be provided when + C(state) is C(present). + required: false + passphrase: + description: + - Passphrase on certificate private key + required: false +notes: + - Requires the f5-sdk Python package on the host. This is as easy as pip + install f5-sdk. + - Requires the netaddr Python package on the host. + - If you use this module, you will not be able to remove the certificates + and keys that are managed, via the web UI. You can only remove them via + tmsh or these modules. +extends_documentation_fragment: f5 +requirements: + - f5-sdk >= 1.3.1 + - BigIP >= v12 +author: + - Kevin Coming (@waffie1) + - Tim Rupp (@caphrim007) +''' + +EXAMPLES = ''' +- name: Import PEM Certificate from local disk + bigip_ssl_certificate: + name: "certificate-name" + server: "lb.mydomain.com" + user: "admin" + password: "secret" + state: "present" + cert_src: "/path/to/cert.crt" + key_src: "/path/to/key.key" + delegate_to: localhost + +- name: Use a file lookup to import PEM Certificate + bigip_ssl_certificate: + name: "certificate-name" + server: "lb.mydomain.com" + user: "admin" + password: "secret" + state: "present" + cert_content: "{{ lookup('file', '/path/to/cert.crt') }}" + key_content: "{{ lookup('file', '/path/to/key.key') }}" + delegate_to: localhost + +- name: "Delete Certificate" + bigip_ssl_certificate: + name: "certificate-name" + server: "lb.mydomain.com" + user: "admin" + password: "secret" + state: "absent" + delegate_to: localhost +''' + +RETURN = ''' +cert_name: + description: > + The name of the SSL certificate. The C(cert_name) and + C(key_name) will be equal to each other. + returned: + - created + - changed + - deleted + type: string + sample: "cert1" +key_name: + description: > + The name of the SSL certificate key. The C(key_name) and + C(cert_name) will be equal to each other. + returned: + - created + - changed + - deleted + type: string + sample: "key1" +partition: + description: Partition in which the cert/key was created + returned: + - changed + - created + - deleted + type: string + sample: "Common" +key_checksum: + description: SHA1 checksum of the key that was provided + return: + - changed + - created + type: string + sample: "cf23df2207d99a74fbe169e3eba035e633b65d94" +cert_checksum: + description: SHA1 checksum of the cert that was provided + return: + - changed + - created + type: string + sample: "f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" +''' + + +try: + from f5.bigip.contexts import TransactionContextManager + from f5.bigip import ManagementRoot + from icontrol.session import iControlUnexpectedHTTPError + HAS_F5SDK = True +except ImportError: + HAS_F5SDK = False + + +import hashlib +import StringIO + + +class BigIpSslCertificate(object): + def __init__(self, *args, **kwargs): + if not HAS_F5SDK: + raise F5ModuleError("The python f5-sdk module is required") + + required_args = ['key_content', 'key_src', 'cert_content', 'cert_src'] + + ksource = kwargs['key_src'] + if ksource: + with open(ksource) as f: + kwargs['key_content'] = f.read() + + csource = kwargs['cert_src'] + if csource: + with open(csource) as f: + kwargs['cert_content'] = f.read() + + if kwargs['state'] == 'present': + if not any(kwargs[k] is not None for k in required_args): + raise F5ModuleError( + "Either 'key_content', 'key_src', 'cert_content' or " + "'cert_src' must be provided" + ) + + # This is the remote BIG-IP path from where it will look for certs + # to install. + self.dlpath = '/var/config/rest/downloads' + + # The params that change in the module + self.cparams = dict() + + # Stores the params that are sent to the module + self.params = kwargs + self.api = ManagementRoot(kwargs['server'], + kwargs['user'], + kwargs['password'], + port=kwargs['server_port']) + + def exists(self): + cert = self.cert_exists() + key = self.key_exists() + + if cert and key: + return True + else: + return False + + def get_hash(self, content): + k = hashlib.sha1() + s = StringIO.StringIO(content) + while True: + data = s.read(1024) + if not data: + break + k.update(data) + return k.hexdigest() + + def present(self): + current = self.read() + changed = False + do_key = False + do_cert = False + chash = None + khash = None + + check_mode = self.params['check_mode'] + name = self.params['name'] + partition = self.params['partition'] + cert_content = self.params['cert_content'] + key_content = self.params['key_content'] + passphrase = self.params['passphrase'] + + # Technically you dont need to provide us with anything in the form + # of content for your cert, but that's kind of illogical, so we just + # return saying you didn't "do" anything if you left the cert and keys + # empty. + if not cert_content and not key_content: + return False + + if key_content is not None: + if 'key_checksum' in current: + khash = self.get_hash(key_content) + if khash not in current['key_checksum']: + do_key = "update" + else: + do_key = "create" + + if cert_content is not None: + if 'cert_checksum' in current: + chash = self.get_hash(cert_content) + if chash not in current['cert_checksum']: + do_cert = "update" + else: + do_cert = "create" + + if do_cert or do_key: + changed = True + params = dict() + params['cert_name'] = name + params['key_name'] = name + params['partition'] = partition + if khash: + params['key_checksum'] = khash + if chash: + params['cert_checksum'] = chash + self.cparams = params + + if check_mode: + return changed + + if not do_cert and not do_key: + return False + + tx = self.api.tm.transactions.transaction + with TransactionContextManager(tx) as api: + if do_cert: + # Upload the content of a certificate as a StringIO object + cstring = StringIO.StringIO(cert_content) + filename = "%s.crt" % (name) + filepath = os.path.join(self.dlpath, filename) + api.shared.file_transfer.uploads.upload_stringio( + cstring, + filename + ) + + if do_cert == "update": + # Install the certificate + params = { + 'name': name, + 'partition': partition + } + cert = api.tm.sys.file.ssl_certs.ssl_cert.load(**params) + + # This works because, while the source path is the same, + # calling update causes the file to be re-read + cert.update() + changed = True + elif do_cert == "create": + # Install the certificate + params = { + 'sourcePath': "file://" + filepath, + 'name': name, + 'partition': partition + } + api.tm.sys.file.ssl_certs.ssl_cert.create(**params) + changed = True + + if do_key: + # Upload the content of a certificate key as a StringIO object + kstring = StringIO.StringIO(key_content) + filename = "%s.key" % (name) + filepath = os.path.join(self.dlpath, filename) + api.shared.file_transfer.uploads.upload_stringio( + kstring, + filename + ) + + if do_key == "update": + # Install the key + params = { + 'name': name, + 'partition': partition + } + key = api.tm.sys.file.ssl_keys.ssl_key.load(**params) + + params = dict() + + if passphrase: + params['passphrase'] = passphrase + else: + params['passphrase'] = None + + key.update(**params) + changed = True + elif do_key == "create": + # Install the key + params = { + 'sourcePath': "file://" + filepath, + 'name': name, + 'partition': partition + } + if passphrase: + params['passphrase'] = self.params['passphrase'] + else: + params['passphrase'] = None + + api.tm.sys.file.ssl_keys.ssl_key.create(**params) + changed = True + return changed + + def key_exists(self): + return self.api.tm.sys.file.ssl_keys.ssl_key.exists( + name=self.params['name'], + partition=self.params['partition'] + ) + + def cert_exists(self): + return self.api.tm.sys.file.ssl_certs.ssl_cert.exists( + name=self.params['name'], + partition=self.params['partition'] + ) + + def read(self): + p = dict() + name = self.params['name'] + partition = self.params['partition'] + + if self.key_exists(): + key = self.api.tm.sys.file.ssl_keys.ssl_key.load( + name=name, + partition=partition + ) + if hasattr(key, 'checksum'): + p['key_checksum'] = str(key.checksum) + + if self.cert_exists(): + cert = self.api.tm.sys.file.ssl_certs.ssl_cert.load( + name=name, + partition=partition + ) + if hasattr(cert, 'checksum'): + p['cert_checksum'] = str(cert.checksum) + + p['name'] = name + return p + + def flush(self): + result = dict() + state = self.params['state'] + + try: + if state == "present": + changed = self.present() + elif state == "absent": + changed = self.absent() + except iControlUnexpectedHTTPError as e: + raise F5ModuleError(str(e)) + + result.update(**self.cparams) + result.update(dict(changed=changed)) + return result + + def absent(self): + changed = False + + if self.exists(): + changed = self.delete() + + return changed + + def delete(self): + changed = False + + check_mode = self.params['check_mode'] + + delete_cert = self.cert_exists() + delete_key = self.key_exists() + + if not delete_cert and not delete_key: + return changed + + if check_mode: + params = dict() + params['cert_name'] = name + params['key_name'] = name + params['partition'] = partition + self.cparams = params + return True + + tx = self.api.tm.transactions.transaction + with TransactionContextManager(tx) as api: + if delete_cert: + # Delete the certificate + c = api.tm.sys.file.ssl_certs.ssl_cert.load( + name=self.params['name'], + partition=self.params['partition'] + ) + c.delete() + changed = True + + if delete_key: + # Delete the certificate key + k = self.api.tm.sys.file.ssl_keys.ssl_key.load( + name=self.params['name'], + partition=self.params['partition'] + ) + k.delete() + changed = True + return changed + + +def main(): + argument_spec = f5_argument_spec() + + meta_args = dict( + name=dict(type='str', required=True), + cert_content=dict(type='str', default=None), + cert_src=dict(type='path', default=None), + key_content=dict(type='str', default=None), + key_src=dict(type='path', default=None), + passphrase=dict(type='str', default=None, no_log=True) + ) + + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + mutually_exclusive=[ + ['key_content', 'key_src'], + ['cert_content', 'cert_src'] + ] + ) + + try: + obj = BigIpSslCertificate(check_mode=module.check_mode, + **module.params) + result = obj.flush() + module.exit_json(**result) + except F5ModuleError as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.basic import * +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_sys_db.py b/lib/ansible/modules/extras/network/f5/bigip_sys_db.py new file mode 100644 index 0000000000..54f5dd74fc --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_sys_db.py @@ -0,0 +1,221 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: bigip_sys_db +short_description: Manage BIG-IP system database variables +description: + - Manage BIG-IP system database variables +version_added: "2.2" +options: + key: + description: + - The database variable to manipulate. + required: true + state: + description: + - The state of the variable on the system. When C(present), guarantees + that an existing variable is set to C(value). When C(reset) sets the + variable back to the default value. At least one of value and state + C(reset) are required. + required: false + default: present + choices: + - present + - reset + value: + description: + - The value to set the key to. At least one of value and state C(reset) + are required. + required: false +notes: + - Requires the f5-sdk Python package on the host. This is as easy as pip + install f5-sdk. + - Requires BIG-IP version 12.0.0 or greater +extends_documentation_fragment: f5 +requirements: + - f5-sdk +author: + - Tim Rupp (@caphrim007) +''' + +EXAMPLES = ''' +- name: Set the boot.quiet DB variable on the BIG-IP + bigip_sys_db: + user: "admin" + password: "secret" + server: "lb.mydomain.com" + key: "boot.quiet" + value: "disable" + delegate_to: localhost + +- name: Disable the initial setup screen + bigip_sys_db: + user: "admin" + password: "secret" + server: "lb.mydomain.com" + key: "setup.run" + value: "false" + delegate_to: localhost + +- name: Reset the initial setup screen + bigip_sys_db: + user: "admin" + password: "secret" + server: "lb.mydomain.com" + key: "setup.run" + state: "reset" + delegate_to: localhost +''' + +RETURN = ''' +name: + description: The key in the system database that was specified + returned: changed and success + type: string + sample: "setup.run" +default_value: + description: The default value of the key + returned: changed and success + type: string + sample: "true" +value: + description: The value that you set the key to + returned: changed and success + type: string + sample: "false" +''' + +try: + from f5.bigip import ManagementRoot + HAS_F5SDK = True +except ImportError: + HAS_F5SDK = False + + +class BigIpSysDb(object): + def __init__(self, *args, **kwargs): + if not HAS_F5SDK: + raise F5ModuleError("The python f5-sdk module is required") + + self.params = kwargs + self.api = ManagementRoot(kwargs['server'], + kwargs['user'], + kwargs['password'], + port=kwargs['server_port']) + + def flush(self): + result = dict() + state = self.params['state'] + value = self.params['value'] + + if not state == 'reset' and not value: + raise F5ModuleError( + "When setting a key, a value must be supplied" + ) + + current = self.read() + + if self.params['check_mode']: + if value == current: + changed = False + else: + changed = True + else: + if state == "present": + changed = self.present() + elif state == "reset": + changed = self.reset() + current = self.read() + result.update( + name=current.name, + default_value=current.defaultValue, + value=current.value + ) + + result.update(dict(changed=changed)) + return result + + def read(self): + dbs = self.api.tm.sys.dbs.db.load( + name=self.params['key'] + ) + return dbs + + def present(self): + current = self.read() + + if current.value == self.params['value']: + return False + + current.update(value=self.params['value']) + current.refresh() + + if current.value != self.params['value']: + raise F5ModuleError( + "Failed to set the DB variable" + ) + return True + + def reset(self): + current = self.read() + + default = current.defaultValue + if current.value == default: + return False + + current.update(value=default) + current.refresh() + + if current.value != current.defaultValue: + raise F5ModuleError( + "Failed to reset the DB variable" + ) + + return True + + +def main(): + argument_spec = f5_argument_spec() + + meta_args = dict( + key=dict(required=True), + state=dict(default='present', choices=['present', 'reset']), + value=dict(required=False, default=None) + ) + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + try: + obj = BigIpSysDb(check_mode=module.check_mode, **module.params) + result = obj.flush() + + module.exit_json(**result) + except F5ModuleError as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.basic import * +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_virtual_server.py b/lib/ansible/modules/extras/network/f5/bigip_virtual_server.py new file mode 100644 index 0000000000..89d25103f6 --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_virtual_server.py @@ -0,0 +1,614 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Etienne Carriere <etienne.carriere@gmail.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: bigip_virtual_server +short_description: "Manages F5 BIG-IP LTM virtual servers" +description: + - "Manages F5 BIG-IP LTM virtual servers via iControl SOAP API" +version_added: "2.1" +author: + - Etienne Carriere (@Etienne-Carriere) + - Tim Rupp (@caphrim007) +notes: + - "Requires BIG-IP software version >= 11" + - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)" + - "Best run as a local_action in your playbook" +requirements: + - bigsuds +options: + state: + description: + - Virtual Server state + - Absent, delete the VS if present + - C(present) (and its synonym enabled), create if needed the VS and set + state to enabled + - C(disabled), create if needed the VS and set state to disabled + required: false + default: present + choices: + - present + - absent + - enabled + - disabled + aliases: [] + partition: + description: + - Partition + required: false + default: 'Common' + name: + description: + - Virtual server name + required: true + aliases: + - vs + destination: + description: + - Destination IP of the virtual server (only host is currently supported). + Required when state=present and vs does not exist. + required: true + aliases: + - address + - ip + port: + description: + - Port of the virtual server . Required when state=present and vs does not exist + required: false + default: None + all_profiles: + description: + - List of all Profiles (HTTP,ClientSSL,ServerSSL,etc) that must be used + by the virtual server + required: false + default: None + all_rules: + version_added: "2.2" + description: + - List of rules to be applied in priority order + required: false + default: None + all_enabled_vlans: + version_added: "2.2" + description: + - List of vlans to be enabled + required: false + default: None + pool: + description: + - Default pool for the virtual server + required: false + default: None + snat: + description: + - Source network address policy + required: false + default: None + default_persistence_profile: + description: + - Default Profile which manages the session persistence + required: false + default: None + description: + description: + - Virtual server description + required: false + default: None +extends_documentation_fragment: f5 +''' + +EXAMPLES = ''' +- name: Add virtual server + bigip_virtual_server: + server: lb.mydomain.net + user: admin + password: secret + state: present + partition: MyPartition + name: myvirtualserver + destination: "{{ ansible_default_ipv4['address'] }}" + port: 443 + pool: "{{ mypool }}" + snat: Automap + description: Test Virtual Server + all_profiles: + - http + - clientssl + all_enabled_vlans: + - /Common/vlan2 + delegate_to: localhost + +- name: Modify Port of the Virtual Server + bigip_virtual_server: + server: lb.mydomain.net + user: admin + password: secret + state: present + partition: MyPartition + name: myvirtualserver + port: 8080 + delegate_to: localhost + +- name: Delete virtual server + bigip_virtual_server: + server: lb.mydomain.net + user: admin + password: secret + state: absent + partition: MyPartition + name: myvirtualserver + delegate_to: localhost +''' + +RETURN = ''' +--- +deleted: + description: Name of a virtual server that was deleted + returned: changed + type: string + sample: "my-virtual-server" +''' + + +# map of state values +STATES = { + 'enabled': 'STATE_ENABLED', + 'disabled': 'STATE_DISABLED' +} + +STATUSES = { + 'enabled': 'SESSION_STATUS_ENABLED', + 'disabled': 'SESSION_STATUS_DISABLED', + 'offline': 'SESSION_STATUS_FORCED_DISABLED' +} + + +def vs_exists(api, vs): + # hack to determine if pool exists + result = False + try: + api.LocalLB.VirtualServer.get_object_status(virtual_servers=[vs]) + result = True + except bigsuds.OperationFailed as e: + if "was not found" in str(e): + result = False + else: + # genuine exception + raise + return result + + +def vs_create(api, name, destination, port, pool): + _profiles = [[{'profile_context': 'PROFILE_CONTEXT_TYPE_ALL', 'profile_name': 'tcp'}]] + created = False + # a bit of a hack to handle concurrent runs of this module. + # even though we've checked the vs doesn't exist, + # it may exist by the time we run create_vs(). + # this catches the exception and does something smart + # about it! + try: + api.LocalLB.VirtualServer.create( + definitions=[{'name': [name], 'address': [destination], 'port': port, 'protocol': 'PROTOCOL_TCP'}], + wildmasks=['255.255.255.255'], + resources=[{'type': 'RESOURCE_TYPE_POOL', 'default_pool_name': pool}], + profiles=_profiles) + created = True + return created + except bigsuds.OperationFailed as e: + if "already exists" not in str(e): + raise Exception('Error on creating Virtual Server : %s' % e) + + +def vs_remove(api, name): + api.LocalLB.VirtualServer.delete_virtual_server( + virtual_servers=[name] + ) + + +def get_rules(api, name): + return api.LocalLB.VirtualServer.get_rule( + virtual_servers=[name] + )[0] + + +def set_rules(api, name, rules_list): + updated = False + if rules_list is None: + return False + rules_list = list(enumerate(rules_list)) + try: + current_rules = map(lambda x: (x['priority'], x['rule_name']), get_rules(api, name)) + to_add_rules = [] + for i, x in rules_list: + if (i, x) not in current_rules: + to_add_rules.append({'priority': i, 'rule_name': x}) + to_del_rules = [] + for i, x in current_rules: + if (i, x) not in rules_list: + to_del_rules.append({'priority': i, 'rule_name': x}) + if len(to_del_rules) > 0: + api.LocalLB.VirtualServer.remove_rule( + virtual_servers=[name], + rules=[to_del_rules] + ) + updated = True + if len(to_add_rules) > 0: + api.LocalLB.VirtualServer.add_rule( + virtual_servers=[name], + rules=[to_add_rules] + ) + updated = True + return updated + except bigsuds.OperationFailed as e: + raise Exception('Error on setting rules : %s' % e) + + +def get_profiles(api, name): + return api.LocalLB.VirtualServer.get_profile( + virtual_servers=[name] + )[0] + + +def set_profiles(api, name, profiles_list): + updated = False + try: + if profiles_list is None: + return False + current_profiles = map(lambda x: x['profile_name'], get_profiles(api, name)) + to_add_profiles = [] + for x in profiles_list: + if x not in current_profiles: + to_add_profiles.append({'profile_context': 'PROFILE_CONTEXT_TYPE_ALL', 'profile_name': x}) + to_del_profiles = [] + for x in current_profiles: + if (x not in profiles_list) and (x != "/Common/tcp"): + to_del_profiles.append({'profile_context': 'PROFILE_CONTEXT_TYPE_ALL', 'profile_name': x}) + if len(to_del_profiles) > 0: + api.LocalLB.VirtualServer.remove_profile( + virtual_servers=[name], + profiles=[to_del_profiles] + ) + updated = True + if len(to_add_profiles) > 0: + api.LocalLB.VirtualServer.add_profile( + virtual_servers=[name], + profiles=[to_add_profiles] + ) + updated = True + return updated + except bigsuds.OperationFailed as e: + raise Exception('Error on setting profiles : %s' % e) + +def set_enabled_vlans(api, name, vlans_enabled_list): + updated = False + try: + if vlans_enabled_list is None: + return False + + to_add_vlans = [] + for x in vlans_enabled_list: + to_add_vlans.append(x) + + api.LocalLB.VirtualServer.set_vlan( + virtual_servers=[name], + vlans = [{ 'state':'STATE_ENABLED', 'vlans':[to_add_vlans] }] + ) + updated = True + return updated + except bigsuds.OperationFailed as e: + raise Exception('Error on setting enabled vlans : %s' % e) + +def set_snat(api, name, snat): + updated = False + try: + current_state = get_snat_type(api, name) + if snat is None: + return updated + elif snat == 'None' and current_state != 'SRC_TRANS_NONE': + api.LocalLB.VirtualServer.set_source_address_translation_none( + virtual_servers=[name] + ) + updated = True + elif snat == 'Automap' and current_state != 'SRC_TRANS_AUTOMAP': + api.LocalLB.VirtualServer.set_source_address_translation_automap( + virtual_servers=[name] + ) + updated = True + return updated + except bigsuds.OperationFailed as e: + raise Exception('Error on setting snat : %s' % e) + + +def get_snat_type(api, name): + return api.LocalLB.VirtualServer.get_source_address_translation_type( + virtual_servers=[name] + )[0] + + +def get_pool(api, name): + return api.LocalLB.VirtualServer.get_default_pool_name( + virtual_servers=[name] + )[0] + + +def set_pool(api, name, pool): + updated = False + try: + current_pool = get_pool(api, name) + if pool is not None and (pool != current_pool): + api.LocalLB.VirtualServer.set_default_pool_name( + virtual_servers=[name], + default_pools=[pool] + ) + updated = True + return updated + except bigsuds.OperationFailed as e: + raise Exception('Error on setting pool : %s' % e) + + +def get_destination(api, name): + return api.LocalLB.VirtualServer.get_destination_v2( + virtual_servers=[name] + )[0] + + +def set_destination(api, name, destination): + updated = False + try: + current_destination = get_destination(api, name) + if destination is not None and destination != current_destination['address']: + api.LocalLB.VirtualServer.set_destination_v2( + virtual_servers=[name], + destinations=[{'address': destination, 'port': current_destination['port']}] + ) + updated = True + return updated + except bigsuds.OperationFailed as e: + raise Exception('Error on setting destination : %s' % e) + + +def set_port(api, name, port): + updated = False + try: + current_destination = get_destination(api, name) + if port is not None and port != current_destination['port']: + api.LocalLB.VirtualServer.set_destination_v2( + virtual_servers=[name], + destinations=[{'address': current_destination['address'], 'port': port}] + ) + updated = True + return updated + except bigsuds.OperationFailed as e: + raise Exception('Error on setting port : %s' % e) + + +def get_state(api, name): + return api.LocalLB.VirtualServer.get_enabled_state( + virtual_servers=[name] + )[0] + + +def set_state(api, name, state): + updated = False + try: + current_state = get_state(api, name) + # We consider that being present is equivalent to enabled + if state == 'present': + state = 'enabled' + if STATES[state] != current_state: + api.LocalLB.VirtualServer.set_enabled_state( + virtual_servers=[name], + states=[STATES[state]] + ) + updated = True + return updated + except bigsuds.OperationFailed as e: + raise Exception('Error on setting state : %s' % e) + + +def get_description(api, name): + return api.LocalLB.VirtualServer.get_description( + virtual_servers=[name] + )[0] + + +def set_description(api, name, description): + updated = False + try: + current_description = get_description(api, name) + if description is not None and current_description != description: + api.LocalLB.VirtualServer.set_description( + virtual_servers=[name], + descriptions=[description] + ) + updated = True + return updated + except bigsuds.OperationFailed as e: + raise Exception('Error on setting description : %s ' % e) + + +def get_persistence_profiles(api, name): + return api.LocalLB.VirtualServer.get_persistence_profile( + virtual_servers=[name] + )[0] + + +def set_default_persistence_profiles(api, name, persistence_profile): + updated = False + if persistence_profile is None: + return updated + try: + current_persistence_profiles = get_persistence_profiles(api, name) + default = None + for profile in current_persistence_profiles: + if profile['default_profile']: + default = profile['profile_name'] + break + if default is not None and default != persistence_profile: + api.LocalLB.VirtualServer.remove_persistence_profile( + virtual_servers=[name], + profiles=[[{'profile_name': default, 'default_profile': True}]] + ) + if default != persistence_profile: + api.LocalLB.VirtualServer.add_persistence_profile( + virtual_servers=[name], + profiles=[[{'profile_name': persistence_profile, 'default_profile': True}]] + ) + updated = True + return updated + except bigsuds.OperationFailed as e: + raise Exception('Error on setting default persistence profile : %s' % e) + + +def main(): + argument_spec = f5_argument_spec() + argument_spec.update(dict( + state=dict(type='str', default='present', + choices=['present', 'absent', 'disabled', 'enabled']), + name=dict(type='str', required=True, aliases=['vs']), + destination=dict(type='str', aliases=['address', 'ip']), + port=dict(type='int'), + all_profiles=dict(type='list'), + all_rules=dict(type='list'), + all_enabled_vlans=dict(type='list'), + pool=dict(type='str'), + description=dict(type='str'), + snat=dict(type='str'), + default_persistence_profile=dict(type='str') + )) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + if not bigsuds_found: + module.fail_json(msg="the python bigsuds module is required") + + if module.params['validate_certs']: + import ssl + if not hasattr(ssl, 'SSLContext'): + module.fail_json(msg='bigsuds does not support verifying certificates with python < 2.7.9. Either update python or set validate_certs=False on the task') + + server = module.params['server'] + server_port = module.params['server_port'] + user = module.params['user'] + password = module.params['password'] + state = module.params['state'] + partition = module.params['partition'] + validate_certs = module.params['validate_certs'] + + name = fq_name(partition, module.params['name']) + destination = module.params['destination'] + port = module.params['port'] + all_profiles = fq_list_names(partition, module.params['all_profiles']) + all_rules = fq_list_names(partition, module.params['all_rules']) + all_enabled_vlans = fq_list_names(partition, module.params['all_enabled_vlans']) + pool = fq_name(partition, module.params['pool']) + description = module.params['description'] + snat = module.params['snat'] + default_persistence_profile = fq_name(partition, module.params['default_persistence_profile']) + + if 1 > port > 65535: + module.fail_json(msg="valid ports must be in range 1 - 65535") + + try: + api = bigip_api(server, user, password, validate_certs, port=server_port) + result = {'changed': False} # default + + if state == 'absent': + if not module.check_mode: + if vs_exists(api, name): + # hack to handle concurrent runs of module + # pool might be gone before we actually remove + try: + vs_remove(api, name) + result = {'changed': True, 'deleted': name} + except bigsuds.OperationFailed as e: + if "was not found" in str(e): + result['changed'] = False + else: + raise + else: + # check-mode return value + result = {'changed': True} + + else: + update = False + if not vs_exists(api, name): + if (not destination) or (not port): + module.fail_json(msg="both destination and port must be supplied to create a VS") + if not module.check_mode: + # a bit of a hack to handle concurrent runs of this module. + # even though we've checked the virtual_server doesn't exist, + # it may exist by the time we run virtual_server(). + # this catches the exception and does something smart + # about it! + try: + vs_create(api, name, destination, port, pool) + set_profiles(api, name, all_profiles) + set_enabled_vlans(api, name, all_enabled_vlans) + set_rules(api, name, all_rules) + set_snat(api, name, snat) + set_description(api, name, description) + set_default_persistence_profiles(api, name, default_persistence_profile) + set_state(api, name, state) + result = {'changed': True} + except bigsuds.OperationFailed as e: + raise Exception('Error on creating Virtual Server : %s' % e) + else: + # check-mode return value + result = {'changed': True} + else: + update = True + if update: + # VS exists + if not module.check_mode: + # Have a transaction for all the changes + try: + api.System.Session.start_transaction() + result['changed'] |= set_destination(api, name, fq_name(partition, destination)) + result['changed'] |= set_port(api, name, port) + result['changed'] |= set_pool(api, name, pool) + result['changed'] |= set_description(api, name, description) + result['changed'] |= set_snat(api, name, snat) + result['changed'] |= set_profiles(api, name, all_profiles) + result['changed'] |= set_enabled_vlans(api, name, all_enabled_vlans) + result['changed'] |= set_rules(api, name, all_rules) + result['changed'] |= set_default_persistence_profiles(api, name, default_persistence_profile) + result['changed'] |= set_state(api, name, state) + api.System.Session.submit_transaction() + except Exception as e: + raise Exception("Error on updating Virtual Server : %s" % e) + else: + # check-mode return value + result = {'changed': True} + + except Exception as e: + module.fail_json(msg="received exception: %s" % e) + + module.exit_json(**result) +# import module snippets +from ansible.module_utils.basic import * +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/extras/network/f5/bigip_vlan.py b/lib/ansible/modules/extras/network/f5/bigip_vlan.py new file mode 100644 index 0000000000..4e13d2508c --- /dev/null +++ b/lib/ansible/modules/extras/network/f5/bigip_vlan.py @@ -0,0 +1,445 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: bigip_vlan +short_description: Manage VLANs on a BIG-IP system +description: + - Manage VLANs on a BIG-IP system +version_added: "2.2" +options: + description: + description: + - The description to give to the VLAN. + tagged_interfaces: + description: + - Specifies a list of tagged interfaces and trunks that you want to + configure for the VLAN. Use tagged interfaces or trunks when + you want to assign a single interface or trunk to multiple VLANs. + required: false + aliases: + - tagged_interface + untagged_interfaces: + description: + - Specifies a list of untagged interfaces and trunks that you want to + configure for the VLAN. + required: false + aliases: + - untagged_interface + name: + description: + - The VLAN to manage. If the special VLAN C(ALL) is specified with + the C(state) value of C(absent) then all VLANs will be removed. + required: true + state: + description: + - The state of the VLAN on the system. When C(present), guarantees + that the VLAN exists with the provided attributes. When C(absent), + removes the VLAN from the system. + required: false + default: present + choices: + - absent + - present + tag: + description: + - Tag number for the VLAN. The tag number can be any integer between 1 + and 4094. The system automatically assigns a tag number if you do not + specify a value. +notes: + - Requires the f5-sdk Python package on the host. This is as easy as pip + install f5-sdk. + - Requires BIG-IP versions >= 12.0.0 +extends_documentation_fragment: f5 +requirements: + - f5-sdk +author: + - Tim Rupp (@caphrim007) +''' + +EXAMPLES = ''' +- name: Create VLAN + bigip_vlan: + name: "net1" + password: "secret" + server: "lb.mydomain.com" + user: "admin" + validate_certs: "no" + delegate_to: localhost + +- name: Set VLAN tag + bigip_vlan: + name: "net1" + password: "secret" + server: "lb.mydomain.com" + tag: "2345" + user: "admin" + validate_certs: "no" + delegate_to: localhost + +- name: Add VLAN 2345 as tagged to interface 1.1 + bigip_vlan: + tagged_interface: 1.1 + name: "net1" + password: "secret" + server: "lb.mydomain.com" + tag: "2345" + user: "admin" + validate_certs: "no" + delegate_to: localhost + +- name: Add VLAN 1234 as tagged to interfaces 1.1 and 1.2 + bigip_vlan: + tagged_interfaces: + - 1.1 + - 1.2 + name: "net1" + password: "secret" + server: "lb.mydomain.com" + tag: "1234" + user: "admin" + validate_certs: "no" + delegate_to: localhost +''' + +RETURN = ''' +description: + description: The description set on the VLAN + returned: changed + type: string + sample: foo VLAN +interfaces: + description: Interfaces that the VLAN is assigned to + returned: changed + type: list + sample: ['1.1','1.2'] +name: + description: The name of the VLAN + returned: changed + type: string + sample: net1 +partition: + description: The partition that the VLAN was created on + returned: changed + type: string + sample: Common +tag: + description: The ID of the VLAN + returned: changed + type: int + sample: 2345 +''' + +try: + from f5.bigip import ManagementRoot + from icontrol.session import iControlUnexpectedHTTPError + HAS_F5SDK = True +except ImportError: + HAS_F5SDK = False + + +class BigIpVlan(object): + def __init__(self, *args, **kwargs): + if not HAS_F5SDK: + raise F5ModuleError("The python f5-sdk module is required") + + # The params that change in the module + self.cparams = dict() + + # Stores the params that are sent to the module + self.params = kwargs + self.api = ManagementRoot(kwargs['server'], + kwargs['user'], + kwargs['password'], + port=kwargs['server_port']) + + def present(self): + if self.exists(): + return self.update() + else: + return self.create() + + def absent(self): + changed = False + + if self.exists(): + changed = self.delete() + + return changed + + def read(self): + """Read information and transform it + + The values that are returned by BIG-IP in the f5-sdk can have encoding + attached to them as well as be completely missing in some cases. + + Therefore, this method will transform the data from the BIG-IP into a + format that is more easily consumable by the rest of the class and the + parameters that are supported by the module. + """ + p = dict() + name = self.params['name'] + partition = self.params['partition'] + r = self.api.tm.net.vlans.vlan.load( + name=name, + partition=partition + ) + ifcs = r.interfaces_s.get_collection() + if hasattr(r, 'tag'): + p['tag'] = int(r.tag) + if hasattr(r, 'description'): + p['description'] = str(r.description) + if len(ifcs) is not 0: + untagged = [] + tagged = [] + for x in ifcs: + if hasattr(x, 'tagged'): + tagged.append(str(x.name)) + elif hasattr(x, 'untagged'): + untagged.append(str(x.name)) + if untagged: + p['untagged_interfaces'] = list(set(untagged)) + if tagged: + p['tagged_interfaces'] = list(set(tagged)) + p['name'] = name + return p + + def create(self): + params = dict() + + check_mode = self.params['check_mode'] + description = self.params['description'] + name = self.params['name'] + untagged_interfaces = self.params['untagged_interfaces'] + tagged_interfaces = self.params['tagged_interfaces'] + partition = self.params['partition'] + tag = self.params['tag'] + + if tag is not None: + params['tag'] = tag + + if untagged_interfaces is not None or tagged_interfaces is not None: + tmp = [] + ifcs = self.api.tm.net.interfaces.get_collection() + ifcs = [str(x.name) for x in ifcs] + + if len(ifcs) is 0: + raise F5ModuleError( + 'No interfaces were found' + ) + + pinterfaces = [] + if untagged_interfaces: + interfaces = untagged_interfaces + elif tagged_interfaces: + interfaces = tagged_interfaces + + for ifc in interfaces: + ifc = str(ifc) + if ifc in ifcs: + pinterfaces.append(ifc) + + if tagged_interfaces: + tmp = [dict(name=x, tagged=True) for x in pinterfaces] + elif untagged_interfaces: + tmp = [dict(name=x, untagged=True) for x in pinterfaces] + + if tmp: + params['interfaces'] = tmp + + if description is not None: + params['description'] = self.params['description'] + + params['name'] = name + params['partition'] = partition + + self.cparams = camel_dict_to_snake_dict(params) + if check_mode: + return True + + d = self.api.tm.net.vlans.vlan + d.create(**params) + + if self.exists(): + return True + else: + raise F5ModuleError("Failed to create the VLAN") + + def update(self): + changed = False + params = dict() + current = self.read() + + check_mode = self.params['check_mode'] + description = self.params['description'] + name = self.params['name'] + tag = self.params['tag'] + partition = self.params['partition'] + tagged_interfaces = self.params['tagged_interfaces'] + untagged_interfaces = self.params['untagged_interfaces'] + + if untagged_interfaces is not None or tagged_interfaces is not None: + ifcs = self.api.tm.net.interfaces.get_collection() + ifcs = [str(x.name) for x in ifcs] + + if len(ifcs) is 0: + raise F5ModuleError( + 'No interfaces were found' + ) + + pinterfaces = [] + if untagged_interfaces: + interfaces = untagged_interfaces + elif tagged_interfaces: + interfaces = tagged_interfaces + + for ifc in interfaces: + ifc = str(ifc) + if ifc in ifcs: + pinterfaces.append(ifc) + else: + raise F5ModuleError( + 'The specified interface "%s" was not found' % (ifc) + ) + + if tagged_interfaces: + tmp = [dict(name=x, tagged=True) for x in pinterfaces] + if 'tagged_interfaces' in current: + if pinterfaces != current['tagged_interfaces']: + params['interfaces'] = tmp + else: + params['interfaces'] = tmp + elif untagged_interfaces: + tmp = [dict(name=x, untagged=True) for x in pinterfaces] + if 'untagged_interfaces' in current: + if pinterfaces != current['untagged_interfaces']: + params['interfaces'] = tmp + else: + params['interfaces'] = tmp + + if description is not None: + if 'description' in current: + if description != current['description']: + params['description'] = description + else: + params['description'] = description + + if tag is not None: + if 'tag' in current: + if tag != current['tag']: + params['tag'] = tag + else: + params['tag'] = tag + + if params: + changed = True + params['name'] = name + params['partition'] = partition + if check_mode: + return changed + self.cparams = camel_dict_to_snake_dict(params) + else: + return changed + + r = self.api.tm.net.vlans.vlan.load( + name=name, + partition=partition + ) + r.update(**params) + r.refresh() + + return True + + def delete(self): + params = dict() + check_mode = self.params['check_mode'] + + params['name'] = self.params['name'] + params['partition'] = self.params['partition'] + + self.cparams = camel_dict_to_snake_dict(params) + if check_mode: + return True + + dc = self.api.tm.net.vlans.vlan.load(**params) + dc.delete() + + if self.exists(): + raise F5ModuleError("Failed to delete the VLAN") + return True + + def exists(self): + name = self.params['name'] + partition = self.params['partition'] + return self.api.tm.net.vlans.vlan.exists( + name=name, + partition=partition + ) + + def flush(self): + result = dict() + state = self.params['state'] + + try: + if state == "present": + changed = self.present() + elif state == "absent": + changed = self.absent() + except iControlUnexpectedHTTPError as e: + raise F5ModuleError(str(e)) + + result.update(**self.cparams) + result.update(dict(changed=changed)) + return result + + +def main(): + argument_spec = f5_argument_spec() + + meta_args = dict( + description=dict(required=False, default=None), + tagged_interfaces=dict(required=False, default=None, type='list', aliases=['tagged_interface']), + untagged_interfaces=dict(required=False, default=None, type='list', aliases=['untagged_interface']), + name=dict(required=True), + tag=dict(required=False, default=None, type='int') + ) + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + mutually_exclusive=[ + ['tagged_interfaces', 'untagged_interfaces'] + ] + ) + + try: + obj = BigIpVlan(check_mode=module.check_mode, **module.params) + result = obj.flush() + + module.exit_json(**result) + except F5ModuleError as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.basic import * +from ansible.module_utils.ec2 import camel_dict_to_snake_dict +from ansible.module_utils.f5 import * + +if __name__ == '__main__': + main() |