powervc-driver/cinder-powervc/powervc/volume/driver/powervc.py

316 lines
10 KiB
Python

from __future__ import absolute_import
# Copyright 2013, 2014 IBM Corp.
import logging
import sys
from cinder import exception
from cinder.openstack.common import log as cinderLogging
from cinder.volume.driver import VolumeDriver
from cinderclient.exceptions import NotFound
from oslo.config import cfg
from powervc.common import config
from powervc.common import constants as common_constants
from powervc.common.gettextutils import _
from powervc.volume.manager import constants
from powervc.volume.driver import service
volume_driver_opts = [
# Ignore delete errors so an exception is not thrown during a
# delete. When set to true, this allows the volume to be deleted
# on the hosting OS even if an exception occurs. When set to false,
# exceptions during delete prevent the volume from being deleted
# on the hosting OS.
cfg.BoolOpt('volume_driver_ignore_delete_error', default=False)
]
CONF = config.CONF
CONF.register_opts(volume_driver_opts, group='powervc')
LOG = cinderLogging.getLogger(__name__)
def _load_power_config(argv):
"""
Loads the powervc config.
"""
# Cinder is typically started with the --config-file option.
# This prevents the default config files from loading since
# the olso config code will only load those
# config files as specified on the command line.
# If the cinder is started with the
# --config-file option then append our powervc.conf file to
# the command line so it gets loaded as well.
for arg in argv:
if arg == '--config-file' or arg.startswith('--config-file='):
argv[len(argv):] = ["--config-file"] + \
[cfg.find_config_files(project='powervc',
prog='powervc')[0]]
break
config.parse_power_config(argv, 'cinder')
_load_power_config(sys.argv)
# must load powervc config before importing factory when
# called with import utils for a driver
from powervc.common.client import factory
class PowerVCDriver(VolumeDriver):
"""
Implements the cinder volume driver for powerVC
"""
def __init__(self, *args, **kwargs):
super(PowerVCDriver, self).__init__(*args, **kwargs)
CONF.log_opt_values(LOG, logging.INFO)
self._service = service.PowerVCService()
if not service.PowerVCService._client:
service.PowerVCService._client = factory.POWERVC.new_client(str(
common_constants.SERVICE_TYPES.volume))
def check_for_setup_error(self):
"""
Checks for setup errors. Nothing to do for powervc.
"""
pass
def initialize_connection(self, volume, connector):
"""
Allow connection to connector and return connection info.
In the PowerVC cinder driver, it does not need to be implemented.
"""
LOG.debug("Enter - initialize_connection")
return {'driver_volume_type': '', 'data': {}}
LOG.debug("Exit - initialize_connection")
def validate_connector(self, connector):
"""
Fail if connector doesn't contain all the data needed by driver.
In the PowerVC cinder driver, it does not need to be implemented.
"""
return True
def terminate_connection(self, volume_ref, connector, force):
"""Do nothing since connection is not used"""
pass
def create_export(self, context, volume):
"""
Exports the volume. Nothing to do for powervc
"""
pass
def accept_transfer(self, context, volume_ref, new_user, new_project):
"""
Accept a volume that has been offered for transfer.
Nothing to do for powervc
"""
pass
def create_cloned_volume(self, volume_ref, srcvol_ref):
"""
Clone a volume from an existing volume.
Currently not supported by powervc.
Add stub to pass tempest.
"""
pass
def copy_image_to_volume(self, context, volume_ref, image_service,
image_id):
"""
Copy a glance image to a volume.
Currently not supported by powervc.
Add stub to pass tempest.
"""
pass
def copy_volume_to_image(self, context, volume, image_service, image_meta):
"""
Upload an exsiting volume into powervc as a glance image
Currently not supported by powervc.
Add stub to pass tempest.
"""
pass
def create_snapshot(self, snapshot_ref):
"""
Create a snapshot.
Currently not supported by powervc.
Add stub to pass tempest.
"""
pass
def delete_snapshot(self, snapshot_ref):
"""
Delete a snapshot.
Currently not supported by powervc.
Add stub to pass tempest.
"""
pass
def create_volume_from_snapshot(self, volume, snapshot_ref):
"""
Create a volume from the snapshot.
Currently not supported by powervc.
Add stub to pass tempest.
"""
pass
def extend_volume(self, volume, new_size):
"""
Extend a volume size.
Currently not supported by powervc.
Add stub to pass tempest.
"""
pass
def create_volume(self, volume):
"""
Creates a volume with the specified volume attributes
:returns: a dictionary of updates to the volume db, for example
adding metadata
"""
LOG.info(_("Creating volume with volume: %s."), volume)
size = getattr(volume, 'size', None)
display_name = getattr(volume, 'display_name', None)
display_description = getattr(volume, 'display_description', None)
volume_type_obj = getattr(volume, 'volume_type', None)
metadatas = getattr(volume, 'volume_metadata', None)
meta = {}
if metadatas:
# Use map() to get a list of 'key', 'value' tuple
# dict() can convert a list of tuple to dict obj
meta = dict(map(lambda m: (getattr(m, 'key'),
getattr(m, 'value')), metadatas))
if (size is None):
raise exception.InvalidVolume(reason='size is None')
LOG.info(_("Creating volume %s of size %sG."),
self._get_vol_name(volume),
size)
volume_data_updates = self._service.create_volume(
local_volume_id=volume.id,
size=size,
display_name=display_name,
display_description=display_description,
metadata=meta,
volume_type=getattr(volume_type_obj, 'id',
None))
return volume_data_updates
def delete_volume(self, volume):
"""
Deletes the specfied volume from powervc
"""
try:
LOG.info(_("Deleting volume %s."), self._get_vol_name(volume))
pvc_volume_id = None
for metaDataItem in volume.volume_metadata:
if metaDataItem.key == constants.LOCAL_PVC_PREFIX + 'id':
pvc_volume_id = metaDataItem.value
break
if pvc_volume_id is not None:
self._service.delete_volume(pvc_volume_id)
else:
LOG.warning(_("Volume metadata does not "
"contain a powervc volume identifier."))
except NotFound:
LOG.debug(_("Volume id %s was already deleted on powervc"),
pvc_volume_id)
LOG.info(_("Volume %s deleted."), self._get_vol_name(volume))
except Exception as e:
if CONF.powervc.volume_driver_ignore_delete_error:
LOG.error(_("Volume %s deleted, however the following "
"error occurred "
"which prevented the backing volume in PowerVC "
"from being deleted: %s"),
self._get_vol_name(volume),
str(e))
else:
raise
def ensure_export(self, context, volume):
"""
Makes sure the volume is exported. Nothing to do for powervc
"""
pass
def remove_export(self, context, volume):
"""
Removes the export. Nothing to do for powervc
"""
pass
def get_volume_stats(self, refresh=False):
"""
Gets the volume statistics for this driver. Cinder periodically calls
this to get the latest volume stats. The stats are stored in the
instance attribute called _stats
"""
if refresh:
self._update_volume_status()
return self._stats
def _update_volume_status(self):
"""
Retrieve volumes stats info from powervc.
For now just make something up
"""
LOG.debug(_("Getting volume stats from powervc"))
# get accessible storage providers list
sp_list = self._list_storage_providers()
free_capacity_gb = 0
total_capacity_gb = 0
for sp in sp_list:
free_capacity_gb += getattr(sp, 'free_capacity_gb', 0)
total_capacity_gb += getattr(sp, 'total_capacity_gb', 0)
data = {}
data["volume_backend_name"] = constants.POWERVC_VOLUME_BACKEND
data["vendor_name"] = 'IBM'
data["driver_version"] = 1.0
data["storage_protocol"] = 'Openstack'
data['total_capacity_gb'] = total_capacity_gb
data['free_capacity_gb'] = free_capacity_gb
data['reserved_percentage'] = 0
data['QoS_support'] = False
self._stats = data
LOG.debug(self._stats)
def _list_storage_providers(self):
return self._service.list_storage_providers()
def _get_vol_name(self, volume):
"""
Returns the name of the volume or its id
"""
name = getattr(volume, 'display_name', None)
if name:
return name
else:
return volume.id
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
self._service.attach_volume(context, volume, instance_uuid, host_name,
mountpoint)
def detach_volume(self, context, volume, attachment):
"""Callback for volume detached."""
# wait for volume to be detached
self._service.detach_volume(context, volume)