@ -18,15 +18,14 @@ module: bigip_gtm_pool
short_description : Manages F5 BIG - IP GTM pools
description :
- Manages F5 BIG - IP GTM pools .
version_added : " 2.4 "
version_added : 2.4
options :
state :
description :
- Pool member state . When C ( present ) , ensures that the pool is
created and enabled . When C ( absent ) , ensures that the pool is
removed from the system . When C ( enabled ) or C ( disabled ) , ensures
that the pool is enabled or disabled ( respectively ) on the remote
device .
- Pool state . When C ( present ) , ensures that the pool is created and enabled .
When C ( absent ) , ensures that the pool is removed from the system . When
C ( enabled ) or C ( disabled ) , ensures that the pool is enabled or disabled
( respectively ) on the remote device .
choices :
- present
- absent
@ -122,6 +121,60 @@ options:
- Device partition to manage resources on .
default : Common
version_added : 2.5
members :
description :
- Members to assign to the pool .
- The order of the members in this list is the order that they will be listed in the pool .
suboptions :
server :
description :
- Name of the server which the pool member is a part of .
required : True
virtual_server :
description :
- Name of the virtual server , associated with the server , that the pool member is a part of .
required : True
version_added : 2.6
monitors :
description :
- Specifies the health monitors that the system currently uses to monitor this resource .
- When C ( availability_requirements . type ) is C ( require ) , you may only have a single monitor in the
C ( monitors ) list .
version_added : 2.6
availability_requirements :
description :
- Specifies , if you activate more than one health monitor , the number of health
monitors that must receive successful responses in order for the link to be
considered available .
suboptions :
type :
description :
- Monitor rule type when C ( monitors ) is specified .
- When creating a new pool , if this value is not specified , the default of ' all ' will be used .
choices : [ ' all ' , ' at_least ' , ' require ' ]
at_least :
description :
- Specifies the minimum number of active health monitors that must be successful
before the link is considered up .
- This parameter is only relevant when a C ( type ) of C ( at_least ) is used .
- This parameter will be ignored if a type of either C ( all ) or C ( require ) is used .
number_of_probes :
description :
- Specifies the minimum number of probes that must succeed for this server to be declared up .
- When creating a new virtual server , if this parameter is specified , then the C ( number_of_probers )
parameter must also be specified .
- The value of this parameter should always be B ( lower ) than , or B ( equal to ) , the value of C ( number_of_probers ) .
- This parameter is only relevant when a C ( type ) of C ( require ) is used .
- This parameter will be ignored if a type of either C ( all ) or C ( at_least ) is used .
number_of_probers :
description :
- Specifies the number of probers that should be used when running probes .
- When creating a new virtual server , if this parameter is specified , then the C ( number_of_probes )
parameter must also be specified .
- The value of this parameter should always be B ( higher ) than , or B ( equal to ) , the value of C ( number_of_probers ) .
- This parameter is only relevant when a C ( type ) of C ( require ) is used .
- This parameter will be ignored if a type of either C ( all ) or C ( at_least ) is used .
version_added : 2.6
notes :
- Requires the netaddr Python package on the host . This is as easy as
pip install netaddr .
@ -153,6 +206,24 @@ fallback_ip:
returned : changed
type : string
sample : 10.10 .10 .10
monitors :
description : The new list of monitors for the resource .
returned : changed
type : list
sample : [ ' /Common/monitor1 ' , ' /Common/monitor2 ' ]
members :
description : List of members in the pool .
returned : changed
type : complex
contains :
server :
description : The name of the server portion of the member .
returned : changed
type : string
virtual_server :
description : The name of the virtual server portion of the member .
returned : changed
type : string
'''
EXAMPLES = r '''
@ -174,37 +245,37 @@ EXAMPLES = r'''
delegate_to : localhost
'''
from distutils . version import LooseVersion
import copy
import re
from ansible . module_utils . basic import AnsibleModule
from ansible . module_utils . basic import env_fallback
HAS_DEVEL_IMPORTS = False
from distutils . version import LooseVersion
try :
# Sideband repository used for dev
from library . module_utils . network . f5 . bigip import HAS_F5SDK
from library . module_utils . network . f5 . bigip import F5Client
from library . module_utils . network . f5 . common import F5ModuleError
from library . module_utils . network . f5 . common import AnsibleF5Parameters
from library . module_utils . network . f5 . common import cleanup_tokens
from library . module_utils . network . f5 . common import fq dn _name
from library . module_utils . network . f5 . common import fq _name
from library . module_utils . network . f5 . common import f5_argument_spec
try :
from library . module_utils . network . f5 . common import iControlUnexpectedHTTPError
from f5 . sdk_exception import LazyAttributesRequired
except ImportError :
HAS_F5SDK = False
HAS_DEVEL_IMPORTS = True
except ImportError :
# Upstream Ansible
from ansible . module_utils . network . f5 . bigip import HAS_F5SDK
from ansible . module_utils . network . f5 . bigip import F5Client
from ansible . module_utils . network . f5 . common import F5ModuleError
from ansible . module_utils . network . f5 . common import AnsibleF5Parameters
from ansible . module_utils . network . f5 . common import cleanup_tokens
from ansible . module_utils . network . f5 . common import fq dn _name
from ansible . module_utils . network . f5 . common import fq _name
from ansible . module_utils . network . f5 . common import f5_argument_spec
try :
from ansible . module_utils . network . f5 . common import iControlUnexpectedHTTPError
from f5 . sdk_exception import LazyAttributesRequired
except ImportError :
HAS_F5SDK = False
@ -214,8 +285,6 @@ try:
except ImportError :
HAS_NETADDR = False
import copy
class Parameters ( AnsibleF5Parameters ) :
api_map = {
@ -225,19 +294,47 @@ class Parameters(AnsibleF5Parameters):
' verifyMemberAvailability ' : ' verify_member_availability ' ,
' fallbackIpv4 ' : ' fallback_ip ' ,
' fallbackIpv6 ' : ' fallback_ip ' ,
' fallbackIp ' : ' fallback_ip '
' fallbackIp ' : ' fallback_ip ' ,
' membersReference ' : ' members ' ,
' monitor ' : ' monitors '
}
updatables = [
' preferred_lb_method ' , ' alternate_lb_method ' , ' fallback_lb_method ' ,
' fallback_ip ' , ' state '
' alternate_lb_method ' ,
' fallback_ip ' ,
' fallback_lb_method ' ,
' members ' ,
' monitors ' ,
' preferred_lb_method ' ,
' state ' ,
]
returnables = [
' preferred_lb_method ' , ' alternate_lb_method ' , ' fallback_lb_method ' ,
' fallback_ip '
' alternate_lb_method ' ,
' fallback_ip ' ,
' fallback_lb_method ' ,
' members ' ,
' monitors ' ,
' preferred_lb_method ' ,
' enabled ' ,
' disabled '
]
api_attributes = [
' loadBalancingMode ' , ' alternateMode ' , ' fallbackMode ' , ' verifyMemberAvailability ' ,
' fallbackIpv4 ' , ' fallbackIpv6 ' , ' fallbackIp ' , ' enabled ' , ' disabled '
' alternateMode ' ,
' disabled ' ,
' enabled ' ,
' fallbackIp ' ,
' fallbackIpv4 ' ,
' fallbackIpv6 ' ,
' fallbackMode ' ,
' loadBalancingMode ' ,
' members ' ,
' verifyMemberAvailability ' ,
# The monitor attribute is not included here, because it can break the
# API calls to the device. If this bug is ever fixed, uncomment this code.
#
# monitor
]
def to_return ( self ) :
@ -316,10 +413,252 @@ class Parameters(AnsibleF5Parameters):
return True
class ApiParameters ( Parameters ) :
@property
def members ( self ) :
result = [ ]
if self . _values [ ' members ' ] is None or ' items ' not in self . _values [ ' members ' ] :
return [ ]
for item in self . _values [ ' members ' ] [ ' items ' ] :
result . append ( dict ( item = item [ ' fullPath ' ] , order = item [ ' memberOrder ' ] ) )
result = [ x [ ' item ' ] for x in sorted ( result , key = lambda k : k [ ' order ' ] ) ]
return result
@property
def availability_requirement_type ( self ) :
if self . _values [ ' monitors ' ] is None :
return None
if ' min ' in self . _values [ ' monitors ' ] :
return ' at_least '
elif ' require ' in self . _values [ ' monitors ' ] :
return ' require '
else :
return ' all '
@property
def monitors_list ( self ) :
if self . _values [ ' monitors ' ] is None :
return [ ]
try :
result = re . findall ( r ' / \ w+/[^ \ s}]+ ' , self . _values [ ' monitors ' ] )
result . sort ( )
return result
except Exception :
return self . _values [ ' monitors ' ]
@property
def monitors ( self ) :
if self . _values [ ' monitors ' ] is None :
return None
if self . _values [ ' monitors ' ] == ' default ' :
return ' default '
monitors = [ fq_name ( self . partition , x ) for x in self . monitors_list ]
if self . availability_requirement_type == ' at_least ' :
monitors = ' ' . join ( monitors )
result = ' min {0} of {{ {1} }} ' . format ( self . at_least , monitors )
elif self . availability_requirement_type == ' require ' :
monitors = ' ' . join ( monitors )
result = ' require {0} from {1} {{ {2} }} ' . format ( self . number_of_probes , self . number_of_probers , monitors )
else :
result = ' and ' . join ( monitors ) . strip ( )
return result
@property
def number_of_probes ( self ) :
""" Returns the probes value from the monitor string.
The monitor string for a Require monitor looks like this .
require 1 from 2 { / Common / tcp }
This method parses out the first of the numeric values . This values represents
the " probes " value that can be updated in the module .
Returns :
int : The probes value if found . None otherwise .
"""
if self . _values [ ' monitors ' ] is None :
return None
pattern = r ' require \ s+(?P<probes> \ d+) \ s+from '
matches = re . search ( pattern , self . _values [ ' monitors ' ] )
if matches is None :
return None
return matches . group ( ' probes ' )
@property
def number_of_probers ( self ) :
""" Returns the probers value from the monitor string.
The monitor string for a Require monitor looks like this .
require 1 from 2 { / Common / tcp }
This method parses out the first of the numeric values . This values represents
the " probers " value that can be updated in the module .
Returns :
int : The probers value if found . None otherwise .
"""
if self . _values [ ' monitors ' ] is None :
return None
pattern = r ' require \ s+ \ d+ \ s+from \ s+(?P<probers> \ d+) \ s+ '
matches = re . search ( pattern , self . _values [ ' monitors ' ] )
if matches is None :
return None
return matches . group ( ' probers ' )
@property
def at_least ( self ) :
""" Returns the ' at least ' value from the monitor string.
The monitor string for a Require monitor looks like this .
min 1 of { / Common / gateway_icmp }
This method parses out the first of the numeric values . This values represents
the " at_least " value that can be updated in the module .
Returns :
int : The at_least value if found . None otherwise .
"""
if self . _values [ ' monitors ' ] is None :
return None
pattern = r ' min \ s+(?P<least> \ d+) \ s+of \ s+ '
matches = re . search ( pattern , self . _values [ ' monitors ' ] )
if matches is None :
return None
return matches . group ( ' least ' )
class ModuleParameters ( Parameters ) :
def _get_availability_value ( self , type ) :
if self . _values [ ' availability_requirements ' ] is None :
return None
if self . _values [ ' availability_requirements ' ] [ type ] is None :
return None
return int ( self . _values [ ' availability_requirements ' ] [ type ] )
@property
def members ( self ) :
if self . _values [ ' members ' ] is None :
return None
if len ( self . _values [ ' members ' ] ) == 1 and self . _values [ ' members ' ] [ 0 ] == ' ' :
return [ ]
result = [ ]
for member in self . _values [ ' members ' ] :
if ' server ' not in member :
raise F5ModuleError (
" One of the provided members is missing a ' server ' sub-option. "
)
if ' virtual_server ' not in member :
raise F5ModuleError (
" One of the provided members is missing a ' virtual_server ' sub-option. "
)
name = ' {0} : {1} ' . format ( member [ ' server ' ] , member [ ' virtual_server ' ] )
name = fq_name ( self . partition , name )
if name in result :
continue
result . append ( name )
result = list ( result )
return result
@property
def monitors_list ( self ) :
if self . _values [ ' monitors ' ] is None :
return [ ]
try :
result = re . findall ( r ' / \ w+/[^ \ s}]+ ' , self . _values [ ' monitors ' ] )
result . sort ( )
return result
except Exception :
return self . _values [ ' monitors ' ]
@property
def monitors ( self ) :
if self . _values [ ' monitors ' ] is None :
return None
if len ( self . _values [ ' monitors ' ] ) == 1 and self . _values [ ' monitors ' ] [ 0 ] == ' ' :
return ' default '
monitors = [ fq_name ( self . partition , x ) for x in self . monitors_list ]
if self . availability_requirement_type == ' at_least ' :
if self . at_least > len ( self . monitors_list ) :
raise F5ModuleError (
" The ' at_least ' value must not exceed the number of ' monitors ' . "
)
monitors = ' ' . join ( monitors )
result = ' min {0} of {{ {1} }} ' . format ( self . at_least , monitors )
elif self . availability_requirement_type == ' require ' :
monitors = ' ' . join ( monitors )
if self . number_of_probes > self . number_of_probers :
raise F5ModuleError (
" The ' number_of_probes ' must not exceed the ' number_of_probers ' . "
)
result = ' require {0} from {1} {{ {2} }} ' . format ( self . number_of_probes , self . number_of_probers , monitors )
else :
result = ' and ' . join ( monitors ) . strip ( )
return result
@property
def availability_requirement_type ( self ) :
if self . _values [ ' availability_requirements ' ] is None :
return None
return self . _values [ ' availability_requirements ' ] [ ' type ' ]
@property
def number_of_probes ( self ) :
return self . _get_availability_value ( ' number_of_probes ' )
@property
def number_of_probers ( self ) :
return self . _get_availability_value ( ' number_of_probers ' )
@property
def at_least ( self ) :
return self . _get_availability_value ( ' at_least ' )
class Changes ( Parameters ) :
pass
class UsableChanges ( Changes ) :
@property
def monitors ( self ) :
if self . _values [ ' monitors ' ] is None :
return None
return self . _values [ ' monitors ' ]
@property
def members ( self ) :
results = [ ]
if self . _values [ ' members ' ] is None :
return None
for idx , member in enumerate ( self . _values [ ' members ' ] ) :
result = dict (
name = member ,
memberOrder = idx
)
results . append ( result )
return results
class ReportableChanges ( Changes ) :
@property
def members ( self ) :
results = [ ]
if self . _values [ ' members ' ] is None :
return None
for member in self . _values [ ' members ' ] :
parts = member . split ( ' : ' )
results . append ( dict (
server = fq_name ( self . partition , parts [ 0 ] ) ,
virtual_server = fq_name ( self . partition , parts [ 1 ] )
) )
return results
class Difference ( object ) :
def __init__ ( self , want , have = None ) :
self . want = want
@ -352,6 +691,21 @@ class Difference(object):
enabled = True
)
@property
def monitors ( self ) :
if self . want . monitors is None :
return None
if self . want . monitors == ' default ' and self . have . monitors == ' default ' :
return None
if self . want . monitors == ' default ' and self . have . monitors is None :
return None
if self . want . monitors == ' default ' and len ( self . have . monitors ) > 0 :
return ' default '
if self . have . monitors is None :
return self . want . monitors
if self . have . monitors != self . want . monitors :
return self . want . monitors
class ModuleManager ( object ) :
def __init__ ( self , * args , * * kwargs ) :
@ -396,8 +750,8 @@ class BaseManager(object):
self . module = kwargs . get ( ' module ' , None )
self . client = kwargs . get ( ' client ' , None )
self . have = None
self . want = Parameters( params = self . module . params )
self . changes = Changes( )
self . want = Module Parameters( params = self . module . params )
self . changes = Usable Changes( )
def _set_changed_options ( self ) :
changed = { }
@ -405,7 +759,7 @@ class BaseManager(object):
if getattr ( self . want , key ) is not None :
changed [ key ] = getattr ( self . want , key )
if changed :
self . changes = Changes( params = changed )
self . changes = Usable Changes( params = changed )
def _update_changed_options ( self ) :
diff = Difference ( self . want , self . have )
@ -421,7 +775,7 @@ class BaseManager(object):
else :
changed [ k ] = change
if changed :
self . changes = Changes( params = changed )
self . changes = Usable Changes( params = changed )
return True
return False
@ -438,11 +792,21 @@ class BaseManager(object):
except iControlUnexpectedHTTPError as e :
raise F5ModuleError ( str ( e ) )
changes = self . changes . to_return ( )
reportable = ReportableChanges ( params = self . changes . to_return ( ) )
changes = reportable . to_return ( )
result . update ( * * changes )
result . update ( dict ( changed = changed ) )
self . _announce_deprecations ( result )
return result
def _announce_deprecations ( self , result ) :
warnings = result . pop ( ' __warnings ' , [ ] )
for warning in warnings :
self . module . deprecate (
msg = warning [ ' msg ' ] ,
version = warning [ ' version ' ]
)
def present ( self ) :
if self . exists ( ) :
return self . update ( )
@ -474,7 +838,14 @@ class BaseManager(object):
self . want . update ( { ' disabled ' : True } )
elif self . want . state in [ ' present ' , ' enabled ' ] :
self . want . update ( { ' enabled ' : True } )
self . _set_changed_options ( )
if self . want . availability_requirement_type == ' require ' and len ( self . want . monitors_list ) > 1 :
raise F5ModuleError (
" Only one monitor may be specified when using an availability_requirement type of ' require ' "
)
if self . module . check_mode :
return True
self . create_on_device ( )
@ -491,6 +862,36 @@ class BaseManager(object):
raise F5ModuleError ( " Failed to delete the GTM pool " )
return True
def update_monitors_on_device ( self ) :
""" Updates the monitors string on a virtual server
There is a long - standing bug in GTM virtual servers where the monitor value
is a string that includes braces . These braces cause the REST API to panic and
fail to update or create any resources that have an " at_least " or " require "
set of availability_requirements .
This method exists to do a tmsh command to cause the update to take place on
the device .
Preferably , this method can be removed and the bug be fixed . The API should
be working , obviously , but the more concerning issue is if tmsh commands change
over time , breaking this method .
"""
command = ' tmsh modify gtm pool {0} / {1} / {2} monitor {3} ' . format (
self . want . type , self . want . partition , self . want . name , self . want . monitors
)
output = self . client . api . tm . util . bash . exec_cmd (
' run ' ,
utilCmdArgs = ' -c " {0} " ' . format ( command )
)
try :
if hasattr ( output , ' commandResult ' ) :
if len ( output . commandResult . strip ( ) ) > 0 :
raise F5ModuleError ( output . commandResult )
except ( AttributeError , NameError , LazyAttributesRequired ) :
pass
return True
class TypedManager ( BaseManager ) :
def __init__ ( self , * args , * * kwargs ) :
@ -535,7 +936,10 @@ class TypedManager(BaseManager):
name = self . want . name ,
partition = self . want . partition
)
result . modify ( * * params )
if params :
result . modify ( * * params )
if self . want . monitors :
self . update_monitors_on_device ( )
def read_current_from_device ( self ) :
pools = self . client . api . tm . gtm . pools
@ -543,13 +947,18 @@ class TypedManager(BaseManager):
resource = getattr ( collection , self . want . type )
result = resource . load (
name = self . want . name ,
partition = self . want . partition
partition = self . want . partition ,
requests_params = dict (
params = dict (
expandSubcollections = ' true '
)
)
)
result = result . attrs
return Parameters ( params = result )
return Api Parameters( params = result )
def create_on_device ( self ) :
params = self . want . api_params ( )
params = self . changes . api_params ( )
pools = self . client . api . tm . gtm . pools
collection = getattr ( pools , self . want . collection )
resource = getattr ( collection , self . want . type )
@ -558,6 +967,8 @@ class TypedManager(BaseManager):
partition = self . want . partition ,
* * params
)
if self . want . monitors :
self . update_monitors_on_device ( )
def remove_from_device ( self ) :
pools = self . client . api . tm . gtm . pools
@ -586,6 +997,8 @@ class UntypedManager(BaseManager):
partition = self . want . partition
)
resource . modify ( * * params )
if self . want . monitors :
self . update_monitors_on_device ( )
def read_current_from_device ( self ) :
resource = self . client . api . tm . gtm . pools . pool . load (
@ -593,15 +1006,17 @@ class UntypedManager(BaseManager):
partition = self . want . partition
)
result = resource . attrs
return Parameters( params = result )
return Api Parameters( params = result )
def create_on_device ( self ) :
params = self . want . api_params ( )
params = self . changes . api_params ( )
self . client . api . tm . gtm . pools . pool . create (
name = self . want . name ,
partition = self . want . partition ,
* * params
)
if self . want . monitors :
self . update_monitors_on_device ( )
def remove_from_device ( self ) :
resource = self . client . api . tm . gtm . pools . pool . load (
@ -656,7 +1071,35 @@ class ArgumentSpec(object):
partition = dict (
default = ' Common ' ,
fallback = ( env_fallback , [ ' F5_PARTITION ' ] )
)
) ,
members = dict (
type = ' list ' ,
options = dict (
server = dict ( required = True ) ,
virtual_server = dict ( required = True )
)
) ,
availability_requirements = dict (
type = ' dict ' ,
options = dict (
type = dict (
choices = [ ' all ' , ' at_least ' , ' require ' ] ,
required = True
) ,
at_least = dict ( type = ' int ' ) ,
number_of_probes = dict ( type = ' int ' ) ,
number_of_probers = dict ( type = ' int ' )
) ,
mutually_exclusive = [
[ ' at_least ' , ' number_of_probes ' ] ,
[ ' at_least ' , ' number_of_probers ' ] ,
] ,
required_if = [
[ ' type ' , ' at_least ' , [ ' at_least ' ] ] ,
[ ' type ' , ' require ' , [ ' number_of_probes ' , ' number_of_probers ' ] ]
]
) ,
monitors = dict ( type = ' list ' ) ,
)
self . argument_spec = { }
self . argument_spec . update ( f5_argument_spec )