diff --git a/network/f5/bigip_node.py b/network/f5/bigip_node.py index 7a648341c0c..5f3a33f7838 100644 --- a/network/f5/bigip_node.py +++ b/network/f5/bigip_node.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - +# # (c) 2013, Matt Hite # # This file is part of Ansible @@ -23,148 +23,139 @@ DOCUMENTATION = ''' module: bigip_node short_description: "Manages F5 BIG-IP LTM nodes" description: - - "Manages F5 BIG-IP LTM nodes via iControl SOAP API" + - "Manages F5 BIG-IP LTM nodes via iControl SOAP API" version_added: "1.4" author: - - Matt Hite (@mhite) - - Tim Rupp (@caphrim007) + - 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" + - "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 + - 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 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'] + server: description: - description: - - "Node description." - required: false - default: null - choices: [] + - 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 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: [] ''' EXAMPLES = ''' - -## playbook task examples: - ---- -# file bigip-test.yml -# ... -- hosts: bigip-test - tasks: - - name: Add node - local_action: > - bigip_node - server=lb.mydomain.com - user=admin - password=mysecret - state=present - partition=matthite - host="{{ ansible_default_ipv4["address"] }}" - name="{{ ansible_default_ipv4["address"] }}" +- 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. @@ -173,38 +164,38 @@ EXAMPLES = ''' # 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: mysecret - state: present - partition: Common - host: "{{ ansible_default_ipv4["address"] }}" - name: mytestserver +- 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 - local_action: > - bigip_node - server=lb.mydomain.com - user=admin - password=mysecret - state=present - partition=matthite - name="{{ ansible_default_ipv4["address"] }}" - description="Our best server yet" +- 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 - local_action: > - bigip_node - server=lb.mydomain.com - user=admin - password=mysecret - state=absent - partition=matthite - name="{{ ansible_default_ipv4["address"] }}" +- 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 @@ -219,27 +210,26 @@ EXAMPLES = ''' # # See https://devcentral.f5.com/questions/icontrol-equivalent-call-for-b-node-down - - name: Force node offline - local_action: > - bigip_node - server=lb.mydomain.com - user=admin - password=mysecret - state=present - session_state=disabled - monitor_state=disabled - partition=matthite - name="{{ ansible_default_ipv4["address"] }}" - +- 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, e: + except bigsuds.OperationFailed as e: if "was not found" in str(e): result = False else: @@ -247,12 +237,17 @@ def node_exists(api, address): raise return result + def create_node_address(api, address, name): try: - api.LocalLB.NodeAddressV2.create(nodes=[name], addresses=[address], limits=[0]) + api.LocalLB.NodeAddressV2.create( + nodes=[name], + addresses=[address], + limits=[0] + ) result = True desc = "" - except bigsuds.OperationFailed, e: + except bigsuds.OperationFailed as e: if "already exists" in str(e): result = False desc = "referenced name or IP already in use" @@ -261,15 +256,17 @@ def create_node_address(api, address, name): 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, e: + except bigsuds.OperationFailed as e: if "is referenced by a member of pool" in str(e): result = False desc = "node referenced by pool" @@ -278,33 +275,40 @@ def delete_node_address(api, address): 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() @@ -312,37 +316,36 @@ def get_monitors(api, name): 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() - argument_spec.update(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') - ) + 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, + 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'): @@ -373,7 +376,6 @@ def main(): 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") @@ -415,7 +417,7 @@ def main(): elif state == 'present': if not node_exists(api, address): if host is None: - module.fail_json(msg="host parameter required when " \ + 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) @@ -442,8 +444,8 @@ def main(): # 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; " \ + 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) @@ -454,7 +456,7 @@ def main(): session_state) result = {'changed': True} elif session_state == 'disabled' and \ - session_status != 'force_disabled': + session_status != 'force_disabled': if not module.check_mode: set_node_session_enabled_state(api, address, session_state) @@ -468,7 +470,7 @@ def main(): monitor_state) result = {'changed': True} elif monitor_state == 'disabled' and \ - monitor_status != 'forced_down': + monitor_status != 'forced_down': if not module.check_mode: set_node_monitor_state(api, address, monitor_state) @@ -484,13 +486,11 @@ def main(): if not module.check_mode: set_monitors(api, address, monitor_type, quorum, monitors) result = {'changed': True} - - except Exception, e: + 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 *