Revision 9dcb5b8a

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