Statistics
| Branch: | Tag: | Revision:

root / api / servers.py @ 64938cb0

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, get_network, get_nic,
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 nic_to_dict(nic):
74
    d = {
75
        'id': nic.network.id,
76
        'name': nic.network.name,
77
        'mac': nic.mac,
78
        'firewallProfile': nic.firewall_profile}
79
    if nic.ipv4 or nic.ipv6:
80
        d['values'] = [
81
            {'version': 4, 'addr': nic.ipv4 or ''},
82
            {'version': 6, 'addr': nic.ipv6 or ''}]
83
    return d
84

    
85
def metadata_to_dict(vm):
86
    vm_meta = vm.virtualmachinemetadata_set.all()
87
    return dict((meta.meta_key, meta.meta_value) for meta in vm_meta)
88

    
89
def vm_to_dict(vm, detail=False):
90
    d = dict(id=vm.id, name=vm.name)
91
    if detail:
92
        d['status'] = get_rsapi_state(vm)
93
        d['progress'] = 100 if get_rsapi_state(vm) == 'ACTIVE' else 0
94
        d['hostId'] = vm.hostid
95
        d['updated'] = isoformat(vm.updated)
96
        d['created'] = isoformat(vm.created)
97
        d['flavorRef'] = vm.flavor.id
98
        d['imageRef'] = vm.sourceimage.id
99

    
100
        metadata = metadata_to_dict(vm)
101
        if metadata:
102
            d['metadata'] = {'values': metadata}
103

    
104
        addresses = [nic_to_dict(nic) for nic in vm.nics.all()]
105
        d['addresses'] = {'values': addresses}
106
    return d
107

    
108

    
109
def render_server(request, server, status=200):
110
    if request.serialization == 'xml':
111
        data = render_to_string('server.xml', {'server': server, 'is_root': True})
112
    else:
113
        data = json.dumps({'server': server})
114
    return HttpResponse(data, status=status)
115

    
116

    
117
@api_method('GET')
118
def list_servers(request, detail=False):
119
    # Normal Response Codes: 200, 203
120
    # Error Response Codes: computeFault (400, 500),
121
    #                       serviceUnavailable (503),
122
    #                       unauthorized (401),
123
    #                       badRequest (400),
124
    #                       overLimit (413)
125

    
126
    since = isoparse(request.GET.get('changes-since'))
127

    
128
    if since:
129
        user_vms = VirtualMachine.objects.filter(owner=request.user, updated__gte=since)
130
        if not user_vms:
131
            return HttpResponse(status=304)
132
    else:
133
        user_vms = VirtualMachine.objects.filter(owner=request.user, deleted=False)
134
    servers = [vm_to_dict(server, detail) for server in user_vms]
135

    
136
    if request.serialization == 'xml':
137
        data = render_to_string('list_servers.xml', {'servers': servers, 'detail': detail})
138
    else:
139
        data = json.dumps({'servers': {'values': servers}})
140

    
141
    return HttpResponse(data, status=200)
142

    
143
@api_method('POST')
144
def create_server(request):
145
    # Normal Response Code: 202
146
    # Error Response Codes: computeFault (400, 500),
147
    #                       serviceUnavailable (503),
148
    #                       unauthorized (401),
149
    #                       badMediaType(415),
150
    #                       itemNotFound (404),
151
    #                       badRequest (400),
152
    #                       serverCapacityUnavailable (503),
153
    #                       overLimit (413)
154

    
155
    req = get_request_dict(request)
156
    owner = request.user
157
    
158
    try:
159
        server = req['server']
160
        name = server['name']
161
        metadata = server.get('metadata', {})
162
        assert isinstance(metadata, dict)
163
        image_id = server['imageRef']
164
        flavor_id = server['flavorRef']
165
    except (KeyError, AssertionError):
166
        raise BadRequest('Malformed request.')
167
    
168
    image = get_image(image_id, owner)
169
    flavor = get_flavor(flavor_id)
170
    password = random_password()
171
    
172
    # We must save the VM instance now, so that it gets a valid vm.backend_id.
173
    vm = VirtualMachine.objects.create(name=name, owner=owner, sourceimage=image, flavor=flavor)
174
    
175
    try:
176
        create_instance(vm, flavor, password)
177
    except GanetiApiError:
178
        vm.delete()
179
        raise ServiceUnavailable('Could not create server.')
180

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

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

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

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

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

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

    
218
    req = get_request_dict(request)
219

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

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

    
229
    return HttpResponse(status=204)
230

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

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

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

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

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

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

    
273
    vm = get_vm(server_id, request.user)
274
    addresses = [nic_to_dict(nic) for nic in vm.nics.all()]
275
    
276
    if request.serialization == 'xml':
277
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
278
    else:
279
        data = json.dumps({'addresses': {'values': addresses}})
280

    
281
    return HttpResponse(data, status=200)
282

    
283
@api_method('GET')
284
def list_addresses_by_network(request, server_id, network_id):
285
    # Normal Response Codes: 200, 203
286
    # Error Response Codes: computeFault (400, 500),
287
    #                       serviceUnavailable (503),
288
    #                       unauthorized (401),
289
    #                       badRequest (400),
290
    #                       itemNotFound (404),
291
    #                       overLimit (413)
292
    
293
    owner = request.user
294
    machine = get_vm(server_id, owner)
295
    network = get_network(network_id, owner)
296
    nic = get_nic(machine, network)
297
    address = nic_to_dict(nic)
298
    
299
    if request.serialization == 'xml':
300
        data = render_to_string('address.xml', {'address': address})
301
    else:
302
        data = json.dumps({'network': address})
303

    
304
    return HttpResponse(data, status=200)
305

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

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

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

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

    
338
    updated = {}
339

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

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

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

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

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

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

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

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