Merge "Remove meaningless volume negative test"
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 18a6afc..4e9bb88 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -82,14 +82,6 @@
             if host != target_host:
                 return target_host
 
-    def _volume_clean_up(self, server_id, volume_id):
-        body = self.volumes_client.show_volume(volume_id)['volume']
-        if body['status'] == 'in-use':
-            self.servers_client.detach_volume(server_id, volume_id)
-            waiters.wait_for_volume_status(self.volumes_client,
-                                           volume_id, 'available')
-        self.volumes_client.delete_volume(volume_id)
-
     def _test_live_migration(self, state='ACTIVE', volume_backed=False):
         """Tests live migration between two hosts.
 
@@ -151,22 +143,15 @@
                       block_migrate_cinder_iscsi,
                       'Block Live migration not configured for iSCSI')
     def test_iscsi_volume(self):
-        server_id = self.create_test_server(wait_until="ACTIVE")['id']
+        server = self.create_test_server(wait_until="ACTIVE")
+        server_id = server['id']
         actual_host = self._get_host_for_server(server_id)
         target_host = self._get_host_other_than(actual_host)
 
-        volume = self.volumes_client.create_volume(
-            size=CONF.volume.volume_size, display_name='test')['volume']
-
-        waiters.wait_for_volume_status(self.volumes_client,
-                                       volume['id'], 'available')
-        self.addCleanup(self._volume_clean_up, server_id, volume['id'])
+        volume = self.create_volume()
 
         # Attach the volume to the server
-        self.servers_client.attach_volume(server_id, volumeId=volume['id'],
-                                          device='/dev/xvdb')
-        waiters.wait_for_volume_status(self.volumes_client,
-                                       volume['id'], 'in-use')
+        self.attach_volume(server, volume, device='/dev/xvdb')
 
         self._migrate_server_to(server_id, target_host)
         waiters.wait_for_server_status(self.servers_client,
diff --git a/tempest/api/compute/admin/test_migrations.py b/tempest/api/compute/admin/test_migrations.py
index 62dbfe4..4f075eb 100644
--- a/tempest/api/compute/admin/test_migrations.py
+++ b/tempest/api/compute/admin/test_migrations.py
@@ -106,10 +106,7 @@
         server = self.servers_client.show_server(server['id'])['server']
         self.assertEqual(flavor['id'], server['flavor']['id'])
 
-    @test.idempotent_id('4bf0be52-3b6f-4746-9a27-3143636fe30d')
-    @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
-                          'Cold migration not available.')
-    def test_cold_migration(self):
+    def _test_cold_migrate_server(self, revert=False):
         if CONF.compute.min_compute_nodes < 2:
             msg = "Less than 2 compute nodes, skipping multinode tests."
             raise self.skipException(msg)
@@ -123,10 +120,27 @@
         waiters.wait_for_server_status(self.servers_client,
                                        server['id'], 'VERIFY_RESIZE')
 
-        self.servers_client.confirm_resize_server(server['id'])
+        if revert:
+            self.servers_client.revert_resize_server(server['id'])
+            assert_func = self.assertEqual
+        else:
+            self.servers_client.confirm_resize_server(server['id'])
+            assert_func = self.assertNotEqual
+
         waiters.wait_for_server_status(self.servers_client,
                                        server['id'], 'ACTIVE')
         dst_host = self.admin_servers_client.show_server(
             server['id'])['server']['OS-EXT-SRV-ATTR:host']
+        assert_func(src_host, dst_host)
 
-        self.assertNotEqual(src_host, dst_host)
+    @test.idempotent_id('4bf0be52-3b6f-4746-9a27-3143636fe30d')
+    @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
+                          'Cold migration not available.')
+    def test_cold_migration(self):
+        self._test_cold_migrate_server(revert=False)
+
+    @test.idempotent_id('caa1aa8b-f4ef-4374-be0d-95f001c2ac2d')
+    @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
+                          'Cold migration not available.')
+    def test_revert_cold_migration(self):
+        self._test_cold_migrate_server(revert=True)
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index a4578ae..b738e82 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -404,10 +404,21 @@
                 LOG.exception('Waiting for deletion of volume %s failed',
                               volume['id'])
 
-    def attach_volume(self, server, volume):
-        """Attaches volume to server and waits for 'in-use' volume status."""
+    def attach_volume(self, server, volume, device=None):
+        """Attaches volume to server and waits for 'in-use' volume status.
+
+        The volume will be detached when the test tears down.
+
+        :param server: The server to which the volume will be attached.
+        :param volume: The volume to attach.
+        :param device: Optional mountpoint for the attached volume. Note that
+            this is not guaranteed for all hypervisors and is not recommended.
+        """
+        attach_kwargs = dict(volumeId=volume['id'])
+        if device:
+            attach_kwargs['device'] = device
         self.servers_client.attach_volume(
-            server['id'], volumeId=volume['id'])
+            server['id'], **attach_kwargs)
         # On teardown detach the volume and wait for it to be available. This
         # is so we don't error out when trying to delete the volume during
         # teardown.
diff --git a/tempest/api/compute/servers/test_delete_server.py b/tempest/api/compute/servers/test_delete_server.py
index 079465d..07f46c5 100644
--- a/tempest/api/compute/servers/test_delete_server.py
+++ b/tempest/api/compute/servers/test_delete_server.py
@@ -106,24 +106,15 @@
     @test.services('volume')
     def test_delete_server_while_in_attached_volume(self):
         # Delete a server while a volume is attached to it
-        volumes_client = self.volumes_extensions_client
         device = '/dev/%s' % CONF.compute.volume_device_name
         server = self.create_test_server(wait_until='ACTIVE')
 
-        volume = (volumes_client.create_volume(size=CONF.volume.volume_size)
-                  ['volume'])
-        self.addCleanup(volumes_client.delete_volume, volume['id'])
-        waiters.wait_for_volume_status(volumes_client,
-                                       volume['id'], 'available')
-        self.client.attach_volume(server['id'],
-                                  volumeId=volume['id'],
-                                  device=device)
-        waiters.wait_for_volume_status(volumes_client,
-                                       volume['id'], 'in-use')
+        volume = self.create_volume()
+        self.attach_volume(server, volume, device=device)
 
         self.client.delete_server(server['id'])
         waiters.wait_for_server_termination(self.client, server['id'])
-        waiters.wait_for_volume_status(volumes_client,
+        waiters.wait_for_volume_status(self.volumes_client,
                                        volume['id'], 'available')
 
 
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 062e920..28ee739 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -235,20 +235,10 @@
     @test.services('volume')
     def test_rebuild_server_with_volume_attached(self):
         # create a new volume and attach it to the server
-        volume = self.volumes_client.create_volume(
-            size=CONF.volume.volume_size)
-        volume = volume['volume']
-        self.addCleanup(self.volumes_client.delete_volume, volume['id'])
-        waiters.wait_for_volume_status(self.volumes_client, volume['id'],
-                                       'available')
+        volume = self.create_volume()
 
-        self.client.attach_volume(self.server_id, volumeId=volume['id'])
-        self.addCleanup(waiters.wait_for_volume_status, self.volumes_client,
-                        volume['id'], 'available')
-        self.addCleanup(self.client.detach_volume,
-                        self.server_id, volume['id'])
-        waiters.wait_for_volume_status(self.volumes_client, volume['id'],
-                                       'in-use')
+        server = self.client.show_server(self.server_id)['server']
+        self.attach_volume(server, volume)
 
         # run general rebuild test
         self.test_rebuild_server()
diff --git a/tempest/api/compute/servers/test_server_rescue_negative.py b/tempest/api/compute/servers/test_server_rescue_negative.py
index 8d63b6b..41b648c 100644
--- a/tempest/api/compute/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/servers/test_server_rescue_negative.py
@@ -60,20 +60,6 @@
         waiters.wait_for_server_status(cls.servers_client,
                                        cls.server_id, 'ACTIVE')
 
-    def _create_volume(self):
-        volume = self.volumes_extensions_client.create_volume(
-            size=CONF.volume.volume_size, display_name=data_utils.rand_name(
-                self.__class__.__name__ + '_volume'))['volume']
-        self.addCleanup(self.delete_volume, volume['id'])
-        waiters.wait_for_volume_status(self.volumes_extensions_client,
-                                       volume['id'], 'available')
-        return volume
-
-    def _detach(self, server_id, volume_id):
-        self.servers_client.detach_volume(server_id, volume_id)
-        waiters.wait_for_volume_status(self.volumes_extensions_client,
-                                       volume_id, 'available')
-
     def _unrescue(self, server_id):
         self.servers_client.unrescue_server(server_id)
         waiters.wait_for_server_status(self.servers_client,
@@ -125,7 +111,7 @@
     @test.services('volume')
     @test.attr(type=['negative'])
     def test_rescued_vm_attach_volume(self):
-        volume = self._create_volume()
+        volume = self.create_volume()
 
         # Rescue the server
         self.servers_client.rescue_server(self.server_id,
@@ -145,14 +131,11 @@
     @test.services('volume')
     @test.attr(type=['negative'])
     def test_rescued_vm_detach_volume(self):
-        volume = self._create_volume()
+        volume = self.create_volume()
 
         # Attach the volume to the server
-        self.servers_client.attach_volume(self.server_id,
-                                          volumeId=volume['id'],
-                                          device='/dev/%s' % self.device)
-        waiters.wait_for_volume_status(self.volumes_extensions_client,
-                                       volume['id'], 'in-use')
+        server = self.servers_client.show_server(self.server_id)['server']
+        self.attach_volume(server, volume, device='/dev/%s' % self.device)
 
         # Rescue the server
         self.servers_client.rescue_server(self.server_id,
@@ -160,7 +143,6 @@
         waiters.wait_for_server_status(self.servers_client,
                                        self.server_id, 'RESCUE')
         # addCleanup is a LIFO queue
-        self.addCleanup(self._detach, self.server_id, volume['id'])
         self.addCleanup(self._unrescue, self.server_id)
 
         # Detach the volume from the server expecting failure
diff --git a/tempest/api/object_storage/test_container_services_negative.py b/tempest/api/object_storage/test_container_services_negative.py
new file mode 100644
index 0000000..ed99eb2
--- /dev/null
+++ b/tempest/api/object_storage/test_container_services_negative.py
@@ -0,0 +1,167 @@
+# Copyright 2016 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.object_storage import base
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions
+from tempest import test
+
+
+class ContainerNegativeTest(base.BaseObjectTest):
+
+    @classmethod
+    def resource_setup(cls):
+        super(ContainerNegativeTest, cls).resource_setup()
+
+        # use /info to get default constraints
+        _, body = cls.account_client.list_extensions()
+        cls.constraints = body['swift']
+
+    @test.attr(type=["negative"])
+    @test.idempotent_id('30686921-4bed-4764-a038-40d741ed4e78')
+    def test_create_container_name_exceeds_max_length(self):
+        # Attempts to create a container name that is longer than max
+        max_length = self.constraints['max_container_name_length']
+        # create a container with long name
+        container_name = data_utils.arbitrary_string(size=max_length + 1)
+        ex = self.assertRaises(exceptions.BadRequest,
+                               self.container_client.create_container,
+                               container_name)
+        self.assertIn('Container name length of ' + str(max_length + 1) +
+                      ' longer than ' + str(max_length), str(ex))
+
+    @test.attr(type=["negative"])
+    @test.idempotent_id('41e645bf-2e68-4f84-bf7b-c71aa5cd76ce')
+    def test_create_container_metadata_name_exceeds_max_length(self):
+        # Attempts to create container with metadata name
+        # that is longer than max.
+        max_length = self.constraints['max_meta_name_length']
+        container_name = data_utils.rand_name(name='TestContainer')
+        metadata_name = data_utils.arbitrary_string(size=max_length + 1)
+        metadata = {metadata_name: 'penguin'}
+        ex = self.assertRaises(exceptions.BadRequest,
+                               self.container_client.create_container,
+                               container_name, metadata=metadata)
+        self.assertIn('Metadata name too long', str(ex))
+
+    @test.attr(type=["negative"])
+    @test.idempotent_id('81e36922-326b-4b7c-8155-3bbceecd7a82')
+    def test_create_container_metadata_value_exceeds_max_length(self):
+        # Attempts to create container with metadata value
+        # that is longer than max.
+        max_length = self.constraints['max_meta_value_length']
+        container_name = data_utils.rand_name(name='TestContainer')
+        metadata_value = data_utils.arbitrary_string(size=max_length + 1)
+        metadata = {'animal': metadata_value}
+        ex = self.assertRaises(exceptions.BadRequest,
+                               self.container_client.create_container,
+                               container_name, metadata=metadata)
+        self.assertIn('Metadata value longer than ' + str(max_length), str(ex))
+
+    @test.attr(type=["negative"])
+    @test.idempotent_id('ac666539-d566-4f02-8ceb-58e968dfb732')
+    def test_create_container_metadata_exceeds_overall_metadata_count(self):
+        # Attempts to create container with metadata that exceeds the
+        # default count
+        max_count = self.constraints['max_meta_count']
+        container_name = data_utils.rand_name(name='TestContainer')
+        metadata = {}
+        for i in range(max_count + 1):
+            metadata['animal-' + str(i)] = 'penguin'
+
+        ex = self.assertRaises(exceptions.BadRequest,
+                               self.container_client.create_container,
+                               container_name, metadata=metadata)
+        self.assertIn('Too many metadata items; max ' + str(max_count),
+                      str(ex))
+
+    @test.attr(type=["negative"])
+    @test.idempotent_id('1a95ab2e-b712-4a98-8a4d-8ce21b7557d6')
+    def test_get_metadata_headers_with_invalid_container_name(self):
+        # Attempts to retrieve metadata headers with an invalid
+        # container name.
+        invalid_name = data_utils.rand_name(name="TestInvalidContainer")
+
+        self.assertRaises(exceptions.NotFound,
+                          self.container_client.list_container_metadata,
+                          invalid_name)
+
+    @test.attr(type=["negative"])
+    @test.idempotent_id('125a24fa-90a7-4cfc-b604-44e49d788390')
+    def test_update_metadata_with_nonexistent_container_name(self):
+        # Attempts to update metadata using a nonexistent container name.
+        nonexistent_name = data_utils.rand_name(
+            name="TestNonexistentContainer")
+        metadata = {'animal': 'penguin'}
+
+        self.assertRaises(exceptions.NotFound,
+                          self.container_client.update_container_metadata,
+                          nonexistent_name, metadata)
+
+    @test.attr(type=["negative"])
+    @test.idempotent_id('65387dbf-a0e2-4aac-9ddc-16eb3f1f69ba')
+    def test_delete_with_nonexistent_container_name(self):
+        # Attempts to delete metadata using a nonexistent container name.
+        nonexistent_name = data_utils.rand_name(
+            name="TestNonexistentContainer")
+        metadata = {'animal': 'penguin'}
+
+        self.assertRaises(exceptions.NotFound,
+                          self.container_client.delete_container_metadata,
+                          nonexistent_name, metadata)
+
+    @test.attr(type=["negative"])
+    @test.idempotent_id('14331d21-1e81-420a-beea-19cb5e5207f5')
+    def test_list_all_container_objects_with_nonexistent_container(self):
+        # Attempts to get a listing of all objects on a container
+        # that doesn't exist.
+        nonexistent_name = data_utils.rand_name(
+            name="TestNonexistentContainer")
+
+        self.assertRaises(exceptions.NotFound,
+                          self.container_client.list_all_container_objects,
+                          nonexistent_name)
+
+    @test.attr(type=["negative"])
+    @test.idempotent_id('86b2ab08-92d5-493d-acd2-85f0c848819e')
+    def test_list_all_container_objects_on_deleted_container(self):
+        # Attempts to get a listing of all objects on a container
+        # that was deleted.
+        container_name = self.create_container()
+        # delete container
+        resp, _ = self.container_client.delete_container(container_name)
+        self.assertHeaders(resp, 'Container', 'DELETE')
+
+        self.assertRaises(exceptions.NotFound,
+                          self.container_client.list_all_container_objects,
+                          container_name)
+
+    @test.attr(type=["negative"])
+    @test.idempotent_id('42da116e-1e8c-4c96-9e06-2f13884ed2b1')
+    def test_delete_non_empty_container(self):
+        # create a container and an object within it
+        # attempt to delete a container that isn't empty.
+        container_name = self.create_container()
+        self.addCleanup(self.container_client.delete_container,
+                        container_name)
+        object_name, _ = self.create_object(container_name)
+        self.addCleanup(self.object_client.delete_object,
+                        container_name, object_name)
+
+        ex = self.assertRaises(exceptions.Conflict,
+                               self.container_client.delete_container,
+                               container_name)
+        self.assertIn('An object with that identifier already exists',
+                      str(ex))
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index fe105e8..b47a5f0 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -18,7 +18,7 @@
 from tempest.common import waiters
 from tempest import test
 
-QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes']
+QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes', 'backups']
 QUOTA_USAGE_KEYS = ['reserved', 'limit', 'in_use']
 
 
@@ -54,7 +54,8 @@
             self.demo_tenant_id)['quota_set']
         new_quota_set = {'gigabytes': 1009,
                          'volumes': 11,
-                         'snapshots': 11}
+                         'snapshots': 11,
+                         'backups': 11}
 
         # Update limits for all quota resources
         quota_set = self.admin_quotas_client.update_quota_set(
diff --git a/tempest/lib/services/volume/v2/qos_client.py b/tempest/lib/services/volume/v2/qos_client.py
index 5fac00f..40d4a3f 100644
--- a/tempest/lib/services/volume/v2/qos_client.py
+++ b/tempest/lib/services/volume/v2/qos_client.py
@@ -41,8 +41,11 @@
     def create_qos(self, **kwargs):
         """Create a QoS Specification.
 
-        Available params: see http://developer.openstack.org/
-                              api-ref-blockstorage-v2.html#createQoSSpec
+        For a full list of available parameters, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/block-storage/v2/index.html
+                                ?expanded=create-qos-specification-detail
+                                #quality-of-service-qos-specifications-qos-specs
         """
         post_body = json.dumps({'qos_specs': kwargs})
         resp, body = self.post('qos-specs', post_body)
@@ -76,8 +79,11 @@
     def set_qos_key(self, qos_id, **kwargs):
         """Set the specified keys/values of QoS specification.
 
-        Available params: see http://developer.openstack.org/
-                              api-ref-blockstorage-v2.html#setQoSKey
+        For a full list of available parameters, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/block-storage/v2/index.html
+                            ?expanded=set-keys-in-qos-specification-detail
+                            #quality-of-service-qos-specifications-qos-specs
         """
         put_body = json.dumps({"qos_specs": kwargs})
         resp, body = self.put('qos-specs/%s' % qos_id, put_body)
@@ -90,7 +96,11 @@
 
         :param keys: keys to delete from the QoS specification.
 
-        TODO(jordanP): Add a link once LP #1524877 is fixed.
+        For a full list of available parameters, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/block-storage/v2/index.html
+                            ?expanded=unset-keys-in-qos-specification-detail
+                            #quality-of-service-qos-specifications-qos-specs
         """
         put_body = json.dumps({'keys': keys})
         resp, body = self.put('qos-specs/%s/delete_keys' % qos_id, put_body)