Merge "Adds cinder backup functional tests"
This commit is contained in:
commit
3fb814da91
@ -840,6 +840,9 @@
|
|||||||
# (boolean value)
|
# (boolean value)
|
||||||
#multi_backend=false
|
#multi_backend=false
|
||||||
|
|
||||||
|
# Runs Cinder volumes backup test (boolean value)
|
||||||
|
#backup=true
|
||||||
|
|
||||||
# Is the v1 volume API enabled (boolean value)
|
# Is the v1 volume API enabled (boolean value)
|
||||||
#api_v1=true
|
#api_v1=true
|
||||||
|
|
||||||
|
67
tempest/api/volume/admin/test_volumes_backup.py
Normal file
67
tempest/api/volume/admin/test_volumes_backup.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# Copyright 2014 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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 tempest.api.volume.base import BaseVolumeV1AdminTest
|
||||||
|
from tempest.common.utils import data_utils
|
||||||
|
from tempest import config
|
||||||
|
from tempest.openstack.common import log as logging
|
||||||
|
from tempest import test
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class VolumesBackupsTest(BaseVolumeV1AdminTest):
|
||||||
|
_interface = "json"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super(VolumesBackupsTest, cls).setUpClass()
|
||||||
|
|
||||||
|
if not CONF.volume_feature_enabled.backup:
|
||||||
|
raise cls.skipException("Cinder backup feature disabled")
|
||||||
|
|
||||||
|
cls.volumes_adm_client = cls.os_adm.volumes_client
|
||||||
|
cls.backups_adm_client = cls.os_adm.backups_client
|
||||||
|
cls.volume = cls.create_volume()
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
def test_volume_backup_create_get_restore_delete(self):
|
||||||
|
backup_name = data_utils.rand_name('Backup')
|
||||||
|
create_backup = self.backups_adm_client.create_backup
|
||||||
|
resp, backup = create_backup(self.volume['id'],
|
||||||
|
name=backup_name)
|
||||||
|
self.assertEqual(202, resp.status)
|
||||||
|
self.addCleanup(self.backups_adm_client.delete_backup,
|
||||||
|
backup['id'])
|
||||||
|
self.assertEqual(backup['name'], backup_name)
|
||||||
|
self.volumes_adm_client.wait_for_volume_status(self.volume['id'],
|
||||||
|
'available')
|
||||||
|
self.backups_adm_client.wait_for_backup_status(backup['id'],
|
||||||
|
'available')
|
||||||
|
|
||||||
|
resp, backup = self.backups_adm_client.get_backup(backup['id'])
|
||||||
|
self.assertEqual(200, resp.status)
|
||||||
|
self.assertEqual(backup['name'], backup_name)
|
||||||
|
|
||||||
|
resp, restore = self.backups_adm_client.restore_backup(backup['id'])
|
||||||
|
self.assertEqual(202, resp.status)
|
||||||
|
self.addCleanup(self.volumes_adm_client.delete_volume,
|
||||||
|
restore['volume_id'])
|
||||||
|
self.assertEqual(restore['backup_id'], backup['id'])
|
||||||
|
self.backups_adm_client.wait_for_backup_status(backup['id'],
|
||||||
|
'available')
|
||||||
|
self.volumes_adm_client.wait_for_volume_status(restore['volume_id'],
|
||||||
|
'available')
|
@ -106,6 +106,7 @@ class BaseVolumeV1Test(BaseVolumeTest):
|
|||||||
super(BaseVolumeV1Test, cls).setUpClass()
|
super(BaseVolumeV1Test, cls).setUpClass()
|
||||||
cls.snapshots_client = cls.os.snapshots_client
|
cls.snapshots_client = cls.os.snapshots_client
|
||||||
cls.volumes_client = cls.os.volumes_client
|
cls.volumes_client = cls.os.volumes_client
|
||||||
|
cls.backups_client = cls.os.backups_client
|
||||||
cls.volumes_extension_client = cls.os.volumes_extension_client
|
cls.volumes_extension_client = cls.os.volumes_extension_client
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -148,6 +148,7 @@ from tempest.services.volume.json.admin.volume_hosts_client import \
|
|||||||
VolumeHostsClientJSON
|
VolumeHostsClientJSON
|
||||||
from tempest.services.volume.json.admin.volume_types_client import \
|
from tempest.services.volume.json.admin.volume_types_client import \
|
||||||
VolumeTypesClientJSON
|
VolumeTypesClientJSON
|
||||||
|
from tempest.services.volume.json.backups_client import BackupsClientJSON
|
||||||
from tempest.services.volume.json.extensions_client import \
|
from tempest.services.volume.json.extensions_client import \
|
||||||
ExtensionsClientJSON as VolumeExtensionClientJSON
|
ExtensionsClientJSON as VolumeExtensionClientJSON
|
||||||
from tempest.services.volume.json.snapshots_client import SnapshotsClientJSON
|
from tempest.services.volume.json.snapshots_client import SnapshotsClientJSON
|
||||||
@ -158,6 +159,7 @@ from tempest.services.volume.xml.admin.volume_hosts_client import \
|
|||||||
VolumeHostsClientXML
|
VolumeHostsClientXML
|
||||||
from tempest.services.volume.xml.admin.volume_types_client import \
|
from tempest.services.volume.xml.admin.volume_types_client import \
|
||||||
VolumeTypesClientXML
|
VolumeTypesClientXML
|
||||||
|
from tempest.services.volume.xml.backups_client import BackupsClientXML
|
||||||
from tempest.services.volume.xml.extensions_client import \
|
from tempest.services.volume.xml.extensions_client import \
|
||||||
ExtensionsClientXML as VolumeExtensionClientXML
|
ExtensionsClientXML as VolumeExtensionClientXML
|
||||||
from tempest.services.volume.xml.snapshots_client import SnapshotsClientXML
|
from tempest.services.volume.xml.snapshots_client import SnapshotsClientXML
|
||||||
@ -212,6 +214,7 @@ class Manager(object):
|
|||||||
auth_provider)
|
auth_provider)
|
||||||
self.floating_ips_client = FloatingIPsClientXML(
|
self.floating_ips_client = FloatingIPsClientXML(
|
||||||
auth_provider)
|
auth_provider)
|
||||||
|
self.backups_client = BackupsClientXML(auth_provider)
|
||||||
self.snapshots_client = SnapshotsClientXML(auth_provider)
|
self.snapshots_client = SnapshotsClientXML(auth_provider)
|
||||||
self.volumes_client = VolumesClientXML(auth_provider)
|
self.volumes_client = VolumesClientXML(auth_provider)
|
||||||
self.volumes_v2_client = VolumesV2ClientXML(auth_provider)
|
self.volumes_v2_client = VolumesV2ClientXML(auth_provider)
|
||||||
@ -277,6 +280,7 @@ class Manager(object):
|
|||||||
auth_provider)
|
auth_provider)
|
||||||
self.floating_ips_client = FloatingIPsClientJSON(
|
self.floating_ips_client = FloatingIPsClientJSON(
|
||||||
auth_provider)
|
auth_provider)
|
||||||
|
self.backups_client = BackupsClientJSON(auth_provider)
|
||||||
self.snapshots_client = SnapshotsClientJSON(auth_provider)
|
self.snapshots_client = SnapshotsClientJSON(auth_provider)
|
||||||
self.volumes_client = VolumesClientJSON(auth_provider)
|
self.volumes_client = VolumesClientJSON(auth_provider)
|
||||||
self.volumes_v2_client = VolumesV2ClientJSON(auth_provider)
|
self.volumes_v2_client = VolumesV2ClientJSON(auth_provider)
|
||||||
|
@ -397,6 +397,9 @@ VolumeFeaturesGroup = [
|
|||||||
cfg.BoolOpt('multi_backend',
|
cfg.BoolOpt('multi_backend',
|
||||||
default=False,
|
default=False,
|
||||||
help="Runs Cinder multi-backend test (requires 2 backends)"),
|
help="Runs Cinder multi-backend test (requires 2 backends)"),
|
||||||
|
cfg.BoolOpt('backup',
|
||||||
|
default=True,
|
||||||
|
help='Runs Cinder volumes backup test'),
|
||||||
cfg.ListOpt('api_extensions',
|
cfg.ListOpt('api_extensions',
|
||||||
default=['all'],
|
default=['all'],
|
||||||
help='A list of enabled extensions with a special entry all '
|
help='A list of enabled extensions with a special entry all '
|
||||||
|
@ -100,6 +100,10 @@ class SnapshotBuildErrorException(TempestException):
|
|||||||
message = "Snapshot %(snapshot_id)s failed to build and is in ERROR status"
|
message = "Snapshot %(snapshot_id)s failed to build and is in ERROR status"
|
||||||
|
|
||||||
|
|
||||||
|
class VolumeBackupException(TempestException):
|
||||||
|
message = "Volume backup %(backup_id)s failed and is in ERROR status"
|
||||||
|
|
||||||
|
|
||||||
class StackBuildErrorException(TempestException):
|
class StackBuildErrorException(TempestException):
|
||||||
message = ("Stack %(stack_identifier)s is in %(stack_status)s status "
|
message = ("Stack %(stack_identifier)s is in %(stack_status)s status "
|
||||||
"due to '%(stack_status_reason)s'")
|
"due to '%(stack_status_reason)s'")
|
||||||
|
89
tempest/services/volume/json/backups_client.py
Normal file
89
tempest/services/volume/json/backups_client.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# Copyright 2014 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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 time
|
||||||
|
|
||||||
|
from tempest.common import rest_client
|
||||||
|
from tempest import config
|
||||||
|
from tempest import exceptions
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class BackupsClientJSON(rest_client.RestClient):
|
||||||
|
"""
|
||||||
|
Client class to send CRUD Volume backup API requests to a Cinder endpoint
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, auth_provider):
|
||||||
|
super(BackupsClientJSON, self).__init__(auth_provider)
|
||||||
|
self.service = CONF.volume.catalog_type
|
||||||
|
self.build_interval = CONF.volume.build_interval
|
||||||
|
self.build_timeout = CONF.volume.build_timeout
|
||||||
|
|
||||||
|
def create_backup(self, volume_id, container=None, name=None,
|
||||||
|
description=None):
|
||||||
|
"""Creates a backup of volume."""
|
||||||
|
post_body = {'volume_id': volume_id}
|
||||||
|
if container:
|
||||||
|
post_body['container'] = container
|
||||||
|
if name:
|
||||||
|
post_body['name'] = name
|
||||||
|
if description:
|
||||||
|
post_body['description'] = description
|
||||||
|
post_body = json.dumps({'backup': post_body})
|
||||||
|
resp, body = self.post('backups', post_body)
|
||||||
|
body = json.loads(body)
|
||||||
|
return resp, body['backup']
|
||||||
|
|
||||||
|
def restore_backup(self, backup_id, volume_id=None):
|
||||||
|
"""Restore volume from backup."""
|
||||||
|
post_body = {'volume_id': volume_id}
|
||||||
|
post_body = json.dumps({'restore': post_body})
|
||||||
|
resp, body = self.post('backups/%s/restore' % (backup_id), post_body)
|
||||||
|
body = json.loads(body)
|
||||||
|
return resp, body['restore']
|
||||||
|
|
||||||
|
def delete_backup(self, backup_id):
|
||||||
|
"""Delete a backup of volume."""
|
||||||
|
resp, body = self.delete('backups/%s' % (str(backup_id)))
|
||||||
|
return resp, body
|
||||||
|
|
||||||
|
def get_backup(self, backup_id):
|
||||||
|
"""Returns the details of a single backup."""
|
||||||
|
url = "backups/%s" % str(backup_id)
|
||||||
|
resp, body = self.get(url)
|
||||||
|
body = json.loads(body)
|
||||||
|
return resp, body['backup']
|
||||||
|
|
||||||
|
def wait_for_backup_status(self, backup_id, status):
|
||||||
|
"""Waits for a Backup to reach a given status."""
|
||||||
|
resp, body = self.get_backup(backup_id)
|
||||||
|
backup_status = body['status']
|
||||||
|
start = int(time.time())
|
||||||
|
|
||||||
|
while backup_status != status:
|
||||||
|
time.sleep(self.build_interval)
|
||||||
|
resp, body = self.get_backup(backup_id)
|
||||||
|
backup_status = body['status']
|
||||||
|
if backup_status == 'error':
|
||||||
|
raise exceptions.VolumeBackupException(backup_id=backup_id)
|
||||||
|
|
||||||
|
if int(time.time()) - start >= self.build_timeout:
|
||||||
|
message = ('Volume backup %s failed to reach %s status within '
|
||||||
|
'the required time (%s s).' %
|
||||||
|
(backup_id, status, self.build_timeout))
|
||||||
|
raise exceptions.TimeoutException(message)
|
25
tempest/services/volume/xml/backups_client.py
Normal file
25
tempest/services/volume/xml/backups_client.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Copyright 2014 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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 tempest.common.rest_client import RestClientXML
|
||||||
|
|
||||||
|
|
||||||
|
class BackupsClientXML(RestClientXML):
|
||||||
|
"""
|
||||||
|
Client class to send CRUD Volume Backup API requests to a Cinder endpoint
|
||||||
|
"""
|
||||||
|
|
||||||
|
#TODO(gfidente): XML client isn't yet implemented because of bug 1270589
|
||||||
|
pass
|
Loading…
Reference in New Issue
Block a user