From 36537186e3b9f7ca5e9c6f95aff4eb6d0aa77317 Mon Sep 17 00:00:00 2001 From: George Nikolopoulos <giorgos.nikolopoulos@citrix.com> Date: Tue, 1 Aug 2017 20:35:29 +0300 Subject: [PATCH] New module: manage Citrix Netscaler content switching policy configuration (network/netscaler/netscaler_cs_policy) (#26189) * Add netscaler_cs_policy * Correct version_added --- .../network/netscaler/netscaler_cs_policy.py | 298 ++++++++++++++++ .../netscaler_cs_policy/defaults/main.yaml | 6 + .../netscaler_cs_policy/sample_inventory | 5 + .../roles/netscaler_cs_policy/tasks/main.yaml | 6 + .../netscaler_cs_policy/tasks/nitro.yaml | 14 + .../netscaler_cs_policy/tasks/testbed.yaml | 15 + .../tests/nitro/policy_domain.yaml | 85 +++++ .../tests/nitro/policy_domain/remove.yaml | 13 + .../tests/nitro/policy_domain/setup.yaml | 14 + .../tests/nitro/policy_domain/update.yaml | 13 + .../tests/nitro/policy_rule.yaml | 57 +++ .../tests/nitro/policy_rule/remove.yaml | 13 + .../tests/nitro/policy_rule/setup.yaml | 15 + .../tests/nitro/policy_url.yaml | 57 +++ .../tests/nitro/policy_url/remove.yaml | 13 + .../tests/nitro/policy_url/setup.yaml | 14 + .../netscaler/test_netscaler_cs_policy.py | 324 ++++++++++++++++++ 17 files changed, 962 insertions(+) create mode 100644 lib/ansible/modules/network/netscaler/netscaler_cs_policy.py create mode 100644 test/integration/roles/netscaler_cs_policy/defaults/main.yaml create mode 100644 test/integration/roles/netscaler_cs_policy/sample_inventory create mode 100644 test/integration/roles/netscaler_cs_policy/tasks/main.yaml create mode 100644 test/integration/roles/netscaler_cs_policy/tasks/nitro.yaml create mode 100644 test/integration/roles/netscaler_cs_policy/tasks/testbed.yaml create mode 100644 test/integration/roles/netscaler_cs_policy/tests/nitro/policy_domain.yaml create mode 100644 test/integration/roles/netscaler_cs_policy/tests/nitro/policy_domain/remove.yaml create mode 100644 test/integration/roles/netscaler_cs_policy/tests/nitro/policy_domain/setup.yaml create mode 100644 test/integration/roles/netscaler_cs_policy/tests/nitro/policy_domain/update.yaml create mode 100644 test/integration/roles/netscaler_cs_policy/tests/nitro/policy_rule.yaml create mode 100644 test/integration/roles/netscaler_cs_policy/tests/nitro/policy_rule/remove.yaml create mode 100644 test/integration/roles/netscaler_cs_policy/tests/nitro/policy_rule/setup.yaml create mode 100644 test/integration/roles/netscaler_cs_policy/tests/nitro/policy_url.yaml create mode 100644 test/integration/roles/netscaler_cs_policy/tests/nitro/policy_url/remove.yaml create mode 100644 test/integration/roles/netscaler_cs_policy/tests/nitro/policy_url/setup.yaml create mode 100644 test/units/modules/network/netscaler/test_netscaler_cs_policy.py diff --git a/lib/ansible/modules/network/netscaler/netscaler_cs_policy.py b/lib/ansible/modules/network/netscaler/netscaler_cs_policy.py new file mode 100644 index 0000000000..6826f3bfc7 --- /dev/null +++ b/lib/ansible/modules/network/netscaler/netscaler_cs_policy.py @@ -0,0 +1,298 @@ +#!/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 <http://www.gnu.org/licenses/>. +# + + +ANSIBLE_METADATA = {'status': ['preview'], + 'supported_by': 'community', + 'metadata_version': '1.0'} + + +DOCUMENTATION = ''' +--- +module: netscaler_cs_policy +short_description: Manage content switching policy +description: + - Manage content switching policy. + - "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: + + policyname: + description: + - >- + Name for the content switching policy. Must begin with an ASCII alphanumeric or underscore C(_) + character, and must contain only ASCII alphanumeric, underscore, hash C(#), period C(.), space C( ), colon + C(:), at sign C(@), equal sign C(=), and hyphen C(-) characters. Cannot be changed after a policy is + created. + - "The following requirement applies only to the NetScaler CLI:" + - >- + If the name includes one or more spaces, enclose the name in double or single quotation marks (for + example, my policy or my policy). + - "Minimum length = 1" + + url: + description: + - >- + URL string that is matched with the URL of a request. Can contain a wildcard character. Specify the + string value in the following format: C([[prefix] [*]] [.suffix]). + - "Minimum length = 1" + - "Maximum length = 208" + + rule: + description: + - >- + Expression, or name of a named expression, against which traffic is evaluated. Written in the classic + or default syntax. + - "Note:" + - >- + Maximum length of a string literal in the expression is 255 characters. A longer string can be split + into smaller strings of up to 255 characters each, and the smaller strings concatenated with the + + operator. For example, you can create a 500-character string as follows: '"<string of 255 + characters>" + "<string of 245 characters>"' + + domain: + description: + - "The domain name. The string value can range to 63 characters." + - "Minimum length = 1" + + action: + description: + - >- + Content switching action that names the target load balancing virtual server to which the traffic is + switched. + +extends_documentation_fragment: netscaler +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' +- name: Create url cs policy + delegate_to: localhost + netscaler_cs_policy: + nsip: 172.18.0.2 + nitro_user: nsroot + nitro_pass: nsroot + validate_certs: no + + state: present + + policyname: policy_1 + url: /example/ +''' + +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: "Could not load nitro python sdk" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dict + sample: { 'url': 'difference. ours: (str) example1 other: (str) /example1' } +''' + +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 +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.cs.cspolicy import cspolicy + 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 policy_exists(client, module): + log('Checking if policy exists') + if cspolicy.count_filtered(client, 'policyname:%s' % module.params['policyname']) > 0: + return True + else: + return False + + +def policy_identical(client, module, cspolicy_proxy): + log('Checking if defined policy is identical to configured') + if cspolicy.count_filtered(client, 'policyname:%s' % module.params['policyname']) == 0: + return False + policy_list = cspolicy.get_filtered(client, 'policyname:%s' % module.params['policyname']) + diff_dict = cspolicy_proxy.diff_object(policy_list[0]) + if 'ip' in diff_dict: + del diff_dict['ip'] + if len(diff_dict) == 0: + return True + else: + return False + + +def diff_list(client, module, cspolicy_proxy): + policy_list = cspolicy.get_filtered(client, 'policyname:%s' % module.params['policyname']) + return cspolicy_proxy.diff_object(policy_list[0]) + + +def main(): + + module_specific_arguments = dict( + policyname=dict(type='str'), + url=dict(type='str'), + rule=dict(type='str'), + domain=dict(type='str'), + action=dict(type='str'), + ) + + hand_inserted_arguments = dict( + ) + + 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)) == "<class 'requests.exceptions.ConnectionError'>": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "<class 'requests.exceptions.SSLError'>": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'policyname', + 'url', + 'rule', + 'domain', + 'action', + ] + readonly_attrs = [ + 'vstype', + 'hits', + 'bindhits', + 'labelname', + 'labeltype', + 'priority', + 'activepolicy', + 'cspolicytype', + ] + + transforms = { + } + + # Instantiate config proxy + cspolicy_proxy = ConfigProxy( + actual=cspolicy(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + transforms=transforms, + ) + + try: + ensure_feature_is_enabled(client, 'CS') + + # Apply appropriate state + if module.params['state'] == 'present': + log('Sanity checks for state present') + if not policy_exists(client, module): + if not module.check_mode: + cspolicy_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not policy_identical(client, module, cspolicy_proxy): + if not module.check_mode: + cspolicy_proxy.update() + 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 present') + if not policy_exists(client, module): + module.fail_json(msg='Policy does not exist', **module_result) + if not policy_identical(client, module, cspolicy_proxy): + module.fail_json(msg='Policy differs from configured', diff=diff_list(client, module, cspolicy_proxy), **module_result) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if policy_exists(client, module): + if not module.check_mode: + cspolicy_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 policy_exists(client, module): + module.fail_json(msg='Policy 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_policy/defaults/main.yaml b/test/integration/roles/netscaler_cs_policy/defaults/main.yaml new file mode 100644 index 0000000000..641801f660 --- /dev/null +++ b/test/integration/roles/netscaler_cs_policy/defaults/main.yaml @@ -0,0 +1,6 @@ +--- +testcase: "*" +test_cases: [] + +nitro_user: nsroot +nitro_pass: nsroot diff --git a/test/integration/roles/netscaler_cs_policy/sample_inventory b/test/integration/roles/netscaler_cs_policy/sample_inventory new file mode 100644 index 0000000000..4263579691 --- /dev/null +++ b/test/integration/roles/netscaler_cs_policy/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_policy/tasks/main.yaml b/test/integration/roles/netscaler_cs_policy/tasks/main.yaml new file mode 100644 index 0000000000..9a197e4d77 --- /dev/null +++ b/test/integration/roles/netscaler_cs_policy/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_policy/tasks/nitro.yaml b/test/integration/roles/netscaler_cs_policy/tasks/nitro.yaml new file mode 100644 index 0000000000..00ab502dda --- /dev/null +++ b/test/integration/roles/netscaler_cs_policy/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_policy/tasks/testbed.yaml b/test/integration/roles/netscaler_cs_policy/tasks/testbed.yaml new file mode 100644 index 0000000000..beee58edb4 --- /dev/null +++ b/test/integration/roles/netscaler_cs_policy/tasks/testbed.yaml @@ -0,0 +1,15 @@ +--- + + +- name: Setup cs action + delegate_to: localhost + + netscaler_cs_action: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: "{{ state }}" + + name: action-1 + targetvserverexpr: '"mylb_" + HTTP.REQ.URL.SUFFIX' diff --git a/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_domain.yaml b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_domain.yaml new file mode 100644 index 0000000000..652ba51bde --- /dev/null +++ b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_domain.yaml @@ -0,0 +1,85 @@ +--- + +- include: "{{ role_path }}/tests/nitro/policy_domain/setup.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/policy_domain/setup.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/policy_domain/setup.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/policy_domain/setup.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/policy_domain/update.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/policy_domain/update.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/policy_domain/update.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/policy_domain/update.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/policy_domain/remove.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/policy_domain/remove.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/policy_domain/remove.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/policy_domain/remove.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed diff --git a/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_domain/remove.yaml b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_domain/remove.yaml new file mode 100644 index 0000000000..a698feb494 --- /dev/null +++ b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_domain/remove.yaml @@ -0,0 +1,13 @@ +--- + +- name: Setup cs policy + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_policy: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: absent + policyname: somepolicy diff --git a/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_domain/setup.yaml b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_domain/setup.yaml new file mode 100644 index 0000000000..114523c8af --- /dev/null +++ b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_domain/setup.yaml @@ -0,0 +1,14 @@ +--- + + +- name: Setup cs policy + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_policy: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + policyname: somepolicy + domain: example.com diff --git a/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_domain/update.yaml b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_domain/update.yaml new file mode 100644 index 0000000000..a3191541af --- /dev/null +++ b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_domain/update.yaml @@ -0,0 +1,13 @@ +--- + +- name: Update cs policy + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_policy: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + policyname: somepolicy + domain: example2.com diff --git a/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_rule.yaml b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_rule.yaml new file mode 100644 index 0000000000..cedd225ead --- /dev/null +++ b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_rule.yaml @@ -0,0 +1,57 @@ +--- + +- include: "{{ role_path }}/tests/nitro/policy_rule/setup.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/policy_rule/setup.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/policy_rule/setup.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/policy_rule/setup.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/policy_rule/remove.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/policy_rule/remove.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/policy_rule/remove.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/policy_rule/remove.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed diff --git a/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_rule/remove.yaml b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_rule/remove.yaml new file mode 100644 index 0000000000..1fc4dc256f --- /dev/null +++ b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_rule/remove.yaml @@ -0,0 +1,13 @@ +--- + +- name: Setup cs policy + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_policy: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: absent + policyname: somepolicy_rule diff --git a/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_rule/setup.yaml b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_rule/setup.yaml new file mode 100644 index 0000000000..8c85636a91 --- /dev/null +++ b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_rule/setup.yaml @@ -0,0 +1,15 @@ +--- + + +- name: Setup cs policy + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_policy: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + policyname: somepolicy_rule + rule: CLIENT.IP.SRC.SUBNET(24).EQ(10.217.84.0) + action: action-1 diff --git a/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_url.yaml b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_url.yaml new file mode 100644 index 0000000000..8b8b3db54c --- /dev/null +++ b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_url.yaml @@ -0,0 +1,57 @@ +--- + +- include: "{{ role_path }}/tests/nitro/policy_url/setup.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/policy_url/setup.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/policy_url/setup.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/policy_url/setup.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/policy_url/remove.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/policy_url/remove.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/policy_url/remove.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/policy_url/remove.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed diff --git a/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_url/remove.yaml b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_url/remove.yaml new file mode 100644 index 0000000000..a698feb494 --- /dev/null +++ b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_url/remove.yaml @@ -0,0 +1,13 @@ +--- + +- name: Setup cs policy + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_policy: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: absent + policyname: somepolicy diff --git a/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_url/setup.yaml b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_url/setup.yaml new file mode 100644 index 0000000000..d9f2c6db0e --- /dev/null +++ b/test/integration/roles/netscaler_cs_policy/tests/nitro/policy_url/setup.yaml @@ -0,0 +1,14 @@ +--- + + +- name: Setup cs policy + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_cs_policy: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + policyname: somepolicy + url: /example.com/basket diff --git a/test/units/modules/network/netscaler/test_netscaler_cs_policy.py b/test/units/modules/network/netscaler/test_netscaler_cs_policy.py new file mode 100644 index 0000000000..42d5f2f8de --- /dev/null +++ b/test/units/modules/network/netscaler/test_netscaler_cs_policy.py @@ -0,0 +1,324 @@ + +# 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 <http://www.gnu.org/licenses/>. +# + +from ansible.compat.tests.mock import patch, Mock, MagicMock, call + +import sys + +if sys.version_info[:2] != (2, 6): + import requests + + +from .netscaler_module import TestModule, nitro_base_patcher, set_module_args + + +class TestNetscalerCSPolicyModule(TestModule): + + @classmethod + def setUpClass(cls): + class MockException(Exception): + pass + cls.MockException = MockException + m = MagicMock() + nssrc_modules_mock = { + 'nssrc.com.citrix.netscaler.nitro.resource.config.cs': m, + 'nssrc.com.citrix.netscaler.nitro.resource.config.cs.cspolicy': 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 set_module_state(self, state): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state=state, + )) + + def setUp(self): + self.nitro_base_patcher.start() + self.nitro_specific_patcher.start() + + 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 + self.set_module_state('present') + self.nitro_base_patcher.stop() + self.nitro_specific_patcher.stop() + from ansible.modules.network.netscaler import netscaler_cs_policy + self.module = netscaler_cs_policy + result = self.failed() + self.assertEqual(result['msg'], 'Could not load nitro python sdk') + + def test_graceful_nitro_error_on_login(self): + self.set_module_state('present') + from ansible.modules.network.netscaler import netscaler_cs_policy + + 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_policy.get_nitro_client', m): + with patch('ansible.modules.network.netscaler.netscaler_cs_policy.nitro_exception', MockException): + self.module = netscaler_cs_policy + 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') + self.set_module_state('present') + from ansible.modules.network.netscaler import netscaler_cs_policy + + 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_policy', + get_nitro_client=m, + nitro_exception=self.MockException, + ): + self.module = netscaler_cs_policy + result = self.failed() + self.assertTrue(result['msg'].startswith('Connection error'), msg='Connection error was not handled gracefully') + + def test_graceful_login_error(self): + self.set_module_state('present') + from ansible.modules.network.netscaler import netscaler_cs_policy + + 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_policy', + get_nitro_client=m, + nitro_exception=MockException, + ): + self.module = netscaler_cs_policy + result = self.failed() + self.assertTrue(result['msg'].startswith('SSL Error'), msg='SSL Error was not handled gracefully') + + def test_create_non_existing_cs_policy(self): + self.set_module_state('present') + from ansible.modules.network.netscaler import netscaler_cs_policy + cs_policy_mock = MagicMock() + attrs = { + 'diff_object.return_value': {}, + } + cs_policy_mock.configure_mock(**attrs) + + m = MagicMock(return_value=cs_policy_mock) + policy_exists_mock = Mock(side_effect=[False, True]) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_policy', + ConfigProxy=m, + policy_exists=policy_exists_mock, + nitro_exception=self.MockException, + ensure_feature_is_enabled=Mock(), + ): + self.module = netscaler_cs_policy + result = self.exited() + cs_policy_mock.assert_has_calls([call.add()]) + self.assertTrue(result['changed'], msg='Change not recorded') + + def test_update_cs_policy_when_cs_policy_differs(self): + self.set_module_state('present') + from ansible.modules.network.netscaler import netscaler_cs_policy + cs_policy_mock = MagicMock() + attrs = { + 'diff_object.return_value': {}, + } + cs_policy_mock.configure_mock(**attrs) + + m = MagicMock(return_value=cs_policy_mock) + policy_exists_mock = Mock(side_effect=[True, True]) + policy_identical_mock = Mock(side_effect=[False, True]) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_policy', + ConfigProxy=m, + policy_exists=policy_exists_mock, + policy_identical=policy_identical_mock, + ensure_feature_is_enabled=Mock(), + nitro_exception=self.MockException, + ): + self.module = netscaler_cs_policy + result = self.exited() + cs_policy_mock.assert_has_calls([call.update()]) + self.assertTrue(result['changed'], msg='Change not recorded') + + def test_no_change_to_module_when_all_identical(self): + self.set_module_state('present') + from ansible.modules.network.netscaler import netscaler_cs_policy + cs_policy_mock = MagicMock() + attrs = { + 'diff_object.return_value': {}, + } + cs_policy_mock.configure_mock(**attrs) + + m = MagicMock(return_value=cs_policy_mock) + policy_exists_mock = Mock(side_effect=[True, True]) + policy_identical_mock = Mock(side_effect=[True, True]) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_policy', + ConfigProxy=m, + policy_exists=policy_exists_mock, + policy_identical=policy_identical_mock, + ensure_feature_is_enabled=Mock(), + nitro_exception=self.MockException, + ): + self.module = netscaler_cs_policy + result = self.exited() + self.assertFalse(result['changed'], msg='Erroneous changed status update') + + def test_absent_operation(self): + self.set_module_state('absent') + from ansible.modules.network.netscaler import netscaler_cs_policy + cs_policy_mock = MagicMock() + attrs = { + 'diff_object.return_value': {}, + } + cs_policy_mock.configure_mock(**attrs) + + m = MagicMock(return_value=cs_policy_mock) + policy_exists_mock = Mock(side_effect=[True, False]) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_policy', + ConfigProxy=m, + policy_exists=policy_exists_mock, + nitro_exception=self.MockException, + ensure_feature_is_enabled=Mock(), + + ): + self.module = netscaler_cs_policy + result = self.exited() + cs_policy_mock.assert_has_calls([call.delete()]) + self.assertTrue(result['changed'], msg='Changed status not set correctly') + + def test_absent_operation_no_change(self): + self.set_module_state('absent') + from ansible.modules.network.netscaler import netscaler_cs_policy + cs_policy_mock = MagicMock() + attrs = { + 'diff_object.return_value': {}, + } + cs_policy_mock.configure_mock(**attrs) + + m = MagicMock(return_value=cs_policy_mock) + policy_exists_mock = Mock(side_effect=[False, False]) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_policy', + ConfigProxy=m, + policy_exists=policy_exists_mock, + nitro_exception=self.MockException, + ensure_feature_is_enabled=Mock(), + + ): + self.module = netscaler_cs_policy + result = self.exited() + cs_policy_mock.assert_not_called() + self.assertFalse(result['changed'], msg='Changed status not set correctly') + + def test_graceful_nitro_exception_operation_present(self): + self.set_module_state('present') + from ansible.modules.network.netscaler import netscaler_cs_policy + + 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_policy', + policy_exists=m, + ensure_feature_is_enabled=Mock(), + nitro_exception=MockException + ): + self.module = netscaler_cs_policy + result = self.failed() + self.assertTrue( + result['msg'].startswith('nitro exception'), + msg='Nitro exception not caught on operation present' + ) + + def test_graceful_nitro_exception_operation_absent(self): + self.set_module_state('absent') + from ansible.modules.network.netscaler import netscaler_cs_policy + + 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_policy', + policy_exists=m, + nitro_exception=MockException, + ensure_feature_is_enabled=Mock(), + ): + self.module = netscaler_cs_policy + result = self.failed() + self.assertTrue( + result['msg'].startswith('nitro exception'), + msg='Nitro exception not caught on operation absent' + ) + + def test_ensure_feature_is_enabled_called(self): + self.set_module_state('present') + from ansible.modules.network.netscaler import netscaler_cs_policy + + client_mock = Mock() + ensure_feature_is_enabled_mock = Mock() + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_cs_policy', + get_nitro_client=Mock(return_value=client_mock), + policy_exists=Mock(side_effect=[True, True]), + nitro_exception=self.MockException, + ensure_feature_is_enabled=ensure_feature_is_enabled_mock, + ): + self.module = netscaler_cs_policy + result = self.exited() + ensure_feature_is_enabled_mock.assert_has_calls([call(client_mock, 'CS')])