Commit c7e0bbe2 authored by Antony Chazapis's avatar Antony Chazapis
Browse files

Initial API layout.

parent 37ff2e2c
# vim: ts=4 sts=4 et ai sw=4 fileencoding=utf-8
#
# Copyright (c) 2011 Greek Research and Technology Network
#
from api.models import Container, Object, Metadata
from django.contrib import admin
admin.site.register(Container)
admin.site.register(Object)
admin.site.register(Metadata)
\ No newline at end of file
# 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
# 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.')
# vim: ts=4 sts=4 et ai sw=4 fileencoding=utf-8
#
# Copyright (c) 2011 Greek Research and Technology Network
#
from django.db import models
class Container(models.Model):
account = models.CharField(max_length = 256)
name = models.CharField(max_length = 256)
date_created = models.DateTimeField(auto_now_add = True)
def __unicode__(self):
return self.name
class Object(models.Model):
container = models.ForeignKey(Container)
name = models.CharField(max_length = 1024)
length = models.IntegerField()
type = models.CharField(max_length = 256)
hash = models.CharField(max_length = 256)
data = models.FileField(upload_to = 'data', max_length = 256)
date_created = models.DateTimeField(auto_now_add = True)
date_modified = models.DateTimeField(auto_now = True)
def __unicode__(self):
return self.name
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
"""
This file demonstrates two different styles of tests (one doctest and one
unittest). These will both pass when you run "manage.py test".
Replace these with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.failUnlessEqual(1 + 1, 2)
__test__ = {"doctest": """
Another way to test that 1 + 1 is equal to 2.
>>> 1 + 1 == 2
True
"""}
# vim: ts=4 sts=4 et ai sw=4 fileencoding=utf-8
#
# Copyright (c) 2011 Greek Research and Technology Network
#
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')
)
# 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):
# if request.serialization == 'xml':
# response['Content-Type'] = 'application/xml'
# elif request.serialization == 'atom':
# response['Content-Type'] = 'application/atom+xml'
# else:
# response['Content-Type'] = 'application/json'
#
response['Content-Type'] = 'text/plain; charset=UTF-8'
response['Server'] = 'GRNET Pithos v.0.1'
if settings.TEST:
response['Date'] = format_date_time(time())
# def render_metadata(request, metadata, use_values=False, status=200):
# if request.serialization == 'xml':
# data = render_to_string('metadata.xml', {'metadata': metadata})
# else:
# d = {'metadata': {'values': metadata}} if use_values else {'metadata': metadata}
# data = json.dumps(d)
# return HttpResponse(data, status=status)
#
# def render_meta(request, meta, status=200):
# if request.serialization == 'xml':
# data = render_to_string('meta.xml', {'meta': meta})
# else:
# data = json.dumps({'meta': {meta.meta_key: meta.meta_value}})
# return HttpResponse(data, status=status)
def render_fault(request, fault):
if settings.DEBUG or settings.TEST:
fault.details = format_exc(fault)
# if request.serialization == 'xml':
# data = render_to_string('fault.xml', {'fault': fault})
# else:
# d = {fault.name: {'code': fault.code, 'message': fault.message, 'details': fault.details}}
# data = json.dumps(d)
# resp = HttpResponse(data, status=fault.code)
resp = HttpResponse(status=fault.code)
update_response_headers(request, resp)
return resp
# def request_serialization(request, atom_allowed=False):
# """Return the serialization format requested.
#
# Valid formats are 'json', 'xml' and 'atom' if `atom_allowed` is True.
# """
#
# path = request.path
#
# if path.endswith('.json'):
# return 'json'
# elif path.endswith('.xml'):
# return 'xml'
# elif atom_allowed and path.endswith('.atom'):
# return 'atom'
#
# 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':
# return 'xml'
# elif atom_allowed and accept == 'application/atom+xml':
# return 'atom'
#
# return 'json'
def api_method(http_method=None, atom_allowed=False):
"""Decorator function for views that implement an API method."""
def decorator(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
try:
#request.serialization = request_serialization(request, atom_allowed)
if http_method and request.method != http_method:
raise BadRequest('Method not allowed.')
resp = func(request, *args, **kwargs)
update_response_headers(request, resp)
return resp
except Fault, fault:
return render_fault(request, fault)
except BaseException, e:
logging.exception('Unexpected error: %s' % e)
fault = ServiceUnavailable('Unexpected error')
return render_fault(request, fault)
return wrapper
return decorator
# Create your views here.
def create_container(name):
return
def delete_container(name):
return
def get_container_meta(name):
return {'name': name, 'count': 0, 'bytes': 0}
def list_containers():
return []
def list_objects(container, prefix='', delimiter='/'):
return []
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(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
#!/usr/bin/env python
from django.core.management import execute_manager
try:
import settings # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.exit(1)
if __name__ == "__main__":
execute_manager(settings)
# Django settings for pithos project.
import os
PROJECT_PATH = os.path.dirname(os.path.abspath(__file__)) + '/'
DEBUG = True
TEMPLATE_DEBUG = DEBUG
# A quick-n-dirty way to know if we're running unit tests
import sys
TEST = False
if len(sys.argv) >= 2:
if os.path.basename(sys.argv[0]) == 'manage.py' and \
(sys.argv[1] == 'test' or sys.argv[1] == 'hudson'):
TEST = True
ADMINS = (
# ('Your Name', 'your_email@domain.com'),
)
MANAGERS = ADMINS
DATABASES = {
'default': {
'ENGINE': 'sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
'NAME': PROJECT_PATH + 'pithos.db', # Or path to database file if using sqlite3.
'USER': '', # Not used with sqlite3.
'PASSWORD': '', # Not used with sqlite3.
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
}
}
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# On Unix systems, a value of None will cause Django to use the same
# timezone as the operating system.
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'UTC'
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True
# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale
USE_L10N = True
# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = ''
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases).
# Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = ''
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/media/'
# Make this unique, and don't share it with anybody.
SECRET_KEY = '$j0cdrfm*0sc2j+e@@2f-&3-_@2=^!z#+b-8o4_i10@2%ev7si'
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
)
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
)
ROOT_URLCONF = 'pithos.urls'
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.admin',
'django.contrib.admindocs',
'api'
)
# vim: ts=4 sts=4 et ai sw=4 fileencoding=utf-8
#
# Copyright © 2011 Greek Research and Technology Network
#
from django.conf.urls.defaults import *
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
(r'^v1/', include('pithos.api.urls')),