Move verification of response attributes into service client
Moves the verification of service/volume response body attributes to the service client rather than in each test which uses the service client. Partially implements blueprint nova-api-attribute-test Change-Id: Ie829e2beb1e065a2804dab93835c3d1933fd419d
This commit is contained in:
parent
43302f364f
commit
c266b28ace
@ -14,7 +14,6 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from tempest.api.compute.api_schema import services as schema
|
||||
from tempest.api.compute import base
|
||||
from tempest import test
|
||||
|
||||
@ -33,7 +32,7 @@ class ServicesAdminTestJSON(base.BaseV2ComputeAdminTest):
|
||||
@test.attr(type='gate')
|
||||
def test_list_services(self):
|
||||
resp, services = self.client.list_services()
|
||||
self.validate_response(schema.list_services, resp, services)
|
||||
self.assertEqual(200, resp.status)
|
||||
self.assertNotEqual(0, len(services))
|
||||
|
||||
@test.attr(type='gate')
|
||||
@ -41,7 +40,7 @@ class ServicesAdminTestJSON(base.BaseV2ComputeAdminTest):
|
||||
binary_name = 'nova-compute'
|
||||
params = {'binary': binary_name}
|
||||
resp, services = self.client.list_services(params)
|
||||
self.validate_response(schema.list_services, resp, services)
|
||||
self.assertEqual(200, resp.status)
|
||||
self.assertNotEqual(0, len(services))
|
||||
for service in services:
|
||||
self.assertEqual(binary_name, service['binary'])
|
||||
@ -49,14 +48,12 @@ class ServicesAdminTestJSON(base.BaseV2ComputeAdminTest):
|
||||
@test.attr(type='gate')
|
||||
def test_get_service_by_host_name(self):
|
||||
resp, services = self.client.list_services()
|
||||
self.validate_response(schema.list_services, resp, services)
|
||||
host_name = services[0]['host']
|
||||
services_on_host = [service for service in services if
|
||||
service['host'] == host_name]
|
||||
params = {'host': host_name}
|
||||
|
||||
resp, services = self.client.list_services(params)
|
||||
self.validate_response(schema.list_services, resp, services)
|
||||
|
||||
# we could have a periodic job checkin between the 2 service
|
||||
# lookups, so only compare binary lists.
|
||||
@ -70,13 +67,12 @@ class ServicesAdminTestJSON(base.BaseV2ComputeAdminTest):
|
||||
@test.attr(type='gate')
|
||||
def test_get_service_by_service_and_host_name(self):
|
||||
resp, services = self.client.list_services()
|
||||
self.validate_response(schema.list_services, resp, services)
|
||||
host_name = services[0]['host']
|
||||
binary_name = services[0]['binary']
|
||||
params = {'host': host_name, 'binary': binary_name}
|
||||
|
||||
resp, services = self.client.list_services(params)
|
||||
self.validate_response(schema.list_services, resp, services)
|
||||
self.assertEqual(200, resp.status)
|
||||
self.assertEqual(1, len(services))
|
||||
self.assertEqual(host_name, services[0]['host'])
|
||||
self.assertEqual(binary_name, services[0]['binary'])
|
||||
|
@ -1,38 +0,0 @@
|
||||
# Copyright 2014 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.
|
||||
|
||||
list_services = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
# NOTE: Now the type of 'id' is integer, but here allows
|
||||
# 'string' also because we will be able to change it to
|
||||
# 'uuid' in the future.
|
||||
'id': {'type': ['integer', 'string']},
|
||||
'zone': {'type': 'string'},
|
||||
'host': {'type': 'string'},
|
||||
'state': {'type': 'string'},
|
||||
'binary': {'type': 'string'},
|
||||
'status': {'type': 'string'},
|
||||
'updated_at': {'type': 'string'},
|
||||
'disabled_reason': {'type': ['string', 'null']},
|
||||
},
|
||||
'required': ['id', 'zone', 'host', 'state', 'binary', 'status',
|
||||
'updated_at', 'disabled_reason'],
|
||||
},
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
# Copyright 2014 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.
|
||||
|
||||
get_volume = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
# NOTE: Now the type of 'id' is integer, but here allows
|
||||
# 'string' also because we will be able to change it to
|
||||
# 'uuid' in the future.
|
||||
'id': {'type': ['integer', 'string']},
|
||||
'status': {'type': 'string'},
|
||||
'displayName': {'type': ['string', 'null']},
|
||||
'availabilityZone': {'type': 'string'},
|
||||
'createdAt': {'type': 'string'},
|
||||
'displayDescription': {'type': ['string', 'null']},
|
||||
'volumeType': {'type': 'string'},
|
||||
'snapshotId': {'type': ['string', 'null']},
|
||||
'metadata': {'type': 'object'},
|
||||
'size': {'type': 'integer'},
|
||||
'attachments': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'id': {'type': ['integer', 'string']},
|
||||
'device': {'type': 'string'},
|
||||
'volumeId': {'type': ['integer', 'string']},
|
||||
'serverId': {'type': ['integer', 'string']},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'required': ['id', 'status', 'displayName', 'availabilityZone',
|
||||
'createdAt', 'displayDescription', 'volumeType',
|
||||
'snapshotId', 'metadata', 'size', 'attachments'],
|
||||
},
|
||||
}
|
@ -15,8 +15,6 @@
|
||||
|
||||
import time
|
||||
|
||||
import jsonschema
|
||||
|
||||
from tempest import clients
|
||||
from tempest.common.utils import data_utils
|
||||
from tempest import config
|
||||
@ -178,32 +176,6 @@ class BaseComputeTest(tempest.test.BaseTestCase):
|
||||
|
||||
return resp, body
|
||||
|
||||
@classmethod
|
||||
def validate_response(cls, schema, resp, body):
|
||||
response_code = schema['status_code']
|
||||
if resp.status not in response_code:
|
||||
msg = ("The status code(%s) is different than the expected "
|
||||
"one(%s)") % (resp.status, response_code)
|
||||
raise exceptions.InvalidHttpSuccessCode(msg)
|
||||
response_schema = schema.get('response_body')
|
||||
if response_schema:
|
||||
if cls._interface == 'xml':
|
||||
# NOTE: xml client of Tempest is broken and cannot get some
|
||||
# keys. The best way is to fix it, but now xml format has been
|
||||
# marked as "deprecated" in Nova API and xml client will be
|
||||
# removed from Tempest.
|
||||
# So now this test does not check attributes if xml.
|
||||
return
|
||||
try:
|
||||
jsonschema.validate(body, response_schema)
|
||||
except jsonschema.ValidationError as ex:
|
||||
msg = ("HTTP response body is invalid (%s)") % ex
|
||||
raise exceptions.InvalidHTTPResponseBody(msg)
|
||||
else:
|
||||
if body:
|
||||
msg = ("HTTP response body should not exist (%s)") % body
|
||||
raise exceptions.InvalidHTTPResponseBody(msg)
|
||||
|
||||
def wait_for(self, condition):
|
||||
"""Repeatedly calls condition() until a timeout."""
|
||||
start_time = int(time.time())
|
||||
|
@ -14,7 +14,6 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from tempest.api.compute.api_schema import services as schema
|
||||
from tempest.api.compute import base
|
||||
from tempest.test import attr
|
||||
|
||||
@ -33,7 +32,7 @@ class ServicesAdminV3Test(base.BaseV3ComputeAdminTest):
|
||||
@attr(type='gate')
|
||||
def test_list_services(self):
|
||||
resp, services = self.client.list_services()
|
||||
self.validate_response(schema.list_services, resp, services)
|
||||
self.assertEqual(200, resp.status)
|
||||
self.assertNotEqual(0, len(services))
|
||||
|
||||
@attr(type='gate')
|
||||
@ -41,7 +40,7 @@ class ServicesAdminV3Test(base.BaseV3ComputeAdminTest):
|
||||
binary_name = 'nova-compute'
|
||||
params = {'binary': binary_name}
|
||||
resp, services = self.client.list_services(params)
|
||||
self.validate_response(schema.list_services, resp, services)
|
||||
self.assertEqual(200, resp.status)
|
||||
self.assertNotEqual(0, len(services))
|
||||
for service in services:
|
||||
self.assertEqual(binary_name, service['binary'])
|
||||
@ -49,14 +48,13 @@ class ServicesAdminV3Test(base.BaseV3ComputeAdminTest):
|
||||
@attr(type='gate')
|
||||
def test_get_service_by_host_name(self):
|
||||
resp, services = self.client.list_services()
|
||||
self.validate_response(schema.list_services, resp, services)
|
||||
self.assertEqual(200, resp.status)
|
||||
host_name = services[0]['host']
|
||||
services_on_host = [service for service in services if
|
||||
service['host'] == host_name]
|
||||
params = {'host': host_name}
|
||||
|
||||
resp, services = self.client.list_services(params)
|
||||
self.validate_response(schema.list_services, resp, services)
|
||||
|
||||
# we could have a periodic job checkin between the 2 service
|
||||
# lookups, so only compare binary lists.
|
||||
@ -70,13 +68,12 @@ class ServicesAdminV3Test(base.BaseV3ComputeAdminTest):
|
||||
@attr(type='gate')
|
||||
def test_get_service_by_service_and_host_name(self):
|
||||
resp, services = self.client.list_services()
|
||||
self.validate_response(schema.list_services, resp, services)
|
||||
host_name = services[0]['host']
|
||||
binary_name = services[0]['binary']
|
||||
params = {'host': host_name, 'binary': binary_name}
|
||||
|
||||
resp, services = self.client.list_services(params)
|
||||
self.validate_response(schema.list_services, resp, services)
|
||||
self.assertEqual(200, resp.status)
|
||||
self.assertEqual(1, len(services))
|
||||
self.assertEqual(host_name, services[0]['host'])
|
||||
self.assertEqual(binary_name, services[0]['binary'])
|
||||
|
@ -13,7 +13,6 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from tempest.api.compute.api_schema.v2 import volumes as schema
|
||||
from tempest.api.compute import base
|
||||
from tempest.common.utils import data_utils
|
||||
from tempest import config
|
||||
@ -44,7 +43,9 @@ class VolumesGetTestJSON(base.BaseV2ComputeTest):
|
||||
display_name=v_name,
|
||||
metadata=metadata)
|
||||
self.addCleanup(self.delete_volume, volume['id'])
|
||||
self.validate_response(schema.get_volume, resp, volume)
|
||||
self.assertEqual(200, resp.status)
|
||||
self.assertIn('id', volume)
|
||||
self.assertIn('displayName', volume)
|
||||
self.assertEqual(volume['displayName'], v_name,
|
||||
"The created volume name is not equal "
|
||||
"to the requested name")
|
||||
@ -54,7 +55,7 @@ class VolumesGetTestJSON(base.BaseV2ComputeTest):
|
||||
self.client.wait_for_volume_status(volume['id'], 'available')
|
||||
# GET Volume
|
||||
resp, fetched_volume = self.client.get_volume(volume['id'])
|
||||
self.validate_response(schema.get_volume, resp, fetched_volume)
|
||||
self.assertEqual(200, resp.status)
|
||||
# Verification of details of fetched Volume
|
||||
self.assertEqual(v_name,
|
||||
fetched_volume['displayName'],
|
||||
|
44
tempest/api_schema/compute/services.py
Normal file
44
tempest/api_schema/compute/services.py
Normal file
@ -0,0 +1,44 @@
|
||||
# Copyright 2014 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.
|
||||
|
||||
list_services = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'services': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
# NOTE: Now the type of 'id' is integer, but here
|
||||
# allows 'string' also because we will be able to
|
||||
# change it to 'uuid' in the future.
|
||||
'id': {'type': ['integer', 'string']},
|
||||
'zone': {'type': 'string'},
|
||||
'host': {'type': 'string'},
|
||||
'state': {'type': 'string'},
|
||||
'binary': {'type': 'string'},
|
||||
'status': {'type': 'string'},
|
||||
'updated_at': {'type': 'string'},
|
||||
'disabled_reason': {'type': ['string', 'null']}
|
||||
},
|
||||
'required': ['id', 'zone', 'host', 'state', 'binary',
|
||||
'status', 'updated_at', 'disabled_reason']
|
||||
}
|
||||
}
|
||||
},
|
||||
'required': ['services']
|
||||
}
|
||||
}
|
0
tempest/api_schema/compute/v2/__init__.py
Normal file
0
tempest/api_schema/compute/v2/__init__.py
Normal file
56
tempest/api_schema/compute/v2/volumes.py
Normal file
56
tempest/api_schema/compute/v2/volumes.py
Normal file
@ -0,0 +1,56 @@
|
||||
# Copyright 2014 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.
|
||||
|
||||
get_volume = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'volume': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
# NOTE: Now the type of 'id' is integer, but here allows
|
||||
# 'string' also because we will be able to change it to
|
||||
# 'uuid' in the future.
|
||||
'id': {'type': ['integer', 'string']},
|
||||
'status': {'type': 'string'},
|
||||
'displayName': {'type': ['string', 'null']},
|
||||
'availabilityZone': {'type': 'string'},
|
||||
'createdAt': {'type': 'string'},
|
||||
'displayDescription': {'type': ['string', 'null']},
|
||||
'volumeType': {'type': 'string'},
|
||||
'snapshotId': {'type': ['string', 'null']},
|
||||
'metadata': {'type': 'object'},
|
||||
'size': {'type': 'integer'},
|
||||
'attachments': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'id': {'type': ['integer', 'string']},
|
||||
'device': {'type': 'string'},
|
||||
'volumeId': {'type': ['integer', 'string']},
|
||||
'serverId': {'type': ['integer', 'string']}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'required': ['id', 'status', 'displayName', 'availabilityZone',
|
||||
'createdAt', 'displayDescription', 'volumeType',
|
||||
'snapshotId', 'metadata', 'size', 'attachments']
|
||||
}
|
||||
},
|
||||
'required': ['volume']
|
||||
}
|
||||
}
|
@ -21,6 +21,8 @@ from lxml import etree
|
||||
import re
|
||||
import time
|
||||
|
||||
import jsonschema
|
||||
|
||||
from tempest.common import http
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
@ -502,6 +504,31 @@ class RestClient(object):
|
||||
% self.__class__.__name__)
|
||||
raise NotImplementedError(message)
|
||||
|
||||
@classmethod
|
||||
def validate_response(cls, schema, resp, body):
|
||||
# Only check the response if the status code is a success code
|
||||
# TODO(cyeoh): Eventually we should be able to verify that a failure
|
||||
# code if it exists is something that we expect. This is explicitly
|
||||
# declared in the V3 API and so we should be able to export this in
|
||||
# the response schema. For now we'll ignore it.
|
||||
if str(resp.status).startswith('2'):
|
||||
response_code = schema['status_code']
|
||||
if resp.status not in response_code:
|
||||
msg = ("The status code(%s) is different than the expected "
|
||||
"one(%s)") % (resp.status, response_code)
|
||||
raise exceptions.InvalidHttpSuccessCode(msg)
|
||||
response_schema = schema.get('response_body')
|
||||
if response_schema:
|
||||
try:
|
||||
jsonschema.validate(body, response_schema)
|
||||
except jsonschema.ValidationError as ex:
|
||||
msg = ("HTTP response body is invalid (%s)") % ex
|
||||
raise exceptions.InvalidHTTPResponseBody(msg)
|
||||
else:
|
||||
if body:
|
||||
msg = ("HTTP response body should not exist (%s)") % body
|
||||
raise exceptions.InvalidHTTPResponseBody(msg)
|
||||
|
||||
|
||||
class NegativeRestClient(RestClient):
|
||||
"""
|
||||
|
@ -17,6 +17,7 @@
|
||||
import json
|
||||
import urllib
|
||||
|
||||
from tempest.api_schema.compute import services as schema
|
||||
from tempest.common import rest_client
|
||||
from tempest import config
|
||||
|
||||
@ -36,6 +37,7 @@ class ServicesClientJSON(rest_client.RestClient):
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_services, resp, body)
|
||||
return resp, body['services']
|
||||
|
||||
def enable_service(self, host_name, binary):
|
||||
|
@ -17,6 +17,7 @@ import json
|
||||
import time
|
||||
import urllib
|
||||
|
||||
from tempest.api_schema.compute.v2 import volumes as schema
|
||||
from tempest.common import rest_client
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
@ -58,6 +59,7 @@ class VolumesExtensionsClientJSON(rest_client.RestClient):
|
||||
url = "os-volumes/%s" % str(volume_id)
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.get_volume, resp, body)
|
||||
return resp, body['volume']
|
||||
|
||||
def create_volume(self, size, **kwargs):
|
||||
|
@ -17,6 +17,7 @@
|
||||
import json
|
||||
import urllib
|
||||
|
||||
from tempest.api_schema.compute import services as schema
|
||||
from tempest.common import rest_client
|
||||
from tempest import config
|
||||
|
||||
@ -36,6 +37,7 @@ class ServicesV3ClientJSON(rest_client.RestClient):
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_services, resp, body)
|
||||
return resp, body['services']
|
||||
|
||||
def enable_service(self, host_name, binary):
|
||||
|
Loading…
x
Reference in New Issue
Block a user