bigip_monitor_tcp 16 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 27 28 29 30 31 32
#!/usr/bin/python
# -*- coding: utf-8 -*-

# (c) 2013, serge van Ginderachter <serge@vanginderachter.be>
#
# 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_monitor_tcp
short_description: "Manages F5 BIG-IP LTM tcp monitors"
description:
    - "Manages F5 BIG-IP LTM tcp monitors via iControl SOAP API"
version_added: "1.4"
author: Serge van Ginderachter
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"
33
    - "Monitor API documentation: https://devcentral.f5.com/wiki/iControl.LocalLB__Monitor.ashx"
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 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
requirements:
    - bigsuds
options:
    server:
        description:
            - BIG-IP host
        required: true
        default: null
    user:
        description:
            - BIG-IP username
        required: true
        default: null
    password:
        description:
            - BIG-IP password
        required: true
        default: null
    state:
        description:
            - Monitor state
        required: false
        default: 'present'
        choices: ['present', 'absent']
    name:
        description:
            - Monitor name
        required: true
        default: null
        aliases: ['monitor']
    partition:
        description:
            - Partition for the monitor
        required: false
        default: 'Common'
    type:
        description:
            - The template type of this monitor template
        required: false
        default: 'tcp'
        choices: [ 'TTYPE_TCP', 'TTYPE_TCP_ECHO', 'TTYPE_TCP_HALF_OPEN']
    parent:
        description:
            - The parent template of this monitor template
        required: false
        default: 'tcp'
        choices: [ 'tcp', 'tcp_echo', 'tcp_half_open']
    parent_partition:
        description:
            - Partition for the parent monitor
        required: false
        default: 'Common'
    send:
        description:
            - The send string for the monitor call
        required: true
        default: none
    receive:
        description:
            - The receive string for the monitor call
        required: true
        default: none
    ip:
        description: 
            - IP address part of the ipport definition. The default API setting
              is "0.0.0.0".
        required: false
        default: none
    port:
        description: 
            - port address part op the ipport definition. Tyhe default API
              setting is 0.
        required: false
        default: none
    interval:
        description: 
            - The interval specifying how frequently the monitor instance
              of this template will run. By default, this interval is used for up and
              down states. The default API setting is 5.
        required: false
        default: none
    timeout:
        description:
            - The number of seconds in which the node or service must respond to
              the monitor request. If the target responds within the set time
              period, it is considered up. If the target does not respond within
              the set time period, it is considered down. You can change this
              number to any number you want, however, it should be 3 times the
              interval number of seconds plus 1 second. The default API setting
              is 16.
        required: false
        default: none
    time_until_up:
        description:
            - Specifies the amount of time in seconds after the first successful
              response before a node will be marked up. A value of 0 will cause a
              node to be marked up immediately after a valid response is received
              from the node. The default API setting is 0.
        required: false
        default: none
'''

EXAMPLES = '''

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
- name: BIGIP F5 | Create TCP Monitor
  local_action:
    module:             bigip_monitor_tcp
    state:              present
    server:             "{{ f5server }}"
    user:               "{{ f5user }}"
    password:           "{{ f5password }}"
    name:               "{{ item.monitorname }}"
    type:               tcp
    send:               "{{ item.send }}"
    receive:            "{{ item.receive }}"
  with_items: f5monitors-tcp
- name: BIGIP F5 | Create TCP half open Monitor
  local_action:
    module:             bigip_monitor_tcp
    state:              present
    server:             "{{ f5server }}"
    user:               "{{ f5user }}"
    password:           "{{ f5password }}"
    name:               "{{ item.monitorname }}"
    type:               tcp
    send:               "{{ item.send }}"
    receive:            "{{ item.receive }}"
  with_items: f5monitors-halftcp
- name: BIGIP F5 | Remove TCP Monitor
  local_action:
    module:             bigip_monitor_tcp
    state:              absent
    server:             "{{ f5server }}"
    user:               "{{ f5user }}"
    password:           "{{ f5password }}"
    name:               "{{ monitorname }}"
  with_flattened:
  - f5monitors-tcp
  - f5monitors-halftcp

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 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 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
'''

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

TEMPLATE_TYPE = DEFAULT_TEMPLATE_TYPE = 'TTYPE_TCP'
TEMPLATE_TYPE_CHOICES = ['tcp', 'tcp_echo', 'tcp_half_open']
DEFAULT_PARENT = DEFAULT_TEMPLATE_TYPE_CHOICE = DEFAULT_TEMPLATE_TYPE.replace('TTYPE_', '').lower()


# ===========================================
# bigip_monitor module generic methods.
# these should be re-useable for other monitor types
#

def bigip_api(bigip, user, password):

    api = bigsuds.BIGIP(hostname=bigip, username=user, password=password)
    return api


def check_monitor_exists(module, api, monitor, parent):

    # hack to determine if monitor exists
    result = False
    try:
        ttype = api.LocalLB.Monitor.get_template_type(template_names=[monitor])[0]
        parent2 = api.LocalLB.Monitor.get_parent_template(template_names=[monitor])[0]
        if ttype == TEMPLATE_TYPE and parent == parent2:
            result = True
        else:
            module.fail_json(msg='Monitor already exists, but has a different type (%s) or parent(%s)' % (ttype, parent))
    except bigsuds.OperationFailed, e:
        if "was not found" in str(e):
            result = False
        else:
            # genuine exception
            raise
    return result


def create_monitor(api, monitor, template_attributes):

    try: 
        api.LocalLB.Monitor.create_template(templates=[{'template_name': monitor, 'template_type': TEMPLATE_TYPE}], template_attributes=[template_attributes])
    except bigsuds.OperationFailed, e:
        if "already exists" in str(e):
            return False
        else:
            # genuine exception
            raise
    return True


def delete_monitor(api, monitor):

    try:
        api.LocalLB.Monitor.delete_template(template_names=[monitor])
    except bigsuds.OperationFailed, e:
        # maybe it was deleted since we checked
        if "was not found" in str(e):
            return False
        else:
            # genuine exception
            raise
    return True


def check_string_property(api, monitor, str_property):

    return str_property == api.LocalLB.Monitor.get_template_string_property([monitor], [str_property['type']])[0]


def set_string_property(api, monitor, str_property):

    api.LocalLB.Monitor.set_template_string_property(template_names=[monitor], values=[str_property])


def check_integer_property(api, monitor, int_property):

    return int_property == api.LocalLB.Monitor.get_template_integer_property([monitor], [int_property['type']])[0]


def set_integer_property(api, monitor, int_property):

    api.LocalLB.Monitor.set_template_int_property(template_names=[monitor], values=[int_property])


def update_monitor_properties(api, module, monitor, template_string_properties, template_integer_properties):

    changed = False
    for str_property in template_string_properties:
        if str_property['value'] is not None and not check_string_property(api, monitor, str_property):
            if not module.check_mode:
                set_string_property(api, monitor, str_property)
            changed = True
    for int_property in template_integer_properties:
        if int_property['value'] is not None and not check_integer_property(api, monitor, int_property):
            if not module.check_mode:
                set_integer_property(api, monitor, int_property)
            changed = True

    return changed


def get_ipport(api, monitor):

    return api.LocalLB.Monitor.get_template_destination(template_names=[monitor])[0]


def set_ipport(api, monitor, ipport):

    try:
        api.LocalLB.Monitor.set_template_destination(template_names=[monitor], destinations=[ipport])
        return True, ""

    except bigsuds.OperationFailed, e:
        if "Cannot modify the address type of monitor" in str(e):
            return False, "Cannot modify the address type of monitor if already assigned to a pool."
        else:
            # genuine exception
            raise

# ===========================================
# main loop
#
# writing a module for other monitor types should 
# only need an updated main() (and monitor specific functions)

def main():

    # begin monitor specific stuff

    module = AnsibleModule(
        argument_spec = dict(
            server    = dict(required=True),
            user      = dict(required=True),
            password  = dict(required=True),
            partition = dict(default='Common'),
            state     = dict(default='present', choices=['present', 'absent']),
            name      = dict(required=True),
            type      = dict(default=DEFAULT_TEMPLATE_TYPE_CHOICE, choices=TEMPLATE_TYPE_CHOICES),
            parent    = dict(default=DEFAULT_PARENT),
            parent_partition = dict(default='Common'),
            send      = dict(required=False),
            receive   = dict(required=False),
            ip        = dict(required=False),
            port      = dict(required=False, type='int'),
            interval  = dict(required=False, type='int'),
            timeout   = dict(required=False, type='int'),
            time_until_up = dict(required=False, type='int', default=0)
        ),
        supports_check_mode=True
    )

    server = module.params['server']
    user = module.params['user']
    password = module.params['password']
    partition = module.params['partition']
    parent_partition = module.params['parent_partition']
    state = module.params['state']
    name = module.params['name']
    type = 'TTYPE_' + module.params['type'].upper()
    parent = "/%s/%s" % (parent_partition, module.params['parent'])
    monitor = "/%s/%s" % (partition, name)
    send = module.params['send']
    receive = module.params['receive']
    ip = module.params['ip']
    port = module.params['port']
    interval = module.params['interval']
    timeout = module.params['timeout']
    time_until_up = module.params['time_until_up']

    # tcp monitor has multiple types, so overrule
    global TEMPLATE_TYPE
    TEMPLATE_TYPE = type

    # end monitor specific stuff

    if not bigsuds_found:
        module.fail_json(msg="the python bigsuds module is required")
    api = bigip_api(server, user, password)
    monitor_exists = check_monitor_exists(module, api, monitor, parent)


    # ipport is a special setting
    if monitor_exists: # make sure to not update current settings if not asked
        cur_ipport = get_ipport(api, monitor)
        if ip is None:
            ip = cur_ipport['ipport']['address']
        if port is None:
            port = cur_ipport['ipport']['port']
    else: # use API defaults if not defined to create it
371 372 373 374 375 376 377 378 379 380 381 382
        if interval is None:        
            interval = 5
        if timeout is None:         
            timeout = 16
        if ip is None:              
            ip = '0.0.0.0'
        if port is None:            
            port = 0
        if send is None:            
            send = ''
        if receive is None:         
            receive = ''
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467

    # define and set address type
    if ip == '0.0.0.0' and port == 0:
        address_type = 'ATYPE_STAR_ADDRESS_STAR_PORT'
    elif ip == '0.0.0.0' and port != 0:
        address_type = 'ATYPE_STAR_ADDRESS_EXPLICIT_PORT'
    elif ip != '0.0.0.0' and port != 0:
        address_type = 'ATYPE_EXPLICIT_ADDRESS_EXPLICIT_PORT'
    else:
        address_type = 'ATYPE_UNSET'

    ipport = {'address_type': address_type,
              'ipport': {'address': ip,
                         'port': port}}

    template_attributes = {'parent_template': parent,
                           'interval': interval,
                           'timeout': timeout,
                           'dest_ipport': ipport,
                           'is_read_only': False,
                           'is_directly_usable': True}

    # monitor specific stuff
    if type == 'TTYPE_TCP':
        template_string_properties = [{'type': 'STYPE_SEND',
                                       'value': send},
                                      {'type': 'STYPE_RECEIVE',
                                       'value': receive}]
    else:
        template_string_properties = []

    template_integer_properties = [{'type': 'ITYPE_INTERVAL',
                                     'value': interval},
                                   {'type': 'ITYPE_TIMEOUT',
                                    'value': timeout},
                                   {'type': 'ITYPE_TIME_UNTIL_UP',
                                    'value': interval}]

    # main logic, monitor generic

    try:
        result = {'changed': False}  # default


        if state == 'absent':
            if monitor_exists:
                if not module.check_mode:
                    # possible race condition if same task 
                    # on other node deleted it first
                    result['changed'] |= delete_monitor(api, monitor)
                else:
                    result['changed'] |= True

        else: # state present
            ## check for monitor itself
            if not monitor_exists: # create it
                if not module.check_mode: 
                    # again, check changed status here b/c race conditions
                    # if other task already created it
                    result['changed'] |= create_monitor(api, monitor, template_attributes)
                else: 
                    result['changed'] |= True

            ## check for monitor parameters
            # whether it already existed, or was just created, now update
            # the update functions need to check for check mode but
            # cannot update settings if it doesn't exist which happens in check mode
            if monitor_exists and not module.check_mode:
                result['changed'] |= update_monitor_properties(api, module, monitor,
                                                               template_string_properties,
                                                               template_integer_properties)
            # else assume nothing changed

            # we just have to update the ipport if monitor already exists and it's different
            if monitor_exists and cur_ipport != ipport:
                set_ipport(api, monitor, ipport)    
                result['changed'] |= True
            #else: monitor doesn't exist (check mode) or ipport is already ok


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

    module.exit_json(**result)

468 469
# import module snippets
from ansible.module_utils.basic import *
470 471
main()