Statistics
| Branch: | Tag: | Revision:

root / api / servers.py @ 40777cc8

History | View | Annotate | Download (14 kB)

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

    
5
import logging
6

    
7
from django.conf.urls.defaults import patterns
8
from django.http import HttpResponse
9
from django.template.loader import render_to_string
10
from django.utils import simplejson as json
11

    
12
from synnefo.api.actions import server_actions
13
from synnefo.api.common import method_not_allowed
14
from synnefo.api.faults import BadRequest, ItemNotFound, ServiceUnavailable
15
from synnefo.api.util import (isoformat, isoparse, random_password,
16
                                get_vm, get_vm_meta, get_image, get_flavor,
17
                                get_request_dict, render_metadata, render_meta, api_method)
18
from synnefo.db.models import VirtualMachine, VirtualMachineMetadata
19
from synnefo.logic.backend import create_instance, delete_instance
20
from synnefo.logic.utils import get_rsapi_state
21
from synnefo.util.rapi import GanetiApiError
22

    
23

    
24
urlpatterns = patterns('synnefo.api.servers',
25
    (r'^(?:/|.json|.xml)?$', 'demux'),
26
    (r'^/detail(?:.json|.xml)?$', 'list_servers', {'detail': True}),
27
    (r'^/(\d+)(?:.json|.xml)?$', 'server_demux'),
28
    (r'^/(\d+)/action(?:.json|.xml)?$', 'server_action'),
29
    (r'^/(\d+)/ips(?:.json|.xml)?$', 'list_addresses'),
30
    (r'^/(\d+)/ips/(.+?)(?:.json|.xml)?$', 'list_addresses_by_network'),
31
    (r'^/(\d+)/meta(?:.json|.xml)?$', 'metadata_demux'),
32
    (r'^/(\d+)/meta/(.+?)(?:.json|.xml)?$', 'metadata_item_demux'),
33
)
34

    
35

    
36
def demux(request):
37
    if request.method == 'GET':
38
        return list_servers(request)
39
    elif request.method == 'POST':
40
        return create_server(request)
41
    else:
42
        return method_not_allowed(request)
43

    
44
def server_demux(request, server_id):
45
    if request.method == 'GET':
46
        return get_server_details(request, server_id)
47
    elif request.method == 'PUT':
48
        return update_server_name(request, server_id)
49
    elif request.method == 'DELETE':
50
        return delete_server(request, server_id)
51
    else:
52
        return method_not_allowed(request)
53

    
54
def metadata_demux(request, server_id):
55
    if request.method == 'GET':
56
        return list_metadata(request, server_id)
57
    elif request.method == 'POST':
58
        return update_metadata(request, server_id)
59
    else:
60
        return method_not_allowed(request)
61

    
62
def metadata_item_demux(request, server_id, key):
63
    if request.method == 'GET':
64
        return get_metadata_item(request, server_id, key)
65
    elif request.method == 'PUT':
66
        return create_metadata_item(request, server_id, key)
67
    elif request.method == 'DELETE':
68
        return delete_metadata_item(request, server_id, key)
69
    else:
70
        return method_not_allowed(request)
71

    
72

    
73
def address_to_dict(ipfour, ipsix):
74
    return {'id': 'public',
75
            'values': [{'version': 4, 'addr': ipfour}, {'version': 6, 'addr': ipsix}]}
76

    
77
def metadata_to_dict(vm):
78
    vm_meta = vm.virtualmachinemetadata_set.all()
79
    return dict((meta.meta_key, meta.meta_value) for meta in vm_meta)
80

    
81
def vm_to_dict(vm, detail=False):
82
    d = dict(id=vm.id, name=vm.name)
83
    if detail:
84
        d['status'] = get_rsapi_state(vm)
85
        d['progress'] = 100 if get_rsapi_state(vm) == 'ACTIVE' else 0
86
        d['hostId'] = vm.hostid
87
        d['updated'] = isoformat(vm.updated)
88
        d['created'] = isoformat(vm.created)
89
        d['flavorRef'] = vm.flavor.id
90
        d['imageRef'] = vm.sourceimage.id
91
        
92
        metadata = metadata_to_dict(vm)
93
        if metadata:
94
            d['metadata'] = {'values': metadata}
95
        
96
        addresses = [address_to_dict(vm.ipfour, vm.ipsix)]
97
        d['addresses'] = {'values': addresses}
98
    return d
99

    
100

    
101
def render_server(request, server, status=200):
102
    if request.serialization == 'xml':
103
        data = render_to_string('server.xml', {'server': server, 'is_root': True})
104
    else:
105
        data = json.dumps({'server': server})
106
    return HttpResponse(data, status=status)
107
    
108

    
109
@api_method('GET')
110
def list_servers(request, detail=False):
111
    # Normal Response Codes: 200, 203
112
    # Error Response Codes: computeFault (400, 500),
113
    #                       serviceUnavailable (503),
114
    #                       unauthorized (401),
115
    #                       badRequest (400),
116
    #                       overLimit (413)
117
    
118
    since = isoparse(request.GET.get('changes-since'))
119
    
120
    if since:
121
        user_vms = VirtualMachine.objects.filter(owner=request.user, updated__gte=since)
122
        if not user_vms:
123
            return HttpResponse(status=304)
124
    else:
125
        user_vms = VirtualMachine.objects.filter(owner=request.user, deleted=False)
126
    servers = [vm_to_dict(server, detail) for server in user_vms]
127
    
128
    if request.serialization == 'xml':
129
        data = render_to_string('list_servers.xml', {'servers': servers, 'detail': detail})
130
    else:
131
        data = json.dumps({'servers': {'values': servers}})
132
    
133
    return HttpResponse(data, status=200)
134

    
135
@api_method('POST')
136
def create_server(request):
137
    # Normal Response Code: 202
138
    # Error Response Codes: computeFault (400, 500),
139
    #                       serviceUnavailable (503),
140
    #                       unauthorized (401),
141
    #                       badMediaType(415),
142
    #                       itemNotFound (404),
143
    #                       badRequest (400),
144
    #                       serverCapacityUnavailable (503),
145
    #                       overLimit (413)
146
    
147
    req = get_request_dict(request)
148
    
149
    try:
150
        server = req['server']
151
        name = server['name']
152
        metadata = server.get('metadata', {})
153
        assert isinstance(metadata, dict)
154
        image_id = server['imageRef']
155
        flavor_id = server['flavorRef']
156
    except (KeyError, AssertionError):
157
        raise BadRequest('Malformed request.')
158
    
159
    image = get_image(image_id, request.user)
160
    flavor = get_flavor(flavor_id)
161
    
162
    # We must save the VM instance now, so that it gets a valid vm.backend_id.
163
    vm = VirtualMachine.objects.create(
164
        name=name,
165
        owner=request.user,
166
        sourceimage=image,
167
        ipfour='0.0.0.0',
168
        ipsix='::1',
169
        flavor=flavor)
170
    
171
    password = random_password()
172
                
173
    try:
174
        create_instance(vm, flavor, password)
175
    except GanetiApiError:
176
        vm.delete()
177
        raise ServiceUnavailable('Could not create server.')
178
        
179
    for key, val in metadata.items():
180
        VirtualMachineMetadata.objects.create(meta_key=key, meta_value=val, vm=vm)
181
    
182
    logging.info('created vm with %s cpus, %s ram and %s storage',
183
                    flavor.cpu, flavor.ram, flavor.disk)
184
    
185
    server = vm_to_dict(vm, detail=True)
186
    server['status'] = 'BUILD'
187
    server['adminPass'] = password
188
    return render_server(request, server, status=202)
189

    
190
@api_method('GET')
191
def get_server_details(request, server_id):
192
    # Normal Response Codes: 200, 203
193
    # Error Response Codes: computeFault (400, 500),
194
    #                       serviceUnavailable (503),
195
    #                       unauthorized (401),
196
    #                       badRequest (400),
197
    #                       itemNotFound (404),
198
    #                       overLimit (413)
199
    
200
    vm = get_vm(server_id, request.user)
201
    server = vm_to_dict(vm, detail=True)
202
    return render_server(request, server)
203

    
204
@api_method('PUT')
205
def update_server_name(request, server_id):
206
    # Normal Response Code: 204
207
    # Error Response Codes: computeFault (400, 500),
208
    #                       serviceUnavailable (503),
209
    #                       unauthorized (401),
210
    #                       badRequest (400),
211
    #                       badMediaType(415),
212
    #                       itemNotFound (404),
213
    #                       buildInProgress (409),
214
    #                       overLimit (413)
215
    
216
    req = get_request_dict(request)
217
    
218
    try:
219
        name = req['server']['name']
220
    except (TypeError, KeyError):
221
        raise BadRequest('Malformed request.')
222
    
223
    vm = get_vm(server_id, request.user)
224
    vm.name = name
225
    vm.save()
226
    
227
    return HttpResponse(status=204)
228

    
229
@api_method('DELETE')
230
def delete_server(request, server_id):
231
    # Normal Response Codes: 204
232
    # Error Response Codes: computeFault (400, 500),
233
    #                       serviceUnavailable (503),
234
    #                       unauthorized (401),
235
    #                       itemNotFound (404),
236
    #                       unauthorized (401),
237
    #                       buildInProgress (409),
238
    #                       overLimit (413)
239
    
240
    vm = get_vm(server_id, request.user)
241
    delete_instance(vm)
242
    return HttpResponse(status=204)
243

    
244
@api_method('POST')
245
def server_action(request, server_id):
246
    vm = get_vm(server_id, request.user)
247
    req = get_request_dict(request)
248
    if len(req) != 1:
249
        raise BadRequest('Malformed request.')
250
    
251
    key = req.keys()[0]
252
    val = req[key]
253
    
254
    try:
255
        assert isinstance(val, dict)
256
        return server_actions[key](request, vm, req[key])
257
    except KeyError:
258
        raise BadRequest('Unknown action.')
259
    except AssertionError:
260
        raise BadRequest('Invalid argument.')
261

    
262
@api_method('GET')
263
def list_addresses(request, server_id):
264
    # Normal Response Codes: 200, 203
265
    # Error Response Codes: computeFault (400, 500),
266
    #                       serviceUnavailable (503),
267
    #                       unauthorized (401),
268
    #                       badRequest (400),
269
    #                       overLimit (413)
270
    
271
    vm = get_vm(server_id, request.user)
272
    addresses = [address_to_dict(vm.ipfour, vm.ipsix)]
273
    
274
    if request.serialization == 'xml':
275
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
276
    else:
277
        data = json.dumps({'addresses': {'values': addresses}})
278
    
279
    return HttpResponse(data, status=200)
280

    
281
@api_method('GET')
282
def list_addresses_by_network(request, server_id, network_id):
283
    # Normal Response Codes: 200, 203
284
    # Error Response Codes: computeFault (400, 500),
285
    #                       serviceUnavailable (503),
286
    #                       unauthorized (401),
287
    #                       badRequest (400),
288
    #                       itemNotFound (404),
289
    #                       overLimit (413)
290
    
291
    vm = get_vm(server_id, request.user)
292
    if network_id != 'public':
293
        raise ItemNotFound('Unknown network.')
294
    
295
    address = address_to_dict(vm.ipfour, vm.ipsix)
296
    
297
    if request.serialization == 'xml':
298
        data = render_to_string('address.xml', {'address': address})
299
    else:
300
        data = json.dumps({'network': address})
301
    
302
    return HttpResponse(data, status=200)
303

    
304
@api_method('GET')
305
def list_metadata(request, server_id):
306
    # Normal Response Codes: 200, 203
307
    # Error Response Codes: computeFault (400, 500),
308
    #                       serviceUnavailable (503),
309
    #                       unauthorized (401),
310
    #                       badRequest (400),
311
    #                       overLimit (413)
312

    
313
    vm = get_vm(server_id, request.user)
314
    metadata = metadata_to_dict(vm)
315
    return render_metadata(request, metadata, use_values=True, status=200)
316

    
317
@api_method('POST')
318
def update_metadata(request, server_id):
319
    # Normal Response Code: 201
320
    # Error Response Codes: computeFault (400, 500),
321
    #                       serviceUnavailable (503),
322
    #                       unauthorized (401),
323
    #                       badRequest (400),
324
    #                       buildInProgress (409),
325
    #                       badMediaType(415),
326
    #                       overLimit (413)
327

    
328
    vm = get_vm(server_id, request.user)
329
    req = get_request_dict(request)
330
    try:
331
        metadata = req['metadata']
332
        assert isinstance(metadata, dict)
333
    except (KeyError, AssertionError):
334
        raise BadRequest('Malformed request.')
335
    
336
    updated = {}
337
    
338
    for key, val in metadata.items():
339
        try:
340
            meta = VirtualMachineMetadata.objects.get(meta_key=key, vm=vm)
341
            meta.meta_value = val
342
            meta.save()
343
            updated[key] = val
344
        except VirtualMachineMetadata.DoesNotExist:
345
            pass    # Ignore non-existent metadata
346
    
347
    return render_metadata(request, updated, status=201)
348

    
349
@api_method('GET')
350
def get_metadata_item(request, server_id, key):
351
    # Normal Response Codes: 200, 203
352
    # Error Response Codes: computeFault (400, 500),
353
    #                       serviceUnavailable (503),
354
    #                       unauthorized (401),
355
    #                       itemNotFound (404),
356
    #                       badRequest (400),
357
    #                       overLimit (413)
358
    
359
    vm = get_vm(server_id, request.user)
360
    meta = get_vm_meta(vm, key)
361
    return render_meta(request, meta, status=200)
362

    
363
@api_method('PUT')
364
def create_metadata_item(request, server_id, key):
365
    # Normal Response Code: 201
366
    # Error Response Codes: computeFault (400, 500),
367
    #                       serviceUnavailable (503),
368
    #                       unauthorized (401),
369
    #                       itemNotFound (404),
370
    #                       badRequest (400),
371
    #                       buildInProgress (409),
372
    #                       badMediaType(415),
373
    #                       overLimit (413)
374

    
375
    vm = get_vm(server_id, request.user)
376
    req = get_request_dict(request)
377
    try:
378
        metadict = req['meta']
379
        assert isinstance(metadict, dict)
380
        assert len(metadict) == 1
381
        assert key in metadict
382
    except (KeyError, AssertionError):
383
        raise BadRequest('Malformed request.')
384
    
385
    meta, created = VirtualMachineMetadata.objects.get_or_create(meta_key=key, vm=vm)
386
    meta.meta_value = metadict[key]
387
    meta.save()
388
    return render_meta(request, meta, status=201)
389

    
390
@api_method('DELETE')
391
def delete_metadata_item(request, server_id, key):
392
    # Normal Response Code: 204
393
    # Error Response Codes: computeFault (400, 500),
394
    #                       serviceUnavailable (503),
395
    #                       unauthorized (401),
396
    #                       itemNotFound (404),
397
    #                       badRequest (400),
398
    #                       buildInProgress (409),
399
    #                       badMediaType(415),
400
    #                       overLimit (413),
401
    
402
    vm = get_vm(server_id, request.user)
403
    meta = get_vm_meta(vm, key)
404
    meta.delete()
405
    return HttpResponse(status=204)