Fixes failing API Tests
Speed up failure state detection Fixed test_list_services_multiple_page Fixed invalid exception thrown Ignore flavor creation tests when config setting is set to False Added negative test for origin with duplicate rules Removed falcon validators Refactored Service and Flavor Schema Validation functions Fixed issue with duplicate request_url rules for origin, caching Change-Id: I83ee6f88e37c1b9033df20ed46dcc7f650d71202 Closes-Bug: 1394720
This commit is contained in:
parent
713c8a93d6
commit
8f9f33c5e0
|
@ -24,14 +24,13 @@ except ImportError:
|
|||
use_uwsgi = False
|
||||
|
||||
import jsonpatch
|
||||
import jsonschema
|
||||
|
||||
from poppy.common import errors
|
||||
from poppy.manager import base
|
||||
from poppy.model import service
|
||||
from poppy.openstack.common import log
|
||||
from poppy.transport.validators import helpers as validators
|
||||
from poppy.transport.validators.schemas import service as service_schema
|
||||
from poppy.transport.validators.stoplight import exceptions
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
@ -81,7 +80,7 @@ class DefaultServicesController(base.ServicesController):
|
|||
|
||||
:param project_id
|
||||
:param service_obj
|
||||
:raises LoookupError, ValueError
|
||||
:raises LookupError, ValueError
|
||||
"""
|
||||
try:
|
||||
flavor = self.flavor_controller.get(service_obj.flavor_id)
|
||||
|
@ -127,6 +126,7 @@ class DefaultServicesController(base.ServicesController):
|
|||
:param project_id
|
||||
:param service_id
|
||||
:param service_updates
|
||||
:raises LookupError, ValueError
|
||||
"""
|
||||
# get the current service object
|
||||
try:
|
||||
|
@ -140,7 +140,7 @@ class DefaultServicesController(base.ServicesController):
|
|||
|
||||
service_old_json = json.loads(json.dumps(service_old.to_dict()))
|
||||
|
||||
# remove fileds that cannot be part of PATCH
|
||||
# remove fields that cannot be part of PATCH
|
||||
del service_old_json['service_id']
|
||||
del service_old_json['status']
|
||||
del service_old_json['provider_details']
|
||||
|
@ -149,21 +149,16 @@ class DefaultServicesController(base.ServicesController):
|
|||
service_old_json, service_updates)
|
||||
|
||||
# validate the updates
|
||||
patch_schema = service_schema.ServiceSchema.get_schema("service",
|
||||
"POST")
|
||||
errors_list = list(
|
||||
jsonschema.Draft3Validator(patch_schema).iter_errors(
|
||||
service_new_json))
|
||||
schema = service_schema.ServiceSchema.get_schema("service", "POST")
|
||||
validators.is_valid_service_configuration(service_new_json, schema)
|
||||
|
||||
if len(errors_list) > 0:
|
||||
details = dict(errors=[{
|
||||
'message': '-'.join([
|
||||
"[%s]" % "][".join(repr(p) for p in error.path),
|
||||
str(getattr(error, "message", error))
|
||||
])}
|
||||
for error in errors_list])
|
||||
raise exceptions.ValidationFailed(json.dumps(details))
|
||||
try:
|
||||
self.flavor_controller.get(service_new_json['flavor_id'])
|
||||
# raise a lookup error if the flavor is not found
|
||||
except LookupError as e:
|
||||
raise e
|
||||
|
||||
# must be valid, carry on
|
||||
service_new_json['service_id'] = service_old.service_id
|
||||
service_new = service.Service.init_from_dict(service_new_json)
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ class FlavorsController(base.Controller, hooks.HookController):
|
|||
@pecan.expose('json')
|
||||
@decorators.validate(
|
||||
request=rule.Rule(
|
||||
helpers.json_matches_schema(
|
||||
helpers.json_matches_flavor_schema(
|
||||
schema.FlavorSchema.get_schema("flavor", "POST")),
|
||||
helpers.abort_with_message,
|
||||
stoplight_helpers.pecan_getter))
|
||||
|
|
|
@ -155,7 +155,7 @@ class ServicesController(base.Controller, hooks.HookController):
|
|||
@pecan.expose('json')
|
||||
@decorators.validate(
|
||||
request=rule.Rule(
|
||||
helpers.json_matches_schema(
|
||||
helpers.json_matches_service_schema(
|
||||
service.ServiceSchema.get_schema("service", "POST")),
|
||||
helpers.abort_with_message,
|
||||
stoplight_helpers.pecan_getter))
|
||||
|
@ -198,7 +198,7 @@ class ServicesController(base.Controller, hooks.HookController):
|
|||
helpers.is_valid_service_id(),
|
||||
helpers.abort_with_message),
|
||||
request=rule.Rule(
|
||||
helpers.json_matches_schema(
|
||||
helpers.json_matches_service_schema(
|
||||
service.ServiceSchema.get_schema("service", "PATCH")),
|
||||
helpers.abort_with_message,
|
||||
stoplight_helpers.pecan_getter))
|
||||
|
@ -216,11 +216,13 @@ class ServicesController(base.Controller, hooks.HookController):
|
|||
self.project_id, service_id, service_updates)
|
||||
except exceptions.ValidationFailed as e:
|
||||
pecan.abort(400, detail=str(e))
|
||||
except ValueError as e:
|
||||
except LookupError as e: # error handler for no flavor
|
||||
pecan.abort(400, detail=str(e))
|
||||
except ValueError as e: # error handler for existing service name
|
||||
pecan.abort(400, detail=str(e))
|
||||
except errors.ServiceNotFound as e:
|
||||
pecan.abort(404, detail=str(e))
|
||||
except errors.ServiceStatusNotDeployed as e:
|
||||
except errors.ServiceStatusNeitherDeployedNorFailed as e:
|
||||
pecan.abort(400, detail=str(e))
|
||||
except Exception as e:
|
||||
pecan.abort(400, detail=str(e))
|
||||
|
|
|
@ -13,15 +13,10 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import collections
|
||||
import functools
|
||||
import json
|
||||
import uuid
|
||||
|
||||
try:
|
||||
import falcon
|
||||
except ImportError:
|
||||
from poppy.transport.validators import fake_falcon as falcon
|
||||
import jsonschema
|
||||
import pecan
|
||||
|
||||
|
@ -37,50 +32,6 @@ def req_accepts_json_pecan(request, desired_content_type='application/json'):
|
|||
raise exceptions.ValidationFailed('Invalid Accept Header')
|
||||
|
||||
|
||||
def require_accepts_json_falcon(req, resp, params=None):
|
||||
"""Raises an exception if the request does not accept JSON
|
||||
|
||||
Meant to be used as a `before` hook.
|
||||
|
||||
:param req: request sent
|
||||
:type req: falcon.request.Request
|
||||
:param resp: response object to return
|
||||
:type resp: falcon.response.Response
|
||||
:param params: additional parameters passed to responders
|
||||
:type params: dict
|
||||
:rtype: None
|
||||
:raises: falcon.HTTPNotAcceptable
|
||||
"""
|
||||
if not req.client_accepts('application/json'):
|
||||
raise falcon.HTTPNotAcceptable(
|
||||
u"""
|
||||
Endpoint only serves `application/json`; specify client-side"""
|
||||
'media type support with the "Accept" header.',
|
||||
href=u'http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html',
|
||||
href_text=u'14.1 Accept, Hypertext Transfer Protocol -- HTTP/1.1')
|
||||
|
||||
|
||||
class DummyResponse(object):
|
||||
pass
|
||||
|
||||
|
||||
def custom_abort_falcon(error_info=None):
|
||||
"""Error_handler for with_schema
|
||||
|
||||
Meant to be used with falcon transport.
|
||||
|
||||
param errors: a list of validation exceptions
|
||||
"""
|
||||
ret = DummyResponse()
|
||||
ret.code = 400
|
||||
if not isinstance(error_info, collections.Iterable):
|
||||
error_info = [error_info]
|
||||
details = dict(errors=[{'message': str(getattr(error, "message", error))}
|
||||
for error in error_info])
|
||||
ret.message = json.dumps(details)
|
||||
return ret
|
||||
|
||||
|
||||
def custom_abort_pecan(errors_info):
|
||||
"""Error_handler for with_schema
|
||||
|
||||
|
@ -98,32 +49,6 @@ def custom_abort_pecan(errors_info):
|
|||
'Content-Type': "application/json"})
|
||||
|
||||
|
||||
def with_schema_falcon(request, schema=None):
|
||||
"""Use to decorate a falcon style controller route
|
||||
|
||||
:param request: A falcon request
|
||||
:param schema: a Json schema to validate against
|
||||
"""
|
||||
validation_failed = False
|
||||
v_error = None
|
||||
if schema is not None:
|
||||
errors_list = []
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
errors_list = list(
|
||||
jsonschema.Draft3Validator(schema).iter_errors(data))
|
||||
except ValueError:
|
||||
validation_failed = True
|
||||
v_error = ["Invalid JSON body in request"]
|
||||
|
||||
if len(errors_list) > 0:
|
||||
validation_failed = True
|
||||
v_error = errors_list
|
||||
|
||||
if validation_failed:
|
||||
raise exceptions.ValidationFailed(repr(v_error))
|
||||
|
||||
|
||||
def with_schema_pecan(request, schema=None, handler=custom_abort_pecan,
|
||||
**kwargs):
|
||||
"""Decorate a Pecan/Flask style controller form validation.
|
||||
|
@ -165,17 +90,40 @@ def with_schema_pecan(request, schema=None, handler=custom_abort_pecan,
|
|||
return decorator
|
||||
|
||||
|
||||
def json_matches_schema_inner(request, schema=None):
|
||||
errors_list = []
|
||||
def json_matches_service_schema(input_schema):
|
||||
return functools.partial(
|
||||
json_matches_service_schema_inner,
|
||||
schema=input_schema)
|
||||
|
||||
|
||||
def json_matches_service_schema_inner(request, schema=None):
|
||||
try:
|
||||
data = json.loads(request.body.decode('utf-8'))
|
||||
except ValueError:
|
||||
raise exceptions.ValidationFailed('Invalid JSON string')
|
||||
|
||||
is_valid_service_configuration(data, schema)
|
||||
|
||||
|
||||
def json_matches_flavor_schema(input_schema):
|
||||
return functools.partial(
|
||||
json_matches_flavor_schema_inner,
|
||||
schema=input_schema)
|
||||
|
||||
|
||||
def json_matches_flavor_schema_inner(request, schema=None):
|
||||
try:
|
||||
data = json.loads(request.body.decode('utf-8'))
|
||||
except ValueError:
|
||||
raise exceptions.ValidationFailed('Invalid JSON string')
|
||||
|
||||
is_valid_flavor_configuration(data, schema)
|
||||
|
||||
|
||||
def is_valid_service_configuration(service, schema):
|
||||
if schema is not None:
|
||||
errors_list = list(
|
||||
jsonschema.Draft3Validator(schema).iter_errors(data))
|
||||
jsonschema.Draft3Validator(schema).iter_errors(service))
|
||||
|
||||
if len(errors_list) > 0:
|
||||
details = dict(errors=[{
|
||||
|
@ -185,14 +133,36 @@ def json_matches_schema_inner(request, schema=None):
|
|||
])}
|
||||
for error in errors_list])
|
||||
raise exceptions.ValidationFailed(json.dumps(details))
|
||||
else:
|
||||
return
|
||||
|
||||
# Schema structure is valid. Check the functional rules.
|
||||
|
||||
def json_matches_schema(input_schema):
|
||||
return functools.partial(
|
||||
json_matches_schema_inner,
|
||||
schema=input_schema)
|
||||
# 1. origin rules must be unique
|
||||
if 'origins' in service:
|
||||
origin_rules = []
|
||||
for origin in service['origins']:
|
||||
if 'rules' in origin:
|
||||
for rule in origin['rules']:
|
||||
request_url = rule['request_url']
|
||||
if request_url in origin_rules:
|
||||
raise exceptions.ValidationFailed(
|
||||
'Origins - the request_url must be unique')
|
||||
else:
|
||||
origin_rules.append(request_url)
|
||||
|
||||
# 2. caching rules must be unique
|
||||
if 'caching' in service:
|
||||
caching_rules = []
|
||||
for caching in service['caching']:
|
||||
if 'rules' in caching:
|
||||
for rule in caching['rules']:
|
||||
request_url = rule['request_url']
|
||||
if request_url in caching_rules:
|
||||
raise exceptions.ValidationFailed(
|
||||
'Caching Rules - the request_url must be unique')
|
||||
else:
|
||||
caching_rules.append(request_url)
|
||||
|
||||
return
|
||||
|
||||
|
||||
@decorators.validation_function
|
||||
|
@ -203,6 +173,23 @@ def is_valid_service_id(service_id):
|
|||
raise exceptions.ValidationFailed('Invalid service id')
|
||||
|
||||
|
||||
def is_valid_flavor_configuration(flavor, schema):
|
||||
if schema is not None:
|
||||
errors_list = list(
|
||||
jsonschema.Draft3Validator(schema).iter_errors(flavor))
|
||||
|
||||
if len(errors_list) > 0:
|
||||
details = dict(errors=[{
|
||||
'message': '-'.join([
|
||||
"[%s]" % "][".join(repr(p) for p in error.path),
|
||||
str(getattr(error, "message", error))
|
||||
])}
|
||||
for error in errors_list])
|
||||
raise exceptions.ValidationFailed(json.dumps(details))
|
||||
|
||||
return
|
||||
|
||||
|
||||
@decorators.validation_function
|
||||
def is_valid_flavor_id(flavor_id):
|
||||
pass
|
||||
|
@ -211,3 +198,7 @@ def is_valid_flavor_id(flavor_id):
|
|||
def abort_with_message(error_info):
|
||||
pecan.abort(400, detail=getattr(error_info, "message", ""),
|
||||
headers={'Content-Type': "application/json"})
|
||||
|
||||
|
||||
class DummyResponse(object):
|
||||
pass
|
||||
|
|
|
@ -258,24 +258,8 @@ class ServiceSchema(schema_base.SchemaBase):
|
|||
'referrer': {
|
||||
'type': 'string',
|
||||
'minLength': 3,
|
||||
'maxLength': 1024},
|
||||
'request_url': {
|
||||
'type': 'string',
|
||||
'minLength': 3,
|
||||
'maxLength': 1024},
|
||||
'http_host': {
|
||||
'type': 'string',
|
||||
'minLength': 3,
|
||||
'maxLength': 256},
|
||||
'client_ip': {
|
||||
'type': 'string'},
|
||||
'http_method': {
|
||||
'type': 'string',
|
||||
'enum': [
|
||||
'GET',
|
||||
'PUT',
|
||||
'POST',
|
||||
'PATCH']}}},
|
||||
'maxLength': 1024}
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -38,12 +38,22 @@ The API tests require cassandra running in your local machine, in order to
|
|||
run via tox. It is assumed you already have the Cassandra instance up &
|
||||
running locally. You can make the API tests part of tox, by overriding the
|
||||
default positional argument in tox.ini::
|
||||
example : tox -- --exclude=None
|
||||
|
||||
tox -- --exclude=None
|
||||
|
||||
Alternatively, you can run tox with docker containers running Cassandra::
|
||||
|
||||
This will require docker (or boot2docker for MacOSX) to already be installed on the system.
|
||||
Dont forget to update your ~/.poppy/tests.conf to point to your docker ip.
|
||||
This will require docker (or boot2docker for MacOSX) to already be installed on the system.
|
||||
It will use the fig_local.yaml file to mount your local folder, as described in the Docker folder.
|
||||
Also, dont forget to update your ~/.poppy/tests.conf to point to your docker ip address.
|
||||
|
||||
|
||||
Example 1: Run all API tests::
|
||||
|
||||
tox -e apidocker api
|
||||
|
||||
Example 2: Run a particular test function::
|
||||
|
||||
tox -e apidocker api/services/test_services.py:TestCreateService -- -m test_create_service_positive
|
||||
|
||||
example : tox -e apidocker
|
||||
|
||||
|
|
|
@ -33,6 +33,9 @@ class TestCreateFlavors(base.TestBase):
|
|||
|
||||
@ddt.file_data('data_create_flavor.json')
|
||||
def test_create_flavor(self, test_data):
|
||||
if self.test_config.generate_flavors is False:
|
||||
self.skipTest(
|
||||
'Flavor Generation is currently disabled in configuration')
|
||||
|
||||
provider_list = test_data['provider_list']
|
||||
limits = test_data['limits']
|
||||
|
@ -61,6 +64,10 @@ class TestCreateFlavors(base.TestBase):
|
|||
|
||||
@ddt.file_data('data_create_flavor_negative.json')
|
||||
def test_create_flavor_negative_tests(self, test_data):
|
||||
if self.test_config.generate_flavors is False:
|
||||
self.skipTest(
|
||||
'Flavor Generation is currently disabled in configuration')
|
||||
|
||||
if 'skip_test' in test_data:
|
||||
self.skipTest('Not Implemented - bp# post-flavors-error-handling')
|
||||
|
||||
|
@ -78,6 +85,9 @@ class TestCreateFlavors(base.TestBase):
|
|||
self.assertEqual(resp.status_code, 400)
|
||||
|
||||
def test_delete_flavor(self):
|
||||
if self.test_config.generate_flavors is False:
|
||||
self.skipTest(
|
||||
'Flavor Generation is currently disabled in configuration')
|
||||
|
||||
resp = self.client.delete_flavor(flavor_id=self.flavor_id)
|
||||
self.assertEqual(resp.status_code, 204)
|
||||
|
@ -86,7 +96,8 @@ class TestCreateFlavors(base.TestBase):
|
|||
self.assertEqual(resp.status_code, 404)
|
||||
|
||||
def tearDown(self):
|
||||
self.client.delete_flavor(flavor_id=self.flavor_id)
|
||||
if self.test_config.generate_flavors:
|
||||
self.client.delete_flavor(flavor_id=self.flavor_id)
|
||||
super(TestCreateFlavors, self).tearDown()
|
||||
|
||||
|
||||
|
|
|
@ -127,18 +127,33 @@
|
|||
"service_name": "many_origin_list",
|
||||
"domain_list": [{"domain": "mywebsite.com"},
|
||||
{"domain": "blog.mywebsite.com"}],
|
||||
"origin_list": [
|
||||
{"origin": "origin1"},
|
||||
{"origin": "origin2", "rules": [{"name" : "index", "request_url" : "/index1.htm"}]},
|
||||
{"origin": "origin3", "rules": [{"name" : "index", "request_url" : "/index2.htm"}]},
|
||||
{"origin": "origin4", "rules": [{"name" : "index", "request_url" : "/index3.htm"}]},
|
||||
{"origin": "origin5", "rules": [{"name" : "index", "request_url" : "/index4.htm"}]},
|
||||
{"origin": "origin6", "rules": [{"name" : "index", "request_url" : "/index5.htm"}]},
|
||||
{"origin": "origin7", "rules": [{"name" : "index", "request_url" : "/index6.htm"}]},
|
||||
{"origin": "origin8", "rules": [{"name" : "index", "request_url" : "/index7.htm"}]},
|
||||
{"origin": "origin9", "rules": [{"name" : "index", "request_url" : "/index8.htm"}]},
|
||||
{"origin": "origin10", "rules": [{"name" : "index", "request_url" : "/index9.htm"}]},
|
||||
{"origin": "origin11", "rules": [{"name" : "index", "request_url" : "/index10.htm"}]}
|
||||
],
|
||||
"caching_list": [{"name": "default", "ttl": 3600},
|
||||
{"name": "home",
|
||||
"ttl": 1200,
|
||||
"rules": [{"name" : "index",
|
||||
"request_url" : "/index.htm"}]}],
|
||||
"restrictions_list": []
|
||||
},
|
||||
"duplicate_origin_rule": {
|
||||
"service_name": "many_origin_list",
|
||||
"domain_list": [{"domain": "mywebsite.com"}],
|
||||
"origin_list": [
|
||||
{"origin": "origin1"},
|
||||
{"origin": "origin2", "rules": [{"name" : "index", "request_url" : "/index.htm"}]},
|
||||
{"origin": "origin3", "rules": [{"name" : "index", "request_url" : "/index.htm"}]},
|
||||
{"origin": "origin4", "rules": [{"name" : "index", "request_url" : "/index.htm"}]},
|
||||
{"origin": "origin5", "rules": [{"name" : "index", "request_url" : "/index.htm"}]},
|
||||
{"origin": "origin6", "rules": [{"name" : "index", "request_url" : "/index.htm"}]},
|
||||
{"origin": "origin7", "rules": [{"name" : "index", "request_url" : "/index.htm"}]},
|
||||
{"origin": "origin8", "rules": [{"name" : "index", "request_url" : "/index.htm"}]},
|
||||
{"origin": "origin9", "rules": [{"name" : "index", "request_url" : "/index.htm"}]},
|
||||
{"origin": "origin10", "rules": [{"name" : "index", "request_url" : "/index.htm"}]},
|
||||
{"origin": "origin11", "rules": [{"name" : "index", "request_url" : "/index.htm"}]}
|
||||
{"origin": "origin3", "rules": [{"name" : "index", "request_url" : "/index.htm"}]}
|
||||
],
|
||||
"caching_list": [{"name": "default", "ttl": 3600},
|
||||
{"name": "home",
|
||||
|
@ -241,6 +256,24 @@
|
|||
"request_url" : "/index.htm"}]}],
|
||||
"restrictions_list": []
|
||||
},
|
||||
"caching_duplicate_rule": {
|
||||
"service_name": "my_service_name",
|
||||
"domain_list": [{"domain": "mywebsite.com", "protocol": "http"},
|
||||
{"domain": "blog.mywebsite.com", "protocol": "http"}],
|
||||
"origin_list": [{"origin": "mywebsite1.com",
|
||||
"port": 443,
|
||||
"ssl": false}],
|
||||
"caching_list": [{"name": "default", "ttl": 3600},
|
||||
{"name": "home",
|
||||
"ttl": 20,
|
||||
"rules": [{"name" : "index",
|
||||
"request_url" : "/index.htm"}]},
|
||||
{"name": "images",
|
||||
"ttl": 30,
|
||||
"rules": [{"name" : "images",
|
||||
"request_url" : "/index.htm"}]}],
|
||||
"restrictions_list": []
|
||||
},
|
||||
"restrictions_empty_item": {
|
||||
"service_name": "my_service_name",
|
||||
"domain_list": [{"domain": "mywebsite.com", "protocol": "http"},
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
{"op": "add",
|
||||
"path": "/origins/2",
|
||||
"value": {"origin": "4.2.5.4", "port": 80, "ssl": false,
|
||||
"rules": [{"name" : "origin", "request_url" : "/origin.htm"}]}
|
||||
"rules": [{"name" : "origin", "request_url" : "/origin2.htm"}]}
|
||||
}
|
||||
],
|
||||
"add_and_remove_origin": [
|
||||
|
@ -71,7 +71,7 @@
|
|||
{"op": "add",
|
||||
"path": "/caching/-",
|
||||
"value": {"name": "cache_name", "ttl": 111,
|
||||
"rules": [{"name" : "index","request_url" : "/index.htm"}]}}
|
||||
"rules": [{"name" : "index","request_url" : "/cats.jpg"}]}}
|
||||
],
|
||||
"replace_caching": [
|
||||
{"op": "replace",
|
||||
|
@ -86,7 +86,7 @@
|
|||
{"op": "add",
|
||||
"path": "/caching/-",
|
||||
"value": {"name": "cache_name", "ttl": 111,
|
||||
"rules": [{"name" : "index","request_url" : "/index.htm"}]}
|
||||
"rules": [{"name" : "index","request_url" : "/dogs.jpg"}]}
|
||||
},
|
||||
{"op": "add",
|
||||
"path": "/caching/-",
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
"update_name": {
|
||||
"name": "my_new_name"
|
||||
},
|
||||
"update_domain_list": {
|
||||
"domain_list": [{"domain": "mynewwebsite.com"},
|
||||
{"domain": "newblog.mywebsite.com"}]
|
||||
},
|
||||
"update_origin_list": {
|
||||
"origin_list": [{"origin": "www.neworigin.com",
|
||||
"port": 443,
|
||||
"ssl": false}]
|
||||
},
|
||||
"update_flavorRef": {
|
||||
"flavorRef": "premium"
|
||||
},
|
||||
"update_caching_list": {
|
||||
"caching_list": [{"newname": "default", "ttl": 3600},
|
||||
{"name": "home",
|
||||
"ttl": 1200,
|
||||
"rules": [{"name" : "index",
|
||||
"request_url" : "/index.htm"}]}]
|
||||
}
|
||||
}
|
|
@ -4,6 +4,18 @@
|
|||
"path": "/origins/-",
|
||||
"value": {"origin": "1.2.3.4", "port": 80}}
|
||||
],
|
||||
"add_duplicate_origin_rule": [
|
||||
{"op": "add",
|
||||
"path": "/origins/1",
|
||||
"value": {"origin": "1.2.3.4", "port": 80, "ssl": false,
|
||||
"rules": [{"name" : "origin", "request_url" : "/origin.htm"}]}
|
||||
},
|
||||
{"op": "add",
|
||||
"path": "/origins/2",
|
||||
"value": {"origin": "4.2.5.4", "port": 80, "ssl": false,
|
||||
"rules": [{"name" : "origin", "request_url" : "/origin.htm"}]}
|
||||
}
|
||||
],
|
||||
"empty_list": [],
|
||||
"empty_dict":[{}],
|
||||
"remove_name": [
|
||||
|
@ -57,5 +69,14 @@
|
|||
{"op": "replace",
|
||||
"path": "/domains/0",
|
||||
"value": {}}
|
||||
],
|
||||
"remove_flavor": [
|
||||
{"op": "remove",
|
||||
"path": "/flavor_id"}
|
||||
],
|
||||
"replace_flavor_nonexistant": [
|
||||
{"op": "replace",
|
||||
"path": "/flavor_id",
|
||||
"value": "nonexistant"}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
import cgi
|
||||
import time
|
||||
import urlparse
|
||||
import uuid
|
||||
|
||||
import ddt
|
||||
|
@ -276,14 +277,26 @@ class TestListServices(base.TestBase):
|
|||
self.assertEqual(len(body['services']), limit)
|
||||
self.assertSchema(body, services.list_services)
|
||||
|
||||
def test_list_services_multiple_page(self):
|
||||
self.service_list = [self._create_test_service() for _ in range(15)]
|
||||
resp = self.client.list_services()
|
||||
@ddt.data(3)
|
||||
def test_list_services_multiple_page(self, num):
|
||||
self.service_list = [self._create_test_service() for _ in range(num)]
|
||||
url_param = {'limit': num - 1}
|
||||
resp = self.client.list_services(param=url_param)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
body = resp.json()
|
||||
# TODO(malini): remove hard coded value with configurable value
|
||||
self.assertEqual(len(body['services']), 10)
|
||||
self.assertEqual(len(body['services']), num - 1)
|
||||
self.assertSchema(body, services.list_services)
|
||||
|
||||
# get second page
|
||||
next_page_uri = urlparse.urlparse(body['links'][0]['href'])
|
||||
marker = urlparse.parse_qs(next_page_uri.query)['marker'][0]
|
||||
url_param = {'marker': marker}
|
||||
resp = self.client.list_services(param=url_param)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
body = resp.json()
|
||||
self.assertEqual(len(body['services']), 1)
|
||||
self.assertSchema(body, services.list_services)
|
||||
|
||||
@attrib.attr('smoke')
|
||||
|
@ -409,7 +422,8 @@ class TestServiceActions(base.TestBase):
|
|||
# TODO(malini): Add test to verify that a failed service can be
|
||||
# deleted.
|
||||
# Placeholder till we figure out how to create provider side failure.
|
||||
pass
|
||||
self.skipTest(
|
||||
'Until we figure out how to create provider side failure.')
|
||||
|
||||
def test_get_service(self):
|
||||
resp = self.client.get_service(location=self.service_url)
|
||||
|
@ -437,7 +451,8 @@ class TestServiceActions(base.TestBase):
|
|||
# TODO(malini): Add test to verify that failed service will return
|
||||
# status 'failed' on get_service with error message from the provider.
|
||||
# Placeholder till we figure out how to create provider side failure.
|
||||
pass
|
||||
self.skipTest(
|
||||
'Until we figure out how to create provider side failure.')
|
||||
|
||||
def tearDown(self):
|
||||
self.client.delete_service(location=self.service_url)
|
||||
|
@ -471,7 +486,7 @@ class TestServicePatch(base.TestBase):
|
|||
self.restrictions_list = [
|
||||
{"name": "website only",
|
||||
"rules": [{"name": "mywebsite.com",
|
||||
"http_host": "www.mywebsite.com"}]}]
|
||||
"referrer": "www.mywebsite.com"}]}]
|
||||
|
||||
resp = self.client.create_service(
|
||||
service_name=self.service_name,
|
||||
|
@ -483,7 +498,7 @@ class TestServicePatch(base.TestBase):
|
|||
|
||||
self.service_url = resp.headers["location"]
|
||||
|
||||
self.expected_service_details = {
|
||||
self.original_service_details = {
|
||||
"name": self.service_name,
|
||||
"domains": self.domain_list,
|
||||
"origins": self.origin_list,
|
||||
|
@ -494,9 +509,15 @@ class TestServicePatch(base.TestBase):
|
|||
self.client.wait_for_service_status(
|
||||
location=self.service_url,
|
||||
status='deployed',
|
||||
abort_on_status='failed',
|
||||
retry_interval=self.test_config.status_check_retry_interval,
|
||||
retry_timeout=self.test_config.status_check_retry_timeout)
|
||||
|
||||
resp = self.client.get_service(location=self.service_url)
|
||||
body = resp.json()
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual(body['status'], 'deployed')
|
||||
|
||||
def _assert_service_details(self, actual_response, expected_response):
|
||||
self.assertEqual(actual_response['name'],
|
||||
expected_response['name'])
|
||||
|
@ -512,7 +533,6 @@ class TestServicePatch(base.TestBase):
|
|||
expected_response['flavor_id'])
|
||||
|
||||
@ddt.file_data('data_patch_service.json')
|
||||
# @ddt.file_data('data_patch_subset.json')
|
||||
def test_patch_service(self, test_data):
|
||||
|
||||
for item in test_data:
|
||||
|
@ -523,23 +543,22 @@ class TestServicePatch(base.TestBase):
|
|||
item['value']['domain'] = str(uuid.uuid1()) + '.com'
|
||||
|
||||
patch = jsonpatch.JsonPatch(test_data)
|
||||
expected_service_details = patch.apply(self.expected_service_details)
|
||||
expected_service_details = patch.apply(self.original_service_details)
|
||||
|
||||
resp = self.client.patch_service(location=self.service_url,
|
||||
request_body=test_data)
|
||||
self.assertEqual(resp.status_code, 202)
|
||||
|
||||
resp = self.client.get_service(location=self.service_url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
self.client.wait_for_service_status(
|
||||
location=self.service_url,
|
||||
status='deployed',
|
||||
abort_on_status='failed',
|
||||
retry_interval=self.test_config.status_check_retry_interval,
|
||||
retry_timeout=self.test_config.status_check_retry_timeout)
|
||||
|
||||
resp = self.client.get_service(location=self.service_url)
|
||||
body = resp.json()
|
||||
self.assertEqual(body['status'], 'deployed')
|
||||
|
||||
self._assert_service_details(body, expected_service_details)
|
||||
|
||||
|
@ -550,22 +569,14 @@ class TestServicePatch(base.TestBase):
|
|||
request_body=test_data)
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
|
||||
# nothing should have changed.
|
||||
resp = self.client.get_service(location=self.service_url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
body = resp.json()
|
||||
self.assertEqual(body['status'], 'deployed')
|
||||
for item in self.domain_list:
|
||||
if 'protocol' not in item:
|
||||
item['protocol'] = 'http'
|
||||
self.assertEqual(sorted(self.domain_list), sorted(body['domains']))
|
||||
|
||||
for item in self.origin_list:
|
||||
if 'rules' not in item:
|
||||
item[u'rules'] = []
|
||||
self.assertEqual(sorted(self.origin_list), sorted(body['origins']))
|
||||
# TODO(malini): Uncomment below after caching is implemented.
|
||||
self.assertEqual(sorted(self.caching_list), sorted(body['caching']))
|
||||
self._assert_service_details(body, self.original_service_details)
|
||||
|
||||
def tearDown(self):
|
||||
self.client.delete_service(location=self.service_url)
|
||||
|
|
|
@ -210,7 +210,9 @@ class PoppyClient(client.AutoMarshallingHTTPClient):
|
|||
|
||||
return self.request('DELETE', url)
|
||||
|
||||
def wait_for_service_status(self, location, status, retry_interval=2,
|
||||
def wait_for_service_status(self, location, status,
|
||||
abort_on_status=None,
|
||||
retry_interval=2,
|
||||
retry_timeout=30):
|
||||
"""Waits for a service to reach a given status."""
|
||||
current_status = ''
|
||||
|
@ -223,6 +225,11 @@ class PoppyClient(client.AutoMarshallingHTTPClient):
|
|||
current_status = body['status']
|
||||
if (current_status == status):
|
||||
return
|
||||
|
||||
if abort_on_status is not None:
|
||||
if current_status == abort_on_status:
|
||||
return
|
||||
|
||||
current_time = int(time.time())
|
||||
if current_time > stop_time:
|
||||
return
|
||||
|
|
|
@ -104,7 +104,7 @@ list_services_link = {
|
|||
'href': {'type': 'string',
|
||||
'pattern':
|
||||
'(https?)(:/{1,3})([a-z0-9\.\-:]{1,400})'
|
||||
'(/v1\.0/([a-z0-9]{1,400})/services'
|
||||
'(/v1\.0/([a-z0-9]{1,400}/)?services'
|
||||
'\?marker=)(' + uuid4 + ')'
|
||||
'(&limit=)([1-9]|1[0-9])'}},
|
||||
'required': ['rel', 'href'],
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
# Copyright (c) 2014 Rackspace, Inc.
|
||||
#
|
||||
# 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 functools
|
||||
|
||||
from poppy.common import errors
|
||||
from poppy.transport.validators import helpers
|
||||
from poppy.transport.validators.schemas import service
|
||||
from poppy.transport.validators.stoplight import decorators
|
||||
from poppy.transport.validators.stoplight import exceptions
|
||||
from poppy.transport.validators.stoplight import rule
|
||||
from tests.functional.transport.validator import base
|
||||
|
||||
|
||||
testing_schema = service.ServiceSchema.get_schema("service", "POST")
|
||||
request_fit_schema = functools.partial(
|
||||
helpers.with_schema_falcon,
|
||||
schema=testing_schema)
|
||||
|
||||
|
||||
class DummyFalconEndpoint(object):
|
||||
# falcon style endpoint
|
||||
|
||||
@decorators.validate(
|
||||
request=rule.Rule(
|
||||
request_fit_schema,
|
||||
lambda error_info: base.abort(404)
|
||||
),
|
||||
response=rule.Rule(
|
||||
base.is_response(),
|
||||
lambda error_info: base.abort(404))
|
||||
)
|
||||
def get_falcon_style(self, request, response):
|
||||
return "Hello, World!"
|
||||
|
||||
@decorators.validate(
|
||||
request=rule.Rule(request_fit_schema,
|
||||
helpers.custom_abort_falcon),
|
||||
response=rule.Rule(base.is_response(),
|
||||
helpers.custom_abort_falcon)
|
||||
)
|
||||
def get_falcon_style_custom_abort(self, request, response):
|
||||
return "Hello, World!"
|
||||
|
||||
|
||||
class TestValidationFunctionsFalcon(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.ep = DummyFalconEndpoint()
|
||||
super(TestValidationFunctionsFalcon, self).setUp()
|
||||
|
||||
def test_with_schema(self):
|
||||
self.assertEqual(
|
||||
helpers.with_schema_falcon(
|
||||
base.fake_request_good,
|
||||
schema=testing_schema),
|
||||
None)
|
||||
with self.assertRaisesRegexp(exceptions.ValidationFailed, "domain"):
|
||||
helpers.with_schema_falcon(
|
||||
base.fake_request_bad_missing_domain,
|
||||
schema=testing_schema)
|
||||
with self.assertRaisesRegexp(exceptions.ValidationFailed,
|
||||
"Invalid JSON body in request"):
|
||||
helpers.with_schema_falcon(
|
||||
base.fake_request_bad_invalid_json_body,
|
||||
schema=testing_schema)
|
||||
|
||||
def test_partial_with_schema(self):
|
||||
self.assertEqual(request_fit_schema(base.fake_request_good), None)
|
||||
with self.assertRaisesRegexp(exceptions.ValidationFailed, "domain"):
|
||||
request_fit_schema(base.fake_request_bad_missing_domain)
|
||||
with self.assertRaisesRegexp(exceptions.ValidationFailed,
|
||||
"Invalid JSON body in request"):
|
||||
request_fit_schema(base.fake_request_bad_invalid_json_body)
|
||||
|
||||
def test_schema_base(self):
|
||||
with self.assertRaises(errors.InvalidResourceName):
|
||||
service.ServiceSchema.get_schema("invalid_resource", "PUT")
|
||||
with self.assertRaises(errors.InvalidOperation):
|
||||
service.ServiceSchema.get_schema("service", "INVALID_HTTP_VERB")
|
||||
|
||||
def test_accept_header(self):
|
||||
req = base.DummyRequestWithInvalidHeader()
|
||||
resp = helpers.DummyResponse()
|
||||
|
||||
with self.assertRaises(helpers.falcon.HTTPNotAcceptable):
|
||||
helpers.require_accepts_json_falcon(req, resp)
|
||||
|
||||
def test_falcon_endpoint(self):
|
||||
class DummyResponse(object):
|
||||
pass
|
||||
|
||||
response = DummyResponse()
|
||||
|
||||
global error_count
|
||||
|
||||
# Try to call with good inputs
|
||||
oldcount = base.error_count
|
||||
ret = self.ep.get_falcon_style(base.fake_request_good, response)
|
||||
self.assertEqual(oldcount, base.error_count)
|
||||
self.assertEqual(
|
||||
ret,
|
||||
"Hello, World!",
|
||||
"testing not passed on endpoint: get_falcon_style with valid data")
|
||||
|
||||
# Try to call with bad inputs
|
||||
oldcount = base.error_count
|
||||
self.ep.get_falcon_style(
|
||||
base.fake_request_bad_missing_domain,
|
||||
response)
|
||||
self.assertEqual(oldcount + 1, base.error_count)
|
||||
|
||||
# Try to call with bad inputs
|
||||
self.ep.get_falcon_style_custom_abort(
|
||||
base.fake_request_bad_missing_domain,
|
||||
response)
|
Loading…
Reference in New Issue