Statistics
| Branch: | Tag: | Revision:

root / api / servers.py @ 19da4325

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
from django.conf import settings
35
from django.conf.urls.defaults import patterns
36
from django.http import HttpResponse
37
from django.template.loader import render_to_string
38
from django.utils import simplejson as json
39

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

    
49
_log = log.get_logger("synnefo.api")
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' \
124
                        else vm.buildpercentage
125
        d['hostId'] = vm.hostid
126
        d['updated'] = util.isoformat(vm.updated)
127
        d['created'] = util.isoformat(vm.created)
128
        d['flavorRef'] = vm.flavor.id
129
        d['imageRef'] = vm.sourceimage.id
130

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

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

    
140

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

    
150

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

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

    
179
    return HttpResponse(data, status=200)
180

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

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

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

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

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

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

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

    
267
    req = util.get_request_dict(request)
268

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

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

    
278
    return HttpResponse(status=204)
279

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

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

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

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

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

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

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

    
330
    return HttpResponse(data, status=200)
331

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

    
353
    return HttpResponse(data, status=200)
354

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

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

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

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

    
387
    updated = {}
388

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

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

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

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

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

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

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

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

    
493
    return HttpResponse(data, status=200)