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 oslo_log 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)