@ -1,6 +1,6 @@
#!/usr/bin/python
#!/usr/bin/python
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#
# (c) 2013, Matt Hite <mhite@hotmail.com>
# (c) 2013, Matt Hite <mhite@hotmail.com>
#
#
# This file is part of Ansible
# This file is part of Ansible
@ -23,148 +23,139 @@ DOCUMENTATION = '''
module : bigip_node
module : bigip_node
short_description : " Manages F5 BIG-IP LTM nodes "
short_description : " Manages F5 BIG-IP LTM nodes "
description :
description :
- " Manages F5 BIG-IP LTM nodes via iControl SOAP API "
- " Manages F5 BIG-IP LTM nodes via iControl SOAP API "
version_added : " 1.4 "
version_added : " 1.4 "
author :
author :
- Matt Hite ( @mhite )
- Matt Hite ( @mhite )
- Tim Rupp ( @caphrim007 )
- Tim Rupp ( @caphrim007 )
notes :
notes :
- " Requires BIG-IP software version >= 11 "
- " Requires BIG-IP software version >= 11 "
- " F5 developed module ' bigsuds ' required (see http://devcentral.f5.com) "
- " F5 developed module ' bigsuds ' required (see http://devcentral.f5.com) "
- " Best run as a local_action in your playbook "
- " Best run as a local_action in your playbook "
requirements :
requirements :
- bigsuds
- bigsuds
options :
options :
server :
server :
description :
description :
- BIG - IP host
- BIG - IP host
required : true
required : true
default : null
default : null
choices : [ ]
choices : [ ]
aliases : [ ]
aliases : [ ]
server_port :
server_port :
description :
description :
- BIG - IP server port
- BIG - IP server port
required : false
required : false
default : 443
default : 443
version_added : " 2.2 "
version_added : " 2.2 "
user :
user :
description :
description :
- BIG - IP username
- BIG - IP username
required : true
required : true
default : null
default : null
choices : [ ]
choices : [ ]
aliases : [ ]
aliases : [ ]
password :
password :
description :
description :
- BIG - IP password
- BIG - IP password
required : true
required : true
default : null
default : null
choices : [ ]
choices : [ ]
aliases : [ ]
aliases : [ ]
validate_certs :
validate_certs :
description :
description :
- If C ( no ) , SSL certificates will not be validated . This should only be used
- 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
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
validate on python > = 2.7 .9 and never validate on python < = 2.7 .8
required : false
required : false
default : ' yes '
default : ' yes '
choices : [ ' yes ' , ' no ' ]
choices : [ ' yes ' , ' no ' ]
version_added : 1.9 .1
version_added : 2.0
state :
state :
description :
description :
- Pool member state
- Pool member state
required : true
required : true
default : present
default : present
choices : [ ' present ' , ' absent ' ]
choices : [ ' present ' , ' absent ' ]
aliases : [ ]
aliases : [ ]
session_state :
session_state :
description :
description :
- Set new session availability status for node
- Set new session availability status for node
version_added : " 1.9 "
version_added : " 1.9 "
required : false
required : false
default : null
default : null
choices : [ ' enabled ' , ' disabled ' ]
choices : [ ' enabled ' , ' disabled ' ]
aliases : [ ]
aliases : [ ]
monitor_state :
monitor_state :
description :
description :
- Set monitor availability status for node
- Set monitor availability status for node
version_added : " 1.9 "
version_added : " 1.9 "
required : false
required : false
default : null
default : null
choices : [ ' enabled ' , ' disabled ' ]
choices : [ ' enabled ' , ' disabled ' ]
aliases : [ ]
aliases : [ ]
partition :
partition :
description :
- Partition
required : false
default : ' Common '
choices : [ ]
aliases : [ ]
name :
description :
- " Node name "
required : false
default : null
choices : [ ]
monitor_type :
description :
- Monitor rule type when monitors > 1
version_added : " 2.2 "
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 : " 2.2 "
required : False
default : null
choices : [ ]
aliases : [ ]
monitors :
description :
- Monitor template name list . Always use the full path to the monitor .
version_added : " 2.2 "
required : False
default : null
choices : [ ]
aliases : [ ]
host :
description :
- " Node IP. Required when state=present and node does not exist. Error when state=absent. "
required : true
default : null
choices : [ ]
aliases : [ ' address ' , ' ip ' ]
description :
description :
description :
- Partition
- " Node description. "
required : false
required : false
default : ' Common '
default : null
choices : [ ]
choices : [ ]
aliases : [ ]
name :
description :
- " Node name "
required : false
default : null
choices : [ ]
monitor_type :
description :
- Monitor rule type when monitors > 1
version_added : " 2.2 "
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 : " 2.2 "
required : False
default : null
choices : [ ]
aliases : [ ]
monitors :
description :
- Monitor template name list . Always use the full path to the monitor .
version_added : " 2.2 "
required : False
default : null
choices : [ ]
aliases : [ ]
host :
description :
- " Node IP. Required when state=present and node does not exist. Error when state=absent. "
required : true
default : null
choices : [ ]
aliases : [ ' address ' , ' ip ' ]
description :
description :
- " Node description. "
required : false
default : null
choices : [ ]
'''
'''
EXAMPLES = '''
EXAMPLES = '''
- name : Add node
## playbook task examples:
bigip_node :
server : " lb.mydomain.com "
- - -
user : " admin "
# file bigip-test.yml
password : " secret "
# ...
state : " present "
- hosts : bigip - test
partition : " Common "
tasks :
host : " 10.20.30.40 "
- name : Add node
name : " 10.20.30.40 "
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
# Note that the BIG-IP automatically names the node using the
# IP address specified in previous play's host parameter.
# IP address specified in previous play's host parameter.
@ -173,38 +164,38 @@ EXAMPLES = '''
# Alternatively, you could have specified a name with the
# Alternatively, you could have specified a name with the
# name parameter when state=present.
# name parameter when state=present.
- name : Add node with a single ' ping ' monitor
- name : Add node with a single ' ping ' monitor
bigip_node :
bigip_node :
server : lb . mydomain . com
server : " lb.mydomain.com "
user : admin
user : " admin "
password : mysecret
password : " secret "
state : present
state : " present "
partition : Common
partition : " Common "
host : " {{ ansible_default_ipv4[ " address " ] }} "
host : " 10.20.30.40 "
name : mytestserver
name : " mytestserver "
monitors :
monitors :
- / Common / icmp
- / Common / icmp
delegate_to : localhost
- name : Modify node description
local_action : >
- name : Modify node description
bigip_node
bigip_node :
server = lb . mydomain . com
server : " lb.mydomain.com "
user = admin
user : " admin "
password = mysecret
password : " secret "
state = present
state : " present "
partition = matthite
partition : " Common "
name = " {{ ansible_default_ipv4[ " address " ] }} "
name : " 10.20.30.40 "
description = " Our best server yet "
description : " Our best server yet "
delegate_to : localhost
- name : Delete node
local_action : >
- name : Delete node
bigip_node
bigip_node :
server = lb . mydomain . com
server : " lb.mydomain.com "
user = admin
user : " admin "
password = mysecret
password : " secret "
state = absent
state : " absent "
partition = matthite
partition : " Common "
name = " {{ ansible_default_ipv4[ " address " ] }} "
name : " 10.20.30.40 "
# The BIG-IP GUI doesn't map directly to the API calls for "Node ->
# The BIG-IP GUI doesn't map directly to the API calls for "Node ->
# General Properties -> State". The following states map to API monitor
# General Properties -> State". The following states map to API monitor
@ -219,27 +210,26 @@ EXAMPLES = '''
#
#
# See https://devcentral.f5.com/questions/icontrol-equivalent-call-for-b-node-down
# See https://devcentral.f5.com/questions/icontrol-equivalent-call-for-b-node-down
- name : Force node offline
- name : Force node offline
local_action : >
bigip_node :
bigip_node
server : " lb.mydomain.com "
server = lb . mydomain . com
user : " admin "
user = admin
password : " mysecret "
password = mysecret
state : " present "
state = present
session_state : " disabled "
session_state = disabled
monitor_state : " disabled "
monitor_state = disabled
partition : " Common "
partition = matthite
name : " 10.20.30.40 "
name = " {{ ansible_default_ipv4[ " address " ] }} "
'''
'''
def node_exists ( api , address ) :
def node_exists ( api , address ) :
# hack to determine if node exists
# hack to determine if node exists
result = False
result = False
try :
try :
api . LocalLB . NodeAddressV2 . get_object_status ( nodes = [ address ] )
api . LocalLB . NodeAddressV2 . get_object_status ( nodes = [ address ] )
result = True
result = True
except bigsuds . OperationFailed , e :
except bigsuds . OperationFailed as e :
if " was not found " in str ( e ) :
if " was not found " in str ( e ) :
result = False
result = False
else :
else :
@ -247,12 +237,17 @@ def node_exists(api, address):
raise
raise
return result
return result
def create_node_address ( api , address , name ) :
def create_node_address ( api , address , name ) :
try :
try :
api . LocalLB . NodeAddressV2 . create ( nodes = [ name ] , addresses = [ address ] , limits = [ 0 ] )
api . LocalLB . NodeAddressV2 . create (
nodes = [ name ] ,
addresses = [ address ] ,
limits = [ 0 ]
)
result = True
result = True
desc = " "
desc = " "
except bigsuds . OperationFailed , e :
except bigsuds . OperationFailed as e :
if " already exists " in str ( e ) :
if " already exists " in str ( e ) :
result = False
result = False
desc = " referenced name or IP already in use "
desc = " referenced name or IP already in use "
@ -261,15 +256,17 @@ def create_node_address(api, address, name):
raise
raise
return ( result , desc )
return ( result , desc )
def get_node_address ( api , name ) :
def get_node_address ( api , name ) :
return api . LocalLB . NodeAddressV2 . get_address ( nodes = [ name ] ) [ 0 ]
return api . LocalLB . NodeAddressV2 . get_address ( nodes = [ name ] ) [ 0 ]
def delete_node_address ( api , address ) :
def delete_node_address ( api , address ) :
try :
try :
api . LocalLB . NodeAddressV2 . delete_node_address ( nodes = [ address ] )
api . LocalLB . NodeAddressV2 . delete_node_address ( nodes = [ address ] )
result = True
result = True
desc = " "
desc = " "
except bigsuds . OperationFailed , e :
except bigsuds . OperationFailed as e :
if " is referenced by a member of pool " in str ( e ) :
if " is referenced by a member of pool " in str ( e ) :
result = False
result = False
desc = " node referenced by pool "
desc = " node referenced by pool "
@ -278,33 +275,40 @@ def delete_node_address(api, address):
raise
raise
return ( result , desc )
return ( result , desc )
def set_node_description ( api , name , description ) :
def set_node_description ( api , name , description ) :
api . LocalLB . NodeAddressV2 . set_description ( nodes = [ name ] ,
api . LocalLB . NodeAddressV2 . set_description ( nodes = [ name ] ,
descriptions = [ description ] )
descriptions = [ description ] )
def get_node_description ( api , name ) :
def get_node_description ( api , name ) :
return api . LocalLB . NodeAddressV2 . get_description ( nodes = [ name ] ) [ 0 ]
return api . LocalLB . NodeAddressV2 . get_description ( nodes = [ name ] ) [ 0 ]
def set_node_session_enabled_state ( api , name , session_state ) :
def set_node_session_enabled_state ( api , name , session_state ) :
session_state = " STATE_ %s " % session_state . strip ( ) . upper ( )
session_state = " STATE_ %s " % session_state . strip ( ) . upper ( )
api . LocalLB . NodeAddressV2 . set_session_enabled_state ( nodes = [ name ] ,
api . LocalLB . NodeAddressV2 . set_session_enabled_state ( nodes = [ name ] ,
states = [ session_state ] )
states = [ session_state ] )
def get_node_session_status ( api , name ) :
def get_node_session_status ( api , name ) :
result = api . LocalLB . NodeAddressV2 . get_session_status ( nodes = [ name ] ) [ 0 ]
result = api . LocalLB . NodeAddressV2 . get_session_status ( nodes = [ name ] ) [ 0 ]
result = result . split ( " SESSION_STATUS_ " ) [ - 1 ] . lower ( )
result = result . split ( " SESSION_STATUS_ " ) [ - 1 ] . lower ( )
return result
return result
def set_node_monitor_state ( api , name , monitor_state ) :
def set_node_monitor_state ( api , name , monitor_state ) :
monitor_state = " STATE_ %s " % monitor_state . strip ( ) . upper ( )
monitor_state = " STATE_ %s " % monitor_state . strip ( ) . upper ( )
api . LocalLB . NodeAddressV2 . set_monitor_state ( nodes = [ name ] ,
api . LocalLB . NodeAddressV2 . set_monitor_state ( nodes = [ name ] ,
states = [ monitor_state ] )
states = [ monitor_state ] )
def get_node_monitor_status ( api , name ) :
def get_node_monitor_status ( api , name ) :
result = api . LocalLB . NodeAddressV2 . get_monitor_status ( nodes = [ name ] ) [ 0 ]
result = api . LocalLB . NodeAddressV2 . get_monitor_status ( nodes = [ name ] ) [ 0 ]
result = result . split ( " MONITOR_STATUS_ " ) [ - 1 ] . lower ( )
result = result . split ( " MONITOR_STATUS_ " ) [ - 1 ] . lower ( )
return result
return result
def get_monitors ( api , name ) :
def get_monitors ( api , name ) :
result = api . LocalLB . NodeAddressV2 . get_monitor_rule ( nodes = [ name ] ) [ 0 ]
result = api . LocalLB . NodeAddressV2 . get_monitor_rule ( nodes = [ name ] ) [ 0 ]
monitor_type = result [ ' type ' ] . split ( " MONITOR_RULE_TYPE_ " ) [ - 1 ] . lower ( )
monitor_type = result [ ' type ' ] . split ( " MONITOR_RULE_TYPE_ " ) [ - 1 ] . lower ( )
@ -312,37 +316,36 @@ def get_monitors(api, name):
monitor_templates = result [ ' monitor_templates ' ]
monitor_templates = result [ ' monitor_templates ' ]
return ( monitor_type , quorum , monitor_templates )
return ( monitor_type , quorum , monitor_templates )
def set_monitors ( api , name , monitor_type , quorum , monitor_templates ) :
def set_monitors ( api , name , monitor_type , quorum , monitor_templates ) :
monitor_type = " MONITOR_RULE_TYPE_ %s " % monitor_type . strip ( ) . upper ( )
monitor_type = " MONITOR_RULE_TYPE_ %s " % monitor_type . strip ( ) . upper ( )
monitor_rule = { ' type ' : monitor_type , ' quorum ' : quorum , ' monitor_templates ' : monitor_templates }
monitor_rule = { ' type ' : monitor_type , ' quorum ' : quorum , ' monitor_templates ' : monitor_templates }
api . LocalLB . NodeAddressV2 . set_monitor_rule ( nodes = [ name ] ,
api . LocalLB . NodeAddressV2 . set_monitor_rule ( nodes = [ name ] ,
monitor_rules = [ monitor_rule ] )
monitor_rules = [ monitor_rule ] )
def main ( ) :
def main ( ) :
monitor_type_choices = [ ' and_list ' , ' m_of_n ' ]
monitor_type_choices = [ ' and_list ' , ' m_of_n ' ]
argument_spec = f5_argument_spec ( )
argument_spec = f5_argument_spec ( )
argument_spec . update ( dict (
meta_args = dict (
session_state = dict ( type = ' str ' , choices = [ ' enabled ' , ' disabled ' ] ) ,
session_state = dict ( type = ' str ' , choices = [ ' enabled ' , ' disabled ' ] ) ,
monitor_state = dict ( type = ' str ' , choices = [ ' enabled ' , ' disabled ' ] ) ,
monitor_state = dict ( type = ' str ' , choices = [ ' enabled ' , ' disabled ' ] ) ,
name = dict ( type = ' str ' , required = True ) ,
name = dict ( type = ' str ' , required = True ) ,
host = dict ( type = ' str ' , aliases = [ ' address ' , ' ip ' ] ) ,
host = dict ( type = ' str ' , aliases = [ ' address ' , ' ip ' ] ) ,
description = dict ( type = ' str ' ) ,
description = dict ( type = ' str ' ) ,
monitor_type = dict ( type = ' str ' , choices = monitor_type_choices ) ,
monitor_type = dict ( type = ' str ' , choices = monitor_type_choices ) ,
quorum = dict ( type = ' int ' ) ,
quorum = dict ( type = ' int ' ) ,
monitors = dict ( type = ' list ' )
monitors = dict ( type = ' list ' )
)
)
)
argument_spec . update ( meta_args )
module = AnsibleModule (
module = AnsibleModule (
argument_spec = argument_spec ,
argument_spec = argument_spec ,
supports_check_mode = True
supports_check_mode = True
)
)
if not bigsuds_found :
module . fail_json ( msg = " the python bigsuds module is required " )
if module . params [ ' validate_certs ' ] :
if module . params [ ' validate_certs ' ] :
import ssl
import ssl
if not hasattr ( ssl , ' SSLContext ' ) :
if not hasattr ( ssl , ' SSLContext ' ) :
@ -373,7 +376,6 @@ def main():
monitors . append ( fq_name ( partition , monitor ) )
monitors . append ( fq_name ( partition , monitor ) )
# sanity check user supplied values
# sanity check user supplied values
if state == ' absent ' and host is not None :
if state == ' absent ' and host is not None :
module . fail_json ( msg = " host parameter invalid when state=absent " )
module . fail_json ( msg = " host parameter invalid when state=absent " )
@ -415,7 +417,7 @@ def main():
elif state == ' present ' :
elif state == ' present ' :
if not node_exists ( api , address ) :
if not node_exists ( api , address ) :
if host is None :
if host is None :
module . fail_json ( msg = " host parameter required when " \
module . fail_json ( msg = " host parameter required when "
" state=present and node does not exist " )
" state=present and node does not exist " )
if not module . check_mode :
if not module . check_mode :
created , desc = create_node_address ( api , address = host , name = address )
created , desc = create_node_address ( api , address = host , name = address )
@ -442,8 +444,8 @@ def main():
# node exists -- potentially modify attributes
# node exists -- potentially modify attributes
if host is not None :
if host is not None :
if get_node_address ( api , address ) != host :
if get_node_address ( api , address ) != host :
module . fail_json ( msg = " Changing the node address is " \
module . fail_json ( msg = " Changing the node address is "
" not supported by the API; " \
" not supported by the API; "
" delete and recreate the node. " )
" delete and recreate the node. " )
if session_state is not None :
if session_state is not None :
session_status = get_node_session_status ( api , address )
session_status = get_node_session_status ( api , address )
@ -454,7 +456,7 @@ def main():
session_state )
session_state )
result = { ' changed ' : True }
result = { ' changed ' : True }
elif session_state == ' disabled ' and \
elif session_state == ' disabled ' and \
session_status != ' force_disabled ' :
session_status != ' force_disabled ' :
if not module . check_mode :
if not module . check_mode :
set_node_session_enabled_state ( api , address ,
set_node_session_enabled_state ( api , address ,
session_state )
session_state )
@ -468,7 +470,7 @@ def main():
monitor_state )
monitor_state )
result = { ' changed ' : True }
result = { ' changed ' : True }
elif monitor_state == ' disabled ' and \
elif monitor_state == ' disabled ' and \
monitor_status != ' forced_down ' :
monitor_status != ' forced_down ' :
if not module . check_mode :
if not module . check_mode :
set_node_monitor_state ( api , address ,
set_node_monitor_state ( api , address ,
monitor_state )
monitor_state )
@ -484,13 +486,11 @@ def main():
if not module . check_mode :
if not module . check_mode :
set_monitors ( api , address , monitor_type , quorum , monitors )
set_monitors ( api , address , monitor_type , quorum , monitors )
result = { ' changed ' : True }
result = { ' changed ' : True }
except Exception as e :
except Exception , e :
module . fail_json ( msg = " received exception: %s " % e )
module . fail_json ( msg = " received exception: %s " % e )
module . exit_json ( * * result )
module . exit_json ( * * result )
# import module snippets
from ansible . module_utils . basic import *
from ansible . module_utils . basic import *
from ansible . module_utils . f5 import *
from ansible . module_utils . f5 import *