Merge "Added discoverable capabilities."
This commit is contained in:
commit
34b4bf34d2
@ -15,7 +15,7 @@
|
|||||||
import hmac
|
import hmac
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
from os.path import basename
|
from os.path import basename
|
||||||
from sys import argv, exit
|
from sys import argv, exit, stderr
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
|
|
||||||
@ -59,9 +59,11 @@ if __name__ == '__main__':
|
|||||||
# have '/'s.
|
# have '/'s.
|
||||||
if len(parts) != 5 or parts[0] or parts[1] != 'v1' or not parts[2] or \
|
if len(parts) != 5 or parts[0] or parts[1] != 'v1' or not parts[2] or \
|
||||||
not parts[3] or not parts[4].strip('/'):
|
not parts[3] or not parts[4].strip('/'):
|
||||||
print '<path> must point to an object.'
|
stderr.write(
|
||||||
print 'For example: /v1/account/container/object'
|
'WARNING: "%s" does not refer to an object '
|
||||||
exit(1)
|
'(e.g. /v1/account/container/object).\n' % path)
|
||||||
|
stderr.write(
|
||||||
|
'WARNING: Non-object paths will be rejected by tempurl.\n')
|
||||||
sig = hmac.new(key, '%s\n%s\n%s' % (method, expires, path),
|
sig = hmac.new(key, '%s\n%s\n%s' % (method, expires, path),
|
||||||
sha1).hexdigest()
|
sha1).hexdigest()
|
||||||
print '%s?temp_url_sig=%s&temp_url_expires=%s' % (path, sig, expires)
|
print '%s?temp_url_sig=%s&temp_url_expires=%s' % (path, sig, expires)
|
||||||
|
@ -216,3 +216,22 @@ List Endpoints
|
|||||||
.. automodule:: swift.common.middleware.list_endpoints
|
.. automodule:: swift.common.middleware.list_endpoints
|
||||||
:members:
|
:members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
Discoverability
|
||||||
|
===============
|
||||||
|
|
||||||
|
Swift can optionally be configured to provide clients with an interface
|
||||||
|
providing details about the installation. If configured, a GET request to
|
||||||
|
/info will return configuration data in JSON format. An example
|
||||||
|
response::
|
||||||
|
|
||||||
|
{"swift": {"version": "1.8.1"}, "staticweb": {}, "tempurl": {}}
|
||||||
|
|
||||||
|
This would signify to the client that swift version 1.8.1 is running and that
|
||||||
|
staticweb and tempurl are available in this installation.
|
||||||
|
|
||||||
|
There may be administrator-only information available via /info. To
|
||||||
|
retrieve it, one must use an HMAC-signed request, similar to TempURL.
|
||||||
|
The signature may be produced like so:
|
||||||
|
|
||||||
|
swift-temp-url GET 3600 /info secret 2>/dev/null | sed s/temp_url/swiftinfo/g
|
||||||
|
@ -5,7 +5,20 @@
|
|||||||
# backlog = 4096
|
# backlog = 4096
|
||||||
# swift_dir = /etc/swift
|
# swift_dir = /etc/swift
|
||||||
# user = swift
|
# user = swift
|
||||||
|
|
||||||
|
# Enables exposing configuration settings via HTTP GET /info.
|
||||||
|
# expose_info = true
|
||||||
|
|
||||||
|
# Key to use for admin calls that are HMAC signed. Default is empty,
|
||||||
|
# which will disable admin calls to /info.
|
||||||
|
# admin_key = secret_admin_key
|
||||||
#
|
#
|
||||||
|
# Allows the ability to withhold sections from showing up in the public
|
||||||
|
# calls to /info. The following would cause the sections 'container_quotas'
|
||||||
|
# and 'tempurl' to not be listed. Default is empty, allowing all registered
|
||||||
|
# fetures to be listed via HTTP GET /info.
|
||||||
|
# disallowed_sections = container_quotas, tempurl
|
||||||
|
|
||||||
# Use an integer to override the number of pre-forked processes that will
|
# Use an integer to override the number of pre-forked processes that will
|
||||||
# accept connections. Should default to the number of effective cpu
|
# accept connections. Should default to the number of effective cpu
|
||||||
# cores in the system. It's worth noting that individual workers will
|
# cores in the system. It's worth noting that individual workers will
|
||||||
|
@ -48,6 +48,7 @@ post -m quota-bytes:
|
|||||||
|
|
||||||
from swift.common.swob import HTTPForbidden, HTTPRequestEntityTooLarge, \
|
from swift.common.swob import HTTPForbidden, HTTPRequestEntityTooLarge, \
|
||||||
HTTPBadRequest, wsgify
|
HTTPBadRequest, wsgify
|
||||||
|
from swift.common.utils import register_swift_info
|
||||||
from swift.proxy.controllers.base import get_account_info, get_object_info
|
from swift.proxy.controllers.base import get_account_info, get_object_info
|
||||||
|
|
||||||
|
|
||||||
@ -132,6 +133,8 @@ class AccountQuotaMiddleware(object):
|
|||||||
|
|
||||||
def filter_factory(global_conf, **local_conf):
|
def filter_factory(global_conf, **local_conf):
|
||||||
"""Returns a WSGI filter app for use with paste.deploy."""
|
"""Returns a WSGI filter app for use with paste.deploy."""
|
||||||
|
register_swift_info('account_quotas')
|
||||||
|
|
||||||
def account_quota_filter(app):
|
def account_quota_filter(app):
|
||||||
return AccountQuotaMiddleware(app)
|
return AccountQuotaMiddleware(app)
|
||||||
return account_quota_filter
|
return account_quota_filter
|
||||||
|
@ -22,7 +22,7 @@ from swift.common.swob import Request, HTTPBadGateway, \
|
|||||||
HTTPCreated, HTTPBadRequest, HTTPNotFound, HTTPUnauthorized, HTTPOk, \
|
HTTPCreated, HTTPBadRequest, HTTPNotFound, HTTPUnauthorized, HTTPOk, \
|
||||||
HTTPPreconditionFailed, HTTPRequestEntityTooLarge, HTTPNotAcceptable, \
|
HTTPPreconditionFailed, HTTPRequestEntityTooLarge, HTTPNotAcceptable, \
|
||||||
HTTPLengthRequired, HTTPException, HTTPServerError, wsgify
|
HTTPLengthRequired, HTTPException, HTTPServerError, wsgify
|
||||||
from swift.common.utils import json, get_logger
|
from swift.common.utils import json, get_logger, register_swift_info
|
||||||
from swift.common.constraints import check_utf8, MAX_FILE_SIZE
|
from swift.common.constraints import check_utf8, MAX_FILE_SIZE
|
||||||
from swift.common.http import HTTP_UNAUTHORIZED, HTTP_NOT_FOUND
|
from swift.common.http import HTTP_UNAUTHORIZED, HTTP_NOT_FOUND
|
||||||
from swift.common.constraints import MAX_OBJECT_NAME_LENGTH, \
|
from swift.common.constraints import MAX_OBJECT_NAME_LENGTH, \
|
||||||
@ -542,6 +542,7 @@ class Bulk(object):
|
|||||||
def filter_factory(global_conf, **local_conf):
|
def filter_factory(global_conf, **local_conf):
|
||||||
conf = global_conf.copy()
|
conf = global_conf.copy()
|
||||||
conf.update(local_conf)
|
conf.update(local_conf)
|
||||||
|
register_swift_info('bulk')
|
||||||
|
|
||||||
def bulk_filter(app):
|
def bulk_filter(app):
|
||||||
return Bulk(app, conf)
|
return Bulk(app, conf)
|
||||||
|
@ -43,8 +43,9 @@ set:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from swift.common.http import is_success
|
from swift.common.http import is_success
|
||||||
from swift.proxy.controllers.base import get_container_info, get_object_info
|
|
||||||
from swift.common.swob import Response, HTTPBadRequest, wsgify
|
from swift.common.swob import Response, HTTPBadRequest, wsgify
|
||||||
|
from swift.common.utils import register_swift_info
|
||||||
|
from swift.proxy.controllers.base import get_container_info, get_object_info
|
||||||
|
|
||||||
|
|
||||||
class ContainerQuotaMiddleware(object):
|
class ContainerQuotaMiddleware(object):
|
||||||
@ -113,6 +114,8 @@ class ContainerQuotaMiddleware(object):
|
|||||||
|
|
||||||
|
|
||||||
def filter_factory(global_conf, **local_conf):
|
def filter_factory(global_conf, **local_conf):
|
||||||
|
register_swift_info('container_quotas')
|
||||||
|
|
||||||
def container_quota_filter(app):
|
def container_quota_filter(app):
|
||||||
return ContainerQuotaMiddleware(app)
|
return ContainerQuotaMiddleware(app)
|
||||||
return container_quota_filter
|
return container_quota_filter
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from swift.common.swob import Request, Response
|
from swift.common.swob import Request, Response
|
||||||
|
from swift.common.utils import register_swift_info
|
||||||
|
|
||||||
|
|
||||||
class CrossDomainMiddleware(object):
|
class CrossDomainMiddleware(object):
|
||||||
@ -84,6 +85,7 @@ class CrossDomainMiddleware(object):
|
|||||||
def filter_factory(global_conf, **local_conf):
|
def filter_factory(global_conf, **local_conf):
|
||||||
conf = global_conf.copy()
|
conf = global_conf.copy()
|
||||||
conf.update(local_conf)
|
conf.update(local_conf)
|
||||||
|
register_swift_info('crossdomain')
|
||||||
|
|
||||||
def crossdomain_filter(app):
|
def crossdomain_filter(app):
|
||||||
return CrossDomainMiddleware(app, conf)
|
return CrossDomainMiddleware(app, conf)
|
||||||
|
@ -110,7 +110,7 @@ from time import time
|
|||||||
from urllib import quote
|
from urllib import quote
|
||||||
|
|
||||||
from swift.common.middleware.tempurl import get_tempurl_keys_from_metadata
|
from swift.common.middleware.tempurl import get_tempurl_keys_from_metadata
|
||||||
from swift.common.utils import streq_const_time
|
from swift.common.utils import streq_const_time, register_swift_info
|
||||||
from swift.common.wsgi import make_pre_authed_env
|
from swift.common.wsgi import make_pre_authed_env
|
||||||
from swift.common.swob import HTTPUnauthorized
|
from swift.common.swob import HTTPUnauthorized
|
||||||
from swift.proxy.controllers.base import get_account_info
|
from swift.proxy.controllers.base import get_account_info
|
||||||
@ -502,4 +502,5 @@ def filter_factory(global_conf, **local_conf):
|
|||||||
"""Returns the WSGI filter for use with paste.deploy."""
|
"""Returns the WSGI filter for use with paste.deploy."""
|
||||||
conf = global_conf.copy()
|
conf = global_conf.copy()
|
||||||
conf.update(local_conf)
|
conf.update(local_conf)
|
||||||
|
register_swift_info('formpost')
|
||||||
return lambda app: FormPost(app, conf)
|
return lambda app: FormPost(app, conf)
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
from swift.common import utils as swift_utils
|
from swift.common import utils as swift_utils
|
||||||
from swift.common.middleware import acl as swift_acl
|
from swift.common.middleware import acl as swift_acl
|
||||||
from swift.common.swob import HTTPNotFound, HTTPForbidden, HTTPUnauthorized
|
from swift.common.swob import HTTPNotFound, HTTPForbidden, HTTPUnauthorized
|
||||||
|
from swift.common.utils import register_swift_info
|
||||||
|
|
||||||
|
|
||||||
class KeystoneAuth(object):
|
class KeystoneAuth(object):
|
||||||
@ -334,6 +335,7 @@ def filter_factory(global_conf, **local_conf):
|
|||||||
"""Returns a WSGI filter app for use with paste.deploy."""
|
"""Returns a WSGI filter app for use with paste.deploy."""
|
||||||
conf = global_conf.copy()
|
conf = global_conf.copy()
|
||||||
conf.update(local_conf)
|
conf.update(local_conf)
|
||||||
|
register_swift_info('keystoneauth')
|
||||||
|
|
||||||
def auth_filter(app):
|
def auth_filter(app):
|
||||||
return KeystoneAuth(app, conf)
|
return KeystoneAuth(app, conf)
|
||||||
|
@ -17,7 +17,7 @@ from swift import gettext_ as _
|
|||||||
|
|
||||||
import eventlet
|
import eventlet
|
||||||
|
|
||||||
from swift.common.utils import cache_from_env, get_logger
|
from swift.common.utils import cache_from_env, get_logger, register_swift_info
|
||||||
from swift.proxy.controllers.base import get_container_memcache_key
|
from swift.proxy.controllers.base import get_container_memcache_key
|
||||||
from swift.common.memcached import MemcacheConnectionError
|
from swift.common.memcached import MemcacheConnectionError
|
||||||
from swift.common.swob import Request, Response
|
from swift.common.swob import Request, Response
|
||||||
@ -274,6 +274,7 @@ def filter_factory(global_conf, **local_conf):
|
|||||||
"""
|
"""
|
||||||
conf = global_conf.copy()
|
conf = global_conf.copy()
|
||||||
conf.update(local_conf)
|
conf.update(local_conf)
|
||||||
|
register_swift_info('ratelimit')
|
||||||
|
|
||||||
def limit_filter(app):
|
def limit_filter(app):
|
||||||
return RateLimitMiddleware(app, conf)
|
return RateLimitMiddleware(app, conf)
|
||||||
|
@ -143,7 +143,8 @@ from swift.common.swob import Request, HTTPBadRequest, HTTPServerError, \
|
|||||||
HTTPMethodNotAllowed, HTTPRequestEntityTooLarge, HTTPLengthRequired, \
|
HTTPMethodNotAllowed, HTTPRequestEntityTooLarge, HTTPLengthRequired, \
|
||||||
HTTPOk, HTTPPreconditionFailed, HTTPException, HTTPNotFound, \
|
HTTPOk, HTTPPreconditionFailed, HTTPException, HTTPNotFound, \
|
||||||
HTTPUnauthorized
|
HTTPUnauthorized
|
||||||
from swift.common.utils import json, get_logger, config_true_value
|
from swift.common.utils import (json, get_logger, config_true_value,
|
||||||
|
register_swift_info)
|
||||||
from swift.common.constraints import check_utf8, MAX_BUFFERED_SLO_SEGMENTS
|
from swift.common.constraints import check_utf8, MAX_BUFFERED_SLO_SEGMENTS
|
||||||
from swift.common.http import HTTP_NOT_FOUND, HTTP_UNAUTHORIZED
|
from swift.common.http import HTTP_NOT_FOUND, HTTP_UNAUTHORIZED
|
||||||
from swift.common.wsgi import WSGIContext
|
from swift.common.wsgi import WSGIContext
|
||||||
@ -461,6 +462,7 @@ class StaticLargeObject(object):
|
|||||||
def filter_factory(global_conf, **local_conf):
|
def filter_factory(global_conf, **local_conf):
|
||||||
conf = global_conf.copy()
|
conf = global_conf.copy()
|
||||||
conf.update(local_conf)
|
conf.update(local_conf)
|
||||||
|
register_swift_info('slo')
|
||||||
|
|
||||||
def slo_filter(app):
|
def slo_filter(app):
|
||||||
return StaticLargeObject(app, conf)
|
return StaticLargeObject(app, conf)
|
||||||
|
@ -120,7 +120,7 @@ import cgi
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from swift.common.utils import human_readable, split_path, config_true_value, \
|
from swift.common.utils import human_readable, split_path, config_true_value, \
|
||||||
json, quote, get_valid_utf8_str
|
json, quote, get_valid_utf8_str, register_swift_info
|
||||||
from swift.common.wsgi import make_pre_authed_env, WSGIContext
|
from swift.common.wsgi import make_pre_authed_env, WSGIContext
|
||||||
from swift.common.http import is_success, is_redirection, HTTP_NOT_FOUND
|
from swift.common.http import is_success, is_redirection, HTTP_NOT_FOUND
|
||||||
from swift.common.swob import Response, HTTPMovedPermanently, HTTPNotFound
|
from swift.common.swob import Response, HTTPMovedPermanently, HTTPNotFound
|
||||||
@ -468,6 +468,7 @@ def filter_factory(global_conf, **local_conf):
|
|||||||
"""Returns a Static Web WSGI filter for use with paste.deploy."""
|
"""Returns a Static Web WSGI filter for use with paste.deploy."""
|
||||||
conf = global_conf.copy()
|
conf = global_conf.copy()
|
||||||
conf.update(local_conf)
|
conf.update(local_conf)
|
||||||
|
register_swift_info('staticweb')
|
||||||
|
|
||||||
def staticweb_filter(app):
|
def staticweb_filter(app):
|
||||||
return StaticWeb(app, conf)
|
return StaticWeb(app, conf)
|
||||||
|
@ -28,7 +28,7 @@ from swift.common.swob import HTTPBadRequest, HTTPForbidden, HTTPNotFound, \
|
|||||||
|
|
||||||
from swift.common.middleware.acl import clean_acl, parse_acl, referrer_allowed
|
from swift.common.middleware.acl import clean_acl, parse_acl, referrer_allowed
|
||||||
from swift.common.utils import cache_from_env, get_logger, \
|
from swift.common.utils import cache_from_env, get_logger, \
|
||||||
split_path, config_true_value
|
split_path, config_true_value, register_swift_info
|
||||||
|
|
||||||
|
|
||||||
class TempAuth(object):
|
class TempAuth(object):
|
||||||
@ -510,6 +510,7 @@ def filter_factory(global_conf, **local_conf):
|
|||||||
"""Returns a WSGI filter app for use with paste.deploy."""
|
"""Returns a WSGI filter app for use with paste.deploy."""
|
||||||
conf = global_conf.copy()
|
conf = global_conf.copy()
|
||||||
conf.update(local_conf)
|
conf.update(local_conf)
|
||||||
|
register_swift_info('tempauth')
|
||||||
|
|
||||||
def auth_filter(app):
|
def auth_filter(app):
|
||||||
return TempAuth(app, conf)
|
return TempAuth(app, conf)
|
||||||
|
@ -89,8 +89,6 @@ __all__ = ['TempURL', 'filter_factory',
|
|||||||
'DEFAULT_OUTGOING_ALLOW_HEADERS']
|
'DEFAULT_OUTGOING_ALLOW_HEADERS']
|
||||||
|
|
||||||
|
|
||||||
import hmac
|
|
||||||
from hashlib import sha1
|
|
||||||
from os.path import basename
|
from os.path import basename
|
||||||
from time import time
|
from time import time
|
||||||
from urllib import urlencode
|
from urllib import urlencode
|
||||||
@ -98,7 +96,8 @@ from urlparse import parse_qs
|
|||||||
|
|
||||||
from swift.proxy.controllers.base import get_account_info
|
from swift.proxy.controllers.base import get_account_info
|
||||||
from swift.common.swob import HeaderKeyDict, HTTPUnauthorized
|
from swift.common.swob import HeaderKeyDict, HTTPUnauthorized
|
||||||
from swift.common.utils import split_path, get_valid_utf8_str
|
from swift.common.utils import split_path, get_valid_utf8_str, \
|
||||||
|
register_swift_info, get_hmac
|
||||||
|
|
||||||
|
|
||||||
#: Default headers to remove from incoming requests. Simply a whitespace
|
#: Default headers to remove from incoming requests. Simply a whitespace
|
||||||
@ -389,31 +388,10 @@ class TempURL(object):
|
|||||||
:param keys: Key strings, from the X-Account-Meta-Temp-URL-Key[-2] of
|
:param keys: Key strings, from the X-Account-Meta-Temp-URL-Key[-2] of
|
||||||
the account.
|
the account.
|
||||||
"""
|
"""
|
||||||
return [self._get_hmac(env, expires, key, request_method)
|
|
||||||
for key in keys]
|
|
||||||
|
|
||||||
def _get_hmac(self, env, expires, key, request_method=None):
|
|
||||||
"""
|
|
||||||
Returns the hexdigest string of the HMAC-SHA1 (RFC 2104) for
|
|
||||||
the request.
|
|
||||||
|
|
||||||
:param env: The WSGI environment for the request.
|
|
||||||
:param expires: Unix timestamp as an int for when the URL
|
|
||||||
expires.
|
|
||||||
:param key: Key str, from the X-Account-Meta-Temp-URL-Key of
|
|
||||||
the account.
|
|
||||||
:param request_method: Optional override of the request in
|
|
||||||
the WSGI env. For example, if a HEAD
|
|
||||||
does not match, you may wish to
|
|
||||||
override with GET to still allow the
|
|
||||||
HEAD.
|
|
||||||
:returns: hexdigest str of the HMAC-SHA1 for the request.
|
|
||||||
"""
|
|
||||||
if not request_method:
|
if not request_method:
|
||||||
request_method = env['REQUEST_METHOD']
|
request_method = env['REQUEST_METHOD']
|
||||||
return hmac.new(
|
return [get_hmac(
|
||||||
key, '%s\n%s\n%s' % (request_method, expires,
|
request_method, env['PATH_INFO'], expires, key) for key in keys]
|
||||||
env['PATH_INFO']), sha1).hexdigest()
|
|
||||||
|
|
||||||
def _invalid(self, env, start_response):
|
def _invalid(self, env, start_response):
|
||||||
"""
|
"""
|
||||||
@ -492,4 +470,5 @@ def filter_factory(global_conf, **local_conf):
|
|||||||
"""Returns the WSGI filter for use with paste.deploy."""
|
"""Returns the WSGI filter for use with paste.deploy."""
|
||||||
conf = global_conf.copy()
|
conf = global_conf.copy()
|
||||||
conf.update(local_conf)
|
conf.update(local_conf)
|
||||||
|
register_swift_info('tempurl')
|
||||||
return lambda app: TempURL(app, conf)
|
return lambda app: TempURL(app, conf)
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
import errno
|
import errno
|
||||||
import fcntl
|
import fcntl
|
||||||
|
import hmac
|
||||||
import operator
|
import operator
|
||||||
import os
|
import os
|
||||||
import pwd
|
import pwd
|
||||||
@ -26,7 +27,7 @@ import threading as stdlib_threading
|
|||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
import functools
|
import functools
|
||||||
from hashlib import md5
|
from hashlib import md5, sha1
|
||||||
from random import random, shuffle
|
from random import random, shuffle
|
||||||
from urllib import quote as _quote
|
from urllib import quote as _quote
|
||||||
from contextlib import contextmanager, closing
|
from contextlib import contextmanager, closing
|
||||||
@ -100,6 +101,78 @@ if hash_conf.read('/etc/swift/swift.conf'):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_hmac(request_method, path, expires, key):
|
||||||
|
"""
|
||||||
|
Returns the hexdigest string of the HMAC-SHA1 (RFC 2104) for
|
||||||
|
the request.
|
||||||
|
|
||||||
|
:param request_method: Request method to allow.
|
||||||
|
:param path: The path to the resource to allow access to.
|
||||||
|
:param expires: Unix timestamp as an int for when the URL
|
||||||
|
expires.
|
||||||
|
:param key: HMAC shared secret.
|
||||||
|
|
||||||
|
:returns: hexdigest str of the HMAC-SHA1 for the request.
|
||||||
|
"""
|
||||||
|
return hmac.new(
|
||||||
|
key, '%s\n%s\n%s' % (request_method, expires, path), sha1).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
# Used by get_swift_info and register_swift_info to store information about
|
||||||
|
# the swift cluster.
|
||||||
|
_swift_info = {}
|
||||||
|
_swift_admin_info = {}
|
||||||
|
|
||||||
|
|
||||||
|
def get_swift_info(admin=False, disallowed_sections=None):
|
||||||
|
"""
|
||||||
|
Returns information about the swift cluster that has been previously
|
||||||
|
registered with the register_swift_info call.
|
||||||
|
|
||||||
|
:param admin: boolean value, if True will additionally return an 'admin'
|
||||||
|
section with information previously registered as admin
|
||||||
|
info.
|
||||||
|
:param disallowed_sections: list of section names to be withheld from the
|
||||||
|
information returned.
|
||||||
|
:returns: dictionary of information about the swift cluster.
|
||||||
|
"""
|
||||||
|
disallowed_sections = disallowed_sections or []
|
||||||
|
info = {}
|
||||||
|
for section in _swift_info:
|
||||||
|
if section in disallowed_sections:
|
||||||
|
continue
|
||||||
|
info[section] = dict(_swift_info[section].items())
|
||||||
|
if admin:
|
||||||
|
info['admin'] = dict(_swift_admin_info)
|
||||||
|
info['admin']['disallowed_sections'] = list(disallowed_sections)
|
||||||
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
def register_swift_info(name='swift', admin=False, **kwargs):
|
||||||
|
"""
|
||||||
|
Registers information about the swift cluster to be retrieved with calls
|
||||||
|
to get_swift_info.
|
||||||
|
|
||||||
|
:param name: string, the section name to place the information under.
|
||||||
|
:param admin: boolean, if True, information will be registered to an
|
||||||
|
admin section which can optionally be withheld when
|
||||||
|
requesting the information.
|
||||||
|
:param kwargs: key value arguments representing the information to be
|
||||||
|
added.
|
||||||
|
"""
|
||||||
|
if name == 'admin' or name == 'disallowed_sections':
|
||||||
|
raise ValueError('\'{0}\' is reserved name.'.format(name))
|
||||||
|
|
||||||
|
if admin:
|
||||||
|
dict_to_use = _swift_admin_info
|
||||||
|
else:
|
||||||
|
dict_to_use = _swift_info
|
||||||
|
if name not in dict_to_use:
|
||||||
|
dict_to_use[name] = {}
|
||||||
|
for key, val in kwargs.iteritems():
|
||||||
|
dict_to_use[name][key] = val
|
||||||
|
|
||||||
|
|
||||||
def backward(f, blocksize=4096):
|
def backward(f, blocksize=4096):
|
||||||
"""
|
"""
|
||||||
A generator returning lines from a file starting with the last line,
|
A generator returning lines from a file starting with the last line,
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from swift.proxy.controllers.base import Controller
|
from swift.proxy.controllers.base import Controller
|
||||||
|
from swift.proxy.controllers.info import InfoController
|
||||||
from swift.proxy.controllers.obj import ObjectController
|
from swift.proxy.controllers.obj import ObjectController
|
||||||
from swift.proxy.controllers.account import AccountController
|
from swift.proxy.controllers.account import AccountController
|
||||||
from swift.proxy.controllers.container import ContainerController
|
from swift.proxy.controllers.container import ContainerController
|
||||||
@ -20,5 +21,6 @@ __all__ = [
|
|||||||
'AccountController',
|
'AccountController',
|
||||||
'ContainerController',
|
'ContainerController',
|
||||||
'Controller',
|
'Controller',
|
||||||
|
'InfoController',
|
||||||
'ObjectController',
|
'ObjectController',
|
||||||
]
|
]
|
||||||
|
100
swift/proxy/controllers/info.py
Normal file
100
swift/proxy/controllers/info.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# Copyright (c) 2010-2012 OpenStack Foundation
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
from swift.common.utils import public, get_hmac, get_swift_info, json
|
||||||
|
from swift.proxy.controllers.base import Controller, delay_denial
|
||||||
|
from swift.common.swob import HTTPOk, HTTPForbidden, HTTPUnauthorized
|
||||||
|
|
||||||
|
|
||||||
|
class InfoController(Controller):
|
||||||
|
"""WSGI controller for info requests"""
|
||||||
|
server_type = 'Info'
|
||||||
|
|
||||||
|
def __init__(self, app, version, expose_info, disallowed_sections,
|
||||||
|
admin_key):
|
||||||
|
Controller.__init__(self, app)
|
||||||
|
self.expose_info = expose_info
|
||||||
|
self.disallowed_sections = disallowed_sections
|
||||||
|
self.admin_key = admin_key
|
||||||
|
self.allowed_hmac_methods = {
|
||||||
|
'HEAD': ['HEAD', 'GET'],
|
||||||
|
'GET': ['GET']}
|
||||||
|
|
||||||
|
@public
|
||||||
|
@delay_denial
|
||||||
|
def GET(self, req):
|
||||||
|
return self.GETorHEAD(req)
|
||||||
|
|
||||||
|
@public
|
||||||
|
@delay_denial
|
||||||
|
def HEAD(self, req):
|
||||||
|
return self.GETorHEAD(req)
|
||||||
|
|
||||||
|
@public
|
||||||
|
@delay_denial
|
||||||
|
def OPTIONS(self, req):
|
||||||
|
return HTTPOk(request=req, headers={'Allow': 'HEAD, GET, OPTIONS'})
|
||||||
|
|
||||||
|
def GETorHEAD(self, req):
|
||||||
|
"""Handler for HTTP GET/HEAD requests."""
|
||||||
|
"""
|
||||||
|
Handles requests to /info
|
||||||
|
Should return a WSGI-style callable (such as swob.Response).
|
||||||
|
|
||||||
|
:param req: swob.Request object
|
||||||
|
"""
|
||||||
|
if not self.expose_info:
|
||||||
|
return HTTPForbidden(request=req)
|
||||||
|
|
||||||
|
admin_request = False
|
||||||
|
sig = req.params.get('swiftinfo_sig', '')
|
||||||
|
expires = req.params.get('swiftinfo_expires', '')
|
||||||
|
|
||||||
|
if sig != '' or expires != '':
|
||||||
|
admin_request = True
|
||||||
|
if not self.admin_key:
|
||||||
|
return HTTPForbidden(request=req)
|
||||||
|
try:
|
||||||
|
expires = int(expires)
|
||||||
|
except ValueError:
|
||||||
|
return HTTPUnauthorized(request=req)
|
||||||
|
if expires < time():
|
||||||
|
return HTTPUnauthorized(request=req)
|
||||||
|
|
||||||
|
valid_sigs = []
|
||||||
|
for method in self.allowed_hmac_methods[req.method]:
|
||||||
|
valid_sigs.append(get_hmac(method,
|
||||||
|
'/info',
|
||||||
|
expires,
|
||||||
|
self.admin_key))
|
||||||
|
|
||||||
|
if sig not in valid_sigs:
|
||||||
|
return HTTPUnauthorized(request=req)
|
||||||
|
|
||||||
|
headers = {}
|
||||||
|
if 'Origin' in req.headers:
|
||||||
|
headers['Access-Control-Allow-Origin'] = req.headers['Origin']
|
||||||
|
headers['Access-Control-Expose-Headers'] = ', '.join(
|
||||||
|
['x-trans-id'])
|
||||||
|
|
||||||
|
info = json.dumps(get_swift_info(
|
||||||
|
admin=admin_request, disallowed_sections=self.disallowed_sections))
|
||||||
|
|
||||||
|
return HTTPOk(request=req,
|
||||||
|
headers=headers,
|
||||||
|
body=info,
|
||||||
|
content_type='application/json; charset=UTF-8')
|
@ -23,13 +23,15 @@ import itertools
|
|||||||
|
|
||||||
from eventlet import Timeout
|
from eventlet import Timeout
|
||||||
|
|
||||||
|
from swift import __canonical_version__ as swift_version
|
||||||
from swift.common.ring import Ring
|
from swift.common.ring import Ring
|
||||||
from swift.common.utils import cache_from_env, get_logger, \
|
from swift.common.utils import cache_from_env, get_logger, \
|
||||||
get_remote_client, split_path, config_true_value, generate_trans_id, \
|
get_remote_client, split_path, config_true_value, generate_trans_id, \
|
||||||
affinity_key_function, affinity_locality_predicate
|
affinity_key_function, affinity_locality_predicate, list_from_csv, \
|
||||||
|
register_swift_info
|
||||||
from swift.common.constraints import check_utf8
|
from swift.common.constraints import check_utf8
|
||||||
from swift.proxy.controllers import AccountController, ObjectController, \
|
from swift.proxy.controllers import AccountController, ObjectController, \
|
||||||
ContainerController
|
ContainerController, InfoController
|
||||||
from swift.common.swob import HTTPBadRequest, HTTPForbidden, \
|
from swift.common.swob import HTTPBadRequest, HTTPForbidden, \
|
||||||
HTTPMethodNotAllowed, HTTPNotFound, HTTPPreconditionFailed, \
|
HTTPMethodNotAllowed, HTTPNotFound, HTTPPreconditionFailed, \
|
||||||
HTTPServerError, HTTPException, Request
|
HTTPServerError, HTTPException, Request
|
||||||
@ -164,6 +166,12 @@ class Application(object):
|
|||||||
# ** Because it affects the client as well, currently, we use the
|
# ** Because it affects the client as well, currently, we use the
|
||||||
# client chunk size as the govenor and not the object chunk size.
|
# client chunk size as the govenor and not the object chunk size.
|
||||||
socket._fileobject.default_bufsize = self.client_chunk_size
|
socket._fileobject.default_bufsize = self.client_chunk_size
|
||||||
|
self.expose_info = config_true_value(
|
||||||
|
conf.get('expose_info', 'yes'))
|
||||||
|
self.disallowed_sections = list_from_csv(
|
||||||
|
conf.get('disallowed_sections'))
|
||||||
|
self.admin_key = conf.get('admin_key', None)
|
||||||
|
register_swift_info(version=swift_version)
|
||||||
|
|
||||||
def get_controller(self, path):
|
def get_controller(self, path):
|
||||||
"""
|
"""
|
||||||
@ -174,6 +182,13 @@ class Application(object):
|
|||||||
|
|
||||||
:raises: ValueError (thrown by split_path) if given invalid path
|
:raises: ValueError (thrown by split_path) if given invalid path
|
||||||
"""
|
"""
|
||||||
|
if path == '/info':
|
||||||
|
d = dict(version=None,
|
||||||
|
expose_info=self.expose_info,
|
||||||
|
disallowed_sections=self.disallowed_sections,
|
||||||
|
admin_key=self.admin_key)
|
||||||
|
return InfoController, d
|
||||||
|
|
||||||
version, account, container, obj = split_path(path, 1, 4, True)
|
version, account, container, obj = split_path(path, 1, 4, True)
|
||||||
d = dict(version=version,
|
d = dict(version=version,
|
||||||
account_name=account,
|
account_name=account,
|
||||||
|
@ -687,17 +687,17 @@ class TestTempURL(unittest.TestCase):
|
|||||||
s, e)}),
|
s, e)}),
|
||||||
(s, 0, None, None))
|
(s, 0, None, None))
|
||||||
|
|
||||||
def test_get_hmac(self):
|
def test_get_hmacs(self):
|
||||||
self.assertEquals(
|
self.assertEquals(
|
||||||
self.tempurl._get_hmac(
|
self.tempurl._get_hmacs(
|
||||||
{'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c/o'},
|
{'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c/o'},
|
||||||
1, 'abc'),
|
1, ['abc']),
|
||||||
'026d7f7cc25256450423c7ad03fc9f5ffc1dab6d')
|
['026d7f7cc25256450423c7ad03fc9f5ffc1dab6d'])
|
||||||
self.assertEquals(
|
self.assertEquals(
|
||||||
self.tempurl._get_hmac(
|
self.tempurl._get_hmacs(
|
||||||
{'REQUEST_METHOD': 'HEAD', 'PATH_INFO': '/v1/a/c/o'},
|
{'REQUEST_METHOD': 'HEAD', 'PATH_INFO': '/v1/a/c/o'},
|
||||||
1, 'abc', request_method='GET'),
|
1, ['abc'], request_method='GET'),
|
||||||
'026d7f7cc25256450423c7ad03fc9f5ffc1dab6d')
|
['026d7f7cc25256450423c7ad03fc9f5ffc1dab6d'])
|
||||||
|
|
||||||
def test_invalid(self):
|
def test_invalid(self):
|
||||||
|
|
||||||
|
@ -1662,6 +1662,159 @@ log_name = %(yarr)s'''
|
|||||||
self.assertEquals('abc_%EF%BF%BD%EF%BF%BD%EC%BC%9D%EF%BF%BD',
|
self.assertEquals('abc_%EF%BF%BD%EF%BF%BD%EC%BC%9D%EF%BF%BD',
|
||||||
utils.quote(invalid_utf8_str))
|
utils.quote(invalid_utf8_str))
|
||||||
|
|
||||||
|
def test_get_hmac(self):
|
||||||
|
self.assertEquals(
|
||||||
|
utils.get_hmac('GET', '/path', 1, 'abc'),
|
||||||
|
'b17f6ff8da0e251737aa9e3ee69a881e3e092e2f')
|
||||||
|
|
||||||
|
|
||||||
|
class TestSwiftInfo(unittest.TestCase):
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
utils._swift_info = {}
|
||||||
|
utils._swift_admin_info = {}
|
||||||
|
|
||||||
|
def test_register_swift_info(self):
|
||||||
|
utils.register_swift_info(foo='bar')
|
||||||
|
utils.register_swift_info(lorem='ipsum')
|
||||||
|
utils.register_swift_info('cap1', cap1_foo='cap1_bar')
|
||||||
|
utils.register_swift_info('cap1', cap1_lorem='cap1_ipsum')
|
||||||
|
|
||||||
|
self.assertTrue('swift' in utils._swift_info)
|
||||||
|
self.assertTrue('foo' in utils._swift_info['swift'])
|
||||||
|
self.assertEqual(utils._swift_info['swift']['foo'], 'bar')
|
||||||
|
self.assertTrue('lorem' in utils._swift_info['swift'])
|
||||||
|
self.assertEqual(utils._swift_info['swift']['lorem'], 'ipsum')
|
||||||
|
|
||||||
|
self.assertTrue('cap1' in utils._swift_info)
|
||||||
|
self.assertTrue('cap1_foo' in utils._swift_info['cap1'])
|
||||||
|
self.assertEqual(utils._swift_info['cap1']['cap1_foo'], 'cap1_bar')
|
||||||
|
self.assertTrue('cap1_lorem' in utils._swift_info['cap1'])
|
||||||
|
self.assertEqual(utils._swift_info['cap1']['cap1_lorem'], 'cap1_ipsum')
|
||||||
|
|
||||||
|
self.assertRaises(ValueError,
|
||||||
|
utils.register_swift_info, 'admin', foo='bar')
|
||||||
|
|
||||||
|
self.assertRaises(ValueError,
|
||||||
|
utils.register_swift_info, 'disallowed_sections',
|
||||||
|
disallowed_sections=None)
|
||||||
|
|
||||||
|
def test_get_swift_info(self):
|
||||||
|
utils._swift_info = {'swift': {'foo': 'bar'},
|
||||||
|
'cap1': {'cap1_foo': 'cap1_bar'}}
|
||||||
|
utils._swift_admin_info = {'admin_cap1': {'ac1_foo': 'ac1_bar'}}
|
||||||
|
|
||||||
|
info = utils.get_swift_info()
|
||||||
|
|
||||||
|
self.assertTrue('admin' not in info)
|
||||||
|
|
||||||
|
self.assertTrue('swift' in info)
|
||||||
|
self.assertTrue('foo' in info['swift'])
|
||||||
|
self.assertEqual(utils._swift_info['swift']['foo'], 'bar')
|
||||||
|
|
||||||
|
self.assertTrue('cap1' in info)
|
||||||
|
self.assertTrue('cap1_foo' in info['cap1'])
|
||||||
|
self.assertEqual(utils._swift_info['cap1']['cap1_foo'], 'cap1_bar')
|
||||||
|
|
||||||
|
def test_get_swift_info_with_disallowed_sections(self):
|
||||||
|
utils._swift_info = {'swift': {'foo': 'bar'},
|
||||||
|
'cap1': {'cap1_foo': 'cap1_bar'},
|
||||||
|
'cap2': {'cap2_foo': 'cap2_bar'},
|
||||||
|
'cap3': {'cap3_foo': 'cap3_bar'}}
|
||||||
|
utils._swift_admin_info = {'admin_cap1': {'ac1_foo': 'ac1_bar'}}
|
||||||
|
|
||||||
|
info = utils.get_swift_info(disallowed_sections=['cap1', 'cap3'])
|
||||||
|
|
||||||
|
self.assertTrue('admin' not in info)
|
||||||
|
|
||||||
|
self.assertTrue('swift' in info)
|
||||||
|
self.assertTrue('foo' in info['swift'])
|
||||||
|
self.assertEqual(info['swift']['foo'], 'bar')
|
||||||
|
|
||||||
|
self.assertTrue('cap1' not in info)
|
||||||
|
|
||||||
|
self.assertTrue('cap2' in info)
|
||||||
|
self.assertTrue('cap2_foo' in info['cap2'])
|
||||||
|
self.assertEqual(info['cap2']['cap2_foo'], 'cap2_bar')
|
||||||
|
|
||||||
|
self.assertTrue('cap3' not in info)
|
||||||
|
|
||||||
|
def test_register_swift_admin_info(self):
|
||||||
|
utils.register_swift_info(admin=True, admin_foo='admin_bar')
|
||||||
|
utils.register_swift_info(admin=True, admin_lorem='admin_ipsum')
|
||||||
|
utils.register_swift_info('cap1', admin=True, ac1_foo='ac1_bar')
|
||||||
|
utils.register_swift_info('cap1', admin=True, ac1_lorem='ac1_ipsum')
|
||||||
|
|
||||||
|
self.assertTrue('swift' in utils._swift_admin_info)
|
||||||
|
self.assertTrue('admin_foo' in utils._swift_admin_info['swift'])
|
||||||
|
self.assertEqual(
|
||||||
|
utils._swift_admin_info['swift']['admin_foo'], 'admin_bar')
|
||||||
|
self.assertTrue('admin_lorem' in utils._swift_admin_info['swift'])
|
||||||
|
self.assertEqual(
|
||||||
|
utils._swift_admin_info['swift']['admin_lorem'], 'admin_ipsum')
|
||||||
|
|
||||||
|
self.assertTrue('cap1' in utils._swift_admin_info)
|
||||||
|
self.assertTrue('ac1_foo' in utils._swift_admin_info['cap1'])
|
||||||
|
self.assertEqual(
|
||||||
|
utils._swift_admin_info['cap1']['ac1_foo'], 'ac1_bar')
|
||||||
|
self.assertTrue('ac1_lorem' in utils._swift_admin_info['cap1'])
|
||||||
|
self.assertEqual(
|
||||||
|
utils._swift_admin_info['cap1']['ac1_lorem'], 'ac1_ipsum')
|
||||||
|
|
||||||
|
self.assertTrue('swift' not in utils._swift_info)
|
||||||
|
self.assertTrue('cap1' not in utils._swift_info)
|
||||||
|
|
||||||
|
def test_get_swift_admin_info(self):
|
||||||
|
utils._swift_info = {'swift': {'foo': 'bar'},
|
||||||
|
'cap1': {'cap1_foo': 'cap1_bar'}}
|
||||||
|
utils._swift_admin_info = {'admin_cap1': {'ac1_foo': 'ac1_bar'}}
|
||||||
|
|
||||||
|
info = utils.get_swift_info(admin=True)
|
||||||
|
|
||||||
|
self.assertTrue('admin' in info)
|
||||||
|
self.assertTrue('admin_cap1' in info['admin'])
|
||||||
|
self.assertTrue('ac1_foo' in info['admin']['admin_cap1'])
|
||||||
|
self.assertEqual(info['admin']['admin_cap1']['ac1_foo'], 'ac1_bar')
|
||||||
|
|
||||||
|
self.assertTrue('swift' in info)
|
||||||
|
self.assertTrue('foo' in info['swift'])
|
||||||
|
self.assertEqual(utils._swift_info['swift']['foo'], 'bar')
|
||||||
|
|
||||||
|
self.assertTrue('cap1' in info)
|
||||||
|
self.assertTrue('cap1_foo' in info['cap1'])
|
||||||
|
self.assertEqual(utils._swift_info['cap1']['cap1_foo'], 'cap1_bar')
|
||||||
|
|
||||||
|
def test_get_swift_admin_info_with_disallowed_sections(self):
|
||||||
|
utils._swift_info = {'swift': {'foo': 'bar'},
|
||||||
|
'cap1': {'cap1_foo': 'cap1_bar'},
|
||||||
|
'cap2': {'cap2_foo': 'cap2_bar'},
|
||||||
|
'cap3': {'cap3_foo': 'cap3_bar'}}
|
||||||
|
utils._swift_admin_info = {'admin_cap1': {'ac1_foo': 'ac1_bar'}}
|
||||||
|
|
||||||
|
info = utils.get_swift_info(
|
||||||
|
admin=True, disallowed_sections=['cap1', 'cap3'])
|
||||||
|
|
||||||
|
self.assertTrue('admin' in info)
|
||||||
|
self.assertTrue('admin_cap1' in info['admin'])
|
||||||
|
self.assertTrue('ac1_foo' in info['admin']['admin_cap1'])
|
||||||
|
self.assertEqual(info['admin']['admin_cap1']['ac1_foo'], 'ac1_bar')
|
||||||
|
self.assertTrue('disallowed_sections' in info['admin'])
|
||||||
|
self.assertTrue('cap1' in info['admin']['disallowed_sections'])
|
||||||
|
self.assertTrue('cap2' not in info['admin']['disallowed_sections'])
|
||||||
|
self.assertTrue('cap3' in info['admin']['disallowed_sections'])
|
||||||
|
|
||||||
|
self.assertTrue('swift' in info)
|
||||||
|
self.assertTrue('foo' in info['swift'])
|
||||||
|
self.assertEqual(info['swift']['foo'], 'bar')
|
||||||
|
|
||||||
|
self.assertTrue('cap1' not in info)
|
||||||
|
|
||||||
|
self.assertTrue('cap2' in info)
|
||||||
|
self.assertTrue('cap2_foo' in info['cap2'])
|
||||||
|
self.assertEqual(info['cap2']['cap2_foo'], 'cap2_bar')
|
||||||
|
|
||||||
|
self.assertTrue('cap3' not in info)
|
||||||
|
|
||||||
|
|
||||||
class TestFileLikeIter(unittest.TestCase):
|
class TestFileLikeIter(unittest.TestCase):
|
||||||
|
|
||||||
|
293
test/unit/proxy/controllers/test_info.py
Normal file
293
test/unit/proxy/controllers/test_info.py
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
# Copyright (c) 2010-2012 OpenStack Foundation
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
import time
|
||||||
|
from mock import Mock
|
||||||
|
|
||||||
|
from swift.proxy.controllers import InfoController
|
||||||
|
from swift.proxy.server import Application as ProxyApp
|
||||||
|
from swift.common import utils
|
||||||
|
from swift.common.utils import json
|
||||||
|
from swift.common.swob import Request, HTTPException
|
||||||
|
|
||||||
|
|
||||||
|
class TestInfoController(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
utils._swift_info = {}
|
||||||
|
utils._swift_admin_info = {}
|
||||||
|
|
||||||
|
def get_controller(self, expose_info=None, disallowed_sections=None,
|
||||||
|
admin_key=None):
|
||||||
|
disallowed_sections = disallowed_sections or []
|
||||||
|
|
||||||
|
app = Mock(spec=ProxyApp)
|
||||||
|
return InfoController(app, None, expose_info,
|
||||||
|
disallowed_sections, admin_key)
|
||||||
|
|
||||||
|
def start_response(self, status, headers):
|
||||||
|
self.got_statuses.append(status)
|
||||||
|
for h in headers:
|
||||||
|
self.got_headers.append({h[0]: h[1]})
|
||||||
|
|
||||||
|
def test_disabled_info(self):
|
||||||
|
controller = self.get_controller(expose_info=False)
|
||||||
|
|
||||||
|
req = Request.blank(
|
||||||
|
'/info', environ={'REQUEST_METHOD': 'GET'})
|
||||||
|
resp = controller.GET(req)
|
||||||
|
self.assertTrue(isinstance(resp, HTTPException))
|
||||||
|
self.assertEqual('403 Forbidden', str(resp))
|
||||||
|
|
||||||
|
def test_get_info(self):
|
||||||
|
controller = self.get_controller(expose_info=True)
|
||||||
|
utils._swift_info = {'foo': {'bar': 'baz'}}
|
||||||
|
utils._swift_admin_info = {'qux': {'quux': 'corge'}}
|
||||||
|
|
||||||
|
req = Request.blank(
|
||||||
|
'/info', environ={'REQUEST_METHOD': 'GET'})
|
||||||
|
resp = controller.GET(req)
|
||||||
|
self.assertTrue(isinstance(resp, HTTPException))
|
||||||
|
self.assertEqual('200 OK', str(resp))
|
||||||
|
info = json.loads(resp.body)
|
||||||
|
self.assertTrue('admin' not in info)
|
||||||
|
self.assertTrue('foo' in info)
|
||||||
|
self.assertTrue('bar' in info['foo'])
|
||||||
|
self.assertEqual(info['foo']['bar'], 'baz')
|
||||||
|
|
||||||
|
def test_options_info(self):
|
||||||
|
controller = self.get_controller(expose_info=True)
|
||||||
|
|
||||||
|
req = Request.blank(
|
||||||
|
'/info', environ={'REQUEST_METHOD': 'GET'})
|
||||||
|
resp = controller.OPTIONS(req)
|
||||||
|
self.assertTrue(isinstance(resp, HTTPException))
|
||||||
|
self.assertEqual('200 OK', str(resp))
|
||||||
|
self.assertTrue('Allow' in resp.headers)
|
||||||
|
|
||||||
|
def test_get_info_cors(self):
|
||||||
|
controller = self.get_controller(expose_info=True)
|
||||||
|
utils._swift_info = {'foo': {'bar': 'baz'}}
|
||||||
|
utils._swift_admin_info = {'qux': {'quux': 'corge'}}
|
||||||
|
|
||||||
|
req = Request.blank(
|
||||||
|
'/info', environ={'REQUEST_METHOD': 'GET'},
|
||||||
|
headers={'Origin': 'http://example.com'})
|
||||||
|
resp = controller.GET(req)
|
||||||
|
self.assertTrue(isinstance(resp, HTTPException))
|
||||||
|
self.assertEqual('200 OK', str(resp))
|
||||||
|
info = json.loads(resp.body)
|
||||||
|
self.assertTrue('admin' not in info)
|
||||||
|
self.assertTrue('foo' in info)
|
||||||
|
self.assertTrue('bar' in info['foo'])
|
||||||
|
self.assertEqual(info['foo']['bar'], 'baz')
|
||||||
|
self.assertTrue('Access-Control-Allow-Origin' in resp.headers)
|
||||||
|
self.assertTrue('Access-Control-Expose-Headers' in resp.headers)
|
||||||
|
|
||||||
|
def test_head_info(self):
|
||||||
|
controller = self.get_controller(expose_info=True)
|
||||||
|
utils._swift_info = {'foo': {'bar': 'baz'}}
|
||||||
|
utils._swift_admin_info = {'qux': {'quux': 'corge'}}
|
||||||
|
|
||||||
|
req = Request.blank(
|
||||||
|
'/info', environ={'REQUEST_METHOD': 'HEAD'})
|
||||||
|
resp = controller.HEAD(req)
|
||||||
|
self.assertTrue(isinstance(resp, HTTPException))
|
||||||
|
self.assertEqual('200 OK', str(resp))
|
||||||
|
|
||||||
|
def test_disallow_info(self):
|
||||||
|
controller = self.get_controller(expose_info=True,
|
||||||
|
disallowed_sections=['foo2'])
|
||||||
|
utils._swift_info = {'foo': {'bar': 'baz'},
|
||||||
|
'foo2': {'bar2': 'baz2'}}
|
||||||
|
utils._swift_admin_info = {'qux': {'quux': 'corge'}}
|
||||||
|
|
||||||
|
req = Request.blank(
|
||||||
|
'/info', environ={'REQUEST_METHOD': 'GET'})
|
||||||
|
resp = controller.GET(req)
|
||||||
|
self.assertTrue(isinstance(resp, HTTPException))
|
||||||
|
self.assertEqual('200 OK', str(resp))
|
||||||
|
info = json.loads(resp.body)
|
||||||
|
self.assertTrue('foo' in info)
|
||||||
|
self.assertTrue('bar' in info['foo'])
|
||||||
|
self.assertEqual(info['foo']['bar'], 'baz')
|
||||||
|
self.assertTrue('foo2' not in info)
|
||||||
|
|
||||||
|
def test_disabled_admin_info(self):
|
||||||
|
controller = self.get_controller(expose_info=True, admin_key='')
|
||||||
|
utils._swift_info = {'foo': {'bar': 'baz'}}
|
||||||
|
utils._swift_admin_info = {'qux': {'quux': 'corge'}}
|
||||||
|
|
||||||
|
expires = int(time.time() + 86400)
|
||||||
|
sig = utils.get_hmac('GET', '/info', expires, '')
|
||||||
|
path = '/info?swiftinfo_sig={sig}&swiftinfo_expires={expires}'.format(
|
||||||
|
sig=sig, expires=expires)
|
||||||
|
req = Request.blank(
|
||||||
|
path, environ={'REQUEST_METHOD': 'GET'})
|
||||||
|
resp = controller.GET(req)
|
||||||
|
self.assertTrue(isinstance(resp, HTTPException))
|
||||||
|
self.assertEqual('403 Forbidden', str(resp))
|
||||||
|
|
||||||
|
def test_get_admin_info(self):
|
||||||
|
controller = self.get_controller(expose_info=True,
|
||||||
|
admin_key='secret-admin-key')
|
||||||
|
utils._swift_info = {'foo': {'bar': 'baz'}}
|
||||||
|
utils._swift_admin_info = {'qux': {'quux': 'corge'}}
|
||||||
|
|
||||||
|
expires = int(time.time() + 86400)
|
||||||
|
sig = utils.get_hmac('GET', '/info', expires, 'secret-admin-key')
|
||||||
|
path = '/info?swiftinfo_sig={sig}&swiftinfo_expires={expires}'.format(
|
||||||
|
sig=sig, expires=expires)
|
||||||
|
req = Request.blank(
|
||||||
|
path, environ={'REQUEST_METHOD': 'GET'})
|
||||||
|
resp = controller.GET(req)
|
||||||
|
self.assertTrue(isinstance(resp, HTTPException))
|
||||||
|
self.assertEqual('200 OK', str(resp))
|
||||||
|
info = json.loads(resp.body)
|
||||||
|
self.assertTrue('admin' in info)
|
||||||
|
self.assertTrue('qux' in info['admin'])
|
||||||
|
self.assertTrue('quux' in info['admin']['qux'])
|
||||||
|
self.assertEqual(info['admin']['qux']['quux'], 'corge')
|
||||||
|
|
||||||
|
def test_head_admin_info(self):
|
||||||
|
controller = self.get_controller(expose_info=True,
|
||||||
|
admin_key='secret-admin-key')
|
||||||
|
utils._swift_info = {'foo': {'bar': 'baz'}}
|
||||||
|
utils._swift_admin_info = {'qux': {'quux': 'corge'}}
|
||||||
|
|
||||||
|
expires = int(time.time() + 86400)
|
||||||
|
sig = utils.get_hmac('GET', '/info', expires, 'secret-admin-key')
|
||||||
|
path = '/info?swiftinfo_sig={sig}&swiftinfo_expires={expires}'.format(
|
||||||
|
sig=sig, expires=expires)
|
||||||
|
req = Request.blank(
|
||||||
|
path, environ={'REQUEST_METHOD': 'HEAD'})
|
||||||
|
resp = controller.GET(req)
|
||||||
|
self.assertTrue(isinstance(resp, HTTPException))
|
||||||
|
self.assertEqual('200 OK', str(resp))
|
||||||
|
|
||||||
|
expires = int(time.time() + 86400)
|
||||||
|
sig = utils.get_hmac('HEAD', '/info', expires, 'secret-admin-key')
|
||||||
|
path = '/info?swiftinfo_sig={sig}&swiftinfo_expires={expires}'.format(
|
||||||
|
sig=sig, expires=expires)
|
||||||
|
req = Request.blank(
|
||||||
|
path, environ={'REQUEST_METHOD': 'HEAD'})
|
||||||
|
resp = controller.GET(req)
|
||||||
|
self.assertTrue(isinstance(resp, HTTPException))
|
||||||
|
self.assertEqual('200 OK', str(resp))
|
||||||
|
|
||||||
|
def test_get_admin_info_invalid_method(self):
|
||||||
|
controller = self.get_controller(expose_info=True,
|
||||||
|
admin_key='secret-admin-key')
|
||||||
|
utils._swift_info = {'foo': {'bar': 'baz'}}
|
||||||
|
utils._swift_admin_info = {'qux': {'quux': 'corge'}}
|
||||||
|
|
||||||
|
expires = int(time.time() + 86400)
|
||||||
|
sig = utils.get_hmac('HEAD', '/info', expires, 'secret-admin-key')
|
||||||
|
path = '/info?swiftinfo_sig={sig}&swiftinfo_expires={expires}'.format(
|
||||||
|
sig=sig, expires=expires)
|
||||||
|
req = Request.blank(
|
||||||
|
path, environ={'REQUEST_METHOD': 'GET'})
|
||||||
|
resp = controller.GET(req)
|
||||||
|
self.assertTrue(isinstance(resp, HTTPException))
|
||||||
|
self.assertEqual('401 Unauthorized', str(resp))
|
||||||
|
|
||||||
|
def test_get_admin_info_invalid_expires(self):
|
||||||
|
controller = self.get_controller(expose_info=True,
|
||||||
|
admin_key='secret-admin-key')
|
||||||
|
utils._swift_info = {'foo': {'bar': 'baz'}}
|
||||||
|
utils._swift_admin_info = {'qux': {'quux': 'corge'}}
|
||||||
|
|
||||||
|
expires = 1
|
||||||
|
sig = utils.get_hmac('GET', '/info', expires, 'secret-admin-key')
|
||||||
|
path = '/info?swiftinfo_sig={sig}&swiftinfo_expires={expires}'.format(
|
||||||
|
sig=sig, expires=expires)
|
||||||
|
req = Request.blank(
|
||||||
|
path, environ={'REQUEST_METHOD': 'GET'})
|
||||||
|
resp = controller.GET(req)
|
||||||
|
self.assertTrue(isinstance(resp, HTTPException))
|
||||||
|
self.assertEqual('401 Unauthorized', str(resp))
|
||||||
|
|
||||||
|
expires = 'abc'
|
||||||
|
sig = utils.get_hmac('GET', '/info', expires, 'secret-admin-key')
|
||||||
|
path = '/info?swiftinfo_sig={sig}&swiftinfo_expires={expires}'.format(
|
||||||
|
sig=sig, expires=expires)
|
||||||
|
req = Request.blank(
|
||||||
|
path, environ={'REQUEST_METHOD': 'GET'})
|
||||||
|
resp = controller.GET(req)
|
||||||
|
self.assertTrue(isinstance(resp, HTTPException))
|
||||||
|
self.assertEqual('401 Unauthorized', str(resp))
|
||||||
|
|
||||||
|
def test_get_admin_info_invalid_path(self):
|
||||||
|
controller = self.get_controller(expose_info=True,
|
||||||
|
admin_key='secret-admin-key')
|
||||||
|
utils._swift_info = {'foo': {'bar': 'baz'}}
|
||||||
|
utils._swift_admin_info = {'qux': {'quux': 'corge'}}
|
||||||
|
|
||||||
|
expires = int(time.time() + 86400)
|
||||||
|
sig = utils.get_hmac('GET', '/foo', expires, 'secret-admin-key')
|
||||||
|
path = '/info?swiftinfo_sig={sig}&swiftinfo_expires={expires}'.format(
|
||||||
|
sig=sig, expires=expires)
|
||||||
|
req = Request.blank(
|
||||||
|
path, environ={'REQUEST_METHOD': 'GET'})
|
||||||
|
resp = controller.GET(req)
|
||||||
|
self.assertTrue(isinstance(resp, HTTPException))
|
||||||
|
self.assertEqual('401 Unauthorized', str(resp))
|
||||||
|
|
||||||
|
def test_get_admin_info_invalid_key(self):
|
||||||
|
controller = self.get_controller(expose_info=True,
|
||||||
|
admin_key='secret-admin-key')
|
||||||
|
utils._swift_info = {'foo': {'bar': 'baz'}}
|
||||||
|
utils._swift_admin_info = {'qux': {'quux': 'corge'}}
|
||||||
|
|
||||||
|
expires = int(time.time() + 86400)
|
||||||
|
sig = utils.get_hmac('GET', '/foo', expires, 'invalid-admin-key')
|
||||||
|
path = '/info?swiftinfo_sig={sig}&swiftinfo_expires={expires}'.format(
|
||||||
|
sig=sig, expires=expires)
|
||||||
|
req = Request.blank(
|
||||||
|
path, environ={'REQUEST_METHOD': 'GET'})
|
||||||
|
resp = controller.GET(req)
|
||||||
|
self.assertTrue(isinstance(resp, HTTPException))
|
||||||
|
self.assertEqual('401 Unauthorized', str(resp))
|
||||||
|
|
||||||
|
def test_admin_disallow_info(self):
|
||||||
|
controller = self.get_controller(expose_info=True,
|
||||||
|
disallowed_sections=['foo2'],
|
||||||
|
admin_key='secret-admin-key')
|
||||||
|
utils._swift_info = {'foo': {'bar': 'baz'},
|
||||||
|
'foo2': {'bar2': 'baz2'}}
|
||||||
|
utils._swift_admin_info = {'qux': {'quux': 'corge'}}
|
||||||
|
|
||||||
|
expires = int(time.time() + 86400)
|
||||||
|
sig = utils.get_hmac('GET', '/info', expires, 'secret-admin-key')
|
||||||
|
path = '/info?swiftinfo_sig={sig}&swiftinfo_expires={expires}'.format(
|
||||||
|
sig=sig, expires=expires)
|
||||||
|
req = Request.blank(
|
||||||
|
path, environ={'REQUEST_METHOD': 'GET'})
|
||||||
|
resp = controller.GET(req)
|
||||||
|
self.assertTrue(isinstance(resp, HTTPException))
|
||||||
|
self.assertEqual('200 OK', str(resp))
|
||||||
|
info = json.loads(resp.body)
|
||||||
|
self.assertTrue('foo2' not in info)
|
||||||
|
self.assertTrue('admin' in info)
|
||||||
|
self.assertTrue('disallowed_sections' in info['admin'])
|
||||||
|
self.assertTrue('foo2' in info['admin']['disallowed_sections'])
|
||||||
|
self.assertTrue('qux' in info['admin'])
|
||||||
|
self.assertTrue('quux' in info['admin']['qux'])
|
||||||
|
self.assertEqual(info['admin']['qux']['quux'], 'corge')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
@ -650,6 +650,34 @@ class TestProxyServer(unittest.TestCase):
|
|||||||
{'region': 2, 'zone': 1, 'ip': '127.0.0.1'}]
|
{'region': 2, 'zone': 1, 'ip': '127.0.0.1'}]
|
||||||
self.assertEquals(exp_sorted, app_sorted)
|
self.assertEquals(exp_sorted, app_sorted)
|
||||||
|
|
||||||
|
def test_info_defaults(self):
|
||||||
|
app = proxy_server.Application({}, FakeMemcache(),
|
||||||
|
account_ring=FakeRing(),
|
||||||
|
container_ring=FakeRing(),
|
||||||
|
object_ring=FakeRing())
|
||||||
|
|
||||||
|
self.assertTrue(app.expose_info)
|
||||||
|
self.assertTrue(isinstance(app.disallowed_sections, list))
|
||||||
|
self.assertEqual(0, len(app.disallowed_sections))
|
||||||
|
self.assertTrue(app.admin_key is None)
|
||||||
|
|
||||||
|
def test_get_info_controller(self):
|
||||||
|
path = '/info'
|
||||||
|
app = proxy_server.Application({}, FakeMemcache(),
|
||||||
|
account_ring=FakeRing(),
|
||||||
|
container_ring=FakeRing(),
|
||||||
|
object_ring=FakeRing())
|
||||||
|
|
||||||
|
controller, path_parts = app.get_controller(path)
|
||||||
|
|
||||||
|
self.assertTrue('version' in path_parts)
|
||||||
|
self.assertTrue(path_parts['version'] is None)
|
||||||
|
self.assertTrue('disallowed_sections' in path_parts)
|
||||||
|
self.assertTrue('expose_info' in path_parts)
|
||||||
|
self.assertTrue('admin_key' in path_parts)
|
||||||
|
|
||||||
|
self.assertEqual(controller.__name__, 'InfoController')
|
||||||
|
|
||||||
|
|
||||||
class TestObjectController(unittest.TestCase):
|
class TestObjectController(unittest.TestCase):
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user