Statistics
| Branch: | Tag: | Revision:

root / api / servers.py @ ccd0d474

History | View | Annotate | Download (16 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
)
61

    
62

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

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

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

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

    
99

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

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

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

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

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

    
138

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

    
148

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

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

    
177
    return HttpResponse(data, status=200)
178

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

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

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

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

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

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

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

    
265
    req = util.get_request_dict(request)
266

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

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

    
276
    return HttpResponse(status=204)
277

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

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

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

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

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

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

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

    
328
    return HttpResponse(data, status=200)
329

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

    
351
    return HttpResponse(data, status=200)
352

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

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

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

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

    
385
    updated = {}
386

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

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

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

    
415
@util.api_method('PUT')
416
def create_metadata_item(request, server_id, key):
417
    # Normal Response Code: 201
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
    vm = util.get_vm(server_id, request.user)
428
    req = util.get_request_dict(request)
429
    try:
430
        metadict = req['meta']
431
        assert isinstance(metadict, dict)
432
        assert len(metadict) == 1
433
        assert key in metadict
434
    except (KeyError, AssertionError):
435
        raise faults.BadRequest("Malformed request")
436
    
437
    meta, created = VirtualMachineMetadata.objects.get_or_create(
438
        meta_key=key,
439
        vm=vm)
440
    
441
    meta.meta_value = metadict[key]
442
    meta.save()
443
    vm.save()
444
    return util.render_meta(request, meta, status=201)
445

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

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