Merge "Separate base microversion client and compute client"
diff --git a/tempest/common/api_version_utils.py b/tempest/common/api_version_utils.py
index ac3322e..98601a7 100644
--- a/tempest/common/api_version_utils.py
+++ b/tempest/common/api_version_utils.py
@@ -69,3 +69,27 @@
     cfg_version = api_version_request.APIVersionRequest(cfg_min_version)
     max_version = cfg_version if cfg_version >= test_version else test_version
     return max_version.get_string()
+
+
+def assert_version_header_matches_request(api_microversion_header_name,
+                                          api_microversion,
+                                          response_header):
+    """Checks API microversion in resposne header
+
+    Verify whether microversion is present in response header
+    and with specified 'api_microversion' value.
+
+    @param: api_microversion_header_name: Microversion header name
+            Example- "X-OpenStack-Nova-API-Version"
+    @param: api_microversion: Microversion number like "2.10"
+    @param: response_header: Response header where microversion is
+            expected to be present.
+    """
+    api_microversion_header_name = api_microversion_header_name.lower()
+    if (api_microversion_header_name not in response_header or
+        api_microversion != response_header[api_microversion_header_name]):
+        msg = ("Microversion header '%s' with value '%s' does not match in "
+               "response - %s. " % (api_microversion_header_name,
+                                    api_microversion,
+                                    response_header))
+        raise exceptions.InvalidHTTPResponseHeader(msg)
diff --git a/tempest/services/base_microversion_client.py b/tempest/services/base_microversion_client.py
new file mode 100644
index 0000000..4c750f5
--- /dev/null
+++ b/tempest/services/base_microversion_client.py
@@ -0,0 +1,54 @@
+# 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_lib.common import rest_client
+
+
+class BaseMicroversionClient(rest_client.RestClient):
+    """Base class to support microversion in service clients
+
+    This class is used to support microversion in service clients.
+    This provides feature to make API request with microversion.
+    Service clients derived from this class will be able to send API
+    request to server with or without microversion.
+    If api_microversion is not set on service client then API request will be
+    normal request without microversion.
+
+    """
+    def __init__(self, auth_provider, service, region,
+                 api_microversion_header_name, **kwargs):
+        """Base Microversion Client __init__
+
+        :param auth_provider: an auth provider object used to wrap requests in
+                              auth
+        :param str service: The service name to use for the catalog lookup
+        :param str region: The region to use for the catalog lookup
+        :param str api_microversion_header_name: The microversion header name
+                                                 to use for sending API
+                                                 request with microversion
+        :param kwargs: kwargs required by rest_client.RestClient
+        """
+        super(BaseMicroversionClient, self).__init__(
+            auth_provider, service, region, **kwargs)
+        self.api_microversion_header_name = api_microversion_header_name
+        self.api_microversion = None
+
+    def get_headers(self):
+        headers = super(BaseMicroversionClient, self).get_headers()
+        if self.api_microversion:
+            headers[self.api_microversion_header_name] = self.api_microversion
+        return headers
+
+    def set_api_microversion(self, microversion):
+        self.api_microversion = microversion
diff --git a/tempest/services/compute/json/base.py b/tempest/services/compute/json/base_compute_client.py
similarity index 69%
rename from tempest/services/compute/json/base.py
rename to tempest/services/compute/json/base_compute_client.py
index 40d3056..5349af6 100644
--- a/tempest/services/compute/json/base.py
+++ b/tempest/services/compute/json/base_compute_client.py
@@ -12,23 +12,31 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest_lib.common import rest_client
-
 from tempest.common import api_version_request
+from tempest.common import api_version_utils
 from tempest import exceptions
+from tempest.services import base_microversion_client
 
 
-class BaseComputeClient(rest_client.RestClient):
-    api_microversion = None
+class BaseComputeClient(base_microversion_client.BaseMicroversionClient):
 
-    def get_headers(self):
-        headers = super(BaseComputeClient, self).get_headers()
-        if self.api_microversion:
-            headers['X-OpenStack-Nova-API-Version'] = self.api_microversion
-        return headers
+    def __init__(self, auth_provider, service, region,
+                 api_microversion_header_name='X-OpenStack-Nova-API-Version',
+                 **kwargs):
+        super(BaseComputeClient, self).__init__(
+            auth_provider, service, region,
+            api_microversion_header_name, **kwargs)
 
-    def set_api_microversion(self, microversion):
-        self.api_microversion = microversion
+    def request(self, method, url, extra_headers=False, headers=None,
+                body=None):
+        resp, resp_body = super(BaseComputeClient, self).request(
+            method, url, extra_headers, headers, body)
+        if self.api_microversion and self.api_microversion != 'latest':
+            api_version_utils.assert_version_header_matches_request(
+                self.api_microversion_header_name,
+                self.api_microversion,
+                resp)
+        return resp, resp_body
 
     def get_schema(self, schema_versions_info):
         """Get JSON schema
diff --git a/tempest/services/compute/json/keypairs_client.py b/tempest/services/compute/json/keypairs_client.py
index e956100..ec9b1e0 100644
--- a/tempest/services/compute/json/keypairs_client.py
+++ b/tempest/services/compute/json/keypairs_client.py
@@ -18,10 +18,10 @@
 from tempest.api_schema.response.compute.v2_1 import keypairs as schemav21
 from tempest.api_schema.response.compute.v2_2 import keypairs as schemav22
 from tempest.common import service_client
-from tempest.services.compute.json import base
+from tempest.services.compute.json import base_compute_client
 
 
-class KeyPairsClient(base.BaseComputeClient):
+class KeyPairsClient(base_compute_client.BaseComputeClient):
 
     schema_versions_info = [{'min': None, 'max': '2.1', 'schema': schemav21},
                             {'min': '2.2', 'max': None, 'schema': schemav22}]
diff --git a/tempest/tests/common/test_api_version_utils.py b/tempest/tests/common/test_api_version_utils.py
index 9f399a2..babf965 100644
--- a/tempest/tests/common/test_api_version_utils.py
+++ b/tempest/tests/common/test_api_version_utils.py
@@ -219,3 +219,31 @@
 
     def test_both_min_version_equal(self):
         self._test_request_version('2.3', '2.3', expected_version='2.3')
+
+
+class TestMicroversionHeaderMatches(base.TestCase):
+
+    def test_header_matches(self):
+        microversion_header_name = 'x-openstack-xyz-api-version'
+        request_microversion = '2.1'
+        test_respose = {microversion_header_name: request_microversion}
+        api_version_utils.assert_version_header_matches_request(
+            microversion_header_name, request_microversion, test_respose)
+
+    def test_header_does_not_match(self):
+        microversion_header_name = 'x-openstack-xyz-api-version'
+        request_microversion = '2.1'
+        test_respose = {microversion_header_name: '2.2'}
+        self.assertRaises(
+            exceptions.InvalidHTTPResponseHeader,
+            api_version_utils.assert_version_header_matches_request,
+            microversion_header_name, request_microversion, test_respose)
+
+    def test_header_not_present(self):
+        microversion_header_name = 'x-openstack-xyz-api-version'
+        request_microversion = '2.1'
+        test_respose = {}
+        self.assertRaises(
+            exceptions.InvalidHTTPResponseHeader,
+            api_version_utils.assert_version_header_matches_request,
+            microversion_header_name, request_microversion, test_respose)
diff --git a/tempest/tests/services/compute/test_base_compute_client.py b/tempest/tests/services/compute/test_base_compute_client.py
index 134fe39..7a55cdb 100644
--- a/tempest/tests/services/compute/test_base_compute_client.py
+++ b/tempest/tests/services/compute/test_base_compute_client.py
@@ -13,64 +13,48 @@
 #    under the License.
 
 import httplib2
-import mock
+from oslotest import mockpatch
 from tempest_lib.common import rest_client
 
 from tempest import exceptions
-from tempest.services.compute.json import base as base_compute_client
+from tempest.services.compute.json import base_compute_client
 from tempest.tests import fake_auth_provider
 from tempest.tests.services.compute import base
 
 
-class TestClientWithoutMicroversionHeader(base.BaseComputeServiceTest):
+class TestMicroversionHeaderCheck(base.BaseComputeServiceTest):
 
     def setUp(self):
-        super(TestClientWithoutMicroversionHeader, self).setUp()
+        super(TestMicroversionHeaderCheck, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
         self.client = base_compute_client.BaseComputeClient(
             fake_auth, 'compute', 'regionOne')
+        self.client.set_api_microversion('2.2')
 
-    def test_no_microverion_header(self):
-        header = self.client.get_headers()
-        self.assertNotIn('X-OpenStack-Nova-API-Version', header)
+    def _check_microverion_header_in_response(self, fake_response):
+        def request(*args, **kwargs):
+            return (httplib2.Response(fake_response), {})
 
-    def test_no_microverion_header_in_raw_request(self):
-        def raw_request(*args, **kwargs):
-            self.assertNotIn('X-OpenStack-Nova-API-Version', kwargs['headers'])
-            return (httplib2.Response({'status': 200}), {})
+        self.useFixture(mockpatch.PatchObject(
+            rest_client.RestClient,
+            'request',
+            side_effect=request))
 
-        with mock.patch.object(rest_client.RestClient,
-                               'raw_request') as mock_get:
-            mock_get.side_effect = raw_request
-            self.client.get('fake_url')
+    def test_correct_microverion_in_response(self):
+        fake_response = {self.client.api_microversion_header_name: '2.2'}
+        self._check_microverion_header_in_response(fake_response)
+        self.client.get('fake_url')
 
+    def test_incorrect_microverion_in_response(self):
+        fake_response = {self.client.api_microversion_header_name: '2.3'}
+        self._check_microverion_header_in_response(fake_response)
+        self.assertRaises(exceptions.InvalidHTTPResponseHeader,
+                          self.client.get, 'fake_url')
 
-class TestClientWithMicroversionHeader(base.BaseComputeServiceTest):
-
-    def setUp(self):
-        super(TestClientWithMicroversionHeader, self).setUp()
-        fake_auth = fake_auth_provider.FakeAuthProvider()
-        self.client = base_compute_client.BaseComputeClient(
-            fake_auth, 'compute', 'regionOne')
-        self.client.api_microversion = '2.2'
-
-    def test_microverion_header(self):
-        header = self.client.get_headers()
-        self.assertIn('X-OpenStack-Nova-API-Version', header)
-        self.assertEqual(self.client.api_microversion,
-                         header['X-OpenStack-Nova-API-Version'])
-
-    def test_microverion_header_in_raw_request(self):
-        def raw_request(*args, **kwargs):
-            self.assertIn('X-OpenStack-Nova-API-Version', kwargs['headers'])
-            self.assertEqual(self.client.api_microversion,
-                             kwargs['headers']['X-OpenStack-Nova-API-Version'])
-            return (httplib2.Response({'status': 200}), {})
-
-        with mock.patch.object(rest_client.RestClient,
-                               'raw_request') as mock_get:
-            mock_get.side_effect = raw_request
-            self.client.get('fake_url')
+    def test_no_microverion_header_in_response(self):
+        self._check_microverion_header_in_response({})
+        self.assertRaises(exceptions.InvalidHTTPResponseHeader,
+                          self.client.get, 'fake_url')
 
 
 class DummyServiceClient1(base_compute_client.BaseComputeClient):
diff --git a/tempest/tests/services/test_base_microversion_client.py b/tempest/tests/services/test_base_microversion_client.py
new file mode 100644
index 0000000..11b8170
--- /dev/null
+++ b/tempest/tests/services/test_base_microversion_client.py
@@ -0,0 +1,75 @@
+# 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.
+
+import httplib2
+import mock
+from tempest_lib.common import rest_client
+
+from tempest.services import base_microversion_client
+from tempest.tests import fake_auth_provider
+from tempest.tests.services.compute import base
+
+
+class TestClientWithoutMicroversionHeader(base.BaseComputeServiceTest):
+
+    def setUp(self):
+        super(TestClientWithoutMicroversionHeader, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = base_microversion_client.BaseMicroversionClient(
+            fake_auth, 'compute', 'regionOne', 'X-OpenStack-Nova-API-Version')
+
+    def test_no_microverion_header(self):
+        header = self.client.get_headers()
+        self.assertNotIn(self.client.api_microversion_header_name, header)
+
+    def test_no_microverion_header_in_raw_request(self):
+        def raw_request(*args, **kwargs):
+            self.assertNotIn(self.client.api_microversion_header_name,
+                             kwargs['headers'])
+            return (httplib2.Response({'status': 200}), {})
+
+        with mock.patch.object(rest_client.RestClient,
+                               'raw_request') as mock_get:
+            mock_get.side_effect = raw_request
+            self.client.get('fake_url')
+
+
+class TestClientWithMicroversionHeader(base.BaseComputeServiceTest):
+
+    def setUp(self):
+        super(TestClientWithMicroversionHeader, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = base_microversion_client.BaseMicroversionClient(
+            fake_auth, 'compute', 'regionOne', 'X-OpenStack-Nova-API-Version')
+        self.client.set_api_microversion('2.2')
+
+    def test_microverion_header(self):
+        header = self.client.get_headers()
+        self.assertIn(self.client.api_microversion_header_name, header)
+        self.assertEqual(self.client.api_microversion,
+                         header[self.client.api_microversion_header_name])
+
+    def test_microverion_header_in_raw_request(self):
+        def raw_request(*args, **kwargs):
+            self.assertIn(self.client.api_microversion_header_name,
+                          kwargs['headers'])
+            self.assertEqual(
+                self.client.api_microversion,
+                kwargs['headers'][self.client.api_microversion_header_name])
+            return (httplib2.Response({'status': 200}), {})
+
+        with mock.patch.object(rest_client.RestClient,
+                               'raw_request') as mock_get:
+            mock_get.side_effect = raw_request
+            self.client.get('fake_url')