Revision 9dcb5b8a snf-cyclades-app/synnefo/api/util.py
b/snf-cyclades-app/synnefo/api/util.py | ||
---|---|---|
70 | 70 |
log = getLogger('synnefo.api') |
71 | 71 |
|
72 | 72 |
|
73 |
class UTC(tzinfo): |
|
74 |
def utcoffset(self, dt): |
|
75 |
return timedelta(0) |
|
76 |
|
|
77 |
def tzname(self, dt): |
|
78 |
return 'UTC' |
|
79 |
|
|
80 |
def dst(self, dt): |
|
81 |
return timedelta(0) |
|
82 |
|
|
83 |
|
|
84 |
def isoformat(d): |
|
85 |
"""Return an ISO8601 date string that includes a timezone.""" |
|
86 |
|
|
87 |
return d.replace(tzinfo=UTC()).isoformat() |
|
88 |
|
|
89 |
|
|
90 |
def isoparse(s): |
|
91 |
"""Parse an ISO8601 date string into a datetime object.""" |
|
92 |
|
|
93 |
if not s: |
|
94 |
return None |
|
95 |
|
|
96 |
try: |
|
97 |
since = dateutil.parser.parse(s) |
|
98 |
utc_since = since.astimezone(UTC()).replace(tzinfo=None) |
|
99 |
except ValueError: |
|
100 |
raise faults.BadRequest('Invalid changes-since parameter.') |
|
101 |
|
|
102 |
now = datetime.datetime.now() |
|
103 |
if utc_since > now: |
|
104 |
raise faults.BadRequest('changes-since value set in the future.') |
|
105 |
|
|
106 |
if now - utc_since > timedelta(seconds=settings.POLL_LIMIT): |
|
107 |
raise faults.BadRequest('Too old changes-since value.') |
|
108 |
|
|
109 |
return utc_since |
|
110 |
|
|
111 |
|
|
112 | 73 |
def random_password(): |
113 | 74 |
"""Generates a random password |
114 | 75 |
|
... | ... | |
222 | 183 |
raise faults.ItemNotFound('Flavor not found.') |
223 | 184 |
|
224 | 185 |
|
186 |
def get_flavor_provider(flavor): |
|
187 |
"""Extract provider from disk template. |
|
188 |
|
|
189 |
Provider for `ext` disk_template is encoded in the disk template |
|
190 |
name, which is formed `ext_<provider_name>`. Provider is None |
|
191 |
for all other disk templates. |
|
192 |
|
|
193 |
""" |
|
194 |
disk_template = flavor.disk_template |
|
195 |
provider = None |
|
196 |
if disk_template.startswith("ext"): |
|
197 |
disk_template, provider = disk_template.split("_", 1) |
|
198 |
return disk_template, provider |
|
199 |
|
|
200 |
|
|
225 | 201 |
def get_network(network_id, user_id, for_update=False): |
226 | 202 |
"""Return a Network instance or raise ItemNotFound.""" |
227 | 203 |
|
... | ... | |
358 | 334 |
return nic |
359 | 335 |
|
360 | 336 |
|
361 |
def get_request_dict(request): |
|
362 |
"""Returns data sent by the client as a python dict.""" |
|
363 |
|
|
364 |
data = request.raw_post_data |
|
365 |
if request.META.get('CONTENT_TYPE').startswith('application/json'): |
|
366 |
try: |
|
367 |
return json.loads(data) |
|
368 |
except ValueError: |
|
369 |
raise faults.BadRequest('Invalid JSON data.') |
|
370 |
else: |
|
371 |
raise faults.BadRequest('Unsupported Content-Type.') |
|
372 |
|
|
373 |
|
|
374 |
def update_response_headers(request, response): |
|
375 |
if request.serialization == 'xml': |
|
376 |
response['Content-Type'] = 'application/xml' |
|
377 |
elif request.serialization == 'atom': |
|
378 |
response['Content-Type'] = 'application/atom+xml' |
|
379 |
else: |
|
380 |
response['Content-Type'] = 'application/json' |
|
381 |
|
|
382 |
if settings.TEST: |
|
383 |
response['Date'] = format_date_time(time()) |
|
384 |
|
|
385 |
add_never_cache_headers(response) |
|
386 |
|
|
387 |
|
|
388 | 337 |
def render_metadata(request, metadata, use_values=False, status=200): |
389 | 338 |
if request.serialization == 'xml': |
390 | 339 |
data = render_to_string('metadata.xml', {'metadata': metadata}) |
... | ... | |
405 | 354 |
return HttpResponse(data, status=status) |
406 | 355 |
|
407 | 356 |
|
408 |
def render_fault(request, fault): |
|
409 |
if settings.DEBUG or settings.TEST: |
|
410 |
fault.details = format_exc(fault) |
|
411 |
|
|
412 |
if request.serialization == 'xml': |
|
413 |
data = render_to_string('fault.xml', {'fault': fault}) |
|
414 |
else: |
|
415 |
d = {fault.name: {'code': fault.code, |
|
416 |
'message': fault.message, |
|
417 |
'details': fault.details}} |
|
418 |
data = json.dumps(d) |
|
419 |
|
|
420 |
resp = HttpResponse(data, status=fault.code) |
|
421 |
update_response_headers(request, resp) |
|
422 |
return resp |
|
423 |
|
|
424 |
|
|
425 |
def request_serialization(request, atom_allowed=False): |
|
426 |
"""Return the serialization format requested. |
|
427 |
|
|
428 |
Valid formats are 'json', 'xml' and 'atom' if `atom_allowed` is True. |
|
429 |
""" |
|
430 |
|
|
431 |
path = request.path |
|
432 |
|
|
433 |
if path.endswith('.json'): |
|
434 |
return 'json' |
|
435 |
elif path.endswith('.xml'): |
|
436 |
return 'xml' |
|
437 |
elif atom_allowed and path.endswith('.atom'): |
|
438 |
return 'atom' |
|
439 |
|
|
440 |
for item in request.META.get('HTTP_ACCEPT', '').split(','): |
|
441 |
accept, sep, rest = item.strip().partition(';') |
|
442 |
if accept == 'application/json': |
|
443 |
return 'json' |
|
444 |
elif accept == 'application/xml': |
|
445 |
return 'xml' |
|
446 |
elif atom_allowed and accept == 'application/atom+xml': |
|
447 |
return 'atom' |
|
448 |
|
|
449 |
return 'json' |
|
450 |
|
|
451 |
|
|
452 |
def api_method(http_method=None, atom_allowed=False): |
|
453 |
"""Decorator function for views that implement an API method.""" |
|
454 |
|
|
455 |
def decorator(func): |
|
456 |
@wraps(func) |
|
457 |
def wrapper(request, *args, **kwargs): |
|
458 |
try: |
|
459 |
request.serialization = request_serialization(request, |
|
460 |
atom_allowed) |
|
461 |
get_user(request, settings.ASTAKOS_URL) |
|
462 |
if not request.user_uniq: |
|
463 |
raise faults.Unauthorized('No user found.') |
|
464 |
if http_method and request.method != http_method: |
|
465 |
raise faults.BadRequest('Method not allowed.') |
|
466 |
|
|
467 |
resp = func(request, *args, **kwargs) |
|
468 |
update_response_headers(request, resp) |
|
469 |
return resp |
|
470 |
except faults.Fault, fault: |
|
471 |
if fault.code >= 500: |
|
472 |
log.exception('API fault') |
|
473 |
return render_fault(request, fault) |
|
474 |
except BaseException: |
|
475 |
log.exception('Unexpected error') |
|
476 |
fault = faults.ServiceUnavailable('Unexpected error.') |
|
477 |
return render_fault(request, fault) |
|
478 |
return wrapper |
|
479 |
return decorator |
|
480 |
|
|
481 |
|
|
482 | 357 |
def construct_nic_id(nic): |
483 | 358 |
return "-".join(["nic", unicode(nic.machine.id), unicode(nic.index)]) |
484 | 359 |
|
... | ... | |
505 | 380 |
raise faults.BadRequest("Malformed personality in request") |
506 | 381 |
|
507 | 382 |
|
508 |
def get_flavor_provider(flavor): |
|
509 |
"""Extract provider from disk template. |
|
510 |
|
|
511 |
Provider for `ext` disk_template is encoded in the disk template |
|
512 |
name, which is formed `ext_<provider_name>`. Provider is None |
|
513 |
for all other disk templates. |
|
514 |
|
|
515 |
""" |
|
516 |
disk_template = flavor.disk_template |
|
517 |
provider = None |
|
518 |
if disk_template.startswith("ext"): |
|
519 |
disk_template, provider = disk_template.split("_", 1) |
|
520 |
return disk_template, provider |
|
521 |
|
|
522 |
|
|
523 | 383 |
def values_from_flavor(flavor): |
524 | 384 |
"""Get Ganeti connectivity info from flavor type. |
525 | 385 |
|
Also available in: Unified diff