Merge "Remove one-line function in test_volumes_actions"
diff --git a/REVIEWING.rst b/REVIEWING.rst
index 676a217..cfe7f4c 100644
--- a/REVIEWING.rst
+++ b/REVIEWING.rst
@@ -13,6 +13,13 @@
 it. Tests which aren't executed either because of configuration or skips should
 not be accepted.
 
+If a new test is added that depends on a new config option (like a feature
+flag), the commit message must reference a change in DevStack or DevStack-Gate
+that enables the execution of this newly introduced test. This reference could
+either be a `Cross-Repository Dependency <http://docs.openstack.org/infra/
+manual/developers.html#cross-repository-dependencies>`_ or a simple link
+to a Gerrit review.
+
 
 Unit Tests
 ----------
diff --git a/releasenotes/notes/13.1.0-volume-clients-as-library-309030c7a16e62ab.yaml b/releasenotes/notes/13.1.0-volume-clients-as-library-309030c7a16e62ab.yaml
new file mode 100644
index 0000000..056e199
--- /dev/null
+++ b/releasenotes/notes/13.1.0-volume-clients-as-library-309030c7a16e62ab.yaml
@@ -0,0 +1,10 @@
+---
+features:
+  - |
+    Define volume service clients as libraries.
+    The following volume service clients are defined as library interface,
+    so the other projects can use these modules as stable libraries without
+    any maintenance changes.
+
+    * volumes_client(v1)
+    * volumes_client(v2)
diff --git a/requirements.txt b/requirements.txt
index 4af8bb3..fa6c413 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -15,7 +15,6 @@
 oslo.utils>=3.16.0 # Apache-2.0
 six>=1.9.0 # MIT
 fixtures>=3.0.0 # Apache-2.0/BSD
-testscenarios>=0.4 # Apache-2.0/BSD
 PyYAML>=3.10.0 # MIT
 python-subunit>=0.0.18 # Apache-2.0/BSD
 stevedore>=1.17.1 # Apache-2.0
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 4e9bb88..72d5b18 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -128,8 +128,7 @@
     def test_live_block_migration_paused(self):
         self._test_live_migration(state='PAUSED')
 
-    @decorators.skip_because(bug="1549511",
-                             condition=CONF.service_available.neutron)
+    @decorators.skip_because(bug="1524898")
     @test.idempotent_id('5071cf17-3004-4257-ae61-73a84e28badd')
     @test.services('volume')
     def test_volume_backed_live_migration(self):
diff --git a/tempest/api/compute/volumes/test_attach_volume_negative.py b/tempest/api/compute/volumes/test_attach_volume_negative.py
new file mode 100644
index 0000000..b7fa0fe
--- /dev/null
+++ b/tempest/api/compute/volumes/test_attach_volume_negative.py
@@ -0,0 +1,41 @@
+# Copyright 2016 NEC Corporation.  All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api.compute import base
+from tempest import config
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+CONF = config.CONF
+
+
+class AttachVolumeNegativeTest(base.BaseV2ComputeTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(AttachVolumeNegativeTest, cls).skip_checks()
+        if not CONF.service_available.cinder:
+            skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
+
+    @test.idempotent_id('a313b5cd-fbd0-49cc-94de-870e99f763c7')
+    def test_delete_attached_volume(self):
+        server = self.create_test_server(wait_until='ACTIVE')
+        volume = self.create_volume()
+
+        path = "/dev/%s" % CONF.compute.volume_device_name
+        self.attach_volume(server, volume, device=path)
+
+        self.assertRaises(lib_exc.BadRequest,
+                          self.delete_volume, volume['id'])
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 3825f84..819ef90 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -296,7 +296,7 @@
                           subnet_id)
 
         # Since create_subnet adds the subnet to the delete list, and it is
-        # is actually deleted here - this will create and issue, hence remove
+        # actually deleted here - this will create and issue, hence remove
         # it from the list.
         self.subnets.pop()
 
diff --git a/tempest/config.py b/tempest/config.py
index 1940393..8ce38f9 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -245,7 +245,7 @@
                     "projects. If multiple networks are available for a "
                     "project, this is the network which will be used for "
                     "creating servers if tempest does not create a network or "
-                    "s network is not specified elsewhere. It may be used for "
+                    "a network is not specified elsewhere. It may be used for "
                     "ssh validation only if floating IPs are disabled."),
     cfg.StrOpt('catalog_type',
                default='compute',
@@ -347,12 +347,10 @@
                 help="Does the test environment support suspend/resume?"),
     cfg.BoolOpt('cold_migration',
                 default=True,
-                help="Does the test environment support cold migration "
-                     "available?"),
+                help="Does the test environment support cold migration?"),
     cfg.BoolOpt('live_migration',
                 default=True,
-                help="Does the test environment support live migration "
-                     "available?"),
+                help="Does the test environment support live migration?"),
     cfg.BoolOpt('metadata_service',
                 default=True,
                 help="Does the test environment support metadata service? "
diff --git a/tempest/services/volume/base/base_volumes_client.py b/tempest/lib/services/volume/v1/volumes_client.py
similarity index 98%
rename from tempest/services/volume/base/base_volumes_client.py
rename to tempest/lib/services/volume/v1/volumes_client.py
index 1cb1ef5..3df8da4 100644
--- a/tempest/services/volume/base/base_volumes_client.py
+++ b/tempest/lib/services/volume/v1/volumes_client.py
@@ -21,11 +21,9 @@
 from tempest.lib import exceptions as lib_exc
 
 
-class BaseVolumesClient(rest_client.RestClient):
+class VolumesClient(rest_client.RestClient):
     """Base client class to send CRUD Volume API requests"""
 
-    create_resp = 200
-
     def _prepare_params(self, params):
         """Prepares params for use in get or _ext_get methods.
 
@@ -69,7 +67,7 @@
         post_body = json.dumps({'volume': kwargs})
         resp, body = self.post('volumes', post_body)
         body = json.loads(body)
-        self.expected_success(self.create_resp, resp.status)
+        self.expected_success(200, resp.status)
         return rest_client.ResponseBody(resp, body)
 
     def update_volume(self, volume_id, **kwargs):
diff --git a/tempest/services/volume/base/base_volumes_client.py b/tempest/lib/services/volume/v2/volumes_client.py
similarity index 84%
copy from tempest/services/volume/base/base_volumes_client.py
copy to tempest/lib/services/volume/v2/volumes_client.py
index 1cb1ef5..b1930e1 100644
--- a/tempest/services/volume/base/base_volumes_client.py
+++ b/tempest/lib/services/volume/v2/volumes_client.py
@@ -21,10 +21,9 @@
 from tempest.lib import exceptions as lib_exc
 
 
-class BaseVolumesClient(rest_client.RestClient):
-    """Base client class to send CRUD Volume API requests"""
-
-    create_resp = 200
+class VolumesClient(rest_client.RestClient):
+    """Client class to send CRUD Volume V2 API requests"""
+    api_version = "v2"
 
     def _prepare_params(self, params):
         """Prepares params for use in get or _ext_get methods.
@@ -69,7 +68,7 @@
         post_body = json.dumps({'volume': kwargs})
         resp, body = self.post('volumes', post_body)
         body = json.loads(body)
-        self.expected_success(self.create_resp, resp.status)
+        self.expected_success(202, resp.status)
         return rest_client.ResponseBody(resp, body)
 
     def update_volume(self, volume_id, **kwargs):
@@ -293,3 +292,49 @@
         post_body = json.dumps({'os-retype': kwargs})
         resp, body = self.post('volumes/%s/action' % volume_id, post_body)
         self.expected_success(202, resp.status)
+
+    def update_volume_image_metadata(self, volume_id, **kwargs):
+        """Update image metadata for the volume.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-blockstorage-v2.html
+                              #setVolumeimagemetadata
+        """
+        post_body = json.dumps({'os-set_image_metadata': {'metadata': kwargs}})
+        url = "volumes/%s/action" % (volume_id)
+        resp, body = self.post(url, post_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_volume_image_metadata(self, volume_id, key_name):
+        """Delete image metadata item for the volume."""
+        post_body = json.dumps({'os-unset_image_metadata': {'key': key_name}})
+        url = "volumes/%s/action" % (volume_id)
+        resp, body = self.post(url, post_body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_pools(self, detail=False):
+        # List all the volumes pools (hosts)
+        url = 'scheduler-stats/get_pools'
+        if detail:
+            url += '?detail=True'
+
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_backend_capabilities(self, host):
+        """Shows capabilities for a storage back end.
+
+         Output params: see http://developer.openstack.org/
+                            api-ref-blockstorage-v2.html
+                            #showBackendCapabilities
+        """
+        url = 'capabilities/%s' % host
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/v1/__init__.py b/tempest/services/volume/v1/__init__.py
index 376ab72..7fb3ed3 100644
--- a/tempest/services/volume/v1/__init__.py
+++ b/tempest/services/volume/v1/__init__.py
@@ -24,7 +24,7 @@
 from tempest.lib.services.volume.v1.services_client import ServicesClient
 from tempest.lib.services.volume.v1.snapshots_client import SnapshotsClient
 from tempest.lib.services.volume.v1.types_client import TypesClient
-from tempest.services.volume.v1.json.volumes_client import VolumesClient
+from tempest.lib.services.volume.v1.volumes_client import VolumesClient
 
 __all__ = ['AvailabilityZoneClient', 'EncryptionTypesClient',
            'ExtensionsClient', 'HostsClient', 'QuotasClient',
diff --git a/tempest/services/volume/v1/json/volumes_client.py b/tempest/services/volume/v1/json/volumes_client.py
deleted file mode 100644
index 7782043..0000000
--- a/tempest/services/volume/v1/json/volumes_client.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright 2012 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.services.volume.base import base_volumes_client
-
-
-class VolumesClient(base_volumes_client.BaseVolumesClient):
-    """Client class to send CRUD Volume V1 API requests"""
diff --git a/tempest/services/volume/v2/__init__.py b/tempest/services/volume/v2/__init__.py
index 5774977..8edaf2a 100644
--- a/tempest/services/volume/v2/__init__.py
+++ b/tempest/services/volume/v2/__init__.py
@@ -24,7 +24,7 @@
 from tempest.lib.services.volume.v2.services_client import ServicesClient
 from tempest.lib.services.volume.v2.snapshots_client import SnapshotsClient
 from tempest.lib.services.volume.v2.types_client import TypesClient
-from tempest.services.volume.v2.json.volumes_client import VolumesClient
+from tempest.lib.services.volume.v2.volumes_client import VolumesClient
 
 __all__ = ['AvailabilityZoneClient', 'BackupsClient', 'EncryptionTypesClient',
            'ExtensionsClient', 'HostsClient', 'QosSpecsClient', 'QuotasClient',
diff --git a/tempest/services/volume/v2/json/volumes_client.py b/tempest/services/volume/v2/json/volumes_client.py
deleted file mode 100644
index f21a1a3..0000000
--- a/tempest/services/volume/v2/json/volumes_client.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# Copyright 2012 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 oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
-from tempest.services.volume.base import base_volumes_client
-
-
-class VolumesClient(base_volumes_client.BaseVolumesClient):
-    """Client class to send CRUD Volume V2 API requests"""
-    api_version = "v2"
-    create_resp = 202
-
-    def update_volume_image_metadata(self, volume_id, **kwargs):
-        """Update image metadata for the volume.
-
-        Available params: see http://developer.openstack.org/
-                              api-ref-blockstorage-v2.html
-                              #setVolumeimagemetadata
-        """
-        post_body = json.dumps({'os-set_image_metadata': {'metadata': kwargs}})
-        url = "volumes/%s/action" % (volume_id)
-        resp, body = self.post(url, post_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_volume_image_metadata(self, volume_id, key_name):
-        """Delete image metadata item for the volume."""
-        post_body = json.dumps({'os-unset_image_metadata': {'key': key_name}})
-        url = "volumes/%s/action" % (volume_id)
-        resp, body = self.post(url, post_body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_pools(self, detail=False):
-        # List all the volumes pools (hosts)
-        url = 'scheduler-stats/get_pools'
-        if detail:
-            url += '?detail=True'
-
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_backend_capabilities(self, host):
-        """Shows capabilities for a storage back end.
-
-         Output params: see http://developer.openstack.org/
-                            api-ref-blockstorage-v2.html
-                            #showBackendCapabilities
-        """
-        url = 'capabilities/%s' % host
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index a56f837..a826337 100644
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -18,7 +18,7 @@
 
 from tempest.common import waiters
 from tempest import exceptions
-from tempest.services.volume.base import base_volumes_client
+from tempest.lib.services.volume.v2 import volumes_client
 from tempest.tests import base
 import tempest.tests.utils as utils
 
@@ -57,7 +57,7 @@
     def test_wait_for_volume_status_error_restoring(self, mock_sleep):
         # Tests that the wait method raises VolumeRestoreErrorException if
         # the volume status is 'error_restoring'.
-        client = mock.Mock(spec=base_volumes_client.BaseVolumesClient,
+        client = mock.Mock(spec=volumes_client.VolumesClient,
                            build_interval=1)
         volume1 = {'volume': {'status': 'restoring-backup'}}
         volume2 = {'volume': {'status': 'error_restoring'}}