示例#1
0
    def _verify_image_volume(self, context, image_meta, image_service):
        # This method just verifies that IF we have a cache volume that
        # it's still up to date and current WRT the image in Glance
        # ie an image-update hasn't occurred since we grabbed it

        # If it's out of date, just delete it and we'll create a new one
        # Any other case we don't care and just return without doing anything

        sfaccount = self._get_sfaccount(
            self.configuration.sf_template_account_name)

        params = {'accountID': sfaccount['accountID']}
        sf_vol = self._get_sf_volume(image_meta['id'], params)
        if sf_vol is None:
            return

        # Check updated_at field, delete copy and update if needed
        if sf_vol['attributes']['image_info']['image_updated_at'] ==\
                image_meta['updated_at'].isoformat():
            return
        else:
            # Bummer, it's been updated, delete it
            params = {'accountID': sfaccount['accountID']}
            params = {'volumeID': sf_vol['volumeID']}
            data = self._issue_api_request('DeleteVolume', params)
            if 'result' not in data:
                msg = _("Failed to delete SolidFire Image-Volume: %s") % data
                raise exception.SolidFireAPIException(msg)

            if not self._create_image_volume(context,
                                             image_meta,
                                             image_service,
                                             image_meta['id']):
                msg = _("Failed to create SolidFire Image-Volume")
                raise exception.SolidFireAPIException(msg)
示例#2
0
    def unmanage(self, volume):
        """Mark SolidFire Volume as unmanaged (export from Cinder)."""

        LOG.debug("Enter SolidFire unmanage...")
        sfaccount = self._get_sfaccount(volume['project_id'])
        if sfaccount is None:
            LOG.error(_LE("Account for Volume ID %s was not found on "
                          "the SolidFire Cluster while attempting "
                          "unmanage operation!") % volume['id'])
            raise exception.SolidFireAPIException("Failed to find account "
                                                  "for volume.")

        params = {'accountID': sfaccount['accountID']}
        sf_vol = self._get_sf_volume(volume['id'], params)
        if sf_vol is None:
            raise exception.VolumeNotFound(volume_id=volume['id'])

        export_time = timeutils.strtime()
        attributes = sf_vol['attributes']
        attributes['os_exported_at'] = export_time
        params = {'volumeID': int(sf_vol['volumeID']),
                  'attributes': attributes}

        data = self._issue_api_request('ModifyVolume',
                                       params, version='5.0')
        if 'result' not in data:
            raise exception.SolidFireAPIDataException(data=data)
示例#3
0
    def _issue_api_request(self, method, params, version='1.0', endpoint=None):
        if params is None:
            params = {}

        if endpoint is None:
            endpoint = self._endpoint
        payload = {'method': method, 'params': params}

        url = '%s/json-rpc/%s/' % (endpoint['url'], version)
        req = requests.post(url,
                            data=json.dumps(payload),
                            auth=(endpoint['login'], endpoint['passwd']),
                            verify=False,
                            timeout=30)

        response = req.json()
        req.close()
        if (('error' in response) and
                (response['error']['name'] in self.retryable_errors)):
            msg = ('Retryable error (%s) encountered during '
                   'SolidFire API call.' % response['error']['name'])
            LOG.debug(msg)
            raise exception.SolidFireRetryableException(message=msg)

        if 'error' in response:
            msg = _('API response: %s') % response
            raise exception.SolidFireAPIException(msg)

        return response
示例#4
0
    def _issue_api_request(self, method_name, params):
        """All API requests to SolidFire device go through this method

        Simple json-rpc web based API calls.
        each call takes a set of paramaters (dict)
        and returns results in a dict as well.
        """

        host = FLAGS.san_ip
        # For now 443 is the only port our server accepts requests on
        port = 443

        # NOTE(john-griffith): Probably don't need this, but the idea is
        # we provide a request_id so we can correlate
        # responses with requests
        request_id = int(uuid.uuid4())  # just generate a random number

        cluster_admin = FLAGS.san_login
        cluster_password = FLAGS.san_password

        command = {'method': method_name, 'id': request_id}

        if params is not None:
            command['params'] = params

        payload = json.dumps(command, ensure_ascii=False)
        payload.encode('utf-8')
        # we use json-rpc, webserver needs to see json-rpc in header
        header = {'Content-Type': 'application/json-rpc; charset=utf-8'}

        if cluster_password is not None:
            # base64.encodestring includes a newline character
            # in the result, make sure we strip it off
            auth_key = base64.encodestring(
                '%s:%s' % (cluster_admin, cluster_password))[:-1]
            header['Authorization'] = 'Basic %s' % auth_key

        LOG.debug(_("Payload for SolidFire API call: %s"), payload)
        connection = httplib.HTTPSConnection(host, port)
        connection.request('POST', '/json-rpc/1.0', payload, header)
        response = connection.getresponse()
        data = {}

        if response.status != 200:
            connection.close()
            raise exception.SolidFireAPIException(status=response.status)

        else:
            data = response.read()
            try:
                data = json.loads(data)

            except (TypeError, ValueError), exc:
                connection.close()
                msg = _("Call to json.loads() raised an exception: %s") % exc
                raise exception.SfJsonEncodeFailure(msg)

            connection.close()
示例#5
0
    def manage_existing(self, volume, external_ref):
        """Manages an existing SolidFire Volume (import to Cinder).

        Renames the Volume to match the expected name for the volume.
        Also need to consider things like QoS, Emulation, account/tenant.
        """

        sfid = external_ref.get('source-id', None)
        sfname = external_ref.get('name', None)
        if sfid is None:
            raise exception.SolidFireAPIException("Manage existing volume "
                                                  "requires 'source-id'.")

        # First get the volume on the SF cluster (MUST be active)
        params = {'startVolumeID': sfid, 'limit': 1}
        data = self._issue_api_request('ListActiveVolumes', params)
        if 'result' not in data:
            raise exception.SolidFireAPIDataException(data=data)
        sf_ref = data['result']['volumes'][0]

        sfaccount = self._create_sfaccount(volume['project_id'])

        attributes = {}
        qos = {}
        if (self.configuration.sf_allow_tenant_qos
                and volume.get('volume_metadata') is not None):
            qos = self._set_qos_presets(volume)

        ctxt = context.get_admin_context()
        type_id = volume.get('volume_type_id', None)
        if type_id is not None:
            qos = self._set_qos_by_volume_type(ctxt, type_id)

        import_time = timeutils.strtime(volume['created_at'])
        attributes = {
            'uuid': volume['id'],
            'is_clone': 'False',
            'os_imported_at': import_time,
            'old_name': sfname
        }
        if qos:
            for k, v in qos.items():
                attributes[k] = str(v)

        params = {
            'name': volume['name'],
            'volumeID': sf_ref['volumeID'],
            'accountID': sfaccount['accountID'],
            'enable512e': self.configuration.sf_emulate_512,
            'attributes': attributes,
            'qos': qos
        }

        data = self._issue_api_request('ModifyVolume', params, version='5.0')
        if 'result' not in data:
            raise exception.SolidFireAPIDataException(data=data)

        return self._get_model_info(sfaccount, sf_ref['volumeID'])
示例#6
0
    def _get_cluster_info(self):
        """Query the SolidFire cluster for some property info."""
        params = {}
        data = self._issue_api_request('GetClusterInfo', params)
        if 'result' not in data:
            msg = _("API response: %s") % data
            raise exception.SolidFireAPIException(msg)

        return data['result']
示例#7
0
 def func_retry(*args, **kwargs):
     _tries, _delay = tries, delay
     while _tries > 1:
         try:
             return f(*args, **kwargs)
         except exc_tuple:
             time.sleep(_delay)
             _tries -= 1
             _delay *= backoff
             LOG.debug('Retrying %s, (%s attempts remaining)...' %
                       (args, _tries))
     # NOTE(jdg): Don't log the params passed here
     # some cmds like createAccount will have sensitive
     # info in the params, grab only the second tuple
     # which should be the Method
     msg = (_('Retry count exceeded for command: %s') % (args[1], ))
     LOG.error(msg)
     raise exception.SolidFireAPIException(message=msg)
示例#8
0
    def manage_existing_get_size(self, volume, external_ref):
        """Return size of an existing LV for manage_existing.

        existing_ref is a dictionary of the form:
        {'name': <name of existing volume on SF Cluster>}
        """

        sfid = external_ref.get('source-id', None)
        if sfid is None:
            raise exception.SolidFireAPIException("Manage existing get size "
                                                  "requires 'id'.")

        params = {'startVolumeID': int(sfid), 'limit': 1}
        data = self._issue_api_request('ListActiveVolumes', params)
        if 'result' not in data:
            raise exception.SolidFireAPIDataException(data=data)
        sf_ref = data['result']['volumes'][0]
        return int(sf_ref['totalSize']) / int(units.Gi)
示例#9
0
    def _get_sf_volume(self, uuid, params):
        # TODO(jdg): Going to fix this shortly to not iterate
        # but instead use the cinder UUID and our internal
        # mapping to get this more efficiently
        data = self._issue_api_request('ListVolumesForAccount', params)
        if 'result' not in data:
            msg = _("Failed to get SolidFire Volume: %s") % data
            raise exception.SolidFireAPIException(msg)

        found_count = 0
        sf_volref = None
        for v in data['result']['volumes']:
            # NOTE(jdg): In the case of "name" we can't
            # update that on manage/import, so we use
            # the uuid attribute
            meta = v.get('attributes')
            alt_id = meta.get('uuid', 'empty')

            if uuid in v['name'] or uuid in alt_id:
                found_count += 1
                sf_volref = v
                LOG.debug("Mapped SolidFire volumeID %(sfid)s "
                          "to cinder ID %(uuid)s." % {
                              'sfid': v['volumeID'],
                              'uuid': uuid
                          })

        if found_count == 0:
            # NOTE(jdg): Previously we would raise here, but there are cases
            # where this might be a cleanup for a failed delete.
            # Until we get better states we'll just log an error
            LOG.error(_LE("Volume %s, not found on SF Cluster."), uuid)

        if found_count > 1:
            LOG.error(
                _LE("Found %(count)s volumes mapped to id: %(uuid)s.") % {
                    'count': found_count,
                    'uuid': uuid
                })
            raise exception.DuplicateSfVolumeNames(vol_name=uuid)

        return sf_volref
示例#10
0
    def _do_clone_volume(self, src_uuid, src_project_id, v_ref):
        """Create a clone of an existing volume.

        Currently snapshots are the same as clones on the SF cluster.
        Due to the way the SF cluster works there's no loss in efficiency
        or space usage between the two.  The only thing different right
        now is the restore snapshot functionality which has not been
        implemented in the pre-release version of the SolidFire Cluster.

        """
        attributes = {}
        qos = {}

        sfaccount = self._get_sfaccount(src_project_id)
        params = {'accountID': sfaccount['accountID']}

        sf_vol = self._get_sf_volume(src_uuid, params)
        if sf_vol is None:
            raise exception.VolumeNotFound(volume_id=src_uuid)

        if src_project_id != v_ref['project_id']:
            sfaccount = self._create_sfaccount(v_ref['project_id'])

        params = {'volumeID': int(sf_vol['volumeID']),
                  'name': 'UUID-%s' % v_ref['id'],
                  'newSize': int(v_ref['size'] * self.GB),
                  'newAccountID': sfaccount['accountID']}
        data = self._issue_api_request('CloneVolume', params)

        if (('result' not in data) or ('volumeID' not in data['result'])):
            msg = _("API response: %s") % data
            raise exception.SolidFireAPIException(msg)
        sf_volume_id = data['result']['volumeID']

        if (self.configuration.sf_allow_tenant_qos and
                v_ref.get('volume_metadata')is not None):
            qos = self._set_qos_presets(v_ref)

        ctxt = context.get_admin_context()
        type_id = v_ref.get('volume_type_id', None)
        if type_id is not None:
            qos = self._set_qos_by_volume_type(ctxt, type_id)

        # NOTE(jdg): all attributes are copied via clone, need to do an update
        # to set any that were provided
        params = {'volumeID': sf_volume_id}

        create_time = timeutils.strtime(v_ref['created_at'])
        attributes = {'uuid': v_ref['id'],
                      'is_clone': 'True',
                      'src_uuid': src_uuid,
                      'created_at': create_time}
        if qos:
            params['qos'] = qos
            for k, v in qos.items():
                attributes[k] = str(v)

        params['attributes'] = attributes
        data = self._issue_api_request('ModifyVolume', params)

        model_update = self._get_model_info(sfaccount, sf_volume_id)
        if model_update is None:
            mesg = _('Failed to get model update from clone')
            raise exception.SolidFireAPIException(mesg)

        return (data, sfaccount, model_update)