Statistics
| Branch: | Tag: | Revision:

root / api / servers.py @ bc923fb7

History | View | Annotate | Download (17.2 kB)

1
# Copyright 2011 GRNET S.A. All rights reserved.
2
# 
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
# 
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
# 
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
# 
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
# 
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
import logging
35

    
36
from django.conf import settings
37
from django.conf.urls.defaults import patterns
38
from django.http import HttpResponse
39
from django.template.loader import render_to_string
40
from django.utils import simplejson as json
41

    
42
from synnefo.api import faults, util
43
from synnefo.api.actions import server_actions
44
from synnefo.api.common import method_not_allowed
45
from synnefo.db.models import VirtualMachine, VirtualMachineMetadata
46
from synnefo.logic.backend import create_instance, delete_instance
47
from synnefo.logic.utils import get_rsapi_state
48
from synnefo.util.rapi import GanetiApiError
49

    
50

    
51
urlpatterns = patterns('synnefo.api.servers',
52
    (r'^(?:/|.json|.xml)?$', 'demux'),
53
    (r'^/detail(?:.json|.xml)?$', 'list_servers', {'detail': True}),
54
    (r'^/(\d+)(?:.json|.xml)?$', 'server_demux'),
55
    (r'^/(\d+)/action(?:.json|.xml)?$', 'server_action'),
56
    (r'^/(\d+)/ips(?:.json|.xml)?$', 'list_addresses'),
57
    (r'^/(\d+)/ips/(.+?)(?:.json|.xml)?$', 'list_addresses_by_network'),
58
    (r'^/(\d+)/meta(?:.json|.xml)?$', 'metadata_demux'),
59
    (r'^/(\d+)/meta/(.+?)(?:.json|.xml)?$', 'metadata_item_demux'),
60
    (r'^/(\d+)/stats(?:.json|.xml)?$', 'server_stats'),
61
)
62

    
63

    
64
def demux(request):
65
    if request.method == 'GET':
66
        return list_servers(request)
67
    elif request.method == 'POST':
68
        return create_server(request)
69
    else:
70
        return method_not_allowed(request)
71

    
72
def server_demux(request, server_id):
73
    if request.method == 'GET':
74
        return get_server_details(request, server_id)
75
    elif request.method == 'PUT':
76
        return update_server_name(request, server_id)
77
    elif request.method == 'DELETE':
78
        return delete_server(request, server_id)
79
    else:
80
        return method_not_allowed(request)
81

    
82
def metadata_demux(request, server_id):
83
    if request.method == 'GET':
84
        return list_metadata(request, server_id)
85
    elif request.method == 'POST':
86
        return update_metadata(request, server_id)
87
    else:
88
        return method_not_allowed(request)
89

    
90
def metadata_item_demux(request, server_id, key):
91
    if request.method == 'GET':
92
        return get_metadata_item(request, server_id, key)
93
    elif request.method == 'PUT':
94
        return create_metadata_item(request, server_id, key)
95
    elif request.method == 'DELETE':
96
        return delete_metadata_item(request, server_id, key)
97
    else:
98
        return method_not_allowed(request)
99

    
100

    
101
def nic_to_dict(nic):
102
    network = nic.network
103
    network_id = str(network.id) if not network.public else 'public'
104
    d = {'id': network_id, 'name': network.name, 'mac': nic.mac}
105
    if nic.firewall_profile:
106
        d['firewallProfile'] = nic.firewall_profile
107
    if nic.ipv4 or nic.ipv6:
108
        d['values'] = []
109
        if nic.ipv4:
110
            d['values'].append({'version': 4, 'addr': nic.ipv4})
111
        if nic.ipv6:
112
            d['values'].append({'version': 6, 'addr': nic.ipv6})
113
    return d
114

    
115
def metadata_to_dict(vm):
116
    vm_meta = vm.virtualmachinemetadata_set.all()
117
    return dict((meta.meta_key, meta.meta_value) for meta in vm_meta)
118

    
119
def vm_to_dict(vm, detail=False):
120
    d = dict(id=vm.id, name=vm.name)
121
    if detail:
122
        d['status'] = get_rsapi_state(vm)
123
        d['progress'] = 100 if get_rsapi_state(vm) == 'ACTIVE' else 0
124
        d['hostId'] = vm.hostid
125
        d['updated'] = util.isoformat(vm.updated)
126
        d['created'] = util.isoformat(vm.created)
127
        d['flavorRef'] = vm.flavor.id
128
        d['imageRef'] = vm.sourceimage.id
129

    
130
        metadata = metadata_to_dict(vm)
131
        if metadata:
132
            d['metadata'] = {'values': metadata}
133

    
134
        addresses = [nic_to_dict(nic) for nic in vm.nics.all()]
135
        if addresses:
136
            d['addresses'] = {'values': addresses}
137
    return d
138

    
139

    
140
def render_server(request, server, status=200):
141
    if request.serialization == 'xml':
142
        data = render_to_string('server.xml', {
143
            'server': server,
144
            'is_root': True})
145
    else:
146
        data = json.dumps({'server': server})
147
    return HttpResponse(data, status=status)
148

    
149

    
150
@util.api_method('GET')
151
def list_servers(request, detail=False):
152
    # Normal Response Codes: 200, 203
153
    # Error Response Codes: computeFault (400, 500),
154
    #                       serviceUnavailable (503),
155
    #                       unauthorized (401),
156
    #                       badRequest (400),
157
    #                       overLimit (413)
158
    
159
    user_vms = VirtualMachine.objects.filter(owner=request.user)
160
    since = util.isoparse(request.GET.get('changes-since'))
161
    
162
    if since:
163
        user_vms = user_vms.filter(updated__gte=since)
164
        if not user_vms:
165
            return HttpResponse(status=304)
166
    else:
167
        user_vms = user_vms.filter(deleted=False)
168
    
169
    servers = [vm_to_dict(server, detail) for server in user_vms]
170

    
171
    if request.serialization == 'xml':
172
        data = render_to_string('list_servers.xml', {
173
            'servers': servers,
174
            'detail': detail})
175
    else:
176
        data = json.dumps({'servers': {'values': servers}})
177

    
178
    return HttpResponse(data, status=200)
179

    
180
@util.api_method('POST')
181
def create_server(request):
182
    # Normal Response Code: 202
183
    # Error Response Codes: computeFault (400, 500),
184
    #                       serviceUnavailable (503),
185
    #                       unauthorized (401),
186
    #                       badMediaType(415),
187
    #                       itemNotFound (404),
188
    #                       badRequest (400),
189
    #                       serverCapacityUnavailable (503),
190
    #                       overLimit (413)
191

    
192
    req = util.get_request_dict(request)
193
    owner = request.user
194
    
195
    try:
196
        server = req['server']
197
        name = server['name']
198
        metadata = server.get('metadata', {})
199
        assert isinstance(metadata, dict)
200
        image_id = server['imageRef']
201
        flavor_id = server['flavorRef']
202
    except (KeyError, AssertionError):
203
        raise faults.BadRequest("Malformed request")
204
    
205
    image = util.get_image(image_id, owner)
206
    flavor = util.get_flavor(flavor_id)
207
    password = util.random_password()
208
    
209
    count = VirtualMachine.objects.filter(owner=owner, deleted=False).count()
210
    if count >= settings.MAX_VMS_PER_USER:
211
        raise faults.OverLimit("Maximum number of servers reached")
212
    
213
    # We must save the VM instance now, so that it gets a valid vm.backend_id.
214
    vm = VirtualMachine.objects.create(
215
        name=name,
216
        owner=owner,
217
        sourceimage=image,
218
        flavor=flavor)
219
    
220
    try:
221
        create_instance(vm, flavor, image, password)
222
    except GanetiApiError:
223
        vm.delete()
224
        raise faults.ServiceUnavailable("Could not create server")
225

    
226
    for key, val in metadata.items():
227
        VirtualMachineMetadata.objects.create(
228
            meta_key=key,
229
            meta_value=val,
230
            vm=vm)
231
    
232
    logging.info('created vm with %s cpus, %s ram and %s storage',
233
                    flavor.cpu, flavor.ram, flavor.disk)
234

    
235
    server = vm_to_dict(vm, detail=True)
236
    server['status'] = 'BUILD'
237
    server['adminPass'] = password
238
    return render_server(request, server, status=202)
239

    
240
@util.api_method('GET')
241
def get_server_details(request, server_id):
242
    # Normal Response Codes: 200, 203
243
    # Error Response Codes: computeFault (400, 500),
244
    #                       serviceUnavailable (503),
245
    #                       unauthorized (401),
246
    #                       badRequest (400),
247
    #                       itemNotFound (404),
248
    #                       overLimit (413)
249

    
250
    vm = util.get_vm(server_id, request.user)
251
    server = vm_to_dict(vm, detail=True)
252
    return render_server(request, server)
253

    
254
@util.api_method('PUT')
255
def update_server_name(request, server_id):
256
    # Normal Response Code: 204
257
    # Error Response Codes: computeFault (400, 500),
258
    #                       serviceUnavailable (503),
259
    #                       unauthorized (401),
260
    #                       badRequest (400),
261
    #                       badMediaType(415),
262
    #                       itemNotFound (404),
263
    #                       buildInProgress (409),
264
    #                       overLimit (413)
265

    
266
    req = util.get_request_dict(request)
267

    
268
    try:
269
        name = req['server']['name']
270
    except (TypeError, KeyError):
271
        raise faults.BadRequest("Malformed request")
272

    
273
    vm = util.get_vm(server_id, request.user)
274
    vm.name = name
275
    vm.save()
276

    
277
    return HttpResponse(status=204)
278

    
279
@util.api_method('DELETE')
280
def delete_server(request, server_id):
281
    # Normal Response Codes: 204
282
    # Error Response Codes: computeFault (400, 500),
283
    #                       serviceUnavailable (503),
284
    #                       unauthorized (401),
285
    #                       itemNotFound (404),
286
    #                       unauthorized (401),
287
    #                       buildInProgress (409),
288
    #                       overLimit (413)
289

    
290
    vm = util.get_vm(server_id, request.user)
291
    delete_instance(vm)
292
    return HttpResponse(status=204)
293

    
294
@util.api_method('POST')
295
def server_action(request, server_id):
296
    vm = util.get_vm(server_id, request.user)
297
    req = util.get_request_dict(request)
298
    if len(req) != 1:
299
        raise faults.BadRequest("Malformed request")
300

    
301
    key = req.keys()[0]
302
    val = req[key]
303

    
304
    try:
305
        assert isinstance(val, dict)
306
        return server_actions[key](request, vm, req[key])
307
    except KeyError:
308
        raise faults.BadRequest("Unknown action")
309
    except AssertionError:
310
        raise faults.BadRequest("Invalid argument")
311

    
312
@util.api_method('GET')
313
def list_addresses(request, server_id):
314
    # Normal Response Codes: 200, 203
315
    # Error Response Codes: computeFault (400, 500),
316
    #                       serviceUnavailable (503),
317
    #                       unauthorized (401),
318
    #                       badRequest (400),
319
    #                       overLimit (413)
320

    
321
    vm = util.get_vm(server_id, request.user)
322
    addresses = [nic_to_dict(nic) for nic in vm.nics.all()]
323
    
324
    if request.serialization == 'xml':
325
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
326
    else:
327
        data = json.dumps({'addresses': {'values': addresses}})
328

    
329
    return HttpResponse(data, status=200)
330

    
331
@util.api_method('GET')
332
def list_addresses_by_network(request, server_id, network_id):
333
    # Normal Response Codes: 200, 203
334
    # Error Response Codes: computeFault (400, 500),
335
    #                       serviceUnavailable (503),
336
    #                       unauthorized (401),
337
    #                       badRequest (400),
338
    #                       itemNotFound (404),
339
    #                       overLimit (413)
340
    
341
    owner = request.user
342
    machine = util.get_vm(server_id, owner)
343
    network = util.get_network(network_id, owner)
344
    nic = util.get_nic(machine, network)
345
    address = nic_to_dict(nic)
346
    
347
    if request.serialization == 'xml':
348
        data = render_to_string('address.xml', {'address': address})
349
    else:
350
        data = json.dumps({'network': address})
351

    
352
    return HttpResponse(data, status=200)
353

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

    
363
    vm = util.get_vm(server_id, request.user)
364
    metadata = metadata_to_dict(vm)
365
    return util.render_metadata(request, metadata, use_values=True, status=200)
366

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

    
378
    vm = util.get_vm(server_id, request.user)
379
    req = util.get_request_dict(request)
380
    try:
381
        metadata = req['metadata']
382
        assert isinstance(metadata, dict)
383
    except (KeyError, AssertionError):
384
        raise faults.BadRequest("Malformed request")
385

    
386
    updated = {}
387

    
388
    for key, val in metadata.items():
389
        try:
390
            meta = VirtualMachineMetadata.objects.get(meta_key=key, vm=vm)
391
            meta.meta_value = val
392
            meta.save()
393
            updated[key] = val
394
        except VirtualMachineMetadata.DoesNotExist:
395
            pass    # Ignore non-existent metadata
396
    
397
    if updated:
398
        vm.save()
399
    
400
    return util.render_metadata(request, updated, status=201)
401

    
402
@util.api_method('GET')
403
def get_metadata_item(request, server_id, key):
404
    # Normal Response Codes: 200, 203
405
    # Error Response Codes: computeFault (400, 500),
406
    #                       serviceUnavailable (503),
407
    #                       unauthorized (401),
408
    #                       itemNotFound (404),
409
    #                       badRequest (400),
410
    #                       overLimit (413)
411

    
412
    vm = util.get_vm(server_id, request.user)
413
    meta = util.get_vm_meta(vm, key)
414
    return util.render_meta(request, meta, status=200)
415

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

    
428
    vm = util.get_vm(server_id, request.user)
429
    req = util.get_request_dict(request)
430
    try:
431
        metadict = req['meta']
432
        assert isinstance(metadict, dict)
433
        assert len(metadict) == 1
434
        assert key in metadict
435
    except (KeyError, AssertionError):
436
        raise faults.BadRequest("Malformed request")
437
    
438
    meta, created = VirtualMachineMetadata.objects.get_or_create(
439
        meta_key=key,
440
        vm=vm)
441
    
442
    meta.meta_value = metadict[key]
443
    meta.save()
444
    vm.save()
445
    return util.render_meta(request, meta, status=201)
446

    
447
@util.api_method('DELETE')
448
def delete_metadata_item(request, server_id, key):
449
    # Normal Response Code: 204
450
    # Error Response Codes: computeFault (400, 500),
451
    #                       serviceUnavailable (503),
452
    #                       unauthorized (401),
453
    #                       itemNotFound (404),
454
    #                       badRequest (400),
455
    #                       buildInProgress (409),
456
    #                       badMediaType(415),
457
    #                       overLimit (413),
458

    
459
    vm = util.get_vm(server_id, request.user)
460
    meta = util.get_vm_meta(vm, key)
461
    meta.delete()
462
    vm.save()
463
    return HttpResponse(status=204)
464

    
465
@util.api_method('GET')
466
def server_stats(request, server_id):
467
    # Normal Response Codes: 200
468
    # Error Response Codes: computeFault (400, 500),
469
    #                       serviceUnavailable (503),
470
    #                       unauthorized (401),
471
    #                       badRequest (400),
472
    #                       itemNotFound (404),
473
    #                       overLimit (413)
474
    
475
    vm = util.get_vm(server_id, request.user)
476
    #secret = util.encrypt(vm.backend_id)
477
    secret = vm.backend_id      # XXX disable backend id encryption
478
    
479
    stats = {
480
        'serverRef': vm.id,
481
        'refresh': settings.STATS_REFRESH_PERIOD,
482
        'cpuBar': settings.CPU_BAR_GRAPH_URL % secret,
483
        'cpuTimeSeries': settings.CPU_TIMESERIES_GRAPH_URL % secret,
484
        'netBar': settings.NET_BAR_GRAPH_URL % secret,
485
        'netTimeSeries': settings.NET_TIMESERIES_GRAPH_URL % secret}
486
    
487
    if request.serialization == 'xml':
488
        data = render_to_string('server_stats.xml', stats)
489
    else:
490
        data = json.dumps({'stats': stats})
491

    
492
    return HttpResponse(data, status=200)