class NetAppONTAPCommandREST(object): ''' calls a CLI command ''' def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(command=dict(required=True, type='str'), verb=dict( required=True, type='str', choices=['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS']), params=dict(required=False, type='dict', default={}), body=dict(required=False, type='dict', default={}))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.restApi = OntapRestAPI(self.module) parameters = self.module.params # set up state variables self.command = parameters['command'] self.verb = parameters['verb'] self.params = parameters['params'] self.body = parameters['body'] if self.restApi.is_rest(): self.use_rest = True else: self.module.fail_json(msg="use na_ontap_command for non-rest cli") def run_command(self): api = "private/cli/" + self.command if self.verb == 'POST': message, error = self.restApi.post(api, self.body, self.params) elif self.verb == 'GET': message, error = self.restApi.get(api, self.params) elif self.verb == 'PATCH': message, error = self.restApi.patch(api, self.body, self.params) elif self.verb == 'DELETE': message, error = self.restApi.delete(api, self.body, self.params) elif self.verb == 'OPTIONS': message, error = self.restApi.options(api, self.params) else: self.module.fail_json(msg='Error running command %s:' % self.command, exception=traceback.format_exc()) if error: self.module.fail_json(msg=error) return message def apply(self): ''' calls the command and returns raw output ''' changed = True output = self.run_command() self.module.exit_json(changed=changed, msg=output)
class NetAppOntapWwpnAlias(object): def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), wwpn=dict(required=False, type='str'), vserver=dict(required=True, type='str'))) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[('state', 'present', ['wwpn']) ], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # REST API should be used for ONTAP 9.6 or higher. self.restApi = OntapRestAPI(self.module) if self.restApi.is_rest(): self.use_rest = True else: self.module.fail_json(msg="This module only supports REST API.") def get_alias(self, uuid): params = { 'fields': 'alias,wwpn', 'alias': self.parameters['name'], 'svm.uuid': uuid } api = 'network/fc/wwpn-aliases' message, error = self.restApi.get(api, params) if error is not None: self.module.fail_json(msg="Error on fetching wwpn alias: %s" % error) if message['num_records'] > 0: return { 'name': message['records'][0]['alias'], 'wwpn': message['records'][0]['wwpn'], } else: return None def create_alias(self, uuid, is_modify=False): params = { 'alias': self.parameters['name'], 'wwpn': self.parameters['wwpn'], 'svm.uuid': uuid } api = 'network/fc/wwpn-aliases' message, error = self.restApi.post(api, params) if error is not None: if is_modify: self.module.fail_json( msg= "Error on modifying wwpn alias when trying to re-create alias: %s." % error) else: self.module.fail_json(msg="Error on creating wwpn alias: %s." % error) def delete_alias(self, uuid, is_modify=False): params = {'alias': self.parameters['name'], 'svm.uuid': uuid} api = 'network/fc/wwpn-aliases/' message, error = self.restApi.delete(api, params) if error is not None: if is_modify: self.module.fail_json( msg= "Error on modifying wwpn alias when trying to delete alias: %s." % error) else: self.module.fail_json(msg="Error on deleting wwpn alias: %s." % error) def get_svm_uuid(self): """ Get a svm's UUID :return: uuid of the svm. """ params = {'fields': 'uuid', 'name': self.parameters['vserver']} api = "svm/svms" message, error = self.restApi.get(api, params) if error is not None: self.module.fail_json(msg="Error on fetching svm uuid: %s" % error) return message['records'][0]['uuid'] def apply(self): cd_action, uuid, modify = None, None, None uuid = self.get_svm_uuid() current = self.get_alias(uuid) cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_alias(uuid) elif cd_action == 'delete': self.delete_alias(uuid) elif modify: self.delete_alias(uuid, is_modify=True) self.create_alias(uuid, is_modify=True) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPMetroCluster(object): def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(choices=['present'], default='present'), dr_pairs=dict(required=True, type='list', elements='dict', options=dict( node_name=dict(required=True, type='str'), partner_node_name=dict(required=True, type='str') )), partner_cluster_name=dict(required=True, type='str') )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.restApi = OntapRestAPI(self.module) self.use_rest = self.restApi.is_rest() if not self.use_rest: self.module.fail_json(msg="na_ontap_metrocluster only supports REST API") def get_metrocluster(self): attrs = None api = 'cluster/metrocluster' options = {'fields': '*'} message, error = self.restApi.get(api, options) if error: self.module.fail_json(msg=error) if message is not None: local = message['local'] if local['configuration_state'] != "not_configured": attrs = { 'configuration_state': local['configuration_state'], 'partner_cluster_reachable': local['partner_cluster_reachable'], 'partner_cluster_name': local['cluster']['name'] } return attrs def create_metrocluster(self): api = 'cluster/metrocluster' options = {} dr_pairs = [] for pair in self.parameters['dr_pairs']: dr_pairs.append({'node': {'name': pair['node_name']}, 'partner': {'name': pair['partner_node_name']}}) partner_cluster = {'name': self.parameters['partner_cluster_name']} data = {'dr_pairs': dr_pairs, 'partner_cluster': partner_cluster} message, error = self.restApi.post(api, data, options) if error is not None: self.module.fail_json(msg="%s" % error) self.restApi.wait_on_job(message['job'], self.parameters['hostname']) def apply(self): current = self.get_metrocluster() cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_metrocluster() # Since there is no modify or delete, we will return no change else: self.module.fail_json(msg="Modify and Delete currently not support in API") self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapSecurityCertificates(object): ''' object initialize and class methods ''' def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( common_name=dict(required=False, type='str'), name=dict(required=False, type='str'), state=dict(required=False, choices=['present', 'absent'], default='present'), type=dict(required=False, choices=['client', 'server', 'client_ca', 'server_ca', 'root_ca']), svm=dict(required=False, type='str', aliases=['vserver']), public_certificate=dict(required=False, type='str'), private_key=dict(required=False, type='str'), signing_request=dict(required=False, type='str'), expiry_time=dict(required=False, type='str'), key_size=dict(required=False, type='int'), hash_function=dict(required=False, type='str'), intermediate_certificates=dict(required=False, type='list', elements='str'), ignore_name_if_not_supported=dict(required=False, type='bool', default=True) )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if self.parameters.get('name') is None: if self.parameters.get('common_name') is None or self.parameters.get('type') is None: error = "'name' or ('common_name' and 'type') are required parameters." self.module.fail_json(msg=error) # ONTAP 9.6 and 9.7 do not support name. We'll change this to True if we detect an issue. self.ignore_name_param = False # API should be used for ONTAP 9.6 or higher self.rest_api = OntapRestAPI(self.module) if self.rest_api.is_rest(): self.use_rest = True else: self.module.fail_json(msg=self.rest_api.requires_ontap_9_6('na_ontap_security_certificates')) def get_certificate(self): """ Fetch uuid if certificate exists. NOTE: because of a bug in ONTAP 9.6 and 9.7, name is not supported. We are falling back to using common_name and type, but unicity is not guaranteed. :return: Dictionary if certificate with same name is found None if not found """ error = "'name' or ('common_name', 'type') are required." for key in ('name', 'common_name'): if self.parameters.get(key) is None: continue data = {'fields': 'uuid', key: self.parameters[key], } if self.parameters.get('svm') is not None: data['svm.name'] = self.parameters['svm'] else: data['scope'] = 'cluster' if key == 'common_name': if self.parameters.get('type') is not None: data['type'] = self.parameters['type'] else: error = "When using 'common_name', 'type' is required." break api = "security/certificates" message, error = self.rest_api.get(api, data) if error: try: name_not_supported_error = (key == 'name') and (error['message'] == 'Unexpected argument "name".') except (KeyError, TypeError): name_not_supported_error = False if name_not_supported_error: if self.parameters['ignore_name_if_not_supported'] and self.parameters.get('common_name') is not None: # let's attempt a retry using common_name self.ignore_name_param = True continue error = "ONTAP 9.6 and 9.7 do not support 'name'. Use 'common_name' and 'type' as a work-around." # report success, or any other error as is break if error: self.module.fail_json(msg='Error calling API: %s - %s' % (api, error)) if len(message['records']) == 1: return message['records'][0] if len(message['records']) > 1: error = 'Duplicate records with same common_name are preventing safe operations: %s' % repr(message) self.module.fail_json(msg=error) return None def create_or_install_certificate(self): """ Create or install certificate :return: message (should be empty dict) """ required_keys = ['type', 'common_name'] optional_keys = ['public_certificate', 'private_key', 'expiry_time', 'key_size', 'hash_function'] if not self.ignore_name_param: optional_keys.append('name') # special key: svm if not set(required_keys).issubset(set(self.parameters.keys())): self.module.fail_json(msg='Error creating or installing certificate: one or more of the following options are missing: %s' % (', '.join(required_keys))) data = dict() if self.parameters.get('svm') is not None: data['svm'] = {'name': self.parameters['svm']} for key in required_keys + optional_keys: if self.parameters.get(key) is not None: data[key] = self.parameters[key] api = "security/certificates" message, error = self.rest_api.post(api, data) if error: if self.parameters.get('svm') is None and error.get('target') == 'uuid': error['target'] = 'cluster' if error.get('message') == 'duplicate entry': error['message'] += '. Same certificate may already exist under a different name.' self.module.fail_json(msg="Error creating or installing certificate: %s" % error) return message def sign_certificate(self, uuid): """ sign certificate :return: a dictionary with key "public_certificate" """ api = "security/certificates/%s/sign" % uuid data = {'signing_request': self.parameters['signing_request']} optional_keys = ['expiry_time', 'hash_function'] for key in optional_keys: if self.parameters.get(key) is not None: data[key] = self.parameters[key] message, error = self.rest_api.post(api, data) if error: self.module.fail_json(msg="Error signing certificate: %s" % error) return message def delete_certificate(self, uuid): """ Delete certificate :return: message (should be empty dict) """ api = "security/certificates/%s" % uuid message, error = self.rest_api.delete(api) if error: self.module.fail_json(msg="Error deleting certificate: %s" % error) return message def apply(self): """ Apply action to create/install/sign/delete certificate :return: None """ # TODO: add telemetry for REST current = self.get_certificate() cd_action = self.na_helper.get_cd_action(current, self.parameters) message = None if self.parameters.get('signing_request') is not None: error = None if self.parameters['state'] == 'absent': error = "'signing_request' is not supported with 'state' set to 'absent'" elif current is None: scope = 'cluster' if self.parameters.get('svm') is None else "svm: %s" % self.parameters.get('svm') error = "signing certificate with name '%s' not found on %s" % (self.parameters.get('name'), scope) elif cd_action is not None: error = "'signing_request' is exclusive with other actions: create, install, delete" if error is not None: self.module.fail_json(msg=error) self.na_helper.changed = True if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': message = self.create_or_install_certificate() elif cd_action == 'delete': message = self.delete_certificate(current['uuid']) elif self.parameters.get('signing_request') is not None: message = self.sign_certificate(current['uuid']) results = {'changed': self.na_helper.changed} if message: results['ontap_info'] = message self.module.exit_json(**results)
class NetAppOntapUser(object): """ Common operations to manage users and roles. """ def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), applications=dict(required=True, type='list', elements='str', aliases=['application'], choices=['console', 'http', 'ontapi', 'rsh', 'snmp', 'sp', 'service-processor', 'ssh', 'telnet'],), authentication_method=dict(required=True, type='str', choices=['community', 'password', 'publickey', 'domain', 'nsswitch', 'usm', 'cert']), set_password=dict(required=False, type='str', no_log=True), role_name=dict(required=False, type='str'), lock_user=dict(required=False, type='bool'), vserver=dict(required=True, type='str', aliases=['svm']), authentication_protocol=dict(required=False, type='str', choices=['none', 'md5', 'sha', 'sha2-256']), authentication_password=dict(required=False, type='str', no_log=True), engine_id=dict(required=False, type='str'), privacy_protocol=dict(required=False, type='str', choices=['none', 'des', 'aes128']), privacy_password=dict(required=False, type='str', no_log=True), remote_switch_ipaddress=dict(required=False, type='str') )) self.module = AnsibleModule( argument_spec=self.argument_spec, required_if=[ ('state', 'present', ['role_name']) ], supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # REST API should be used for ONTAP 9.6 or higher self.restApi = OntapRestAPI(self.module) # some attributes are not supported in earlier REST implementation unsupported_rest_properties = ['authentication_password', 'authentication_protocol', 'engine_id', 'privacy_password', 'privacy_protocol'] used_unsupported_rest_properties = [x for x in unsupported_rest_properties if x in self.parameters] self.use_rest, error = self.restApi.is_rest(used_unsupported_rest_properties) if error is not None: self.module.fail_json(msg=error) if not self.use_rest: if not HAS_NETAPP_LIB: self.module.fail_json(msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver']) else: if 'snmp' in self.parameters['applications']: self.module.fail_json(msg="Snmp as application is not supported in REST.") def get_user_rest(self): api = 'security/accounts' params = { 'name': self.parameters['name'] } if self.parameters.get('vserver') is None: # vserser is empty for cluster params['scope'] = 'cluster' else: params['owner.name'] = self.parameters['vserver'] message, error = self.restApi.get(api, params) if error: self.module.fail_json(msg='Error while fetching user info: %s' % error) if message['num_records'] == 1: return message['records'][0]['owner']['uuid'], message['records'][0]['name'] if message['num_records'] > 1: self.module.fail_json(msg='Error while fetching user info, found multiple entries: %s' % repr(message)) return None def get_user_details_rest(self, name, uuid): params = { 'fields': 'role,applications,locked' } api = "security/accounts/%s/%s" % (uuid, name) message, error = self.restApi.get(api, params) if error: self.module.fail_json(msg='Error while fetching user details: %s' % error) if message: return_value = { 'role_name': message['role']['name'], 'applications': [app['application'] for app in message['applications']] } if "locked" in message: return_value['lock_user'] = message['locked'] return return_value def get_user(self, application=None): """ Checks if the user exists. :param: application: application to grant access to :return: Dictionary if user found None if user is not found """ security_login_get_iter = netapp_utils.zapi.NaElement('security-login-get-iter') query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-account-info', **{'vserver': self.parameters['vserver'], 'user-name': self.parameters['name'], 'authentication-method': self.parameters['authentication_method']}) if application is not None: query_details.add_new_child('application', application) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) security_login_get_iter.add_child_elem(query) try: result = self.server.invoke_successfully(security_login_get_iter, enable_tunneling=False) if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) >= 1: interface_attributes = result.get_child_by_name('attributes-list').\ get_child_by_name('security-login-account-info') return_value = { 'lock_user': interface_attributes.get_child_content('is-locked'), 'role_name': interface_attributes.get_child_content('role-name') } return return_value return None except netapp_utils.zapi.NaApiError as error: # Error 16034 denotes a user not being found. if to_native(error.code) == "16034": return None # Error 16043 denotes the user existing, but the application missing elif to_native(error.code) == "16043": return None else: self.module.fail_json(msg='Error getting user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def create_user_rest(self, apps=None): app_list = list() if apps is not None: for app in apps: mydict = { "application": app, "authentication_methods": self.parameters['authentication_method'].split(), } app_list.append(mydict) api = 'security/accounts' params = { 'name': self.parameters['name'], 'role.name': self.parameters['role_name'], 'applications': app_list } if self.parameters.get('vserver') is not None: # vserser is empty for cluster params['owner.name'] = self.parameters['vserver'] if 'set_password' in self.parameters: params['password'] = self.parameters['set_password'] if 'lock_user' in self.parameters: params['locked'] = self.parameters['lock_user'] dummy, error = self.restApi.post(api, params) if error: self.module.fail_json(msg='Error while creating user: %s' % error) def create_user(self, application): """ creates the user for the given application and authentication_method :param: application: application to grant access to """ user_create = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-create', **{'vserver': self.parameters['vserver'], 'user-name': self.parameters['name'], 'application': application, 'authentication-method': self.parameters['authentication_method'], 'role-name': self.parameters.get('role_name')}) if self.parameters.get('set_password') is not None: user_create.add_new_child('password', self.parameters.get('set_password')) if self.parameters.get('authentication_method') == 'usm': if self.parameters.get('remote_switch_ipaddress') is not None: user_create.add_new_child('remote-switch-ipaddress', self.parameters.get('remote_switch_ipaddress')) snmpv3_login_info = netapp_utils.zapi.NaElement('snmpv3-login-info') if self.parameters.get('authentication_password') is not None: snmpv3_login_info.add_new_child('authentication-password', self.parameters['authentication_password']) if self.parameters.get('authentication_protocol') is not None: snmpv3_login_info.add_new_child('authentication-protocol', self.parameters['authentication_protocol']) if self.parameters.get('engine_id') is not None: snmpv3_login_info.add_new_child('engine-id', self.parameters['engine_id']) if self.parameters.get('privacy_password') is not None: snmpv3_login_info.add_new_child('privacy-password', self.parameters['privacy_password']) if self.parameters.get('privacy_protocol') is not None: snmpv3_login_info.add_new_child('privacy-protocol', self.parameters['privacy_protocol']) user_create.add_child_elem(snmpv3_login_info) try: self.server.invoke_successfully(user_create, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def lock_unlock_user_rest(self, useruuid, username, value=None): data = { 'locked': value } params = { 'name': self.parameters['name'], 'owner.uuid': useruuid, } api = "security/accounts/%s/%s" % (useruuid, username) dummy, error = self.restApi.patch(api, data, params) if error: self.module.fail_json(msg='Error while locking/unlocking user: %s' % error) def lock_given_user(self): """ locks the user :return: True if user locked False if lock user is not performed :rtype: bool """ user_lock = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-lock', **{'vserver': self.parameters['vserver'], 'user-name': self.parameters['name']}) try: self.server.invoke_successfully(user_lock, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error locking user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def unlock_given_user(self): """ unlocks the user :return: True if user unlocked False if unlock user is not performed :rtype: bool """ user_unlock = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-unlock', **{'vserver': self.parameters['vserver'], 'user-name': self.parameters['name']}) try: self.server.invoke_successfully(user_unlock, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: if to_native(error.code) == '13114': return False else: self.module.fail_json(msg='Error unlocking user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) return True def delete_user_rest(self): uuid, username = self.get_user_rest() data = {} params = { 'name': username, 'owner.uuid': uuid, } api = "security/accounts/%s/%s" % (uuid, username) dummy, error = self.restApi.delete(api, data, params) if error: self.module.fail_json(msg='Error while deleting user : %s' % error) def delete_user(self, application): """ deletes the user for the given application and authentication_method :param: application: application to grant access to """ user_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-delete', **{'vserver': self.parameters['vserver'], 'user-name': self.parameters['name'], 'application': application, 'authentication-method': self.parameters['authentication_method']}) try: self.server.invoke_successfully(user_delete, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error removing user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) @staticmethod def is_repeated_password(message): return message.startswith('New password must be different than last 6 passwords.') \ or message.startswith('New password must be different from last 6 passwords.') \ or message.startswith('New password must be different than the old password.') \ or message.startswith('New password must be different from the old password.') def change_password_rest(self, useruuid, username): data = { 'password': self.parameters['set_password'], } params = { 'name': self.parameters['name'], 'owner.uuid': useruuid, } api = "security/accounts/%s/%s" % (useruuid, username) dummy, error = self.restApi.patch(api, data, params) if error: if 'message' in error and self.is_repeated_password(error['message']): # if the password is reused, assume idempotency return False else: self.module.fail_json(msg='Error while updating user password: %s' % error) return True def change_password(self): """ Changes the password :return: True if password updated False if password is not updated :rtype: bool """ # self.server.set_vserver(self.parameters['vserver']) modify_password = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-modify-password', **{ 'new-password': str(self.parameters.get('set_password')), 'user-name': self.parameters['name']}) try: self.server.invoke_successfully(modify_password, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: if to_native(error.code) == '13114': return False # if the user give the same password, instead of returning an error, return ok if to_native(error.code) == '13214' and self.is_repeated_password(error.message): return False self.module.fail_json(msg='Error setting password for user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) self.server.set_vserver(None) return True def modify_apps_rest(self, useruuid, username, apps=None): app_list = list() if apps is not None: for app in apps: mydict = { "application": app, "authentication_methods": self.parameters['authentication_method'].split(), } app_list.append(mydict) data = { 'role.name': self.parameters['role_name'], 'applications': app_list } params = { 'name': self.parameters['name'], 'owner.uuid': useruuid, } api = "security/accounts/%s/%s" % (useruuid, username) dummy, error = self.restApi.patch(api, data, params) if error: self.module.fail_json(msg='Error while modifying user details: %s' % error) def modify_user(self, application): """ Modify user """ user_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-modify', **{'vserver': self.parameters['vserver'], 'user-name': self.parameters['name'], 'application': application, 'authentication-method': self.parameters['authentication_method'], 'role-name': self.parameters.get('role_name')}) try: self.server.invoke_successfully(user_modify, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def apply_for_rest(self): current = self.get_user_rest() if current is not None: uuid, name = current current = self.get_user_details_rest(name, uuid) cd_action = self.na_helper.get_cd_action(current, self.parameters) modify_decision = self.na_helper.get_modified_attributes(current, self.parameters) if current and 'lock_user' not in current: # REST does not return locked if password is not set if cd_action is None and self.parameters.get('lock_user') is not None: if self.parameters.get('set_password') is None: self.module.fail_json(msg='Error: cannot modify lock state if password is not set.') modify_decision['lock_user'] = self.parameters['lock_user'] self.na_helper.changed = True if self.na_helper.changed and not self.module.check_mode: if cd_action == 'create': self.create_user_rest(self.parameters['applications']) elif cd_action == 'delete': self.delete_user_rest() elif modify_decision: if 'role_name' in modify_decision or 'applications' in modify_decision: self.modify_apps_rest(uuid, name, self.parameters['applications']) if cd_action is None and self.parameters.get('set_password') is not None: # if check_mode, don't attempt to change the password, but assume it would be changed if self.module.check_mode or self.change_password_rest(uuid, name): self.na_helper.changed = True if cd_action is None and self.na_helper.changed and not self.module.check_mode: # lock/unlock actions require password to be set if modify_decision and 'lock_user' in modify_decision: self.lock_unlock_user_rest(uuid, name, self.parameters['lock_user']) self.module.exit_json(changed=self.na_helper.changed) def apply(self): if self.use_rest: self.apply_for_rest() else: create_delete_decision = {} modify_decision = {} netapp_utils.ems_log_event("na_ontap_user", self.server) for application in self.parameters['applications']: current = self.get_user(application) if current is not None: current['lock_user'] = self.na_helper.get_value_for_bool(True, current['lock_user']) cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is not None: create_delete_decision[application] = cd_action else: modify_decision[application] = self.na_helper.get_modified_attributes(current, self.parameters) if not create_delete_decision and self.parameters.get('state') == 'present': if self.parameters.get('set_password') is not None: self.na_helper.changed = True if self.na_helper.changed: if self.module.check_mode: pass else: for application in create_delete_decision: if create_delete_decision[application] == 'create': self.create_user(application) elif create_delete_decision[application] == 'delete': self.delete_user(application) lock_user = False for application in modify_decision: if 'role_name' in modify_decision[application]: self.modify_user(application) if 'lock_user' in modify_decision[application]: lock_user = True if not create_delete_decision and self.parameters.get('set_password') is not None: # if change password return false nothing has changed so we need to set changed to False self.na_helper.changed = self.change_password() # NOTE: unlock has to be performed after setting a password if lock_user: if self.parameters.get('lock_user'): self.lock_given_user() else: self.unlock_given_user() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapMccipMediator(object): """ Mediator object for Add/Remove/Display """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, choices=['present', 'absent'], default='present'), mediator_address=dict(required=True, type='str'), mediator_user=dict(required=True, type='str'), mediator_password=dict(required=True, type='str', no_log=True), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.restApi = OntapRestAPI(self.module) self.use_rest = self.restApi.is_rest() if not self.use_rest: self.module.fail_json( msg="na_ontap_metrocluster only supports REST API") def add_mediator(self): """ Adds an ONTAP Mediator to MCC configuration """ api = 'cluster/mediators' params = { 'ip_address': self.parameters['mediator_address'], 'password': self.parameters['mediator_password'], 'user': self.parameters['mediator_user'] } message, error = self.restApi.post(api, params) if error: self.module.fail_json(msg=error) def remove_mediator(self, current_uuid): """ Removes the ONTAP Mediator from MCC configuration """ api = 'cluster/mediators' params = { 'ip_address': self.parameters['mediator_address'], 'password': self.parameters['mediator_password'], 'user': self.parameters['mediator_user'], 'uuid': current_uuid } message, error = self.restApi.delete(api, params) if error: self.module.fail_json(msg=error) def get_mediator(self): """ Determine if the MCC configuration has added an ONTAP Mediator """ api = "cluster/mediators" message, error = self.restApi.get(api, None) if error: self.module.fail_json(msg=error) if message['num_records'] > 0: return message['records'][0]['uuid'] return None def apply(self): """ Apply action to MCC Mediator """ current = self.get_mediator() cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.add_mediator() elif cd_action == 'delete': self.remove_mediator(current) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapObjectStoreConfig(object): ''' object initialize and class methods ''' def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(name=dict(required=True, type='str'), state=dict(required=False, choices=['present', 'absent'], default='present'), provider_type=dict(required=False, type='str'), server=dict(required=False, type='str'), container=dict(required=False, type='str'), access_key=dict(required=False, type='str'), secret_password=dict(required=False, type='str', no_log=True))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # API should be used for ONTAP 9.6 or higher, Zapi for lower version self.restApi = OntapRestAPI(self.module) if self.restApi.is_rest(): self.use_rest = True else: if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module) def get_aggr_object_store(self): """ Fetch details if object store config exists. :return: Dictionary of current details if object store config found None if object store config is not found """ if self.use_rest: data = {'fields': 'uuid,name', 'name': self.parameters['name']} api = "cloud/targets" message, error = self.restApi.get(api, data) if error: self.module.fail_json(msg=error) if len(message['records']) != 0: return message['records'][0] return None else: aggr_object_store_get_iter = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-object-store-config-get', **{'object-store-name': self.parameters['name']}) result = None try: result = self.server.invoke_successfully( aggr_object_store_get_iter, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: # Error 15661 denotes an object store not being found. if to_native(error.code) == "15661": pass else: self.module.fail_json(msg=to_native(error), exception=traceback.format_exc()) return result def create_aggr_object_store(self): """ Create aggregate object store config :return: None """ required_keys = set( ['provider_type', 'server', 'container', 'access_key']) if not required_keys.issubset(set(self.parameters.keys())): self.module.fail_json( msg= 'Error provisioning object store %s: one of the following parameters are missing ' '%s' % (self.parameters['name'], ', '.join(required_keys))) if self.use_rest: data = { 'name': self.parameters['name'], 'provider_type': self.parameters['provider_type'], 'server': self.parameters['server'], 'container': self.parameters['container'], 'access_key': self.parameters['access_key'], 'owner': 'fabricpool' } if self.parameters.get('secret_password'): data['secret_password'] = self.parameters['secret_password'] api = "cloud/targets" message, error = self.restApi.post(api, data) if error: self.module.fail_json(msg=error) else: options = { 'object-store-name': self.parameters['name'], 'provider-type': self.parameters['provider_type'], 'server': self.parameters['server'], 's3-name': self.parameters['container'], 'access-key': self.parameters['access_key'] } if self.parameters.get('secret_password'): options['secret-password'] = self.parameters['secret_password'] object_store_create = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-object-store-config-create', **options) try: self.server.invoke_successfully(object_store_create, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error provisioning object store config %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_aggr_object_store(self, uuid=None): """ Delete aggregate object store config :return: None """ if self.use_rest: api = "cloud/targets/" data = {'uuid': uuid} message, error = self.restApi.delete(api, data) if error: self.module.fail_json(msg=error) else: object_store_destroy = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-object-store-config-delete', **{'object-store-name': self.parameters['name']}) try: self.server.invoke_successfully(object_store_destroy, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error removing object store config %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def asup_log_for_cserver(self, event_name): """ Fetch admin vserver for the given cluster Create and Autosupport log event with the given module name :param event_name: Name of the event log :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event(event_name, cserver) def apply(self): """ Apply action to the object store config :return: None """ uuid = None if not self.use_rest: self.asup_log_for_cserver("na_ontap_object_store_config") current = self.get_aggr_object_store() cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_aggr_object_store() elif cd_action == 'delete': if self.use_rest: uuid = current['uuid'] self.delete_aggr_object_store(uuid) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapLoginMessages(object): """ modify and delete login banner and motd """ def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(vserver=dict(required=True, type='str'), banner=dict(required=False, type='str'), motd_message=dict(required=False, type='str', aliases=['message']), show_cluster_motd=dict(default=True, type='bool'))) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True, required_one_of=[['show_cluster_motd', 'banner', 'motd_message']]) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.rest_api = OntapRestAPI(self.module) if self.rest_api.is_rest(): self.use_rest = True else: if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def get_banner_motd(self, uuid=None): if self.use_rest: api = 'security/login/messages/' + uuid params = {'fields': '*'} message, error = self.rest_api.get(api, params) if error: self.module.fail_json( msg='Error when fetching login_banner info: %s' % error) return_result = dict() return_result['banner'] = message['banner'].rstrip( ) if message.get('banner') else '' return_result['motd_message'] = message['message'].rstrip( ) if message.get('message') else '' if message.get('show_cluster_message'): return_result['show_cluster_message'] = message[ 'show_cluster_message'] return return_result else: login_banner_get_iter = netapp_utils.zapi.NaElement( 'vserver-login-banner-get-iter') query = netapp_utils.zapi.NaElement('query') login_banner_info = netapp_utils.zapi.NaElement( 'vserver-login-banner-info') login_banner_info.add_new_child('vserver', self.parameters['vserver']) query.add_child_elem(login_banner_info) login_banner_get_iter.add_child_elem(query) return_result = dict() try: result = self.server.invoke_successfully(login_banner_get_iter, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error fetching login_banner info: %s' % to_native(error), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) > 0: login_banner_info = result.get_child_by_name( 'attributes-list').get_child_by_name( 'vserver-login-banner-info') return_result['banner'] = login_banner_info.get_child_content( 'message') return_result['banner'] = str(return_result['banner']).rstrip() # if the message is '-' that means the banner doesn't exist. if return_result['banner'] == '-' or return_result[ 'banner'] == 'None': return_result['banner'] = '' motd_get_iter = netapp_utils.zapi.NaElement( 'vserver-motd-get-iter') query = netapp_utils.zapi.NaElement('query') motd_info = netapp_utils.zapi.NaElement('vserver-motd-info') motd_info.add_new_child('vserver', self.parameters['vserver']) query.add_child_elem(motd_info) motd_get_iter.add_child_elem(query) try: result = self.server.invoke_successfully(motd_get_iter, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching motd info: %s' % to_native(error), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) > 0: motd_info = result.get_child_by_name( 'attributes-list').get_child_by_name('vserver-motd-info') return_result['motd_message'] = motd_info.get_child_content( 'message') return_result['motd_message'] = str( return_result['motd_message']).rstrip() return_result[ 'show_cluster_motd'] = True if motd_info.get_child_content( 'is-cluster-message-enabled') == 'true' else False if return_result['motd_message'] == 'None': return_result['motd_message'] = '' return return_result def modify_banner(self, modify, uuid): if self.use_rest: api = 'security/login/messages/' + uuid params = {"banner": modify['banner']} dummy, error = self.rest_api.patch(api, params) if error: self.module.fail_json(msg='Error when modifying banner: %s' % error) else: login_banner_modify = netapp_utils.zapi.NaElement( 'vserver-login-banner-modify-iter') login_banner_modify.add_new_child('message', modify['banner']) query = netapp_utils.zapi.NaElement('query') login_banner_info = netapp_utils.zapi.NaElement( 'vserver-login-banner-info') login_banner_info.add_new_child('vserver', self.parameters['vserver']) query.add_child_elem(login_banner_info) login_banner_modify.add_child_elem(query) try: self.server.invoke_successfully(login_banner_modify, enable_tunneling=False) except netapp_utils.zapi.NaApiError as err: self.module.fail_json(msg="Error modifying login_banner: %s" % (to_native(err)), exception=traceback.format_exc()) def modify_motd(self, modify, uuid): if self.use_rest: api = 'security/login/messages/' + uuid params = { 'message': modify['motd_message'], } if modify.get('show_cluster_motd'): params['show_cluster_message'] = modify['show_cluster_motd'] dummy, error = self.rest_api.patch(api, params) if error: self.module.fail_json(msg='Error when modifying motd: %s' % error) else: motd_create = netapp_utils.zapi.NaElement( 'vserver-motd-modify-iter') if modify.get('motd_message') is not None: motd_create.add_new_child('message', modify['motd_message']) if modify.get('show_cluster_motd') is not None: motd_create.add_new_child( 'is-cluster-message-enabled', 'true' if modify['show_cluster_motd'] is True else 'false') query = netapp_utils.zapi.NaElement('query') motd_info = netapp_utils.zapi.NaElement('vserver-motd-info') motd_info.add_new_child('vserver', self.parameters['vserver']) query.add_child_elem(motd_info) motd_create.add_child_elem(query) try: self.server.invoke_successfully(motd_create, enable_tunneling=False) except netapp_utils.zapi.NaApiError as err: self.module.fail_json(msg="Error modifying motd: %s" % (to_native(err)), exception=traceback.format_exc()) def get_svm_uuid(self): """ Get a svm's uuid :return: uuid of the svm """ params = {'name': self.parameters['vserver'], 'fields': 'uuid'} api = 'svm/svms' message, error = self.rest_api.get(api, params) if error is not None: self.module.fail_json(msg="%s" % error) if message['num_records'] == 0: self.module.fail_json( msg= "Error fetching specified vserver. Please make sure vserver name is correct. For cluster vserver, Please use ZAPI." ) return message['records'][0]['uuid'] def apply(self): uuid = None modify = None if self.use_rest: uuid = self.get_svm_uuid() else: netapp_utils.ems_log_event("na_ontap_login_banner", self.server) current = self.get_banner_motd(uuid=uuid) modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if modify.get('banner') is not None: self.modify_banner(modify, uuid=uuid) if modify.get('show_cluster_motd') is not None or modify.get( 'motd_message') is not None: self.modify_motd(modify, uuid=uuid) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapStorageAutoGiveback(object): """ Enable or disable storage failover for a specified node """ def __init__(self): """ Initialize the ONTAP Storage auto giveback class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(name=dict(required=True, type='str'), auto_giveback_enabled=dict(required=True, type='bool'), auto_giveback_after_panic_enabled=dict(required=False, type='bool'))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) # set up variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.rest_api = OntapRestAPI(self.module) self.use_rest = self.rest_api.is_rest() if not self.use_rest: if not netapp_utils.has_netapp_lib(): self.module.fail_json( msg='The python NetApp-Lib module is required') else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module) def get_storage_auto_giveback(self): """ get the storage failover giveback options for a given node :return: dict for options """ return_value = None if self.use_rest: api = "private/cli/storage/failover" query = { 'fields': 'node,auto_giveback,auto_giveback_after_panic', 'node': self.parameters['name'], } message, error = self.rest_api.get(api, query) records, error = rrh.check_for_0_or_1_records(api, message, error) if error is None and records is not None: return_value = { 'name': message['records'][0]['node'], 'auto_giveback_enabled': message['records'][0]['auto_giveback'], 'auto_giveback_after_panic_enabled': message['records'][0]['auto_giveback_after_panic'] } if error: self.module.fail_json(msg=error) if not records: error = "REST API did not return failover options for node %s" % ( self.parameters['name']) self.module.fail_json(msg=error) else: storage_auto_giveback_get_iter = netapp_utils.zapi.NaElement( 'cf-get-iter') try: result = self.server.invoke_successfully( storage_auto_giveback_get_iter, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error getting auto giveback info for node %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('attributes-list'): attributes_list = result.get_child_by_name('attributes-list') for storage_failover_info_attributes in attributes_list.get_children( ): sfo_node_info = storage_failover_info_attributes.get_child_by_name( 'sfo-node-info') node_related_info = sfo_node_info.get_child_by_name( 'node-related-info') if node_related_info.get_child_content( 'node') == self.parameters['name']: sfo_options_info = storage_failover_info_attributes.get_child_by_name( 'sfo-options-info') options_related_info = sfo_options_info.get_child_by_name( 'options-related-info') sfo_giveback_options_info = options_related_info.get_child_by_name( 'sfo-giveback-options-info') giveback_options = sfo_giveback_options_info.get_child_by_name( 'giveback-options') return_value = { 'name': node_related_info.get_child_content('node'), 'auto_giveback_enabled': self.na_helper.get_value_for_bool( True, options_related_info.get_child_content( 'auto-giveback-enabled')), 'auto_giveback_after_panic_enabled': self.na_helper.get_value_for_bool( True, giveback_options.get_child_content( 'auto-giveback-after-panic-enabled')), } break return return_value def modify_storage_auto_giveback(self): """ Modifies storage failover giveback options for a specified node """ if self.use_rest: api = "private/cli/storage/failover" body = dict() query = {'node': self.parameters['name']} body['auto_giveback'] = self.parameters['auto_giveback_enabled'] if 'auto_giveback_after_panic_enabled' in self.parameters: body['auto_giveback_after_panic'] = self.parameters[ 'auto_giveback_after_panic_enabled'] dummy, error = self.rest_api.patch(api, body, query) if error: self.module.fail_json(msg=error) else: storage_auto_giveback_enable = netapp_utils.zapi.NaElement( 'cf-modify-iter') attributes_info = netapp_utils.zapi.NaElement( 'options-related-info-modify') query_info = netapp_utils.zapi.NaElement( 'options-related-info-modify') attributes_info.add_new_child('node', self.parameters['name']) attributes_info.add_new_child( 'auto-giveback-enabled', self.na_helper.get_value_for_bool( from_zapi=False, value=self.parameters['auto_giveback_enabled'])) if 'auto_giveback_after_panic_enabled' in self.parameters: sfo_give_back_options_info_modify = netapp_utils.zapi.NaElement( 'sfo-giveback-options-info-modify') give_back_options_modify = netapp_utils.zapi.NaElement( 'giveback-options-modify') give_back_options_modify.add_new_child( 'auto-giveback-after-panic-enabled', self.na_helper.get_value_for_bool( from_zapi=False, value=self. parameters['auto_giveback_after_panic_enabled'])) sfo_give_back_options_info_modify.add_child_elem( give_back_options_modify) attributes_info.add_child_elem( sfo_give_back_options_info_modify) query = netapp_utils.zapi.NaElement('query') attributes = netapp_utils.zapi.NaElement("attributes") query.add_child_elem(query_info) attributes.add_child_elem(attributes_info) storage_auto_giveback_enable.add_child_elem(query) storage_auto_giveback_enable.add_child_elem(attributes) try: result = self.server.invoke_successfully( storage_auto_giveback_enable, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying auto giveback for node %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def ems_log_event(self): results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) return netapp_utils.ems_log_event("na_ontap_storage_auto_giveback", cserver) def apply(self): if not self.use_rest: self.ems_log_event() current = self.get_storage_auto_giveback() modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if not self.module.check_mode: self.modify_storage_auto_giveback() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapVscan(object): def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( enable=dict(type='bool', default=True), vserver=dict(required=True, type='str'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # API should be used for ONTAP 9.6 or higher, Zapi for lower version self.restApi = OntapRestAPI(self.module) if self.restApi.is_rest(): self.use_rest = True else: if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def get_vscan(self): if self.use_rest: params = { 'fields': 'svm,enabled', "svm.name": self.parameters['vserver'] } api = "protocols/vscan" message, error = self.restApi.get(api, params) if error: self.module.fail_json(msg=error) return message['records'][0] else: vscan_status_iter = netapp_utils.zapi.NaElement( 'vscan-status-get-iter') vscan_status_info = netapp_utils.zapi.NaElement( 'vscan-status-info') vscan_status_info.add_new_child('vserver', self.parameters['vserver']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(vscan_status_info) vscan_status_iter.add_child_elem(query) try: result = self.server.invoke_successfully( vscan_status_iter, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error getting Vscan info for Vserver %s: %s' % (self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) >= 1: return result.get_child_by_name( 'attributes-list').get_child_by_name('vscan-status-info') def enable_vscan(self, uuid=None): if self.use_rest: params = {"svm.name": self.parameters['vserver']} data = {"enabled": self.parameters['enable']} api = "protocols/vscan/" + uuid message, error = self.restApi.patch(api, data, params) if error is not None: self.module.fail_json(msg=error) # self.module.fail_json(msg=repr(self.restApi.errors), log=repr(self.restApi.debug_logs)) else: vscan_status_obj = netapp_utils.zapi.NaElement( "vscan-status-modify") vscan_status_obj.add_new_child('is-vscan-enabled', str(self.parameters['enable'])) try: self.server.invoke_successfully(vscan_status_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error Enable/Disabling Vscan: %s" % to_native(error), exception=traceback.format_exc()) def asup_log(self): if self.use_rest: # TODO: logging for Rest return else: # Either we are using ZAPI, or REST failed when it should not try: netapp_utils.ems_log_event("na_ontap_vscan", self.server) except Exception: # TODO: we may fail to connect to REST or ZAPI, the line below shows REST issues only # self.module.fail_json(msg=repr(self.restApi.errors), log=repr(self.restApi.debug_logs)) pass def apply(self): changed = False self.asup_log() current = self.get_vscan() if self.use_rest: if current['enabled'] != self.parameters['enable']: if not self.module.check_mode: self.enable_vscan(current['svm']['uuid']) changed = True else: if current.get_child_content('is-vscan-enabled') != str( self.parameters['enable']).lower(): if not self.module.check_mode: self.enable_vscan() changed = True self.module.exit_json(changed=changed)
class NetAppOntapSnapMirrorPolicy(object): """ Create, Modifies and Destroys a SnapMirror policy """ def __init__(self): """ Initialize the Ontap SnapMirror policy class """ self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, choices=['present', 'absent'], default='present'), vserver=dict(required=True, type='str'), policy_name=dict(required=True, type='str'), comment=dict(required=False, type='str'), policy_type=dict(required=False, type='str', choices=[ 'vault', 'async_mirror', 'mirror_vault', 'strict_sync_mirror', 'sync_mirror' ]), tries=dict(required=False, type='str'), transfer_priority=dict(required=False, type='str', choices=['low', 'normal']), common_snapshot_schedule=dict(required=False, type='str'), ignore_atime=dict(required=False, type='bool'), is_network_compression_enabled=dict(required=False, type='bool'), owner=dict(required=False, type='str', choices=['cluster_admin', 'vserver_admin']), restart=dict(required=False, type='str', choices=['always', 'never', 'default']), snapmirror_label=dict(required=False, type="list", elements="str"), keep=dict(required=False, type="list", elements="int"), prefix=dict(required=False, type="list", elements="str"), schedule=dict(required=False, type="list", elements="str"), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) # set up variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # API should be used for ONTAP 9.6 or higher, Zapi for lower version self.restApi = OntapRestAPI(self.module) # some attributes are not supported in earlier REST implementation unsupported_rest_properties = [ 'owner', 'restart', 'transfer_priority', 'tries', 'ignore_atime', 'common_snapshot_schedule' ] used_unsupported_rest_properties = [ x for x in unsupported_rest_properties if x in self.parameters ] self.use_rest, error = self.restApi.is_rest( used_unsupported_rest_properties) if error: self.module.fail_json(msg=error) if not self.use_rest: if HAS_NETAPP_LIB is False: self.module.fail_json( msg='The python NetApp-Lib module is required') else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def get_snapmirror_policy(self): if self.use_rest: data = { 'fields': 'uuid,name,svm.name,comment,network_compression_enabled,type,retention', 'name': self.parameters['policy_name'], 'svm.name': self.parameters['vserver'] } api = "snapmirror/policies" message, error = self.restApi.get(api, data) if error: self.module.fail_json(msg=error) if len(message['records']) != 0: return_value = { 'uuid': message['records'][0]['uuid'], 'vserver': message['records'][0]['svm']['name'], 'policy_name': message['records'][0]['name'], 'comment': '', 'is_network_compression_enabled': message['records'][0]['network_compression_enabled'], 'snapmirror_label': list(), 'keep': list(), 'prefix': list(), 'schedule': list() } if 'type' in message['records'][0]: policy_type = message['records'][0]['type'] if policy_type == 'async': policy_type = 'async_mirror' elif policy_type == 'sync': policy_type = 'sync_mirror' return_value['policy_type'] = policy_type if 'comment' in message['records'][0]: return_value['comment'] = message['records'][0]['comment'] if 'retention' in message['records'][0]: for rule in message['records'][0]['retention']: return_value['snapmirror_label'].append(rule['label']) return_value['keep'].append(int(rule['count'])) if rule['prefix'] == '-': return_value['prefix'].append('') else: return_value['prefix'].append(rule['prefix']) if rule['creation_schedule']['name'] == '-': return_value['schedule'].append('') else: return_value['schedule'].append( rule['creation_schedule']['name']) return return_value return None else: return_value = None snapmirror_policy_get_iter = netapp_utils.zapi.NaElement( 'snapmirror-policy-get-iter') snapmirror_policy_info = netapp_utils.zapi.NaElement( 'snapmirror-policy-info') snapmirror_policy_info.add_new_child( 'policy-name', self.parameters['policy_name']) snapmirror_policy_info.add_new_child('vserver', self.parameters['vserver']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(snapmirror_policy_info) snapmirror_policy_get_iter.add_child_elem(query) try: result = self.server.invoke_successfully( snapmirror_policy_get_iter, True) if result.get_child_by_name('attributes-list'): snapmirror_policy_attributes = result['attributes-list'][ 'snapmirror-policy-info'] return_value = { 'policy_name': snapmirror_policy_attributes['policy-name'], 'tries': snapmirror_policy_attributes['tries'], 'transfer_priority': snapmirror_policy_attributes['transfer-priority'], 'is_network_compression_enabled': self.na_helper.get_value_for_bool( True, snapmirror_policy_attributes[ 'is-network-compression-enabled']), 'restart': snapmirror_policy_attributes['restart'], 'ignore_atime': self.na_helper.get_value_for_bool( True, snapmirror_policy_attributes['ignore-atime']), 'vserver': snapmirror_policy_attributes['vserver-name'], 'comment': '', 'snapmirror_label': list(), 'keep': list(), 'prefix': list(), 'schedule': list() } if snapmirror_policy_attributes.get_child_content( 'comment') is not None: return_value['comment'] = snapmirror_policy_attributes[ 'comment'] if snapmirror_policy_attributes.get_child_content( 'type') is not None: return_value[ 'policy_type'] = snapmirror_policy_attributes[ 'type'] if snapmirror_policy_attributes.get_child_by_name( 'snapmirror-policy-rules'): for rule in snapmirror_policy_attributes[ 'snapmirror-policy-rules'].get_children(): # Ignore builtin rules if rule.get_child_content('snapmirror-label') == "sm_created" or \ rule.get_child_content('snapmirror-label') == "all_source_snapshots": continue return_value['snapmirror_label'].append( rule.get_child_content('snapmirror-label')) return_value['keep'].append( int(rule.get_child_content('keep'))) prefix = rule.get_child_content('prefix') if prefix is None or prefix == '-': prefix = '' return_value['prefix'].append(prefix) schedule = rule.get_child_content('schedule') if schedule is None or schedule == '-': schedule = '' return_value['schedule'].append(schedule) except netapp_utils.zapi.NaApiError as error: if 'NetApp API failed. Reason - 13001:' in to_native(error): # Policy does not exist pass else: self.module.fail_json( msg='Error getting snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) return return_value def validate_parameters(self): """ Validate snapmirror policy rules :return: None """ # For snapmirror policy rules, 'snapmirror_label' is required. if 'snapmirror_label' in self.parameters: # Check size of 'snapmirror_label' list is 0-10. Can have zero rules. # Take builtin 'sm_created' rule into account for 'mirror_vault'. if (('policy_type' in self.parameters and self.parameters['policy_type'] == 'mirror_vault' and len(self.parameters['snapmirror_label']) > 9) or len(self.parameters['snapmirror_label']) > 10): self.module.fail_json( msg="Error: A SnapMirror Policy can have up to a maximum of " "10 rules (including builtin rules), with a 'keep' value " "representing the maximum number of Snapshot copies for each rule" ) # 'keep' must be supplied as long as there is at least one snapmirror_label if len(self.parameters['snapmirror_label'] ) > 0 and 'keep' not in self.parameters: self.module.fail_json( msg="Error: Missing 'keep' parameter. When specifying the " "'snapmirror_label' parameter, the 'keep' parameter must " "also be supplied") # Make sure other rule values match same number of 'snapmirror_label' values. for rule_parameter in ['keep', 'prefix', 'schedule']: if rule_parameter in self.parameters: if len(self.parameters['snapmirror_label']) > len( self.parameters[rule_parameter]): self.module.fail_json( msg="Error: Each 'snapmirror_label' value must have " "an accompanying '%s' value" % rule_parameter) if len(self.parameters[rule_parameter]) > len( self.parameters['snapmirror_label']): self.module.fail_json( msg= "Error: Each '%s' value must have an accompanying " "'snapmirror_label' value" % rule_parameter) else: # 'snapmirror_label' not supplied. # Bail out if other rule parameters have been supplied. for rule_parameter in ['keep', 'prefix', 'schedule']: if rule_parameter in self.parameters: self.module.fail_json( msg="Error: Missing 'snapmirror_label' parameter. When " "specifying the '%s' parameter, the 'snapmirror_label' " "parameter must also be supplied" % rule_parameter) # Schedule must be supplied if prefix is supplied. if 'prefix' in self.parameters and 'schedule' not in self.parameters: self.module.fail_json( msg="Error: Missing 'schedule' parameter. When " "specifying the 'prefix' parameter, the 'schedule' " "parameter must also be supplied") def create_snapmirror_policy(self): """ Creates a new storage efficiency policy """ self.validate_parameters() if self.use_rest: data = { 'name': self.parameters['policy_name'], 'svm': { 'name': self.parameters['vserver'] } } if 'policy_type' in self.parameters.keys(): if 'async_mirror' in self.parameters['policy_type']: data['type'] = 'async' elif 'sync_mirror' in self.parameters['policy_type']: data['type'] = 'sync' data['sync_type'] = 'sync' else: self.module.fail_json( msg= 'policy type in REST only supports options async_mirror or sync_mirror, given %s' % (self.parameters['policy_type'])) data = self.create_snapmirror_policy_obj_for_rest( data, data['type']) else: data = self.create_snapmirror_policy_obj_for_rest(data) api = "snapmirror/policies" response, error = self.restApi.post(api, data) if error: self.module.fail_json(msg=error) if 'job' in response: self.restApi.wait_on_job(response['job'], increment=5) else: snapmirror_policy_obj = netapp_utils.zapi.NaElement( "snapmirror-policy-create") snapmirror_policy_obj.add_new_child("policy-name", self.parameters['policy_name']) if 'policy_type' in self.parameters.keys(): snapmirror_policy_obj.add_new_child( "type", self.parameters['policy_type']) snapmirror_policy_obj = self.create_snapmirror_policy_obj( snapmirror_policy_obj) try: self.server.invoke_successfully(snapmirror_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) def create_snapmirror_policy_obj(self, snapmirror_policy_obj): if 'comment' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("comment", self.parameters['comment']) if 'common_snapshot_schedule' in self.parameters.keys( ) and 'sync_mirror' in self.parameters['policy_type']: snapmirror_policy_obj.add_new_child( "common-snapshot-schedule", self.parameters['common_snapshot_schedule']) if 'ignore_atime' in self.parameters.keys(): snapmirror_policy_obj.add_new_child( "ignore-atime", self.na_helper.get_value_for_bool( False, self.parameters['ignore_atime'])) if 'is_network_compression_enabled' in self.parameters.keys(): snapmirror_policy_obj.add_new_child( "is-network-compression-enabled", self.na_helper.get_value_for_bool( False, self.parameters['is_network_compression_enabled'])) if 'owner' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("owner", self.parameters['owner']) if 'restart' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("restart", self.parameters['restart']) if 'transfer_priority' in self.parameters.keys(): snapmirror_policy_obj.add_new_child( "transfer-priority", self.parameters['transfer_priority']) if 'tries' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("tries", self.parameters['tries']) return snapmirror_policy_obj def create_snapmirror_policy_obj_for_rest(self, snapmirror_policy_obj, policy_type=None): if 'comment' in self.parameters.keys(): snapmirror_policy_obj["comment"] = self.parameters['comment'] if 'is_network_compression_enabled' in self.parameters: if policy_type == 'async': snapmirror_policy_obj[ "network_compression_enabled"] = self.parameters[ 'is_network_compression_enabled'] elif policy_type == 'sync': self.module.fail_json( msg= "Input parameter network_compression_enabled is not valid for SnapMirror policy type sync" ) return snapmirror_policy_obj def create_snapmirror_policy_retention_obj_for_rest(self, rules=None): """ Create SnapMirror policy retention REST object. :param list rules: e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': 'daily', 'schedule': 'daily'}, ... ] :return: List of retention REST objects. e.g. [{'label': 'daily', 'count': 7, 'prefix': 'daily', 'creation_schedule': {'name': 'daily'}}, ... ] """ snapmirror_policy_retention_objs = list() if rules is not None: for rule in rules: retention = { 'label': rule['snapmirror_label'], 'count': str(rule['keep']) } if 'prefix' in rule and rule['prefix'] != '': retention['prefix'] = rule['prefix'] if 'schedule' in rule and rule['schedule'] != '': retention['creation_schedule'] = {'name': rule['schedule']} snapmirror_policy_retention_objs.append(retention) return snapmirror_policy_retention_objs def delete_snapmirror_policy(self, uuid=None): """ Deletes a snapmirror policy """ if self.use_rest: api = "snapmirror/policies" data = {'uuid': uuid} dummy, error = self.restApi.delete(api, data) if error: self.module.fail_json(msg=error) else: snapmirror_policy_obj = netapp_utils.zapi.NaElement( "snapmirror-policy-delete") snapmirror_policy_obj.add_new_child("policy-name", self.parameters['policy_name']) try: self.server.invoke_successfully(snapmirror_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) def modify_snapmirror_policy(self, uuid=None, policy_type=None): """ Modifies a snapmirror policy """ if self.use_rest: api = "snapmirror/policies/" + uuid data = self.create_snapmirror_policy_obj_for_rest( dict(), policy_type) dummy, error = self.restApi.patch(api, data) if error: self.module.fail_json(msg=error) else: snapmirror_policy_obj = netapp_utils.zapi.NaElement( "snapmirror-policy-modify") snapmirror_policy_obj = self.create_snapmirror_policy_obj( snapmirror_policy_obj) # Only modify snapmirror policy if a specific snapmirror policy attribute needs # modifying. It may be that only snapmirror policy rules are being modified. if snapmirror_policy_obj.get_children(): snapmirror_policy_obj.add_new_child( "policy-name", self.parameters['policy_name']) try: self.server.invoke_successfully(snapmirror_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) def identify_new_snapmirror_policy_rules(self, current=None): """ Identify new rules that should be added. :return: List of new rules to be added e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, ... ] """ new_rules = list() if 'snapmirror_label' in self.parameters: for snapmirror_label in self.parameters['snapmirror_label']: snapmirror_label = snapmirror_label.strip() # Construct new rule. prefix and schedule are optional. snapmirror_label_index = self.parameters[ 'snapmirror_label'].index(snapmirror_label) rule = dict({ 'snapmirror_label': snapmirror_label, 'keep': self.parameters['keep'][snapmirror_label_index] }) if 'prefix' in self.parameters: rule['prefix'] = self.parameters['prefix'][ snapmirror_label_index] else: rule['prefix'] = '' if 'schedule' in self.parameters: rule['schedule'] = self.parameters['schedule'][ snapmirror_label_index] else: rule['schedule'] = '' if current is not None and 'snapmirror_label' in current: if snapmirror_label not in current['snapmirror_label']: # Rule doesn't exist. Add new rule. new_rules.append(rule) else: # No current or any rules. Add new rule. new_rules.append(rule) return new_rules def identify_obsolete_snapmirror_policy_rules(self, current=None): """ Identify existing rules that should be deleted :return: List of rules to be deleted e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, ... ] """ obsolete_rules = list() if 'snapmirror_label' in self.parameters: if current is not None and 'snapmirror_label' in current: # Iterate existing rules. for snapmirror_label in current['snapmirror_label']: snapmirror_label = snapmirror_label.strip() if snapmirror_label not in [ item.strip() for item in self.parameters['snapmirror_label'] ]: # Existing rule isn't in parameters. Delete existing rule. current_snapmirror_label_index = current[ 'snapmirror_label'].index(snapmirror_label) rule = dict({ 'snapmirror_label': snapmirror_label, 'keep': current['keep'][current_snapmirror_label_index], 'prefix': current['prefix'][current_snapmirror_label_index], 'schedule': current['schedule'][current_snapmirror_label_index] }) obsolete_rules.append(rule) return obsolete_rules def identify_modified_snapmirror_policy_rules(self, current=None): """ Identify self.parameters rules that will be modified or not. :return: List of 'modified' rules and a list of 'unmodified' rules e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, ... ] """ modified_rules = list() unmodified_rules = list() if 'snapmirror_label' in self.parameters: for snapmirror_label in self.parameters['snapmirror_label']: snapmirror_label = snapmirror_label.strip() if current is not None and 'snapmirror_label' in current: if snapmirror_label in current['snapmirror_label']: # Rule exists. Identify whether it requires modification or not. modified = False rule = dict() rule['snapmirror_label'] = snapmirror_label # Get indexes of current and supplied rule. current_snapmirror_label_index = current[ 'snapmirror_label'].index(snapmirror_label) snapmirror_label_index = self.parameters[ 'snapmirror_label'].index(snapmirror_label) # Check if keep modified if self.parameters['keep'][ snapmirror_label_index] != current['keep'][ current_snapmirror_label_index]: modified = True rule['keep'] = self.parameters['keep'][ snapmirror_label_index] else: rule['keep'] = current['keep'][ current_snapmirror_label_index] # Check if prefix modified if 'prefix' in self.parameters: if self.parameters['prefix'][ snapmirror_label_index] != current[ 'prefix'][ current_snapmirror_label_index]: modified = True rule['prefix'] = self.parameters['prefix'][ snapmirror_label_index] else: rule['prefix'] = current['prefix'][ current_snapmirror_label_index] else: rule['prefix'] = current['prefix'][ current_snapmirror_label_index] # Check if schedule modified if 'schedule' in self.parameters: if self.parameters['schedule'][ snapmirror_label_index] != current[ 'schedule'][ current_snapmirror_label_index]: modified = True rule['schedule'] = self.parameters['schedule'][ snapmirror_label_index] else: rule['schedule'] = current['schedule'][ current_snapmirror_label_index] else: rule['schedule'] = current['schedule'][ current_snapmirror_label_index] if modified: modified_rules.append(rule) else: unmodified_rules.append(rule) return modified_rules, unmodified_rules def identify_snapmirror_policy_rules_with_schedule(self, rules=None): """ Identify rules that are using a schedule or not. At least one non-schedule rule must be added to a policy before schedule rules are added. :return: List of rules with schedules and a list of rules without schedules e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': 'daily', 'schedule': 'daily'}, ... ], [{'snapmirror_label': 'weekly', 'keep': 5, 'prefix': '', 'schedule': ''}, ... ] """ schedule_rules = list() non_schedule_rules = list() if rules is not None: for rule in rules: if 'schedule' in rule: schedule_rules.append(rule) else: non_schedule_rules.append(rule) return schedule_rules, non_schedule_rules def modify_snapmirror_policy_rules(self, current=None, uuid=None): """ Modify existing rules in snapmirror policy :return: None """ self.validate_parameters() # Need 'snapmirror_label' to add/modify/delete rules if 'snapmirror_label' not in self.parameters: return obsolete_rules = self.identify_obsolete_snapmirror_policy_rules( current) new_rules = self.identify_new_snapmirror_policy_rules(current) modified_rules, unmodified_rules = self.identify_modified_snapmirror_policy_rules( current) if self.use_rest: api = "snapmirror/policies/" + uuid data = {'retention': list()} # As rule 'prefix' can't be unset, have to delete existing rules first. # Builtin rules remain. dummy, error = self.restApi.patch(api, data) if error: self.module.fail_json(msg=error) # Re-add desired rules. rules = unmodified_rules + modified_rules + new_rules data[ 'retention'] = self.create_snapmirror_policy_retention_obj_for_rest( rules) if len(data['retention']) > 0: dummy, error = self.restApi.patch(api, data) if error: self.module.fail_json(msg=error) else: delete_rules = obsolete_rules + modified_rules add_schedule_rules, add_non_schedule_rules = self.identify_snapmirror_policy_rules_with_schedule( new_rules + modified_rules) # Delete rules no longer required or modified rules that will be re-added. for rule in delete_rules: options = { 'policy-name': self.parameters['policy_name'], 'snapmirror-label': rule['snapmirror_label'] } self.modify_snapmirror_policy_rule( options, 'snapmirror-policy-remove-rule') # Add rules. At least one non-schedule rule must exist before # a rule with a schedule can be added, otherwise zapi will complain. for rule in add_non_schedule_rules + add_schedule_rules: options = { 'policy-name': self.parameters['policy_name'], 'snapmirror-label': rule['snapmirror_label'], 'keep': str(rule['keep']) } if 'prefix' in rule and rule['prefix'] != '': options['prefix'] = rule['prefix'] if 'schedule' in rule and rule['schedule'] != '': options['schedule'] = rule['schedule'] self.modify_snapmirror_policy_rule( options, 'snapmirror-policy-add-rule') def modify_snapmirror_policy_rule(self, options, zapi): """ Add, modify or remove a rule to/from a snapmirror policy """ snapmirror_obj = netapp_utils.zapi.NaElement.create_node_with_children( zapi, **options) try: self.server.invoke_successfully(snapmirror_obj, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying snapmirror policy rule %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) def asup_log_for_cserver(self): results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event("na_ontap_snapmirror_policy", cserver) def apply(self): uuid = None if not self.use_rest: self.asup_log_for_cserver() current, modify = self.get_snapmirror_policy(), None cd_action = self.na_helper.get_cd_action(current, self.parameters) if current and cd_action is None and self.parameters[ 'state'] == 'present': modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_snapmirror_policy() if self.use_rest: current = self.get_snapmirror_policy() uuid = current['uuid'] self.modify_snapmirror_policy_rules(current, uuid) else: self.modify_snapmirror_policy_rules(current) elif cd_action == 'delete': if self.use_rest: uuid = current['uuid'] self.delete_snapmirror_policy(uuid) elif modify: if self.use_rest: uuid = current['uuid'] self.modify_snapmirror_policy(uuid, current['policy_type']) self.modify_snapmirror_policy_rules(current, uuid) else: self.modify_snapmirror_policy() self.modify_snapmirror_policy_rules(current) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapDomainTunnel(object): def __init__(self): """ Initialize the ONTAP domain tunnel class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(choices=['present', 'absent'], default='present'), vserver=dict(required=True, type='str'))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.rest_api = OntapRestAPI(self.module) self.use_rest = self.rest_api.is_rest() if not self.use_rest: self.module.fail_json(msg=self.rest_api.requires_ontap_version( 'na_ontap_domain_tunnel', '9.7')) def get_domain_tunnel(self): """ Get the current domain tunnel info """ api = "/security/authentication/cluster/ad-proxy" message, error = self.rest_api.get(api) if error: if int(error['code']) != 4: # error code 4 is empty table self.module.fail_json(msg=error) if message: message = {'vserver': message['svm']['name']} return message else: return None def create_domain_tunnel(self): """ Creates the domain tunnel on the specified vserver """ api = "/security/authentication/cluster/ad-proxy" body = {"svm": {"name": self.parameters['vserver']}} dummy, error = self.rest_api.post(api, body) if error: self.module.fail_json(msg=error) def modify_domain_tunnel(self): """ Modifies the domain tunnel on the specified vserver """ api = "/security/authentication/cluster/ad-proxy" body = {"svm": {"name": self.parameters['vserver']}} dummy, error = self.rest_api.patch(api, body) if error: self.module.fail_json(msg=error) def delete_domain_tunnel(self): """ Deletes the current domain tunnel """ api = "/security/authentication/cluster/ad-proxy" dummy, error = self.rest_api.delete(api) if error: self.module.fail_json(msg=error) def apply(self): current = self.get_domain_tunnel() cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if not self.module.check_mode: if cd_action == 'create': self.create_domain_tunnel() elif cd_action == 'delete': self.delete_domain_tunnel() elif modify: self.modify_domain_tunnel() self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPSnmpTraphosts(object): """Class with SNMP methods""" def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), ip_address=dict(required=True, type='str'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.restApi = OntapRestAPI(self.module) if not self.restApi.is_rest(): self.module.fail_json( msg="na_ontap_snmp_traphosts only support Rest and ONTAP 9.6+") def get_snmp_traphosts(self): params = {'ip_address': self.parameters['ip_address']} api = 'support/snmp/traphosts' message, error = self.restApi.get(api, params) if error: self.module.fail_json(msg=error) if not message['records']: return None return message['records'] def create_snmp_traphost(self): api = '/support/snmp/traphosts' params = {'host': self.parameters['ip_address']} message, error = self.restApi.post(api, params) if error: self.module.fail_json(msg=error) def delete_snmp_traphost(self): api = '/support/snmp/traphosts/' + self.parameters['ip_address'] params = None message, error = self.restApi.delete(api, params) if error is not None: self.module.fail_json(msg="Error deleting traphost: %s" % error) def apply(self): """ Apply action to SNMP traphost """ current = self.get_snmp_traphosts() cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_snmp_traphost() elif cd_action == 'delete': self.delete_snmp_traphost() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapIgroup(object): """Create/Delete/Rename Igroups and Modify initiators list""" def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str', default=None), os_type=dict(required=False, type='str', aliases=['ostype']), igroups=dict(required=False, type='list', elements='str'), initiator_group_type=dict(required=False, type='str', choices=['fcp', 'iscsi', 'mixed'], aliases=['protocol']), initiators=dict(required=False, type='list', elements='str', aliases=['initiator']), vserver=dict(required=True, type='str'), force_remove_initiator=dict(required=False, type='bool', default=False, aliases=['allow_delete_while_mapped']), bind_portset=dict(required=False, type='str') )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True, mutually_exclusive=[('igroups', 'initiators')] ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.rest_modify_zapi_to_rest = dict( # initiator_group_type (protocol) cannot be changed after create bind_portset='portset', name='name', os_type='os_type' ) if self.module.params.get('initiators') is not None: self.parameters['initiators'] = [self.na_helper.sanitize_wwn(initiator) for initiator in self.module.params['initiators']] self.rest_api = OntapRestAPI(self.module) self.use_rest = self.rest_api.is_rest() def too_old_for_rest(minimum_generation, minimum_major): return self.use_rest and self.rest_api.get_ontap_version() < (minimum_generation, minimum_major) ontap_99_options = ['bind_portset'] if too_old_for_rest(9, 9) and any(x in self.parameters for x in ontap_99_options): self.module.warn('Warning: falling back to ZAPI: %s' % self.rest_api.options_require_ontap_version(ontap_99_options, version='9.9')) self.use_rest = False ontap_99_options = ['igroups'] if not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9) and any(x in self.parameters for x in ontap_99_options): self.module.fail_json(msg='Error: %s' % self.rest_api.options_require_ontap_version(ontap_99_options, version='9.9')) if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9): if 'igroups' in self.parameters: # we may need to remove existing initiators self.parameters['initiators'] = list() elif 'initiators' in self.parameters: # we may need to remove existing igroups self.parameters['igroups'] = list() if not self.use_rest: if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver']) def fail_on_error(self, error, stack=False): if error is None: return elements = dict(msg="Error: %s" % error) if stack: elements['stack'] = traceback.format_stack() self.module.fail_json(**elements) def get_igroup_rest(self, name): api = "protocols/san/igroups" fields = 'name,uuid,svm,initiators,os_type,protocol' if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9): fields += ',igroups' query = dict(name=name, fields=fields) query['svm.name'] = self.parameters['vserver'] response, error = self.rest_api.get(api, query) igroup, error = rrh.check_for_0_or_1_records(api, response, error) self.fail_on_error(error) if igroup: try: igroup_details = dict( name=igroup['name'], uuid=igroup['uuid'], vserver=igroup['svm']['name'], os_type=igroup['os_type'], initiator_group_type=igroup['protocol'], name_to_uuid=dict() ) except KeyError as exc: self.module.fail_json(msg='Error: unexpected igroup body: %s, KeyError on %s' % (str(igroup), str(exc))) igroup_details['name_to_key'] = dict() for attr in ('igroups', 'initiators'): if attr in igroup: igroup_details[attr] = [item['name'] for item in igroup[attr]] # for initiators, there is no uuid, so we're using name as the key igroup_details['name_to_uuid'][attr] = dict((item['name'], item.get('uuid', item['name'])) for item in igroup[attr]) else: igroup_details[attr] = [] igroup_details['name_to_uuid'][attr] = dict() return igroup_details return None def get_igroup(self, name): """ Return details about the igroup :param: name : Name of the igroup :return: Details about the igroup. None if not found. :rtype: dict """ if self.use_rest: return self.get_igroup_rest(name) igroup_info = netapp_utils.zapi.NaElement('igroup-get-iter') attributes = dict(query={'initiator-group-info': {'initiator-group-name': name, 'vserver': self.parameters['vserver']}}) igroup_info.translate_struct(attributes) current = None try: result = self.server.invoke_successfully(igroup_info, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching igroup info %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1: igroup_info = result.get_child_by_name('attributes-list') initiator_group_info = igroup_info.get_child_by_name('initiator-group-info') initiators = [] if initiator_group_info.get_child_by_name('initiators'): current_initiators = initiator_group_info['initiators'].get_children() for initiator in current_initiators: initiators.append(initiator['initiator-name']) current = { 'initiators': initiators, # place holder, not used for ZAPI 'name_to_uuid': dict(initiators=dict()) } zapi_to_params = { 'vserver': 'vserver', 'initiator-group-os-type': 'os_type', 'initiator-group-portset-name': 'bind_portset', 'initiator-group-type': 'initiator_group_type' } for attr in zapi_to_params: value = igroup_info.get_child_content(attr) if value is not None: current[zapi_to_params[attr]] = value return current def check_what_is_valid(self, what): if self.use_rest and what in ('igroups', 'initiators'): return if what == 'initiators': return raise KeyError('what=%s' % what) def add_initiators_or_igroups_rest(self, uuid, what, names): self.check_what_is_valid(what) api = "protocols/san/igroups/%s/%s" % (uuid, what) records = [dict(name=name) for name in names] body = dict(records=records) dummy, error = self.rest_api.post(api, body) self.fail_on_error(error) def add_initiators_or_igroups(self, uuid, what, current_names): """ Add the list of desired initiators to igroup unless they are already set :return: None """ self.check_what_is_valid(what) # don't add if initiators/igroups is empty string if self.parameters.get(what) == [''] or self.parameters.get(what) is None: return names_to_add = [name for name in self.parameters[what] if name not in current_names] if self.use_rest and uuid is not None and names_to_add: self.add_initiators_or_igroups_rest(uuid, what, names_to_add) else: for name in names_to_add: self.modify_initiator(name, 'igroup-add') def delete_initiator_or_igroup_rest(self, uuid, what, name): self.check_what_is_valid(what) api = "protocols/san/igroups/%s/%s/%s" % (uuid, what, name) dummy, error = self.rest_api.delete(api) self.fail_on_error(error) def remove_initiators_or_igroups(self, uuid, what, current_names, mapping): """ Removes current names from igroup unless they are still desired :return: None """ self.check_what_is_valid(what) for name in current_names: if name not in self.parameters.get(what, list()): if self.use_rest: self.delete_initiator_or_igroup_rest(uuid, what, mapping[name]) else: self.modify_initiator(name, 'igroup-remove') def modify_initiator(self, initiator, zapi): """ Add or remove an initiator to/from an igroup """ options = {'initiator-group-name': self.parameters['name'], 'initiator': initiator} igroup_modify = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options) try: self.server.invoke_successfully(igroup_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying igroup initiator %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def create_igroup_rest(self): api = "protocols/san/igroups" body = dict( name=self.parameters['name'], os_type=self.parameters['os_type']) body['svm'] = dict(name=self.parameters['vserver']) mapping = dict( initiator_group_type='protocol', bind_portset='portset', igroups='igroups', initiators='initiators' ) for option in mapping: value = self.parameters.get(option) if value is not None: if option in ('igroups', 'initiators'): # we may have an empty list, ignore it value = [dict(name=name) for name in value] if value else None if value is not None: body[mapping[option]] = value dummy, error = self.rest_api.post(api, body) self.fail_on_error(error) def create_igroup(self): """ Create the igroup. """ if self.use_rest: self.create_igroup_rest() return options = {'initiator-group-name': self.parameters['name']} if self.parameters.get('os_type') is not None: options['os-type'] = self.parameters['os_type'] if self.parameters.get('initiator_group_type') is not None: options['initiator-group-type'] = self.parameters['initiator_group_type'] if self.parameters.get('bind_portset') is not None: options['bind-portset'] = self.parameters['bind_portset'] igroup_create = netapp_utils.zapi.NaElement.create_node_with_children( 'igroup-create', **options) try: self.server.invoke_successfully(igroup_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error provisioning igroup %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) self.add_initiators_or_igroups(None, 'initiators', []) def modify_igroup_rest(self, uuid, modify): api = "protocols/san/igroups/%s" % uuid body = dict() for option in modify: if option not in self.rest_modify_zapi_to_rest: self.module.fail_json(msg='Error: modifying %s is not supported in REST' % option) body[self.rest_modify_zapi_to_rest[option]] = modify[option] if body: dummy, error = self.rest_api.patch(api, body) self.fail_on_error(error) def delete_igroup_rest(self, uuid): api = "protocols/san/igroups/%s" % uuid if self.parameters['force_remove_initiator']: query = dict(allow_delete_while_mapped=True) else: query = None dummy, error = self.rest_api.delete(api, params=query) self.fail_on_error(error) def delete_igroup(self, uuid): """ Delete the igroup. """ if self.use_rest: self.delete_igroup_rest(uuid) return igroup_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'igroup-destroy', **{'initiator-group-name': self.parameters['name'], 'force': 'true' if self.parameters['force_remove_initiator'] else 'false'}) try: self.server.invoke_successfully(igroup_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting igroup %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_igroup(self): """ Rename the igroup. """ if self.use_rest: self.module.fail_json('Internal error, should not call rename, but use modify') igroup_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'igroup-rename', **{'initiator-group-name': self.parameters['from_name'], 'initiator-group-new-name': str(self.parameters['name'])}) try: self.server.invoke_successfully(igroup_rename, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error renaming igroup %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def report_error_in_modify(self, modify, context): if modify: if len(modify) > 1: tag = 'any of ' else: tag = '' self.module.fail_json(msg='Error: modifying %s %s is not supported in %s' % (tag, str(modify), context)) def validate_modify(self, modify): """Identify options that cannot be modified for REST or ZAPI """ if not modify: return modify_local = dict(modify) modify_local.pop('igroups', None) modify_local.pop('initiators', None) if not self.use_rest: self.report_error_in_modify(modify_local, 'ZAPI') return for option in modify: if option in self.rest_modify_zapi_to_rest: modify_local.pop(option) self.report_error_in_modify(modify_local, 'REST') def autosupport_log(self): if not self.use_rest: netapp_utils.ems_log_event("na_ontap_igroup", self.server) def is_rename_action(self, cd_action, current): old = self.get_igroup(self.parameters['from_name']) rename = self.na_helper.is_rename_action(old, current) if rename is None: self.module.fail_json(msg='Error: igroup with from_name=%s not found' % self.parameters.get('from_name')) if rename: current = old cd_action = None return cd_action, rename, current def apply(self): self.autosupport_log() uuid = None rename, modify = None, None current = self.get_igroup(self.parameters['name']) cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action == 'create' and self.parameters.get('from_name'): cd_action, rename, current = self.is_rename_action(cd_action, current) if cd_action is None and self.parameters['state'] == 'present': modify = self.na_helper.get_modified_attributes(current, self.parameters) # a change in name is handled in rename for ZAPI, but REST can use modify if self.use_rest: rename = False else: modify.pop('name', None) if current and self.use_rest: uuid = current['uuid'] if cd_action == 'create' and self.use_rest and 'os_type' not in self.parameters: self.module.fail_json(msg='Error: os_type is a required parameter when creating an igroup with REST') self.validate_modify(modify) if self.na_helper.changed and not self.module.check_mode: if rename: self.rename_igroup() elif cd_action == 'create': self.create_igroup() elif cd_action == 'delete': self.delete_igroup(uuid) if modify: for attr in ('igroups', 'initiators'): if attr in current: # we need to remove everything first self.remove_initiators_or_igroups(uuid, attr, current[attr], current['name_to_uuid'][attr]) for attr in ('igroups', 'initiators'): if attr in current: self.add_initiators_or_igroups(uuid, attr, current[attr]) modify.pop(attr, None) if modify: self.modify_igroup_rest(uuid, modify) self.module.exit_json(changed=self.na_helper.changed, current=current, modify=modify)
class NetAppOntapQTree(object): '''Class with qtree operations''' def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), flexvol_name=dict(required=True, type='str'), vserver=dict(required=True, type='str'), export_policy=dict(required=False, type='str'), security_style=dict(required=False, type='str', choices=['unix', 'ntfs', 'mixed']), oplocks=dict(required=False, type='str', choices=['enabled', 'disabled']), unix_permissions=dict(required=False, type='str'), force_delete=dict(required=False, type='bool', default=True), wait_for_completion=dict(required=False, type='bool', default=True), time_out=dict(required=False, type='int', default=180), )) self.module = AnsibleModule( argument_spec=self.argument_spec, required_if=[ ('state', 'present', ['flexvol_name']) ], supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.rest_api = OntapRestAPI(self.module) if self.rest_api.is_rest(): self.use_rest = True else: if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def get_qtree(self, name=None): """ Checks if the qtree exists. :param: name : qtree name :return: Details about the qtree False if qtree is not found :rtype: bool """ if name is None: name = self.parameters['name'] if self.use_rest: api = "storage/qtrees" query = {'fields': 'export_policy,unix_permissions,security_style,volume', 'svm.name': self.parameters['vserver'], 'volume': self.parameters['flexvol_name'], 'name': name} message, error = self.rest_api.get(api, query) if error: self.module.fail_json(msg=error) if len(message.keys()) == 0: return None elif 'records' in message and len(message['records']) == 0: return None elif 'records' not in message: error = "Unexpected response in get_qtree from %s: %s" % (api, repr(message)) self.module.fail_json(msg=error) return message['records'][0] else: qtree_list_iter = netapp_utils.zapi.NaElement('qtree-list-iter') query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'qtree-info', **{'vserver': self.parameters['vserver'], 'volume': self.parameters['flexvol_name'], 'qtree': name}) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) qtree_list_iter.add_child_elem(query) result = self.server.invoke_successfully(qtree_list_iter, enable_tunneling=True) return_q = None if (result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1): return_q = {'export_policy': result['attributes-list']['qtree-info']['export-policy'], 'oplocks': result['attributes-list']['qtree-info']['oplocks'], 'security_style': result['attributes-list']['qtree-info']['security-style']} if result['attributes-list']['qtree-info'].get_child_by_name('mode'): return_q['unix_permissions'] = result['attributes-list']['qtree-info']['mode'] else: return_q['unix_permissions'] = '' return return_q def create_qtree(self): """ Create a qtree """ if self.use_rest: api = "storage/qtrees" body = {'name': self.parameters['name'], 'volume': {'name': self.parameters['flexvol_name']}, 'svm': {'name': self.parameters['vserver']}} if self.parameters.get('export_policy'): body['export_policy'] = self.parameters['export_policy'] if self.parameters.get('security_style'): body['security_style'] = self.parameters['security_style'] if self.parameters.get('unix_permissions'): body['unix_permissions'] = self.parameters['unix_permissions'] __, error = self.rest_api.post(api, body) if error: self.module.fail_json(msg=error) else: options = {'qtree': self.parameters['name'], 'volume': self.parameters['flexvol_name']} if self.parameters.get('export_policy'): options['export-policy'] = self.parameters['export_policy'] if self.parameters.get('security_style'): options['security-style'] = self.parameters['security_style'] if self.parameters.get('oplocks'): options['oplocks'] = self.parameters['oplocks'] if self.parameters.get('unix_permissions'): options['mode'] = self.parameters['unix_permissions'] qtree_create = netapp_utils.zapi.NaElement.create_node_with_children( 'qtree-create', **options) try: self.server.invoke_successfully(qtree_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error provisioning qtree %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_qtree(self, current): """ Delete a qtree """ if self.use_rest: uuid = current['volume']['uuid'] qid = str(current['id']) api = "storage/qtrees/%s/%s" % (uuid, qid) query = {'return_timeout': 3} response, error = self.rest_api.delete(api, params=query) if error: self.module.fail_json(msg=error) if 'job' in response and self.parameters['wait_for_completion']: message, error = self.rest_api.wait_on_job(response['job'], timeout=self.parameters['time_out'], increment=10) if error: self.module.fail_json(msg="%s" % error) else: path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name']) options = {'qtree': path} if self.parameters['force_delete']: options['force'] = "true" qtree_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'qtree-delete', **options) try: self.server.invoke_successfully(qtree_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error deleting qtree %s: %s" % (path, to_native(error)), exception=traceback.format_exc()) def rename_qtree(self, current): """ Rename a qtree """ if self.use_rest: body = {'name': self.parameters['name']} uuid = current['volume']['uuid'] qid = str(current['id']) api = "storage/qtrees/%s/%s" % (uuid, qid) dummy, error = self.rest_api.patch(api, body) if error: self.module.fail_json(msg=error) else: path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['from_name']) new_path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name']) qtree_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'qtree-rename', **{'qtree': path, 'new-qtree-name': new_path}) try: self.server.invoke_successfully(qtree_rename, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error renaming qtree %s: %s" % (self.parameters['from_name'], to_native(error)), exception=traceback.format_exc()) def modify_qtree(self, current): """ Modify a qtree """ if self.use_rest: now = datetime.datetime.now() body = {} if self.parameters.get('security_style'): body['security_style'] = self.parameters['security_style'] if self.parameters.get('unix_permissions'): body['unix_permissions'] = self.parameters['unix_permissions'] if self.parameters.get('export_policy'): body['export_policy'] = {'name': self.parameters['export_policy']} uuid = current['volume']['uuid'] qid = str(current['id']) api = "storage/qtrees/%s/%s" % (uuid, qid) timeout = 120 query = {'return_timeout': timeout} dummy, error = self.rest_api.patch(api, body, query) later = datetime.datetime.now() time_elapsed = later - now # modify will not return any error if return_timeout is 0, so we set it to 120 seconds as default if time_elapsed.seconds > (timeout - 1): self.module.fail_json(msg="Too long to run") if error: self.module.fail_json(msg=error) else: options = {'qtree': self.parameters['name'], 'volume': self.parameters['flexvol_name']} if self.parameters.get('export_policy'): options['export-policy'] = self.parameters['export_policy'] if self.parameters.get('security_style'): options['security-style'] = self.parameters['security_style'] if self.parameters.get('oplocks'): options['oplocks'] = self.parameters['oplocks'] if self.parameters.get('unix_permissions'): options['mode'] = self.parameters['unix_permissions'] qtree_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'qtree-modify', **options) try: self.server.invoke_successfully(qtree_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying qtree %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def apply(self): '''Call create/delete/modify/rename operations''' if not self.use_rest: netapp_utils.ems_log_event("na_ontap_qtree", self.server) current = self.get_qtree() rename, cd_action, modify = None, None, None if self.parameters.get('from_name'): from_qtree = self.get_qtree(self.parameters['from_name']) rename = self.na_helper.is_rename_action(from_qtree, current) if rename is None: self.module.fail_json(msg='Error renaming: qtree %s does not exist' % self.parameters['from_name']) if rename: current = from_qtree else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': if self.parameters.get('security_style') and self.parameters['security_style'] != current['security_style']: modify = True if self.parameters.get('unix_permissions') and \ self.parameters['unix_permissions'] != str(current['unix_permissions']): modify = True # rest and zapi handle export policy differently if self.use_rest: if self.parameters.get('export_policy') and \ self.parameters['export_policy'] != current['export_policy']['name']: modify = True else: if self.parameters.get('export_policy') and \ self.parameters['export_policy'] != current['export_policy']: modify = True if self.use_rest and cd_action == 'delete' and not self.parameters['force_delete']: self.module.fail_json(msg='Error: force_delete option is not supported for REST, unless set to true.') if modify: self.na_helper.changed = True if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_qtree() elif cd_action == 'delete': self.delete_qtree(current) else: if rename: self.rename_qtree(current) if modify: self.modify_qtree(current) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapSecurityConfig(object): """ Modifies SSL Security Config """ def __init__(self): """ Initialize the ONTAP Security Config class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(name=dict(required=False, type='str', default='ssl'), is_fips_enabled=dict(required=False, type='bool'), supported_ciphers=dict(required=False, type='str'), supported_protocols=dict( required=False, type='list', elements='str', choices=['TLSv1.2', 'TLSv1.1', 'TLSv1']))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if 'is_fips_enabled' in self.parameters and 'supported_ciphers' in self.parameters: # if fips is enabled, supported ciphers should not be specified. if self.parameters['is_fips_enabled']: self.module.fail_json( msg= 'is_fips_enabled was specified as true and supported_ciphers was specified. \ If fips is enabled then supported ciphers should not be specified' ) if 'is_fips_enabled' in self.parameters and 'supported_protocols' in self.parameters: # if fips is enabled, TLSv1 is not a supported protocol. if self.parameters[ 'is_fips_enabled'] and 'TLSv1' in self.parameters[ 'supported_protocols']: self.module.fail_json( msg= 'is_fips_enabled was specified as true and TLSv1 was specified as a supported protocol. \ If fips is enabled then TLSv1 is not a supported protocol') if 'supported_ciphers' in self.parameters: self.parameters['supported_ciphers'] = self.parameters[ 'supported_ciphers'].replace('\\', '') self.rest_api = OntapRestAPI(self.module) self.use_rest = self.rest_api.is_rest() if not self.use_rest: if HAS_NETAPP_LIB is False: self.module.fail_json( msg='The python NetApp-Lib module is required') else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module) def get_security_config(self): """ Get the current security configuration """ if self.use_rest: api = "private/cli/security/config" query = { 'fields': 'interface,is-fips-enabled,supported-protocols,supported-ciphers' } message, error = self.rest_api.get(api, query) if error: self.module.fail_json(msg=error) if not message: self.module.fail_json( msg="get_security_config expected a message") return_value = { 'name': message['records'][0]['interface'], 'is_fips_enabled': message['records'][0]['is_fips_enabled'], 'supported_ciphers': message['records'][0]['supported_ciphers'], 'supported_protocols': message['records'][0]['supported_protocols'] } else: return_value = None security_config_get_iter = netapp_utils.zapi.NaElement( 'security-config-get') security_config_info = netapp_utils.zapi.NaElement( 'desired-attributes') if 'is_fips_enabled' in self.parameters: security_config_info.add_new_child( 'is-fips-enabled', self.na_helper.get_value_for_bool( from_zapi=False, value=self.parameters['is_fips_enabled'])) if 'supported_ciphers' in self.parameters: security_config_info.add_new_child( 'supported-ciphers', self.parameters['supported_ciphers']) if 'supported_protocols' in self.parameters: security_config_info.add_new_child( 'supported-protocols', ','.join(self.parameters['supported_protocols'])) security_config_get_iter.add_child_elem(security_config_info) security_config_get_iter.add_new_child('interface', self.parameters['name']) try: result = self.server.invoke_successfully( security_config_get_iter, True) security_supported_protocols = [] if result.get_child_by_name('attributes'): attributes = result.get_child_by_name('attributes') security_config_attributes = attributes.get_child_by_name( 'security-config-info') supported_protocols = security_config_attributes.get_child_by_name( 'supported-protocols') for supported_protocol in supported_protocols.get_children( ): security_supported_protocols.append( supported_protocol.get_content()) return_value = { 'name': security_config_attributes['interface'], 'is_fips_enabled': self.na_helper.get_value_for_bool( from_zapi=True, value=security_config_attributes['is-fips-enabled'] ), 'supported_ciphers': security_config_attributes['supported-ciphers'], 'supported_protocols': security_supported_protocols, } except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error getting security config for interface %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) return return_value def modify_security_config(self): """ Modifies the security configuration. """ if self.use_rest: # url contains the value for 'name' due to the interface not being supported through body using the api. api = "private/cli/security/config?interface=%s" % ( self.parameters['name']) body = {} if 'is_fips_enabled' in self.parameters: body['is_fips_enabled'] = self.parameters['is_fips_enabled'] if 'supported_ciphers' in self.parameters: body['supported_ciphers'] = self.parameters[ 'supported_ciphers'] if 'supported_protocols' in self.parameters: body['supported_protocols'] = self.parameters[ 'supported_protocols'] dummy, error = self.rest_api.patch(api, body) if error: self.module.fail_json(msg=error) else: security_config_obj = netapp_utils.zapi.NaElement( "security-config-modify") security_config_obj.add_new_child("interface", self.parameters['name']) if 'is_fips_enabled' in self.parameters: self.parameters[ 'is_fips_enabled'] = self.na_helper.get_value_for_bool( from_zapi=False, value=self.parameters['is_fips_enabled']) security_config_obj.add_new_child( 'is-fips-enabled', self.parameters['is_fips_enabled']) if 'supported_ciphers' in self.parameters: security_config_obj.add_new_child( 'supported-ciphers', self.parameters['supported_ciphers']) if 'supported_protocols' in self.parameters: supported_protocol_obj = netapp_utils.zapi.NaElement( "supported-protocols") for protocol in self.parameters['supported_protocols']: supported_protocol_obj.add_new_child('string', protocol) security_config_obj.add_child_elem(supported_protocol_obj) try: self.server.invoke_successfully(security_config_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying security config for interface %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def ems_log_event(self): results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event("na_ontap_security_config", cserver) def apply(self): if not self.use_rest: self.ems_log_event() current = self.get_security_config() modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if not self.module.check_mode: self.modify_security_config() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapStorageFailover(object): """ Enable or disable storage failover for a specified node """ def __init__(self): """ Initialize the Ontap Storage failover class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, choices=['present', 'absent'], default='present'), node_name=dict(required=True, type='str'), )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) # set up variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if self.parameters['state'] == 'present': self.parameters['is_enabled'] = True else: self.parameters['is_enabled'] = False self.rest_api = OntapRestAPI(self.module) self.use_rest = self.rest_api.is_rest() if not self.use_rest: if HAS_NETAPP_LIB is False: self.module.fail_json(msg='The python NetApp-Lib module is required') else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) def get_storage_failover(self): """ get the storage failover for a given node :return: dict of is-enabled: true if enabled is true None if not """ if self.use_rest: return_value = None api = "cluster/nodes" query = { 'fields': 'uuid,ha.enabled', 'name': self.parameters['node_name'] } message, error = self.rest_api.get(api, query) records, error = rrh.check_for_0_or_1_records(api, message, error) if error is None and records is not None: return_value = { 'uuid': message['records'][0]['uuid'], 'is_enabled': message['records'][0]['ha']['enabled'] } if error: self.module.fail_json(msg=error) if not records: error = "REST API did not return failover details for node %s" % (self.parameters['node_name']) self.module.fail_json(msg=error) return return_value else: storage_failover_get_iter = netapp_utils.zapi.NaElement('cf-status') storage_failover_get_iter.add_new_child('node', self.parameters['node_name']) try: result = self.server.invoke_successfully(storage_failover_get_iter, True) return_value = {'is_enabled': self.na_helper.get_value_for_bool(True, result.get_child_content('is-enabled'))} except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error getting storage failover info for node %s: %s' % ( self.parameters['node_name'], to_native(error)), exception=traceback.format_exc()) return return_value def modify_storage_failover(self, current): """ Modifies storage failover for a specified node """ if self.use_rest: api = "cluster/nodes" query = { 'uuid': current['uuid'], 'return_timeout': 60 # Timeout added to allow for return messages } body = {'ha': {'enabled': self.parameters['is_enabled']}} message, error = self.rest_api.patch(api, body, query) if error: self.module.fail_json(msg=error) else: if self.parameters['state'] == 'present': cf_service = 'cf-service-enable' else: cf_service = 'cf-service-disable' storage_failover_modify = netapp_utils.zapi.NaElement(cf_service) storage_failover_modify.add_new_child('node', self.parameters['node_name']) try: result = self.server.invoke_successfully(storage_failover_modify, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying storage failover for node %s: %s' % ( self.parameters['node_name'], to_native(error)), exception=traceback.format_exc()) def ems_log_event(self): results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) return netapp_utils.ems_log_event("na_ontap_storage_failover", cserver) def apply(self): if not self.use_rest: self.ems_log_event() current = self.get_storage_failover() modify = self.na_helper.get_modified_attributes(current, self.parameters) if self.na_helper.changed: if not self.module.check_mode: self.modify_storage_failover(current) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapCifsLocalGroupMember(object): """ Add or remove CIFS local group members """ def __init__(self): """ Initialize the Ontap CifsLocalGroupMember class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), vserver=dict(required=True, type='str'), group=dict(required=True, type='str'), member=dict(required=True, type='str'))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) # set up variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.rest_api = OntapRestAPI(self.module) self.use_rest = self.rest_api.is_rest() if not self.use_rest: if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def get_cifs_local_group_member(self): return_value = None if self.use_rest: api = "private/cli/vserver/cifs/users-and-groups/local-group/members" query = { 'group-name': self.parameters['group'], 'fields': 'member', 'vserver': self.parameters['vserver'], 'member': self.parameters['member'] } message, error = self.rest_api.get(api, query) if error: self.module.fail_json(msg=error) if len(message.keys()) == 0: return None elif 'records' in message and len(message['records']) == 0: return None elif 'records' not in message: error = "Unexpected response in get_cifs_local_group_member from %s: %s" % ( api, repr(message)) self.module.fail_json(msg=error) return_value = { 'group': message['records'][0]['group_name'], 'member': message['records'][0]['member'], 'vserver': message['records'][0]['vserver'] } return return_value else: group_members_get_iter = netapp_utils.zapi.NaElement( 'cifs-local-group-members-get-iter') group_members_info = netapp_utils.zapi.NaElement( 'cifs-local-group-members') group_members_info.add_new_child('group-name', self.parameters['group']) group_members_info.add_new_child('vserver', self.parameters['vserver']) group_members_info.add_new_child('member', self.parameters['member']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(group_members_info) group_members_get_iter.add_child_elem(query) try: result = self.server.invoke_successfully( group_members_get_iter, True) if result.get_child_by_name('attributes-list'): group_member_policy_attributes = result['attributes-list'][ 'cifs-local-group-members'] return_value = { 'group': group_member_policy_attributes['group-name'], 'member': group_member_policy_attributes['member'], 'vserver': group_member_policy_attributes['vserver'] } except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg= 'Error getting CIFS local group members for group %s on vserver %s: %s' % (self.parameters['group'], self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) return return_value def add_cifs_local_group_member(self): """ Adds a member to a CIFS local group """ if self.use_rest: api = "private/cli/vserver/cifs/users-and-groups/local-group/add-members" body = { "vserver": self.parameters['vserver'], "group-name": self.parameters['group'], "member-names": [self.parameters['member']] } dummy, error = self.rest_api.post(api, body) if error: self.module.fail_json(msg=error) else: group_members_obj = netapp_utils.zapi.NaElement( "cifs-local-group-members-add-members") group_members_obj.add_new_child("group-name", self.parameters['group']) member_names = netapp_utils.zapi.NaElement("member-names") member_names.add_new_child('cifs-name', self.parameters['member']) group_members_obj.add_child_elem(member_names) try: self.server.invoke_successfully(group_members_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg= 'Error adding member %s to cifs local group %s on vserver %s: %s' % (self.parameters['member'], self.parameters['group'], self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) def remove_cifs_local_group_member(self): """ Removes a member from a CIFS local group """ if self.use_rest: api = "private/cli/vserver/cifs/users-and-groups/local-group/remove-members" body = { "vserver": self.parameters['vserver'], "group-name": self.parameters['group'], "member-names": [self.parameters['member']] } dummy, error = self.rest_api.post(api, body) if error: self.module.fail_json(msg=error) else: group_members_obj = netapp_utils.zapi.NaElement( "cifs-local-group-members-remove-members") group_members_obj.add_new_child("group-name", self.parameters['group']) member_names = netapp_utils.zapi.NaElement("member-names") member_names.add_new_child('cifs-name', self.parameters['member']) group_members_obj.add_child_elem(member_names) try: self.server.invoke_successfully(group_members_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg= 'Error removing member %s from cifs local group %s on vserver %s: %s' % (self.parameters['member'], self.parameters['group'], self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) def apply(self): if not self.use_rest: netapp_utils.ems_log_event("na_ontap_cifs_local_group_member", self.server) current = self.get_cifs_local_group_member() cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if not self.module.check_mode: if cd_action == 'create': self.add_cifs_local_group_member() elif cd_action == 'delete': self.remove_cifs_local_group_member() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapNetRoutes(object): """ Create, Modifies and Destroys a Net Route """ def __init__(self): """ Initialize the Ontap Net Route class """ self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), vserver=dict(required=True, type='str'), destination=dict(required=True, type='str'), gateway=dict(required=True, type='str'), metric=dict(required=False, type='int'), from_destination=dict(required=False, type='str', default=None), from_gateway=dict(required=False, type='str', default=None), from_metric=dict(required=False, type='int', default=None), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.restApi = OntapRestAPI(self.module) # some attributes are not supported in earlier REST implementation unsupported_rest_properties = ['metric', 'from_metric'] used_unsupported_rest_properties = [ x for x in unsupported_rest_properties if x in self.parameters ] self.use_rest, error = self.restApi.is_rest( used_unsupported_rest_properties) if error is not None: self.module.fail_json(msg=error) if not self.use_rest: if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) return def create_net_route(self, current_metric=None): """ Creates a new Route """ if self.use_rest: api = "network/ip/routes" params = { 'gateway': self.parameters['gateway'], 'svm': self.parameters['vserver'] } if self.parameters.get('destination') is not None: d = self.parameters['destination'].split('/') params['destination'] = {'address': d[0], 'netmask': d[1]} __, error = self.restApi.post(api, params) if error: self.module.fail_json(msg=error) else: route_obj = netapp_utils.zapi.NaElement('net-routes-create') route_obj.add_new_child("destination", self.parameters['destination']) route_obj.add_new_child("gateway", self.parameters['gateway']) if current_metric is None and self.parameters.get( 'metric') is not None: metric = self.parameters['metric'] else: metric = current_metric # Metric can be None, Can't set metric to none if metric is not None: route_obj.add_new_child("metric", str(metric)) try: self.server.invoke_successfully(route_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating net route: %s' % (to_native(error)), exception=traceback.format_exc()) def delete_net_route(self, params): """ Deletes a given Route """ if self.use_rest: uuid = params['uuid'] api = "network/ip/routes/" + uuid data = None message, error = self.restApi.delete(api, data) if error: self.module.fail_json(msg=error) else: route_obj = netapp_utils.zapi.NaElement('net-routes-destroy') if params is None: params = self.parameters route_obj.add_new_child("destination", params['destination']) route_obj.add_new_child("gateway", params['gateway']) try: self.server.invoke_successfully(route_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting net route: %s' % (to_native(error)), exception=traceback.format_exc()) def modify_net_route(self, current, desired): """ Modify a net route Since we cannot modify a route, we are deleting the existing route, and creating a new one. """ if self.use_rest: if desired.get('destination') is not None: d = desired['destination'].split('/') if d[0] != current['destination']['address'] or d[ 1] != current['destination']['netmask']: self.na_helper.changed = True self.parameters['destination'] = desired['destination'] else: self.parameters['destination'] = '%s/%s' % ( current['destination']['address'], current['destination']['netmask']) if desired.get('gateway') is not None: if desired['gateway'] != current['gateway']: self.na_helper.changed = True self.parameters['gateway'] = desired['gateway'] else: self.parameters['gateway'] = current['gateway'] if not self.na_helper.changed or self.module.check_mode: return params = { 'destination': '%s/%s' % (current['destination']['address'], current['destination']['netmask']), 'gateway': current['gateway'] } target = self.get_net_route(params) self.delete_net_route(target) self.create_net_route() return else: # return if there is nothing to change for key, val in desired.items(): if val != current[key]: self.na_helper.changed = True break if not self.na_helper.changed or self.module.check_mode: return # delete and re-create with new params self.delete_net_route(current) route_obj = netapp_utils.zapi.NaElement('net-routes-create') for attribute in ['metric', 'destination', 'gateway']: if desired.get(attribute) is not None: value = desired[attribute] else: value = current[attribute] route_obj.add_new_child(attribute, str(value)) try: result = self.server.invoke_successfully(route_obj, True) except netapp_utils.zapi.NaApiError as error: # restore the old route, create the route with the existing metric self.create_net_route(current['metric']) # return if desired route already exists if to_native(error.code) == '13001': return # Invalid value specified for any of the attributes self.module.fail_json(msg='Error modifying net route: %s' % (to_native(error)), exception=traceback.format_exc()) def get_net_route(self, params=None): """ Checks to see if a route exist or not :return: NaElement object if a route exists, None otherwise """ if params is not None: # we need either destination or gateway to fetch desired route if params.get('destination') is None and params.get( 'gateway') is None: return None if self.use_rest: api = "network/ip/routes" data = {'fields': 'destination,gateway,svm'} message, error = self.restApi.get(api, data) if error: self.module.fail_json(msg=error) if len(message.keys()) == 0: return None elif 'records' in message and len(message['records']) == 0: return None elif 'records' not in message: error = "Unexpected response in get_net_route from %s: %s" % ( api, repr(message)) self.module.fail_json(msg=error) if params is None: params = self.parameters else: if params.get('destination') is None: params['destination'] = self.parameters['destination'] if params.get('gateway') is None: params['gateway'] = self.parameters['gateway'] params['vserver'] = self.parameters['vserver'] for record in message['records']: if record['gateway'] == params['gateway'] and \ record['destination']['address'] == params['destination'].split('/')[0] and \ record.get('svm') and record['svm']['name'] == params['vserver']: return record return None else: current = None route_obj = netapp_utils.zapi.NaElement('net-routes-get') for attr in ['destination', 'gateway']: if params and params.get(attr) is not None: value = params[attr] else: value = self.parameters[attr] route_obj.add_new_child(attr, value) try: result = self.server.invoke_successfully(route_obj, True) if result.get_child_by_name('attributes') is not None: route_info = result.get_child_by_name( 'attributes').get_child_by_name('net-vs-routes-info') current = { 'destination': route_info.get_child_content('destination'), 'gateway': route_info.get_child_content('gateway'), 'metric': int(route_info.get_child_content('metric')) } except netapp_utils.zapi.NaApiError as error: # Error 13040 denotes a route doesn't exist. if to_native(error.code) == "15661": return None self.module.fail_json(msg='Error fetching net route: %s' % (to_native(error)), exception=traceback.format_exc()) return current @staticmethod def is_modify_action(current, desired): """ Get desired action to be applied for net routes Destination and gateway are unique params for a route and cannot be duplicated So if a route with desired destination or gateway exists already, we don't try to modify :param current: current details :param desired: desired details :return: create / delete / modify / None """ if current is None and desired is None: # this is invalid # cannot modify a non existent resource return None if current is None and desired is not None: # idempotency or duplication # we need not create return False if current is not None and desired is not None: # we can't modify an ambiguous route (idempotency/duplication) return False return True def get_params_to_be_modified(self, current): """ Get parameters and values that need to be modified :param current: current details :return: dict(), None """ if current is None: return None desired = dict() if self.parameters.get('new_destination') is not None and \ self.parameters['new_destination'] != current['destination']: desired['destination'] = self.parameters['new_destination'] if self.parameters.get('new_gateway') is not None and \ self.parameters['new_gateway'] != current['gateway']: desired['gateway'] = self.parameters['new_gateway'] if self.parameters.get('new_metric') is not None and \ self.parameters['new_metric'] != current['metric']: desired['metric'] = self.parameters['new_metric'] return desired def apply(self): """ Run Module based on play book """ if not self.use_rest: netapp_utils.ems_log_event("na_ontap_net_routes", self.server) current = self.get_net_route() modify, cd_action = None, None if self.use_rest: modify_params = { 'gateway': self.parameters.get('from_gateway'), 'destination': self.parameters.get('from_destination') } if any(modify_params.values()): # destination and gateway combination is unique, and is considered like a id. so modify destination # or gateway is considered a rename action. old_params = self.get_net_route(modify_params) modify = self.na_helper.is_rename_action(old_params, current) if modify is None: self.module.fail_json( msg="Error modifying: route %s does not exist" % self.parameters['from_destination']) else: cd_action = self.na_helper.get_cd_action( current, self.parameters) else: modify_params = { 'destination': self.parameters.get('from_destination'), 'gateway': self.parameters.get('from_gateway'), 'metric': self.parameters.get('from_metric') } # if any from_* param is present in playbook, check for modify action if any(modify_params.values()): # destination and gateway combination is unique, and is considered like a id. so modify destination # or gateway is considered a rename action. metric is considered an attribute of the route so it is # considered as modify. if modify_params.get('metric') is not None: modify = True old_params = current else: # get parameters that are eligible for modify old_params = self.get_net_route(modify_params) modify = self.na_helper.is_rename_action( old_params, current) if modify is None: self.module.fail_json( msg="Error modifying: route %s does not exist" % self.parameters['from_destination']) else: cd_action = self.na_helper.get_cd_action( current, self.parameters) if cd_action == 'create': if not self.module.check_mode: self.create_net_route() elif cd_action == 'delete': if not self.module.check_mode: self.delete_net_route(current) elif modify: desired = {} for key, value in old_params.items(): desired[key] = value for key, value in modify_params.items(): if value is not None: desired[key] = self.parameters.get(key) self.modify_net_route(old_params, desired) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapVolumeEfficiency(object): """ Creates, Modifies and Disables a Volume Efficiency """ def __init__(self): """ Initialize the ONTAP Volume Efficiency class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, choices=['present', 'absent'], default='present'), vserver=dict(required=True, type='str'), path=dict(required=True, type='str'), schedule=dict(required=False, type='str'), policy=dict(required=False, choices=['auto', 'default', 'inline-only', '-'], type='str'), enable_inline_compression=dict(required=False, type='bool'), enable_compression=dict(required=False, type='bool'), enable_inline_dedupe=dict(required=False, type='bool'), enable_data_compaction=dict(required=False, type='bool'), enable_cross_volume_inline_dedupe=dict(required=False, type='bool'), enable_cross_volume_background_dedupe=dict(required=False, type='bool') )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True, mutually_exclusive=[('policy', 'schedule')] ) # set up variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if self.parameters['state'] == 'present': self.parameters['enabled'] = 'enabled' else: self.parameters['enabled'] = 'disabled' self.rest_api = OntapRestAPI(self.module) self.use_rest = self.rest_api.is_rest() if not self.use_rest: if HAS_NETAPP_LIB is False: self.module.fail_json(msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver']) def get_volume_efficiency(self): """ get the storage efficiency for a given path :return: dict of sis if exist, None if not """ return_value = None if self.use_rest: api = 'private/cli/volume/efficiency' query = { 'fields': 'path,volume,state,schedule,compression,inline_compression,inline_dedupe,policy,data_compaction,' 'cross_volume_inline_dedupe,cross_volume_background_dedupe', 'path': self.parameters['path'], 'vserver': self.parameters['vserver'] } message, error = self.rest_api.get(api, query) if error: self.module.fail_json(msg=error) if len(message.keys()) == 0: return None if 'records' in message and len(message['records']) == 0: return None if 'records' not in message: error = "Unexpected response in api call from %s: %s" % (api, repr(message)) self.module.fail_json(msg=error) return_value = { 'path': message['records'][0]['path'], 'enabled': message['records'][0]['state'], 'schedule': message['records'][0]['schedule'], 'enable_inline_compression': message['records'][0]['inline_compression'], 'enable_compression': message['records'][0]['compression'], 'enable_inline_dedupe': message['records'][0]['inline_dedupe'], 'enable_data_compaction': message['records'][0]['data_compaction'], 'enable_cross_volume_inline_dedupe': message['records'][0]['cross_volume_inline_dedupe'], 'enable_cross_volume_background_dedupe': message['records'][0]['cross_volume_background_dedupe'] } if 'policy' in message['records'][0]: return_value['policy'] = message['records'][0]['policy'] else: return_value['policy'] = '-' return return_value else: sis_get_iter = netapp_utils.zapi.NaElement('sis-get-iter') sis_status_info = netapp_utils.zapi.NaElement('sis-status-info') sis_status_info.add_new_child('path', self.parameters['path']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(sis_status_info) sis_get_iter.add_child_elem(query) result = self.server.invoke_successfully(sis_get_iter, True) try: if result.get_child_by_name('attributes-list'): sis_status_attributes = result['attributes-list']['sis-status-info'] return_value = { 'path': sis_status_attributes['path'], 'enabled': sis_status_attributes['state'], 'schedule': sis_status_attributes['schedule'], 'enable_inline_compression': self.na_helper.get_value_for_bool( True, sis_status_attributes.get_child_content('is-inline-compression-enabled') ), 'enable_compression': self.na_helper.get_value_for_bool(True, sis_status_attributes.get_child_content('is-compression-enabled')), 'enable_inline_dedupe': self.na_helper.get_value_for_bool(True, sis_status_attributes.get_child_content('is-inline-dedupe-enabled')), 'enable_data_compaction': self.na_helper.get_value_for_bool( True, sis_status_attributes.get_child_content('is-data-compaction-enabled') ), 'enable_cross_volume_inline_dedupe': self.na_helper.get_value_for_bool( True, sis_status_attributes.get_child_content('is-cross-volume-inline-dedupe-enabled') ), 'enable_cross_volume_background_dedupe': self.na_helper.get_value_for_bool( True, sis_status_attributes.get_child_content('is-cross-volume-background-dedupe-enabled') ) } if sis_status_attributes.get_child_by_name('policy'): return_value['policy'] = sis_status_attributes['policy'] else: return_value['policy'] = '-' except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error getting volume efficiency for path %s on vserver %s: %s' % ( self.parameters['path'], self.parameters['vserver'], to_native(error)), exception=traceback.format_exc() ) return return_value def enable_volume_efficiency(self): """ Enables Volume efficiency for a given volume by path """ if self.use_rest: api = 'private/cli/volume/efficiency/on' body = dict() query = { 'path': self.parameters['path'], 'vserver': self.parameters['vserver'] } message, error = self.rest_api.patch(api, body, query) if error: self.module.fail_json(msg=error) elif message['num_records'] == 0: error = 'Error enabling storage efficiency for path %s on vserver %s as the path provided does not exist.' % (self.parameters['path'], self.parameters['vserver']) self.module.fail_json(msg=error) else: sis_enable = netapp_utils.zapi.NaElement("sis-enable") sis_enable.add_new_child("path", self.parameters['path']) try: self.server.invoke_successfully(sis_enable, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error enabling storage efficiency for path %s on vserver %s: %s' % (self.parameters['path'], self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) def disable_volume_efficiency(self): """ Disables Volume efficiency for a given volume by path """ if self.use_rest: api = 'private/cli/volume/efficiency/off' body = dict() query = { 'path': self.parameters['path'], 'vserver': self.parameters['vserver'] } dummy, error = self.rest_api.patch(api, body, query) if error: self.module.fail_json(msg=error) else: sis_disable = netapp_utils.zapi.NaElement("sis-disable") sis_disable.add_new_child("path", self.parameters['path']) try: self.server.invoke_successfully(sis_disable, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error disabling storage efficiency for path %s: %s' % (self.parameters['path'], to_native(error)), exception=traceback.format_exc()) def modify_volume_efficiency(self): """ Modifies volume efficiency settings for a given volume by path """ if self.use_rest: api = 'private/cli/volume/efficiency' body = dict() query = { 'path': self.parameters['path'], 'vserver': self.parameters['vserver'] } if 'schedule' in self.parameters: body['schedule'] = self.parameters['schedule'] if 'policy' in self.parameters: body['policy'] = self.parameters['policy'] if 'enable_compression' in self.parameters: body['compression'] = self.parameters['enable_compression'] if 'enable_inline_compression' in self.parameters: body['inline_compression'] = self.parameters['enable_inline_compression'] if 'enable_inline_dedupe' in self.parameters: body['inline_dedupe'] = self.parameters['enable_inline_dedupe'] if 'enable_data_compaction' in self.parameters: body['data_compaction'] = self.parameters['enable_data_compaction'] if 'enable_cross_volume_inline_dedupe' in self.parameters: body['cross_volume_inline_dedupe'] = self.parameters['enable_cross_volume_inline_dedupe'] if 'enable_cross_volume_background_dedupe' in self.parameters: body['cross_volume_background_dedupe'] = self.parameters['enable_cross_volume_background_dedupe'] dummy, error = self.rest_api.patch(api, body, query) if error: self.module.fail_json(msg=error) else: sis_config_obj = netapp_utils.zapi.NaElement("sis-set-config") sis_config_obj.add_new_child('path', self.parameters['path']) if 'schedule' in self.parameters: sis_config_obj.add_new_child('schedule', self.parameters['schedule']) if 'policy' in self.parameters: sis_config_obj.add_new_child('policy-name', self.parameters['policy']) if 'enable_compression' in self.parameters: sis_config_obj.add_new_child('enable-compression', self.na_helper.get_value_for_bool(False, self.parameters['enable_compression'])) if 'enable_inline_compression' in self.parameters: sis_config_obj.add_new_child('enable-inline-compression', self.na_helper.get_value_for_bool( False, self.parameters['enable_inline_compression']) ) if 'enable_inline_dedupe' in self.parameters: sis_config_obj.add_new_child('enable-inline-dedupe', self.na_helper.get_value_for_bool( False, self.parameters['enable_inline_dedupe']) ) if 'enable_data_compaction' in self.parameters: sis_config_obj.add_new_child('enable-data-compaction', self.na_helper.get_value_for_bool( False, self.parameters['enable_data_compaction']) ) if 'enable_cross_volume_inline_dedupe' in self.parameters: sis_config_obj.add_new_child('enable-cross-volume-inline-dedupe', self.na_helper.get_value_for_bool( False, self.parameters['enable_cross_volume_inline_dedupe']) ) if 'enable_cross_volume_background_dedupe' in self.parameters: sis_config_obj.add_new_child('enable-cross-volume-background-dedupe', self.na_helper.get_value_for_bool( False, self.parameters['enable_cross_volume_background_dedupe']) ) try: self.server.invoke_successfully(sis_config_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying storage efficiency for path %s: %s' % (self.parameters['path'], to_native(error)), exception=traceback.format_exc()) def apply(self): if not self.use_rest: netapp_utils.ems_log_event("na_ontap_volume_efficiency", self.server) current = self.get_volume_efficiency() # If the volume efficiency does not exist for a given path to create this current is set to disabled # this is for ONTAP systems that do not enable efficiency by default. if current is None: current = {'enabled': 'disabled'} modify = self.na_helper.get_modified_attributes(current, self.parameters) if self.na_helper.changed: if not self.module.check_mode: if self.parameters['state'] == 'present' and current['enabled'] == 'disabled': self.enable_volume_efficiency() # Checking to see if there are any additional parameters that need to be set after enabling volume efficiency required for Non-AFF systems current = self.get_volume_efficiency() modify = self.na_helper.get_modified_attributes(current, self.parameters) elif self.parameters['state'] == 'absent' and current['enabled'] == 'enabled': self.disable_volume_efficiency() if 'enabled' in modify: del modify['enabled'] # Removed the enabled key if there is anything remaining in the modify dict we need to modify. if modify: self.modify_volume_efficiency() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapDns(object): """ Enable and Disable dns """ def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), vserver=dict(required=True, type='str'), domains=dict(required=False, type='list'), nameservers=dict(required=False, type='list'), skip_validation=dict(required=False, type='bool'))) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[('state', 'present', ['domains', 'nameservers'])], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # Cluster vserver and data vserver use different REST API. self.is_cluster = False # REST API should be used for ONTAP 9.6 or higher, ZAPI for lower version self.restApi = OntapRestAPI(self.module) # some attributes are not supported in earlier REST implementation unsupported_rest_properties = ['skip_validation'] used_unsupported_rest_properties = [ x for x in unsupported_rest_properties if x in self.parameters ] self.use_rest, error = self.restApi.is_rest( used_unsupported_rest_properties) if error is not None: self.module.fail_json(msg=error) if not self.use_rest: if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) return def create_dns(self): """ Create DNS server :return: none """ if self.use_rest: if self.is_cluster: api = 'cluster' params = { 'dns_domains': self.parameters['domains'], 'name_servers': self.parameters['nameservers'] } message, error = self.restApi.patch(api, params) if error: self.module.fail_json(msg=error) else: api = 'name-services/dns' params = { 'domains': self.parameters['domains'], 'servers': self.parameters['nameservers'], 'svm': { 'name': self.parameters['vserver'] } } message, error = self.restApi.post(api, params) if error: self.module.fail_json(msg=error) else: dns = netapp_utils.zapi.NaElement('net-dns-create') nameservers = netapp_utils.zapi.NaElement('name-servers') domains = netapp_utils.zapi.NaElement('domains') for each in self.parameters['nameservers']: ip_address = netapp_utils.zapi.NaElement('ip-address') ip_address.set_content(each) nameservers.add_child_elem(ip_address) dns.add_child_elem(nameservers) for each in self.parameters['domains']: domain = netapp_utils.zapi.NaElement('string') domain.set_content(each) domains.add_child_elem(domain) dns.add_child_elem(domains) if self.parameters.get('skip_validation'): validation = netapp_utils.zapi.NaElement( 'skip-config-validation') validation.set_content(str(self.parameters['skip_validation'])) dns.add_child_elem(validation) try: self.server.invoke_successfully(dns, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating dns: %s' % (to_native(error)), exception=traceback.format_exc()) def destroy_dns(self, dns_attrs): """ Destroys an already created dns :return: """ if self.use_rest: if self.is_cluster: error = 'cluster operation for deleting DNS is not supported with REST.' self.module.fail_json(msg=error) api = 'name-services/dns/' + dns_attrs['uuid'] data = None message, error = self.restApi.delete(api, data) if error: self.module.fail_json(msg=error) else: try: self.server.invoke_successfully( netapp_utils.zapi.NaElement('net-dns-destroy'), True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error destroying dns %s' % (to_native(error)), exception=traceback.format_exc()) def get_cluster(self): api = "cluster" message, error = self.restApi.get(api, None) if error: self.module.fail_json(msg=error) if len(message.keys()) == 0: self.module.fail_json(msg="no data from cluster %s" % str(message)) return message def get_cluster_dns(self): cluster_attrs = self.get_cluster() dns_attrs = None if self.parameters['vserver'] == cluster_attrs['name']: dns_attrs = { 'domains': cluster_attrs.get('dns_domains'), 'nameservers': cluster_attrs.get('name_servers'), 'uuid': cluster_attrs['uuid'], } self.is_cluster = True if dns_attrs['domains'] is None and dns_attrs[ 'nameservers'] is None: dns_attrs = None return dns_attrs def get_dns(self): if self.use_rest: api = "name-services/dns" params = { 'fields': 'domains,servers,svm', "svm.name": self.parameters['vserver'] } message, error = self.restApi.get(api, params) if error: self.module.fail_json(msg=error) if len(message.keys()) == 0: message = None elif 'records' in message and len(message['records']) == 0: message = None elif 'records' not in message or len(message['records']) != 1: error = "Unexpected response from %s: %s" % (api, repr(message)) self.module.fail_json(msg=error) if message is not None: record = message['records'][0] attrs = { 'domains': record['domains'], 'nameservers': record['servers'], 'uuid': record['svm']['uuid'] } return attrs return None else: dns_obj = netapp_utils.zapi.NaElement('net-dns-get') try: result = self.server.invoke_successfully(dns_obj, True) except netapp_utils.zapi.NaApiError as error: if to_native(error.code) == "15661": # 15661 is object not found return None else: self.module.fail_json(msg=to_native(error), exception=traceback.format_exc()) # read data for modify attrs = dict() attributes = result.get_child_by_name('attributes') dns_info = attributes.get_child_by_name('net-dns-info') nameservers = dns_info.get_child_by_name('name-servers') attrs['nameservers'] = [ each.get_content() for each in nameservers.get_children() ] domains = dns_info.get_child_by_name('domains') attrs['domains'] = [ each.get_content() for each in domains.get_children() ] attrs['skip_validation'] = dns_info.get_child_by_name( 'skip-config-validation') return attrs def modify_dns(self, dns_attrs): if self.use_rest: changed = False params = {} if dns_attrs['nameservers'] != self.parameters['nameservers']: changed = True params['servers'] = self.parameters['nameservers'] if dns_attrs['domains'] != self.parameters['domains']: changed = True params['domains'] = self.parameters['domains'] if changed: uuid = dns_attrs['uuid'] api = "name-services/dns/" + uuid if self.is_cluster: api = 'cluster' params = { 'dns_domains': self.parameters['domains'], 'name_servers': self.parameters['nameservers'] } message, error = self.restApi.patch(api, params) if error: self.module.fail_json(msg=error) else: changed = False dns = netapp_utils.zapi.NaElement('net-dns-modify') if dns_attrs['nameservers'] != self.parameters['nameservers']: changed = True nameservers = netapp_utils.zapi.NaElement('name-servers') for each in self.parameters['nameservers']: ip_address = netapp_utils.zapi.NaElement('ip-address') ip_address.set_content(each) nameservers.add_child_elem(ip_address) dns.add_child_elem(nameservers) if dns_attrs['domains'] != self.parameters['domains']: changed = True domains = netapp_utils.zapi.NaElement('domains') for each in self.parameters['domains']: domain = netapp_utils.zapi.NaElement('string') domain.set_content(each) domains.add_child_elem(domain) dns.add_child_elem(domains) if changed: if self.parameters.get('skip_validation'): validation = netapp_utils.zapi.NaElement( 'skip-config-validation') validation.set_content( str(self.parameters['skip_validation'])) dns.add_child_elem(validation) try: self.server.invoke_successfully(dns, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying dns %s' % (to_native(error)), exception=traceback.format_exc()) return changed def apply(self): # asup logging if not self.use_rest: netapp_utils.ems_log_event("na_ontap_dns", self.server) dns_attrs = self.get_dns() if self.use_rest and dns_attrs is None: # There is a chance we are working at the cluster level dns_attrs = self.get_cluster_dns() changed = False if self.parameters['state'] == 'present': if dns_attrs is not None: changed = self.modify_dns(dns_attrs) else: self.create_dns() changed = True else: if dns_attrs is not None: self.destroy_dns(dns_attrs) changed = True self.module.exit_json(changed=changed)
class NetAppONTAPJob(object): '''Class with job schedule cron methods''' def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), job_minutes=dict(required=False, type='list', elements='str'), job_months=dict(required=False, type='list', elements='str'), job_hours=dict(required=False, type='list', elements='str'), job_days_of_month=dict(required=False, type='list', elements='str'), job_days_of_week=dict(required=False, type='list', elements='str'))) self.uuid = None self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.set_playbook_zapi_key_map() self.set_playbook_api_key_map() self.restApi = OntapRestAPI(self.module) if self.restApi.is_rest(): self.use_rest = True else: if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module) def set_playbook_zapi_key_map(self): self.na_helper.zapi_string_keys = { 'name': 'job-schedule-name', } self.na_helper.zapi_list_keys = { 'job_minutes': ('job-schedule-cron-minute', 'cron-minute'), 'job_months': ('job-schedule-cron-month', 'cron-month'), 'job_hours': ('job-schedule-cron-hour', 'cron-hour'), 'job_days_of_month': ('job-schedule-cron-day', 'cron-day-of-month'), 'job_days_of_week': ('job-schedule-cron-day-of-week', 'cron-day-of-week') } def set_playbook_api_key_map(self): self.na_helper.api_list_keys = { 'job_minutes': 'minutes', 'job_months': 'months', 'job_hours': 'hours', 'job_days_of_month': 'days', 'job_days_of_week': 'weekdays' } def get_job_schedule(self): """ Return details about the job :param: name : Job name :return: Details about the Job. None if not found. :rtype: dict """ if self.use_rest: params = {'name': self.parameters['name']} api = '/cluster/schedules' message, error = self.restApi.get(api, params) if error is not None: self.module.fail_json( msg="Error on fetching job schedule: %s" % error) if message['num_records'] > 0: self.uuid = message['records'][0]['uuid'] job_details = dict() job_details['name'] = message['records'][0]['name'] for key, value in self.na_helper.api_list_keys.items(): if value in message['records'][0]['cron']: job_details[key] = message['records'][0]['cron'][value] # convert list of int to list of string for key, value in job_details.items(): if isinstance(value, list): for i in range(len(value)): value[i] = str(value[i]) return job_details else: job_get_iter = netapp_utils.zapi.NaElement( 'job-schedule-cron-get-iter') job_get_iter.translate_struct({ 'query': { 'job-schedule-cron-info': { 'job-schedule-name': self.parameters['name'] } } }) result = self.server.invoke_successfully(job_get_iter, True) job_details = None # check if job exists if result.get_child_by_name('num-records') and int( result['num-records']) >= 1: job_info = result['attributes-list']['job-schedule-cron-info'] job_details = dict() for item_key, zapi_key in self.na_helper.zapi_string_keys.items( ): job_details[item_key] = job_info[zapi_key] for item_key, zapi_key in self.na_helper.zapi_list_keys.items( ): parent, dummy = zapi_key job_details[item_key] = self.na_helper.get_value_for_list( from_zapi=True, zapi_parent=job_info.get_child_by_name(parent)) # if any of the job_hours, job_minutes, job_months, job_days are empty: # it means the value is -1 for ZAPI if not job_details[item_key]: job_details[item_key] = ['-1'] return job_details def add_job_details(self, na_element_object, values): """ Add children node for create or modify NaElement object :param na_element_object: modify or create NaElement object :param values: dictionary of cron values to be added :return: None """ for item_key in values: if item_key in self.na_helper.zapi_string_keys: zapi_key = self.na_helper.zapi_string_keys.get(item_key) na_element_object[zapi_key] = values[item_key] elif item_key in self.na_helper.zapi_list_keys: parent_key, child_key = self.na_helper.zapi_list_keys.get( item_key) na_element_object.add_child_elem( self.na_helper.get_value_for_list( from_zapi=False, zapi_parent=parent_key, zapi_child=child_key, data=values.get(item_key))) def create_job_schedule(self): """ Creates a job schedule """ # job_minutes is mandatory for create if self.parameters.get('job_minutes') is None: self.module.fail_json( msg='Error: missing required parameter job_minutes for create') if self.use_rest: cron = dict() for key, value in self.na_helper.api_list_keys.items(): # -1 means all in zapi, while empty means all in api. if self.parameters.get(key): if len(self.parameters[key]) == 1 and int( self.parameters[key][0]) == -1: pass else: cron[value] = self.parameters[key] params = {'name': self.parameters['name'], 'cron': cron} api = '/cluster/schedules' message, error = self.restApi.post(api, params) if error is not None: self.module.fail_json( msg="Error on creating job schedule: %s" % error) else: job_schedule_create = netapp_utils.zapi.NaElement( 'job-schedule-cron-create') self.add_job_details(job_schedule_create, self.parameters) try: self.server.invoke_successfully(job_schedule_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating job schedule %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_job_schedule(self): """ Delete a job schedule """ if self.use_rest: api = '/cluster/schedules/' + self.uuid message, error = self.restApi.delete(api, {}) if error is not None: self.module.fail_json( msg="Error on deleting job schedule: %s" % error) else: job_schedule_delete = netapp_utils.zapi.NaElement( 'job-schedule-cron-destroy') self.add_job_details(job_schedule_delete, self.parameters) try: self.server.invoke_successfully(job_schedule_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting job schedule %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def modify_job_schedule(self, params, current): """ modify a job schedule """ if self.use_rest: cron = dict() for key, value in self.na_helper.api_list_keys.items(): # -1 means all in zapi, while empty means all in api. if params.get(key): if len(self.parameters[key]) == 1 and int( self.parameters[key][0]) == -1: pass else: cron[value] = self.parameters[key] # Usually only include modify attributes, but omitting an attribute means all in api. # Need to add the current attributes in params. elif current.get(key): cron[value] = current[key] params = {'cron': cron} api = '/cluster/schedules/' + self.uuid message, error = self.restApi.patch(api, params) if error is not None: self.module.fail_json( msg="Error on modifying job schedule: %s" % error) else: job_schedule_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'job-schedule-cron-modify', **{'job-schedule-name': self.parameters['name']}) self.add_job_details(job_schedule_modify, params) try: self.server.invoke_successfully(job_schedule_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying job schedule %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def autosupport_log(self): """ Autosupport log for job_schedule :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event("na_ontap_job_schedule", cserver) def apply(self): """ Apply action to job-schedule """ if not self.use_rest: self.autosupport_log() current = self.get_job_schedule() action = self.na_helper.get_cd_action(current, self.parameters) if action is None and self.parameters['state'] == 'present': modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if action == 'create': self.create_job_schedule() elif action == 'delete': self.delete_job_schedule() elif modify: self.modify_job_schedule(modify, current) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPExportPolicy(object): """ Class with export policy methods """ def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str', default=None), vserver=dict(required=True, type='str') )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.restApi = OntapRestAPI(self.module) if self.restApi.is_rest(): self.use_rest = True else: if HAS_NETAPP_LIB is False: self.module.fail_json(msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver']) def get_export_policy(self, name=None, uuid=None): """ Return details about the export-policy :param: name : Name of the export-policy :return: Details about the export-policy. None if not found. :rtype: dict """ if name is None: name = self.parameters['name'] if self.use_rest: params = {'fields': 'name', 'name': name, 'svm.uuid': uuid} api = 'protocols/nfs/export-policies/' message, error = self.restApi.get(api, params) if error is not None: self.module.fail_json(msg="Error on fetching export policy: %s" % error) if message['num_records'] > 0: return {'policy-name': message['records'][0]['name']} else: return None else: export_policy_iter = netapp_utils.zapi.NaElement('export-policy-get-iter') export_policy_info = netapp_utils.zapi.NaElement('export-policy-info') export_policy_info.add_new_child('policy-name', name) export_policy_info.add_new_child('vserver', self.parameters['vserver']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(export_policy_info) export_policy_iter.add_child_elem(query) result = self.server.invoke_successfully(export_policy_iter, True) return_value = None # check if query returns the expected export-policy if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: export_policy = result.get_child_by_name('attributes-list').get_child_by_name('export-policy-info').get_child_by_name('policy-name') return_value = { 'policy-name': export_policy } return return_value def create_export_policy(self, uuid=None): """ Creates an export policy """ if self.use_rest: params = {'name': self.parameters['name'], 'svm.uuid': uuid} api = 'protocols/nfs/export-policies' message, error = self.restApi.post(api, params) if error is not None: self.module.fail_json(msg="Error on creating export policy: %s" % error) else: export_policy_create = netapp_utils.zapi.NaElement.create_node_with_children( 'export-policy-create', **{'policy-name': self.parameters['name']}) try: self.server.invoke_successfully(export_policy_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error on creating export-policy %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_export_policy(self, policy_id=None): """ Delete export-policy """ if self.use_rest: params = {} api = 'protocols/nfs/export-policies/' + str(policy_id) message, error = self.restApi.delete(api, params) if error is not None: self.module.fail_json(msg=" Error on deleting export policy: %s" % error) else: export_policy_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'export-policy-destroy', **{'policy-name': self.parameters['name'], }) try: self.server.invoke_successfully(export_policy_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error on deleting export-policy %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_export_policy(self, policy_id=None): """ Rename the export-policy. """ if self.use_rest: params = {'name': self.parameters['name']} api = 'protocols/nfs/export-policies/' + str(policy_id) message, error = self.restApi.patch(api, params) if error is not None: self.module.fail_json(msg="Error on renaming export policy: %s" % error) else: export_policy_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'export-policy-rename', **{'policy-name': self.parameters['from_name'], 'new-policy-name': self.parameters['name']}) try: self.server.invoke_successfully(export_policy_rename, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error on renaming export-policy %s:%s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def get_export_policy_id(self, name=None): """ Get a export policy's id :return: id of the export policy """ if name is None: name = self.parameters['name'] params = {'fields': 'id', 'svm.name': self.parameters['vserver'], 'name': name } api = 'protocols/nfs/export-policies' message, error = self.restApi.get(api, params) if error is not None: self.module.fail_json(msg="%s" % error) if message['num_records'] == 0: return None else: return message['records'][0]['id'] def get_export_policy_svm_uuid(self): """ Get a svm's uuid :return: uuid of the svm """ params = {'svm.name': self.parameters['vserver']} api = 'protocols/nfs/export-policies' message, error = self.restApi.get(api, params) if error is not None: self.module.fail_json(msg="%s" % error) return message['records'][0]['svm']['uuid'] def apply(self): """ Apply action to export-policy """ policy_id, uuid = None, None cd_action, rename = None, None if not self.use_rest: netapp_utils.ems_log_event("na_ontap_export_policy", self.server) if self.use_rest: uuid = self.get_export_policy_svm_uuid() if self.parameters.get('from_name'): policy_id = self.get_export_policy_id(self.parameters['from_name']) else: policy_id = self.get_export_policy_id() current = self.get_export_policy(uuid=uuid) if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action(self.get_export_policy(self.parameters['from_name']), current) if rename is None: self.module.fail_json(msg="Error renaming: export policy %s does not exist" % self.parameters['from_name']) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_export_policy(policy_id=policy_id) elif cd_action == 'create': self.create_export_policy(uuid=uuid) elif cd_action == 'delete': self.delete_export_policy(policy_id=policy_id) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPasupInvoke(object): ''' send ASUP message ''' def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(name=dict(required=False, type='str'), autosupport_message=dict(required=False, type='str', aliases=["message"]), type=dict(required=False, choices=['test', 'performance', 'all'], default='all'), uri=dict(required=False, type='str'))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # REST API should be used for ONTAP 9.6 or higher. self.rest_api = OntapRestAPI(self.module) if self.rest_api.is_rest(): self.use_rest = True else: if not HAS_NETAPP_LIB: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module) def get_nodes(self): nodes = list() node_obj = netapp_utils.zapi.NaElement('system-node-get-iter') desired_attributes = netapp_utils.zapi.NaElement('desired-attributes') node_details_info = netapp_utils.zapi.NaElement('node-details-info') node_details_info.add_new_child('node', '') desired_attributes.add_child_elem(node_details_info) node_obj.add_child_elem(desired_attributes) try: result = self.server.invoke_successfully(node_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg=to_native(error), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) > 0: node_info = result.get_child_by_name('attributes-list') if node_info is not None: nodes = [ node_details.get_child_content('node') for node_details in node_info.get_children() ] return nodes def send_zapi_message(self, params, node_name): params['node-name'] = node_name send_message = netapp_utils.zapi.NaElement.create_node_with_children( 'autosupport-invoke', **params) try: self.server.invoke_successfully(send_message, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error on sending autosupport message to node %s: %s." % (node_name, to_native(error)), exception=traceback.format_exc()) def send_message(self): params = dict() if self.parameters.get('autosupport_message'): params['message'] = self.parameters['autosupport_message'] if self.parameters.get('type'): params['type'] = self.parameters['type'] if self.parameters.get('uri'): params['uri'] = self.parameters['uri'] if self.use_rest: if self.parameters.get('name'): params['node.name'] = self.parameters['name'] node_name = params['node.name'] else: node_name = '*' api = 'support/autosupport/messages' dummy, error = self.rest_api.post(api, params) if error is not None: self.module.fail_json( msg="Error on sending autosupport message to node %s: %s." % (node_name, error)) else: if self.parameters.get('name'): node_names = [self.parameters['name']] else: # simulate REST behavior by sending to all nodes in the cluster node_names = self.get_nodes() for name in node_names: self.send_zapi_message(params, name) def ems_log_event(self): results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) return netapp_utils.ems_log_event("na_ontap_autosupport_invoke", cserver) def apply(self): if not self.use_rest: self.ems_log_event() if self.module.check_mode: pass else: self.send_message() self.module.exit_json(changed=True)
class NetAppOntapSVM(object): def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), root_volume=dict(type='str'), root_volume_aggregate=dict(type='str'), root_volume_security_style=dict( type='str', choices=['unix', 'ntfs', 'mixed', 'unified']), allowed_protocols=dict(type='list', elements='str'), aggr_list=dict(type='list', elements='str'), ipspace=dict(type='str', required=False), snapshot_policy=dict(type='str', required=False), language=dict(type='str', required=False), subtype=dict(type='str', choices=[ 'default', 'dp_destination', 'sync_source', 'sync_destination' ]), comment=dict(type="str", required=False))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # Ontap documentation uses C.UTF-8, but actually stores as c.utf_8. if 'language' in self.parameters and self.parameters['language'].lower( ) == 'c.utf-8': self.parameters['language'] = 'c.utf_8' self.restApi = OntapRestAPI(self.module) # with REST, to force synchronous operations self.timeout = self.restApi.timeout # root volume not supported with rest api unsupported_rest_properties = [ 'root_volume', 'root_volume_aggregate', 'root_volume_security_style' ] used_unsupported_rest_properties = [ x for x in unsupported_rest_properties if x in self.parameters ] self.use_rest, error = self.restApi.is_rest( used_unsupported_rest_properties) if error is not None: self.module.fail_json(msg=error) if not self.use_rest: if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module) @staticmethod def clean_up_output(vserver_details): vserver_details['root_volume'] = None vserver_details['root_volume_aggregate'] = None vserver_details['root_volume_security_style'] = None vserver_details['aggr_list'] = [] for aggr in vserver_details['aggregates']: vserver_details['aggr_list'].append(aggr['name']) vserver_details.pop('aggregates') vserver_details['ipspace'] = vserver_details['ipspace']['name'] vserver_details['snapshot_policy'] = vserver_details[ 'snapshot_policy']['name'] vserver_details['allowed_protocols'] = [] if 'cifs' in vserver_details: if vserver_details['cifs']['enabled']: vserver_details['allowed_protocols'].append('cifs') vserver_details.pop('cifs') if 'fcp' in vserver_details: if vserver_details['fcp']['enabled']: vserver_details['allowed_protocols'].append('fcp') vserver_details.pop('fcp') if 'issi' in vserver_details: if vserver_details['iscsi']['enabled']: vserver_details['allowed_protocols'].append('iscsi') vserver_details.pop('iscsi') if 'nvme' in vserver_details: if vserver_details['nvme']['enabled']: vserver_details['allowed_protocols'].append('nvme') vserver_details.pop('nvme') if 'nfs' in vserver_details: if vserver_details['nfs']['enabled']: vserver_details['allowed_protocols'].append('nfs') vserver_details.pop('nfs') return vserver_details def get_vserver(self, vserver_name=None): """ Checks if vserver exists. :return: vserver object if vserver found None if vserver is not found :rtype: object/None """ if vserver_name is None: vserver_name = self.parameters['name'] if self.use_rest: api = 'svm/svms' params = { 'fields': 'subtype,aggregates,language,snapshot_policy,ipspace,comment,nfs,cifs,fcp,iscsi,nvme' } message, error = self.restApi.get(api, params) if error: self.module.fail_json(msg=error) if len(message.keys()) == 0: return None elif 'records' in message and len(message['records']) == 0: return None elif 'records' not in message: error = "Unexpected response in get_net_route from %s: %s" % ( api, repr(message)) self.module.fail_json(msg=error) vserver_details = None for record in message['records']: if record['name'] == vserver_name: vserver_details = copy.deepcopy(record) break if vserver_details is None: return None return self.clean_up_output(vserver_details) else: vserver_info = netapp_utils.zapi.NaElement('vserver-get-iter') query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-info', **{'vserver-name': vserver_name}) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) vserver_info.add_child_elem(query) result = self.server.invoke_successfully(vserver_info, enable_tunneling=False) vserver_details = None if (result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1): attributes_list = result.get_child_by_name('attributes-list') vserver_info = attributes_list.get_child_by_name( 'vserver-info') aggr_list = list() ''' vserver aggr-list can be empty by default''' get_list = vserver_info.get_child_by_name('aggr-list') if get_list is not None: aggregates = get_list.get_children() for aggr in aggregates: aggr_list.append(aggr.get_content()) protocols = list() '''allowed-protocols is not empty for data SVM, but is for node SVM''' allowed_protocols = vserver_info.get_child_by_name( 'allowed-protocols') if allowed_protocols is not None: get_protocols = allowed_protocols.get_children() for protocol in get_protocols: protocols.append(protocol.get_content()) vserver_details = { 'name': vserver_info.get_child_content('vserver-name'), 'root_volume': vserver_info.get_child_content('root-volume'), 'root_volume_aggregate': vserver_info.get_child_content('root-volume-aggregate'), 'root_volume_security_style': vserver_info.get_child_content( 'root-volume-security-style'), 'subtype': vserver_info.get_child_content('vserver-subtype'), 'aggr_list': aggr_list, 'language': vserver_info.get_child_content('language'), 'snapshot_policy': vserver_info.get_child_content('snapshot-policy'), 'allowed_protocols': protocols, 'ipspace': vserver_info.get_child_content('ipspace'), 'comment': vserver_info.get_child_content('comment') } return vserver_details def create_vserver(self): if self.use_rest: api = 'svm/svms' params = {'name': self.parameters['name']} if self.parameters.get('language'): params['language'] = self.parameters['language'] if self.parameters.get('ipspace'): params['ipspace'] = self.parameters['ipspace'] if self.parameters.get('snapshot_policy'): params['snapshot_policy'] = self.parameters['snapshot_policy'] if self.parameters.get('subtype'): params['subtype'] = self.parameters['subtype'] if self.parameters.get('comment'): params['comment'] = self.parameters['comment'] if self.parameters.get('aggr_list'): params['aggregates'] = [] for aggr in self.parameters['aggr_list']: params['aggregates'].append({'name': aggr}) if self.parameters.get('allowed_protocols'): for protocol in self.parameters['allowed_protocols']: params[protocol] = {'enabled': 'true'} # for a sync operation data = {'return_timeout': self.timeout} __, error = self.restApi.post(api, params, data) if error: self.module.fail_json(msg=error) else: options = {'vserver-name': self.parameters['name']} self.add_parameter_to_dict(options, 'root_volume', 'root-volume') self.add_parameter_to_dict(options, 'root_volume_aggregate', 'root-volume-aggregate') self.add_parameter_to_dict(options, 'root_volume_security_style', 'root-volume-security-style') self.add_parameter_to_dict(options, 'language', 'language') self.add_parameter_to_dict(options, 'ipspace', 'ipspace') self.add_parameter_to_dict(options, 'snapshot_policy', 'snapshot-policy') self.add_parameter_to_dict(options, 'subtype', 'vserver-subtype') self.add_parameter_to_dict(options, 'comment', 'comment') vserver_create = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-create', **options) try: self.server.invoke_successfully(vserver_create, enable_tunneling=False) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error provisioning SVM %s: %s' % (self.parameters['name'], to_native(e)), exception=traceback.format_exc()) # add allowed-protocols, aggr-list after creation, # since vserver-create doesn't allow these attributes during creation options = dict() for key in ('allowed_protocols', 'aggr_list'): if self.parameters.get(key): options[key] = self.parameters[key] if options: self.modify_vserver(options) def delete_vserver(self, current=None): if self.use_rest: if current is None: self.module.fail_json( msg='Internal error, expecting SVM object in delete') api = 'svm/svms/%s' % current['uuid'] params = {} # for a sync operation data = {'return_timeout': self.timeout} __, error = self.restApi.delete(api, params, data) if error: self.module.fail_json(msg=error) else: vserver_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-destroy', **{'vserver-name': self.parameters['name']}) try: self.server.invoke_successfully(vserver_delete, enable_tunneling=False) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error deleting SVM %s: %s' % (self.parameters['name'], to_native(e)), exception=traceback.format_exc()) def rename_vserver(self, current=None): if self.use_rest: if current is None: self.module.fail_json( msg='Internal error, expecting SVM object in rename') api = 'svm/svms/%s' % current['uuid'] params = {'name': self.parameters['name']} # for a sync operation data = {'return_timeout': self.timeout} __, error = self.restApi.patch(api, params, data) if error: self.module.fail_json(msg=error) else: vserver_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-rename', **{ 'vserver-name': self.parameters['from_name'], 'new-name': self.parameters['name'] }) try: self.server.invoke_successfully(vserver_rename, enable_tunneling=False) except netapp_utils.zapi.NaApiError as e: self.module.fail_json( msg='Error renaming SVM %s: %s' % (self.parameters['from_name'], to_native(e)), exception=traceback.format_exc()) def modify_vserver(self, modify, current=None): ''' Modify vserver. :param modify: list of modify attributes :param current: with rest, SVM object to modify ''' if self.use_rest: if current is None: self.module.fail_json( msg='Internal error, expecting SVM object in modify') api = 'svm/svms/%s' % current['uuid'] for attribute in modify: if attribute == 'snapshot_policy' or attribute == 'allowed_protocols' or attribute == 'aggr_list': self.module.fail_json( msg='REST API does not support modify of %s' % attribute) # for a sync operation data = {'return_timeout': self.timeout} __, error = self.restApi.patch(api, modify, data) if error: self.module.fail_json(msg=error) else: vserver_modify = netapp_utils.zapi.NaElement('vserver-modify') vserver_modify.add_new_child('vserver-name', self.parameters['name']) for attribute in modify: if attribute == 'language': vserver_modify.add_new_child('language', self.parameters['language']) if attribute == 'snapshot_policy': vserver_modify.add_new_child( 'snapshot-policy', self.parameters['snapshot_policy']) if attribute == 'comment': vserver_modify.add_new_child('comment', self.parameters['comment']) if attribute == 'allowed_protocols': allowed_protocols = netapp_utils.zapi.NaElement( 'allowed-protocols') for protocol in self.parameters['allowed_protocols']: allowed_protocols.add_new_child('protocol', protocol) vserver_modify.add_child_elem(allowed_protocols) if attribute == 'aggr_list': aggregates = netapp_utils.zapi.NaElement('aggr-list') for aggr in self.parameters['aggr_list']: aggregates.add_new_child('aggr-name', aggr) vserver_modify.add_child_elem(aggregates) try: self.server.invoke_successfully(vserver_modify, enable_tunneling=False) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error modifying SVM %s: %s' % (self.parameters['name'], to_native(e)), exception=traceback.format_exc()) def add_parameter_to_dict(self, adict, name, key=None, tostr=False): ''' add defined parameter (not None) to adict using key. :param adict: a dictionary. :param name: name in self.parameters. :param key: key in adict. :param tostr: boolean. ''' if key is None: key = name if self.parameters.get(name) is not None: if tostr: adict[key] = str(self.parameters.get(name)) else: adict[key] = self.parameters.get(name) def apply(self): '''Call create/modify/delete operations.''' if not self.use_rest: self.asup_log_for_cserver("na_ontap_svm") current = self.get_vserver() cd_action, rename = None, None if self.parameters.get('from_name'): old_svm = self.get_vserver(self.parameters['from_name']) rename = self.na_helper.is_rename_action(old_svm, current) if rename is None: self.module.fail_json( msg='Error renaming SVM %s: no SVM with from_name %s.' % (self.parameters['name'], self.parameters['from_name'])) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes( current, self.parameters) for attribute in modify: if attribute in [ 'root_volume', 'root_volume_aggregate', 'root_volume_security_style', 'subtype', 'ipspace' ]: self.module.fail_json( msg='Error modifying SVM %s: can not modify %s.' % (self.parameters['name'], attribute)) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_vserver(old_svm) # If rename is True, cd_action is None, but modify could be true or false. if cd_action == 'create': self.create_vserver() elif cd_action == 'delete': self.delete_vserver(current) elif modify: self.modify_vserver(modify, current) self.module.exit_json(changed=self.na_helper.changed) def asup_log_for_cserver(self, event_name): """ Fetch admin vserver for the given cluster Create and Autosupport log event with the given module name :param event_name: Name of the event log :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event(event_name, cserver)
class NetAppOntapLogForward(object): def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(choices=['present', 'absent'], default='present'), destination=dict(required=True, type='str'), port=dict(required=True, type='int'), facility=dict(required=False, type='str', choices=[ 'kern', 'user', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7' ]), force=dict(required=False, type='bool'), protocol=dict(required=False, type='str', choices=[ 'udp_unencrypted', 'tcp_unencrypted', 'tcp_encrypted' ]), verify_server=dict(required=False, type='bool'))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.rest_api = OntapRestAPI(self.module) self.use_rest = self.rest_api.is_rest() if not self.use_rest: if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module) def get_log_forward_config(self): """ gets log forward configuration :return: dict of log forward properties if exist, None if not """ if self.use_rest: log_forward_config = None api = "security/audit/destinations" query = { 'fields': 'port,protocol,facility,address,verify_server', 'address': self.parameters['destination'], 'port': self.parameters['port'] } message, error = self.rest_api.get(api, query) if error: self.module.fail_json(msg=error) if len(message.keys()) == 0: return None elif 'records' in message and len(message['records']) == 0: return None elif 'records' not in message: error = "Unexpected response in get_security_key_manager from %s: %s" % ( api, repr(message)) self.module.fail_json(msg=error) log_forward_config = { 'destination': message['records'][0]['address'], 'facility': message['records'][0]['facility'], 'port': message['records'][0]['port'], 'protocol': message['records'][0]['protocol'], 'verify_server': message['records'][0]['verify_server'] } return log_forward_config else: log_forward_config = None log_forward_get = netapp_utils.zapi.NaElement( 'cluster-log-forward-get') log_forward_get.add_new_child('destination', self.parameters['destination']) log_forward_get.add_new_child( 'port', self.na_helper.get_value_for_int(False, self.parameters['port'])) try: result = self.server.invoke_successfully(log_forward_get, True) except netapp_utils.zapi.NaApiError as error: if to_native(error.code) == "15661": # config doesnt exist return None else: self.module.fail_json( msg= 'Error getting log forward configuration for destination %s on port %s: %s' % (self.parameters['destination'], self.na_helper.get_value_for_int( False, self.parameters['port']), to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('attributes'): log_forward_attributes = result.get_child_by_name('attributes') cluster_log_forward_info = log_forward_attributes.get_child_by_name( 'cluster-log-forward-info') log_forward_config = { 'destination': cluster_log_forward_info.get_child_content('destination'), 'facility': cluster_log_forward_info.get_child_content('facility'), 'port': self.na_helper.get_value_for_int( True, cluster_log_forward_info.get_child_content('port')), 'protocol': cluster_log_forward_info.get_child_content('protocol'), 'verify_server': self.na_helper.get_value_for_bool( True, cluster_log_forward_info.get_child_content( 'verify-server')) } return log_forward_config def create_log_forward_config(self): """ Creates a log forward config :return: nothing """ if self.use_rest: api = "security/audit/destinations" body = dict() body['address'] = self.parameters['destination'] body['port'] = self.parameters['port'] for attr in ('protocol', 'facility', 'verify_server', 'force'): if attr in self.parameters: body[attr] = self.parameters[attr] dummy, error = self.rest_api.post(api, body) if error: self.module.fail_json(msg=error) else: log_forward_config_obj = netapp_utils.zapi.NaElement( 'cluster-log-forward-create') log_forward_config_obj.add_new_child( 'destination', self.parameters['destination']) log_forward_config_obj.add_new_child( 'port', self.na_helper.get_value_for_int(False, self.parameters['port'])) if 'facility' in self.parameters: log_forward_config_obj.add_new_child( 'facility', self.parameters['facility']) if 'force' in self.parameters: log_forward_config_obj.add_new_child( 'force', self.na_helper.get_value_for_bool( False, self.parameters['force'])) if 'protocol' in self.parameters: log_forward_config_obj.add_new_child( 'protocol', self.parameters['protocol']) if 'verify_server' in self.parameters: log_forward_config_obj.add_new_child( 'verify-server', self.na_helper.get_value_for_bool( False, self.parameters['verify_server'])) try: self.server.invoke_successfully(log_forward_config_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg= 'Error creating log forward config with destination %s on port %s: %s' % (self.parameters['destination'], self.na_helper.get_value_for_int( False, self.parameters['port']), to_native(error)), exception=traceback.format_exc()) def modify_log_forward_config(self): # need to recreate as protocol can't be changed self.destroy_log_forward_config() self.create_log_forward_config() def destroy_log_forward_config(self): """ Delete a log forward configuration :return: nothing """ if self.use_rest: api = "security/audit/destinations/%s/%s" % ( self.parameters['destination'], self.parameters['port']) body = None query = {'return_timeout': 3} dummy, error = self.rest_api.delete(api, body, query) if error: self.module.fail_json(msg=error) else: log_forward_config_obj = netapp_utils.zapi.NaElement( 'cluster-log-forward-destroy') log_forward_config_obj.add_new_child( 'destination', self.parameters['destination']) log_forward_config_obj.add_new_child( 'port', self.na_helper.get_value_for_int(False, self.parameters['port'])) try: self.server.invoke_successfully(log_forward_config_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg= 'Error destroying log forward destination %s on port %s: %s' % (self.parameters['destination'], self.na_helper.get_value_for_int( False, self.parameters['port']), to_native(error)), exception=traceback.format_exc()) def ems_log_event(self): results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) return netapp_utils.ems_log_event("na_ontap_log_forward", cserver) def apply(self): if not self.use_rest: self.ems_log_event() current = self.get_log_forward_config() cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if not self.module.check_mode: if cd_action == 'create': self.create_log_forward_config() elif cd_action == 'delete': self.destroy_log_forward_config() elif modify: self.modify_log_forward_config() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapIpspace(object): '''Class with ipspace operations''' def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.rest_api = OntapRestAPI(self.module) if self.rest_api.is_rest(): self.use_rest = True else: if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module) return def ipspace_get_iter(self, name): """ Return net-ipspaces-get-iter query results :param name: Name of the ipspace :return: NaElement if ipspace found, None otherwise """ ipspace_get_iter = netapp_utils.zapi.NaElement('net-ipspaces-get-iter') query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'net-ipspaces-info', **{'ipspace': name}) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) ipspace_get_iter.add_child_elem(query) try: result = self.server.invoke_successfully(ipspace_get_iter, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: # Error 14636 denotes an ipspace does not exist # Error 13073 denotes an ipspace not found if to_native(error.code) == "14636" or to_native( error.code) == "13073": return None else: self.module.fail_json(msg=to_native(error), exception=traceback.format_exc()) return result def get_ipspace(self, name=None): """ Fetch details if ipspace exists :param name: Name of the ipspace to be fetched :return: Dictionary of current details if ipspace found None if ipspace is not found """ if name is None: name = self.parameters['name'] if self.use_rest: api = 'network/ipspaces' params = None message, error = self.rest_api.get(api, params) if error: self.module.fail_json(msg=error) if len(message.keys()) == 0: return None elif 'records' in message and len(message['records']) == 0: return None elif 'records' not in message: error = "Unexpected response from %s: %s" % (api, repr(message)) self.module.fail_json(msg=error) for record in message['records']: if record['name'] == name: return record return None else: ipspace_get = self.ipspace_get_iter(name) if (ipspace_get and ipspace_get.get_child_by_name('num-records') and int(ipspace_get.get_child_content('num-records')) >= 1): current_ipspace = dict() attr_list = ipspace_get.get_child_by_name('attributes-list') attr = attr_list.get_child_by_name('net-ipspaces-info') current_ipspace['name'] = attr.get_child_content('ipspace') return current_ipspace return None def create_ipspace(self): """ Create ipspace :return: None """ if self.use_rest: api = 'network/ipspaces' params = {'name': self.parameters['name']} dummy, error = self.rest_api.post(api, params) if error: self.module.fail_json(msg=error) else: ipspace_create = netapp_utils.zapi.NaElement.create_node_with_children( 'net-ipspaces-create', **{'ipspace': self.parameters['name']}) try: self.server.invoke_successfully(ipspace_create, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error provisioning ipspace %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_ipspace(self): """ Destroy ipspace :return: None """ if self.use_rest: current = self.get_ipspace() if current is not None: uuid = current['uuid'] api = 'network/ipspaces/' + uuid dummy, error = self.rest_api.delete(api) if error: self.module.fail_json(msg=error) else: ipspace_destroy = netapp_utils.zapi.NaElement.create_node_with_children( 'net-ipspaces-destroy', **{'ipspace': self.parameters['name']}) try: self.server.invoke_successfully(ipspace_destroy, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error removing ipspace %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_ipspace(self): """ Rename an ipspace :return: Nothing """ if self.use_rest: current = self.get_ipspace(self.parameters['from_name']) if current is None: self.module.fail_json(msg="Error renaming ipspace %s" % (self.parameters['from_name'])) uuid = current['uuid'] api = 'network/ipspaces/' + uuid params = {'name': self.parameters['name']} dummy, error = self.rest_api.patch(api, params) if error: self.module.fail_json(msg=error) else: ipspace_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'net-ipspaces-rename', **{ 'ipspace': self.parameters['from_name'], 'new-name': self.parameters['name'] }) try: self.server.invoke_successfully(ipspace_rename, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error renaming ipspace %s: %s" % (self.parameters['from_name'], to_native(error)), exception=traceback.format_exc()) def apply(self): """ Apply action to the ipspace :return: Nothing """ current = self.get_ipspace() # rename and create are mutually exclusive rename, cd_action = None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action( self.get_ipspace(self.parameters['from_name']), current) if rename is None: self.module.fail_json( msg="Error renaming: ipspace %s does not exist" % self.parameters['from_name']) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_ipspace() elif cd_action == 'create': self.create_ipspace() elif cd_action == 'delete': self.delete_ipspace() self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPNdmp(object): ''' modify vserver cifs security ''' def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.modifiable_options = dict( abort_on_disk_error=dict(required=False, type='bool'), authtype=dict(required=False, type='list'), backup_log_enable=dict(required=False, type='bool'), data_port_range=dict(required=False, type='str'), debug_enable=dict(required=False, type='bool'), debug_filter=dict(required=False, type='str'), dump_detailed_stats=dict(required=False, type='bool'), dump_logical_find=dict(required=False, type='str'), enable=dict(required=False, type='bool'), fh_dir_retry_interval=dict(required=False, type='int'), fh_node_retry_interval=dict(required=False, type='int'), ignore_ctime_enabled=dict(required=False, type='bool'), is_secure_control_connection_enabled=dict(required=False, type='bool'), offset_map_enable=dict(required=False, type='bool'), per_qtree_exclude_enable=dict(required=False, type='bool'), preferred_interface_role=dict(required=False, type='list'), restore_vm_cache_size=dict(required=False, type='int'), secondary_debug_filter=dict(required=False, type='str'), tcpnodelay=dict(required=False, type='bool'), tcpwinsize=dict(required=False, type='int') ) self.argument_spec.update(dict( vserver=dict(required=True, type='str') )) self.argument_spec.update(self.modifiable_options) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # API should be used for ONTAP 9.6 or higher, ZAPI for lower version self.restApi = OntapRestAPI(self.module) unsupported_rest_properties = ['abort_on_disk_error', 'backup_log_enable', 'data_port_range', 'debug_enable', 'debug_filter', 'dump_detailed_stats', 'dump_logical_find', 'fh_dir_retry_interval', 'fh_node_retry_interval', 'ignore_ctime_enabled', 'is_secure_control_connection_enabled', 'offset_map_enable', 'per_qtree_exclude_enable', 'preferred_interface_role', 'restore_vm_cache_size', 'secondary_debug_filter', 'tcpnodelay', 'tcpwinsize'] used_unsupported_rest_properties = [x for x in unsupported_rest_properties if x in self.parameters] self.use_rest, error = self.restApi.is_rest(used_unsupported_rest_properties) if error is not None: self.module.fail_json(msg=error) if not self.use_rest: if HAS_NETAPP_LIB is False: self.module.fail_json(msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver']) def get_ndmp_svm_uuid(self): """ Get a svm's UUID :return: uuid of the node """ params = {'svm.name': self.parameters['vserver']} api = "protocols/ndmp/svms" message, error = self.restApi.get(api, params) if error is not None: self.module.fail_json(msg=error) if 'records' in message and len(message['records']) == 0: self.module.fail_json(msg='Error fetching uuid for vserver %s: ' % (self.parameters['vserver'])) if len(message.keys()) == 0: error = "No information collected from %s: %s" % (api, repr(message)) self.module.fail_json(msg=error) elif 'records' not in message: error = "Unexpected response from %s: %s" % (api, repr(message)) self.module.fail_json(msg=error) return message['records'][0]['svm']['uuid'] def ndmp_get_iter(self, uuid=None): """ get current vserver ndmp attributes. :return: a dict of ndmp attributes. """ if self.use_rest: data = dict() params = {'fields': 'authentication_types,enabled'} api = '/protocols/ndmp/svms/' + uuid message, error = self.restApi.get(api, params) data['enable'] = message['enabled'] data['authtype'] = message['authentication_types'] if error: self.module.fail_json(msg=error) return data else: ndmp_get = netapp_utils.zapi.NaElement('ndmp-vserver-attributes-get-iter') query = netapp_utils.zapi.NaElement('query') ndmp_info = netapp_utils.zapi.NaElement('ndmp-vserver-attributes-info') ndmp_info.add_new_child('vserver', self.parameters['vserver']) query.add_child_elem(ndmp_info) ndmp_get.add_child_elem(query) ndmp_details = dict() try: result = self.server.invoke_successfully(ndmp_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching ndmp from %s: %s' % (self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0: ndmp_attributes = result.get_child_by_name('attributes-list').get_child_by_name('ndmp-vserver-attributes-info') self.get_ndmp_details(ndmp_details, ndmp_attributes) return ndmp_details def get_ndmp_details(self, ndmp_details, ndmp_attributes): """ :param ndmp_details: a dict of current ndmp. :param ndmp_attributes: ndmp returned from api call in xml format. :return: None """ for option in self.modifiable_options.keys(): option_type = self.modifiable_options[option]['type'] if option_type == 'bool': ndmp_details[option] = self.str_to_bool(ndmp_attributes.get_child_content(self.attribute_to_name(option))) elif option_type == 'int': ndmp_details[option] = int(ndmp_attributes.get_child_content(self.attribute_to_name(option))) elif option_type == 'list': child_list = ndmp_attributes.get_child_by_name(self.attribute_to_name(option)) values = [child.get_content() for child in child_list.get_children()] ndmp_details[option] = values else: ndmp_details[option] = ndmp_attributes.get_child_content(self.attribute_to_name(option)) def modify_ndmp(self, modify): """ :param modify: A list of attributes to modify :return: None """ if self.use_rest: ndmp = dict() uuid = self.get_ndmp_svm_uuid() if self.parameters.get('enable'): ndmp['enabled'] = self.parameters['enable'] if self.parameters.get('authtype'): ndmp['authentication_types'] = self.parameters['authtype'] api = "protocols/ndmp/svms/" + uuid message, error = self.restApi.patch(api, ndmp) if error: self.module.fail_json(msg=error) else: ndmp_modify = netapp_utils.zapi.NaElement('ndmp-vserver-attributes-modify') for attribute in modify: if attribute == 'authtype': authtypes = netapp_utils.zapi.NaElement('authtype') types = self.parameters['authtype'] for authtype in types: authtypes.add_new_child('ndmpd-authtypes', authtype) ndmp_modify.add_child_elem(authtypes) elif attribute == 'preferred_interface_role': preferred_interface_roles = netapp_utils.zapi.NaElement('preferred-interface-role') roles = self.parameters['preferred_interface_role'] for role in roles: preferred_interface_roles.add_new_child('netport-role', role) ndmp_modify.add_child_elem(preferred_interface_roles) else: ndmp_modify.add_new_child(self.attribute_to_name(attribute), str(self.parameters[attribute])) try: self.server.invoke_successfully(ndmp_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error modifying ndmp on %s: %s' % (self.parameters['vserver'], to_native(e)), exception=traceback.format_exc()) @staticmethod def attribute_to_name(attribute): return str.replace(attribute, '_', '-') @staticmethod def str_to_bool(s): if s == 'true': return True else: return False def apply(self): """Call modify operations.""" uuid = None if not self.use_rest: self.asup_log_for_cserver("na_ontap_ndmp") if self.use_rest: # we only have the svm name, we need to the the uuid for the svm uuid = self.get_ndmp_svm_uuid() current = self.ndmp_get_iter(uuid=uuid) modify = self.na_helper.get_modified_attributes(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if modify: self.modify_ndmp(modify) self.module.exit_json(changed=self.na_helper.changed) def asup_log_for_cserver(self, event_name): """ Fetch admin vserver for the given cluster Create and Autosupport log event with the given module name :param event_name: Name of the event log :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event(event_name, cserver)
class NetAppOntapVolumeAutosize(object): def __init__(self): self.use_rest = False # Volume_autosize returns KB and not B like Volume so values are shifted down 1 self._size_unit_map = dict( k=1, m=1024, g=1024 ** 2, t=1024 ** 3, ) self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( volume=dict(required=True, type="str"), mode=dict(required=False, choices=['grow', 'grow_shrink', 'off']), vserver=dict(required=True, type='str'), grow_threshold_percent=dict(required=False, type='int'), increment_size=dict(required=False, type='str'), maximum_size=dict(required=False, type='str'), minimum_size=dict(required=False, type='str'), reset=dict(required=False, type='bool'), shrink_threshold_percent=dict(required=False, type='int') )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True, mutually_exclusive=[ ['reset', 'maximum_size'], ['reset', 'increment_size'], ['reset', 'minimum_size'], ['reset', 'grow_threshold_percent'], ['reset', 'shrink_threshold_percent'], ['reset', 'mode'] ] ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # API should be used for ONTAP 9.6 or higher, ZAPI for lower version self.restApi = OntapRestAPI(self.module) if self.restApi.is_rest(): self.use_rest = True # increment size and reset are not supported with rest api if self.parameters.get('increment_size'): self.module.fail_json(msg="Rest API does not support increment size, please switch to ZAPI") if self.parameters.get('reset'): self.module.fail_json(msg="Rest API does not support reset, please switch to ZAPI") else: if HAS_NETAPP_LIB is False: self.module.fail_json(msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver']) def get_volume_autosize(self, uuid=None): """ Get volume_autosize information from the ONTAP system :return: """ if self.use_rest: params = {'fields': 'autosize'} api = 'storage/volumes/' + uuid message, error = self.restApi.get(api, params) if error is not None: self.module.fail_json(msg="%s" % error) return self._create_get_volume_return(message['autosize']) else: volume_autosize_info = netapp_utils.zapi.NaElement('volume-autosize-get') volume_autosize_info.add_new_child('volume', self.parameters['volume']) try: result = self.server.invoke_successfully(volume_autosize_info, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching volume autosize infor for %s : %s' % (self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) return self._create_get_volume_return(result) def _create_get_volume_return(self, results): """ Create a return value from volume-autosize-get info file :param results: :return: """ return_value = {} if self.use_rest: if 'mode' in results: return_value['mode'] = results['mode'] if 'grow_threshold' in results: return_value['grow_threshold_percent'] = results['grow_threshold'] if 'maximum' in results: return_value['maximum_size'] = results['maximum'] if 'minimum' in results: return_value['minimum_size'] = results['minimum'] if 'shrink_threshold' in results: return_value['shrink_threshold_percent'] = results['shrink_threshold'] else: if results.get_child_by_name('mode'): return_value['mode'] = results.get_child_content('mode') if results.get_child_by_name('grow-threshold-percent'): return_value['grow_threshold_percent'] = int(results.get_child_content('grow-threshold-percent')) if results.get_child_by_name('increment-size'): return_value['increment_size'] = results.get_child_content('increment-size') if results.get_child_by_name('maximum-size'): return_value['maximum_size'] = results.get_child_content('maximum-size') if results.get_child_by_name('minimum-size'): return_value['minimum_size'] = results.get_child_content('minimum-size') if results.get_child_by_name('shrink-threshold-percent'): return_value['shrink_threshold_percent'] = int(results.get_child_content('shrink-threshold-percent')) if return_value == {}: return_value = None return return_value def modify_volume_autosize(self, uuid=None): """ Modify a Volumes autosize :return: """ if self.use_rest: params = {} data = {} autosize = {} if self.parameters.get('mode'): autosize['mode'] = self.parameters['mode'] if self.parameters.get('grow_threshold_percent'): autosize['grow_threshold'] = self.parameters['grow_threshold_percent'] if self.parameters.get('maximum_size'): autosize['maximum'] = self.parameters['maximum_size'] if self.parameters.get('minimum_size'): autosize['minimum'] = self.parameters['minimum_size'] if self.parameters.get('shrink_threshold_percent'): autosize['shrink_threshold'] = self.parameters['shrink_threshold_percent'] data['autosize'] = autosize api = "storage/volumes/" + uuid message, error = self.restApi.patch(api, data, params) if error is not None: self.module.fail_json(msg="%s" % error) else: volume_autosize_info = netapp_utils.zapi.NaElement('volume-autosize-set') volume_autosize_info.add_new_child('volume', self.parameters['volume']) if self.parameters.get('mode'): volume_autosize_info.add_new_child('mode', self.parameters['mode']) if self.parameters.get('grow_threshold_percent'): volume_autosize_info.add_new_child('grow-threshold-percent', str(self.parameters['grow_threshold_percent'])) if self.parameters.get('increment_size'): volume_autosize_info.add_new_child('increment-size', self.parameters['increment_size']) if self.parameters.get('reset') is not None: volume_autosize_info.add_new_child('reset', str(self.parameters['reset'])) if self.parameters.get('maximum_size'): volume_autosize_info.add_new_child('maximum-size', self.parameters['maximum_size']) if self.parameters.get('minimum_size'): volume_autosize_info.add_new_child('minimum-size', self.parameters['minimum_size']) if self.parameters.get('shrink_threshold_percent'): volume_autosize_info.add_new_child('shrink-threshold-percent', str(self.parameters['shrink_threshold_percent'])) try: self.server.invoke_successfully(volume_autosize_info, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error modify volume autosize for %s: %s" % (self.parameters["volume"], to_native(error)), exception=traceback.format_exc()) def modify_to_kb(self, converted_parameters): """ Save a coverted parameter :param converted_parameters: Dic of all parameters :return: """ for attr in ['maximum_size', 'minimum_size', 'increment_size']: if converted_parameters.get(attr): if self.use_rest: converted_parameters[attr] = self.convert_to_byte(attr, converted_parameters) else: converted_parameters[attr] = str(self.convert_to_kb(attr, converted_parameters)) return converted_parameters def convert_to_kb(self, variable, converted_parameters): """ Convert a number 10m in to its correct KB size :param variable: the Parameter we are going to covert :param converted_parameters: Dic of all parameters :return: """ if converted_parameters.get(variable)[-1] not in ['k', 'm', 'g', 't']: self.module.fail_json(msg="%s must end with a k, m, g or t" % variable) return self._size_unit_map[converted_parameters.get(variable)[-1]] * int(converted_parameters.get(variable)[:-1]) def convert_to_byte(self, variable, converted_parameters): if converted_parameters.get(variable)[-1] not in ['k', 'm', 'g', 't']: self.module.fail_json(msg="%s must end with a k, m, g or t" % variable) return (self._size_unit_map[converted_parameters.get(variable)[-1]] * int(converted_parameters.get(variable)[:-1])) * 1024 def get_volume_uuid(self): """ Get a volume's UUID :return: uuid of the volume """ params = {'fields': '*', 'name': self.parameters['volume'], 'svm.name': self.parameters['vserver']} api = "storage/volumes" message, error = self.restApi.get(api, params) if error is not None: self.module.fail_json(msg="%s" % error) return message['records'][0]['uuid'] def apply(self): # TODO Logging for rest uuid = None if not self.use_rest: netapp_utils.ems_log_event("na_ontap_volume_autosize", self.server) if self.use_rest: # we only have the volume name, we need to the the uuid for the volume uuid = self.get_volume_uuid() current = self.get_volume_autosize(uuid=uuid) converted_parameters = copy.deepcopy(self.parameters) converted_parameters = self.modify_to_kb(converted_parameters) self.na_helper.get_modified_attributes(current, converted_parameters) if self.parameters.get('reset') is True: self.na_helper.changed = True if self.na_helper.changed: if not self.module.check_mode: self.modify_volume_autosize(uuid=uuid) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapSnapMirrorPolicy(object): """ Create, Modifies and Destroys a SnapMirror policy """ def __init__(self): """ Initialize the Ontap SnapMirror policy class """ self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, choices=['present', 'absent'], default='present'), vserver=dict(required=True, type='str'), policy_name=dict(required=True, type='str'), comment=dict(required=False, type='str'), policy_type=dict(required=False, type='str', choices=['vault', 'async_mirror', 'mirror_vault', 'strict_sync_mirror', 'sync_mirror']), tries=dict(required=False, type='str'), transfer_priority=dict(required=False, type='str', choices=['low', 'normal']), common_snapshot_schedule=dict(required=False, type='str'), ignore_atime=dict(required=False, type='bool'), is_network_compression_enabled=dict(required=False, type='bool'), owner=dict(required=False, type='str', choices=['cluster_admin', 'vserver_admin']), restart=dict(required=False, type='str', choices=['always', 'never', 'default']), )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) # set up variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # API should be used for ONTAP 9.6 or higher, Zapi for lower version self.restApi = OntapRestAPI(self.module) # some attributes are not supported in earlier REST implementation unsupported_rest_properties = ['owner', 'restart', 'transfer_priority', 'tries', 'ignore_atime', 'common_snapshot_schedule'] used_unsupported_rest_properties = [x for x in unsupported_rest_properties if x in self.parameters] self.use_rest, error = self.restApi.is_rest(used_unsupported_rest_properties) if error: self.module.fail_json(msg=error) if not self.use_rest: if HAS_NETAPP_LIB is False: self.module.fail_json(msg='The python NetApp-Lib module is required') else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver']) def get_snapmirror_policy(self): if self.use_rest: data = {'fields': 'uuid,name,svm.name,comment,network_compression_enabled,type', 'name': self.parameters['policy_name'], 'svm.name': self.parameters['vserver']} api = "snapmirror/policies" message, error = self.restApi.get(api, data) if error: self.module.fail_json(msg=error) if len(message['records']) != 0: return message['records'][0] return None else: return_value = None snapmirror_policy_get_iter = netapp_utils.zapi.NaElement('snapmirror-policy-get-iter') snapmirror_policy_info = netapp_utils.zapi.NaElement('snapmirror-policy-info') snapmirror_policy_info.add_new_child('policy-name', self.parameters['policy_name']) snapmirror_policy_info.add_new_child('vserver', self.parameters['vserver']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(snapmirror_policy_info) snapmirror_policy_get_iter.add_child_elem(query) try: result = self.server.invoke_successfully(snapmirror_policy_get_iter, True) if result.get_child_by_name('attributes-list'): snapmirror_policy_attributes = result['attributes-list']['snapmirror-policy-info'] return_value = { 'policy_name': snapmirror_policy_attributes['policy-name'], 'tries': snapmirror_policy_attributes['tries'], 'transfer_priority': snapmirror_policy_attributes['transfer-priority'], 'is_network_compression_enabled': self.na_helper.get_value_for_bool (True, snapmirror_policy_attributes['is-network-compression-enabled']), 'restart': snapmirror_policy_attributes['restart'], 'ignore_atime': self.na_helper.get_value_for_bool(True, snapmirror_policy_attributes['ignore-atime']), 'vserver': snapmirror_policy_attributes['vserver-name'], } if snapmirror_policy_attributes.get_child_content('comment') is not None: return_value['comment'] = snapmirror_policy_attributes['comment'] if snapmirror_policy_attributes.get_child_content('type') is not None: return_value['policy_type'] = snapmirror_policy_attributes['type'] except netapp_utils.zapi.NaApiError as error: if 'NetApp API failed. Reason - 13001:' in to_native(error): # Policy does not exist pass else: self.module.fail_json(msg='Error getting snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) return return_value def create_snapmirror_policy(self): """ Creates a new storage efficiency policy """ if self.use_rest: data = {'name': self.parameters['policy_name'], 'svm': {'name': self.parameters['vserver']}} if 'policy_type' in self.parameters.keys(): if 'async_mirror' in self.parameters['policy_type']: data['type'] = 'async' elif 'sync_mirror' in self.parameters['policy_type']: data['type'] = 'sync' data['sync_type'] = 'sync' else: self.module.fail_json(msg='policy type in REST only supports options async_mirror or sync_mirror, given %s' % (self.parameters['policy_type'])) data = self.create_snapmirror_policy_obj_for_rest(data, data['type']) api = "snapmirror/policies" message, error = self.restApi.post(api, data) if error: self.module.fail_json(msg=error) else: snapmirror_policy_obj = netapp_utils.zapi.NaElement("snapmirror-policy-create") snapmirror_policy_obj.add_new_child("policy-name", self.parameters['policy_name']) if 'policy_type' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("type", self.parameters['policy_type']) snapmirror_policy_obj = self.create_snapmirror_policy_obj(snapmirror_policy_obj) try: self.server.invoke_successfully(snapmirror_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) def create_snapmirror_policy_obj(self, snapmirror_policy_obj): if 'comment' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("comment", self.parameters['comment']) if 'common_snapshot_schedule' in self.parameters.keys() and 'sync_mirror' in self.parameters['policy_type']: snapmirror_policy_obj.add_new_child("common-snapshot-schedule", self.parameters['common_snapshot_schedule']) if 'ignore_atime' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("ignore-atime", self.na_helper.get_value_for_bool(False, self.parameters['ignore_atime'])) if 'is_network_compression_enabled' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("is-network-compression-enabled", self.na_helper.get_value_for_bool(False, self.parameters['is_network_compression_enabled'])) if 'owner' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("owner", self.parameters['owner']) if 'restart' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("restart", self.parameters['restart']) if 'transfer_priority' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("transfer-priority", self.parameters['transfer_priority']) if 'tries' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("tries", self.parameters['tries']) return snapmirror_policy_obj def create_snapmirror_policy_obj_for_rest(self, snapmirror_policy_obj, type=None): if 'comment' in self.parameters.keys(): snapmirror_policy_obj["comment"] = self.parameters['comment'] if 'is_network_compression_enabled' in self.parameters: if 'async' in type: snapmirror_policy_obj["network_compression_enabled"] = self.parameters['is_network_compression_enabled'] elif 'sync' in type: self.module.fail_json(msg="Input parameter network_compression_enabled is not valid for SnapMirror policy type sync") return snapmirror_policy_obj def delete_snapmirror_policy(self, uuid=None): """ Deletes a snapmirror policy """ if self.use_rest: api = "snapmirror/policies" data = {'uuid': uuid} message, error = self.restApi.delete(api, data) if error: self.module.fail_json(msg=error) else: snapmirror_policy_obj = netapp_utils.zapi.NaElement("snapmirror-policy-delete") snapmirror_policy_obj.add_new_child("policy-name", self.parameters['policy_name']) try: self.server.invoke_successfully(snapmirror_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) def modify_snapmirror_policy(self, uuid=None, type=None): """ Modifies a snapmirror policy """ if self.use_rest: api = "snapmirror/policies/" + uuid data = self.create_snapmirror_policy_obj_for_rest(dict(), type) message, error = self.restApi.patch(api, data) if error: self.module.fail_json(msg=error) else: snapmirror_policy_obj = netapp_utils.zapi.NaElement("snapmirror-policy-modify") snapmirror_policy_obj.add_new_child("policy-name", self.parameters['policy_name']) snapmirror_policy_obj = self.create_snapmirror_policy_obj(snapmirror_policy_obj) try: self.server.invoke_successfully(snapmirror_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) def asup_log_for_cserver(self): results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event("snapmirror_policy", cserver) def apply(self): uuid = None if not self.use_rest: self.asup_log_for_cserver() current, modify = self.get_snapmirror_policy(), None cd_action = self.na_helper.get_cd_action(current, self.parameters) if current and cd_action is None and self.parameters['state'] == 'present': modify = self.na_helper.get_modified_attributes(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_snapmirror_policy() elif cd_action == 'delete': if self.use_rest: uuid = current['uuid'] self.delete_snapmirror_policy(uuid) elif modify: if self.use_rest: uuid = current['uuid'] self.modify_snapmirror_policy(uuid, current['type']) else: self.modify_snapmirror_policy() self.module.exit_json(changed=self.na_helper.changed)