Merge "javelin: Detach the volume before destruction"
diff --git a/HACKING.rst b/HACKING.rst
index 025bf74..29d5bf4 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -227,3 +227,48 @@
2. The unit tests cannot use setUpClass, instead fixtures and testresources
should be used for shared state between tests.
+
+
+.. _TestDocumentation:
+
+Test Documentation
+------------------
+For tests being added we need to require inline documentation in the form of
+docstings to explain what is being tested. In API tests for a new API a class
+level docstring should be added to an API reference doc. If one doesn't exist
+a TODO comment should be put indicating that the reference needs to be added.
+For individual API test cases a method level docstring should be used to
+explain the functionality being tested if the test name isn't descriptive
+enough. For example::
+
+ def test_get_role_by_id(self):
+ """Get a role by its id."""
+
+the docstring there is superfluous and shouldn't be added. but for a method
+like::
+
+ def test_volume_backup_create_get_detailed_list_restore_delete(self):
+ pass
+
+a docstring would be useful because while the test title is fairly descriptive
+the operations being performed are complex enough that a bit more explanation
+will help people figure out the intent of the test.
+
+For scenario tests a class level docstring describing the steps in the scenario
+is required. If there is more than one test case in the class individual
+docstrings for the workflow in each test methods can be used instead. A good
+example of this would be::
+
+ class TestVolumeBootPattern(manager.OfficialClientTest):
+ """
+ This test case attempts to reproduce the following steps:
+
+ * Create in Cinder some bootable volume importing a Glance image
+ * Boot an instance from the bootable volume
+ * Write content to the volume
+ * Delete an instance and Boot a new instance from the volume
+ * Check written content in the instance
+ * Create a volume snapshot while the instance is running
+ * Boot an additional instance from the new snapshot based volume
+ * Check written content in the instance booted from snapshot
+ """
diff --git a/REVIEWING.rst b/REVIEWING.rst
index d6dc83e..74bd2ad 100644
--- a/REVIEWING.rst
+++ b/REVIEWING.rst
@@ -51,6 +51,15 @@
whether to skip or not.
+Test Documentation
+------------------
+When a new test is being added refer to the :ref:`TestDocumentation` section in
+hacking to see if the requirements are being met. With the exception of a class
+level docstring linking to the API ref doc in the API tests and a docstring for
+scenario tests this is up to the reviewers discretion whether a docstring is
+required or not.
+
+
When to approve
---------------
* Every patch needs two +2s before being approved.
diff --git a/requirements.txt b/requirements.txt
index 9a3b74d..708ede3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,25 +1,28 @@
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
pbr>=0.6,!=0.7,<1.0
anyjson>=0.3.3
httplib2>=0.7.5
jsonschema>=2.0.0,<3.0.0
testtools>=0.9.34
lxml>=2.3
-boto>=2.12.0,!=2.13.0
+boto>=2.32.1
paramiko>=1.13.0
-netaddr>=0.7.6
+netaddr>=0.7.12
python-ceilometerclient>=1.0.6
-python-glanceclient>=0.13.1
-python-keystoneclient>=0.9.0
-python-novaclient>=2.17.0
-python-neutronclient>=2.3.5,<3
-python-cinderclient>=1.0.7
+python-glanceclient>=0.14.0
+python-keystoneclient>=0.10.0
+python-novaclient>=2.18.0
+python-neutronclient>=2.3.6,<3
+python-cinderclient>=1.1.0
python-heatclient>=0.2.9
-python-ironicclient
-python-saharaclient>=0.6.0
-python-swiftclient>=2.0.2
+python-ironicclient>=0.2.1
+python-saharaclient>=0.7.3
+python-swiftclient>=2.2.0
testresources>=0.2.4
testrepository>=0.0.18
-oslo.config>=1.2.1
+oslo.config>=1.4.0 # Apache-2.0
six>=1.7.0
iso8601>=0.1.9
fixtures>=0.3.14
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 6068d35..9933646 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -1248,38 +1248,38 @@
cls.container_client = cls.manager.container_client
cls.object_client = cls.manager.object_client
- def _get_swift_stat(self):
+ def get_swift_stat(self):
"""get swift status for our user account."""
self.account_client.list_account_containers()
LOG.debug('Swift status information obtained successfully')
- def _create_container(self, container_name=None):
+ def create_container(self, container_name=None):
name = container_name or data_utils.rand_name(
'swift-scenario-container')
self.container_client.create_container(name)
# look for the container to assure it is created
- self._list_and_check_container_objects(name)
+ self.list_and_check_container_objects(name)
LOG.debug('Container %s created' % (name))
return name
- def _delete_container(self, container_name):
+ def delete_container(self, container_name):
self.container_client.delete_container(container_name)
LOG.debug('Container %s deleted' % (container_name))
- def _upload_object_to_container(self, container_name, obj_name=None):
+ def upload_object_to_container(self, container_name, obj_name=None):
obj_name = obj_name or data_utils.rand_name('swift-scenario-object')
obj_data = data_utils.arbitrary_string()
self.object_client.create_object(container_name, obj_name, obj_data)
return obj_name, obj_data
- def _delete_object(self, container_name, filename):
+ def delete_object(self, container_name, filename):
self.object_client.delete_object(container_name, filename)
- self._list_and_check_container_objects(container_name,
- not_present_obj=[filename])
+ self.list_and_check_container_objects(container_name,
+ not_present_obj=[filename])
- def _list_and_check_container_objects(self, container_name,
- present_obj=None,
- not_present_obj=None):
+ def list_and_check_container_objects(self, container_name,
+ present_obj=None,
+ not_present_obj=None):
"""
List objects for a given container and assert which are present and
which are not.
@@ -1297,7 +1297,7 @@
for obj in not_present_obj:
self.assertNotIn(obj, object_list)
- def _change_container_acl(self, container_name, acl):
+ def change_container_acl(self, container_name, acl):
metadata_param = {'metadata_prefix': 'x-container-',
'metadata': {'read': acl}}
self.container_client.update_container_metadata(container_name,
@@ -1305,6 +1305,6 @@
resp, _ = self.container_client.list_container_metadata(container_name)
self.assertEqual(resp['x-container-read'], acl)
- def _download_and_verify(self, container_name, obj_name, expected_data):
+ def download_and_verify(self, container_name, obj_name, expected_data):
_, obj = self.object_client.get_object(container_name, obj_name)
self.assertEqual(obj, expected_data)
diff --git a/tempest/scenario/test_swift_basic_ops.py b/tempest/scenario/test_swift_basic_ops.py
index ad74ec4..9e0fee0 100644
--- a/tempest/scenario/test_swift_basic_ops.py
+++ b/tempest/scenario/test_swift_basic_ops.py
@@ -41,13 +41,13 @@
@test.services('object_storage')
def test_swift_basic_ops(self):
- self._get_swift_stat()
- container_name = self._create_container()
- obj_name, obj_data = self._upload_object_to_container(container_name)
- self._list_and_check_container_objects(container_name, [obj_name])
- self._download_and_verify(container_name, obj_name, obj_data)
- self._delete_object(container_name, obj_name)
- self._delete_container(container_name)
+ self.get_swift_stat()
+ container_name = self.create_container()
+ obj_name, obj_data = self.upload_object_to_container(container_name)
+ self.list_and_check_container_objects(container_name, [obj_name])
+ self.download_and_verify(container_name, obj_name, obj_data)
+ self.delete_object(container_name, obj_name)
+ self.delete_container(container_name)
@test.services('object_storage')
def test_swift_acl_anonymous_download(self):
@@ -58,15 +58,15 @@
4. Check if the object can be download by anonymous user
5. Delete the object and container
"""
- container_name = self._create_container()
- obj_name, _ = self._upload_object_to_container(container_name)
+ container_name = self.create_container()
+ obj_name, _ = self.upload_object_to_container(container_name)
obj_url = '%s/%s/%s' % (self.object_client.base_url,
container_name, obj_name)
http_client = http.ClosingHttp()
resp, _ = http_client.request(obj_url, 'GET')
self.assertEqual(resp.status, 401)
- self._change_container_acl(container_name, '.r:*')
+ self.change_container_acl(container_name, '.r:*')
resp, _ = http_client.request(obj_url, 'GET')
self.assertEqual(resp.status, 200)
- self._delete_object(container_name, obj_name)
- self._delete_container(container_name)
+ self.delete_object(container_name, obj_name)
+ self.delete_container(container_name)
diff --git a/tempest/scenario/utils.py b/tempest/scenario/utils.py
index e2adb34..c20f20c 100644
--- a/tempest/scenario/utils.py
+++ b/tempest/scenario/utils.py
@@ -40,33 +40,33 @@
self.non_ssh_image_pattern = \
CONF.input_scenario.non_ssh_image_regex
# Setup clients
- ocm = clients.OfficialClientManager(
- auth.get_default_credentials('user'))
- self.client = ocm.compute_client
+ os = clients.Manager()
+ self.images_client = os.images_client
+ self.flavors_client = os.flavors_client
def ssh_user(self, image_id):
- _image = self.client.images.get(image_id)
+ _, _image = self.images_client.get_image(image_id)
for regex, user in self.ssh_users:
# First match wins
- if re.match(regex, _image.name) is not None:
+ if re.match(regex, _image['name']) is not None:
return user
else:
return self.default_ssh_user
def _is_sshable_image(self, image):
return not re.search(pattern=self.non_ssh_image_pattern,
- string=str(image.name))
+ string=str(image['name']))
def is_sshable_image(self, image_id):
- _image = self.client.images.get(image_id)
+ _, _image = self.images_client.get_image(image_id)
return self._is_sshable_image(_image)
def _is_flavor_enough(self, flavor, image):
- return image.minDisk <= flavor.disk
+ return image['minDisk'] <= flavor['disk']
def is_flavor_enough(self, flavor_id, image_id):
- _image = self.client.images.get(image_id)
- _flavor = self.client.flavors.get(flavor_id)
+ _, _image = self.images_client.get_image(image_id)
+ _, _flavor = self.flavors_client.get_flavor_details(flavor_id)
return self._is_flavor_enough(_flavor, _image)
@@ -81,7 +81,7 @@
load_tests = testscenarios.load_tests_apply_scenarios
- class TestInputScenario(manager.OfficialClientTest):
+ class TestInputScenario(manager.ScenarioTest):
scenario_utils = utils.InputScenarioUtils()
scenario_flavor = scenario_utils.scenario_flavors
@@ -91,17 +91,18 @@
def test_create_server_metadata(self):
name = rand_name('instance')
- _ = self.compute_client.servers.create(name=name,
- flavor=self.flavor_ref,
- image=self.image_ref)
+ self.servers_client.create_server(name=name,
+ flavor_ref=self.flavor_ref,
+ image_ref=self.image_ref)
"""
validchars = "-_.{ascii}{digit}".format(ascii=string.ascii_letters,
digit=string.digits)
def __init__(self):
- ocm = clients.OfficialClientManager(
+ os = clients.Manager(
auth.get_default_credentials('user', fill_in=False))
- self.client = ocm.compute_client
+ self.images_client = os.images_client
+ self.flavors_client = os.flavors_client
self.image_pattern = CONF.input_scenario.image_regex
self.flavor_pattern = CONF.input_scenario.flavor_regex
@@ -118,10 +119,11 @@
if not CONF.service_available.glance:
return []
if not hasattr(self, '_scenario_images'):
- images = self.client.images.list(detailed=False)
+ _, images = self.images_client.list_images()
self._scenario_images = [
- (self._normalize_name(i.name), dict(image_ref=i.id))
- for i in images if re.search(self.image_pattern, str(i.name))
+ (self._normalize_name(i['name']), dict(image_ref=i['id']))
+ for i in images if re.search(self.image_pattern,
+ str(i['name']))
]
return self._scenario_images
@@ -131,10 +133,11 @@
:return: a scenario with name and uuid of flavors
"""
if not hasattr(self, '_scenario_flavors'):
- flavors = self.client.flavors.list(detailed=False)
+ _, flavors = self.flavors_client.list_flavors()
self._scenario_flavors = [
- (self._normalize_name(f.name), dict(flavor_ref=f.id))
- for f in flavors if re.search(self.flavor_pattern, str(f.name))
+ (self._normalize_name(f['name']), dict(flavor_ref=f['id']))
+ for f in flavors if re.search(self.flavor_pattern,
+ str(f['name']))
]
return self._scenario_flavors
diff --git a/test-requirements.txt b/test-requirements.txt
index cd8154b..ba70259 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,10 +1,13 @@
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
hacking>=0.9.2,<0.10
# needed for doc build
sphinx>=1.1.2,!=1.2.0,<1.3
python-subunit>=0.0.18
-oslosphinx
+oslosphinx>=2.2.0 # Apache-2.0
mox>=0.5.3
mock>=1.0
coverage>=3.6
-oslotest
-stevedore>=0.14
+oslotest>=1.1.0 # Apache-2.0
+stevedore>=1.0.0 # Apache-2.0