from __future__ import absolute_import # Copyright 2013, 2014 IBM Corp. import httplib from cinderclient import exceptions from oslo_log import log as logging from powervc.common import constants as common_constants from powervc.common.gettextutils import _ from powervc.volume.manager import constants from cinder import exception from cinder import db from cinder import context from cinder.openstack.common import loopingcall LOG = logging.getLogger(__name__) class PowerVCService(object): """A service that exposes PowerVC functionality. The services provided here are called by the driver. The services leverage the nova client to interface to the PowerVC. This design keeps the driver and client interface clean and simple and provides a workspace for any data manipulation and utility work that may need to be done. """ _client = None def __init__(self, pvc_client=None): """Initializer.""" from powervc.common.client import factory if(PowerVCService._client is None): PowerVCService._client = \ factory.POWERVC.new_client( str(common_constants.SERVICE_TYPES.volume)) # Add version checking as required def create_volume(self, local_volume_id, size, snapshot_id=None, source_volid=None, display_name=None, display_description=None, volume_type=None, user_id=None, project_id=None, availability_zone=None, metadata=None, imageRef=None): """ Creates a volume on powervc """ # Use the standard cinderclient to create volume # TODO Do not pass metadata to PowerVC currently as we don't # know if this has a conflict with PowerVC design. pvc_volume = PowerVCService._client.volumes.create(size, snapshot_id, source_volid, display_name, display_description, volume_type, user_id, project_id, availability_zone, {}, imageRef) # update powervc uuid to db immediately to avoid duplicated # synchronization additional_volume_data = {} additional_volume_data['metadata'] = metadata additional_volume_data['metadata'][constants.LOCAL_PVC_PREFIX + 'id'] \ = pvc_volume.id db.volume_update(context.get_admin_context(), local_volume_id, additional_volume_data) LOG.info(_("Volume %s start to create with PVC UUID: %s"), local_volume_id, pvc_volume.id) temp_status = getattr(pvc_volume, 'status', None) if temp_status == constants.STATUS_CREATING: LOG.debug(_( 'wait until created volume status is available or ERROR')) timer = loopingcall.FixedIntervalLoopingCall( self._wait_for_state_change, pvc_volume.id, getattr(pvc_volume, 'status', None), constants.STATUS_AVAILABLE, constants.STATUS_CREATING) try: timer.start(interval=10).wait() # set status to available additional_volume_data['status'] = \ constants.STATUS_AVAILABLE except: latest_pvc_volume = PowerVCService._client.volumes.get( pvc_volume.id) additional_volume_data['status'] = getattr(latest_pvc_volume, 'status', '') else: LOG.debug(_('Not in creating status, just set as powerVC status')) additional_volume_data['status'] = temp_status # return updated volume status information return additional_volume_data def _wait_for_state_change(self, volume_id, original_state, expected_state, middle_state): """ Utility method to wait for a volume to change to the expected state. The process of some operation contains three states. during the operation. If the operation has no middle state, it can be set as original state. """ volume = None try: volume = PowerVCService._client.volumes.get(volume_id) except exceptions.NotFound: raise exception.VolumeNotFound('volume not found: %s' % volume_id) if volume.status == expected_state: LOG.debug( "Operation %(vm_id)s successfully, " + "status changed to %(state)s" % {'vm_id': volume.id, 'state': expected_state}) raise loopingcall.LoopingCallDone() if (volume.status != original_state and volume.status != expected_state and volume.status != middle_state): raise exception.InvalidVolume() def delete_volume(self, pvc_volume_id): """ Deletes the specified powervc volume id from powervc """ LOG.debug(_("Deleting pvc volume: %s"), pvc_volume_id) if not pvc_volume_id: raise AttributeError(_("Powervc volume identifier must be " "specified")) existed_pvc_volume = None try: existed_pvc_volume = PowerVCService._client.volumes.get( pvc_volume_id) except exceptions.NotFound: LOG.critical(_("pvc: %s no longer existed in powervc, ignore"), pvc_volume_id) raise temp_status = getattr(existed_pvc_volume, 'status', None) if temp_status == constants.STATUS_DELETING: # Volume in deleting status, do not perform delete operation # again LOG.warning( _("pvc: %s is deleting in powervc, wait for status"), pvc_volume_id) else: # volume available for deleting, perform delete opeartion PowerVCService._client.volumes.delete(pvc_volume_id) LOG.debug(_( 'wait until created volume deleted or status is ERROR')) timer = loopingcall.FixedIntervalLoopingCall( self._wait_for_state_change, existed_pvc_volume.id, getattr(existed_pvc_volume, 'status', None), '', constants.STATUS_DELETING) try: timer.start(interval=10).wait() except exception.VolumeNotFound: # deleted complete LOG.info(_("pvc: %s deleted successfully"), pvc_volume_id) except exception.InvalidVolume: LOG.critical(_("pvc: %s deleted failed, "), pvc_volume_id) # when delete failed raise exception raise exception.CinderException( _('Volume deletion failed for id: %s'), pvc_volume_id) def _validate_response(self, response): """ Validates an HTTP response to a REST API request made by this service. The method will simply return if the HTTP error code indicates success (i.e. between 200 and 300). Any other errors, this method will raise the exception. Note: Appropriate exceptions to be added... Nova client throws an exception for 404 :param response: the HTTP response to validate """ if response is None: return httpResponse = response[0] # Any non-successful response >399 is an error if httpResponse.status_code >= httplib.BAD_REQUEST: LOG.critical(_("Service: got this response: %s") % httpResponse) LOG.debug("Service: got this response: %s" % httpResponse) raise exceptions.BadRequest(httpResponse) def list_volume_types(self): return PowerVCService._client.volume_types.list() def get_volume_type(self, vol_type_id): return PowerVCService._client.volume_types.get(vol_type_id) def get_volume_type_by_name(self, volume_type_name): pvc_volume_type = None if volume_type_name is None or PowerVCService._client is None: return pvc_volume_type pvc_volume_type_list = self.list_volume_types() if pvc_volume_type_list is None: return volume_type_name for volume_type in pvc_volume_type_list: if volume_type_name == volume_type._info["name"]: pvc_volume_type = volume_type break return pvc_volume_type def get_volumes(self): pvc_volumes = None if PowerVCService._client is None: return pvc_volumes pvc_volumes = PowerVCService._client.volumes.list() return pvc_volumes def get_volume_by_name(self, display_name): pvc_volume = None if display_name is None or PowerVCService._client is None: return pvc_volume pvc_volume_list = self.get_volumes() if pvc_volume_list is None: return pvc_volume for volume in pvc_volume_list: if display_name == volume._info["display_name"]: pvc_volume = volume break return pvc_volume def get_volume_by_id(self, volume_id): pvc_volume = None if volume_id is None or PowerVCService._client is None: return pvc_volume try: pvc_volume = PowerVCService._client.volumes.get(volume_id) except exceptions.NotFound: LOG.debug("get_volume_by_id volume %s not found" % volume_id) pvc_volume = None return pvc_volume def list_storage_providers(self): return PowerVCService._client.storage_providers.list() def attach_volume(self, context, volume, instance_uuid, host_name, mountpoint): """Callback for volume attached to instance or host.""" # wait for volume to be attached, the volume-attach operation is done # in nova pvc_volume_id = None for metaDataItem in volume.volume_metadata: if metaDataItem.key == constants.LOCAL_PVC_PREFIX + 'id': pvc_volume_id = metaDataItem.value break else: LOG.warning('Fail to get pvc_id %s' % volume_id) raise exceptions.BadRequest LOG.debug('wait until PVC volume %s status to in-use', pvc_volume_id) FILPC = loopingcall.FixedIntervalLoopingCall timer = FILPC(self._wait_for_state_change, pvc_volume_id, constants.STATUS_AVAILABLE, constants.STATUS_INUSE, constants.STATUS_ATTACHING) try: timer.start(interval=10).wait() except: LOG.error("volume %s attaching failed, ", volume.id) # when attached failed raise exception raise exception.CinderException('Volume %s attaching failed', volume.id) def detach_volume(self, context, volume): """Callback for volume detach to instance or host.""" # wait for volume to be detached, the volume-detach operation is done # in nova pvc_volume_id = None for metaDataItem in volume.volume_metadata: if metaDataItem.key == constants.LOCAL_PVC_PREFIX + 'id': pvc_volume_id = metaDataItem.value break else: LOG.warning('Fail to get pvc_id %s' % volume_id) raise exceptions.BadRequest LOG.debug('wait until PVC volume %s status to available', pvc_volume_id) FILPC = loopingcall.FixedIntervalLoopingCall timer = FILPC(self._wait_for_state_change, pvc_volume_id, constants.STATUS_INUSE, constants.STATUS_AVAILABLE, constants.STATUS_DETACHING) try: timer.start(interval=10).wait() except: LOG.error("volume %s detaching failed, ", volume.id) # when attached failed raise exception raise exception.CinderException('Volume %s detaching failed', volume.id)