Statistics
| Branch: | Tag: | Revision:

root / api / servers.py @ 1c03e74e

History | View | Annotate | Download (14.5 kB)

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

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

    
11
from synnefo.api.actions import server_actions
12
from synnefo.api.common import method_not_allowed
13
from synnefo.api.faults import BadRequest, ItemNotFound
14
from synnefo.api.util import *
15
from synnefo.db.models import Image, Flavor, VirtualMachine, VirtualMachineMetadata
16
from synnefo.logic.utils import get_rsapi_state
17
from synnefo.util.rapi import GanetiRapiClient
18
from synnefo.logic import backend
19

    
20
import logging
21

    
22

    
23
rapi = GanetiRapiClient(*settings.GANETI_CLUSTER_INFO)
24

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

    
36

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

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

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

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

    
73

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

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

    
82
def vm_to_dict(vm, detail=False):
83
    d = dict(id=vm.id, name=vm.name)
84
    if detail:
85
        d['status'] = get_rsapi_state(vm)
86
        d['progress'] = 100 if get_rsapi_state(vm) == 'ACTIVE' else 0
87
        d['hostId'] = vm.hostid
88
        d['updated'] = isoformat(vm.updated)
89
        d['created'] = isoformat(vm.created)
90
        d['flavorRef'] = vm.flavor.id
91
        d['imageRef'] = vm.sourceimage.id
92
        
93
        metadata = metadata_to_dict(vm)
94
        if metadata:
95
            d['metadata'] = {'values': metadata}
96
        
97
        addresses = [address_to_dict(vm.ipfour, vm.ipsix)]
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
    owner = get_user()
120
    since = isoparse(request.GET.get('changes-since'))
121
    
122
    if since:
123
        user_vms = VirtualMachine.objects.filter(updated__gt=since)
124
        if not user_vms:
125
            return HttpResponse(status=304)
126
    else:
127
        user_vms = VirtualMachine.objects.filter(owner=owner, deleted=False)
128
    servers = [vm_to_dict(server, detail) for server in user_vms]
129
    
130
    if request.serialization == 'xml':
131
        data = render_to_string('list_servers.xml', {'servers': servers, 'detail': detail})
132
    else:
133
        data = json.dumps({'servers': {'values': servers}})
134
    
135
    return HttpResponse(data, status=200)
136

    
137
@api_method('POST')
138
def create_server(request):
139
    # Normal Response Code: 202
140
    # Error Response Codes: computeFault (400, 500),
141
    #                       serviceUnavailable (503),
142
    #                       unauthorized (401),
143
    #                       badMediaType(415),
144
    #                       itemNotFound (404),
145
    #                       badRequest (400),
146
    #                       serverCapacityUnavailable (503),
147
    #                       overLimit (413)
148
    
149
    req = get_request_dict(request)
150
    
151
    try:
152
        server = req['server']
153
        name = server['name']
154
        metadata = server.get('metadata', {})
155
        assert isinstance(metadata, dict)
156
        sourceimage = Image.objects.get(id=server['imageRef'])
157
        flavor = Flavor.objects.get(id=server['flavorRef'])
158
    except (KeyError, AssertionError):
159
        raise BadRequest('Malformed request.')
160
    except Image.DoesNotExist:
161
        raise ItemNotFound
162
    except Flavor.DoesNotExist:
163
        raise ItemNotFound
164
    
165
    vm = VirtualMachine(
166
        name=name,
167
        owner=get_user(),
168
        sourceimage=sourceimage,
169
        ipfour='0.0.0.0',
170
        ipsix='::1',
171
        flavor=flavor)
172

    
173
    # Pick a random password for the VM.
174
    # FIXME: This must be passed to the Ganeti OS provider via CreateInstance()
175
    passwd = random_password()
176

    
177
    # We *must* save the VM instance now,
178
    # so that it gets a vm.id and vm.backend_id is valid.
179
    vm.save() 
180
                
181
    if request.META.get('SERVER_NAME', None) == 'testserver':
182
        backend_name = 'test-server'
183
        dry_run = True
184
    else:
185
        backend_name = vm.backend_id
186
        dry_run = False
187
    
188
    try:
189
        jobId = rapi.CreateInstance(
190
            mode='create',
191
            name=backend_name,
192
            disk_template='plain',
193
            disks=[{"size": 2000}],         #FIXME: Always ask for a 2GB disk for now
194
            nics=[{}],
195
            os='debootstrap+default',       #TODO: select OS from imageRef
196
            ip_check=False,
197
            name_check=False,
198
            pnode=rapi.GetNodes()[0],       #TODO: verify if this is necessary
199
            dry_run=dry_run,
200
            beparams=dict(auto_balance=True, vcpus=flavor.cpu, memory=flavor.ram))
201
    except Exception, e:
202
        vm.delete()
203
        raise e
204
        
205
    for key, val in metadata.items():
206
        VirtualMachineMetadata.objects.create(meta_key=key, meta_value=val, vm=vm)
207
    
208
    logging.info('created vm with %s cpus, %s ram and %s storage' % (flavor.cpu, flavor.ram, flavor.disk))
209
    
210
    server = vm_to_dict(vm, detail=True)
211
    server['status'] = 'BUILD'
212
    server['adminPass'] = passwd
213
    return render_server(request, server, status=202)
214

    
215
@api_method('GET')
216
def get_server_details(request, server_id):
217
    # Normal Response Codes: 200, 203
218
    # Error Response Codes: computeFault (400, 500),
219
    #                       serviceUnavailable (503),
220
    #                       unauthorized (401),
221
    #                       badRequest (400),
222
    #                       itemNotFound (404),
223
    #                       overLimit (413)
224
    
225
    vm = get_vm(server_id)
226
    server = vm_to_dict(vm, detail=True)
227
    return render_server(request, server)
228

    
229
@api_method('PUT')
230
def update_server_name(request, server_id):
231
    # Normal Response Code: 204
232
    # Error Response Codes: computeFault (400, 500),
233
    #                       serviceUnavailable (503),
234
    #                       unauthorized (401),
235
    #                       badRequest (400),
236
    #                       badMediaType(415),
237
    #                       itemNotFound (404),
238
    #                       buildInProgress (409),
239
    #                       overLimit (413)
240
    
241
    req = get_request_dict(request)
242
    
243
    try:
244
        name = req['server']['name']
245
    except KeyError:
246
        raise BadRequest('Malformed request.')
247
    
248
    vm = get_vm(server_id)
249
    vm.name = name
250
    vm.save()
251
    
252
    return HttpResponse(status=204)
253

    
254
@api_method('DELETE')
255
def delete_server(request, server_id):
256
    # Normal Response Codes: 204
257
    # Error Response Codes: computeFault (400, 500),
258
    #                       serviceUnavailable (503),
259
    #                       unauthorized (401),
260
    #                       itemNotFound (404),
261
    #                       unauthorized (401),
262
    #                       buildInProgress (409),
263
    #                       overLimit (413)
264
    
265
    vm = get_vm(server_id)
266
    backend.start_action(vm, 'DESTROY')
267
    rapi.DeleteInstance(vm.backend_id)
268
    return HttpResponse(status=204)
269

    
270
@api_method('POST')
271
def server_action(request, server_id):
272
    vm = get_vm(server_id)
273
    req = get_request_dict(request)
274
    if len(req) != 1:
275
        raise BadRequest('Malformed request.')
276
    
277
    key = req.keys()[0]
278
    val = req[key]
279
    
280
    try:
281
        assert isinstance(val, dict)
282
        return server_actions[key](request, vm, req[key])
283
    except KeyError:
284
        raise BadRequest('Unknown action.')
285
    except AssertionError:
286
        raise BadRequest('Invalid argument.')
287

    
288
@api_method('GET')
289
def list_addresses(request, server_id):
290
    # Normal Response Codes: 200, 203
291
    # Error Response Codes: computeFault (400, 500),
292
    #                       serviceUnavailable (503),
293
    #                       unauthorized (401),
294
    #                       badRequest (400),
295
    #                       overLimit (413)
296
    
297
    vm = get_vm(server_id)
298
    addresses = [address_to_dict(vm.ipfour, vm.ipsix)]
299
    
300
    if request.serialization == 'xml':
301
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
302
    else:
303
        data = json.dumps({'addresses': {'values': addresses}})
304
    
305
    return HttpResponse(data, status=200)
306

    
307
@api_method('GET')
308
def list_addresses_by_network(request, server_id, network_id):
309
    # Normal Response Codes: 200, 203
310
    # Error Response Codes: computeFault (400, 500),
311
    #                       serviceUnavailable (503),
312
    #                       unauthorized (401),
313
    #                       badRequest (400),
314
    #                       itemNotFound (404),
315
    #                       overLimit (413)
316
    
317
    vm = get_vm(server_id)
318
    if network_id != 'public':
319
        raise ItemNotFound('Unknown network.')
320
    
321
    address = address_to_dict(vm.ipfour, vm.ipsix)
322
    
323
    if request.serialization == 'xml':
324
        data = render_to_string('address.xml', {'address': address})
325
    else:
326
        data = json.dumps({'network': address})
327
    
328
    return HttpResponse(data, status=200)
329

    
330
@api_method('GET')
331
def list_metadata(request, server_id):
332
    # Normal Response Codes: 200, 203
333
    # Error Response Codes: computeFault (400, 500),
334
    #                       serviceUnavailable (503),
335
    #                       unauthorized (401),
336
    #                       badRequest (400),
337
    #                       overLimit (413)
338

    
339
    vm = get_vm(server_id)
340
    metadata = metadata_to_dict(vm)
341
    return render_metadata(request, metadata, use_values=True, status=200)
342

    
343
@api_method('POST')
344
def update_metadata(request, server_id):
345
    # Normal Response Code: 201
346
    # Error Response Codes: computeFault (400, 500),
347
    #                       serviceUnavailable (503),
348
    #                       unauthorized (401),
349
    #                       badRequest (400),
350
    #                       buildInProgress (409),
351
    #                       badMediaType(415),
352
    #                       overLimit (413)
353

    
354
    vm = get_vm(server_id)
355
    req = get_request_dict(request)
356
    try:
357
        metadata = req['metadata']
358
        assert isinstance(metadata, dict)
359
    except (KeyError, AssertionError):
360
        raise BadRequest('Malformed request.')
361
    
362
    updated = {}
363
    
364
    for key, val in metadata.items():
365
        try:
366
            meta = VirtualMachineMetadata.objects.get(meta_key=key, vm=vm)
367
            meta.meta_value = val
368
            meta.save()
369
            updated[key] = val
370
        except VirtualMachineMetadata.DoesNotExist:
371
            pass    # Ignore non-existent metadata
372
    
373
    return render_metadata(request, metadata, status=201)
374

    
375
@api_method('GET')
376
def get_metadata_item(request, server_id, key):
377
    # Normal Response Codes: 200, 203
378
    # Error Response Codes: computeFault (400, 500),
379
    #                       serviceUnavailable (503),
380
    #                       unauthorized (401),
381
    #                       itemNotFound (404),
382
    #                       badRequest (400),
383
    #                       overLimit (413)
384

    
385
    meta = get_vm_meta(server_id, key)
386
    return render_meta(request, meta, status=200)
387

    
388
@api_method('PUT')
389
def create_metadata_item(request, server_id, key):
390
    # Normal Response Code: 201
391
    # Error Response Codes: computeFault (400, 500),
392
    #                       serviceUnavailable (503),
393
    #                       unauthorized (401),
394
    #                       itemNotFound (404),
395
    #                       badRequest (400),
396
    #                       buildInProgress (409),
397
    #                       badMediaType(415),
398
    #                       overLimit (413)
399

    
400
    vm = get_vm(server_id)
401
    req = get_request_dict(request)
402
    try:
403
        metadict = req['meta']
404
        assert isinstance(metadict, dict)
405
        assert len(metadict) == 1
406
        assert key in metadict
407
    except (KeyError, AssertionError):
408
        raise BadRequest('Malformed request.')
409
    
410
    meta, created = VirtualMachineMetadata.objects.get_or_create(meta_key=key, vm=vm)
411
    meta.meta_value = metadict[key]
412
    meta.save()
413
    return render_meta(request, meta, status=201)
414

    
415
@api_method('DELETE')
416
def delete_metadata_item(request, server_id, key):
417
    # Normal Response Code: 204
418
    # Error Response Codes: computeFault (400, 500),
419
    #                       serviceUnavailable (503),
420
    #                       unauthorized (401),
421
    #                       itemNotFound (404),
422
    #                       badRequest (400),
423
    #                       buildInProgress (409),
424
    #                       badMediaType(415),
425
    #                       overLimit (413),
426

    
427
    meta = get_vm_meta(server_id, key)
428
    meta.delete()
429
    return HttpResponse(status=204)