Manage and unmanage snapshot
Add APIs to support manage and unmanage share snapshots. Also add support in the Generic driver. This only supports for DHSS=False driver mode. Add provider_location column to the share_snapshots table to save data used to identify the snapshot on the backend. Also need to bump microversion. APIImpact DocImpact Change-Id: I87a066173c85d969607d132accd9f0e9bd49c235 Implements: blueprint manage-unmanage-snapshot
This commit is contained in:
parent
66a7ce241e
commit
69b00b5b26
@ -36,7 +36,7 @@ ShareGroup = [
|
||||
help="The minimum api microversion is configured to be the "
|
||||
"value of the minimum microversion supported by Manila."),
|
||||
cfg.StrOpt("max_api_microversion",
|
||||
default="2.11",
|
||||
default="2.12",
|
||||
help="The maximum api microversion is configured to be the "
|
||||
"value of the latest microversion supported by Manila."),
|
||||
cfg.StrOpt("region",
|
||||
@ -128,11 +128,6 @@ ShareGroup = [
|
||||
help="Whether to suppress errors with clean up operation "
|
||||
"or not. There are cases when we may want to skip "
|
||||
"such errors and catch only test errors."),
|
||||
cfg.BoolOpt("run_manage_unmanage_tests",
|
||||
default=False,
|
||||
help="Defines whether to run manage/unmanage tests or not. "
|
||||
"These test may leave orphaned resources, so be careful "
|
||||
"enabling this opt."),
|
||||
|
||||
# Switching ON/OFF test suites filtered by features
|
||||
cfg.BoolOpt("run_quota_tests",
|
||||
@ -161,6 +156,16 @@ ShareGroup = [
|
||||
cfg.BoolOpt("run_migration_tests",
|
||||
default=False,
|
||||
help="Enable or disable migration tests."),
|
||||
cfg.BoolOpt("run_manage_unmanage_tests",
|
||||
default=False,
|
||||
help="Defines whether to run manage/unmanage tests or not. "
|
||||
"These test may leave orphaned resources, so be careful "
|
||||
"enabling this opt."),
|
||||
cfg.BoolOpt("run_manage_unmanage_snapshot_tests",
|
||||
default=False,
|
||||
help="Defines whether to run manage/unmanage snapshot tests "
|
||||
"or not. These tests may leave orphaned resources, so be "
|
||||
"careful enabling this opt."),
|
||||
|
||||
cfg.StrOpt("image_with_share_tools",
|
||||
default="manila-service-image",
|
||||
|
@ -437,6 +437,113 @@ class SharesV2Client(shares_client.SharesClient):
|
||||
self.expected_success(202, resp.status)
|
||||
return body
|
||||
|
||||
###############
|
||||
|
||||
def create_snapshot(self, share_id, name=None, description=None,
|
||||
force=False, version=LATEST_MICROVERSION):
|
||||
if name is None:
|
||||
name = data_utils.rand_name("tempest-created-share-snap")
|
||||
if description is None:
|
||||
description = data_utils.rand_name(
|
||||
"tempest-created-share-snap-desc")
|
||||
post_body = {
|
||||
"snapshot": {
|
||||
"name": name,
|
||||
"force": force,
|
||||
"description": description,
|
||||
"share_id": share_id,
|
||||
}
|
||||
}
|
||||
body = json.dumps(post_body)
|
||||
resp, body = self.post("snapshots", body, version=version)
|
||||
self.expected_success(202, resp.status)
|
||||
return self._parse_resp(body)
|
||||
|
||||
def get_snapshot(self, snapshot_id, version=LATEST_MICROVERSION):
|
||||
resp, body = self.get("snapshots/%s" % snapshot_id, version=version)
|
||||
self.expected_success(200, resp.status)
|
||||
return self._parse_resp(body)
|
||||
|
||||
def list_snapshots(self, detailed=False, params=None,
|
||||
version=LATEST_MICROVERSION):
|
||||
"""Get list of share snapshots w/o filters."""
|
||||
uri = 'snapshots/detail' if detailed else 'snapshots'
|
||||
uri += '?%s' % urlparse.urlencode(params) if params else ''
|
||||
resp, body = self.get(uri, version=version)
|
||||
self.expected_success(200, resp.status)
|
||||
return self._parse_resp(body)
|
||||
|
||||
def list_snapshots_with_detail(self, params=None,
|
||||
version=LATEST_MICROVERSION):
|
||||
"""Get detailed list of share snapshots w/o filters."""
|
||||
return self.list_snapshots(detailed=True, params=params,
|
||||
version=version)
|
||||
|
||||
def delete_snapshot(self, snap_id, version=LATEST_MICROVERSION):
|
||||
resp, body = self.delete("snapshots/%s" % snap_id, version=version)
|
||||
self.expected_success(202, resp.status)
|
||||
return body
|
||||
|
||||
def wait_for_snapshot_status(self, snapshot_id, status,
|
||||
version=LATEST_MICROVERSION):
|
||||
"""Waits for a snapshot to reach a given status."""
|
||||
body = self.get_snapshot(snapshot_id, version=version)
|
||||
snapshot_name = body['name']
|
||||
snapshot_status = body['status']
|
||||
start = int(time.time())
|
||||
|
||||
while snapshot_status != status:
|
||||
time.sleep(self.build_interval)
|
||||
body = self.get_snapshot(snapshot_id, version=version)
|
||||
snapshot_status = body['status']
|
||||
if 'error' in snapshot_status:
|
||||
raise (share_exceptions.
|
||||
SnapshotBuildErrorException(snapshot_id=snapshot_id))
|
||||
|
||||
if int(time.time()) - start >= self.build_timeout:
|
||||
message = ('Share Snapshot %s failed to reach %s status '
|
||||
'within the required time (%s s).' %
|
||||
(snapshot_name, status, self.build_timeout))
|
||||
raise exceptions.TimeoutException(message)
|
||||
|
||||
def manage_snapshot(self, share_id, provider_location,
|
||||
name=None, description=None,
|
||||
version=LATEST_MICROVERSION,
|
||||
driver_options=None):
|
||||
if name is None:
|
||||
name = data_utils.rand_name("tempest-manage-snapshot")
|
||||
if description is None:
|
||||
description = data_utils.rand_name("tempest-manage-snapshot-desc")
|
||||
post_body = {
|
||||
"snapshot": {
|
||||
"share_id": share_id,
|
||||
"provider_location": provider_location,
|
||||
"name": name,
|
||||
"description": description,
|
||||
"driver_options": driver_options if driver_options else {},
|
||||
}
|
||||
}
|
||||
url = 'snapshots/manage'
|
||||
body = json.dumps(post_body)
|
||||
resp, body = self.post(url, body, version=version)
|
||||
self.expected_success(202, resp.status)
|
||||
return self._parse_resp(body)
|
||||
|
||||
def unmanage_snapshot(self, snapshot_id, version=LATEST_MICROVERSION,
|
||||
body=None):
|
||||
url = 'snapshots'
|
||||
action_name = 'action'
|
||||
if body is None:
|
||||
body = json.dumps({'unmanage': {}})
|
||||
resp, body = self.post(
|
||||
"%(url)s/%(snapshot_id)s/%(action_name)s" % {
|
||||
'url': url, 'snapshot_id': snapshot_id,
|
||||
'action_name': action_name},
|
||||
body,
|
||||
version=version)
|
||||
self.expected_success(202, resp.status)
|
||||
return body
|
||||
|
||||
###############
|
||||
|
||||
def _get_access_action_name(self, version, action):
|
||||
|
143
manila_tempest_tests/tests/api/admin/test_snapshot_manage.py
Normal file
143
manila_tempest_tests/tests/api/admin/test_snapshot_manage.py
Normal file
@ -0,0 +1,143 @@
|
||||
# Copyright 2015 EMC Corporation.
|
||||
# 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 six
|
||||
from tempest import config
|
||||
from tempest import test
|
||||
from tempest_lib.common.utils import data_utils
|
||||
from tempest_lib import exceptions as lib_exc
|
||||
import testtools
|
||||
|
||||
from manila_tempest_tests.tests.api import base
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class ManageNFSSnapshotTest(base.BaseSharesAdminTest):
|
||||
protocol = 'nfs'
|
||||
|
||||
# NOTE(vponomaryov): be careful running these tests using generic driver
|
||||
# because cinder volume snapshots won't be deleted.
|
||||
|
||||
@classmethod
|
||||
@base.skip_if_microversion_lt("2.12")
|
||||
@testtools.skipIf(
|
||||
CONF.share.multitenancy_enabled,
|
||||
"Only for driver_handles_share_servers = False driver mode.")
|
||||
@testtools.skipUnless(
|
||||
CONF.share.run_manage_unmanage_snapshot_tests,
|
||||
"Manage/unmanage snapshot tests are disabled.")
|
||||
def resource_setup(cls):
|
||||
super(ManageNFSSnapshotTest, cls).resource_setup()
|
||||
if cls.protocol not in CONF.share.enable_protocols:
|
||||
message = "%s tests are disabled" % cls.protocol
|
||||
raise cls.skipException(message)
|
||||
|
||||
# Create share type
|
||||
cls.st_name = data_utils.rand_name("tempest-manage-st-name")
|
||||
cls.extra_specs = {
|
||||
'storage_protocol': CONF.share.capability_storage_protocol,
|
||||
'driver_handles_share_servers': False,
|
||||
'snapshot_support': six.text_type(
|
||||
CONF.share.capability_snapshot_support),
|
||||
}
|
||||
|
||||
cls.st = cls.create_share_type(
|
||||
name=cls.st_name,
|
||||
cleanup_in_class=True,
|
||||
extra_specs=cls.extra_specs)
|
||||
|
||||
creation_data = {'kwargs': {
|
||||
'share_type_id': cls.st['share_type']['id'],
|
||||
'share_protocol': cls.protocol,
|
||||
}}
|
||||
|
||||
# Data for creating shares
|
||||
data = [creation_data]
|
||||
shares_created = cls.create_shares(data)
|
||||
|
||||
cls.snapshot = None
|
||||
cls.shares = []
|
||||
# Load all share data (host, etc.)
|
||||
for share in shares_created:
|
||||
cls.shares.append(cls.shares_v2_client.get_share(share['id']))
|
||||
# Create snapshot
|
||||
snap_name = data_utils.rand_name("tempest-snapshot-name")
|
||||
snap_desc = data_utils.rand_name(
|
||||
"tempest-snapshot-description")
|
||||
snap = cls.create_snapshot_wait_for_active(
|
||||
share['id'], snap_name, snap_desc)
|
||||
cls.snapshot = cls.shares_v2_client.get_snapshot(snap['id'])
|
||||
# Unmanage snapshot
|
||||
cls.shares_v2_client.unmanage_snapshot(snap['id'])
|
||||
cls.shares_client.wait_for_resource_deletion(
|
||||
snapshot_id=snap['id'])
|
||||
|
||||
def _test_manage(self, snapshot, version=CONF.share.max_api_microversion):
|
||||
name = ("Name for 'managed' snapshot that had ID %s" %
|
||||
snapshot['id'])
|
||||
description = "Description for 'managed' snapshot"
|
||||
|
||||
# Manage snapshot
|
||||
share_id = snapshot['share_id']
|
||||
snapshot = self.shares_v2_client.manage_snapshot(
|
||||
share_id,
|
||||
snapshot['provider_location'],
|
||||
name=name,
|
||||
description=description,
|
||||
driver_options={}
|
||||
)
|
||||
|
||||
# Add managed snapshot to cleanup queue
|
||||
self.method_resources.insert(
|
||||
0, {'type': 'snapshot', 'id': snapshot['id'],
|
||||
'client': self.shares_v2_client})
|
||||
|
||||
# Wait for success
|
||||
self.shares_v2_client.wait_for_snapshot_status(snapshot['id'],
|
||||
'available')
|
||||
|
||||
# Verify data of managed snapshot
|
||||
get_snapshot = self.shares_v2_client.get_snapshot(snapshot['id'])
|
||||
self.assertEqual(name, get_snapshot['name'])
|
||||
self.assertEqual(description, get_snapshot['description'])
|
||||
self.assertEqual(snapshot['share_id'], get_snapshot['share_id'])
|
||||
self.assertEqual(snapshot['provider_location'],
|
||||
get_snapshot['provider_location'])
|
||||
|
||||
# Delete snapshot
|
||||
self.shares_v2_client.delete_snapshot(get_snapshot['id'])
|
||||
self.shares_client.wait_for_resource_deletion(
|
||||
snapshot_id=get_snapshot['id'])
|
||||
self.assertRaises(lib_exc.NotFound,
|
||||
self.shares_v2_client.get_snapshot,
|
||||
get_snapshot['id'])
|
||||
|
||||
@test.attr(type=["gate", "smoke"])
|
||||
def test_manage(self):
|
||||
# Manage snapshot
|
||||
self._test_manage(snapshot=self.snapshot)
|
||||
|
||||
|
||||
class ManageCIFSSnapshotTest(ManageNFSSnapshotTest):
|
||||
protocol = 'cifs'
|
||||
|
||||
|
||||
class ManageGLUSTERFSSnapshotTest(ManageNFSSnapshotTest):
|
||||
protocol = 'glusterfs'
|
||||
|
||||
|
||||
class ManageHDFSSnapshotTest(ManageNFSSnapshotTest):
|
||||
protocol = 'hdfs'
|
@ -0,0 +1,109 @@
|
||||
# Copyright 2015 EMC Corporation.
|
||||
# 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 six
|
||||
from tempest import config
|
||||
from tempest import test
|
||||
from tempest_lib.common.utils import data_utils
|
||||
from tempest_lib import exceptions as lib_exc
|
||||
import testtools
|
||||
|
||||
from manila_tempest_tests.tests.api import base
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class ManageNFSSnapshotNegativeTest(base.BaseSharesAdminTest):
|
||||
protocol = 'nfs'
|
||||
|
||||
@classmethod
|
||||
@base.skip_if_microversion_lt("2.12")
|
||||
@testtools.skipIf(
|
||||
CONF.share.multitenancy_enabled,
|
||||
"Only for driver_handles_share_servers = False driver mode.")
|
||||
@testtools.skipUnless(
|
||||
CONF.share.run_manage_unmanage_snapshot_tests,
|
||||
"Manage/unmanage snapshot tests are disabled.")
|
||||
def resource_setup(cls):
|
||||
super(ManageNFSSnapshotNegativeTest, cls).resource_setup()
|
||||
if cls.protocol not in CONF.share.enable_protocols:
|
||||
message = "%s tests are disabled" % cls.protocol
|
||||
raise cls.skipException(message)
|
||||
|
||||
# Create share type
|
||||
cls.st_name = data_utils.rand_name("tempest-manage-st-name")
|
||||
cls.extra_specs = {
|
||||
'storage_protocol': CONF.share.capability_storage_protocol,
|
||||
'driver_handles_share_servers': False,
|
||||
'snapshot_support': six.text_type(
|
||||
CONF.share.capability_snapshot_support),
|
||||
}
|
||||
|
||||
cls.st = cls.create_share_type(
|
||||
name=cls.st_name,
|
||||
cleanup_in_class=True,
|
||||
extra_specs=cls.extra_specs)
|
||||
|
||||
# Create share
|
||||
cls.share = cls.create_share(
|
||||
share_type_id=cls.st['share_type']['id'],
|
||||
share_protocol=cls.protocol
|
||||
)
|
||||
|
||||
@test.attr(type=["gate", "smoke", "negative", ])
|
||||
def test_manage_not_found(self):
|
||||
# Manage snapshot fails
|
||||
self.assertRaises(lib_exc.NotFound,
|
||||
self.shares_v2_client.manage_snapshot,
|
||||
'fake-share-id',
|
||||
'fake-vol-snap-id',
|
||||
driver_options={})
|
||||
|
||||
@test.attr(type=["gate", "smoke", "negative", ])
|
||||
def test_manage_already_exists(self):
|
||||
# Manage already existing snapshot fails
|
||||
|
||||
# Create snapshot
|
||||
snap = self.create_snapshot_wait_for_active(self.share['id'])
|
||||
get_snap = self.shares_v2_client.get_snapshot(snap['id'])
|
||||
self.assertEqual(self.share['id'], get_snap['share_id'])
|
||||
self.assertIsNotNone(get_snap['provider_location'])
|
||||
|
||||
# Manage snapshot fails
|
||||
self.assertRaises(lib_exc.Conflict,
|
||||
self.shares_v2_client.manage_snapshot,
|
||||
self.share['id'],
|
||||
get_snap['provider_location'],
|
||||
driver_options={})
|
||||
|
||||
# Delete snapshot
|
||||
self.shares_v2_client.delete_snapshot(get_snap['id'])
|
||||
self.shares_client.wait_for_resource_deletion(
|
||||
snapshot_id=get_snap['id'])
|
||||
self.assertRaises(lib_exc.NotFound,
|
||||
self.shares_v2_client.get_snapshot,
|
||||
get_snap['id'])
|
||||
|
||||
|
||||
class ManageCIFSSnapshotNegativeTest(ManageNFSSnapshotNegativeTest):
|
||||
protocol = 'cifs'
|
||||
|
||||
|
||||
class ManageGLUSTERFSSnapshotNegativeTest(ManageNFSSnapshotNegativeTest):
|
||||
protocol = 'glusterfs'
|
||||
|
||||
|
||||
class ManageHDFSSnapshotNegativeTest(ManageNFSSnapshotNegativeTest):
|
||||
protocol = 'hdfs'
|
@ -79,6 +79,7 @@ def network_synchronized(f):
|
||||
|
||||
|
||||
skip_if_microversion_not_supported = utils.skip_if_microversion_not_supported
|
||||
skip_if_microversion_lt = utils.skip_if_microversion_lt
|
||||
|
||||
|
||||
class BaseSharesTest(test.BaseTestCase):
|
||||
@ -104,6 +105,13 @@ class BaseSharesTest(test.BaseTestCase):
|
||||
raise self.skipException(
|
||||
"Microversion '%s' is not supported." % microversion)
|
||||
|
||||
def skip_if_microversion_lt(self, microversion):
|
||||
if utils.is_microversion_lt(CONF.share.max_api_microversion,
|
||||
microversion):
|
||||
raise self.skipException(
|
||||
"Microversion must be greater than or equal to '%s'." %
|
||||
microversion)
|
||||
|
||||
@classmethod
|
||||
def get_client_with_isolated_creds(cls,
|
||||
name=None,
|
||||
|
@ -81,6 +81,15 @@ def skip_if_microversion_not_supported(microversion):
|
||||
return lambda f: f
|
||||
|
||||
|
||||
def skip_if_microversion_lt(microversion):
|
||||
"""Decorator for tests that are microversion-specific."""
|
||||
if is_microversion_lt(CONF.share.max_api_microversion, microversion):
|
||||
reason = ("Skipped. Test requires microversion greater than or "
|
||||
"equal to '%s'." % microversion)
|
||||
return testtools.skip(reason)
|
||||
return lambda f: f
|
||||
|
||||
|
||||
def rand_ip():
|
||||
"""This uses the TEST-NET-3 range of reserved IP addresses.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user