Add a volume from snapshot test case

Fixes bug #1034513

Change-Id: Ie37a0ae59c2dc2d805113c73a824951acef13663
This commit is contained in:
Attila Fazekas 2013-01-31 16:41:04 +01:00
parent 5c5bda92cd
commit 36b1fcf417
9 changed files with 390 additions and 16 deletions

View File

@ -57,7 +57,9 @@ from tempest.services.network.json.network_client import NetworkClient
from tempest.services.object_storage.account_client import AccountClient
from tempest.services.object_storage.container_client import ContainerClient
from tempest.services.object_storage.object_client import ObjectClient
from tempest.services.volume.json.snapshots_client import SnapshotsClientJSON
from tempest.services.volume.json.volumes_client import VolumesClientJSON
from tempest.services.volume.xml.snapshots_client import SnapshotsClientXML
from tempest.services.volume.xml.volumes_client import VolumesClientXML
from tempest.services.object_storage.object_client import \
ObjectClientCustomizedHeader
@ -111,6 +113,11 @@ FLOAT_CLIENTS = {
"xml": FloatingIPsClientXML,
}
SNAPSHOTS_CLIENTS = {
"json": SnapshotsClientJSON,
"xml": SnapshotsClientXML,
}
VOLUMES_CLIENTS = {
"json": VolumesClientJSON,
"xml": VolumesClientXML,
@ -184,6 +191,7 @@ class Manager(object):
vol_ext_cli = VOLUMES_EXTENSIONS_CLIENTS[interface](*client_args)
self.volumes_extensions_client = vol_ext_cli
self.floating_ips_client = FLOAT_CLIENTS[interface](*client_args)
self.snapshots_client = SNAPSHOTS_CLIENTS[interface](*client_args)
self.volumes_client = VOLUMES_CLIENTS[interface](*client_args)
self.identity_client = IDENTITY_CLIENT[interface](*client_args)
self.token_client = TOKEN_CLIENT[interface](self.config)

View File

@ -86,6 +86,10 @@ class VolumeBuildErrorException(TempestException):
message = "Volume %(volume_id)s failed to build and is in ERROR status"
class SnapshotBuildErrorException(TempestException):
message = "Snapshot %(snapshot_id)s failed to build and is in ERROR status"
class BadRequest(RestClientException):
message = "Bad request"

View File

@ -40,6 +40,7 @@ from tempest.services.compute.json import security_groups_client
from tempest.services.compute.json import servers_client
from tempest.services.compute.json import volumes_extensions_client
from tempest.services.network.json import network_client
from tempest.services.volume.json import snapshots_client
from tempest.services.volume.json import volumes_client
NetworkClient = network_client.NetworkClient
@ -53,6 +54,7 @@ SecurityGroupsClient = security_groups_client.SecurityGroupsClientJSON
KeyPairsClient = keypairs_client.KeyPairsClientJSON
VolumesExtensionsClient = volumes_extensions_client.VolumesExtensionsClientJSON
VolumesClient = volumes_client.VolumesClientJSON
SnapshotsClient = snapshots_client.SnapshotsClientJSON
QuotasClient = quotas_client.QuotasClientJSON
LOG = logging.getLogger(__name__)
@ -252,6 +254,7 @@ class ComputeFuzzClientManager(FuzzClientManager):
self.floating_ips_client = FloatingIPsClient(*client_args)
self.volumes_extensions_client = VolumesExtensionsClient(*client_args)
self.volumes_client = VolumesClient(*client_args)
self.snapshots_client = SnapshotsClient(*client_args)
self.quotas_client = QuotasClient(*client_args)
self.network_client = NetworkClient(*client_args)

View File

@ -0,0 +1,125 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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.
import json
import logging
import time
import urllib
from tempest.common.rest_client import RestClient
from tempest import exceptions
LOG = logging.getLogger(__name__)
class SnapshotsClientJSON(RestClient):
"""Client class to send CRUD Volume API requests."""
def __init__(self, config, username, password, auth_url, tenant_name=None):
super(SnapshotsClientJSON, self).__init__(config, username, password,
auth_url, tenant_name)
self.service = self.config.volume.catalog_type
self.build_interval = self.config.volume.build_interval
self.build_timeout = self.config.volume.build_timeout
def list_snapshots(self, params=None):
"""List all the snapshot."""
url = 'snapshots'
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
body = json.loads(body)
return resp, body['snapshots']
def list_snapshot_with_detail(self, params=None):
"""List the details of all snapshots."""
url = 'snapshots/detail'
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
body = json.loads(body)
return resp, body['snapshots']
def get_snapshot(self, snapshot_id):
"""Returns the details of a single snapshot."""
url = "snapshots/%s" % str(snapshot_id)
resp, body = self.get(url)
body = json.loads(body)
return resp, body['snapshot']
def create_snapshot(self, volume_id, **kwargs):
"""
Creates a new snapshot.
volume_id(Required): id of the volume.
force: Create a snapshot even if the volume attached (Default=False)
display_name: Optional snapshot Name.
display_description: User friendly snapshot description.
"""
post_body = {'volume_id': volume_id}
post_body.update(kwargs)
post_body = json.dumps({'snapshot': post_body})
resp, body = self.post('snapshots', post_body, self.headers)
body = json.loads(body)
return resp, body['snapshot']
#NOTE(afazekas): just for the wait function
def _get_snapshot_status(self, snapshot_id):
resp, body = self.get_snapshot(snapshot_id)
status = body['status']
#NOTE(afazekas): snapshot can reach an "error"
# state in a "normal" lifecycle
if (status == 'error'):
raise exceptions.SnapshotBuildErrorException(
snapshot_id=snapshot_id)
return status
#NOTE(afazkas): Wait reinvented again. It is not in the correct layer
def wait_for_snapshot_status(self, snapshot_id, status):
"""Waits for a Snapshot to reach a given status."""
start_time = time.time()
old_value = value = self._get_snapshot_status(snapshot_id)
while True:
dtime = time.time() - start_time
time.sleep(self.build_interval)
if value != old_value:
LOG.info('Value transition from "%s" to "%s"'
'in %d second(s).', old_value,
value, dtime)
if (value == status):
return value
if dtime > self.build_timeout:
message = ('Time Limit Exceeded! (%ds)'
'while waiting for %s, '
'but we got %s.' %
(self.build_timeout, status, value))
raise exceptions.TimeoutException(message)
time.sleep(self.build_interval)
old_value = value
value = self._get_snapshot_status(snapshot_id)
def delete_snapshot(self, snapshot_id):
"""Delete Snapshot."""
return self.delete("snapshots/%s" % str(snapshot_id))
def is_resource_deleted(self, id):
try:
self.get_snapshot(id)
except exceptions.NotFound:
return True
return False

View File

@ -71,14 +71,10 @@ class VolumesClientJSON(RestClient):
display_name: Optional Volume Name.
metadata: A dictionary of values to be used as metadata.
volume_type: Optional Name of volume_type for the volume
snapshot_id: When specified the volume is created from this snapshot
"""
post_body = {
'size': size,
'display_name': kwargs.get('display_name'),
'metadata': kwargs.get('metadata'),
'volume_type': kwargs.get('volume_type')
}
post_body = {'size': size}
post_body.update(kwargs)
post_body = json.dumps({'volume': post_body})
resp, body = self.post('volumes', post_body, self.headers)
body = json.loads(body)

View File

@ -0,0 +1,138 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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.
import logging
import time
import urllib
from lxml import etree
from tempest.common.rest_client import RestClientXML
from tempest import exceptions
from tempest.services.compute.xml.common import Document
from tempest.services.compute.xml.common import Element
from tempest.services.compute.xml.common import xml_to_json
from tempest.services.compute.xml.common import XMLNS_11
LOG = logging.getLogger(__name__)
class SnapshotsClientXML(RestClientXML):
"""Client class to send CRUD Volume API requests."""
def __init__(self, config, username, password, auth_url, tenant_name=None):
super(SnapshotsClientXML, self).__init__(config, username, password,
auth_url, tenant_name)
self.service = self.config.volume.catalog_type
self.build_interval = self.config.volume.build_interval
self.build_timeout = self.config.volume.build_timeout
def list_snapshots(self, params=None):
"""List all snapshot."""
url = 'snapshots'
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url, self.headers)
body = etree.fromstring(body)
return resp, xml_to_json(body)
def list_snapshots_with_detail(self, params=None):
"""List all the details of snapshot."""
url = 'snapshots/detail'
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url, self.headers)
body = etree.fromstring(body)
snapshots = []
return resp, snapshots(xml_to_json(body))
def get_snapshot(self, snapshot_id):
"""Returns the details of a single snapshot."""
url = "snapshots/%s" % str(snapshot_id)
resp, body = self.get(url, self.headers)
body = etree.fromstring(body)
return resp, xml_to_json(body)
def create_snapshot(self, volume_id, **kwargs):
""" Creates a new snapshot.
volume_id(Required): id of the volume.
force: Create a snapshot even if the volume attached (Default=False)
display_name: Optional snapshot Name.
display_description: User friendly snapshot description.
"""
#NOTE(afazekas): it should use the volume namaspace
snapshot = Element("snapshot", xmlns=XMLNS_11, volume_id=volume_id)
for key, value in kwargs.items():
snapshot.add_attr(key, value)
resp, body = self.post('snapshots', str(Document(snapshot)),
self.headers)
body = xml_to_json(etree.fromstring(body))
return resp, body
def _get_snapshot_status(self, snapshot_id):
resp, body = self.get_snapshot(snapshot_id)
return body['status']
#NOTE(afazekas): just for the wait function
def _get_snapshot_status(self, snapshot_id):
resp, body = self.get_snapshot(snapshot_id)
status = body['status']
#NOTE(afazekas): snapshot can reach an "error"
# state in a "normal" lifecycle
if (status == 'error'):
raise exceptions.SnapshotBuildErrorException(
snapshot_id=snapshot_id)
return status
#NOTE(afazkas): Wait reinvented again. It is not in the correct layer
def wait_for_snapshot_status(self, snapshot_id, status):
"""Waits for a Snapshot to reach a given status."""
start_time = time.time()
old_value = value = self._get_snapshot_status(snapshot_id)
while True:
dtime = time.time() - start_time
time.sleep(self.build_interval)
if value != old_value:
LOG.info('Value transition from "%s" to "%s"'
'in %d second(s).', old_value,
value, dtime)
if (value == status):
return value
if dtime > self.build_timeout:
message = ('Time Limit Exceeded! (%ds)'
'while waiting for %s, '
'but we got %s.' %
(self.build_timeout, status, value))
raise exceptions.TimeoutException(message)
time.sleep(self.build_interval)
old_value = value
value = self._get_snapshot_status(snapshot_id)
def delete_snapshot(self, snapshot_id):
"""Delete Snapshot."""
return self.delete("snapshots/%s" % str(snapshot_id))
def is_resource_deleted(self, id):
try:
self.get_snapshot(id)
except exceptions.NotFound:
return True
return False

View File

@ -101,6 +101,7 @@ class VolumesClientXML(RestClientXML):
:param snapshot_id: When specified the volume is created from
this snapshot
"""
#NOTE(afazekas): it should use a volume namespace
volume = Element("volume", xmlns=XMLNS_11, size=size)
if 'metadata' in kwargs:

View File

@ -50,12 +50,14 @@ class BaseVolumeTest(testtools.TestCase):
cls.os = os
cls.volumes_client = os.volumes_client
cls.snapshots_client = os.snapshots_client
cls.servers_client = os.servers_client
cls.image_ref = cls.config.compute.image_ref
cls.flavor_ref = cls.config.compute.flavor_ref
cls.build_interval = cls.config.volume.build_interval
cls.build_timeout = cls.config.volume.build_timeout
cls.volumes = {}
cls.snapshots = []
cls.volumes = []
skip_msg = ("%s skipped as Cinder endpoint is not available" %
cls.__name__)
@ -120,19 +122,61 @@ class BaseVolumeTest(testtools.TestCase):
@classmethod
def tearDownClass(cls):
cls.clear_snapshots()
cls.clear_volumes()
cls.clear_isolated_creds()
def create_volume(self, size=1, metadata={}):
@classmethod
def create_snapshot(cls, volume_id=1, **kwargs):
"""Wrapper utility that returns a test snapshot."""
resp, snapshot = cls.snapshots_client.create_snapshot(volume_id,
**kwargs)
assert 200 == resp.status
cls.snapshots_client.wait_for_snapshot_status(snapshot['id'],
'available')
cls.snapshots.append(snapshot)
return snapshot
#NOTE(afazekas): these create_* and clean_* could be defined
# only in a single location in the source, and could be more general.
@classmethod
def create_volume(cls, size=1, **kwargs):
"""Wrapper utility that returns a test volume."""
display_name = rand_name(self.__class__.__name__ + "-volume")
cli_resp = self.volumes_client.create_volume(size=size,
display_name=display_name,
metdata=metadata)
resp, volume = cli_resp
self.volumes_client.wait_for_volume_status(volume['id'], 'available')
self.volumes.append(volume)
resp, volume = cls.volumes_client.create_volume(size, **kwargs)
assert 200 == resp.status
cls.volumes_client.wait_for_volume_status(volume['id'], 'available')
cls.volumes.append(volume)
return volume
@classmethod
def clear_volumes(cls):
for volume in cls.volumes:
try:
cls.volume_client.delete_volume(volume['id'])
except Exception:
pass
for volume in cls.volumes:
try:
cls.servers_client.wait_for_resource_deletion(volume['id'])
except Exception:
pass
@classmethod
def clear_snapshots(cls):
for snapshot in cls.snapshots:
try:
cls.snapshots_client.delete_snapshot(snapshot['id'])
except Exception:
pass
for snapshot in cls.snapshots:
try:
cls.snapshots_client.wait_for_resource_deletion(snapshot['id'])
except Exception:
pass
def wait_for(self, condition):
"""Repeatedly calls condition() until a timeout."""
start_time = int(time.time())

View File

@ -0,0 +1,55 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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.
from nose.plugins.attrib import attr
from tempest.common.utils.data_utils import rand_name
from tempest.tests.volume import base
class VolumesSnapshotTestBase(object):
def test_volume_from_snapshot(self):
volume_origin = self.create_volume(size=1)
snapshot = self.create_snapshot(volume_origin['id'])
volume_snap = self.create_volume(size=1,
snapshot_id=
snapshot['id'])
self.snapshots_client.delete_snapshot(snapshot['id'])
self.client.delete_volume(volume_snap['id'])
self.snapshots_client.wait_for_resource_deletion(snapshot['id'])
self.snapshots.remove(snapshot)
self.client.delete_volume(volume_origin['id'])
self.client.wait_for_resource_deletion(volume_snap['id'])
self.volumes.remove(volume_snap)
self.client.wait_for_resource_deletion(volume_origin['id'])
self.volumes.remove(volume_origin)
class VolumesSnapshotTestXML(base.BaseVolumeTestXML,
VolumesSnapshotTestBase):
@classmethod
def setUpClass(cls):
cls._interface = "xml"
super(VolumesSnapshotTestXML, cls).setUpClass()
cls.client = cls.volumes_client
class VolumesSnapshotTestJSON(base.BaseVolumeTestJSON,
VolumesSnapshotTestBase):
@classmethod
def setUpClass(cls):
cls._interface = "json"
super(VolumesSnapshotTestJSON, cls).setUpClass()
cls.client = cls.volumes_client