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_db.sqlalchemy import enginefacade
|
||||
from oslo_log import log as logging
|
||||
from oslo_middleware import request_id
|
||||
import webob.dec
|
||||
|
@ -55,6 +56,11 @@ class NoAuthMiddleware(Middleware):
|
|||
return self.application
|
||||
|
||||
|
||||
@enginefacade.transaction_context_provider
|
||||
class RequestContext(context.RequestContext):
|
||||
pass
|
||||
|
||||
|
||||
class PlacementKeystoneContext(Middleware):
|
||||
"""Make a request context from keystone headers."""
|
||||
|
||||
|
@ -62,7 +68,7 @@ class PlacementKeystoneContext(Middleware):
|
|||
def __call__(self, req):
|
||||
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)
|
||||
|
||||
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 handler
|
||||
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
|
||||
# now this is "nova" but we probably want "placement" eventually.
|
||||
NAME = "nova"
|
||||
|
||||
|
||||
# Make sure that objects are registered for this running of the
|
||||
# placement API.
|
||||
objects.register_all()
|
||||
|
||||
|
||||
def deploy(conf, project_name):
|
||||
"""Assemble the middleware pipeline leading to the placement app."""
|
||||
if conf.auth_strategy == 'noauth2':
|
||||
|
|
|
@ -26,15 +26,30 @@ method.
|
|||
import routes
|
||||
import webob
|
||||
|
||||
from nova.api.openstack.placement.handlers import resource_provider
|
||||
from nova.api.openstack.placement.handlers import root
|
||||
from nova.api.openstack.placement import util
|
||||
from nova import exception
|
||||
|
||||
|
||||
# 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 = {
|
||||
'/': {
|
||||
'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):
|
||||
"""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
|
||||
the methods that are possible on the provided URL.
|
||||
|
@ -111,4 +126,12 @@ class PlacementHandler(object):
|
|||
raise webob.exc.HTTPForbidden(
|
||||
'admin required',
|
||||
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."""
|
||||
|
||||
import functools
|
||||
import jsonschema
|
||||
from oslo_middleware import request_id
|
||||
from oslo_utils import uuidutils
|
||||
import webob
|
||||
|
||||
# NOTE(cdent): avoid cyclical import conflict between util and
|
||||
|
@ -20,6 +22,18 @@ import webob
|
|||
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):
|
||||
"""If accept is set explicitly, try to follow it.
|
||||
|
||||
|
@ -95,6 +109,16 @@ def require_content(content_type):
|
|||
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):
|
||||
"""Extract the value of a named field in a URL.
|
||||
|
||||
|
|
|
@ -10,11 +10,15 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
|
||||
from gabbi import fixture
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from nova.api.openstack.placement import deploy
|
||||
from nova import conf
|
||||
from nova import config
|
||||
from nova.tests import fixtures
|
||||
|
||||
|
||||
CONF = conf.CONF
|
||||
|
@ -32,10 +36,33 @@ class APIFixture(fixture.GabbiFixture):
|
|||
|
||||
def start_fixture(self):
|
||||
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,
|
||||
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):
|
||||
self.placement_db_fixture.cleanup()
|
||||
self.api_db_fixture.cleanup()
|
||||
self.main_db_fixture.cleanup()
|
||||
if self.conf:
|
||||
self.conf.reset()
|
||||
|
|
|
@ -26,6 +26,10 @@ tests:
|
|||
response_json_paths:
|
||||
$.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
|
||||
DELETE: /
|
||||
status: 405
|
||||
|
@ -37,3 +41,76 @@ tests:
|
|||
- name: 200 at home
|
||||
GET: /
|
||||
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 util
|
||||
from nova import objects
|
||||
from nova import test
|
||||
from nova.tests import uuidsentinel
|
||||
|
||||
|
||||
class TestCheckAccept(test.NoDBTestCase):
|
||||
|
@ -177,3 +179,27 @@ class TestRequireContent(test.NoDBTestCase):
|
|||
req = webob.Request.blank('/')
|
||||
req.content_type = 'application/json'
|
||||
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