Merge "Add tests for getting nova api version details"
diff --git a/tempest/api/compute/test_versions.py b/tempest/api/compute/test_versions.py
index 369cf31..f94cee6 100644
--- a/tempest/api/compute/test_versions.py
+++ b/tempest/api/compute/test_versions.py
@@ -20,5 +20,53 @@
 
     @test.idempotent_id('6c0a0990-43b6-4529-9b61-5fd8daf7c55c')
     def test_list_api_versions(self):
+        """Test that a get of the unversioned url returns the choices doc.
+
+        A key feature in OpenStack services is the idea that you can
+        GET / on the service and get a list of the versioned endpoints
+        that you can access. This comes back as a status 300
+        request. It's important that this is available to API
+        consumers to discover the API they can use.
+
+        """
         result = self.versions_client.list_versions()
-        self.assertIsNotNone(result)
+        versions = result['versions']
+        # NOTE(sdague): at a later point we may want to loosen this
+        # up, but for now this should be true of all Novas deployed.
+        self.assertEqual(versions[0]['id'], 'v2.0',
+                         "The first listed version should be v2.0")
+
+    @test.idempotent_id('b953a29e-929c-4a8e-81be-ec3a7e03cb76')
+    def test_get_version_details(self):
+        """Test individual version endpoints info works.
+
+        In addition to the GET / version request, there is also a
+        version info document stored at the top of the versioned
+        endpoints. This provides access to details about that
+        endpoint, including min / max version if that implements
+        microversions.
+
+        This test starts with the version list, iterates all the
+        returned endpoints, and fetches them. This will also ensure
+        that all the version links are followable constructs which
+        will help detect configuration issues when SSL termination
+        isn't done completely for a site.
+
+        """
+        result = self.versions_client.list_versions()
+        versions = result['versions']
+
+        # iterate through all the versions that are returned and
+        # attempt to get their version documents.
+        for version in versions:
+            links = [x for x in version['links'] if x['rel'] == 'self']
+            self.assertEqual(
+                len(links), 1,
+                "There should only be 1 self link in %s" % version)
+            link = links[0]
+            # this is schema validated
+            result = self.versions_client.get_version_by_url(link['href'])
+            # ensure the new self link matches the old one
+            newlinks = [x for x in result['version']['links']
+                        if x['rel'] == 'self']
+            self.assertEqual(links, newlinks)
diff --git a/tempest/api_schema/response/compute/v2_1/versions.py b/tempest/api_schema/response/compute/v2_1/versions.py
index f08695c..08a9fab 100644
--- a/tempest/api_schema/response/compute/v2_1/versions.py
+++ b/tempest/api_schema/response/compute/v2_1/versions.py
@@ -12,6 +12,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import copy
+
+
 _version = {
     'type': 'object',
     'properties': {
@@ -23,6 +26,7 @@
                 'properties': {
                     'href': {'type': 'string', 'format': 'uri'},
                     'rel': {'type': 'string'},
+                    'type': {'type': 'string'},
                 },
                 'required': ['href', 'rel'],
                 'additionalProperties': False
@@ -31,10 +35,18 @@
         'status': {'type': 'string'},
         'updated': {'type': 'string', 'format': 'date-time'},
         'version': {'type': 'string'},
-        'min_version': {'type': 'string'}
+        'min_version': {'type': 'string'},
+        'media-types': {
+            'type': 'array',
+            'properties': {
+                'base': {'type': 'string'},
+                'type': {'type': 'string'},
+            }
+        },
     },
     # NOTE: version and min_version have been added since Kilo,
     # so they should not be required.
+    # NOTE(sdague): media-types only shows up in single version requests.
     'required': ['id', 'links', 'status', 'updated'],
     'additionalProperties': False
 }
@@ -53,3 +65,46 @@
         'additionalProperties': False
     }
 }
+
+
+_detail_get_version = copy.deepcopy(_version)
+_detail_get_version['properties'].pop('min_version')
+_detail_get_version['properties'].pop('version')
+_detail_get_version['properties'].pop('updated')
+_detail_get_version['properties']['media-types'] = {
+    'type': 'array',
+    'items': {
+        'type': 'object',
+        'properties': {
+            'base': {'type': 'string'},
+            'type': {'type': 'string'}
+        }
+    }
+}
+_detail_get_version['required'] = ['id', 'links', 'status', 'media-types']
+
+get_version = {
+    'status_code': [300],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'choices': {
+                'type': 'array',
+                'items': _detail_get_version
+            }
+        },
+        'required': ['choices'],
+        'additionalProperties': False
+    }
+}
+
+get_one_version = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'version': _version
+        },
+        'additionalProperties': False
+    }
+}
diff --git a/tempest/services/compute/json/versions_client.py b/tempest/services/compute/json/versions_client.py
index cbad02c..48c0e8d 100644
--- a/tempest/services/compute/json/versions_client.py
+++ b/tempest/services/compute/json/versions_client.py
@@ -21,7 +21,7 @@
 
 class VersionsClient(service_client.ServiceClient):
 
-    def list_versions(self):
+    def _get_base_version_url(self):
         # NOTE: The URL which is gotten from keystone's catalog contains
         # API version and project-id like "v2/{project-id}", but we need
         # to access the URL which doesn't contain them for getting API
@@ -29,9 +29,27 @@
         # get().
         endpoint = self.base_url
         url = urllib.parse.urlparse(endpoint)
-        version_url = '%s://%s/' % (url.scheme, url.netloc)
+        return '%s://%s/' % (url.scheme, url.netloc)
 
+    def list_versions(self):
+        version_url = self._get_base_version_url()
         resp, body = self.raw_request(version_url, 'GET')
         body = json.loads(body)
         self.validate_response(schema.list_versions, resp, body)
         return service_client.ResponseBody(resp, body)
+
+    def get_version_by_url(self, version_url):
+        """Get the version document by url.
+
+        This gets the version document for a url, useful in testing
+        the contents of things like /v2/ or /v2.1/ in Nova. That
+        controller needs authenticated access, so we have to get
+        ourselves a token before making the request.
+
+        """
+        # we need a token for this request
+        resp, body = self.raw_request(version_url, 'GET',
+                                      {'X-Auth-Token': self.token})
+        body = json.loads(body)
+        self.validate_response(schema.get_one_version, resp, body)
+        return service_client.ResponseBody(resp, body)