#!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2013, Matt Hite # # 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 . 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: server: description: - BIG-IP host required: true default: null choices: [] aliases: [] server_port: description: - BIG-IP server port required: false default: 443 version_added: "2.2" user: description: - BIG-IP username required: true default: null choices: [] aliases: [] password: description: - BIG-IP password required: true default: null choices: [] aliases: [] validate_certs: description: - If C(no), SSL certificates will not be validated. This should only be used on personally controlled sites. Prior to 2.0, this module would always validate on python >= 2.7.9 and never validate on python <= 2.7.8 required: false default: 'yes' choices: - yes - no version_added: 2.0 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: [] ''' 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()