Containers API.
[pithos] / api / util.py
1 #\r
2 # Copyright (c) 2011 Greek Research and Technology Network\r
3 #\r
4 \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
12 \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
17 \r
18 from pithos.api.faults import Fault, BadRequest, ItemNotFound, ServiceUnavailable\r
19 #from synnefo.db.models import SynnefoUser, Image, ImageMetadata, VirtualMachine, VirtualMachineMetadata\r
20 \r
21 import datetime\r
22 import dateutil.parser\r
23 import logging\r
24 \r
25 # class UTC(tzinfo):\r
26 #     def utcoffset(self, dt):\r
27 #         return timedelta(0)\r
28 #     \r
29 #     def tzname(self, dt):\r
30 #         return 'UTC'\r
31 #     \r
32 #     def dst(self, dt):\r
33 #         return timedelta(0)\r
34\r
35\r
36 # def isoformat(d):\r
37 #     """Return an ISO8601 date string that includes a timezon."""\r
38 #     \r
39 #     return d.replace(tzinfo=UTC()).isoformat()\r
40\r
41 # def isoparse(s):\r
42 #     """Parse an ISO8601 date string into a datetime object."""\r
43 #     \r
44 #     if not s:\r
45 #         return None\r
46 #     \r
47 #     try:\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
52 #     \r
53 #     now = datetime.datetime.now()\r
54 #     if utc_since > now:\r
55 #         raise BadRequest('changes-since value set in the future.')\r
56 #     \r
57 #     if now - utc_since > timedelta(seconds=settings.POLL_LIMIT):\r
58 #         raise BadRequest('Too old changes-since value.')\r
59 #     \r
60 #     return utc_since\r
61 #     \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
65\r
66\r
67 # def get_user():\r
68 #     # XXX Placeholder function, everything belongs to a single SynnefoUser for now\r
69 #     try:\r
70 #         return SynnefoUser.objects.all()[0]\r
71 #     except IndexError:\r
72 #         raise Unauthorized\r
73\r
74 # def get_vm(server_id):\r
75 #     """Return a VirtualMachine instance or raise ItemNotFound."""\r
76 #     \r
77 #     try:\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
84\r
85 # def get_vm_meta(server_id, key):\r
86 #     """Return a VirtualMachineMetadata instance or raise ItemNotFound."""\r
87 #     \r
88 #     try:\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
93\r
94 # def get_image(image_id):\r
95 #     """Return an Image instance or raise ItemNotFound."""\r
96 #     \r
97 #     try:\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
102\r
103 # def get_image_meta(image_id, key):\r
104 #     """Return a ImageMetadata instance or raise ItemNotFound."""\r
105\r
106 #     try:\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
111\r
112\r
113 # def get_request_dict(request):\r
114 #     """Returns data sent by the client as a python dict."""\r
115 #     \r
116 #     data = request.raw_post_data\r
117 #     if request.META.get('CONTENT_TYPE').startswith('application/json'):\r
118 #         try:\r
119 #             return json.loads(data)\r
120 #         except ValueError:\r
121 #             raise BadRequest('Invalid JSON data.')\r
122 #     else:\r
123 #         raise BadRequest('Unsupported Content-Type.')\r
124 \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
130     else:\r
131         response['Content-Type'] = 'text/plain; charset=UTF-8'\r
132 \r
133     if settings.TEST:\r
134         response['Date'] = format_date_time(time())\r
135 \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
139 #     else:\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
143\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
147 #     else:\r
148 #         data = json.dumps({'meta': {meta.meta_key: meta.meta_value}})\r
149 #     return HttpResponse(data, status=status)\r
150 \r
151 def render_fault(request, fault):\r
152     if settings.DEBUG or settings.TEST:\r
153         fault.details = format_exc(fault)\r
154     \r
155 #     if request.serialization == 'xml':\r
156 #         data = render_to_string('fault.xml', {'fault': fault})\r
157 #     else:\r
158 #         d = {fault.name: {'code': fault.code, 'message': fault.message, 'details': fault.details}}\r
159 #         data = json.dumps(d)\r
160     \r
161 #     resp = HttpResponse(data, status=fault.code)\r
162     resp = HttpResponse(status = fault.code)\r
163     update_response_headers(request, resp)\r
164     return resp\r
165 \r
166 def request_serialization(request, format_allowed=False):\r
167     """Return the serialization format requested.\r
168        \r
169        Valid formats are 'text' and 'json', 'xml' if `format_allowed` is True.\r
170     """\r
171     \r
172     if not format_allowed:\r
173         return 'text'\r
174     \r
175     format = request.GET.get('format')\r
176     if format == 'json':\r
177         return 'json'\r
178     elif format == 'xml':\r
179         return 'xml'\r
180     \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
184 #             return 'json'\r
185 #         elif accept == 'application/xml':\r
186 #             return 'xml'\r
187     \r
188     return 'text'\r
189 \r
190 def api_method(http_method = None, format_allowed = False):\r
191     """Decorator function for views that implement an API method."""\r
192     \r
193     def decorator(func):\r
194         @wraps(func)\r
195         def wrapper(request, *args, **kwargs):\r
196             try:\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
204                 \r
205                 resp = func(request, *args, **kwargs)\r
206                 update_response_headers(request, resp)\r
207                 return resp\r
208             \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
215         return wrapper\r
216     return decorator\r