From 94195dd0e5f492665f3c145e7bd3402ef1e33b39 Mon Sep 17 00:00:00 2001 From: Antony Chazapis Date: Mon, 25 Apr 2011 20:36:54 +0300 Subject: [PATCH] Containers API. --- api/functions.py | 132 ++++++++++++++++++++++++++++++------------ api/templates/containers.xml | 4 +- api/templates/objects.xml | 15 +++++ api/util.py | 20 ++----- backends/dummy_debug.py | 125 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 244 insertions(+), 52 deletions(-) create mode 100644 api/templates/objects.xml create mode 100644 backends/dummy_debug.py diff --git a/api/functions.py b/api/functions.py index 908eab7..71ac21d 100644 --- a/api/functions.py +++ b/api/functions.py @@ -7,11 +7,13 @@ from django.template.loader import render_to_string from django.utils import simplejson as json from pithos.api.faults import Fault, BadRequest, Unauthorized -from pithos.api.util import api_method, binary_search_name +from pithos.api.util import api_method + +from pithos.backends.dummy_debug import * import logging -logging.basicConfig(level=logging.INFO) +logging.basicConfig(level=logging.DEBUG) @api_method('GET') def authenticate(request): @@ -25,9 +27,6 @@ def authenticate(request): if not x_auth_user or not x_auth_key: raise BadRequest('Missing auth user or key.') - # TODO: Authenticate. - #if x_auth_user == "test": - # raise Unauthorized() response = HttpResponse(status = 204) response['X-Auth-Token'] = 'eaaafd18-0fed-4b3a-81b4-663c99ec1cbb' @@ -56,7 +55,6 @@ def container_demux(request, v_account, v_container): return method_not_allowed(request) def object_demux(request, v_account, v_container, v_object): - # TODO: Check parameter sizes. if request.method == 'HEAD': return object_meta(request, v_account, v_container, v_object) elif request.method == 'GET': @@ -72,7 +70,18 @@ def object_demux(request, v_account, v_container, v_object): @api_method('HEAD') def account_meta(request, v_account): - return HttpResponse("account_meta: %s" % v_account) + # Normal Response Codes: 204 + # Error Response Codes: serviceUnavailable (503), + # itemNotFound (404), + # unauthorized (401), + # badRequest (400) + + container_count, bytes_count = get_account_meta(request.user) + + response = HttpResponse(status = 204) + response['X-Account-Container-Count'] = container_count + response['X-Account-Total-Bytes-Used'] = bytes_count + return response @api_method('GET', format_allowed = True) def container_list(request, v_account): @@ -81,60 +90,111 @@ def container_list(request, v_account): # unauthorized (401), # badRequest (400) - containers = [ - {'name': '1', 'count': 2, 'bytes': 123}, - {'name': '2', 'count': 22, 'bytes': 245}, - {'name': '3', 'count': 222, 'bytes': 83745}, - {'name': 'four', 'count': 2222, 'bytes': 274365} - ] - - if len(containers) == 0: - return HttpResponse(status = 204) - - limit = request.GET.get('limit') marker = request.GET.get('marker') - - start = 0 - if marker: - try: - start = binary_search_name(containers, marker) + 1 - except ValueError: - pass + limit = request.GET.get('limit') if limit: try: limit = int(limit) except ValueError: limit = None - if not limit or limit > 10000: - limit = 10000 - containers = containers[start:start + limit] + containers = list_containers(request.user, marker, limit) + if len(containers) == 0: + return HttpResponse(status = 204) + if request.serialization == 'xml': - # TODO: The xml must include the account name as well. - data = render_to_string('containers.xml', {'containers': containers}) + data = render_to_string('containers.xml', {'account': request.user, 'containers': containers}) elif request.serialization == 'json': data = json.dumps(containers) else: data = '\n'.join(x['name'] for x in containers) - # TODO: Return 404 when the account is not found. return HttpResponse(data, status = 200) @api_method('HEAD') def container_meta(request, v_account, v_container): - return HttpResponse("container_meta: %s %s" % (v_account, v_container)) + # Normal Response Codes: 204 + # Error Response Codes: serviceUnavailable (503), + # itemNotFound (404), + # unauthorized (401), + # badRequest (400) + + object_count, bytes_count = get_container_meta(request.user, v_container) + + response = HttpResponse(status = 204) + response['X-Container-Object-Count'] = object_count + response['X-Container-Bytes-Used'] = bytes_count + return response @api_method('PUT') def container_create(request, v_account, v_container): - return HttpResponse("container_create: %s %s" % (v_account, v_container)) + # Normal Response Codes: 201, 202 + # Error Response Codes: serviceUnavailable (503), + # itemNotFound (404), + # unauthorized (401), + # badRequest (400) + + if create_container(request.user, v_container): + return HttpResponse(status = 201) + else: + return HttpResponse(status = 202) @api_method('DELETE') def container_delete(request, v_account, v_container): - return HttpResponse("container_delete: %s %s" % (v_account, v_container)) + # Normal Response Codes: 204 + # Error Response Codes: serviceUnavailable (503), + # itemNotFound (404), + # unauthorized (401), + # badRequest (400) + + object_count, bytes_count = get_container_meta(request.user, v_container) + if object_count > 0: + return HttpResponse(status = 409) + + delete_container(request.user, v_container) + return HttpResponse(status = 204) -@api_method('GET') +@api_method('GET', format_allowed = True) def object_list(request, v_account, v_container): - return HttpResponse("object_list: %s %s" % (v_account, v_container)) + # Normal Response Codes: 200, 204 + # Error Response Codes: serviceUnavailable (503), + # itemNotFound (404), + # unauthorized (401), + # badRequest (400) + + path = request.GET.get('path') + prefix = request.GET.get('prefix') + delimiter = request.GET.get('delimiter') + logging.debug("path: %s", path) + + # Path overrides prefix and delimiter. + if path: + prefix = path + delimiter = '/' + # Naming policy. + if prefix and delimiter: + prefix = prefix + delimiter + + marker = request.GET.get('marker') + limit = request.GET.get('limit') + if limit: + try: + limit = int(limit) + except ValueError: + limit = None + + objects = list_objects(request.user, v_container, prefix, delimiter, marker, limit) + if len(objects) == 0: + return HttpResponse(status = 204) + + if request.serialization == 'xml': + data = render_to_string('objects.xml', {'container': v_container, 'objects': objects}) + elif request.serialization == 'json': + data = json.dumps(objects) + else: + data = '\n'.join(x['name'] for x in objects) + + return HttpResponse(data, status = 200) @api_method('HEAD') def object_meta(request, v_account, v_container, v_object): diff --git a/api/templates/containers.xml b/api/templates/containers.xml index c82a1ea..370bf0e 100644 --- a/api/templates/containers.xml +++ b/api/templates/containers.xml @@ -1,10 +1,12 @@ {% spaceless %} - + {% for container in containers %} {{ container.name }} + {{ container.count }} + {{ container.bytes }} {% endfor %} diff --git a/api/templates/objects.xml b/api/templates/objects.xml new file mode 100644 index 0000000..39bddaf --- /dev/null +++ b/api/templates/objects.xml @@ -0,0 +1,15 @@ +{% spaceless %} + + + + {% for object in objects %} + + {{ object.name }} + {{ object.hash }} + {{ object.bytes }} + {{ object.content_type }} + {{ object.last_modified }} + + {% endfor %} + +{% endspaceless %} diff --git a/api/util.py b/api/util.py index f2d8505..34e8e16 100644 --- a/api/util.py +++ b/api/util.py @@ -22,20 +22,6 @@ import datetime import dateutil.parser import logging -def binary_search_name(a, x, lo = 0, hi = None): - if hi is None: - hi = len(a) - while lo < hi: - mid = (lo + hi) // 2 - midval = a[mid]['name'] - if midval < x: - lo = mid + 1 - elif midval > x: - hi = mid - else: - return mid - raise ValueError() - # class UTC(tzinfo): # def utcoffset(self, dt): # return timedelta(0) @@ -173,7 +159,7 @@ def render_fault(request, fault): # data = json.dumps(d) # resp = HttpResponse(data, status=fault.code) - resp = HttpResponse(status=fault.code) + resp = HttpResponse(status = fault.code) update_response_headers(request, resp) return resp @@ -209,6 +195,10 @@ def api_method(http_method = None, format_allowed = False): def wrapper(request, *args, **kwargs): try: request.serialization = request_serialization(request, format_allowed) + # TODO: Authenticate. + # TODO: Return 401/404 when the account is not found. + request.user = "test" + # TODO: Check parameter sizes. if http_method and request.method != http_method: raise BadRequest('Method not allowed.') diff --git a/backends/dummy_debug.py b/backends/dummy_debug.py new file mode 100644 index 0000000..6a50ced --- /dev/null +++ b/backends/dummy_debug.py @@ -0,0 +1,125 @@ +""" +Dummy backend with debugging output + +A backend with no functionality other than producing debugging output. +""" + +import logging + +def binary_search_name(a, x, lo = 0, hi = None): + """ + Search a sorted array of dicts for the value of the key 'name'. + Raises ValueError if the value is not found. + + a -- the array + x -- the value to search for + """ + if hi is None: + hi = len(a) + while lo < hi: + mid = (lo + hi) // 2 + midval = a[mid]['name'] + if midval < x: + lo = mid + 1 + elif midval > x: + hi = mid + else: + return mid + raise ValueError() + +def get_account_meta(account): + logging.debug("get_account_meta: %s %s", account, name) + return {'count': 13, 'bytes': 3148237468} + +def create_container(account, name): + """ + Returns True if the container was created, False if it already exists. + """ + logging.debug("create_container: %s %s", account, name) + return True + +def delete_container(account, name): + logging.debug("delete_container: %s %s", account, name) + return + +def get_container_meta(account, name): + logging.debug("get_container_meta: %s %s", account, name) + return {'count': 22, 'bytes': 245} + +def list_containers(account, marker = None, limit = 10000): + logging.debug("list_containers: %s %s %s", account, marker, limit) + + containers = [ + {'name': '1', 'count': 2, 'bytes': 123}, + {'name': '2', 'count': 22, 'bytes': 245}, + {'name': '3', 'count': 222, 'bytes': 83745}, + {'name': 'four', 'count': 2222, 'bytes': 274365} + ] + + start = 0 + if marker: + try: + start = binary_search_name(containers, marker) + 1 + except ValueError: + pass + if not limit or limit > 10000: + limit = 10000 + + return containers[start:start + limit] + +def list_objects(account, container, prefix = None, delimiter = None, marker = None, limit = 10000): + logging.debug("list_objects: %s %s %s %s %s %s", account, container, prefix, delimiter, marker, limit) + + objects = [ + {'name': 'other', 'hash': 'dfgs', 'bytes': 0, 'content_type': 'application/directory', 'last_modified': 23453454}, + {'name': 'other/something', 'hash': 'vkajf', 'bytes': 234, 'content_type': 'application/octet-stream', 'last_modified': 878434562}, + {'name': 'photos', 'hash': 'kajdsn', 'bytes': 0, 'content_type': 'application/directory', 'last_modified': 1983274}, + {'name': 'photos/asdf', 'hash': 'jadsfkj', 'bytes': 0, 'content_type': 'application/directory', 'last_modified': 378465873}, + {'name': 'photos/asdf/test', 'hash': 'sudfhius', 'bytes': 37284, 'content_type': 'text/plain', 'last_modified': 93674212}, + {'name': 'photos/me.jpg', 'hash': 'sdgsdfgsf', 'bytes': 534, 'content_type': 'image/jpeg', 'last_modified': 262345345}, + {'name': 'photos/text.txt', 'hash': 'asdfasd', 'bytes': 34243, 'content_type': 'text/plain', 'last_modified': 45345345} + ] + + if prefix or delimiter: + if prefix: + objects = [x for x in objects if x['name'].startswith(prefix)] + if delimiter: + pseudo_objects = {} + for x in objects: + pseudo_name = x['name'][len(prefix):] + i = pseudo_name.find(delimiter) + if i != -1: + pseudo_name = pseudo_name[:i] + # TODO: Virtual directories. + if pseudo_name not in pseudo_objects: + pseudo_objects[pseudo_name] = x + objects = sorted(pseudo_objects.values(), key=lambda o: o['name']) + + start = 0 + if marker: + try: + start = binary_search_name(objects, marker) + 1 + except ValueError: + pass + if not limit or limit > 10000: + limit = 10000 + + return objects[start:start + limit] + +def get_object_meta(container, name): + return {'name': name, 'hash': '', 'bytes': 0} + +def get_object_data(container, name, offset=0, length=0): + return '' + +def update_object_data(container, name, meta, data): + return + +def update_object_meta(container, name, meta): + return + +def copy_object(container, name, new_name): + return + +def delete_object(container, name): + return -- 1.7.10.4