bigip_node 8.87 KB
Newer Older
1
#!/usr/bin/python
Matt Hite committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
# -*- 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
24
short_description: "Manages F5 BIG-IP LTM nodes"
Matt Hite committed
25
description:
26
    - "Manages F5 BIG-IP LTM nodes via iControl SOAP API"
27
version_added: "1.4"
Matt Hite committed
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
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:
72
        description:
Matt Hite committed
73 74 75 76 77 78
            - "Node name"
        required: false
        default: null
        choices: []
    host:
        description:
79
            - "Node IP. Required when state=present and node does not exist. Error when state=absent."
Matt Hite committed
80 81 82
        required: true
        default: null
        choices: []
83
        aliases: ['address', 'ip']
84 85
    description:
        description:
86
            - "Node description."
87 88 89
        required: false
        default: null
        choices: []
Matt Hite committed
90 91 92 93
'''

EXAMPLES = '''

94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
## 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"] }}"
Matt Hite committed
139 140 141 142 143 144 145 146 147 148

'''

try:
    import bigsuds
except ImportError:
    bigsuds_found = False
else:
    bigsuds_found = True

149 150
# ==========================
# bigip_node module specific
Matt Hite committed
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
#

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):
172 173
    try:
        api.LocalLB.NodeAddressV2.create(nodes=[name], addresses=[address], limits=[0])
174 175
        result = True
        desc = ""
176
    except bigsuds.OperationFailed, e:
177 178 179
        if "already exists" in str(e):
            result = False
            desc = "referenced name or IP already in use"
180 181 182
        else:
            # genuine exception
            raise
183
    return (result, desc)
184 185 186

def get_node_address(api, name):
    return api.LocalLB.NodeAddressV2.get_address(nodes=[name])[0]
Matt Hite committed
187 188 189 190 191

def delete_node_address(api, address):
    try:
        api.LocalLB.NodeAddressV2.delete_node_address(nodes=[address])
        result = True
192
        desc = ""
Matt Hite committed
193 194 195
    except bigsuds.OperationFailed, e:
        if "is referenced by a member of pool" in str(e):
            result = False
196
            desc = "node referenced by pool"
Matt Hite committed
197 198 199
        else:
            # genuine exception
            raise
200
    return (result, desc)
Matt Hite committed
201

202 203 204 205 206 207 208
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]

Matt Hite committed
209 210 211 212 213 214 215 216 217 218
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']),
219
            description = dict(type='str')
Matt Hite committed
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
        ),
        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)
235
    description = module.params['description']
Matt Hite committed
236

237 238
    if state == 'absent' and host is not None:
        module.fail_json(msg="host parameter invalid when state=absent")
Matt Hite committed
239 240 241 242 243 244 245 246

    try:
        api = bigip_api(server, user, password)
        result = {'changed': False}  # default

        if state == 'absent':
            if node_exists(api, address):
                if not module.check_mode:
247
                    deleted, desc = delete_node_address(api, address)
Matt Hite committed
248
                    if not deleted:
249
                        module.fail_json(msg="unable to delete: %s" % desc)
Matt Hite committed
250 251 252
                    else:
                        result = {'changed': True}
                else:
253
                    # check-mode return value
Matt Hite committed
254 255 256 257
                    result = {'changed': True}

        elif state == 'present':
            if not node_exists(api, address):
258 259 260
                if host is None:
                    module.fail_json(msg="host parameter required when " \
                                         "state=present and node does not exist")
Matt Hite committed
261
                if not module.check_mode:
262 263 264 265 266 267 268 269 270 271 272
                    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}
Matt Hite committed
273 274 275
            else:
                # node exists -- potentially modify attributes
                if host is not None:
276 277 278 279 280 281
                    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:
282 283 284
                        if not module.check_mode:
                            set_node_description(api, address, description)
                        result = {'changed': True}
Matt Hite committed
285 286 287 288 289 290

    except Exception, e:
        module.fail_json(msg="received exception: %s" % e)

    module.exit_json(**result)

291 292
# import module snippets
from ansible.module_utils.basic import *
Matt Hite committed
293 294
main()