Statistics
| Branch: | Tag: | Revision:

root / api / handlers.py @ c5a032c4

History | View | Annotate | Download (37.9 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
        #changes_since should be on ISO 8601 format
132
        try:
133
            changes_since = request.GET.get("changes-since", 0)
134
            if changes_since:
135
                last_update = datetime.strptime(changes_since, "%Y-%m-%dT%H:%M:%S" )
136
                virtual_servers = VirtualMachine.objects.filter(updated__gt=last_update)
137
                if not len(virtual_servers):
138
                    return notModified
139
            else:
140
                virtual_servers = VirtualMachine.objects.filter(deleted=False)
141
            #get all VM's for now, FIX it to take the user's VMs only yet. also don't get deleted VM's
142
        except Exception, e:
143
            raise fault.badRequest        
144
        try:
145
            if not detail:
146
                return { "servers": [ { "id": s.id, "name": s.name } for s in virtual_servers ] }
147
            else:
148
                virtual_servers_list = [{'status': server.deleted and "DELETED" or server.rsapi_state, 
149
                                         'flavorRef': server.flavor.id, 
150
                                         'name': server.name, 
151
                                         'id': server.id, 
152
                                         'created': server.created, 
153
                                         'updated': server.updated,
154
                                         'imageRef': server.sourceimage.id, 
155
                                         'hostId': server.hostid, 
156
                                         'progress': server.rsapi_state == 'ACTIVE' and 100 or 0, 
157
                                         #'metadata': {'Server_Label': server.description },
158
                                         'metadata':[{'meta': { 'key': {metadata.meta_key: metadata.meta_value}}} for metadata in server.virtualmachinemetadata_set.all()],                                    
159
                                         'addresses': {'public': { 'ip': {'addr': server.ipfour}, 'ip6': {'addr': server.ipsix}},'private': ''},      
160

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

    
168

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

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

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

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

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

    
265

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

    
272

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

    
296
        raise fault.itemNotFound
297

    
298

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

    
316

    
317

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

321
     handles Reboot, Shutdown and Start actions. 
322

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

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

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

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

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

    
352

    
353

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

357
     handles Reboot, Shutdown and Start actions. 
358

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

363
    """
364

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

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

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

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

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

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

    
408

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

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

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

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

    
422

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

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

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

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

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

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

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

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

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

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

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

    
566

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

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

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

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

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

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

    
612

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

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

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

622
    """
623

    
624

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

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

631
        Returns: OK
632
        Faults: cloudServersFault, serviceUnavailable, unauthorized,
633
                badRequest, itemNotFound
634
        """
635

    
636
        #changes_since should be on ISO 8601 format
637
        try:
638
            changes_since = request.GET.get("changes-since", 0)
639
            if changes_since:
640
                last_update = datetime.strptime(changes_since, "%Y-%m-%dT%H:%M:%S" )
641
                images = Image.objects.filter(updated__gt=last_update)
642
                if not len(images):
643
                    return notModified
644
            else:
645
                images = Image.objects.all()
646
        except Exception, e:
647
            raise fault.badRequest        
648
        try:
649
            images_list = [ {'created': image.created.isoformat(), 
650
                        'id': image.id,
651
                        'name': image.name,
652
                        'updated': image.updated.isoformat(),    
653
                        'status': image.state, 
654
                        'progress': image.state == 'ACTIVE' and 100 or 0, 
655
                        'size': image.size, 
656
                        'serverId': image.sourcevm and image.sourcevm.id or "",
657
                        #'metadata':[{'meta': { 'key': {metadata.meta_key: metadata.meta_value}}} for metadata in image.imagemetadata_set.all()]
658
                        'metadata':{'meta': { 'key': {'description': image.description}}},
659
                       } for image in images]
660
            # Images info is stored in the DB. Ganeti is not aware of this
661
            if id == "detail":
662
                return { "images": images_list }
663
            elif id is None:
664
                return { "images": [ { "id": s['id'], "name": s['name'] } for s in images_list ] }
665
            else:        
666
                image = images.get(id=id)
667
                return { "image":  {'created': image.created.isoformat(), 
668
                    'id': image.id,
669
                    'name': image.name,
670
                    'updated': image.updated.isoformat(),    
671
                    'description': image.description, 
672
                    'status': image.state, 
673
                    'progress': image.state == 'ACTIVE' and 100 or 0, 
674
                    'size': image.size, 
675
                    'serverId': image.sourcevm and image.sourcevm.id or "",
676
                    #'metadata':[{'meta': { 'key': {metadata.meta_key: metadata.meta_value}}} for metadata in image.imagemetadata_set.all()]
677
                    'metadata':{'meta': { 'key': {'description': image.description}}},
678
                   } }
679
        except Image.DoesNotExist:
680
                    raise fault.itemNotFound
681
        except Image.MultipleObjectsReturned:
682
                    raise fault.serviceUnavailable
683
        except Exception, e:
684
                    log.exception('Unexpected error: %s' % e)
685
                    raise fault.serviceUnavailable
686

    
687
    def create(self, request):
688
        """Create a new image"""
689
        return accepted
690

    
691

    
692
class ImageMetadataHandler(BaseHandler):
693
    """Handles Metadata of a specific Image
694

695
    the handler Lists, Creates, Updates and Deletes Metadata values
696

697
    @HTTP methods: POST, DELETE, PUT, GET
698
    @Parameters: POST data with key value pairs
699

700
    """
701
    allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
702

    
703
    def read(self, request, id, key=None):
704
        """List metadata of the specific server"""
705
        if key is None:
706
            return self.read_allkeys(request, id)
707
        else:
708
            return self.read_onekey(request, id, key)
709

    
710
    def read_allkeys(self, request, id):
711
        """Returns all the key value pairs of the specified server"""
712
        try:
713
            image = Image.objects.get(pk=id)
714
            return {
715
                "metadata": [{
716
                    "meta": { 
717
                        "key": {m.meta_key: m.meta_value}}} for m in image.imagemetadata_set.all()]
718
            }
719
        except Image.DoesNotExist:
720
            raise fault.itemNotFound
721
        except Image.MultipleObjectsReturned:
722
            raise fault.serviceUnavailable
723
        except Exception, e:
724
            log.exception('Unexpected error: %s' % e)
725
            raise fault.serviceUnavailable
726
        
727
    def read_onekey(self, request, id, key):
728
        """Returns the specified metadata key of the specified server"""
729
        try:
730
            image = Image.objects.get(pk=id)
731
            return {
732
                "metadata": {
733
                    "values": [
734
                        {m.meta_key: m.meta_value} for m in image.imagemetadata_set.filter(meta_key=key)
735
                    ]
736
                }
737
            }
738
        except ImageMetadata.DoesNotExist:
739
            raise fault.itemNotFound            
740
        except Image.DoesNotExist:
741
            raise fault.itemNotFound
742
        except Image.MultipleObjectsReturned:
743
            raise fault.serviceUnavailable
744
        except Exception, e:
745
            log.exception('Unexpected error: %s' % e)
746
            raise fault.serviceUnavailable
747

    
748
    def create(self, request, id, key=None):
749
        """Create or Update all metadata for the specified Image"""
750
        if key is not None:
751
            log.exception('The POST request should not pass a key in the URL')
752
            raise fault.badRequest
753
        try:
754
            metadata = json.loads(request.raw_post_data)['metadata']
755
        except Exception as e:
756
            log.exception('Malformed create request: %s - %s' % (e, request.raw_post_data))
757
            raise fault.badRequest
758

    
759
        try:
760
            image = Image.objects.get(pk=id)
761
            for x in metadata.keys():
762
                img_meta, created = image.imagemetadata_set.get_or_create(meta_key=x)
763
                img_meta.meta_value = metadata[x] 
764
                img_meta.save()
765
            return {
766
                "metadata": [{
767
                    "meta": { 
768
                        "key": {m.meta_key: m.meta_value}}} for m in image.imagemetadata_set.all()]
769
            }        
770
        except Image.DoesNotExist:
771
            raise fault.itemNotFound
772
        except Image.MultipleObjectsReturned:
773
            raise fault.serviceUnavailable
774
        except ImageMetadata.DoesNotExist:
775
            raise fault.itemNotFound
776
        except ImageMetadata.MultipleObjectsReturned:
777
            raise fault.serviceUnavailable
778
        except Exception, e:
779
            log.exception('Unexpected error: %s' % e)
780
            raise fault.serviceUnavailable
781

    
782
    def update(self, request, id, key=None):
783
        """Update or Create the specified metadata key for the specified Image"""
784
        if key is None:
785
            log.exception('No metadata key specified in URL')
786
            raise fault.badRequest
787
        try:
788
            metadata = json.loads(request.raw_post_data)['meta']
789
            metadata_value = metadata[key]
790
        except Exception as e:
791
            log.exception('Malformed create request: %s - %s' % (e, request.raw_post_data))
792
            raise fault.badRequest
793

    
794
        try:
795
            image = Image.objects.get(pk=id)
796
            img_meta, created = image.imagemetadata_set.get_or_create(meta_key=key)
797
            img_meta.meta_value = metadata_value 
798
            img_meta.save()
799
            return {"meta": {img_meta.meta_key: img_meta.meta_value}}
800
        
801
        except Image.DoesNotExist:
802
            raise fault.itemNotFound
803
        except Image.MultipleObjectsReturned:
804
            raise fault.serviceUnavailable
805
        except ImageMetadata.DoesNotExist:
806
            raise fault.itemNotFound
807
        except ImageMetadata.MultipleObjectsReturned:
808
            raise fault.serviceUnavailable
809
        except Exception, e:
810
            log.exception('Unexpected error: %s' % e)
811
            raise fault.serviceUnavailable
812

    
813
    def delete(self, request, id, key=None):
814
        """Delete the specified metadata key"""
815
        if key is None:
816
            log.exception('No metadata key specified in URL')
817
            raise fault.badRequest
818
        try:
819
            image = Image.objects.get(pk=id)
820
            image.imagemetadata_set.get(meta_key=key).delete()
821
        except ImageMetadata.DoesNotExist:
822
            raise fault.itemNotFound
823
        except Image.DoesNotExist:
824
            raise fault.itemNotFound
825
        except ImageMetadata.MultipleObjectsReturned:
826
            raise fault.serviceUnavailable
827
        except Image.MultipleObjectsReturned:
828
            raise fault.serviceUnavailable
829
        except Exception, e:
830
            log.exception('Unexpected error: %s' % e)
831
            raise fault.serviceUnavailable
832

    
833

    
834
class SharedIPGroupHandler(BaseHandler):
835
    allowed_methods = ('GET', 'POST', 'DELETE')
836

    
837
    def read(self, request, id=None):
838
        """List Shared IP Groups"""
839
        if id is None:
840
            return {}
841
        elif id == "detail":
842
            return {}
843
        else:
844
            raise fault.itemNotFound
845

    
846
    def create(self, request, id):
847
        """Creates a new Shared IP Group"""
848
        return created
849

    
850
    def delete(self, request, id):
851
        """Deletes a Shared IP Group"""
852
        raise fault.itemNotFound
853

    
854

    
855
class VirtualMachineGroupHandler(BaseHandler):
856
    """Handler responsible for Virtual Machine Groups
857

858
     creates, lists, deletes virtual machine groups
859

860
     @HTTP methods: GET, POST, DELETE
861
     @Parameters: POST data 
862
     @Responses: HTTP 202 if successfully get the Groups list, itemNotFound, serviceUnavailable otherwise
863

864
    """
865

    
866
    allowed_methods = ('GET', 'POST', 'DELETE')
867

    
868
    def read(self, request, id=None):
869
        """List Groups"""
870
        try:
871
            vmgroups = VirtualMachineGroup.objects.all() 
872
            vmgroups_list = [ {'id': vmgroup.id, \
873
                  'name': vmgroup.name,  \
874
                   'server_id': [machine.id for machine in vmgroup.machines.all()] \
875
                   } for vmgroup in vmgroups]
876
            # Group info is stored in the DB. Ganeti is not aware of this
877
            if id == "detail":
878
                return { "groups": vmgroups_list }
879
            elif id is None:
880
                return { "groups": [ { "id": s['id'], "name": s['name'] } for s in vmgroups_list ] }
881
            else:
882
                vmgroup = vmgroups.get(id=id)
883

    
884
                return { "group":  {'id': vmgroup.id, \
885
                  'name': vmgroup.name,  \
886
                   'server_id': [machine.id for machine in vmgroup.machines.all()] \
887
                   } }
888

    
889

    
890
        except VirtualMachineGroup.DoesNotExist:
891
                    raise fault.itemNotFound
892
        except VirtualMachineGroup.MultipleObjectsReturned:
893
                    raise fault.serviceUnavailable
894
        except Exception, e:
895
                    log.exception('Unexpected error: %s' % e)
896
                    raise fault.serviceUnavailable
897

    
898

    
899

    
900
    def create(self, request, id):
901
        """Creates a Group"""
902
        return created
903

    
904
    def delete(self, request, id):
905
        """Deletes a  Group"""
906
        raise fault.itemNotFound
907

    
908

    
909
class LimitHandler(BaseHandler):
910
    allowed_methods = ('GET',)
911

    
912
    # XXX: hookup with @throttle
913

    
914
    rate = [
915
        {
916
           "verb" : "POST",
917
           "URI" : "*",
918
           "regex" : ".*",
919
           "value" : 10,
920
           "remaining" : 2,
921
           "unit" : "MINUTE",
922
           "resetTime" : 1244425439
923
        },
924
        {
925
           "verb" : "POST",
926
           "URI" : "*/servers",
927
           "regex" : "^/servers",
928
           "value" : 25,
929
           "remaining" : 24,
930
           "unit" : "DAY",
931
           "resetTime" : 1244511839
932
        },
933
        {
934
           "verb" : "PUT",
935
           "URI" : "*",
936
           "regex" : ".*",
937
           "value" : 10,
938
           "remaining" : 2,
939
           "unit" : "MINUTE",
940
           "resetTime" : 1244425439
941
        },
942
        {
943
           "verb" : "GET",
944
           "URI" : "*",
945
           "regex" : ".*",
946
           "value" : 3,
947
           "remaining" : 3,
948
           "unit" : "MINUTE",
949
           "resetTime" : 1244425439
950
        },
951
        {
952
           "verb" : "DELETE",
953
           "URI" : "*",
954
           "regex" : ".*",
955
           "value" : 100,
956
           "remaining" : 100,
957
           "unit" : "MINUTE",
958
           "resetTime" : 1244425439
959
        }
960
    ]
961

    
962
    absolute = {
963
        "maxTotalRAMSize" : 51200,
964
        "maxIPGroups" : 50,
965
        "maxIPGroupMembers" : 25
966
    }
967

    
968
    def read(self, request):
969
        return { "limits": {
970
                "rate": self.rate,
971
                "absolute": self.absolute,
972
               }
973
            }
974

    
975

    
976
class DiskHandler(BaseHandler):
977
    allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
978

    
979
    def read(self, request, id=None):
980
        """List Disks"""
981
        if id is None:
982
            return self.read_all(request)
983
        elif id == "detail":
984
            return self.read_all(request, detail=True)
985
        else:
986
            return self.read_one(request, id)
987

    
988
    def read_one(self, request, id):
989
        """List one Disk with the specified id with all details"""
990
        # FIXME Get detailed info from the DB 
991
        # for the Disk with the specified id
992
        try:
993
            disk = Disk.objects.get(pk=id)
994
            disk_details = {
995
                "id" : disk.id, 
996
                "name" : disk.name, 
997
                "size" : disk.size,
998
                "created" : disk.created, 
999
                "serverId" : disk.vm.id
1000
            }
1001
            return { "disks" : disk_details }
1002
        except:
1003
            raise fault.itemNotFound
1004

    
1005
    @paginator
1006
    def read_all(self, request, detail=False):
1007
        """List all Disks. If -detail- is set list them with all details"""
1008
        if not detail:
1009
            disks = Disk.objects.filter(owner=SynnefoUser.objects.all()[0])
1010
            return { "disks": [ { "id": disk.id, "name": disk.name } for disk in disks ] }
1011
        else:
1012
            disks = Disk.objects.filter(owner=SynnefoUser.objects.all()[0])
1013
            disks_details = [ {
1014
                "id" : disk.id, 
1015
                "name" : disk.name,
1016
                "size" : disk.size,
1017
                "created" : disk.created, 
1018
                "serverId" : disk.vm.id,
1019
            } for disk in disks ]
1020
            return { "disks":  disks_details }                
1021

    
1022
    def create(self, request):
1023
        """Create a new Disk"""
1024
        # FIXME Create a partial DB entry, 
1025
        # then call the backend for actual creation
1026
        pass
1027

    
1028
    def update(self, request, id):
1029
        """Rename the Disk with the specified id"""
1030
        # FIXME Change the Disk's name in the DB
1031
        pass
1032

    
1033
    def delete(self, request, id):
1034
        """Destroy the Disk with the specified id"""
1035
        # Call the backend for actual destruction
1036
        pass