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