def _cleanup_one_backup(self, ctxt, backup): if backup['status'] == fields.BackupStatus.CREATING: LOG.info(_LI('Resetting backup %s to error (was creating).'), backup['id']) volume = storage.Volume.get_by_id(ctxt, backup.volume_id) self._cleanup_one_volume(ctxt, volume) err = 'incomplete backup reset on manager restart' self._update_backup_error(backup, ctxt, err) elif backup['status'] == fields.BackupStatus.RESTORING: LOG.info( _LI('Resetting backup %s to ' 'available (was restoring).'), backup['id']) volume = storage.Volume.get_by_id(ctxt, backup.restore_volume_id) self._cleanup_one_volume(ctxt, volume) backup.status = fields.BackupStatus.AVAILABLE backup.save() elif backup['status'] == fields.BackupStatus.DELETING: LOG.info(_LI('Resuming delete on backup: %s.'), backup['id']) if CONF.backup_service_inithost_offload: # Offload all the pending backup delete operations to the # threadpool to prevent the main backup service thread # from being blocked. self._add_to_threadpool(self.delete_backup, ctxt, backup) else: # By default, delete backups sequentially self.delete_backup(ctxt, backup)
def remove_export(self, context, volume): try: iscsi_target, lun = self._get_target_and_lun(context, volume) except exception.NotFound: LOG.info(_LI("Skipping remove_export. No iscsi_target " "provisioned for volume: %s"), volume['id']) return try: # NOTE: provider_location may be unset if the volume hasn't # been exported location = volume['provider_location'].split(' ') iqn = location[1] # ietadm show will exit with an error # this export has already been removed self.show_target(iscsi_target, iqn=iqn) except Exception: LOG.info(_LI("Skipping remove_export. No iscsi_target " "is presently exported for volume: %s"), volume['id']) return # NOTE: For TgtAdm case volume['id'] is the ONLY param we need self.remove_iscsi_target(iscsi_target, lun, volume['id'], volume['name'])
def _cleanup_one_backup(self, ctxt, backup): if backup['status'] == fields.BackupStatus.CREATING: LOG.info(_LI('Resetting backup %s to error (was creating).'), backup['id']) volume = storage.Volume.get_by_id(ctxt, backup.volume_id) self._cleanup_one_volume(ctxt, volume) err = 'incomplete backup reset on manager restart' self._update_backup_error(backup, ctxt, err) elif backup['status'] == fields.BackupStatus.RESTORING: LOG.info(_LI('Resetting backup %s to ' 'available (was restoring).'), backup['id']) volume = storage.Volume.get_by_id(ctxt, backup.restore_volume_id) self._cleanup_one_volume(ctxt, volume) backup.status = fields.BackupStatus.AVAILABLE backup.save() elif backup['status'] == fields.BackupStatus.DELETING: LOG.info(_LI('Resuming delete on backup: %s.'), backup['id']) if CONF.backup_service_inithost_offload: # Offload all the pending backup delete operations to the # threadpool to prevent the main backup service thread # from being blocked. self._add_to_threadpool(self.delete_backup, ctxt, backup) else: # By default, delete backups sequentially self.delete_backup(ctxt, backup)
def ensure_export(self, context, volume, volume_path): """Recreate exports for logical volumes.""" # Restore saved configuration file if no target exists. if not self._get_targets(): LOG.info(_LI('Restoring iSCSI target from configuration file')) self._restore_configuration() return LOG.info(_LI("Skipping ensure_export. Found existing iSCSI target."))
def ensure_export(self, context, volume, volume_path): """Recreate exports for logical volumes.""" # Restore saved configuration file if no target exists. if not self._get_targets(): LOG.info(_LI('Restoring iSCSI target from configuration file')) self._restore_configuration() return LOG.info(_LI("Skipping ensure_export. Found existing iSCSI target."))
def create_from_src(self, req, body): """Create a new consistency group from a source. The source can be a CG snapshot or a CG. Note that this does not require volume_types as the "create" API above. """ LOG.debug('Creating new consistency group %s.', body) self.assert_valid_body(body, 'consistencygroup-from-src') context = req.environ['storage.context'] consistencygroup = body['consistencygroup-from-src'] self.validate_name_and_description(consistencygroup) name = consistencygroup.get('name', None) description = consistencygroup.get('description', None) cgsnapshot_id = consistencygroup.get('cgsnapshot_id', None) source_cgid = consistencygroup.get('source_cgid', None) if not cgsnapshot_id and not source_cgid: msg = _("Either 'cgsnapshot_id' or 'source_cgid' must be " "provided to create consistency group %(name)s " "from source.") % {'name': name} raise exc.HTTPBadRequest(explanation=msg) if cgsnapshot_id and source_cgid: msg = _("Cannot provide both 'cgsnapshot_id' and 'source_cgid' " "to create consistency group %(name)s from " "source.") % {'name': name} raise exc.HTTPBadRequest(explanation=msg) if cgsnapshot_id: LOG.info(_LI("Creating consistency group %(name)s from " "cgsnapshot %(snap)s."), {'name': name, 'snap': cgsnapshot_id}, context=context) elif source_cgid: LOG.info(_LI("Creating consistency group %(name)s from " "source consistency group %(source_cgid)s."), {'name': name, 'source_cgid': source_cgid}, context=context) try: new_consistencygroup = self.consistencygroup_api.create_from_src( context, name, description, cgsnapshot_id, source_cgid) except exception.InvalidConsistencyGroup as error: raise exc.HTTPBadRequest(explanation=error.msg) except exception.CgSnapshotNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) except exception.ConsistencyGroupNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) except exception.CinderException as error: raise exc.HTTPBadRequest(explanation=error.msg) retval = self._view_builder.summary(req, new_consistencygroup) return retval
def get_friendly_zone_name(zoning_policy, initiator, target, host_name, storage_system, zone_name_prefix, supported_chars): """Utility function implementation of _get_friendly_zone_name. Get friendly zone name is used to form the zone name based on the details provided by the caller :param zoning_policy - determines the zoning policy is either initiator-target or initiator :param initiator - initiator WWN :param target - target WWN :param host_name - Host name returned from Volume Driver :param storage_system - Storage name returned from Volume Driver :param zone_name_prefix - user defined zone prefix configured in storage.conf :param supported_chars - Supported character set of FC switch vendor. Example: 'abc123_-$'. These are defined in the FC zone drivers. """ if host_name is None: host_name = '' if storage_system is None: storage_system = '' if zoning_policy == 'initiator-target': host_name = host_name[:14] storage_system = storage_system[:14] if len(host_name) > 0 and len(storage_system) > 0: zone_name = (host_name + "_" + initiator.replace(':', '') + "_" + storage_system + "_" + target.replace(':', '')) else: zone_name = (zone_name_prefix + initiator.replace(':', '') + target.replace(':', '')) LOG.info(_LI("Zone name created using prefix because either " "host name or storage system is none.")) else: host_name = host_name[:47] if len(host_name) > 0: zone_name = (host_name + "_" + initiator.replace(':', '')) else: zone_name = (zone_name_prefix + initiator.replace(':', '')) LOG.info(_LI("Zone name created using prefix because host " "name is none.")) LOG.info(_LI("Friendly zone name after forming: %(zonename)s"), {'zonename': zone_name}) zone_name = re.sub('[^%s]' % supported_chars, '', zone_name) return zone_name
def _clone_image_volume(self, context, volume, image_location, image_meta): """Create a volume efficiently from an existing image. Returns a dict of volume properties eg. provider_location, boolean indicating whether cloning occurred """ if not image_location: return None, False if (image_meta.get('container_format') != 'bare' or image_meta.get('disk_format') != 'raw'): LOG.info(_LI("Requested image %(id)s is not in raw format."), {'id': image_meta.get('id')}) return None, False image_volume = None direct_url, locations = image_location urls = set([direct_url] + [loc.get('url') for loc in locations or []]) image_volume_ids = [url[9:] for url in urls if url and url.startswith('storage://')] image_volumes = self.db.volume_get_all_by_host( context, volume['host'], filters={'id': image_volume_ids}) for image_volume in image_volumes: # For the case image volume is stored in the service tenant, # image_owner volume metadata should also be checked. image_owner = None volume_metadata = image_volume.get('volume_metadata') or {} for m in volume_metadata: if m['key'] == 'image_owner': image_owner = m['value'] if (image_meta['owner'] != volume['project_id'] and image_meta['owner'] != image_owner): LOG.info(_LI("Skipping image volume %(id)s because " "it is not accessible by current Tenant."), {'id': image_volume.id}) continue LOG.info(_LI("Will clone a volume from the image volume " "%(id)s."), {'id': image_volume.id}) break else: LOG.debug("No accessible image volume for image %(id)s found.", {'id': image_meta['id']}) return None, False try: return self.driver.create_cloned_volume(volume, image_volume), True except (NotImplementedError, exception.CinderException): LOG.exception(_LE('Failed to clone image volume %(id)s.'), {'id': image_volume['id']}) return None, False
def clear_volume(volume_size, volume_path, volume_clear=None, volume_clear_size=None, volume_clear_ionice=None, throttle=None): """Unprovision old volumes to prevent data leaking between users.""" if volume_clear is None: volume_clear = CONF.volume_clear if volume_clear_size is None: volume_clear_size = CONF.volume_clear_size if volume_clear_size == 0: volume_clear_size = volume_size if volume_clear_ionice is None: volume_clear_ionice = CONF.volume_clear_ionice LOG.info(_LI("Performing secure delete on volume: %s"), volume_path) # We pass sparse=False explicitly here so that zero blocks are not # skipped in order to clear the volume. if volume_clear == 'zero': return copy_volume('/dev/zero', volume_path, volume_clear_size, CONF.volume_dd_blocksize, sync=True, execute=utils.execute, ionice=volume_clear_ionice, throttle=throttle, sparse=False) elif volume_clear == 'shred': clear_cmd = ['shred', '-n3'] if volume_clear_size: clear_cmd.append('-s%dMiB' % volume_clear_size) else: raise exception.InvalidConfigurationValue(option='volume_clear', value=volume_clear) clear_cmd.append(volume_path) start_time = timeutils.utcnow() utils.execute(*clear_cmd, run_as_root=True) duration = timeutils.delta_seconds(start_time, timeutils.utcnow()) # NOTE(jdg): use a default of 1, mostly for unit test, but in # some incredible event this is 0 (cirros image?) don't barf if duration < 1: duration = 1 LOG.info(_LI('Elapsed time for clear volume: %.2f sec'), duration)
def export_record(self, context, backup): """Export all volume backup metadata details to allow clean import. Export backup metadata so it could be re-imported into the database without any prerequisite in the backup database. :param context: running context :param backup: backup object to export :returns: backup_record - a description of how to import the backup :returns: contains 'backup_url' - how to import the backup, and :returns: 'backup_service' describing the needed driver. :raises: InvalidBackup """ LOG.info(_LI('Export record started, backup: %s.'), backup.id) expected_status = fields.BackupStatus.AVAILABLE actual_status = backup.status if actual_status != expected_status: err = (_('Export backup aborted, expected backup status ' '%(expected_status)s but got %(actual_status)s.') % { 'expected_status': expected_status, 'actual_status': actual_status }) raise exception.InvalidBackup(reason=err) backup_record = {} backup_record['backup_service'] = backup.service backup_service = self._map_service_to_driver(backup.service) configured_service = self.driver_name if backup_service != configured_service: err = (_('Export record aborted, the backup service currently' ' configured [%(configured_service)s] is not the' ' backup service that was used to create this' ' backup [%(backup_service)s].') % { 'configured_service': configured_service, 'backup_service': backup_service }) raise exception.InvalidBackup(reason=err) # Call driver to create backup description string try: backup_service = self.service.get_backup_driver(context) driver_info = backup_service.export_record(backup) backup_url = backup.encode_record(driver_info=driver_info) backup_record['backup_url'] = backup_url except Exception as err: msg = six.text_type(err) raise exception.InvalidBackup(reason=msg) LOG.info(_LI('Export record finished, backup %s exported.'), backup.id) return backup_record
def export_record(self, context, backup): """Export all volume backup metadata details to allow clean import. Export backup metadata so it could be re-imported into the database without any prerequisite in the backup database. :param context: running context :param backup: backup object to export :returns: backup_record - a description of how to import the backup :returns: contains 'backup_url' - how to import the backup, and :returns: 'backup_service' describing the needed driver. :raises: InvalidBackup """ LOG.info(_LI('Export record started, backup: %s.'), backup.id) expected_status = fields.BackupStatus.AVAILABLE actual_status = backup.status if actual_status != expected_status: err = (_('Export backup aborted, expected backup status ' '%(expected_status)s but got %(actual_status)s.') % {'expected_status': expected_status, 'actual_status': actual_status}) raise exception.InvalidBackup(reason=err) backup_record = {} backup_record['backup_service'] = backup.service backup_service = self._map_service_to_driver(backup.service) configured_service = self.driver_name if backup_service != configured_service: err = (_('Export record aborted, the backup service currently' ' configured [%(configured_service)s] is not the' ' backup service that was used to create this' ' backup [%(backup_service)s].') % {'configured_service': configured_service, 'backup_service': backup_service}) raise exception.InvalidBackup(reason=err) # Call driver to create backup description string try: backup_service = self.service.get_backup_driver(context) driver_info = backup_service.export_record(backup) backup_url = backup.encode_record(driver_info=driver_info) backup_record['backup_url'] = backup_url except Exception as err: msg = six.text_type(err) raise exception.InvalidBackup(reason=msg) LOG.info(_LI('Export record finished, backup %s exported.'), backup.id) return backup_record
def _cleanup_one_volume(self, ctxt, volume): if volume['status'] == 'backing-up': self._detach_all_attachments(ctxt, volume) LOG.info(_LI('Resetting volume %(vol_id)s to previous ' 'status %(status)s (was backing-up).'), {'vol_id': volume['id'], 'status': volume['previous_status']}) self.db.volume_update(ctxt, volume['id'], {'status': volume['previous_status']}) elif volume['status'] == 'restoring-backup': self._detach_all_attachments(ctxt, volume) LOG.info(_LI('setting volume %s to error_restoring ' '(was restoring-backup).'), volume['id']) self.db.volume_update(ctxt, volume['id'], {'status': 'error_restoring'})
def _convert_image(prefix, source, dest, out_format, run_as_root=True): """Convert image to other format.""" cmd = prefix + ('qemu-img', 'convert', '-O', out_format, source, dest) # Check whether O_DIRECT is supported and set '-t none' if it is # This is needed to ensure that all data hit the device before # it gets unmapped remotely from the host for some backends # Reference Bug: #1363016 # NOTE(jdg): In the case of file devices qemu does the # flush properly and more efficiently than would be done # setting O_DIRECT, so check for that and skip the # setting for non BLK devs if (utils.is_blk_device(dest) and volume_utils.check_for_odirect_support( source, dest, 'oflag=direct')): cmd = prefix + ('qemu-img', 'convert', '-t', 'none', '-O', out_format, source, dest) start_time = timeutils.utcnow() utils.execute(*cmd, run_as_root=run_as_root) duration = timeutils.delta_seconds(start_time, timeutils.utcnow()) # NOTE(jdg): use a default of 1, mostly for unit test, but in # some incredible event this is 0 (cirros image?) don't barf if duration < 1: duration = 1 try: image_size = qemu_img_info(source, run_as_root=True).virtual_size except ValueError as e: msg = _LI("The image was successfully converted, but image size " "is unavailable. src %(src)s, dest %(dest)s. %(error)s") LOG.info(msg, {"src": source, "dest": dest, "error": e}) return fsz_mb = image_size / units.Mi mbps = (fsz_mb / duration) msg = ("Image conversion details: src %(src)s, size %(sz).2f MB, " "duration %(duration).2f sec, destination %(dest)s") LOG.debug(msg, { "src": source, "sz": fsz_mb, "duration": duration, "dest": dest }) msg = _LI("Converted %(sz).2f MB image at %(mbps).2f MB/s") LOG.info(msg, {"sz": fsz_mb, "mbps": mbps})
def get_friendly_zone_name(zoning_policy, initiator, target, host_name, storage_system, zone_name_prefix, supported_chars): """Utility function implementation of _get_friendly_zone_name. Get friendly zone name is used to form the zone name based on the details provided by the caller :param zoning_policy - determines the zoning policy is either initiator-target or initiator :param initiator - initiator WWN :param target - target WWN :param host_name - Host name returned from Volume Driver :param storage_system - Storage name returned from Volume Driver :param zone_name_prefix - user defined zone prefix configured in storage.conf :param supported_chars - Supported character set of FC switch vendor. Example: 'abc123_-$'. These are defined in the FC zone drivers. """ if host_name is None: host_name = '' if storage_system is None: storage_system = '' if zoning_policy == 'initiator-target': host_name = host_name[:14] storage_system = storage_system[:14] if len(host_name) > 0 and len(storage_system) > 0: zone_name = (host_name + "_" + initiator.replace(':', '') + "_" + storage_system + "_" + target.replace(':', '')) else: zone_name = (zone_name_prefix + initiator.replace(':', '') + target.replace(':', '')) LOG.info( _LI("Zone name created using prefix because either " "host name or storage system is none.")) else: host_name = host_name[:47] if len(host_name) > 0: zone_name = (host_name + "_" + initiator.replace(':', '')) else: zone_name = (zone_name_prefix + initiator.replace(':', '')) LOG.info( _LI("Zone name created using prefix because host " "name is none.")) LOG.info(_LI("Friendly zone name after forming: %(zonename)s"), {'zonename': zone_name}) zone_name = re.sub('[^%s]' % supported_chars, '', zone_name) return zone_name
def _copy_volume_with_file(src, dest, size_in_m): src_handle = src if isinstance(src, six.string_types): src_handle = _open_volume_with_path(src, 'rb') dest_handle = dest if isinstance(dest, six.string_types): dest_handle = _open_volume_with_path(dest, 'wb') if not src_handle: raise exception.DeviceUnavailable( _("Failed to copy volume, source device unavailable.")) if not dest_handle: raise exception.DeviceUnavailable( _("Failed to copy volume, destination device unavailable.")) start_time = timeutils.utcnow() _transfer_data(src_handle, dest_handle, size_in_m * units.Mi, units.Mi * 4) duration = max(1, timeutils.delta_seconds(start_time, timeutils.utcnow())) if isinstance(src, six.string_types): src_handle.close() if isinstance(dest, six.string_types): dest_handle.close() mbps = (size_in_m / duration) LOG.info( _LI("Volume copy completed (%(size_in_m).2f MB at " "%(mbps).2f MB/s)."), { 'size_in_m': size_in_m, 'mbps': mbps })
def get_valid_initiator_target_map(self, initiator_target_map, add_control): """Reference count check for end devices. Looks up the reference count for each initiator-target pair from the map and returns a filtered list based on the operation type add_control - operation type can be true for add connection control and false for remove connection control """ filtered_i_t_map = {} for initiator in initiator_target_map.keys(): t_list = initiator_target_map[initiator] for target in t_list: count = self.get_zoning_state_ref_count(initiator, target) if add_control: if count > 0: t_list.remove(target) # update count = count + 1 else: if count > 1: t_list.remove(target) # update count = count - 1 if t_list: filtered_i_t_map[initiator] = t_list else: LOG.info( _LI("No targets to add or remove connection for " "initiator: %(init_wwn)s"), {'init_wwn': initiator}) return filtered_i_t_map
def delete(self, backup): """Delete the given backup from Ceph object store.""" LOG.debug('Delete started for backup=%s', backup['id']) delete_failed = False try: self._try_delete_base_image(backup['id'], backup['volume_id']) except self.rbd.ImageNotFound: LOG.warning( _LW("RBD image for backup %(backup)s of volume %(volume)s " "not found. Deleting backup metadata."), {'backup': backup['id'], 'volume': backup['volume_id']}) delete_failed = True with rbd_driver.RADOSClient(self) as client: VolumeMetadataBackup(client, backup['id']).remove_if_exists() if delete_failed: LOG.info(_LI("Delete of backup '%(backup)s' " "for volume '%(volume)s' " "finished with warning."), {'backup': backup['id'], 'volume': backup['volume_id']}) else: LOG.debug("Delete of backup '%(backup)s' for volume " "'%(volume)s' finished.", {'backup': backup['id'], 'volume': backup['volume_id']})
def execute(self, context, volume, volume_spec): new_status = self.status_translation.get(volume_spec.get('status'), 'available') update = { 'status': new_status, 'launched_at': timeutils.utcnow(), } try: # TODO(harlowja): is it acceptable to only log if this fails?? # or are there other side-effects that this will cause if the # status isn't updated correctly (aka it will likely be stuck in # 'creating' if this fails)?? volume.update(update) volume.save() # Now use the parent to notify. super(CreateVolumeOnFinishTask, self).execute(context, volume) except exception.CinderException: LOG.exception(_LE("Failed updating volume %(volume_id)s with " "%(update)s"), {'volume_id': volume.id, 'update': update}) # Even if the update fails, the volume is ready. LOG.info(_LI("Volume %(volume_name)s (%(volume_id)s): " "created successfully"), {'volume_name': volume_spec['volume_name'], 'volume_id': volume.id})
def unmanage(self, req, id, body): """Stop managing a volume. This action is very much like a delete, except that a different method (unmanage) is called on the Cinder driver. This has the effect of removing the volume from Cinder management without actually removing the backend storage object associated with it. There are no required parameters. A Not Found error is returned if the specified volume does not exist. A Bad Request error is returned if the specified volume is still attached to an instance. """ context = req.environ['storage.context'] authorize(context) LOG.info(_LI("Unmanage volume with id: %s"), id, context=context) try: vol = self.volume_api.get(context, id) self.volume_api.delete(context, vol, unmanage_only=True) except exception.VolumeNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) return webob.Response(status_int=202)
def unmanage(self, req, id, body): """Stop managing a snapshot. This action is very much like a delete, except that a different method (unmanage) is called on the Cinder driver. This has the effect of removing the snapshot from Cinder management without actually removing the backend storage object associated with it. There are no required parameters. A Not Found error is returned if the specified snapshot does not exist. """ context = req.environ['storage.context'] authorize(context) LOG.info(_LI("Unmanage snapshot with id: %s"), id, context=context) try: snapshot = self.volume_api.get_snapshot(context, id) self.volume_api.delete_snapshot(context, snapshot, unmanage_only=True) except exception.SnapshotNotFound as ex: raise exc.HTTPNotFound(explanation=ex.msg) except exception.InvalidSnapshot as ex: raise exc.HTTPBadRequest(explanation=ex.msg) return webob.Response(status_int=202)
def _copy_volume_with_file(src, dest, size_in_m): src_handle = src if isinstance(src, six.string_types): src_handle = _open_volume_with_path(src, 'rb') dest_handle = dest if isinstance(dest, six.string_types): dest_handle = _open_volume_with_path(dest, 'wb') if not src_handle: raise exception.DeviceUnavailable( _("Failed to copy volume, source device unavailable.")) if not dest_handle: raise exception.DeviceUnavailable( _("Failed to copy volume, destination device unavailable.")) start_time = timeutils.utcnow() _transfer_data(src_handle, dest_handle, size_in_m * units.Mi, units.Mi * 4) duration = max(1, timeutils.delta_seconds(start_time, timeutils.utcnow())) if isinstance(src, six.string_types): src_handle.close() if isinstance(dest, six.string_types): dest_handle.close() mbps = (size_in_m / duration) LOG.info(_LI("Volume copy completed (%(size_in_m).2f MB at " "%(mbps).2f MB/s)."), {'size_in_m': size_in_m, 'mbps': mbps})
def create(self, req, body): """Create a new consistency group.""" LOG.debug('Creating new consistency group %s', body) self.assert_valid_body(body, 'consistencygroup') context = req.environ['storage.context'] consistencygroup = body['consistencygroup'] self.validate_name_and_description(consistencygroup) name = consistencygroup.get('name', None) description = consistencygroup.get('description', None) volume_types = consistencygroup.get('volume_types', None) if not volume_types: msg = _("volume_types must be provided to create " "consistency group %(name)s.") % {'name': name} raise exc.HTTPBadRequest(explanation=msg) availability_zone = consistencygroup.get('availability_zone', None) LOG.info(_LI("Creating consistency group %(name)s."), {'name': name}, context=context) try: new_consistencygroup = self.consistencygroup_api.create( context, name, description, volume_types, availability_zone=availability_zone) except exception.InvalidConsistencyGroup as error: raise exc.HTTPBadRequest(explanation=error.msg) except exception.InvalidVolumeType as error: raise exc.HTTPBadRequest(explanation=error.msg) except exception.ConsistencyGroupNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) retval = self._view_builder.summary(req, new_consistencygroup) return retval
def get_filtered_objects(self, filter_classes, objs, filter_properties, index=0): """Get objects after filter :param filter_classes: filters that will be used to filter the objects :param objs: objects that will be filtered :param filter_properties: client filter properties :param index: This value needs to be increased in the caller function of get_filtered_objects when handling each resource. """ list_objs = list(objs) LOG.debug("Starting with %d host(s)", len(list_objs)) for filter_cls in filter_classes: cls_name = filter_cls.__name__ filter_class = filter_cls() if filter_class.run_filter_for_index(index): objs = filter_class.filter_all(list_objs, filter_properties) if objs is None: LOG.debug("Filter %(cls_name)s says to stop filtering", {'cls_name': cls_name}) return list_objs = list(objs) msg = (_LI("Filter %(cls_name)s returned %(obj_len)d host(s)") % {'cls_name': cls_name, 'obj_len': len(list_objs)}) if not list_objs: LOG.info(msg) break LOG.debug(msg) return list_objs
def _save_vol_base_meta(self, container, volume_id): """Save base volume metadata to container. This will fetch all fields from the db Volume object for volume_id and save them in the provided container dictionary. """ type_tag = self.TYPE_TAG_VOL_BASE_META LOG.debug("Getting metadata type '%s'", type_tag) meta = self.db.volume_get(self.context, volume_id) if meta: container[type_tag] = {} for key, value in meta: # Exclude fields that are "not JSON serializable" if not self._is_serializable(value): LOG.info(_LI("Unable to serialize field '%s' - excluding " "from backup"), key) continue # Copy the encryption key uuid for backup if key is 'encryption_key_id' and value is not None: value = keymgr.API().copy_key(self.context, value) LOG.debug("Copying encryption key uuid for backup.") container[type_tag][key] = value LOG.debug("Completed fetching metadata type '%s'", type_tag) else: LOG.debug("No metadata type '%s' available", type_tag)
def delete(self, req, id, body): """Delete a consistency group.""" LOG.debug('delete called for member %s', id) context = req.environ['storage.context'] force = False if body: if not self.is_valid_body(body, 'consistencygroup'): msg = _("Missing required element 'consistencygroup' in " "request body.") raise exc.HTTPBadRequest(explanation=msg) cg_body = body['consistencygroup'] try: force = strutils.bool_from_string(cg_body.get('force', False), strict=True) except ValueError: msg = _("Invalid value '%s' for force.") % force raise exc.HTTPBadRequest(explanation=msg) LOG.info(_LI('Delete consistency group with id: %s'), id, context=context) try: group = self.consistencygroup_api.get(context, id) self.consistencygroup_api.delete(context, group, force) except exception.ConsistencyGroupNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) except exception.InvalidConsistencyGroup as error: raise exc.HTTPBadRequest(explanation=error.msg) return webob.Response(status_int=202)
def fetch(context, image_service, image_id, path, _user_id, _project_id): # TODO(vish): Improve context handling and add owner and auth data # when it is added to glance. Right now there is no # auth checking in glance, so we assume that access was # checked before we got here. start_time = timeutils.utcnow() with fileutils.remove_path_on_error(path): with open(path, "wb") as image_file: image_service.download(context, image_id, image_file) duration = timeutils.delta_seconds(start_time, timeutils.utcnow()) # NOTE(jdg): use a default of 1, mostly for unit test, but in # some incredible event this is 0 (cirros image?) don't barf if duration < 1: duration = 1 fsz_mb = os.stat(image_file.name).st_size / units.Mi mbps = (fsz_mb / duration) msg = ("Image fetch details: dest %(dest)s, size %(sz).2f MB, " "duration %(duration).2f sec") LOG.debug(msg, { "dest": image_file.name, "sz": fsz_mb, "duration": duration }) msg = _LI("Image download %(sz).2f MB at %(mbps).2f MB/s") LOG.info(msg, {"sz": fsz_mb, "mbps": mbps})
def _reconnect(self): """Reconnect with jittered exponential backoff increase.""" LOG.info(_LI('Reconnecting to coordination backend.')) cap = cfg.CONF.coordination.max_reconnect_backoff backoff = base = cfg.CONF.coordination.initial_reconnect_backoff for attempt in itertools.count(1): try: self._start() break except coordination.ToozError: backoff = min(cap, random.uniform(base, backoff * 3)) msg = _LW('Reconnect attempt %(attempt)s failed. ' 'Next try in %(backoff).2fs.') LOG.warning(msg, {'attempt': attempt, 'backoff': backoff}) self._dead.wait(backoff) LOG.info(_LI('Reconnected to coordination backend.'))
def get_valid_initiator_target_map(self, initiator_target_map, add_control): """Reference count check for end devices. Looks up the reference count for each initiator-target pair from the map and returns a filtered list based on the operation type add_control - operation type can be true for add connection control and false for remove connection control """ filtered_i_t_map = {} for initiator in initiator_target_map.keys(): t_list = initiator_target_map[initiator] for target in t_list: count = self.get_zoning_state_ref_count(initiator, target) if add_control: if count > 0: t_list.remove(target) # update count = count + 1 else: if count > 1: t_list.remove(target) # update count = count - 1 if t_list: filtered_i_t_map[initiator] = t_list else: LOG.info(_LI("No targets to add or remove connection for " "initiator: %(init_wwn)s"), {'init_wwn': initiator}) return filtered_i_t_map
def unmanage(self, req, id, body): """Stop managing a snapshot. This action is very much like a delete, except that a different method (unmanage) is called on the Cinder driver. This has the effect of removing the snapshot from Cinder management without actually removing the backend storage object associated with it. There are no required parameters. A Not Found error is returned if the specified snapshot does not exist. """ context = req.environ['storage.context'] authorize(context) LOG.info(_LI("Unmanage snapshot with id: %s"), id, context=context) try: snapshot = self.volume_api.get_snapshot(context, id) self.volume_api.delete_snapshot(context, snapshot, unmanage_only=True) except exception.SnapshotNotFound as ex: raise exc.HTTPNotFound(explanation=ex.msg) except exception.InvalidSnapshot as ex: raise exc.HTTPBadRequest(explanation=ex.msg) return webob.Response(status_int=202)
def create(self, context, volume_id, display_name): """Creates an entry in the transfers table.""" volume_api.check_policy(context, 'create_transfer') LOG.info(_LI("Generating transfer record for volume %s"), volume_id) volume_ref = self.db.volume_get(context, volume_id) if volume_ref['status'] != "available": raise exception.InvalidVolume(reason=_("status must be available")) volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.create.start") # The salt is just a short random string. salt = self._get_random_string(CONF.volume_transfer_salt_length) auth_key = self._get_random_string(CONF.volume_transfer_key_length) crypt_hash = self._get_crypt_hash(salt, auth_key) # TODO(ollie): Transfer expiry needs to be implemented. transfer_rec = {'volume_id': volume_id, 'display_name': display_name, 'salt': salt, 'crypt_hash': crypt_hash, 'expires_at': None} try: transfer = self.db.transfer_create(context, transfer_rec) except Exception: LOG.error(_LE("Failed to create transfer record " "for %s"), volume_id) raise volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.create.end") return {'id': transfer['id'], 'volume_id': transfer['volume_id'], 'display_name': transfer['display_name'], 'auth_key': auth_key, 'created_at': transfer['created_at']}
def _reconnect(self): """Reconnect with jittered exponential backoff increase.""" LOG.info(_LI('Reconnecting to coordination backend.')) cap = cfg.CONF.coordination.max_reconnect_backoff backoff = base = cfg.CONF.coordination.initial_reconnect_backoff for attempt in itertools.count(1): try: self._start() break except coordination.ToozError: backoff = min(cap, random.uniform(base, backoff * 3)) msg = _LW('Reconnect attempt %(attempt)s failed. ' 'Next try in %(backoff).2fs.') LOG.warning(msg, {'attempt': attempt, 'backoff': backoff}) self._dead.wait(backoff) LOG.info(_LI('Reconnected to coordination backend.'))
def delete(self, req, id, body): """Delete a consistency group.""" LOG.debug('delete called for member %s', id) context = req.environ['storage.context'] force = False if body: if not self.is_valid_body(body, 'consistencygroup'): msg = _("Missing required element 'consistencygroup' in " "request body.") raise exc.HTTPBadRequest(explanation=msg) cg_body = body['consistencygroup'] try: force = strutils.bool_from_string(cg_body.get('force', False), strict=True) except ValueError: msg = _("Invalid value '%s' for force.") % force raise exc.HTTPBadRequest(explanation=msg) LOG.info(_LI('Delete consistency group with id: %s'), id, context=context) try: group = self.consistencygroup_api.get(context, id) self.consistencygroup_api.delete(context, group, force) except exception.ConsistencyGroupNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) except exception.InvalidConsistencyGroup as error: raise exc.HTTPBadRequest(explanation=error.msg) return webob.Response(status_int=202)
def accept(self, req, id, body): """Accept a new volume transfer.""" transfer_id = id LOG.debug('Accepting volume transfer %s', transfer_id) self.assert_valid_body(body, 'accept') context = req.environ['storage.context'] accept = body['accept'] try: auth_key = accept['auth_key'] except KeyError: msg = _("Incorrect request body format") raise exc.HTTPBadRequest(explanation=msg) LOG.info(_LI("Accepting transfer %s"), transfer_id, context=context) try: accepted_transfer = self.transfer_api.accept(context, transfer_id, auth_key) except exception.VolumeSizeExceedsAvailableQuota as error: raise exc.HTTPRequestEntityTooLarge( explanation=error.msg, headers={'Retry-After': '0'}) except exception.InvalidVolume as error: raise exc.HTTPBadRequest(explanation=error.msg) transfer = \ self._view_builder.summary(req, dict(accepted_transfer)) return transfer
def create(self, req, body): """Create a new volume transfer.""" LOG.debug('Creating new volume transfer %s', body) self.assert_valid_body(body, 'transfer') context = req.environ['storage.context'] transfer = body['transfer'] try: volume_id = transfer['volume_id'] except KeyError: msg = _("Incorrect request body format") raise exc.HTTPBadRequest(explanation=msg) name = transfer.get('name', None) if name is not None: self.validate_string_length(name, 'Transfer name', min_length=1, max_length=255, remove_whitespaces=True) name = name.strip() LOG.info(_LI("Creating transfer of volume %s"), volume_id, context=context) try: new_transfer = self.transfer_api.create(context, volume_id, name) except exception.InvalidVolume as error: raise exc.HTTPBadRequest(explanation=error.msg) except exception.VolumeNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) transfer = self._view_builder.create(req, dict(new_transfer)) return transfer
def accept(self, req, id, body): """Accept a new volume transfer.""" transfer_id = id LOG.debug('Accepting volume transfer %s', transfer_id) self.assert_valid_body(body, 'accept') context = req.environ['storage.context'] accept = body['accept'] try: auth_key = accept['auth_key'] except KeyError: msg = _("Incorrect request body format") raise exc.HTTPBadRequest(explanation=msg) LOG.info(_LI("Accepting transfer %s"), transfer_id, context=context) try: accepted_transfer = self.transfer_api.accept( context, transfer_id, auth_key) except exception.VolumeSizeExceedsAvailableQuota as error: raise exc.HTTPRequestEntityTooLarge(explanation=error.msg, headers={'Retry-After': '0'}) except exception.InvalidVolume as error: raise exc.HTTPBadRequest(explanation=error.msg) transfer = \ self._view_builder.summary(req, dict(accepted_transfer)) return transfer
def _update_snapshot_status(self, req, id, body): """Update database fields related to status of a snapshot. Intended for creation of snapshots, so snapshot state must start as 'creating' and be changed to 'available', 'creating', or 'error'. """ context = req.environ['storage.context'] authorize(context, 'update_snapshot_status') LOG.debug("body: %s", body) try: status = body['os-update_snapshot_status']['status'] except KeyError: msg = _("'status' must be specified.") raise webob.exc.HTTPBadRequest(explanation=msg) # Allowed state transitions status_map = {'creating': ['creating', 'available', 'error'], 'deleting': ['deleting', 'error_deleting']} current_snapshot = storage.Snapshot.get_by_id(context, id) if current_snapshot.status not in status_map: msg = _("Snapshot status %(cur)s not allowed for " "update_snapshot_status") % { 'cur': current_snapshot.status} raise webob.exc.HTTPBadRequest(explanation=msg) if status not in status_map[current_snapshot.status]: msg = _("Provided snapshot status %(provided)s not allowed for " "snapshot with status %(current)s.") % \ {'provided': status, 'current': current_snapshot.status} raise webob.exc.HTTPBadRequest(explanation=msg) update_dict = {'id': id, 'status': status} progress = body['os-update_snapshot_status'].get('progress', None) if progress: # This is expected to be a string like '73%' msg = _('progress must be an integer percentage') try: integer = int(progress[:-1]) except ValueError: raise webob.exc.HTTPBadRequest(explanation=msg) if integer < 0 or integer > 100 or progress[-1] != '%': raise webob.exc.HTTPBadRequest(explanation=msg) update_dict.update({'progress': progress}) LOG.info(_LI("Updating snapshot %(id)s with info %(dict)s"), {'id': id, 'dict': update_dict}) current_snapshot.update(update_dict) current_snapshot.save() return webob.Response(status_int=202)
def __init__(self): LOG.info( _LI('Initializing extension manager.CONF.osapi_volume_extension = %s' ), CONF.osapi_volume_extension) self.cls_list = CONF.osapi_volume_extension self.extensions = {} self._load_extensions()
def _diff_restore_allowed(self, base_name, backup, volume, volume_file, rados_client): """Determine if differential restore is possible and restore point. Determine whether a differential restore is possible/allowed, and find out the restore point if backup base is diff-format. In order for a differential restore to be performed we need: * destination volume must be RBD * destination volume must have zero extents * backup base image must exist * backup must have a restore point * target volume is different from source volume of backup Returns True if differential restore is allowed, False otherwise. Return the restore point if back base is diff-format. """ # NOTE(dosaboy): base_name here must be diff format. rbd_exists, base_name = self._rbd_image_exists(base_name, backup['volume_id'], rados_client) if not rbd_exists: return False, None # Get the restore point. If no restore point is found, we assume # that the backup was not performed using diff/incremental methods # so we enforce full copy. restore_point = self._get_restore_point(base_name, backup['id']) if restore_point: if self._file_is_rbd(volume_file): # If the volume we are restoring to is the volume the backup # was made from, force a full restore since a diff will not # work in this case. if volume['id'] == backup['volume_id']: LOG.debug("Destination volume is same as backup source " "volume %s - forcing full copy.", volume['id']) return False, restore_point # If the destination volume has extents we cannot allow a diff # restore. if self._rbd_has_extents(volume_file.rbd_image): # We return the restore point so that a full copy is done # from snapshot. LOG.debug("Destination has extents - forcing full copy") return False, restore_point return True, restore_point else: LOG.info(_LI("No restore point found for backup=" "'%(backup)s' of volume %(volume)s " "although base image is found - " "forcing full copy."), {'backup': backup['id'], 'volume': backup['volume_id']}) return False, restore_point
def clear_volume(volume_size, volume_path, volume_clear=None, volume_clear_size=None, volume_clear_ionice=None, throttle=None): """Unprovision old volumes to prevent data leaking between users.""" if volume_clear is None: volume_clear = CONF.volume_clear if volume_clear_size is None: volume_clear_size = CONF.volume_clear_size if volume_clear_size == 0: volume_clear_size = volume_size if volume_clear_ionice is None: volume_clear_ionice = CONF.volume_clear_ionice LOG.info(_LI("Performing secure delete on volume: %s"), volume_path) # We pass sparse=False explicitly here so that zero blocks are not # skipped in order to clear the volume. if volume_clear == 'zero': return copy_volume('/dev/zero', volume_path, volume_clear_size, CONF.volume_dd_blocksize, sync=True, execute=utils.execute, ionice=volume_clear_ionice, throttle=throttle, sparse=False) elif volume_clear == 'shred': clear_cmd = ['shred', '-n3'] if volume_clear_size: clear_cmd.append('-s%dMiB' % volume_clear_size) else: raise exception.InvalidConfigurationValue( option='volume_clear', value=volume_clear) clear_cmd.append(volume_path) start_time = timeutils.utcnow() utils.execute(*clear_cmd, run_as_root=True) duration = timeutils.delta_seconds(start_time, timeutils.utcnow()) # NOTE(jdg): use a default of 1, mostly for unit test, but in # some incredible event this is 0 (cirros image?) don't barf if duration < 1: duration = 1 LOG.info(_LI('Elapsed time for clear volume: %.2f sec'), duration)
def reset(self): """Method executed when SIGHUP is caught by the process. We're utilizing it to reset RPC API version pins to avoid restart of the service when rolling upgrade is completed. """ LOG.info(_LI('Resetting cached RPC version pins.')) rpc.LAST_OBJ_VERSIONS = {} rpc.LAST_RPC_VERSIONS = {}
def default(self, string): dom = utils.safe_minidom_parse_string(string) key_node = self.find_first_child_named(dom, 'keys') if not key_node: LOG.info(_LI("Unable to parse XML input.")) msg = _("Unable to parse XML request. " "Please provide XML in correct format.") raise webob.exc.HTTPBadRequest(explanation=msg) return {'body': {'keys': self._extract_keys(key_node)}}
def _cleanup_one_volume(self, ctxt, volume): if volume['status'] == 'backing-up': self._detach_all_attachments(ctxt, volume) LOG.info( _LI('Resetting volume %(vol_id)s to previous ' 'status %(status)s (was backing-up).'), { 'vol_id': volume['id'], 'status': volume['previous_status'] }) self.db.volume_update(ctxt, volume['id'], {'status': volume['previous_status']}) elif volume['status'] == 'restoring-backup': self._detach_all_attachments(ctxt, volume) LOG.info( _LI('setting volume %s to error_restoring ' '(was restoring-backup).'), volume['id']) self.db.volume_update(ctxt, volume['id'], {'status': 'error_restoring'})
def default(self, string): dom = utils.safe_minidom_parse_string(string) key_node = self.find_first_child_named(dom, 'keys') if not key_node: LOG.info(_LI("Unable to parse XML input.")) msg = _("Unable to parse XML request. " "Please provide XML in correct format.") raise webob.exc.HTTPBadRequest(explanation=msg) return {'body': {'keys': self._extract_keys(key_node)}}
def update(self, req, id, body): """Update the consistency group. Expected format of the input parameter 'body': { "consistencygroup": { "name": "my_cg", "description": "My consistency group", "add_volumes": "volume-uuid-1,volume-uuid-2,..." "remove_volumes": "volume-uuid-8,volume-uuid-9,..." } } """ LOG.debug('Update called for consistency group %s.', id) if not body: msg = _("Missing request body.") raise exc.HTTPBadRequest(explanation=msg) self.assert_valid_body(body, 'consistencygroup') context = req.environ['storage.context'] consistencygroup = body.get('consistencygroup', None) self.validate_name_and_description(consistencygroup) name = consistencygroup.get('name', None) description = consistencygroup.get('description', None) add_volumes = consistencygroup.get('add_volumes', None) remove_volumes = consistencygroup.get('remove_volumes', None) if (not name and not description and not add_volumes and not remove_volumes): msg = _("Name, description, add_volumes, and remove_volumes " "can not be all empty in the request body.") raise exc.HTTPBadRequest(explanation=msg) LOG.info(_LI("Updating consistency group %(id)s with name %(name)s " "description: %(description)s add_volumes: " "%(add_volumes)s remove_volumes: %(remove_volumes)s."), { 'id': id, 'name': name, 'description': description, 'add_volumes': add_volumes, 'remove_volumes': remove_volumes }, context=context) try: group = self.consistencygroup_api.get(context, id) self.consistencygroup_api.update(context, group, name, description, add_volumes, remove_volumes) except exception.ConsistencyGroupNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) except exception.InvalidConsistencyGroup as error: raise exc.HTTPBadRequest(explanation=error.msg) return webob.Response(status_int=202)
def update(self, req, id, body): """Update the consistency group. Expected format of the input parameter 'body': { "consistencygroup": { "name": "my_cg", "description": "My consistency group", "add_volumes": "volume-uuid-1,volume-uuid-2,..." "remove_volumes": "volume-uuid-8,volume-uuid-9,..." } } """ LOG.debug('Update called for consistency group %s.', id) if not body: msg = _("Missing request body.") raise exc.HTTPBadRequest(explanation=msg) self.assert_valid_body(body, 'consistencygroup') context = req.environ['storage.context'] consistencygroup = body.get('consistencygroup', None) self.validate_name_and_description(consistencygroup) name = consistencygroup.get('name', None) description = consistencygroup.get('description', None) add_volumes = consistencygroup.get('add_volumes', None) remove_volumes = consistencygroup.get('remove_volumes', None) if (not name and not description and not add_volumes and not remove_volumes): msg = _("Name, description, add_volumes, and remove_volumes " "can not be all empty in the request body.") raise exc.HTTPBadRequest(explanation=msg) LOG.info(_LI("Updating consistency group %(id)s with name %(name)s " "description: %(description)s add_volumes: " "%(add_volumes)s remove_volumes: %(remove_volumes)s."), {'id': id, 'name': name, 'description': description, 'add_volumes': add_volumes, 'remove_volumes': remove_volumes}, context=context) try: group = self.consistencygroup_api.get(context, id) self.consistencygroup_api.update( context, group, name, description, add_volumes, remove_volumes) except exception.ConsistencyGroupNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) except exception.InvalidConsistencyGroup as error: raise exc.HTTPBadRequest(explanation=error.msg) return webob.Response(status_int=202)
def _is_serializable(value): """Returns True if value is serializable.""" try: jsonutils.dumps(value) except TypeError: LOG.info(_LI("Value with type=%s is not serializable"), type(value)) return False return True
def remove_iscsi_target(self, tid, lun, vol_id, vol_name, **kwargs): LOG.info(_LI("Removing iscsi_target for volume: %s"), vol_id) try: self._delete_logicalunit(tid, lun) session_info = self._find_sid_cid_for_target(tid, vol_name, vol_id) if session_info: sid, cid = session_info self._force_delete_target(tid, sid, cid) self._delete_target(tid) except putils.ProcessExecutionError: LOG.exception( _LE("Failed to remove iscsi target for volume " "id:%s"), vol_id) raise exception.ISCSITargetRemoveFailed(volume_id=vol_id) vol_uuid_file = vol_name conf_file = self.iet_conf if os.path.exists(conf_file): try: with utils.temporary_chown(conf_file): with open(conf_file, 'r+') as iet_conf_text: full_txt = iet_conf_text.readlines() new_iet_conf_txt = [] count = 0 for line in full_txt: if count > 0: count -= 1 continue elif vol_uuid_file in line: count = 2 continue else: new_iet_conf_txt.append(line) iet_conf_text.seek(0) iet_conf_text.truncate(0) iet_conf_text.writelines(new_iet_conf_txt) except Exception: LOG.exception( _LE("Failed to update %(conf)s for volume id " "%(vol_id)s after removing iscsi target"), { 'conf': conf_file, 'vol_id': vol_id }) raise exception.ISCSITargetRemoveFailed(volume_id=vol_id) else: LOG.warning( _LW("Failed to update %(conf)s for volume id " "%(vol_id)s after removing iscsi target. " "%(conf)s does not exist."), { 'conf': conf_file, 'vol_id': vol_id })
def __init__(self, ip, port, conn_timeout, login, password=None, privatekey=None, *args, **kwargs): self.ip = ip self.port = port self.login = login self.password = password self.conn_timeout = conn_timeout if conn_timeout else None self.privatekey = privatekey self.hosts_key_file = None # Validate good config setting here. # Paramiko handles the case where the file is inaccessible. if not CONF.ssh_hosts_key_file: raise exception.ParameterNotFound(param='ssh_hosts_key_file') elif not os.path.isfile(CONF.ssh_hosts_key_file): # If using the default path, just create the file. if CONF.state_path in CONF.ssh_hosts_key_file: open(CONF.ssh_hosts_key_file, 'a').close() else: msg = (_("Unable to find ssh_hosts_key_file: %s") % CONF.ssh_hosts_key_file) raise exception.InvalidInput(reason=msg) if 'hosts_key_file' in kwargs.keys(): self.hosts_key_file = kwargs.pop('hosts_key_file') LOG.info( _LI("Secondary ssh hosts key file %(kwargs)s will be " "loaded along with %(conf)s from /etc/storage.conf."), { 'kwargs': self.hosts_key_file, 'conf': CONF.ssh_hosts_key_file }) LOG.debug( "Setting strict_ssh_host_key_policy to '%(policy)s' " "using ssh_hosts_key_file '%(key_file)s'.", { 'policy': CONF.strict_ssh_host_key_policy, 'key_file': CONF.ssh_hosts_key_file }) self.strict_ssh_host_key_policy = CONF.strict_ssh_host_key_policy if not self.hosts_key_file: self.hosts_key_file = CONF.ssh_hosts_key_file else: self.hosts_key_file += ',' + CONF.ssh_hosts_key_file super(SSHPool, self).__init__(*args, **kwargs)
def delete(self, req, id): """Delete a transfer.""" context = req.environ['storage.context'] LOG.info(_LI("Delete transfer with id: %s"), id, context=context) try: self.transfer_api.delete(context, transfer_id=id) except exception.TransferNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) return webob.Response(status_int=202)
def register(self, ext): # Do nothing if the extension doesn't check out if not self._check_extension(ext): return alias = ext.alias LOG.info(_LI('Loaded extension: %s'), alias) if alias in self.extensions: raise exception.Error("Found duplicate extension: %s" % alias) self.extensions[alias] = ext
def create_lookup_service(): config = configuration.Configuration(manager.volume_manager_opts) LOG.debug("Zoning mode: %s.", config.safe_get('zoning_mode')) if config.safe_get('zoning_mode') == 'fabric': LOG.debug("FC Lookup Service enabled.") lookup = fc_san_lookup_service.FCSanLookupService() LOG.info(_LI("Using FC lookup service %s."), lookup.lookup_service) return lookup else: LOG.debug("FC Lookup Service not enabled in storage.conf.") return None
def create_lookup_service(): config = configuration.Configuration(manager.volume_manager_opts) LOG.debug("Zoning mode: %s.", config.safe_get('zoning_mode')) if config.safe_get('zoning_mode') == 'fabric': LOG.debug("FC Lookup Service enabled.") lookup = fc_san_lookup_service.FCSanLookupService() LOG.info(_LI("Using FC lookup service %s."), lookup.lookup_service) return lookup else: LOG.debug("FC Lookup Service not enabled in storage.conf.") return None
def register(self, ext): # Do nothing if the extension doesn't check out if not self._check_extension(ext): return alias = ext.alias LOG.info(_LI('Loaded extension: %s'), alias) if alias in self.extensions: raise exception.Error("Found duplicate extension: %s" % alias) self.extensions[alias] = ext
def delete(self, req, id): """Delete a transfer.""" context = req.environ['storage.context'] LOG.info(_LI("Delete transfer with id: %s"), id, context=context) try: self.transfer_api.delete(context, transfer_id=id) except exception.TransferNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) return webob.Response(status_int=202)
def delete(self, req, id): """Delete a volume.""" context = req.environ['storage.context'] LOG.info(_LI("Delete volume with id: %s"), id, context=context) try: volume = self.volume_api.get(context, id) self.volume_api.delete(context, volume) except exception.NotFound: raise exc.HTTPNotFound() return webob.Response(status_int=202)
def create(self, req, body): """Creates a new snapshot.""" kwargs = {} context = req.environ['storage.context'] self.assert_valid_body(body, 'snapshot') snapshot = body['snapshot'] kwargs['metadata'] = snapshot.get('metadata', None) try: volume_id = snapshot['volume_id'] except KeyError: msg = _("'volume_id' must be specified") raise exc.HTTPBadRequest(explanation=msg) try: volume = self.volume_api.get(context, volume_id) except exception.VolumeNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) force = snapshot.get('force', False) msg = _LI("Create snapshot from volume %s") LOG.info(msg, volume_id, context=context) self.validate_name_and_description(snapshot) # NOTE(thingee): v2 API allows name instead of display_name if 'name' in snapshot: snapshot['display_name'] = snapshot.pop('name') try: force = strutils.bool_from_string(force, strict=True) except ValueError as error: err_msg = encodeutils.exception_to_unicode(error) msg = _("Invalid value for 'force': '%s'") % err_msg raise exception.InvalidParameterValue(err=msg) if force: new_snapshot = self.volume_api.create_snapshot_force( context, volume, snapshot.get('display_name'), snapshot.get('description'), **kwargs) else: new_snapshot = self.volume_api.create_snapshot( context, volume, snapshot.get('display_name'), snapshot.get('description'), **kwargs) req.cache_db_snapshot(new_snapshot) return self._view_builder.detail(req, new_snapshot)
def _set_enabled_status(self, req, host, enabled): """Sets the specified host's ability to accept new volumes.""" context = req.environ['storage.context'] state = "enabled" if enabled else "disabled" LOG.info(_LI("Setting host %(host)s to %(state)s."), { 'host': host, 'state': state }) result = self.api.set_host_enabled(context, host=host, enabled=enabled) if result not in ("enabled", "disabled"): # An error message was returned raise webob.exc.HTTPBadRequest(explanation=result) return {"host": host, "status": result}
def delete(self, req, id): """Delete a snapshot.""" context = req.environ['storage.context'] LOG.info(_LI("Delete snapshot with id: %s"), id, context=context) try: snapshot = self.volume_api.get_snapshot(context, id) self.volume_api.delete_snapshot(context, snapshot) except exception.SnapshotNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) return webob.Response(status_int=202)