From 58680f38c3642c9c495bdf2809c370c5f647e1f3 Mon Sep 17 00:00:00 2001 From: Matt Hite Date: Mon, 2 Sep 2013 10:53:18 +0200 Subject: [PATCH 1/4] Initial bigip_node version --- library/net_infrastructure/bigip_node | 210 ++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 library/net_infrastructure/bigip_node diff --git a/library/net_infrastructure/bigip_node b/library/net_infrastructure/bigip_node new file mode 100644 index 0000000000..f97b386fff --- /dev/null +++ b/library/net_infrastructure/bigip_node @@ -0,0 +1,210 @@ +#!/usr/bin/env 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_node +short_description: "Manages F5 BIG-IP LTM pool members" +description: + - "Manages F5 BIG-IP LTM pool members via iControl SOAP API" +version_added: "TBD" +author: Matt Hite +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: [] + user: + description: + - BIG-IP username + required: true + default: null + choices: [] + aliases: [] + password: + description: + - BIG-IP password + required: true + default: null + choices: [] + aliases: [] + state: + description: + - Pool member state + required: true + default: present + choices: ['present', 'absent'] + aliases: [] + partition: + description: + - Partition + required: false + default: 'Common' + choices: [] + aliases: [] + name: + description + - "Node name" + required: false + default: null + choices: [] + host: + description: + - "Node IP. Required when state=present. Ignored when state=present." + required: true + default: null + choices: [] + aliases: ['address'] +''' + +EXAMPLES = ''' + +TBD +TBD +TBD +TBD + +''' + +try: + import bigsuds +except ImportError: + bigsuds_found = False +else: + bigsuds_found = True +import traceback + +# =========================================== +# bigip_pool_member module specific support methods. +# + +def bigip_api(bigip, user, password): + api = bigsuds.BIGIP(hostname=bigip, username=user, password=password) + return api + +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: + if "was not found" in str(e): + result = False + else: + # genuine exception + raise + return result + +def create_node_address(api, address, name): + api.LocalLB.NodeAddressV2.create(nodes=[name], addresses=[address], limits=[0]) + +def delete_node_address(api, address): + result = False + try: + api.LocalLB.NodeAddressV2.delete_node_address(nodes=[address]) + result = True + except bigsuds.OperationFailed, e: + if "is referenced by a member of pool" in str(e): + result = False + else: + # genuine exception + raise + return result + +def main(): + + module = AnsibleModule( + argument_spec = dict( + server = dict(type='str', required=True), + user = dict(type='str', required=True), + password = dict(type='str', required=True), + state = dict(type='str', default='present', choices=['present', 'absent']), + partition = dict(type='str', default='Common'), + name = dict(type='str', required=True), + host = dict(type='str', aliases=['address', 'ip']), + ), + supports_check_mode=True + ) + + if not bigsuds_found: + module.fail_json(msg="the python bigsuds module is required") + + server = module.params['server'] + user = module.params['user'] + password = module.params['password'] + state = module.params['state'] + partition = module.params['partition'] + host = module.params['host'] + name = module.params['name'] + address = "/%s/%s" % (partition, name) + + # sanity check user supplied values + + if state == 'present' and host is None: + module.fail_json(msg='host required when state=present') + + try: + api = bigip_api(server, user, password) + result = {'changed': False} # default + + if state == 'absent': + if node_exists(api, address): + if not module.check_mode: + deleted = delete_node_address(api, address) + if not deleted: + module.fail_json(msg="unable to delete: node referenced by pool") + else: + result = {'changed': True} + else: + result = {'changed': True} + + elif state == 'present': + if not node_exists(api, address): + if not module.check_mode: + if not node_exists(api, address): + create_node_address(api, address=host, name=address) + result = {'changed': True} + else: + # node exists -- potentially modify attributes + if host is not None: + + + pass + + except Exception, e: + traceback.print_exc(file=sys.stdout) + module.fail_json(msg="received exception: %s" % e) + + module.exit_json(**result) + +# include magic from lib/ansible/module_common.py +#<> +main() + From faae84bf0ecd6a0fcc27aa632a69844169094352 Mon Sep 17 00:00:00 2001 From: Serge van Ginderachter Date: Mon, 2 Sep 2013 16:10:09 +0200 Subject: [PATCH 2/4] bigip_node: additional code - checks if address already assigned to other node name - add description for node - check for node addres changes - add missing code "node exists, potentially modify attributes" --- library/net_infrastructure/bigip_node | 48 ++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/library/net_infrastructure/bigip_node b/library/net_infrastructure/bigip_node index f97b386fff..a7120e2273 100644 --- a/library/net_infrastructure/bigip_node +++ b/library/net_infrastructure/bigip_node @@ -76,11 +76,17 @@ options: choices: [] host: description: - - "Node IP. Required when state=present. Ignored when state=present." + - "Node IP. Required when state=present. Ignored when state=absent." required: true default: null choices: [] aliases: ['address'] + description: + description: + - "Optional node description." + required: false + default: null + choices: [] ''' EXAMPLES = ''' @@ -123,7 +129,19 @@ def node_exists(api, address): return result def create_node_address(api, address, name): - api.LocalLB.NodeAddressV2.create(nodes=[name], addresses=[address], limits=[0]) + try: + api.LocalLB.NodeAddressV2.create(nodes=[name], addresses=[address], limits=[0]) + return True, "" + except bigsuds.OperationFailed, e: + if "invalid node address" in str(e) and "already exists" in str(e): + return (False, "The referenced IP address is already in use.") + else: + # genuine exception + raise + + +def get_node_address(api, name): + return api.LocalLB.NodeAddressV2.get_address(nodes=[name])[0] def delete_node_address(api, address): result = False @@ -138,6 +156,14 @@ def delete_node_address(api, address): raise return result +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 main(): module = AnsibleModule( @@ -149,6 +175,7 @@ def main(): partition = dict(type='str', default='Common'), name = dict(type='str', required=True), host = dict(type='str', aliases=['address', 'ip']), + description = dict(type='str', default='') ), supports_check_mode=True ) @@ -164,6 +191,7 @@ def main(): host = module.params['host'] name = module.params['name'] address = "/%s/%s" % (partition, name) + description = module.params['description'] # sanity check user supplied values @@ -189,14 +217,24 @@ def main(): if not node_exists(api, address): if not module.check_mode: if not node_exists(api, address): - create_node_address(api, address=host, name=address) + res, desc = create_node_address(api, address=host, name=address) + if not res: + module.fail_json(msg=desc) result = {'changed': True} else: # node exists -- potentially modify attributes if host is not None: + current_address = get_node_address(api, address) + if current_address != host: + module.fail_json(msg="""Changing the node address is not + supported by the API, delete and + recreate the node.""") - - pass + current_descrip = get_node_description(api, address) + if current_descrip != description: + if not module.check_mode: + set_node_description(api, address, description) + result = {'changed': True} except Exception, e: traceback.print_exc(file=sys.stdout) From f1a94adbbebe0fd4bf3363e40e5699d031f69714 Mon Sep 17 00:00:00 2001 From: Matt Hite Date: Fri, 6 Sep 2013 17:15:01 -0700 Subject: [PATCH 3/4] Documentation additions and major refactor --- library/net_infrastructure/bigip_node | 132 ++++++++++++++++++-------- 1 file changed, 90 insertions(+), 42 deletions(-) diff --git a/library/net_infrastructure/bigip_node b/library/net_infrastructure/bigip_node index a7120e2273..3f04911524 100644 --- a/library/net_infrastructure/bigip_node +++ b/library/net_infrastructure/bigip_node @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2013, Matt Hite @@ -21,10 +21,10 @@ DOCUMENTATION = ''' --- module: bigip_node -short_description: "Manages F5 BIG-IP LTM pool members" +short_description: "Manages F5 BIG-IP LTM nodes" description: - - "Manages F5 BIG-IP LTM pool members via iControl SOAP API" -version_added: "TBD" + - "Manages F5 BIG-IP LTM nodes via iControl SOAP API" +version_added: "1.3" author: Matt Hite notes: - "Requires BIG-IP software version >= 11" @@ -69,21 +69,21 @@ options: choices: [] aliases: [] name: - description + description: - "Node name" required: false default: null choices: [] host: description: - - "Node IP. Required when state=present. Ignored when state=absent." + - "Node IP. Required when state=present and node does not exist. Error when state=absent." required: true default: null choices: [] - aliases: ['address'] + aliases: ['address', 'ip'] description: description: - - "Optional node description." + - "Node description." required: false default: null choices: [] @@ -91,10 +91,51 @@ options: EXAMPLES = ''' -TBD -TBD -TBD -TBD +## 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"] }}" + +# 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: 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: Delete node + local_action: > + bigip_node + server=lb.mydomain.com + user=admin + password=mysecret + state=absent + partition=matthite + name="{{ ansible_default_ipv4["address"] }}" ''' @@ -106,8 +147,8 @@ else: bigsuds_found = True import traceback -# =========================================== -# bigip_pool_member module specific support methods. +# ========================== +# bigip_node module specific # def bigip_api(bigip, user, password): @@ -131,41 +172,42 @@ def node_exists(api, address): def create_node_address(api, address, name): try: api.LocalLB.NodeAddressV2.create(nodes=[name], addresses=[address], limits=[0]) - return True, "" + result = True + desc = "" except bigsuds.OperationFailed, e: - if "invalid node address" in str(e) and "already exists" in str(e): - return (False, "The referenced IP address is already in use.") + 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): - result = False try: api.LocalLB.NodeAddressV2.delete_node_address(nodes=[address]) result = True + desc = "" except bigsuds.OperationFailed, 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 + 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 main(): - module = AnsibleModule( argument_spec = dict( server = dict(type='str', required=True), @@ -175,7 +217,7 @@ def main(): partition = dict(type='str', default='Common'), name = dict(type='str', required=True), host = dict(type='str', aliases=['address', 'ip']), - description = dict(type='str', default='') + description = dict(type='str') ), supports_check_mode=True ) @@ -193,10 +235,8 @@ def main(): address = "/%s/%s" % (partition, name) description = module.params['description'] - # sanity check user supplied values - - if state == 'present' and host is None: - module.fail_json(msg='host required when state=present') + if state == 'absent' and host is not None: + module.fail_json(msg="host parameter invalid when state=absent") try: api = bigip_api(server, user, password) @@ -205,33 +245,41 @@ def main(): if state == 'absent': if node_exists(api, address): if not module.check_mode: - deleted = delete_node_address(api, address) + deleted, desc = delete_node_address(api, address) if not deleted: - module.fail_json(msg="unable to delete: node referenced by pool") + 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: - if not node_exists(api, address): - res, desc = create_node_address(api, address=host, name=address) - if not res: - module.fail_json(msg=desc) - result = {'changed': True} + 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 description is not None: + set_node_description(api, address, description) + result = {'changed': True} + else: + # check-mode return value + result = {'changed': True} else: # node exists -- potentially modify attributes if host is not None: - current_address = get_node_address(api, address) - if current_address != host: - module.fail_json(msg="""Changing the node address is not - supported by the API, delete and - recreate the node.""") - - current_descrip = get_node_description(api, address) - if current_descrip != description: + 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 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} From 25f7391d1af189a16bdf789f308a7e87cdfb6b60 Mon Sep 17 00:00:00 2001 From: Matt Hite Date: Mon, 28 Oct 2013 14:51:51 -0700 Subject: [PATCH 4/4] Fixed version_added and removed reference to traceback module --- library/net_infrastructure/bigip_node | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/library/net_infrastructure/bigip_node b/library/net_infrastructure/bigip_node index 3f04911524..9c71b8d8e0 100644 --- a/library/net_infrastructure/bigip_node +++ b/library/net_infrastructure/bigip_node @@ -24,7 +24,7 @@ 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.3" +version_added: "1.4" author: Matt Hite notes: - "Requires BIG-IP software version >= 11" @@ -145,7 +145,6 @@ except ImportError: bigsuds_found = False else: bigsuds_found = True -import traceback # ========================== # bigip_node module specific @@ -285,7 +284,6 @@ def main(): result = {'changed': True} except Exception, e: - traceback.print_exc(file=sys.stdout) module.fail_json(msg="received exception: %s" % e) module.exit_json(**result)