Statistics
| Branch: | Tag: | Revision:

root / api / handlers.py @ 4e6f9904

History | View | Annotate | Download (37.4 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
backend_prefix_id = settings.BACKEND_PREFIX_ID
30

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

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

    
57

    
58
class VersionHandler(AnonymousBaseHandler):
59
    allowed_methods = ('GET',)
60

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

    
78

    
79
class ServerHandler(BaseHandler):
80
    """Handler responsible for the Servers
81

82
     handles the listing of Virtual Machines, Creates and Destroys VM's
83

84
     @HTTP methods: POST, DELETE, PUT, GET
85
     @Parameters: POST data with the create data (cpu, ram, etc)
86
     @Responses: HTTP 200 if successfully call rapi, 304 if not modified, itemNotFound or serviceUnavailable otherwise
87

88
    """
89
    allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
90

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

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

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

    
128

    
129
    @paginator
130
    def read_all(self, request, detail=False):
131
        try:
132
            changes_since = request.GET.get("changes-since", 0)
133
            if changes_since:
134
                last_update = datetime.fromtimestamp(int(changes_since))
135
                virtual_servers = VirtualMachine.objects.filter(updated__gt=last_update)
136
                if not len(virtual_servers):
137
                    return notModified
138
            else:
139
                virtual_servers = VirtualMachine.objects.filter(deleted=False)
140
            #get all VM's for now, FIX it to take the user's VMs only yet. also don't get deleted VM's
141
        except Exception, e:
142
            raise fault.badRequest        
143
        try:
144
            if not detail:
145
                return { "servers": [ { "id": s.id, "name": s.name } for s in virtual_servers ] }
146
            else:
147
                virtual_servers_list = [{'status': server.deleted and "DELETED" or server.rsapi_state, 
148
                                         'flavorRef': server.flavor.id, 
149
                                         'name': server.name, 
150
                                         'id': server.id, 
151
                                         'created': server.created, 
152
                                         'updated': server.updated,
153
                                         'imageRef': server.sourceimage.id, 
154
                                         'hostId': server.hostid, 
155
                                         'progress': server.rsapi_state == 'ACTIVE' and 100 or 0, 
156
                                         #'metadata': {'Server_Label': server.description },
157
                                         'metadata':[{'meta': { 'key': {metadata.meta_key: metadata.meta_value}}} for metadata in server.virtualmachinemetadata_set.all()],                                    
158
                                         'addresses': {'public': { 'ip': {'addr': server.ipfour}, 'ip6': {'addr': server.ipsix}},'private': ''},      
159

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

    
167

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

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

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

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

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

    
264

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

    
271

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

    
295
        raise fault.itemNotFound
296

    
297

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

    
315

    
316

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

320
     handles Reboot, Shutdown and Start actions. 
321

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

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

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

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

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

    
351

    
352

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

356
     handles Reboot, Shutdown and Start actions. 
357

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

362
    """
363

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

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

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

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

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

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

    
407

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

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

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

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

    
421

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

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

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

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

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

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

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

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

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

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

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

    
565

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

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

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

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

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

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

    
611

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

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

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

621
    """
622

    
623

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

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

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

    
674
    def create(self, request):
675
        """Create a new image"""
676
        return accepted
677

    
678

    
679
class ImageMetadataHandler(BaseHandler):
680
    """Handles Metadata of a specific Image
681

682
    the handler Lists, Creates, Updates and Deletes Metadata values
683

684
    @HTTP methods: POST, DELETE, PUT, GET
685
    @Parameters: POST data with key value pairs
686

687
    """
688
    allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
689

    
690
    def read(self, request, id, key=None):
691
        """List metadata of the specific server"""
692
        if key is None:
693
            return self.read_allkeys(request, id)
694
        else:
695
            return self.read_onekey(request, id, key)
696

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

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

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

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

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

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

    
820

    
821
class SharedIPGroupHandler(BaseHandler):
822
    allowed_methods = ('GET', 'POST', 'DELETE')
823

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

    
833
    def create(self, request, id):
834
        """Creates a new Shared IP Group"""
835
        return created
836

    
837
    def delete(self, request, id):
838
        """Deletes a Shared IP Group"""
839
        raise fault.itemNotFound
840

    
841

    
842
class VirtualMachineGroupHandler(BaseHandler):
843
    """Handler responsible for Virtual Machine Groups
844

845
     creates, lists, deletes virtual machine groups
846

847
     @HTTP methods: GET, POST, DELETE
848
     @Parameters: POST data 
849
     @Responses: HTTP 202 if successfully get the Groups list, itemNotFound, serviceUnavailable otherwise
850

851
    """
852

    
853
    allowed_methods = ('GET', 'POST', 'DELETE')
854

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

    
871
                return { "group":  {'id': vmgroup.id, \
872
                  'name': vmgroup.name,  \
873
                   'server_id': [machine.id for machine in vmgroup.machines.all()] \
874
                   } }
875

    
876

    
877
        except VirtualMachineGroup.DoesNotExist:
878
                    raise fault.itemNotFound
879
        except VirtualMachineGroup.MultipleObjectsReturned:
880
                    raise fault.serviceUnavailable
881
        except Exception, e:
882
                    log.exception('Unexpected error: %s' % e)
883
                    raise fault.serviceUnavailable
884

    
885

    
886

    
887
    def create(self, request, id):
888
        """Creates a Group"""
889
        return created
890

    
891
    def delete(self, request, id):
892
        """Deletes a  Group"""
893
        raise fault.itemNotFound
894

    
895

    
896
class LimitHandler(BaseHandler):
897
    allowed_methods = ('GET',)
898

    
899
    # XXX: hookup with @throttle
900

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

    
949
    absolute = {
950
        "maxTotalRAMSize" : 51200,
951
        "maxIPGroups" : 50,
952
        "maxIPGroupMembers" : 25
953
    }
954

    
955
    def read(self, request):
956
        return { "limits": {
957
                "rate": self.rate,
958
                "absolute": self.absolute,
959
               }
960
            }
961

    
962

    
963
class DiskHandler(BaseHandler):
964
    allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
965

    
966
    def read(self, request, id=None):
967
        """List Disks"""
968
        if id is None:
969
            return self.read_all(request)
970
        elif id == "detail":
971
            return self.read_all(request, detail=True)
972
        else:
973
            return self.read_one(request, id)
974

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

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

    
1009
    def create(self, request):
1010
        """Create a new Disk"""
1011
        # FIXME Create a partial DB entry, 
1012
        # then call the backend for actual creation
1013
        pass
1014

    
1015
    def update(self, request, id):
1016
        """Rename the Disk with the specified id"""
1017
        # FIXME Change the Disk's name in the DB
1018
        pass
1019

    
1020
    def delete(self, request, id):
1021
        """Destroy the Disk with the specified id"""
1022
        # Call the backend for actual destruction
1023
        pass