Statistics
| Branch: | Tag: | Revision:

root / api / servers.py @ 5509b599

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
        addresses.extend({'id': str(network.id), 'values': []} for network in vm.network_set.all())
98
        d['addresses'] = {'values': addresses}
99
    return d
100

    
101

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

    
109

    
110
@api_method('GET')
111
def list_servers(request, detail=False):
112
    # Normal Response Codes: 200, 203
113
    # Error Response Codes: computeFault (400, 500),
114
    #                       serviceUnavailable (503),
115
    #                       unauthorized (401),
116
    #                       badRequest (400),
117
    #                       overLimit (413)
118

    
119
    since = isoparse(request.GET.get('changes-since'))
120

    
121
    if since:
122
        user_vms = VirtualMachine.objects.filter(owner=request.user, updated__gte=since)
123
        if not user_vms:
124
            return HttpResponse(status=304)
125
    else:
126
        user_vms = VirtualMachine.objects.filter(owner=request.user, deleted=False)
127
    servers = [vm_to_dict(server, detail) for server in user_vms]
128

    
129
    if request.serialization == 'xml':
130
        data = render_to_string('list_servers.xml', {'servers': servers, 'detail': detail})
131
    else:
132
        data = json.dumps({'servers': {'values': servers}})
133

    
134
    return HttpResponse(data, status=200)
135

    
136
@api_method('POST')
137
def create_server(request):
138
    # Normal Response Code: 202
139
    # Error Response Codes: computeFault (400, 500),
140
    #                       serviceUnavailable (503),
141
    #                       unauthorized (401),
142
    #                       badMediaType(415),
143
    #                       itemNotFound (404),
144
    #                       badRequest (400),
145
    #                       serverCapacityUnavailable (503),
146
    #                       overLimit (413)
147

    
148
    req = get_request_dict(request)
149

    
150
    try:
151
        server = req['server']
152
        name = server['name']
153
        metadata = server.get('metadata', {})
154
        assert isinstance(metadata, dict)
155
        image_id = server['imageRef']
156
        flavor_id = server['flavorRef']
157
    except (KeyError, AssertionError):
158
        raise BadRequest('Malformed request.')
159

    
160
    image = get_image(image_id, request.user)
161
    flavor = get_flavor(flavor_id)
162

    
163
    # We must save the VM instance now, so that it gets a valid vm.backend_id.
164
    vm = VirtualMachine.objects.create(
165
        name=name,
166
        owner=request.user,
167
        sourceimage=image,
168
        ipfour='0.0.0.0',
169
        ipsix='::1',
170
        flavor=flavor)
171

    
172
    password = random_password()
173

    
174
    try:
175
        create_instance(vm, flavor, password)
176
    except GanetiApiError:
177
        vm.delete()
178
        raise ServiceUnavailable('Could not create server.')
179

    
180
    for key, val in metadata.items():
181
        VirtualMachineMetadata.objects.create(meta_key=key, meta_value=val, vm=vm)
182

    
183
    logging.info('created vm with %s cpus, %s ram and %s storage',
184
                    flavor.cpu, flavor.ram, flavor.disk)
185

    
186
    server = vm_to_dict(vm, detail=True)
187
    server['status'] = 'BUILD'
188
    server['adminPass'] = password
189
    return render_server(request, server, status=202)
190

    
191
@api_method('GET')
192
def get_server_details(request, server_id):
193
    # Normal Response Codes: 200, 203
194
    # Error Response Codes: computeFault (400, 500),
195
    #                       serviceUnavailable (503),
196
    #                       unauthorized (401),
197
    #                       badRequest (400),
198
    #                       itemNotFound (404),
199
    #                       overLimit (413)
200

    
201
    vm = get_vm(server_id, request.user)
202
    server = vm_to_dict(vm, detail=True)
203
    return render_server(request, server)
204

    
205
@api_method('PUT')
206
def update_server_name(request, server_id):
207
    # Normal Response Code: 204
208
    # Error Response Codes: computeFault (400, 500),
209
    #                       serviceUnavailable (503),
210
    #                       unauthorized (401),
211
    #                       badRequest (400),
212
    #                       badMediaType(415),
213
    #                       itemNotFound (404),
214
    #                       buildInProgress (409),
215
    #                       overLimit (413)
216

    
217
    req = get_request_dict(request)
218

    
219
    try:
220
        name = req['server']['name']
221
    except (TypeError, KeyError):
222
        raise BadRequest('Malformed request.')
223

    
224
    vm = get_vm(server_id, request.user)
225
    vm.name = name
226
    vm.save()
227

    
228
    return HttpResponse(status=204)
229

    
230
@api_method('DELETE')
231
def delete_server(request, server_id):
232
    # Normal Response Codes: 204
233
    # Error Response Codes: computeFault (400, 500),
234
    #                       serviceUnavailable (503),
235
    #                       unauthorized (401),
236
    #                       itemNotFound (404),
237
    #                       unauthorized (401),
238
    #                       buildInProgress (409),
239
    #                       overLimit (413)
240

    
241
    vm = get_vm(server_id, request.user)
242
    delete_instance(vm)
243
    return HttpResponse(status=204)
244

    
245
@api_method('POST')
246
def server_action(request, server_id):
247
    vm = get_vm(server_id, request.user)
248
    req = get_request_dict(request)
249
    if len(req) != 1:
250
        raise BadRequest('Malformed request.')
251

    
252
    key = req.keys()[0]
253
    val = req[key]
254

    
255
    try:
256
        assert isinstance(val, dict)
257
        return server_actions[key](request, vm, req[key])
258
    except KeyError:
259
        raise BadRequest('Unknown action.')
260
    except AssertionError:
261
        raise BadRequest('Invalid argument.')
262

    
263
@api_method('GET')
264
def list_addresses(request, server_id):
265
    # Normal Response Codes: 200, 203
266
    # Error Response Codes: computeFault (400, 500),
267
    #                       serviceUnavailable (503),
268
    #                       unauthorized (401),
269
    #                       badRequest (400),
270
    #                       overLimit (413)
271

    
272
    vm = get_vm(server_id, request.user)
273
    addresses = [address_to_dict(vm.ipfour, vm.ipsix)]
274

    
275
    if request.serialization == 'xml':
276
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
277
    else:
278
        data = json.dumps({'addresses': {'values': addresses}})
279

    
280
    return HttpResponse(data, status=200)
281

    
282
@api_method('GET')
283
def list_addresses_by_network(request, server_id, network_id):
284
    # Normal Response Codes: 200, 203
285
    # Error Response Codes: computeFault (400, 500),
286
    #                       serviceUnavailable (503),
287
    #                       unauthorized (401),
288
    #                       badRequest (400),
289
    #                       itemNotFound (404),
290
    #                       overLimit (413)
291

    
292
    vm = get_vm(server_id, request.user)
293
    if network_id != 'public':
294
        raise ItemNotFound('Unknown network.')
295

    
296
    address = address_to_dict(vm.ipfour, vm.ipsix)
297

    
298
    if request.serialization == 'xml':
299
        data = render_to_string('address.xml', {'address': address})
300
    else:
301
        data = json.dumps({'network': address})
302

    
303
    return HttpResponse(data, status=200)
304

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

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

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

    
329
    vm = get_vm(server_id, request.user)
330
    req = get_request_dict(request)
331
    try:
332
        metadata = req['metadata']
333
        assert isinstance(metadata, dict)
334
    except (KeyError, AssertionError):
335
        raise BadRequest('Malformed request.')
336

    
337
    updated = {}
338

    
339
    for key, val in metadata.items():
340
        try:
341
            meta = VirtualMachineMetadata.objects.get(meta_key=key, vm=vm)
342
            meta.meta_value = val
343
            meta.save()
344
            updated[key] = val
345
        except VirtualMachineMetadata.DoesNotExist:
346
            pass    # Ignore non-existent metadata
347
    
348
    if updated:
349
        vm.save()
350
    
351
    return render_metadata(request, updated, status=201)
352

    
353
@api_method('GET')
354
def get_metadata_item(request, server_id, key):
355
    # Normal Response Codes: 200, 203
356
    # Error Response Codes: computeFault (400, 500),
357
    #                       serviceUnavailable (503),
358
    #                       unauthorized (401),
359
    #                       itemNotFound (404),
360
    #                       badRequest (400),
361
    #                       overLimit (413)
362

    
363
    vm = get_vm(server_id, request.user)
364
    meta = get_vm_meta(vm, key)
365
    return render_meta(request, meta, status=200)
366

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

    
379
    vm = get_vm(server_id, request.user)
380
    req = get_request_dict(request)
381
    try:
382
        metadict = req['meta']
383
        assert isinstance(metadict, dict)
384
        assert len(metadict) == 1
385
        assert key in metadict
386
    except (KeyError, AssertionError):
387
        raise BadRequest('Malformed request.')
388

    
389
    meta, created = VirtualMachineMetadata.objects.get_or_create(meta_key=key, vm=vm)
390
    meta.meta_value = metadict[key]
391
    meta.save()
392
    vm.save()
393
    return render_meta(request, meta, status=201)
394

    
395
@api_method('DELETE')
396
def delete_metadata_item(request, server_id, key):
397
    # Normal Response Code: 204
398
    # Error Response Codes: computeFault (400, 500),
399
    #                       serviceUnavailable (503),
400
    #                       unauthorized (401),
401
    #                       itemNotFound (404),
402
    #                       badRequest (400),
403
    #                       buildInProgress (409),
404
    #                       badMediaType(415),
405
    #                       overLimit (413),
406

    
407
    vm = get_vm(server_id, request.user)
408
    meta = get_vm_meta(vm, key)
409
    meta.delete()
410
    vm.save()
411
    return HttpResponse(status=204)