#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (c) 2017 Citrix Systems # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'} DOCUMENTATION = ''' --- module: netscaler_gslb_vserver short_description: Configure gslb vserver entities in Netscaler. description: - Configure gslb vserver entities in Netscaler. author: George Nikolopoulos (@giorgos-nikolopoulos) options: name: description: - >- Name for the GSLB virtual server. Must begin with an ASCII alphanumeric or underscore C(_) character, and must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space, colon C(:), at C(@), equals C(=), and hyphen C(-) characters. Can be changed after the virtual server is created. - "Minimum length = 1" servicetype: choices: - 'HTTP' - 'FTP' - 'TCP' - 'UDP' - 'SSL' - 'SSL_BRIDGE' - 'SSL_TCP' - 'NNTP' - 'ANY' - 'SIP_UDP' - 'SIP_TCP' - 'SIP_SSL' - 'RADIUS' - 'RDP' - 'RTSP' - 'MYSQL' - 'MSSQL' - 'ORACLE' description: - "Protocol used by services bound to the virtual server." - >- dnsrecordtype: choices: - 'A' - 'AAAA' - 'CNAME' - 'NAPTR' description: - "DNS record type to associate with the GSLB virtual server's domain name." - "Default value: A" - "Possible values = A, AAAA, CNAME, NAPTR" lbmethod: choices: - 'ROUNDROBIN' - 'LEASTCONNECTION' - 'LEASTRESPONSETIME' - 'SOURCEIPHASH' - 'LEASTBANDWIDTH' - 'LEASTPACKETS' - 'STATICPROXIMITY' - 'RTT' - 'CUSTOMLOAD' description: - "Load balancing method for the GSLB virtual server." - "Default value: LEASTCONNECTION" - >- Possible values = ROUNDROBIN, LEASTCONNECTION, LEASTRESPONSETIME, SOURCEIPHASH, LEASTBANDWIDTH, LEASTPACKETS, STATICPROXIMITY, RTT, CUSTOMLOAD backuplbmethod: choices: - 'ROUNDROBIN' - 'LEASTCONNECTION' - 'LEASTRESPONSETIME' - 'SOURCEIPHASH' - 'LEASTBANDWIDTH' - 'LEASTPACKETS' - 'STATICPROXIMITY' - 'RTT' - 'CUSTOMLOAD' description: - >- Backup load balancing method. Becomes operational if the primary load balancing method fails or cannot be used. Valid only if the primary method is based on either round-trip time (RTT) or static proximity. netmask: description: - "IPv4 network mask for use in the SOURCEIPHASH load balancing method." - "Minimum length = 1" v6netmasklen: description: - >- Number of bits to consider, in an IPv6 source IP address, for creating the hash that is required by the C(SOURCEIPHASH) load balancing method. - "Default value: C(128)" - "Minimum value = C(1)" - "Maximum value = C(128)" tolerance: description: - >- Site selection tolerance, in milliseconds, for implementing the RTT load balancing method. If a site's RTT deviates from the lowest RTT by more than the specified tolerance, the site is not considered when the NetScaler appliance makes a GSLB decision. The appliance implements the round robin method of global server load balancing between sites whose RTT values are within the specified tolerance. If the tolerance is 0 (zero), the appliance always sends clients the IP address of the site with the lowest RTT. - "Minimum value = C(0)" - "Maximum value = C(100)" persistencetype: choices: - 'SOURCEIP' - 'NONE' description: - "Use source IP address based persistence for the virtual server." - >- After the load balancing method selects a service for the first packet, the IP address received in response to the DNS query is used for subsequent requests from the same client. persistenceid: description: - >- The persistence ID for the GSLB virtual server. The ID is a positive integer that enables GSLB sites to identify the GSLB virtual server, and is required if source IP address based or spill over based persistence is enabled on the virtual server. - "Minimum value = C(0)" - "Maximum value = C(65535)" persistmask: description: - >- The optional IPv4 network mask applied to IPv4 addresses to establish source IP address based persistence. - "Minimum length = 1" v6persistmasklen: description: - >- Number of bits to consider in an IPv6 source IP address when creating source IP address based persistence sessions. - "Default value: C(128)" - "Minimum value = C(1)" - "Maximum value = C(128)" timeout: description: - "Idle time, in minutes, after which a persistence entry is cleared." - "Default value: C(2)" - "Minimum value = C(2)" - "Maximum value = C(1440)" mir: choices: - 'enabled' - 'disabled' description: - "Include multiple IP addresses in the DNS responses sent to clients." disableprimaryondown: choices: - 'enabled' - 'disabled' description: - >- Continue to direct traffic to the backup chain even after the primary GSLB virtual server returns to the UP state. Used when spillover is configured for the virtual server. dynamicweight: choices: - 'SERVICECOUNT' - 'SERVICEWEIGHT' - 'DISABLED' description: - >- Specify if the appliance should consider the service count, service weights, or ignore both when using weight-based load balancing methods. The state of the number of services bound to the virtual server help the appliance to select the service. considereffectivestate: choices: - 'NONE' - 'STATE_ONLY' description: - >- If the primary state of all bound GSLB services is DOWN, consider the effective states of all the GSLB services, obtained through the Metrics Exchange Protocol (MEP), when determining the state of the GSLB virtual server. To consider the effective state, set the parameter to STATE_ONLY. To disregard the effective state, set the parameter to NONE. - >- The effective state of a GSLB service is the ability of the corresponding virtual server to serve traffic. The effective state of the load balancing virtual server, which is transferred to the GSLB service, is UP even if only one virtual server in the backup chain of virtual servers is in the UP state. comment: description: - "Any comments that you might want to associate with the GSLB virtual server." somethod: choices: - 'CONNECTION' - 'DYNAMICCONNECTION' - 'BANDWIDTH' - 'HEALTH' - 'NONE' description: - "Type of threshold that, when exceeded, triggers spillover. Available settings function as follows:" - "* C(CONNECTION) - Spillover occurs when the number of client connections exceeds the threshold." - >- * C(DYNAMICCONNECTION) - Spillover occurs when the number of client connections at the GSLB virtual server exceeds the sum of the maximum client (Max Clients) settings for bound GSLB services. Do not specify a spillover threshold for this setting, because the threshold is implied by the Max Clients settings of the bound GSLB services. - >- * C(BANDWIDTH) - Spillover occurs when the bandwidth consumed by the GSLB virtual server's incoming and outgoing traffic exceeds the threshold. - >- * C(HEALTH) - Spillover occurs when the percentage of weights of the GSLB services that are UP drops below the threshold. For example, if services gslbSvc1, gslbSvc2, and gslbSvc3 are bound to a virtual server, with weights 1, 2, and 3, and the spillover threshold is 50%, spillover occurs if gslbSvc1 and gslbSvc3 or gslbSvc2 and gslbSvc3 transition to DOWN. - "* C(NONE) - Spillover does not occur." sopersistence: choices: - 'enabled' - 'disabled' description: - >- If spillover occurs, maintain source IP address based persistence for both primary and backup GSLB virtual servers. sopersistencetimeout: description: - "Timeout for spillover persistence, in minutes." - "Default value: C(2)" - "Minimum value = C(2)" - "Maximum value = C(1440)" sothreshold: description: - >- Threshold at which spillover occurs. Specify an integer for the CONNECTION spillover method, a bandwidth value in kilobits per second for the BANDWIDTH method (do not enter the units), or a percentage for the HEALTH method (do not enter the percentage symbol). - "Minimum value = C(1)" - "Maximum value = C(4294967287)" sobackupaction: choices: - 'DROP' - 'ACCEPT' - 'REDIRECT' description: - >- Action to be performed if spillover is to take effect, but no backup chain to spillover is usable or exists. appflowlog: choices: - 'enabled' - 'disabled' description: - "Enable logging appflow flow information." domain_bindings: description: - >- List of bindings for domains for this glsb vserver. suboptions: cookietimeout: description: - Timeout, in minutes, for the GSLB site cookie. domainname: description: - Domain name for which to change the time to live (TTL) and/or backup service IP address. ttl: description: - Time to live (TTL) for the domain. sitedomainttl: description: - >- TTL, in seconds, for all internally created site domains (created when a site prefix is configured on a GSLB service) that are associated with this virtual server. - Minimum value = C(1) service_bindings: description: - List of bindings for gslb services bound to this gslb virtual server. suboptions: servicename: description: - Name of the GSLB service for which to change the weight. weight: description: - Weight to assign to the GSLB service. disabled: description: - When set to C(yes) the GSLB Vserver state will be set to C(disabled). - When set to C(no) the GSLB Vserver state will be set to C(enabled). - >- Note that due to limitations of the underlying NITRO API a C(disabled) state change alone does not cause the module result to report a changed status. type: bool default: false extends_documentation_fragment: - community.general.netscaler requirements: - nitro python sdk ''' EXAMPLES = ''' ''' RETURN = ''' ''' import copy try: from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbvserver import gslbvserver from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbvserver_gslbservice_binding import gslbvserver_gslbservice_binding from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbvserver_domain_binding import gslbvserver_domain_binding from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception PYTHON_SDK_IMPORTED = True except ImportError as e: PYTHON_SDK_IMPORTED = False from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.general.plugins.module_utils.network.netscaler.netscaler import ( ConfigProxy, get_nitro_client, netscaler_common_arguments, log, loglines, ensure_feature_is_enabled, get_immutables_intersection, complete_missing_attributes ) gslbvserver_domain_binding_rw_attrs = [ 'name', 'domainname', 'backupipflag', 'cookietimeout', 'backupip', 'ttl', 'sitedomainttl', 'cookie_domainflag', ] gslbvserver_gslbservice_binding_rw_attrs = [ 'name', 'servicename', 'weight', ] def get_actual_domain_bindings(client, module): log('get_actual_domain_bindings') # Get actual domain bindings and index them by domainname actual_domain_bindings = {} if gslbvserver_domain_binding.count(client, name=module.params['name']) != 0: # Get all domain bindings associated with the named gslb vserver fetched_domain_bindings = gslbvserver_domain_binding.get(client, name=module.params['name']) # index by domainname for binding in fetched_domain_bindings: complete_missing_attributes(binding, gslbvserver_domain_binding_rw_attrs, fill_value=None) actual_domain_bindings[binding.domainname] = binding return actual_domain_bindings def get_configured_domain_bindings_proxys(client, module): log('get_configured_domain_bindings_proxys') configured_domain_proxys = {} # Get configured domain bindings and index them by domainname if module.params['domain_bindings'] is not None: for configured_domain_binding in module.params['domain_bindings']: binding_values = copy.deepcopy(configured_domain_binding) binding_values['name'] = module.params['name'] gslbvserver_domain_binding_proxy = ConfigProxy( actual=gslbvserver_domain_binding(), client=client, attribute_values_dict=binding_values, readwrite_attrs=gslbvserver_domain_binding_rw_attrs, readonly_attrs=[], ) configured_domain_proxys[configured_domain_binding['domainname']] = gslbvserver_domain_binding_proxy return configured_domain_proxys def sync_domain_bindings(client, module): log('sync_domain_bindings') actual_domain_bindings = get_actual_domain_bindings(client, module) configured_domain_proxys = get_configured_domain_bindings_proxys(client, module) # Delete actual bindings not in configured bindings for domainname, actual_domain_binding in actual_domain_bindings.items(): if domainname not in configured_domain_proxys.keys(): log('Deleting absent binding for domain %s' % domainname) gslbvserver_domain_binding.delete(client, actual_domain_binding) # Delete actual bindings that differ from configured for proxy_key, binding_proxy in configured_domain_proxys.items(): if proxy_key in actual_domain_bindings: actual_binding = actual_domain_bindings[proxy_key] if not binding_proxy.has_equal_attributes(actual_binding): log('Deleting differing binding for domain %s' % binding_proxy.domainname) gslbvserver_domain_binding.delete(client, actual_binding) log('Adding anew binding for domain %s' % binding_proxy.domainname) binding_proxy.add() # Add configured domains that are missing from actual for proxy_key, binding_proxy in configured_domain_proxys.items(): if proxy_key not in actual_domain_bindings.keys(): log('Adding domain binding for domain %s' % binding_proxy.domainname) binding_proxy.add() def domain_bindings_identical(client, module): log('domain_bindings_identical') actual_domain_bindings = get_actual_domain_bindings(client, module) configured_domain_proxys = get_configured_domain_bindings_proxys(client, module) actual_keyset = set(actual_domain_bindings.keys()) configured_keyset = set(configured_domain_proxys.keys()) symmetric_difference = actual_keyset ^ configured_keyset log('symmetric difference %s' % symmetric_difference) if len(symmetric_difference) != 0: return False # Item for item equality test for key, proxy in configured_domain_proxys.items(): diff = proxy.diff_object(actual_domain_bindings[key]) if 'backupipflag' in diff: del diff['backupipflag'] if not len(diff) == 0: return False # Fallthrough to True result return True def get_actual_service_bindings(client, module): log('get_actual_service_bindings') # Get actual domain bindings and index them by domainname actual_bindings = {} if gslbvserver_gslbservice_binding.count(client, name=module.params['name']) != 0: # Get all service bindings associated with the named gslb vserver fetched_bindings = gslbvserver_gslbservice_binding.get(client, name=module.params['name']) # index by servicename for binding in fetched_bindings: complete_missing_attributes(binding, gslbvserver_gslbservice_binding_rw_attrs, fill_value=None) actual_bindings[binding.servicename] = binding return actual_bindings def get_configured_service_bindings(client, module): log('get_configured_service_bindings_proxys') configured_proxys = {} # Get configured domain bindings and index them by domainname if module.params['service_bindings'] is not None: for configured_binding in module.params['service_bindings']: binding_values = copy.deepcopy(configured_binding) binding_values['name'] = module.params['name'] gslbvserver_service_binding_proxy = ConfigProxy( actual=gslbvserver_gslbservice_binding(), client=client, attribute_values_dict=binding_values, readwrite_attrs=gslbvserver_gslbservice_binding_rw_attrs, readonly_attrs=[], ) configured_proxys[configured_binding['servicename']] = gslbvserver_service_binding_proxy return configured_proxys def sync_service_bindings(client, module): actual = get_actual_service_bindings(client, module) configured = get_configured_service_bindings(client, module) # Delete extraneous extraneous_service_bindings = list(set(actual.keys()) - set(configured.keys())) for servicename in extraneous_service_bindings: log('Deleting missing binding from service %s' % servicename) binding = actual[servicename] binding.name = module.params['name'] gslbvserver_gslbservice_binding.delete(client, binding) # Recreate different common_service_bindings = list(set(actual.keys()) & set(configured.keys())) for servicename in common_service_bindings: proxy = configured[servicename] binding = actual[servicename] if not proxy.has_equal_attributes(actual): log('Recreating differing service binding %s' % servicename) gslbvserver_gslbservice_binding.delete(client, binding) proxy.add() # Add missing missing_service_bindings = list(set(configured.keys()) - set(actual.keys())) for servicename in missing_service_bindings: proxy = configured[servicename] log('Adding missing service binding %s' % servicename) proxy.add() def service_bindings_identical(client, module): actual_bindings = get_actual_service_bindings(client, module) configured_proxys = get_configured_service_bindings(client, module) actual_keyset = set(actual_bindings.keys()) configured_keyset = set(configured_proxys.keys()) symmetric_difference = actual_keyset ^ configured_keyset if len(symmetric_difference) != 0: return False # Item for item equality test for key, proxy in configured_proxys.items(): if key in actual_bindings.keys(): if not proxy.has_equal_attributes(actual_bindings[key]): return False # Fallthrough to True result return True def gslb_vserver_exists(client, module): if gslbvserver.count_filtered(client, 'name:%s' % module.params['name']) > 0: return True else: return False def gslb_vserver_identical(client, module, gslb_vserver_proxy): gslb_vserver_list = gslbvserver.get_filtered(client, 'name:%s' % module.params['name']) diff_dict = gslb_vserver_proxy.diff_object(gslb_vserver_list[0]) if len(diff_dict) != 0: return False else: return True def all_identical(client, module, gslb_vserver_proxy): return ( gslb_vserver_identical(client, module, gslb_vserver_proxy) and domain_bindings_identical(client, module) and service_bindings_identical(client, module) ) def diff_list(client, module, gslb_vserver_proxy): gslb_vserver_list = gslbvserver.get_filtered(client, 'name:%s' % module.params['name']) return gslb_vserver_proxy.diff_object(gslb_vserver_list[0]) def do_state_change(client, module, gslb_vserver_proxy): if module.params['disabled']: log('Disabling glsb_vserver') result = gslbvserver.disable(client, gslb_vserver_proxy.actual) else: log('Enabling gslbvserver') result = gslbvserver.enable(client, gslb_vserver_proxy.actual) return result def main(): module_specific_arguments = dict( name=dict(type='str'), servicetype=dict( type='str', choices=[ 'HTTP', 'FTP', 'TCP', 'UDP', 'SSL', 'SSL_BRIDGE', 'SSL_TCP', 'NNTP', 'ANY', 'SIP_UDP', 'SIP_TCP', 'SIP_SSL', 'RADIUS', 'RDP', 'RTSP', 'MYSQL', 'MSSQL', 'ORACLE', ] ), dnsrecordtype=dict( type='str', choices=[ 'A', 'AAAA', 'CNAME', 'NAPTR', ] ), lbmethod=dict( type='str', choices=[ 'ROUNDROBIN', 'LEASTCONNECTION', 'LEASTRESPONSETIME', 'SOURCEIPHASH', 'LEASTBANDWIDTH', 'LEASTPACKETS', 'STATICPROXIMITY', 'RTT', 'CUSTOMLOAD', ] ), backuplbmethod=dict( type='str', choices=[ 'ROUNDROBIN', 'LEASTCONNECTION', 'LEASTRESPONSETIME', 'SOURCEIPHASH', 'LEASTBANDWIDTH', 'LEASTPACKETS', 'STATICPROXIMITY', 'RTT', 'CUSTOMLOAD', ] ), netmask=dict(type='str'), v6netmasklen=dict(type='float'), tolerance=dict(type='float'), persistencetype=dict( type='str', choices=[ 'SOURCEIP', 'NONE', ] ), persistenceid=dict(type='float'), persistmask=dict(type='str'), v6persistmasklen=dict(type='float'), timeout=dict(type='float'), mir=dict( type='str', choices=[ 'enabled', 'disabled', ] ), disableprimaryondown=dict( type='str', choices=[ 'enabled', 'disabled', ] ), dynamicweight=dict( type='str', choices=[ 'SERVICECOUNT', 'SERVICEWEIGHT', 'DISABLED', ] ), considereffectivestate=dict( type='str', choices=[ 'NONE', 'STATE_ONLY', ] ), comment=dict(type='str'), somethod=dict( type='str', choices=[ 'CONNECTION', 'DYNAMICCONNECTION', 'BANDWIDTH', 'HEALTH', 'NONE', ] ), sopersistence=dict( type='str', choices=[ 'enabled', 'disabled', ] ), sopersistencetimeout=dict(type='float'), sothreshold=dict(type='float'), sobackupaction=dict( type='str', choices=[ 'DROP', 'ACCEPT', 'REDIRECT', ] ), appflowlog=dict( type='str', choices=[ 'enabled', 'disabled', ] ), domainname=dict(type='str'), cookie_domain=dict(type='str'), ) hand_inserted_arguments = dict( domain_bindings=dict(type='list'), service_bindings=dict(type='list'), disabled=dict( type='bool', default=False, ), ) argument_spec = dict() argument_spec.update(netscaler_common_arguments) argument_spec.update(module_specific_arguments) argument_spec.update(hand_inserted_arguments) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, ) module_result = dict( changed=False, failed=False, loglines=loglines, ) # Fail the module if imports failed if not PYTHON_SDK_IMPORTED: module.fail_json(msg='Could not load nitro python sdk') # Fallthrough to rest of execution client = get_nitro_client(module) try: client.login() except nitro_exception as e: msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) module.fail_json(msg=msg) except Exception as e: if str(type(e)) == "": module.fail_json(msg='Connection error %s' % str(e)) elif str(type(e)) == "": module.fail_json(msg='SSL Error %s' % str(e)) else: module.fail_json(msg='Unexpected error during login %s' % str(e)) readwrite_attrs = [ 'name', 'servicetype', 'dnsrecordtype', 'lbmethod', 'backuplbmethod', 'netmask', 'v6netmasklen', 'tolerance', 'persistencetype', 'persistenceid', 'persistmask', 'v6persistmasklen', 'timeout', 'mir', 'disableprimaryondown', 'dynamicweight', 'considereffectivestate', 'comment', 'somethod', 'sopersistence', 'sopersistencetimeout', 'sothreshold', 'sobackupaction', 'appflowlog', 'cookie_domain', ] readonly_attrs = [ 'curstate', 'status', 'lbrrreason', 'iscname', 'sitepersistence', 'totalservices', 'activeservices', 'statechangetimesec', 'statechangetimemsec', 'tickssincelaststatechange', 'health', 'policyname', 'priority', 'gotopriorityexpression', 'type', 'vsvrbindsvcip', 'vsvrbindsvcport', '__count', ] immutable_attrs = [ 'name', 'servicetype', ] transforms = { 'mir': [lambda v: v.upper()], 'disableprimaryondown': [lambda v: v.upper()], 'sopersistence': [lambda v: v.upper()], 'appflowlog': [lambda v: v.upper()], } # Instantiate config proxy gslb_vserver_proxy = ConfigProxy( actual=gslbvserver(), client=client, attribute_values_dict=module.params, readwrite_attrs=readwrite_attrs, readonly_attrs=readonly_attrs, immutable_attrs=immutable_attrs, transforms=transforms, ) try: ensure_feature_is_enabled(client, 'GSLB') # Apply appropriate state if module.params['state'] == 'present': log('Applying state present') if not gslb_vserver_exists(client, module): log('Creating object') if not module.check_mode: gslb_vserver_proxy.add() sync_domain_bindings(client, module) sync_service_bindings(client, module) if module.params['save_config']: client.save_config() module_result['changed'] = True elif not all_identical(client, module, gslb_vserver_proxy): log('Entering update actions') # Check if we try to change value of immutable attributes if not gslb_vserver_identical(client, module, gslb_vserver_proxy): log('Updating gslb vserver') immutables_changed = get_immutables_intersection(gslb_vserver_proxy, diff_list(client, module, gslb_vserver_proxy).keys()) if immutables_changed != []: module.fail_json( msg='Cannot update immutable attributes %s' % (immutables_changed,), diff=diff_list(client, module, gslb_vserver_proxy), **module_result ) if not module.check_mode: gslb_vserver_proxy.update() # Update domain bindings if not domain_bindings_identical(client, module): if not module.check_mode: sync_domain_bindings(client, module) # Update service bindings if not service_bindings_identical(client, module): if not module.check_mode: sync_service_bindings(client, module) module_result['changed'] = True if not module.check_mode: if module.params['save_config']: client.save_config() else: module_result['changed'] = False if not module.check_mode: res = do_state_change(client, module, gslb_vserver_proxy) if res.errorcode != 0: msg = 'Error when setting disabled state. errorcode: %s message: %s' % (res.errorcode, res.message) module.fail_json(msg=msg, **module_result) # Sanity check for state if not module.check_mode: if not gslb_vserver_exists(client, module): module.fail_json(msg='GSLB Vserver does not exist', **module_result) if not gslb_vserver_identical(client, module, gslb_vserver_proxy): module.fail_json(msg='GSLB Vserver differs from configured', diff=diff_list(client, module, gslb_vserver_proxy), **module_result) if not domain_bindings_identical(client, module): module.fail_json(msg='Domain bindings differ from configured', diff=diff_list(client, module, gslb_vserver_proxy), **module_result) if not service_bindings_identical(client, module): module.fail_json(msg='Service bindings differ from configured', diff=diff_list(client, module, gslb_vserver_proxy), **module_result) elif module.params['state'] == 'absent': if gslb_vserver_exists(client, module): if not module.check_mode: gslb_vserver_proxy.delete() if module.params['save_config']: client.save_config() module_result['changed'] = True else: module_result['changed'] = False # Sanity check for state if not module.check_mode: if gslb_vserver_exists(client, module): module.fail_json(msg='GSLB Vserver still exists', **module_result) except nitro_exception as e: msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) module.fail_json(msg=msg, **module_result) client.logout() module.exit_json(**module_result) if __name__ == "__main__": main()