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
26 # class UTC(tzinfo):
\r
27 # def utcoffset(self, dt):
\r
28 # return timedelta(0)
\r
30 # def tzname(self, dt):
\r
33 # def dst(self, dt):
\r
34 # return timedelta(0)
\r
38 # """Return an ISO8601 date string that includes a timezon."""
\r
40 # return d.replace(tzinfo=UTC()).isoformat()
\r
43 # """Parse an ISO8601 date string into a datetime object."""
\r
49 # since = dateutil.parser.parse(s)
\r
50 # utc_since = since.astimezone(UTC()).replace(tzinfo=None)
\r
51 # except ValueError:
\r
52 # raise BadRequest('Invalid changes-since parameter.')
\r
54 # now = datetime.datetime.now()
\r
55 # if utc_since > now:
\r
56 # raise BadRequest('changes-since value set in the future.')
\r
58 # if now - utc_since > timedelta(seconds=settings.POLL_LIMIT):
\r
59 # raise BadRequest('Too old changes-since value.')
\r
63 # def random_password(length=8):
\r
64 # pool = ascii_letters + digits
\r
65 # return ''.join(choice(pool) for i in range(length))
\r
69 # # XXX Placeholder function, everything belongs to a single SynnefoUser for now
\r
71 # return SynnefoUser.objects.all()[0]
\r
72 # except IndexError:
\r
73 # raise Unauthorized
\r
75 # def get_vm(server_id):
\r
76 # """Return a VirtualMachine instance or raise ItemNotFound."""
\r
79 # server_id = int(server_id)
\r
80 # return VirtualMachine.objects.get(id=server_id)
\r
81 # except ValueError:
\r
82 # raise BadRequest('Invalid server ID.')
\r
83 # except VirtualMachine.DoesNotExist:
\r
84 # raise ItemNotFound('Server not found.')
\r
86 # def get_vm_meta(server_id, key):
\r
87 # """Return a VirtualMachineMetadata instance or raise ItemNotFound."""
\r
90 # server_id = int(server_id)
\r
91 # return VirtualMachineMetadata.objects.get(meta_key=key, vm=server_id)
\r
92 # except VirtualMachineMetadata.DoesNotExist:
\r
93 # raise ItemNotFound('Metadata key not found.')
\r
95 # def get_image(image_id):
\r
96 # """Return an Image instance or raise ItemNotFound."""
\r
99 # image_id = int(image_id)
\r
100 # return Image.objects.get(id=image_id)
\r
101 # except Image.DoesNotExist:
\r
102 # raise ItemNotFound('Image not found.')
\r
104 # def get_image_meta(image_id, key):
\r
105 # """Return a ImageMetadata instance or raise ItemNotFound."""
\r
108 # image_id = int(image_id)
\r
109 # return ImageMetadata.objects.get(meta_key=key, image=image_id)
\r
110 # except ImageMetadata.DoesNotExist:
\r
111 # raise ItemNotFound('Metadata key not found.')
\r
114 # def get_request_dict(request):
\r
115 # """Returns data sent by the client as a python dict."""
\r
117 # data = request.raw_post_data
\r
118 # if request.META.get('CONTENT_TYPE').startswith('application/json'):
\r
120 # return json.loads(data)
\r
121 # except ValueError:
\r
122 # raise BadRequest('Invalid JSON data.')
\r
124 # raise BadRequest('Unsupported Content-Type.')
\r
126 def update_response_headers(request, response):
\r
127 if request.serialization == 'xml':
\r
128 response['Content-Type'] = 'application/xml; charset=UTF-8'
\r
129 elif request.serialization == 'json':
\r
130 response['Content-Type'] = 'application/json; charset=UTF-8'
\r
132 response['Content-Type'] = 'text/plain; charset=UTF-8'
\r
135 response['Date'] = format_date_time(time())
\r
137 # def render_metadata(request, metadata, use_values=False, status=200):
\r
138 # if request.serialization == 'xml':
\r
139 # data = render_to_string('metadata.xml', {'metadata': metadata})
\r
141 # d = {'metadata': {'values': metadata}} if use_values else {'metadata': metadata}
\r
142 # data = json.dumps(d)
\r
143 # return HttpResponse(data, status=status)
\r
145 # def render_meta(request, meta, status=200):
\r
146 # if request.serialization == 'xml':
\r
147 # data = render_to_string('meta.xml', {'meta': meta})
\r
149 # data = json.dumps({'meta': {meta.meta_key: meta.meta_value}})
\r
150 # return HttpResponse(data, status=status)
\r
152 def render_fault(request, fault):
\r
153 if settings.DEBUG or settings.TEST:
\r
154 fault.details = format_exc(fault)
\r
156 # if request.serialization == 'xml':
\r
157 # data = render_to_string('fault.xml', {'fault': fault})
\r
159 # d = {fault.name: {'code': fault.code, 'message': fault.message, 'details': fault.details}}
\r
160 # data = json.dumps(d)
\r
162 # resp = HttpResponse(data, status=fault.code)
\r
163 resp = HttpResponse(status=fault.code)
\r
164 update_response_headers(request, resp)
\r
167 def request_serialization(request, format_allowed=False):
\r
168 """Return the serialization format requested.
\r
170 Valid formats are 'text' and 'json', 'xml' if `format_allowed` is True.
\r
173 if not format_allowed:
\r
176 format = request.GET.get('format')
\r
177 if format == 'json':
\r
179 elif format == 'xml':
\r
182 # for item in request.META.get('HTTP_ACCEPT', '').split(','):
\r
183 # accept, sep, rest = item.strip().partition(';')
\r
184 # if accept == 'application/json':
\r
186 # elif accept == 'application/xml':
\r
191 def api_method(http_method = None, format_allowed = False):
\r
192 """Decorator function for views that implement an API method."""
\r
194 def decorator(func):
\r
196 def wrapper(request, *args, **kwargs):
\r
198 request.serialization = request_serialization(request, format_allowed)
\r
199 if http_method and request.method != http_method:
\r
200 raise BadRequest('Method not allowed.')
\r
202 resp = func(request, *args, **kwargs)
\r
203 update_response_headers(request, resp)
\r
206 except Fault, fault:
\r
207 return render_fault(request, fault)
\r
208 except BaseException, e:
\r
209 logging.exception('Unexpected error: %s' % e)
\r
210 fault = ServiceUnavailable('Unexpected error')
\r
211 return render_fault(request, fault)
\r