Initial code base commit.
Change-Id: Id1e336028fa662ddee865841ac7b6c31a316f854 Closes-Bug: #1317383
This commit is contained in:
parent
ab7092bf77
commit
5e1c266859
21
cinder-powervc/.project
Normal file
21
cinder-powervc/.project
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>cinder-powervc</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
<project>cinder</project>
|
||||
<project>cinder-client</project>
|
||||
<project>common-powervc</project>
|
||||
<project>oslo</project>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.python.pydev.PyDevBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.python.pydev.pythonNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
10
cinder-powervc/.pydevproject
Normal file
10
cinder-powervc/.pydevproject
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?eclipse-pydev version="1.0"?>
|
||||
|
||||
<pydev_project>
|
||||
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
|
||||
<path>/cinder-powervc</path>
|
||||
</pydev_pathproperty>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
|
||||
</pydev_project>
|
63
cinder-powervc/bin/cinder-powervc
Normal file
63
cinder-powervc/bin/cinder-powervc
Normal file
@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python
|
||||
COPYRIGHT = """
|
||||
*************************************************************
|
||||
Licensed Materials - Property of IBM
|
||||
|
||||
OCO Source Materials
|
||||
|
||||
(C) Copyright IBM Corp. 2013 All Rights Reserved
|
||||
*************************************************************
|
||||
"""
|
||||
|
||||
"""Starter script for the PowerVC cinder-volume Service."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import eventlet
|
||||
import traceback
|
||||
|
||||
# If ../powervc/__init__.py exists, add ../ to Python search path, so that
|
||||
# it will override what happens to be installed in /usr/(local/)lib/python.
|
||||
POSSIBLE_TOPDIR = os.path.normpath(os.path.join(
|
||||
os.path.abspath(sys.argv[0]),
|
||||
os.pardir,
|
||||
os.pardir))
|
||||
|
||||
if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'powervc', '__init__.py')):
|
||||
sys.path.insert(0, POSSIBLE_TOPDIR)
|
||||
|
||||
from cinder.openstack.common import gettextutils
|
||||
|
||||
# TODO RYKAL
|
||||
# This should go in the base __init__ folder I think
|
||||
gettextutils.install('cinder')
|
||||
|
||||
from cinder import utils
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder.openstack.common import service
|
||||
from cinder.common import config as cinder_config
|
||||
from powervc.common import config
|
||||
# NOTE: parse config before import manager
|
||||
config.parse_power_config(sys.argv, 'cinder')
|
||||
|
||||
from powervc.volume.manager import manager
|
||||
|
||||
eventlet.patcher.monkey_patch(os=False, socket=True, time=True)
|
||||
|
||||
logging.setup('powervc')
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
logging.setup('powervc')
|
||||
utils.monkey_patch()
|
||||
LOG.info(_('Launching PowerVC Driver StorageManager service...'))
|
||||
launcher = service.ServiceLauncher()
|
||||
launcher.launch_service(manager.PowerVCCinderManager())
|
||||
launcher.wait()
|
||||
LOG.info(_('PowerVC Driver StorageManager service ended'))
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
raise
|
103
cinder-powervc/init/openstack-cinder-powervc
Normal file
103
cinder-powervc/init/openstack-cinder-powervc
Normal file
@ -0,0 +1,103 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# openstack-cinder-powervc OpenStack PowerVC Cinder Manager
|
||||
#
|
||||
# chkconfig: - 98 02
|
||||
# description: Provides PowerVC manage-to support.
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides:
|
||||
# Required-Start: $remote_fs $network $syslog
|
||||
# Required-Stop: $remote_fs $syslog
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: OpenStack PowerVC Cinder Manager
|
||||
# Description:
|
||||
### END INIT INFO
|
||||
|
||||
. /etc/rc.d/init.d/functions
|
||||
|
||||
suffix=powervc
|
||||
prog=openstack-cinder-powervc
|
||||
exec="/opt/ibm/openstack/powervc-driver/bin/cinder-$suffix"
|
||||
config="/etc/powervc/powervc.conf"
|
||||
cinderconf="/etc/cinder/cinder.conf"
|
||||
pidfile="/var/run/$suffix/cinder-$suffix.pid"
|
||||
logfile="/var/log/$suffix/cinder-$suffix.log"
|
||||
|
||||
[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog
|
||||
|
||||
lockfile=/var/lock/subsys/$prog
|
||||
|
||||
start() {
|
||||
[ -x $exec ] || exit 5
|
||||
[ -f $config ] || exit 6
|
||||
echo -n $"Starting $prog: "
|
||||
daemon --user powervc --pidfile $pidfile "$exec --config-file $config --config-file $cinderconf --logfile $logfile &>/dev/null & echo \$! > $pidfile"
|
||||
retval=$?
|
||||
echo
|
||||
[ $retval -eq 0 ] && touch $lockfile
|
||||
return $retval
|
||||
}
|
||||
|
||||
stop() {
|
||||
echo -n $"Stopping $prog: "
|
||||
killproc -p $pidfile $prog
|
||||
retval=$?
|
||||
echo
|
||||
[ $retval -eq 0 ] && rm -f $lockfile
|
||||
return $retval
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
reload() {
|
||||
restart
|
||||
}
|
||||
|
||||
force_reload() {
|
||||
restart
|
||||
}
|
||||
|
||||
rh_status() {
|
||||
status -p $pidfile $prog
|
||||
}
|
||||
|
||||
rh_status_q() {
|
||||
rh_status >/dev/null 2>&1
|
||||
}
|
||||
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
rh_status_q && exit 0
|
||||
$1
|
||||
;;
|
||||
stop)
|
||||
rh_status_q || exit 0
|
||||
$1
|
||||
;;
|
||||
restart)
|
||||
$1
|
||||
;;
|
||||
reload)
|
||||
rh_status_q || exit 7
|
||||
$1
|
||||
;;
|
||||
force-reload)
|
||||
force_reload
|
||||
;;
|
||||
status)
|
||||
rh_status
|
||||
;;
|
||||
condrestart|try-restart)
|
||||
rh_status_q || exit 0
|
||||
restart
|
||||
;;
|
||||
*)
|
||||
echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
|
||||
exit 2
|
||||
esac
|
||||
exit $?
|
9
cinder-powervc/powervc/__init__.py
Normal file
9
cinder-powervc/powervc/__init__.py
Normal file
@ -0,0 +1,9 @@
|
||||
COPYRIGHT = """
|
||||
*************************************************************
|
||||
Licensed Materials - Property of IBM
|
||||
|
||||
OCO Source Materials
|
||||
|
||||
(C) Copyright IBM Corp. 2013 All Rights Reserved
|
||||
*************************************************************
|
||||
"""
|
0
cinder-powervc/powervc/volume/__init__.py
Normal file
0
cinder-powervc/powervc/volume/__init__.py
Normal file
9
cinder-powervc/powervc/volume/driver/__init__.py
Normal file
9
cinder-powervc/powervc/volume/driver/__init__.py
Normal file
@ -0,0 +1,9 @@
|
||||
COPYRIGHT = """
|
||||
*************************************************************
|
||||
Licensed Materials - Property of IBM
|
||||
|
||||
OCO Source Materials
|
||||
|
||||
(C) Copyright IBM Corp. 2013 All Rights Reserved
|
||||
*************************************************************
|
||||
"""
|
310
cinder-powervc/powervc/volume/driver/powervc.py
Normal file
310
cinder-powervc/powervc/volume/driver/powervc.py
Normal file
@ -0,0 +1,310 @@
|
||||
from __future__ import absolute_import
|
||||
COPYRIGHT = """
|
||||
*************************************************************
|
||||
Licensed Materials - Property of IBM
|
||||
|
||||
OCO Source Materials
|
||||
|
||||
(C) Copyright IBM Corp. 2013 All Rights Reserved
|
||||
*************************************************************
|
||||
"""
|
||||
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):
|
||||
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
|
280
cinder-powervc/powervc/volume/driver/service.py
Normal file
280
cinder-powervc/powervc/volume/driver/service.py
Normal file
@ -0,0 +1,280 @@
|
||||
from __future__ import absolute_import
|
||||
COPYRIGHT = """
|
||||
*************************************************************
|
||||
Licensed Materials - Property of IBM
|
||||
|
||||
OCO Source Materials
|
||||
|
||||
(C) Copyright IBM Corp. 2013 All Rights Reserved
|
||||
*************************************************************
|
||||
"""
|
||||
|
||||
import httplib
|
||||
|
||||
from cinderclient import exceptions
|
||||
from cinder.openstack.common 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()
|
9
cinder-powervc/powervc/volume/manager/__init__.py
Normal file
9
cinder-powervc/powervc/volume/manager/__init__.py
Normal file
@ -0,0 +1,9 @@
|
||||
COPYRIGHT = """
|
||||
*************************************************************
|
||||
Licensed Materials - Property of IBM
|
||||
|
||||
OCO Source Materials
|
||||
|
||||
(C) Copyright IBM Corp. 2013 All Rights Reserved
|
||||
*************************************************************
|
||||
"""
|
61
cinder-powervc/powervc/volume/manager/constants.py
Normal file
61
cinder-powervc/powervc/volume/manager/constants.py
Normal file
@ -0,0 +1,61 @@
|
||||
COPYRIGHT = """
|
||||
*************************************************************
|
||||
Licensed Materials - Property of IBM
|
||||
|
||||
OCO Source Materials
|
||||
|
||||
(C) Copyright IBM Corp. 2013 All Rights Reserved
|
||||
*************************************************************
|
||||
"""
|
||||
|
||||
"""
|
||||
All constants.
|
||||
"""
|
||||
# Instance metadata keys that will store pvc related infor.
|
||||
# in the local nova DB.
|
||||
PVC_TENANT = "pvc_tenant" # project in pvc
|
||||
PVC_SCG = "pvc_scg" # pvc storage connection group
|
||||
PVC_ID = "pvc_id" # pvc instance uuid
|
||||
|
||||
PPC64 = "ppc64" # Found on the wiki
|
||||
# The default image for pvc instance if no match found.
|
||||
DEFAULT_IMG = "SCE Default Image"
|
||||
DEFAULT_SCG = "storage connection group"
|
||||
|
||||
# Suffix to append to sync event notifications
|
||||
SYNC_EVENT_SUFFIX = 'sync'
|
||||
|
||||
LOCAL_PVC_VOLUME_TYPE_PREFIX = 'pvc:'
|
||||
|
||||
LOCAL_PVC_PREFIX = 'pvc:'
|
||||
|
||||
# The composite PowerVC storage backend
|
||||
POWERVC_VOLUME_BACKEND = 'powervc'
|
||||
|
||||
# PowerVC volume & volume type notification events that we listen for
|
||||
EVENT_VOLUME_TYPE_CREATE = 'volume_type.create'
|
||||
EVENT_VOLUME_TYPE_DELETE = 'volume_type.delete'
|
||||
EVENT_VOLUME_TYPE_EXTRA_SPECS_CREATE = 'volume_type_extra_specs.create'
|
||||
EVENT_VOLUME_TYPE_EXTRA_SPECS_UPDATE = 'volume_type_extra_specs.update'
|
||||
EVENT_VOLUME_TYPE_EXTRA_SPECS_DELETE = 'volume_type_extra_specs.delete'
|
||||
|
||||
EVENT_VOLUME_CREATE_START = 'volume.create.start'
|
||||
EVENT_VOLUME_CREATE_END = 'volume.create.end'
|
||||
EVENT_VOLUME_DELETE_START = 'volume.delete.start'
|
||||
EVENT_VOLUME_DELETE_END = 'volume.delete.end'
|
||||
EVENT_VOLUME_UPDATE = 'volume.update'
|
||||
EVENT_VOLUME_ATTACH_START = 'volume.attach.start'
|
||||
EVENT_VOLUME_ATTACH_END = 'volume.attach.end'
|
||||
EVENT_VOLUME_DETACH_START = 'volume.detach.start'
|
||||
EVENT_VOLUME_DETACH_END = 'volume.detach.end'
|
||||
EVENT_VOLUME_IMPORT_START = 'volume.import.start'
|
||||
EVENT_VOLUME_IMPORT_END = 'volume.import.end'
|
||||
|
||||
# PowerVC volume operation status
|
||||
STATUS_AVAILABLE = 'available'
|
||||
STATUS_ERROR = 'error'
|
||||
STATUS_CREATING = 'creating'
|
||||
STATUS_DELETING = 'deleting'
|
||||
|
||||
#multi-backends configuration option for PowerVCDriver
|
||||
BACKEND_POWERVCDRIVER = "powervcdriver"
|
1316
cinder-powervc/powervc/volume/manager/manager.py
Normal file
1316
cinder-powervc/powervc/volume/manager/manager.py
Normal file
File diff suppressed because it is too large
Load Diff
192
cinder-powervc/run_tests.sh
Executable file
192
cinder-powervc/run_tests.sh
Executable file
@ -0,0 +1,192 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2012 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
set -eu
|
||||
|
||||
function usage {
|
||||
echo "Usage: $0 [OPTION]..."
|
||||
echo "Run PowerVC Cinder's test suite(s)"
|
||||
echo ""
|
||||
echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
|
||||
echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
|
||||
echo " -r, --recreate-db Recreate the test database (deprecated, as this is now the default)."
|
||||
echo " -n, --no-recreate-db Don't recreate the test database."
|
||||
echo " -x, --stop Stop running tests after the first error or failure."
|
||||
echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
|
||||
echo " -u, --update Update the virtual environment with any newer package versions"
|
||||
echo " -p, --pep8 Just run flake8"
|
||||
echo " -8, --8 Just run flake8, don't show PEP8 text for each error"
|
||||
echo " -P, --no-pep8 Don't run flake8"
|
||||
echo " -c, --coverage Generate coverage report"
|
||||
echo " -h, --help Print this usage message"
|
||||
echo " --hide-elapsed Don't print the elapsed time for each test along with slow test list"
|
||||
echo " --standard-threads Don't do the eventlet threading monkeypatch."
|
||||
echo ""
|
||||
echo "Note: with no options specified, the script will try to run the tests in a virtual environment,"
|
||||
echo " If no virtualenv is found, the script will ask if you would like to create one. If you "
|
||||
echo " prefer to run tests NOT in a virtual environment, simply pass the -N option."
|
||||
exit
|
||||
}
|
||||
|
||||
function process_option {
|
||||
case "$1" in
|
||||
-h|--help) usage;;
|
||||
-V|--virtual-env) always_venv=1; never_venv=0;;
|
||||
-N|--no-virtual-env) always_venv=0; never_venv=1;;
|
||||
-r|--recreate-db) recreate_db=1;;
|
||||
-n|--no-recreate-db) recreate_db=0;;
|
||||
-f|--force) force=1;;
|
||||
-u|--update) update=1;;
|
||||
-p|--pep8) just_flake8=1;;
|
||||
-8|--8) short_flake8=1;;
|
||||
-P|--no-pep8) no_flake8=1;;
|
||||
-c|--coverage) coverage=1;;
|
||||
--standard-threads)
|
||||
export STANDARD_THREADS=1
|
||||
;;
|
||||
-*) noseopts="$noseopts $1";;
|
||||
*) noseargs="$noseargs $1"
|
||||
esac
|
||||
}
|
||||
|
||||
venv=.venv
|
||||
with_venv=tools/with_venv.sh
|
||||
always_venv=0
|
||||
never_venv=0
|
||||
force=0
|
||||
noseargs=
|
||||
noseopts=
|
||||
wrapper=""
|
||||
just_flake8=0
|
||||
short_flake8=0
|
||||
no_flake8=0
|
||||
coverage=0
|
||||
recreate_db=1
|
||||
update=0
|
||||
|
||||
for arg in "$@"; do
|
||||
process_option $arg
|
||||
done
|
||||
|
||||
# If enabled, tell nose to collect coverage data
|
||||
if [ $coverage -eq 1 ]; then
|
||||
noseopts="$noseopts --with-coverage --cover-package=cinder-powervc"
|
||||
fi
|
||||
|
||||
function run_tests {
|
||||
# Just run the test suites in current environment
|
||||
${wrapper} $NOSETESTS
|
||||
# If we get some short import error right away, print the error log directly
|
||||
RESULT=$?
|
||||
if [ "$RESULT" -ne "0" ];
|
||||
then
|
||||
ERRSIZE=`wc -l run_tests.log | awk '{print \$1}'`
|
||||
if [ "$ERRSIZE" -lt "40" ];
|
||||
then
|
||||
cat run_tests.log
|
||||
fi
|
||||
fi
|
||||
return $RESULT
|
||||
}
|
||||
|
||||
function run_flake8 {
|
||||
FLAGS=--show-pep8
|
||||
if [ $# -gt 0 ] && [ 'short' == ''$1 ]
|
||||
then
|
||||
FLAGS=''
|
||||
fi
|
||||
|
||||
|
||||
echo "Running flake8 ..."
|
||||
# Just run flake8 in current environment
|
||||
#echo ${wrapper} flake8 $FLAGS powervc | tee pep8.txt
|
||||
${wrapper} flake8 $FLAGS powervc | tee pep8.txt
|
||||
RESULT=${PIPESTATUS[0]}
|
||||
return $RESULT
|
||||
}
|
||||
|
||||
NOSETESTS="nosetests $noseopts $noseargs"
|
||||
|
||||
if [ $never_venv -eq 0 ]
|
||||
then
|
||||
# Remove the virtual environment if --force used
|
||||
if [ $force -eq 1 ]; then
|
||||
echo "Cleaning virtualenv..."
|
||||
rm -rf ${venv}
|
||||
fi
|
||||
if [ $update -eq 1 ]; then
|
||||
echo "Updating virtualenv..."
|
||||
python tools/install_venv.py
|
||||
fi
|
||||
if [ -e ${venv} ]; then
|
||||
wrapper="${with_venv}"
|
||||
else
|
||||
if [ $always_venv -eq 1 ]; then
|
||||
# Automatically install the virtualenv
|
||||
python tools/install_venv.py
|
||||
wrapper="${with_venv}"
|
||||
else
|
||||
echo -e "No virtual environment found...create one? (Y/n) \c"
|
||||
read use_ve
|
||||
if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then
|
||||
# Install the virtualenv and run the test suite in it
|
||||
python tools/install_venv.py
|
||||
wrapper=${with_venv}
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Delete old coverage data from previous runs
|
||||
if [ $coverage -eq 1 ]; then
|
||||
${wrapper} coverage erase
|
||||
fi
|
||||
|
||||
|
||||
if [ $just_flake8 -eq 1 ]; then
|
||||
run_flake8
|
||||
RESULT=$?
|
||||
echo "RESULT $RESULT"
|
||||
exit $RESULT
|
||||
fi
|
||||
|
||||
if [ $short_flake8 -eq 1 ]; then
|
||||
run_flake8 short
|
||||
RESULT=$?
|
||||
exit $RESULT
|
||||
fi
|
||||
|
||||
run_tests
|
||||
RESULT=$?
|
||||
|
||||
# NOTE(sirp): we only want to run flake8 when we're running the full-test
|
||||
# suite, not when we're running tests individually. To handle this, we need to
|
||||
# distinguish between options (noseopts), which begin with a '-', and arguments
|
||||
# (noseargs).
|
||||
if [ -z "$noseargs" ]; then
|
||||
if [ $no_flake8 -eq 0 ]; then
|
||||
run_flake8
|
||||
TMP_RESULT=$?
|
||||
RESULT=$(($TMP_RESULT + $RESULT))
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $coverage -eq 1 ]; then
|
||||
echo "Generating coverage report in covhtml/"
|
||||
${wrapper} coverage html -d covhtml -i
|
||||
fi
|
||||
|
||||
exit $RESULT
|
0
cinder-powervc/test/__init__.py
Normal file
0
cinder-powervc/test/__init__.py
Normal file
30
cinder-powervc/test/cinderclienttest.py
Normal file
30
cinder-powervc/test/cinderclienttest.py
Normal file
@ -0,0 +1,30 @@
|
||||
COPYRIGHT = """
|
||||
*************************************************************
|
||||
Licensed Materials - Property of IBM
|
||||
|
||||
OCO Source Materials
|
||||
|
||||
(C) Copyright IBM Corp. 2013 All Rights Reserved
|
||||
*************************************************************
|
||||
"""
|
||||
|
||||
"""
|
||||
Simple cinder client tests
|
||||
|
||||
TODO: Convert to pyunit and use config file
|
||||
"""
|
||||
|
||||
import powervc.common.constants as constants
|
||||
|
||||
from powervc.common import config
|
||||
config.parse_power_config((), 'powervc')
|
||||
import powervc.common.client.factory as clients
|
||||
|
||||
|
||||
cinder_client = clients.POWERVC.new_client(str(constants.SERVICE_TYPES.volume))
|
||||
|
||||
print '=' * 10, 'Listing volumes', '=' * 10
|
||||
vol_list = cinder_client.volumes.list()
|
||||
for vol in vol_list:
|
||||
print str(vol.display_name), str(vol.display_description), \
|
||||
vol.id
|
51
cinder-powervc/test/fake_volume_type.py
Normal file
51
cinder-powervc/test/fake_volume_type.py
Normal file
@ -0,0 +1,51 @@
|
||||
COPYRIGHT = """
|
||||
*************************************************************
|
||||
Licensed Materials - Property of IBM
|
||||
|
||||
OCO Source Materials
|
||||
|
||||
(C) Copyright IBM Corp. 2013 All Rights Reserved
|
||||
*************************************************************
|
||||
"""
|
||||
|
||||
"""
|
||||
The class FakeVolumeType is used to produce the fake
|
||||
data of the OpenStack cinder volume type
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
|
||||
class FakeVolumeType():
|
||||
|
||||
volume_type = dict()
|
||||
|
||||
items = {
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
'deleted',
|
||||
'id',
|
||||
'name',
|
||||
'extra_specs'
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.volume_type['id'] = "18b28659-966d-4913-bdda-2ca3cc68fb59"
|
||||
self.volume_type['created_at'] = \
|
||||
datetime.datetime(2013, 8, 12, 5, 59, 25)
|
||||
self.volume_type['updated_at'] = \
|
||||
datetime.datetime(2013, 8, 12, 5, 59, 25)
|
||||
self.volume_type['deleted_at'] = None
|
||||
self.os_instance['deleted'] = False
|
||||
self.os_instance['name'] = "mengxd-01"
|
||||
self.os_instance['extra_specs'] = {
|
||||
'drivers:rsize': '2',
|
||||
'drivers:storage_pool': 'r3-c3-ch1-jhusta',
|
||||
'capabilities:volume_backend_name': 'shared_v7000_1'
|
||||
}
|
||||
|
||||
def update(self, **update):
|
||||
|
||||
self.self.volume_type.update(**update)
|
0
cinder-powervc/test/powervc/__init__.py
Normal file
0
cinder-powervc/test/powervc/__init__.py
Normal file
0
cinder-powervc/test/powervc/volume/__init__.py
Normal file
0
cinder-powervc/test/powervc/volume/__init__.py
Normal file
225
cinder-powervc/test/powervc/volume/driver/test_powervc_cinder.py
Normal file
225
cinder-powervc/test/powervc/volume/driver/test_powervc_cinder.py
Normal file
@ -0,0 +1,225 @@
|
||||
COPYRIGHT = """
|
||||
*************************************************************
|
||||
Licensed Materials - Property of IBM
|
||||
|
||||
OCO Source Materials
|
||||
|
||||
(C) Copyright IBM Corp. 2013 All Rights Reserved
|
||||
*************************************************************
|
||||
"""
|
||||
# mock module
|
||||
import mock
|
||||
import sys
|
||||
import stubout
|
||||
import unittest
|
||||
|
||||
sys.modules['powervc.common.client'] = mock.MagicMock()
|
||||
# import _
|
||||
from cinder.openstack.common import gettextutils
|
||||
gettextutils.install('cinder')
|
||||
from powervc.common import config
|
||||
from cinder import exception
|
||||
from cinder import db
|
||||
from powervc.volume.driver.service import PowerVCService
|
||||
|
||||
import six
|
||||
|
||||
|
||||
class StorageProvider():
|
||||
def __init__(self, i):
|
||||
self.free_capacity_gb = (i + 1) * 5
|
||||
self.total_capacity_gb = (i + 1) * 10
|
||||
|
||||
|
||||
class VolumeMetadataWithPVCID():
|
||||
|
||||
def __init__(self, pvc_id="1234"):
|
||||
self.key = "pvc:id"
|
||||
self.value = pvc_id
|
||||
|
||||
|
||||
class Volume():
|
||||
def __init__(self, info):
|
||||
self._info = info
|
||||
self._add_details(info)
|
||||
|
||||
def setattr(self, key, val):
|
||||
self.__setattr__(key, val)
|
||||
|
||||
def _add_details(self, info):
|
||||
for (k, v) in six.iteritems(info):
|
||||
try:
|
||||
setattr(self, k, v)
|
||||
except AttributeError:
|
||||
# In this case we already defined the attribute on the class
|
||||
pass
|
||||
|
||||
|
||||
class PowerVCDriverTestCase(unittest.TestCase):
|
||||
stubs = stubout.StubOutForTesting()
|
||||
|
||||
def setUp(self):
|
||||
super(PowerVCDriverTestCase, self).setUp()
|
||||
|
||||
self.stubs.Set(PowerVCService, '_client', mock.MagicMock())
|
||||
# we need mock load config file before import PowerVCDriver class
|
||||
config.parse_power_config = mock.MagicMock()
|
||||
config.CONF.log_opt_values = mock.MagicMock()
|
||||
from powervc.volume.driver.powervc import PowerVCDriver
|
||||
self.powervc_cinder_driver = PowerVCDriver()
|
||||
|
||||
def test_create_volume_no_size_raise_exception(self):
|
||||
self.assertRaises(exception.InvalidVolume,
|
||||
self.powervc_cinder_driver.create_volume,
|
||||
None)
|
||||
|
||||
def test_create_volume_succeed(self):
|
||||
# local volume passed to driver
|
||||
vol = {'id': 1234,
|
||||
'size': 1}
|
||||
volume = Volume(vol)
|
||||
# fake volume after call creating volume from pvc
|
||||
ret_vol_after_created = {'id': 4321,
|
||||
'status': 'creating'}
|
||||
ret_volume_after_created = Volume(ret_vol_after_created)
|
||||
# fake volume after call get volume from pvc
|
||||
ret_vol_get = {'id': 4321,
|
||||
'status': 'available'}
|
||||
ret_volume_get = Volume(ret_vol_get)
|
||||
|
||||
# mock create volume restAPI
|
||||
PowerVCService._client.volumes.create = \
|
||||
mock.MagicMock(return_value=ret_volume_after_created)
|
||||
# mock get volume restAPI
|
||||
PowerVCService._client.volumes.get = \
|
||||
mock.MagicMock(return_value=ret_volume_get)
|
||||
# mock db access operation
|
||||
db.volume_update = mock.MagicMock(return_value=None)
|
||||
|
||||
dic = self.powervc_cinder_driver.create_volume(volume)
|
||||
self.assertEqual({'status': 'available',
|
||||
'metadata': {'pvc:id': 4321}},
|
||||
dic, "return vol doesn't match")
|
||||
|
||||
def test_create_volume_failed(self):
|
||||
# local volume passed to driver
|
||||
vol = {'id': 1234,
|
||||
'size': 1}
|
||||
volume = Volume(vol)
|
||||
# fake volume after call creating volume from pvc
|
||||
ret_vol_after_created = {'id': 4321,
|
||||
'status': 'creating'}
|
||||
ret_volume_after_created = Volume(ret_vol_after_created)
|
||||
# fake volume after call get volume from pvc
|
||||
ret_vol_get = {'id': 4321,
|
||||
'status': 'error'}
|
||||
ret_volume_get = Volume(ret_vol_get)
|
||||
|
||||
# mock create volume restAPI
|
||||
PowerVCService._client.volumes.create = \
|
||||
mock.MagicMock(return_value=ret_volume_after_created)
|
||||
# mock get volume restAPI
|
||||
PowerVCService._client.volumes.get = \
|
||||
mock.MagicMock(return_value=ret_volume_get)
|
||||
# mock db access operation
|
||||
db.volume_update = mock.MagicMock(return_value=None)
|
||||
|
||||
dic = self.powervc_cinder_driver.create_volume(volume)
|
||||
self.assertEqual({'status': 'error',
|
||||
'metadata': {'pvc:id': 4321}},
|
||||
dic, "return vol doesn't match")
|
||||
|
||||
def test_create_volume_not_found(self):
|
||||
# local volume passed to driver
|
||||
vol = {'id': 1234,
|
||||
'size': 1}
|
||||
volume = Volume(vol)
|
||||
# fake volume after call creating volume from pvc
|
||||
ret_vol_after_created = {'id': 4321,
|
||||
'status': 'creating'}
|
||||
ret_volume_after_created = Volume(ret_vol_after_created)
|
||||
# fake volume after call get volume from pvc
|
||||
ret_vol_get = {'id': 4321,
|
||||
'status': 'error'}
|
||||
ret_volume_get = Volume(ret_vol_get)
|
||||
|
||||
# mock create volume restAPI
|
||||
PowerVCService._client.volumes.create = \
|
||||
mock.MagicMock(return_value=ret_volume_after_created)
|
||||
# mock get volume restAPI
|
||||
# first time raise an exception,
|
||||
# second time return a error volume
|
||||
PowerVCService._client.volumes.get = \
|
||||
mock.MagicMock(side_effect=[exception.NotFound,
|
||||
ret_volume_get])
|
||||
# mock db access operation
|
||||
db.volume_update = mock.MagicMock(return_value=None)
|
||||
|
||||
dic = self.powervc_cinder_driver.create_volume(volume)
|
||||
self.assertEqual({'status': 'error',
|
||||
'metadata': {'pvc:id': 4321}},
|
||||
dic, "return vol doesn't match")
|
||||
|
||||
def test_delete_volume_success(self):
|
||||
#fake volume which will be passed to driver service
|
||||
vol_info = {'id': 1234,
|
||||
'size': 1}
|
||||
volume = Volume(vol_info)
|
||||
setattr(volume, 'volume_metadata', [VolumeMetadataWithPVCID("1234")])
|
||||
#fake existed volume
|
||||
existed_vol_info = {"status": 'available', 'id': 1234}
|
||||
existed_volume_get = Volume(existed_vol_info)
|
||||
|
||||
#fake volume after delete
|
||||
after_delete_vol_info = {"status": '', 'id': 1234}
|
||||
after_delete_volume_get = Volume(after_delete_vol_info)
|
||||
|
||||
#mock rest API
|
||||
PowerVCService._client.volumes.get = \
|
||||
mock.MagicMock(side_effect=[existed_volume_get,
|
||||
after_delete_volume_get])
|
||||
|
||||
self.powervc_cinder_driver.delete_volume(volume)
|
||||
|
||||
def test_delete_volume_no_powervc_attribute_error(self):
|
||||
#fake volume which will be passed to driver service
|
||||
vol_info = {'id': 1234, 'size': 1}
|
||||
volume = Volume(vol_info)
|
||||
self.assertRaises(AttributeError,
|
||||
self.powervc_cinder_driver.delete_volume,
|
||||
volume)
|
||||
|
||||
def test_delete_volume_not_found_exception(self):
|
||||
vol_info = {'id': 1234, 'size': 1}
|
||||
volume = Volume(vol_info)
|
||||
setattr(volume, 'volume_metadata', [VolumeMetadataWithPVCID("1234")])
|
||||
|
||||
PowerVCService._client.volumes.get = \
|
||||
mock.MagicMock(side_effect=exception.NotFound())
|
||||
|
||||
self.assertRaises(exception.NotFound,
|
||||
self.powervc_cinder_driver.delete_volume,
|
||||
volume)
|
||||
|
||||
def test_get_volume_stats(self):
|
||||
# fake a storage provider list
|
||||
ret_sp = [StorageProvider(i) for i in range(10)]
|
||||
# mock rest api
|
||||
PowerVCService._client.storage_providers.list = \
|
||||
mock.MagicMock(return_value=ret_sp)
|
||||
# fake a expected return dictionary
|
||||
expected_ret_dic = {}
|
||||
expected_ret_dic["volume_backend_name"] = 'powervc'
|
||||
expected_ret_dic["vendor_name"] = 'IBM'
|
||||
expected_ret_dic["driver_version"] = 1.0
|
||||
expected_ret_dic["storage_protocol"] = 'Openstack'
|
||||
expected_ret_dic['total_capacity_gb'] = 550
|
||||
expected_ret_dic['free_capacity_gb'] = 275
|
||||
expected_ret_dic['reserved_percentage'] = 0
|
||||
expected_ret_dic['QoS_support'] = False
|
||||
|
||||
ret_dic = self.powervc_cinder_driver.get_volume_stats(True)
|
||||
|
||||
self.assertEqual(expected_ret_dic,
|
||||
ret_dic,
|
||||
"return stats should be matched")
|
121
cinder-powervc/test/powervc/volume/manager/test_manager.py
Normal file
121
cinder-powervc/test/powervc/volume/manager/test_manager.py
Normal file
@ -0,0 +1,121 @@
|
||||
COPYRIGHT = """
|
||||
*************************************************************
|
||||
Licensed Materials - Property of IBM
|
||||
|
||||
OCO Source Materials
|
||||
|
||||
(C) Copyright IBM Corp. 2013 All Rights Reserved
|
||||
*************************************************************
|
||||
"""
|
||||
from cinder.openstack.common import gettextutils
|
||||
gettextutils.install('cinder')
|
||||
import unittest
|
||||
import mox
|
||||
from powervc.volume.manager.manager import PowerVCCinderManager
|
||||
from powervc.volume.driver.service import PowerVCService
|
||||
|
||||
fake_volume_type = {'id': '',
|
||||
'name': 'fake_volume_type'
|
||||
}
|
||||
|
||||
|
||||
fake_volume = {'display_name': 'fake_volume',
|
||||
'display_description': 'This is a fake volume',
|
||||
'volume_type_id': '',
|
||||
'status': '',
|
||||
'host': 'powervc',
|
||||
'size': 1,
|
||||
'availability_zone': 'nova',
|
||||
'bootable': 0,
|
||||
'snapshot_id': '',
|
||||
'source_volid': '',
|
||||
'metadata': {},
|
||||
'project_id': 'admin',
|
||||
'user_id': 'admin',
|
||||
'attached_host': 'fake_attached_host',
|
||||
'mountpoint': '',
|
||||
'instance_uuid': '',
|
||||
'attach_status': ''}
|
||||
|
||||
fake_message = {'payload': {'volume_id': '', 'display_name': ''}}
|
||||
|
||||