Exemple #1
0
    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)
Exemple #2
0
    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'])
Exemple #3
0
    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)
Exemple #4
0
    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."))
Exemple #5
0
    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."))
Exemple #6
0
    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
Exemple #7
0
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
Exemple #8
0
    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
Exemple #9
0
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)
Exemple #10
0
    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
Exemple #11
0
    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
Exemple #12
0
 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'})
Exemple #13
0
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})
Exemple #14
0
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
Exemple #15
0
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
            })
Exemple #16
0
    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
Exemple #17
0
    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']})
Exemple #18
0
 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})
Exemple #19
0
    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)
Exemple #20
0
    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)
Exemple #21
0
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})
Exemple #22
0
    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
Exemple #23
0
    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
Exemple #24
0
    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)
Exemple #25
0
    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)
Exemple #26
0
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})
Exemple #27
0
 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.'))
Exemple #28
0
    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
Exemple #29
0
    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)
Exemple #30
0
    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']}
Exemple #31
0
 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.'))
Exemple #32
0
    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)
Exemple #33
0
    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
Exemple #34
0
    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
Exemple #35
0
    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
Exemple #36
0
    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)
Exemple #37
0
    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()
Exemple #38
0
    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
Exemple #39
0
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)
Exemple #40
0
    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 = {}
Exemple #41
0
 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)}}
Exemple #42
0
 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'})
Exemple #43
0
 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)}}
Exemple #44
0
    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)
Exemple #45
0
    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)
Exemple #46
0
    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
Exemple #47
0
    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
                    })
Exemple #48
0
    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)
Exemple #49
0
    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)
Exemple #50
0
    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
Exemple #51
0
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
Exemple #52
0
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
Exemple #53
0
    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
Exemple #54
0
    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)
Exemple #55
0
    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)
Exemple #56
0
    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)
Exemple #57
0
 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}
Exemple #58
0
    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)