Revision d8e50a39 api/servers.py

b/api/servers.py
2 2
# Copyright (c) 2010 Greek Research and Technology Network
3 3
#
4 4

  
5
from logging import getLogger
6

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

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

  
20
import logging
21

  
19 22

  
20
log = getLogger('synnefo.api.servers')
21 23
rapi = GanetiRapiClient(*settings.GANETI_CLUSTER_INFO)
22 24

  
23 25
urlpatterns = patterns('synnefo.api.servers',
......
27 29
    (r'^/(\d+)/action(?:.json|.xml)?$', 'server_action'),
28 30
    (r'^/(\d+)/ips(?:.json|.xml)?$', 'list_addresses'),
29 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'),
30 34
)
31 35

  
32 36

  
......
36 40
    elif request.method == 'POST':
37 41
        return create_server(request)
38 42
    else:
39
        fault = BadRequest()
40
        return render_fault(request, fault)
43
        return method_not_allowed(request)
41 44

  
42 45
def server_demux(request, server_id):
43 46
    if request.method == 'GET':
......
47 50
    elif request.method == 'DELETE':
48 51
        return delete_server(request, server_id)
49 52
    else:
50
        fault = BadRequest()
51
        return render_fault(request, fault)
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)
52 72

  
53 73

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

  
58
def server_to_dict(server, detail=False):
59
    d = dict(id=server.id, name=server.name)
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)
60 84
    if detail:
61
        d['status'] = utils.get_rsapi_state(server)
62
        d['progress'] = 100 if utils.get_rsapi_state(server) == 'ACTIVE' else 0
63
        d['hostId'] = server.hostid
64
        d['updated'] = server.updated.isoformat()
65
        d['created'] = server.created.isoformat()
66
        d['flavorRef'] = server.flavor.id
67
        d['imageRef'] = server.sourceimage.id
68
        #d['description'] = server.description       # XXX Not in OpenStack docs
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
69 92
        
70
        server_meta = server.virtualmachinemetadata_set.all()
71
        metadata = dict((meta.meta_key, meta.meta_value) for meta in server_meta)
93
        metadata = metadata_to_dict(vm)
72 94
        if metadata:
73 95
            d['metadata'] = {'values': metadata}
74 96
        
75
        addresses = [address_to_dict(server.ipfour, server.ipsix)]
97
        addresses = [address_to_dict(vm.ipfour, vm.ipsix)]
76 98
        d['addresses'] = {'values': addresses}
77 99
    return d
78 100

  
79
def render_server(request, serverdict, status=200):
80
    if request.type == 'xml':
81
        data = render_to_string('server.xml', dict(server=serverdict, is_root=True))
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})
82 105
    else:
83
        data = json.dumps({'server': serverdict})
106
        data = json.dumps({'server': server})
84 107
    return HttpResponse(data, status=status)
85

  
108
    
86 109

  
87 110
@api_method('GET')
88 111
def list_servers(request, detail=False):
......
94 117
    #                       overLimit (413)
95 118
    
96 119
    owner = get_user()
97
    user_servers = VirtualMachine.objects.filter(owner=owner, deleted=False)
98
    servers = [server_to_dict(server, detail) for server in user_servers]
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]
99 129
    
100
    if request.type == 'xml':
101
        data = render_to_string('list_servers.xml', dict(servers=servers, detail=detail))
130
    if request.serialization == 'xml':
131
        data = render_to_string('list_servers.xml', {'servers': servers, 'detail': detail})
102 132
    else:
103 133
        data = json.dumps({'servers': {'values': servers}})
104 134
    
......
124 154
        sourceimage = Image.objects.get(id=server['imageRef'])
125 155
        flavor = Flavor.objects.get(id=server['flavorRef'])
126 156
    except KeyError:
127
        raise BadRequest
157
        raise BadRequest('Malformed request.')
128 158
    except Image.DoesNotExist:
129 159
        raise ItemNotFound
130 160
    except Flavor.DoesNotExist:
131 161
        raise ItemNotFound
132 162
    
133
    server = VirtualMachine.objects.create(
163
    vm = VirtualMachine.objects.create(
134 164
        name=name,
135 165
        owner=get_user(),
136 166
        sourceimage=sourceimage,
......
142 172
        name = 'test-server'
143 173
        dry_run = True
144 174
    else:
145
        name = server.backend_id
175
        name = vm.backend_id
146 176
        dry_run = False
147 177
    
148 178
    jobId = rapi.CreateInstance(
......
158 188
        dry_run=dry_run,
159 189
        beparams=dict(auto_balance=True, vcpus=flavor.cpu, memory=flavor.ram))
160 190
    
161
    server.save()
191
    vm.save()
162 192
        
163
    log.info('created vm with %s cpus, %s ram and %s storage' % (flavor.cpu, flavor.ram, flavor.disk))
193
    logging.info('created vm with %s cpus, %s ram and %s storage' % (flavor.cpu, flavor.ram, flavor.disk))
164 194
    
165
    serverdict = server_to_dict(server, detail=True)
166
    serverdict['status'] = 'BUILD'
167
    serverdict['adminPass'] = random_password()
168
    return render_server(request, serverdict, status=202)
195
    server = vm_to_dict(vm, detail=True)
196
    server['status'] = 'BUILD'
197
    server['adminPass'] = random_password()
198
    return render_server(request, server, status=202)
169 199

  
170 200
@api_method('GET')
171 201
def get_server_details(request, server_id):
......
177 207
    #                       itemNotFound (404),
178 208
    #                       overLimit (413)
179 209
    
180
    try:
181
        server_id = int(server_id)
182
        server = VirtualMachine.objects.get(id=server_id)
183
    except VirtualMachine.DoesNotExist:
184
        raise ItemNotFound
185
    
186
    serverdict = server_to_dict(server, detail=True)
187
    return render_server(request, serverdict)
210
    vm = get_vm(server_id)
211
    server = vm_to_dict(vm, detail=True)
212
    return render_server(request, server)
188 213

  
189 214
@api_method('PUT')
190 215
def update_server_name(request, server_id):
......
202 227
    
203 228
    try:
204 229
        name = req['server']['name']
205
        server_id = int(server_id)
206
        server = VirtualMachine.objects.get(id=server_id)
207 230
    except KeyError:
208
        raise BadRequest
209
    except VirtualMachine.DoesNotExist:
210
        raise ItemNotFound
231
        raise BadRequest('Malformed request.')
211 232
    
212
    server.name = name
213
    server.save()
233
    vm = get_vm(server_id)
234
    vm.name = name
235
    vm.save()
214 236
    
215 237
    return HttpResponse(status=204)
216 238

  
......
225 247
    #                       buildInProgress (409),
226 248
    #                       overLimit (413)
227 249
    
228
    try:
229
        server_id = int(server_id)
230
        server = VirtualMachine.objects.get(id=server_id)
231
    except VirtualMachine.DoesNotExist:
232
        raise ItemNotFound
233
    
234
    backend.start_action(server, 'DESTROY')
235
    rapi.DeleteInstance(server.backend_id)
250
    vm = get_vm(server_id)
251
    backend.start_action(vm, 'DESTROY')
252
    rapi.DeleteInstance(vm.backend_id)
236 253
    return HttpResponse(status=204)
237 254

  
238 255
@api_method('POST')
239 256
def server_action(request, server_id):
240
    try:
241
        server_id = int(server_id)
242
        server = VirtualMachine.objects.get(id=server_id)
243
    except VirtualMachine.DoesNotExist:
244
        raise ItemNotFound
245

  
257
    vm = get_vm(server_id)
246 258
    req = get_request_dict(request)
247 259
    if len(req) != 1:
248
        raise BadRequest
260
        raise BadRequest('Malformed request.')
249 261
    
250 262
    key = req.keys()[0]
251
    if key not in server_actions:
252
        raise BadRequest
263
    val = req[key]
253 264
    
254
    return server_actions[key](server, req[key])
265
    try:
266
        assert isinstance(val, dict)
267
        return server_actions[key](vm, req[key])
268
    except KeyError:
269
        raise BadRequest('Unknown action.')
270
    except AssertionError:
271
        raise BadRequest('Invalid argument.')
255 272

  
256 273
@api_method('GET')
257 274
def list_addresses(request, server_id):
......
262 279
    #                       badRequest (400),
263 280
    #                       overLimit (413)
264 281
    
265
    try:
266
        server_id = int(server_id)
267
        server = VirtualMachine.objects.get(id=server_id)
268
    except VirtualMachine.DoesNotExist:
269
        raise ItemNotFound
270
    
271
    addresses = [address_to_dict(server.ipfour, server.ipsix)]
282
    vm = get_vm(server_id)
283
    addresses = [address_to_dict(vm.ipfour, vm.ipsix)]
272 284
    
273
    if request.type == 'xml':
285
    if request.serialization == 'xml':
274 286
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
275 287
    else:
276 288
        data = json.dumps({'addresses': {'values': addresses}})
......
287 299
    #                       itemNotFound (404),
288 300
    #                       overLimit (413)
289 301
    
290
    try:
291
        server_id = int(server_id)
292
        server = VirtualMachine.objects.get(id=server_id)
293
    except VirtualMachine.DoesNotExist:
294
        raise ItemNotFound
295
    
302
    vm = get_vm(server_id)
296 303
    if network_id != 'public':
297
        raise ItemNotFound
304
        raise ItemNotFound('Unknown network.')
298 305
    
299
    address = address_to_dict(server.ipfour, server.ipsix)
306
    address = address_to_dict(vm.ipfour, vm.ipsix)
300 307
    
301
    if request.type == 'xml':
308
    if request.serialization == 'xml':
302 309
        data = render_to_string('address.xml', {'address': address})
303 310
    else:
304 311
        data = json.dumps({'network': address})
305 312
    
306 313
    return HttpResponse(data, status=200)
314

  
315
@api_method('GET')
316
def list_metadata(request, server_id):
317
    # Normal Response Codes: 200, 203
318
    # Error Response Codes: computeFault (400, 500),
319
    #                       serviceUnavailable (503),
320
    #                       unauthorized (401),
321
    #                       badRequest (400),
322
    #                       overLimit (413)
323

  
324
    vm = get_vm(server_id)
325
    metadata = metadata_to_dict(vm)
326
    
327
    if request.serialization == 'xml':
328
        data = render_to_string('metadata.xml', {'metadata': metadata})
329
    else:
330
        data = json.dumps({'metadata': {'values': metadata}})
331
    
332
    return HttpResponse(data, status=200)
333
    
334

  
335
@api_method('POST')
336
def update_metadata(request, server_id):
337
    # Normal Response Code: 201
338
    # Error Response Codes: computeFault (400, 500),
339
    #                       serviceUnavailable (503),
340
    #                       unauthorized (401),
341
    #                       badRequest (400),
342
    #                       buildInProgress (409),
343
    #                       badMediaType(415),
344
    #                       overLimit (413)
345

  
346
    vm = get_vm(server_id)
347
    req = get_request_dict(request)
348
    try:
349
        metadata = req['metadata']
350
        assert isinstance(metadata, dict)
351
    except (KeyError, AssertionError):
352
        raise BadRequest('Malformed request.')
353
    
354
    updated = {}
355
    
356
    for key, val in metadata.items():
357
        try:
358
            meta = VirtualMachineMetadata.objects.get(meta_key=key, vm=vm)
359
            meta.meta_value = val
360
            meta.save()
361
            updated[key] = val
362
        except VirtualMachineMetadata.DoesNotExist:
363
            pass    # Ignore non-existent metadata
364
    
365
    if request.serialization == 'xml':
366
        data = render_to_string('servers/metadata.xml', {'metadata': updated})
367
    else:
368
        data = json.dumps({'metadata': updated})
369
    return HttpResponse(data, status=201)
370

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

  
381
    meta = get_vm_meta(server_id, key)
382
    if request.serialization == 'xml':
383
        data = render_to_string('meta.xml', {'meta': meta})
384
    else:
385
        data = json.dumps({'meta': {key: meta.meta_value}})
386
    return HttpResponse(data, 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
    
414
    if request.serialization == 'xml':
415
        data = render_to_string('servers/meta.xml', {'meta': meta})
416
    else:
417
        data = json.dumps({'meta': {key: meta.meta_value}})
418
    return HttpResponse(data, status=201)
419

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

  
432
    meta = get_vm_meta(server_id, key)
433
    meta.delete()
434
    return HttpResponse(status=204)

Also available in: Unified diff