root / api / util.py @ 838c404d
History | View | Annotate | Download (6.7 kB)
1 | 7e2f9d4b | Giorgos Verigakis | #
|
---|---|---|---|
2 | 7e2f9d4b | Giorgos Verigakis | # Copyright (c) 2010 Greek Research and Technology Network
|
3 | 7e2f9d4b | Giorgos Verigakis | #
|
4 | 7e2f9d4b | Giorgos Verigakis | |
5 | d8e50a39 | Giorgos Verigakis | from datetime import timedelta, tzinfo |
6 | d8e50a39 | Giorgos Verigakis | from functools import wraps |
7 | d8e50a39 | Giorgos Verigakis | from random import choice |
8 | d8e50a39 | Giorgos Verigakis | from string import ascii_letters, digits |
9 | d8e50a39 | Giorgos Verigakis | from traceback import format_exc |
10 | 7e2f9d4b | Giorgos Verigakis | |
11 | d8e50a39 | Giorgos Verigakis | from django.conf import settings |
12 | 7e2f9d4b | Giorgos Verigakis | from django.http import HttpResponse |
13 | 7e2f9d4b | Giorgos Verigakis | from django.template.loader import render_to_string |
14 | 29a59bc1 | Giorgos Verigakis | from django.utils import simplejson as json |
15 | 7e2f9d4b | Giorgos Verigakis | |
16 | 432fc8c3 | Giorgos Verigakis | from synnefo.api.faults import Fault, BadRequest, ItemNotFound, ServiceUnavailable |
17 | 432fc8c3 | Giorgos Verigakis | from synnefo.db.models import SynnefoUser, Image, ImageMetadata, VirtualMachine, VirtualMachineMetadata |
18 | 7e2f9d4b | Giorgos Verigakis | |
19 | d8e50a39 | Giorgos Verigakis | import datetime |
20 | d8e50a39 | Giorgos Verigakis | import dateutil.parser |
21 | d8e50a39 | Giorgos Verigakis | import logging |
22 | 7e2f9d4b | Giorgos Verigakis | |
23 | 7e2f9d4b | Giorgos Verigakis | |
24 | d8e50a39 | Giorgos Verigakis | class UTC(tzinfo): |
25 | d8e50a39 | Giorgos Verigakis | def utcoffset(self, dt): |
26 | d8e50a39 | Giorgos Verigakis | return timedelta(0) |
27 | d8e50a39 | Giorgos Verigakis | |
28 | d8e50a39 | Giorgos Verigakis | def tzname(self, dt): |
29 | d8e50a39 | Giorgos Verigakis | return 'UTC' |
30 | d8e50a39 | Giorgos Verigakis | |
31 | d8e50a39 | Giorgos Verigakis | def dst(self, dt): |
32 | d8e50a39 | Giorgos Verigakis | return timedelta(0) |
33 | 7e2f9d4b | Giorgos Verigakis | |
34 | 7e2f9d4b | Giorgos Verigakis | |
35 | d8e50a39 | Giorgos Verigakis | def isoformat(d): |
36 | d8e50a39 | Giorgos Verigakis | """Return an ISO8601 date string that includes a timezon."""
|
37 | d8e50a39 | Giorgos Verigakis | |
38 | d8e50a39 | Giorgos Verigakis | return d.replace(tzinfo=UTC()).isoformat()
|
39 | d8e50a39 | Giorgos Verigakis | |
40 | d8e50a39 | Giorgos Verigakis | def isoparse(s): |
41 | d8e50a39 | Giorgos Verigakis | """Parse an ISO8601 date string into a datetime object."""
|
42 | d8e50a39 | Giorgos Verigakis | |
43 | d8e50a39 | Giorgos Verigakis | if not s: |
44 | d8e50a39 | Giorgos Verigakis | return None |
45 | d8e50a39 | Giorgos Verigakis | |
46 | d8e50a39 | Giorgos Verigakis | try:
|
47 | d8e50a39 | Giorgos Verigakis | since = dateutil.parser.parse(s) |
48 | d8e50a39 | Giorgos Verigakis | except ValueError: |
49 | d8e50a39 | Giorgos Verigakis | raise BadRequest('Invalid changes-since parameter.') |
50 | d8e50a39 | Giorgos Verigakis | |
51 | d8e50a39 | Giorgos Verigakis | now = datetime.datetime.now(UTC()) |
52 | d8e50a39 | Giorgos Verigakis | if since > now:
|
53 | d8e50a39 | Giorgos Verigakis | raise BadRequest('changes-since value set in the future.') |
54 | d8e50a39 | Giorgos Verigakis | |
55 | d8e50a39 | Giorgos Verigakis | if now - since > timedelta(seconds=settings.POLL_LIMIT):
|
56 | d8e50a39 | Giorgos Verigakis | raise BadRequest('Too old changes-since value.') |
57 | d8e50a39 | Giorgos Verigakis | |
58 | d8e50a39 | Giorgos Verigakis | return since
|
59 | d8e50a39 | Giorgos Verigakis | |
60 | d8e50a39 | Giorgos Verigakis | def random_password(length=8): |
61 | d8e50a39 | Giorgos Verigakis | pool = ascii_letters + digits |
62 | d8e50a39 | Giorgos Verigakis | return ''.join(choice(pool) for i in range(length)) |
63 | d8e50a39 | Giorgos Verigakis | |
64 | 7e2f9d4b | Giorgos Verigakis | |
65 | 7e2f9d4b | Giorgos Verigakis | def get_user(): |
66 | 7e2f9d4b | Giorgos Verigakis | # XXX Placeholder function, everything belongs to a single SynnefoUser for now
|
67 | 7e2f9d4b | Giorgos Verigakis | try:
|
68 | 7e2f9d4b | Giorgos Verigakis | return SynnefoUser.objects.all()[0] |
69 | 7e2f9d4b | Giorgos Verigakis | except IndexError: |
70 | 7e2f9d4b | Giorgos Verigakis | raise Unauthorized
|
71 | 7e2f9d4b | Giorgos Verigakis | |
72 | d8e50a39 | Giorgos Verigakis | def get_vm(server_id): |
73 | d8e50a39 | Giorgos Verigakis | """Return a VirtualMachine instance or raise ItemNotFound."""
|
74 | d8e50a39 | Giorgos Verigakis | |
75 | d8e50a39 | Giorgos Verigakis | try:
|
76 | d8e50a39 | Giorgos Verigakis | server_id = int(server_id)
|
77 | d8e50a39 | Giorgos Verigakis | return VirtualMachine.objects.get(id=server_id)
|
78 | d8e50a39 | Giorgos Verigakis | except ValueError: |
79 | d8e50a39 | Giorgos Verigakis | raise BadRequest('Invalid server ID.') |
80 | d8e50a39 | Giorgos Verigakis | except VirtualMachine.DoesNotExist:
|
81 | d8e50a39 | Giorgos Verigakis | raise ItemNotFound('Server not found.') |
82 | d8e50a39 | Giorgos Verigakis | |
83 | d8e50a39 | Giorgos Verigakis | def get_vm_meta(server_id, key): |
84 | d8e50a39 | Giorgos Verigakis | """Return a VirtualMachineMetadata instance or raise ItemNotFound."""
|
85 | d8e50a39 | Giorgos Verigakis | |
86 | d8e50a39 | Giorgos Verigakis | try:
|
87 | d8e50a39 | Giorgos Verigakis | server_id = int(server_id)
|
88 | d8e50a39 | Giorgos Verigakis | return VirtualMachineMetadata.objects.get(meta_key=key, vm=server_id)
|
89 | d8e50a39 | Giorgos Verigakis | except VirtualMachineMetadata.DoesNotExist:
|
90 | d8e50a39 | Giorgos Verigakis | raise ItemNotFound('Metadata key not found.') |
91 | d8e50a39 | Giorgos Verigakis | |
92 | d8e50a39 | Giorgos Verigakis | def get_image(image_id): |
93 | d8e50a39 | Giorgos Verigakis | """Return an Image instance or raise ItemNotFound."""
|
94 | d8e50a39 | Giorgos Verigakis | |
95 | d8e50a39 | Giorgos Verigakis | try:
|
96 | d8e50a39 | Giorgos Verigakis | image_id = int(image_id)
|
97 | d8e50a39 | Giorgos Verigakis | return Image.objects.get(id=image_id)
|
98 | d8e50a39 | Giorgos Verigakis | except Image.DoesNotExist:
|
99 | d8e50a39 | Giorgos Verigakis | raise ItemNotFound('Image not found.') |
100 | d8e50a39 | Giorgos Verigakis | |
101 | 432fc8c3 | Giorgos Verigakis | def get_image_meta(image_id, key): |
102 | 432fc8c3 | Giorgos Verigakis | """Return a ImageMetadata instance or raise ItemNotFound."""
|
103 | 432fc8c3 | Giorgos Verigakis | |
104 | 432fc8c3 | Giorgos Verigakis | try:
|
105 | 432fc8c3 | Giorgos Verigakis | image_id = int(image_id)
|
106 | 432fc8c3 | Giorgos Verigakis | return ImageMetadata.objects.get(meta_key=key, image=image_id)
|
107 | 432fc8c3 | Giorgos Verigakis | except ImageMetadata.DoesNotExist:
|
108 | 432fc8c3 | Giorgos Verigakis | raise ItemNotFound('Metadata key not found.') |
109 | 432fc8c3 | Giorgos Verigakis | |
110 | d8e50a39 | Giorgos Verigakis | |
111 | 7e2f9d4b | Giorgos Verigakis | def get_request_dict(request): |
112 | d8e50a39 | Giorgos Verigakis | """Returns data sent by the client as a python dict."""
|
113 | d8e50a39 | Giorgos Verigakis | |
114 | 7e2f9d4b | Giorgos Verigakis | data = request.raw_post_data |
115 | 7e45ddef | Dimitris Moraitis | if request.META.get('CONTENT_TYPE').startswith('application/json'): |
116 | 7e2f9d4b | Giorgos Verigakis | try:
|
117 | 7e2f9d4b | Giorgos Verigakis | return json.loads(data)
|
118 | 7e2f9d4b | Giorgos Verigakis | except ValueError: |
119 | d8e50a39 | Giorgos Verigakis | raise BadRequest('Invalid JSON data.') |
120 | d8e50a39 | Giorgos Verigakis | else:
|
121 | d8e50a39 | Giorgos Verigakis | raise BadRequest('Unsupported Content-Type.') |
122 | 7e2f9d4b | Giorgos Verigakis | |
123 | 432fc8c3 | Giorgos Verigakis | |
124 | 432fc8c3 | Giorgos Verigakis | def render_metadata(request, metadata, use_values=False, status=200): |
125 | 432fc8c3 | Giorgos Verigakis | if request.serialization == 'xml': |
126 | 432fc8c3 | Giorgos Verigakis | data = render_to_string('metadata.xml', {'metadata': metadata}) |
127 | 432fc8c3 | Giorgos Verigakis | else:
|
128 | 432fc8c3 | Giorgos Verigakis | d = {'metadata': {'values': metadata}} if use_values else {'metadata': metadata} |
129 | 432fc8c3 | Giorgos Verigakis | data = json.dumps(d) |
130 | 432fc8c3 | Giorgos Verigakis | return HttpResponse(data, status=status)
|
131 | 432fc8c3 | Giorgos Verigakis | |
132 | 432fc8c3 | Giorgos Verigakis | def render_meta(request, meta, status=200): |
133 | 432fc8c3 | Giorgos Verigakis | if request.serialization == 'xml': |
134 | 432fc8c3 | Giorgos Verigakis | data = render_to_string('meta.xml', {'meta': meta}) |
135 | 432fc8c3 | Giorgos Verigakis | else:
|
136 | 432fc8c3 | Giorgos Verigakis | data = json.dumps({'meta': {meta.meta_key: meta.meta_value}})
|
137 | 432fc8c3 | Giorgos Verigakis | return HttpResponse(data, status=status)
|
138 | 432fc8c3 | Giorgos Verigakis | |
139 | c36934a7 | Giorgos Verigakis | def render_fault(request, fault): |
140 | d8e50a39 | Giorgos Verigakis | if settings.DEBUG or request.META.get('SERVER_NAME') == 'testserver': |
141 | 7e2f9d4b | Giorgos Verigakis | fault.details = format_exc(fault) |
142 | d8e50a39 | Giorgos Verigakis | |
143 | d8e50a39 | Giorgos Verigakis | if request.serialization == 'xml': |
144 | d8e50a39 | Giorgos Verigakis | data = render_to_string('fault.xml', {'fault': fault}) |
145 | 7e2f9d4b | Giorgos Verigakis | else:
|
146 | 7e2f9d4b | Giorgos Verigakis | d = {fault.name: {'code': fault.code, 'message': fault.message, 'details': fault.details}} |
147 | 7e2f9d4b | Giorgos Verigakis | data = json.dumps(d) |
148 | d8e50a39 | Giorgos Verigakis | |
149 | f0656db1 | Giorgos Verigakis | resp = HttpResponse(data, status=fault.code) |
150 | f0656db1 | Giorgos Verigakis | |
151 | f0656db1 | Giorgos Verigakis | if request.serialization == 'xml': |
152 | f0656db1 | Giorgos Verigakis | resp['Content-Type'] = 'application/xml' |
153 | f0656db1 | Giorgos Verigakis | elif request.serialization == 'atom': |
154 | f0656db1 | Giorgos Verigakis | resp['Content-Type'] = 'application/atom+xml' |
155 | f0656db1 | Giorgos Verigakis | else:
|
156 | f0656db1 | Giorgos Verigakis | resp['Content-Type'] = 'application/json' |
157 | f0656db1 | Giorgos Verigakis | |
158 | f0656db1 | Giorgos Verigakis | return resp
|
159 | d8e50a39 | Giorgos Verigakis | |
160 | 432fc8c3 | Giorgos Verigakis | |
161 | d8e50a39 | Giorgos Verigakis | def request_serialization(request, atom_allowed=False): |
162 | d8e50a39 | Giorgos Verigakis | """Return the serialization format requested.
|
163 | d8e50a39 | Giorgos Verigakis |
|
164 | d8e50a39 | Giorgos Verigakis | Valid formats are 'json', 'xml' and 'atom' if `atom_allowed` is True.
|
165 | d8e50a39 | Giorgos Verigakis | """
|
166 | d8e50a39 | Giorgos Verigakis | |
167 | d8e50a39 | Giorgos Verigakis | path = request.path |
168 | d8e50a39 | Giorgos Verigakis | |
169 | d8e50a39 | Giorgos Verigakis | if path.endswith('.json'): |
170 | d8e50a39 | Giorgos Verigakis | return 'json' |
171 | d8e50a39 | Giorgos Verigakis | elif path.endswith('.xml'): |
172 | d8e50a39 | Giorgos Verigakis | return 'xml' |
173 | d8e50a39 | Giorgos Verigakis | elif atom_allowed and path.endswith('.atom'): |
174 | d8e50a39 | Giorgos Verigakis | return 'atom' |
175 | d8e50a39 | Giorgos Verigakis | |
176 | d8e50a39 | Giorgos Verigakis | for item in request.META.get('HTTP_ACCEPT', '').split(','): |
177 | d8e50a39 | Giorgos Verigakis | accept, sep, rest = item.strip().partition(';')
|
178 | d8e50a39 | Giorgos Verigakis | if accept == 'application/json': |
179 | d8e50a39 | Giorgos Verigakis | return 'json' |
180 | d8e50a39 | Giorgos Verigakis | elif accept == 'application/xml': |
181 | d8e50a39 | Giorgos Verigakis | return 'xml' |
182 | d8e50a39 | Giorgos Verigakis | elif atom_allowed and accept == 'application/atom+xml': |
183 | d8e50a39 | Giorgos Verigakis | return 'atom' |
184 | d8e50a39 | Giorgos Verigakis | |
185 | d8e50a39 | Giorgos Verigakis | return 'json' |
186 | 7e2f9d4b | Giorgos Verigakis | |
187 | d8e50a39 | Giorgos Verigakis | def api_method(http_method=None, atom_allowed=False): |
188 | d8e50a39 | Giorgos Verigakis | """Decorator function for views that implement an API method."""
|
189 | d8e50a39 | Giorgos Verigakis | |
190 | c36934a7 | Giorgos Verigakis | def decorator(func): |
191 | c36934a7 | Giorgos Verigakis | @wraps(func)
|
192 | c36934a7 | Giorgos Verigakis | def wrapper(request, *args, **kwargs): |
193 | c36934a7 | Giorgos Verigakis | try:
|
194 | d8e50a39 | Giorgos Verigakis | request.serialization = request_serialization(request, atom_allowed) |
195 | d8e50a39 | Giorgos Verigakis | if http_method and request.method != http_method: |
196 | d8e50a39 | Giorgos Verigakis | raise BadRequest('Method not allowed.') |
197 | c36934a7 | Giorgos Verigakis | |
198 | c36934a7 | Giorgos Verigakis | resp = func(request, *args, **kwargs) |
199 | d8e50a39 | Giorgos Verigakis | if request.serialization == 'xml': |
200 | d8e50a39 | Giorgos Verigakis | resp['Content-Type'] = 'application/xml' |
201 | d8e50a39 | Giorgos Verigakis | elif request.serialization == 'atom': |
202 | d8e50a39 | Giorgos Verigakis | resp['Content-Type'] = 'application/atom+xml' |
203 | d8e50a39 | Giorgos Verigakis | else:
|
204 | d8e50a39 | Giorgos Verigakis | resp['Content-Type'] = 'application/json' |
205 | d8e50a39 | Giorgos Verigakis | |
206 | c36934a7 | Giorgos Verigakis | return resp
|
207 | d8e50a39 | Giorgos Verigakis | |
208 | c36934a7 | Giorgos Verigakis | except Fault, fault:
|
209 | c36934a7 | Giorgos Verigakis | return render_fault(request, fault)
|
210 | a62a4d20 | Giorgos Verigakis | except BaseException, e: |
211 | d8e50a39 | Giorgos Verigakis | logging.exception('Unexpected error: %s' % e)
|
212 | d8e50a39 | Giorgos Verigakis | fault = ServiceUnavailable('Unexpected error')
|
213 | c36934a7 | Giorgos Verigakis | return render_fault(request, fault)
|
214 | c36934a7 | Giorgos Verigakis | return wrapper
|
215 | c36934a7 | Giorgos Verigakis | return decorator |