Initial code base commit.

Change-Id: Id1e336028fa662ddee865841ac7b6c31a316f854
Closes-Bug: #1317383
This commit is contained in:
Le Tian Ren 2014-05-08 17:01:18 +08:00
parent ab7092bf77
commit 5e1c266859
163 changed files with 26406 additions and 0 deletions

21
cinder-powervc/.project Normal file
View 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>

View 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>

View 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

View 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 $?

View File

@ -0,0 +1,9 @@
COPYRIGHT = """
*************************************************************
Licensed Materials - Property of IBM
OCO Source Materials
(C) Copyright IBM Corp. 2013 All Rights Reserved
*************************************************************
"""

View File

@ -0,0 +1,9 @@
COPYRIGHT = """
*************************************************************
Licensed Materials - Property of IBM
OCO Source Materials
(C) Copyright IBM Corp. 2013 All Rights Reserved
*************************************************************
"""

View 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

View 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()

View File

@ -0,0 +1,9 @@
COPYRIGHT = """
*************************************************************
Licensed Materials - Property of IBM
OCO Source Materials
(C) Copyright IBM Corp. 2013 All Rights Reserved
*************************************************************
"""

View 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"

File diff suppressed because it is too large Load Diff

192
cinder-powervc/run_tests.sh Executable file
View 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

View File

View 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

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

View File

View 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")

View 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': ''}}