Statistics
| Branch: | Tag: | Revision:

root / api / handlers.py @ 1cea389e

History | View | Annotate | Download (17.1 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
            vm = 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
        return { "server": vm } 
94

    
95

    
96
    @paginator
97
    def read_all(self, request, detail=False):
98
        virtual_servers = VirtualMachine.objects.filter(deleted=False) 
99
        #get all VM's for now, FIX it to take the user's VMs only yet. also don't get deleted VM's
100

    
101
        if not detail:
102
            return { "servers": [ { "id": s.id, "name": s.name } for s in virtual_servers ] }
103
        else:
104
            virtual_servers_list = [{'status': server.rsapi_state, 
105
                                     'flavorId': server.flavor.id, 
106
                                     'name': server.name, 
107
                                     'id': server.id, 
108
                                     'imageId': server.sourceimage.id, 
109
                                     'hostId': server.hostid, 
110
                                     #'metadata': {'Server_Label': server.description },
111
                                     'metadata':[{'meta': { 'key': {metadata.meta_key: metadata.meta_value}}} for metadata in server.virtualmachinemetadata_set.all()],                                    
112
                                     'addresses': {'public': { 'ip': {'addr': server.ipfour}, 'ip6': {'addr': server.ipsix}},'private': ''},      
113

    
114
                                    } for server in virtual_servers]
115
            #pass some fake data regarding ip, since we don't have any such data            
116
            return { "servers":  virtual_servers_list }                
117

    
118

    
119
    def create(self, request):
120
        #TODO: add random pass, metadata       
121
        try:
122
            #here we have the options for cpu, ram etc
123
            options_request = json.loads(request.POST.get('create', None)) 
124
        except Exception, e:
125
            raise fault.badRequest
126
        cpu = options_request.get('cpu','')
127
        ram = options_request.get('ram','')
128
        name = options_request.get('name','')
129
        storage = options_request.get('storage','')
130

    
131
        try:
132
            pnode = rapi.GetNodes()[0]
133
            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})
134
        except GanetiApiError, CertificateError:
135
            raise fault.serviceUnavailable
136
        except Exception, e:
137
            raise fault.serviceUnavailable
138
        return accepted
139

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

    
142
    def update(self, request, id):
143
        return noContent
144

    
145
    def delete(self, request, id):
146
        try:
147
            vm = VirtualMachine.objects.get(id=id)
148
        except VirtualMachine.DoesNotExist:
149
            raise fault.itemNotFound
150
        except VirtualMachine.MultipleObjectsReturned:
151
            raise fault.serviceUnavailable
152
        except Exception, e:
153
            raise fault.serviceUnavailable
154

    
155
        #TODO: set the status to DESTROYED
156
        try:
157
            vm.start_action('DESTROY')
158
        except Exception, e:
159
            raise fault.serviceUnavailable
160

    
161
        try:
162
            rapi.DeleteInstance(vm.backend_id)
163
        except GanetiApiError, CertificateError:
164
            raise fault.serviceUnavailable
165
        except Exception, e:
166
            raise fault.serviceUnavailable
167

    
168
        return accepted        
169

    
170
class ServerAddressHandler(BaseHandler):
171
    allowed_methods = ('GET', 'PUT', 'DELETE')
172

    
173
    def read(self, request, id, type=None):
174
        """List IP addresses for a server"""
175

    
176
        if type is None:
177
            pass
178
        elif type == "private":
179
            pass
180
        elif type == "public":
181
            pass
182
        return {}
183

    
184
    def update(self, request, id, address):
185
        """Share an IP address to another in the group"""
186
        return accepted
187

    
188
    def delete(self, request, id, address):
189
        """Unshare an IP address"""
190
        return accepted
191

    
192

    
193
class ServerActionHandler(BaseHandler):
194
    """Handler responsible for Server Actions
195

196
     handles Reboot, Shutdown and Start actions. 
197

198
     @HTTP methods: POST, DELETE, PUT
199
     @Parameters: POST data with the action (reboot, shutdown, start)
200
     @Responses: HTTP 202 if successfully call rapi, itemNotFound, serviceUnavailable otherwise
201

202
    """
203

    
204
    allowed_methods = ('POST', 'DELETE',  'PUT')
205

    
206
    def create(self, request, id):
207
        """Reboot, Shutdown, Start virtual machine"""
208

    
209
        reboot_request = request.POST.get('reboot', None)
210
        shutdown_request = request.POST.get('shutdown', None)
211
        start_request = request.POST.get('start', None)
212
        #action not implemented
213
        action = reboot_request and 'REBOOT' or shutdown_request and 'SUSPEND' or start_request and 'START'
214
        if not action:
215
            raise fault.notImplemented 
216
        #test if we can get the vm
217
        try:
218
            vm = VirtualMachine.objects.get(id=id)
219
        except VirtualMachine.DoesNotExist:
220
            raise fault.itemNotFound
221
        except VirtualMachine.MultipleObjectsReturned:
222
            raise fault.serviceUnavailable
223
        except Exception, e:
224
            raise fault.serviceUnavailable
225

    
226
        try:
227
            vm.start_action(action)
228
        except Exception, e:
229
            raise fault.serviceUnavailable
230

    
231
        try:
232
            if reboot_request:
233
                rapi.RebootInstance(vm.backend_id)
234
            elif shutdown_request:
235
                rapi.ShutdownInstance(vm.backend_id)
236
            elif start_request:
237
                rapi.StartupInstance(vm.backend_id)
238
            return accepted
239
        except GanetiApiError, CertificateError:
240
            raise fault.serviceUnavailable
241
        except Exception, e:
242
            raise fault.serviceUnavailable
243

    
244
    def delete(self, request, id):
245
        """Delete an Instance"""
246
        return accepted
247

    
248
    def update(self, request, id):
249
        return noContent
250

    
251

    
252

    
253

    
254
class ServerBackupHandler(BaseHandler):
255
    """ Backup Schedules are not implemented yet, return notImplemented """
256
    allowed_methods = ('GET', 'POST', 'DELETE')
257

    
258
    def read(self, request, id):
259
        raise fault.notImplemented
260

    
261
    def create(self, request, id):
262
        raise fault.notImplemented
263

    
264
    def delete(self, request, id):
265
        raise fault.notImplemented
266

    
267

    
268
class FlavorHandler(BaseHandler):
269
    allowed_methods = ('GET',)
270
    flavors = Flavor.objects.all()
271
    flavors = [ {'id': flavor.id, 'name': flavor.name, 'ram': flavor.ram, \
272
             'disk': flavor.disk} for flavor in flavors]
273

    
274
    def read(self, request, id=None):
275
        """
276
        List flavors or retrieve one
277

278
        Returns: OK
279
        Faults: cloudServersFault, serviceUnavailable, unauthorized,
280
                badRequest, itemNotFound
281
        """
282
        if id is None:
283
            simple = map(lambda v: {
284
                        "id": v['id'],
285
                        "name": v['name'],
286
                    }, self.flavors)
287
            return { "flavors": simple }
288
        elif id == "detail":
289
            return { "flavors": self.flavors }
290
        else:
291
            for flavor in self.flavors:
292
                if str(flavor['id']) == id:
293
                    return { "flavor": flavor }
294
            raise fault.itemNotFound
295

    
296

    
297
class ImageHandler(BaseHandler):
298
    """Handler responsible for Images
299

300
     handles the listing, creation and delete of Images. 
301

302
     @HTTP methods: GET, POST
303
     @Parameters: POST data 
304
     @Responses: HTTP 202 if successfully create Image or get the Images list, itemNotFound, serviceUnavailable otherwise
305

306
    """
307

    
308

    
309
    allowed_methods = ('GET', 'POST')
310

    
311
    def read(self, request, id=None):
312
        """
313
        List images or retrieve one
314

315
        Returns: OK
316
        Faults: cloudServersFault, serviceUnavailable, unauthorized,
317
                badRequest, itemNotFound
318
        """
319
        images = Image.objects.all()
320
        images_list = [ {'created': image.created.isoformat(), 
321
                    'id': image.id,
322
                    'name': image.name,
323
                    'updated': image.updated.isoformat(),    
324
                    'description': image.description, 
325
                    'state': image.state, 
326
                    'vm_id': image.vm_id
327
                   } for image in images]
328
        if rapi: # Images info is stored in the DB. Ganeti is not aware of this
329
            if id == "detail":
330
                return { "images": images_list }
331
            elif id is None:
332
                return { "images": [ { "id": s['id'], "name": s['name'] } for s in images_list ] }
333
            else:
334
                try:
335
                    image = images.get(id=id)
336
                except Image.DoesNotExist:
337
                    raise fault.itemNotFound
338
                except Image.MultipleObjectsReturned:
339
                    raise fault.serviceUnavailable
340
                except Exception, e:
341
                    raise fault.serviceUnavailable
342

    
343
                return { "image":  {'created': image.created.isoformat(), 
344
                    'id': image.id,
345
                    'name': image.name,
346
                    'updated': image.updated.isoformat(),    
347
                    'description': image.description, 
348
                    'state': image.state, 
349
                    'vm_id': image.vm_id
350
                   } }
351

    
352
        else:
353
            raise fault.serviceUnavailable
354

    
355
    def create(self, request):
356
        """Create a new image"""
357
        return accepted
358

    
359

    
360
class SharedIPGroupHandler(BaseHandler):
361
    allowed_methods = ('GET', 'POST', 'DELETE')
362

    
363
    def read(self, request, id=None):
364
        """List Shared IP Groups"""
365
        if id is None:
366
            return {}
367
        elif id == "detail":
368
            return {}
369
        else:
370
            raise fault.itemNotFound
371

    
372
    def create(self, request, id):
373
        """Creates a new Shared IP Group"""
374
        return created
375

    
376
    def delete(self, request, id):
377
        """Deletes a Shared IP Group"""
378
        return noContent
379

    
380

    
381
class VirtualMachineGroupHandler(BaseHandler):
382
    allowed_methods = ('GET', 'POST', 'DELETE')
383

    
384
    def read(self, request, id=None):
385
        """List Groups"""
386
        vmgroups = VirtualMachineGroup.objects.all() 
387
        vmgroups = [ {'id': vmgroup.id, \
388
              'name': vmgroup.name,  \
389
               'server_id': [machine.id for machine in vmgroup.machines.all()] \
390
               } for vmgroup in vmgroups]
391
        if rapi: # Group info is stored in the DB. Ganeti is not aware of this
392
            if id == "detail":
393
                return { "groups": vmgroups }
394
            elif id is None:
395
                return { "groups": [ { "id": s['id'], "name": s['name'] } for s in vmgroups ] }
396
            else:
397
                return { "groups": vmgroups[0] }
398

    
399

    
400
    def create(self, request, id):
401
        """Creates a Group"""
402
        return created
403

    
404
    def delete(self, request, id):
405
        """Deletes a  Group"""
406
        return noContent
407

    
408

    
409
class LimitHandler(BaseHandler):
410
    allowed_methods = ('GET',)
411

    
412
    # XXX: hookup with @throttle
413

    
414
    rate = [
415
        {
416
           "verb" : "POST",
417
           "URI" : "*",
418
           "regex" : ".*",
419
           "value" : 10,
420
           "remaining" : 2,
421
           "unit" : "MINUTE",
422
           "resetTime" : 1244425439
423
        },
424
        {
425
           "verb" : "POST",
426
           "URI" : "*/servers",
427
           "regex" : "^/servers",
428
           "value" : 25,
429
           "remaining" : 24,
430
           "unit" : "DAY",
431
           "resetTime" : 1244511839
432
        },
433
        {
434
           "verb" : "PUT",
435
           "URI" : "*",
436
           "regex" : ".*",
437
           "value" : 10,
438
           "remaining" : 2,
439
           "unit" : "MINUTE",
440
           "resetTime" : 1244425439
441
        },
442
        {
443
           "verb" : "GET",
444
           "URI" : "*",
445
           "regex" : ".*",
446
           "value" : 3,
447
           "remaining" : 3,
448
           "unit" : "MINUTE",
449
           "resetTime" : 1244425439
450
        },
451
        {
452
           "verb" : "DELETE",
453
           "URI" : "*",
454
           "regex" : ".*",
455
           "value" : 100,
456
           "remaining" : 100,
457
           "unit" : "MINUTE",
458
           "resetTime" : 1244425439
459
        }
460
    ]
461

    
462
    absolute = {
463
        "maxTotalRAMSize" : 51200,
464
        "maxIPGroups" : 50,
465
        "maxIPGroupMembers" : 25
466
    }
467

    
468
    def read(self, request):
469
        return { "limits": {
470
                "rate": self.rate,
471
                "absolute": self.absolute,
472
               }
473
            }
474

    
475

    
476
class DiskHandler(BaseHandler):
477
    allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
478

    
479
    def read(self, request, id=None):
480
        """List Disks"""
481
        if id is None:
482
            return self.read_all(request)
483
        elif id == "detail":
484
            return self.read_all(request, detail=True)
485
        else:
486
            return self.read_one(request, id)
487

    
488
    def read_one(self, request, id):
489
        """List one Disk with the specified id with all details"""
490
        # FIXME Get detailed info from the DB 
491
        # for the Disk with the specified id
492
        try:
493
            disk = Disk.objects.get(pk=id)
494
            disk_details = {
495
                "id" : disk.id, 
496
                "name" : disk.name, 
497
                "size" : disk.size,
498
                "created" : disk.created, 
499
                "serverId" : disk.vm.id
500
            }
501
            return { "disks" : disk_details }
502
        except:
503
            raise fault.itemNotFound
504

    
505
    @paginator
506
    def read_all(self, request, detail=False):
507
        """List all Disks. If -detail- is set list them with all details"""
508
        if not detail:
509
            disks = Disk.objects.filter(owner=SynnefoUser.objects.all()[0])
510
            return { "disks": [ { "id": disk.id, "name": disk.name } for disk in disks ] }
511
        else:
512
            disks = Disk.objects.filter(owner=SynnefoUser.objects.all()[0])
513
            disks_details = [ {
514
                "id" : disk.id, 
515
                "name" : disk.name,
516
                "size" : disk.size,
517
                "created" : disk.created, 
518
                "serverId" : disk.vm.id,
519
            } for disk in disks ]
520
            return { "disks":  disks_details }                
521

    
522
    def create(self, request):
523
        """Create a new Disk"""
524
        # FIXME Create a partial DB entry, 
525
        # then call the backend for actual creation
526
        pass
527

    
528
    def update(self, request, id):
529
        """Rename the Disk with the specified id"""
530
        # FIXME Change the Disk's name in the DB
531
        pass
532

    
533
    def delete(self, request, id):
534
        """Destroy the Disk with the specified id"""
535
        # Call the backend for actual destruction
536
        pass