2013-07-17 22:37:04 +02:00
#!/usr/bin/python
2013-05-09 19:47:14 +02:00
# -*- 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
2013-07-31 23:12:04 +02:00
short_description : " Manages F5 BIG-IP LTM pools "
2013-05-09 19:47:14 +02:00
description :
2016-07-09 09:21:10 +02:00
- Manages F5 BIG - IP LTM pools via iControl SOAP API
version_added : 1.2
2016-06-23 11:33:49 +02:00
author :
2016-07-09 09:21:10 +02:00
- Matt Hite ( @mhite )
- Tim Rupp ( @caphrim007 )
2013-05-09 19:47:14 +02:00
notes :
2016-07-09 09:21:10 +02:00
- 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
2013-05-09 19:47:14 +02:00
requirements :
2016-07-09 09:21:10 +02:00
- bigsuds
2013-05-09 19:47:14 +02:00
options :
2016-07-09 09:21:10 +02:00
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 / pool member state
required : false
default : present
choices :
- present
- absent
aliases : [ ]
name :
description :
- Pool name
required : true
default : null
choices : [ ]
aliases :
- pool
partition :
description :
- Partition of pool / pool member
required : false
default : ' Common '
choices : [ ]
aliases : [ ]
lb_method :
description :
- Load balancing method
version_added : " 1.3 "
required : False
default : ' round_robin '
choices :
- round_robin
- ratio_member
- least_connection_member
- observed_member
- predictive_member
- ratio_node_address
- least_connection_node_address
- fastest_node_address
- observed_node_address
- predictive_node_address
- dynamic_ratio
- fastest_app_response
- least_sessions
- dynamic_ratio_member
- l3_addr
- weighted_least_connection_member
- weighted_least_connection_node_address
- ratio_session
- ratio_least_connection_member
- ratio_least_connection_node_address
aliases : [ ]
monitor_type :
description :
- Monitor rule type when monitors > 1
version_added : " 1.3 "
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 : " 1.3 "
required : False
default : null
choices : [ ]
aliases : [ ]
monitors :
description :
- Monitor template name list . Always use the full path to the monitor .
version_added : " 1.3 "
required : False
default : null
choices : [ ]
aliases : [ ]
slow_ramp_time :
description :
- Sets the ramp - up time ( in seconds ) to gradually ramp up the load on
newly added or freshly detected up pool members
version_added : " 1.3 "
required : False
default : null
choices : [ ]
aliases : [ ]
reselect_tries :
description :
- Sets the number of times the system tries to contact a pool member
after a passive failure
version_added : " 2.2 "
required : False
default : null
choices : [ ]
aliases : [ ]
service_down_action :
description :
- Sets the action to take when node goes down in pool
version_added : " 1.3 "
required : False
default : null
choices :
- none
- reset
- drop
- reselect
aliases : [ ]
host :
description :
- " Pool member IP "
required : False
default : null
choices : [ ]
aliases :
- address
port :
description :
- Pool member port
required : False
default : null
choices : [ ]
aliases : [ ]
2013-05-09 19:47:14 +02:00
'''
EXAMPLES = '''
2016-07-09 09:21:10 +02:00
- name : Create pool
bigip_pool :
server : " lb.mydomain.com "
user : " admin "
password : " secret "
state : " present "
name : " my-pool "
partition : " Common "
lb_method : " least_connection_member "
slow_ramp_time : 120
delegate_to : localhost
- name : Modify load balancer method
bigip_pool :
server : " lb.mydomain.com "
user : " admin "
password : " secret "
state : " present "
name : " my-pool "
partition : " Common "
lb_method : " round_robin "
- name : Add pool member
bigip_pool :
server : " lb.mydomain.com "
user : " admin "
password : " secret "
state : " present "
name : " my-pool "
partition : " Common "
host : " {{ ansible_default_ipv4[ " address " ] }} "
port : 80
- name : Remove pool member from pool
bigip_pool :
server : " lb.mydomain.com "
user : " admin "
password : " secret "
state : " absent "
name : " my-pool "
partition : " Common "
host : " {{ ansible_default_ipv4[ " address " ] }} "
port : 80
- name : Delete pool
bigip_pool :
server : " lb.mydomain.com "
user : " admin "
password : " secret "
state : " absent "
name : " my-pool "
partition : " Common "
'''
2013-05-09 19:47:14 +02:00
2016-07-09 09:21:10 +02:00
RETURN = '''
2013-05-09 19:47:14 +02:00
'''
2016-07-09 09:21:10 +02:00
2013-05-09 19:47:14 +02:00
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
2016-07-09 09:21:10 +02:00
except bigsuds . OperationFailed as e :
2013-05-09 19:47:14 +02:00
if " was not found " in str ( e ) :
result = False
else :
# genuine exception
raise
return result
2016-07-09 09:21:10 +02:00
2013-07-31 23:12:04 +02:00
def create_pool ( api , pool , lb_method ) :
2013-08-10 00:46:02 +02:00
# create requires lb_method but we don't want to default
# to a value on subsequent runs
if not lb_method :
lb_method = ' round_robin '
2013-07-31 23:12:04 +02:00
lb_method = " LB_METHOD_ %s " % lb_method . strip ( ) . upper ( )
api . LocalLB . Pool . create_v2 ( pool_names = [ pool ] , lb_methods = [ lb_method ] ,
members = [ [ ] ] )
2016-07-09 09:21:10 +02:00
2013-07-31 23:12:04 +02:00
def remove_pool ( api , pool ) :
api . LocalLB . Pool . delete_pool ( pool_names = [ pool ] )
2016-07-09 09:21:10 +02:00
2013-07-31 23:12:04 +02:00
def get_lb_method ( api , pool ) :
lb_method = api . LocalLB . Pool . get_lb_method ( pool_names = [ pool ] ) [ 0 ]
lb_method = lb_method . strip ( ) . replace ( ' LB_METHOD_ ' , ' ' ) . lower ( )
return lb_method
2016-07-09 09:21:10 +02:00
2013-07-31 23:12:04 +02:00
def set_lb_method ( api , pool , lb_method ) :
lb_method = " LB_METHOD_ %s " % lb_method . strip ( ) . upper ( )
api . LocalLB . Pool . set_lb_method ( pool_names = [ pool ] , lb_methods = [ lb_method ] )
2016-07-09 09:21:10 +02:00
2013-07-31 23:12:04 +02:00
def get_monitors ( api , pool ) :
result = api . LocalLB . Pool . get_monitor_association ( pool_names = [ pool ] ) [ 0 ] [ ' monitor_rule ' ]
monitor_type = result [ ' type ' ] . split ( " MONITOR_RULE_TYPE_ " ) [ - 1 ] . lower ( )
quorum = result [ ' quorum ' ]
monitor_templates = result [ ' monitor_templates ' ]
return ( monitor_type , quorum , monitor_templates )
2016-07-09 09:21:10 +02:00
2013-07-31 23:12:04 +02:00
def set_monitors ( api , pool , 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 }
monitor_association = { ' pool_name ' : pool , ' monitor_rule ' : monitor_rule }
api . LocalLB . Pool . set_monitor_association ( monitor_associations = [ monitor_association ] )
2016-07-09 09:21:10 +02:00
2013-07-31 23:12:04 +02:00
def get_slow_ramp_time ( api , pool ) :
result = api . LocalLB . Pool . get_slow_ramp_time ( pool_names = [ pool ] ) [ 0 ]
return result
2016-07-09 09:21:10 +02:00
2013-07-31 23:12:04 +02:00
def set_slow_ramp_time ( api , pool , seconds ) :
api . LocalLB . Pool . set_slow_ramp_time ( pool_names = [ pool ] , values = [ seconds ] )
2016-07-09 09:21:10 +02:00
2016-05-08 23:30:04 +02:00
def get_reselect_tries ( api , pool ) :
result = api . LocalLB . Pool . get_reselect_tries ( pool_names = [ pool ] ) [ 0 ]
return result
2016-07-09 09:21:10 +02:00
2016-05-08 23:30:04 +02:00
def set_reselect_tries ( api , pool , tries ) :
api . LocalLB . Pool . set_reselect_tries ( pool_names = [ pool ] , values = [ tries ] )
2016-07-09 09:21:10 +02:00
2013-07-31 23:12:04 +02:00
def get_action_on_service_down ( api , pool ) :
result = api . LocalLB . Pool . get_action_on_service_down ( pool_names = [ pool ] ) [ 0 ]
result = result . split ( " SERVICE_DOWN_ACTION_ " ) [ - 1 ] . lower ( )
return result
2016-07-09 09:21:10 +02:00
2013-07-31 23:12:04 +02:00
def set_action_on_service_down ( api , pool , action ) :
action = " SERVICE_DOWN_ACTION_ %s " % action . strip ( ) . upper ( )
api . LocalLB . Pool . set_action_on_service_down ( pool_names = [ pool ] , actions = [ action ] )
2016-07-09 09:21:10 +02:00
2013-05-09 19:47:14 +02:00
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
2016-07-09 09:21:10 +02:00
except bigsuds . OperationFailed as e :
2013-05-09 19:47:14 +02:00
if " was not found " in str ( e ) :
result = False
else :
# genuine exception
raise
return result
2016-07-09 09:21:10 +02:00
2013-05-09 19:47:14 +02:00
def delete_node_address ( api , address ) :
result = False
try :
api . LocalLB . NodeAddressV2 . delete_node_address ( nodes = [ address ] )
result = True
2016-07-09 09:21:10 +02:00
except bigsuds . OperationFailed as e :
2013-05-09 19:47:14 +02:00
if " is referenced by a member of pool " in str ( e ) :
result = False
else :
# genuine exception
raise
return result
2016-07-09 09:21:10 +02:00
2013-05-09 19:47:14 +02:00
def remove_pool_member ( api , pool , address , port ) :
members = [ { ' address ' : address , ' port ' : port } ]
api . LocalLB . Pool . remove_member_v2 ( pool_names = [ pool ] , members = [ members ] )
2016-07-09 09:21:10 +02:00
2013-05-09 19:47:14 +02:00
def add_pool_member ( api , pool , address , port ) :
members = [ { ' address ' : address , ' port ' : port } ]
api . LocalLB . Pool . add_member_v2 ( pool_names = [ pool ] , members = [ members ] )
2016-07-09 09:21:10 +02:00
2013-05-09 19:47:14 +02:00
def main ( ) :
2013-07-31 23:12:04 +02:00
lb_method_choices = [ ' round_robin ' , ' ratio_member ' ,
' least_connection_member ' , ' observed_member ' ,
' predictive_member ' , ' ratio_node_address ' ,
' least_connection_node_address ' ,
' fastest_node_address ' , ' observed_node_address ' ,
' predictive_node_address ' , ' dynamic_ratio ' ,
' fastest_app_response ' , ' least_sessions ' ,
2016-06-23 11:33:49 +02:00
' dynamic_ratio_member ' , ' l3_addr ' ,
2013-07-31 23:12:04 +02:00
' weighted_least_connection_member ' ,
' weighted_least_connection_node_address ' ,
' ratio_session ' , ' ratio_least_connection_member ' ,
' ratio_least_connection_node_address ' ]
monitor_type_choices = [ ' and_list ' , ' m_of_n ' ]
service_down_choices = [ ' none ' , ' reset ' , ' drop ' , ' reselect ' ]
2016-07-09 09:21:10 +02:00
argument_spec = f5_argument_spec ( )
meta_args = dict (
name = dict ( type = ' str ' , required = True , aliases = [ ' pool ' ] ) ,
lb_method = dict ( type = ' str ' , choices = lb_method_choices ) ,
monitor_type = dict ( type = ' str ' , choices = monitor_type_choices ) ,
quorum = dict ( type = ' int ' ) ,
monitors = dict ( type = ' list ' ) ,
slow_ramp_time = dict ( type = ' int ' ) ,
reselect_tries = dict ( type = ' int ' ) ,
service_down_action = dict ( type = ' str ' , choices = service_down_choices ) ,
host = dict ( type = ' str ' , aliases = [ ' address ' ] ) ,
port = dict ( type = ' int ' )
2015-06-03 08:22:18 +02:00
)
2016-07-09 09:21:10 +02:00
argument_spec . update ( meta_args )
2015-06-03 08:22:18 +02:00
module = AnsibleModule (
2016-07-09 09:21:10 +02:00
argument_spec = argument_spec ,
2013-05-09 19:47:14 +02:00
supports_check_mode = True
)
2016-05-04 07:50:45 +02:00
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 ' ) :
module . fail_json ( msg = ' bigsuds does not support verifying certificates with python < 2.7.9. Either update python or set validate_certs=False on the task ' )
server = module . params [ ' server ' ]
2016-06-23 11:33:49 +02:00
server_port = module . params [ ' server_port ' ]
2016-05-04 07:50:45 +02:00
user = module . params [ ' user ' ]
password = module . params [ ' password ' ]
state = module . params [ ' state ' ]
partition = module . params [ ' partition ' ]
validate_certs = module . params [ ' validate_certs ' ]
2013-05-09 19:47:14 +02:00
name = module . params [ ' name ' ]
2016-07-09 09:21:10 +02:00
pool = fq_name ( partition , name )
2013-07-31 23:12:04 +02:00
lb_method = module . params [ ' lb_method ' ]
if lb_method :
lb_method = lb_method . lower ( )
monitor_type = module . params [ ' monitor_type ' ]
if monitor_type :
monitor_type = monitor_type . lower ( )
quorum = module . params [ ' quorum ' ]
monitors = module . params [ ' monitors ' ]
2013-08-14 07:00:49 +02:00
if monitors :
monitors = [ ]
for monitor in module . params [ ' monitors ' ] :
2015-06-03 08:22:18 +02:00
monitors . append ( fq_name ( partition , monitor ) )
2013-07-31 23:12:04 +02:00
slow_ramp_time = module . params [ ' slow_ramp_time ' ]
2016-05-08 23:30:04 +02:00
reselect_tries = module . params [ ' reselect_tries ' ]
2013-07-31 23:12:04 +02:00
service_down_action = module . params [ ' service_down_action ' ]
if service_down_action :
service_down_action = service_down_action . lower ( )
2013-05-09 19:47:14 +02:00
host = module . params [ ' host ' ]
2016-07-09 09:21:10 +02:00
address = fq_name ( partition , host )
2013-07-31 23:12:04 +02:00
port = module . params [ ' port ' ]
# sanity check user supplied values
2016-03-29 23:01:52 +02:00
if ( host and port is None ) or ( port is not None and not host ) :
2013-07-31 23:12:04 +02:00
module . fail_json ( msg = " both host and port must be supplied " )
2016-04-11 19:57:55 +02:00
if port is not None and ( 0 > port or port > 65535 ) :
2016-03-29 23:01:52 +02:00
module . fail_json ( msg = " valid ports must be in range 0 - 65535 " )
2013-07-31 23:12:04 +02:00
if monitors :
if len ( monitors ) == 1 :
# set default required values for single monitor
quorum = 0
monitor_type = ' single '
elif len ( monitors ) > 1 :
if not monitor_type :
module . fail_json ( msg = " monitor_type required for monitors > 1 " )
if monitor_type == ' m_of_n ' and not quorum :
module . fail_json ( msg = " quorum value required for monitor_type m_of_n " )
if monitor_type != ' m_of_n ' :
quorum = 0
elif monitor_type :
# no monitors specified but monitor_type exists
module . fail_json ( msg = " monitor_type require monitors parameter " )
elif quorum is not None :
# no monitors specified but quorum exists
module . fail_json ( msg = " quorum requires monitors parameter " )
2013-05-09 19:47:14 +02:00
try :
2016-06-23 11:33:49 +02:00
api = bigip_api ( server , user , password , validate_certs , port = server_port )
2013-07-31 23:12:04 +02:00
result = { ' changed ' : False } # default
2013-05-09 19:47:14 +02:00
if state == ' absent ' :
2013-07-31 23:12:04 +02:00
if host and port and pool :
# member removal takes precedent
if pool_exists ( api , pool ) and 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 pool_exists ( api , pool ) :
# no host/port supplied, must be pool removal
if not module . check_mode :
2013-08-10 00:46:02 +02:00
# hack to handle concurrent runs of module
# pool might be gone before we actually remove it
try :
remove_pool ( api , pool )
result = { ' changed ' : True }
2016-07-09 09:21:10 +02:00
except bigsuds . OperationFailed as e :
2013-08-10 00:46:02 +02:00
if " was not found " in str ( e ) :
result = { ' changed ' : False }
else :
# genuine exception
raise
else :
# check-mode return value
result = { ' changed ' : True }
2013-07-31 23:12:04 +02:00
2013-05-09 19:47:14 +02:00
elif state == ' present ' :
2013-08-10 00:46:02 +02:00
update = False
2013-07-31 23:12:04 +02:00
if not pool_exists ( api , pool ) :
# pool does not exist -- need to create it
if not module . check_mode :
2013-08-10 00:46:02 +02:00
# a bit of a hack to handle concurrent runs of this module.
# even though we've checked the pool doesn't exist,
# it may exist by the time we run create_pool().
# this catches the exception and does something smart
# about it!
try :
create_pool ( api , pool , lb_method )
result = { ' changed ' : True }
2016-07-09 09:21:10 +02:00
except bigsuds . OperationFailed as e :
2013-08-10 00:46:02 +02:00
if " already exists " in str ( e ) :
update = True
else :
# genuine exception
raise
else :
if monitors :
set_monitors ( api , pool , monitor_type , quorum , monitors )
if slow_ramp_time :
set_slow_ramp_time ( api , pool , slow_ramp_time )
2016-05-08 23:30:04 +02:00
if reselect_tries :
set_reselect_tries ( api , pool , reselect_tries )
2013-08-10 00:46:02 +02:00
if service_down_action :
set_action_on_service_down ( api , pool , service_down_action )
if host and port :
add_pool_member ( api , pool , address , port )
else :
# check-mode return value
result = { ' changed ' : True }
2013-05-09 19:47:14 +02:00
else :
2013-07-31 23:12:04 +02:00
# pool exists -- potentially modify attributes
2013-08-10 00:46:02 +02:00
update = True
if update :
2013-07-31 23:12:04 +02:00
if lb_method and lb_method != get_lb_method ( api , pool ) :
if not module . check_mode :
set_lb_method ( api , pool , lb_method )
result = { ' changed ' : True }
if monitors :
t_monitor_type , t_quorum , t_monitor_templates = get_monitors ( api , pool )
if ( t_monitor_type != monitor_type ) or ( t_quorum != quorum ) or ( set ( t_monitor_templates ) != set ( monitors ) ) :
if not module . check_mode :
set_monitors ( api , pool , monitor_type , quorum , monitors )
result = { ' changed ' : True }
if slow_ramp_time and slow_ramp_time != get_slow_ramp_time ( api , pool ) :
if not module . check_mode :
set_slow_ramp_time ( api , pool , slow_ramp_time )
result = { ' changed ' : True }
2016-05-08 23:30:04 +02:00
if reselect_tries and reselect_tries != get_reselect_tries ( api , pool ) :
if not module . check_mode :
set_reselect_tries ( api , pool , reselect_tries )
result = { ' changed ' : True }
2013-07-31 23:12:04 +02:00
if service_down_action and service_down_action != get_action_on_service_down ( api , pool ) :
if not module . check_mode :
set_action_on_service_down ( api , pool , service_down_action )
result = { ' changed ' : True }
if ( host and port ) and not member_exists ( api , pool , address , port ) :
if not module . check_mode :
add_pool_member ( api , pool , address , port )
result = { ' changed ' : True }
2016-03-29 23:01:52 +02:00
if ( host and port == 0 ) and not member_exists ( api , pool , address , port ) :
if not module . check_mode :
add_pool_member ( api , pool , address , port )
result = { ' changed ' : True }
2013-07-31 23:12:04 +02:00
2016-07-09 09:21:10 +02:00
except Exception as e :
2013-05-09 19:47:14 +02:00
module . fail_json ( msg = " received exception: %s " % e )
module . exit_json ( * * result )
2013-12-02 21:11:23 +01:00
from ansible . module_utils . basic import *
2015-06-03 08:22:18 +02:00
from ansible . module_utils . f5 import *
2016-04-25 21:34:18 +02:00
if __name__ == ' __main__ ' :
main ( )