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)