def create_destination_flexvol(self, src_backend_name, dest_backend_name, src_flexvol_name, dest_flexvol_name): """Create a SnapMirror mirror target FlexVol for a given source.""" dest_backend_config = config_utils.get_backend_configuration( dest_backend_name) dest_vserver = dest_backend_config.netapp_vserver dest_client = config_utils.get_client_for_backend( dest_backend_name, vserver_name=dest_vserver) source_backend_config = config_utils.get_backend_configuration( src_backend_name) src_vserver = source_backend_config.netapp_vserver src_client = config_utils.get_client_for_backend( src_backend_name, vserver_name=src_vserver) provisioning_options = ( src_client.get_provisioning_options_from_flexvol(src_flexvol_name)) # If the source is encrypted then the destination needs to be # encrypted too. Using is_flexvol_encrypted because it includes # a simple check to ensure that the NVE feature is supported. if src_client.is_flexvol_encrypted(src_flexvol_name, src_vserver): provisioning_options['encrypt'] = 'true' # Remove size and volume_type size = provisioning_options.pop('size', None) if not size: msg = _("Unable to read the size of the source FlexVol (%s) " "to create a SnapMirror destination.") raise exception.NetAppDriverException(msg % src_flexvol_name) provisioning_options.pop('volume_type', None) source_aggregate = provisioning_options.pop('aggregate') aggregate_map = self._get_replication_aggregate_map( src_backend_name, dest_backend_name) if not aggregate_map.get(source_aggregate): msg = _("Unable to find configuration matching the source " "aggregate (%s) and the destination aggregate. Option " "netapp_replication_aggregate_map may be incorrect.") raise exception.NetAppDriverException(message=msg % source_aggregate) destination_aggregate = aggregate_map[source_aggregate] # NOTE(gouthamr): The volume is intentionally created as a Data # Protection volume; junction-path will be added on breaking # the mirror. dest_client.create_flexvol(dest_flexvol_name, destination_aggregate, size, volume_type='dp', **provisioning_options)
def _invoke(self, method, path, data=None, use_system=True, timeout=None, verify=False, **kwargs): """Invokes end point for resource on path.""" url = self._get_resource_url(path, use_system, **kwargs) if self._content_type == 'json': headers = {'Accept': 'application/json', 'Content-Type': 'application/json'} if cinder_utils.TRACE_API: self._log_http_request(method, url, headers, data) data = json.dumps(data) if data else None res = self.invoke_service(method, url, data=data, headers=headers, timeout=timeout, verify=verify) try: res_dict = res.json() if res.text else None # This should only occur if we expected JSON, but were sent # something else except scanner.JSONDecodeError: res_dict = None if cinder_utils.TRACE_API: self._log_http_response(res.status_code, dict(res.headers), res_dict) self._eval_response(res) return res_dict else: raise exception.NetAppDriverException( _("Content type not supported."))
def _check_host_type(self): host_type = (self.configuration.netapp_host_type or self.DEFAULT_HOST_TYPE) self.host_type = self.HOST_TYPES.get(host_type) if not self.host_type: raise exception.NetAppDriverException( _('Configured host type is not supported.'))
def create_group_snapshot(self, context, group_snapshot, snapshots): """Creates a Cinder group snapshot object. The Cinder group snapshot object is created by making use of an ONTAP consistency group snapshot in order to provide write-order consistency for a set of flexvols snapshots. First, a list of the flexvols backing the given Cinder group must be gathered. An ONTAP group-snapshot of these flexvols will create a snapshot copy of all the Cinder volumes in the generic volume group. For each Cinder volume in the group, it is then necessary to clone its backing file from the ONTAP cg-snapshot. The naming convention used to for the clones is what indicates the clone's role as a Cinder snapshot and its inclusion in a Cinder group. The ONTAP cg-snapshot of the flexvols is deleted after the cloning operation is completed. :returns: An implicit update for the group snapshot and snapshot models that is then used by the manager to set the models to available. """ try: if volume_utils.is_group_a_cg_snapshot_type(group_snapshot): self._create_consistent_group_snapshot(group_snapshot, snapshots) else: for snapshot in snapshots: self._clone_backing_file_for_volume( snapshot['volume_name'], snapshot['name'], snapshot['volume_id'], is_snapshot=True) except Exception as ex: err_msg = (_("Create group snapshot failed (%s).") % ex) LOG.exception(err_msg, resource=group_snapshot) raise exception.NetAppDriverException(err_msg) return None, None
def invoke_service(self, method='GET', url=None, params=None, data=None, headers=None, timeout=None, verify=False): url = url or self._endpoint try: response = self.conn.request(method, url, params, data, headers=headers, timeout=timeout, verify=verify) # Catching error conditions other than the perceived ones. # Helps propagating only known exceptions back to the caller. except Exception as e: LOG.exception( _LE("Unexpected error while invoking web service." " Error - %s."), e) raise exception.NetAppDriverException( _("Invoking web service failed.")) self._eval_response(response) return response
def _get_latest_volume(self, uid): label = utils.convert_uuid_to_es_fmt(uid) for vol in self._client.list_volumes(): if vol.get('label') == label: self._cache_volume(vol) return self._get_cached_volume(label) raise exception.NetAppDriverException(_("Volume %s not found."), uid)
def _create_volume(self, eseries_pool_label, eseries_volume_label, size_gb): """Creates volume with given label and size.""" target_pool = None pools = self._client.list_storage_pools() for pool in pools: if pool["label"] == eseries_pool_label: target_pool = pool break if not target_pool: msg = _("Pools %s does not exist") raise exception.NetAppDriverException(msg % eseries_pool_label) try: vol = self._client.create_volume(target_pool['volumeGroupRef'], eseries_volume_label, size_gb) LOG.info(_("Created volume with label %s."), eseries_volume_label) except exception.NetAppDriverException as e: with excutils.save_and_reraise_exception(): LOG.error(_("Error creating volume. Msg - %s."), six.text_type(e)) return vol
def _copy_volume_high_prior_readonly(self, src_vol, dst_vol): """Copies src volume to dest volume.""" LOG.info(_LI("Copying src vol %(src)s to dest vol %(dst)s."), {'src': src_vol['label'], 'dst': dst_vol['label']}) try: job = None job = self._client.create_volume_copy_job(src_vol['id'], dst_vol['volumeRef']) while True: j_st = self._client.list_vol_copy_job(job['volcopyRef']) if (j_st['status'] == 'inProgress' or j_st['status'] == 'pending' or j_st['status'] == 'unknown'): time.sleep(self.SLEEP_SECS) continue if j_st['status'] == 'failed' or j_st['status'] == 'halted': LOG.error(_LE("Vol copy job status %s."), j_st['status']) raise exception.NetAppDriverException( _("Vol copy job for dest %s failed.") % dst_vol['label']) LOG.info(_LI("Vol copy job completed for dest %s."), dst_vol['label']) break finally: if job: try: self._client.delete_vol_copy_job(job['volcopyRef']) except exception.NetAppDriverException: LOG.warning(_LW("Failure deleting " "job %s."), job['volcopyRef']) else: LOG.warning(_LW('Volume copy job for src vol %s not found.'), src_vol['id']) LOG.info(_LI('Copy job to dest vol %s completed.'), dst_vol['label'])
def _invoke(self, method, path, data=None, use_system=True, timeout=None, verify=False, **kwargs): """Invokes end point for resource on path.""" scrubbed_data = copy.deepcopy(data) if scrubbed_data: if 'password' in scrubbed_data: scrubbed_data['password'] = "******" if 'storedPassword' in scrubbed_data: scrubbed_data['storedPassword'] = "******" params = {'m': method, 'p': path, 'd': scrubbed_data, 'sys': use_system, 't': timeout, 'v': verify, 'k': kwargs} LOG.debug("Invoking rest with method: %(m)s, path: %(p)s," " data: %(d)s, use_system: %(sys)s, timeout: %(t)s," " verify: %(v)s, kwargs: %(k)s." % (params)) url = self._get_resource_url(path, use_system, **kwargs) if self._content_type == 'json': headers = {'Accept': 'application/json', 'Content-Type': 'application/json'} data = json.dumps(data) if data else None res = self.invoke_service(method, url, data=data, headers=headers, timeout=timeout, verify=verify) return res.json() if res.text else None else: raise exception.NetAppDriverException( _("Content type not supported."))
def _get_iscsi_service_details(self): """Gets iscsi iqn, ip and port information.""" ports = [] hw_inventory = self._client.list_hardware_inventory() iscsi_ports = hw_inventory.get('iscsiPorts') if iscsi_ports: for port in iscsi_ports: if (port.get('ipv4Enabled') and port.get('iqn') and port.get('ipv4Data') and port['ipv4Data'].get('ipv4AddressData') and port['ipv4Data']['ipv4AddressData'].get('ipv4Address') and port['ipv4Data']['ipv4AddressData'].get('configState') == 'configured'): iscsi_det = {} iscsi_det['ip'] =\ port['ipv4Data']['ipv4AddressData']['ipv4Address'] iscsi_det['iqn'] = port['iqn'] iscsi_det['tcp_port'] = port.get('tcpListenPort') iscsi_det['controller'] = port.get('controllerId') ports.append(iscsi_det) if not ports: msg = _('No good iscsi portals found for %s.') raise exception.NetAppDriverException(msg % self._client.get_system_id()) return ports
def _check_host_type(self): self.host_type =\ self.HOST_TYPES.get(self.configuration.netapp_eseries_host_type, None) if not self.host_type: raise exception.NetAppDriverException( _('Configured host type is not supported.'))
def check_for_setup_error(self): self.host_type =\ self.HOST_TYPES.get(self.configuration.netapp_eseries_host_type, None) if not self.host_type: raise exception.NetAppDriverException( _('Configured host type is not supported.')) self._check_storage_system() self._populate_system_objects()
def _get_free_lun(self, host): """Gets free lun for given host.""" luns = self._get_vol_mapping_for_host_frm_array(host['hostRef']) used_luns = set(map(lambda lun: int(lun['lun']), luns)) for lun in xrange(self.MAX_LUNS_PER_HOST): if lun not in used_luns: return lun msg = _("No free luns. Host might exceeded max luns.") raise exception.NetAppDriverException(msg)
def check_for_setup_error(self): """Check that the driver is working and can communicate. Discovers the LUNs on the NetApp server. """ if self.lun_ostype not in self.ALLOWED_LUN_OS_TYPES: msg = _("Invalid value for NetApp configuration" " option netapp_lun_ostype.") LOG.error(msg) raise exception.NetAppDriverException(msg) if self.host_type not in self.ALLOWED_IGROUP_HOST_TYPES: msg = _("Invalid value for NetApp configuration" " option netapp_host_type.") LOG.error(msg) raise exception.NetAppDriverException(msg) lun_list = self.zapi_client.get_lun_list() self._extract_and_populate_luns(lun_list) LOG.debug("Success getting list of LUNs from server.")
def check_for_setup_error(self): """Check that the driver is working and can communicate.""" if not self._get_flexvol_to_pool_map(): msg = _('No pools are available for provisioning volumes. ' 'Ensure that the configuration option ' 'netapp_pool_name_search_pattern is set correctly.') raise exception.NetAppDriverException(msg) self._add_looping_tasks() super(NetAppBlockStorageCmodeLibrary, self).check_for_setup_error()
def _check_storage_system(self): """Checks whether system is registered and has good status.""" try: system = self._client.list_storage_system() except exception.NetAppDriverException: with excutils.save_and_reraise_exception(): LOG.info( _LI("System with controller addresses [%s] is not " "registered with web service."), self.configuration.netapp_controller_ips) password_not_in_sync = False if system.get('status', '').lower() == 'passwordoutofsync': password_not_in_sync = True new_pwd = self.configuration.netapp_sa_password self._client.update_stored_system_password(new_pwd) time.sleep(self.SLEEP_SECS) sa_comm_timeout = 60 comm_time = 0 while True: system = self._client.list_storage_system() status = system.get('status', '').lower() # wait if array not contacted or # password was not in sync previously. if ((status == 'nevercontacted') or (password_not_in_sync and status == 'passwordoutofsync')): LOG.info(_LI('Waiting for web service array communication.')) time.sleep(self.SLEEP_SECS) comm_time = comm_time + self.SLEEP_SECS if comm_time >= sa_comm_timeout: msg = _("Failure in communication between web service and" " array. Waited %s seconds. Verify array" " configuration parameters.") raise exception.NetAppDriverException(msg % sa_comm_timeout) else: break msg_dict = {'id': system.get('id'), 'status': status} if (status == 'passwordoutofsync' or status == 'notsupported' or status == 'offline'): raise exception.NetAppDriverException( _("System %(id)s found with bad status - " "%(status)s.") % msg_dict) LOG.info(_LI("System %(id)s has %(status)s status."), msg_dict) return True
def move_volume_mapping_via_symbol(self, map_ref, to_ref, lun_id): """Moves a map from one host/host_group object to another.""" path = "/storage-systems/{system-id}/symbol/moveLUNMapping" data = {'lunMappingRef': map_ref, 'lun': int(lun_id), 'mapRef': to_ref} return_code = self._invoke('POST', path, data) if return_code == 'ok': return {'lun': lun_id} msg = _("Failed to move LUN mapping. Return code: %s") % return_code raise exception.NetAppDriverException(msg)
def wait_for_quiesced(): snapmirror = dest_client.get_snapmirrors( src_vserver, src_flexvol_name, dest_vserver, dest_flexvol_name, desired_attributes=['relationship-status', 'mirror-state'])[0] if snapmirror.get('relationship-status') != 'quiesced': msg = _("SnapMirror relationship is not quiesced.") raise exception.NetAppDriverException(reason=msg)
def _get_iscsi_portal_for_vol(self, volume, portals, anyController=True): """Get the iscsi portal info relevant to volume.""" for portal in portals: if portal.get('controller') == volume.get('currentManager'): return portal if anyController and portals: return portals[0] msg = _('No good iscsi portal found in supplied list for %s.') raise exception.NetAppDriverException(msg % self._client.get_system_id())
def _get_free_lun(client, host, multiattach_enabled, mappings): """Returns least used LUN ID available on the given host.""" if not _is_host_full(client, host): unused_luns = _get_unused_lun_ids(mappings) if unused_luns: chosen_lun = random.sample(unused_luns, 1) return chosen_lun[0] elif multiattach_enabled: msg = _("No unused LUN IDs are available on the host; " "multiattach is enabled which requires that all LUN IDs " "to be unique across the entire host group.") raise exception.NetAppDriverException(msg) used_lun_counts = _get_used_lun_id_counter(mappings) # most_common returns an arbitrary tuple of members with same frequency for lun_id, __ in reversed(used_lun_counts.most_common()): if _is_lun_id_available_on_host(client, host, lun_id): return lun_id msg = _("No free LUN IDs left. Maximum number of volumes that can be " "attached to host (%s) has been exceeded.") raise exception.NetAppDriverException(msg % utils.MAX_LUNS_PER_HOST)
def _eval_response(self, response): """Evaluates response before passing result to invoker.""" status_code = int(response.status_code) # codes >= 300 are not ok and to be treated as errors if status_code >= 300: # Response code 422 returns error code and message if status_code == 422: msg = _("Response error - %s.") % response.text else: msg = _("Response error code - %s.") % status_code raise exception.NetAppDriverException(msg)
def create_volume(self, pool, label, size, unit='gb', seg_size=0, read_cache=None, write_cache=None, flash_cache=None, data_assurance=None, thin_provision=False): """Creates a volume on array with the configured attributes Note: if read_cache, write_cache, flash_cache, or data_assurance are not provided, the default will be utilized by the Webservice. :param pool: The pool unique identifier :param label: The unqiue label for the volume :param size: The capacity in units :param unit: The unit for capacity :param seg_size: The segment size for the volume, expressed in KB. Default will allow the Webservice to choose. :param read_cache: If true, enable read caching, if false, explicitly disable it. :param write_cache: If true, enable write caching, if false, explicitly disable it. :param flash_cache: If true, add the volume to a Flash Cache :param data_assurance: If true, enable the Data Assurance capability :returns: The created volume """ # Utilize the new API if it is available if self.features.SSC_API_V2: path = "/storage-systems/{system-id}/ssc/volumes" data = {'poolId': pool, 'name': label, 'sizeUnit': unit, 'size': int(size), 'dataAssuranceEnable': data_assurance, 'flashCacheEnable': flash_cache, 'readCacheEnable': read_cache, 'writeCacheEnable': write_cache, 'thinProvision': thin_provision} # Use the old API else: # Determine if there are were extra specs provided that are not # supported extra_specs = [read_cache, write_cache] unsupported_spec = any([spec is not None for spec in extra_specs]) if(unsupported_spec): msg = _("E-series proxy API version %(current_version)s does " "not support full set of SSC extra specs. The proxy" " version must be at at least %(min_version)s.") min_version = self.features.SSC_API_V2.minimum_version raise exception.NetAppDriverException(msg % {'current_version': self.api_version, 'min_version': min_version}) path = "/storage-systems/{system-id}/volumes" data = {'poolId': pool, 'name': label, 'sizeUnit': unit, 'size': int(size), 'segSize': seg_size} return self._invoke('POST', path, data)
def check_for_setup_error(self): """Check that the driver is working and can communicate.""" ssc_cmode.check_ssc_api_permissions(self.zapi_client) ssc_cmode.refresh_cluster_ssc(self, self.zapi_client.get_connection(), self.vserver, synchronous=True) if not self._get_filtered_pools(): msg = _('No pools are available for provisioning volumes. ' 'Ensure that the configuration option ' 'netapp_pool_name_search_pattern is set correctly.') raise exception.NetAppDriverException(msg) super(NetAppBlockStorageCmodeLibrary, self).check_for_setup_error() self._start_periodic_tasks()
def get_export_host_junction_path(share): if '[' in share and ']' in share: try: # ipv6 host = re.search(r'\[(.*)\]', share).group(1) junction_path = share.split(':')[-1] except AttributeError: raise exception.NetAppDriverException(_("Share '%s' is " "not in a valid " "format.") % share) else: # ipv4 path = share.split(':') if len(path) == 2: host = path[0] junction_path = path[1] else: raise exception.NetAppDriverException(_("Share '%s' is " "not in a valid " "format.") % share) return host, junction_path
def _schedule_and_create_volume(self, label, size_gb): """Creates volume with given label and size.""" avl_pools = self._get_sorted_avl_storage_pools(size_gb) for pool in avl_pools: try: vol = self._client.create_volume(pool['volumeGroupRef'], label, size_gb) LOG.info(_("Created volume with label %s."), label) return vol except exception.NetAppDriverException as e: LOG.error(_("Error creating volume. Msg - %s."), e) msg = _("Failure creating volume %s.") raise exception.NetAppDriverException(msg % label)
def _create_volume(self, eseries_pool_label, eseries_volume_label, size_gb): """Creates volume with given label and size.""" if self.configuration.netapp_enable_multiattach: volumes = self._client.list_volumes() # NOTE(ameade): Ensure we do not create more volumes than we could # map to the multi attach ESeries host group. if len(volumes) > utils.MAX_LUNS_PER_HOST_GROUP: msg = (_("Cannot create more than %(req)s volumes on the " "ESeries array when 'netapp_enable_multiattach' is " "set to true.") % { 'req': utils.MAX_LUNS_PER_HOST_GROUP }) raise exception.NetAppDriverException(msg) target_pool = None pools = self._client.list_storage_pools() for pool in pools: if pool["label"] == eseries_pool_label: target_pool = pool break if not target_pool: msg = _("Pools %s does not exist") raise exception.NetAppDriverException(msg % eseries_pool_label) try: vol = self._client.create_volume(target_pool['volumeGroupRef'], eseries_volume_label, size_gb) LOG.info(_LI("Created volume with " "label %s."), eseries_volume_label) except exception.NetAppDriverException as e: with excutils.save_and_reraise_exception(): LOG.error(_LE("Error creating volume. Msg - %s."), six.text_type(e)) return vol
def _get_free_lun(client, host, maps=None): """Gets free LUN for given host.""" ref = host['hostRef'] luns = maps or _get_vol_mapping_for_host_frm_array(client, ref) if host['clusterRef'] != utils.NULL_REF: host_group_maps = _get_vol_mapping_for_host_group_frm_array( client, host['clusterRef']) luns.extend(host_group_maps) used_luns = set(map(lambda lun: int(lun['lun']), luns)) for lun in xrange(utils.MAX_LUNS_PER_HOST): if lun not in used_luns: return lun msg = _("No free LUNs. Host might have exceeded max number of LUNs.") raise exception.NetAppDriverException(msg)
def expand_volume(self, object_id, new_capacity, capacity_unit='gb'): """Increase the capacity of a volume""" if not self.features.SSC_API_V2: msg = _("E-series proxy API version %(current_version)s does " "not support this expansion API. The proxy" " version must be at at least %(min_version)s") min_version = self.features.SSC_API_V2.minimum_version msg_args = {'current_version': self.api_version, 'min_version': min_version} raise exception.NetAppDriverException(msg % msg_args) path = self.RESOURCE_PATHS.get('ssc_volume') data = {'newSize': new_capacity, 'sizeUnit': capacity_unit} return self._invoke('POST', path, data, **{'object-id': object_id})
def send_iter_request(self, api_name, api_args=None, enable_tunneling=True, max_page_length=DEFAULT_MAX_PAGE_LENGTH): """Invoke an iterator-style getter API.""" if not api_args: api_args = {} api_args['max-records'] = max_page_length # Get first page result = self.send_request(api_name, api_args, enable_tunneling=enable_tunneling) # Most commonly, we can just return here if there is no more data next_tag = result.get_child_content('next-tag') if not next_tag: return result # Ensure pagination data is valid and prepare to store remaining pages num_records = self._get_record_count(result) attributes_list = result.get_child_by_name('attributes-list') if not attributes_list: msg = _('Missing attributes list for API %s.') % api_name raise exception.NetAppDriverException(msg) # Get remaining pages, saving data into first page while next_tag is not None: next_api_args = copy.deepcopy(api_args) next_api_args['tag'] = next_tag next_result = self.send_request(api_name, next_api_args, enable_tunneling=enable_tunneling) next_attributes_list = next_result.get_child_by_name( 'attributes-list') or netapp_api.NaElement('none') for record in next_attributes_list.get_children(): attributes_list.add_child_elem(record) num_records += self._get_record_count(next_result) next_tag = next_result.get_child_content('next-tag') result.get_child_by_name('num-records').set_content( six.text_type(num_records)) result.get_child_by_name('next-tag').set_content('') return result
def map_volume_to_single_host(client, volume, eseries_vol, host, vol_map): """Maps the e-series volume to host with initiator.""" msg = "Attempting to map volume %s to single host." LOG.debug(msg % volume['id']) # If volume is not mapped on the backend, map directly to host if not vol_map: mappings = _get_vol_mapping_for_host_frm_array(client, host['hostRef']) lun = _get_free_lun(client, host, mappings) return client.create_volume_mapping(eseries_vol['volumeRef'], host['hostRef'], lun) # If volume is already mapped to desired host if vol_map.get('mapRef') == host['hostRef']: return vol_map multiattach_cluster_ref = None try: host_group = client.get_host_group_by_name( utils.MULTI_ATTACH_HOST_GROUP_NAME) multiattach_cluster_ref = host_group['clusterRef'] except exception.NotFound: pass # Volume is mapped to the multiattach host group if vol_map.get('mapRef') == multiattach_cluster_ref: LOG.debug("Volume %s is mapped to multiattach host group." % volume['id']) # If volume is not currently attached according to Cinder, it is # safe to delete the mapping if not (volume['attach_status'] == 'attached'): msg = (_("Volume %(vol)s is not currently attached, " "moving existing mapping to host %(host)s.") % { 'vol': volume['id'], 'host': host['label'] }) LOG.debug(msg) mappings = _get_vol_mapping_for_host_frm_array( client, host['hostRef']) lun = _get_free_lun(client, host, mappings) return client.move_volume_mapping_via_symbol( vol_map.get('mapRef'), host['hostRef'], lun) # If we got this far, volume must be mapped to something else msg = _("Cannot attach already attached volume %s; " "multiattach is disabled via the " "'netapp_enable_multiattach' configuration option.") raise exception.NetAppDriverException(msg % volume['id'])