Statistics
| Branch: | Tag: | Revision:

root / api / util.py @ 7e45ddef

History | View | Annotate | Download (5.7 kB)

1
#
2
# Copyright (c) 2010 Greek Research and Technology Network
3
#
4

    
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
10

    
11
from django.conf import settings
12
from django.http import HttpResponse
13
from django.template.loader import render_to_string
14
from django.utils import simplejson as json
15

    
16
from synnefo.api.faults import *
17
from synnefo.db.models import *
18

    
19
import datetime
20
import dateutil.parser
21
import logging
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)
33

    
34

    
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

    
64

    
65
def get_user():
66
    # XXX Placeholder function, everything belongs to a single SynnefoUser for now
67
    try:
68
        return SynnefoUser.objects.all()[0]
69
    except IndexError:
70
        raise Unauthorized
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

    
102
def get_request_dict(request):
103
    """Returns data sent by the client as a python dict."""
104
    
105
    data = request.raw_post_data
106
    if request.META.get('CONTENT_TYPE').startswith('application/json'):
107
        try:
108
            return json.loads(data)
109
        except ValueError:
110
            raise BadRequest('Invalid JSON data.')
111
    else:
112
        raise BadRequest('Unsupported Content-Type.')
113

    
114
def render_fault(request, fault):
115
    if settings.DEBUG or request.META.get('SERVER_NAME') == 'testserver':
116
        fault.details = format_exc(fault)
117
    
118
    if request.serialization == 'xml':
119
        data = render_to_string('fault.xml', {'fault': fault})
120
    else:
121
        d = {fault.name: {'code': fault.code, 'message': fault.message, 'details': fault.details}}
122
        data = json.dumps(d)
123
    
124
    resp = HttpResponse(data, status=fault.code)
125
    
126
    if request.serialization == 'xml':
127
        resp['Content-Type'] = 'application/xml'
128
    elif request.serialization == 'atom':
129
        resp['Content-Type'] = 'application/atom+xml'
130
    else:
131
        resp['Content-Type'] = 'application/json'
132
    
133
    return resp
134

    
135
def request_serialization(request, atom_allowed=False):
136
    """Return the serialization format requested.
137
       
138
       Valid formats are 'json', 'xml' and 'atom' if `atom_allowed` is True.
139
    """
140
    
141
    path = request.path
142
    
143
    if path.endswith('.json'):
144
        return 'json'
145
    elif path.endswith('.xml'):
146
        return 'xml'
147
    elif atom_allowed and path.endswith('.atom'):
148
        return 'atom'
149
    
150
    for item in request.META.get('HTTP_ACCEPT', '').split(','):
151
        accept, sep, rest = item.strip().partition(';')
152
        if accept == 'application/json':
153
            return 'json'
154
        elif accept == 'application/xml':
155
            return 'xml'
156
        elif atom_allowed and accept == 'application/atom+xml':
157
            return 'atom'
158
    
159
    return 'json'
160

    
161
def api_method(http_method=None, atom_allowed=False):
162
    """Decorator function for views that implement an API method."""
163
    
164
    def decorator(func):
165
        @wraps(func)
166
        def wrapper(request, *args, **kwargs):
167
            try:
168
                request.serialization = request_serialization(request, atom_allowed)
169
                if http_method and request.method != http_method:
170
                    raise BadRequest('Method not allowed.')
171
                
172
                resp = func(request, *args, **kwargs)
173
                if request.serialization == 'xml':
174
                    resp['Content-Type'] = 'application/xml'
175
                elif request.serialization == 'atom':
176
                    resp['Content-Type'] = 'application/atom+xml'
177
                else:
178
                    resp['Content-Type'] = 'application/json'
179
                
180
                return resp
181
            
182
            except Fault, fault:
183
                return render_fault(request, fault)
184
            except BaseException, e:
185
                logging.exception('Unexpected error: %s' % e)
186
                fault = ServiceUnavailable('Unexpected error')
187
                return render_fault(request, fault)
188
        return wrapper
189
    return decorator