Changes versioned URIs to be /v1/ instead of /v1.0/
Adds middleware that detects versioned URIs and also detects media types in the Accept: header and attempts to determine the API controller to return for the client request. Adds a bunch of functional test cases for variations of calling the versioned and unversioned URIs with and without Accept: headers.
This commit is contained in:
@@ -51,19 +51,17 @@ swift_store_container = glance
|
|||||||
# Do we create the container if it does not exist?
|
# Do we create the container if it does not exist?
|
||||||
swift_store_create_container_on_put = False
|
swift_store_create_container_on_put = False
|
||||||
|
|
||||||
[composite:glance-api]
|
[pipeline:glance-api]
|
||||||
use = egg:Paste#urlmap
|
pipeline = versionnegotiation apiv1app
|
||||||
/: versions
|
|
||||||
/v1.0: api_1_0
|
|
||||||
|
|
||||||
[pipeline:api_1_0]
|
|
||||||
pipeline = api_1_0_app
|
|
||||||
|
|
||||||
[pipeline:versions]
|
[pipeline:versions]
|
||||||
pipeline = versions_app
|
pipeline = versionsapp
|
||||||
|
|
||||||
[app:versions_app]
|
[app:versionsapp]
|
||||||
paste.app_factory = glance.api.versions:app_factory
|
paste.app_factory = glance.api.versions:app_factory
|
||||||
|
|
||||||
[app:api_1_0_app]
|
[app:apiv1app]
|
||||||
paste.app_factory = glance.api.v1_0:app_factory
|
paste.app_factory = glance.api.v1:app_factory
|
||||||
|
|
||||||
|
[filter:versionnegotiation]
|
||||||
|
paste.filter_factory = glance.api.middleware.version_negotiation:filter_factory
|
||||||
|
16
glance/api/middleware/__init__.py
Normal file
16
glance/api/middleware/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2011 OpenStack LLC.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
118
glance/api/middleware/version_negotiation.py
Normal file
118
glance/api/middleware/version_negotiation.py
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2011 OpenStack LLC.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
A filter middleware that inspects the requested URI for a version string
|
||||||
|
and/or Accept headers and attempts to negotiate an API controller to
|
||||||
|
return
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
|
import routes
|
||||||
|
|
||||||
|
from glance.api import v1
|
||||||
|
from glance.api import versions
|
||||||
|
from glance.common import wsgi
|
||||||
|
|
||||||
|
logger = logging.getLogger('glance.api.middleware.version_negotiation')
|
||||||
|
|
||||||
|
|
||||||
|
class VersionNegotiationFilter(wsgi.Middleware):
|
||||||
|
|
||||||
|
def __init__(self, app, options):
|
||||||
|
self.versions_app = versions.Controller(options)
|
||||||
|
self.version_uri_regex = re.compile(r"^v(\d+)\.?(\d+)?")
|
||||||
|
self.options = options
|
||||||
|
super(VersionNegotiationFilter, self).__init__(app)
|
||||||
|
|
||||||
|
def process_request(self, req):
|
||||||
|
"""
|
||||||
|
If there is a version identifier in the URI, simply
|
||||||
|
return the correct API controller, otherwise, if we
|
||||||
|
find an Accept: header, process it
|
||||||
|
"""
|
||||||
|
# See if a version identifier is in the URI passed to
|
||||||
|
# us already. If so, simply return the right version
|
||||||
|
# API controller
|
||||||
|
logger.debug("Processing request: %s %s Accept: %s",
|
||||||
|
req.method, req.path, req.accept)
|
||||||
|
match = self._match_version_string(req.path_info_peek(), req)
|
||||||
|
if match:
|
||||||
|
logger.debug("Matched versioned URI. Version: %d.%d",
|
||||||
|
req.environ['api.major_version'],
|
||||||
|
req.environ['api.minor_version'])
|
||||||
|
if req.environ['api.major_version'] == 1:
|
||||||
|
# Strip the version from the path
|
||||||
|
req.path_info_pop()
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return self.versions_app
|
||||||
|
|
||||||
|
accept = req.headers['Accept']
|
||||||
|
if accept.startswith('application/vnd.openstack.images'):
|
||||||
|
token_loc = len('application/vnd.openstack.images')
|
||||||
|
accept_version = accept[token_loc:]
|
||||||
|
match = self._match_version_string(accept_version, req)
|
||||||
|
if match:
|
||||||
|
logger.debug("Matched versioned media type. Version: %d.%d",
|
||||||
|
req.environ['api.major_version'],
|
||||||
|
req.environ['api.minor_version'])
|
||||||
|
if req.environ['api.major_version'] == 1:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return self.versions_app
|
||||||
|
else:
|
||||||
|
if req.accept not in ('*/*', ''):
|
||||||
|
logger.debug("Unknown accept header: %s..."
|
||||||
|
"returning version choices.", req.accept)
|
||||||
|
return self.versions_app
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _match_version_string(self, subject, req):
|
||||||
|
"""
|
||||||
|
Given a subject string, tries to match a major and/or
|
||||||
|
minor version number. If found, sets the api.major_version
|
||||||
|
and api.minor_version environ variables.
|
||||||
|
|
||||||
|
Returns True if there was a match, false otherwise.
|
||||||
|
|
||||||
|
:param subject: The string to check
|
||||||
|
:param req: Webob.Request object
|
||||||
|
"""
|
||||||
|
match = self.version_uri_regex.match(subject)
|
||||||
|
if match:
|
||||||
|
major_version, minor_version = match.groups(0)
|
||||||
|
major_version = int(major_version)
|
||||||
|
minor_version = int(minor_version)
|
||||||
|
req.environ['api.major_version'] = major_version
|
||||||
|
req.environ['api.minor_version'] = minor_version
|
||||||
|
return match is not None
|
||||||
|
|
||||||
|
|
||||||
|
def filter_factory(global_conf, **local_conf):
|
||||||
|
"""
|
||||||
|
Factory method for paste.deploy
|
||||||
|
"""
|
||||||
|
conf = global_conf.copy()
|
||||||
|
conf.update(local_conf)
|
||||||
|
|
||||||
|
def filter(app):
|
||||||
|
return VersionNegotiationFilter(app, conf)
|
||||||
|
|
||||||
|
return filter
|
@@ -19,15 +19,15 @@ import logging
|
|||||||
|
|
||||||
import routes
|
import routes
|
||||||
|
|
||||||
from glance.api.v1_0 import images
|
from glance.api.v1 import images
|
||||||
from glance.common import wsgi
|
from glance.common import wsgi
|
||||||
|
|
||||||
logger = logging.getLogger('glance.api.v1_0')
|
logger = logging.getLogger('glance.api.v1')
|
||||||
|
|
||||||
|
|
||||||
class API(wsgi.Router):
|
class API(wsgi.Router):
|
||||||
|
|
||||||
"""WSGI router for Glance v1.0 API requests."""
|
"""WSGI router for Glance v1 API requests."""
|
||||||
|
|
||||||
def __init__(self, options):
|
def __init__(self, options):
|
||||||
self.options = options
|
self.options = options
|
@@ -16,7 +16,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
/images endpoint for Glance v1.0 API
|
/images endpoint for Glance v1 API
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import httplib
|
import httplib
|
||||||
@@ -40,13 +40,13 @@ from glance import registry
|
|||||||
from glance import utils
|
from glance import utils
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('glance.api.v1_0.images')
|
logger = logging.getLogger('glance.api.v1.images')
|
||||||
|
|
||||||
|
|
||||||
class Controller(wsgi.Controller):
|
class Controller(wsgi.Controller):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
WSGI controller for images resource in Glance v1.0 API
|
WSGI controller for images resource in Glance v1 API
|
||||||
|
|
||||||
The images resource API is a RESTful web service for image data. The API
|
The images resource API is a RESTful web service for image data. The API
|
||||||
is as follows::
|
is as follows::
|
||||||
@@ -131,7 +131,7 @@ class Controller(wsgi.Controller):
|
|||||||
|
|
||||||
res = Response(request=req)
|
res = Response(request=req)
|
||||||
utils.inject_image_meta_into_headers(res, image)
|
utils.inject_image_meta_into_headers(res, image)
|
||||||
res.headers.add('Location', "/v1.0/images/%s" % id)
|
res.headers.add('Location', "/v1/images/%s" % id)
|
||||||
res.headers.add('ETag', image['checksum'])
|
res.headers.add('ETag', image['checksum'])
|
||||||
|
|
||||||
return req.get_response(res)
|
return req.get_response(res)
|
||||||
@@ -164,7 +164,7 @@ class Controller(wsgi.Controller):
|
|||||||
# Using app_iter blanks content-length, so we set it here...
|
# Using app_iter blanks content-length, so we set it here...
|
||||||
res.headers.add('Content-Length', image['size'])
|
res.headers.add('Content-Length', image['size'])
|
||||||
utils.inject_image_meta_into_headers(res, image)
|
utils.inject_image_meta_into_headers(res, image)
|
||||||
res.headers.add('Location', "/v1.0/images/%s" % id)
|
res.headers.add('Location', "/v1/images/%s" % id)
|
||||||
res.headers.add('ETag', image['checksum'])
|
res.headers.add('ETag', image['checksum'])
|
||||||
return req.get_response(res)
|
return req.get_response(res)
|
||||||
|
|
||||||
@@ -386,7 +386,7 @@ class Controller(wsgi.Controller):
|
|||||||
# URI of the resource newly-created.
|
# URI of the resource newly-created.
|
||||||
res = Response(request=req, body=json.dumps(dict(image=image_meta)),
|
res = Response(request=req, body=json.dumps(dict(image=image_meta)),
|
||||||
status=httplib.CREATED, content_type="text/plain")
|
status=httplib.CREATED, content_type="text/plain")
|
||||||
res.headers.add('Location', "/v1.0/images/%s" % image_id)
|
res.headers.add('Location', "/v1/images/%s" % image_id)
|
||||||
res.headers.add('ETag', image_meta['checksum'])
|
res.headers.add('ETag', image_meta['checksum'])
|
||||||
|
|
||||||
return req.get_response(res)
|
return req.get_response(res)
|
@@ -56,7 +56,7 @@ class Controller(object):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
def get_href(self):
|
def get_href(self):
|
||||||
return "http://%s:%s/v1.0" % (self.options['bind_host'],
|
return "http://%s:%s/v1/" % (self.options['bind_host'],
|
||||||
self.options['bind_port'])
|
self.options['bind_port'])
|
||||||
|
|
||||||
|
|
||||||
|
@@ -177,13 +177,13 @@ class BaseClient(object):
|
|||||||
return response.status
|
return response.status
|
||||||
|
|
||||||
|
|
||||||
class V1_0_Client(BaseClient):
|
class V1Client(BaseClient):
|
||||||
|
|
||||||
"""Main client class for accessing Glance resources"""
|
"""Main client class for accessing Glance resources"""
|
||||||
|
|
||||||
DEFAULT_PORT = 9292
|
DEFAULT_PORT = 9292
|
||||||
|
|
||||||
def __init__(self, host, port=None, use_ssl=False, doc_root="/v1.0"):
|
def __init__(self, host, port=None, use_ssl=False, doc_root="/v1"):
|
||||||
"""
|
"""
|
||||||
Creates a new client to a Glance API service.
|
Creates a new client to a Glance API service.
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ class V1_0_Client(BaseClient):
|
|||||||
|
|
||||||
def do_request(self, method, action, body=None, headers=None):
|
def do_request(self, method, action, body=None, headers=None):
|
||||||
action = "%s/%s" % (self.doc_root, action.lstrip("/"))
|
action = "%s/%s" % (self.doc_root, action.lstrip("/"))
|
||||||
return super(V1_0_Client, self).do_request(method, action,
|
return super(V1Client, self).do_request(method, action,
|
||||||
body, headers)
|
body, headers)
|
||||||
|
|
||||||
def get_images(self):
|
def get_images(self):
|
||||||
@@ -297,4 +297,4 @@ class V1_0_Client(BaseClient):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
Client = V1_0_Client
|
Client = V1Client
|
||||||
|
@@ -148,22 +148,20 @@ registry_host = 0.0.0.0
|
|||||||
registry_port = %(registry_port)s
|
registry_port = %(registry_port)s
|
||||||
log_file = %(log_file)s
|
log_file = %(log_file)s
|
||||||
|
|
||||||
[composite:glance-api]
|
[pipeline:glance-api]
|
||||||
use = egg:Paste#urlmap
|
pipeline = versionnegotiation apiv1app
|
||||||
/: versions
|
|
||||||
/v1.0: api_1_0
|
|
||||||
|
|
||||||
[pipeline:api_1_0]
|
|
||||||
pipeline = api_1_0_app
|
|
||||||
|
|
||||||
[pipeline:versions]
|
[pipeline:versions]
|
||||||
pipeline = versions_app
|
pipeline = versionsapp
|
||||||
|
|
||||||
[app:versions_app]
|
[app:versionsapp]
|
||||||
paste.app_factory = glance.api.versions:app_factory
|
paste.app_factory = glance.api.versions:app_factory
|
||||||
|
|
||||||
[app:api_1_0_app]
|
[app:apiv1app]
|
||||||
paste.app_factory = glance.api.v1_0:app_factory
|
paste.app_factory = glance.api.v1:app_factory
|
||||||
|
|
||||||
|
[filter:versionnegotiation]
|
||||||
|
paste.filter_factory = glance.api.middleware.version_negotiation:filter_factory
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@@ -72,7 +72,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
|
|
||||||
# 0. GET /images
|
# 0. GET /images
|
||||||
# Verify no public images
|
# Verify no public images
|
||||||
cmd = "curl -g http://0.0.0.0:%d/v1.0/images" % api_port
|
cmd = "curl http://0.0.0.0:%d/v1/images" % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
|
|
||||||
# 1. GET /images/detail
|
# 1. GET /images/detail
|
||||||
# Verify no public images
|
# Verify no public images
|
||||||
cmd = "curl -g http://0.0.0.0:%d/v1.0/images/detail" % api_port
|
cmd = "curl http://0.0.0.0:%d/v1/images/detail" % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
|
|
||||||
# 2. HEAD /images/1
|
# 2. HEAD /images/1
|
||||||
# Verify 404 returned
|
# Verify 404 returned
|
||||||
cmd = "curl -i -X HEAD http://0.0.0.0:%d/v1.0/images/1" % api_port
|
cmd = "curl -i -X HEAD http://0.0.0.0:%d/v1/images/1" % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
"-H 'X-Image-Meta-Name: Image1' "
|
"-H 'X-Image-Meta-Name: Image1' "
|
||||||
"-H 'X-Image-Meta-Is-Public: True' "
|
"-H 'X-Image-Meta-Is-Public: True' "
|
||||||
"--data-binary \"%s\" "
|
"--data-binary \"%s\" "
|
||||||
"http://0.0.0.0:%d/v1.0/images") % (image_data, api_port)
|
"http://0.0.0.0:%d/v1/images") % (image_data, api_port)
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
self.assertEqual(0, exitcode)
|
self.assertEqual(0, exitcode)
|
||||||
@@ -123,7 +123,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
|
|
||||||
# 4. HEAD /images
|
# 4. HEAD /images
|
||||||
# Verify image found now
|
# Verify image found now
|
||||||
cmd = "curl -i -X HEAD http://0.0.0.0:%d/v1.0/images/1" % api_port
|
cmd = "curl -i -X HEAD http://0.0.0.0:%d/v1/images/1" % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
# 5. GET /images/1
|
# 5. GET /images/1
|
||||||
# Verify all information on image we just added is correct
|
# Verify all information on image we just added is correct
|
||||||
|
|
||||||
cmd = "curl -i -g http://0.0.0.0:%d/v1.0/images/1" % api_port
|
cmd = "curl -i http://0.0.0.0:%d/v1/images/1" % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
@@ -212,7 +212,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
|
|
||||||
# 6. GET /images
|
# 6. GET /images
|
||||||
# Verify no public images
|
# Verify no public images
|
||||||
cmd = "curl -g http://0.0.0.0:%d/v1.0/images" % api_port
|
cmd = "curl http://0.0.0.0:%d/v1/images" % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
@@ -229,7 +229,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
|
|
||||||
# 7. GET /images/detail
|
# 7. GET /images/detail
|
||||||
# Verify image and all its metadata
|
# Verify image and all its metadata
|
||||||
cmd = "curl -g http://0.0.0.0:%d/v1.0/images/detail" % api_port
|
cmd = "curl http://0.0.0.0:%d/v1/images/detail" % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
@@ -266,7 +266,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
cmd = ("curl -i -X PUT "
|
cmd = ("curl -i -X PUT "
|
||||||
"-H 'X-Image-Meta-Property-Distro: Ubuntu' "
|
"-H 'X-Image-Meta-Property-Distro: Ubuntu' "
|
||||||
"-H 'X-Image-Meta-Property-Arch: x86_64' "
|
"-H 'X-Image-Meta-Property-Arch: x86_64' "
|
||||||
"http://0.0.0.0:%d/v1.0/images/1") % api_port
|
"http://0.0.0.0:%d/v1/images/1") % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
self.assertEqual(0, exitcode)
|
self.assertEqual(0, exitcode)
|
||||||
@@ -278,7 +278,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
|
|
||||||
# 9. GET /images/detail
|
# 9. GET /images/detail
|
||||||
# Verify image and all its metadata
|
# Verify image and all its metadata
|
||||||
cmd = "curl -g http://0.0.0.0:%d/v1.0/images/detail" % api_port
|
cmd = "curl http://0.0.0.0:%d/v1/images/detail" % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
@@ -312,7 +312,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
# 10. PUT /images/1 and remove a previously existing property.
|
# 10. PUT /images/1 and remove a previously existing property.
|
||||||
cmd = ("curl -i -X PUT "
|
cmd = ("curl -i -X PUT "
|
||||||
"-H 'X-Image-Meta-Property-Arch: x86_64' "
|
"-H 'X-Image-Meta-Property-Arch: x86_64' "
|
||||||
"http://0.0.0.0:%d/v1.0/images/1") % api_port
|
"http://0.0.0.0:%d/v1/images/1") % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
self.assertEqual(0, exitcode)
|
self.assertEqual(0, exitcode)
|
||||||
@@ -322,7 +322,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
|
|
||||||
self.assertEqual("HTTP/1.1 200 OK", status_line)
|
self.assertEqual("HTTP/1.1 200 OK", status_line)
|
||||||
|
|
||||||
cmd = "curl -g http://0.0.0.0:%d/v1.0/images/detail" % api_port
|
cmd = "curl http://0.0.0.0:%d/v1/images/detail" % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
@@ -336,7 +336,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
cmd = ("curl -i -X PUT "
|
cmd = ("curl -i -X PUT "
|
||||||
"-H 'X-Image-Meta-Property-Distro: Ubuntu' "
|
"-H 'X-Image-Meta-Property-Distro: Ubuntu' "
|
||||||
"-H 'X-Image-Meta-Property-Arch: x86_64' "
|
"-H 'X-Image-Meta-Property-Arch: x86_64' "
|
||||||
"http://0.0.0.0:%d/v1.0/images/1") % api_port
|
"http://0.0.0.0:%d/v1/images/1") % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
self.assertEqual(0, exitcode)
|
self.assertEqual(0, exitcode)
|
||||||
@@ -346,7 +346,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
|
|
||||||
self.assertEqual("HTTP/1.1 200 OK", status_line)
|
self.assertEqual("HTTP/1.1 200 OK", status_line)
|
||||||
|
|
||||||
cmd = "curl -g http://0.0.0.0:%d/v1.0/images/detail" % api_port
|
cmd = "curl http://0.0.0.0:%d/v1/images/detail" % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
@@ -391,7 +391,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
|
|
||||||
# 0. GET /images
|
# 0. GET /images
|
||||||
# Verify no public images
|
# Verify no public images
|
||||||
cmd = "curl -g http://0.0.0.0:%d/v1.0/images" % api_port
|
cmd = "curl http://0.0.0.0:%d/v1/images" % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
@@ -405,7 +405,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
"-H 'Expect: ' " # Necessary otherwise sends 100 Continue
|
"-H 'Expect: ' " # Necessary otherwise sends 100 Continue
|
||||||
"-H 'X-Image-Meta-Name: Image1' "
|
"-H 'X-Image-Meta-Name: Image1' "
|
||||||
"-H 'X-Image-Meta-Is-Public: True' "
|
"-H 'X-Image-Meta-Is-Public: True' "
|
||||||
"http://0.0.0.0:%d/v1.0/images") % api_port
|
"http://0.0.0.0:%d/v1/images") % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
self.assertEqual(0, exitcode)
|
self.assertEqual(0, exitcode)
|
||||||
@@ -417,7 +417,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
|
|
||||||
# 2. GET /images
|
# 2. GET /images
|
||||||
# Verify 1 public image
|
# Verify 1 public image
|
||||||
cmd = "curl -g http://0.0.0.0:%d/v1.0/images" % api_port
|
cmd = "curl http://0.0.0.0:%d/v1/images" % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
@@ -433,7 +433,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
|
|
||||||
# 3. HEAD /images
|
# 3. HEAD /images
|
||||||
# Verify status is in queued
|
# Verify status is in queued
|
||||||
cmd = "curl -i -X HEAD http://0.0.0.0:%d/v1.0/images/1" % api_port
|
cmd = "curl -i -X HEAD http://0.0.0.0:%d/v1/images/1" % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
@@ -453,7 +453,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
"-H 'Expect: ' " # Necessary otherwise sends 100 Continue
|
"-H 'Expect: ' " # Necessary otherwise sends 100 Continue
|
||||||
"-H 'Content-Type: application/octet-stream' "
|
"-H 'Content-Type: application/octet-stream' "
|
||||||
"--data-binary \"%s\" "
|
"--data-binary \"%s\" "
|
||||||
"http://0.0.0.0:%d/v1.0/images/1") % (image_data, api_port)
|
"http://0.0.0.0:%d/v1/images/1") % (image_data, api_port)
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
self.assertEqual(0, exitcode)
|
self.assertEqual(0, exitcode)
|
||||||
@@ -465,7 +465,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
|
|
||||||
# 5. HEAD /images
|
# 5. HEAD /images
|
||||||
# Verify status is in active
|
# Verify status is in active
|
||||||
cmd = "curl -i -X HEAD http://0.0.0.0:%d/v1.0/images/1" % api_port
|
cmd = "curl -i -X HEAD http://0.0.0.0:%d/v1/images/1" % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
@@ -480,7 +480,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
|
|
||||||
# 6. GET /images
|
# 6. GET /images
|
||||||
# Verify 1 public image still...
|
# Verify 1 public image still...
|
||||||
cmd = "curl -g http://0.0.0.0:%d/v1.0/images" % api_port
|
cmd = "curl http://0.0.0.0:%d/v1/images" % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
@@ -494,6 +494,139 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
"size": 5120}
|
"size": 5120}
|
||||||
self.assertEqual(expected, image)
|
self.assertEqual(expected, image)
|
||||||
|
|
||||||
|
def test_version_variations(self):
|
||||||
|
"""
|
||||||
|
We test that various calls to the images and root endpoints are
|
||||||
|
handled properly, and that usage of the Accept: header does
|
||||||
|
content negotiation properly.
|
||||||
|
|
||||||
|
0. GET / with no Accept: header
|
||||||
|
Verify version choices returned.
|
||||||
|
1. GET /images with no Accept: header
|
||||||
|
Verify version choices returned.
|
||||||
|
2. GET /v1/images with no Accept: header
|
||||||
|
Verify empty image list returned.
|
||||||
|
3. GET / with an Accept: unknown header
|
||||||
|
Verify version choices returned. Verify message in API log about
|
||||||
|
unknown accept header.
|
||||||
|
4. GET / with an Accept: application/vnd.openstack.images-v1
|
||||||
|
Verify empty image list returned
|
||||||
|
5. GET /images with a Accept: application/vnd.openstack.compute-v1
|
||||||
|
header. Verify version choices returned. Verify message in API log
|
||||||
|
about unknown accept header.
|
||||||
|
6. GET /v1.0/images with no Accept: header
|
||||||
|
Verify empty image list returned
|
||||||
|
7. GET /v1.a/images with no Accept: header
|
||||||
|
Verify empty image list returned
|
||||||
|
8. GET /va.1/images with no Accept: header
|
||||||
|
Verify version choices returned.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.cleanup()
|
||||||
|
self.start_servers()
|
||||||
|
|
||||||
|
api_port = self.api_port
|
||||||
|
registry_port = self.registry_port
|
||||||
|
|
||||||
|
versions = {'versions': [{
|
||||||
|
"id": "v1.0",
|
||||||
|
"status": "CURRENT",
|
||||||
|
"links": [{
|
||||||
|
"rel": "self",
|
||||||
|
"href": "http://0.0.0.0:%d/v1/" % api_port}]}]}
|
||||||
|
versions_json = json.dumps(versions)
|
||||||
|
images = {'images': []}
|
||||||
|
images_json = json.dumps(images)
|
||||||
|
|
||||||
|
# 0. GET / with no Accept: header
|
||||||
|
# Verify version choices returned.
|
||||||
|
cmd = "curl http://0.0.0.0:%d/" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual(versions_json, out.strip())
|
||||||
|
|
||||||
|
# 1. GET /images with no Accept: header
|
||||||
|
# Verify version choices returned.
|
||||||
|
cmd = "curl http://0.0.0.0:%d/images" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual(versions_json, out.strip())
|
||||||
|
|
||||||
|
# 2. GET /v1/images with no Accept: header
|
||||||
|
# Verify empty images list returned.
|
||||||
|
cmd = "curl http://0.0.0.0:%d/v1/images" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual(images_json, out.strip())
|
||||||
|
|
||||||
|
# 3. GET / with Accept: unknown header
|
||||||
|
# Verify version choices returned. Verify message in API log about
|
||||||
|
# unknown accept header.
|
||||||
|
cmd = "curl -H 'Accept: unknown' http://0.0.0.0:%d/" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual(versions_json, out.strip())
|
||||||
|
self.assertTrue('Unknown accept header'
|
||||||
|
in open(self.api_server.log_file).read())
|
||||||
|
|
||||||
|
# 5. GET / with an Accept: application/vnd.openstack.images-v1
|
||||||
|
# Verify empty image list returned
|
||||||
|
cmd = ("curl -H 'Accept: application/vnd.openstack.images-v1' "
|
||||||
|
"http://0.0.0.0:%d/images") % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual(images_json, out.strip())
|
||||||
|
|
||||||
|
# 5. GET /images with a Accept: application/vnd.openstack.compute-v1
|
||||||
|
# header. Verify version choices returned. Verify message in API log
|
||||||
|
# about unknown accept header.
|
||||||
|
cmd = ("curl -H 'Accept: application/vnd.openstack.compute-v1' "
|
||||||
|
"http://0.0.0.0:%d/images") % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual(versions_json, out.strip())
|
||||||
|
self.assertTrue('Unknown accept header'
|
||||||
|
in open(self.api_server.log_file).read())
|
||||||
|
|
||||||
|
# 6. GET /v1.0/images with no Accept: header
|
||||||
|
# Verify empty image list returned
|
||||||
|
cmd = "curl http://0.0.0.0:%d/v1.0/images" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual(images_json, out.strip())
|
||||||
|
|
||||||
|
# 7. GET /v1.a/images with no Accept: header
|
||||||
|
# Verify empty image list returned
|
||||||
|
cmd = "curl http://0.0.0.0:%d/v1.a/images" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual(images_json, out.strip())
|
||||||
|
|
||||||
|
# 8. GET /va.1/images with no Accept: header
|
||||||
|
# Verify version choices returned
|
||||||
|
cmd = "curl http://0.0.0.0:%d/va.1/images" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual(versions_json, out.strip())
|
||||||
|
|
||||||
def test_size_greater_2G_mysql(self):
|
def test_size_greater_2G_mysql(self):
|
||||||
"""
|
"""
|
||||||
A test against the actual datastore backend for the registry
|
A test against the actual datastore backend for the registry
|
||||||
@@ -519,7 +652,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
"-H 'X-Image-Meta-Size: %d' "
|
"-H 'X-Image-Meta-Size: %d' "
|
||||||
"-H 'X-Image-Meta-Name: Image1' "
|
"-H 'X-Image-Meta-Name: Image1' "
|
||||||
"-H 'X-Image-Meta-Is-Public: True' "
|
"-H 'X-Image-Meta-Is-Public: True' "
|
||||||
"http://0.0.0.0:%d/v1.0/images") % (FIVE_GB, api_port)
|
"http://0.0.0.0:%d/v1/images") % (FIVE_GB, api_port)
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
self.assertEqual(0, exitcode)
|
self.assertEqual(0, exitcode)
|
||||||
@@ -539,8 +672,8 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
|
|
||||||
self.assertTrue(new_image_uri is not None,
|
self.assertTrue(new_image_uri is not None,
|
||||||
"Could not find a new image URI!")
|
"Could not find a new image URI!")
|
||||||
self.assertTrue("v1.0/images" in new_image_uri,
|
self.assertTrue("v1/images" in new_image_uri,
|
||||||
"v1.0/images not in %s" % new_image_uri)
|
"v1/images not in %s" % new_image_uri)
|
||||||
|
|
||||||
# 2. HEAD /images
|
# 2. HEAD /images
|
||||||
# Verify image size is what was passed in, and not truncated
|
# Verify image size is what was passed in, and not truncated
|
||||||
@@ -582,7 +715,7 @@ class TestCurlApi(functional.FunctionalTest):
|
|||||||
test_data_file.write("XXX")
|
test_data_file.write("XXX")
|
||||||
test_data_file.flush()
|
test_data_file.flush()
|
||||||
cmd = ("curl -i -X POST --upload-file %s "
|
cmd = ("curl -i -X POST --upload-file %s "
|
||||||
"http://0.0.0.0:%d/v1.0/images") % (test_data_file.name,
|
"http://0.0.0.0:%d/v1/images") % (test_data_file.name,
|
||||||
api_port)
|
api_port)
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
|
@@ -46,7 +46,7 @@ class TestMiscellaneous(functional.FunctionalTest):
|
|||||||
api_port = self.api_port
|
api_port = self.api_port
|
||||||
registry_port = self.registry_port
|
registry_port = self.registry_port
|
||||||
|
|
||||||
cmd = "curl -g http://0.0.0.0:%d/v1.0/images" % api_port
|
cmd = "curl -g http://0.0.0.0:%d/v1/images" % api_port
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ class TestMiscellaneous(functional.FunctionalTest):
|
|||||||
cmd = "curl -X POST -H 'Content-Type: application/octet-stream' "\
|
cmd = "curl -X POST -H 'Content-Type: application/octet-stream' "\
|
||||||
"-H 'X-Image-Meta-Name: ImageName' "\
|
"-H 'X-Image-Meta-Name: ImageName' "\
|
||||||
"-H 'X-Image-Meta-Disk-Format: Invalid' "\
|
"-H 'X-Image-Meta-Disk-Format: Invalid' "\
|
||||||
"http://0.0.0.0:%d/v1.0/images" % api_port
|
"http://0.0.0.0:%d/v1/images" % api_port
|
||||||
ignored, out, err = execute(cmd)
|
ignored, out, err = execute(cmd)
|
||||||
|
|
||||||
self.assertTrue('Invalid disk format' in out,
|
self.assertTrue('Invalid disk format' in out,
|
||||||
|
@@ -29,7 +29,7 @@ import webob
|
|||||||
|
|
||||||
from glance.common import exception
|
from glance.common import exception
|
||||||
from glance.registry import server as rserver
|
from glance.registry import server as rserver
|
||||||
from glance.api import v1_0 as server
|
from glance.api import v1 as server
|
||||||
import glance.store
|
import glance.store
|
||||||
import glance.store.filesystem
|
import glance.store.filesystem
|
||||||
import glance.store.http
|
import glance.store.http
|
||||||
|
@@ -24,7 +24,7 @@ import unittest
|
|||||||
import stubout
|
import stubout
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from glance.api import v1_0 as server
|
from glance.api import v1 as server
|
||||||
from glance.registry import server as rserver
|
from glance.registry import server as rserver
|
||||||
from tests import stubs
|
from tests import stubs
|
||||||
|
|
||||||
|
@@ -46,5 +46,5 @@ class VersionsTest(unittest.TestCase):
|
|||||||
"links": [
|
"links": [
|
||||||
{
|
{
|
||||||
"rel": "self",
|
"rel": "self",
|
||||||
"href": "http://0.0.0.0:9292/v1.0"}]}]
|
"href": "http://0.0.0.0:9292/v1/"}]}]
|
||||||
self.assertEqual(results, expected)
|
self.assertEqual(results, expected)
|
||||||
|
@@ -4,7 +4,6 @@ pep8==0.5.0
|
|||||||
pylint==0.19
|
pylint==0.19
|
||||||
anyjson
|
anyjson
|
||||||
eventlet>=0.9.12
|
eventlet>=0.9.12
|
||||||
Paste
|
|
||||||
PasteDeploy
|
PasteDeploy
|
||||||
routes
|
routes
|
||||||
webob
|
webob
|
||||||
|
Reference in New Issue
Block a user