Statistics
| Branch: | Tag: | Revision:

root / api / util.py @ 838c404d

History | View | Annotate | Download (6.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 Fault, BadRequest, ItemNotFound, ServiceUnavailable
17
from synnefo.db.models import SynnefoUser, Image, ImageMetadata, VirtualMachine, VirtualMachineMetadata
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
def get_image_meta(image_id, key):
102
    """Return a ImageMetadata instance or raise ItemNotFound."""
103

    
104
    try:
105
        image_id = int(image_id)
106
        return ImageMetadata.objects.get(meta_key=key, image=image_id)
107
    except ImageMetadata.DoesNotExist:
108
        raise ItemNotFound('Metadata key not found.')
109

    
110

    
111
def get_request_dict(request):
112
    """Returns data sent by the client as a python dict."""
113
    
114
    data = request.raw_post_data
115
    if request.META.get('CONTENT_TYPE').startswith('application/json'):
116
        try:
117
            return json.loads(data)
118
        except ValueError:
119
            raise BadRequest('Invalid JSON data.')
120
    else:
121
        raise BadRequest('Unsupported Content-Type.')
122

    
123

    
124
def render_metadata(request, metadata, use_values=False, status=200):
125
    if request.serialization == 'xml':
126
        data = render_to_string('metadata.xml', {'metadata': metadata})
127
    else:
128
        d = {'metadata': {'values': metadata}} if use_values else {'metadata': metadata}
129
        data = json.dumps(d)
130
    return HttpResponse(data, status=status)
131

    
132
def render_meta(request, meta, status=200):
133
    if request.serialization == 'xml':
134
        data = render_to_string('meta.xml', {'meta': meta})
135
    else:
136
        data = json.dumps({'meta': {meta.meta_key: meta.meta_value}})
137
    return HttpResponse(data, status=status)
138

    
139
def render_fault(request, fault):
140
    if settings.DEBUG or request.META.get('SERVER_NAME') == 'testserver':
141
        fault.details = format_exc(fault)
142
    
143
    if request.serialization == 'xml':
144
        data = render_to_string('fault.xml', {'fault': fault})
145
    else:
146
        d = {fault.name: {'code': fault.code, 'message': fault.message, 'details': fault.details}}
147
        data = json.dumps(d)
148
    
149
    resp = HttpResponse(data, status=fault.code)
150
    
151
    if request.serialization == 'xml':
152
        resp['Content-Type'] = 'application/xml'
153
    elif request.serialization == 'atom':
154
        resp['Content-Type'] = 'application/atom+xml'
155
    else:
156
        resp['Content-Type'] = 'application/json'
157
    
158
    return resp
159

    
160

    
161
def request_serialization(request, atom_allowed=False):
162
    """Return the serialization format requested.
163
       
164
       Valid formats are 'json', 'xml' and 'atom' if `atom_allowed` is True.
165
    """
166
    
167
    path = request.path
168
    
169
    if path.endswith('.json'):
170
        return 'json'
171
    elif path.endswith('.xml'):
172
        return 'xml'
173
    elif atom_allowed and path.endswith('.atom'):
174
        return 'atom'
175
    
176
    for item in request.META.get('HTTP_ACCEPT', '').split(','):
177
        accept, sep, rest = item.strip().partition(';')
178
        if accept == 'application/json':
179
            return 'json'
180
        elif accept == 'application/xml':
181
            return 'xml'
182
        elif atom_allowed and accept == 'application/atom+xml':
183
            return 'atom'
184
    
185
    return 'json'
186

    
187
def api_method(http_method=None, atom_allowed=False):
188
    """Decorator function for views that implement an API method."""
189
    
190
    def decorator(func):
191
        @wraps(func)
192
        def wrapper(request, *args, **kwargs):
193
            try:
194
                request.serialization = request_serialization(request, atom_allowed)
195
                if http_method and request.method != http_method:
196
                    raise BadRequest('Method not allowed.')
197
                
198
                resp = func(request, *args, **kwargs)
199
                if request.serialization == 'xml':
200
                    resp['Content-Type'] = 'application/xml'
201
                elif request.serialization == 'atom':
202
                    resp['Content-Type'] = 'application/atom+xml'
203
                else:
204
                    resp['Content-Type'] = 'application/json'
205
                
206
                return resp
207
            
208
            except Fault, fault:
209
                return render_fault(request, fault)
210
            except BaseException, e:
211
                logging.exception('Unexpected error: %s' % e)
212
                fault = ServiceUnavailable('Unexpected error')
213
                return render_fault(request, fault)
214
        return wrapper
215
    return decorator