Merge "Add an images client."
diff --git a/.testr.conf b/.testr.conf
new file mode 100644
index 0000000..a0262d8
--- /dev/null
+++ b/.testr.conf
@@ -0,0 +1,4 @@
+[DEFAULT]
+test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./tempest $LISTOPT $IDOPTION
+test_id_option=--load-list $IDFILE
+test_list_option=--list
diff --git a/tempest/clients.py b/tempest/clients.py
index b178d2f..150cf67 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -57,7 +57,9 @@
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 @@
"xml": FloatingIPsClientXML,
}
+SNAPSHOTS_CLIENTS = {
+ "json": SnapshotsClientJSON,
+ "xml": SnapshotsClientXML,
+}
+
VOLUMES_CLIENTS = {
"json": VolumesClientJSON,
"xml": VolumesClientXML,
@@ -184,6 +191,7 @@
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)
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index bcfc40b..577aa13 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -86,6 +86,10 @@
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"
diff --git a/tempest/manager.py b/tempest/manager.py
index 344b8fb..6f23727 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -40,6 +40,7 @@
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 @@
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 @@
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)
diff --git a/tempest/services/volume/json/snapshots_client.py b/tempest/services/volume/json/snapshots_client.py
new file mode 100644
index 0000000..9545d0b
--- /dev/null
+++ b/tempest/services/volume/json/snapshots_client.py
@@ -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
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index cc5a115..75e1a8b 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -71,14 +71,10 @@
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)
diff --git a/tempest/services/volume/xml/snapshots_client.py b/tempest/services/volume/xml/snapshots_client.py
new file mode 100644
index 0000000..c89f66e
--- /dev/null
+++ b/tempest/services/volume/xml/snapshots_client.py
@@ -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
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index 440a80e..862ffae 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -101,6 +101,7 @@
: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:
diff --git a/tempest/test.py b/tempest/test.py
index 7804e12..58fadeb 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -18,6 +18,7 @@
import logging
import time
+import nose.plugins.attrib
import testtools
from tempest import manager
@@ -25,6 +26,25 @@
LOG = logging.getLogger(__name__)
+def attr(*args, **kwargs):
+ """A decorator which applies the nose and testtools attr decorator
+
+ This decorator applies the nose attr decorator as well as the
+ the testtools.testcase.attr if it is in the list of attributes
+ to testtools we want to apply."""
+
+ def decorator(f):
+ testtool_attributes = ('smoke')
+
+ if 'type' in kwargs and kwargs['type'] in testtool_attributes:
+ return nose.plugins.attrib.attr(*args, **kwargs)(
+ testtools.testcase.attr(kwargs['type'])(f))
+ else:
+ return nose.plugins.attrib.attr(*args, **kwargs)(f)
+
+ return decorator
+
+
class TestCase(testtools.TestCase):
"""
diff --git a/tempest/testboto.py b/tempest/testboto.py
index 8bcaa33..1b768ef 100644
--- a/tempest/testboto.py
+++ b/tempest/testboto.py
@@ -122,7 +122,8 @@
return string + ")"
-class BotoTestCase(testtools.TestCase,
+class BotoTestCase(testtools.testcase.WithAttributes,
+ testtools.TestCase,
testresources.ResourcedTestCase):
"""Recommended to use as base class for boto related test."""
diff --git a/tempest/tests/boto/test_ec2_instance_run.py b/tempest/tests/boto/test_ec2_instance_run.py
index 1adb5fb..8358ef9 100644
--- a/tempest/tests/boto/test_ec2_instance_run.py
+++ b/tempest/tests/boto/test_ec2_instance_run.py
@@ -20,13 +20,13 @@
from boto.exception import EC2ResponseError
from boto.s3.key import Key
-from nose.plugins.attrib import attr
import testtools
from tempest import clients
from tempest.common.utils.data_utils import rand_name
from tempest.common.utils.linux.remote_client import RemoteClient
from tempest.exceptions import EC2RegisterImageException
+from tempest.test import attr
from tempest.testboto import BotoTestCase
import tempest.tests.boto
from tempest.tests.boto.utils.s3 import s3_upload_dir
diff --git a/tempest/tests/boto/test_ec2_keys.py b/tempest/tests/boto/test_ec2_keys.py
index dea895f..d96ee11 100644
--- a/tempest/tests/boto/test_ec2_keys.py
+++ b/tempest/tests/boto/test_ec2_keys.py
@@ -15,11 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
import testtools
from tempest import clients
from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
from tempest.testboto import BotoTestCase
diff --git a/tempest/tests/boto/test_ec2_network.py b/tempest/tests/boto/test_ec2_network.py
index 76103c2..ef476a2 100644
--- a/tempest/tests/boto/test_ec2_network.py
+++ b/tempest/tests/boto/test_ec2_network.py
@@ -15,10 +15,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
import testtools
from tempest import clients
+from tempest.test import attr
from tempest.testboto import BotoTestCase
diff --git a/tempest/tests/boto/test_ec2_security_groups.py b/tempest/tests/boto/test_ec2_security_groups.py
index ed7bedb..5981408 100644
--- a/tempest/tests/boto/test_ec2_security_groups.py
+++ b/tempest/tests/boto/test_ec2_security_groups.py
@@ -15,11 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
import testtools
from tempest import clients
from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
from tempest.testboto import BotoTestCase
diff --git a/tempest/tests/boto/test_ec2_volumes.py b/tempest/tests/boto/test_ec2_volumes.py
index 7397cdb..7d3c5ab 100644
--- a/tempest/tests/boto/test_ec2_volumes.py
+++ b/tempest/tests/boto/test_ec2_volumes.py
@@ -18,10 +18,10 @@
import logging
import time
-from nose.plugins.attrib import attr
import testtools
from tempest import clients
+from tempest.test import attr
from tempest.testboto import BotoTestCase
LOG = logging.getLogger(__name__)
diff --git a/tempest/tests/boto/test_s3_buckets.py b/tempest/tests/boto/test_s3_buckets.py
index 36aa319..a4d1927 100644
--- a/tempest/tests/boto/test_s3_buckets.py
+++ b/tempest/tests/boto/test_s3_buckets.py
@@ -15,11 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
import testtools
from tempest import clients
from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
from tempest.testboto import BotoTestCase
diff --git a/tempest/tests/boto/test_s3_ec2_images.py b/tempest/tests/boto/test_s3_ec2_images.py
index e0dc124..da248e1 100644
--- a/tempest/tests/boto/test_s3_ec2_images.py
+++ b/tempest/tests/boto/test_s3_ec2_images.py
@@ -19,11 +19,11 @@
import os
from boto.s3.key import Key
-from nose.plugins.attrib import attr
import testtools
from tempest import clients
from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
from tempest.testboto import BotoTestCase
import tempest.tests.boto
from tempest.tests.boto.utils.s3 import s3_upload_dir
diff --git a/tempest/tests/boto/test_s3_objects.py b/tempest/tests/boto/test_s3_objects.py
index cacd99f..e7bc4b1 100644
--- a/tempest/tests/boto/test_s3_objects.py
+++ b/tempest/tests/boto/test_s3_objects.py
@@ -18,11 +18,11 @@
from contextlib import closing
from boto.s3.key import Key
-from nose.plugins.attrib import attr
import testtools
from tempest import clients
from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
from tempest.testboto import BotoTestCase
from tempest.tests import boto
diff --git a/tempest/tests/compute/admin/test_flavors.py b/tempest/tests/compute/admin/test_flavors.py
index 2967666..fe977c1 100644
--- a/tempest/tests/compute/admin/test_flavors.py
+++ b/tempest/tests/compute/admin/test_flavors.py
@@ -15,11 +15,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
import testtools
from tempest.common.utils.data_utils import rand_int_id
from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
from tempest.tests import compute
from tempest.tests.compute import base
@@ -307,6 +308,12 @@
self.assertEqual(resp.status, 202)
self.client.wait_for_resource_deletion(flavor_id)
+ @attr(type='negative')
+ def test_invalid_is_public_string(self):
+ self.assertRaises(exceptions.BadRequest,
+ self.client.list_flavors_with_detail,
+ {'is_public': 'invalid'})
+
class FlavorsAdminTestXML(base.BaseComputeAdminTestXML,
base.BaseComputeTestXML,
diff --git a/tempest/tests/compute/admin/test_quotas.py b/tempest/tests/compute/admin/test_quotas.py
index b2b515a..0f46d62 100644
--- a/tempest/tests/compute/admin/test_quotas.py
+++ b/tempest/tests/compute/admin/test_quotas.py
@@ -15,12 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
-
from tempest import exceptions
from tempest.services.compute.admin.json \
import quotas_client as adm_quotas_json
from tempest.services.compute.admin.xml import quotas_client as adm_quotas_xml
+from tempest.test import attr
from tempest.tests import compute
from tempest.tests.compute import base
@@ -70,7 +69,7 @@
try:
resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
self.assertEqual(200, resp.status)
- self.assertSequenceEqual(expected_quota_set, quota_set)
+ self.assertEqual(expected_quota_set, quota_set)
except Exception:
self.fail("Admin could not get the default quota set for a tenant")
@@ -88,7 +87,7 @@
self.demo_tenant_id,
**new_quota_set)
self.assertEqual(200, resp.status)
- self.assertSequenceEqual(new_quota_set, quota_set)
+ self.assertEqual(new_quota_set, quota_set)
except Exception:
self.fail("Admin could not update quota set for the tenant")
finally:
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index 3810046..f2af1fc 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -33,7 +33,8 @@
LOG = logging.getLogger(__name__)
-class BaseCompTest(testtools.TestCase,
+class BaseCompTest(testtools.testcase.WithAttributes,
+ testtools.TestCase,
testresources.ResourcedTestCase):
"""Base test case class for all Compute API tests."""
diff --git a/tempest/tests/compute/flavors/test_flavors.py b/tempest/tests/compute/flavors/test_flavors.py
index 298984f..8b49390 100644
--- a/tempest/tests/compute/flavors/test_flavors.py
+++ b/tempest/tests/compute/flavors/test_flavors.py
@@ -15,8 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.compute import base
@@ -131,6 +131,18 @@
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
@attr(type='negative')
+ def test_invalid_minRam_filter(self):
+ self.assertRaises(exceptions.BadRequest,
+ self.client.list_flavors_with_detail,
+ {'minRam': 'invalid'})
+
+ @attr(type='negative')
+ def test_invalid_minDisk_filter(self):
+ self.assertRaises(exceptions.BadRequest,
+ self.client.list_flavors_with_detail,
+ {'minDisk': 'invalid'})
+
+ @attr(type='negative')
def test_get_flavor_details_for_invalid_flavor_id(self):
# Ensure 404 returned for non-existant flavor ID
self.assertRaises(exceptions.NotFound, self.client.get_flavor_details,
diff --git a/tempest/tests/compute/floating_ips/test_floating_ips_actions.py b/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
index 59faf66..9f0cda0 100644
--- a/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
@@ -15,12 +15,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
import testtools
from tempest import clients
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.compute import base
diff --git a/tempest/tests/compute/floating_ips/test_list_floating_ips.py b/tempest/tests/compute/floating_ips/test_list_floating_ips.py
index ea99e89..e534e3c 100644
--- a/tempest/tests/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/tests/compute/floating_ips/test_list_floating_ips.py
@@ -15,11 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
import testtools
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.compute import base
diff --git a/tempest/tests/compute/images/test_image_metadata.py b/tempest/tests/compute/images/test_image_metadata.py
index 94bdca7..7119a8e 100644
--- a/tempest/tests/compute/images/test_image_metadata.py
+++ b/tempest/tests/compute/images/test_image_metadata.py
@@ -15,10 +15,9 @@
# 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 import exceptions
+from tempest.test import attr
from tempest.tests.compute.base import BaseComputeTest
diff --git a/tempest/tests/compute/images/test_images.py b/tempest/tests/compute/images/test_images.py
index 1fc03b8..446ef50 100644
--- a/tempest/tests/compute/images/test_images.py
+++ b/tempest/tests/compute/images/test_images.py
@@ -15,7 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
import testtools
from tempest import clients
@@ -23,6 +22,7 @@
from tempest.common.utils.data_utils import rand_name
import tempest.config
from tempest import exceptions
+from tempest.test import attr
from tempest.tests import compute
from tempest.tests.compute import base
diff --git a/tempest/tests/compute/images/test_images_oneserver.py b/tempest/tests/compute/images/test_images_oneserver.py
index 232d5d3..5cc921b 100644
--- a/tempest/tests/compute/images/test_images_oneserver.py
+++ b/tempest/tests/compute/images/test_images_oneserver.py
@@ -15,7 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
import testtools
from tempest import clients
@@ -23,6 +22,7 @@
from tempest.common.utils.data_utils import rand_name
import tempest.config
from tempest import exceptions
+from tempest.test import attr
from tempest.tests import compute
from tempest.tests.compute import base
diff --git a/tempest/tests/compute/images/test_images_whitebox.py b/tempest/tests/compute/images/test_images_whitebox.py
index b2419b4..88f3b53 100644
--- a/tempest/tests/compute/images/test_images_whitebox.py
+++ b/tempest/tests/compute/images/test_images_whitebox.py
@@ -15,10 +15,9 @@
# 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 import exceptions
+from tempest.test import attr
from tempest.tests.compute import base
from tempest import whitebox
diff --git a/tempest/tests/compute/images/test_list_image_filters.py b/tempest/tests/compute/images/test_list_image_filters.py
index 26119e3..316d90a 100644
--- a/tempest/tests/compute/images/test_list_image_filters.py
+++ b/tempest/tests/compute/images/test_list_image_filters.py
@@ -15,11 +15,10 @@
# 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 parse_image_id
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.compute.base import BaseComputeTest
diff --git a/tempest/tests/compute/images/test_list_images.py b/tempest/tests/compute/images/test_list_images.py
index da92ca8..b9b57ee 100644
--- a/tempest/tests/compute/images/test_list_images.py
+++ b/tempest/tests/compute/images/test_list_images.py
@@ -15,11 +15,10 @@
# 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 parse_image_id
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.compute.base import BaseComputeTest
diff --git a/tempest/tests/compute/keypairs/test_keypairs.py b/tempest/tests/compute/keypairs/test_keypairs.py
index 45c9079..6f4569b 100644
--- a/tempest/tests/compute/keypairs/test_keypairs.py
+++ b/tempest/tests/compute/keypairs/test_keypairs.py
@@ -15,11 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
import testtools
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.compute.base import BaseComputeTestJSON
from tempest.tests.compute.base import BaseComputeTestXML
diff --git a/tempest/tests/compute/security_groups/test_security_group_rules.py b/tempest/tests/compute/security_groups/test_security_group_rules.py
index 805adf4..41ad2c8 100644
--- a/tempest/tests/compute/security_groups/test_security_group_rules.py
+++ b/tempest/tests/compute/security_groups/test_security_group_rules.py
@@ -15,10 +15,9 @@
# 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 import exceptions
+from tempest.test import attr
from tempest.tests.compute import base
diff --git a/tempest/tests/compute/security_groups/test_security_groups.py b/tempest/tests/compute/security_groups/test_security_groups.py
index 5c0bd82..f6caf2b 100644
--- a/tempest/tests/compute/security_groups/test_security_groups.py
+++ b/tempest/tests/compute/security_groups/test_security_groups.py
@@ -15,10 +15,9 @@
# 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 import exceptions
+from tempest.test import attr
from tempest.tests.compute import base
diff --git a/tempest/tests/compute/servers/test_console_output.py b/tempest/tests/compute/servers/test_console_output.py
index 1eadbb5..78639f8 100644
--- a/tempest/tests/compute/servers/test_console_output.py
+++ b/tempest/tests/compute/servers/test_console_output.py
@@ -15,11 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
import testtools
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.compute import base
diff --git a/tempest/tests/compute/servers/test_create_server.py b/tempest/tests/compute/servers/test_create_server.py
index 0dcc79f..54f1131 100644
--- a/tempest/tests/compute/servers/test_create_server.py
+++ b/tempest/tests/compute/servers/test_create_server.py
@@ -17,13 +17,13 @@
import base64
-from nose.plugins.attrib import attr
import testtools
from tempest.common.utils.data_utils import rand_name
from tempest.common.utils.linux.remote_client import RemoteClient
import tempest.config
+from tempest.test import attr
from tempest.tests import compute
from tempest.tests.compute import base
diff --git a/tempest/tests/compute/servers/test_disk_config.py b/tempest/tests/compute/servers/test_disk_config.py
index c3a37ff..3987e69 100644
--- a/tempest/tests/compute/servers/test_disk_config.py
+++ b/tempest/tests/compute/servers/test_disk_config.py
@@ -15,11 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
import testtools
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
from tempest.tests import compute
from tempest.tests.compute.base import BaseComputeTest
diff --git a/tempest/tests/compute/servers/test_list_server_filters.py b/tempest/tests/compute/servers/test_list_server_filters.py
index 45ea3a0..e07a905 100644
--- a/tempest/tests/compute/servers/test_list_server_filters.py
+++ b/tempest/tests/compute/servers/test_list_server_filters.py
@@ -16,11 +16,11 @@
# under the License.
-from nose.plugins.attrib import attr
import testtools
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.compute import base
from tempest.tests import utils
diff --git a/tempest/tests/compute/servers/test_server_actions.py b/tempest/tests/compute/servers/test_server_actions.py
index fd35461..c30538f 100644
--- a/tempest/tests/compute/servers/test_server_actions.py
+++ b/tempest/tests/compute/servers/test_server_actions.py
@@ -18,13 +18,13 @@
import base64
import time
-from nose.plugins.attrib import attr
import testtools
from tempest.common.utils.data_utils import rand_name
from tempest.common.utils.linux.remote_client import RemoteClient
import tempest.config
from tempest import exceptions
+from tempest.test import attr
from tempest.tests import compute
from tempest.tests.compute import base
@@ -122,13 +122,24 @@
linux_client = RemoteClient(server, self.ssh_user, password)
self.assertTrue(linux_client.can_authenticate())
+ def _detect_server_image_flavor(self, server_id):
+ # Detects the current server image flavor ref.
+ resp, server = self.client.get_server(self.server_id)
+ current_flavor = server['flavor']['id']
+ new_flavor_ref = self.flavor_ref_alt \
+ if int(current_flavor) == self.flavor_ref else self.flavor_ref
+ return int(current_flavor), int(new_flavor_ref)
+
@attr(type='smoke')
@testtools.skipIf(not resize_available, 'Resize not available.')
def test_resize_server_confirm(self):
# The server's RAM and disk space should be modified to that of
# the provided flavor
- resp, server = self.client.resize(self.server_id, self.flavor_ref_alt)
+ previous_flavor_ref, new_flavor_ref = \
+ self._detect_server_image_flavor(self.server_id)
+
+ resp, server = self.client.resize(self.server_id, new_flavor_ref)
self.assertEqual(202, resp.status)
self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
@@ -136,7 +147,7 @@
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
resp, server = self.client.get_server(self.server_id)
- self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
+ self.assertEqual(new_flavor_ref, int(server['flavor']['id']))
@attr(type='positive')
@testtools.skipIf(not resize_available, 'Resize not available.')
@@ -144,7 +155,10 @@
# The server's RAM and disk space should return to its original
# values after a resize is reverted
- resp, server = self.client.resize(self.server_id, self.flavor_ref_alt)
+ previous_flavor_ref, new_flavor_ref = \
+ self._detect_server_image_flavor(self.server_id)
+
+ resp, server = self.client.resize(self.server_id, new_flavor_ref)
self.assertEqual(202, resp.status)
self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
@@ -155,7 +169,7 @@
resp, server = self.client.get_server(self.server_id)
start = int(time.time())
- while server['flavor']['id'] != self.flavor_ref:
+ while int(server['flavor']['id']) != previous_flavor_ref:
time.sleep(self.build_interval)
resp, server = self.client.get_server(self.server_id)
diff --git a/tempest/tests/compute/servers/test_server_addresses.py b/tempest/tests/compute/servers/test_server_addresses.py
index 6b0f7ae..8888281 100644
--- a/tempest/tests/compute/servers/test_server_addresses.py
+++ b/tempest/tests/compute/servers/test_server_addresses.py
@@ -15,10 +15,9 @@
# 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 import exceptions
+from tempest.test import attr
from tempest.tests.compute.base import BaseComputeTest
diff --git a/tempest/tests/compute/servers/test_server_metadata.py b/tempest/tests/compute/servers/test_server_metadata.py
index 7db963e..a214d15 100644
--- a/tempest/tests/compute/servers/test_server_metadata.py
+++ b/tempest/tests/compute/servers/test_server_metadata.py
@@ -15,10 +15,9 @@
# 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 import exceptions
+from tempest.test import attr
from tempest.tests.compute.base import BaseComputeTest
@@ -87,6 +86,16 @@
# no teardown - all creates should fail
+ @attr(type='negative')
+ def test_create_metadata_key_error(self):
+ # Blank key should trigger an error.
+ meta = {'': 'data1'}
+ name = rand_name('server')
+ self.assertRaises(exceptions.BadRequest,
+ self.create_server_with_extras,
+ name, self.image_ref,
+ self.flavor_ref, meta=meta)
+
def test_update_server_metadata(self):
# The server's metadata values should be updated to the
# provided values
@@ -177,6 +186,14 @@
self.fail('An update should not happen for a nonexistant image')
@attr(type='negative')
+ def test_update_metadata_key_error(self):
+ # Blank key should trigger an error.
+ meta = {'': 'data1'}
+ self.assertRaises(exceptions.BadRequest,
+ self.client.update_server_metadata,
+ self.server_id, meta=meta)
+
+ @attr(type='negative')
def test_delete_nonexistant_server_metadata_item(self):
# Negative test: Should not be able to delete metadata item from a
# nonexistant server
diff --git a/tempest/tests/compute/servers/test_server_personality.py b/tempest/tests/compute/servers/test_server_personality.py
index e98e559..862cfb2 100644
--- a/tempest/tests/compute/servers/test_server_personality.py
+++ b/tempest/tests/compute/servers/test_server_personality.py
@@ -17,10 +17,9 @@
import base64
-from nose.plugins.attrib import attr
-
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.compute import base
diff --git a/tempest/tests/compute/servers/test_servers.py b/tempest/tests/compute/servers/test_servers.py
index e0d4d44..fbcfe85 100644
--- a/tempest/tests/compute/servers/test_servers.py
+++ b/tempest/tests/compute/servers/test_servers.py
@@ -15,9 +15,8 @@
# 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.test import attr
from tempest.tests.compute import base
diff --git a/tempest/tests/compute/servers/test_servers_negative.py b/tempest/tests/compute/servers/test_servers_negative.py
index ea63360..55d9581 100644
--- a/tempest/tests/compute/servers/test_servers_negative.py
+++ b/tempest/tests/compute/servers/test_servers_negative.py
@@ -17,12 +17,12 @@
import sys
-from nose.plugins.attrib import attr
import testtools
from tempest import clients
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.compute.base import BaseComputeTest
diff --git a/tempest/tests/compute/servers/test_servers_whitebox.py b/tempest/tests/compute/servers/test_servers_whitebox.py
index 502f16b..94502c3 100644
--- a/tempest/tests/compute/servers/test_servers_whitebox.py
+++ b/tempest/tests/compute/servers/test_servers_whitebox.py
@@ -15,9 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
-
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.identity.base import BaseIdentityAdminTest
from tempest import whitebox
diff --git a/tempest/tests/compute/test_extensions.py b/tempest/tests/compute/test_extensions.py
index 829e295..36a04d2 100644
--- a/tempest/tests/compute/test_extensions.py
+++ b/tempest/tests/compute/test_extensions.py
@@ -15,8 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
+from tempest.test import attr
from tempest.tests.compute import base
diff --git a/tempest/tests/compute/test_live_block_migration.py b/tempest/tests/compute/test_live_block_migration.py
index 1b651ab..d22e6b5 100644
--- a/tempest/tests/compute/test_live_block_migration.py
+++ b/tempest/tests/compute/test_live_block_migration.py
@@ -18,7 +18,6 @@
import random
import string
-from nose.plugins.attrib import attr
import testtools
from tempest.common.utils.linux.remote_client import RemoteClient
@@ -26,6 +25,7 @@
from tempest import exceptions
from tempest.services.compute.json.hosts_client import HostsClientJSON
from tempest.services.compute.json.servers_client import ServersClientJSON
+from tempest.test import attr
from tempest.tests.compute import base
diff --git a/tempest/tests/compute/test_quotas.py b/tempest/tests/compute/test_quotas.py
index 9306351..c2ad358 100644
--- a/tempest/tests/compute/test_quotas.py
+++ b/tempest/tests/compute/test_quotas.py
@@ -15,8 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
-
+from tempest.test import attr
from tempest.tests.compute import base
@@ -43,7 +42,7 @@
try:
resp, quota_set = self.client.get_quota_set(self.tenant_id)
self.assertEqual(200, resp.status)
- self.assertSequenceEqual(expected_quota_set, quota_set)
+ self.assertEqual(expected_quota_set, quota_set)
except Exception:
self.fail("Quota set for tenant did not have default limits")
diff --git a/tempest/tests/compute/volumes/test_attach_volume.py b/tempest/tests/compute/volumes/test_attach_volume.py
index 0e0e4a5..edf3179 100644
--- a/tempest/tests/compute/volumes/test_attach_volume.py
+++ b/tempest/tests/compute/volumes/test_attach_volume.py
@@ -15,13 +15,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
import testtools
from tempest import clients
from tempest.common.utils.data_utils import rand_name
from tempest.common.utils.linux.remote_client import RemoteClient
import tempest.config
+from tempest.test import attr
from tempest.tests.compute import base
diff --git a/tempest/tests/compute/volumes/test_volumes_get.py b/tempest/tests/compute/volumes/test_volumes_get.py
index afb00cd..edbfc32 100644
--- a/tempest/tests/compute/volumes/test_volumes_get.py
+++ b/tempest/tests/compute/volumes/test_volumes_get.py
@@ -15,9 +15,8 @@
# 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.test import attr
from tempest.tests.compute import base
diff --git a/tempest/tests/identity/admin/test_services.py b/tempest/tests/identity/admin/test_services.py
index b74266c..0246f8c 100644
--- a/tempest/tests/identity/admin/test_services.py
+++ b/tempest/tests/identity/admin/test_services.py
@@ -16,10 +16,9 @@
# under the License.
-from nose.plugins.attrib import attr
-
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.identity import base
diff --git a/tempest/tests/identity/admin/test_tenants.py b/tempest/tests/identity/admin/test_tenants.py
index 54383f1..9321b07 100644
--- a/tempest/tests/identity/admin/test_tenants.py
+++ b/tempest/tests/identity/admin/test_tenants.py
@@ -17,9 +17,9 @@
import testtools
-from nose.plugins.attrib import attr
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.identity import base
diff --git a/tempest/tests/identity/admin/test_users.py b/tempest/tests/identity/admin/test_users.py
index ef7d934..8396b91 100644
--- a/tempest/tests/identity/admin/test_users.py
+++ b/tempest/tests/identity/admin/test_users.py
@@ -15,9 +15,9 @@
# 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 import exceptions
+from tempest.test import attr
from tempest.tests.identity import base
import testtools
from testtools.matchers._basic import Contains
diff --git a/tempest/tests/identity/base.py b/tempest/tests/identity/base.py
index cbd943e..fbff88f 100644
--- a/tempest/tests/identity/base.py
+++ b/tempest/tests/identity/base.py
@@ -21,7 +21,8 @@
from tempest.common.utils.data_utils import rand_name
-class BaseIdAdminTest(testtools.TestCase):
+class BaseIdAdminTest(testtools.testcase.WithAttributes,
+ testtools.TestCase):
@classmethod
def setUpClass(cls):
diff --git a/tempest/tests/image/test_images.py b/tempest/tests/image/test_images.py
index 304f8fe..511f8b0 100644
--- a/tempest/tests/image/test_images.py
+++ b/tempest/tests/image/test_images.py
@@ -20,14 +20,15 @@
import testtools
-from nose.plugins.attrib import attr
+from tempest.test import attr
from tempest import clients
from tempest import exceptions
-class CreateRegisterImagesTest(testtools.TestCase):
+class CreateRegisterImagesTest(testtools.testcase.WithAttributes,
+ testtools.TestCase):
"""
Here we test the registration and creation of images
@@ -107,7 +108,8 @@
self.assertEqual('active', body.get('status'))
-class ListImagesTest(testtools.TestCase):
+class ListImagesTest(testtools.testcase.WithAttributes,
+ testtools.TestCase):
"""
Here we test the listing of image information
diff --git a/tempest/tests/network/base.py b/tempest/tests/network/base.py
index 01330cc..5af7e2c 100644
--- a/tempest/tests/network/base.py
+++ b/tempest/tests/network/base.py
@@ -22,7 +22,8 @@
from tempest import exceptions
-class BaseNetworkTest(testtools.TestCase):
+class BaseNetworkTest(testtools.testcase.WithAttributes,
+ testtools.TestCase):
@classmethod
def setUpClass(cls):
diff --git a/tempest/tests/network/test_networks.py b/tempest/tests/network/test_networks.py
index d7f09c4..136279f 100644
--- a/tempest/tests/network/test_networks.py
+++ b/tempest/tests/network/test_networks.py
@@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nose.plugins.attrib import attr
+from tempest.test import attr
from tempest.common.utils.data_utils import rand_name
from tempest.tests.network import base
diff --git a/tempest/tests/object_storage/base.py b/tempest/tests/object_storage/base.py
index 8c32ffc..376deb7 100644
--- a/tempest/tests/object_storage/base.py
+++ b/tempest/tests/object_storage/base.py
@@ -23,7 +23,8 @@
from tempest.tests.identity.base import DataGenerator
-class BaseObjectTest(testtools.TestCase):
+class BaseObjectTest(testtools.testcase.WithAttributes,
+ testtools.TestCase):
@classmethod
def setUpClass(cls):
diff --git a/tempest/tests/object_storage/test_account_services.py b/tempest/tests/object_storage/test_account_services.py
index e34e349..14f94f7 100644
--- a/tempest/tests/object_storage/test_account_services.py
+++ b/tempest/tests/object_storage/test_account_services.py
@@ -15,9 +15,9 @@
# 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 import exceptions
+from tempest.test import attr
from tempest.tests.object_storage import base
diff --git a/tempest/tests/object_storage/test_container_services.py b/tempest/tests/object_storage/test_container_services.py
index fe09341..2c5b1ff 100644
--- a/tempest/tests/object_storage/test_container_services.py
+++ b/tempest/tests/object_storage/test_container_services.py
@@ -15,10 +15,9 @@
# 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 arbitrary_string
from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
from tempest.tests.object_storage import base
diff --git a/tempest/tests/object_storage/test_container_sync.py b/tempest/tests/object_storage/test_container_sync.py
index f156f45..f087aff 100644
--- a/tempest/tests/object_storage/test_container_sync.py
+++ b/tempest/tests/object_storage/test_container_sync.py
@@ -15,9 +15,9 @@
# 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 arbitrary_string
from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
from tempest.tests.object_storage import base
import testtools
import time
diff --git a/tempest/tests/object_storage/test_object_expiry.py b/tempest/tests/object_storage/test_object_expiry.py
index 8e6b23b..1738ddf 100644
--- a/tempest/tests/object_storage/test_object_expiry.py
+++ b/tempest/tests/object_storage/test_object_expiry.py
@@ -15,10 +15,10 @@
# 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 arbitrary_string
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.object_storage import base
import testtools
from time import sleep
diff --git a/tempest/tests/object_storage/test_object_services.py b/tempest/tests/object_storage/test_object_services.py
index d5b6d5c..e0a2fbb 100644
--- a/tempest/tests/object_storage/test_object_services.py
+++ b/tempest/tests/object_storage/test_object_services.py
@@ -15,11 +15,10 @@
# 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 arbitrary_string
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.object_storage import base
import testtools
from time import time
diff --git a/tempest/tests/object_storage/test_object_version.py b/tempest/tests/object_storage/test_object_version.py
index 28e0893..bc1c045 100644
--- a/tempest/tests/object_storage/test_object_version.py
+++ b/tempest/tests/object_storage/test_object_version.py
@@ -15,8 +15,8 @@
# 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.test import attr
from tempest.tests.object_storage import base
diff --git a/tempest/tests/volume/base.py b/tempest/tests/volume/base.py
index efa74b5..6625180 100644
--- a/tempest/tests/volume/base.py
+++ b/tempest/tests/volume/base.py
@@ -28,7 +28,8 @@
LOG = logging.getLogger(__name__)
-class BaseVolumeTest(testtools.TestCase):
+class BaseVolumeTest(testtools.testcase.WithAttributes,
+ testtools.TestCase):
"""Base test case class for all Cinder API tests."""
@@ -49,12 +50,14 @@
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__)
@@ -119,19 +122,61 @@
@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())
diff --git a/tempest/tests/volume/test_volumes_actions.py b/tempest/tests/volume/test_volumes_actions.py
index dd93b89..fb9b975 100644
--- a/tempest/tests/volume/test_volumes_actions.py
+++ b/tempest/tests/volume/test_volumes_actions.py
@@ -15,9 +15,8 @@
# 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.test import attr
from tempest.tests.volume.base import BaseVolumeTest
diff --git a/tempest/tests/volume/test_volumes_get.py b/tempest/tests/volume/test_volumes_get.py
index bc64ff4..bd271ed 100644
--- a/tempest/tests/volume/test_volumes_get.py
+++ b/tempest/tests/volume/test_volumes_get.py
@@ -15,9 +15,8 @@
# 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.test import attr
from tempest.tests.volume import base
diff --git a/tempest/tests/volume/test_volumes_list.py b/tempest/tests/volume/test_volumes_list.py
index 92d3d3f..64691d4 100644
--- a/tempest/tests/volume/test_volumes_list.py
+++ b/tempest/tests/volume/test_volumes_list.py
@@ -15,10 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-import nose
-from nose.plugins.attrib import attr
-
from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
from tempest.tests.volume import base
diff --git a/tempest/tests/volume/test_volumes_snapshots.py b/tempest/tests/volume/test_volumes_snapshots.py
new file mode 100644
index 0000000..75ea155
--- /dev/null
+++ b/tempest/tests/volume/test_volumes_snapshots.py
@@ -0,0 +1,53 @@
+# 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 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
diff --git a/tools/pip-requires b/tools/pip-requires
index 0147cd8..5c45a49 100644
--- a/tools/pip-requires
+++ b/tools/pip-requires
@@ -12,3 +12,4 @@
python-quantumclient>=2.1
testresources
keyring
+testrepository