Merge "Move verification of response attributes into service client"

This commit is contained in:
Jenkins 2014-03-15 14:06:41 +00:00 committed by Gerrit Code Review
commit 77aa8566cd
15 changed files with 144 additions and 133 deletions

View File

@ -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'])

View File

@ -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'],
},
}
}

View File

@ -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'],
},
}

View File

@ -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())

View File

@ -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'])

View File

@ -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'],

View 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']
}
}

View 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']
}
}

View File

@ -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):
"""

View File

@ -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):

View File

@ -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):

View File

@ -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):