class AzureRMNetAppCapacityPool(AzureRMModuleBase): def __init__(self): self.module_arg_spec = dict( resource_group=dict(type='str', required=True), name=dict(type='str', required=True), account_name=dict(type='str', required=True), location=dict(type='str', required=False), state=dict(choices=['present', 'absent'], default='present', type='str'), size=dict(type='int', required=False, default=1), ) self.module = AnsibleModule( argument_spec=self.module_arg_spec, required_if=[ ('state', 'present', ['location']), ], supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # authentication - using CLI if HAS_AZURE_MGMT_NETAPP is False: self.module.fail_json(msg="the python Azure-mgmt-NetApp module is required") if HAS_AZURE_COMMON is False: self.module.fail_json(msg="the python azure-common module is required") self.client = get_client_from_cli_profile(AzureNetAppFilesManagementClient) super(AzureRMNetAppCapacityPool, self).__init__(derived_arg_spec=self.module_arg_spec, supports_check_mode=True) def get_azure_netapp_capacity_pool(self): """ Returns capacity pool object for an existing pool Return None if capacity pool does not exist """ try: capacity_pool_get = self.client.pools.get(self.parameters['resource_group'], self.parameters['account_name'], self.parameters['name']) except CloudError: # capacity pool does not exist return None return capacity_pool_get def create_azure_netapp_capacity_pool(self): """ Create a capacity pool for the given Azure NetApp Account :return: None """ capacity_pool_body = CapacityPool( location=self.parameters['location'], size=self.parameters['size'] * SIZE_POOL ) try: self.client.pools.create_or_update(body=capacity_pool_body, resource_group_name=self.parameters['resource_group'], account_name=self.parameters['account_name'], pool_name=self.parameters['name']) except CloudError as error: self.module.fail_json(msg='Error creating capacity pool %s for Azure NetApp account %s: %s' % (self.parameters['name'], self.parameters['account_name'], to_native(error)), exception=traceback.format_exc()) def delete_azure_netapp_capacity_pool(self): """ Delete a capacity pool for the given Azure NetApp Account :return: None """ try: self.client.pools.delete(resource_group_name=self.parameters['resource_group'], account_name=self.parameters['account_name'], pool_name=self.parameters['name']) except CloudError as error: self.module.fail_json(msg='Error deleting capacity pool %s for Azure NetApp account %s: %s' % (self.parameters['name'], self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def exec_module(self, **kwargs): current = self.get_azure_netapp_capacity_pool() 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_azure_netapp_capacity_pool() elif cd_action == 'delete': self.delete_azure_netapp_capacity_pool() self.module.exit_json(changed=self.na_helper.changed)
class AwsCvsNetappSnapshot(object): """ Contains methods to parse arguments, derive details of AWS_CVS objects and send requests to AWS CVS via the restApi """ def __init__(self): """ Parse arguments, setup state variables, check parameters and ensure request module is installed """ self.argument_spec = netapp_utils.aws_cvs_host_argument_spec() self.argument_spec.update( dict(state=dict(required=True, choices=['present', 'absent']), region=dict(required=True, type='str'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), fileSystemId=dict(required=False, type='str'))) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[ ('state', 'present', ['name', 'fileSystemId']), ], supports_check_mode=True) self.na_helper = NetAppModule() # set up state variables self.parameters = self.na_helper.set_parameters(self.module.params) # Calling generic AWSCVS restApi class self.restApi = AwsCvsRestAPI(self.module) # Checking for the parameters passed and create new parameters list self.data = {} for key in self.parameters.keys(): self.data[key] = self.parameters[key] def getSnapshotId(self, name): # Check if snapshot exists # Return snapshot Id If Snapshot is found, None otherwise list_snapshots, error = self.restApi.get('Snapshots') if error: self.module.fail_json(msg=error) for snapshot in list_snapshots: if snapshot['name'] == name: return snapshot['snapshotId'] return None def getfilesystemId(self): # Check given FileSystem is exists # Return fileSystemId is found, None otherwise list_filesystem, error = self.restApi.get('FileSystems') if error: self.module.fail_json(msg=error) for FileSystem in list_filesystem: if FileSystem['fileSystemId'] == self.parameters['fileSystemId']: return FileSystem['fileSystemId'] elif FileSystem['creationToken'] == self.parameters[ 'fileSystemId']: return FileSystem['fileSystemId'] return None def create_snapshot(self): # Create Snapshot api = 'Snapshots' response, error = self.restApi.post(api, self.data) if error: self.module.fail_json(msg=error) def rename_snapshot(self, snapshotId): # Rename Snapshot api = 'Snapshots/' + snapshotId response, error = self.restApi.put(api, self.data) if error: self.module.fail_json(msg=error) def delete_snapshot(self, snapshotId): # Delete Snapshot api = 'Snapshots/' + snapshotId data = None response, error = self.restApi.delete(api, self.data) if error: self.module.fail_json(msg=error) def apply(self): """ Perform pre-checks, call functions and exit """ self.snapshotId = self.getSnapshotId(self.data['name']) if self.snapshotId is None and 'fileSystemId' in self.data: self.fileSystemId = self.getfilesystemId() self.data['fileSystemId'] = self.fileSystemId if self.fileSystemId is None: self.module.fail_json( msg='Error: Specified filesystem id %s does not exist ' % self.data['fileSystemId']) cd_action = self.na_helper.get_cd_action(self.snapshotId, self.data) result_message = "" if self.na_helper.changed: if self.module.check_mode: # Skip changes result_message = "Check mode, skipping changes" else: if cd_action == "delete": self.delete_snapshot(self.snapshotId) result_message = "Snapshot Deleted" elif cd_action == "create": if 'from_name' in self.data: # If cd_action is create and from_name is given snapshotId = self.getSnapshotId(self.data['from_name']) if snapshotId is not None: # If resource pointed by from_name exists, rename the snapshot to name self.rename_snapshot(snapshotId) result_message = "Snapshot Updated" else: # If resource pointed by from_name does not exists, error out self.module.fail_json( msg="Resource does not exist : %s" % self.data['from_name']) else: self.create_snapshot() # If from_name is not defined, Create from scratch. result_message = "Snapshot Created" self.module.exit_json(changed=self.na_helper.changed, msg=result_message)
class NetAppONTAPFirewallPolicy(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'), allow_list=dict(required=False, type="list"), policy=dict(required=True, type='str'), service=dict(required=True, type='str', choices=[ 'dns', 'http', 'https', 'ndmp', 'ndmps', 'ntp', 'rsh', 'snmp', 'ssh', 'telnet' ]), vserver=dict(required=True, type="str"), enable=dict(required=False, type="str", choices=['enable', 'disable'], default='enable'), logging=dict(required=False, type="str", choices=["enable", 'disable'], default='disable'), node=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) 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 create_firewall_policy(self): """ Create a firewall policy :return: Nothing """ net_firewall_policy_obj = netapp_utils.zapi.NaElement( "net-firewall-policy-create") net_firewall_policy_obj = self.create_modify_policy( net_firewall_policy_obj) try: self.server.invoke_successfully(net_firewall_policy_obj, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error creating Firewall Policy: %s" % (to_native(error)), exception=traceback.format_exc()) def destroy_firewall_policy(self): """ Destroy a Firewall Policy :return: None """ net_firewall_policy_obj = netapp_utils.zapi.NaElement( "net-firewall-policy-destroy") net_firewall_policy_obj.add_new_child('policy', self.parameters['policy']) net_firewall_policy_obj.add_new_child('service', self.parameters['service']) net_firewall_policy_obj.add_new_child('vserver', self.parameters['vserver']) try: self.server.invoke_successfully(net_firewall_policy_obj, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error destroying Firewall Policy: %s" % (to_native(error)), exception=traceback.format_exc()) def get_firewall_policy(self): """ Get a firewall policy :return: returns a firewall policy object, or returns False if there are none """ net_firewall_policy_obj = netapp_utils.zapi.NaElement( "net-firewall-policy-get-iter") net_firewall_policy_info = netapp_utils.zapi.NaElement( "net-firewall-policy-info") query = netapp_utils.zapi.NaElement('query') net_firewall_policy_info.add_new_child('policy', self.parameters['policy']) query.add_child_elem(net_firewall_policy_info) net_firewall_policy_obj.add_child_elem(query) result = self.server.invoke_successfully(net_firewall_policy_obj, True) if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: return result return False def modify_firewall_policy(self): """ Modify a firewall Policy :return: none """ net_firewall_policy_obj = netapp_utils.zapi.NaElement( "net-firewall-policy-modify") net_firewall_policy_obj = self.create_modify_policy( net_firewall_policy_obj) try: self.server.invoke_successfully(net_firewall_policy_obj, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error modifying Firewall Policy: %s" % (to_native(error)), exception=traceback.format_exc()) def create_modify_policy(self, net_firewall_policy_obj): """ Set up the parameters for creating or modifying a policy :param net_firewall_policy_obj: The Firewall policy to modify :return: """ net_firewall_policy_obj.add_new_child('policy', self.parameters['policy']) net_firewall_policy_obj.add_new_child('service', self.parameters['service']) net_firewall_policy_obj.add_new_child('vserver', self.parameters['vserver']) allow_ip_list = netapp_utils.zapi.NaElement("allow-list") for each in self.parameters['allow_list']: net_firewall_policy_ip = netapp_utils.zapi.NaElement("ip-and-mask") net_firewall_policy_ip.set_content(each) allow_ip_list.add_child_elem(net_firewall_policy_ip) net_firewall_policy_obj.add_child_elem(allow_ip_list) return net_firewall_policy_obj def get_firewall_config(self): """ Get a firewall configuration :return: the firewall configuration """ net_firewall_config_obj = netapp_utils.zapi.NaElement( "net-firewall-config-get") net_firewall_config_obj.add_new_child('node-name', self.parameters['node']) try: result = self.server.invoke_successfully(net_firewall_config_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error getting Firewall Configuration: %s" % (to_native(error)), exception=traceback.format_exc()) return result def check_policy(self, policy): """ Check to see if a policy has been changed or not :param policy: policy to check :return: True if the policy has changed, False if there are no changes """ changed = False attributes_list = policy.get_child_by_name('attributes-list') policy_info = attributes_list.get_child_by_name( 'net-firewall-policy-info') allow_list = policy_info.get_child_by_name('allow-list') for each in allow_list.get_children(): if each.get_content() not in self.parameters['allow_list']: changed = True if self.parameters['service'] != policy_info.get_child_by_name( 'service').get_content(): changed = True if self.parameters['policy'] != policy_info.get_child_by_name( 'policy').get_content(): changed = True return changed def modify_firewall_config(self): """ Modify the configuration of a firewall :return: none """ net_firewall_config_obj = netapp_utils.zapi.NaElement( "net-firewall-config-modify") net_firewall_config_obj.add_new_child('node-name', self.parameters['node']) net_firewall_config_obj.add_new_child('is-enabled', self.parameters['enable']) net_firewall_config_obj.add_new_child('is-logging', self.parameters['logging']) try: self.server.invoke_successfully(net_firewall_config_obj, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error modifying Firewall Config: %s" % (to_native(error)), exception=traceback.format_exc()) def check_config(self, config): """ check to see if a firewall configuration has changed or not :param config: The configuration to check :return: true if it has changed, false if it has not """ changed = False attributes_list = config.get_child_by_name('attributes') firewall_info = attributes_list.get_child_by_name( 'net-firewall-config-info') enable = firewall_info.get_child_by_name('is-enabled') logging = firewall_info.get_child_by_name('is-logging') if self.parameters['enable'] == 'enable': is_enable = "true" else: is_enable = "false" if enable != is_enable: changed = True if self.parameters['logging'] == 'logging': is_logging = "true" else: is_logging = "false" if logging != is_logging: changed = True return changed def apply(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_firewall_policy", cserver) changed = False if self.parameters['state'] == 'present': policy = self.get_firewall_policy() if not policy: self.create_firewall_policy() if not self.check_config(self.get_firewall_config()): self.modify_firewall_config() changed = True else: if self.check_policy(policy): self.modify_firewall_policy() changed = True if not self.check_config(self.get_firewall_config()): self.modify_firewall_config() changed = True else: if self.get_firewall_policy(): self.destroy_firewall_policy() if not self.check_config(self.get_firewall_config()): self.modify_firewall_config() changed = True else: if not self.check_config(self.get_firewall_config()): self.modify_firewall_config() changed = True self.module.exit_json(changed=changed)
class NetAppONTAPMotd(object): def __init__(self): argument_spec = netapp_utils.na_ontap_host_argument_spec() argument_spec.update( dict(state=dict(required=False, default='present', choices=['present', 'absent']), vserver=dict(required=True, type='str'), message=dict(default='', type='str'), show_cluster_motd=dict(default=True, type='bool'))) self.module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def motd_get_iter(self): """ Compose NaElement object to query current motd :return: NaElement object for vserver-motd-get-iter """ 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('is-cluster-message-enabled', str(self.parameters['show_cluster_motd'])) motd_info.add_new_child('vserver', self.parameters['vserver']) query.add_child_elem(motd_info) motd_get_iter.add_child_elem(query) return motd_get_iter def motd_get(self): """ Get current motd :return: Dictionary of current motd details if query successful, else None """ motd_get_iter = self.motd_get_iter() motd_result = dict() 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') motd_result['message'] = motd_info.get_child_content('message') motd_result['message'] = str(motd_result['message']).rstrip() motd_result[ 'show_cluster_motd'] = True if motd_info.get_child_content( 'is-cluster-message-enabled') == 'true' else False motd_result['vserver'] = motd_info.get_child_content('vserver') return motd_result return None def modify_motd(self): motd_create = netapp_utils.zapi.NaElement('vserver-motd-modify-iter') motd_create.add_new_child('message', self.parameters['message']) motd_create.add_new_child( 'is-cluster-message-enabled', 'true' if self.parameters['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 creating motd: %s" % (to_native(err)), exception=traceback.format_exc()) return motd_create def apply(self): """ Applies action from playbook """ netapp_utils.ems_log_event("na_ontap_motd", self.server) current = self.motd_get() if self.parameters['state'] == 'present' and self.parameters[ 'message'] == "": self.module.fail_json(msg="message parameter cannot be empty") if self.parameters['state'] == 'absent': # Just make sure it is empty self.parameters['message'] = '' if current['message'] == 'None': current = None cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': self.na_helper.get_modified_attributes(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: self.modify_motd() self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPNVMe(object): """ Class with NVMe service methods """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( state=dict(type='str', default='present', choices=['absent', 'present']), vserver=dict(type='str', required=True), status_admin=dict(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) 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_nvme(self): """ Get current nvme details :return: dict if nvme exists, None otherwise """ nvme_get = netapp_utils.zapi.NaElement('nvme-get-iter') query = { 'query': { 'nvme-target-service-info': { 'vserver': self.parameters['vserver'] } } } nvme_get.translate_struct(query) try: result = self.server.invoke_successfully(nvme_get, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching nvme info: %s' % to_native(error)) 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') nvme_info = attributes_list.get_child_by_name('nvme-target-service-info') return_value = {'status_admin': nvme_info.get_child_content('is-available')} return return_value return None def create_nvme(self): """ Create NVMe service """ nvme_create = netapp_utils.zapi.NaElement('nvme-create') if self.parameters.get('status_admin') is not None: options = {'is-available': self.parameters['status_admin']} nvme_create.translate_struct(options) try: self.server.invoke_successfully(nvme_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating nvme for vserver %s: %s' % (self.parameters['vserver'], to_native(error))) def delete_nvme(self): """ Delete NVMe service """ nvme_delete = netapp_utils.zapi.NaElement('nvme-delete') try: self.server.invoke_successfully(nvme_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting nvme for vserver %s: %s' % (self.parameters['vserver'], to_native(error))) def modify_nvme(self, status=None): """ Modify NVMe service """ if status is None: status = self.parameters['status_admin'] options = {'is-available': status} nvme_modify = netapp_utils.zapi.NaElement('nvme-modify') nvme_modify.translate_struct(options) try: self.server.invoke_successfully(nvme_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying nvme for vserver %s: %s' % (self.parameters['vserver'], to_native(error))) def apply(self): """ Apply action to NVMe service """ netapp_utils.ems_log_event("na_ontap_nvme", self.server) current = self.get_nvme() cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.parameters.get('status_admin') is not None: self.parameters['status_admin'] = self.na_helper.get_value_for_bool(False, self.parameters['status_admin']) 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_nvme() elif cd_action == 'delete': # NVMe status_admin needs to be down before deleting it self.modify_nvme('false') self.delete_nvme() elif modify: self.modify_nvme() self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPQuotas(object): '''Class with quotas methods''' 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'), vserver=dict(required=True, type='str'), volume=dict(required=True, type='str'), quota_target=dict(required=True, type='str'), qtree=dict(required=False, type='str', default=""), type=dict(required=True, type='str', choices=['user', 'group', 'tree']), policy=dict(required=False, type='str'), set_quota_status=dict(required=False, type='bool'), file_limit=dict(required=False, type='str', default='-'), disk_limit=dict(required=False, type='str', default='-'), threshold=dict(required=False, type='str', default='-'))) 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 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_quota_status(self): """ Return details about the quota status :param: name : volume name :return: status of the quota. None if not found. :rtype: dict """ quota_status_get = netapp_utils.zapi.NaElement('quota-status') quota_status_get.translate_struct( {'volume': self.parameters['volume']}) try: result = self.server.invoke_successfully(quota_status_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching quotas status info: %s' % to_native(error), exception=traceback.format_exc()) if result: return result['status'] return None def get_quotas(self): """ Get quota details :return: name of volume if quota exists, None otherwise """ quota_get = netapp_utils.zapi.NaElement('quota-list-entries-iter') query = { 'query': { 'quota-entry': { 'volume': self.parameters['volume'], 'quota-target': self.parameters['quota_target'], 'quota-type': self.parameters['type'] } } } quota_get.translate_struct(query) if self.parameters.get('policy'): quota_get['query']['quota-entry'].add_new_child( 'policy', self.parameters['policy']) try: result = self.server.invoke_successfully(quota_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching quotas 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')) >= 1: return_values = { 'volume': result['attributes-list']['quota-entry']['volume'], 'file_limit': result['attributes-list']['quota-entry']['file-limit'], 'disk_limit': result['attributes-list']['quota-entry']['disk-limit'], 'threshold': result['attributes-list']['quota-entry']['threshold'] } return return_values return None def quota_entry_set(self): """ Adds a quota entry """ options = { 'volume': self.parameters['volume'], 'quota-target': self.parameters['quota_target'], 'quota-type': self.parameters['type'], 'qtree': self.parameters['qtree'], 'file-limit': self.parameters['file_limit'], 'disk-limit': self.parameters['disk_limit'], 'threshold': self.parameters['threshold'] } if self.parameters.get('policy'): options['policy'] = self.parameters['policy'] set_entry = netapp_utils.zapi.NaElement.create_node_with_children( 'quota-set-entry', **options) try: self.server.invoke_successfully(set_entry, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error adding/modifying quota entry %s: %s' % (self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def quota_entry_delete(self): """ Deletes a quota entry """ options = { 'volume': self.parameters['volume'], 'quota-target': self.parameters['quota_target'], 'quota-type': self.parameters['type'], 'qtree': self.parameters['qtree'] } set_entry = netapp_utils.zapi.NaElement.create_node_with_children( 'quota-delete-entry', **options) if self.parameters.get('policy'): set_entry.add_new_child('policy', self.parameters['policy']) try: self.server.invoke_successfully(set_entry, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting quota entry %s: %s' % (self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def quota_entry_modify(self, modify_attrs): """ Modifies a quota entry """ options = { 'volume': self.parameters['volume'], 'quota-target': self.parameters['quota_target'], 'quota-type': self.parameters['type'], 'qtree': self.parameters['qtree'] } options.update(modify_attrs) if self.parameters.get('policy'): options['policy'] = str(self.parameters['policy']) modify_entry = netapp_utils.zapi.NaElement.create_node_with_children( 'quota-modify-entry', **options) try: self.server.invoke_successfully(modify_entry, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying quota entry %s: %s' % (self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def on_or_off_quota(self, status): """ on or off quota """ quota = netapp_utils.zapi.NaElement.create_node_with_children( status, **{'volume': self.parameters['volume']}) try: self.server.invoke_successfully(quota, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error setting %s for %s: %s' % (status, self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def apply(self): """ Apply action to quotas """ netapp_utils.ems_log_event("na_ontap_quotas", self.server) modify_quota_status = None modify_quota = None current = self.get_quotas() if 'set_quota_status' in self.parameters: quota_status = self.get_quota_status() if quota_status is not None: quota_status_action = self.na_helper.get_modified_attributes( { 'set_quota_status': True if quota_status == 'on' else False }, self.parameters) if quota_status_action: modify_quota_status = 'quota-on' if quota_status_action[ 'set_quota_status'] else 'quota-off' cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None: modify_quota = 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.quota_entry_set() elif cd_action == 'delete': self.quota_entry_delete() elif modify_quota is not None: for key in list(modify_quota): modify_quota[key.replace("_", "-")] = modify_quota.pop(key) self.quota_entry_modify(modify_quota) if modify_quota_status is not None: self.on_or_off_quota(modify_quota_status) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapPorts(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'), vserver=dict(required=False, type='str'), names=dict(required=True, type='list'), resource_name=dict(required=True, type='str'), resource_type=dict(required=True, type='str', choices=['broadcast_domain', 'portset']), ipspace=dict(required=False, type='str'), portset_type=dict(required=False, type='str', choices=['fcp', 'iscsi', 'mixed']), )) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[ ('resource_type', 'portset', ['vserver']), ], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: if self.parameters['resource_type'] == 'broadcast_domain': self.server = netapp_utils.setup_na_ontap_zapi( module=self.module) elif self.parameters['resource_type'] == 'portset': self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def add_broadcast_domain_ports(self, ports): """ Add broadcast domain ports :param: ports to be added. """ domain_obj = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-add-ports') domain_obj.add_new_child("broadcast-domain", self.parameters['resource_name']) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) ports_obj = netapp_utils.zapi.NaElement('ports') domain_obj.add_child_elem(ports_obj) for port in ports: ports_obj.add_new_child('net-qualified-port-name', port) try: self.server.invoke_successfully(domain_obj, True) return True except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error adding port for broadcast domain %s: %s' % (self.parameters['resource_name'], to_native(error)), exception=traceback.format_exc()) def remove_broadcast_domain_ports(self, ports): """ Deletes broadcast domain ports :param: ports to be removed. """ domain_obj = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-remove-ports') domain_obj.add_new_child("broadcast-domain", self.parameters['resource_name']) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) ports_obj = netapp_utils.zapi.NaElement('ports') domain_obj.add_child_elem(ports_obj) for port in ports: ports_obj.add_new_child('net-qualified-port-name', port) try: self.server.invoke_successfully(domain_obj, True) return True except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error removing port for broadcast domain %s: %s' % (self.parameters['resource_name'], to_native(error)), exception=traceback.format_exc()) def get_broadcast_domain_ports(self): """ Return details about the broadcast domain ports. :return: Details about the broadcast domain ports. [] if not found. :rtype: list """ domain_get_iter = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-get-iter') broadcast_domain_info = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-info') broadcast_domain_info.add_new_child('broadcast-domain', self.parameters['resource_name']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(broadcast_domain_info) domain_get_iter.add_child_elem(query) result = self.server.invoke_successfully(domain_get_iter, True) ports = [] if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: domain_info = result.get_child_by_name( 'attributes-list').get_child_by_name( 'net-port-broadcast-domain-info') domain_ports = domain_info.get_child_by_name('ports') if domain_ports is not None: ports = [ port.get_child_content('port') for port in domain_ports.get_children() ] return ports def remove_portset_ports(self, port): """ Removes all existing ports from portset :return: None """ options = { 'portset-name': self.parameters['resource_name'], 'portset-port-name': port.strip() } portset_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'portset-remove', **options) try: self.server.invoke_successfully(portset_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error removing port in portset %s: %s' % (self.parameters['resource_name'], to_native(error)), exception=traceback.format_exc()) def add_portset_ports(self, port): """ Add the list of ports to portset :return: None """ options = { 'portset-name': self.parameters['resource_name'], 'portset-port-name': port.strip() } portset_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'portset-add', **options) try: self.server.invoke_successfully(portset_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error adding port in portset %s: %s' % (self.parameters['resource_name'], to_native(error)), exception=traceback.format_exc()) def portset_get_iter(self): """ Compose NaElement object to query current portset using vserver, portset-name and portset-type parameters :return: NaElement object for portset-get-iter with query """ portset_get = netapp_utils.zapi.NaElement('portset-get-iter') query = netapp_utils.zapi.NaElement('query') portset_info = netapp_utils.zapi.NaElement('portset-info') portset_info.add_new_child('vserver', self.parameters['vserver']) portset_info.add_new_child('portset-name', self.parameters['resource_name']) if self.parameters.get('portset_type'): portset_info.add_new_child('portset-type', self.parameters['portset_type']) query.add_child_elem(portset_info) portset_get.add_child_elem(query) return portset_get def portset_get(self): """ Get current portset info :return: List of current ports if query successful, else return [] """ portset_get_iter = self.portset_get_iter() result, ports = None, [] try: result = self.server.invoke_successfully(portset_get_iter, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error fetching portset %s: %s' % (self.parameters['resource_name'], to_native(error)), exception=traceback.format_exc()) # return portset details if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) > 0: portset_get_info = result.get_child_by_name( 'attributes-list').get_child_by_name('portset-info') if int(portset_get_info.get_child_content( 'portset-port-total')) > 0: port_info = portset_get_info.get_child_by_name( 'portset-port-info') ports = [ port.get_content() for port in port_info.get_children() ] return ports def modify_broadcast_domain_ports(self): """ compare current and desire ports. Call add or remove ports methods if needed. :return: None. """ current_ports = self.get_broadcast_domain_ports() cd_ports = self.parameters['names'] if self.parameters['state'] == 'present': ports_to_add = [ port for port in cd_ports if port not in current_ports ] if len(ports_to_add) > 0: self.add_broadcast_domain_ports(ports_to_add) self.na_helper.changed = True if self.parameters['state'] == 'absent': ports_to_remove = [ port for port in cd_ports if port in current_ports ] if len(ports_to_remove) > 0: self.remove_broadcast_domain_ports(ports_to_remove) self.na_helper.changed = True def modify_portset_ports(self): current_ports = self.portset_get() cd_ports = self.parameters['names'] if self.parameters['state'] == 'present': ports_to_add = [ port for port in cd_ports if port not in current_ports ] if len(ports_to_add) > 0: for port in ports_to_add: self.add_portset_ports(port) self.na_helper.changed = True if self.parameters['state'] == 'absent': ports_to_remove = [ port for port in cd_ports if port in current_ports ] if len(ports_to_remove) > 0: for port in ports_to_remove: self.remove_portset_ports(port) self.na_helper.changed = True def apply(self): self.asup_log_for_cserver("na_ontap_ports") if self.parameters['resource_type'] == 'broadcast_domain': self.modify_broadcast_domain_ports() elif self.parameters['resource_type'] == 'portset': self.modify_portset_ports() 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 NetAppONTAPClusterPeer(object): """ Class with cluster peer methods """ 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'), source_intercluster_lif=dict(required=False, type='str'), dest_intercluster_lif=dict(required=False, type='str'), passphrase=dict(required=False, type='str', no_log=True), dest_hostname=dict(required=True, type='str'), dest_username=dict(required=False, type='str'), dest_password=dict(required=False, type='str', no_log=True), source_cluster_name=dict(required=False, type='str'), dest_cluster_name=dict(required=False, type='str'))) self.module = AnsibleModule(argument_spec=self.argument_spec, required_together=[[ 'source_intercluster_lif', 'dest_intercluster_lif', 'passphrase' ]], required_if=[('state', 'absent', [ 'source_cluster_name', 'dest_cluster_name' ])], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) 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) # set destination server connection self.module.params['hostname'] = self.parameters['dest_hostname'] if self.parameters.get('dest_username'): self.module.params['username'] = self.parameters[ 'dest_username'] if self.parameters.get('dest_password'): self.module.params['password'] = self.parameters[ 'dest_password'] self.dest_server = netapp_utils.setup_na_ontap_zapi( module=self.module) def cluster_peer_get_iter(self, cluster): """ Compose NaElement object to query current source cluster using peer-cluster-name and peer-addresses parameters :param cluster: type of cluster (source or destination) :return: NaElement object for cluster-get-iter with query """ cluster_peer_get = netapp_utils.zapi.NaElement('cluster-peer-get-iter') query = netapp_utils.zapi.NaElement('query') cluster_peer_info = netapp_utils.zapi.NaElement('cluster-peer-info') if cluster == 'source': peer_lif, peer_cluster = 'dest_intercluster_lif', 'dest_cluster_name' else: peer_lif, peer_cluster = 'source_intercluster_lif', 'source_cluster_name' peer_addresses = netapp_utils.zapi.NaElement('peer-addresses') if self.parameters.get(peer_lif): peer_addresses.add_new_child('remote-inet-address', self.parameters[peer_lif]) cluster_peer_info.add_child_elem(peer_addresses) if self.parameters.get(peer_cluster): cluster_peer_info.add_new_child('cluster-name', self.parameters[peer_cluster]) query.add_child_elem(cluster_peer_info) cluster_peer_get.add_child_elem(query) return cluster_peer_get def cluster_peer_get(self, cluster): """ Get current cluster peer info :param cluster: type of cluster (source or destination) :return: Dictionary of current cluster peer details if query successful, else return None """ cluster_peer_get_iter = self.cluster_peer_get_iter(cluster) cluster_info = dict() if cluster == 'source': server = self.server else: server = self.dest_server try: result = server.invoke_successfully(cluster_peer_get_iter, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error fetching cluster peer %s: %s' % (self.parameters['dest_cluster_name'], to_native(error)), exception=traceback.format_exc()) # return cluster peer details if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) >= 1: cluster_peer_info = result.get_child_by_name( 'attributes-list').get_child_by_name('cluster-peer-info') cluster_info['cluster_name'] = cluster_peer_info.get_child_content( 'cluster-name') peers = cluster_peer_info.get_child_by_name('peer-addresses') cluster_info['peer-addresses'] = [ peer.get_content() for peer in peers.get_children() ] return cluster_info return None def cluster_peer_delete(self, cluster): """ Delete a cluster peer on source or destination For source cluster, peer cluster-name = destination cluster name and vice-versa :param cluster: type of cluster (source or destination) :return: """ if cluster == 'source': server, peer_cluster_name = self.server, self.parameters[ 'dest_cluster_name'] else: server, peer_cluster_name = self.dest_server, self.parameters[ 'source_cluster_name'] cluster_peer_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'cluster-peer-delete', **{'cluster-name': peer_cluster_name}) try: server.invoke_successfully(cluster_peer_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting cluster peer %s: %s' % (peer_cluster_name, to_native(error)), exception=traceback.format_exc()) def cluster_peer_create(self, cluster): """ Create a cluster peer on source or destination For source cluster, peer address = destination inter-cluster LIF and vice-versa :param cluster: type of cluster (source or destination) :return: None """ cluster_peer_create = netapp_utils.zapi.NaElement.create_node_with_children( 'cluster-peer-create', **{'passphrase': self.parameters['passphrase']}) peer_addresses = netapp_utils.zapi.NaElement('peer-addresses') if cluster == 'source': server, peer_address = self.server, self.parameters[ 'dest_intercluster_lif'] else: server, peer_address = self.dest_server, self.parameters[ 'source_intercluster_lif'] peer_addresses.add_new_child('remote-inet-address', peer_address) cluster_peer_create.add_child_elem(peer_addresses) try: server.invoke_successfully(cluster_peer_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating cluster peer %s: %s' % (peer_address, to_native(error)), exception=traceback.format_exc()) def apply(self): """ Apply action to cluster peer :return: None """ source = self.cluster_peer_get('source') destination = self.cluster_peer_get('destination') source_action = self.na_helper.get_cd_action(source, self.parameters) destination_action = self.na_helper.get_cd_action( destination, self.parameters) self.na_helper.changed = False # create only if expected cluster peer relation is not present on both source and destination clusters if source_action == 'create' and destination_action == 'create': self.cluster_peer_create('source') self.cluster_peer_create('destination') self.na_helper.changed = True # delete peer relation in cluster where relation is present else: if source_action == 'delete': self.cluster_peer_delete('source') self.na_helper.changed = True if destination_action == 'delete': self.cluster_peer_delete('destination') self.na_helper.changed = True self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapQTree(object): '''Class with qtree operations''' 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'), from_name=dict(required=False, type='str'), flexvol_name=dict(type='str'), vserver=dict(required=True, type='str'), export_policy=dict(required=False, type='str'), security_style=dict(required=False, choices=['unix', 'ntfs', 'mixed']), oplocks=dict(required=False, choices=['enabled', 'disabled']), unix_permissions=dict(required=False, type='str'), )) 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) 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'] 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 = False 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'], 'unix_permissions': result['attributes-list']['qtree-info']['mode'], 'oplocks': result['attributes-list']['qtree-info']['oplocks'], 'security_style': result['attributes-list']['qtree-info']['security-style'] } return return_q def create_qtree(self): """ Create a qtree """ 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): """ Delete a qtree """ path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name']) qtree_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'qtree-delete', **{'qtree': path}) 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): """ Rename a qtree """ 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): """ Modify a qtree """ 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''' # changed = False # rename_qtree = False # modified_qtree = None changed, rename_qtree, modified_qtree = False, False, None netapp_utils.ems_log_event("na_ontap_qtree", self.server) qtree_detail = self.get_qtree() if qtree_detail: # delete or modify qtree if self.parameters['state'] == 'absent': # delete changed = True else: modified_qtree = self.na_helper.get_modified_attributes( qtree_detail, self.parameters) if modified_qtree is not None: changed = True elif self.parameters['state'] == 'present': # create or rename qtree if self.parameters.get('from_name'): if self.get_qtree(self.parameters['from_name']) is None: self.module.fail_json( msg="Error renaming qtree %s: does not exists" % self.parameters['from_name']) else: changed = True rename_qtree = True else: changed = True if changed: if self.module.check_mode: pass else: if self.parameters['state'] == 'present': if rename_qtree: self.rename_qtree() elif modified_qtree: self.modify_qtree() else: self.create_qtree() elif self.parameters['state'] == 'absent': self.delete_qtree() self.module.exit_json(changed=changed)
class NetAppOntapAdaptiveQosPolicyGroup(object): """ Create, delete, modify and rename a policy group. """ def __init__(self): """ Initialize the Ontap qos policy group class. """ 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'), vserver=dict(required=True, type='str'), absolute_min_iops=dict(required=False, type='str'), expected_iops=dict(required=False, type='str'), peak_iops=dict(required=False, type='str'), peak_iops_allocation=dict( choices=['allocated_space', 'used_space'], default='used_space'), force=dict(required=False, type='bool', default=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) 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_policy_group(self, policy_group_name=None): """ Return details of a policy group. :param policy_group_name: policy group name :return: policy group details. :rtype: dict. """ if policy_group_name is None: policy_group_name = self.parameters['name'] policy_group_get_iter = netapp_utils.zapi.NaElement( 'qos-adaptive-policy-group-get-iter') policy_group_info = netapp_utils.zapi.NaElement( 'qos-adaptive-policy-group-info') policy_group_info.add_new_child('policy-group', policy_group_name) policy_group_info.add_new_child('vserver', self.parameters['vserver']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(policy_group_info) policy_group_get_iter.add_child_elem(query) result = self.server.invoke_successfully(policy_group_get_iter, True) policy_group_detail = None if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) == 1: policy_info = result.get_child_by_name( 'attributes-list').get_child_by_name( 'qos-adaptive-policy-group-info') policy_group_detail = { 'name': policy_info.get_child_content('policy-group'), 'vserver': policy_info.get_child_content('vserver'), 'absolute_min_iops': policy_info.get_child_content('absolute-min-iops'), 'expected_iops': policy_info.get_child_content('expected-iops'), 'peak_iops': policy_info.get_child_content('peak-iops'), 'peak_iops_allocation': policy_info.get_child_content('peak-iops-allocation') } return policy_group_detail def create_policy_group(self): """ create a policy group name. """ policy_group = netapp_utils.zapi.NaElement( 'qos-adaptive-policy-group-create') policy_group.add_new_child('policy-group', self.parameters['name']) policy_group.add_new_child('vserver', self.parameters['vserver']) if self.parameters.get('absolute_min_iops'): policy_group.add_new_child('absolute-min-iops', self.parameters['absolute_min_iops']) if self.parameters.get('expected_iops'): policy_group.add_new_child('expected-iops', self.parameters['expected_iops']) if self.parameters.get('peak_iops'): policy_group.add_new_child('peak-iops', self.parameters['peak_iops']) if self.parameters.get('peak_iops_allocation'): policy_group.add_new_child('peak-iops-allocation', self.parameters['peak_iops_allocation']) try: self.server.invoke_successfully(policy_group, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating adaptive qos policy group %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_policy_group(self, policy_group=None): """ delete an existing policy group. :param policy_group: policy group name. """ if policy_group is None: policy_group = self.parameters['name'] policy_group_obj = netapp_utils.zapi.NaElement( 'qos-adaptive-policy-group-delete') policy_group_obj.add_new_child('policy-group', policy_group) if self.parameters.get('force'): policy_group_obj.add_new_child('force', str(self.parameters['force'])) try: self.server.invoke_successfully(policy_group_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting adaptive qos policy group %s: %s' % (policy_group, to_native(error)), exception=traceback.format_exc()) def modify_policy_group(self): """ Modify policy group. """ policy_group_obj = netapp_utils.zapi.NaElement( 'qos-adaptive-policy-group-modify') policy_group_obj.add_new_child('policy-group', self.parameters['name']) if self.parameters.get('absolute_min_iops'): policy_group_obj.add_new_child( 'absolute-min-iops', self.parameters['absolute_min_iops']) if self.parameters.get('expected_iops'): policy_group_obj.add_new_child('expected-iops', self.parameters['expected_iops']) if self.parameters.get('peak_iops'): policy_group_obj.add_new_child('peak-iops', self.parameters['peak_iops']) if self.parameters.get('peak_iops_allocation'): policy_group_obj.add_new_child( 'peak-iops-allocation', self.parameters['peak_iops_allocation']) try: self.server.invoke_successfully(policy_group_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying adaptive qos policy group %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_policy_group(self): """ Rename policy group name. """ rename_obj = netapp_utils.zapi.NaElement( 'qos-adaptive-policy-group-rename') rename_obj.add_new_child('new-name', self.parameters['name']) rename_obj.add_new_child('policy-group-name', self.parameters['from_name']) try: self.server.invoke_successfully(rename_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error renaming adaptive qos policy group %s: %s' % (self.parameters['from_name'], to_native(error)), exception=traceback.format_exc()) def modify_helper(self, modify): """ helper method to modify policy group. :param modify: modified attributes. """ for attribute in modify.keys(): if attribute in [ 'absolute_min_iops', 'expected_iops', 'peak_iops', 'peak_iops_allocation' ]: self.modify_policy_group() def apply(self): """ Run module based on playbook """ self.autosupport_log("na_ontap_qos_policy_group") current = self.get_policy_group() rename, cd_action = None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action( self.get_policy_group(self.parameters['from_name']), current) else: 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 self.module.check_mode: pass else: if rename: self.rename_policy_group() if cd_action == 'create': self.create_policy_group() elif cd_action == 'delete': self.delete_policy_group() elif modify: self.modify_helper(modify) self.module.exit_json(changed=self.na_helper.changed) def autosupport_log(self, event_name): """ Create a log event against the provided vserver """ server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) netapp_utils.ems_log_event(event_name, server)
class NetAppONTAPCifsSecurity(object): ''' modify vserver cifs security ''' def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( vserver=dict(required=True, type='str'), kerberos_clock_skew=dict(required=False, type='int'), kerberos_ticket_age=dict(required=False, type='int'), kerberos_renew_age=dict(required=False, type='int'), kerberos_kdc_timeout=dict(required=False, type='int'), is_signing_required=dict(required=False, type='bool'), is_password_complexity_required=dict(required=False, type='bool'), is_aes_encryption_enabled=dict(required=False, type='bool'), is_smb_encryption_required=dict(required=False, type='bool'), lm_compatibility_level=dict(required=False, choices=['lm_ntlm_ntlmv2_krb', 'ntlm_ntlmv2_krb', 'ntlmv2_krb', 'krb']), referral_enabled_for_ad_ldap=dict(required=False, type='bool'), session_security_for_ad_ldap=dict(required=False, choices=['none', 'sign', 'seal']), smb1_enabled_for_dc_connections=dict(required=False, choices=['false', 'true', 'system_default']), smb2_enabled_for_dc_connections=dict(required=False, choices=['false', 'true', 'system_default']), use_start_tls_for_ad_ldap=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) 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 cifs_security_get_iter(self): """ get current vserver cifs security. :return: a dict of vserver cifs security """ cifs_security_get = netapp_utils.zapi.NaElement('cifs-security-get-iter') query = netapp_utils.zapi.NaElement('query') cifs_security = netapp_utils.zapi.NaElement('cifs-security') cifs_security.add_new_child('vserver', self.parameters['vserver']) query.add_child_elem(cifs_security) cifs_security_get.add_child_elem(query) cifs_security_details = dict() try: result = self.server.invoke_successfully(cifs_security_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching cifs security 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: cifs_security_info = result.get_child_by_name('attributes-list').get_child_by_name('cifs-security') cifs_security_details['kerberos_clock_skew'] = cifs_security_info.get_child_content('kerberos-clock-skew') cifs_security_details['kerberos_ticket_age'] = cifs_security_info.get_child_content('kerberos-ticket-age') cifs_security_details['kerberos_renew_age'] = cifs_security_info.get_child_content('kerberos-renew-age') cifs_security_details['kerberos_kdc_timeout'] = cifs_security_info.get_child_content('kerberos-kdc-timeout') cifs_security_details['is_signing_required'] = bool(cifs_security_info.get_child_content('is-signing-required')) cifs_security_details['is_password_complexity_required'] = bool(cifs_security_info.get_child_content('is-password-complexity-required')) cifs_security_details['is_aes_encryption_enabled'] = bool(cifs_security_info.get_child_content('is-aes-encryption-enabled')) cifs_security_details['is_smb_encryption_required'] = bool(cifs_security_info.get_child_content('is-smb-encryption-required')) cifs_security_details['lm_compatibility_level'] = cifs_security_info.get_child_content('lm-compatibility-level') cifs_security_details['referral_enabled_for_ad_ldap'] = bool(cifs_security_info.get_child_content('referral-enabled-for-ad-ldap')) cifs_security_details['session_security_for_ad_ldap'] = cifs_security_info.get_child_content('session-security-for-ad-ldap') cifs_security_details['smb1_enabled_for_dc_connections'] = cifs_security_info.get_child_content('smb1-enabled-for-dc-connections') cifs_security_details['smb2_enabled_for_dc_connections'] = cifs_security_info.get_child_content('smb2-enabled-for-dc-connections') cifs_security_details['use_start_tls_for_ad_ldap'] = bool(cifs_security_info.get_child_content('use-start-tls-for-ad-ldap')) return cifs_security_details return None def cifs_security_modify(self, modify): """ :param modify: A list of attributes to modify :return: None """ cifs_security_modify = netapp_utils.zapi.NaElement('cifs-security-modify') for attribute in modify: cifs_security_modify.add_new_child(self.attribute_to_name(attribute), str(self.parameters[attribute])) try: self.server.invoke_successfully(cifs_security_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error modifying cifs security on %s: %s' % (self.parameters['vserver'], to_native(e)), exception=traceback.format_exc()) @staticmethod def attribute_to_name(attribute): return str.replace(attribute, '_', '-') def apply(self): """Call modify operations.""" self.asup_log_for_cserver("na_ontap_vserver_cifs_security") current = self.cifs_security_get_iter() 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.cifs_security_modify(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)
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'), source_vserver=dict(required=False, type='str'), destination_vserver=dict(required=False, type='str'), source_volume=dict(required=False, type='str'), destination_volume=dict(required=False, type='str'), source_path=dict(required=False, type='str'), destination_path=dict(required=False, type='str'), schedule=dict(required=False, type='str'), policy=dict(required=False, type='str'), relationship_type=dict(required=False, type='str', choices=[ 'data_protection', 'load_sharing', 'vault', 'restore', 'transition_data_protection', 'extended_data_protection' ]), source_hostname=dict(required=False, type='str'), connection_type=dict(required=False, type='str', choices=[ 'ontap_ontap', 'elementsw_ontap', 'ontap_elementsw' ], default='ontap_ontap'), source_username=dict(required=False, type='str'), source_password=dict(required=False, type='str', no_log=True), max_transfer_rate=dict(required=False, type='int'), identity_preserve=dict(required=False, type='bool'))) self.module = AnsibleModule( argument_spec=self.argument_spec, required_together=(['source_volume', 'destination_volume'], ['source_vserver', 'destination_vserver']), supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # setup later if required self.source_server = None # only for ElementSW -> ONTAP snapmirroring, validate if ElementSW SDK is available if self.parameters.get('connection_type') in [ 'elementsw_ontap', 'ontap_elementsw' ]: if HAS_SF_SDK is False: self.module.fail_json( msg="Unable to import the SolidFire Python SDK") if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") if self.parameters.get('connection_type') != 'ontap_elementsw': self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) else: if self.parameters.get('source_username'): self.module.params['username'] = self.parameters[ 'source_username'] if self.parameters.get('source_password'): self.module.params['password'] = self.parameters[ 'source_password'] self.module.params['hostname'] = self.parameters['source_hostname'] self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
class NetAppONTAPSnapmirror(object): """ Class with Snapmirror methods """ 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'), source_vserver=dict(required=False, type='str'), destination_vserver=dict(required=False, type='str'), source_volume=dict(required=False, type='str'), destination_volume=dict(required=False, type='str'), source_path=dict(required=False, type='str'), destination_path=dict(required=False, type='str'), schedule=dict(required=False, type='str'), policy=dict(required=False, type='str'), relationship_type=dict(required=False, type='str', choices=[ 'data_protection', 'load_sharing', 'vault', 'restore', 'transition_data_protection', 'extended_data_protection' ]), source_hostname=dict(required=False, type='str'), connection_type=dict(required=False, type='str', choices=[ 'ontap_ontap', 'elementsw_ontap', 'ontap_elementsw' ], default='ontap_ontap'), source_username=dict(required=False, type='str'), source_password=dict(required=False, type='str', no_log=True), max_transfer_rate=dict(required=False, type='int'), identity_preserve=dict(required=False, type='bool'))) self.module = AnsibleModule( argument_spec=self.argument_spec, required_together=(['source_volume', 'destination_volume'], ['source_vserver', 'destination_vserver']), supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # setup later if required self.source_server = None # only for ElementSW -> ONTAP snapmirroring, validate if ElementSW SDK is available if self.parameters.get('connection_type') in [ 'elementsw_ontap', 'ontap_elementsw' ]: if HAS_SF_SDK is False: self.module.fail_json( msg="Unable to import the SolidFire Python SDK") if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") if self.parameters.get('connection_type') != 'ontap_elementsw': self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) else: if self.parameters.get('source_username'): self.module.params['username'] = self.parameters[ 'source_username'] if self.parameters.get('source_password'): self.module.params['password'] = self.parameters[ 'source_password'] self.module.params['hostname'] = self.parameters['source_hostname'] self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) def set_element_connection(self, kind): if kind == 'source': self.module.params['hostname'] = self.parameters['source_hostname'] self.module.params['username'] = self.parameters['source_username'] self.module.params['password'] = self.parameters['source_password'] elif kind == 'destination': self.module.params['hostname'] = self.parameters['hostname'] self.module.params['username'] = self.parameters['username'] self.module.params['password'] = self.parameters['password'] elem = netapp_utils.create_sf_connection(module=self.module) elementsw_helper = NaElementSWModule(elem) return elementsw_helper, elem def snapmirror_get_iter(self, destination=None): """ Compose NaElement object to query current SnapMirror relations using destination-path SnapMirror relation for a destination path is unique :return: NaElement object for SnapMirror-get-iter """ snapmirror_get_iter = netapp_utils.zapi.NaElement( 'snapmirror-get-iter') query = netapp_utils.zapi.NaElement('query') snapmirror_info = netapp_utils.zapi.NaElement('snapmirror-info') if destination is None: destination = self.parameters['destination_path'] snapmirror_info.add_new_child('destination-location', destination) query.add_child_elem(snapmirror_info) snapmirror_get_iter.add_child_elem(query) return snapmirror_get_iter def snapmirror_get(self, destination=None): """ Get current SnapMirror relations :return: Dictionary of current SnapMirror details if query successful, else None """ snapmirror_get_iter = self.snapmirror_get_iter(destination) snap_info = dict() try: result = self.server.invoke_successfully(snapmirror_get_iter, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching snapmirror 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: snapmirror_info = result.get_child_by_name( 'attributes-list').get_child_by_name('snapmirror-info') snap_info['mirror_state'] = snapmirror_info.get_child_content( 'mirror-state') snap_info['status'] = snapmirror_info.get_child_content( 'relationship-status') snap_info['schedule'] = snapmirror_info.get_child_content( 'schedule') snap_info['policy'] = snapmirror_info.get_child_content('policy') snap_info['relationship'] = snapmirror_info.get_child_content( 'relationship-type') if snapmirror_info.get_child_by_name('max-transfer-rate'): snap_info['max_transfer_rate'] = int( snapmirror_info.get_child_content('max-transfer-rate')) if snap_info['schedule'] is None: snap_info['schedule'] = "" return snap_info return None def check_if_remote_volume_exists(self): """ Validate existence of source volume :return: True if volume exists, False otherwise """ self.set_source_cluster_connection() # do a get volume to check if volume exists or not volume_info = netapp_utils.zapi.NaElement('volume-get-iter') volume_attributes = netapp_utils.zapi.NaElement('volume-attributes') volume_id_attributes = netapp_utils.zapi.NaElement( 'volume-id-attributes') volume_id_attributes.add_new_child('name', self.parameters['source_volume']) # if source_volume is present, then source_vserver is also guaranteed to be present volume_id_attributes.add_new_child('vserver-name', self.parameters['source_vserver']) volume_attributes.add_child_elem(volume_id_attributes) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(volume_attributes) volume_info.add_child_elem(query) try: result = self.source_server.invoke_successfully(volume_info, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error fetching source volume details %s : %s' % (self.parameters['source_volume'], to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) > 0: return True return False def snapmirror_create(self): """ Create a SnapMirror relationship """ if self.parameters.get('source_hostname') and self.parameters.get( 'source_volume'): if not self.check_if_remote_volume_exists(): self.module.fail_json( msg= 'Source volume does not exist. Please specify a volume that exists' ) options = { 'source-location': self.parameters['source_path'], 'destination-location': self.parameters['destination_path'] } snapmirror_create = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-create', **options) if self.parameters.get('relationship_type'): snapmirror_create.add_new_child( 'relationship-type', self.parameters['relationship_type']) if self.parameters.get('schedule'): snapmirror_create.add_new_child('schedule', self.parameters['schedule']) if self.parameters.get('policy'): snapmirror_create.add_new_child('policy', self.parameters['policy']) if self.parameters.get('max_transfer_rate'): snapmirror_create.add_new_child( 'max-transfer-rate', str(self.parameters['max_transfer_rate'])) if self.parameters.get('identity_preserve'): snapmirror_create.add_new_child( 'identity-preserve', str(self.parameters['identity_preserve'])) try: self.server.invoke_successfully(snapmirror_create, enable_tunneling=True) self.snapmirror_initialize() except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating SnapMirror %s' % to_native(error), exception=traceback.format_exc()) def set_source_cluster_connection(self): """ Setup ontap ZAPI server connection for source hostname :return: None """ if self.parameters.get('source_username'): self.module.params['username'] = self.parameters['source_username'] if self.parameters.get('source_password'): self.module.params['password'] = self.parameters['source_password'] self.module.params['hostname'] = self.parameters['source_hostname'] self.source_server = netapp_utils.setup_na_ontap_zapi( module=self.module) def delete_snapmirror(self, is_hci, relationship_type): """ Delete a SnapMirror relationship #1. Quiesce the SnapMirror relationship at destination #2. Break the SnapMirror relationship at the destination #3. Release the SnapMirror at source #4. Delete SnapMirror at destination """ if not is_hci: if not self.parameters.get('source_hostname'): self.module.fail_json( msg='Missing parameters for delete: Please specify the ' 'source cluster hostname to release the SnapMirror relation' ) # Quiesce at destination self.snapmirror_quiesce() # Break at destination if relationship_type not in ['load_sharing', 'vault']: self.snapmirror_break() # if source is ONTAP, release the destination at source cluster if not is_hci: self.set_source_cluster_connection() if self.get_destination(): # Release at source self.snapmirror_release() # Delete at destination self.snapmirror_delete() def snapmirror_quiesce(self): """ Quiesce SnapMirror relationship - disable all future transfers to this destination """ options = {'destination-location': self.parameters['destination_path']} snapmirror_quiesce = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-quiesce', **options) try: self.server.invoke_successfully(snapmirror_quiesce, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error Quiescing SnapMirror : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_delete(self): """ Delete SnapMirror relationship at destination cluster """ options = {'destination-location': self.parameters['destination_path']} snapmirror_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-destroy', **options) try: self.server.invoke_successfully(snapmirror_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting SnapMirror : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_break(self, destination=None): """ Break SnapMirror relationship at destination cluster """ if destination is None: destination = self.parameters['destination_path'] options = {'destination-location': destination} snapmirror_break = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-break', **options) try: self.server.invoke_successfully(snapmirror_break, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error breaking SnapMirror relationship : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_release(self): """ Release SnapMirror relationship from source cluster """ options = {'destination-location': self.parameters['destination_path']} snapmirror_release = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-release', **options) try: self.source_server.invoke_successfully(snapmirror_release, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error releasing SnapMirror relationship : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_abort(self): """ Abort a SnapMirror relationship in progress """ options = {'destination-location': self.parameters['destination_path']} snapmirror_abort = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-abort', **options) try: self.server.invoke_successfully(snapmirror_abort, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error aborting SnapMirror relationship : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_initialize(self): """ Initialize SnapMirror based on relationship type """ current = self.snapmirror_get() if current['mirror_state'] != 'snapmirrored': initialize_zapi = 'snapmirror-initialize' if self.parameters.get('relationship_type') and self.parameters[ 'relationship_type'] == 'load_sharing': initialize_zapi = 'snapmirror-initialize-ls-set' options = {'source-location': self.parameters['source_path']} else: options = { 'destination-location': self.parameters['destination_path'] } snapmirror_init = netapp_utils.zapi.NaElement.create_node_with_children( initialize_zapi, **options) try: self.server.invoke_successfully(snapmirror_init, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error initializing SnapMirror : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_modify(self, modify): """ Modify SnapMirror schedule or policy """ options = {'destination-location': self.parameters['destination_path']} snapmirror_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-modify', **options) if modify.get('schedule') is not None: snapmirror_modify.add_new_child('schedule', modify.get('schedule')) if modify.get('policy'): snapmirror_modify.add_new_child('policy', modify.get('policy')) if modify.get('max_transfer_rate'): snapmirror_modify.add_new_child( 'max-transfer-rate', str(modify.get('max_transfer_rate'))) try: self.server.invoke_successfully(snapmirror_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying SnapMirror schedule or policy : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_update(self): """ Update data in destination endpoint """ options = {'destination-location': self.parameters['destination_path']} snapmirror_update = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-update', **options) try: result = self.server.invoke_successfully(snapmirror_update, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error updating SnapMirror : %s' % (to_native(error)), exception=traceback.format_exc()) def check_parameters(self): """ Validate parameters and fail if one or more required params are missing Update source and destination path from vserver and volume parameters """ if self.parameters['state'] == 'present'\ and (self.parameters.get('source_path') or self.parameters.get('destination_path')): if not self.parameters.get( 'destination_path') or not self.parameters.get( 'source_path'): self.module.fail_json( msg='Missing parameters: Source path or Destination path') elif self.parameters.get('source_volume'): if not self.parameters.get( 'source_vserver') or not self.parameters.get( 'destination_vserver'): self.module.fail_json( msg= 'Missing parameters: source vserver or destination vserver or both' ) self.parameters['source_path'] = self.parameters[ 'source_vserver'] + ":" + self.parameters['source_volume'] self.parameters['destination_path'] = self.parameters['destination_vserver'] + ":" +\ self.parameters['destination_volume'] elif self.parameters.get('source_vserver'): self.parameters[ 'source_path'] = self.parameters['source_vserver'] + ":" self.parameters['destination_path'] = self.parameters[ 'destination_vserver'] + ":" def get_destination(self): result = None release_get = netapp_utils.zapi.NaElement( 'snapmirror-get-destination-iter') query = netapp_utils.zapi.NaElement('query') snapmirror_dest_info = netapp_utils.zapi.NaElement( 'snapmirror-destination-info') snapmirror_dest_info.add_new_child('destination-location', self.parameters['destination_path']) query.add_child_elem(snapmirror_dest_info) release_get.add_child_elem(query) try: result = self.source_server.invoke_successfully( release_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error fetching snapmirror destinations 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: return True return None @staticmethod def element_source_path_format_matches(value): return re.match( pattern=r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\/lun\/[0-9]+", string=value) def check_elementsw_parameters(self, kind='source'): """ Validate all ElementSW cluster parameters required for managing the SnapMirror relationship Validate if both source and destination paths are present Validate if source_path follows the required format Validate SVIP Validate if ElementSW volume exists :return: None """ path = None if kind == 'destination': path = self.parameters.get('destination_path') elif kind == 'source': path = self.parameters.get('source_path') if path is None: self.module.fail_json( msg="Error: Missing required parameter %s_path for " "connection_type %s" % (kind, self.parameters['connection_type'])) else: if NetAppONTAPSnapmirror.element_source_path_format_matches( path) is None: self.module.fail_json( msg="Error: invalid %s_path %s. " "If the path is a ElementSW cluster, the value should be of the format" " <Element_SVIP>:/lun/<Element_VOLUME_ID>" % (kind, path)) # validate source_path elementsw_helper, elem = self.set_element_connection(kind) self.validate_elementsw_svip(path, elem) self.check_if_elementsw_volume_exists(path, elementsw_helper) def validate_elementsw_svip(self, path, elem): """ Validate ElementSW cluster SVIP :return: None """ result = None try: result = elem.get_cluster_info() except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error fetching SVIP", exception=to_native(err)) if result and result.cluster_info.svip: cluster_svip = result.cluster_info.svip svip = path.split(':')[0] # split IP address from source_path if svip != cluster_svip: self.module.fail_json(msg="Error: Invalid SVIP") def check_if_elementsw_volume_exists(self, path, elementsw_helper): """ Check if remote ElementSW volume exists :return: None """ volume_id, vol_id = None, path.split('/')[-1] try: volume_id = elementsw_helper.volume_id_exists(int(vol_id)) except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error fetching Volume details", exception=to_native(err)) if volume_id is None: self.module.fail_json( msg= "Error: Source volume does not exist in the ElementSW cluster") 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 SnapMirror """ self.asup_log_for_cserver("na_ontap_snapmirror") # source is ElementSW if self.parameters['state'] == 'present' and self.parameters.get( 'connection_type') == 'elementsw_ontap': self.check_elementsw_parameters() elif self.parameters.get('connection_type') == 'ontap_elementsw': self.check_elementsw_parameters('destination') else: self.check_parameters() if self.parameters['state'] == 'present' and self.parameters.get( 'connection_type') == 'ontap_elementsw': current_elementsw_ontap = self.snapmirror_get( self.parameters['source_path']) if current_elementsw_ontap is None: self.module.fail_json( msg= 'Error: creating an ONTAP to ElementSW snapmirror relationship requires an ' 'established SnapMirror relation from ElementSW to ONTAP cluster' ) current = self.snapmirror_get() cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes( current, self.parameters) element_snapmirror = False if cd_action == 'create': self.snapmirror_create() elif cd_action == 'delete': if current['status'] == 'transferring': self.snapmirror_abort() else: if self.parameters.get('connection_type') == 'elementsw_ontap': element_snapmirror = True self.delete_snapmirror(element_snapmirror, current['relationship']) else: if modify: self.snapmirror_modify(modify) # check for initialize if current and current['mirror_state'] != 'snapmirrored': self.snapmirror_initialize() # set changed explicitly for initialize self.na_helper.changed = True # Update when create is called again, or modify is being called if self.parameters['state'] == 'present': self.snapmirror_update() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapSnapshotPolicy(object): """ Creates and deletes a Snapshot Policy """ 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"), enabled=dict(required=False, type="bool"), # count is a list of integers count=dict(required=False, type="list", elements="int"), comment=dict(required=False, type="str"), schedule=dict(required=False, type="list", elements="str"), snapmirror_label=dict(required=False, type="list", elements="str"), vserver=dict(required=False, type="str") )) self.module = AnsibleModule( argument_spec=self.argument_spec, required_if=[ ('state', 'present', ['enabled', 'count', 'schedule']), ], supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: if 'vserver' in self.parameters: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver']) else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) return def get_snapshot_policy(self): """ Checks to see if a snapshot policy exists or not :return: Return policy details if a snapshot policy exists, None if it doesn't """ snapshot_obj = netapp_utils.zapi.NaElement("snapshot-policy-get-iter") # compose query query = netapp_utils.zapi.NaElement("query") snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-policy-info") snapshot_info_obj.add_new_child("policy", self.parameters['name']) if 'vserver' in self.parameters: snapshot_info_obj.add_new_child("vserver-name", self.parameters['vserver']) query.add_child_elem(snapshot_info_obj) snapshot_obj.add_child_elem(query) try: result = self.server.invoke_successfully(snapshot_obj, True) if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: snapshot_policy = result.get_child_by_name('attributes-list').get_child_by_name('snapshot-policy-info') current = {} current['name'] = snapshot_policy.get_child_content('policy') current['vserver'] = snapshot_policy.get_child_content('vserver-name') current['enabled'] = False if snapshot_policy.get_child_content('enabled').lower() == 'false' else True current['comment'] = snapshot_policy.get_child_content('comment') or '' current['schedule'], current['count'], current['snapmirror_label'] = [], [], [] if snapshot_policy.get_child_by_name('snapshot-policy-schedules'): for schedule in snapshot_policy['snapshot-policy-schedules'].get_children(): current['schedule'].append(schedule.get_child_content('schedule')) current['count'].append(int(schedule.get_child_content('count'))) snapmirror_label = schedule.get_child_content('snapmirror-label') if snapmirror_label is None or snapmirror_label == '-': snapmirror_label = '' current['snapmirror_label'].append(snapmirror_label) return current except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg=to_native(error), exception=traceback.format_exc()) return None def validate_parameters(self): """ Validate if each schedule has a count associated :return: None """ if 'count' not in self.parameters or 'schedule' not in self.parameters or \ len(self.parameters['count']) > 5 or len(self.parameters['schedule']) > 5 or \ len(self.parameters['count']) < 1 or len(self.parameters['schedule']) < 1 or \ len(self.parameters['count']) != len(self.parameters['schedule']): self.module.fail_json(msg="Error: A Snapshot policy must have at least 1 " "schedule and can have up to a maximum of 5 schedules, with a count " "representing the maximum number of Snapshot copies for each schedule") if 'snapmirror_label' in self.parameters: if len(self.parameters['snapmirror_label']) != len(self.parameters['schedule']): self.module.fail_json(msg="Error: Each Snapshot Policy schedule must have an " "accompanying SnapMirror Label") def modify_snapshot_policy(self, current): """ Modifies an existing snapshot policy """ # Set up required variables to modify snapshot policy options = {'policy': self.parameters['name']} modify = False # Set up optional variables to modify snapshot policy if 'enabled' in self.parameters and self.parameters['enabled'] != current['enabled']: options['enabled'] = str(self.parameters['enabled']) modify = True if 'comment' in self.parameters and self.parameters['comment'] != current['comment']: options['comment'] = self.parameters['comment'] modify = True if modify: snapshot_obj = netapp_utils.zapi.NaElement.create_node_with_children('snapshot-policy-modify', **options) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying snapshot policy %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def modify_snapshot_policy_schedules(self, current): """ Modify existing schedules in snapshot policy :return: None """ self.validate_parameters() delete_schedules, modify_schedules, add_schedules = [], [], [] if 'snapmirror_label' in self.parameters: snapmirror_labels = self.parameters['snapmirror_label'] else: # User hasn't supplied any snapmirror labels. snapmirror_labels = [None] * len(self.parameters['schedule']) # Identify schedules for deletion for schedule in current['schedule']: schedule = schedule.strip() if schedule not in [item.strip() for item in self.parameters['schedule']]: options = {'policy': current['name'], 'schedule': schedule} delete_schedules.append(options) # Identify schedules to be modified or added for schedule, count, snapmirror_label in zip(self.parameters['schedule'], self.parameters['count'], snapmirror_labels): schedule = schedule.strip() if snapmirror_label is not None: snapmirror_label = snapmirror_label.strip() options = {'policy': current['name'], 'schedule': schedule} if schedule in current['schedule']: # Schedule exists. Only modify if it has changed. modify = False schedule_index = current['schedule'].index(schedule) if count != current['count'][schedule_index]: options['new-count'] = str(count) modify = True if snapmirror_label is not None: if snapmirror_label != current['snapmirror_label'][schedule_index]: options['new-snapmirror-label'] = snapmirror_label modify = True if modify: modify_schedules.append(options) else: # New schedule options['count'] = str(count) if snapmirror_label is not None and snapmirror_label != '': options['snapmirror-label'] = snapmirror_label add_schedules.append(options) # Delete N-1 schedules no longer required. Must leave 1 schedule in policy # at any one time. Delete last one afterwards. while len(delete_schedules) > 1: options = delete_schedules.pop() self.modify_snapshot_policy_schedule(options, 'snapshot-policy-remove-schedule') # Modify schedules. while len(modify_schedules) > 0: options = modify_schedules.pop() self.modify_snapshot_policy_schedule(options, 'snapshot-policy-modify-schedule') # Add N-1 new schedules. Add last one after last schedule has been deleted. while len(add_schedules) > 1: options = add_schedules.pop() self.modify_snapshot_policy_schedule(options, 'snapshot-policy-add-schedule') # Delete last schedule no longer required. while len(delete_schedules) > 0: options = delete_schedules.pop() self.modify_snapshot_policy_schedule(options, 'snapshot-policy-remove-schedule') # Add last new schedule. while len(add_schedules) > 0: options = add_schedules.pop() self.modify_snapshot_policy_schedule(options, 'snapshot-policy-add-schedule') def modify_snapshot_policy_schedule(self, options, zapi): """ Add, modify or remove a schedule to/from a snapshot policy """ snapshot_obj = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options) try: self.server.invoke_successfully(snapshot_obj, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying snapshot policy schedule %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def create_snapshot_policy(self): """ Creates a new snapshot policy """ # set up required variables to create a snapshot policy self.validate_parameters() options = {'policy': self.parameters['name'], 'enabled': str(self.parameters['enabled']), } if 'snapmirror_label' in self.parameters: snapmirror_labels = self.parameters['snapmirror_label'] else: # User hasn't supplied any snapmirror labels. snapmirror_labels = [None] * len(self.parameters['schedule']) # zapi attribute for first schedule is schedule1, second is schedule2 and so on positions = [str(i) for i in range(1, len(self.parameters['schedule']) + 1)] for schedule, count, snapmirror_label, position in zip(self.parameters['schedule'], self.parameters['count'], snapmirror_labels, positions): schedule = schedule.strip() options['count' + position] = str(count) options['schedule' + position] = schedule if snapmirror_label is not None: snapmirror_label = snapmirror_label.strip() if snapmirror_label != '': options['snapmirror-label' + position] = snapmirror_label snapshot_obj = netapp_utils.zapi.NaElement.create_node_with_children('snapshot-policy-create', **options) # Set up optional variables to create a snapshot policy if self.parameters.get('comment'): snapshot_obj.add_new_child("comment", self.parameters['comment']) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating snapshot policy %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_snapshot_policy(self): """ Deletes an existing snapshot policy """ snapshot_obj = netapp_utils.zapi.NaElement("snapshot-policy-delete") # Set up required variables to delete a snapshot policy snapshot_obj.add_new_child("policy", self.parameters['name']) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting snapshot policy %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): """ Check to see which play we should run """ self.asup_log_for_cserver("na_ontap_snapshot_policy") current = self.get_snapshot_policy() modify = None cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': # Don't sort schedule/count/snapmirror_label lists as it can # mess up the intended parameter order. 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_snapshot_policy() elif cd_action == 'delete': self.delete_snapshot_policy() if modify: self.modify_snapshot_policy(current) self.modify_snapshot_policy_schedules(current) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPJob(object): '''Class with job schedule cron methods''' 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'), job_minutes=dict(required=False, type='int'), job_month=dict(required=False, type='int'), job_hour=dict(required=False, type='int'), job_day_of_month=dict(required=False, type='int'), job_day_of_week=dict(required=False, type='int') )) 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 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_job_schedule(self): """ Return details about the job :param: name : Job name :return: Details about the Job. None if not found. :rtype: dict """ 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_exists_info = result['attributes-list']['job-schedule-cron-info'] job_details = { 'name': job_exists_info.get_child_content('job-schedule-name'), 'job_minutes': int(job_exists_info['job-schedule-cron-minute']['cron-minute']), # set default values to other job attributes (by default, cron runs on all months if months is empty) 'job_hour': 0, 'job_month': -1, 'job_day_of_month': 0, 'job_day_of_week': 0 } if job_exists_info.get_child_by_name('job-schedule-cron-hour'): job_details['job_hour'] = int(job_exists_info['job-schedule-cron-hour']['cron-hour']) if job_exists_info.get_child_by_name('job-schedule-cron-month'): job_details['job_month'] = int(job_exists_info['job-schedule-cron-month']['cron-month']) if job_exists_info.get_child_by_name('job-schedule-cron-day'): job_details['job_day_of_month'] = int(job_exists_info['job-schedule-cron-day']['cron-day-of-month']) if job_exists_info.get_child_by_name('job-schedule-cron-day-of-week'): job_details['job_day_of_week'] = int(job_exists_info['job-schedule-cron-day-of-week'] ['cron-day-of-week']) 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: modif or create NaElement object :param values: dictionary of cron values to be added :return: None """ if values.get('job_minutes'): na_element_object.add_node_with_children( 'job-schedule-cron-minute', **{'cron-minute': str(values['job_minutes'])}) if values.get('job_hour'): na_element_object.add_node_with_children( 'job-schedule-cron-hour', **{'cron-hour': str(values['job_hour'])}) if values.get('job_month'): na_element_object.add_node_with_children( 'job-schedule-cron-month', **{'cron-month': str(values['job_month'])}) if values.get('job_day_of_month'): na_element_object.add_node_with_children( 'job-schedule-cron-day', **{'cron-day-of-month': str(values['job_day_of_month'])}) if values.get('job_day_of_week'): na_element_object.add_node_with_children( 'job-schedule-cron-day-of-week', **{'cron-day-of-week': str(values['job_day_of_week'])}) 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') job_schedule_create = netapp_utils.zapi.NaElement.create_node_with_children( 'job-schedule-cron-create', **{'job-schedule-name': self.parameters['name']}) 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 """ job_schedule_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'job-schedule-cron-destroy', **{'job-schedule-name': self.parameters['name']}) 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): """ modify a job schedule """ 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_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 """ self.autosupport_log() current = self.get_job_schedule() 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 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) self.module.exit_json(changed=self.na_helper.changed)
class ElementSWVolumePair(object): ''' class to handle volume pairing operations ''' def __init__(self): """ Setup Ansible parameters and SolidFire connection """ self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), src_volume=dict(required=True, type='str'), src_account=dict(required=True, type='str'), dest_volume=dict(required=True, type='str'), dest_account=dict(required=True, type='str'), mode=dict(required=False, type='str', choices=['async', 'sync', 'snapshotsonly'], default='async'), dest_mvip=dict(required=True, type='str'), dest_username=dict(required=False, type='str'), dest_password=dict(required=False, type='str', no_log=True))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) if HAS_SF_SDK is False: self.module.fail_json( msg="Unable to import the SolidFire Python SDK") else: self.elem = netapp_utils.create_sf_connection(module=self.module) self.elementsw_helper = NaElementSWModule(self.elem) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # get element_sw_connection for destination cluster # overwrite existing source host, user and password with destination credentials self.module.params['hostname'] = self.parameters['dest_mvip'] # username and password is same as source, # if dest_username and dest_password aren't specified if self.parameters.get('dest_username'): self.module.params['username'] = self.parameters['dest_username'] if self.parameters.get('dest_password'): self.module.params['password'] = self.parameters['dest_password'] self.dest_elem = netapp_utils.create_sf_connection(module=self.module) self.dest_elementsw_helper = NaElementSWModule(self.dest_elem) def check_if_already_paired(self, vol_id): """ Check for idempotency A volume can have only one pair Return paired-volume-id if volume is paired already None if volume is not paired """ paired_volumes = self.elem.list_volumes(volume_ids=[vol_id], is_paired=True) for vol in paired_volumes.volumes: for pair in vol.volume_pairs: if pair is not None: return pair.remote_volume_id return None def pair_volumes(self): """ Start volume pairing on source, and complete on target volume """ try: pair_key = self.elem.start_volume_pairing( volume_id=self.parameters['src_vol_id'], mode=self.parameters['mode']) self.dest_elem.complete_volume_pairing( volume_pairing_key=pair_key.volume_pairing_key, volume_id=self.parameters['dest_vol_id']) except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error pairing volume id %s" % (self.parameters['src_vol_id']), exception=to_native(err)) def pairing_exists(self, src_id, dest_id): src_paired = self.check_if_already_paired( self.parameters['src_vol_id']) dest_paired = self.check_if_already_paired( self.parameters['dest_vol_id']) if src_paired is not None or dest_paired is not None: return True return None def unpair_volumes(self): """ Delete volume pair """ try: self.elem.remove_volume_pair( volume_id=self.parameters['src_vol_id']) self.dest_elem.remove_volume_pair( volume_id=self.parameters['dest_vol_id']) except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error unpairing volume ids %s and %s" % (self.parameters['src_vol_id'], self.parameters['dest_vol_id']), exception=to_native(err)) def get_account_id(self, account, type): """ Get source and destination account IDs """ try: if type == 'src': self.parameters[ 'src_account_id'] = self.elementsw_helper.account_exists( account) elif type == 'dest': self.parameters[ 'dest_account_id'] = self.dest_elementsw_helper.account_exists( account) except solidfire.common.ApiServerError as err: self.module.fail_json( msg="Error: either account %s or %s does not exist" % (self.parameters['src_account'], self.parameters['dest_account']), exception=to_native(err)) def get_volume_id(self, volume, type): """ Get source and destination volume IDs """ if type == 'src': self.parameters[ 'src_vol_id'] = self.elementsw_helper.volume_exists( volume, self.parameters['src_account_id']) if self.parameters['src_vol_id'] is None: self.module.fail_json( msg="Error: source volume %s does not exist" % (self.parameters['src_volume'])) elif type == 'dest': self.parameters[ 'dest_vol_id'] = self.dest_elementsw_helper.volume_exists( volume, self.parameters['dest_account_id']) if self.parameters['dest_vol_id'] is None: self.module.fail_json( msg="Error: destination volume %s does not exist" % (self.parameters['dest_volume'])) def get_ids(self): """ Get IDs for volumes and accounts """ self.get_account_id(self.parameters['src_account'], 'src') self.get_account_id(self.parameters['dest_account'], 'dest') self.get_volume_id(self.parameters['src_volume'], 'src') self.get_volume_id(self.parameters['dest_volume'], 'dest') def apply(self): """ Call create / delete volume pair methods """ self.get_ids() paired = self.pairing_exists(self.parameters['src_vol_id'], self.parameters['dest_vol_id']) # calling helper to determine action cd_action = self.na_helper.get_cd_action(paired, self.parameters) if cd_action == "create": self.pair_volumes() elif cd_action == "delete": self.unpair_volumes() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapFCP(object): """ Enable and Disable FCP """ 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'), vserver=dict(required=True, type='str'), status=dict(required=False, choices=['up', 'down'], default='up') )) 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 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_fcp(self): """ Create's and Starts an FCP :return: none """ try: self.server.invoke_successfully(netapp_utils.zapi.NaElement('fcp-service-create'), True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating FCP: %s' % (to_native(error)), exception=traceback.format_exc()) def start_fcp(self): """ Starts an existing FCP :return: none """ try: self.server.invoke_successfully(netapp_utils.zapi.NaElement('fcp-service-start'), True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error starting FCP %s' % (to_native(error)), exception=traceback.format_exc()) def stop_fcp(self): """ Steps an Existing FCP :return: none """ try: self.server.invoke_successfully(netapp_utils.zapi.NaElement('fcp-service-stop'), True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error Stoping FCP %s' % (to_native(error)), exception=traceback.format_exc()) def destroy_fcp(self): """ Destroys an already stopped FCP :return: """ try: self.server.invoke_successfully(netapp_utils.zapi.NaElement('fcp-service-destroy'), True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error destroying FCP %s' % (to_native(error)), exception=traceback.format_exc()) def get_fcp(self): fcp_obj = netapp_utils.zapi.NaElement('fcp-service-get-iter') fcp_info = netapp_utils.zapi.NaElement('fcp-service-info') fcp_info.add_new_child('vserver', self.parameters['vserver']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(fcp_info) fcp_obj.add_child_elem(query) result = self.server.invoke_successfully(fcp_obj, True) # There can only be 1 FCP per vserver. If true, one is set up, else one isn't set up if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) >= 1: return True else: return False def current_status(self): try: status = self.server.invoke_successfully(netapp_utils.zapi.NaElement('fcp-service-status'), True) return status.get_child_content('is-available') == 'true' except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error destroying FCP: %s' % (to_native(error)), exception=traceback.format_exc()) def apply(self): exists = self.get_fcp() changed = False if self.parameters['state'] == 'present': if exists: if self.parameters['status'] == 'up': if not self.current_status(): self.start_fcp() changed = True else: if self.current_status(): self.stop_fcp() changed = True else: self.create_fcp() if self.parameters['status'] == 'up': self.start_fcp() changed = True else: if exists: if self.current_status(): self.stop_fcp() self.destroy_fcp() changed = True self.module.exit_json(changed=changed)
class NetAppOntapNode(object): """ Rename node """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( name=dict(required=True, type='str'), from_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) if HAS_NETAPP_LIB is False: self.module.fail_json(msg="the python NetApp-Lib module is required") else: self.cluster = netapp_utils.setup_na_ontap_zapi(module=self.module) return def rename_node(self): """ Rename an existing node :return: none """ node_obj = netapp_utils.zapi.NaElement('system-node-rename') node_obj.add_new_child('node', self.parameters['from_name']) node_obj.add_new_child('new-name', self.parameters['name']) try: self.cluster.invoke_successfully(node_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating node: %s' % (to_native(error)), exception=traceback.format_exc()) def get_node(self, name): node_obj = netapp_utils.zapi.NaElement('system-node-get') node_obj.add_new_child('node', name) try: self.cluster.invoke_successfully(node_obj, True) except netapp_utils.zapi.NaApiError as error: if to_native(error.code) == "13115": # 13115 (EINVALIDINPUTERROR) if the node does not exist return None else: self.module.fail_json(msg=to_native( error), exception=traceback.format_exc()) return True def apply(self): # logging ems event results = netapp_utils.get_cserver(self.cluster) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event("na_ontap_node", cserver) exists = self.get_node(self.parameters['name']) from_exists = self.get_node(self.parameters['from_name']) changed = False if exists: pass else: if from_exists: self.rename_node() changed = True else: self.module.fail_json(msg='Error renaming node, from_name %s does not exist' % self.parameters['from_name']) self.module.exit_json(changed=changed)
class NetAppONTAPasup(object): """Class with autosupport methods""" 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'), node_name=dict(required=True, type='str'), transport=dict(required=False, type='str', choices=['smtp', 'http', 'https']), noteto=dict(required=False, type='list'), post_url=dict(reuired=False, type='str'), support=dict(required=False, type='bool'), mail_hosts=dict(required=False, type='list'))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=False) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # present or absent requires modifying state to enabled or disabled self.parameters['service_state'] = 'started' if self.parameters[ 'state'] == 'present' else 'stopped' if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_ontap_zapi(module=self.module) def get_autosupport_config(self): """ Invoke zapi - get current autosupport status @return: 'true' or 'false' / FAILURE with an error_message """ asup_details = netapp_utils.zapi.NaElement('autosupport-config-get') asup_details.add_new_child('node-name', self.parameters['node_name']) asup_info = dict() try: result = self.server.invoke_successfully(asup_details, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='%s' % to_native(error), exception=traceback.format_exc()) # zapi invoke successful asup_attr_info = result.get_child_by_name( 'attributes').get_child_by_name('autosupport-config-info') current_state = asup_attr_info.get_child_content('is-enabled') if current_state == 'true': asup_info['service_state'] = 'started' elif current_state == 'false': asup_info['service_state'] = 'stopped' current_support = asup_attr_info.get_child_content( 'is-support-enabled') if current_support == 'true': asup_info['support'] = True elif current_support == 'false': asup_info['support'] = False asup_info['transport'] = asup_attr_info.get_child_content('transport') asup_info['post_url'] = asup_attr_info.get_child_content('post-url') mail_hosts = asup_attr_info.get_child_by_name('mail-hosts') # mail hosts has one valid entry always if mail_hosts is not None: # get list of mail hosts asup_info['mail_hosts'] = [ mail.get_content() for mail in mail_hosts.get_children() ] email_list = asup_attr_info.get_child_by_name('noteto') # if email_list is empty, noteto is also empty asup_info['noteto'] = [] if email_list is None else [ email.get_content() for email in email_list.get_children() ] return asup_info def modify_autosupport_config(self, modify): """ Invoke zapi - modify autosupport config @return: NaElement object / FAILURE with an error_message """ asup_details = netapp_utils.zapi.NaElement('autosupport-config-modify') asup_details.add_new_child('node-name', self.parameters['node_name']) if modify.get('service_state'): if modify.get('service_state') == 'started': asup_details.add_new_child('is-enabled', 'true') elif modify.get('service_state') == 'stopped': asup_details.add_new_child('is-enabled', 'false') if modify.get('support') is not None: if modify.get('support') is True: asup_details.add_new_child('is-support-enabled', 'true') elif modify.get('support') is False: asup_details.add_new_child('is-support-enabled', 'false') if modify.get('transport'): asup_details.add_new_child('transport', modify['transport']) if modify.get('post_url'): asup_details.add_new_child('post-url', modify['post_url']) if modify.get('noteto'): asup_email = netapp_utils.zapi.NaElement('noteto') asup_details.add_child_elem(asup_email) for email in modify.get('noteto'): asup_email.add_new_child('mail-address', email) if modify.get('mail_hosts'): asup_mail_hosts = netapp_utils.zapi.NaElement('mail-hosts') asup_details.add_child_elem(asup_mail_hosts) for mail in modify.get('mail_hosts'): asup_mail_hosts.add_new_child('string', mail) try: result = self.server.invoke_successfully(asup_details, enable_tunneling=True) return result except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='%s' % to_native(error), exception=traceback.format_exc()) def apply(self): """ Apply action to autosupport """ 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_autosupport", cserver) current = self.get_autosupport_config() 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_autosupport_config(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPJob(object): '''Class with job schedule cron methods''' 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'), job_minutes=dict(required=False, type='list'), job_months=dict(required=False, type='list'), job_hours=dict(required=False, type='list'), job_days_of_month=dict(required=False, type='list'), job_days_of_week=dict(required=False, type='list') )) 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() 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 get_job_schedule(self): """ Return details about the job :param: name : Job name :return: Details about the Job. None if not found. :rtype: dict """ 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: modif 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') 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 """ 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): """ modify a job schedule """ 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 """ 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) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPSnapmirror(object): """ Class with Snapmirror methods """ 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'), source_vserver=dict(required=False, type='str'), destination_vserver=dict(required=False, type='str'), source_volume=dict(required=False, type='str'), destination_volume=dict(required=False, type='str'), source_path=dict(required=False, type='str'), destination_path=dict(required=False, type='str'), schedule=dict(required=False, type='str'), relationship_type=dict(required=False, type='str', choices=[ 'data_protection', 'load_sharing', 'vault', 'restore', 'transition_data_protection', 'extended_data_protection' ]), source_hostname=dict(required=False, type='str'), source_username=dict(required=False, type='str'), source_password=dict(required=False, type='str', no_log=True))) self.module = AnsibleModule( argument_spec=self.argument_spec, required_together=(['source_volume', 'destination_volume'], ['source_vserver', 'destination_vserver']), supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # setup later if required self.source_server = None 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 snapmirror_get_iter(self): """ Compose NaElement object to query current SnapMirror relations using destination-path SnapMirror relation for a destination path is unique :return: NaElement object for SnapMirror-get-iter """ snapmirror_get_iter = netapp_utils.zapi.NaElement( 'snapmirror-get-iter') query = netapp_utils.zapi.NaElement('query') snapmirror_info = netapp_utils.zapi.NaElement('snapmirror-info') snapmirror_info.add_new_child('destination-location', self.parameters['destination_path']) query.add_child_elem(snapmirror_info) snapmirror_get_iter.add_child_elem(query) return snapmirror_get_iter def snapmirror_get(self): """ Get current SnapMirror relations :return: Dictionary of current SnapMirror details if query successful, else None """ snapmirror_get_iter = self.snapmirror_get_iter() snap_info = dict() try: result = self.server.invoke_successfully(snapmirror_get_iter, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching snapmirror 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: snapmirror_info = result.get_child_by_name( 'attributes-list').get_child_by_name('snapmirror-info') snap_info['mirror_state'] = snapmirror_info.get_child_content( 'mirror-state') snap_info['status'] = snapmirror_info.get_child_content( 'relationship-status') snap_info['schedule'] = snapmirror_info.get_child_content( 'schedule') if snap_info['schedule'] is None: snap_info['schedule'] = "" return snap_info return None def snapmirror_create(self): """ Create a SnapMirror relationship """ options = { 'source-location': self.parameters['source_path'], 'destination-location': self.parameters['destination_path'] } snapmirror_create = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-create', **options) if self.parameters.get('relationship_type'): snapmirror_create.add_new_child( 'relationship-type', self.parameters['relationship_type']) if self.parameters.get('schedule'): snapmirror_create.add_new_child('schedule', self.parameters['schedule']) try: self.server.invoke_successfully(snapmirror_create, enable_tunneling=True) self.snapmirror_initialize() except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating SnapMirror %s' % to_native(error), exception=traceback.format_exc()) def delete_snapmirror(self): """ Delete a SnapMirror relationship #1. Quiesce the SnapMirror relationship at destination #2. Break the SnapMirror relationship at the source #3. Release the SnapMirror at source #4. Delete SnapMirror at destination """ if not self.parameters.get('source_hostname'): self.module.fail_json( msg='Missing parameters for delete: Please specify the ' 'source cluster hostname to release the SnapMirror relation') if self.parameters.get('source_username'): self.module.params['username'] = self.parameters['source_username'] if self.parameters.get('source_password'): self.module.params['password'] = self.parameters['source_password'] self.module.params['hostname'] = self.parameters['source_hostname'] self.source_server = netapp_utils.setup_ontap_zapi(module=self.module) self.snapmirror_quiesce() if self.parameters.get('relationship_type') and \ self.parameters.get('relationship_type') not in ['load_sharing', 'vault']: self.snapmirror_break() if self.get_destination(): self.snapmirror_release() self.snapmirror_delete() def snapmirror_quiesce(self): """ Quiesce SnapMirror relationship - disable all future transfers to this destination """ options = {'destination-location': self.parameters['destination_path']} snapmirror_quiesce = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-quiesce', **options) try: self.server.invoke_successfully(snapmirror_quiesce, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error Quiescing SnapMirror : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_delete(self): """ Delete SnapMirror relationship at destination cluster """ options = {'destination-location': self.parameters['destination_path']} snapmirror_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-destroy', **options) try: self.server.invoke_successfully(snapmirror_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting SnapMirror : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_break(self): """ Break SnapMirror relationship at destination cluster """ options = {'destination-location': self.parameters['destination_path']} snapmirror_break = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-break', **options) try: self.server.invoke_successfully(snapmirror_break, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error breaking SnapMirror relationship : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_release(self): """ Release SnapMirror relationship from source cluster """ options = {'destination-location': self.parameters['destination_path']} snapmirror_release = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-release', **options) try: self.source_server.invoke_successfully(snapmirror_release, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error releasing SnapMirror relationship : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_abort(self): """ Abort a SnapMirror relationship in progress """ options = {'destination-location': self.parameters['destination_path']} snapmirror_abort = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-abort', **options) try: self.server.invoke_successfully(snapmirror_abort, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error aborting SnapMirror relationship : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_initialize(self): """ Initialize SnapMirror based on relationship type """ current = self.snapmirror_get() if current['mirror_state'] != 'snapmirrored': initialize_zapi = 'snapmirror-initialize' if self.parameters.get('relationship_type') and self.parameters[ 'relationship_type'] == 'load_sharing': initialize_zapi = 'snapmirror-initialize-ls-set' options = {'source-location': self.parameters['source_path']} else: options = { 'destination-location': self.parameters['destination_path'] } snapmirror_init = netapp_utils.zapi.NaElement.create_node_with_children( initialize_zapi, **options) try: self.server.invoke_successfully(snapmirror_init, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error initializing SnapMirror : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_modify(self, modify): """ Modify SnapMirror schedule """ options = { 'destination-location': self.parameters['destination_path'], 'schedule': modify.get('schedule') } snapmirror_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-modify', **options) try: self.server.invoke_successfully(snapmirror_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying SnapMirror schedule : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_update(self): """ Update data in destination endpoint """ options = {'destination-location': self.parameters['destination_path']} snapmirror_update = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-update', **options) try: result = self.server.invoke_successfully(snapmirror_update, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error updating SnapMirror : %s' % (to_native(error)), exception=traceback.format_exc()) def check_parameters(self): """ Validate parameters and fail if one or more required params are missing Update source and destination path from vserver and volume parameters """ if self.parameters['state'] == 'present'\ and (self.parameters.get('source_path') or self.parameters.get('destination_path')): if not self.parameters.get( 'destination_path') or not self.parameters.get( 'source_path'): self.module.fail_json( msg='Missing parameters: Source path or Destination path') elif self.parameters.get('source_volume'): if not self.parameters.get( 'source_vserver') or not self.parameters.get( 'destination_vserver'): self.module.fail_json( msg= 'Missing parameters: source vserver or destination vserver or both' ) self.parameters['source_path'] = self.parameters[ 'source_vserver'] + ":" + self.parameters['source_volume'] self.parameters['destination_path'] = self.parameters['destination_vserver'] + ":" +\ self.parameters['destination_volume'] elif self.parameters.get('source_vserver'): self.parameters[ 'source_path'] = self.parameters['source_vserver'] + ":" self.parameters['destination_path'] = self.parameters[ 'destination_vserver'] + ":" def get_destination(self): release_get = netapp_utils.zapi.NaElement( 'snapmirror-get-destination-iter') query = netapp_utils.zapi.NaElement('query') snapmirror_dest_info = netapp_utils.zapi.NaElement( 'snapmirror-destination-info') snapmirror_dest_info.add_new_child('destination-location', self.parameters['destination_path']) query.add_child_elem(snapmirror_dest_info) release_get.add_child_elem(query) try: result = self.source_server.invoke_successfully( release_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error fetching snapmirror destinations 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: return True return None def apply(self): """ Apply action to SnapMirror """ self.check_parameters() current = self.snapmirror_get() cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes( current, self.parameters) if cd_action == 'create': self.snapmirror_create() elif cd_action == 'delete': if current['status'] == 'transferring': self.snapmirror_abort() else: self.delete_snapmirror() else: if modify: self.snapmirror_modify(modify) # check for initialize if current and current['mirror_state'] != 'snapmirrored': self.snapmirror_initialize() # set changed explicitly for initialize self.na_helper.changed = True # Update when create is called again, or modify is being called if self.parameters['state'] == 'present': self.snapmirror_update() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapKerberosRealm(object): ''' Kerberos Realm definition class ''' def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( admin_server_ip=dict(required=False, default=None, type='str'), admin_server_port=dict(required=False, default=None, type='str'), clock_skew=dict(required=False, default=None, type='str'), comment=dict(required=False, default=None, type='str'), kdc_ip=dict(required_if=[["state", "present"]], default=None, type='str'), kdc_port=dict(required=False, default=None, type='str'), kdc_vendor=dict(required_if=[["state", "present"]], default=None, type='str', choices=['Microsoft', 'Other']), pw_server_ip=dict(required=False, default=None, type='str'), pw_server_port=dict(required=False, default=None, type='str'), realm=dict(required=True, type='str'), state=dict(required=False, choices=['present', 'absent'], default='present'), vserver=dict(required=True, type='str') )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True, required_if=[('state', 'present', ['kdc_vendor', 'kdc_ip'])], ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) 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']) self.simple_attributes = [ 'admin_server_ip', 'admin_server_port', 'clock_skew', 'kdc_ip', 'kdc_port', 'kdc_vendor', ] def get_krbrealm(self, realm_name=None, vserver_name=None): ''' Checks if Kerberos Realm config exists. :return: kerberos realm object if found None if not found :rtype: object/None ''' # Make query krbrealm_info = netapp_utils.zapi.NaElement('kerberos-realm-get-iter') if realm_name is None: realm_name = self.parameters['realm'] if vserver_name is None: vserver_name = self.parameters['vserver'] query_details = netapp_utils.zapi.NaElement.create_node_with_children('kerberos-realm', **{'realm': realm_name, 'vserver-name': vserver_name}) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) krbrealm_info.add_child_elem(query) result = self.server.invoke_successfully(krbrealm_info, enable_tunneling=True) # Get Kerberos Realm details krbrealm_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') config_info = attributes_list.get_child_by_name('kerberos-realm') krbrealm_details = { 'admin_server_ip': config_info.get_child_content('admin-server-ip'), 'admin_server_port': config_info.get_child_content('admin-server-port'), 'clock_skew': config_info.get_child_content('clock-skew'), 'kdc_ip': config_info.get_child_content('kdc-ip'), 'kdc_port': config_info.get_child_content('kdc-port'), 'kdc_vendor': config_info.get_child_content('kdc-vendor'), 'pw_server_ip': config_info.get_child_content('password-server-ip'), 'pw_server_port': config_info.get_child_content('password-server-port'), 'realm': config_info.get_child_content('realm'), 'vserver': config_info.get_child_content('vserver'), } return krbrealm_details def create_krbrealm(self): '''supported Create Kerberos Realm configuration ''' options = { 'realm': self.parameters['realm'] } # Other options/attributes for attribute in self.simple_attributes: if self.parameters.get(attribute) is not None: options[str(attribute).replace('_', '-')] = self.parameters[attribute] if self.parameters.get('pw_server_ip') is not None: options['password-server-ip'] = self.parameters['pw_server_ip'] if self.parameters.get('pw_server_port') is not None: options['password-server-port'] = self.parameters['pw_server_port'] # Initialize NaElement krbrealm_create = netapp_utils.zapi.NaElement.create_node_with_children('kerberos-realm-create', **options) # Try to create Kerberos Realm configuration try: self.server.invoke_successfully(krbrealm_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as errcatch: self.module.fail_json(msg='Error creating Kerberos Realm configuration %s: %s' % (self.parameters['realm'], to_native(errcatch)), exception=traceback.format_exc()) def delete_krbrealm(self): ''' Delete Kerberos Realm configuration ''' krbrealm_delete = netapp_utils.zapi.NaElement.create_node_with_children('kerberos-realm-delete', **{'realm': self.parameters['realm']}) try: self.server.invoke_successfully(krbrealm_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as errcatch: self.module.fail_json(msg='Error deleting Kerberos Realm configuration %s: %s' % ( self.parameters['realm'], to_native(errcatch)), exception=traceback.format_exc()) def modify_krbrealm(self, modify): ''' Modify Kerberos Realm :param modify: list of modify attributes ''' krbrealm_modify = netapp_utils.zapi.NaElement('kerberos-realm-modify') krbrealm_modify.add_new_child('realm', self.parameters['realm']) for attribute in modify: if attribute in self.simple_attributes: krbrealm_modify.add_new_child(str(attribute).replace('_', '-'), self.parameters[attribute]) if attribute == 'pw_server_ip': krbrealm_modify.add_new_child('password-server-ip', self.parameters['pw_server_ip']) if attribute == 'pw_server_port': krbrealm_modify.add_new_child('password-server-port', self.parameters['pw_server_port']) # Try to modify Kerberos Realm try: self.server.invoke_successfully(krbrealm_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as errcatch: self.module.fail_json(msg='Error modifying Kerberos Realm %s: %s' % (self.parameters['realm'], to_native(errcatch)), exception=traceback.format_exc()) def apply(self): '''Call create/modify/delete operations.''' current = self.get_krbrealm() cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes(current, self.parameters) # create an ems log event for users with auto support turned on netapp_utils.ems_log_event("na_ontap_kerberos_realm", self.server) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_krbrealm() elif cd_action == 'delete': self.delete_krbrealm() elif modify: self.modify_krbrealm(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPasup(object): """Class with autosupport methods""" 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'), node_name=dict(required=True, type='str'), transport=dict(required=False, type='str', choices=['smtp', 'http', 'https']), noteto=dict(required=False, type='list'), post_url=dict(reuired=False, type='str'), support=dict(required=False, type='bool'), mail_hosts=dict(required=False, type='list'), from_address=dict(required=False, type='str'), partner_addresses=dict(required=False, type='list'), to_addresses=dict(required=False, type='list'), proxy_url=dict(required=False, type='str'), hostname_in_subject=dict(required=False, type='bool'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=False) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # present or absent requires modifying state to enabled or disabled self.parameters['service_state'] = 'started' if self.parameters[ 'state'] == 'present' else 'stopped' self.set_playbook_zapi_key_map() 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 = { 'node_name': 'node-name', 'transport': 'transport', 'post_url': 'post-url', 'from_address': 'from', 'proxy_url': 'proxy-url' } self.na_helper.zapi_list_keys = { 'noteto': ('noteto', 'mail-address'), 'mail_hosts': ('mail-hosts', 'string'), 'partner_addresses': ('partner-address', 'mail-address'), 'to_addresses': ('to', 'mail-address'), } self.na_helper.zapi_bool_keys = { 'support': 'is-support-enabled', 'hostname_in_subject': 'is-node-in-subject' } def get_autosupport_config(self): """ Invoke zapi - get current autosupport details :return: dict() """ asup_details = netapp_utils.zapi.NaElement('autosupport-config-get') asup_details.add_new_child('node-name', self.parameters['node_name']) asup_info = dict() try: result = self.server.invoke_successfully(asup_details, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='%s' % to_native(error), exception=traceback.format_exc()) # zapi invoke successful asup_attr_info = result.get_child_by_name( 'attributes').get_child_by_name('autosupport-config-info') asup_info['service_state'] = 'started' if asup_attr_info[ 'is-enabled'] == 'true' else 'stopped' for item_key, zapi_key in self.na_helper.zapi_string_keys.items(): asup_info[item_key] = asup_attr_info[zapi_key] for item_key, zapi_key in self.na_helper.zapi_bool_keys.items(): asup_info[item_key] = self.na_helper.get_value_for_bool( from_zapi=True, value=asup_attr_info[zapi_key]) for item_key, zapi_key in self.na_helper.zapi_list_keys.items(): parent, dummy = zapi_key asup_info[item_key] = self.na_helper.get_value_for_list( from_zapi=True, zapi_parent=asup_attr_info.get_child_by_name(parent)) return asup_info def modify_autosupport_config(self, modify): """ Invoke zapi - modify autosupport config @return: NaElement object / FAILURE with an error_message """ asup_details = {'node-name': self.parameters['node_name']} if modify.get('service_state'): asup_details['is-enabled'] = 'true' if modify.get( 'service_state') == 'started' else 'false' asup_config = netapp_utils.zapi.NaElement('autosupport-config-modify') for item_key in modify: if item_key in self.na_helper.zapi_string_keys: zapi_key = self.na_helper.zapi_string_keys.get(item_key) asup_details[zapi_key] = modify[item_key] elif item_key in self.na_helper.zapi_bool_keys: zapi_key = self.na_helper.zapi_bool_keys.get(item_key) asup_details[zapi_key] = self.na_helper.get_value_for_bool( from_zapi=False, value=modify[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) asup_config.add_child_elem( self.na_helper.get_value_for_list( from_zapi=False, zapi_parent=parent_key, zapi_child=child_key, data=modify.get(item_key))) asup_config.translate_struct(asup_details) try: return self.server.invoke_successfully(asup_config, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='%s' % to_native(error), exception=traceback.format_exc()) def autosupport_log(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_autosupport", cserver) def apply(self): """ Apply action to autosupport """ current = self.get_autosupport_config() modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: self.modify_autosupport_config(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapClusterHA(object): """ object initialize and class methods """ 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'), )) 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 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 modify_cluster_ha(self, configure): """ Enable or disable HA on cluster :return: None """ cluster_ha_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'cluster-ha-modify', **{'ha-configured': configure}) try: self.server.invoke_successfully(cluster_ha_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying cluster HA to %s: %s' % (configure, to_native(error)), exception=traceback.format_exc()) def get_cluster_ha_enabled(self): """ Get current cluster HA details :return: dict if enabled, None if disabled """ cluster_ha_get = netapp_utils.zapi.NaElement('cluster-ha-get') try: result = self.server.invoke_successfully(cluster_ha_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching cluster HA details', exception=traceback.format_exc()) cluster_ha_info = result.get_child_by_name( 'attributes').get_child_by_name('cluster-ha-info') if cluster_ha_info.get_child_content('ha-configured') == 'true': return {'ha-configured': True} return None def apply(self): """ Apply action to cluster HA """ 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_cluster_ha", cserver) current = self.get_cluster_ha_enabled() cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action == 'create': self.modify_cluster_ha("true") elif cd_action == 'delete': self.modify_cluster_ha("false") self.module.exit_json(changed=self.na_helper.changed)
class ElementSWClusterPair(object): """ class to handle cluster pairing operations """ def __init__(self): """ Setup Ansible parameters and ElementSW connection """ self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, choices=['present', 'absent'], default='present'), dest_mvip=dict(required=True, type='str'), dest_username=dict(required=False, type='str'), dest_password=dict(required=False, type='str', no_log=True) )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) if HAS_SF_SDK is False: self.module.fail_json(msg="Unable to import the SolidFire Python SDK") else: self.elem = netapp_utils.create_sf_connection(module=self.module) self.elementsw_helper = NaElementSWModule(self.elem) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # get element_sw_connection for destination cluster # overwrite existing source host, user and password with destination credentials self.module.params['hostname'] = self.parameters['dest_mvip'] # username and password is same as source, # if dest_username and dest_password aren't specified if self.parameters.get('dest_username'): self.module.params['username'] = self.parameters['dest_username'] if self.parameters.get('dest_password'): self.module.params['password'] = self.parameters['dest_password'] self.dest_elem = netapp_utils.create_sf_connection(module=self.module) self.dest_elementsw_helper = NaElementSWModule(self.dest_elem) def check_if_already_paired(self): """ Check for idempotency """ # src cluster and dest cluster exist paired_clusters = self.elem.list_cluster_pairs() for pair in paired_clusters.cluster_pairs: if pair.mvip == self.parameters['dest_mvip']: return pair.cluster_pair_id return None def pair_clusters(self): """ Start cluster pairing on source, and complete on target cluster """ try: pair_key = self.elem.start_cluster_pairing() self.dest_elem.complete_cluster_pairing( cluster_pairing_key=pair_key.cluster_pairing_key) except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error pairing cluster %s and %s" % (self.parameters['hostname'], self.parameters['dest_mvip']), exception=to_native(err)) def unpair_clusters(self, pair_id): """ Delete cluster pair """ try: self.elem.remove_cluster_pair(cluster_pair_id=pair_id) self.dest_elem.remove_cluster_pair(cluster_pair_id=pair_id) except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error unpairing cluster %s and %s" % (self.parameters['hostname'], self.parameters['dest_mvip']), exception=to_native(err)) def apply(self): """ Call create / delete cluster pair methods """ pair_id = self.check_if_already_paired() # calling helper to determine action cd_action = self.na_helper.get_cd_action(pair_id, self.parameters) if cd_action == "create": self.pair_clusters() elif cd_action == "delete": self.unpair_clusters(pair_id) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapBroadcastDomain(object): """ Create, Modifies and Destroys a Broadcast domain """ def __init__(self): """ Initialize the ONTAP Broadcast Domain 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'), name=dict(required=True, type='str', aliases=["broadcast_domain"]), ipspace=dict(required=False, type='str'), mtu=dict(required=False, type='str'), ports=dict(required=False, type='list'), 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) 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 get_broadcast_domain(self, broadcast_domain=None): """ Return details about the broadcast domain :param broadcast_domain: specific broadcast domain to get. :return: Details about the broadcast domain. None if not found. :rtype: dict """ if broadcast_domain is None: broadcast_domain = self.parameters['name'] domain_get_iter = netapp_utils.zapi.NaElement('net-port-broadcast-domain-get-iter') broadcast_domain_info = netapp_utils.zapi.NaElement('net-port-broadcast-domain-info') broadcast_domain_info.add_new_child('broadcast-domain', broadcast_domain) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(broadcast_domain_info) domain_get_iter.add_child_elem(query) result = self.server.invoke_successfully(domain_get_iter, True) domain_exists = None # check if broadcast_domain exists if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: domain_info = result.get_child_by_name('attributes-list').\ get_child_by_name('net-port-broadcast-domain-info') domain_name = domain_info.get_child_content('broadcast-domain') domain_mtu = domain_info.get_child_content('mtu') domain_ipspace = domain_info.get_child_content('ipspace') domain_ports = domain_info.get_child_by_name('ports') if domain_ports is not None: ports = [port.get_child_content('port') for port in domain_ports.get_children()] else: ports = [] domain_exists = { 'domain-name': domain_name, 'mtu': domain_mtu, 'ipspace': domain_ipspace, 'ports': ports } return domain_exists def create_broadcast_domain(self): """ Creates a new broadcast domain """ domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-create') domain_obj.add_new_child("broadcast-domain", self.parameters['name']) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) if self.parameters.get('mtu'): domain_obj.add_new_child("mtu", self.parameters['mtu']) if self.parameters.get('ports'): ports_obj = netapp_utils.zapi.NaElement('ports') domain_obj.add_child_elem(ports_obj) for port in self.parameters['ports']: ports_obj.add_new_child('net-qualified-port-name', port) try: self.server.invoke_successfully(domain_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating broadcast domain %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_broadcast_domain(self, broadcast_domain=None): """ Deletes a broadcast domain """ if broadcast_domain is None: broadcast_domain = self.parameters['name'] domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-destroy') domain_obj.add_new_child("broadcast-domain", broadcast_domain) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) try: self.server.invoke_successfully(domain_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting broadcast domain %s: %s' % (broadcast_domain, to_native(error)), exception=traceback.format_exc()) def modify_broadcast_domain(self): """ Modifies ipspace and mtu options of a broadcast domain """ domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-modify') domain_obj.add_new_child("broadcast-domain", self.parameters['name']) if self.parameters.get('mtu'): domain_obj.add_new_child("mtu", self.parameters['mtu']) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) try: self.server.invoke_successfully(domain_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying broadcast domain %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def split_broadcast_domain(self): """ split broadcast domain """ domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-split') domain_obj.add_new_child("broadcast-domain", self.parameters['from_name']) domain_obj.add_new_child("new-broadcast-domain", self.parameters['name']) if self.parameters.get('ports'): ports_obj = netapp_utils.zapi.NaElement('ports') domain_obj.add_child_elem(ports_obj) for port in self.parameters['ports']: ports_obj.add_new_child('net-qualified-port-name', port) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) try: self.server.invoke_successfully(domain_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error splitting broadcast domain %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) if len(self.get_broadcast_domain_ports(self.parameters['from_name'])) == 0: self.delete_broadcast_domain(self.parameters['from_name']) def modify_redirect(self, modify): """ :param modify: modify attributes. """ for attribute in modify.keys(): if attribute == 'mtu': self.modify_broadcast_domain() if attribute == 'ports': self.modify_broadcast_domain_ports() def get_modify_attributes(self, current, split): """ :param current: current state. :param split: True or False of split action. :return: list of modified attributes. """ modify = None if self.parameters['state'] == 'present': # split already handled ipspace and ports. if self.parameters.get('from_name'): current = self.get_broadcast_domain(self.parameters['from_name']) if split: modify = self.na_helper.get_modified_attributes(current, self.parameters) if modify.get('ipspace'): del modify['ipspace'] if modify.get('ports'): del modify['ports'] # ipspace can not be modified. else: modify = self.na_helper.get_modified_attributes(current, self.parameters) if modify.get('ipspace'): self.module.fail_json(msg='A domain ipspace can not be modified after the domain has been created.', exception=traceback.format_exc()) return modify def modify_broadcast_domain_ports(self): """ compare current and desire ports. Call add or remove ports methods if needed. :return: None. """ current_ports = self.get_broadcast_domain_ports() expect_ports = self.parameters['ports'] # if want to remove all ports, simply delete the broadcast domain. if len(expect_ports) == 0: self.delete_broadcast_domain() return ports_to_remove = list(set(current_ports) - set(expect_ports)) ports_to_add = list(set(expect_ports) - set(current_ports)) if len(ports_to_add) > 0: self.add_broadcast_domain_ports(ports_to_add) if len(ports_to_remove) > 0: self.delete_broadcast_domain_ports(ports_to_remove) def add_broadcast_domain_ports(self, ports): """ Creates new broadcast domain ports """ domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-add-ports') domain_obj.add_new_child("broadcast-domain", self.parameters['name']) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) if ports: ports_obj = netapp_utils.zapi.NaElement('ports') domain_obj.add_child_elem(ports_obj) for port in ports: ports_obj.add_new_child('net-qualified-port-name', port) try: self.server.invoke_successfully(domain_obj, True) return True except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating port for broadcast domain %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_broadcast_domain_ports(self, ports): """ Deletes broadcast domain ports :param: ports to be deleted. """ domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-remove-ports') domain_obj.add_new_child("broadcast-domain", self.parameters['name']) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) if ports: ports_obj = netapp_utils.zapi.NaElement('ports') domain_obj.add_child_elem(ports_obj) for port in ports: ports_obj.add_new_child('net-qualified-port-name', port) try: self.server.invoke_successfully(domain_obj, True) return True except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting port for broadcast domain %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def get_broadcast_domain_ports(self, broadcast_domain=None): """ Return details about the broadcast domain ports. :return: Details about the broadcast domain ports. None if not found. :rtype: list """ if broadcast_domain is None: broadcast_domain = self.parameters['name'] domain_get_iter = netapp_utils.zapi.NaElement('net-port-broadcast-domain-get-iter') broadcast_domain_info = netapp_utils.zapi.NaElement('net-port-broadcast-domain-info') broadcast_domain_info.add_new_child('broadcast-domain', broadcast_domain) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(broadcast_domain_info) domain_get_iter.add_child_elem(query) result = self.server.invoke_successfully(domain_get_iter, True) ports = [] if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: domain_info = result.get_child_by_name('attributes-list').get_child_by_name('net-port-broadcast-domain-info') domain_ports = domain_info.get_child_by_name('ports') if domain_ports is not None: ports = [port.get_child_content('port') for port in domain_ports.get_children()] return ports def apply(self): """ Run Module based on play book """ self.asup_log_for_cserver("na_ontap_broadcast_domain") current = self.get_broadcast_domain() cd_action, split = None, None cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action == 'create': # either create new domain or split domain. if self.parameters.get('from_name'): split = self.na_helper.is_rename_action(self.get_broadcast_domain(self.parameters['from_name']), current) if split is None: self.module.fail_json(msg='A domain can not be split if it does not exist.', exception=traceback.format_exc()) if split: cd_action = None modify = self.get_modify_attributes(current, split) if self.na_helper.changed: if self.module.check_mode: pass else: if split: self.split_broadcast_domain() if cd_action == 'create': self.create_broadcast_domain() elif cd_action == 'delete': self.delete_broadcast_domain() elif modify: self.modify_redirect(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 NetAppOntapSnapshotPolicy(object): """ Creates and deletes a Snapshot Policy """ 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"), enabled=dict(required=False, type="bool"), count=dict(required=False, type="int"), comment=dict(required=False, type="str"), schedule=dict(required=False, type="str"))) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[ ('state', 'present', ['enabled', 'count', 'schedule']), ], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) 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 get_snapshot_policy(self): """ Checks to see if a snapshot policy exists or not :return: Return policy details if a snapshot policy exists, None if it doesn't """ snapshot_obj = netapp_utils.zapi.NaElement("snapshot-policy-get-iter") # compose query query = netapp_utils.zapi.NaElement("query") snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-policy-info") snapshot_info_obj.add_new_child("policy", self.parameters['name']) query.add_child_elem(snapshot_info_obj) snapshot_obj.add_child_elem(query) try: result = self.server.invoke_successfully(snapshot_obj, True) if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: return result except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg=to_native(error), exception=traceback.format_exc()) return None def create_snapshot_policy(self): """ Creates a new snapshot policy """ # set up required variables to create a snapshot policy options = { 'policy': self.parameters['name'], 'enabled': str(self.parameters['enabled']), 'count1': str(self.parameters['count']), 'schedule1': self.parameters['schedule'] } snapshot_obj = netapp_utils.zapi.NaElement.create_node_with_children( 'snapshot-policy-create', **options) # Set up optional variables to create a snapshot policy if self.parameters.get('comment'): snapshot_obj.add_new_child("comment", self.parameters['comment']) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating snapshot policy %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_snapshot_policy(self): """ Deletes an existing snapshot policy """ snapshot_obj = netapp_utils.zapi.NaElement("snapshot-policy-delete") # Set up required variables to delete a snapshot policy snapshot_obj.add_new_child("policy", self.parameters['name']) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting snapshot policy %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): """ Check to see which play we should run """ self.asup_log_for_cserver("na_ontap_snapshot_policy") current = self.get_snapshot_policy() 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_snapshot_policy() elif cd_action == 'delete': self.delete_snapshot_policy() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapDns(object): """ Enable and Disable dns """ 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'), vserver=dict(required=True, type='str'), domains=dict(required=False, type='list'), nameservers=dict(required=False, type='list'))) 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 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 """ 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) 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): """ Destroys an already created dns :return: """ 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_dns(self): 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() ] return attrs def modify_dns(self, dns_attrs): 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: 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 netapp_utils.ems_log_event("na_ontap_dns", self.vserver) dns_attrs = self.get_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() changed = True self.module.exit_json(changed=changed)
class NetAppOntapAggregate(object): ''' object initialize and class methods ''' 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'), service_state=dict(required=False, choices=['online', 'offline']), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), disk_count=dict(required=False, type='int', default=None), disk_type=dict(required=False, choices=[ 'ATA', 'BSAS', 'FCAL', 'FSAS', 'LUN', 'MSATA', 'SAS', 'SSD', 'VMDISK' ]), raid_type=dict(required=False, type='str'), disk_size=dict(required=False, type='int'), nodes=dict(required=False, type='list'), raid_size=dict(required=False, type='int'), unmount_volumes=dict(required=False, type='bool'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[('service_state', 'offline', ['unmount_volumes'])], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) 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 aggr_get_iter(self, name): """ Return aggr-get-iter query results :param name: Name of the aggregate :return: NaElement if aggregate found, None otherwise """ aggr_get_iter = netapp_utils.zapi.NaElement('aggr-get-iter') query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-attributes', **{'aggregate-name': name}) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) aggr_get_iter.add_child_elem(query) try: result = self.server.invoke_successfully(aggr_get_iter, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: # Error 13040 denotes an aggregate not being found. if to_native(error.code) == "13040": return None else: self.module.fail_json(msg=to_native(error), exception=traceback.format_exc()) return result def get_aggr(self, name=None): """ Fetch details if aggregate exists. :param name: Name of the aggregate to be fetched :return: Dictionary of current details if aggregate found None if aggregate is not found """ if name is None: name = self.parameters['name'] aggr_get = self.aggr_get_iter(name) if (aggr_get and aggr_get.get_child_by_name('num-records') and int(aggr_get.get_child_content('num-records')) >= 1): current_aggr = dict() attr = aggr_get.get_child_by_name( 'attributes-list').get_child_by_name('aggr-attributes') current_aggr['service_state'] = attr.get_child_by_name( 'aggr-raid-attributes').get_child_content('state') return current_aggr return None def aggregate_online(self): """ Set state of an offline aggregate to online :return: None """ online_aggr = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-online', **{ 'aggregate': self.parameters['name'], 'force-online': 'true' }) try: self.server.invoke_successfully(online_aggr, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error changing the state of aggregate %s to %s: %s' % (self.parameters['name'], self.parameters['service_state'], to_native(error)), exception=traceback.format_exc()) def aggregate_offline(self): """ Set state of an online aggregate to offline :return: None """ offline_aggr = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-offline', **{ 'aggregate': self.parameters['name'], 'force-offline': 'false', 'unmount-volumes': str(self.parameters['unmount_volumes']) }) try: self.server.invoke_successfully(offline_aggr, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error changing the state of aggregate %s to %s: %s' % (self.parameters['name'], self.parameters['service_state'], to_native(error)), exception=traceback.format_exc()) def create_aggr(self): """ Create aggregate :return: None """ if not self.parameters.get('disk_count'): self.module.fail_json(msg='Error provisioning aggregate %s: \ disk_count is required' % self.parameters['name']) options = { 'aggregate': self.parameters['name'], 'disk-count': str(self.parameters['disk_count']) } if self.parameters.get('disk_type'): options['disk-type'] = self.parameters['disk_type'] if self.parameters.get('raid_size'): options['raid-size'] = str(self.parameters['raid_size']) if self.parameters.get('raid_type'): options['raid-type'] = self.parameters['raid_type'] if self.parameters.get('disk_size'): options['disk-size'] = str(self.parameters['disk_size']) aggr_create = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-create', **options) if self.parameters.get('nodes'): nodes_obj = netapp_utils.zapi.NaElement('nodes') aggr_create.add_child_elem(nodes_obj) for node in self.parameters['nodes']: nodes_obj.add_new_child('node-name', node) try: self.server.invoke_successfully(aggr_create, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error provisioning aggregate %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_aggr(self): """ Delete aggregate. :return: None """ aggr_destroy = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-destroy', **{'aggregate': self.parameters['name']}) try: self.server.invoke_successfully(aggr_destroy, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error removing aggregate %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_aggregate(self): """ Rename aggregate. """ aggr_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-rename', **{ 'aggregate': self.parameters['from_name'], 'new-aggregate-name': self.parameters['name'] }) try: self.server.invoke_successfully(aggr_rename, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error renaming aggregate %s: %s" % (self.parameters['from_name'], to_native(error)), exception=traceback.format_exc()) def modify_aggr(self, modify): """ Modify state of the aggregate :param modify: dictionary of parameters to be modified :return: None """ if modify['service_state'] == 'offline': self.aggregate_offline() elif modify['service_state'] == 'online': self.aggregate_online() 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 aggregate :return: None """ self.asup_log_for_cserver("na_ontap_aggregate") current = self.get_aggr() # 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_aggr(self.parameters['from_name']), current) if rename is None: self.module.fail_json( msg="Error renaming: aggregate %s does not exist" % 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) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_aggregate() elif cd_action == 'create': self.create_aggr() elif cd_action == 'delete': self.delete_aggr() elif modify: self.modify_aggr(modify) 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.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'), destination=dict(required=True, type='str'), gateway=dict(required=True, type='str'), metric=dict(required=False, type='str'), new_destination=dict(required=False, type='str', default=None), new_gateway=dict(required=False, type='str', default=None), new_metric=dict(required=False, type='str', 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) 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 """ 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 route_obj.add_new_child("metric", 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): """ Deletes a given Route """ route_obj = netapp_utils.zapi.NaElement('net-routes-destroy') route_obj.add_new_child("destination", self.parameters['destination']) route_obj.add_new_child("gateway", self.parameters['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 """ # return if there is nothing to change for key, val in desired.items(): if val == current[key]: self.na_helper.changed = False return # delete and re-create with new params self.delete_net_route() 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, 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 at least on of the new_destination or new_gateway to fetch desired route if params.get('destination') is None and params.get( 'gateway') is None: return None current = dict() 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) route_info = result.get_child_by_name( 'attributes').get_child_by_name('net-vs-routes-info') current['destination'] = route_info.get_child_content( 'destination') current['gateway'] = route_info.get_child_content('gateway') current['metric'] = 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 def is_modify_action(self, 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 """ # changed = False netapp_utils.ems_log_event("na_ontap_net_routes", self.server) # route_exists = False current = self.get_net_route() modify, cd_action = None, None modify_params = { 'destination': self.parameters.get('new_destination'), 'gateway': self.parameters.get('new_gateway'), 'metric': self.parameters.get('new_metric') } # if any new_* param is present in playbook, check for modify action if any(modify_params.values()): # get parameters that are eligible for modify d = self.get_net_route(modify_params) modify = self.na_helper.is_rename_action(current, d) if modify is None: self.module.fail_json( msg="Error modifying: route %s does not exist" % self.parameters['destination']) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action == 'create': self.create_net_route() elif cd_action == 'delete': self.delete_net_route() elif modify: self.modify_net_route(current, modify_params) self.module.exit_json(changed=self.na_helper.changed)