Statistics
| Branch: | Tag: | Revision:

root / api / handlers.py @ 9eef701d

History | View | Annotate | Download (16.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
11
from synnefo.db.models import *
12
from util.rapi import GanetiRapiClient
13

    
14

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

    
23
backend_prefix_id = settings.BACKEND_PREFIX_ID
24

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

    
40

    
41
class VersionHandler(AnonymousBaseHandler):
42
    allowed_methods = ('GET',)
43

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

    
57

    
58
class ServerHandler(BaseHandler):
59
    allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
60

    
61
    def read(self, request, id=None):
62
        from time import sleep
63
        sleep(0.5)
64
        #TODO: delete the sleep once the mock objects are removed
65
        if id is None:
66
            return self.read_all(request)
67
        elif id == "detail":
68
            return self.read_all(request, detail=True)
69
        else:
70
            return self.read_one(request, id)
71

    
72
    def read_one(self, request, id):
73
        try:
74
            instance = VirtualMachine.objects.get(id=id)
75
            return { "server": instance } #FIXME
76
        except:
77
            raise fault.itemNotFound
78

    
79
    @paginator
80
    def read_all(self, request, detail=False):
81
        virtual_servers = VirtualMachine.objects.all()
82
        virtual_servers = [virtual_server for virtual_server in  virtual_servers if virtual_server.rsapi_state !="DELETED"]
83
        #get all VM's for now, FIX it to take the user's VMs only yet. also don't get deleted VM's
84

    
85
        if not detail:
86
            return { "servers": [ { "id": s.id, "name": s.name } for s in virtual_servers ] }
87
        else:
88
            virtual_servers_list = [{'status': server.rsapi_state, 
89
                                     'flavorId': server.flavor.id, 
90
                                     'name': server.name, 
91
                                     'id': server.id, 
92
                                     'imageId': server.sourceimage.id, 
93
                                     'metadata': {'Server_Label': server.description, 
94
                                                  'hostId': '9e107d9d372bb6826bd81d3542a419d6',
95
                                                  'addresses': {'public': ['67.23.10.133'],
96
                                                                'private': ['10.176.42.17'],
97
                                                                }
98
                                                  }
99
                                    } for server in virtual_servers]
100
            #pass some fake data regarding ip, since we don't have any such data
101
            return { "servers":  virtual_servers_list }                
102

    
103

    
104
    def create(self, request):
105
        print 'create machine was called'
106
        #TODO: add random pass, metadata       
107
        try:
108
            options_request = json.loads(request.POST.get('create', None)) #here we have the options for cpu, ram etc
109
            cpu = options_request.get('cpu','')
110
            ram = options_request.get('ram','')
111
            name = options_request.get('name','')
112
            storage = options_request.get('storage','')
113
            pnode = rapi.GetNodes()[0]
114
            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})
115
            return accepted
116
        except: # something bad happened. FIXME: return code
117
            return noContent
118

    
119
        #TODO: replace with real data from request.POST
120
        #TODO: create the VM in the database
121

    
122
    def update(self, request, id):
123
        return noContent
124

    
125
    def delete(self, request, id):
126
        try:
127
            instance = VirtualMachine.objects.get(id=id)
128
            print 'deleting machine %s' % instance.name
129
            instance._operstate = 'DESTROYED'
130
            return accepted
131
            #rapi.DeleteInstance(instance.name)
132
        except:
133
            raise fault.itemNotFound
134

    
135

    
136
class ServerAddressHandler(BaseHandler):
137
    allowed_methods = ('GET', 'PUT', 'DELETE')
138

    
139
    def read(self, request, id, type=None):
140
        """List IP addresses for a server"""
141

    
142
        if type is None:
143
            pass
144
        elif type == "private":
145
            pass
146
        elif type == "public":
147
            pass
148
        return {}
149

    
150
    def update(self, request, id, address):
151
        """Share an IP address to another in the group"""
152
        return accepted
153

    
154
    def delete(self, request, id, address):
155
        """Unshare an IP address"""
156
        return accepted
157

    
158

    
159
class ServerActionHandler(BaseHandler):
160
    allowed_methods = ('POST', 'DELETE', 'GET', 'PUT')
161
#TODO: remove GET/PUT
162
    
163
    def read(self, request, id):
164
        return accepted
165

    
166
    def create(self, request, id):
167
        """Reboot, rebuild, resize, confirm resized, revert resized"""
168
        try:
169
            machine = VirtualMachine.objects.get(id=id)
170
        except:
171
            return noContent
172
        #FIXME: for now make a list with only one machine. This will be a list of machines (for the list view)
173
        reboot_request = request.POST.get('reboot', None)
174
        shutdown_request = request.POST.get('shutdown', None)
175
        start_request = request.POST.get('start', None)
176
        if reboot_request:
177
            return self.action_start([machine], 'reboot')          
178
        elif shutdown_request:
179
            return self.action_start([machine], 'shutdown')          
180
        elif start_request:
181
            return self.action_start([machine], 'start')          
182
        return noContent #FIXME: when does this happen?
183

    
184

    
185
    def delete(self, request, id):
186
        """Delete an Instance"""
187
        return accepted
188

    
189
    def update(self, request, id):
190
        return noContent
191

    
192
    def action_start(self, list_of_machines, action):
193
        if action == 'reboot':
194
            try:
195
                for machine in list_of_machines:
196
                    rapi.RebootInstance(machine)
197
                return accepted
198
            except: # something bad happened.
199
#FIXME: return code. Rackspace error response code(s): cloudServersFault (400, 500), serviceUnavailable (503), unauthorized(401), badRequest (400), badMediaType(415), itemNotFound (404), buildInProgress (409), overLimit (413)
200
                return noContent
201
        if action == 'shutdown':        
202
            try:
203
                for machine in list_of_machines:
204
                    rapi.ShutdownInstance(machine)
205
                return accepted
206
            except: # something bad happened. FIXME: return code
207
                return noContent
208
        if action == 'start':    
209
            try:
210
                for machine in list_of_machines:
211
                    rapi.StartupInstance(machine)
212
                return accepted
213
            except: # something bad happened. FIXME: return code
214
                return noContent
215

    
216

    
217

    
218
#read is called on GET requests
219
#create is called on POST, and creates new objects, and should return them (or rc.CREATED.)
220
#update is called on PUT, and should update an existing product and return them (or rc.ALL_OK.)
221
#delete is called on DELETE, and should delete an existing object. Should not return anything, just rc.DELETED.'''
222

    
223

    
224
class ServerBackupHandler(BaseHandler):
225
    """ Backup Schedules are not implemented yet, return notImplemented """
226
    allowed_methods = ('GET', 'POST', 'DELETE')
227

    
228
    def read(self, request, id):
229
        raise fault.notImplemented
230

    
231
    def create(self, request, id):
232
        raise fault.notImplemented
233

    
234
    def delete(self, request, id):
235
        raise fault.notImplemented
236

    
237

    
238
class FlavorHandler(BaseHandler):
239
    allowed_methods = ('GET',)
240
    flavors = Flavor.objects.all()
241
    flavors = [ {'id': flavor.id, 'name': flavor.name, 'ram': flavor.ram, \
242
             'disk': flavor.disk} for flavor in flavors]
243

    
244
    def read(self, request, id=None):
245
        """
246
        List flavors or retrieve one
247

248
        Returns: OK
249
        Faults: cloudServersFault, serviceUnavailable, unauthorized,
250
                badRequest, itemNotFound
251
        """
252
        if id is None:
253
            simple = map(lambda v: {
254
                        "id": v['id'],
255
                        "name": v['name'],
256
                    }, self.flavors)
257
            return { "flavors": simple }
258
        elif id == "detail":
259
            return { "flavors": self.flavors }
260
        else:
261
            for flavor in self.flavors:
262
                if str(flavor['id']) == id:
263
                    return { "flavor": flavor }
264
            raise fault.itemNotFound
265

    
266

    
267
class ImageHandler(BaseHandler):
268
    allowed_methods = ('GET', 'POST')
269

    
270
    def read(self, request, id=None):
271
        """
272
        List images or retrieve one
273

274
        Returns: OK
275
        Faults: cloudServersFault, serviceUnavailable, unauthorized,
276
                badRequest, itemNotFound
277
        """
278
        images = Image.objects.all()
279
        images_list = [ {'created': image.created.isoformat(), 
280
                    'id': image.id,
281
                    'name': image.name,
282
                    'updated': image.updated.isoformat(),    
283
                    'description': image.description, 
284
                    'state': image.state, 
285
                    'vm_id': image.vm_id
286
                   } for image in images]
287
        if rapi: # Images info is stored in the DB. Ganeti is not aware of this
288
            if id == "detail":
289
                return { "images": images_list }
290
            elif id is None:
291
                return { "images": [ { "id": s['id'], "name": s['name'] } for s in images_list ] }
292
            else:
293
                try:
294
                    image = images.get(id=id)
295
                    return { "image":  {'created': image.created.isoformat(), 
296
                    'id': image.id,
297
                    'name': image.name,
298
                    'updated': image.updated.isoformat(),    
299
                    'description': image.description, 
300
                    'state': image.state, 
301
                    'vm_id': image.vm_id
302
                   } }
303
                except: 
304
                    raise fault.itemNotFound
305
        else:
306
            raise fault.serviceUnavailable
307

    
308
    def create(self, request):
309
        """Create a new image"""
310
        return accepted
311

    
312

    
313
class SharedIPGroupHandler(BaseHandler):
314
    allowed_methods = ('GET', 'POST', 'DELETE')
315

    
316
    def read(self, request, id=None):
317
        """List Shared IP Groups"""
318
        if id is None:
319
            return {}
320
        elif id == "detail":
321
            return {}
322
        else:
323
            raise fault.itemNotFound
324

    
325
    def create(self, request, id):
326
        """Creates a new Shared IP Group"""
327
        return created
328

    
329
    def delete(self, request, id):
330
        """Deletes a Shared IP Group"""
331
        return noContent
332

    
333

    
334
class VirtualMachineGroupHandler(BaseHandler):
335
    allowed_methods = ('GET', 'POST', 'DELETE')
336

    
337
    def read(self, request, id=None):
338
        """List Groups"""
339
        vmgroups = VirtualMachineGroup.objects.all() 
340
        vmgroups = [ {'id': vmgroup.id, \
341
              'name': vmgroup.name,  \
342
               'server_id': [machine.id for machine in vmgroup.machines.all()] \
343
               } for vmgroup in vmgroups]
344
        if rapi: # Group info is stored in the DB. Ganeti is not aware of this
345
            if id == "detail":
346
                return { "groups": vmgroups }
347
            elif id is None:
348
                return { "groups": [ { "id": s['id'], "name": s['name'] } for s in vmgroups ] }
349
            else:
350
                return { "groups": vmgroups[0] }
351

    
352

    
353
    def create(self, request, id):
354
        """Creates a Group"""
355
        return created
356

    
357
    def delete(self, request, id):
358
        """Deletes a  Group"""
359
        return noContent
360

    
361

    
362
class LimitHandler(BaseHandler):
363
    allowed_methods = ('GET',)
364

    
365
    # XXX: hookup with @throttle
366

    
367
    rate = [
368
        {
369
           "verb" : "POST",
370
           "URI" : "*",
371
           "regex" : ".*",
372
           "value" : 10,
373
           "remaining" : 2,
374
           "unit" : "MINUTE",
375
           "resetTime" : 1244425439
376
        },
377
        {
378
           "verb" : "POST",
379
           "URI" : "*/servers",
380
           "regex" : "^/servers",
381
           "value" : 25,
382
           "remaining" : 24,
383
           "unit" : "DAY",
384
           "resetTime" : 1244511839
385
        },
386
        {
387
           "verb" : "PUT",
388
           "URI" : "*",
389
           "regex" : ".*",
390
           "value" : 10,
391
           "remaining" : 2,
392
           "unit" : "MINUTE",
393
           "resetTime" : 1244425439
394
        },
395
        {
396
           "verb" : "GET",
397
           "URI" : "*",
398
           "regex" : ".*",
399
           "value" : 3,
400
           "remaining" : 3,
401
           "unit" : "MINUTE",
402
           "resetTime" : 1244425439
403
        },
404
        {
405
           "verb" : "DELETE",
406
           "URI" : "*",
407
           "regex" : ".*",
408
           "value" : 100,
409
           "remaining" : 100,
410
           "unit" : "MINUTE",
411
           "resetTime" : 1244425439
412
        }
413
    ]
414

    
415
    absolute = {
416
        "maxTotalRAMSize" : 51200,
417
        "maxIPGroups" : 50,
418
        "maxIPGroupMembers" : 25
419
    }
420

    
421
    def read(self, request):
422
        return { "limits": {
423
                "rate": self.rate,
424
                "absolute": self.absolute,
425
               }
426
            }
427

    
428

    
429
class DiskHandler(BaseHandler):
430
    allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
431

    
432
    def read(self, request, id=None):
433
        """List Disks"""
434
        if id is None:
435
            return self.read_all(request)
436
        elif id == "detail":
437
            return self.read_all(request, detail=True)
438
        else:
439
            return self.read_one(request, id)
440

    
441
    def read_one(self, request, id):
442
        """List one Disk with the specified id with all details"""
443
        # FIXME Get detailed info from the DB 
444
        # for the Disk with the specified id
445
        try:
446
            disk = Disk.objects.get(pk=id)
447
            disk_details = {
448
                "id" : disk.id, 
449
                "name" : disk.name, 
450
                "size" : disk.size,
451
                "created" : disk.created, 
452
                "serverId" : disk.vm.id
453
            }
454
            return { "disks" : disk_details }
455
        except:
456
            raise fault.itemNotFound
457

    
458
    @paginator
459
    def read_all(self, request, detail=False):
460
        """List all Disks. If -detail- is set list them with all details"""
461
        if not detail:
462
            disks = Disk.objects.filter(owner=SynnefoUser.objects.all()[0])
463
            return { "disks": [ { "id": disk.id, "name": disk.name } for disk in disks ] }
464
        else:
465
            disks = Disk.objects.filter(owner=SynnefoUser.objects.all()[0])
466
            disks_details = [ {
467
                "id" : disk.id, 
468
                "name" : disk.name,
469
                "size" : disk.size,
470
                "created" : disk.created, 
471
                "serverId" : disk.vm.id,
472
            } for disk in disks ]
473
            return { "disks":  disks_details }                
474

    
475
    def create(self, request):
476
        """Create a new Disk"""
477
        # FIXME Create a partial DB entry, 
478
        # then call the backend for actual creation
479
        pass
480

    
481
    def update(self, request, id):
482
        """Rename the Disk with the specified id"""
483
        # FIXME Change the Disk's name in the DB
484
        pass
485

    
486
    def delete(self, request, id):
487
        """Destroy the Disk with the specified id"""
488
        # Call the backend for actual destruction
489
        pass