diff --git a/lib/ansible/modules/network/netscaler/netscaler_cs_vserver.py b/lib/ansible/modules/network/netscaler/netscaler_cs_vserver.py new file mode 100644 index 0000000000..5ea012be1f --- /dev/null +++ b/lib/ansible/modules/network/netscaler/netscaler_cs_vserver.py @@ -0,0 +1,1211 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 . +# + + +ANSIBLE_METADATA = {'status': ['preview'], + 'supported_by': 'community', + 'metadata_version': '1.0'} + + +DOCUMENTATION = ''' +--- +module: netscaler_cs_vserver +short_description: Manage content switching vserver +description: + - Manage content switching vserver + - This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance + +version_added: "2.4" + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + name: + description: + - >- + Name for the content switching 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 sign C(@), equal sign C(=), and hyphen C(-) characters. + - "Cannot be changed after the CS virtual server is created." + - "Minimum length = 1" + + td: + description: + - >- + Integer value that uniquely identifies the traffic domain in which you want to configure the entity. + If you do not specify an ID, the entity becomes part of the default traffic domain, which has an ID + of 0. + - "Minimum value = 0" + - "Maximum value = 4094" + + servicetype: + choices: + - 'HTTP' + - 'SSL' + - 'TCP' + - 'FTP' + - 'RTSP' + - 'SSL_TCP' + - 'UDP' + - 'DNS' + - 'SIP_UDP' + - 'SIP_TCP' + - 'SIP_SSL' + - 'ANY' + - 'RADIUS' + - 'RDP' + - 'MYSQL' + - 'MSSQL' + - 'DIAMETER' + - 'SSL_DIAMETER' + - 'DNS_TCP' + - 'ORACLE' + - 'SMPP' + description: + - "Protocol used by the virtual server." + + ipv46: + description: + - "IP address of the content switching virtual server." + - "Minimum length = 1" + + targettype: + choices: + - 'GSLB' + description: + - "Virtual server target type." + + ippattern: + description: + - >- + IP address pattern, in dotted decimal notation, for identifying packets to be accepted by the virtual + server. The IP Mask parameter specifies which part of the destination IP address is matched against + the pattern. Mutually exclusive with the IP Address parameter. + - >- + For example, if the IP pattern assigned to the virtual server is C(198.51.100.0) and the IP mask is + C(255.255.240.0) (a forward mask), the first 20 bits in the destination IP addresses are matched with + the first 20 bits in the pattern. The virtual server accepts requests with IP addresses that range + from 198.51.96.1 to 198.51.111.254. You can also use a pattern such as C(0.0.2.2) and a mask such as + C(0.0.255.255) (a reverse mask). + - >- + If a destination IP address matches more than one IP pattern, the pattern with the longest match is + selected, and the associated virtual server processes the request. For example, if the virtual + servers, C(vs1) and C(vs2), have the same IP pattern, C(0.0.100.128), but different IP masks of C(0.0.255.255) + and C(0.0.224.255), a destination IP address of 198.51.100.128 has the longest match with the IP pattern + of C(vs1). If a destination IP address matches two or more virtual servers to the same extent, the + request is processed by the virtual server whose port number matches the port number in the request. + + ipmask: + description: + - >- + IP mask, in dotted decimal notation, for the IP Pattern parameter. Can have leading or trailing + non-zero octets (for example, C(255.255.240.0) or C(0.0.255.255)). Accordingly, the mask specifies whether + the first n bits or the last n bits of the destination IP address in a client request are to be + matched with the corresponding bits in the IP pattern. The former is called a forward mask. The + latter is called a reverse mask. + + range: + description: + - >- + Number of consecutive IP addresses, starting with the address specified by the IP Address parameter, + to include in a range of addresses assigned to this virtual server. + - "Minimum value = C(1)" + - "Maximum value = C(254)" + + port: + description: + - "Port number for content switching virtual server." + - "Minimum value = 1" + - "Range C(1) - C(65535)" + - "* in CLI is represented as 65535 in NITRO API" + + stateupdate: + choices: + - 'ENABLED' + - 'DISABLED' + description: + - >- + Enable state updates for a specific content switching virtual server. By default, the Content + Switching virtual server is always UP, regardless of the state of the Load Balancing virtual servers + bound to it. This parameter interacts with the global setting as follows: + - "Global Level | Vserver Level | Result" + - "ENABLED ENABLED ENABLED" + - "ENABLED DISABLED ENABLED" + - "DISABLED ENABLED ENABLED" + - "DISABLED DISABLED DISABLED" + - >- + If you want to enable state updates for only some content switching virtual servers, be sure to + disable the state update parameter. + + cacheable: + description: + - >- + Use this option to specify whether a virtual server, used for load balancing or content switching, + routes requests to the cache redirection virtual server before sending it to the configured servers. + type: bool + + redirecturl: + description: + - >- + URL to which traffic is redirected if the virtual server becomes unavailable. The service type of the + virtual server should be either C(HTTP) or C(SSL). + - >- + Caution: Make sure that the domain in the URL does not match the domain specified for a content + switching policy. If it does, requests are continuously redirected to the unavailable virtual server. + - "Minimum length = 1" + + clttimeout: + description: + - "Idle time, in seconds, after which the client connection is terminated. The default values are:" + - "Minimum value = C(0)" + - "Maximum value = C(31536000)" + + precedence: + choices: + - 'RULE' + - 'URL' + description: + - >- + Type of precedence to use for both RULE-based and URL-based policies on the content switching virtual + server. With the default C(RULE) setting, incoming requests are evaluated against the rule-based + content switching policies. If none of the rules match, the URL in the request is evaluated against + the URL-based content switching policies. + + casesensitive: + description: + - >- + Consider case in URLs (for policies that use URLs instead of RULES). For example, with the C(on) + setting, the URLs /a/1.html and /A/1.HTML are treated differently and can have different targets (set + by content switching policies). With the C(off) setting, /a/1.html and /A/1.HTML are switched to the + same target. + type: bool + + somethod: + choices: + - 'CONNECTION' + - 'DYNAMICCONNECTION' + - 'BANDWIDTH' + - 'HEALTH' + - 'NONE' + description: + - >- + Type of spillover used to divert traffic to the backup virtual server when the primary virtual server + reaches the spillover threshold. Connection spillover is based on the number of connections. + Bandwidth spillover is based on the total Kbps of incoming and outgoing traffic. + + sopersistence: + choices: + - 'ENABLED' + - 'DISABLED' + description: + - "Maintain source-IP based persistence on primary and backup virtual servers." + + sopersistencetimeout: + description: + - "Time-out value, in minutes, for spillover persistence." + - "Minimum value = C(2)" + - "Maximum value = C(1440)" + + sothreshold: + description: + - >- + Depending on the spillover method, the maximum number of connections or the maximum total bandwidth + (Kbps) that a virtual server can handle before spillover occurs. + - "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. + + redirectportrewrite: + choices: + - 'ENABLED' + - 'DISABLED' + description: + - "State of port rewrite while performing HTTP redirect." + + downstateflush: + choices: + - 'ENABLED' + - 'DISABLED' + description: + - >- + Flush all active transactions associated with a virtual server whose state transitions from UP to + DOWN. Do not enable this option for applications that must complete their transactions. + + backupvserver: + description: + - >- + Name of the backup virtual server that you are configuring. Must begin with an ASCII alphanumeric or + underscore C(_) character, and must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), + space C( ), colon C(:), at sign C(@), equal sign C(=), and hyphen C(-) characters. Can be changed after the + backup virtual server is created. You can assign a different backup virtual server or rename the + existing virtual server. + - "Minimum length = 1" + + disableprimaryondown: + choices: + - 'ENABLED' + - 'DISABLED' + description: + - >- + Continue forwarding the traffic to backup virtual server even after the primary server comes UP from + the DOWN state. + + insertvserveripport: + choices: + - 'OFF' + - 'VIPADDR' + - 'V6TOV4MAPPING' + description: + - >- + Insert the virtual server's VIP address and port number in the request header. Available values + function as follows: + - "C(VIPADDR) - Header contains the vserver's IP address and port number without any translation." + - "C(OFF) - The virtual IP and port header insertion option is disabled." + - >- + C(V6TOV4MAPPING) - Header contains the mapped IPv4 address corresponding to the IPv6 address of the + vserver and the port number. An IPv6 address can be mapped to a user-specified IPv4 address using the + set ns ip6 command. + + vipheader: + description: + - "Name of virtual server IP and port header, for use with the VServer IP Port Insertion parameter." + - "Minimum length = 1" + + rtspnat: + description: + - "Enable network address translation (NAT) for real-time streaming protocol (RTSP) connections." + type: bool + + authenticationhost: + description: + - >- + FQDN of the authentication virtual server. The service type of the virtual server should be either + C(HTTP) or C(SSL). + - "Minimum length = 3" + - "Maximum length = 252" + + authentication: + description: + - "Authenticate users who request a connection to the content switching virtual server." + type: bool + + listenpolicy: + description: + - >- + String specifying the listen policy for the content switching virtual server. Can be either the name + of an existing expression or an in-line expression. + + authn401: + description: + - "Enable HTTP 401-response based authentication." + type: bool + + authnvsname: + description: + - >- + Name of authentication virtual server that authenticates the incoming user requests to this content + switching virtual server. . + - "Minimum length = 1" + - "Maximum length = 252" + + push: + choices: + - 'ENABLED' + - 'DISABLED' + description: + - >- + Process traffic with the push virtual server that is bound to this content switching virtual server + (specified by the Push VServer parameter). The service type of the push virtual server should be + either C(HTTP) or C(SSL). + + pushvserver: + description: + - >- + Name of the load balancing virtual server, of type C(PUSH) or C(SSL_PUSH), to which the server pushes + updates received on the client-facing load balancing virtual server. + - "Minimum length = 1" + + pushlabel: + description: + - >- + Expression for extracting the label from the response received from server. This string can be either + an existing rule name or an inline expression. The service type of the virtual server should be + either C(HTTP) or C(SSL). + + pushmulticlients: + description: + - >- + Allow multiple Web 2.0 connections from the same client to connect to the virtual server and expect + updates. + type: bool + + tcpprofilename: + description: + - "Name of the TCP profile containing TCP configuration settings for the virtual server." + - "Minimum length = 1" + - "Maximum length = 127" + + httpprofilename: + description: + - >- + Name of the HTTP profile containing HTTP configuration settings for the virtual server. The service + type of the virtual server should be either C(HTTP) or C(SSL). + - "Minimum length = 1" + - "Maximum length = 127" + + dbprofilename: + description: + - "Name of the DB profile." + - "Minimum length = 1" + - "Maximum length = 127" + + oracleserverversion: + choices: + - '10G' + - '11G' + description: + - "Oracle server version." + + comment: + description: + - "Information about this virtual server." + + mssqlserverversion: + choices: + - '70' + - '2000' + - '2000SP1' + - '2005' + - '2008' + - '2008R2' + - '2012' + - '2014' + description: + - "The version of the MSSQL server." + + l2conn: + description: + - "Use L2 Parameters to identify a connection." + + mysqlprotocolversion: + description: + - "The protocol version returned by the mysql vserver." + + mysqlserverversion: + description: + - "The server version string returned by the mysql vserver." + - "Minimum length = 1" + - "Maximum length = 31" + + mysqlcharacterset: + description: + - "The character set returned by the mysql vserver." + + mysqlservercapabilities: + description: + - "The server capabilities returned by the mysql vserver." + + appflowlog: + choices: + - 'ENABLED' + - 'DISABLED' + description: + - "Enable logging appflow flow information." + + netprofile: + description: + - "The name of the network profile." + - "Minimum length = 1" + - "Maximum length = 127" + + icmpvsrresponse: + choices: + - 'PASSIVE' + - 'ACTIVE' + description: + - "Can be active or passive." + + rhistate: + choices: + - 'PASSIVE' + - 'ACTIVE' + description: + - "A host route is injected according to the setting on the virtual servers" + - >- + * If set to C(PASSIVE) on all the virtual servers that share the IP address, the appliance always + injects the hostroute. + - >- + * If set to C(ACTIVE) on all the virtual servers that share the IP address, the appliance injects even + if one virtual server is UP. + - >- + * If set to C(ACTIVE) on some virtual servers and C(PASSIVE) on the others, the appliance, injects even if + one virtual server set to C(ACTIVE) is UP. + + authnprofile: + description: + - "Name of the authentication profile to be used when authentication is turned on." + + dnsprofilename: + description: + - >- + Name of the DNS profile to be associated with the VServer. DNS profile properties will applied to the + transactions processed by a VServer. This parameter is valid only for DNS and DNS-TCP VServers. + - "Minimum length = 1" + - "Maximum length = 127" + + domainname: + description: + - "Domain name for which to change the time to live (TTL) and/or backup service IP address." + - "Minimum length = 1" + + ttl: + description: + - "." + - "Minimum value = C(1)" + + backupip: + description: + - "." + - "Minimum length = 1" + + cookiedomain: + description: + - "." + - "Minimum length = 1" + + cookietimeout: + description: + - "." + - "Minimum value = C(0)" + - "Maximum value = C(1440)" + + sitedomainttl: + description: + - "." + - "Minimum value = C(1)" + + disabled: + description: + - When set to C(yes) the cs vserver will be disabled. + - When set to C(no) the cs vserver will be 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: 'no' + +extends_documentation_fragment: netscaler +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +# policy_1 must have been already created with the netscaler_cs_policy module +# lbvserver_1 must have been already created with the netscaler_lb_vserver module + +- name: Setup content switching vserver + delegate_to: localhost + netscaler_cs_vserver: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + + state: present + + name: cs_vserver_1 + ipv46: 192.168.1.1 + port: 80 + servicetype: HTTP + + policybindings: + - policyname: policy_1 + targetlbvserver: lbvserver_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: str + 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: dict + sample: { 'clttimeout': 'difference. ours: (float) 100.0 other: (float) 60.0' } +''' + +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, + get_immutables_intersection +) +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.cs.csvserver import csvserver + from nssrc.com.citrix.netscaler.nitro.resource.config.cs.csvserver_cspolicy_binding import csvserver_cspolicy_binding + from nssrc.com.citrix.netscaler.nitro.resource.config.ssl.sslvserver_sslcertkey_binding import sslvserver_sslcertkey_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 cs_vserver_exists(client, module): + if csvserver.count_filtered(client, 'name:%s' % module.params['name']) > 0: + return True + else: + return False + + +def cs_vserver_identical(client, module, csvserver_proxy): + csvserver_list = csvserver.get_filtered(client, 'name:%s' % module.params['name']) + diff_dict = csvserver_proxy.diff_object(csvserver_list[0]) + if len(diff_dict) == 0: + return True + else: + return False + + +def get_configured_policybindings(client, module): + log('Getting configured policy bindigs') + bindings = {} + if module.params['policybindings'] is None: + return bindings + + for binding in module.params['policybindings']: + binding['name'] = module.params['name'] + key = binding['policyname'] + binding_proxy = ConfigProxy( + actual=csvserver_cspolicy_binding(), + client=client, + readwrite_attrs=[ + 'priority', + 'bindpoint', + 'policyname', + 'labelname', + 'gotopriorityexpression', + 'targetlbvserver', + 'name', + 'invoke', + 'labeltype', + ], + readonly_attrs=[], + attribute_values_dict=binding + ) + bindings[key] = binding_proxy + return bindings + + +def get_actual_policybindings(client, module): + log('Getting actual policy bindigs') + bindings = {} + try: + count = csvserver_cspolicy_binding.count(client, name=module.params['name']) + if count == 0: + return bindings + except nitro_exception as e: + if e.errorcode == 258: + return bindings + else: + raise + + for binding in csvserver_cspolicy_binding.get(client, name=module.params['name']): + key = binding.policyname + bindings[key] = binding + + return bindings + + +def cs_policybindings_identical(client, module): + log('Checking policy bindings identical') + actual_bindings = get_actual_policybindings(client, module) + configured_bindings = get_configured_policybindings(client, module) + + actual_keyset = set(actual_bindings.keys()) + configured_keyset = set(configured_bindings.keys()) + if len(actual_keyset ^ configured_keyset) > 0: + return False + + # Compare item to item + for key in actual_bindings.keys(): + configured_binding_proxy = configured_bindings[key] + actual_binding_object = actual_bindings[key] + if not configured_binding_proxy.has_equal_attributes(actual_binding_object): + return False + + # Fallthrough to success + return True + + +def sync_cs_policybindings(client, module): + log('Syncing cs policybindings') + + # Delete all actual bindings + for binding in get_actual_policybindings(client, module).values(): + log('Deleting binding for policy %s' % binding.policyname) + csvserver_cspolicy_binding.delete(client, binding) + + # Add all configured bindings + + for binding in get_configured_policybindings(client, module).values(): + log('Adding binding for policy %s' % binding.policyname) + binding.add() + + +def ssl_certkey_bindings_identical(client, module): + log('Checking if ssl cert key bindings are identical') + vservername = module.params['name'] + if sslvserver_sslcertkey_binding.count(client, vservername) == 0: + bindings = [] + else: + bindings = sslvserver_sslcertkey_binding.get(client, vservername) + + if module.params['ssl_certkey'] is None: + if len(bindings) == 0: + return True + else: + return False + else: + certificate_list = [item.certkeyname for item in bindings] + if certificate_list == [module.params['ssl_certkey']]: + return True + else: + return False + + +def ssl_certkey_bindings_sync(client, module): + log('Syncing certkey bindings') + vservername = module.params['name'] + if sslvserver_sslcertkey_binding.count(client, vservername) == 0: + bindings = [] + else: + bindings = sslvserver_sslcertkey_binding.get(client, vservername) + + # Delete existing bindings + for binding in bindings: + log('Deleting existing binding for certkey %s' % binding.certkeyname) + sslvserver_sslcertkey_binding.delete(client, binding) + + # Add binding if appropriate + if module.params['ssl_certkey'] is not None: + log('Adding binding for certkey %s' % module.params['ssl_certkey']) + binding = sslvserver_sslcertkey_binding() + binding.vservername = module.params['name'] + binding.certkeyname = module.params['ssl_certkey'] + sslvserver_sslcertkey_binding.add(client, binding) + + +def diff_list(client, module, csvserver_proxy): + csvserver_list = csvserver.get_filtered(client, 'name:%s' % module.params['name']) + return csvserver_proxy.diff_object(csvserver_list[0]) + + +def do_state_change(client, module, csvserver_proxy): + if module.params['disabled']: + log('Disabling cs vserver') + result = csvserver.disable(client, csvserver_proxy.actual) + else: + log('Enabling cs vserver') + result = csvserver.enable(client, csvserver_proxy.actual) + return result + + +def main(): + + module_specific_arguments = dict( + + name=dict(type='str'), + td=dict(type='float'), + servicetype=dict( + type='str', + choices=[ + 'HTTP', + 'SSL', + 'TCP', + 'FTP', + 'RTSP', + 'SSL_TCP', + 'UDP', + 'DNS', + 'SIP_UDP', + 'SIP_TCP', + 'SIP_SSL', + 'ANY', + 'RADIUS', + 'RDP', + 'MYSQL', + 'MSSQL', + 'DIAMETER', + 'SSL_DIAMETER', + 'DNS_TCP', + 'ORACLE', + 'SMPP' + ] + ), + + ipv46=dict(type='str'), + dnsrecordtype=dict( + type='str', + choices=[ + 'A', + 'AAAA', + 'CNAME', + 'NAPTR', + ] + ), + ippattern=dict(type='str'), + ipmask=dict(type='str'), + range=dict(type='float'), + port=dict(type='int'), + stateupdate=dict( + type='str', + choices=[ + 'ENABLED', + 'DISABLED', + ] + ), + cacheable=dict(type='bool'), + redirecturl=dict(type='str'), + clttimeout=dict(type='float'), + precedence=dict( + type='str', + choices=[ + 'RULE', + 'URL', + ] + ), + casesensitive=dict(type='bool'), + 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', + ] + ), + redirectportrewrite=dict( + type='str', + choices=[ + 'ENABLED', + 'DISABLED', + ] + ), + downstateflush=dict( + type='str', + choices=[ + 'ENABLED', + 'DISABLED', + ] + ), + disableprimaryondown=dict( + type='str', + choices=[ + 'ENABLED', + 'DISABLED', + ] + ), + insertvserveripport=dict( + type='str', + choices=[ + 'OFF', + 'VIPADDR', + 'V6TOV4MAPPING', + ] + ), + vipheader=dict(type='str'), + rtspnat=dict(type='bool'), + authenticationhost=dict(type='str'), + authentication=dict(type='bool'), + listenpolicy=dict(type='str'), + authn401=dict(type='bool'), + authnvsname=dict(type='str'), + push=dict( + type='str', + choices=[ + 'ENABLED', + 'DISABLED', + ] + ), + pushvserver=dict(type='str'), + pushlabel=dict(type='str'), + pushmulticlients=dict(type='bool'), + tcpprofilename=dict(type='str'), + httpprofilename=dict(type='str'), + dbprofilename=dict(type='str'), + oracleserverversion=dict( + type='str', + choices=[ + '10G', + '11G', + ] + ), + comment=dict(type='str'), + mssqlserverversion=dict( + type='str', + choices=[ + '70', + '2000', + '2000SP1', + '2005', + '2008', + '2008R2', + '2012', + '2014', + ] + ), + l2conn=dict(type='bool'), + mysqlprotocolversion=dict(type='float'), + mysqlserverversion=dict(type='str'), + mysqlcharacterset=dict(type='float'), + mysqlservercapabilities=dict(type='float'), + appflowlog=dict( + type='str', + choices=[ + 'ENABLED', + 'DISABLED', + ] + ), + netprofile=dict(type='str'), + icmpvsrresponse=dict( + type='str', + choices=[ + 'PASSIVE', + 'ACTIVE', + ] + ), + rhistate=dict( + type='str', + choices=[ + 'PASSIVE', + 'ACTIVE', + ] + ), + authnprofile=dict(type='str'), + dnsprofilename=dict(type='str'), + ) + + hand_inserted_arguments = dict( + policybindings=dict(type='list'), + ssl_certkey=dict(type='str'), + 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', + 'td', + 'servicetype', + 'ipv46', + 'dnsrecordtype', + 'ippattern', + 'ipmask', + 'range', + 'port', + 'stateupdate', + 'cacheable', + 'redirecturl', + 'clttimeout', + 'precedence', + 'casesensitive', + 'somethod', + 'sopersistence', + 'sopersistencetimeout', + 'sothreshold', + 'sobackupaction', + 'redirectportrewrite', + 'downstateflush', + 'disableprimaryondown', + 'insertvserveripport', + 'vipheader', + 'rtspnat', + 'authenticationhost', + 'authentication', + 'listenpolicy', + 'authn401', + 'authnvsname', + 'push', + 'pushvserver', + 'pushlabel', + 'pushmulticlients', + 'tcpprofilename', + 'httpprofilename', + 'dbprofilename', + 'oracleserverversion', + 'comment', + 'mssqlserverversion', + 'l2conn', + 'mysqlprotocolversion', + 'mysqlserverversion', + 'mysqlcharacterset', + 'mysqlservercapabilities', + 'appflowlog', + 'netprofile', + 'icmpvsrresponse', + 'rhistate', + 'authnprofile', + 'dnsprofilename', + ] + + readonly_attrs = [ + 'ip', + 'value', + 'ngname', + 'type', + 'curstate', + 'sc', + 'status', + 'cachetype', + 'redirect', + 'homepage', + 'dnsvservername', + 'domain', + 'policyname', + 'servicename', + 'weight', + 'cachevserver', + 'targetvserver', + 'priority', + 'url', + 'gotopriorityexpression', + 'bindpoint', + 'invoke', + 'labeltype', + 'labelname', + 'gt2gb', + 'statechangetimesec', + 'statechangetimemsec', + 'tickssincelaststatechange', + 'ruletype', + 'lbvserver', + 'targetlbvserver', + ] + + immutable_attrs = [ + 'name', + 'td', + 'servicetype', + 'ipv46', + 'targettype', + 'range', + 'port', + 'state', + 'vipheader', + 'newname', + ] + + transforms = { + 'cacheable': ['bool_yes_no'], + 'rtspnat': ['bool_on_off'], + 'authn401': ['bool_on_off'], + 'casesensitive': ['bool_on_off'], + 'authentication': ['bool_on_off'], + 'l2conn': ['bool_on_off'], + 'pushmulticlients': ['bool_yes_no'], + } + + # Instantiate config proxy + csvserver_proxy = ConfigProxy( + actual=csvserver(), + 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, 'CS') + + # Apply appropriate state + if module.params['state'] == 'present': + log('Applying actions for state present') + if not cs_vserver_exists(client, module): + if not module.check_mode: + csvserver_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not cs_vserver_identical(client, module, csvserver_proxy): + + # Check if we try to change value of immutable attributes + immutables_changed = get_immutables_intersection(csvserver_proxy, diff_list(client, module, csvserver_proxy).keys()) + if immutables_changed != []: + module.fail_json( + msg='Cannot update immutable attributes %s' % (immutables_changed,), + diff=diff_list(client, module, csvserver_proxy), + **module_result + ) + + if not module.check_mode: + csvserver_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Check policybindings + if not cs_policybindings_identical(client, module): + if not module.check_mode: + sync_cs_policybindings(client, module) + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + + if module.params['servicetype'] != 'SSL' and module.params['ssl_certkey'] is not None: + module.fail_json(msg='ssl_certkey is applicable only to SSL vservers', **module_result) + + # Check ssl certkey bindings + if module.params['servicetype'] == 'SSL': + if not ssl_certkey_bindings_identical(client, module): + if not module.check_mode: + ssl_certkey_bindings_sync(client, module) + + module_result['changed'] = True + + if not module.check_mode: + res = do_state_change(client, module, csvserver_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: + log('Sanity checks for state present') + if not cs_vserver_exists(client, module): + module.fail_json(msg='CS vserver does not exist', **module_result) + if not cs_vserver_identical(client, module, csvserver_proxy): + module.fail_json(msg='CS vserver differs from configured', diff=diff_list(client, module, csvserver_proxy), **module_result) + if not cs_policybindings_identical(client, module): + module.fail_json(msg='Policy bindings differ') + + if module.params['servicetype'] == 'SSL': + if not ssl_certkey_bindings_identical(client, module): + module.fail_json(msg='sll certkey bindings not identical', **module_result) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if cs_vserver_exists(client, module): + if not module.check_mode: + csvserver_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: + log('Sanity checks for state absent') + if cs_vserver_exists(client, module): + module.fail_json(msg='CS 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() diff --git a/test/integration/roles/netscaler_cs_vserver/defaults/main.yaml b/test/integration/roles/netscaler_cs_vserver/defaults/main.yaml new file mode 100644 index 0000000000..641801f660 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/defaults/main.yaml @@ -0,0 +1,6 @@ +--- +testcase: "*" +test_cases: [] + +nitro_user: nsroot +nitro_pass: nsroot diff --git a/test/integration/roles/netscaler_cs_vserver/sample_inventory b/test/integration/roles/netscaler_cs_vserver/sample_inventory new file mode 100644 index 0000000000..4263579691 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/sample_inventory @@ -0,0 +1,5 @@ + + +[netscaler] + +netscaler01 nsip=172.18.0.2 nitro_user=nsroot nitro_pass=nsroot diff --git a/test/integration/roles/netscaler_cs_vserver/tasks/main.yaml b/test/integration/roles/netscaler_cs_vserver/tasks/main.yaml new file mode 100644 index 0000000000..9a197e4d77 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tasks/main.yaml @@ -0,0 +1,6 @@ +--- +- { include: testbed.yaml, state: present } + +- { include: nitro.yaml, tags: ['nitro'] } + +- { include: testbed.yaml, state: absent } diff --git a/test/integration/roles/netscaler_cs_vserver/tasks/nitro.yaml b/test/integration/roles/netscaler_cs_vserver/tasks/nitro.yaml new file mode 100644 index 0000000000..00ab502dda --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/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_cs_vserver/tasks/testbed.yaml b/test/integration/roles/netscaler_cs_vserver/tasks/testbed.yaml new file mode 100644 index 0000000000..ff521d5443 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tasks/testbed.yaml @@ -0,0 +1,67 @@ +--- + +- name: setup push lb vserver + delegate_to: localhost + netscaler_lb_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: "{{ state }}" + + name: push_lb_vserver + servicetype: PUSH + + +- name: setup http lb vserver + delegate_to: localhost + netscaler_lb_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: "{{ state }}" + + name: lb-vserver-1 + servicetype: HTTP + ipv46: 10.60.60.60 + port: 80 + +- name: setup http lb vserver + delegate_to: localhost + netscaler_lb_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: "{{ state }}" + + name: lb-vserver-2 + servicetype: HTTP + ipv46: 10.60.60.62 + port: 80 + + +- name: setup cs policy + delegate_to: localhost + netscaler_cs_policy: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: "{{ state }}" + + policyname: policy-1 + url: /example.com/basket + +- name: setup cs policy + delegate_to: localhost + netscaler_cs_policy: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: "{{ state }}" + + policyname: policy-2 + url: /example.com/basket2 diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_dns.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_dns.yaml new file mode 100644 index 0000000000..629e5d3c18 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_dns.yaml @@ -0,0 +1,57 @@ +--- + +- include: "{{ role_path }}/tests/nitro/cs_vserver_dns/setup.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_dns/setup.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_dns/setup.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_dns/setup.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_dns/remove.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_dns/remove.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_dns/remove.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_dns/remove.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_dns/remove.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_dns/remove.yaml new file mode 100644 index 0000000000..eea7b58e2b --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_dns/remove.yaml @@ -0,0 +1,13 @@ +--- + +- name: Setup cs policy + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: absent + name: cs-vserver-6 diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_dns/setup.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_dns/setup.yaml new file mode 100644 index 0000000000..ac925910dc --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_dns/setup.yaml @@ -0,0 +1,16 @@ +--- + + +- name: Setup cs vserver + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + name: cs-vserver-6 + servicetype: DNS + ipv46: 192.168.1.6 + port: 80 diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_flap_disabled.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_flap_disabled.yaml new file mode 100644 index 0000000000..92ce9a3ac0 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_flap_disabled.yaml @@ -0,0 +1,9 @@ +--- + +- include: "{{ role_path }}/tests/nitro/cs_vserver_flap_disabled/setup.yaml" + vars: + check_mode: no + +- include: "{{ role_path }}/tests/nitro/cs_vserver_flap_disabled/remove.yaml" + vars: + check_mode: no diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_flap_disabled/remove.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_flap_disabled/remove.yaml new file mode 100644 index 0000000000..6c10f11dc4 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_flap_disabled/remove.yaml @@ -0,0 +1,12 @@ +--- + +- name: remove http cs vserver + register: result + check_mode: "{{ check_mode }}" + delegate_to: localhost + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + state: absent + name: cs-vserver-flap diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_flap_disabled/setup.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_flap_disabled/setup.yaml new file mode 100644 index 0000000000..59a4d57c26 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_flap_disabled/setup.yaml @@ -0,0 +1,39 @@ +--- + +- name: flap http cs vserver + register: result + check_mode: "{{ check_mode }}" + delegate_to: localhost + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + name: cs-vserver-flap + servicetype: HTTP + ipv46: 192.168.1.1 + port: 80 + td: 0 + + disabled: "{{ item|int % 2 }}" + with_sequence: count=20 + delay: 1 + +- name: flap http cs vserver + register: result + check_mode: "{{ check_mode }}" + delegate_to: localhost + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + name: cs-vserver-flap + servicetype: HTTP + ipv46: 192.168.1.1 + port: 80 + td: 0 + + disabled: "{{ item|int % 2 }}" + with_sequence: count=20 + delay: 5 diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_http.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_http.yaml new file mode 100644 index 0000000000..5d3a5eb4f1 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_http.yaml @@ -0,0 +1,85 @@ +--- + +- include: "{{ role_path }}/tests/nitro/cs_vserver_http/setup.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_http/setup.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_http/setup.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_http/setup.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_http/update.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_http/update.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_http/update.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_http/update.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_http/remove.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_http/remove.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_http/remove.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_http/remove.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_http/remove.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_http/remove.yaml new file mode 100644 index 0000000000..c0676ec44f --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_http/remove.yaml @@ -0,0 +1,13 @@ +--- + +- name: Setup cs policy + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: absent + name: cs-vserver-1 diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_http/setup.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_http/setup.yaml new file mode 100644 index 0000000000..52dc42544e --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_http/setup.yaml @@ -0,0 +1,51 @@ +--- + + +- name: Setup cs vserver + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + name: cs-vserver-1 + servicetype: HTTP + ipv46: 192.168.1.1 + td: 0 + + port: 80 + dnsrecordtype: A + range: 2 + stateupdate: DISABLED + cacheable: no + redirecturl: http://newurl.com + clttimeout: 200 + precedence: RULE + casesensitive: on + somethod: CONNECTION + sopersistence: ENABLED + sopersistencetimeout: 50 + sothreshold: 200 + sobackupaction: DROP + redirectportrewrite: DISABLED + downstateflush: DISABLED + disableprimaryondown: DISABLED + insertvserveripport: VIPADDR + vipheader: someheader + rtspnat: off + authenticationhost: newauth.com + authentication: on + listenpolicy: "NONE" + authn401: off + authnvsname: someserver + push: DISABLED + pushvserver: push_lb_vserver + pushlabel: none + pushmulticlients: no + comment: some comment + l2conn: off + appflowlog: ENABLED + icmpvsrresponse: PASSIVE + rhistate: PASSIVE diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_http/update.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_http/update.yaml new file mode 100644 index 0000000000..b61643174e --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_http/update.yaml @@ -0,0 +1,51 @@ +--- + + +- name: Setup cs vserver + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + name: cs-vserver-1 + servicetype: HTTP + ipv46: 192.168.1.1 + td: 0 + + port: 80 + dnsrecordtype: A + range: 2 + stateupdate: DISABLED + cacheable: no + redirecturl: http://url.com + clttimeout: 200 + precedence: RULE + casesensitive: on + somethod: CONNECTION + sopersistence: ENABLED + sopersistencetimeout: 50 + sothreshold: 200 + sobackupaction: DROP + redirectportrewrite: DISABLED + downstateflush: DISABLED + disableprimaryondown: DISABLED + insertvserveripport: VIPADDR + vipheader: someheader + rtspnat: off + authenticationhost: auth.com + authentication: on + listenpolicy: "NONE" + authn401: off + authnvsname: someserver + push: DISABLED + pushvserver: push_lb_vserver + pushlabel: none + pushmulticlients: no + comment: some comment + l2conn: off + appflowlog: ENABLED + icmpvsrresponse: PASSIVE + rhistate: PASSIVE diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_ippattern.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_ippattern.yaml new file mode 100644 index 0000000000..55c1ce2300 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_ippattern.yaml @@ -0,0 +1,57 @@ +--- + +- include: "{{ role_path }}/tests/nitro/cs_vserver_ippattern/setup.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_ippattern/setup.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_ippattern/setup.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_ippattern/setup.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_ippattern/remove.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_ippattern/remove.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_ippattern/remove.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_ippattern/remove.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_ippattern/remove.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_ippattern/remove.yaml new file mode 100644 index 0000000000..2c37b80988 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_ippattern/remove.yaml @@ -0,0 +1,13 @@ +--- + +- name: Setup cs policy + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: absent + name: cs-vserver-2 diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_ippattern/setup.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_ippattern/setup.yaml new file mode 100644 index 0000000000..ce2aa39067 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_ippattern/setup.yaml @@ -0,0 +1,18 @@ +--- + + +- name: Setup cs vserver + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + name: cs-vserver-2 + + servicetype: HTTP + port: 80 + ippattern: 10.78.10.0 + ipmask: 255.255.255.0 diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mssql.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mssql.yaml new file mode 100644 index 0000000000..e0a20b07fe --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mssql.yaml @@ -0,0 +1,57 @@ +--- + +- include: "{{ role_path }}/tests/nitro/cs_vserver_mssql/setup.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_mssql/setup.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_mssql/setup.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_mssql/setup.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_mssql/remove.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_mssql/remove.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_mssql/remove.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_mssql/remove.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mssql/remove.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mssql/remove.yaml new file mode 100644 index 0000000000..0196f23109 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mssql/remove.yaml @@ -0,0 +1,13 @@ +--- + +- name: Setup cs policy + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: absent + name: cs-vserver-4 diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mssql/setup.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mssql/setup.yaml new file mode 100644 index 0000000000..92268655eb --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mssql/setup.yaml @@ -0,0 +1,17 @@ +--- + + +- name: Setup cs vserver + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + name: cs-vserver-4 + servicetype: MSSQL + ipv46: 192.168.1.4 + port: 80 + mssqlserverversion: 2000 diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mysql.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mysql.yaml new file mode 100644 index 0000000000..e160ee377e --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mysql.yaml @@ -0,0 +1,57 @@ +--- + +- include: "{{ role_path }}/tests/nitro/cs_vserver_mysql/setup.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_mysql/setup.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_mysql/setup.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_mysql/setup.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_mysql/remove.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_mysql/remove.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_mysql/remove.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_mysql/remove.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mysql/remove.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mysql/remove.yaml new file mode 100644 index 0000000000..b2555c1b2a --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mysql/remove.yaml @@ -0,0 +1,13 @@ +--- + +- name: Setup cs policy + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: absent + name: cs-vserver-5 diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mysql/setup.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mysql/setup.yaml new file mode 100644 index 0000000000..0557acfca2 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_mysql/setup.yaml @@ -0,0 +1,20 @@ +--- + + +- name: Setup cs vserver + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + name: cs-vserver-5 + servicetype: MYSQL + ipv46: 192.168.1.5 + port: 80 + mysqlprotocolversion: 10 + mysqlserverversion: Version 1 + mysqlcharacterset: 8 + mysqlservercapabilities: 41613 diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_oracle.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_oracle.yaml new file mode 100644 index 0000000000..88a624c6d2 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_oracle.yaml @@ -0,0 +1,57 @@ +--- + +- include: "{{ role_path }}/tests/nitro/cs_vserver_oracle/setup.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_oracle/setup.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_oracle/setup.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_oracle/setup.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_oracle/remove.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_oracle/remove.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_oracle/remove.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_oracle/remove.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_oracle/remove.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_oracle/remove.yaml new file mode 100644 index 0000000000..b62d1eb355 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_oracle/remove.yaml @@ -0,0 +1,13 @@ +--- + +- name: Setup cs policy + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: absent + name: cs-vserver-3 diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_oracle/setup.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_oracle/setup.yaml new file mode 100644 index 0000000000..9768efb574 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_oracle/setup.yaml @@ -0,0 +1,18 @@ +--- + + +- name: Setup cs vserver + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + name: cs-vserver-3 + servicetype: ORACLE + ipv46: 192.168.1.3 + port: 80 + td: 0 + oracleserverversion: 10G diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_policies.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_policies.yaml new file mode 100644 index 0000000000..9c0120f2f1 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_policies.yaml @@ -0,0 +1,85 @@ +--- + +- include: "{{ role_path }}/tests/nitro/cs_vserver_policies/setup.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_policies/setup.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_policies/setup.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_policies/setup.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_policies/update.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_policies/update.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_policies/update.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_policies/update.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_policies/remove.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_policies/remove.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_policies/remove.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/cs_vserver_policies/remove.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_policies/remove.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_policies/remove.yaml new file mode 100644 index 0000000000..b62d1eb355 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_policies/remove.yaml @@ -0,0 +1,13 @@ +--- + +- name: Setup cs policy + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: absent + name: cs-vserver-3 diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_policies/setup.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_policies/setup.yaml new file mode 100644 index 0000000000..e2bacf6c02 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_policies/setup.yaml @@ -0,0 +1,22 @@ +--- + + +- name: Setup cs vserver + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + name: cs-vserver-3 + servicetype: HTTP + ipv46: 192.168.1.3 + port: 80 + policybindings: + - policyname: policy-1 + targetlbvserver: lb-vserver-1 + + - policyname: policy-2 + targetlbvserver: lb-vserver-1 diff --git a/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_policies/update.yaml b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_policies/update.yaml new file mode 100644 index 0000000000..f8060b82c4 --- /dev/null +++ b/test/integration/roles/netscaler_cs_vserver/tests/nitro/cs_vserver_policies/update.yaml @@ -0,0 +1,19 @@ +--- + + +- name: Setup cs vserver + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_vserver: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + name: cs-vserver-3 + servicetype: HTTP + ipv46: 192.168.1.3 + port: 80 + policybindings: + - policyname: policy-1 + targetlbvserver: lb-vserver-2 diff --git a/test/units/modules/network/netscaler/test_netscaler_cs_vserver.py b/test/units/modules/network/netscaler/test_netscaler_cs_vserver.py new file mode 100644 index 0000000000..45f1ac53d8 --- /dev/null +++ b/test/units/modules/network/netscaler/test_netscaler_cs_vserver.py @@ -0,0 +1,758 @@ + +# 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 TestNetscalerCSVserverModule(TestModule): + + @classmethod + def setUpClass(cls): + class MockException(Exception): + pass + + cls.MockException = MockException + + m = MagicMock() + cls.cs_vserver_mock = MagicMock() + cls.cs_vserver_mock.__class__ = MagicMock(add=Mock()) + nssrc_modules_mock = { + 'nssrc.com.citrix.netscaler.nitro.resource.config.cs': m, + 'nssrc.com.citrix.netscaler.nitro.resource.config.cs.csvserver': m, + 'nssrc.com.citrix.netscaler.nitro.resource.config.cs.csvserver.csvserver': m, + 'nssrc.com.citrix.netscaler.nitro.resource.config.cs.csvserver_cspolicy_binding': m, + 'nssrc.com.citrix.netscaler.nitro.resource.config.cs.csvserver_cspolicy_binding.csvserver_cspolicy_binding': m, + 'nssrc.com.citrix.netscaler.nitro.resource.config.ssl': m, + 'nssrc.com.citrix.netscaler.nitro.resource.config.ssl.sslvserver_sslcertkey_binding': m, + 'nssrc.com.citrix.netscaler.nitro.resource.config.ssl.sslvserver_sslcertkey_binding.sslvserver_sslcertkey_binding': 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_cs_vserver + self.module = netscaler_cs_vserver + 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_cs_vserver + + 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_cs_vserver.get_nitro_client', m): + with patch('ansible.modules.network.netscaler.netscaler_cs_vserver.nitro_exception', MockException): + self.module = netscaler_cs_vserver + 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_cs_vserver + + 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_cs_vserver', + get_nitro_client=m, + nitro_exception=MockException, + ): + self.module = netscaler_cs_vserver + 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_cs_vserver + + 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_cs_vserver', + get_nitro_client=m, + nitro_exception=MockException, + ): + self.module = netscaler_cs_vserver + result = self.failed() + self.assertTrue(result['msg'].startswith('SSL Error'), msg='SSL Error was not handled gracefully') + + 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_cs_vserver + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + cs_vserver_proxy_mock = Mock() + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_vserver', + get_nitro_client=m, + cs_vserver_exists=Mock(side_effect=[False, True]), + cs_vserver_identical=Mock(side_effect=[True]), + ensure_feature_is_enabled=Mock(return_value=True), + diff_list=Mock(return_value={}), + nitro_exception=self.MockException, + do_state_change=Mock(return_value=Mock(errorcode=0)), + ConfigProxy=Mock(return_value=cs_vserver_proxy_mock), + ): + self.module = netscaler_cs_vserver + 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_cs_vserver + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + cs_vserver_proxy_mock = Mock() + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_vserver', + get_nitro_client=m, + cs_vserver_exists=Mock(side_effect=[True, False]), + ensure_feature_is_enabled=Mock(return_value=True), + ConfigProxy=Mock(return_value=cs_vserver_proxy_mock), + ): + self.module = netscaler_cs_vserver + 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_cs_vserver + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + cs_vserver_proxy_mock = Mock() + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_vserver', + get_nitro_client=m, + cs_vserver_exists=Mock(side_effect=[False, True]), + cs_vserver_identical=Mock(side_effect=[True]), + diff_list=Mock(return_value={}), + ensure_feature_is_enabled=Mock(return_value=True), + do_state_change=Mock(return_value=Mock(errorcode=0)), + nitro_exception=self.MockException, + ConfigProxy=Mock(return_value=cs_vserver_proxy_mock), + ): + self.module = netscaler_cs_vserver + 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_cs_vserver + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + cs_vserver_proxy_mock = Mock() + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_vserver', + get_nitro_client=m, + cs_vserver_exists=Mock(side_effect=[True, False]), + ensure_feature_is_enabled=Mock(return_value=True), + ConfigProxy=Mock(return_value=cs_vserver_proxy_mock), + ): + self.module = netscaler_cs_vserver + self.exited() + self.assertNotIn(call.save_config(), client_mock.mock_calls) + + def test_new_cs_vserver_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_cs_vserver + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + server_proxy_attrs = { + 'diff_object.return_value': {}, + } + cs_vserver_proxy_mock = Mock() + cs_vserver_proxy_mock.configure_mock(**server_proxy_attrs) + config_proxy_mock = Mock(return_value=cs_vserver_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_vserver', + get_nitro_client=m, + cs_vserver_exists=Mock(side_effect=[False, True]), + cs_vserver_identical=Mock(side_effect=[True]), + ensure_feature_is_enabled=Mock(return_value=True), + ConfigProxy=config_proxy_mock, + nitro_exception=self.MockException, + do_state_change=Mock(return_value=Mock(errorcode=0)), + ): + self.module = netscaler_cs_vserver + self.exited() + cs_vserver_proxy_mock.assert_has_calls([call.add()]) + + def test_modified_cs_vserver_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_cs_vserver + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + server_proxy_attrs = { + 'diff_object.return_value': {}, + } + cs_vserver_proxy_mock = Mock() + cs_vserver_proxy_mock.configure_mock(**server_proxy_attrs) + config_proxy_mock = Mock(return_value=cs_vserver_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_vserver', + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + cs_vserver_exists=Mock(side_effect=[True, True]), + cs_vserver_identical=Mock(side_effect=[False, True]), + ensure_feature_is_enabled=Mock(return_value=True), + nitro_exception=self.MockException, + do_state_change=Mock(return_value=Mock(errorcode=0)), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_cs_vserver + self.exited() + cs_vserver_proxy_mock.assert_has_calls([call.update()]) + + def test_absent_cs_vserver_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_cs_vserver + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + server_proxy_attrs = { + 'diff_object.return_value': {}, + } + cs_vserver_proxy_mock = Mock() + cs_vserver_proxy_mock.configure_mock(**server_proxy_attrs) + config_proxy_mock = Mock(return_value=cs_vserver_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_vserver', + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + cs_vserver_exists=Mock(side_effect=[True, False]), + cs_vserver_identical=Mock(side_effect=[False, True]), + ensure_feature_is_enabled=Mock(return_value=True), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_cs_vserver + self.exited() + cs_vserver_proxy_mock.assert_has_calls([call.delete()]) + + def test_present_cs_vserver_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_cs_vserver + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + server_proxy_attrs = { + 'diff_object.return_value': {}, + } + cs_vserver_proxy_mock = Mock() + cs_vserver_proxy_mock.configure_mock(**server_proxy_attrs) + config_proxy_mock = Mock(return_value=cs_vserver_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_vserver', + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + cs_vserver_exists=Mock(side_effect=[True, True]), + cs_vserver_identical=Mock(side_effect=[True, True]), + ensure_feature_is_enabled=Mock(return_value=True), + do_state_change=Mock(return_value=Mock(errorcode=0)), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_cs_vserver + self.exited() + cs_vserver_proxy_mock.assert_not_called() + + def test_absent_cs_vserver_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_cs_vserver + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + server_proxy_attrs = { + 'diff_object.return_value': {}, + } + cs_vserver_proxy_mock = Mock() + cs_vserver_proxy_mock.configure_mock(**server_proxy_attrs) + config_proxy_mock = Mock(return_value=cs_vserver_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_vserver', + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + cs_vserver_exists=Mock(side_effect=[False, False]), + cs_vserver_identical=Mock(side_effect=[False, False]), + ensure_feature_is_enabled=Mock(return_value=True), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_cs_vserver + self.exited() + cs_vserver_proxy_mock.assert_not_called() + + def test_present_cs_vserver_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_cs_vserver + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + server_proxy_attrs = { + 'diff_object.return_value': {}, + } + cs_vserver_proxy_mock = Mock() + cs_vserver_proxy_mock.configure_mock(**server_proxy_attrs) + config_proxy_mock = Mock(return_value=cs_vserver_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_vserver', + nitro_exception=self.MockException, + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + cs_vserver_exists=Mock(side_effect=[True, True]), + cs_vserver_identical=Mock(side_effect=[False, False]), + ensure_feature_is_enabled=Mock(return_value=True), + do_state_change=Mock(return_value=Mock(errorcode=0)), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_cs_vserver + result = self.failed() + self.assertEqual(result['msg'], 'CS vserver differs from configured') + self.assertTrue(result['failed']) + + def test_present_cs_vserver_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_cs_vserver + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + server_proxy_attrs = { + 'diff_object.return_value': {}, + } + cs_vserver_proxy_mock = Mock() + cs_vserver_proxy_mock.configure_mock(**server_proxy_attrs) + config_proxy_mock = Mock(return_value=cs_vserver_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_vserver', + nitro_exception=self.MockException, + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + cs_vserver_exists=Mock(side_effect=[False, False]), + cs_vserver_identical=Mock(side_effect=[False, False]), + ensure_feature_is_enabled=Mock(return_value=True), + do_state_change=Mock(return_value=Mock(errorcode=0)), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_cs_vserver + result = self.failed() + self.assertEqual(result['msg'], 'CS vserver does not exist') + self.assertTrue(result['failed']) + + def test_present_cs_vserver_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_cs_vserver + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + server_proxy_attrs = { + 'diff_object.return_value': {}, + } + cs_vserver_proxy_mock = Mock() + cs_vserver_proxy_mock.configure_mock(**server_proxy_attrs) + config_proxy_mock = Mock(return_value=cs_vserver_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_vserver', + nitro_exception=self.MockException, + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=['domain']), + cs_vserver_exists=Mock(side_effect=[True, True]), + cs_vserver_identical=Mock(side_effect=[False, False]), + ensure_feature_is_enabled=Mock(return_value=True), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_cs_vserver + result = self.failed() + self.assertEqual(result['msg'], 'Cannot update immutable attributes [\'domain\']') + self.assertTrue(result['failed']) + + def test_absent_cs_vserver_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_cs_vserver + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + server_proxy_attrs = { + 'diff_object.return_value': {}, + } + cs_vserver_proxy_mock = Mock() + cs_vserver_proxy_mock.configure_mock(**server_proxy_attrs) + config_proxy_mock = Mock(return_value=cs_vserver_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_vserver', + nitro_exception=self.MockException, + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + cs_vserver_exists=Mock(side_effect=[True, True]), + cs_vserver_identical=Mock(side_effect=[False, False]), + ensure_feature_is_enabled=Mock(return_value=True), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_cs_vserver + result = self.failed() + self.assertEqual(result['msg'], 'CS vserver 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_cs_vserver + + 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_cs_vserver', + cs_vserver_exists=m, + ensure_feature_is_enabled=Mock(return_value=True), + nitro_exception=MockException + ): + self.module = netscaler_cs_vserver + 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_cs_vserver + + 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_cs_vserver', + cs_vserver_exists=m, + ensure_feature_is_enabled=Mock(return_value=True), + nitro_exception=MockException + ): + self.module = netscaler_cs_vserver + result = self.failed() + self.assertTrue( + result['msg'].startswith('nitro exception'), + msg='Nitro exception not caught on operation absent' + ) + + def test_disabled_state_change_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_cs_vserver + + cs_vserver_proxy_mock = Mock() + + do_state_change_mock = Mock(return_value=Mock(errorcode=0)) + client_mock = Mock() + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_vserver', + get_nitro_client=Mock(return_value=client_mock), + ConfigProxy=Mock(return_value=cs_vserver_proxy_mock), + ensure_feature_is_enabled=Mock(return_value=True), + cs_vserver_exists=Mock(side_effect=[True, True]), + cs_vserver_identical=Mock(side_effect=[True, True]), + nitro_exception=self.MockException, + do_state_change=do_state_change_mock, + ): + self.module = netscaler_cs_vserver + self.exited() + self.assertTrue(len(do_state_change_mock.mock_calls) > 0, msg='Did not call state change') + + def test_cs_vserver_ssl_called(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + servicetype='SSL', + )) + from ansible.modules.network.netscaler import netscaler_cs_vserver + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + server_proxy_attrs = { + 'diff_object.return_value': {}, + } + cs_vserver_proxy_mock = Mock() + cs_vserver_proxy_mock.configure_mock(**server_proxy_attrs) + config_proxy_mock = Mock(return_value=cs_vserver_proxy_mock) + ssl_certkey_bindings_sync_mock = Mock() + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_vserver', + nitro_exception=self.MockException, + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + cs_vserver_exists=Mock(side_effect=[True, True]), + cs_vserver_identical=Mock(side_effect=[False, True]), + ensure_feature_is_enabled=Mock(return_value=True), + ssl_certkey_bindings_identical=Mock(side_effect=[False, True]), + ssl_certkey_bindings_sync=ssl_certkey_bindings_sync_mock, + do_state_change=Mock(return_value=Mock(errorcode=0)), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_cs_vserver + result = self.exited() + self.assertTrue(result['changed']) + self.assertTrue(ssl_certkey_bindings_sync_mock.called) + + def test_cs_vserver_ssl_not_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_cs_vserver + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + server_proxy_attrs = { + 'diff_object.return_value': {}, + } + cs_vserver_proxy_mock = Mock() + cs_vserver_proxy_mock.configure_mock(**server_proxy_attrs) + config_proxy_mock = Mock(return_value=cs_vserver_proxy_mock) + ssl_certkey_bindings_sync_mock = Mock() + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_vserver', + nitro_exception=self.MockException, + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + cs_vserver_exists=Mock(side_effect=[True, True]), + cs_vserver_identical=Mock(side_effect=[False, True]), + ensure_feature_is_enabled=Mock(return_value=True), + ssl_certkey_bindings_identical=Mock(side_effect=[False, True]), + ssl_certkey_bindings_sync=ssl_certkey_bindings_sync_mock, + do_state_change=Mock(return_value=Mock(errorcode=0)), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_cs_vserver + result = self.exited() + self.assertTrue(result['changed']) + self.assertFalse(ssl_certkey_bindings_sync_mock.called)