charm-cinder/tests/basic_deployment.py

814 lines
34 KiB
Python
Executable File

#!/usr/bin/python
import amulet
import types
import inspect
from time import sleep
import cinderclient.v1.client as cinder_client
from charmhelpers.contrib.openstack.amulet.deployment import (
OpenStackAmuletDeployment
)
from charmhelpers.contrib.openstack.amulet.utils import (
OpenStackAmuletUtils,
DEBUG, # flake8: noqa
ERROR
)
# Use DEBUG to turn on debug logging
u = OpenStackAmuletUtils(ERROR)
class CinderBasicDeployment(OpenStackAmuletDeployment):
'''Amulet tests on a basic lvm-backed cinder deployment. Verify
relations, service status, users and endpoint service catalog.
Create, clone, delete volumes. Create volume from glance image.
Create volume snapshot. Create volume from snapshot.'''
# NOTE(beisner): Features and tests vary across Openstack releases.
# https://wiki.openstack.org/wiki/CinderSupportMatrix
def __init__(self, series=None, openstack=None, source=None):
'''Deploy the entire test environment.'''
super(CinderBasicDeployment, self).__init__(series, openstack, source)
self._add_services()
self._add_relations()
self._configure_services()
self._deploy()
self._initialize_tests()
def _add_services(self):
'''Add the service that we're testing, including the number of units,
where this charm is local, and the other charms are from
the charm store.'''
this_service = ('cinder', 1)
other_services = [('mysql', 1), ('rabbitmq-server', 1),
('keystone', 1), ('glance', 1)]
super(CinderBasicDeployment, self)._add_services(this_service,
other_services)
def _add_relations(self):
'''Add relations for the services.'''
relations = {
'keystone:shared-db': 'mysql:shared-db',
'cinder:shared-db': 'mysql:shared-db',
'cinder:identity-service': 'keystone:identity-service',
'cinder:amqp': 'rabbitmq-server:amqp',
'cinder:image-service': 'glance:image-service',
'glance:identity-service': 'keystone:identity-service',
'glance:shared-db': 'mysql:shared-db',
'glance:amqp': 'rabbitmq-server:amqp'
}
super(CinderBasicDeployment, self)._add_relations(relations)
def _configure_services(self):
'''Configure all of the services.'''
keystone_config = {'admin-password': 'openstack',
'admin-token': 'ubuntutesting'}
cinder_config = {'block-device': 'vdb',
'glance-api-version': '2',
'overwrite': 'true'}
mysql_config = {'dataset-size': '50%'}
configs = {'cinder': cinder_config,
'keystone': keystone_config,
'mysql': mysql_config}
super(CinderBasicDeployment, self)._configure_services(configs)
def _initialize_tests(self):
'''Perform final initialization before tests get run.'''
# Access the sentries for inspecting service units
self.cinder_sentry = self.d.sentry.unit['cinder/0']
self.glance_sentry = self.d.sentry.unit['glance/0']
self.mysql_sentry = self.d.sentry.unit['mysql/0']
self.keystone_sentry = self.d.sentry.unit['keystone/0']
self.rabbitmq_sentry = self.d.sentry.unit['rabbitmq-server/0']
# Authenticate admin with keystone
self.keystone = u.authenticate_keystone_admin(self.keystone_sentry,
user='admin',
password='openstack',
tenant='admin')
# Authenticate admin with cinder endpoint
self.cinder = self.authenticate_cinder_admin(self.keystone,
username='admin',
password='openstack',
tenant='admin')
# Authenticate admin with glance endpoint
self.glance = u.authenticate_glance_admin(self.keystone)
u.log.debug('openstack rel: {}'.format(self._get_openstack_release()))
def service_restarted(self, sentry_unit, service, filename,
pgrep_full=False, sleep_time=60):
"""Compare a service's start time vs a file's last modification time
(such as a config file for that service) to determine if the service
has been restarted (within 60s by default). Return when verified."""
# NOTE(beisner): prev rev utilized sleep_time as an arbitrary wait with
# no debug feedback. Added checking timeout loop logic & debug output.
# Increased default timeout to 60s due to test failures.
# NOTE(beisner): need to move to charmhelpers, and adjust calls here.
# It is backward compatible with prev rev by coreycb.
proc_start_time = u._get_proc_start_time(sentry_unit,
service, pgrep_full)
file_mtime = u._get_file_mtime(sentry_unit, filename)
tries = 0
while proc_start_time < file_mtime and tries < (sleep_time/4):
sleep(4)
proc_start_time = u._get_proc_start_time(sentry_unit,
service, pgrep_full)
file_mtime = u._get_file_mtime(sentry_unit, filename)
u.log.debug('proc restart wait: {} {}'.format(tries, service))
tries += 1
u.log.debug('proc-file time diff for {},{}: {}'.format(service,
filename,
proc_start_time - file_mtime))
if proc_start_time >= file_mtime:
return True
else:
u.log.debug('service not restarted within ()s: {}'.format(
service, sleep_time))
return False
def authenticate_cinder_admin(self, keystone, username, password, tenant):
"""Authenticates admin user with cinder."""
# NOTE(beisner): need to move to charmhelpers, and adjust calls here.
# Probably useful on other charm tests.
service_ip = \
self.keystone_sentry.relation('shared-db',
'mysql:shared-db')['private-address']
ep = "http://{}:5000/v2.0".format(service_ip.strip().decode('utf-8'))
return cinder_client.Client(username, password, tenant, ep)
def force_list(self, obj):
'''Determine the object type and return a list. Some Openstack
component API list methods return generators, some return lists.
Where obj is cinder.volumes, cinder.volume_snapshots, glance.images,
or other Openstack object with a list method.'''
# NOTE(beisner): need to move to charmhelpers, and adjust calls here.
# NOTE(beisner): Beware - glance's list method returns a generator,
# and cinder's list method returns a list!
if isinstance(obj.list(), types.ListType):
return obj.list()
elif isinstance(obj.list(), types.GeneratorType):
return list(obj.list())
else:
u.log.debug('unhandled object type: {}'.format(type(obj.list())))
return False
def delete_all_objs(self, obj, item_desc='object', max_wait=60):
'''Delete all objects from openstack component, such as all volumes,
all images or all snapshots. Waits and confirms deletion.'''
# NOTE(beisner): need to move to charmhelpers, and adjust calls here.
# Probably useful on other charm tests.
# Get list of objects to delete
obj_list = self.force_list(obj)
if obj_list == False:
return '{} list failed'.format(item_desc)
if len(obj_list) == 0:
u.log.debug('no {}(s) to delete'.format(item_desc))
return None
# Delete objects
for obj_this in obj_list:
u.log.debug('deleting {}: {}'.format(item_desc, obj_this.id))
try:
obj_this.delete()
except:
return '{} delete failed for {} with status {}'.format(
item_desc, obj_this.id, obj_this.status)
# Wait for objects to disappear
obj_count = len(self.force_list(obj))
tries = 0
while obj_count != 0 and tries <= (max_wait/4):
u.log.debug('{} delete wait: {} {}'.format(item_desc,
tries, obj_count))
sleep(4)
obj_count = len(self.force_list(obj))
tries += 1
if obj_count != 0:
return '{}(s) not deleted, {} remain.'.format(item_desc,
obj_count)
def obj_is_status(self, obj, id, stat='available',
msg='openstack object status check', max_wait=60):
''''Wait for an openstack object status to be as expected.
By default, expect an available status within 60s. Useful
when confirming cinder volumes, snapshots, glance images, etc.
reach a certain state/status within a specified time.'''
# NOTE(beisner): need to move to charmhelpers, and adjust calls here.
# Probably useful on other charm tests.
obj_stat = obj.get(id).status
tries = 0
while obj_stat != stat and tries < (max_wait/4):
u.log.debug(msg + ': {} [{}:{}] {}'.format(tries, obj_stat,
stat, id))
sleep(4)
obj_stat = obj.get(id).status
tries += 1
if obj_stat == stat:
return True
else:
return False
def test_services(self):
'''Verify that the expected services are running on the
corresponding service units.'''
commands = {
self.cinder_sentry: ['status cinder-api',
'status cinder-scheduler',
'status cinder-volume'],
self.glance_sentry: ['status glance-registry',
'status glance-api'],
self.mysql_sentry: ['status mysql'],
self.keystone_sentry: ['status keystone'],
self.rabbitmq_sentry: ['sudo service rabbitmq-server status']
}
u.log.debug('commands: {}'.format(commands))
ret = u.validate_services(commands)
if ret:
amulet.raise_status(amulet.FAIL, msg=ret)
def test_service_catalog(self):
'''Verify that the service catalog endpoint data'''
endpoint_vol = {'adminURL': u.valid_url,
'region': 'RegionOne',
'publicURL': u.valid_url,
'internalURL': u.valid_url}
endpoint_id = {'adminURL': u.valid_url,
'region': 'RegionOne',
'publicURL': u.valid_url,
'internalURL': u.valid_url}
if self._get_openstack_release() >= self.trusty_icehouse:
endpoint_vol['id'] = u.not_null
endpoint_id['id'] = u.not_null
expected = {'image': [endpoint_id],
'identity': [endpoint_id],
'volume': [endpoint_id]}
actual = self.keystone.service_catalog.get_endpoints()
ret = u.validate_svc_catalog_endpoint_data(expected, actual)
if ret:
amulet.raise_status(amulet.FAIL, msg=ret)
def test_cinder_glance_image_service_relation(self):
'''Verify the cinder:glance image-service relation data'''
unit = self.cinder_sentry
relation = ['image-service', 'glance:image-service']
expected = {'private-address': u.valid_ip}
u.log.debug('')
ret = u.validate_relation_data(unit, relation, expected)
if ret:
msg = u.relation_error('cinder image-service', ret)
amulet.raise_status(amulet.FAIL, msg=msg)
def test_glance_cinder_image_service_relation(self):
'''Verify the glance:cinder image-service relation data'''
unit = self.glance_sentry
relation = ['image-service', 'cinder:image-service']
expected = {
'private-address': u.valid_ip,
'glance-api-server': u.valid_url
}
u.log.debug('')
ret = u.validate_relation_data(unit, relation, expected)
if ret:
msg = u.relation_error('glance image-service', ret)
amulet.raise_status(amulet.FAIL, msg=msg)
def test_mysql_cinder_db_relation(self):
'''Verify the mysql:glance shared-db relation data'''
unit = self.mysql_sentry
relation = ['shared-db', 'cinder:shared-db']
expected = {
'private-address': u.valid_ip,
'db_host': u.valid_ip
}
u.log.debug('')
ret = u.validate_relation_data(unit, relation, expected)
if ret:
msg = u.relation_error('mysql shared-db', ret)
amulet.raise_status(amulet.FAIL, msg=msg)
def test_cinder_mysql_db_relation(self):
'''Verify the cinder:mysql shared-db relation data'''
unit = self.cinder_sentry
relation = ['shared-db', 'mysql:shared-db']
expected = {
'private-address': u.valid_ip,
'hostname': u.valid_ip,
'username': 'cinder',
'database': 'cinder'
}
u.log.debug('')
ret = u.validate_relation_data(unit, relation, expected)
if ret:
msg = u.relation_error('cinder shared-db', ret)
amulet.raise_status(amulet.FAIL, msg=msg)
def test_keystone_cinder_id_relation(self):
'''Verify the keystone:cinder identity-service relation data'''
unit = self.keystone_sentry
relation = ['identity-service',
'cinder:identity-service']
expected = {
'service_protocol': 'http',
'service_tenant': 'services',
'admin_token': 'ubuntutesting',
'service_password': u.not_null,
'service_port': '5000',
'auth_port': '35357',
'auth_protocol': 'http',
'private-address': u.valid_ip,
'https_keystone': 'False',
'auth_host': u.valid_ip,
'service_username': 'cinder',
'service_tenant_id': u.not_null,
'service_host': u.valid_ip
}
u.log.debug('')
ret = u.validate_relation_data(unit, relation, expected)
if ret:
msg = u.relation_error('identity-service cinder', ret)
amulet.raise_status(amulet.FAIL, msg=msg)
def test_cinder_keystone_id_relation(self):
'''Verify the cinder:keystone identity-service relation data'''
unit = self.cinder_sentry
relation = ['identity-service',
'keystone:identity-service']
expected = {
'service': 'cinder',
'region': 'RegionOne',
'public_url': u.valid_url,
'internal_url': u.valid_url,
'admin_url': u.valid_url,
'private-address': u.valid_ip
}
u.log.debug('')
ret = u.validate_relation_data(unit, relation, expected)
if ret:
msg = u.relation_error('cinder identity-service', ret)
amulet.raise_status(amulet.FAIL, msg=msg)
def test_rabbitmq_cinder_amqp_relation(self):
'''Verify the rabbitmq-server:cinder amqp relation data'''
unit = self.rabbitmq_sentry
relation = ['amqp', 'cinder:amqp']
expected = {
'private-address': u.valid_ip,
'password': u.not_null,
'hostname': u.valid_ip
}
u.log.debug('')
ret = u.validate_relation_data(unit, relation, expected)
if ret:
msg = u.relation_error('amqp cinder', ret)
amulet.raise_status(amulet.FAIL, msg=msg)
def test_cinder_rabbitmq_amqp_relation(self):
'''Verify the cinder:rabbitmq-server amqp relation data'''
unit = self.cinder_sentry
relation = ['amqp', 'rabbitmq-server:amqp']
expected = {
'private-address': u.valid_ip,
'vhost': 'openstack',
'username': u.not_null
}
u.log.debug('')
ret = u.validate_relation_data(unit, relation, expected)
if ret:
msg = u.relation_error('cinder amqp', ret)
amulet.raise_status(amulet.FAIL, msg=msg)
def test_cinder_default_config(self):
'''Verify default section configs in cinder.conf and
compare some of the parameters to relation data.'''
unit_ci = self.cinder_sentry
unit_mq = self.rabbitmq_sentry
rel_ci_mq = unit_ci.relation('amqp', 'rabbitmq-server:amqp')
rel_mq_ci = unit_mq.relation('amqp', 'cinder:amqp')
u.log.debug('actual ci:mq relation: {}'.format(rel_ci_mq))
u.log.debug('actual mq:ci relation: {}'.format(rel_mq_ci))
conf = '/etc/cinder/cinder.conf'
expected = {'use_syslog': 'False',
'debug': 'False',
'verbose': 'False',
'iscsi_helper': 'tgtadm',
'volume_group': 'cinder-volumes',
'rabbit_userid': 'cinder',
'rabbit_password': rel_mq_ci['password'],
'rabbit_host': rel_mq_ci['hostname'],
'auth_strategy': 'keystone',
'volumes_dir': '/var/lib/cinder/volumes'}
section = 'DEFAULT'
u.log.debug('')
ret = u.validate_config_data(unit_ci, conf, section, expected)
if ret:
msg = 'cinder.conf default config error: {}'.format(ret)
amulet.raise_status(amulet.FAIL, msg=msg)
def test_cinder_auth_config(self):
'''Verify authtoken section config in cinder.conf or
api-paste.ini using glance/keystone relation data.'''
unit_ci = self.cinder_sentry
unit_ks = self.keystone_sentry
rel_ks_ci = unit_ks.relation('identity-service',
'cinder:identity-service')
u.log.debug('actual ks:ci relation: {}'.format(rel_ks_ci))
expected = {'admin_user': rel_ks_ci['service_username'],
'admin_password': rel_ks_ci['service_password'],
'admin_tenant_name': rel_ks_ci['service_tenant'],
'auth_host': rel_ks_ci['auth_host']}
if self._get_openstack_release() >= self.precise_icehouse:
conf = '/etc/cinder/cinder.conf'
section = 'keystone_authtoken'
auth_uri = 'http://' + rel_ks_ci['auth_host'] + \
':' + rel_ks_ci['service_port'] + '/'
expected['auth_uri'] = auth_uri
else:
conf = '/etc/cinder/api-paste.ini'
section = 'filter:authtoken'
ret = u.validate_config_data(unit_ci, conf, section, expected)
if ret:
msg = "cinder auth config error: {}".format(ret)
amulet.raise_status(amulet.FAIL, msg=msg)
def test_cinder_logging_config(self):
''' Inspect select sections and config pairs in logging.conf.'''
unit_ci = self.cinder_sentry
conf = '/etc/cinder/logging.conf'
expected = {
'loggers': {
'keys': 'root, cinder'
},
'logger_cinder': {
'level': 'INFO',
'handlers': 'stderr',
'qualname': 'cinder'
},
'logger_root': {
'level': 'WARNING',
'handlers': 'null'
}
}
for section, pairs in expected.iteritems():
ret = u.validate_config_data(unit_ci, conf, section, pairs)
if ret:
msg = "cinder logging config error: {}".format(ret)
amulet.raise_status(amulet.FAIL, msg=msg)
def test_cinder_rootwrap_config(self):
''' Inspect select config pairs in rootwrap.conf. '''
unit_ci = self.cinder_sentry
conf = '/etc/cinder/rootwrap.conf'
expected = {'filters_path': '/etc/cinder/rootwrap.d,'
'/usr/share/cinder/rootwrap'}
section = 'DEFAULT'
if self._get_openstack_release() >= self.precise_havana:
expected['use_syslog'] = 'False'
expected['exec_dirs'] = '/sbin,/usr/sbin,/bin,/usr/bin'
ret = u.validate_config_data(unit_ci, conf, section, expected)
if ret:
msg = "cinder rootwrap config error: {}".format(ret)
amulet.raise_status(amulet.FAIL, msg=msg)
def test_cinder_endpoint(self):
'''Verify the cinder endpoint data.'''
endpoints = self.keystone.endpoints.list()
admin_port = internal_port = public_port = '8776'
expected = {'id': u.not_null,
'region': 'RegionOne',
'adminurl': u.valid_url,
'internalurl': u.valid_url,
'publicurl': u.valid_url,
'service_id': u.not_null}
ret = u.validate_endpoint_data(endpoints, admin_port, internal_port,
public_port, expected)
if ret:
amulet.raise_status(amulet.FAIL,
msg='glance endpoint: {}'.format(ret))
def test_cinder_restart_on_config_change(self):
'''Verify that cinder services are restarted when
the config is changed.'''
u.log.debug('making charm config change')
self.d.configure('cinder', {'verbose': 'True'})
self.d.configure('cinder', {'debug': 'True'})
if not self.service_restarted(self.cinder_sentry, 'cinder-api',
'/etc/cinder/cinder.conf', sleep_time=30):
msg = "cinder-api service didn't restart after config change"
amulet.raise_status(amulet.FAIL, msg=msg)
if not self.service_restarted(self.cinder_sentry, 'cinder-volume',
'/etc/cinder/cinder.conf', sleep_time=0):
msg = "cinder-volume service didn't restart after conf change"
amulet.raise_status(amulet.FAIL, msg=msg)
u.log.debug('returning to original charm config')
self.d.configure('cinder', {'verbose': 'False'})
self.d.configure('cinder', {'debug': 'False'})
def test_users(self):
'''Verify expected users.'''
user0 = {'name': 'cinder',
'enabled': True,
'tenantId': u.not_null,
'id': u.not_null,
'email': 'juju@localhost'}
user1 = {'name': 'admin',
'enabled': True,
'tenantId': u.not_null,
'id': u.not_null,
'email': 'juju@localhost'}
user2 = {'name': 'glance',
'enabled': True,
'tenantId': u.not_null,
'id': u.not_null,
'email': 'juju@localhost'}
expected = [user0, user1, user2]
actual = self.keystone.users.list()
ret = u.validate_user_data(expected, actual)
if ret:
amulet.raise_status(amulet.FAIL, msg=ret)
def test_000_delete_volumes_snapshots_images(self):
'''Delete all volumes, snapshots and images, if they exist,
as the first of the ordered tests. Useful in re-run scenarios.'''
self.test_900_delete_all_snapshots()
self.test_900_glance_delete_all_images()
self.test_999_delete_all_volumes()
def test_100_create_and_extend_volume(self):
'''Add and confirm a new 1GB volume. In Havana and later,
extend that volume to 2GB.'''
# Create new volume
vol_new = self.cinder.volumes.create(display_name="demo-vol", size=1)
vol_id = vol_new.id
# Wait for volume status to be available
ret = self.obj_is_status(self.cinder.volumes, id=vol_id,
stat = 'available',
msg = 'create vol status wait')
if not ret:
msg = 'volume create failed'
amulet.raise_status(amulet.FAIL, msg=msg)
# NOTE(beisner): Cinder extend is supported only in Havana or later
if self._get_openstack_release() < self.precise_havana:
u.log.debug('Skipping volume extend due to openstack release < H')
return
# Extend volume size
self.cinder.volumes.extend(vol_id, '2')
# Wait for extend
vol_size = self.cinder.volumes.get(vol_id).size
tries = 0
while vol_size != 2 and tries <= 15:
u.log.debug('volume extend size wait: {} {}'.format(tries,
vol_id))
sleep(4)
vol_size = self.cinder.volumes.get(vol_id).size
tries += 1
if vol_size != 2:
msg = 'Failed to extend volume, size is {}'.format(vol_size)
amulet.raise_status(amulet.FAIL, msg=msg)
def test_100_glance_image_create(self):
'''Create new cirros glance image, to be referenced by
a cinder volume create tests in Havana or later.'''
# NOTE(beisner): Cinder create vol-from-img support for lvm and
# rbd(ceph) exists only in Havana or later
if self._get_openstack_release() < self.precise_havana:
u.log.debug('Skipping create glance img due to openstack rel < H')
return
# Create a new image
image_new = u.create_cirros_image(self.glance, 'cirros-image-1')
# Confirm image is created and has status of 'active'
if not image_new:
msg = 'image create failed'
amulet.raise_status(amulet.FAIL, msg=msg)
def test_200_clone_volume(self):
'''Create a new cinder volume, clone it to another cinder volume.'''
# Get volume object and ID
try:
vol = self.cinder.volumes.find(display_name = "demo-vol")
vol_id = vol.id
vol_size = vol.size
except:
msg = ('Volume (demo-vol) not found.')
amulet.raise_status(amulet.FAIL, msg=msg)
if vol.status != 'available':
msg = ('volume status not == available: {}'.format(vol.status))
amulet.raise_status(amulet.FAIL, msg=msg)
# Create new clone volume from source volume
vol_clone = self.cinder.volumes.create(display_name="demo-vol-clone",
size=vol_size,
source_volid=vol_id)
ret = self.obj_is_status(self.cinder.volumes, id=vol_clone.id,
stat = 'available',
msg='clone vol status wait')
if not ret:
msg = 'volume clone failed - from {}'.format(vol_id)
amulet.raise_status(amulet.FAIL, msg=msg)
def test_200_create_volume_from_glance_image(self):
'''Create new volume from glance cirros image (Havana and later),
check status and bootable flag.'''
# NOTE(beisner): Cinder create vol-from-img support for lvm and
# rbd(ceph) exists only in Havana or later
if self._get_openstack_release() < self.precise_havana:
u.log.debug('Skipping create vol from img due to openstack rel < H')
return
# Get image object and id
expected_img_name = 'cirros-image-1'
img_list = list(self.glance.images.list())
img_count = len(img_list)
if img_count != 0:
# NOTE(beisner): glance api has no find method, presume 1st image
img_name = img_list[0].name
img_id = img_list[0].id
else:
msg = 'image not found'
amulet.raise_status(amulet.FAIL, msg=msg)
# Confirm image name
if img_list[0].name != expected_img_name:
msg = 'unexpected image name {}'.format(img_list[0].name)
amulet.raise_status(amulet.FAIL, msg=msg)
# Create new volume from glance image
vol_new = self.cinder.volumes.create(display_name="demo-vol-cirros",
size=1, imageRef=img_id)
vol_id = vol_new.id
# Wait for volume stat to be avail, check that it's flagged bootable
ret = self.obj_is_status(self.cinder.volumes, id=vol_id,
stat='available',
msg='create vol from img status wait')
vol_boot = self.cinder.volumes.get(vol_id).bootable
if not ret or vol_boot != 'true':
vol_stat = self.cinder.volumes.get(vol_id).status
msg = ('vol create failed - from glance img:'
' id:{} stat:{} boot:{}'.format(vol_id,
vol_stat,
vol_boot))
amulet.raise_status(amulet.FAIL, msg=msg)
def test_300_cinder_create_snapshot(self):
'''Create a snapshot of a volume. Use a cirros-based volume where
supported (Havana and newer), and fall back to a vanilla
volume snapshot everywhere else.'''
if self._get_openstack_release() >= self.precise_havana:
vol_src_name = "demo-vol-cirros"
elif self._get_openstack_release() < self.precise_havana:
vol_src_name = "demo-vol"
u.log.debug('creating snapshot of volume: {}'.format(vol_src_name))
# Get volume object and id
try:
vol_src = self.cinder.volumes.find(
display_name = vol_src_name)
vol_id = vol_src.id
except:
msg = ('volume not found while creating snapshot')
amulet.raise_status(amulet.FAIL, msg=msg)
if vol_src.status != 'available':
msg = ('volume status not == available: {}').format(
vol_src.status)
amulet.raise_status(amulet.FAIL, msg=msg)
# Create new snapshot
snap_new = self.cinder.volume_snapshots.create(
volume_id = vol_id,
display_name = 'demo-snapshot')
snap_id = snap_new.id
# Wait for snapshot status to become available
ret = self.obj_is_status(self.cinder.volume_snapshots, id=snap_id,
stat='available',
msg='snapshot create status wait')
if not ret:
snap_stat = self.cinder.volume_snapshots.get(snap_id).status
msg = 'volume snapshot failed: {} {}'.format(snap_id,
snap_stat)
amulet.raise_status(amulet.FAIL, msg=msg)
def test_310_create_volume_from_snapshot(self):
'''Create a new volume from a snapshot of a volume.'''
# Get snapshot object and ID
try:
snap = self.cinder.volume_snapshots.find(
display_name = "demo-snapshot")
snap_id = snap.id
snap_size = snap.size
snap_name = snap.display_name
except:
msg = ('snapshot not found while creating volume')
amulet.raise_status(amulet.FAIL, msg=msg)
if snap.status != 'available':
msg = ('snapshot status not == available: {}').format(
snap.status)
amulet.raise_status(amulet.FAIL, msg=msg)
# Create new volume from snapshot
vol_new = self.cinder.volumes.create(
display_name="demo-vol-from-snap",
snapshot_id=snap_id,
size=snap_size)
vol_id = vol_new.id
# Wait for volume status to be == available
ret = self.obj_is_status(self.cinder.volumes, id=vol_id,
stat='available',
msg='vol from snap create status wait')
if not ret:
vol_stat = self.cinder.volumes.get(vol_id).status
msg = 'volume create failed: {} {}'.format(vol_id,
vol_stat)
amulet.raise_status(amulet.FAIL, msg=msg)
def test_900_confirm_lvm_volume_list(self):
'''Confirm cinder volume IDs with lvm logical volume IDs.
Expect a 1:1 relationship of lvm:cinder volumes.'''
commando = self.cinder_sentry.run('sudo lvs | grep cinder-volumes | '
'awk \'{ print $1 }\'')
vol_list = self.cinder.volumes.list()
lv_id_list = commando[0].split('\n')
vol_count = len(vol_list)
snap_count = len(self.cinder.volume_snapshots.list())
# Expect cinder vol + snap count to match lvm log vol count
if (vol_count + snap_count) != len(lv_id_list):
msg = ('lvm volume count ({}) != cinder volume + snap count '
'({})'.format(len(vol_list), len(lv_id_list)))
amulet.raise_status(amulet.FAIL, msg=msg)
# Expect all cinder vol IDs to exist in the LVM volume list
for vol_this in vol_list:
try:
id_index = lv_id_list.index('volume-' + vol_this.id)
except:
msg = ('volume ID {} not found in '
'LVM volume list.'.format(vol_this.id))
amulet.raise_status(amulet.FAIL, msg=msg)
def test_900_glance_delete_all_images(self):
'''Delete all glance images and confirm deletion.'''
ret = self.delete_all_objs(self.glance.images, item_desc='image')
if ret:
amulet.raise_status(amulet.FAIL, msg=ret)
def test_900_delete_all_snapshots(self):
'''Delete all cinder volume snapshots and confirm deletion.'''
ret = self.delete_all_objs(self.cinder.volume_snapshots, item_desc='snapshot')
if ret:
amulet.raise_status(amulet.FAIL, msg=ret)
def test_999_delete_all_volumes(self):
'''Delete all cinder volumes and confirm deletion,
as the last of the ordered tests.'''
ret = self.delete_all_objs(self.cinder.volumes, item_desc='volume')
if ret:
amulet.raise_status(amulet.FAIL, msg=ret)