Commit 1051cd72 authored by Christos Stavrakakis's avatar Christos Stavrakakis
Browse files

Use common 'api_method' decorator in pithos api

Update all pithos API methods to use the new common 'api_method'
decorator. However, this decorator is not enough for pithos since
pithos methods require some extra tasks:
* Create a PithosBackend connection
* Handle special HTTP headers

For these reasons, pithos defines a custom 'api_method' decorator, which
is decorator by the common 'api_method' decorator.
parent b3763c68
......@@ -35,7 +35,6 @@ from functools import wraps
from synnefo.plankton.backend import ImageBackend
from contextlib import contextmanager
def plankton_method(func):
"""Decorator function for API methods using ImageBackend.
......
......@@ -32,9 +32,7 @@
# or implied, of GRNET S.A.
from xml.dom import minidom
from urllib import unquote
from django.conf import settings
from django.http import HttpResponse
from django.template.loader import render_to_string
from django.utils import simplejson as json
......@@ -44,6 +42,7 @@ from django.views.decorators.csrf import csrf_exempt
from synnefo.lib.astakos import get_user, get_uuids as _get_uuids
from snf_django.lib import api
from snf_django.lib.api import faults
from pithos.api.util import (
......@@ -57,7 +56,8 @@ from pithos.api.util import (
get_content_range, socket_read_iterator, SaveToBackendHandler,
object_data_response, put_object_block, hashmap_md5, simple_list_response,
api_method, is_uuid,
retrieve_uuid, retrieve_displayname, retrieve_uuids, retrieve_displaynames
retrieve_uuid, retrieve_displayname, retrieve_uuids, retrieve_displaynames,
get_pithos_usage
)
from pithos.api.settings import (UPDATE_MD5, TRANSLATE_UUIDS,
......@@ -70,9 +70,9 @@ from pithos.backends.base import (
from pithos.backends.filter import parse_filters
import logging
import hashlib
import logging
logger = logging.getLogger(__name__)
......@@ -102,7 +102,7 @@ def top_demux(request):
return authenticate(request)
return account_list(request)
else:
return method_not_allowed(request)
return api.method_not_allowed(request)
@csrf_exempt
......@@ -121,7 +121,7 @@ def account_demux(request, v_account):
elif request.method == 'GET':
return container_list(request, v_account)
else:
return method_not_allowed(request)
return api.method_not_allowed(request)
@csrf_exempt
......@@ -144,7 +144,7 @@ def container_demux(request, v_account, v_container):
elif request.method == 'GET':
return object_list(request, v_account, v_container)
else:
return method_not_allowed(request)
return api.method_not_allowed(request)
@csrf_exempt
......@@ -174,10 +174,10 @@ def object_demux(request, v_account, v_container, v_object):
elif request.method == 'DELETE':
return object_delete(request, v_account, v_container, v_object)
else:
return method_not_allowed(request)
return api.method_not_allowed(request)
@api_method('GET', user_required=False)
@api_method('GET', user_required=False, logger=logger)
def authenticate(request):
# Normal Response Codes: 204
# Error Response Codes: internalServerError (500),
......@@ -200,7 +200,7 @@ def authenticate(request):
return response
@api_method('GET', format_allowed=True, request_usage=True)
@api_method('GET', format_allowed=True, user_required=True, logger=logger)
def account_list(request):
# Normal Response Codes: 200, 204
# Error Response Codes: internalServerError (500),
......@@ -230,10 +230,11 @@ def account_list(request):
for x in accounts:
if x == request.user_uniq:
continue
usage = get_pithos_usage(request.x_auth_token)
try:
meta = request.backend.get_account_meta(
request.user_uniq, x, 'pithos', include_user_defined=False,
external_quota=request.user_usage)
external_quota=usage)
groups = request.backend.get_account_groups(request.user_uniq, x)
except NotAllowedError:
raise faults.Forbidden('Not allowed')
......@@ -262,7 +263,7 @@ def account_list(request):
return response
@api_method('HEAD', request_usage=True)
@api_method('HEAD', user_required=True, logger=logger)
def account_meta(request, v_account):
# Normal Response Codes: 204
# Error Response Codes: internalServerError (500),
......@@ -270,10 +271,11 @@ def account_meta(request, v_account):
# badRequest (400)
until = get_int_parameter(request.GET.get('until'))
usage = get_pithos_usage(request.x_auth_token)
try:
meta = request.backend.get_account_meta(
request.user_uniq, v_account, 'pithos', until,
external_quota=request.user_usage)
external_quota=usage)
groups = request.backend.get_account_groups(
request.user_uniq, v_account)
......@@ -282,7 +284,7 @@ def account_meta(request, v_account):
groups[k] = retrieve_displaynames(
getattr(request, 'token', None), groups[k])
policy = request.backend.get_account_policy(
request.user_uniq, v_account, external_quota=request.user_usage)
request.user_uniq, v_account, external_quota=usage)
except NotAllowedError:
raise faults.Forbidden('Not allowed')
......@@ -293,7 +295,7 @@ def account_meta(request, v_account):
return response
@api_method('POST')
@api_method('POST', user_required=True, logger=logger)
def account_update(request, v_account):
# Normal Response Codes: 202
# Error Response Codes: internalServerError (500),
......@@ -340,7 +342,7 @@ def account_update(request, v_account):
return HttpResponse(status=202)
@api_method('GET', format_allowed=True, request_usage=True)
@api_method('GET', format_allowed=True, user_required=True, logger=logger)
def container_list(request, v_account):
# Normal Response Codes: 200, 204
# Error Response Codes: internalServerError (500),
......@@ -349,14 +351,15 @@ def container_list(request, v_account):
# badRequest (400)
until = get_int_parameter(request.GET.get('until'))
usage = get_pithos_usage(request.x_auth_token)
try:
meta = request.backend.get_account_meta(
request.user_uniq, v_account, 'pithos', until,
external_quota=request.user_usage)
external_quota=usage)
groups = request.backend.get_account_groups(
request.user_uniq, v_account)
policy = request.backend.get_account_policy(
request.user_uniq, v_account, external_quota=request.user_usage)
request.user_uniq, v_account, external_quota=usage)
except NotAllowedError:
raise faults.Forbidden('Not allowed')
......@@ -425,7 +428,7 @@ def container_list(request, v_account):
return response
@api_method('HEAD')
@api_method('HEAD', user_required=True, logger=logger)
def container_meta(request, v_account, v_container):
# Normal Response Codes: 204
# Error Response Codes: internalServerError (500),
......@@ -454,7 +457,7 @@ def container_meta(request, v_account, v_container):
return response
@api_method('PUT')
@api_method('PUT', user_required=True, logger=logger)
def container_create(request, v_account, v_container):
# Normal Response Codes: 201, 202
# Error Response Codes: internalServerError (500),
......@@ -498,7 +501,7 @@ def container_create(request, v_account, v_container):
return HttpResponse(status=ret)
@api_method('POST', format_allowed=True)
@api_method('POST', format_allowed=True, user_required=True, logger=logger)
def container_update(request, v_account, v_container):
# Normal Response Codes: 202
# Error Response Codes: internalServerError (500),
......@@ -549,7 +552,7 @@ def container_update(request, v_account, v_container):
return response
@api_method('DELETE')
@api_method('DELETE', user_required=True, logger=logger)
def container_delete(request, v_account, v_container):
# Normal Response Codes: 204
# Error Response Codes: internalServerError (500),
......@@ -578,7 +581,7 @@ def container_delete(request, v_account, v_container):
return HttpResponse(status=204)
@api_method('GET', format_allowed=True)
@api_method('GET', format_allowed=True, user_required=True, logger=logger)
def object_list(request, v_account, v_container):
# Normal Response Codes: 200, 204
# Error Response Codes: internalServerError (500),
......@@ -746,7 +749,7 @@ def object_list(request, v_account, v_container):
return response
@api_method('HEAD')
@api_method('HEAD', user_required=True, logger=logger)
def object_meta(request, v_account, v_container, v_object):
# Normal Response Codes: 204
# Error Response Codes: internalServerError (500),
......@@ -795,7 +798,7 @@ def object_meta(request, v_account, v_container, v_object):
return response
@api_method('GET', format_allowed=True)
@api_method('GET', format_allowed=True, user_required=True, logger=logger)
def object_read(request, v_account, v_container, v_object):
# Normal Response Codes: 200, 206
# Error Response Codes: internalServerError (500),
......@@ -937,7 +940,7 @@ def object_read(request, v_account, v_container, v_object):
return object_data_response(request, sizes, hashmaps, meta)
@api_method('PUT', format_allowed=True)
@api_method('PUT', format_allowed=True, user_required=True, logger=logger)
def object_write(request, v_account, v_container, v_object):
# Normal Response Codes: 201
# Error Response Codes: internalServerError (500),
......@@ -1096,7 +1099,7 @@ def object_write(request, v_account, v_container, v_object):
return response
@api_method('POST')
@api_method('POST', user_required=True, logger=logger)
def object_write_form(request, v_account, v_container, v_object):
# Normal Response Codes: 201
# Error Response Codes: internalServerError (500),
......@@ -1129,7 +1132,7 @@ def object_write_form(request, v_account, v_container, v_object):
return response
@api_method('COPY', format_allowed=True)
@api_method('COPY', format_allowed=True, user_required=True, logger=logger)
def object_copy(request, v_account, v_container, v_object):
# Normal Response Codes: 201
# Error Response Codes: internalServerError (500),
......@@ -1171,7 +1174,7 @@ def object_copy(request, v_account, v_container, v_object):
return response
@api_method('MOVE', format_allowed=True)
@api_method('MOVE', format_allowed=True, user_required=True, logger=logger)
def object_move(request, v_account, v_container, v_object):
# Normal Response Codes: 201
# Error Response Codes: internalServerError (500),
......@@ -1212,7 +1215,7 @@ def object_move(request, v_account, v_container, v_object):
return response
@api_method('POST', format_allowed=True)
@api_method('POST', format_allowed=True, user_required=True, logger=logger)
def object_update(request, v_account, v_container, v_object):
# Normal Response Codes: 202, 204
# Error Response Codes: internalServerError (500),
......@@ -1423,7 +1426,7 @@ def object_update(request, v_account, v_container, v_object):
return response
@api_method('DELETE')
@api_method('DELETE', user_required=True, logger=logger)
def object_delete(request, v_account, v_container, v_object):
# Normal Response Codes: 204
# Error Response Codes: internalServerError (500),
......@@ -1446,8 +1449,3 @@ def object_delete(request, v_account, v_container, v_object):
except QuotaError, e:
raise faults.RequestEntityTooLarge('Quota error: %s' % e)
return HttpResponse(status=204)
@api_method()
def method_not_allowed(request):
raise faults.BadRequest('Method not allowed')
# Copyright 2011-2012 GRNET S.A. All rights reserved.
# Copyright 2011-2013 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
......@@ -31,12 +31,10 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
import logging
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from synnefo.lib.astakos import get_user
from snf_django.lib import api
from snf_django.lib.api import faults
from pithos.api.util import (put_object_headers, update_manifest_meta,
......@@ -44,24 +42,22 @@ from pithos.api.util import (put_object_headers, update_manifest_meta,
validate_matching_preconditions,
object_data_response, api_method,
split_container_object_string)
from pithos.api.settings import AUTHENTICATION_URL, AUTHENTICATION_USERS
import logging
logger = logging.getLogger(__name__)
@csrf_exempt
def public_demux(request, v_public):
get_user(request, AUTHENTICATION_URL, AUTHENTICATION_USERS)
if request.method == 'HEAD':
return public_meta(request, v_public)
elif request.method == 'GET':
return public_read(request, v_public)
else:
return method_not_allowed(request)
return api.method_not_allowed(request)
@api_method('HEAD', user_required=False)
@api_method(http_method="HEAD", user_required=False, logger=logger)
def public_meta(request, v_public):
# Normal Response Codes: 204
# Error Response Codes: internalServerError (500),
......@@ -89,7 +85,7 @@ def public_meta(request, v_public):
return response
@api_method('GET', user_required=False)
@api_method(http_method="GET", user_required=False, logger=logger)
def public_read(request, v_public):
# Normal Response Codes: 200, 206
# Error Response Codes: internalServerError (500),
......@@ -139,7 +135,9 @@ def public_read(request, v_public):
try:
for x in objects:
s, h = request.backend.get_object_hashmap(request.user_uniq,
v_account, src_container, x[0], x[1])
v_account,
src_container,
x[0], x[1])
sizes.append(s)
hashmaps.append(h)
except:
......@@ -161,8 +159,3 @@ def public_read(request, v_public):
meta['Content-Disposition'] = 'attachment; filename=%s' % (name,)
return object_data_response(request, sizes, hashmaps, meta, True)
@api_method(user_required=False)
def method_not_allowed(request, **v_args):
raise faults.ItemNotFound('Object does not exist')
......@@ -32,11 +32,8 @@
# or implied, of GRNET S.A.
from functools import wraps
from time import time
from traceback import format_exc
from wsgiref.handlers import format_date_time
from binascii import hexlify, unhexlify
from datetime import datetime, tzinfo, timedelta
from datetime import datetime
from urllib import quote, unquote
from django.conf import settings
......@@ -49,8 +46,9 @@ from django.core.files.uploadhandler import FileUploadHandler
from django.core.files.uploadedfile import UploadedFile
from synnefo.lib.parsedate import parse_http_date_safe, parse_http_date
from synnefo.lib.astakos import get_user
from snf_django.lib.api import faults
from synnefo.lib.astakos import user_for_token
from snf_django.lib import api
from snf_django.lib.api import faults, utils
from pithos.api.settings import (BACKEND_DB_MODULE, BACKEND_DB_CONNECTION,
BACKEND_BLOCK_MODULE, BACKEND_BLOCK_PATH,
......@@ -67,7 +65,6 @@ from pithos.api.settings import (BACKEND_DB_MODULE, BACKEND_DB_CONNECTION,
RADOS_POOL_MAPS, TRANSLATE_UUIDS,
PUBLIC_URL_SECURITY,
PUBLIC_URL_ALPHABET)
from pithos.backends import connect_backend
from pithos.backends.base import (NotAllowedError, QuotaError, ItemNotExists,
VersionNotExists)
from synnefo.lib.astakos import (get_user_uuid, get_displayname,
......@@ -82,29 +79,12 @@ import decimal
logger = logging.getLogger(__name__)
class UTC(tzinfo):
def utcoffset(self, dt):
return timedelta(0)
def tzname(self, dt):
return 'UTC'
def dst(self, dt):
return timedelta(0)
def json_encode_decimal(obj):
if isinstance(obj, decimal.Decimal):
return str(obj)
raise TypeError(repr(obj) + " is not JSON serializable")
def isoformat(d):
"""Return an ISO8601 date string that includes a timezone."""
return d.replace(tzinfo=UTC()).isoformat()
def rename_meta_key(d, old, new):
if old not in d:
return
......@@ -120,7 +100,7 @@ def printable_header_dict(d):
"""
if 'last_modified' in d and d['last_modified']:
d['last_modified'] = isoformat(
d['last_modified'] = utils.isoformat(
datetime.fromtimestamp(d['last_modified']))
return dict([(k.lower().replace('-', '_'), v) for k, v in d.iteritems()])
......@@ -985,6 +965,7 @@ _pithos_backend_pool = PithosBackendPool(
public_url_security=PUBLIC_URL_SECURITY,
public_url_alphabet=PUBLIC_URL_ALPHABET)
def get_backend():
backend = _pithos_backend_pool.pool_get()
backend.default_policy['quota'] = BACKEND_QUOTA
......@@ -1010,13 +991,6 @@ def update_request_headers(request):
def update_response_headers(request, response):
if request.serialization == 'xml':
response['Content-Type'] = 'application/xml; charset=UTF-8'
elif request.serialization == 'json':
response['Content-Type'] = 'application/json; charset=UTF-8'
elif not response['Content-Type']:
response['Content-Type'] = 'text/plain; charset=UTF-8'
if (not response.has_header('Content-Length') and
not (response.has_header('Content-Type') and
response['Content-Type'].startswith('multipart/byteranges'))):
......@@ -1031,103 +1005,41 @@ def update_response_headers(request, response):
response[quote(k)] = quote(v, safe='/=,:@; ')
def render_fault(request, fault):
if isinstance(fault, faults.InternalServerError) and settings.DEBUG:
fault.details = format_exc(fault)
request.serialization = 'text'
data = fault.message + '\n'
if fault.details:
data += '\n' + fault.details
response = HttpResponse(data, status=fault.code)
update_response_headers(request, response)
return response
def request_serialization(request, format_allowed=False):
"""Return the serialization format requested.
Valid formats are 'text' and 'json', 'xml' if 'format_allowed' is True.
"""
if not format_allowed:
return 'text'
format = request.GET.get('format')
if format == 'json':
return 'json'
elif format == 'xml':
return 'xml'
for item in request.META.get('HTTP_ACCEPT', '').split(','):
accept, sep, rest = item.strip().partition(';')
if accept == 'application/json':
return 'json'
elif accept == 'application/xml' or accept == 'text/xml':
return 'xml'
return 'text'
def get_pithos_usage(usage):
def get_pithos_usage(token):
"""Get Pithos Usage from astakos."""
user_info = user_for_token(token, AUTHENTICATION_URL, AUTHENTICATION_USERS,
usage=True)
usage = user_info.get("usage", [])
for u in usage:
if u.get('name') == 'pithos+.diskspace':
return u
def api_method(http_method=None, format_allowed=False, user_required=True,
request_usage=False):
"""Decorator function for views that implement an API method."""
def api_method(http_method=None, user_required=True, logger=None,
format_allowed=False):
def decorator(func):
@api.api_method(http_method=http_method, user_required=user_required,
logger=logger, format_allowed=format_allowed)
@wraps(func)
def wrapper(request, *args, **kwargs):
try:
if http_method and request.method != http_method:
raise faults.BadRequest('Method not allowed.')
if user_required:
token = None
if request.method in ('HEAD', 'GET') and COOKIE_NAME in request.COOKIES:
cookie_value = unquote(
request.COOKIES.get(COOKIE_NAME, ''))
account, sep, token = cookie_value.partition('|')
get_user(request,
AUTHENTICATION_URL,
AUTHENTICATION_USERS,
token,
request_usage)
if getattr(request, 'user', None) is None:
raise faults.Unauthorized('Access denied')
assert getattr(request, 'user_uniq', None) != None
request.user_usage = get_pithos_usage(request.user.get('usage', []))
request.token = request.GET.get('X-Auth-Token', request.META.get('HTTP_X_AUTH_TOKEN', token))
# The args variable may contain up to (account, container, object).
if len(args) > 1 and len(args[1]) > 256:
raise faults.BadRequest('Container name too large.')
if len(args) > 2 and len(args[2]) > 1024:
raise faults.BadRequest('Object name too large.')
# Format and check headers.
update_request_headers(request)
# The args variable may contain up to (account, container, object).
if len(args) > 1 and len(args[1]) > 256:
raise faults.BadRequest("Container name too large")
if len(args) > 2 and len(args[2]) > 1024:
raise faults.BadRequest('Object name too large.')
# Fill in custom request variables.
request.serialization = request_serialization(
request, format_allowed)
try:
# Add a PithosBackend as attribute of the request object
request.backend = get_backend()
# Many API method expect thet X-Auth-Token in request,token
request.token = request.x_auth_token
update_request_headers(request)
response = func(request, *args, **kwargs)
update_response_headers(request, response)
return response
except faults.Fault, fault:
if fault.code >= 500:
logger.exception("API Fault")
return render_fault(request, fault)
except BaseException, e:
logger.exception('Unexpected error: %s' % e)
fault = faults.InternalServerError('Unexpected error')
return render_fault(request, fault)
finally:
if getattr(request, 'backend', None) is not None:
# Always close PithosBackend connection
if getattr(request, "backend", None) is not None:
request.backend.close()
return wrapper
return decorator
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment