Commit 82ea7d7a authored by Sofia Papagiannaki's avatar Sofia Papagiannaki
Browse files

Merge branch 'master' of https://code.grnet.gr/git/pithos

parents 0671dc99 0c814721
# vim: ts=4 sts=4 et ai sw=4 fileencoding=utf-8
#
# Copyright (c) 2011 Greek Research and Technology Network
#
......
# vim: ts=4 sts=4 et ai sw=4 fileencoding=utf-8
#
# Copyright (c) 2011 Greek Research and Technology Network
#
def camelCase(s):
return s[0].lower() + s[1:]
class Fault(Exception):
def __init__(self, message='', details='', name=''):
Exception.__init__(self, message, details, name)
self.message = message
self.details = details
self.name = name or camelCase(self.__class__.__name__)
class BadRequest(Fault):
code = 400
class Unauthorized(Fault):
code = 401
class ResizeNotAllowed(Fault):
code = 403
class ItemNotFound(Fault):
code = 404
class ServiceUnavailable(Fault):
code = 503
#
# Copyright (c) 2011 Greek Research and Technology Network
#
def camelCase(s):
return s[0].lower() + s[1:]
class Fault(Exception):
def __init__(self, message='', details='', name=''):
Exception.__init__(self, message, details, name)
self.message = message
self.details = details
self.name = name or camelCase(self.__class__.__name__)
class BadRequest(Fault):
code = 400
class Unauthorized(Fault):
code = 401
class ResizeNotAllowed(Fault):
code = 403
class ItemNotFound(Fault):
code = 404
class ServiceUnavailable(Fault):
code = 503
# vim: ts=4 sts=4 et ai sw=4 fileencoding=utf-8
#
# Copyright (c) 2011 Greek Research and Technology Network
#
from django.http import HttpResponse
from pithos.api.faults import Fault, BadRequest, Unauthorized
from pithos.api.util import api_method
import logging
logging.basicConfig(level=logging.INFO)
@api_method('GET')
def authenticate(request):
# Normal Response Codes: 204
# Error Response Codes: serviceUnavailable (503),
# unauthorized (401),
# badRequest (400)
logging.debug('request.META: %s' % request.META)
x_auth_user = request.META.get('HTTP_X_AUTH_USER')
x_auth_key = request.META.get('HTTP_X_AUTH_KEY')
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)
# TODO: Automate the Content-Type reply.
response['Content-Type'] = 'text/plain; charset=UTF-8'
response['X-Auth-Token'] = 'eaaafd18-0fed-4b3a-81b4-663c99ec1cbb'
# TODO: Do we support redirections?
#response['X-Storage-Url'] = 'https://storage.grnet.gr/pithos/v1.0/<some reference>'
return response
def account_demux(request, v_account):
if request.method == 'HEAD':
return account_meta(request, v_account)
elif request.method == 'GET':
return container_list(request, v_account)
else:
return method_not_allowed(request)
def container_demux(request, v_account, v_container):
if request.method == 'HEAD':
return container_meta(request, v_account, v_container)
elif request.method == 'GET':
return object_list(request, v_account, v_container)
elif request.method == 'PUT':
return container_create(request, v_account, v_container)
elif request.method == 'DELETE':
return container_delete(request, v_account, v_container)
else:
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':
return object_read(request, v_account, v_container, v_object)
elif request.method == 'PUT':
return object_write(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)
@api_method('HEAD')
def account_meta(request, v_account):
return HttpResponse("account_meta: %s" % v_account)
@api_method('GET')
def container_list(request, v_account):
return HttpResponse("container_list: %s" % v_account)
@api_method('HEAD')
def container_meta(request, v_account, v_container):
return HttpResponse("container_meta: %s %s" % (v_account, v_container))
@api_method('PUT')
def container_create(request, v_account, v_container):
return HttpResponse("container_create: %s %s" % (v_account, v_container))
@api_method('DELETE')
def container_delete(request, v_account, v_container):
return HttpResponse("container_delete: %s %s" % (v_account, v_container))
@api_method('GET')
def object_list(request, v_account, v_container):
return HttpResponse("object_list: %s %s" % (v_account, v_container))
@api_method('HEAD')
def object_meta(request, v_account, v_container, v_object):
return HttpResponse("object_meta: %s %s %s" % (v_account, v_container, v_object))
@api_method('GET')
def object_read(request, v_account, v_container, v_object):
return HttpResponse("object_read: %s %s %s" % (v_account, v_container, v_object))
@api_method('PUT')
def object_write(request, v_account, v_container, v_object):
return HttpResponse("object_write: %s %s %s" % (v_account, v_container, v_object))
@api_method('DELETE')
def object_delete(request, v_account, v_container, v_object):
return HttpResponse("object_delete: %s %s %s" % (v_account, v_container, v_object))
@api_method()
def method_not_allowed(request):
raise BadRequest('Method not allowed.')
#
# Copyright (c) 2011 Greek Research and Technology Network
#
from django.http import HttpResponse
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
from pithos.backends.dummy_debug import *
import logging
logging.basicConfig(level=logging.DEBUG)
@api_method('GET')
def authenticate(request):
# Normal Response Codes: 204
# Error Response Codes: serviceUnavailable (503),
# unauthorized (401),
# badRequest (400)
x_auth_user = request.META.get('HTTP_X_AUTH_USER')
x_auth_key = request.META.get('HTTP_X_AUTH_KEY')
if not x_auth_user or not x_auth_key:
raise BadRequest('Missing auth user or key.')
response = HttpResponse(status = 204)
response['X-Auth-Token'] = 'eaaafd18-0fed-4b3a-81b4-663c99ec1cbb'
# TODO: Do we support redirections?
#response['X-Storage-Url'] = 'https://storage.grnet.gr/pithos/v1.0/<some reference>'
return response
def account_demux(request, v_account):
if request.method == 'HEAD':
return account_meta(request, v_account)
elif request.method == 'GET':
return container_list(request, v_account)
else:
return method_not_allowed(request)
def container_demux(request, v_account, v_container):
if request.method == 'HEAD':
return container_meta(request, v_account, v_container)
elif request.method == 'GET':
return object_list(request, v_account, v_container)
elif request.method == 'PUT':
return container_create(request, v_account, v_container)
elif request.method == 'DELETE':
return container_delete(request, v_account, v_container)
else:
return method_not_allowed(request)
def object_demux(request, v_account, v_container, v_object):
if request.method == 'HEAD':
return object_meta(request, v_account, v_container, v_object)
elif request.method == 'GET':
return object_read(request, v_account, v_container, v_object)
elif request.method == 'PUT':
return object_write(request, v_account, v_container, v_object)
elif request.method == 'POST':
return object_update(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)
@api_method('HEAD')
def account_meta(request, 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):
# Normal Response Codes: 200, 204
# Error Response Codes: serviceUnavailable (503),
# unauthorized (401),
# badRequest (400)
marker = request.GET.get('marker')
limit = request.GET.get('limit')
if limit:
try:
limit = int(limit)
except ValueError:
limit = None
containers = list_containers(request.user, marker, limit)
if len(containers) == 0:
return HttpResponse(status = 204)
if request.serialization == 'xml':
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)
return HttpResponse(data, status = 200)
@api_method('HEAD')
def container_meta(request, 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):
# 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):
# 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', format_allowed = True)
def object_list(request, 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):
# Normal Response Codes: 204
# Error Response Codes: serviceUnavailable (503),
# itemNotFound (404),
# unauthorized (401),
# badRequest (400)
info = get_object_meta(request.user, v_container, v_object)
response = HttpResponse(status = 204)
response['ETag'] = info['hash']
response['Content-Length'] = info['bytes']
response['Content-Type'] = info['content_type']
# TODO: Format time.
response['Last-Modified'] = info['last_modified']
for k, v in info['meta'].iteritems():
response['X-Object-Meta-%s' % k.capitalize()] = v
return response
@api_method('GET')
def object_read(request, v_account, v_container, v_object):
return HttpResponse("object_read: %s %s %s" % (v_account, v_container, v_object))
@api_method('PUT')
def object_write(request, v_account, v_container, v_object):
return HttpResponse("object_write: %s %s %s" % (v_account, v_container, v_object))
@api_method('POST')
def object_update(request, v_account, v_container, v_object):
# Normal Response Codes: 202
# Error Response Codes: serviceUnavailable (503),
# itemNotFound (404),
# unauthorized (401),
# badRequest (400)
prefix = 'X-Object-Meta-'
meta = dict([(k[len(prefix):].lower(), v) for k, v in request.POST.iteritems() if k.startswith(prefix)])
update_object_meta(request.user, v_container, v_object, meta)
return HttpResponse(status = 202)
@api_method('DELETE')
def object_delete(request, v_account, v_container, v_object):
# Normal Response Codes: 204
# Error Response Codes: serviceUnavailable (503),
# itemNotFound (404),
# unauthorized (401),
# badRequest (400)
delete_object(request.user, v_container, v_object)
return HttpResponse(status = 204)
@api_method()
def method_not_allowed(request):
raise BadRequest('Method not allowed.')
# vim: ts=4 sts=4 et ai sw=4 fileencoding=utf-8
#
# Copyright (c) 2011 Greek Research and Technology Network
#
......@@ -29,4 +28,6 @@ class Object(models.Model):
class Metadata(models.Model):
object = models.ForeignKey(Object)
name = models.CharField(max_length = 256)
value = models.CharField(max_length = 1024)
\ No newline at end of file
value = models.CharField(max_length = 1024)
date_created = models.DateTimeField(auto_now_add = True)
date_modified = models.DateTimeField(auto_now = True)
\ No newline at end of file
{% spaceless %}
<?xml version="1.0" encoding="UTF-8"?>
<account name="{{ account }}">
{% for container in containers %}
<container>
<name>{{ container.name }}</name>
<count>{{ container.count }}</count>
<bytes>{{ container.bytes }}</bytes>
</container>
{% endfor %}
</account>
{% endspaceless %}
{% spaceless %}
<?xml version="1.0" encoding="UTF-8"?>
<container name="{{ container }}">
{% for object in objects %}
<object>
<name>{{ object.name }}</name>
<hash>{{ object.hash }}</hash>
<bytes>{{ object.bytes }}</bytes>
<content_type>{{ object.content_type }}</content_type>
<last_modified>{{ object.last_modified }}</last_modified>
</object>
{% endfor %}
</container>
{% endspaceless %}
# vim: ts=4 sts=4 et ai sw=4 fileencoding=utf-8
#
# Copyright (c) 2011 Greek Research and Technology Network
#
......@@ -8,8 +7,8 @@ from django.conf.urls.defaults import *
# TODO: This only works when in this order.
# TODO: Define which characters can be used in each "path" component.
urlpatterns = patterns('pithos.api.functions',
(r'^$', 'authenticate'),
(r'^(?P<v_account>.+?)/(?P<v_container>.+?)/(?P<v_object>.+?)$', 'object_demux'),
(r'^(?P<v_account>.+?)/(?P<v_container>.+?)$', 'container_demux'),
(r'^(?P<v_account>.+?)$', 'account_demux')
(r'^$', 'authenticate'),
(r'^(?P<v_account>.+?)/(?P<v_container>.+?)/(?P<v_object>.+?)$', 'object_demux'),
(r'^(?P<v_account>.+?)/(?P<v_container>.+?)$', 'container_demux'),
(r'^(?P<v_account>.+?)$', 'account_demux')
)
# vim: ts=4 sts=4 et ai sw=4 fileencoding=utf-8
#
# Copyright (c) 2011 Greek Research and Technology Network
#
from datetime import timedelta, tzinfo
from functools import wraps
from random import choice
from string import ascii_letters, digits
from time import time
from traceback import format_exc
from wsgiref.handlers import format_date_time
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
from pithos.api.faults import Fault, BadRequest, ItemNotFound, ServiceUnavailable
#from synnefo.db.models import SynnefoUser, Image, ImageMetadata, VirtualMachine, VirtualMachineMetadata
import datetime
import dateutil.parser
import logging
# class UTC(tzinfo):
# def utcoffset(self, dt):
# return timedelta(0)
#
# def tzname(self, dt):
# return 'UTC'
#
# def dst(self, dt):
# return timedelta(0)
#
#
# def isoformat(d):
# """Return an ISO8601 date string that includes a timezon."""
#
# return d.replace(tzinfo=UTC()).isoformat()
#
# def isoparse(s):
# """Parse an ISO8601 date string into a datetime object."""
#
# if not s:
# return None
#
# try:
# since = dateutil.parser.parse(s)
# utc_since = since.astimezone(UTC()).replace(tzinfo=None)
# except ValueError:
# raise BadRequest('Invalid changes-since parameter.')
#
# now = datetime.datetime.now()
# if utc_since > now:
# raise BadRequest('changes-since value set in the future.')
#
# if now - utc_since > timedelta(seconds=settings.POLL_LIMIT):
# raise BadRequest('Too old changes-since value.')
#
# return utc_since
#
# def random_password(length=8):
# pool = ascii_letters + digits
# return ''.join(choice(pool) for i in range(length))
#
#
# def get_user():
# # XXX Placeholder function, everything belongs to a single SynnefoUser for now
# try:
# return SynnefoUser.objects.all()[0]
# except IndexError:
# raise Unauthorized
#
# def get_vm(server_id):
# """Return a VirtualMachine instance or raise ItemNotFound."""
#
# try:
# server_id = int(server_id)
# return VirtualMachine.objects.get(id=server_id)
# except ValueError:
# raise BadRequest('Invalid server ID.')
# except VirtualMachine.DoesNotExist:
# raise ItemNotFound('Server not found.')
#
# def get_vm_meta(server_id, key):
# """Return a VirtualMachineMetadata instance or raise ItemNotFound."""
#
# try:
# server_id = int(server_id)
# return VirtualMachineMetadata.objects.get(meta_key=key, vm=server_id)
# except VirtualMachineMetadata.DoesNotExist:
# raise ItemNotFound('Metadata key not found.')
#
# def get_image(image_id):
# """Return an Image instance or raise ItemNotFound."""
#
# try:
# image_id = int(image_id)
# return Image.objects.get(id=image_id)
# except Image.DoesNotExist:
# raise ItemNotFound('Image not found.')
#
# def get_image_meta(image_id, key):
# """Return a ImageMetadata instance or raise ItemNotFound."""
#
# try:
# image_id = int(image_id)
# return ImageMetadata.objects.get(meta_key=key, image=image_id)
# except ImageMetadata.DoesNotExist:
# raise ItemNotFound('Metadata key not found.')
#
#
# def get_request_dict(request):
# """Returns data sent by the client as a python dict."""
#
# data = request.raw_post_data
# if request.META.get('CONTENT_TYPE').startswith('application/json'):
# try:
# return json.loads(data)
# except ValueError:
# raise BadRequest('Invalid JSON data.')
# else:
# raise BadRequest('Unsupported Content-Type.')
def update_response_headers(request, response):