Statistics
| Branch: | Tag: | Revision:

root / api / handlers.py @ 37a57502

History | View | Annotate | Download (17.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 json
6
from django.conf import settings
7
from piston.handler import BaseHandler, AnonymousBaseHandler
8
from synnefo.api.faults import fault, noContent, accepted, created
9
from synnefo.api.helpers import instance_to_server, paginator
10
from synnefo.util.rapi import GanetiRapiClient, GanetiApiError, CertificateError
11
from synnefo.db.models import *
12

    
13
try:
14
    rapi = GanetiRapiClient(*settings.GANETI_CLUSTER_INFO)
15
    rapi.GetVersion()
16
except Exception, e:
17
    raise fault.serviceUnavailable
18
#If we can't connect to the rapi successfully, don't do anything
19
#TODO: add logging/admin alerting
20

    
21
backend_prefix_id = settings.BACKEND_PREFIX_ID
22

    
23
VERSIONS = [
24
    {
25
        "status": "CURRENT",
26
        "id": "v1.0",
27
        "docURL" : "http://docs.rackspacecloud.com/servers/api/v1.0/cs-devguide-20090714.pdf ",
28
        "wadl" : "http://docs.rackspacecloud.com/servers/api/v1.0/application.wadl"
29
    },
30
    {
31
        "status": "CURRENT",
32
        "id": "v1.0grnet1",
33
        "docURL" : "None yet",
34
        "wad1" : "None yet"
35
    }
36
]
37

    
38
#read is called on GET requests
39
#create is called on POST, and creates new objects
40
#update is called on PUT, and should update an existing product
41
#delete is called on DELETE, and should delete an existing object
42

    
43

    
44
class VersionHandler(AnonymousBaseHandler):
45
    allowed_methods = ('GET',)
46

    
47
    def read(self, request, number=None):
48
        if number is None:
49
            versions = map(lambda v: {
50
                        "status": v["status"],
51
                        "id": v["id"],
52
                    }, VERSIONS)
53
            return { "versions": versions }
54
        else:
55
            for version in VERSIONS:
56
                if version["id"] == number:
57
                    return { "version": version }
58
            raise fault.itemNotFound
59

    
60

    
61
class ServerHandler(BaseHandler):
62
    """Handler responsible for the Servers
63

64
     handles the listing of Virtual Machines, Creates and Destroys VM's
65

66
     @HTTP methods: POST, DELETE, PUT, GET
67
     @Parameters: POST data with the create data (cpu, ram, etc)
68
     @Responses: HTTP 202 if successfully call rapi, itemNotFound, serviceUnavailable otherwise
69

70
    """
71
    allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
72

    
73
    def read(self, request, id=None):
74
        from time import sleep
75
        sleep(0.5)
76
        #TODO: delete the sleep once the mock objects are removed
77
        if id is None:
78
            return self.read_all(request)
79
        elif id == "detail":
80
            return self.read_all(request, detail=True)
81
        else:
82
            return self.read_one(request, id)
83

    
84
    def read_one(self, request, id):
85
        try:
86
            server = VirtualMachine.objects.get(id=id)
87
        except VirtualMachine.DoesNotExist:
88
            raise fault.itemNotFound
89
        except VirtualMachine.MultipleObjectsReturned:
90
            raise fault.serviceUnavailable
91
        except Exception, e:
92
            raise fault.serviceUnavailable
93

    
94
        server = {'status': server.rsapi_state, 
95
                                     'flavorId': server.flavor.id, 
96
                                     'name': server.name, 
97
                                     'id': server.id, 
98
                                     'imageId': server.sourceimage.id, 
99
                                     'hostId': server.hostid, 
100
                                     #'metadata': {'Server_Label': server.description },
101
                                     'metadata':[{'meta': { 'key': {metadata.meta_key: metadata.meta_value}}} for metadata in server.virtualmachinemetadata_set.all()],                                    
102
                                     'addresses': {'public': { 'ip': {'addr': server.ipfour}, 'ip6': {'addr': server.ipsix}},'private': ''},      
103
                }
104
        return { "server": server } 
105

    
106

    
107
    @paginator
108
    def read_all(self, request, detail=False):
109
        virtual_servers = VirtualMachine.objects.filter(deleted=False) 
110
        #get all VM's for now, FIX it to take the user's VMs only yet. also don't get deleted VM's
111

    
112
        if not detail:
113
            return { "servers": [ { "id": s.id, "name": s.name } for s in virtual_servers ] }
114
        else:
115
            virtual_servers_list = [{'status': server.rsapi_state, 
116
                                     'flavorId': server.flavor.id, 
117
                                     'name': server.name, 
118
                                     'id': server.id, 
119
                                     'imageId': server.sourceimage.id, 
120
                                     'hostId': server.hostid, 
121
                                     #'metadata': {'Server_Label': server.description },
122
                                     'metadata':[{'meta': { 'key': {metadata.meta_key: metadata.meta_value}}} for metadata in server.virtualmachinemetadata_set.all()],                                    
123
                                     'addresses': {'public': { 'ip': {'addr': server.ipfour}, 'ip6': {'addr': server.ipsix}},'private': ''},      
124

    
125
                                    } for server in virtual_servers]
126
            #pass some fake data regarding ip, since we don't have any such data            
127
            return { "servers":  virtual_servers_list }                
128

    
129

    
130
    def create(self, request):
131
        #TODO: add random pass, metadata       
132
        try:
133
            #here we have the options for cpu, ram etc
134
            options_request = json.loads(request.POST.get('create', None)) 
135
        except Exception, e:
136
            raise fault.badRequest
137
        cpu = options_request.get('cpu','')
138
        ram = options_request.get('ram','')
139
        name = options_request.get('name','')
140
        storage = options_request.get('storage','')
141

    
142
        try:
143
            pnode = rapi.GetNodes()[0]
144
            rapi.CreateInstance('create', name, 'plain', [{"size": storage}], [{}], os='debootstrap+default', ip_check=False, name_check=False,pnode=pnode, beparams={'auto_balance': True, 'vcpus': cpu, 'memory': ram})
145
        except GanetiApiError, CertificateError:
146
            raise fault.serviceUnavailable
147
        except Exception, e:
148
            raise fault.serviceUnavailable
149
        return accepted
150

    
151
        #TODO: replace with real data from request.POST and create the VM in the database
152

    
153
    def update(self, request, id):
154
        return noContent
155

    
156
    def delete(self, request, id):
157
        try:
158
            vm = VirtualMachine.objects.get(id=id)
159
        except VirtualMachine.DoesNotExist:
160
            raise fault.itemNotFound
161
        except VirtualMachine.MultipleObjectsReturned:
162
            raise fault.serviceUnavailable
163
        except Exception, e:
164
            raise fault.serviceUnavailable
165

    
166
        #TODO: set the status to DESTROYED
167
        try:
168
            vm.start_action('DESTROY')
169
        except Exception, e:
170
            raise fault.serviceUnavailable
171

    
172
        try:
173
            rapi.DeleteInstance(vm.backend_id)
174
        except GanetiApiError, CertificateError:
175
            raise fault.serviceUnavailable
176
        except Exception, e:
177
            raise fault.serviceUnavailable
178

    
179
        return accepted        
180

    
181
class ServerAddressHandler(BaseHandler):
182
    allowed_methods = ('GET', 'PUT', 'DELETE')
183

    
184
    def read(self, request, id, type=None):
185
        """List IP addresses for a server"""
186

    
187
        if type is None:
188
            pass
189
        elif type == "private":
190
            pass
191
        elif type == "public":
192
            pass
193
        return {}
194

    
195
    def update(self, request, id, address):
196
        """Share an IP address to another in the group"""
197
        return accepted
198

    
199
    def delete(self, request, id, address):
200
        """Unshare an IP address"""
201
        return accepted
202

    
203

    
204
class ServerActionHandler(BaseHandler):
205
    """Handler responsible for Server Actions
206

207
     handles Reboot, Shutdown and Start actions. 
208

209
     @HTTP methods: POST, DELETE, PUT
210
     @Parameters: POST data with the action (reboot, shutdown, start)
211
     @Responses: HTTP 202 if successfully call rapi, itemNotFound, serviceUnavailable otherwise
212

213
    """
214

    
215
    allowed_methods = ('POST', 'DELETE',  'PUT')
216

    
217
    def create(self, request, id):
218
        """Reboot, Shutdown, Start virtual machine"""
219

    
220
        reboot_request = request.POST.get('reboot', None)
221
        shutdown_request = request.POST.get('shutdown', None)
222
        start_request = request.POST.get('start', None)
223
        #action not implemented
224
        action = reboot_request and 'REBOOT' or shutdown_request and 'SUSPEND' or start_request and 'START'
225
        if not action:
226
            raise fault.notImplemented 
227
        #test if we can get the vm
228
        try:
229
            vm = VirtualMachine.objects.get(id=id)
230
        except VirtualMachine.DoesNotExist:
231
            raise fault.itemNotFound
232
        except VirtualMachine.MultipleObjectsReturned:
233
            raise fault.serviceUnavailable
234
        except Exception, e:
235
            raise fault.serviceUnavailable
236

    
237
        try:
238
            vm.start_action(action)
239
        except Exception, e:
240
            raise fault.serviceUnavailable
241

    
242
        try:
243
            if reboot_request:
244
                rapi.RebootInstance(vm.backend_id)
245
            elif shutdown_request:
246
                rapi.ShutdownInstance(vm.backend_id)
247
            elif start_request:
248
                rapi.StartupInstance(vm.backend_id)
249
            return accepted
250
        except GanetiApiError, CertificateError:
251
            raise fault.serviceUnavailable
252
        except Exception, e:
253
            raise fault.serviceUnavailable
254

    
255
    def delete(self, request, id):
256
        """Delete an Instance"""
257
        return accepted
258

    
259
    def update(self, request, id):
260
        return noContent
261

    
262

    
263

    
264

    
265
class ServerBackupHandler(BaseHandler):
266
    """ Backup Schedules are not implemented yet, return notImplemented """
267
    allowed_methods = ('GET', 'POST', 'DELETE')
268

    
269
    def read(self, request, id):
270
        raise fault.notImplemented
271

    
272
    def create(self, request, id):
273
        raise fault.notImplemented
274

    
275
    def delete(self, request, id):
276
        raise fault.notImplemented
277

    
278

    
279
class FlavorHandler(BaseHandler):
280
    allowed_methods = ('GET',)
281
    flavors = Flavor.objects.all()
282
    flavors = [ {'id': flavor.id, 'name': flavor.name, 'ram': flavor.ram, \
283
             'disk': flavor.disk} for flavor in flavors]
284

    
285
    def read(self, request, id=None):
286
        """
287
        List flavors or retrieve one
288

289
        Returns: OK
290
        Faults: cloudServersFault, serviceUnavailable, unauthorized,
291
                badRequest, itemNotFound
292
        """
293
        if id is None:
294
            simple = map(lambda v: {
295
                        "id": v['id'],
296
                        "name": v['name'],
297
                    }, self.flavors)
298
            return { "flavors": simple }
299
        elif id == "detail":
300
            return { "flavors": self.flavors }
301
        else:
302
            for flavor in self.flavors:
303
                if str(flavor['id']) == id:
304
                    return { "flavor": flavor }
305
            raise fault.itemNotFound
306

    
307

    
308
class ImageHandler(BaseHandler):
309
    """Handler responsible for Images
310

311
     handles the listing, creation and delete of Images. 
312

313
     @HTTP methods: GET, POST
314
     @Parameters: POST data 
315
     @Responses: HTTP 202 if successfully create Image or get the Images list, itemNotFound, serviceUnavailable otherwise
316

317
    """
318

    
319

    
320
    allowed_methods = ('GET', 'POST')
321

    
322
    def read(self, request, id=None):
323
        """
324
        List images or retrieve one
325

326
        Returns: OK
327
        Faults: cloudServersFault, serviceUnavailable, unauthorized,
328
                badRequest, itemNotFound
329
        """
330
        images = Image.objects.all()
331
        images_list = [ {'created': image.created.isoformat(), 
332
                    'id': image.id,
333
                    'name': image.name,
334
                    'updated': image.updated.isoformat(),    
335
                    'description': image.description, 
336
                    'state': image.state, 
337
                    'vm_id': image.vm_id
338
                   } for image in images]
339
        if rapi: # Images info is stored in the DB. Ganeti is not aware of this
340
            if id == "detail":
341
                return { "images": images_list }
342
            elif id is None:
343
                return { "images": [ { "id": s['id'], "name": s['name'] } for s in images_list ] }
344
            else:
345
                try:
346
                    image = images.get(id=id)
347
                except Image.DoesNotExist:
348
                    raise fault.itemNotFound
349
                except Image.MultipleObjectsReturned:
350
                    raise fault.serviceUnavailable
351
                except Exception, e:
352
                    raise fault.serviceUnavailable
353

    
354
                return { "image":  {'created': image.created.isoformat(), 
355
                    'id': image.id,
356
                    'name': image.name,
357
                    'updated': image.updated.isoformat(),    
358
                    'description': image.description, 
359
                    'state': image.state, 
360
                    'vm_id': image.vm_id
361
                   } }
362

    
363
        else:
364
            raise fault.serviceUnavailable
365

    
366
    def create(self, request):
367
        """Create a new image"""
368
        return accepted
369

    
370

    
371
class SharedIPGroupHandler(BaseHandler):
372
    allowed_methods = ('GET', 'POST', 'DELETE')
373

    
374
    def read(self, request, id=None):
375
        """List Shared IP Groups"""
376
        if id is None:
377
            return {}
378
        elif id == "detail":
379
            return {}
380
        else:
381
            raise fault.itemNotFound
382

    
383
    def create(self, request, id):
384
        """Creates a new Shared IP Group"""
385
        return created
386

    
387
    def delete(self, request, id):
388
        """Deletes a Shared IP Group"""
389
        return noContent
390

    
391

    
392
class VirtualMachineGroupHandler(BaseHandler):
393
    allowed_methods = ('GET', 'POST', 'DELETE')
394

    
395
    def read(self, request, id=None):
396
        """List Groups"""
397
        vmgroups = VirtualMachineGroup.objects.all() 
398
        vmgroups = [ {'id': vmgroup.id, \
399
              'name': vmgroup.name,  \
400
               'server_id': [machine.id for machine in vmgroup.machines.all()] \
401
               } for vmgroup in vmgroups]
402
        if rapi: # Group info is stored in the DB. Ganeti is not aware of this
403
            if id == "detail":
404
                return { "groups": vmgroups }
405
            elif id is None:
406
                return { "groups": [ { "id": s['id'], "name": s['name'] } for s in vmgroups ] }
407
            else:
408
                return { "groups": vmgroups[0] }
409

    
410

    
411
    def create(self, request, id):
412
        """Creates a Group"""
413
        return created
414

    
415
    def delete(self, request, id):
416
        """Deletes a  Group"""
417
        return noContent
418

    
419

    
420
class LimitHandler(BaseHandler):
421
    allowed_methods = ('GET',)
422

    
423
    # XXX: hookup with @throttle
424

    
425
    rate = [
426
        {
427
           "verb" : "POST",
428
           "URI" : "*",
429
           "regex" : ".*",
430
           "value" : 10,
431
           "remaining" : 2,
432
           "unit" : "MINUTE",
433
           "resetTime" : 1244425439
434
        },
435
        {
436
           "verb" : "POST",
437
           "URI" : "*/servers",
438
           "regex" : "^/servers",
439
           "value" : 25,
440
           "remaining" : 24,
441
           "unit" : "DAY",
442
           "resetTime" : 1244511839
443
        },
444
        {
445
           "verb" : "PUT",
446
           "URI" : "*",
447
           "regex" : ".*",
448
           "value" : 10,
449
           "remaining" : 2,
450
           "unit" : "MINUTE",
451
           "resetTime" : 1244425439
452
        },
453
        {
454
           "verb" : "GET",
455
           "URI" : "*",
456
           "regex" : ".*",
457
           "value" : 3,
458
           "remaining" : 3,
459
           "unit" : "MINUTE",
460
           "resetTime" : 1244425439
461
        },
462
        {
463
           "verb" : "DELETE",
464
           "URI" : "*",
465
           "regex" : ".*",
466
           "value" : 100,
467
           "remaining" : 100,
468
           "unit" : "MINUTE",
469
           "resetTime" : 1244425439
470
        }
471
    ]
472

    
473
    absolute = {
474
        "maxTotalRAMSize" : 51200,
475
        "maxIPGroups" : 50,
476
        "maxIPGroupMembers" : 25
477
    }
478

    
479
    def read(self, request):
480
        return { "limits": {
481
                "rate": self.rate,
482
                "absolute": self.absolute,
483
               }
484
            }
485

    
486

    
487
class DiskHandler(BaseHandler):
488
    allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
489

    
490
    def read(self, request, id=None):
491
        """List Disks"""
492
        if id is None:
493
            return self.read_all(request)
494
        elif id == "detail":
495
            return self.read_all(request, detail=True)
496
        else:
497
            return self.read_one(request, id)
498

    
499
    def read_one(self, request, id):
500
        """List one Disk with the specified id with all details"""
501
        # FIXME Get detailed info from the DB 
502
        # for the Disk with the specified id
503
        try:
504
            disk = Disk.objects.get(pk=id)
505
            disk_details = {
506
                "id" : disk.id, 
507
                "name" : disk.name, 
508
                "size" : disk.size,
509
                "created" : disk.created, 
510
                "serverId" : disk.vm.id
511
            }
512
            return { "disks" : disk_details }
513
        except:
514
            raise fault.itemNotFound
515

    
516
    @paginator
517
    def read_all(self, request, detail=False):
518
        """List all Disks. If -detail- is set list them with all details"""
519
        if not detail:
520
            disks = Disk.objects.filter(owner=SynnefoUser.objects.all()[0])
521
            return { "disks": [ { "id": disk.id, "name": disk.name } for disk in disks ] }
522
        else:
523
            disks = Disk.objects.filter(owner=SynnefoUser.objects.all()[0])
524
            disks_details = [ {
525
                "id" : disk.id, 
526
                "name" : disk.name,
527
                "size" : disk.size,
528
                "created" : disk.created, 
529
                "serverId" : disk.vm.id,
530
            } for disk in disks ]
531
            return { "disks":  disks_details }                
532

    
533
    def create(self, request):
534
        """Create a new Disk"""
535
        # FIXME Create a partial DB entry, 
536
        # then call the backend for actual creation
537
        pass
538

    
539
    def update(self, request, id):
540
        """Rename the Disk with the specified id"""
541
        # FIXME Change the Disk's name in the DB
542
        pass
543

    
544
    def delete(self, request, id):
545
        """Destroy the Disk with the specified id"""
546
        # Call the backend for actual destruction
547
        pass