Add support for resource_providers urls
GET a list of /resource_providers POST to /resource_providers for a new one GET, DELETE, PUT a single /resource_providers/{uuid} Uses jsonschema to validate input when creating or updating. A 'uuid' FormatChecker is added in the util module to verify the format of uuids in request bodies. Note the lengthy comment adjacent explaining its magic. The olso_context RequestContext is subclassed to allow it to be an engine_facade transaction_context_provider. A database fixture is added to the gabbi tests to create and dispose the placement database per each gabbi testsuite (i.e. each YAML file). A resource_provider_url is added to util.py: given a resource_provider object, give the url, accounting for any prefix in SCRIPT_NAME With the addition of working endpoints, basic_http.yaml is extended with more complete testing of basic http behaviors. Tests for resource_providers are in resource-provider.yaml. Change-Id: I799d839101af78b4d89aca175f647efc2b56c401 Partially-Implements: blueprint generic-resource-pools
This commit is contained in:
parent
4e923eb9a6
commit
125cfc97fb
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
|
|
||||||
from oslo_context import context
|
from oslo_context import context
|
||||||
|
from oslo_db.sqlalchemy import enginefacade
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_middleware import request_id
|
from oslo_middleware import request_id
|
||||||
import webob.dec
|
import webob.dec
|
||||||
|
@ -55,6 +56,11 @@ class NoAuthMiddleware(Middleware):
|
||||||
return self.application
|
return self.application
|
||||||
|
|
||||||
|
|
||||||
|
@enginefacade.transaction_context_provider
|
||||||
|
class RequestContext(context.RequestContext):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PlacementKeystoneContext(Middleware):
|
class PlacementKeystoneContext(Middleware):
|
||||||
"""Make a request context from keystone headers."""
|
"""Make a request context from keystone headers."""
|
||||||
|
|
||||||
|
@ -62,7 +68,7 @@ class PlacementKeystoneContext(Middleware):
|
||||||
def __call__(self, req):
|
def __call__(self, req):
|
||||||
req_id = req.environ.get(request_id.ENV_REQUEST_ID)
|
req_id = req.environ.get(request_id.ENV_REQUEST_ID)
|
||||||
|
|
||||||
ctx = context.RequestContext.from_environ(
|
ctx = RequestContext.from_environ(
|
||||||
req.environ, request_id=req_id)
|
req.environ, request_id=req_id)
|
||||||
|
|
||||||
if ctx.user is None:
|
if ctx.user is None:
|
||||||
|
|
|
@ -17,15 +17,19 @@ from oslo_middleware import request_id
|
||||||
from nova.api.openstack.placement import auth
|
from nova.api.openstack.placement import auth
|
||||||
from nova.api.openstack.placement import handler
|
from nova.api.openstack.placement import handler
|
||||||
from nova.api.openstack.placement import microversion
|
from nova.api.openstack.placement import microversion
|
||||||
|
from nova import objects
|
||||||
|
|
||||||
# TODO(cdent): register objects here as this is our startup place,
|
|
||||||
# but only once we start using them.
|
|
||||||
|
|
||||||
# TODO(cdent): NAME points to the config project being used, so for
|
# TODO(cdent): NAME points to the config project being used, so for
|
||||||
# now this is "nova" but we probably want "placement" eventually.
|
# now this is "nova" but we probably want "placement" eventually.
|
||||||
NAME = "nova"
|
NAME = "nova"
|
||||||
|
|
||||||
|
|
||||||
|
# Make sure that objects are registered for this running of the
|
||||||
|
# placement API.
|
||||||
|
objects.register_all()
|
||||||
|
|
||||||
|
|
||||||
def deploy(conf, project_name):
|
def deploy(conf, project_name):
|
||||||
"""Assemble the middleware pipeline leading to the placement app."""
|
"""Assemble the middleware pipeline leading to the placement app."""
|
||||||
if conf.auth_strategy == 'noauth2':
|
if conf.auth_strategy == 'noauth2':
|
||||||
|
|
|
@ -26,15 +26,30 @@ method.
|
||||||
import routes
|
import routes
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
|
from nova.api.openstack.placement.handlers import resource_provider
|
||||||
from nova.api.openstack.placement.handlers import root
|
from nova.api.openstack.placement.handlers import root
|
||||||
from nova.api.openstack.placement import util
|
from nova.api.openstack.placement import util
|
||||||
|
from nova import exception
|
||||||
|
|
||||||
|
|
||||||
# URLs and Handlers
|
# URLs and Handlers
|
||||||
|
# NOTE(cdent): When adding URLs here, do not use regex patterns in
|
||||||
|
# the path parameters (e.g. {uuid:[0-9a-zA-Z-]+}) as that will lead
|
||||||
|
# to 404s that are controlled outside of the individual resources
|
||||||
|
# and thus do not include specific information on the why of the 404.
|
||||||
ROUTE_DECLARATIONS = {
|
ROUTE_DECLARATIONS = {
|
||||||
'/': {
|
'/': {
|
||||||
'GET': root.home,
|
'GET': root.home,
|
||||||
},
|
},
|
||||||
|
'/resource_providers': {
|
||||||
|
'GET': resource_provider.list_resource_providers,
|
||||||
|
'POST': resource_provider.create_resource_provider
|
||||||
|
},
|
||||||
|
'/resource_providers/{uuid}': {
|
||||||
|
'GET': resource_provider.get_resource_provider,
|
||||||
|
'DELETE': resource_provider.delete_resource_provider,
|
||||||
|
'PUT': resource_provider.update_resource_provider
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,7 +71,7 @@ def dispatch(environ, start_response, mapper):
|
||||||
|
|
||||||
|
|
||||||
def handle_405(environ, start_response):
|
def handle_405(environ, start_response):
|
||||||
"""Return a 405 response as required.
|
"""Return a 405 response when method is not allowed.
|
||||||
|
|
||||||
If _methods are in routing_args, send an allow header listing
|
If _methods are in routing_args, send an allow header listing
|
||||||
the methods that are possible on the provided URL.
|
the methods that are possible on the provided URL.
|
||||||
|
@ -111,4 +126,12 @@ class PlacementHandler(object):
|
||||||
raise webob.exc.HTTPForbidden(
|
raise webob.exc.HTTPForbidden(
|
||||||
'admin required',
|
'admin required',
|
||||||
json_formatter=util.json_error_formatter)
|
json_formatter=util.json_error_formatter)
|
||||||
return dispatch(environ, start_response, self._map)
|
try:
|
||||||
|
return dispatch(environ, start_response, self._map)
|
||||||
|
# Trap the small number of nova exceptions that aren't
|
||||||
|
# caught elsewhere and transform them into webob.exc.
|
||||||
|
# These are common exceptions raised when making calls against
|
||||||
|
# nova.objects in the handlers.
|
||||||
|
except exception.NotFound as exc:
|
||||||
|
raise webob.exc.HTTPNotFound(
|
||||||
|
exc, json_formatter=util.json_error_formatter)
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
# 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.
|
||||||
|
"""Placement API handlers for resource providers."""
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
|
import jsonschema
|
||||||
|
from oslo_db import exception as db_exc
|
||||||
|
from oslo_serialization import jsonutils
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
import webob
|
||||||
|
|
||||||
|
from nova.api.openstack.placement import util
|
||||||
|
from nova import exception
|
||||||
|
from nova import objects
|
||||||
|
|
||||||
|
|
||||||
|
POST_RESOURCE_PROVIDER_SCHEMA = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"uuid": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"additionalProperties": False,
|
||||||
|
}
|
||||||
|
# Remove uuid to create the schema for PUTting a resource provider
|
||||||
|
PUT_RESOURCE_PROVIDER_SCHEMA = copy.deepcopy(POST_RESOURCE_PROVIDER_SCHEMA)
|
||||||
|
PUT_RESOURCE_PROVIDER_SCHEMA['properties'].pop('uuid')
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_resource_provider(body, schema):
|
||||||
|
"""Extract and validate resource provider from JSON body."""
|
||||||
|
try:
|
||||||
|
data = jsonutils.loads(body)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise webob.exc.HTTPBadRequest(
|
||||||
|
'Malformed JSON: %s' % exc,
|
||||||
|
json_formatter=util.json_error_formatter)
|
||||||
|
try:
|
||||||
|
jsonschema.validate(data, schema,
|
||||||
|
format_checker=jsonschema.FormatChecker())
|
||||||
|
except jsonschema.ValidationError as exc:
|
||||||
|
raise webob.exc.HTTPBadRequest(
|
||||||
|
'JSON does not validate: %s' % exc,
|
||||||
|
json_formatter=util.json_error_formatter)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _serialize_links(environ, resource_provider):
|
||||||
|
url = util.resource_provider_url(environ, resource_provider)
|
||||||
|
links = [{'rel': 'self', 'href': url}]
|
||||||
|
for rel in ('aggregates', 'inventories', 'usages'):
|
||||||
|
links.append({'rel': rel, 'href': '%s/%s' % (url, rel)})
|
||||||
|
return links
|
||||||
|
|
||||||
|
|
||||||
|
def _serialize_provider(environ, resource_provider):
|
||||||
|
data = {
|
||||||
|
'uuid': resource_provider.uuid,
|
||||||
|
'name': resource_provider.name,
|
||||||
|
'generation': resource_provider.generation,
|
||||||
|
'links': _serialize_links(environ, resource_provider)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _serialize_providers(environ, resource_providers):
|
||||||
|
output = []
|
||||||
|
for provider in resource_providers:
|
||||||
|
provider_data = _serialize_provider(environ, provider)
|
||||||
|
output.append(provider_data)
|
||||||
|
return {"resource_providers": output}
|
||||||
|
|
||||||
|
|
||||||
|
@webob.dec.wsgify
|
||||||
|
@util.require_content('application/json')
|
||||||
|
def create_resource_provider(req):
|
||||||
|
"""POST to create a resource provider.
|
||||||
|
|
||||||
|
On success return a 201 response with an empty body and a location
|
||||||
|
header pointing to the newly created resource provider.
|
||||||
|
"""
|
||||||
|
context = req.environ['placement.context']
|
||||||
|
data = _extract_resource_provider(req.body,
|
||||||
|
POST_RESOURCE_PROVIDER_SCHEMA)
|
||||||
|
|
||||||
|
try:
|
||||||
|
uuid = data.get('uuid', uuidutils.generate_uuid())
|
||||||
|
resource_provider = objects.ResourceProvider(
|
||||||
|
context, name=data['name'], uuid=uuid)
|
||||||
|
resource_provider.create()
|
||||||
|
except db_exc.DBDuplicateEntry as exc:
|
||||||
|
raise webob.exc.HTTPConflict(
|
||||||
|
'Conflicting resource provider already exists: %s' % exc,
|
||||||
|
json_formatter=util.json_error_formatter)
|
||||||
|
except exception.ObjectActionError as exc:
|
||||||
|
raise webob.exc.HTTPBadRequest(
|
||||||
|
'Unable to create resource provider %s: %s' % (uuid, exc),
|
||||||
|
json_formatter=util.json_error_formatter)
|
||||||
|
|
||||||
|
req.response.location = util.resource_provider_url(
|
||||||
|
req.environ, resource_provider)
|
||||||
|
req.response.status = 201
|
||||||
|
req.response.content_type = None
|
||||||
|
return req.response
|
||||||
|
|
||||||
|
|
||||||
|
@webob.dec.wsgify
|
||||||
|
def delete_resource_provider(req):
|
||||||
|
"""DELETE to destroy a single resource provider.
|
||||||
|
|
||||||
|
On success return a 204 and an empty body.
|
||||||
|
"""
|
||||||
|
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
||||||
|
context = req.environ['placement.context']
|
||||||
|
# The containing application will catch a not found here.
|
||||||
|
resource_provider = objects.ResourceProvider.get_by_uuid(
|
||||||
|
context, uuid)
|
||||||
|
try:
|
||||||
|
resource_provider.destroy()
|
||||||
|
except exception.ResourceProviderInUse as exc:
|
||||||
|
raise webob.exc.HTTPConflict(
|
||||||
|
'Unable to delete resource provider %s: %s' % (uuid, exc),
|
||||||
|
json_formatter=util.json_error_formatter)
|
||||||
|
req.response.status = 204
|
||||||
|
req.response.content_type = None
|
||||||
|
return req.response
|
||||||
|
|
||||||
|
|
||||||
|
@webob.dec.wsgify
|
||||||
|
@util.check_accept('application/json')
|
||||||
|
def get_resource_provider(req):
|
||||||
|
"""Get a single resource provider.
|
||||||
|
|
||||||
|
On success return a 200 with an application/json body representing
|
||||||
|
the resource provider.
|
||||||
|
"""
|
||||||
|
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
||||||
|
# The containing application will catch a not found here.
|
||||||
|
context = req.environ['placement.context']
|
||||||
|
|
||||||
|
resource_provider = objects.ResourceProvider.get_by_uuid(
|
||||||
|
context, uuid)
|
||||||
|
|
||||||
|
req.response.body = jsonutils.dumps(
|
||||||
|
_serialize_provider(req.environ, resource_provider))
|
||||||
|
req.response.content_type = 'application/json'
|
||||||
|
return req.response
|
||||||
|
|
||||||
|
|
||||||
|
@webob.dec.wsgify
|
||||||
|
@util.check_accept('application/json')
|
||||||
|
def list_resource_providers(req):
|
||||||
|
"""GET a list of resource providers.
|
||||||
|
|
||||||
|
On success return a 200 and an application/json body representing
|
||||||
|
a collection of resource providers.
|
||||||
|
"""
|
||||||
|
context = req.environ['placement.context']
|
||||||
|
resource_providers = objects.ResourceProviderList.get_all_by_filters(
|
||||||
|
context)
|
||||||
|
response = req.response
|
||||||
|
response.body = jsonutils.dumps(_serialize_providers(
|
||||||
|
req.environ, resource_providers))
|
||||||
|
response.content_type = 'application/json'
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@webob.dec.wsgify
|
||||||
|
@util.require_content('application/json')
|
||||||
|
def update_resource_provider(req):
|
||||||
|
"""PUT to update a single resource provider.
|
||||||
|
|
||||||
|
On success return a 200 response with a representation of the updated
|
||||||
|
resource provider.
|
||||||
|
"""
|
||||||
|
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
||||||
|
context = req.environ['placement.context']
|
||||||
|
|
||||||
|
# The containing application will catch a not found here.
|
||||||
|
resource_provider = objects.ResourceProvider.get_by_uuid(
|
||||||
|
context, uuid)
|
||||||
|
|
||||||
|
data = _extract_resource_provider(req.body,
|
||||||
|
PUT_RESOURCE_PROVIDER_SCHEMA)
|
||||||
|
|
||||||
|
resource_provider.name = data['name']
|
||||||
|
|
||||||
|
try:
|
||||||
|
resource_provider.save()
|
||||||
|
except db_exc.DBDuplicateEntry as exc:
|
||||||
|
raise webob.exc.HTTPConflict(
|
||||||
|
'Conflicting resource provider already exists: %s' % exc,
|
||||||
|
json_formatter=util.json_error_formatter)
|
||||||
|
except exception.ObjectActionError as exc:
|
||||||
|
raise webob.exc.HTTPBadRequest(
|
||||||
|
'Unable to save resource provider %s: %s' % (uuid, exc),
|
||||||
|
json_formatter=util.json_error_formatter)
|
||||||
|
|
||||||
|
req.response.body = jsonutils.dumps(
|
||||||
|
_serialize_provider(req.environ, resource_provider))
|
||||||
|
req.response.status = 200
|
||||||
|
req.response.content_type = 'application/json'
|
||||||
|
return req.response
|
|
@ -12,7 +12,9 @@
|
||||||
"""Utility methods for placement API."""
|
"""Utility methods for placement API."""
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
import jsonschema
|
||||||
from oslo_middleware import request_id
|
from oslo_middleware import request_id
|
||||||
|
from oslo_utils import uuidutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
# NOTE(cdent): avoid cyclical import conflict between util and
|
# NOTE(cdent): avoid cyclical import conflict between util and
|
||||||
|
@ -20,6 +22,18 @@ import webob
|
||||||
import nova.api.openstack.placement.microversion
|
import nova.api.openstack.placement.microversion
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE(cdent): This registers a FormatChecker on the jsonschema
|
||||||
|
# module. Do not delete this code! Although it appears that nothing
|
||||||
|
# is using the decorated method it is being used in JSON schema
|
||||||
|
# validations to check uuid formatted strings. The addition of a uuid
|
||||||
|
# format checker is an implicit result of loading the util module.
|
||||||
|
# Since util.json_error_formatter # needs to be imported when jsonschema
|
||||||
|
# is doing validation this works.
|
||||||
|
@jsonschema.FormatChecker.cls_checks('uuid')
|
||||||
|
def _validate_uuid_format(instance):
|
||||||
|
return uuidutils.is_uuid_like(instance)
|
||||||
|
|
||||||
|
|
||||||
def check_accept(*types):
|
def check_accept(*types):
|
||||||
"""If accept is set explicitly, try to follow it.
|
"""If accept is set explicitly, try to follow it.
|
||||||
|
|
||||||
|
@ -95,6 +109,16 @@ def require_content(content_type):
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def resource_provider_url(environ, resource_provider):
|
||||||
|
"""Produce the URL for a resource provider.
|
||||||
|
|
||||||
|
If SCRIPT_NAME is present, it is the mount point of the placement
|
||||||
|
WSGI app.
|
||||||
|
"""
|
||||||
|
prefix = environ.get('SCRIPT_NAME', '')
|
||||||
|
return '%s/resource_providers/%s' % (prefix, resource_provider.uuid)
|
||||||
|
|
||||||
|
|
||||||
def wsgi_path_item(environ, name):
|
def wsgi_path_item(environ, name):
|
||||||
"""Extract the value of a named field in a URL.
|
"""Extract the value of a named field in a URL.
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,15 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from gabbi import fixture
|
from gabbi import fixture
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
from nova.api.openstack.placement import deploy
|
from nova.api.openstack.placement import deploy
|
||||||
from nova import conf
|
from nova import conf
|
||||||
from nova import config
|
from nova import config
|
||||||
|
from nova.tests import fixtures
|
||||||
|
|
||||||
|
|
||||||
CONF = conf.CONF
|
CONF = conf.CONF
|
||||||
|
@ -32,10 +36,33 @@ class APIFixture(fixture.GabbiFixture):
|
||||||
|
|
||||||
def start_fixture(self):
|
def start_fixture(self):
|
||||||
self.conf = CONF
|
self.conf = CONF
|
||||||
|
self.conf.set_override('auth_strategy', 'noauth2')
|
||||||
|
# Be explicit about all three database connections to avoid
|
||||||
|
# potential conflicts with config on disk.
|
||||||
|
self.conf.set_override('connection', "sqlite://", group='database')
|
||||||
|
self.conf.set_override('connection', "sqlite://",
|
||||||
|
group='api_database')
|
||||||
|
self.conf.set_override('connection', "sqlite://",
|
||||||
|
group='placement_database')
|
||||||
config.parse_args([], default_config_files=None, configure_db=False,
|
config.parse_args([], default_config_files=None, configure_db=False,
|
||||||
init_rpc=False)
|
init_rpc=False)
|
||||||
self.conf.set_override('auth_strategy', 'noauth2')
|
|
||||||
|
self.placement_db_fixture = fixtures.Database('placement')
|
||||||
|
# NOTE(cdent): api and main database are not used but we still need
|
||||||
|
# to manage them to make the fixtures work correctly and not cause
|
||||||
|
# conflicts with other tests in the same process.
|
||||||
|
self.api_db_fixture = fixtures.Database('api')
|
||||||
|
self.main_db_fixture = fixtures.Database('main')
|
||||||
|
self.placement_db_fixture.reset()
|
||||||
|
self.api_db_fixture.reset()
|
||||||
|
self.main_db_fixture.reset()
|
||||||
|
|
||||||
|
os.environ['RP_UUID'] = uuidutils.generate_uuid()
|
||||||
|
os.environ['RP_NAME'] = uuidutils.generate_uuid()
|
||||||
|
|
||||||
def stop_fixture(self):
|
def stop_fixture(self):
|
||||||
|
self.placement_db_fixture.cleanup()
|
||||||
|
self.api_db_fixture.cleanup()
|
||||||
|
self.main_db_fixture.cleanup()
|
||||||
if self.conf:
|
if self.conf:
|
||||||
self.conf.reset()
|
self.conf.reset()
|
||||||
|
|
|
@ -26,6 +26,10 @@ tests:
|
||||||
response_json_paths:
|
response_json_paths:
|
||||||
$.errors[0].request_id: /req-[a-fA-F0-9-]+/
|
$.errors[0].request_id: /req-[a-fA-F0-9-]+/
|
||||||
|
|
||||||
|
- name: 404 at no resource provider
|
||||||
|
GET: /resource_providers/fd0dd55c-6330-463b-876c-31c54e95cb95
|
||||||
|
status: 404
|
||||||
|
|
||||||
- name: 405 on bad method at root
|
- name: 405 on bad method at root
|
||||||
DELETE: /
|
DELETE: /
|
||||||
status: 405
|
status: 405
|
||||||
|
@ -37,3 +41,76 @@ tests:
|
||||||
- name: 200 at home
|
- name: 200 at home
|
||||||
GET: /
|
GET: /
|
||||||
status: 200
|
status: 200
|
||||||
|
|
||||||
|
- name: 405 on bad method on app
|
||||||
|
DELETE: /resource_providers
|
||||||
|
status: 405
|
||||||
|
|
||||||
|
- name: bad accept resource providers
|
||||||
|
GET: /resource_providers
|
||||||
|
request_headers:
|
||||||
|
accept: text/plain
|
||||||
|
status: 406
|
||||||
|
|
||||||
|
- name: complex accept resource providers
|
||||||
|
GET: /resource_providers
|
||||||
|
request_headers:
|
||||||
|
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
|
||||||
|
status: 200
|
||||||
|
response_json_paths:
|
||||||
|
$.resource_providers: []
|
||||||
|
|
||||||
|
- name: post resource provider wrong content-type
|
||||||
|
POST: /resource_providers
|
||||||
|
request_headers:
|
||||||
|
content-type: text/plain
|
||||||
|
data: I want a resource provider please
|
||||||
|
status: 415
|
||||||
|
|
||||||
|
- name: post resource provider schema mismatch
|
||||||
|
POST: /resource_providers
|
||||||
|
request_headers:
|
||||||
|
content-type: application/json
|
||||||
|
data:
|
||||||
|
transport: car
|
||||||
|
color: blue
|
||||||
|
status: 400
|
||||||
|
|
||||||
|
- name: post good resource provider
|
||||||
|
POST: /resource_providers
|
||||||
|
request_headers:
|
||||||
|
content-type: application/json
|
||||||
|
data:
|
||||||
|
name: $ENVIRON['RP_NAME']
|
||||||
|
uuid: $ENVIRON['RP_UUID']
|
||||||
|
status: 201
|
||||||
|
|
||||||
|
- name: get resource provider wrong accept
|
||||||
|
GET: /resource_providers/$ENVIRON['RP_UUID']
|
||||||
|
request_headers:
|
||||||
|
accept: text/plain
|
||||||
|
status: 406
|
||||||
|
response_strings:
|
||||||
|
- Only application/json is provided
|
||||||
|
|
||||||
|
- name: get resource provider complex accept wild match
|
||||||
|
desc: like a browser, */* should match
|
||||||
|
GET: /resource_providers/$ENVIRON['RP_UUID']
|
||||||
|
request_headers:
|
||||||
|
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
|
||||||
|
response_json_paths:
|
||||||
|
$.uuid: $ENVIRON['RP_UUID']
|
||||||
|
|
||||||
|
- name: get resource provider complex accept no match
|
||||||
|
desc: no */*, no match
|
||||||
|
GET: /resource_providers/$ENVIRON['RP_UUID']
|
||||||
|
request_headers:
|
||||||
|
accept: text/html,application/xhtml+xml,application/xml;q=0.9
|
||||||
|
status: 406
|
||||||
|
|
||||||
|
- name: put poor format resource provider
|
||||||
|
PUT: /resource_providers/$ENVIRON['RP_UUID']
|
||||||
|
request_headers:
|
||||||
|
content-type: text/plain
|
||||||
|
data: Why U no provide?
|
||||||
|
status: 415
|
||||||
|
|
|
@ -0,0 +1,200 @@
|
||||||
|
|
||||||
|
fixtures:
|
||||||
|
- APIFixture
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
request_headers:
|
||||||
|
x-auth-token: admin
|
||||||
|
|
||||||
|
tests:
|
||||||
|
|
||||||
|
- name: what is at resource providers
|
||||||
|
GET: /resource_providers
|
||||||
|
response_json_paths:
|
||||||
|
$.resource_providers: []
|
||||||
|
|
||||||
|
- name: non admin forbidden
|
||||||
|
GET: /resource_providers
|
||||||
|
request_headers:
|
||||||
|
x-auth-token: user
|
||||||
|
accept: application/json
|
||||||
|
status: 403
|
||||||
|
response_json_paths:
|
||||||
|
$.errors[0].title: Forbidden
|
||||||
|
|
||||||
|
- name: non admin forbidden non json
|
||||||
|
GET: /resource_providers
|
||||||
|
request_headers:
|
||||||
|
x-auth-token: user
|
||||||
|
accept: text/plain
|
||||||
|
status: 403
|
||||||
|
response_strings:
|
||||||
|
- admin required
|
||||||
|
|
||||||
|
- name: post new resource provider
|
||||||
|
POST: /resource_providers
|
||||||
|
request_headers:
|
||||||
|
content-type: application/json
|
||||||
|
data:
|
||||||
|
name: $ENVIRON['RP_NAME']
|
||||||
|
uuid: $ENVIRON['RP_UUID']
|
||||||
|
status: 201
|
||||||
|
response_headers:
|
||||||
|
location: //resource_providers/[a-f0-9-]+/
|
||||||
|
response_forbidden_headers:
|
||||||
|
- content-type
|
||||||
|
|
||||||
|
- name: try to create same all again
|
||||||
|
POST: /resource_providers
|
||||||
|
request_headers:
|
||||||
|
content-type: application/json
|
||||||
|
data:
|
||||||
|
name: $ENVIRON['RP_NAME']
|
||||||
|
uuid: $ENVIRON['RP_UUID']
|
||||||
|
status: 409
|
||||||
|
response_strings:
|
||||||
|
- Conflicting resource provider already exists
|
||||||
|
|
||||||
|
- name: try to create same name again
|
||||||
|
POST: /resource_providers
|
||||||
|
request_headers:
|
||||||
|
content-type: application/json
|
||||||
|
data:
|
||||||
|
name: $ENVIRON['RP_NAME']
|
||||||
|
uuid: ada30fb5-566d-4fe1-b43b-28a9e988790c
|
||||||
|
status: 409
|
||||||
|
response_strings:
|
||||||
|
- Conflicting resource provider already exists
|
||||||
|
|
||||||
|
- name: confirm the correct post
|
||||||
|
GET: /resource_providers/$ENVIRON['RP_UUID']
|
||||||
|
request_headers:
|
||||||
|
content-type: application/json
|
||||||
|
response_json_paths:
|
||||||
|
$.uuid: $ENVIRON['RP_UUID']
|
||||||
|
$.name: $ENVIRON['RP_NAME']
|
||||||
|
$.generation: 0
|
||||||
|
$.links[?rel = "self"].href: /resource_providers/$ENVIRON['RP_UUID']
|
||||||
|
$.links[?rel = "inventories"].href: /resource_providers/$ENVIRON['RP_UUID']/inventories
|
||||||
|
$.links[?rel = "aggregates"].href: /resource_providers/$ENVIRON['RP_UUID']/aggregates
|
||||||
|
$.links[?rel = "usages"].href: /resource_providers/$ENVIRON['RP_UUID']/usages
|
||||||
|
|
||||||
|
- name: get resource provider works with no accept
|
||||||
|
GET: /resource_providers/$ENVIRON['RP_UUID']
|
||||||
|
response_headers:
|
||||||
|
content-type: /application/json/
|
||||||
|
response_json_paths:
|
||||||
|
$.uuid: $ENVIRON['RP_UUID']
|
||||||
|
|
||||||
|
- name: list one resource providers
|
||||||
|
GET: /resource_providers
|
||||||
|
response_json_paths:
|
||||||
|
$.resource_providers.`len`: 1
|
||||||
|
$.resource_providers[0].uuid: $ENVIRON['RP_UUID']
|
||||||
|
$.resource_providers[0].name: $ENVIRON['RP_NAME']
|
||||||
|
$.resource_providers[0].generation: 0
|
||||||
|
$.resource_providers[0].links[?rel = "self"].href: /resource_providers/$ENVIRON['RP_UUID']
|
||||||
|
$.resource_providers[0].links[?rel = "inventories"].href: /resource_providers/$ENVIRON['RP_UUID']/inventories
|
||||||
|
$.resource_providers[0].links[?rel = "aggregates"].href: /resource_providers/$ENVIRON['RP_UUID']/aggregates
|
||||||
|
$.resource_providers[0].links[?rel = "usages"].href: /resource_providers/$ENVIRON['RP_UUID']/usages
|
||||||
|
|
||||||
|
- name: update a resource provider
|
||||||
|
PUT: /resource_providers/$RESPONSE['$.resource_providers[0].uuid']
|
||||||
|
request_headers:
|
||||||
|
content-type: application/json
|
||||||
|
data:
|
||||||
|
name: new name
|
||||||
|
status: 200
|
||||||
|
response_headers:
|
||||||
|
content-type: /application/json/
|
||||||
|
response_forbidden_headers:
|
||||||
|
- location
|
||||||
|
response_json_paths:
|
||||||
|
$.generation: 0
|
||||||
|
$.name: new name
|
||||||
|
$.uuid: $ENVIRON['RP_UUID']
|
||||||
|
$.links[?rel = "self"].href: /resource_providers/$ENVIRON['RP_UUID']
|
||||||
|
|
||||||
|
- name: check the name from that update
|
||||||
|
GET: $LAST_URL
|
||||||
|
response_json_paths:
|
||||||
|
$.name: new name
|
||||||
|
|
||||||
|
- name: update a provider poorly
|
||||||
|
PUT: $LAST_URL
|
||||||
|
request_headers:
|
||||||
|
content-type: application/json
|
||||||
|
data:
|
||||||
|
badfield: new name
|
||||||
|
status: 400
|
||||||
|
response_strings:
|
||||||
|
- 'JSON does not validate'
|
||||||
|
|
||||||
|
- name: create a new provider
|
||||||
|
POST: /resource_providers
|
||||||
|
request_headers:
|
||||||
|
content-type: application/json
|
||||||
|
data:
|
||||||
|
name: cow
|
||||||
|
status: 201
|
||||||
|
|
||||||
|
- name: try to rename that provider to existing name
|
||||||
|
PUT: $LOCATION
|
||||||
|
request_headers:
|
||||||
|
content-type: application/json
|
||||||
|
data:
|
||||||
|
name: new name
|
||||||
|
status: 409
|
||||||
|
|
||||||
|
- name: fail to put that provider with uuid
|
||||||
|
PUT: $LAST_URL
|
||||||
|
request_headers:
|
||||||
|
content-type: application/json
|
||||||
|
data:
|
||||||
|
name: second new name
|
||||||
|
uuid: 7d4275fc-8b40-4995-85e2-74fcec2cb3b6
|
||||||
|
status: 400
|
||||||
|
response_strings:
|
||||||
|
- Additional properties are not allowed
|
||||||
|
|
||||||
|
- name: delete resource provider
|
||||||
|
DELETE: $LAST_URL
|
||||||
|
status: 204
|
||||||
|
|
||||||
|
- name: 404 on deleted provider
|
||||||
|
DELETE: $LAST_URL
|
||||||
|
status: 404
|
||||||
|
|
||||||
|
- name: fail to get a provider
|
||||||
|
GET: /resource_providers/random_sauce
|
||||||
|
status: 404
|
||||||
|
|
||||||
|
- name: post resource provider no uuid
|
||||||
|
POST: /resource_providers
|
||||||
|
request_headers:
|
||||||
|
content-type: application/json
|
||||||
|
data:
|
||||||
|
name: a name
|
||||||
|
status: 201
|
||||||
|
response_headers:
|
||||||
|
location: //resource_providers/[a-f0-9-]+/
|
||||||
|
|
||||||
|
- name: post malformed json as json
|
||||||
|
POST: /resource_providers
|
||||||
|
request_headers:
|
||||||
|
content-type: application/json
|
||||||
|
data: '{"foo": }'
|
||||||
|
status: 400
|
||||||
|
response_strings:
|
||||||
|
- 'Malformed JSON:'
|
||||||
|
|
||||||
|
- name: post bad uuid in resource provider
|
||||||
|
POST: /resource_providers
|
||||||
|
request_headers:
|
||||||
|
content-type: application/json
|
||||||
|
data:
|
||||||
|
name: my bad rp
|
||||||
|
uuid: this is not a uuid
|
||||||
|
status: 400
|
||||||
|
response_strings:
|
||||||
|
- "Failed validating 'format'"
|
|
@ -18,7 +18,9 @@ import webob
|
||||||
|
|
||||||
from nova.api.openstack.placement import microversion
|
from nova.api.openstack.placement import microversion
|
||||||
from nova.api.openstack.placement import util
|
from nova.api.openstack.placement import util
|
||||||
|
from nova import objects
|
||||||
from nova import test
|
from nova import test
|
||||||
|
from nova.tests import uuidsentinel
|
||||||
|
|
||||||
|
|
||||||
class TestCheckAccept(test.NoDBTestCase):
|
class TestCheckAccept(test.NoDBTestCase):
|
||||||
|
@ -177,3 +179,27 @@ class TestRequireContent(test.NoDBTestCase):
|
||||||
req = webob.Request.blank('/')
|
req = webob.Request.blank('/')
|
||||||
req.content_type = 'application/json'
|
req.content_type = 'application/json'
|
||||||
self.assertTrue(self.handler(req))
|
self.assertTrue(self.handler(req))
|
||||||
|
|
||||||
|
|
||||||
|
class TestPlacementURLs(test.NoDBTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestPlacementURLs, self).setUp()
|
||||||
|
self.resource_provider = objects.ResourceProvider(
|
||||||
|
name=uuidsentinel.rp_name,
|
||||||
|
uuid=uuidsentinel.rp_uuid)
|
||||||
|
|
||||||
|
def test_resource_provider_url(self):
|
||||||
|
environ = {}
|
||||||
|
expected_url = '/resource_providers/%s' % uuidsentinel.rp_uuid
|
||||||
|
self.assertEqual(expected_url, util.resource_provider_url(
|
||||||
|
environ, self.resource_provider))
|
||||||
|
|
||||||
|
def test_resource_provider_url_prefix(self):
|
||||||
|
# SCRIPT_NAME represents the mount point of a WSGI
|
||||||
|
# application when it is hosted at a path/prefix.
|
||||||
|
environ = {'SCRIPT_NAME': '/placement'}
|
||||||
|
expected_url = ('/placement/resource_providers/%s'
|
||||||
|
% uuidsentinel.rp_uuid)
|
||||||
|
self.assertEqual(expected_url, util.resource_provider_url(
|
||||||
|
environ, self.resource_provider))
|
||||||
|
|
Loading…
Reference in New Issue