From feb212b0a177f3f451c5a57a1c6a13d12192cbe7 Mon Sep 17 00:00:00 2001 From: Michael Price Date: Tue, 28 Aug 2018 07:00:19 -0500 Subject: [PATCH] Define module for managing LDAP for E-Series (#42356) This module will allow users to manage LDAP authentication domains for accessing E-Series systems. --- lib/ansible/module_utils/netapp.py | 5 + .../modules/storage/netapp/netapp_e_ldap.py | 390 ++++++++++++++++ .../storage/netapp/test_netapp_e_ldap.py | 417 ++++++++++++++++++ 3 files changed, 812 insertions(+) create mode 100644 lib/ansible/modules/storage/netapp/netapp_e_ldap.py create mode 100644 test/units/modules/storage/netapp/test_netapp_e_ldap.py diff --git a/lib/ansible/module_utils/netapp.py b/lib/ansible/module_utils/netapp.py index 8be3ef87e1..4484bbb2e3 100644 --- a/lib/ansible/module_utils/netapp.py +++ b/lib/ansible/module_utils/netapp.py @@ -203,7 +203,12 @@ def request(url, data=None, headers=None, method='GET', use_proxy=True, headers = { "Content-Type": "application/json", "Accept": "application/json", + } + headers.update({"netapp-client-type": "Ansible-%s" % ansible_version}) + + if not http_agent: + http_agent = "Ansible / %s" % (ansible_version) try: r = open_url(url=url, data=data, headers=headers, method=method, use_proxy=use_proxy, diff --git a/lib/ansible/modules/storage/netapp/netapp_e_ldap.py b/lib/ansible/modules/storage/netapp/netapp_e_ldap.py new file mode 100644 index 0000000000..c0d89cb7d9 --- /dev/null +++ b/lib/ansible/modules/storage/netapp/netapp_e_ldap.py @@ -0,0 +1,390 @@ +#!/usr/bin/python + +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: netapp_e_ldap +short_description: NetApp E-Series manage LDAP integration to use for authentication +description: + - Configure an E-Series system to allow authentication via an LDAP server +version_added: '2.7' +author: Michael Price (@lmprice) +extends_documentation_fragment: + - netapp.eseries +options: + state: + description: + - Enable/disable LDAP support on the system. Disabling will clear out any existing defined domains. + choices: + - present + - absent + default: present + identifier: + description: + - This is a unique identifier for the configuration (for cases where there are multiple domains configured). + - If this is not specified, but I(state=present), we will utilize a default value of 'default'. + username: + description: + - This is the user account that will be used for querying the LDAP server. + - "Example: CN=MyBindAcct,OU=ServiceAccounts,DC=example,DC=com" + required: yes + aliases: + - bind_username + password: + description: + - This is the password for the bind user account. + required: yes + aliases: + - bind_password + attributes: + description: + - The user attributes that should be considered for the group to role mapping. + - Typically this is used with something like 'memberOf', and a user's access is tested against group + membership or lack thereof. + default: memberOf + server: + description: + - This is the LDAP server url. + - The connection string should be specified as using the ldap or ldaps protocol along with the port + information. + aliases: + - server_url + required: yes + name: + description: + - The domain name[s] that will be utilized when authenticating to identify which domain to utilize. + - Default to use the DNS name of the I(server). + - The only requirement is that the name[s] be resolvable. + - "Example: user@example.com" + required: no + search_base: + description: + - The search base is used to find group memberships of the user. + - "Example: ou=users,dc=example,dc=com" + required: yes + role_mappings: + description: + - This is where you specify which groups should have access to what permissions for the + storage-system. + - For example, all users in group A will be assigned all 4 available roles, which will allow access + to all the management functionality of the system (super-user). Those in group B only have the + storage.monitor role, which will allow only read-only acess. + - This is specified as a mapping of regular expressions to a list of roles. See the examples. + - The roles that will be assigned to to the group/groups matching the provided regex. + - storage.admin allows users full read/write access to storage objects and operations. + - storage.monitor allows users read-only access to storage objects and operations. + - support.admin allows users access to hardware, diagnostic information, the Major Event + Log, and other critical support-related functionality, but not the storage configuration. + - security.admin allows users access to authentication/authorization configuration, as well + as the audit log configuration, and certification management. + required: yes + user_attribute: + description: + - This is the attribute we will use to match the provided username when a user attempts to + authenticate. + default: sAMAccountName + log_path: + description: + - A local path to a file to be used for debug logging + required: no +notes: + - Check mode is supported. + - This module allows you to define one or more LDAP domains identified uniquely by I(identifier) to use for + authentication. Authorization is determined by I(role_mappings), in that different groups of users may be given + different (or no), access to certain aspects of the system and API. + - The local user accounts will still be available if the LDAP server becomes unavailable/inaccessible. + - Generally, you'll need to get the details of your organization's LDAP server before you'll be able to configure + the system for using LDAP authentication; every implementation is likely to be very different. + - This API is currently only supported with the Embedded Web Services API v2.0 and higher, or the Web Services Proxy + v3.0 and higher. +''' + +EXAMPLES = ''' + - name: Disable LDAP authentication + netapp_e_ldap: + api_url: "10.1.1.1:8443" + api_username: "admin" + api_password: "myPass" + ssid: "1" + state: absent + + - name: Remove the 'default' LDAP domain configuration + netapp_e_ldap: + state: absent + identifier: default + + - name: Define a new LDAP domain, utilizing defaults where possible + netapp_e_ldap: + state: present + bind_username: "CN=MyBindAccount,OU=ServiceAccounts,DC=example,DC=com" + bind_password: "mySecretPass" + server: "ldap://example.com:389" + search_base: 'OU=Users,DC=example,DC=com' + role_mappings: + ".*dist-dev-storage.*": + - storage.admin + - security.admin + - support.admin + - storage.monitor +''' + +RETURN = """ +msg: + description: Success message + returned: on success + type: string + sample: The ldap settings have been updated. +""" + +import json +import logging + +try: + import urlparse +except ImportError: + import urllib.parse as urlparse + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.netapp import request, eseries_host_argument_spec +from ansible.module_utils._text import to_native + + +class Ldap(object): + NO_CHANGE_MSG = "No changes were necessary." + + def __init__(self): + argument_spec = eseries_host_argument_spec() + argument_spec.update(dict( + state=dict(type='str', required=False, default='present', + choices=['present', 'absent']), + identifier=dict(type='str', required=False, ), + username=dict(type='str', required=False, aliases=['bind_username']), + password=dict(type='str', required=False, aliases=['bind_password'], no_log=True), + name=dict(type='list', required=False, ), + server=dict(type='str', required=False, aliases=['server_url']), + search_base=dict(type='str', required=False, ), + role_mappings=dict(type='dict', required=False, ), + user_attribute=dict(type='str', required=False, default='sAMAccountName'), + attributes=dict(type='list', default=['memberOf'], required=False, ), + log_path=dict(type='str', required=False), + )) + + required_if = [ + ["state", "present", ["username", "password", "server", "search_base", "role_mappings", ]] + ] + + self.module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_if=required_if) + args = self.module.params + self.ldap = args['state'] == 'present' + self.identifier = args['identifier'] + self.username = args['username'] + self.password = args['password'] + self.names = args['name'] + self.server = args['server'] + self.search_base = args['search_base'] + self.role_mappings = args['role_mappings'] + self.user_attribute = args['user_attribute'] + self.attributes = args['attributes'] + + self.ssid = args['ssid'] + self.url = args['api_url'] + self.creds = dict(url_password=args['api_password'], + validate_certs=args['validate_certs'], + url_username=args['api_username'], + timeout=60) + + self.check_mode = self.module.check_mode + + log_path = args['log_path'] + + # logging setup + self._logger = logging.getLogger(self.__class__.__name__) + + if log_path: + logging.basicConfig( + level=logging.DEBUG, filename=log_path, filemode='w', + format='%(relativeCreated)dms %(levelname)s %(module)s.%(funcName)s:%(lineno)d\n %(message)s') + + if not self.url.endswith('/'): + self.url += '/' + + self.embedded = None + self.base_path = None + + def make_configuration(self): + if not self.identifier: + self.identifier = 'default' + + if not self.names: + parts = urlparse.urlparse(self.server) + netloc = parts.netloc + if ':' in netloc: + netloc = netloc.split(':')[0] + self.names = [netloc] + + roles = list() + for regex in self.role_mappings: + for role in self.role_mappings[regex]: + roles.append(dict(groupRegex=regex, + ignoreCase=True, + name=role)) + + domain = dict(id=self.identifier, + ldapUrl=self.server, + bindLookupUser=dict(user=self.username, password=self.password), + roleMapCollection=roles, + groupAttributes=self.attributes, + names=self.names, + searchBase=self.search_base, + userAttribute=self.user_attribute, + ) + + return domain + + def is_embedded(self): + """Determine whether or not we're using the embedded or proxy implemenation of Web Services""" + if self.embedded is None: + url = self.url + try: + parts = urlparse.urlparse(url) + parts = parts._replace(path='/devmgr/utils/') + url = urlparse.urlunparse(parts) + + (rc, result) = request(url + 'about', **self.creds) + self.embedded = not result['runningAsProxy'] + except Exception as err: + self._logger.exception("Failed to retrieve the About information.") + self.module.fail_json(msg="Failed to determine the Web Services implementation type!" + " Array Id [%s]. Error [%s]." + % (self.ssid, to_native(err))) + + return self.embedded + + def get_full_configuration(self): + try: + (rc, result) = request(self.url + self.base_path, **self.creds) + return result + except Exception as err: + self._logger.exception("Failed to retrieve the LDAP configuration.") + self.module.fail_json(msg="Failed to retrieve LDAP configuration! Array Id [%s]. Error [%s]." + % (self.ssid, to_native(err))) + + def get_configuration(self, identifier): + try: + (rc, result) = request(self.url + self.base_path + '%s' % (identifier), ignore_errors=True, **self.creds) + if rc == 200: + return result + elif rc == 404: + return None + else: + self.module.fail_json(msg="Failed to retrieve LDAP configuration! Array Id [%s]. Error [%s]." + % (self.ssid, result)) + except Exception as err: + self._logger.exception("Failed to retrieve the LDAP configuration.") + self.module.fail_json(msg="Failed to retrieve LDAP configuration! Array Id [%s]. Error [%s]." + % (self.ssid, to_native(err))) + + def update_configuration(self): + # Define a new domain based on the user input + domain = self.make_configuration() + + # This is the current list of configurations + current = self.get_configuration(self.identifier) + + update = current != domain + msg = "No changes were necessary for [%s]." % self.identifier + self._logger.info("Is updated: %s", update) + if update and not self.check_mode: + msg = "The configuration changes were made for [%s]." % self.identifier + try: + if current is None: + api = self.base_path + 'addDomain' + else: + api = self.base_path + '%s' % (domain['id']) + + (rc, result) = request(self.url + api, method='POST', data=json.dumps(domain), **self.creds) + except Exception as err: + self._logger.exception("Failed to modify the LDAP configuration.") + self.module.fail_json(msg="Failed to modify LDAP configuration! Array Id [%s]. Error [%s]." + % (self.ssid, to_native(err))) + + return msg, update + + def clear_single_configuration(self, identifier=None): + if identifier is None: + identifier = self.identifier + + configuration = self.get_configuration(identifier) + updated = False + msg = self.NO_CHANGE_MSG + if configuration: + updated = True + msg = "The LDAP domain configuration for [%s] was cleared." % identifier + if not self.check_mode: + try: + (rc, result) = request(self.url + self.base_path + '%s' % identifier, method='DELETE', **self.creds) + except Exception as err: + self.module.fail_json(msg="Failed to remove LDAP configuration! Array Id [%s]. Error [%s]." + % (self.ssid, to_native(err))) + return msg, updated + + def clear_configuration(self): + configuration = self.get_full_configuration() + updated = False + msg = self.NO_CHANGE_MSG + if configuration['ldapDomains']: + updated = True + msg = "The LDAP configuration for all domains was cleared." + if not self.check_mode: + try: + (rc, result) = request(self.url + self.base_path, method='DELETE', ignore_errors=True, **self.creds) + + # Older versions of NetApp E-Series restAPI does not possess an API to remove all existing configs + if rc == 405: + for config in configuration['ldapDomains']: + self.clear_single_configuration(config['id']) + + except Exception as err: + self.module.fail_json(msg="Failed to clear LDAP configuration! Array Id [%s]. Error [%s]." + % (self.ssid, to_native(err))) + return msg, updated + + def get_base_path(self): + embedded = self.is_embedded() + if embedded: + return 'storage-systems/%s/ldap/' % self.ssid + else: + return '/ldap/' + + def update(self): + self.base_path = self.get_base_path() + + if self.ldap: + msg, update = self.update_configuration() + elif self.identifier: + msg, update = self.clear_single_configuration() + else: + msg, update = self.clear_configuration() + self.module.exit_json(msg=msg, changed=update, ) + + def __call__(self, *args, **kwargs): + self.update() + + +def main(): + settings = Ldap() + settings() + + +if __name__ == '__main__': + main() diff --git a/test/units/modules/storage/netapp/test_netapp_e_ldap.py b/test/units/modules/storage/netapp/test_netapp_e_ldap.py new file mode 100644 index 0000000000..f06528a46a --- /dev/null +++ b/test/units/modules/storage/netapp/test_netapp_e_ldap.py @@ -0,0 +1,417 @@ +# (c) 2018, NetApp Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.modules.storage.netapp.netapp_e_ldap import Ldap +from units.modules.utils import ModuleTestCase, set_module_args, AnsibleFailJson, AnsibleExitJson + +__metaclass__ = type +from ansible.compat.tests import mock + + +class LdapTest(ModuleTestCase): + REQUIRED_PARAMS = { + 'api_username': 'admin', + 'api_password': 'password', + 'api_url': 'http://localhost', + 'ssid': '1', + 'state': 'absent', + 'log_path': './debug.log' + + } + REQ_FUNC = 'ansible.modules.storage.netapp.netapp_e_ldap.request' + + def _make_ldap_instance(self): + self._set_args() + ldap = Ldap() + ldap.base_path = '/' + return ldap + + def _set_args(self, **kwargs): + module_args = self.REQUIRED_PARAMS.copy() + module_args.update(kwargs) + set_module_args(module_args) + + def test_init_defaults(self): + """Validate a basic run with required arguments set.""" + self._set_args(log_path=None, + state='present', + username='myBindAcct', + password='myBindPass', + server='ldap://example.com:384', + search_base='OU=Users,DC=example,DC=com', + role_mappings={'.*': ['storage.monitor']}, + ) + + ldap = Ldap() + + def test_init(self): + """Validate a basic run with required arguments set.""" + self._set_args(log_path=None) + ldap = Ldap() + + def test_is_embedded(self): + """Ensure we can properly detect the type of Web Services instance we're utilizing.""" + self._set_args() + + result = dict(runningAsProxy=False) + + with mock.patch(self.REQ_FUNC, return_value=(200, result)): + ldap = Ldap() + embedded = ldap.is_embedded() + self.assertTrue(embedded) + + result = dict(runningAsProxy=True) + + with mock.patch(self.REQ_FUNC, return_value=(200, result)): + ldap = Ldap() + embedded = ldap.is_embedded() + self.assertFalse(embedded) + + def test_is_embedded_fail(self): + """Ensure we fail gracefully when fetching the About data.""" + + self._set_args() + with self.assertRaises(AnsibleFailJson): + with mock.patch(self.REQ_FUNC, side_effect=Exception): + ldap = Ldap() + ldap.is_embedded() + + def test_get_full_configuration(self): + self._set_args() + + resp = dict(result=None) + + with mock.patch(self.REQ_FUNC, return_value=(200, resp)): + ldap = self._make_ldap_instance() + result = ldap.get_full_configuration() + self.assertEqual(resp, result) + + def test_get_full_configuration_failure(self): + self._set_args() + + resp = dict(result=None) + with self.assertRaises(AnsibleFailJson): + with mock.patch(self.REQ_FUNC, side_effect=Exception): + ldap = self._make_ldap_instance() + ldap.get_full_configuration() + + def test_get_configuration(self): + self._set_args() + + resp = dict(result=None) + + with mock.patch(self.REQ_FUNC, return_value=(200, resp)): + ldap = self._make_ldap_instance() + result = ldap.get_configuration('') + self.assertEqual(resp, result) + + with mock.patch(self.REQ_FUNC, return_value=(404, resp)): + ldap = self._make_ldap_instance() + result = ldap.get_configuration('') + self.assertIsNone(result) + + def test_clear_configuration(self): + self._set_args() + + # No changes are required if the domains are empty + config = dict(ldapDomains=[]) + + ldap = self._make_ldap_instance() + with mock.patch.object(ldap, 'get_full_configuration', return_value=config): + with mock.patch(self.REQ_FUNC, return_value=(204, None)): + msg, result = ldap.clear_configuration() + self.assertFalse(result) + + config = dict(ldapDomains=['abc']) + + # When domains exist, we need to clear + ldap = self._make_ldap_instance() + with mock.patch.object(ldap, 'get_full_configuration', return_value=config): + with mock.patch(self.REQ_FUNC, return_value=(204, None)) as req: + msg, result = ldap.clear_configuration() + self.assertTrue(result) + self.assertTrue(req.called) + + # Valid check_mode makes no changes + req.reset_mock() + ldap.check_mode = True + msg, result = ldap.clear_configuration() + self.assertTrue(result) + self.assertFalse(req.called) + + def test_clear_single_configuration(self): + self._set_args() + + # No changes are required if the domains are empty + config = 'abc' + + ldap = self._make_ldap_instance() + with mock.patch.object(ldap, 'get_configuration', return_value=config): + with mock.patch(self.REQ_FUNC, return_value=(204, None)) as req: + msg, result = ldap.clear_single_configuration() + self.assertTrue(result) + + # Valid check_mode makes no changes + req.reset_mock() + ldap.check_mode = True + msg, result = ldap.clear_single_configuration() + self.assertTrue(result) + self.assertFalse(req.called) + + # When domains exist, we need to clear + ldap = self._make_ldap_instance() + with mock.patch.object(ldap, 'get_configuration', return_value=None): + with mock.patch(self.REQ_FUNC, return_value=(204, None)) as req: + msg, result = ldap.clear_single_configuration() + self.assertFalse(result) + self.assertFalse(req.called) + + def test_update_configuration(self): + self._set_args() + + config = dict(id='abc') + body = dict(id='xyz') + + ldap = self._make_ldap_instance() + with mock.patch.object(ldap, 'make_configuration', return_value=body): + with mock.patch.object(ldap, 'get_configuration', return_value=config): + with mock.patch(self.REQ_FUNC, return_value=(200, None)) as req: + msg, result = ldap.update_configuration() + self.assertTrue(result) + + # Valid check_mode makes no changes + req.reset_mock() + ldap.check_mode = True + msg, result = ldap.update_configuration() + self.assertTrue(result) + self.assertFalse(req.called) + + def test_update(self): + self._set_args() + + ldap = self._make_ldap_instance() + with self.assertRaises(AnsibleExitJson): + with mock.patch.object(ldap, 'get_base_path', return_value='/'): + with mock.patch.object(ldap, 'update_configuration', return_value=('', True)) as update: + ldap.ldap = True + msg, result = ldap.update() + self.assertTrue(result) + self.assertTrue(update.called) + + def test_update_disable(self): + self._set_args() + + ldap = self._make_ldap_instance() + with self.assertRaises(AnsibleExitJson): + with mock.patch.object(ldap, 'get_base_path', return_value='/'): + with mock.patch.object(ldap, 'clear_single_configuration', return_value=('', True)) as update: + ldap.ldap = False + ldap.identifier = 'abc' + msg, result = ldap.update() + self.assertTrue(result) + self.assertTrue(update.called) + + def test_update_disable_all(self): + self._set_args() + + ldap = self._make_ldap_instance() + with self.assertRaises(AnsibleExitJson): + with mock.patch.object(ldap, 'get_base_path', return_value='/'): + with mock.patch.object(ldap, 'clear_configuration', return_value=('', True)) as update: + ldap.ldap = False + msg, result = ldap.update() + self.assertTrue(result) + self.assertTrue(update.called) + + def test_get_configuration_failure(self): + self._set_args() + + with self.assertRaises(AnsibleFailJson): + with mock.patch(self.REQ_FUNC, side_effect=Exception): + ldap = self._make_ldap_instance() + ldap.get_configuration('') + + # We expect this for any code not in [200, 404] + with self.assertRaises(AnsibleFailJson): + with mock.patch(self.REQ_FUNC, return_value=(401, '')): + ldap = self._make_ldap_instance() + result = ldap.get_configuration('') + self.assertIsNone(result) + + def test_make_configuration(self): + """Validate the make_configuration method that translates Ansible params to the input body""" + data = dict(log_path=None, + state='present', + username='myBindAcct', + password='myBindPass', + server='ldap://example.com:384', + search_base='OU=Users,DC=example,DC=com', + role_mappings={'.*': ['storage.monitor']}, + ) + + self._set_args(**data) + ldap = Ldap() + expected = dict(id='default', + bindLookupUser=dict(user=data['username'], + password=data['password'], ), + groupAttributes=['memberOf'], + ldapUrl=data['server'], + names=['example.com'], + searchBase=data['search_base'], + roleMapCollection=[{"groupRegex": ".*", + "ignoreCase": True, + "name": "storage.monitor" + } + ], + userAttribute='sAMAccountName' + ) + + actual = ldap.make_configuration() + self.maxDiff = None + self.assertEqual(expected, actual) + + # + # def test_get_config_on_demand_capable_false(self): + # """Ensure we fail correctly if ASUP is not available on this platform""" + # self._set_args() + # + # expected = dict(asupCapable=True, onDemandCapable=False) + # asup = Asup() + # # Expecting an update + # with self.assertRaisesRegexp(AnsibleFailJson, r"not supported"): + # with mock.patch(self.REQ_FUNC, return_value=(200, expected)): + # asup.get_configuration() + # + # def test_get_config(self): + # """Validate retrieving the ASUP configuration""" + # self._set_args() + # + # expected = dict(asupCapable=True, onDemandCapable=True) + # asup = Asup() + # + # with mock.patch(self.REQ_FUNC, return_value=(200, expected)): + # config = asup.get_configuration() + # self.assertEquals(config, expected) + # + # def test_update_configuration(self): + # """Validate retrieving the ASUP configuration""" + # self._set_args(dict(asup='present')) + # + # expected = dict() + # initial = dict(asupCapable=True, + # asupEnabled=True, + # onDemandEnabled=False, + # remoteDiagsEnabled=False, + # schedule=dict(daysOfWeek=[], dailyMinTime=0, weeklyMinTime=0, dailyMaxTime=24, weeklyMaxTime=24)) + # asup = Asup() + # + # with mock.patch(self.REQ_FUNC, return_value=(200, expected)) as req: + # with mock.patch.object(asup, 'get_configuration', return_value=initial): + # updated = asup.update_configuration() + # self.assertTrue(req.called) + # self.assertTrue(updated) + # + # def test_update_configuration_asup_disable(self): + # """Validate retrieving the ASUP configuration""" + # self._set_args(dict(asup='absent')) + # + # expected = dict() + # initial = dict(asupCapable=True, + # asupEnabled=True, + # onDemandEnabled=False, + # remoteDiagsEnabled=False, + # schedule=dict(daysOfWeek=[], dailyMinTime=0, weeklyMinTime=0, dailyMaxTime=24, weeklyMaxTime=24)) + # asup = Asup() + # + # with mock.patch(self.REQ_FUNC, return_value=(200, expected)) as req: + # with mock.patch.object(asup, 'get_configuration', return_value=initial): + # updated = asup.update_configuration() + # self.assertTrue(updated) + # + # self.assertTrue(req.called) + # + # # Ensure it was called with the right arguments + # called_with = req.call_args + # body = json.loads(called_with[1]['data']) + # self.assertFalse(body['asupEnabled']) + # + # def test_update_configuration_enable(self): + # """Validate retrieving the ASUP configuration""" + # self._set_args(dict(asup='enabled')) + # + # expected = dict() + # initial = dict(asupCapable=False, + # asupEnabled=False, + # onDemandEnabled=False, + # remoteDiagsEnabled=False, + # schedule=dict(daysOfWeek=[], dailyMinTime=0, weeklyMinTime=0, dailyMaxTime=24, weeklyMaxTime=24)) + # asup = Asup() + # + # with mock.patch(self.REQ_FUNC, return_value=(200, expected)) as req: + # with mock.patch.object(asup, 'get_configuration', return_value=initial): + # updated = asup.update_configuration() + # self.assertTrue(updated) + # + # self.assertTrue(req.called) + # + # # Ensure it was called with the right arguments + # called_with = req.call_args + # body = json.loads(called_with[1]['data']) + # self.assertTrue(body['asupEnabled']) + # self.assertTrue(body['onDemandEnabled']) + # self.assertTrue(body['remoteDiagsEnabled']) + # + # def test_update_configuration_request_exception(self): + # """Validate exception handling when request throws an exception.""" + # config_response = dict(asupEnabled=True, + # onDemandEnabled=True, + # remoteDiagsEnabled=True, + # schedule=dict(daysOfWeek=[], + # dailyMinTime=0, + # weeklyMinTime=0, + # dailyMaxTime=24, + # weeklyMaxTime=24)) + # + # self._set_args(dict(state="enabled")) + # asup = Asup() + # with self.assertRaises(Exception): + # with mock.patch.object(asup, 'get_configuration', return_value=config_response): + # with mock.patch(self.REQ_FUNC, side_effect=Exception): + # asup.update_configuration() + # + # def test_init_schedule(self): + # """Validate schedule correct schedule initialization""" + # self._set_args(dict(state="enabled", active=True, days=["sunday", "monday", "tuesday"], start=20, end=24)) + # asup = Asup() + # + # self.assertTrue(asup.asup) + # self.assertEquals(asup.days, ["sunday", "monday", "tuesday"]), + # self.assertEquals(asup.start, 1200) + # self.assertEquals(asup.end, 1439) + # + # def test_init_schedule_invalid(self): + # """Validate updating ASUP with invalid schedule fails test.""" + # self._set_args(dict(state="enabled", active=True, start=22, end=20)) + # with self.assertRaisesRegexp(AnsibleFailJson, r"start time is invalid"): + # Asup() + # + # def test_init_schedule_days_invalid(self): + # """Validate updating ASUP with invalid schedule fails test.""" + # self._set_args(dict(state="enabled", active=True, days=["someday", "thataday", "nonday"])) + # with self.assertRaises(AnsibleFailJson): + # Asup() + # + # def test_update(self): + # """Validate updating ASUP with valid schedule passes""" + # initial = dict(asupCapable=True, + # onDemandCapable=True, + # asupEnabled=True, + # onDemandEnabled=False, + # remoteDiagsEnabled=False, + # schedule=dict(daysOfWeek=[], dailyMinTime=0, weeklyMinTime=0, dailyMaxTime=24, weeklyMaxTime=24)) + # self._set_args(dict(state="enabled", active=True, days=["sunday", "monday", "tuesday"], start=10, end=20)) + # asup = Asup() + # with self.assertRaisesRegexp(AnsibleExitJson, r"ASUP settings have been updated"): + # with mock.patch(self.REQ_FUNC, return_value=(200, dict(asupCapable=True))): + # with mock.patch.object(asup, "get_configuration", return_value=initial): + # asup.update()