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'), from_destination=dict(required=False, type='str', default=None), from_gateway=dict(required=False, type='str', default=None), from_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 # Metric can be None, Can't set metric to none if metric is not None: 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, params=None): """ Deletes a given Route """ route_obj = netapp_utils.zapi.NaElement('net-routes-destroy') if params is None: params = self.parameters route_obj.add_new_child("destination", params['destination']) route_obj.add_new_child("gateway", params['gateway']) try: self.server.invoke_successfully(route_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting net route: %s' % (to_native(error)), exception=traceback.format_exc()) def modify_net_route(self, current, desired): """ Modify a net route """ # return if there is nothing to change for key, val in desired.items(): if val != current[key]: self.na_helper.changed = True break if not self.na_helper.changed: return # delete and re-create with new params self.delete_net_route(current) route_obj = netapp_utils.zapi.NaElement('net-routes-create') for attribute in ['metric', 'destination', 'gateway']: if desired.get(attribute) is not None: value = desired[attribute] else: value = current[attribute] route_obj.add_new_child(attribute, 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 = None route_obj = netapp_utils.zapi.NaElement('net-routes-get') for attr in ['destination', 'gateway']: if params and params.get(attr) is not None: value = params[attr] else: value = self.parameters[attr] route_obj.add_new_child(attr, value) try: result = self.server.invoke_successfully(route_obj, True) if result.get_child_by_name('attributes') is not None: route_info = result.get_child_by_name( 'attributes').get_child_by_name('net-vs-routes-info') current = { 'destination': route_info.get_child_content('destination'), 'gateway': route_info.get_child_content('gateway'), 'metric': 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 """ netapp_utils.ems_log_event("na_ontap_net_routes", self.server) current = self.get_net_route() modify, cd_action = None, None modify_params = { 'destination': self.parameters.get('from_destination'), 'gateway': self.parameters.get('from_gateway'), 'metric': self.parameters.get('from_metric') } # if any from_* param is present in playbook, check for modify action if any(modify_params.values()): # destination and gateway combination is unique, and is considered like a id. so modify destination # or gateway is considered a rename action. metric is considered an attribute of the route so it is # considered as modify. if modify_params.get('metric') is not None: modify = True old_params = current else: # get parameters that are eligible for modify old_params = self.get_net_route(modify_params) modify = self.na_helper.is_rename_action(old_params, current) if modify is None: self.module.fail_json( msg="Error modifying: route %s does not exist" % self.parameters['from_destination']) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action == 'create': self.create_net_route() elif cd_action == 'delete': self.delete_net_route() elif modify: desired = {} for key, value in old_params.items(): desired[key] = value for key, value in modify_params.items(): if value is not None: desired[key] = self.parameters.get(key) self.modify_net_route(old_params, desired) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapVolume(object): '''Class with volume operations''' def __init__(self): '''Initialize module parameters''' self._size_unit_map = dict( bytes=1, b=1, kb=1024, mb=1024 ** 2, gb=1024 ** 3, tb=1024 ** 4, pb=1024 ** 5, eb=1024 ** 6, zb=1024 ** 7, yb=1024 ** 8 ) 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'), vserver=dict(required=True, type='str'), from_name=dict(required=False, type='str'), is_infinite=dict(required=False, type='bool', default=False), is_online=dict(required=False, type='bool', default=True), size=dict(type='int', default=None), size_unit=dict(default='gb', choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb'], type='str'), aggregate_name=dict(type='str', default=None), type=dict(type='str', default=None), policy=dict(type='str', default=None), junction_path=dict(type='str', default=None), space_guarantee=dict(choices=['none', 'volume'], default=None), percent_snapshot_space=dict(type='str', default=None), volume_security_style=dict(choices=['mixed', 'ntfs', 'unified', 'unix'], default='mixed'), encrypt=dict(required=False, type='bool', default=False), efficiency_policy=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 self.parameters.get('size'): self.parameters['size'] = self.parameters['size'] * \ self._size_unit_map[self.parameters['size_unit']] 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.cluster = netapp_utils.setup_na_ontap_zapi(module=self.module) def volume_get_iter(self, vol_name=None): """ Return volume-get-iter query results :param vol_name: name of the volume :return: NaElement """ 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', vol_name) 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.server.invoke_successfully(volume_info, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching volume %s : %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) return result def get_volume(self, vol_name=None): """ Return details about the volume :param: name : Name of the volume :return: Details about the volume. None if not found. :rtype: dict """ if vol_name is None: vol_name = self.parameters['name'] volume_get_iter = self.volume_get_iter(vol_name) return_value = None if volume_get_iter.get_child_by_name('num-records') and \ int(volume_get_iter.get_child_content('num-records')) > 0: volume_attributes = volume_get_iter.get_child_by_name( 'attributes-list').get_child_by_name( 'volume-attributes') # Get volume's current size volume_space_attributes = volume_attributes.get_child_by_name( 'volume-space-attributes') current_size = int(volume_space_attributes.get_child_content('size')) # Get volume's state (online/offline) volume_state_attributes = volume_attributes.get_child_by_name( 'volume-state-attributes') current_state = volume_state_attributes.get_child_content('state') volume_id_attributes = volume_attributes.get_child_by_name( 'volume-id-attributes') aggregate_name = volume_id_attributes.get_child_content( 'containing-aggregate-name') volume_export_attributes = volume_attributes.get_child_by_name( 'volume-export-attributes') policy = volume_export_attributes.get_child_content('policy') space_guarantee = volume_space_attributes.get_child_content( 'space-guarantee') is_online = (current_state == "online") return_value = { 'name': vol_name, 'size': current_size, 'is_online': is_online, 'aggregate_name': aggregate_name, 'policy': policy, 'space_guarantee': space_guarantee, } return return_value def create_volume(self): '''Create ONTAP volume''' if self.parameters.get('aggregate_name') is None: self.module.fail_json(msg='Error provisioning volume %s: \ aggregate_name is required' % self.parameters['name']) options = {'volume': self.parameters['name'], 'containing-aggr-name': self.parameters['aggregate_name'], 'size': str(self.parameters['size'])} if self.parameters.get('percent_snapshot_space'): options['percentage-snapshot-reserve'] = self.parameters['percent_snapshot_space'] if self.parameters.get('type'): options['volume-type'] = self.parameters['type'] if self.parameters.get('policy'): options['export-policy'] = self.parameters['policy'] if self.parameters.get('junction_path'): options['junction-path'] = self.parameters['junction_path'] if self.parameters.get('space_guarantee'): options['space-reserve'] = self.parameters['space_guarantee'] if self.parameters.get('volume_security_style'): options['volume-security-style'] = self.parameters['volume_security_style'] volume_create = netapp_utils.zapi.NaElement.create_node_with_children('volume-create', **options) try: self.server.invoke_successfully(volume_create, enable_tunneling=True) self.ems_log_event("volume-create") except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error provisioning volume %s \ of size %s: %s' % (self.parameters['name'], self.parameters['size'], to_native(error)), exception=traceback.format_exc()) def delete_volume(self): '''Delete ONTAP volume''' if self.parameters.get('is_infinite'): volume_delete = netapp_utils.zapi\ .NaElement.create_node_with_children( 'volume-destroy-async', **{'volume-name': self.parameters['name']}) else: volume_delete = netapp_utils.zapi\ .NaElement.create_node_with_children( 'volume-destroy', **{'name': self.parameters['name'], 'unmount-and-offline': 'true'}) try: self.server.invoke_successfully(volume_delete, enable_tunneling=True) self.ems_log_event("delete") except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting volume %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def move_volume(self): '''Move volume from source aggregate to destination aggregate''' volume_move = netapp_utils.zapi.NaElement.create_node_with_children( 'volume-move-start', **{'source-volume': self.parameters['name'], 'vserver': self.parameters['vserver'], 'dest-aggr': self.parameters['aggregate_name']}) try: self.cluster.invoke_successfully(volume_move, enable_tunneling=True) self.ems_log_event("volume-move") except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error moving volume %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_volume(self): """ Rename the volume. Note: 'is_infinite' needs to be set to True in order to rename an Infinite Volume. """ vol_rename_zapi, vol_name_zapi = ['volume-rename-async', 'volume-name'] if self.parameters['is_infinite']\ else ['volume-rename', 'volume'] volume_rename = netapp_utils.zapi.NaElement.create_node_with_children( vol_rename_zapi, **{vol_name_zapi: self.parameters['from_name'], 'new-volume-name': str(self.parameters['name'])}) try: self.server.invoke_successfully(volume_rename, enable_tunneling=True) self.ems_log_event("volume-rename") except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error renaming volume %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def resize_volume(self): """ Re-size the volume. Note: 'is_infinite' needs to be set to True in order to rename an Infinite Volume. """ vol_size_zapi, vol_name_zapi = ['volume-size-async', 'volume-name'] if self.parameters['is_infinite']\ else ['volume-size', 'volume'] volume_resize = netapp_utils.zapi.NaElement.create_node_with_children( vol_size_zapi, **{vol_name_zapi: self.parameters['name'], 'new-size': str(self.parameters['size'])}) try: self.server.invoke_successfully(volume_resize, enable_tunneling=True) self.ems_log_event("volume-resize") except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error re-sizing volume %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def change_volume_state(self): """ Change volume's state (offline/online). """ if self.parameters['is_online']: # Desired state is online, setup zapi APIs respectively vol_state_zapi, vol_name_zapi = ['volume-online-async', 'volume-name'] if self.parameters['is_infinite']\ else ['volume-online', 'name'] else: # Desired state is offline, setup zapi APIs respectively vol_state_zapi, vol_name_zapi = ['volume-offline-async', 'volume-name'] if self.parameters['is_infinite']\ else ['volume-offline', 'name'] volume_unmount = netapp_utils.zapi.NaElement.create_node_with_children( 'volume-unmount', **{'volume-name': self.parameters['name']}) volume_change_state = netapp_utils.zapi.NaElement.create_node_with_children( vol_state_zapi, **{vol_name_zapi: self.parameters['name']}) try: if not self.parameters['is_online']: # Unmount before offline self.server.invoke_successfully(volume_unmount, enable_tunneling=True) self.server.invoke_successfully(volume_change_state, enable_tunneling=True) self.ems_log_event("change-state") except netapp_utils.zapi.NaApiError as error: state = "online" if self.parameters['is_online'] else "offline" self.module.fail_json(msg='Error changing the state of volume %s to %s: %s' % (self.parameters['name'], state, to_native(error)), exception=traceback.format_exc()) def volume_modify_policy_space(self): """ modify volume parameter 'policy' or 'space_guarantee' """ # TODO: refactor this method vol_mod_iter = netapp_utils.zapi.NaElement('volume-modify-iter') attributes = netapp_utils.zapi.NaElement('attributes') vol_mod_attributes = netapp_utils.zapi.NaElement('volume-attributes') if self.parameters.get('policy'): vol_export_attributes = netapp_utils.zapi.NaElement( 'volume-export-attributes') vol_export_attributes.add_new_child('policy', self.parameters['policy']) vol_mod_attributes.add_child_elem(vol_export_attributes) if self.parameters.get('space_guarantee'): vol_space_attributes = netapp_utils.zapi.NaElement( 'volume-space-attributes') vol_space_attributes.add_new_child( 'space-guarantee', self.parameters['space_guarantee']) vol_mod_attributes.add_child_elem(vol_space_attributes) attributes.add_child_elem(vol_mod_attributes) query = netapp_utils.zapi.NaElement('query') vol_query_attributes = netapp_utils.zapi.NaElement('volume-attributes') vol_id_attributes = netapp_utils.zapi.NaElement('volume-id-attributes') vol_id_attributes.add_new_child('name', self.parameters['name']) vol_query_attributes.add_child_elem(vol_id_attributes) query.add_child_elem(vol_query_attributes) vol_mod_iter.add_child_elem(attributes) vol_mod_iter.add_child_elem(query) try: result = self.server.invoke_successfully(vol_mod_iter, enable_tunneling=True) failures = result.get_child_by_name('failure-list') # handle error if modify space or policy parameter fails if failures is not None and failures.get_child_by_name('volume-modify-iter-info') is not None: error_msg = failures.get_child_by_name('volume-modify-iter-info').get_child_content('error-message') self.module.fail_json(msg="Error modifying volume %s: %s" % (self.parameters['name'], error_msg), exception=traceback.format_exc()) self.ems_log_event("volume-modify") except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying volume %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def modify_volume(self, modify): for attribute in modify.keys(): if attribute == 'size': self.resize_volume() elif attribute == 'is_online': self.change_volume_state() elif attribute == 'aggregate_name': self.move_volume() else: self.volume_modify_policy_space() def apply(self): '''Call create/modify/delete operations''' current = self.get_volume() # 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_volume(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_volume() if cd_action == 'create': self.create_volume() elif cd_action == 'delete': self.delete_volume() elif modify: self.modify_volume(modify) self.module.exit_json(changed=self.na_helper.changed) def ems_log_event(self, state): '''Autosupport log event''' if state == 'create': message = "A Volume has been created, size: " + \ str(self.parameters['size']) + str(self.parameters['size_unit']) elif state == 'delete': message = "A Volume has been deleted" elif state == 'move': message = "A Volume has been moved" elif state == 'rename': message = "A Volume has been renamed" elif state == 'resize': message = "A Volume has been resized to: " + \ str(self.parameters['size']) + str(self.parameters['size_unit']) elif state == 'change': message = "A Volume state has been changed" else: message = "na_ontap_volume has been called" netapp_utils.ems_log_event( "na_ontap_volume", self.server, event=message)
class NetAppOntapIgroup(object): def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str', default=None), ostype=dict(required=False, type='str'), initiator_group_type=dict(required=False, type='str', choices=['fcp', 'iscsi', 'mixed']), initiators=dict(required=False, type='list', aliases=['initiator']), vserver=dict(required=True, type='str'), force_remove_initiator=dict(required=False, type='bool', default=False), bind_portset=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, vserver=self.parameters['vserver']) def get_igroup(self, name): """ Return details about the igroup :param: name : Name of the igroup :return: Details about the igroup. None if not found. :rtype: dict """ igroup_info = netapp_utils.zapi.NaElement('igroup-get-iter') attributes = dict( query={ 'initiator-group-info': { 'initiator-group-name': name, 'vserver': self.parameters['vserver'] } }) igroup_info.translate_struct(attributes) result, current = None, None try: result = self.server.invoke_successfully(igroup_info, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching igroup info %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) >= 1: igroup = result.get_child_by_name( 'attributes-list').get_child_by_name('initiator-group-info') initiators = [] if igroup.get_child_by_name('initiators'): current_initiators = igroup['initiators'].get_children() for initiator in current_initiators: initiators.append(initiator['initiator-name']) current = {'initiators': initiators} return current def add_initiators(self): """ Add the list of initiators to igroup :return: None """ # don't add if initiators is empty string if self.parameters.get('initiators') == [ '' ] or self.parameters.get('initiators') is None: return for initiator in self.parameters['initiators']: self.modify_initiator(initiator, 'igroup-add') def remove_initiators(self, initiators): """ Removes all existing initiators from igroup :return: None """ for initiator in initiators: self.modify_initiator(initiator, 'igroup-remove') def modify_initiator(self, initiator, zapi): """ Add or remove an initiator to/from an igroup """ initiator.strip( ) # remove leading spaces if any (eg: if user types a space after comma in initiators list) options = { 'initiator-group-name': self.parameters['name'], 'initiator': initiator } igroup_modify = netapp_utils.zapi.NaElement.create_node_with_children( zapi, **options) try: self.server.invoke_successfully(igroup_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying igroup initiator %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def create_igroup(self): """ Create the igroup. """ options = {'initiator-group-name': self.parameters['name']} if self.parameters.get('ostype') is not None: options['os-type'] = self.parameters['ostype'] if self.parameters.get('initiator_group_type') is not None: options['initiator-group-type'] = self.parameters[ 'initiator_group_type'] if self.parameters.get('bind_portset') is not None: options['bind-portset'] = self.parameters['bind_portset'] igroup_create = netapp_utils.zapi.NaElement.create_node_with_children( 'igroup-create', **options) try: self.server.invoke_successfully(igroup_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error provisioning igroup %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) self.add_initiators() def delete_igroup(self): """ Delete the igroup. """ igroup_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'igroup-destroy', **{ 'initiator-group-name': self.parameters['name'], 'force': 'true' if self.parameters['force_remove_initiator'] else 'false' }) try: self.server.invoke_successfully(igroup_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting igroup %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_igroup(self): """ Rename the igroup. """ igroup_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'igroup-rename', **{ 'initiator-group-name': self.parameters['from_name'], 'initiator-group-new-name': str(self.parameters['name']) }) try: self.server.invoke_successfully(igroup_rename, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error renaming igroup %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def autosupport_log(self): netapp_utils.ems_log_event("na_ontap_igroup", self.server) def apply(self): self.autosupport_log() current = self.get_igroup(self.parameters['name']) # rename and create are mutually exclusive rename, cd_action, modify = None, None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action( self.get_igroup(self.parameters['from_name']), current) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_igroup() elif cd_action == 'create': self.create_igroup() elif cd_action == 'delete': self.delete_igroup() if modify: self.remove_initiators(current['initiators']) self.add_initiators() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapQosPolicyGroup(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'), max_throughput=dict(required=False, type='str'), min_throughput=dict(required=False, type='str'), 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-policy-group-get-iter') policy_group_info = netapp_utils.zapi.NaElement( 'qos-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-policy-group-info') policy_group_detail = { 'name': policy_info.get_child_content('policy-group'), 'vserver': policy_info.get_child_content('vserver'), 'max_throughput': policy_info.get_child_content('max-throughput'), 'min_throughput': policy_info.get_child_content('min-throughput') } return policy_group_detail def create_policy_group(self): """ create a policy group name. """ policy_group = netapp_utils.zapi.NaElement('qos-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('max_throughput'): policy_group.add_new_child('max-throughput', self.parameters['max_throughput']) if self.parameters.get('min_throughput'): policy_group.add_new_child('min-throughput', self.parameters['min_throughput']) try: self.server.invoke_successfully(policy_group, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating 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-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 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-policy-group-modify') policy_group_obj.add_new_child('policy-group', self.parameters['name']) if self.parameters.get('max_throughput'): policy_group_obj.add_new_child('max-throughput', self.parameters['max_throughput']) if self.parameters.get('min_throughput'): policy_group_obj.add_new_child('min-throughput', self.parameters['min_throughput']) try: self.server.invoke_successfully(policy_group_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying 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-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 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 ['max_throughput', 'min_throughput']: self.modify_policy_group() def apply(self): """ Run module based on playbook """ self.asup_log_for_cserver("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 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 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 broadcas 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 NetAppOntapSnapshot(object): """ Creates, modifies, and deletes a Snapshot """ 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'), from_name=dict(required=False, type='str'), snapshot=dict(required=True, type="str"), volume=dict(required=True, type="str"), async_bool=dict(required=False, type="bool", default=False), comment=dict(required=False, type="str"), snapmirror_label=dict(required=False, type="str"), ignore_owners=dict(required=False, type="bool", default=False), snapshot_instance_uuid=dict(required=False, type="str"), vserver=dict(required=True, type="str"), )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) 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 get_snapshot(self, snapshot_name=None): """ Checks to see if a snapshot exists or not :return: Return True if a snapshot exists, False if it doesn't """ if snapshot_name is None: snapshot_name = self.parameters['snapshot'] snapshot_obj = netapp_utils.zapi.NaElement("snapshot-get-iter") desired_attr = netapp_utils.zapi.NaElement("desired-attributes") snapshot_info = netapp_utils.zapi.NaElement('snapshot-info') comment = netapp_utils.zapi.NaElement('comment') snapmirror_label = netapp_utils.zapi.NaElement('snapmirror-label') # add more desired attributes that are allowed to be modified snapshot_info.add_child_elem(comment) snapshot_info.add_child_elem(snapmirror_label) desired_attr.add_child_elem(snapshot_info) snapshot_obj.add_child_elem(desired_attr) # compose query query = netapp_utils.zapi.NaElement("query") snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info") snapshot_info_obj.add_new_child("name", snapshot_name) snapshot_info_obj.add_new_child("volume", self.parameters['volume']) snapshot_info_obj.add_new_child("vserver", self.parameters['vserver']) query.add_child_elem(snapshot_info_obj) snapshot_obj.add_child_elem(query) result = self.server.invoke_successfully(snapshot_obj, True) return_value = 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') snap_info = attributes_list.get_child_by_name('snapshot-info') return_value = {'comment': snap_info.get_child_content('comment')} if snap_info.get_child_by_name('snapmirror-label'): return_value['snapmirror_label'] = snap_info.get_child_content('snapmirror-label') else: return_value['snapmirror_label'] = None return return_value def create_snapshot(self): """ Creates a new snapshot """ snapshot_obj = netapp_utils.zapi.NaElement("snapshot-create") # set up required variables to create a snapshot snapshot_obj.add_new_child("snapshot", self.parameters['snapshot']) snapshot_obj.add_new_child("volume", self.parameters['volume']) # Set up optional variables to create a snapshot if self.parameters.get('async_bool'): snapshot_obj.add_new_child("async", str(self.parameters['async_bool'])) if self.parameters.get('comment'): snapshot_obj.add_new_child("comment", self.parameters['comment']) if self.parameters.get('snapmirror_label'): snapshot_obj.add_new_child( "snapmirror-label", self.parameters['snapmirror_label']) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating snapshot %s: %s' % (self.parameters['snapshot'], to_native(error)), exception=traceback.format_exc()) def delete_snapshot(self): """ Deletes an existing snapshot """ snapshot_obj = netapp_utils.zapi.NaElement("snapshot-delete") # Set up required variables to delete a snapshot snapshot_obj.add_new_child("snapshot", self.parameters['snapshot']) snapshot_obj.add_new_child("volume", self.parameters['volume']) # set up optional variables to delete a snapshot if self.parameters.get('ignore_owners'): snapshot_obj.add_new_child("ignore-owners", str(self.parameters['ignore_owners'])) if self.parameters.get('snapshot_instance_uuid'): snapshot_obj.add_new_child("snapshot-instance-uuid", self.parameters['snapshot_instance_uuid']) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting snapshot %s: %s' % (self.parameters['snapshot'], to_native(error)), exception=traceback.format_exc()) def modify_snapshot(self): """ Modify an existing snapshot :return: """ snapshot_obj = netapp_utils.zapi.NaElement("snapshot-modify-iter") # Create query object, this is the existing object query = netapp_utils.zapi.NaElement("query") snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info") snapshot_info_obj.add_new_child("name", self.parameters['snapshot']) snapshot_info_obj.add_new_child("vserver", self.parameters['vserver']) query.add_child_elem(snapshot_info_obj) snapshot_obj.add_child_elem(query) # this is what we want to modify in the snapshot object attributes = netapp_utils.zapi.NaElement("attributes") snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info") snapshot_info_obj.add_new_child("name", self.parameters['snapshot']) if self.parameters.get('comment'): snapshot_info_obj.add_new_child("comment", self.parameters['comment']) if self.parameters.get('snapmirror_label'): snapshot_info_obj.add_new_child("snapmirror-label", self.parameters['snapmirror_label']) attributes.add_child_elem(snapshot_info_obj) snapshot_obj.add_child_elem(attributes) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying snapshot %s: %s' % (self.parameters['snapshot'], to_native(error)), exception=traceback.format_exc()) def rename_snapshot(self): """ Rename the snapshot """ snapshot_obj = netapp_utils.zapi.NaElement("snapshot-rename") # set up required variables to rename a snapshot snapshot_obj.add_new_child("current-name", self.parameters['from_name']) snapshot_obj.add_new_child("new-name", self.parameters['snapshot']) snapshot_obj.add_new_child("volume", self.parameters['volume']) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error renaming snapshot %s to %s: %s' % (self.parameters['from_name'], self.parameters['snapshot'], to_native(error)), exception=traceback.format_exc()) def apply(self): """ Check to see which play we should run """ current = self.get_snapshot() netapp_utils.ems_log_event("na_ontap_snapshot", self.server) rename, cd_action = None, None modify = {} if self.parameters.get('from_name'): current_old_name = self.get_snapshot(self.parameters['from_name']) rename = self.na_helper.is_rename_action(current_old_name, current) modify = self.na_helper.get_modified_attributes(current_old_name, self.parameters) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None: 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_snapshot() if cd_action == 'create': self.create_snapshot() elif cd_action == 'delete': self.delete_snapshot() elif modify: self.modify_snapshot() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapSVM(object): def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), root_volume=dict(type='str'), root_volume_aggregate=dict(type='str'), root_volume_security_style=dict( type='str', choices=['unix', 'ntfs', 'mixed', 'unified']), allowed_protocols=dict(type='list'), aggr_list=dict(type='list'), ipspace=dict(type='str', required=False), snapshot_policy=dict(type='str', required=False), language=dict(type='str', required=False), subtype=dict(choices=[ 'default', 'dp_destination', 'sync_source', 'sync_destination' ]), comment=dict(type="str", required=False))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) 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_vserver(self, vserver_name=None): """ Checks if vserver exists. :return: vserver object if vserver found None if vserver is not found :rtype: object/None """ if vserver_name is None: vserver_name = self.parameters['name'] vserver_info = netapp_utils.zapi.NaElement('vserver-get-iter') query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-info', **{'vserver-name': vserver_name}) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) vserver_info.add_child_elem(query) result = self.server.invoke_successfully(vserver_info, enable_tunneling=False) vserver_details = None if (result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1): attributes_list = result.get_child_by_name('attributes-list') vserver_info = attributes_list.get_child_by_name('vserver-info') aggr_list = list() ''' vserver aggr-list can be empty by default''' get_list = vserver_info.get_child_by_name('aggr-list') if get_list is not None: aggregates = get_list.get_children() for aggr in aggregates: aggr_list.append(aggr.get_content()) protocols = list() '''allowed-protocols is not empty for data SVM, but is for node SVM''' allowed_protocols = vserver_info.get_child_by_name( 'allowed-protocols') if allowed_protocols is not None: get_protocols = allowed_protocols.get_children() for protocol in get_protocols: protocols.append(protocol.get_content()) vserver_details = { 'name': vserver_info.get_child_content('vserver-name'), 'root_volume': vserver_info.get_child_content('root-volume'), 'root_volume_aggregate': vserver_info.get_child_content('root-volume-aggregate'), 'root_volume_security_style': vserver_info.get_child_content('root-volume-security-style'), 'subtype': vserver_info.get_child_content('vserver-subtype'), 'aggr_list': aggr_list, 'language': vserver_info.get_child_content('language'), 'snapshot_policy': vserver_info.get_child_content('snapshot-policy'), 'allowed_protocols': protocols, 'ipspace': vserver_info.get_child_content('ipspace'), 'comment': vserver_info.get_child_content('comment') } return vserver_details def create_vserver(self): options = {'vserver-name': self.parameters['name']} self.add_parameter_to_dict(options, 'root_volume', 'root-volume') self.add_parameter_to_dict(options, 'root_volume_aggregate', 'root-volume-aggregate') self.add_parameter_to_dict(options, 'root_volume_security_style', 'root-volume-security-style') self.add_parameter_to_dict(options, 'language', 'language') self.add_parameter_to_dict(options, 'ipspace', 'ipspace') self.add_parameter_to_dict(options, 'snapshot_policy', 'snapshot-policy') self.add_parameter_to_dict(options, 'subtype', 'vserver-subtype') self.add_parameter_to_dict(options, 'comment', 'comment') vserver_create = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-create', **options) try: self.server.invoke_successfully(vserver_create, enable_tunneling=False) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error provisioning SVM %s: %s' % (self.parameters['name'], to_native(e)), exception=traceback.format_exc()) # add allowed-protocols after creation, since vserver-create doesn't allow this attribute during creation if self.parameters.get('allowed_protocols'): self.modify_vserver( {'allowed_protocols': self.parameters['allowed_protocols']}) def delete_vserver(self): vserver_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-destroy', **{'vserver-name': self.parameters['name']}) try: self.server.invoke_successfully(vserver_delete, enable_tunneling=False) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error deleting SVM %s: %s' % (self.parameters['name'], to_native(e)), exception=traceback.format_exc()) def rename_vserver(self): vserver_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-rename', **{ 'vserver-name': self.parameters['from_name'], 'new-name': self.parameters['name'] }) try: self.server.invoke_successfully(vserver_rename, enable_tunneling=False) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error renaming SVM %s: %s' % (self.parameters['from_name'], to_native(e)), exception=traceback.format_exc()) def modify_vserver(self, modify): ''' Modify vserver. :param modify: list of modify attributes ''' vserver_modify = netapp_utils.zapi.NaElement('vserver-modify') vserver_modify.add_new_child('vserver-name', self.parameters['name']) for attribute in modify: if attribute == 'language': vserver_modify.add_new_child('language', self.parameters['language']) if attribute == 'snapshot_policy': vserver_modify.add_new_child( 'snapshot_policy', self.parameters['snapshot_policy']) if attribute == 'comment': vserver_modify.add_new_child('comment', self.parameters['comment']) if attribute == 'allowed_protocols': allowed_protocols = netapp_utils.zapi.NaElement( 'allowed-protocols') for protocol in self.parameters['allowed_protocols']: allowed_protocols.add_new_child('protocol', protocol) vserver_modify.add_child_elem(allowed_protocols) if attribute == 'aggr_list': aggregates = netapp_utils.zapi.NaElement('aggr-list') for aggr in self.parameters['aggr_list']: aggregates.add_new_child('aggr-name', aggr) vserver_modify.add_child_elem(aggregates) try: self.server.invoke_successfully(vserver_modify, enable_tunneling=False) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error modifying SVM %s: %s' % (self.parameters['name'], to_native(e)), exception=traceback.format_exc()) def add_parameter_to_dict(self, adict, name, key=None, tostr=False): ''' add defined parameter (not None) to adict using key. :param adict: a dictionary. :param name: name in self.parameters. :param key: key in adict. :param tostr: boolean. ''' if key is None: key = name if self.parameters.get(name) is not None: if tostr: adict[key] = str(self.parameters.get(name)) else: adict[key] = self.parameters.get(name) def apply(self): '''Call create/modify/delete operations.''' self.asup_log_for_cserver("na_ontap_svm") current = self.get_vserver() cd_action, rename = None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action( self.get_vserver(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) for attribute in modify: if attribute in [ 'root_volume', 'root_volume_aggregate', 'root_volume_security_style', 'subtype', 'ipspace' ]: self.module.fail_json( msg='Error modifying SVM %s: can not modify %s.' % (self.parameters['name'], attribute)) if attribute == 'language': # Ontap documentation uses C.UTF-8, but actually stores as c.utf_8. if self.parameters['language'].lower() == 'c.utf-8': self.parameters['language'] = 'c.utf_8' if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_vserver() # If rename is True, cd_action is None, but modify could be true or false. if cd_action == 'create': self.create_vserver() elif cd_action == 'delete': self.delete_vserver() elif modify: self.modify_vserver(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 NetAppOntapVolume(object): '''Class with volume operations''' def __init__(self): '''Initialize module parameters''' self._size_unit_map = dict(bytes=1, b=1, kb=1024, mb=1024**2, gb=1024**3, tb=1024**4, pb=1024**5, eb=1024**6, zb=1024**7, yb=1024**8) 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'), vserver=dict(required=True, type='str'), from_name=dict(required=False, type='str'), is_infinite=dict(required=False, type='bool', default=False), is_online=dict(required=False, type='bool', default=True), size=dict(type='int', default=None), size_unit=dict(default='gb', choices=[ 'bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb' ], type='str'), aggregate_name=dict(type='str', default=None), type=dict(type='str', default=None), policy=dict(type='str', default=None), junction_path=dict(type='str', default=None), space_guarantee=dict(choices=['none', 'file', 'volume'], default=None), percent_snapshot_space=dict(type='int', default=None), volume_security_style=dict( choices=['mixed', 'ntfs', 'unified', 'unix'], default='mixed'), encrypt=dict(required=False, type='bool', default=False), efficiency_policy=dict(required=False, type='str'), unix_permissions=dict(required=False, type='str'), snapshot_policy=dict(required=False, type='str'), aggr_list=dict(required=False, type='list'), aggr_list_multiplier=dict(required=False, type='int'), snapdir_access=dict(required=False, type='bool'), atime_update=dict(required=False, type='bool'), auto_provision_as=dict(choices=['flexgroup'], required=False, type='str'), wait_for_completion=dict(required=False, type='bool', default=False), time_out=dict(required=False, type='int', default=180), language=dict(type='str', required=False))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.volume_style = None if self.parameters.get('size'): self.parameters['size'] = self.parameters['size'] * \ self._size_unit_map[self.parameters['size_unit']] # ONTAP will return True and False as the string true and false. if 'snapdir_access' in self.parameters: self.parameters['snapdir_access'] = str( self.parameters['snapdir_access']).lower() if 'atime_update' in self.parameters: self.parameters['atime_update'] = str( self.parameters['atime_update']).lower() 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.cluster = netapp_utils.setup_na_ontap_zapi(module=self.module) def volume_get_iter(self, vol_name=None): """ Return volume-get-iter query results :param vol_name: name of the volume :return: NaElement """ 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', vol_name) 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.server.invoke_successfully(volume_info, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching volume %s : %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) return result def get_volume(self, vol_name=None): """ Return details about the volume :param: name : Name of the volume :return: Details about the volume. None if not found. :rtype: dict """ if vol_name is None: vol_name = self.parameters['name'] volume_get_iter = self.volume_get_iter(vol_name) return_value = None if volume_get_iter.get_child_by_name('num-records') and \ int(volume_get_iter.get_child_content('num-records')) > 0: volume_attributes = volume_get_iter['attributes-list'][ 'volume-attributes'] volume_space_attributes = volume_attributes[ 'volume-space-attributes'] volume_state_attributes = volume_attributes[ 'volume-state-attributes'] volume_id_attributes = volume_attributes['volume-id-attributes'] volume_export_attributes = volume_attributes[ 'volume-export-attributes'] volume_security_unix_attributes = volume_attributes[ 'volume-security-attributes'][ 'volume-security-unix-attributes'] volume_snapshot_attributes = volume_attributes[ 'volume-snapshot-attributes'] volume_performance_attributes = volume_attributes[ 'volume-performance-attributes'] # Get volume's state (online/offline) current_state = volume_state_attributes['state'] is_online = (current_state == "online") return_value = { 'name': vol_name, 'size': int(volume_space_attributes['size']), 'is_online': is_online, 'policy': volume_export_attributes['policy'], 'unix_permissions': volume_security_unix_attributes['permissions'], 'snapshot_policy': volume_snapshot_attributes['snapshot-policy'] } if volume_space_attributes.get_child_by_name( 'percentage-snapshot-reserve'): return_value[ 'percent_snapshot_space'] = volume_space_attributes[ 'percentage-snapshot-reserve'] if volume_id_attributes.get_child_by_name( 'containing-aggregate-name'): return_value['aggregate_name'] = volume_id_attributes[ 'containing-aggregate-name'] else: return_value['aggregate_name'] = None if volume_id_attributes.get_child_by_name('junction-path'): return_value['junction_path'] = volume_id_attributes[ 'junction-path'] else: return_value['junction_path'] = '' if volume_id_attributes.get_child_by_name('style-extended'): return_value['style_extended'] = volume_id_attributes[ 'style-extended'] else: return_value['style_extended'] = None if volume_space_attributes.get_child_by_name('space-guarantee'): return_value['space_guarantee'] = volume_space_attributes[ 'space-guarantee'] else: return_value['space_guarantee'] = None if volume_snapshot_attributes.get_child_by_name( 'snapdir-access-enabled'): return_value['snapdir_access'] = volume_snapshot_attributes[ 'snapdir-access-enabled'] else: return_value['snapdir_access'] = None if volume_performance_attributes.get_child_by_name( 'is-atime-update-enabled'): return_value['atime_update'] = volume_performance_attributes[ 'is-atime-update-enabled'] else: return_value['atime_update'] = None return return_value def create_volume(self): '''Create ONTAP volume''' if self.volume_style == 'flexGroup': self.create_volume_async() else: options = self.create_volume_options() volume_create = netapp_utils.zapi.NaElement.create_node_with_children( 'volume-create', **options) try: self.server.invoke_successfully(volume_create, enable_tunneling=True) if self.parameters.get('wait_for_completion'): # round off time_out retries = (self.parameters['time_out'] + 5) // 10 current = self.get_volume() is_online = None if current is None else current[ 'is_online'] while not is_online and retries > 0: time.sleep(10) retries = retries - 1 current = self.get_volume() is_online = None if current is None else current[ 'is_online'] self.ems_log_event("volume-create") except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error provisioning volume %s of size %s: %s' % (self.parameters['name'], self.parameters['size'], to_native(error)), exception=traceback.format_exc()) if self.parameters.get('efficiency_policy'): self.assign_efficiency_policy() def create_volume_async(self): ''' create volume async. ''' options = self.create_volume_options() volume_create = netapp_utils.zapi.NaElement.create_node_with_children( 'volume-create-async', **options) if self.parameters.get('aggr_list'): aggr_list_obj = netapp_utils.zapi.NaElement('aggr-list') volume_create.add_child_elem(aggr_list_obj) for aggr in self.parameters['aggr_list']: aggr_list_obj.add_new_child('aggr-name', aggr) try: result = self.server.invoke_successfully(volume_create, enable_tunneling=True) self.ems_log_event("volume-create") except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error provisioning volume %s of size %s: %s' % (self.parameters['name'], self.parameters['size'], to_native(error)), exception=traceback.format_exc()) self.check_invoke_result(result, 'create') if self.parameters.get('efficiency_policy'): self.assign_efficiency_policy_async() def create_volume_options(self): options = {} if self.volume_style == 'flexGroup': options['volume-name'] = self.parameters['name'] if self.parameters.get('aggr_list_multiplier'): options['aggr-list-multiplier'] = str( self.parameters['aggr_list_multiplier']) if self.parameters.get('auto_provision_as'): options['auto-provision-as'] = self.parameters[ 'auto_provision_as'] if self.parameters.get('space_guarantee'): options['space-guarantee'] = self.parameters['space_guarantee'] if self.parameters.get('size'): options['size'] = str(self.parameters['size']) else: options['volume'] = self.parameters['name'] options['size'] = str(self.parameters['size']) if self.parameters.get('aggregate_name') is None: self.module.fail_json( msg= 'Error provisioning volume %s: aggregate_name is required' % self.parameters['name']) options['containing-aggr-name'] = self.parameters['aggregate_name'] if self.parameters.get('space_guarantee'): options['space-reserve'] = self.parameters['space_guarantee'] if self.parameters.get('snapshot_policy'): options['snapshot-policy'] = self.parameters['snapshot_policy'] if self.parameters.get('unix_permissions'): options['unix-permissions'] = self.parameters['unix_permissions'] if self.parameters.get('volume_security_style'): options['volume-security-style'] = self.parameters[ 'volume_security_style'] if self.parameters.get('policy'): options['export-policy'] = self.parameters['policy'] if self.parameters.get('junction_path'): options['junction-path'] = self.parameters['junction_path'] if self.parameters.get('type'): options['volume-type'] = self.parameters['type'] if self.parameters.get('percent_snapshot_space'): options['percentage-snapshot-reserve'] = self.parameters[ 'percent_snapshot_space'] if self.parameters.get('language'): options['language-code'] = self.parameters['language'] return options def delete_volume(self): '''Delete ONTAP volume''' if self.parameters.get( 'is_infinite') or self.volume_style == 'flexGroup': volume_delete = netapp_utils.zapi\ .NaElement.create_node_with_children( 'volume-destroy-async', **{'volume-name': self.parameters['name'], 'unmount-and-offline': 'true'}) else: volume_delete = netapp_utils.zapi\ .NaElement.create_node_with_children( 'volume-destroy', **{'name': self.parameters['name'], 'unmount-and-offline': 'true'}) try: result = self.server.invoke_successfully(volume_delete, enable_tunneling=True) if self.parameters.get( 'is_infinite') or self.volume_style == 'flexGroup': self.check_invoke_result(result, 'delete') self.ems_log_event("volume-delete") except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting volume %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def move_volume(self): '''Move volume from source aggregate to destination aggregate''' volume_move = netapp_utils.zapi.NaElement.create_node_with_children( 'volume-move-start', **{ 'source-volume': self.parameters['name'], 'vserver': self.parameters['vserver'], 'dest-aggr': self.parameters['aggregate_name'] }) try: self.cluster.invoke_successfully(volume_move, enable_tunneling=True) self.ems_log_event("volume-move") except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error moving volume %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_volume(self): """ Rename the volume. Note: 'is_infinite' needs to be set to True in order to rename an Infinite Volume. Use time_out parameter to set wait time for rename completion. """ vol_rename_zapi, vol_name_zapi = ['volume-rename-async', 'volume-name'] if self.parameters['is_infinite']\ else ['volume-rename', 'volume'] volume_rename = netapp_utils.zapi.NaElement.create_node_with_children( vol_rename_zapi, **{ vol_name_zapi: self.parameters['from_name'], 'new-volume-name': str(self.parameters['name']) }) try: result = self.server.invoke_successfully(volume_rename, enable_tunneling=True) if vol_rename_zapi == 'volume-rename-async': self.check_invoke_result(result, 'rename') self.ems_log_event("volume-rename") except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error renaming volume %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def resize_volume(self): """ Re-size the volume. Note: 'is_infinite' needs to be set to True in order to rename an Infinite Volume. """ vol_size_zapi, vol_name_zapi = ['volume-size-async', 'volume-name']\ if (self.parameters['is_infinite'] or self.volume_style == 'flexGroup')\ else ['volume-size', 'volume'] volume_resize = netapp_utils.zapi.NaElement.create_node_with_children( vol_size_zapi, **{ vol_name_zapi: self.parameters['name'], 'new-size': str(self.parameters['size']) }) try: result = self.server.invoke_successfully(volume_resize, enable_tunneling=True) if vol_size_zapi == 'volume-size-async': self.check_invoke_result(result, 'resize') self.ems_log_event("volume-resize") except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error re-sizing volume %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def change_volume_state(self): """ Change volume's state (offline/online). """ if self.parameters[ 'is_online']: # Desired state is online, setup zapi APIs respectively vol_state_zapi, vol_name_zapi, action = ['volume-online-async', 'volume-name', 'online']\ if (self.parameters['is_infinite'] or self.volume_style == 'flexGroup')\ else ['volume-online', 'name', 'online'] else: # Desired state is offline, setup zapi APIs respectively vol_state_zapi, vol_name_zapi, action = ['volume-offline-async', 'volume-name', 'offline']\ if (self.parameters['is_infinite'] or self.volume_style == 'flexGroup')\ else ['volume-offline', 'name', 'offline'] volume_unmount = netapp_utils.zapi.NaElement.create_node_with_children( 'volume-unmount', **{'volume-name': self.parameters['name']}) volume_change_state = netapp_utils.zapi.NaElement.create_node_with_children( vol_state_zapi, **{vol_name_zapi: self.parameters['name']}) try: if not self.parameters['is_online']: # Unmount before offline self.server.invoke_successfully(volume_unmount, enable_tunneling=True) result = self.server.invoke_successfully(volume_change_state, enable_tunneling=True) if self.volume_style == 'flexGroup' or self.parameters[ 'is_infinite']: self.check_invoke_result(result, action) self.ems_log_event("change-state") except netapp_utils.zapi.NaApiError as error: state = "online" if self.parameters['is_online'] else "offline" self.module.fail_json( msg='Error changing the state of volume %s to %s: %s' % (self.parameters['name'], state, to_native(error)), exception=traceback.format_exc()) def volume_modify_attributes(self): """ modify volume parameter 'policy','unix_permissions','snapshot_policy','space_guarantee','percent_snapshot_space' """ # TODO: refactor this method vol_mod_iter = None if self.volume_style == 'flexGroup' or self.parameters['is_infinite']: vol_mod_iter = netapp_utils.zapi.NaElement( 'volume-modify-iter-async') else: vol_mod_iter = netapp_utils.zapi.NaElement('volume-modify-iter') attributes = netapp_utils.zapi.NaElement('attributes') vol_mod_attributes = netapp_utils.zapi.NaElement('volume-attributes') vol_space_attributes = netapp_utils.zapi.NaElement( 'volume-space-attributes') if self.parameters.get('policy'): vol_export_attributes = netapp_utils.zapi.NaElement( 'volume-export-attributes') vol_export_attributes.add_new_child('policy', self.parameters['policy']) vol_mod_attributes.add_child_elem(vol_export_attributes) if self.parameters.get('space_guarantee'): vol_space_attributes.add_new_child( 'space-guarantee', self.parameters['space_guarantee']) vol_mod_attributes.add_child_elem(vol_space_attributes) if self.parameters.get('percent_snapshot_space'): vol_space_attributes.add_new_child( 'percentage-snapshot-reserve', str(self.parameters['percent_snapshot_space'])) vol_mod_attributes.add_child_elem(vol_space_attributes) if self.parameters.get('unix_permissions'): vol_unix_permissions_attributes = netapp_utils.zapi.NaElement( 'volume-security-unix-attributes') vol_unix_permissions_attributes.add_new_child( 'permissions', self.parameters['unix_permissions']) vol_security_attributes = netapp_utils.zapi.NaElement( 'volume-security-attributes') vol_security_attributes.add_child_elem( vol_unix_permissions_attributes) vol_mod_attributes.add_child_elem(vol_security_attributes) if self.parameters.get('snapshot_policy') or self.parameters.get( 'snapdir_access'): vol_snapshot_policy_attributes = netapp_utils.zapi.NaElement( 'volume-snapshot-attributes') if self.parameters.get('snapshot_policy'): vol_snapshot_policy_attributes.add_new_child( 'snapshot-policy', self.parameters['snapshot_policy']) if self.parameters.get('snapdir_access'): vol_snapshot_policy_attributes.add_new_child( 'snapdir-access-enabled', self.parameters.get('snapdir_access')) vol_mod_attributes.add_child_elem(vol_snapshot_policy_attributes) if self.parameters.get('atime_update'): vol_performance_attributes = netapp_utils.zapi.NaElement( 'volume-performance-attributes') vol_performance_attributes.add_new_child( 'is-atime-update-enabled', self.parameters.get('atime_update')) vol_mod_attributes.add_child_elem(vol_performance_attributes) attributes.add_child_elem(vol_mod_attributes) query = netapp_utils.zapi.NaElement('query') vol_query_attributes = netapp_utils.zapi.NaElement('volume-attributes') vol_id_attributes = netapp_utils.zapi.NaElement('volume-id-attributes') vol_id_attributes.add_new_child('name', self.parameters['name']) vol_query_attributes.add_child_elem(vol_id_attributes) query.add_child_elem(vol_query_attributes) vol_mod_iter.add_child_elem(attributes) vol_mod_iter.add_child_elem(query) try: result = self.server.invoke_successfully(vol_mod_iter, enable_tunneling=True) failures = result.get_child_by_name('failure-list') if self.volume_style == 'flexGroup' or self.parameters[ 'is_infinite']: success = result.get_child_by_name('success-list') success = success.get_child_by_name( 'volume-modify-iter-async-info') results = dict() for key in ('status', 'jobid'): if success.get_child_by_name(key): results[key] = success[key] status = results.get('status') if status == 'in_progress' and 'jobid' in results: if self.parameters['time_out'] == 0: return error = self.check_job_status(results['jobid']) if error is None: return else: self.module.fail_json( msg='Error when modify volume: %s' % error) self.module.fail_json( msg='Unexpected error when modify volume: results is: %s' % repr(results)) # handle error if modify space, policy, or unix-permissions parameter fails if failures is not None: if failures.get_child_by_name( 'volume-modify-iter-info') is not None: return_info = 'volume-modify-iter-info' error_msg = failures.get_child_by_name( return_info).get_child_content('error-message') self.module.fail_json(msg="Error modifying volume %s: %s" % (self.parameters['name'], error_msg), exception=traceback.format_exc()) elif failures.get_child_by_name( 'volume-modify-iter-async-info') is not None: return_info = 'volume-modify-iter-async-info' error_msg = failures.get_child_by_name( return_info).get_child_content('error-message') self.module.fail_json(msg="Error modifying volume %s: %s" % (self.parameters['name'], error_msg), exception=traceback.format_exc()) self.ems_log_event("volume-modify") except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying volume %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def volume_mount(self): """ Mount an existing volume in specified junction_path :return: None """ vol_mount = netapp_utils.zapi.NaElement('volume-mount') vol_mount.add_new_child('volume-name', self.parameters['name']) vol_mount.add_new_child('junction-path', self.parameters['junction_path']) try: self.server.invoke_successfully(vol_mount, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error mounting volume %s on path %s: %s' % (self.parameters['name'], self.parameters['junction_path'], to_native(error)), exception=traceback.format_exc()) def volume_unmount(self): """ Unmount an existing volume :return: None """ vol_unmount = netapp_utils.zapi.NaElement.create_node_with_children( 'volume-unmount', **{'volume-name': self.parameters['name']}) try: self.server.invoke_successfully(vol_unmount, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error unmounting volume %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def modify_volume(self, modify): for attribute in modify.keys(): if attribute == 'size': self.resize_volume() if attribute == 'is_online': self.change_volume_state() if attribute == 'aggregate_name': self.move_volume() if attribute in [ 'space_guarantee', 'policy', 'unix_permissions', 'snapshot_policy', 'percent_snapshot_space', 'snapdir_access', 'atime_update' ]: self.volume_modify_attributes() if attribute == 'junction_path': if modify.get('junction_path') == '': self.volume_unmount() else: self.volume_mount() def compare_chmod_value(self, current): """ compare current unix_permissions to desire unix_permissions. :return: True if the same, False it not the same or desire unix_permissions is not valid. """ desire = self.parameters if current is None: return False octal_value = '' unix_permissions = desire['unix_permissions'] if unix_permissions.isdigit(): return int(current['unix_permissions']) == int(unix_permissions) else: if len(unix_permissions) != 12: return False if unix_permissions[:3] != '---': return False for i in range(3, len(unix_permissions), 3): if unix_permissions[i] not in ['r', '-'] or unix_permissions[i + 1] not in ['w', '-']\ or unix_permissions[i + 2] not in ['x', '-']: return False group_permission = self.char_to_octal(unix_permissions[i:i + 3]) octal_value += str(group_permission) return int(current['unix_permissions']) == int(octal_value) def char_to_octal(self, chars): """ :param chars: Characters to be converted into octal values. :return: octal value of the individual group permission. """ total = 0 if chars[0] == 'r': total += 4 if chars[1] == 'w': total += 2 if chars[2] == 'x': total += 1 return total def get_volume_style(self, current): if current is None: if self.parameters.get('aggr_list') or self.parameters.get( 'aggr_list_multiplier') or self.parameters.get( 'auto_provision_as'): return 'flexGroup' else: if current.get('style_extended'): if current['style_extended'] == 'flexgroup': return 'flexGroup' else: return current['style_extended'] return None def get_job(self, jobid, server): """ Get job details by id """ job_get = netapp_utils.zapi.NaElement('job-get') job_get.add_new_child('job-id', jobid) try: result = server.invoke_successfully(job_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: if to_native(error.code) == "15661": # Not found return None self.module.fail_json(msg='Error fetching job info: %s' % to_native(error), exception=traceback.format_exc()) results = dict() job_info = result.get_child_by_name('attributes').get_child_by_name( 'job-info') results = { 'job-progress': job_info['job-progress'], 'job-state': job_info['job-state'] } if job_info.get_child_by_name('job-completion') is not None: results['job-completion'] = job_info['job-completion'] else: results['job-completion'] = None return results def check_job_status(self, jobid): """ Loop until job is complete """ server = self.server sleep_time = 5 time_out = self.parameters['time_out'] results = self.get_job(jobid, server) while time_out > 0: results = self.get_job(jobid, server) # If running as cluster admin, the job is owned by cluster vserver # rather than the target vserver. if results is None and server == self.server: results = netapp_utils.get_cserver(self.server) server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) continue if results is None: error = 'cannot locate job with id: %d' % int(jobid) break if results['job-state'] in ('queued', 'running'): time.sleep(sleep_time) time_out -= sleep_time continue if results['job-state'] in ('success', 'failure'): break else: self.module.fail_json(msg='Unexpected job status in: %s' % repr(results)) if results is not None: if results['job-state'] == 'success': error = None elif results['job-state'] in ('queued', 'running'): error = 'job completion exceeded expected timer of: %s seconds' % \ self.parameters['time_out'] else: if results['job-completion'] is not None: error = results['job-completion'] else: error = results['job-progress'] return error def check_invoke_result(self, result, action): ''' check invoked api call back result. ''' results = dict() for key in ('result-status', 'result-jobid'): if result.get_child_by_name(key): results[key] = result[key] status = results.get('result-status') if status == 'in_progress' and 'result-jobid' in results: if self.parameters['time_out'] == 0: return error = self.check_job_status(results['result-jobid']) if error is None: return else: self.module.fail_json(msg='Error when %s volume: %s' % (action, error)) if status == 'failed': self.module.fail_json(msg='Operation failed when %s volume.' % action) def assign_efficiency_policy(self): options = {'path': '/vol/' + self.parameters['name']} efficiency_enable = netapp_utils.zapi.NaElement.create_node_with_children( 'sis-enable', **options) try: self.server.invoke_successfully(efficiency_enable, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error enable efficiency on volume %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) options['policy-name'] = self.parameters['efficiency_policy'] efficiency_start = netapp_utils.zapi.NaElement.create_node_with_children( 'sis-set-config', **options) try: self.server.invoke_successfully(efficiency_start, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error setting up an efficiency policy %s on volume %s: %s' % (self.parameters['efficiency_policy'], self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def assign_efficiency_policy_async(self): options = {'volume-name': self.parameters['name']} efficiency_enable = netapp_utils.zapi.NaElement.create_node_with_children( 'sis-enable-async', **options) try: result = self.server.invoke_successfully(efficiency_enable, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error enable efficiency on volume %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) self.check_invoke_result(result, 'enable efficiency on') options['policy-name'] = self.parameters['efficiency_policy'] efficiency_start = netapp_utils.zapi.NaElement.create_node_with_children( 'sis-set-config-async', **options) try: result = self.server.invoke_successfully(efficiency_start, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error setting up an efficiency policy on volume %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) self.check_invoke_result(result, 'set efficiency policy on') def apply(self): '''Call create/modify/delete operations''' current = self.get_volume() self.volume_style = self.get_volume_style(current) # 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_volume(self.parameters['from_name']), current) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.parameters.get('unix_permissions'): # current stores unix_permissions' numeric value. # unix_permission in self.parameter can be either numeric or character. if self.compare_chmod_value(current): del self.parameters['unix_permissions'] if self.parameters.get('percent_snapshot_space'): self.parameters['percent_snapshot_space'] = str( self.parameters['percent_snapshot_space']) 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_volume() if cd_action == 'create': self.create_volume() # if we create, and modify only variable are set (snapdir_access or atime_update) we need to run a modify if 'snapdir_access' in self.parameters or 'atime_update' in self.parameters: self.volume_modify_attributes() elif cd_action == 'delete': self.delete_volume() elif modify: self.modify_volume(modify) self.module.exit_json(changed=self.na_helper.changed) def ems_log_event(self, state): '''Autosupport log event''' if state == 'create': message = "A Volume has been created, size: " + \ str(self.parameters['size']) + str(self.parameters['size_unit']) elif state == 'volume-delete': message = "A Volume has been deleted" elif state == 'volume-move': message = "A Volume has been moved" elif state == 'volume-rename': message = "A Volume has been renamed" elif state == 'volume-resize': message = "A Volume has been resized to: " + \ str(self.parameters['size']) + str(self.parameters['size_unit']) elif state == 'volume-change': message = "A Volume state has been changed" else: message = "na_ontap_volume has been called" netapp_utils.ems_log_event("na_ontap_volume", self.server, event=message)
class NetAppOntapIpspace(object): '''Class with ipspace 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'), )) 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 ipspace_get_iter(self, name): """ Return net-ipspaces-get-iter query results :param name: Name of the ipspace :return: NaElement if ipspace found, None otherwise """ ipspace_get_iter = netapp_utils.zapi.NaElement('net-ipspaces-get-iter') query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'net-ipspaces-info', **{'ipspace': name}) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) ipspace_get_iter.add_child_elem(query) try: result = self.server.invoke_successfully(ipspace_get_iter, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: # Error 14636 denotes an ipspace does not exist # Error 13073 denotes an ipspace not found if to_native(error.code) == "14636" or to_native( error.code) == "13073": return None else: self.module.self.fail_json(msg=to_native(error), exception=traceback.format_exc()) return result def get_ipspace(self, name=None): """ Fetch details if ipspace exists :param name: Name of the ipspace to be fetched :return: Dictionary of current details if ipspace found None if ipspace is not found """ if name is None: name = self.parameters['name'] ipspace_get = self.ipspace_get_iter(name) if (ipspace_get and ipspace_get.get_child_by_name('num-records') and int(ipspace_get.get_child_content('num-records')) >= 1): current_ipspace = dict() attr_list = ipspace_get.get_child_by_name('attributes-list') attr = attr_list.get_child_by_name('net-ipspaces-info') current_ipspace['name'] = attr.get_child_content('ipspace') return current_ipspace return None def create_ipspace(self): """ Create ipspace :return: None """ ipspace_create = netapp_utils.zapi.NaElement.create_node_with_children( 'net-ipspaces-create', **{'ipspace': self.parameters['name']}) try: self.server.invoke_successfully(ipspace_create, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.self.fail_json( msg="Error provisioning ipspace %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_ipspace(self): """ Destroy ipspace :return: None """ ipspace_destroy = netapp_utils.zapi.NaElement.create_node_with_children( 'net-ipspaces-destroy', **{'ipspace': self.parameters['name']}) try: self.server.invoke_successfully(ipspace_destroy, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.self.fail_json( msg="Error removing ipspace %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_ipspace(self): """ Rename an ipspace :return: Nothing """ ipspace_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'net-ipspaces-rename', **{ 'ipspace': self.parameters['from_name'], 'new-name': self.parameters['name'] }) try: self.server.invoke_successfully(ipspace_rename, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error renaming ipspace %s: %s" % (self.parameters['from_name'], to_native(error)), exception=traceback.format_exc()) def apply(self): """ Apply action to the ipspace :return: Nothing """ current = self.get_ipspace() # rename and create are mutually exclusive rename, cd_action = None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action( self.get_ipspace(self.parameters['from_name']), current) if rename is None: self.module.fail_json( msg="Error renaming: ipspace %s does not exist" % self.parameters['from_name']) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_ipspace() elif cd_action == 'create': self.create_ipspace() elif cd_action == 'delete': self.delete_ipspace() self.module.exit_json(changed=self.na_helper.changed)
class 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 = None if (result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1): return_q = { 'export_policy': result['attributes-list']['qtree-info']['export-policy'], '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''' netapp_utils.ems_log_event("na_ontap_qtree", self.server) current = self.get_qtree() rename, cd_action, modify = None, None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action( self.get_qtree(self.parameters['from_name']), current) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_qtree() if cd_action == 'create': self.create_qtree() elif cd_action == 'delete': self.delete_qtree() elif modify: self.modify_qtree() self.module.exit_json(changed=self.na_helper.changed)
class NetAppAWSCVS(object): '''Class for Pool operations ''' def __init__(self): """ Parse arguments, setup state variables, """ 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'), serviceLevel=dict(required=False, choices=['basic', 'standard', 'extreme'], type='str'), sizeInBytes=dict(required=False, type='int'), vendorID=dict(required=False, type='str'), )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.restApi = AwsCvsRestAPI(self.module) self.sizeInBytes_min_value = 4000000000000 def get_aws_netapp_cvs_pool(self, name=None): """ Returns Pool object if exists else Return None """ pool_info = None if name is None: name = self.parameters['name'] pools, error = self.restApi.get('Pools') if error is None and pools is not None: for pool in pools: if 'name' in pool and pool['region'] == self.parameters['region']: if pool['name'] == name: pool_info = pool break return pool_info def create_aws_netapp_cvs_pool(self): """ Create a pool """ api = 'Pools' for key in ['serviceLevel', 'sizeInBytes', 'vendorID']: if key not in self.parameters.keys() or self.parameters[key] is None: self.module.fail_json(changed=False, msg="Mandatory key '%s' required" % (key)) pool = { "name": self.parameters['name'], "region": self.parameters['region'], "serviceLevel": self.parameters['serviceLevel'], "sizeInBytes": self.parameters['sizeInBytes'], "vendorID": self.parameters['vendorID'] } response, error = self.restApi.post(api, pool) if error is not None: self.module.fail_json(changed=False, msg=error) def update_aws_netapp_cvs_pool(self, update_pool_info, pool_id): """ Update a pool """ api = 'Pools/' + pool_id pool = { "name": update_pool_info['name'], "region": self.parameters['region'], "serviceLevel": update_pool_info['serviceLevel'], "sizeInBytes": update_pool_info['sizeInBytes'], "vendorID": update_pool_info['vendorID'] } response, error = self.restApi.put(api, pool) if error is not None: self.module.fail_json(changed=False, msg=error) def delete_aws_netapp_cvs_pool(self, pool_id): """ Delete a pool """ api = 'Pools/' + pool_id data = None response, error = self.restApi.delete(api, data) if error is not None: self.module.fail_json(changed=False, msg=error) def apply(self): """ Perform pre-checks, call functions and exit """ update_required = False cd_action = None if 'sizeInBytes' in self.parameters.keys() and self.parameters['sizeInBytes'] < self.sizeInBytes_min_value: self.module.fail_json(changed=False, msg="sizeInBytes should be greater than or equal to %d" % (self.sizeInBytes_min_value)) current = self.get_aws_netapp_cvs_pool() if self.parameters.get('from_name'): existing = self.get_aws_netapp_cvs_pool(self.parameters['from_name']) rename = self.na_helper.is_rename_action(existing, current) if rename is None: self.module.fail_json(changed=False, msg="unable to rename pool: '%s' does not exist" % self.parameters['from_name']) if rename: current = existing else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': keys_to_check = ['name', 'vendorID', 'sizeInBytes', 'serviceLevel'] update_pool_info, update_required = self.na_helper.compare_and_update_values(current, self.parameters, keys_to_check) if update_required is True: self.na_helper.changed = True cd_action = 'update' if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'update': self.update_aws_netapp_cvs_pool(update_pool_info, current['poolId']) elif cd_action == 'create': self.create_aws_netapp_cvs_pool() elif cd_action == 'delete': self.delete_aws_netapp_cvs_pool(current['poolId']) self.module.exit_json(changed=self.na_helper.changed)