Statistics
| Branch: | Tag: | Revision:

root / api / handlers.py @ 92c53da1

History | View | Annotate | Download (37 kB)

1
# vim: ts=4 sts=4 et ai sw=4 fileencoding=utf-8
2
#
3
# Copyright © 2010 Greek Research and Technology Network
4

    
5
import simplejson as json
6
from django.conf import settings
7
from django.http import HttpResponse
8
from piston.handler import BaseHandler, AnonymousBaseHandler
9
from synnefo.api.faults import fault, noContent, accepted, created, notModified
10
from synnefo.api.helpers import instance_to_server, paginator
11
from synnefo.util.rapi import GanetiRapiClient, GanetiApiError, CertificateError
12
from synnefo.db.models import *
13
from time import sleep
14
import random
15
import string
16
import logging
17
from datetime import datetime
18

    
19
log = logging.getLogger('synnefo.api.handlers')
20

    
21
try:
22
    rapi = GanetiRapiClient(*settings.GANETI_CLUSTER_INFO)
23
    rapi.GetVersion()
24
except Exception, e:
25
    log.exception('Unexpected error: %s' % e)
26
    raise fault.serviceUnavailable
27
#If we can't connect to the rapi successfully, don't do anything
28

    
29
VERSIONS = [
30
    {
31
        "status": "CURRENT",
32
        "id": "v1.0",
33
        "docURL" : "http://docs.rackspacecloud.com/servers/api/v1.0/cs-devguide-20110112.pdf",
34
        "wadl" : "http://docs.rackspacecloud.com/servers/api/v1.0/application.wadl"
35
    },
36
    {
37
        "status": "CURRENT",
38
        "id": "v1.1",
39
        "docURL" : "http://docs.openstack.org/openstack-compute/developer/content/",
40
        "wadl" : "None yet"
41
    },
42
    {
43
        "status": "CURRENT",
44
        "id": "v1.0grnet1",
45
        "docURL" : "None yet",
46
        "wad1" : "None yet"
47
    }
48
]
49

    
50
#read is called on GET requests
51
#create is called on POST, and creates new objects
52
#update is called on PUT, and should update an existing product
53
#delete is called on DELETE, and should delete an existing object
54

    
55

    
56
class VersionHandler(AnonymousBaseHandler):
57
    allowed_methods = ('GET',)
58

    
59
    def read(self, request, number=None):
60
        try:
61
            if number is None:
62
                versions = map(lambda v: {
63
                            "status": v["status"],
64
                            "id": v["id"],
65
                        }, VERSIONS)
66
                return { "versions": versions }
67
            else:
68
                for version in VERSIONS:
69
                    if version["id"] == number:
70
                        return { "version": version }
71
                raise fault.itemNotFound
72
        except Exception, e:
73
            log.exception('Unexpected error: %s' % e)
74
            raise fault.serviceUnavailable
75

    
76

    
77
class ServerHandler(BaseHandler):
78
    """Handler responsible for the Servers
79

80
     handles the listing of Virtual Machines, Creates and Destroys VM's
81

82
     @HTTP methods: POST, DELETE, PUT, GET
83
     @Parameters: POST data with the create data (cpu, ram, etc)
84
     @Responses: HTTP 202 if successfully call rapi, itemNotFound, serviceUnavailable otherwise
85

86
    """
87
    allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
88

    
89
    def read(self, request, id=None):
90
        from time import sleep
91
        sleep(0.5)
92
        #TODO: delete the sleep once the mock objects are removed
93
        if id is None:
94
            return self.read_all(request)
95
        elif id == "detail":
96
            return self.read_all(request, detail=True)
97
        else:
98
            return self.read_one(request, id)
99

    
100
    def read_one(self, request, id):
101
        try:
102
            server = VirtualMachine.objects.get(id=id)
103

    
104
            server = {'status': server.rsapi_state, 
105
                     'flavorRef': server.flavor.id, 
106
                     'name': server.name, 
107
                     'description': server.description, 
108
                     'id': server.id, 
109
                     'imageRef': server.sourceimage.id,
110
                     'created': server.created, 
111
                     'updated': server.updated,
112
                     'hostId': server.hostid, 
113
                     'progress': server.rsapi_state == 'ACTIVE' and 100 or 0, 
114
                     #'metadata': {'Server_Label': server.description },
115
                     'metadata':[{'meta': { 'key': {metadata.meta_key: metadata.meta_value}}} for metadata in server.virtualmachinemetadata_set.all()],                                    
116
                     'addresses': {'public': { 'ip': {'addr': server.ipfour}, 'ip6': {'addr': server.ipsix}},'private': ''},      
117
                    }
118
            return { "server": server } 
119
        except VirtualMachine.DoesNotExist:
120
            raise fault.itemNotFound
121
        except VirtualMachine.MultipleObjectsReturned:
122
            raise fault.serviceUnavailable
123
        except Exception, e:
124
            log.exception('Unexpected error: %s' % e)
125
            raise fault.serviceUnavailable
126

    
127

    
128
    @paginator
129
    def read_all(self, request, detail=False):
130
        try:
131
            changes_since = request.GET.get("changes-since", 0)
132
            if changes_since:
133
                last_update = datetime.fromtimestamp(int(changes_since))
134
                virtual_servers = VirtualMachine.objects.filter(updated__gt=last_update)
135
                if not len(virtual_servers):
136
                    return notModified
137
            else:
138
                virtual_servers = VirtualMachine.objects.filter(deleted=False)
139
            #get all VM's for now, FIX it to take the user's VMs only yet. also don't get deleted VM's
140

    
141
            if not detail:
142
                return { "servers": [ { "id": s.id, "name": s.name } for s in virtual_servers ] }
143
            else:
144
                virtual_servers_list = [{'status': server.rsapi_state, 
145
                                         'flavorRef': server.flavor.id, 
146
                                         'name': server.name, 
147
                                         'id': server.id, 
148
                                         'description': server.description, 
149
                                         'created': server.created, 
150
                                         'updated': server.updated,
151
                                         'deleted': server.deleted,                     
152
                                         'imageRef': server.sourceimage.id, 
153
                                         'hostId': server.hostid, 
154
                                         'progress': server.rsapi_state == 'ACTIVE' and 100 or 0, 
155
                                         #'metadata': {'Server_Label': server.description },
156
                                         'metadata':[{'meta': { 'key': {metadata.meta_key: metadata.meta_value}}} for metadata in server.virtualmachinemetadata_set.all()],                                    
157
                                         'addresses': {'public': { 'ip': {'addr': server.ipfour}, 'ip6': {'addr': server.ipsix}},'private': ''},      
158

    
159
                                        } for server in virtual_servers]
160
                #pass some fake data regarding ip, since we don't have any such data            
161
                return { "servers":  virtual_servers_list }                
162
        except Exception, e:
163
            log.exception('Unexpected error: %s' % e)
164
            raise fault.serviceUnavailable
165

    
166

    
167
    def create(self, request):
168
        """ Parse RackSpace API create request to generate rapi create request
169
        
170
            TODO: auto generate and set password
171
        """
172
        # Check if we have all the necessary data in the JSON request       
173
        try:
174
            server = json.loads(request.raw_post_data)['server']
175
            name = server['name']
176
            flavorRef = server['flavorRef']
177
            flavor = Flavor.objects.get(id=flavorRef)
178
            imageRef = server['imageRef']
179
            image = Image.objects.get(id=imageRef)
180
            metadata = server['metadata']
181
            personality = server.get('personality', None)
182
        except (Flavor.DoesNotExist, Image.DoesNotExist):
183
            raise fault.itemNotFound
184
        except (Flavor.MultipleObjectsReturned, Image.MultipleObjectsReturned):
185
            raise fault.serviceUnavailable
186
        except Exception as e:
187
            log.exception('Malformed create request: %s - %s' % (e, request.raw_post_data))    
188
            raise fault.badRequest
189

    
190
        # TODO: Proper Authn, Authz
191
        # Everything belongs to a single SynnefoUser for now.
192
        try:          
193
            owner = SynnefoUser.objects.all()[0]
194
        except Exception as e:
195
            log.exception('Cannot find a single SynnefoUser in the DB: %s' % (e));
196
            raise fault.unauthorized
197

    
198
        # add the new VM to the local db
199
        try:
200
            vm = VirtualMachine.objects.create(sourceimage=image, ipfour='0.0.0.0', ipsix='::1', flavor=flavor, owner=owner)
201
        except Exception as e:
202
            log.exception("Can't save vm: %s" % e)
203
            raise fault.serviceUnavailable
204

    
205
        try:
206
            vm.name = name
207
            #vm.description = descr
208
            vm.save()            
209
            jobId = rapi.CreateInstance(
210
                'create',
211
                request.META['SERVER_NAME'] == 'testserver' and 'test-server' or vm.backend_id,
212
                'plain',
213
                # disk field of Flavor object is in GB, value specified here is in MB
214
                # FIXME: Always ask for a 2GB disk, current LVM physical groups are too small:
215
                # [{"size": flavor.disk * 1000}],
216
                [{"size": 2000}],
217
                [{}],
218
                #TODO: select OS from imageRef
219
                os='debootstrap+default',
220
                ip_check=False,
221
                name_check=False,
222
                #TODO: verify if this is necessary
223
                pnode = rapi.GetNodes()[0],
224
                # Dry run when called by unit tests
225
                dry_run = request.META['SERVER_NAME'] == 'testserver',
226
                beparams={
227
                            'auto_balance': True,
228
                            'vcpus': flavor.cpu,
229
                            'memory': flavor.ram,
230
                        },
231
                )
232
            log.info('created vm with %s cpus, %s ram and %s storage' % (flavor.cpu, flavor.ram, flavor.disk))
233
        except (GanetiApiError, CertificateError) as e:
234
            log.exception('CreateInstance failed: %s' % e)
235
            vm.deleted = True
236
            vm.save()
237
            raise fault.serviceUnavailable
238
        except Exception as e:
239
            log.exception('Unexpected error: %s' % e)
240
            vm.deleted = True
241
            vm.save()
242
            raise fault.serviceUnavailable            
243
        
244

    
245
        ret = {'server': {
246
                'id' : vm.id,
247
                'name' : vm.name,
248
                "imageRef" : imageRef,
249
                "flavorRef" : flavorRef,
250
                "hostId" : vm.hostid,
251
                "progress" : 0,
252
                "status" : 'BUILD',
253
                "adminPass" : self.random_password(),
254
                "metadata" : {"My Server Name" : vm.description},
255
                "addresses" : {
256
                    "public" : [  ],
257
                    "private" : [  ],
258
                    },
259
                },
260
        }
261
        return HttpResponse(json.dumps(ret), mimetype="application/json", status=202)
262

    
263

    
264
    def random_password(self):
265
        "return random password"
266
        number_of_chars = 8
267
        possible_chars = string.ascii_uppercase + string.ascii_lowercase + string.digits
268
        return ''.join(random.choice(possible_chars) for x in range(number_of_chars))
269

    
270

    
271
    def update(self, request, id):
272
        """Sets and updates Virtual Machine Metadata. 
273
 
274
        """
275
        try:
276
            metadata_request = json.loads(request.raw_post_data)['metadata']
277
            metadata_key = metadata_request.get('metadata_key')
278
            metadata_value = metadata_request.get('metadata_value')
279
 
280
            vm = VirtualMachine.objects.get(id=id)
281
            #we only update virtual machine's name atm
282
            if metadata_key == 'name':
283
                vm.name = metadata_value
284
                vm.save()
285
                return accepted
286
        except VirtualMachine.DoesNotExist:
287
            raise fault.itemNotFound
288
        except VirtualMachine.MultipleObjectsReturned:
289
            raise fault.serviceUnavailable
290
        except Exception, e:
291
            log.exception('Unexpected error: %s' % e)
292
            raise fault.serviceUnavailable
293

    
294
        raise fault.itemNotFound
295

    
296

    
297
    def delete(self, request, id):
298
        try:
299
            vm = VirtualMachine.objects.get(id=id)
300
            #TODO: set the status to DESTROYED
301
            vm.start_action('DESTROY')
302
            rapi.DeleteInstance(vm.backend_id)
303
            return accepted        
304
        except VirtualMachine.DoesNotExist:
305
            raise fault.itemNotFound
306
        except VirtualMachine.MultipleObjectsReturned:
307
            raise fault.serviceUnavailable
308
        except GanetiApiError, CertificateError:
309
            raise fault.serviceUnavailable
310
        except Exception, e:
311
            log.exception('Unexpected error: %s' % e)
312
            raise fault.serviceUnavailable
313

    
314

    
315

    
316
class ServerAddressHandler(BaseHandler):
317
    """Handler responsible for Server Addresses
318

319
     handles Reboot, Shutdown and Start actions. 
320

321
     @HTTP methods: GET
322
     @Parameters: Id of server and networkID (eg public, private)
323
     @Responses: HTTP 200 if successfully call rapi, itemNotFound, serviceUnavailable otherwise
324

325
    """
326
    allowed_methods = ('GET',)
327

    
328
    def read(self, request, id, networkID=None):
329
        """List IP addresses for a server"""
330

    
331
        try:
332
            server = VirtualMachine.objects.get(id=id)
333
            address =  {'public': { 'ip': {'addr': server.ipfour}, 'ip6': {'addr': server.ipsix}},'private': ''}                                          
334
        except VirtualMachine.DoesNotExist:
335
            raise fault.itemNotFound
336
        except VirtualMachine.MultipleObjectsReturned:
337
            raise fault.serviceUnavailable
338
        except Exception, e:
339
            log.exception('Unexpected error: %s' % e)
340
            raise fault.serviceUnavailable
341

    
342
        if networkID == "public":
343
            address = {'public': { 'ip': {'addr': server.ipfour}, 'ip6': {'addr': server.ipsix}}}                            
344
        elif networkID == "private":
345
            address = {'private': ''}    
346
        elif networkID != None:
347
            raise fault.badRequest
348
        return { "addresses": address } 
349

    
350

    
351

    
352
class ServerActionHandler(BaseHandler):
353
    """Handler responsible for Server Actions
354

355
     handles Reboot, Shutdown and Start actions. 
356

357
     @HTTP methods: POST, DELETE, PUT
358
     @Parameters: POST data with the action (reboot, shutdown, start)
359
     @Responses: HTTP 202 if successfully call rapi, itemNotFound, serviceUnavailable otherwise
360

361
    """
362

    
363
    allowed_methods = ('POST', 'DELETE',  'PUT')
364

    
365
    def create(self, request, id):
366
        """Reboot, Shutdown, Start virtual machine"""
367
        
368
        try:
369
            requested_action = json.loads(request.raw_post_data)
370
            reboot_request = requested_action.get('reboot', None)
371
            shutdown_request = requested_action.get('shutdown', None)
372
            start_request = requested_action.get('start', None)
373
            #action not implemented
374
            action = reboot_request and 'REBOOT' or shutdown_request and 'STOP' or start_request and 'START'
375

    
376
            if not action:
377
                raise fault.notImplemented 
378
            #test if we can get the vm
379
            vm = VirtualMachine.objects.get(id=id)
380
            vm.start_action(action)
381

    
382
            if reboot_request:
383
                rapi.RebootInstance(vm.backend_id)
384
            elif shutdown_request:
385
                rapi.ShutdownInstance(vm.backend_id)
386
            elif start_request:
387
                rapi.StartupInstance(vm.backend_id)
388
            return accepted
389
        except VirtualMachine.DoesNotExist:
390
            raise fault.itemNotFound
391
        except VirtualMachine.MultipleObjectsReturned:
392
            raise fault.serviceUnavailable
393
        except GanetiApiError, CertificateError:
394
            raise fault.serviceUnavailable
395
        except Exception, e:
396
            log.exception('Unexpected error: %s' % e)
397
            raise fault.serviceUnavailable
398

    
399
    def delete(self, request, id):
400
        """Delete an Instance"""
401
        return accepted
402

    
403
    def update(self, request, id):
404
        raise fault.itemNotFound
405

    
406

    
407
class ServerBackupHandler(BaseHandler):
408
    """ Backup Schedules are not implemented yet, return notImplemented """
409
    allowed_methods = ('GET', 'POST', 'DELETE')
410

    
411
    def read(self, request, id):
412
        raise fault.notImplemented
413

    
414
    def create(self, request, id):
415
        raise fault.notImplemented
416

    
417
    def delete(self, request, id):
418
        raise fault.notImplemented
419

    
420

    
421
class ServerMetadataHandler(BaseHandler):
422
    """Handles Metadata of a specific Server
423

424
    the handler Lists, Creates, Updates and Deletes Metadata values
425

426
    @HTTP methods: POST, DELETE, PUT, GET
427
    @Parameters: POST data with key value pairs
428

429
    """
430
    allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
431

    
432
    def read(self, request, id, key=None):
433
        """List metadata of the specific server"""
434
        if key is None:
435
            return self.read_allkeys(request, id)
436
        else:
437
            return self.read_onekey(request, id, key)
438

    
439
    def read_allkeys(self, request, id):
440
        """Returns all the key value pairs of the specified server"""
441
        try:
442
            server = VirtualMachine.objects.get(pk=id)
443
            return {
444
                "metadata": {
445
                    "values": [
446
                        {m.meta_key: m.meta_value} for m in server.virtualmachinemetadata_set.all()
447
                    ]
448
                }
449
            }
450
        except VirtualMachine.DoesNotExist:
451
            raise fault.itemNotFound
452
        except VirtualMachine.MultipleObjectsReturned:
453
            raise fault.serviceUnavailable
454
        except Exception, e:
455
            log.exception('Unexpected error: %s' % e)
456
            raise fault.serviceUnavailable
457
        
458
    def read_onekey(self, request, id, key):
459
        """Returns the specified metadata key of the specified server"""
460
        try:
461
            server = VirtualMachine.objects.get(pk=id)
462
            return {
463
                "metadata": {
464
                    "values": [
465
                        {m.meta_key: m.meta_value} for m in server.virtualmachinemetadata_set.filter(meta_key=key)
466
                    ]
467
                }
468
            }
469
        except VirtualMachineMetadata.DoesNotExist:
470
            raise fault.itemNotFound            
471
        except VirtualMachine.DoesNotExist:
472
            raise fault.itemNotFound
473
        except VirtualMachine.MultipleObjectsReturned:
474
            raise fault.serviceUnavailable
475
        except Exception, e:
476
            log.exception('Unexpected error: %s' % e)
477
            raise fault.serviceUnavailable
478

    
479
    def create(self, request, id, key=None):
480
        """Create or Update all metadata for the specified VM"""
481
        if key is not None:
482
            log.exception('The POST request should not pass a key in the URL')
483
            raise fault.badRequest
484
        try:
485
            metadata = json.loads(request.raw_post_data)['metadata']
486
        except Exception as e:
487
            log.exception('Malformed create request: %s - %s' % (e, request.raw_post_data))
488
            raise fault.badRequest
489

    
490
        try:
491
            vm = VirtualMachine.objects.get(pk=id)
492
            for x in metadata.keys():
493
                vm_meta, created = vm.virtualmachinemetadata_set.get_or_create(meta_key=x)
494
                vm_meta.meta_value = metadata[x] 
495
                vm_meta.save()
496
            return {
497
                "metadata": [{
498
                    "meta": { 
499
                        "key": {m.meta_key: m.meta_value}}} for m in vm.virtualmachinemetadata_set.all()]
500
            }        
501
        except VirtualMachine.DoesNotExist:
502
            raise fault.itemNotFound
503
        except VirtualMachine.MultipleObjectsReturned:
504
            raise fault.serviceUnavailable
505
        except VirtualMachineMetadata.DoesNotExist:
506
            raise fault.itemNotFound
507
        except VirtualMachineMetadata.MultipleObjectsReturned:
508
            raise fault.serviceUnavailable
509
        except Exception, e:
510
            log.exception('Unexpected error: %s' % e)
511
            raise fault.serviceUnavailable
512

    
513
    def update(self, request, id, key=None):
514
        """Update or Create the specified metadata key for the specified VM"""
515
        if key is None:
516
            log.exception('No metadata key specified in URL')
517
            raise fault.badRequest
518
        try:
519
            metadata = json.loads(request.raw_post_data)['meta']
520
            metadata_value = metadata[key]
521
        except Exception as e:
522
            log.exception('Malformed create request: %s - %s' % (e, request.raw_post_data))
523
            raise fault.badRequest
524

    
525
        try:
526
            server = VirtualMachine.objects.get(pk=id)
527
            vm_meta, created = server.virtualmachinemetadata_set.get_or_create(meta_key=key)
528
            vm_meta.meta_value = metadata_value 
529
            vm_meta.save()
530
            return {"meta": {vm_meta.meta_key: vm_meta.meta_value}}
531
        
532
        except VirtualMachine.DoesNotExist:
533
            raise fault.itemNotFound
534
        except VirtualMachine.MultipleObjectsReturned:
535
            raise fault.serviceUnavailable
536
        except VirtualMachineMetadata.DoesNotExist:
537
            raise fault.itemNotFound
538
        except VirtualMachineMetadata.MultipleObjectsReturned:
539
            raise fault.serviceUnavailable
540
        except Exception, e:
541
            log.exception('Unexpected error: %s' % e)
542
            raise fault.serviceUnavailable
543

    
544
    def delete(self, request, id, key=None):
545
        """Delete the specified metadata key"""
546
        if key is None:
547
            log.exception('No metadata key specified in URL')
548
            raise fault.badRequest
549
        try:
550
            server = VirtualMachine.objects.get(pk=id)
551
            server.virtualmachinemetadata_set.get(meta_key=key).delete()
552
        except VirtualMachineMetadata.DoesNotExist:
553
            raise fault.itemNotFound
554
        except VirtualMachine.DoesNotExist:
555
            raise fault.itemNotFound
556
        except VirtualMachineMetadata.MultipleObjectsReturned:
557
            raise fault.serviceUnavailable
558
        except VirtualMachine.MultipleObjectsReturned:
559
            raise fault.serviceUnavailable
560
        except Exception, e:
561
            log.exception('Unexpected error: %s' % e)
562
            raise fault.serviceUnavailable
563

    
564

    
565
class FlavorHandler(BaseHandler):
566
    """Handler responsible for Flavors
567

568
    """
569
    allowed_methods = ('GET',)
570

    
571
    def read(self, request, id=None):
572
        """
573
        List flavors or retrieve one
574

575
        Returns: OK
576
        Faults: cloudServersFault, serviceUnavailable, unauthorized,
577
                badRequest, itemNotFound
578
        """
579
        try:
580
            flavors = Flavor.objects.all()
581
            flavors = [ {'id': flavor.id, 'name': flavor.name, 'ram': flavor.ram, \
582
                     'disk': flavor.disk, 'cpu': flavor.cpu} for flavor in flavors]
583

    
584
            if id is None:
585
                simple = map(lambda v: {
586
                            "id": v['id'],
587
                            "name": v['name'],
588
                        }, flavors)
589
                return { "flavors": simple }
590
            elif id == "detail":
591
                return { "flavors": flavors }
592
            else:
593
                flavor = Flavor.objects.get(id=id)
594
                return { "flavor":  {
595
                    'id': flavor.id,
596
                    'name': flavor.name,
597
                    'ram': flavor.ram,
598
                    'disk': flavor.disk,  
599
                    'cpu': flavor.cpu,  
600
                   } }
601

    
602
        except Flavor.DoesNotExist:
603
            raise fault.itemNotFound
604
        except Flavor.MultipleObjectsReturned:
605
            raise fault.serviceUnavailable
606
        except Exception, e:
607
            log.exception('Unexpected error: %s' % e)
608
            raise fault.serviceUnavailable
609

    
610

    
611
class ImageHandler(BaseHandler):
612
    """Handler responsible for Images
613

614
     handles the listing, creation and delete of Images. 
615

616
     @HTTP methods: GET, POST
617
     @Parameters: POST data 
618
     @Responses: HTTP 202 if successfully create Image or get the Images list, itemNotFound, serviceUnavailable otherwise
619

620
    """
621

    
622

    
623
    allowed_methods = ('GET', 'POST')
624

    
625
    def read(self, request, id=None):
626
        """
627
        List images or retrieve one
628

629
        Returns: OK
630
        Faults: cloudServersFault, serviceUnavailable, unauthorized,
631
                badRequest, itemNotFound
632
        """
633
        try:
634
            images = Image.objects.all()
635
            images_list = [ {'created': image.created.isoformat(), 
636
                        'id': image.id,
637
                        'name': image.name,
638
                        'updated': image.updated.isoformat(),    
639
                        'description': image.description, 
640
                        'status': image.state, 
641
                        'progress': image.state == 'ACTIVE' and 100 or 0, 
642
                        'size': image.size, 
643
                        'serverId': image.sourcevm and image.sourcevm.id or ""
644
                       } for image in images]
645
            # Images info is stored in the DB. Ganeti is not aware of this
646
            if id == "detail":
647
                return { "images": images_list }
648
            elif id is None:
649
                return { "images": [ { "id": s['id'], "name": s['name'] } for s in images_list ] }
650
            else:        
651
                image = images.get(id=id)
652
                return { "image":  {'created': image.created.isoformat(), 
653
                    'id': image.id,
654
                    'name': image.name,
655
                    'updated': image.updated.isoformat(),    
656
                    'description': image.description, 
657
                    'status': image.state, 
658
                    'progress': image.state == 'ACTIVE' and 100 or 0, 
659
                    'size': image.size, 
660
                    'serverId': image.sourcevm and image.sourcevm.id or ""
661
                   } }
662
        except Image.DoesNotExist:
663
                    raise fault.itemNotFound
664
        except Image.MultipleObjectsReturned:
665
                    raise fault.serviceUnavailable
666
        except Exception, e:
667
                    log.exception('Unexpected error: %s' % e)
668
                    raise fault.serviceUnavailable
669

    
670
    def create(self, request):
671
        """Create a new image"""
672
        return accepted
673

    
674

    
675
class ImageMetadataHandler(BaseHandler):
676
    """Handles Metadata of a specific Image
677

678
    the handler Lists, Creates, Updates and Deletes Metadata values
679

680
    @HTTP methods: POST, DELETE, PUT, GET
681
    @Parameters: POST data with key value pairs
682

683
    """
684
    allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
685

    
686
    def read(self, request, id, key=None):
687
        """List metadata of the specific server"""
688
        if key is None:
689
            return self.read_allkeys(request, id)
690
        else:
691
            return self.read_onekey(request, id, key)
692

    
693
    def read_allkeys(self, request, id):
694
        """Returns all the key value pairs of the specified server"""
695
        try:
696
            image = Image.objects.get(pk=id)
697
            return {
698
                "metadata": [{
699
                    "meta": { 
700
                        "key": {m.meta_key: m.meta_value}}} for m in image.imagemetadata_set.all()]
701
            }
702
        except Image.DoesNotExist:
703
            raise fault.itemNotFound
704
        except Image.MultipleObjectsReturned:
705
            raise fault.serviceUnavailable
706
        except Exception, e:
707
            log.exception('Unexpected error: %s' % e)
708
            raise fault.serviceUnavailable
709
        
710
    def read_onekey(self, request, id, key):
711
        """Returns the specified metadata key of the specified server"""
712
        try:
713
            image = Image.objects.get(pk=id)
714
            return {
715
                "metadata": {
716
                    "values": [
717
                        {m.meta_key: m.meta_value} for m in image.imagemetadata_set.filter(meta_key=key)
718
                    ]
719
                }
720
            }
721
        except ImageMetadata.DoesNotExist:
722
            raise fault.itemNotFound            
723
        except Image.DoesNotExist:
724
            raise fault.itemNotFound
725
        except Image.MultipleObjectsReturned:
726
            raise fault.serviceUnavailable
727
        except Exception, e:
728
            log.exception('Unexpected error: %s' % e)
729
            raise fault.serviceUnavailable
730

    
731
    def create(self, request, id, key=None):
732
        """Create or Update all metadata for the specified Image"""
733
        if key is not None:
734
            log.exception('The POST request should not pass a key in the URL')
735
            raise fault.badRequest
736
        try:
737
            metadata = json.loads(request.raw_post_data)['metadata']
738
        except Exception as e:
739
            log.exception('Malformed create request: %s - %s' % (e, request.raw_post_data))
740
            raise fault.badRequest
741

    
742
        try:
743
            image = Image.objects.get(pk=id)
744
            for x in metadata.keys():
745
                img_meta, created = image.imagemetadata_set.get_or_create(meta_key=x)
746
                img_meta.meta_value = metadata[x] 
747
                img_meta.save()
748
            return {
749
                "metadata": [{
750
                    "meta": { 
751
                        "key": {m.meta_key: m.meta_value}}} for m in image.imagemetadata_set.all()]
752
            }        
753
        except Image.DoesNotExist:
754
            raise fault.itemNotFound
755
        except Image.MultipleObjectsReturned:
756
            raise fault.serviceUnavailable
757
        except ImageMetadata.DoesNotExist:
758
            raise fault.itemNotFound
759
        except ImageMetadata.MultipleObjectsReturned:
760
            raise fault.serviceUnavailable
761
        except Exception, e:
762
            log.exception('Unexpected error: %s' % e)
763
            raise fault.serviceUnavailable
764

    
765
    def update(self, request, id, key=None):
766
        """Update or Create the specified metadata key for the specified Image"""
767
        if key is None:
768
            log.exception('No metadata key specified in URL')
769
            raise fault.badRequest
770
        try:
771
            metadata = json.loads(request.raw_post_data)['meta']
772
            metadata_value = metadata[key]
773
        except Exception as e:
774
            log.exception('Malformed create request: %s - %s' % (e, request.raw_post_data))
775
            raise fault.badRequest
776

    
777
        try:
778
            image = Image.objects.get(pk=id)
779
            img_meta, created = image.imagemetadata_set.get_or_create(meta_key=key)
780
            img_meta.meta_value = metadata_value 
781
            img_meta.save()
782
            return {"meta": {img_meta.meta_key: img_meta.meta_value}}
783
        
784
        except Image.DoesNotExist:
785
            raise fault.itemNotFound
786
        except Image.MultipleObjectsReturned:
787
            raise fault.serviceUnavailable
788
        except ImageMetadata.DoesNotExist:
789
            raise fault.itemNotFound
790
        except ImageMetadata.MultipleObjectsReturned:
791
            raise fault.serviceUnavailable
792
        except Exception, e:
793
            log.exception('Unexpected error: %s' % e)
794
            raise fault.serviceUnavailable
795

    
796
    def delete(self, request, id, key=None):
797
        """Delete the specified metadata key"""
798
        if key is None:
799
            log.exception('No metadata key specified in URL')
800
            raise fault.badRequest
801
        try:
802
            image = Image.objects.get(pk=id)
803
            image.imagemetadata_set.get(meta_key=key).delete()
804
        except ImageMetadata.DoesNotExist:
805
            raise fault.itemNotFound
806
        except Image.DoesNotExist:
807
            raise fault.itemNotFound
808
        except ImageMetadata.MultipleObjectsReturned:
809
            raise fault.serviceUnavailable
810
        except Image.MultipleObjectsReturned:
811
            raise fault.serviceUnavailable
812
        except Exception, e:
813
            log.exception('Unexpected error: %s' % e)
814
            raise fault.serviceUnavailable
815

    
816

    
817
class SharedIPGroupHandler(BaseHandler):
818
    allowed_methods = ('GET', 'POST', 'DELETE')
819

    
820
    def read(self, request, id=None):
821
        """List Shared IP Groups"""
822
        if id is None:
823
            return {}
824
        elif id == "detail":
825
            return {}
826
        else:
827
            raise fault.itemNotFound
828

    
829
    def create(self, request, id):
830
        """Creates a new Shared IP Group"""
831
        return created
832

    
833
    def delete(self, request, id):
834
        """Deletes a Shared IP Group"""
835
        raise fault.itemNotFound
836

    
837

    
838
class VirtualMachineGroupHandler(BaseHandler):
839
    """Handler responsible for Virtual Machine Groups
840

841
     creates, lists, deletes virtual machine groups
842

843
     @HTTP methods: GET, POST, DELETE
844
     @Parameters: POST data 
845
     @Responses: HTTP 202 if successfully get the Groups list, itemNotFound, serviceUnavailable otherwise
846

847
    """
848

    
849
    allowed_methods = ('GET', 'POST', 'DELETE')
850

    
851
    def read(self, request, id=None):
852
        """List Groups"""
853
        try:
854
            vmgroups = VirtualMachineGroup.objects.all() 
855
            vmgroups_list = [ {'id': vmgroup.id, \
856
                  'name': vmgroup.name,  \
857
                   'server_id': [machine.id for machine in vmgroup.machines.all()] \
858
                   } for vmgroup in vmgroups]
859
            # Group info is stored in the DB. Ganeti is not aware of this
860
            if id == "detail":
861
                return { "groups": vmgroups_list }
862
            elif id is None:
863
                return { "groups": [ { "id": s['id'], "name": s['name'] } for s in vmgroups_list ] }
864
            else:
865
                vmgroup = vmgroups.get(id=id)
866

    
867
                return { "group":  {'id': vmgroup.id, \
868
                  'name': vmgroup.name,  \
869
                   'server_id': [machine.id for machine in vmgroup.machines.all()] \
870
                   } }
871

    
872

    
873
        except VirtualMachineGroup.DoesNotExist:
874
                    raise fault.itemNotFound
875
        except VirtualMachineGroup.MultipleObjectsReturned:
876
                    raise fault.serviceUnavailable
877
        except Exception, e:
878
                    log.exception('Unexpected error: %s' % e)
879
                    raise fault.serviceUnavailable
880

    
881

    
882

    
883
    def create(self, request, id):
884
        """Creates a Group"""
885
        return created
886

    
887
    def delete(self, request, id):
888
        """Deletes a  Group"""
889
        raise fault.itemNotFound
890

    
891

    
892
class LimitHandler(BaseHandler):
893
    allowed_methods = ('GET',)
894

    
895
    # XXX: hookup with @throttle
896

    
897
    rate = [
898
        {
899
           "verb" : "POST",
900
           "URI" : "*",
901
           "regex" : ".*",
902
           "value" : 10,
903
           "remaining" : 2,
904
           "unit" : "MINUTE",
905
           "resetTime" : 1244425439
906
        },
907
        {
908
           "verb" : "POST",
909
           "URI" : "*/servers",
910
           "regex" : "^/servers",
911
           "value" : 25,
912
           "remaining" : 24,
913
           "unit" : "DAY",
914
           "resetTime" : 1244511839
915
        },
916
        {
917
           "verb" : "PUT",
918
           "URI" : "*",
919
           "regex" : ".*",
920
           "value" : 10,
921
           "remaining" : 2,
922
           "unit" : "MINUTE",
923
           "resetTime" : 1244425439
924
        },
925
        {
926
           "verb" : "GET",
927
           "URI" : "*",
928
           "regex" : ".*",
929
           "value" : 3,
930
           "remaining" : 3,
931
           "unit" : "MINUTE",
932
           "resetTime" : 1244425439
933
        },
934
        {
935
           "verb" : "DELETE",
936
           "URI" : "*",
937
           "regex" : ".*",
938
           "value" : 100,
939
           "remaining" : 100,
940
           "unit" : "MINUTE",
941
           "resetTime" : 1244425439
942
        }
943
    ]
944

    
945
    absolute = {
946
        "maxTotalRAMSize" : 51200,
947
        "maxIPGroups" : 50,
948
        "maxIPGroupMembers" : 25
949
    }
950

    
951
    def read(self, request):
952
        return { "limits": {
953
                "rate": self.rate,
954
                "absolute": self.absolute,
955
               }
956
            }
957

    
958

    
959
class DiskHandler(BaseHandler):
960
    allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
961

    
962
    def read(self, request, id=None):
963
        """List Disks"""
964
        if id is None:
965
            return self.read_all(request)
966
        elif id == "detail":
967
            return self.read_all(request, detail=True)
968
        else:
969
            return self.read_one(request, id)
970

    
971
    def read_one(self, request, id):
972
        """List one Disk with the specified id with all details"""
973
        # FIXME Get detailed info from the DB 
974
        # for the Disk with the specified id
975
        try:
976
            disk = Disk.objects.get(pk=id)
977
            disk_details = {
978
                "id" : disk.id, 
979
                "name" : disk.name, 
980
                "size" : disk.size,
981
                "created" : disk.created, 
982
                "serverId" : disk.vm.id
983
            }
984
            return { "disks" : disk_details }
985
        except:
986
            raise fault.itemNotFound
987

    
988
    @paginator
989
    def read_all(self, request, detail=False):
990
        """List all Disks. If -detail- is set list them with all details"""
991
        if not detail:
992
            disks = Disk.objects.filter(owner=SynnefoUser.objects.all()[0])
993
            return { "disks": [ { "id": disk.id, "name": disk.name } for disk in disks ] }
994
        else:
995
            disks = Disk.objects.filter(owner=SynnefoUser.objects.all()[0])
996
            disks_details = [ {
997
                "id" : disk.id, 
998
                "name" : disk.name,
999
                "size" : disk.size,
1000
                "created" : disk.created, 
1001
                "serverId" : disk.vm.id,
1002
            } for disk in disks ]
1003
            return { "disks":  disks_details }                
1004

    
1005
    def create(self, request):
1006
        """Create a new Disk"""
1007
        # FIXME Create a partial DB entry, 
1008
        # then call the backend for actual creation
1009
        pass
1010

    
1011
    def update(self, request, id):
1012
        """Rename the Disk with the specified id"""
1013
        # FIXME Change the Disk's name in the DB
1014
        pass
1015

    
1016
    def delete(self, request, id):
1017
        """Destroy the Disk with the specified id"""
1018
        # Call the backend for actual destruction
1019
        pass