diff --git a/lib/ansible/modules/network/netscaler/netscaler_gslb_service.py b/lib/ansible/modules/network/netscaler/netscaler_gslb_service.py new file mode 100644 index 00000000000..6f372c2bb24 --- /dev/null +++ b/lib/ansible/modules/network/netscaler/netscaler_gslb_service.py @@ -0,0 +1,695 @@ +#!/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 = {'status': ['preview'], + 'supported_by': 'community', + 'metadata_version': '1.0'} + + +DOCUMENTATION = ''' +--- +module: netscaler_gslb_service +short_description: Manage gslb service entities in Netscaler. +description: + - Manage gslb service entities in Netscaler. + +version_added: "2.4" + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + servicename: + description: + - >- + Name for the GSLB service. 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 GSLB service is created. + - >- + - "Minimum length = 1" + + cnameentry: + description: + - "Canonical name of the GSLB service. Used in CNAME-based GSLB." + - "Minimum length = 1" + + + servername: + description: + - "Name of the server hosting the GSLB service." + - "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: + - "Type of service to create." + + port: + description: + - "Port on which the load balancing entity represented by this GSLB service listens." + - "Minimum value = 1" + - "Range 1 - 65535" + - "* in CLI is represented as 65535 in NITRO API" + + publicip: + description: + - >- + The public IP address that a NAT device translates to the GSLB service's private IP address. + Optional. + + publicport: + description: + - >- + The public port associated with the GSLB service's public IP address. The port is mapped to the + service's private port number. Applicable to the local GSLB service. Optional. + + maxclient: + description: + - >- + The maximum number of open connections that the service can support at any given time. A GSLB service + whose connection count reaches the maximum is not considered when a GSLB decision is made, until the + connection count drops below the maximum. + - "Minimum value = C(0)" + - "Maximum value = C(4294967294)" + + healthmonitor: + description: + - "Monitor the health of the GSLB service." + type: bool + + sitename: + description: + - "Name of the GSLB site to which the service belongs." + - "Minimum length = 1" + + cip: + choices: + - 'enabled' + - 'disabled' + description: + - >- + In the request that is forwarded to the GSLB service, insert a header that stores the client's IP + address. Client IP header insertion is used in connection-proxy based site persistence. + + cipheader: + description: + - >- + Name for the HTTP header that stores the client's IP address. Used with the Client IP option. If + client IP header insertion is enabled on the service and a name is not specified for the header, the + NetScaler appliance uses the name specified by the cipHeader parameter in the set ns param command + or, in the GUI, the Client IP Header parameter in the Configure HTTP Parameters dialog box. + - "Minimum length = 1" + + sitepersistence: + choices: + - 'ConnectionProxy' + - 'HTTPRedirect' + - 'NONE' + description: + - "Use cookie-based site persistence. Applicable only to C(HTTP) and C(SSL) GSLB services." + + siteprefix: + description: + - >- + The site's prefix string. When the service is bound to a GSLB virtual server, a GSLB site domain is + generated internally for each bound service-domain pair by concatenating the site prefix of the + service and the name of the domain. If the special string NONE is specified, the site-prefix string + is unset. When implementing HTTP redirect site persistence, the NetScaler appliance redirects GSLB + requests to GSLB services by using their site domains. + + clttimeout: + description: + - >- + Idle time, in seconds, after which a client connection is terminated. Applicable if connection proxy + based site persistence is used. + - "Minimum value = 0" + - "Maximum value = 31536000" + + maxbandwidth: + description: + - >- + Integer specifying the maximum bandwidth allowed for the service. A GSLB service whose bandwidth + reaches the maximum is not considered when a GSLB decision is made, until its bandwidth consumption + drops below the maximum. + + downstateflush: + choices: + - 'enabled' + - 'disabled' + description: + - >- + Flush all active transactions associated with the GSLB service when its state transitions from UP to + DOWN. Do not enable this option for services that must complete their transactions. Applicable if + connection proxy based site persistence is used. + + maxaaausers: + description: + - >- + Maximum number of SSL VPN users that can be logged on concurrently to the VPN virtual server that is + represented by this GSLB service. A GSLB service whose user count reaches the maximum is not + considered when a GSLB decision is made, until the count drops below the maximum. + - "Minimum value = C(0)" + - "Maximum value = C(65535)" + + monthreshold: + description: + - >- + Monitoring threshold value for the GSLB service. If the sum of the weights of the monitors that are + bound to this GSLB service and are in the UP state is not equal to or greater than this threshold + value, the service is marked as DOWN. + - "Minimum value = C(0)" + - "Maximum value = C(65535)" + + hashid: + description: + - "Unique hash identifier for the GSLB service, used by hash based load balancing methods." + - "Minimum value = C(1)" + + comment: + description: + - "Any comments that you might want to associate with the GSLB service." + + appflowlog: + choices: + - 'enabled' + - 'disabled' + description: + - "Enable logging appflow flow information." + + ipaddress: + description: + - >- + IP address for the GSLB service. Should represent a load balancing, content switching, or VPN virtual + server on the NetScaler appliance, or the IP address of another load balancing device. + + monitor_bindings: + description: + - Bind monitors to this gslb service + suboptions: + + weight: + description: + - Weight to assign to the monitor-service binding. + - A larger number specifies a greater weight. + - Contributes to the monitoring threshold, which determines the state of the service. + - Minimum value = C(1) + - Maximum value = C(100) + + monitor_name: + description: + - Monitor name. + +extends_documentation_fragment: netscaler +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +- name: Setup gslb service 2 + + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + + netscaler_gslb_service: + operation: present + + servicename: gslb-service-2 + cnameentry: example.com + sitename: gslb-site-1 +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: "['message 1', 'message 2']" + +msg: + description: Message detailing the failure reason + returned: failure + type: string + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dictionary + sample: "{ 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' }" +''' + +import copy + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.netscaler import ( + ConfigProxy, + get_nitro_client, + netscaler_common_arguments, + log, + loglines, + ensure_feature_is_enabled, + monkey_patch_nitro_api, + get_immutables_intersection, +) + +try: + monkey_patch_nitro_api() + from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbservice import gslbservice + from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbservice_lbmonitor_binding import gslbservice_lbmonitor_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 + + +def gslb_service_exists(client, module): + if gslbservice.count_filtered(client, 'servicename:%s' % module.params['servicename']) > 0: + return True + else: + return False + + +def gslb_service_identical(client, module, gslb_service_proxy): + gslb_service_list = gslbservice.get_filtered(client, 'servicename:%s' % module.params['servicename']) + diff_dict = gslb_service_proxy.diff_object(gslb_service_list[0]) + # Ignore ip attribute missing + if 'ip' in diff_dict: + del diff_dict['ip'] + if len(diff_dict) == 0: + return True + else: + return False + + +def get_actual_monitor_bindings(client, module): + log('get_actual_monitor_bindings') + # Get actual monitor bindings and index them by monitor_name + actual_monitor_bindings = {} + if gslbservice_lbmonitor_binding.count(client, servicename=module.params['servicename']) != 0: + # Get all monitor bindings associated with the named gslb vserver + fetched_bindings = gslbservice_lbmonitor_binding.get(client, servicename=module.params['servicename']) + # index by monitor name + for binding in fetched_bindings: + # complete_missing_attributes(binding, gslbservice_lbmonitor_binding_rw_attrs, fill_value=None) + actual_monitor_bindings[binding.monitor_name] = binding + return actual_monitor_bindings + + +def get_configured_monitor_bindings(client, module): + log('get_configured_monitor_bindings') + configured_monitor_proxys = {} + gslbservice_lbmonitor_binding_rw_attrs = [ + 'weight', + 'servicename', + 'monitor_name', + ] + # Get configured monitor bindings and index them by monitor_name + if module.params['monitor_bindings'] is not None: + for configured_monitor_bindings in module.params['monitor_bindings']: + binding_values = copy.deepcopy(configured_monitor_bindings) + binding_values['servicename'] = module.params['servicename'] + proxy = ConfigProxy( + actual=gslbservice_lbmonitor_binding(), + client=client, + attribute_values_dict=binding_values, + readwrite_attrs=gslbservice_lbmonitor_binding_rw_attrs, + readonly_attrs=[], + ) + configured_monitor_proxys[configured_monitor_bindings['monitor_name']] = proxy + return configured_monitor_proxys + + +def monitor_bindings_identical(client, module): + log('monitor_bindings_identical') + actual_bindings = get_actual_monitor_bindings(client, module) + configured_proxys = get_configured_monitor_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: + log('Symmetric difference %s' % symmetric_difference) + return False + + # Item for item equality test + for key, proxy in configured_proxys.items(): + if not proxy.has_equal_attributes(actual_bindings[key]): + log('monitor binding difference %s' % proxy.diff_object(actual_bindings[key])) + return False + + # Fallthrough to True result + return True + + +def sync_monitor_bindings(client, module): + log('sync_monitor_bindings') + + actual_monitor_bindings = get_actual_monitor_bindings(client, module) + configured_monitor_proxys = get_configured_monitor_bindings(client, module) + + # Delete actual bindings not in configured bindings + for monitor_name, actual_binding in actual_monitor_bindings.items(): + if monitor_name not in configured_monitor_proxys.keys(): + log('Deleting absent binding for monitor %s' % monitor_name) + log('dir is %s' % dir(actual_binding)) + gslbservice_lbmonitor_binding.delete(client, actual_binding) + + # Delete and re-add actual bindings that differ from configured + for proxy_key, binding_proxy in configured_monitor_proxys.items(): + if proxy_key in actual_monitor_bindings: + actual_binding = actual_monitor_bindings[proxy_key] + if not binding_proxy.has_equal_attributes(actual_binding): + log('Deleting differing binding for monitor %s' % actual_binding.monitor_name) + log('dir %s' % dir(actual_binding)) + log('attribute monitor_name %s' % getattr(actual_binding, 'monitor_name')) + log('attribute monitorname %s' % getattr(actual_binding, 'monitorname', None)) + gslbservice_lbmonitor_binding.delete(client, actual_binding) + log('Adding anew binding for monitor %s' % binding_proxy.monitor_name) + binding_proxy.add() + + # Add configured monitors that are missing from actual + for proxy_key, binding_proxy in configured_monitor_proxys.items(): + if proxy_key not in actual_monitor_bindings.keys(): + log('Adding monitor binding for monitor %s' % binding_proxy.monitor_name) + binding_proxy.add() + + +def diff_list(client, module, gslb_service_proxy): + gslb_service_list = gslbservice.get_filtered(client, 'servicename:%s' % module.params['servicename']) + diff_list = gslb_service_proxy.diff_object(gslb_service_list[0]) + if 'ip' in diff_list: + del diff_list['ip'] + return diff_list + + +def all_identical(client, module, gslb_service_proxy): + return gslb_service_identical(client, module, gslb_service_proxy) and monitor_bindings_identical(client, module) + + +def main(): + + module_specific_arguments = dict( + servicename=dict(type='str'), + cnameentry=dict(type='str'), + servername=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', + ] + ), + port=dict(type='int'), + publicip=dict(type='str'), + publicport=dict(type='int'), + maxclient=dict(type='float'), + healthmonitor=dict(type='bool'), + sitename=dict(type='str'), + cip=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + cipheader=dict(type='str'), + sitepersistence=dict( + type='str', + choices=[ + 'ConnectionProxy', + 'HTTPRedirect', + 'NONE', + ] + ), + siteprefix=dict(type='str'), + clttimeout=dict(type='float'), + maxbandwidth=dict(type='float'), + downstateflush=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + maxaaausers=dict(type='float'), + monthreshold=dict(type='float'), + hashid=dict(type='float'), + comment=dict(type='str'), + appflowlog=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + ipaddress=dict(type='str'), + ) + + hand_inserted_arguments = dict( + monitor_bindings=dict(type='list'), + ) + + 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 = [ + 'servicename', + 'cnameentry', + 'ip', + 'servername', + 'servicetype', + 'port', + 'publicip', + 'publicport', + 'maxclient', + 'healthmonitor', + 'sitename', + 'cip', + 'cipheader', + 'sitepersistence', + 'siteprefix', + 'clttimeout', + 'maxbandwidth', + 'downstateflush', + 'maxaaausers', + 'monthreshold', + 'hashid', + 'comment', + 'appflowlog', + 'ipaddress', + ] + + readonly_attrs = [ + 'gslb', + 'svrstate', + 'svreffgslbstate', + 'gslbthreshold', + 'gslbsvcstats', + 'monstate', + 'preferredlocation', + 'monitor_state', + 'statechangetimesec', + 'tickssincelaststatechange', + 'threshold', + 'clmonowner', + 'clmonview', + '__count', + ] + + immutable_attrs = [ + 'servicename', + 'cnameentry', + 'ip', + 'servername', + 'servicetype', + 'port', + 'sitename', + 'state', + 'cipheader', + 'cookietimeout', + 'clttimeout', + 'svrtimeout', + 'viewip', + 'monitor_name_svc', + 'newname', + ] + + transforms = { + 'healthmonitor': ['bool_yes_no'], + 'cip': [lambda v: v.upper()], + 'downstateflush': [lambda v: v.upper()], + 'appflowlog': [lambda v: v.upper()], + } + + # params = copy.deepcopy(module.params) + module.params['ip'] = module.params['ipaddress'] + + # Instantiate config proxy + gslb_service_proxy = ConfigProxy( + actual=gslbservice(), + client=client, + attribute_values_dict=module.params, + transforms=transforms, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + ) + + try: + ensure_feature_is_enabled(client, 'GSLB') + # Apply appropriate state + if module.params['state'] == 'present': + if not gslb_service_exists(client, module): + if not module.check_mode: + gslb_service_proxy.add() + sync_monitor_bindings(client, module) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not all_identical(client, module, gslb_service_proxy): + + # Check if we try to change value of immutable attributes + immutables_changed = get_immutables_intersection(gslb_service_proxy, diff_list(client, module, gslb_service_proxy).keys()) + if immutables_changed != []: + module.fail_json( + msg='Cannot update immutable attributes %s' % (immutables_changed,), + diff=diff_list(client, module, gslb_service_proxy), + **module_result + ) + + # Update main configuration object + if not gslb_service_identical(client, module, gslb_service_proxy): + if not module.check_mode: + gslb_service_proxy.update() + + # Update monitor bindigns + if not monitor_bindings_identical(client, module): + if not module.check_mode: + sync_monitor_bindings(client, module) + + # Fallthrough to save and change status update + module_result['changed'] = True + if module.params['save_config']: + client.save_config() + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + if not gslb_service_exists(client, module): + module.fail_json(msg='GSLB service does not exist', **module_result) + if not gslb_service_identical(client, module, gslb_service_proxy): + module.fail_json( + msg='GSLB service differs from configured', + diff=diff_list(client, module, gslb_service_proxy), + **module_result + ) + if not monitor_bindings_identical(client, module): + module.fail_json( + msg='Monitor bindings differ from configured', + diff=diff_list(client, module, gslb_service_proxy), + **module_result + ) + + elif module.params['state'] == 'absent': + if gslb_service_exists(client, module): + if not module.check_mode: + gslb_service_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_service_exists(client, module): + module.fail_json(msg='GSLB service 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() diff --git a/test/integration/roles/netscaler_gslb_service/defaults/main.yaml b/test/integration/roles/netscaler_gslb_service/defaults/main.yaml new file mode 100644 index 00000000000..641801f6600 --- /dev/null +++ b/test/integration/roles/netscaler_gslb_service/defaults/main.yaml @@ -0,0 +1,6 @@ +--- +testcase: "*" +test_cases: [] + +nitro_user: nsroot +nitro_pass: nsroot diff --git a/test/integration/roles/netscaler_gslb_service/tasks/main.yaml b/test/integration/roles/netscaler_gslb_service/tasks/main.yaml new file mode 100644 index 00000000000..36fde513843 --- /dev/null +++ b/test/integration/roles/netscaler_gslb_service/tasks/main.yaml @@ -0,0 +1,7 @@ +--- + +- { include: testbed.yaml, state: present } + +- { include: nitro.yaml, tags: ['nitro'] } + +- { include: testbed.yaml, state: absent } diff --git a/test/integration/roles/netscaler_gslb_service/tasks/nitro.yaml b/test/integration/roles/netscaler_gslb_service/tasks/nitro.yaml new file mode 100644 index 00000000000..00ab502dda9 --- /dev/null +++ b/test/integration/roles/netscaler_gslb_service/tasks/nitro.yaml @@ -0,0 +1,14 @@ +- name: collect all nitro test cases + find: + paths: "{{ role_path }}/tests/nitro" + patterns: "{{ testcase }}.yaml" + register: test_cases + +- name: set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test case + include: "{{ test_case_to_run }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/roles/netscaler_gslb_service/tasks/testbed.yaml b/test/integration/roles/netscaler_gslb_service/tasks/testbed.yaml new file mode 100644 index 00000000000..f38fc4f015d --- /dev/null +++ b/test/integration/roles/netscaler_gslb_service/tasks/testbed.yaml @@ -0,0 +1,34 @@ +--- + +- name: Setup gslb site + delegate_to: localhost + netscaler_gslb_site: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: "{{ state }}" + + sitename: gslb-site-1 + siteipaddress: 192.168.1.1 + sitetype: LOCAL + publicip: 192.168.1.1 + metricexchange: ENABLED + nwmetricexchange: ENABLED + sessionexchange: ENABLED + triggermonitor: ALWAYS + +- name: setup lb monitor + delegate_to: localhost + netscaler_lb_monitor: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: "{{ state }}" + + monitorname: lb-monitor-1 + type: TCP-ECV + + send: sendstring + recv: recvstring diff --git a/test/integration/roles/netscaler_gslb_service/tests/nitro/cname/remove.yaml b/test/integration/roles/netscaler_gslb_service/tests/nitro/cname/remove.yaml new file mode 100644 index 00000000000..ee39fec378c --- /dev/null +++ b/test/integration/roles/netscaler_gslb_service/tests/nitro/cname/remove.yaml @@ -0,0 +1,12 @@ +--- + +- name: Setup gslb service cname + + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + + netscaler_gslb_service: + operation: absent + + servicename: gslb-service-2 diff --git a/test/integration/roles/netscaler_gslb_service/tests/nitro/cname/setup.yaml b/test/integration/roles/netscaler_gslb_service/tests/nitro/cname/setup.yaml new file mode 100644 index 00000000000..65717b686ea --- /dev/null +++ b/test/integration/roles/netscaler_gslb_service/tests/nitro/cname/setup.yaml @@ -0,0 +1,14 @@ +--- + +- name: Setup gslb service 2 + + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + + netscaler_gslb_service: + operation: present + + servicename: gslb-service-2 + cnameentry: example.com + sitename: gslb-site-1 diff --git a/test/integration/roles/netscaler_gslb_service/tests/nitro/cname/update.yaml b/test/integration/roles/netscaler_gslb_service/tests/nitro/cname/update.yaml new file mode 100644 index 00000000000..5509d808a36 --- /dev/null +++ b/test/integration/roles/netscaler_gslb_service/tests/nitro/cname/update.yaml @@ -0,0 +1,15 @@ +--- + +- name: Setup gslb service 2 + + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + + netscaler_gslb_service: + operation: present + + servicename: gslb-service-2 + cnameentry: example.com + comment: added comment + sitename: gslb-site-1 diff --git a/test/integration/roles/netscaler_gslb_service/tests/nitro/http.yaml b/test/integration/roles/netscaler_gslb_service/tests/nitro/http.yaml new file mode 100644 index 00000000000..1d61889fa38 --- /dev/null +++ b/test/integration/roles/netscaler_gslb_service/tests/nitro/http.yaml @@ -0,0 +1,85 @@ +--- + +- include: "{{ role_path }}/tests/nitro/http/setup.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/http/setup.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/http/setup.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/http/setup.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/http/update.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/http/update.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/http/update.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/http/update.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/http/remove.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/http/remove.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/http/remove.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/http/remove.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed diff --git a/test/integration/roles/netscaler_gslb_service/tests/nitro/http/remove.yaml b/test/integration/roles/netscaler_gslb_service/tests/nitro/http/remove.yaml new file mode 100644 index 00000000000..2fb8386076b --- /dev/null +++ b/test/integration/roles/netscaler_gslb_service/tests/nitro/http/remove.yaml @@ -0,0 +1,15 @@ +--- + +- name: Remove gslb service + + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + + netscaler_gslb_service: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: absent + servicename: gslb-service-1 diff --git a/test/integration/roles/netscaler_gslb_service/tests/nitro/http/setup.yaml b/test/integration/roles/netscaler_gslb_service/tests/nitro/http/setup.yaml new file mode 100644 index 00000000000..4b4a0bc46b9 --- /dev/null +++ b/test/integration/roles/netscaler_gslb_service/tests/nitro/http/setup.yaml @@ -0,0 +1,39 @@ +--- + +- name: Setup gslb service + + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + + netscaler_gslb_service: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + servicename: gslb-service-1 + servicetype: HTTP + sitename: gslb-site-1 + ipaddress: 10.10.10.11 + port: 80 + publicip: 10.10.10.11 + publicport: 80 + maxclient: 100 + healthmonitor: "NO" + cip: enabled + cipheader: hello + sitepersistence: NONE + siteprefix: prefix + clttimeout: 100 + maxbandwidth: 100 + downstateflush: enabled + maxaaausers: 100 + monthreshold: 500 + hashid: 10 + comment: cool gslb service! + appflowlog: enabled + + monitor_bindings: + - + monitor_name: lb-monitor-1 + weight: 100 diff --git a/test/integration/roles/netscaler_gslb_service/tests/nitro/http/update.yaml b/test/integration/roles/netscaler_gslb_service/tests/nitro/http/update.yaml new file mode 100644 index 00000000000..405b550560c --- /dev/null +++ b/test/integration/roles/netscaler_gslb_service/tests/nitro/http/update.yaml @@ -0,0 +1,34 @@ +--- + +- name: Setup gslb service + + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + + netscaler_gslb_service: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + servicename: gslb-service-1 + servicetype: HTTP + sitename: gslb-site-1 + ipaddress: 10.10.10.11 + port: 80 + publicip: 10.10.10.11 + publicport: 80 + maxclient: 100 + healthmonitor: "NO" + cip: enabled + cipheader: hello + sitepersistence: NONE + siteprefix: prefix + clttimeout: 100 + maxbandwidth: 100 + downstateflush: enabled + maxaaausers: 100 + monthreshold: 500 + hashid: 10 + comment: some other comment + appflowlog: enabled diff --git a/test/integration/roles/netscaler_gslb_service/tests/nitro/servername/remove.yaml b/test/integration/roles/netscaler_gslb_service/tests/nitro/servername/remove.yaml new file mode 100644 index 00000000000..a5577102977 --- /dev/null +++ b/test/integration/roles/netscaler_gslb_service/tests/nitro/servername/remove.yaml @@ -0,0 +1,12 @@ +--- + +- name: Remove gslb-service-3 + + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + + netscaler_gslb_service: + operation: absent + + servicename: gslb-service-3 diff --git a/test/integration/roles/netscaler_gslb_service/tests/nitro/servername/setup.yaml b/test/integration/roles/netscaler_gslb_service/tests/nitro/servername/setup.yaml new file mode 100644 index 00000000000..2060b34972a --- /dev/null +++ b/test/integration/roles/netscaler_gslb_service/tests/nitro/servername/setup.yaml @@ -0,0 +1,16 @@ +--- + +- name: Setup gslb service 3 + + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + + netscaler_gslb_service: + operation: present + + servicename: gslb-service-3 + servername: 10.10.10.10 + servicetype: HTTP + port: 80 + sitename: gslb-site-1 diff --git a/test/integration/roles/netscaler_gslb_service/tests/nitro/servername/update.yaml b/test/integration/roles/netscaler_gslb_service/tests/nitro/servername/update.yaml new file mode 100644 index 00000000000..814edae69d4 --- /dev/null +++ b/test/integration/roles/netscaler_gslb_service/tests/nitro/servername/update.yaml @@ -0,0 +1,17 @@ +--- + +- name: Setup gslb service 3 + + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + + netscaler_gslb_service: + operation: present + + servicename: gslb-service-3 + servername: 10.10.10.10 + servicetype: HTTP + port: 80 + comment: added comment + sitename: gslb-site-1 diff --git a/test/integration/roles/netscaler_service/tests/nitro/http_service/setup.yaml b/test/integration/roles/netscaler_service/tests/nitro/http_service/setup.yaml index c55aaac95b9..9f58802ac6a 100644 --- a/test/integration/roles/netscaler_service/tests/nitro/http_service/setup.yaml +++ b/test/integration/roles/netscaler_service/tests/nitro/http_service/setup.yaml @@ -23,7 +23,6 @@ cipheader: client-ip usip: yes useproxyport: yes - sc: off sp: off rtspsessionidremap: off clttimeout: 100 diff --git a/test/integration/roles/netscaler_service/tests/nitro/http_service/update.yaml b/test/integration/roles/netscaler_service/tests/nitro/http_service/update.yaml index 21640617a7f..b606423da54 100644 --- a/test/integration/roles/netscaler_service/tests/nitro/http_service/update.yaml +++ b/test/integration/roles/netscaler_service/tests/nitro/http_service/update.yaml @@ -23,7 +23,6 @@ cipheader: client-ip usip: yes useproxyport: yes - sc: off sp: off rtspsessionidremap: off clttimeout: 100 @@ -40,7 +39,6 @@ comment: some comment appflowlog: ENABLED processlocal: ENABLED - netprofile: net-profile-1 monitor_bindings: - monitorname: http diff --git a/test/units/modules/network/netscaler/test_netscaler_gslb_service.py b/test/units/modules/network/netscaler/test_netscaler_gslb_service.py new file mode 100644 index 00000000000..0f57ae7fedf --- /dev/null +++ b/test/units/modules/network/netscaler/test_netscaler_gslb_service.py @@ -0,0 +1,730 @@ + +# Copyright (c) 2017 Citrix Systems +# +# 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 . +# + +from ansible.compat.tests.mock import patch, Mock, MagicMock, call +from .netscaler_module import TestModule, nitro_base_patcher, set_module_args + +import sys + +if sys.version_info[:2] != (2, 6): + import requests + + +class TestNetscalerGSLBSiteModule(TestModule): + + @classmethod + def setUpClass(cls): + class MockException(Exception): + pass + + cls.MockException = MockException + + m = MagicMock() + nssrc_modules_mock = { + 'nssrc.com.citrix.netscaler.nitro.resource.config.gslb': m, + 'nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbservice': m, + 'nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbservice.gslbservice': m, + 'nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbservice_lbmonitor_binding': m, + 'nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbservice_lbmonitor_binding.gslbservice_lbmonitor_binding': m, + + # The following are needed because of monkey_patch_nitro_api() + 'nssrc.com.citrix.netscaler.nitro.resource.base': m, + 'nssrc.com.citrix.netscaler.nitro.resource.base.Json': m, + 'nssrc.com.citrix.netscaler.nitro.resource.base.Json.Json': m, + 'nssrc.com.citrix.netscaler.nitro.util': m, + 'nssrc.com.citrix.netscaler.nitro.util.nitro_util': m, + 'nssrc.com.citrix.netscaler.nitro.util.nitro_util.nitro_util': m, + } + + cls.nitro_specific_patcher = patch.dict(sys.modules, nssrc_modules_mock) + cls.nitro_base_patcher = nitro_base_patcher + + @classmethod + def tearDownClass(cls): + cls.nitro_base_patcher.stop() + cls.nitro_specific_patcher.stop() + + def setUp(self): + self.nitro_base_patcher.start() + self.nitro_specific_patcher.start() + + # Setup minimal required arguments to pass AnsibleModule argument parsing + + def tearDown(self): + self.nitro_base_patcher.stop() + self.nitro_specific_patcher.stop() + + def test_graceful_nitro_api_import_error(self): + # Stop nitro api patching to cause ImportError + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + self.nitro_base_patcher.stop() + self.nitro_specific_patcher.stop() + from ansible.modules.network.netscaler import netscaler_gslb_service + self.module = netscaler_gslb_service + result = self.failed() + self.assertEqual(result['msg'], 'Could not load nitro python sdk') + + def test_graceful_nitro_error_on_login(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + class MockException(Exception): + def __init__(self, *args, **kwargs): + self.errorcode = 0 + self.message = '' + + client_mock = Mock() + client_mock.login = Mock(side_effect=MockException) + m = Mock(return_value=client_mock) + with patch('ansible.modules.network.netscaler.netscaler_gslb_service.get_nitro_client', m): + with patch('ansible.modules.network.netscaler.netscaler_gslb_service.nitro_exception', MockException): + self.module = netscaler_gslb_service + result = self.failed() + self.assertTrue(result['msg'].startswith('nitro exception'), msg='nitro exception during login not handled properly') + + def test_graceful_no_connection_error(self): + + if sys.version_info[:2] == (2, 6): + self.skipTest('requests library not available under python2.6') + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + class MockException(Exception): + pass + client_mock = Mock() + attrs = {'login.side_effect': requests.exceptions.ConnectionError} + client_mock.configure_mock(**attrs) + m = Mock(return_value=client_mock) + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + get_nitro_client=m, + nitro_exception=MockException, + ): + self.module = netscaler_gslb_service + result = self.failed() + self.assertTrue(result['msg'].startswith('Connection error'), msg='Connection error was not handled gracefully') + + def test_graceful_login_error(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + if sys.version_info[:2] == (2, 6): + self.skipTest('requests library not available under python2.6') + + class MockException(Exception): + pass + client_mock = Mock() + attrs = {'login.side_effect': requests.exceptions.SSLError} + client_mock.configure_mock(**attrs) + m = Mock(return_value=client_mock) + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + get_nitro_client=m, + monkey_patch_nitro_api=Mock(), + nitro_exception=MockException, + ): + self.module = netscaler_gslb_service + result = self.failed() + self.assertTrue(result['msg'].startswith('SSL Error'), msg='SSL Error was not handled gracefully') + + def test_ensure_feature_is_enabled_called(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + gslb_service_proxy_mock = Mock() + ensure_feature_is_enabled_mock = Mock() + client_mock = Mock() + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + get_nitro_client=Mock(return_value=client_mock), + gslb_service_exists=Mock(side_effect=[False, True]), + gslb_service_identical=Mock(side_effect=[True]), + nitro_exception=self.MockException, + ensure_feature_is_enabled=ensure_feature_is_enabled_mock, + monkey_patch_nitro_api=Mock(), + ConfigProxy=Mock(return_value=gslb_service_proxy_mock), + ): + self.module = netscaler_gslb_service + self.exited() + ensure_feature_is_enabled_mock.assert_called_with(client_mock, 'GSLB') + + def test_save_config_called_on_state_present(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + gslb_service_proxy_mock = Mock() + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + get_nitro_client=m, + gslb_service_exists=Mock(side_effect=[False, True]), + gslb_service_identical=Mock(side_effect=[True]), + nitro_exception=self.MockException, + ensure_feature_is_enabled=Mock(), + monkey_patch_nitro_api=Mock(), + ConfigProxy=Mock(return_value=gslb_service_proxy_mock), + ): + self.module = netscaler_gslb_service + self.exited() + self.assertIn(call.save_config(), client_mock.mock_calls) + + def test_save_config_called_on_state_absent(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='absent', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + gslb_service_proxy_mock = Mock() + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + get_nitro_client=m, + gslb_service_exists=Mock(side_effect=[True, False]), + nitro_exception=self.MockException, + ensure_feature_is_enabled=Mock(), + monkey_patch_nitro_api=Mock(), + ConfigProxy=Mock(return_value=gslb_service_proxy_mock), + ): + self.module = netscaler_gslb_service + self.exited() + self.assertIn(call.save_config(), client_mock.mock_calls) + + def test_save_config_not_called_on_state_present(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + save_config=False, + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + gslb_service_proxy_mock = Mock() + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + get_nitro_client=m, + gslb_service_exists=Mock(side_effect=[False, True]), + gslb_service_identical=Mock(side_effect=[True]), + nitro_exception=self.MockException, + ensure_feature_is_enabled=Mock(), + monkey_patch_nitro_api=Mock(), + ConfigProxy=Mock(return_value=gslb_service_proxy_mock), + ): + self.module = netscaler_gslb_service + self.exited() + self.assertNotIn(call.save_config(), client_mock.mock_calls) + + def test_save_config_not_called_on_state_absent(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='absent', + save_config=False, + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + gslb_service_proxy_mock = Mock() + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + get_nitro_client=m, + gslb_service_exists=Mock(side_effect=[True, False]), + nitro_exception=self.MockException, + ensure_feature_is_enabled=Mock(), + monkey_patch_nitro_api=Mock(), + ConfigProxy=Mock(return_value=gslb_service_proxy_mock), + ): + self.module = netscaler_gslb_service + self.exited() + self.assertNotIn(call.save_config(), client_mock.mock_calls) + + def test_new_gslb_site_execution_flow(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + glsb_service_proxy_attrs = { + 'diff_object.return_value': {}, + } + gslb_service_proxy_mock = Mock() + gslb_service_proxy_mock.configure_mock(**glsb_service_proxy_attrs) + config_proxy_mock = Mock(return_value=gslb_service_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + get_nitro_client=m, + gslb_service_exists=Mock(side_effect=[False, True]), + gslb_service_identical=Mock(side_effect=[True]), + nitro_exception=self.MockException, + ensure_feature_is_enabled=Mock(), + monkey_patch_nitro_api=Mock(), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_gslb_service + self.exited() + gslb_service_proxy_mock.assert_has_calls([call.add()]) + + def test_modified_gslb_site_execution_flow(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + glsb_service_proxy_attrs = { + 'diff_object.return_value': {}, + } + gslb_service_proxy_mock = Mock() + gslb_service_proxy_mock.configure_mock(**glsb_service_proxy_attrs) + config_proxy_mock = Mock(return_value=gslb_service_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + gslb_service_exists=Mock(side_effect=[True, True]), + gslb_service_identical=Mock(side_effect=[False, False, True]), + monitor_bindings_identical=Mock(side_effect=[True, True, True]), + ensure_feature_is_enabled=Mock(), + monkey_patch_nitro_api=Mock(), + nitro_exception=self.MockException, + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_gslb_service + self.exited() + gslb_service_proxy_mock.assert_has_calls([call.update()]) + + def test_absent_gslb_site_execution_flow(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='absent', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + glsb_service_proxy_attrs = { + 'diff_object.return_value': {}, + } + gslb_service_proxy_mock = Mock() + gslb_service_proxy_mock.configure_mock(**glsb_service_proxy_attrs) + config_proxy_mock = Mock(return_value=gslb_service_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + gslb_service_exists=Mock(side_effect=[True, False]), + gslb_service_identical=Mock(side_effect=[False, True]), + ensure_feature_is_enabled=Mock(), + monkey_patch_nitro_api=Mock(), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_gslb_service + self.exited() + gslb_service_proxy_mock.assert_has_calls([call.delete()]) + + def test_present_gslb_service_identical_flow(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + glsb_service_proxy_attrs = { + 'diff_object.return_value': {}, + } + gslb_service_proxy_mock = Mock() + gslb_service_proxy_mock.configure_mock(**glsb_service_proxy_attrs) + config_proxy_mock = Mock(return_value=gslb_service_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + gslb_service_exists=Mock(side_effect=[True, True]), + gslb_service_identical=Mock(side_effect=[True, True]), + nitro_exception=self.MockException, + ensure_feature_is_enabled=Mock(), + monkey_patch_nitro_api=Mock(), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_gslb_service + self.exited() + gslb_service_proxy_mock.assert_not_called() + + def test_absent_gslb_site_noop_flow(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='absent', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + glsb_service_proxy_attrs = { + 'diff_object.return_value': {}, + } + gslb_service_proxy_mock = Mock() + gslb_service_proxy_mock.configure_mock(**glsb_service_proxy_attrs) + config_proxy_mock = Mock(return_value=gslb_service_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + gslb_service_exists=Mock(side_effect=[False, False]), + gslb_service_identical=Mock(side_effect=[False, False]), + nitro_exception=self.MockException, + ensure_feature_is_enabled=Mock(), + monkey_patch_nitro_api=Mock(), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_gslb_service + self.exited() + gslb_service_proxy_mock.assert_not_called() + + def test_present_gslb_site_failed_update(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + glsb_service_proxy_attrs = { + 'diff_object.return_value': {}, + } + gslb_service_proxy_mock = Mock() + gslb_service_proxy_mock.configure_mock(**glsb_service_proxy_attrs) + config_proxy_mock = Mock(return_value=gslb_service_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + nitro_exception=self.MockException, + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + gslb_service_exists=Mock(side_effect=[True, True]), + gslb_service_identical=Mock(side_effect=[False, False, False]), + monitor_bindings_identical=Mock(side_effect=[True, True, True]), + ensure_feature_is_enabled=Mock(), + monkey_patch_nitro_api=Mock(), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_gslb_service + result = self.failed() + self.assertEqual(result['msg'], 'GSLB service differs from configured') + self.assertTrue(result['failed']) + + def test_present_gslb_site_failed_monitor_bindings_update(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + glsb_service_proxy_attrs = { + 'diff_object.return_value': {}, + } + gslb_service_proxy_mock = Mock() + gslb_service_proxy_mock.configure_mock(**glsb_service_proxy_attrs) + config_proxy_mock = Mock(return_value=gslb_service_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + nitro_exception=self.MockException, + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + gslb_service_exists=Mock(side_effect=[True, True]), + gslb_service_identical=Mock(side_effect=[False, False, True]), + monitor_bindings_identical=Mock(side_effect=[False, False, False]), + ensure_feature_is_enabled=Mock(), + monkey_patch_nitro_api=Mock(), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_gslb_service + result = self.failed() + self.assertEqual(result['msg'], 'Monitor bindings differ from configured') + self.assertTrue(result['failed']) + + def test_present_gslb_site_failed_create(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + glsb_service_proxy_attrs = { + 'diff_object.return_value': {}, + } + gslb_service_proxy_mock = Mock() + gslb_service_proxy_mock.configure_mock(**glsb_service_proxy_attrs) + config_proxy_mock = Mock(return_value=gslb_service_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + nitro_exception=self.MockException, + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + gslb_service_exists=Mock(side_effect=[False, False]), + gslb_service_identical=Mock(side_effect=[False, False]), + ensure_feature_is_enabled=Mock(), + monkey_patch_nitro_api=Mock(), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_gslb_service + result = self.failed() + self.assertEqual(result['msg'], 'GSLB service does not exist') + self.assertTrue(result['failed']) + + def test_present_gslb_site_update_immutable_attribute(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + glsb_service_proxy_attrs = { + 'diff_object.return_value': {}, + } + gslb_service_proxy_mock = Mock() + gslb_service_proxy_mock.configure_mock(**glsb_service_proxy_attrs) + config_proxy_mock = Mock(return_value=gslb_service_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + nitro_exception=self.MockException, + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=['domain']), + gslb_service_exists=Mock(side_effect=[True, True]), + gslb_service_identical=Mock(side_effect=[False, False]), + ensure_feature_is_enabled=Mock(), + monkey_patch_nitro_api=Mock(), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_gslb_service + result = self.failed() + self.assertEqual(result['msg'], 'Cannot update immutable attributes [\'domain\']') + self.assertTrue(result['failed']) + + def test_absent_gslb_site_failed_delete(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='absent', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + glsb_service_proxy_attrs = { + 'diff_object.return_value': {}, + } + gslb_service_proxy_mock = Mock() + gslb_service_proxy_mock.configure_mock(**glsb_service_proxy_attrs) + config_proxy_mock = Mock(return_value=gslb_service_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + nitro_exception=self.MockException, + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + gslb_service_exists=Mock(side_effect=[True, True]), + gslb_service_identical=Mock(side_effect=[False, False]), + ensure_feature_is_enabled=Mock(), + monkey_patch_nitro_api=Mock(), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_gslb_service + result = self.failed() + self.assertEqual(result['msg'], 'GSLB service still exists') + self.assertTrue(result['failed']) + + def test_graceful_nitro_exception_state_present(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + class MockException(Exception): + def __init__(self, *args, **kwargs): + self.errorcode = 0 + self.message = '' + + m = Mock(side_effect=MockException) + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + gslb_service_exists=m, + ensure_feature_is_enabled=Mock(), + monkey_patch_nitro_api=Mock(), + nitro_exception=MockException + ): + self.module = netscaler_gslb_service + result = self.failed() + self.assertTrue( + result['msg'].startswith('nitro exception'), + msg='Nitro exception not caught on operation absent' + ) + + def test_graceful_nitro_exception_state_absent(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='absent', + )) + from ansible.modules.network.netscaler import netscaler_gslb_service + + class MockException(Exception): + def __init__(self, *args, **kwargs): + self.errorcode = 0 + self.message = '' + + m = Mock(side_effect=MockException) + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_gslb_service', + gslb_service_exists=m, + ensure_feature_is_enabled=Mock(), + monkey_patch_nitro_api=Mock(), + nitro_exception=MockException + ): + self.module = netscaler_gslb_service + result = self.failed() + self.assertTrue( + result['msg'].startswith('nitro exception'), + msg='Nitro exception not caught on operation absent' + )