bigip_pool_member 12.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
#!/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"
27
version_added: "1.4"
28 29 30 31 32
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"
Matt Hite committed
33 34
    - "Supersedes bigip_pool for managing pool members"

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
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: []
Matt Hite committed
66
    pool:
67 68 69 70 71
        description:
            - Pool name. This pool must exist.
        required: true
        default: null
        choices: []
Matt Hite committed
72
        aliases: []
73 74 75 76 77 78 79 80 81
    partition:
        description:
            - Partition
        required: false
        default: 'Common'
        choices: []
        aliases: []
    host:
        description:
Matt Hite committed
82
            - Pool member IP
83 84 85
        required: true
        default: null
        choices: []
Matt Hite committed
86
        aliases: ['address', 'name']
87 88
    port:
        description:
Matt Hite committed
89
            - Pool member port
90 91 92 93 94 95
        required: true
        default: null
        choices: []
        aliases: []
    connection_limit:
        description:
Matt Hite committed
96
            - Pool member connection limit. Setting this to 0 disables the limit.
97 98 99 100 101 102
        required: false
        default: null
        choices: []
        aliases: []
    description:
        description:
Matt Hite committed
103
            - Pool member description
104 105 106 107 108 109
        required: false
        default: null
        choices: []
        aliases: []
    rate_limit:
        description:
Matt Hite committed
110
            - Pool member rate limit (connections-per-second). Setting this to 0 disables the limit.
111 112 113 114 115 116
        required: false
        default: null
        choices: []
        aliases: []
    ratio:
        description:
Matt Hite committed
117
            - Pool member ratio weight. Valid values range from 1 through 100. New pool members -- unless overriden with this value -- default to 1.
118 119 120 121 122 123 124 125
        required: false
        default: null
        choices: []
        aliases: []
'''

EXAMPLES = '''

Matt Hite committed
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
## playbook task examples:

---
# file bigip-test.yml
# ...
- hosts: bigip-test
  tasks:
  - name: Add pool member
    local_action: >
      bigip_pool_member
      server=lb.mydomain.com
      user=admin
      password=mysecret
      state=present
      pool=matthite-pool
      partition=matthite
      host="{{ ansible_default_ipv4["address"] }}"
      port=80
      description="web server"
      connection_limit=100
      rate_limit=50
      ratio=2

  - name: Modify pool member ratio and description
    local_action: >
      bigip_pool_member
      server=lb.mydomain.com
      user=admin
      password=mysecret
      state=present
      pool=matthite-pool
      partition=matthite
      host="{{ ansible_default_ipv4["address"] }}"
      port=80
      ratio=1
      description="nginx server"

  - name: Remove pool member from pool
    local_action: >
      bigip_pool_member
      server=lb.mydomain.com
      user=admin
      password=mysecret
      state=absent
      pool=matthite-pool
      partition=matthite
      host="{{ ansible_default_ipv4["address"] }}"
      port=80
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285

'''

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

# ===========================================
# 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 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, 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, 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, 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 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']),
Matt Hite committed
286
            pool = dict(type='str', required=True),
287
            partition = dict(type='str', default='Common'),
Matt Hite committed
288
            host = dict(type='str', required=True, aliases=['address', 'name']),
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
            port = dict(type='int', required=True),
            connection_limit = dict(type='int'),
            description = dict(type='str'),
            rate_limit = dict(type='int'),
            ratio = dict(type='int')
        ),
        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']
Matt Hite committed
306
    pool = "/%s/%s" % (partition, module.params['pool'])
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
    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 = "/%s/%s" % (partition, host)
    port = module.params['port']

    # sanity check user supplied values

    if (host and not port) or (port and not host):
        module.fail_json(msg="both host and port must be supplied")

    if 1 > port > 65535:
        module.fail_json(msg="valid ports must be in range 1 - 65535")

    try:
        api = bigip_api(server, user, password)
        if not pool_exists(api, pool):
Matt Hite committed
326
            module.fail_json(msg="pool %s does not exist" % pool)
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
        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)
                    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)
                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}

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

    module.exit_json(**result)

375 376
# import module snippets
from ansible.module_utils.basic import *
377 378
main()