def manage_existing(self, share, driver_options): old_export = share['export_location'].split(':') try: ces_ip = old_export[0] old_export_location = old_export[1] except IndexError: msg = _('Incorrect export path. Expected format: ' 'IP:/gpfs_mount_point_base/share_id.') LOG.exception(msg) raise exception.ShareBackendException(msg=msg) if ces_ip not in self.configuration.gpfs_nfs_server_list: msg = _('The CES IP %s is not present in the ' 'configuration option "gpfs_nfs_server_list".') % ces_ip raise exception.ShareBackendException(msg=msg) fsdev = self._get_gpfs_device() if not self._is_share_valid(fsdev, old_export_location): err_msg = _('Given share path %s does not have a valid ' 'share.') % old_export_location raise exception.ManageInvalidShare(reason=err_msg) share_name = self._get_share_name(fsdev, old_export_location) out = self._get_helper(share)._has_client_access(old_export_location) if out: err_msg = _('Clients have access to %s share currently. Evict any ' 'clients before trying again.') % share_name raise exception.ManageInvalidShare(reason=err_msg) share_size, new_export_location = self._manage_existing( fsdev, share, share_name) return {"size": share_size, "export_locations": new_export_location}
def manage_existing(self, share, driver_options): """Manages a share that exists on backend.""" if share['share_proto'].lower() == 'nfs': # 10.0.0.1:/share/example LOG.info("Share %(shr_path)s will be managed with ID" "%(shr_id)s.", {'shr_path': share['export_locations'][0]['path'], 'shr_id': share['id']}) old_path_info = share['export_locations'][0]['path'].split( ':/share/') if len(old_path_info) == 2: ip = old_path_info[0] share_name = old_path_info[1] else: msg = _("Incorrect path. It should have the following format: " "IP:/share/share_name.") raise exception.ShareBackendException(msg=msg) else: msg = _('Invalid NAS protocol: %s') % share['share_proto'] raise exception.InvalidInput(reason=msg) if ip != self.configuration.qnap_share_ip: msg = _("The NAS IP %(ip)s is not configured.") % {'ip': ip} raise exception.ShareBackendException(msg=msg) existing_share = self.api_executor.get_share_info( self.configuration.qnap_poolname, vol_label=share_name) if existing_share is None: msg = _("The share %s trying to be managed was not found on " "backend.") % share['id'] raise exception.ManageInvalidShare(reason=msg) _metadata = {} vol_no = existing_share.find('vol_no').text _metadata['volID'] = vol_no _metadata['volName'] = share_name self.private_storage.update(share['id'], _metadata) # Test to get value from private_storage. volID = self.private_storage.get(share['id'], 'volID') LOG.debug('volID: %s', volID) volName = self.private_storage.get(share['id'], 'volName') LOG.debug('volName: %s', volName) LOG.info("Share %(shr_path)s was successfully managed with ID " "%(shr_id)s.", {'shr_path': share['export_locations'][0]['path'], 'shr_id': share['id']}) vol = self.api_executor.get_specific_volinfo(vol_no) vol_size_gb = int(vol.find('size').text) / units.Gi export_locations = self._get_location_path( share_name, share['share_proto'], self.configuration.qnap_share_ip) return {'size': vol_size_gb, 'export_locations': export_locations}
def _validate_volume_for_manage(self, volume, vserver_client): """Ensure volume is a candidate for becoming a share.""" # Check volume info, extra specs validity if volume['type'] != 'rw' or volume['style'] != 'flex': msg = _('Volume %(volume)s must be a read-write flexible volume.') msg_args = {'volume': volume['name']} raise exception.ManageInvalidShare(reason=msg % msg_args) if vserver_client.volume_has_luns(volume['name']): msg = _('Volume %(volume)s must not contain LUNs.') msg_args = {'volume': volume['name']} raise exception.ManageInvalidShare(reason=msg % msg_args) if vserver_client.volume_has_junctioned_volumes(volume['name']): msg = _('Volume %(volume)s must not have junctioned volumes.') msg_args = {'volume': volume['name']} raise exception.ManageInvalidShare(reason=msg % msg_args)
def _get_mounted_share_size(self, mount_path, server_details): try: size = self._get_mount_stats_by_index(mount_path, server_details, BLOCK_DEVICE_SIZE_INDEX) except Exception as e: msg = _("Cannot calculate size of share %(path)s : %(error)s") % { 'path': mount_path, 'error': six.text_type(e) } raise exception.ManageInvalidShare(reason=msg) return size
def _verify_share_to_manage(self, name, details): lcfg = self.configuration if lcfg.zfssa_manage_policy == 'loose': return if 'custom:manila_managed' not in details: msg = (_("Unknown if the share: %s to be managed is " "already being managed by Manila. Aborting manage " "share. Please add 'manila_managed' custom schema " "property to the share and set its value to False." "Alternatively, set Manila config property " "'zfssa_manage_policy' to 'loose' to remove this " "restriction.") % name) LOG.error(msg) raise exception.ManageInvalidShare(reason=msg) if details['custom:manila_managed'] is True: msg = (_("Share %s is already being managed by Manila.") % name) LOG.error(msg) raise exception.ManageInvalidShare(reason=msg)
def get_volume(): if 'volume_id' in driver_options: try: return self.volume_api.get(self.admin_context, driver_options['volume_id']) except exception.VolumeNotFound as e: raise exception.ManageInvalidShare(reason=six.text_type(e)) # NOTE(vponomaryov): Manila can only combine volume name by itself, # nowhere to get volume ID from. Return None since Cinder volume # names are not unique or fixed, hence, they can not be used for # sure. return None
def _get_mounted_share_size(self, mount_path, server_details): share_size_cmd = ['df', '-PBG', mount_path] output, __ = self._ssh_exec(server_details, share_size_cmd) lines = output.split('\n') try: size = int(lines[1].split()[1][:-1]) except Exception as e: msg = _("Cannot calculate size of share %(path)s : %(error)s") % { 'path': mount_path, 'error': six.text_type(e) } raise exception.ManageInvalidShare(reason=msg) return size
def manage_existing(self, share, driver_options): LOG.debug("Managing share in HSP: %(shr_id)s.", {'shr_id': share['id']}) ip, share_name = share['export_locations'][0]['path'].split(':') try: hsp_share = self.hsp.get_share(name=share_name.strip('/')) except exception.HSPItemNotFoundException: msg = _("The share %s trying to be managed was not found on " "backend.") % share['id'] raise exception.ManageInvalidShare(reason=msg) self.hsp.rename_file_system(hsp_share['properties']['file-system-id'], share['id']) original_name = hsp_share['properties']['file-system-name'] private_storage_content = { 'old_name': original_name, 'new_name': share['id'], } self.private_storage.update(share['id'], private_storage_content) LOG.debug("Filesystem %(original_name)s was renamed to %(name)s.", { 'original_name': original_name, 'name': share['id'] }) file_system = self.hsp.get_file_system(share['id']) LOG.info( _LI("Share %(shr_path)s was successfully managed with ID " "%(shr_id)s."), { 'shr_path': share['export_locations'][0]['path'], 'shr_id': share['id'] }) export_locations = [{ "path": share['export_locations'][0]['path'], "metadata": {}, "is_admin_only": False, }] return { 'size': file_system['properties']['quota'] / units.Gi, 'export_locations': export_locations }
def _manage_existing(self, share_id): """Manages a share that exists on backend. :param share_id: ID of share that will be managed. :returns: Returns a dict with size of share managed and its location (your path in file-system). """ self._ensure_share(share_id) share_size = self.hnas.get_share_quota(share_id) if share_size is None: msg = (_("The share %s trying to be managed does not have a " "quota limit, please set it before manage.") % share_id) raise exception.ManageInvalidShare(msg) path = self.hnas_evs_ip + ':/shares/' + share_id return {'size': share_size, 'export_locations': [path]}
def _get_share_name(self, fsdev, location): try: out, __ = self._gpfs_execute('mmlsfileset', fsdev, '-J', location, '-L', '-Y') except exception.ProcessExecutionError: msg = (_('Given share path %(share_path)s does not exist at ' 'mount point %(mount_point)s.') % {'share_path': location, 'mount_point': fsdev}) LOG.exception(msg) raise exception.ManageInvalidShare(reason=msg) lines = out.splitlines() try: validation_token = lines[0].split(':').index('filesetName') share_name = lines[1].split(':')[validation_token] except (IndexError, ValueError): msg = (_('Failed to check share at %s.') % location) LOG.exception(msg) raise exception.GPFSException(msg) return share_name
def _manage_existing(self, share, hnas_share_id): """Manages a share that exists on backend. :param share: share that will be managed. :param hnas_share_id: HNAS ID of share that will be managed. :returns: Returns a dict with size of the share managed and a list of dicts containing its export locations. """ self._ensure_share(share, hnas_share_id) share_size = self.hnas.get_share_quota(hnas_share_id) if share_size is None: msg = (_("The share %s trying to be managed does not have a " "quota limit, please set it before manage.") % share['id']) raise exception.ManageInvalidShare(reason=msg) export_list = self._get_export_locations(share['share_proto'], hnas_share_id) return {'size': share_size, 'export_locations': export_list}
def manage_existing(self, share, driver_options): try: # retrieve share path from export location, maprfs:// prefix and # metadata (-C -Z -N) should be casted away share_path = share['export_location'].split( )[0][len(self._maprfs_base_path):] info = self._maprfs_util.get_volume_info_by_path( share_path, check_if_exists=True) if not info: msg = _("Share %s not found") % share[ 'export_location'] LOG.error(msg) raise exception.ManageInvalidShare(reason=msg) size = math.ceil(float(info['quota']) / units.Ki) used = math.ceil(float(info['totalused']) / units.Ki) volume_name = info['volumename'] should_rename = self.rename_volume rename_option = driver_options.get('rename') if rename_option: should_rename = strutils.bool_from_string(rename_option) if should_rename: self._maprfs_util.rename_volume(volume_name, share['name']) else: self.api.update_share_metadata(context.get_admin_context(), {'id': share['share_id']}, {'_name': volume_name}) location = self._get_share_export_locations(share, path=share_path) if size == 0: size = used msg = _LW( 'Share %s has no size quota. Total used value will be' ' used as share size') LOG.warning(msg, share['name']) return {'size': size, 'export_locations': location} except (ValueError, KeyError, exception.ProcessExecutionError): msg = _('Failed to manage share.') LOG.exception(msg) raise exception.MapRFSException(msg=msg)
def manage_server(self, context, share_server, identifier, driver_options): """Manage the share server and return compiled back end details. :param context: Current context. :param share_server: Share server model. :param identifier: A driver-specific share server identifier :param driver_options: Dictionary of driver options to assist managing the share server :return: Identifier and dictionary with back end details to be saved in the database. Example:: 'my_new_server_identifier',{'server_name': 'my_old_server'} """ nas_server = self.client.get_nas_server(identifier) if not nas_server: message = ("Could not find the backend share server by server " "name: %s, please make sure the share server is " "existing in the backend." % identifier) raise exception.ManageInvalidShare(reason=message) return identifier, driver_options
def _manage_existing(self, share, hnas_share_id): """Manages a share that exists on backend. :param share: share that will be managed. :param hnas_share_id: HNAS ID of share that will be managed. :returns: Returns a dict with size of share managed and its export location. """ self._ensure_share(share, hnas_share_id) share_size = self.hnas.get_share_quota(hnas_share_id) if share_size is None: msg = (_("The share %s trying to be managed does not have a " "quota limit, please set it before manage.") % share['id']) raise exception.ManageInvalidShare(reason=msg) if share['share_proto'].lower() == 'nfs': path = self.hnas_evs_ip + os.path.join(':/shares', hnas_share_id) else: path = r'\\%s\%s' % (self.hnas_evs_ip, hnas_share_id) return {'size': share_size, 'export_locations': [path]}
def manage_existing(self, share, driver_options, share_server=None): """Manages a share that exists on backend. :param share: Share that will be managed. :param driver_options: Driver-specific options provided by admin. :param share_server: Share server name provided by admin in DHSS=True. :returns: Returns a dict with share size and export location. """ export_locations = share['export_locations'] if not export_locations: message = ("Failed to manage existing share: %s, missing " "export locations." % share['id']) raise exception.ManageInvalidShare(reason=message) try: share_size = int(driver_options.get("size", 0)) except (ValueError, TypeError): msg = _("The driver options' size to manage the share " "%(share_id)s, should be an integer, in format " "driver-options size=<SIZE>. Value specified: " "%(size)s.") % { 'share_id': share['id'], 'size': driver_options.get("size") } raise exception.ManageInvalidShare(reason=msg) if not share_size: msg = _("Share %(share_id)s has no specified size. " "Using default value 1, set size in driver options if you " "want.") % { 'share_id': share['id'] } LOG.warning(msg) share_size = 1 share_id = unity_utils.get_share_backend_id(share) backend_share = self.client.get_share(share_id, share['share_proto']) if not backend_share: message = ("Could not find the share in backend, please make sure " "the export location is right.") raise exception.ManageInvalidShare(reason=message) # Check the share server when in DHSS=true mode if share_server: backend_share_server = self._get_server_name(share_server) if not backend_share_server: message = ("Could not find the backend share server: %s, " "please make sure that share server with the " "specified name exists in the backend.", share_server) raise exception.BadConfigurationException(message) LOG.info( "Share %(shr_path)s is being managed with ID " "%(shr_id)s.", { 'shr_path': share['export_locations'][0]['path'], 'shr_id': share['id'] }) # export_locations was not changed, return original value return { "size": share_size, 'export_locations': { 'path': share['export_locations'][0]['path'] } }
def _manage_container(self, share, vserver_client): """Bring existing volume under management as a share.""" protocol_helper = self._get_helper(share) protocol_helper.set_client(vserver_client) volume_name = protocol_helper.get_share_name_for_share(share) if not volume_name: msg = _('Volume could not be determined from export location ' '%(export)s.') msg_args = {'export': share['export_location']} raise exception.ManageInvalidShare(reason=msg % msg_args) share_name = self._get_valid_share_name(share['id']) aggregate_name = share_utils.extract_host(share['host'], level='pool') # Get existing volume info volume = vserver_client.get_volume_to_manage(aggregate_name, volume_name) if not volume: msg = _('Volume %(volume)s not found on aggregate %(aggr)s.') msg_args = {'volume': volume_name, 'aggr': aggregate_name} raise exception.ManageInvalidShare(reason=msg % msg_args) # Ensure volume is manageable self._validate_volume_for_manage(volume, vserver_client) # Validate extra specs extra_specs = share_types.get_extra_specs_from_share(share) try: self._check_extra_specs_validity(share, extra_specs) self._check_aggregate_extra_specs_validity(aggregate_name, extra_specs) except exception.ManilaException as ex: raise exception.ManageExistingShareTypeMismatch( reason=six.text_type(ex)) provisioning_options = self._get_provisioning_options(extra_specs) debug_args = { 'share': share_name, 'aggr': aggregate_name, 'options': provisioning_options } LOG.debug('Managing share %(share)s on aggregate %(aggr)s with ' 'provisioning options %(options)s', debug_args) # Rename & remount volume on new path vserver_client.unmount_volume(volume_name) vserver_client.set_volume_name(volume_name, share_name) vserver_client.mount_volume(share_name) # Modify volume to match extra specs vserver_client.manage_volume(aggregate_name, share_name, **provisioning_options) # Save original volume info to private storage original_data = { 'original_name': volume['name'], 'original_junction_path': volume['junction-path'] } self.private_storage.update(share['id'], original_data) # When calculating the size, round up to the next GB. return int(math.ceil(float(volume['size']) / units.Gi))
def manage_existing(self, share, driver_options): """Manage existing share to manila. Generic driver accepts only one driver_option 'volume_id'. If an administrator provides this option, then appropriate Cinder volume will be managed by Manila as well. :param share: share data :param driver_options: Empty dict or dict with 'volume_id' option. :return: dict with share size, example: {'size': 1} """ helper = self._get_helper(share) share_server = self.service_instance_manager.get_common_server() server_details = share_server['backend_details'] old_export_location = share['export_locations'][0]['path'] mount_path = helper.get_share_path_by_export_location( share_server['backend_details'], old_export_location) LOG.debug("Manage: mount path = %s", mount_path) mounted = self._is_device_mounted(mount_path, server_details) LOG.debug("Manage: is share mounted = %s", mounted) if not mounted: msg = _("Provided share %s is not mounted.") % share['id'] raise exception.ManageInvalidShare(reason=msg) def get_volume(): if 'volume_id' in driver_options: try: return self.volume_api.get(self.admin_context, driver_options['volume_id']) except exception.VolumeNotFound as e: raise exception.ManageInvalidShare(reason=six.text_type(e)) # NOTE(vponomaryov): Manila can only combine volume name by itself, # nowhere to get volume ID from. Return None since Cinder volume # names are not unique or fixed, hence, they can not be used for # sure. return None share_volume = get_volume() if share_volume: instance_volumes = self.compute_api.instance_volumes_list( self.admin_context, server_details['instance_id']) attached_volumes = [vol.id for vol in instance_volumes] LOG.debug('Manage: attached volumes = %s', six.text_type(attached_volumes)) if share_volume['id'] not in attached_volumes: msg = _("Provided volume %s is not attached " "to service instance.") % share_volume['id'] raise exception.ManageInvalidShare(reason=msg) linked_volume_name = self._get_volume_name(share['id']) if share_volume['name'] != linked_volume_name: LOG.debug('Manage: volume_id = %s' % share_volume['id']) self.volume_api.update(self.admin_context, share_volume['id'], {'name': linked_volume_name}) self.private_storage.update(share['id'], {'volume_id': share_volume['id']}) share_size = share_volume['size'] else: share_size = self._get_mounted_share_size( mount_path, share_server['backend_details']) export_locations = helper.get_exports_for_share( server_details, old_export_location) return {'size': share_size, 'export_locations': export_locations}
def manage_existing(self, share, driver_options): """Manages a share that exists on backend.""" if share['share_proto'].lower() == 'nfs': # 10.0.0.1:/share/example LOG.info( "Share %(shr_path)s will be managed with ID " "%(shr_id)s.", { 'shr_path': share['export_locations'][0]['path'], 'shr_id': share['id'] }) old_path_info = share['export_locations'][0]['path'].split( ':/share/') if len(old_path_info) == 2: ip = old_path_info[0] share_name = old_path_info[1] else: msg = _("Incorrect path. It should have the following format: " "IP:/share/share_name.") raise exception.ShareBackendException(msg=msg) else: msg = _('Invalid NAS protocol: %s') % share['share_proto'] raise exception.InvalidInput(reason=msg) if ip != self.configuration.qnap_share_ip: msg = _("The NAS IP %(ip)s is not configured.") % {'ip': ip} raise exception.ShareBackendException(msg=msg) existing_share = self.api_executor.get_share_info( self.configuration.qnap_poolname, vol_label=share_name) if existing_share is None: msg = _("The share %s trying to be managed was not found on " "backend.") % share['id'] raise exception.ManageInvalidShare(reason=msg) extra_specs = share_types.get_extra_specs_from_share(share) qnap_thin_provision = share_types.parse_boolean_extra_spec( 'thin_provisioning', extra_specs.get("thin_provisioning") or extra_specs.get('capabilities:thin_provisioning') or 'true') qnap_compression = share_types.parse_boolean_extra_spec( 'compression', extra_specs.get("compression") or extra_specs.get('capabilities:compression') or 'true') qnap_deduplication = share_types.parse_boolean_extra_spec( 'dedupe', extra_specs.get("dedupe") or extra_specs.get('capabilities:dedupe') or 'false') qnap_ssd_cache = share_types.parse_boolean_extra_spec( 'qnap_ssd_cache', extra_specs.get("qnap_ssd_cache") or extra_specs.get("capabilities:qnap_ssd_cache") or 'false') LOG.debug( 'qnap_thin_provision: %(qnap_thin_provision)s ' 'qnap_compression: %(qnap_compression)s ' 'qnap_deduplication: %(qnap_deduplication)s ' 'qnap_ssd_cache: %(qnap_ssd_cache)s', { 'qnap_thin_provision': qnap_thin_provision, 'qnap_compression': qnap_compression, 'qnap_deduplication': qnap_deduplication, 'qnap_ssd_cache': qnap_ssd_cache }) if (qnap_deduplication and not qnap_thin_provision): msg = _("Dedupe cannot be enabled without thin_provisioning.") LOG.debug('Dedupe cannot be enabled without thin_provisioning.') raise exception.InvalidExtraSpec(reason=msg) vol_no = existing_share.find('vol_no').text vol = self.api_executor.get_specific_volinfo(vol_no) vol_size_gb = math.ceil(float(vol.find('size').text) / units.Gi) share_dict = { 'sharename': share_name, 'old_sharename': share_name, 'thin_provision': qnap_thin_provision, 'compression': qnap_compression, 'deduplication': qnap_deduplication, 'ssd_cache': qnap_ssd_cache, 'share_proto': share['share_proto'] } self.api_executor.edit_share(share_dict) _metadata = {} _metadata['volID'] = vol_no _metadata['volName'] = share_name _metadata['thin_provision'] = qnap_thin_provision _metadata['compression'] = qnap_compression _metadata['deduplication'] = qnap_deduplication _metadata['ssd_cache'] = qnap_ssd_cache self.private_storage.update(share['id'], _metadata) LOG.info( "Share %(shr_path)s was successfully managed with ID " "%(shr_id)s.", { 'shr_path': share['export_locations'][0]['path'], 'shr_id': share['id'] }) export_locations = self._get_location_path( share_name, share['share_proto'], self.configuration.qnap_share_ip, vol_no) return {'size': vol_size_gb, 'export_locations': export_locations}
def manage_existing(self, share, driver_options): """Manage an existing ZFSSA share. This feature requires an option 'zfssa_name', which specifies the name of the share as appeared in ZFSSA. The driver automatically retrieves information from the ZFSSA backend and returns the correct share size and export location. """ if 'zfssa_name' not in driver_options: msg = _('Name of the share in ZFSSA share has to be ' 'specified in option zfssa_name.') LOG.error(msg) raise exception.ShareBackendException(msg=msg) name = driver_options['zfssa_name'] try: details = self._get_share_details(name) except Exception: LOG.error('Cannot manage share %s', name) raise lcfg = self.configuration input_export_loc = share['export_locations'][0]['path'] proto = share['share_proto'] self._verify_share_to_manage(name, details) # Get and verify share size: size_byte = details['quota'] size_gb = int(math.ceil(size_byte / float(units.Gi))) if size_byte % units.Gi != 0: # Round up the size: new_size_byte = size_gb * units.Gi free_space = self.zfssa.get_project_stats(lcfg.zfssa_pool, lcfg.zfssa_project) diff_space = int(new_size_byte - size_byte) if diff_space > free_space: msg = (_('Quota and reservation of share %(name)s need to be ' 'rounded up to %(size)d. But there is not enough ' 'space in the backend.') % {'name': name, 'size': size_gb}) LOG.error(msg) raise exception.ManageInvalidShare(reason=msg) size_byte = new_size_byte # Get and verify share export location, also update share properties. arg = { 'host': lcfg.zfssa_data_ip, 'mountpoint': input_export_loc, 'name': share['id'], } manage_args = self.default_args.copy() manage_args.update(self.share_args) # The ZFSSA share name has to be updated, as Manila generates a new # share id for each share to be managed. manage_args.update({'name': share['id'], 'quota': size_byte, 'reservation': size_byte}) if proto == 'NFS': export_loc = ("%(host)s:%(mountpoint)s/%(name)s" % arg) manage_args.update({'sharenfs': 'sec=sys', 'sharesmb': 'off'}) elif proto == 'CIFS': export_loc = ("\\\\%(host)s\\%(name)s" % arg) manage_args.update({'sharesmb': 'on', 'sharenfs': 'off'}) else: msg = _('Protocol %s is not supported.') % proto LOG.error(msg) raise exception.ManageInvalidShare(reason=msg) self.zfssa.modify_share(lcfg.zfssa_pool, lcfg.zfssa_project, name, manage_args) return {'size': size_gb, 'export_locations': export_loc}