Statistics
| Branch: | Tag: | Revision:

root / api / util.py @ b19653d1

History | View | Annotate | Download (7.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 time import time
10
from traceback import format_exc
11
from wsgiref.handlers import format_date_time
12

    
13
import datetime
14
import dateutil.parser
15
import logging
16

    
17
from django.conf import settings
18
from django.http import HttpResponse
19
from django.template.loader import render_to_string
20
from django.utils import simplejson as json
21

    
22
from synnefo.api.faults import (Fault, BadRequest, BuildInProgress, ItemNotFound,
23
                                ServiceUnavailable, Unauthorized)
24
from synnefo.db.models import (SynnefoUser, Flavor, Image, ImageMetadata,
25
                                VirtualMachine, VirtualMachineMetadata, Network)
26

    
27

    
28
class UTC(tzinfo):
29
    def utcoffset(self, dt):
30
        return timedelta(0)
31
    
32
    def tzname(self, dt):
33
        return 'UTC'
34
    
35
    def dst(self, dt):
36
        return timedelta(0)
37

    
38

    
39
def isoformat(d):
40
    """Return an ISO8601 date string that includes a timezone."""
41
    
42
    return d.replace(tzinfo=UTC()).isoformat()
43

    
44
def isoparse(s):
45
    """Parse an ISO8601 date string into a datetime object."""
46
    
47
    if not s:
48
        return None
49
    
50
    try:
51
        since = dateutil.parser.parse(s)
52
        utc_since = since.astimezone(UTC()).replace(tzinfo=None)
53
    except ValueError:
54
        raise BadRequest('Invalid changes-since parameter.')
55
    
56
    now = datetime.datetime.now()
57
    if utc_since > now:
58
        raise BadRequest('changes-since value set in the future.')
59
    
60
    if now - utc_since > timedelta(seconds=settings.POLL_LIMIT):
61
        raise BadRequest('Too old changes-since value.')
62
    
63
    return utc_since
64
    
65
def random_password(length=8):
66
    pool = ascii_letters + digits
67
    return ''.join(choice(pool) for i in range(length))
68

    
69

    
70
def get_vm(server_id, owner):
71
    """Return a VirtualMachine instance or raise ItemNotFound."""
72
    
73
    try:
74
        server_id = int(server_id)
75
        return VirtualMachine.objects.get(id=server_id, owner=owner)
76
    except ValueError:
77
        raise BadRequest('Invalid server ID.')
78
    except VirtualMachine.DoesNotExist:
79
        raise ItemNotFound('Server not found.')
80

    
81
def get_vm_meta(vm, key):
82
    """Return a VirtualMachineMetadata instance or raise ItemNotFound."""
83
    
84
    try:
85
        return VirtualMachineMetadata.objects.get(meta_key=key, vm=vm)
86
    except VirtualMachineMetadata.DoesNotExist:
87
        raise ItemNotFound('Metadata key not found.')
88

    
89
def get_image(image_id, owner):
90
    """Return an Image instance or raise ItemNotFound."""
91
    
92
    try:
93
        image_id = int(image_id)
94
        return Image.objects.get(id=image_id, owner=owner)
95
    except ValueError:
96
        raise BadRequest('Invalid image ID.')
97
    except Image.DoesNotExist:
98
        raise ItemNotFound('Image not found.')
99

    
100
def get_image_meta(image, key):
101
    """Return a ImageMetadata instance or raise ItemNotFound."""
102

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

    
108
def get_flavor(flavor_id):
109
    """Return a Flavor instance or raise ItemNotFound."""
110
    
111
    try:
112
        flavor_id = int(flavor_id)
113
        return Flavor.objects.get(id=flavor_id)
114
    except ValueError:
115
        raise BadRequest('Invalid flavor ID.')
116
    except Flavor.DoesNotExist:
117
        raise ItemNotFound('Flavor not found.')
118

    
119
def get_network(network_id, owner):
120
    """Return a Network instance or raise ItemNotFound."""
121
    
122
    try:
123
        return Network.objects.get(id=network_id, owner=owner)
124
    except ValueError:
125
        raise BadRequest('Invalid network ID.')
126
    except Network.DoesNotExist:
127
        raise ItemNotFound('Network not found.')
128

    
129

    
130
def get_request_dict(request):
131
    """Returns data sent by the client as a python dict."""
132
    
133
    data = request.raw_post_data
134
    if request.META.get('CONTENT_TYPE').startswith('application/json'):
135
        try:
136
            return json.loads(data)
137
        except ValueError:
138
            raise BadRequest('Invalid JSON data.')
139
    else:
140
        raise BadRequest('Unsupported Content-Type.')
141

    
142
def update_response_headers(request, response):
143
    if request.serialization == 'xml':
144
        response['Content-Type'] = 'application/xml'
145
    elif request.serialization == 'atom':
146
        response['Content-Type'] = 'application/atom+xml'
147
    else:
148
        response['Content-Type'] = 'application/json'
149
    
150
    if settings.TEST:
151
        response['Date'] = format_date_time(time())
152

    
153
def render_metadata(request, metadata, use_values=False, status=200):
154
    if request.serialization == 'xml':
155
        data = render_to_string('metadata.xml', {'metadata': metadata})
156
    else:
157
        d = {'metadata': {'values': metadata}} if use_values else {'metadata': metadata}
158
        data = json.dumps(d)
159
    return HttpResponse(data, status=status)
160

    
161
def render_meta(request, meta, status=200):
162
    if request.serialization == 'xml':
163
        data = render_to_string('meta.xml', {'meta': meta})
164
    else:
165
        data = json.dumps({'meta': {meta.meta_key: meta.meta_value}})
166
    return HttpResponse(data, status=status)
167

    
168
def render_fault(request, fault):
169
    if settings.DEBUG or settings.TEST:
170
        fault.details = format_exc(fault)
171
    
172
    if request.serialization == 'xml':
173
        data = render_to_string('fault.xml', {'fault': fault})
174
    else:
175
        d = {fault.name: {'code': fault.code, 'message': fault.message, 'details': fault.details}}
176
        data = json.dumps(d)
177
    
178
    resp = HttpResponse(data, status=fault.code)
179
    update_response_headers(request, resp)
180
    return resp
181

    
182

    
183
def request_serialization(request, atom_allowed=False):
184
    """Return the serialization format requested.
185
       
186
    Valid formats are 'json', 'xml' and 'atom' if `atom_allowed` is True.
187
    """
188
    
189
    path = request.path
190
    
191
    if path.endswith('.json'):
192
        return 'json'
193
    elif path.endswith('.xml'):
194
        return 'xml'
195
    elif atom_allowed and path.endswith('.atom'):
196
        return 'atom'
197
    
198
    for item in request.META.get('HTTP_ACCEPT', '').split(','):
199
        accept, sep, rest = item.strip().partition(';')
200
        if accept == 'application/json':
201
            return 'json'
202
        elif accept == 'application/xml':
203
            return 'xml'
204
        elif atom_allowed and accept == 'application/atom+xml':
205
            return 'atom'
206
    
207
    return 'json'
208

    
209
def api_method(http_method=None, atom_allowed=False):
210
    """Decorator function for views that implement an API method."""
211
    
212
    def decorator(func):
213
        @wraps(func)
214
        def wrapper(request, *args, **kwargs):
215
            try:
216
                if not request.user:
217
                    raise Unauthorized('No user found.')
218
                request.serialization = request_serialization(request, atom_allowed)
219
                if http_method and request.method != http_method:
220
                    raise BadRequest('Method not allowed.')
221
                
222
                resp = func(request, *args, **kwargs)
223
                update_response_headers(request, resp)
224
                return resp
225
            except VirtualMachine.DeletedError:
226
                fault = BadRequest('Server has been deleted.')
227
                return render_fault(request, fault)
228
            except VirtualMachine.BuildingError:
229
                fault = BuildInProgress('Server is being built.')
230
                return render_fault(request, fault)
231
            except Fault, fault:
232
                return render_fault(request, fault)
233
            except BaseException, e:
234
                logging.exception('Unexpected error: %s', e)
235
                fault = ServiceUnavailable('Unexpected error.')
236
                return render_fault(request, fault)
237
        return wrapper
238
    return decorator