2 # Copyright (c) 2011 Greek Research and Technology Network
\r
5 from datetime import timedelta, tzinfo
\r
6 from functools import wraps
\r
7 from random import choice
\r
8 from string import ascii_letters, digits
\r
9 from time import time
\r
10 from traceback import format_exc
\r
11 from wsgiref.handlers import format_date_time
\r
13 from django.conf import settings
\r
14 from django.http import HttpResponse
\r
15 from django.template.loader import render_to_string
\r
16 from django.utils import simplejson as json
\r
18 from pithos.api.faults import Fault, BadRequest, ItemNotFound, ServiceUnavailable
\r
19 #from synnefo.db.models import SynnefoUser, Image, ImageMetadata, VirtualMachine, VirtualMachineMetadata
\r
22 import dateutil.parser
\r
25 # class UTC(tzinfo):
\r
26 # def utcoffset(self, dt):
\r
27 # return timedelta(0)
\r
29 # def tzname(self, dt):
\r
32 # def dst(self, dt):
\r
33 # return timedelta(0)
\r
37 # """Return an ISO8601 date string that includes a timezon."""
\r
39 # return d.replace(tzinfo=UTC()).isoformat()
\r
42 # """Parse an ISO8601 date string into a datetime object."""
\r
48 # since = dateutil.parser.parse(s)
\r
49 # utc_since = since.astimezone(UTC()).replace(tzinfo=None)
\r
50 # except ValueError:
\r
51 # raise BadRequest('Invalid changes-since parameter.')
\r
53 # now = datetime.datetime.now()
\r
54 # if utc_since > now:
\r
55 # raise BadRequest('changes-since value set in the future.')
\r
57 # if now - utc_since > timedelta(seconds=settings.POLL_LIMIT):
\r
58 # raise BadRequest('Too old changes-since value.')
\r
62 # def random_password(length=8):
\r
63 # pool = ascii_letters + digits
\r
64 # return ''.join(choice(pool) for i in range(length))
\r
68 # # XXX Placeholder function, everything belongs to a single SynnefoUser for now
\r
70 # return SynnefoUser.objects.all()[0]
\r
71 # except IndexError:
\r
72 # raise Unauthorized
\r
74 # def get_vm(server_id):
\r
75 # """Return a VirtualMachine instance or raise ItemNotFound."""
\r
78 # server_id = int(server_id)
\r
79 # return VirtualMachine.objects.get(id=server_id)
\r
80 # except ValueError:
\r
81 # raise BadRequest('Invalid server ID.')
\r
82 # except VirtualMachine.DoesNotExist:
\r
83 # raise ItemNotFound('Server not found.')
\r
85 # def get_vm_meta(server_id, key):
\r
86 # """Return a VirtualMachineMetadata instance or raise ItemNotFound."""
\r
89 # server_id = int(server_id)
\r
90 # return VirtualMachineMetadata.objects.get(meta_key=key, vm=server_id)
\r
91 # except VirtualMachineMetadata.DoesNotExist:
\r
92 # raise ItemNotFound('Metadata key not found.')
\r
94 # def get_image(image_id):
\r
95 # """Return an Image instance or raise ItemNotFound."""
\r
98 # image_id = int(image_id)
\r
99 # return Image.objects.get(id=image_id)
\r
100 # except Image.DoesNotExist:
\r
101 # raise ItemNotFound('Image not found.')
\r
103 # def get_image_meta(image_id, key):
\r
104 # """Return a ImageMetadata instance or raise ItemNotFound."""
\r
107 # image_id = int(image_id)
\r
108 # return ImageMetadata.objects.get(meta_key=key, image=image_id)
\r
109 # except ImageMetadata.DoesNotExist:
\r
110 # raise ItemNotFound('Metadata key not found.')
\r
113 # def get_request_dict(request):
\r
114 # """Returns data sent by the client as a python dict."""
\r
116 # data = request.raw_post_data
\r
117 # if request.META.get('CONTENT_TYPE').startswith('application/json'):
\r
119 # return json.loads(data)
\r
120 # except ValueError:
\r
121 # raise BadRequest('Invalid JSON data.')
\r
123 # raise BadRequest('Unsupported Content-Type.')
\r
125 def update_response_headers(request, response):
\r
126 if request.serialization == 'xml':
\r
127 response['Content-Type'] = 'application/xml; charset=UTF-8'
\r
128 elif request.serialization == 'json':
\r
129 response['Content-Type'] = 'application/json; charset=UTF-8'
\r
131 response['Content-Type'] = 'text/plain; charset=UTF-8'
\r
134 response['Date'] = format_date_time(time())
\r
136 # def render_metadata(request, metadata, use_values=False, status=200):
\r
137 # if request.serialization == 'xml':
\r
138 # data = render_to_string('metadata.xml', {'metadata': metadata})
\r
140 # d = {'metadata': {'values': metadata}} if use_values else {'metadata': metadata}
\r
141 # data = json.dumps(d)
\r
142 # return HttpResponse(data, status=status)
\r
144 # def render_meta(request, meta, status=200):
\r
145 # if request.serialization == 'xml':
\r
146 # data = render_to_string('meta.xml', {'meta': meta})
\r
148 # data = json.dumps({'meta': {meta.meta_key: meta.meta_value}})
\r
149 # return HttpResponse(data, status=status)
\r
151 def render_fault(request, fault):
\r
152 if settings.DEBUG or settings.TEST:
\r
153 fault.details = format_exc(fault)
\r
155 # if request.serialization == 'xml':
\r
156 # data = render_to_string('fault.xml', {'fault': fault})
\r
158 # d = {fault.name: {'code': fault.code, 'message': fault.message, 'details': fault.details}}
\r
159 # data = json.dumps(d)
\r
161 # resp = HttpResponse(data, status=fault.code)
\r
162 resp = HttpResponse(status = fault.code)
\r
163 update_response_headers(request, resp)
\r
166 def request_serialization(request, format_allowed=False):
\r
167 """Return the serialization format requested.
\r
169 Valid formats are 'text' and 'json', 'xml' if `format_allowed` is True.
\r
172 if not format_allowed:
\r
175 format = request.GET.get('format')
\r
176 if format == 'json':
\r
178 elif format == 'xml':
\r
181 # for item in request.META.get('HTTP_ACCEPT', '').split(','):
\r
182 # accept, sep, rest = item.strip().partition(';')
\r
183 # if accept == 'application/json':
\r
185 # elif accept == 'application/xml':
\r
190 def api_method(http_method = None, format_allowed = False):
\r
191 """Decorator function for views that implement an API method."""
\r
193 def decorator(func):
\r
195 def wrapper(request, *args, **kwargs):
\r
197 request.serialization = request_serialization(request, format_allowed)
\r
198 # TODO: Authenticate.
\r
199 # TODO: Return 401/404 when the account is not found.
\r
200 request.user = "test"
\r
201 # TODO: Check parameter sizes.
\r
202 if http_method and request.method != http_method:
\r
203 raise BadRequest('Method not allowed.')
\r
205 resp = func(request, *args, **kwargs)
\r
206 update_response_headers(request, resp)
\r
209 except Fault, fault:
\r
210 return render_fault(request, fault)
\r
211 except BaseException, e:
\r
212 logging.exception('Unexpected error: %s' % e)
\r
213 fault = ServiceUnavailable('Unexpected error')
\r
214 return render_fault(request, fault)
\r