333 lines
13 KiB
Python
333 lines
13 KiB
Python
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)
|