Statistics
| Branch: | Tag: | Revision:

root / db / models.py @ 41303ed0

History | View | Annotate | Download (19.2 kB)

1
# vim: ts=4 sts=4 et ai sw=4 fileencoding=utf-8
2
# Copyright 2011 GRNET S.A. All rights reserved.
3
#
4
# Redistribution and use in source and binary forms, with or without
5
# modification, are permitted provided that the following conditions
6
# are met:
7
#
8
#   1. Redistributions of source code must retain the above copyright
9
#      notice, this list of conditions and the following disclaimer.
10
#
11
#  2. Redistributions in binary form must reproduce the above copyright
12
#     notice, this list of conditions and the following disclaimer in the
13
#     documentation and/or other materials provided with the distribution.
14
#
15
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
16
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18
# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
19
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25
# SUCH DAMAGE.
26
#
27
# The views and conclusions contained in the software and documentation are
28
# those of the authors and should not be interpreted as representing official
29
# policies, either expressed or implied, of GRNET S.A.
30

    
31

    
32
from django.conf import settings
33
from django.db import models
34

    
35
import datetime
36

    
37
class SynnefoUser(models.Model):
38

    
39
    #TODO: Amend this when we have groups
40
    ACCOUNT_TYPE = (
41
        ('STUDENT', 'Student'),
42
        ('PROFESSOR', 'Professor'),
43
        ('USER', 'Generic User'),
44
        ('HELPDESK', 'Helpdesk User'),
45
        ('ADMIN', 'Admin User')
46
    )
47
    
48
    ACCOUNT_STATE = (
49
        ('ACTIVE', 'Active'),
50
        ('DELETED', 'Deleted'),
51
        ('SUSPENDED', 'Suspended')
52
    )
53
    
54
    name = models.CharField('Synnefo Username', max_length=255, default='')
55
    realname = models.CharField('Real Name', max_length=255, default='')
56
    uniq = models.CharField('External Unique ID', max_length=255,null=True)
57
    credit = models.IntegerField('Credit Balance')
58
    auth_token = models.CharField('Authentication Token', max_length=32, null=True)
59
    auth_token_created = models.DateTimeField('Time of auth token creation', auto_now_add=True, null = True)
60
    auth_token_expires = models.DateTimeField('Time of auth token expiration', auto_now_add=True, null = True)
61
    tmp_auth_token = models.CharField('Temporary authentication token', max_length=32, null=True)
62
    tmp_auth_token_expires = models.DateTimeField('Time of temporary auth token expiration', auto_now_add=True, null = True)
63
    type = models.CharField('Account type', choices=ACCOUNT_TYPE,
64
                                max_length=30)
65
    state = models.CharField('Account state', choices=ACCOUNT_STATE,
66
                                max_length=30, default='ACTIVE')
67
    created = models.DateTimeField('Time of creation', auto_now_add=True)
68
    updated = models.DateTimeField('Time of last update', auto_now=True)
69
    max_invitations = models.IntegerField('Max number of invitations',
70
                                          null=True)
71

    
72
    class Meta:
73
        verbose_name = u'Synnefo User'
74

    
75
    def __unicode__(self):
76
        return self.name
77

    
78
    def get_limit(self, limit_name):
79
        """Returns the limit value for the specified limit"""
80
        limit_objs = Limit.objects.filter(name=limit_name, user=self)
81
        if len(limit_objs) == 1:
82
            return limit_objs[0].value
83
        
84
        return 0
85
        
86
    def _get_credit_quota(self):
87
        """Internal getter function for credit quota"""
88
        return self.get_limit('QUOTA_CREDIT')
89
        
90
    credit_quota = property(_get_credit_quota)
91
    
92
    def _get_monthly_rate(self):
93
        """Internal getter function for monthly credit issue rate"""
94
        return self.get_limit('MONTHLY_RATE')
95
        
96
    monthly_rate = property(_get_monthly_rate)
97
    
98
    def _get_min_credits(self):
99
        """Internal getter function for maximum number of violations"""
100
        return self.get_limit('MIN_CREDITS')
101
        
102
    min_credits = property(_get_min_credits)
103

    
104

    
105
class Image(models.Model):
106
    # This is WIP, FIXME
107
    IMAGE_STATES = (
108
        ('ACTIVE', 'Active'),
109
        ('SAVING', 'Saving'),
110
        ('DELETED', 'Deleted')
111
    )
112

    
113
    # The list of supported Image formats
114
    FORMATS = (
115
        ('dump', 'ext3 dump'),
116
        ('extdump', 'Raw ext2/3/4 dump'),
117
        ('lvm', 'lvm snapshot'),
118
        ('ntfsclone', 'Windows Image produced by ntfsclone'),
119
        ('ntfsdump', 'Raw NTFS dump')
120
    )
121

    
122
    name = models.CharField('Image name', max_length=255)
123
    state = models.CharField('Current Image State', choices=IMAGE_STATES,
124
                                max_length=30)
125
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)
126
    created = models.DateTimeField('Time of creation', auto_now_add=True)
127
    updated = models.DateTimeField('Time of last update', auto_now=True)
128
    sourcevm = models.ForeignKey("VirtualMachine", null=True)
129
    backend_id = models.CharField(max_length=50, default='debian_base')
130
    format = models.CharField(choices=FORMATS, max_length=30, default='dump')
131
    public = models.BooleanField(default=False)
132

    
133
    class Meta:
134
        verbose_name = u'Image'
135

    
136
    def __unicode__(self):
137
        return u'%s' % ( self.name, )
138

    
139

    
140
class ImageMetadata(models.Model):
141
    meta_key = models.CharField('Image metadata key name', max_length=50)
142
    meta_value = models.CharField('Image metadata value', max_length=500)
143
    image = models.ForeignKey(Image, related_name='metadata')
144
    
145
    class Meta:
146
        unique_together = (('meta_key', 'image'),)
147
        verbose_name = u'Key-value pair of Image metadata.'
148
    
149
    def __unicode__(self):
150
        return u'%s, %s for %s' % (self.meta_key, self.meta_value, self.image.name)
151

    
152

    
153
class Limit(models.Model):
154
    LIMITS = (
155
        ('QUOTA_CREDIT', 'Maximum amount of credits per user'),
156
        ('MIN_CREDITS', 'Minimum amount of credits per user'),
157
        ('MONTHLY_RATE', 'Monthly credit issue rate')
158
    )
159
    user = models.ForeignKey(SynnefoUser)
160
    name = models.CharField('Limit key name', choices=LIMITS, max_length=30, null=False)
161
    value = models.IntegerField('Limit current value')
162
    
163
    class Meta:
164
        verbose_name = u'Enforced limit for user'
165
    
166
    def __unicode__(self):
167
        return u'Limit %s for user %s: %d' % (self.value, self.user, self.value)
168

    
169

    
170
class Flavor(models.Model):
171
    cpu = models.IntegerField('Number of CPUs', default=0)
172
    ram = models.IntegerField('Size of RAM', default=0)             # Size in MiB
173
    disk = models.IntegerField('Size of Disk space', default=0)     # Size in GiB
174
    deleted = models.BooleanField('Deleted', default=False)
175
    
176
    class Meta:
177
        verbose_name = u'Virtual machine flavor'
178
        unique_together = ("cpu","ram","disk")
179
            
180
    def _get_name(self):
181
        """Returns flavor name (generated)"""
182
        return u'C%dR%dD%d' % (self.cpu, self.ram, self.disk)
183

    
184
    def _current_cost(self, active):
185
        """Returns active/inactive cost value
186

187
        set active = True to get active cost and False for the inactive.
188

189
        """
190
        fch_list = FlavorCost.objects.filter(flavor=self).order_by('-effective_from')
191
        if len(fch_list) > 0:
192
            if active:
193
                return fch_list[0].cost_active
194
            else:
195
                return fch_list[0].cost_inactive
196

    
197
        return 0
198

    
199
    def _current_cost_active(self):
200
        """Returns current active cost (property method)"""
201
        return self._current_cost(True)
202

    
203
    def _current_cost_inactive(self):
204
        """Returns current inactive cost (property method)"""
205
        return self._current_cost(False)
206

    
207
    name = property(_get_name)
208
    current_cost_active = property(_current_cost_active)
209
    current_cost_inactive = property(_current_cost_inactive)
210

    
211
    def __unicode__(self):
212
        return self.name
213

    
214

    
215
class FlavorCost(models.Model):
216
    cost_active = models.PositiveIntegerField('Active Cost')
217
    cost_inactive = models.PositiveIntegerField('Inactive Cost')
218
    effective_from = models.DateTimeField()
219
    flavor = models.ForeignKey(Flavor)
220
    
221
    class Meta:
222
        verbose_name = u'Pricing history for flavors'
223
    
224
    def __unicode__(self):
225
        return u'Costs (up, down)=(%d, %d) for %s since %s' % \
226
                (int(self.cost_active), int(self.cost_inactive),
227
                 self.flavor.name, self.effective_from)
228

    
229

    
230
class VirtualMachine(models.Model):
231
    # The list of possible actions for a VM
232
    ACTIONS = (
233
       ('CREATE', 'Create VM'),
234
       ('START', 'Start VM'),
235
       ('STOP', 'Shutdown VM'),
236
       ('SUSPEND', 'Admin Suspend VM'),
237
       ('REBOOT', 'Reboot VM'),
238
       ('DESTROY', 'Destroy VM')
239
    )
240
    # The internal operating state of a VM
241
    OPER_STATES = (
242
        ('BUILD', 'Queued for creation'),
243
        ('ERROR', 'Creation failed'),
244
        ('STOPPED', 'Stopped'),
245
        ('STARTED', 'Started'),
246
        ('DESTROYED', 'Destroyed')
247
    )
248
    # The list of possible operations on the backend
249
    BACKEND_OPCODES = (
250
        ('OP_INSTANCE_CREATE', 'Create Instance'),
251
        ('OP_INSTANCE_REMOVE', 'Remove Instance'),
252
        ('OP_INSTANCE_STARTUP', 'Startup Instance'),
253
        ('OP_INSTANCE_SHUTDOWN', 'Shutdown Instance'),
254
        ('OP_INSTANCE_REBOOT', 'Reboot Instance'),
255

    
256
        # These are listed here for completeness,
257
        # and are ignored for the time being
258
        ('OP_INSTANCE_SET_PARAMS', 'Set Instance Parameters'),
259
        ('OP_INSTANCE_QUERY_DATA', 'Query Instance Data'),
260
        ('OP_INSTANCE_REINSTALL', 'Reinstall Instance'),
261
        ('OP_INSTANCE_ACTIVATE_DISKS', 'Activate Disks'),
262
        ('OP_INSTANCE_DEACTIVATE_DISKS', 'Deactivate Disks'),
263
        ('OP_INSTANCE_REPLACE_DISKS', 'Replace Disks'),
264
        ('OP_INSTANCE_MIGRATE', 'Migrate Instance'),
265
        ('OP_INSTANCE_CONSOLE', 'Get Instance Console'),
266
        ('OP_INSTANCE_RECREATE_DISKS', 'Recreate Disks'),
267
        ('OP_INSTANCE_FAILOVER', 'Failover Instance')
268
    )
269
    # A backend job may be in one of the following possible states
270
    BACKEND_STATUSES = (
271
        ('queued', 'request queued'),
272
        ('waiting', 'request waiting for locks'),
273
        ('canceling', 'request being canceled'),
274
        ('running', 'request running'),
275
        ('canceled', 'request canceled'),
276
        ('success', 'request completed successfully'),
277
        ('error', 'request returned error')
278
    )
279

    
280
    # The operating state of a VM,
281
    # upon the successful completion of a backend operation.
282
    # IMPORTANT: Make sure all keys have a corresponding
283
    # entry in BACKEND_OPCODES if you update this field, see #1035, #1111.
284
    OPER_STATE_FROM_OPCODE = {
285
        'OP_INSTANCE_CREATE': 'STARTED',
286
        'OP_INSTANCE_REMOVE': 'DESTROYED',
287
        'OP_INSTANCE_STARTUP': 'STARTED',
288
        'OP_INSTANCE_SHUTDOWN': 'STOPPED',
289
        'OP_INSTANCE_REBOOT': 'STARTED',
290
        'OP_INSTANCE_SET_PARAMS': None,
291
        'OP_INSTANCE_QUERY_DATA': None,
292
        'OP_INSTANCE_REINSTALL' : None,
293
        'OP_INSTANCE_ACTIVATE_DISKS' : None,
294
        'OP_INSTANCE_DEACTIVATE_DISKS': None,
295
        'OP_INSTANCE_REPLACE_DISKS' : None,
296
        'OP_INSTANCE_MIGRATE': None,
297
        'OP_INSTANCE_CONSOLE': None,
298
        'OP_INSTANCE_RECREATE_DISKS': None,
299
        'OP_INSTANCE_FAILOVER': None
300
    }
301

    
302
    # This dictionary contains the correspondence between
303
    # internal operating states and Server States as defined
304
    # by the Rackspace API.
305
    RSAPI_STATE_FROM_OPER_STATE = {
306
        "BUILD": "BUILD",
307
        "ERROR": "ERROR",
308
        "STOPPED": "STOPPED",
309
        "STARTED": "ACTIVE",
310
        "DESTROYED": "DELETED"
311
    }
312

    
313
    name = models.CharField('Virtual Machine Name', max_length=255)
314
    owner = models.ForeignKey(SynnefoUser)
315
    created = models.DateTimeField(auto_now_add=True)
316
    updated = models.DateTimeField(auto_now=True)
317
    charged = models.DateTimeField(default=datetime.datetime.now())
318
    sourceimage = models.ForeignKey("Image", null=False) 
319
    hostid = models.CharField(max_length=100)
320
    flavor = models.ForeignKey(Flavor)
321
    deleted = models.BooleanField('Deleted', default=False)
322
    suspended = models.BooleanField('Administratively Suspended', default=False)
323

    
324
    # VM State 
325
    # The following fields are volatile data, in the sense
326
    # that they need not be persistent in the DB, but rather
327
    # get generated at runtime by quering Ganeti and applying
328
    # updates received from Ganeti.
329
    
330
    # In the future they could be moved to a separate caching layer
331
    # and removed from the database.
332
    # [vkoukis] after discussion with [faidon].
333
    action = models.CharField(choices=ACTIONS, max_length=30, null=True)
334
    operstate = models.CharField(choices=OPER_STATES, max_length=30, null=True)
335
    backendjobid = models.PositiveIntegerField(null=True)
336
    backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30,
337
                                     null=True)
338
    backendjobstatus = models.CharField(choices=BACKEND_STATUSES, max_length=30,
339
                                        null=True)
340
    backendlogmsg = models.TextField(null=True)
341
    buildpercentage = models.IntegerField(default=0)
342

    
343
    # Error classes
344
    class InvalidBackendIdError(Exception):
345
         def __init__(self, value):
346
            self.value = value
347
         def __str__(self):
348
            return repr(self.value)
349

    
350
    class InvalidBackendMsgError(Exception):
351
         def __init__(self, opcode, status):
352
            self.opcode = opcode
353
            self.status = status
354
         def __str__(self):
355
            return repr("<opcode: %s, status: %s>" % (str(self.opcode),
356
                                                      str(self.status)))
357

    
358
    class InvalidActionError(Exception):
359
         def __init__(self, action):
360
            self._action = action
361
         def __str__(self):
362
            return repr(str(self._action))
363
    
364
    class DeletedError(Exception):
365
        pass
366
    
367
    class BuildingError(Exception):
368
        pass
369

    
370
    #class IllegalState(Exception):
371
    #    pass
372
    
373
    def __init__(self, *args, **kw):
374
        """Initialize state for just created VM instances."""
375
        super(VirtualMachine, self).__init__(*args, **kw)
376
        # This gets called BEFORE an instance gets save()d for
377
        # the first time.
378
        if not self.pk: 
379
            self.action = None
380
            self.backendjobid = None
381
            self.backendjobstatus = None
382
            self.backendopcode = None
383
            self.backendlogmsg = None
384
            self.operstate = 'BUILD'
385

    
386
    def _get_backend_id(self):
387
        """Returns the backend id for this VM by prepending backend-prefix."""
388
        if not self.id:
389
            raise VirtualMachine.InvalidBackendIdError("self.id is None")
390
        return '%s%s' % (settings.BACKEND_PREFIX_ID, str(self.id))
391

    
392
    backend_id = property(_get_backend_id)
393

    
394
    class Meta:
395
        verbose_name = u'Virtual machine instance'
396
        get_latest_by = 'created'
397
    
398
    def __unicode__(self):
399
        return self.name
400

    
401

    
402
class VirtualMachineGroup(models.Model):
403
    """Groups of VMs for SynnefoUsers"""
404
    name = models.CharField(max_length=255)
405
    created = models.DateTimeField('Time of creation', auto_now_add=True)
406
    updated = models.DateTimeField('Time of last update', auto_now=True)
407
    owner = models.ForeignKey(SynnefoUser)
408
    machines = models.ManyToManyField(VirtualMachine)
409

    
410
    class Meta:
411
        verbose_name = u'Virtual Machine Group'
412
        verbose_name_plural = 'Virtual Machine Groups'
413
        ordering = ['name']
414
    
415
    def __unicode__(self):
416
        return self.name
417

    
418

    
419
class VirtualMachineMetadata(models.Model):
420
    meta_key = models.CharField(max_length=50)
421
    meta_value = models.CharField(max_length=500)
422
    vm = models.ForeignKey(VirtualMachine, related_name='metadata')
423
    
424
    class Meta:
425
        unique_together = (('meta_key', 'vm'),)
426
        verbose_name = u'Key-value pair of metadata for a VM.'
427
    
428
    def __unicode__(self):
429
        return u'%s, %s for %s' % (self.meta_key, self.meta_value, self.vm.name)
430

    
431

    
432
class Debit(models.Model):
433
    when = models.DateTimeField()
434
    user = models.ForeignKey(SynnefoUser)
435
    vm = models.ForeignKey(VirtualMachine)
436
    description = models.TextField()
437
    
438
    class Meta:
439
        verbose_name = u'Accounting log'
440

    
441
    def __unicode__(self):
442
        return u'%s - %s - %s - %s' % (self.user.id, self.vm.name,
443
                                       str(self.when), self.description)
444

    
445

    
446
class Disk(models.Model):
447
    name = models.CharField(max_length=255)
448
    created = models.DateTimeField('Time of creation', auto_now_add=True)
449
    updated = models.DateTimeField('Time of last update', auto_now=True)
450
    size = models.PositiveIntegerField('Disk size in GBs')
451
    vm = models.ForeignKey(VirtualMachine, blank=True, null=True)
452
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)  
453

    
454
    class Meta:
455
        verbose_name = u'Disk instance'
456

    
457
    def __unicode__(self):
458
        return self.name
459

    
460

    
461
class Network(models.Model):
462
    NETWORK_STATES = (
463
        ('ACTIVE', 'Active'),
464
        ('DELETED', 'Deleted')
465
    )
466
    
467
    name = models.CharField(max_length=255)
468
    created = models.DateTimeField(auto_now_add=True)
469
    updated = models.DateTimeField(auto_now=True)
470
    owner = models.ForeignKey(SynnefoUser, null=True)
471
    state = models.CharField(choices=NETWORK_STATES, max_length=30)
472
    public = models.BooleanField(default=False)
473
    link = models.ForeignKey('NetworkLink', related_name='+')
474
    machines = models.ManyToManyField(VirtualMachine,
475
                                        through='NetworkInterface')
476
    
477
    def __unicode__(self):
478
        return self.name
479

    
480

    
481
class Invitations(models.Model):
482
    source = models.ForeignKey(SynnefoUser, related_name="source")
483
    target = models.ForeignKey(SynnefoUser, related_name="target")
484
    accepted = models.BooleanField('Is the invitation accepted?', default=False)
485
    created = models.DateTimeField(auto_now_add=True)
486
    updated = models.DateTimeField(auto_now=True)
487
    level = models.IntegerField('Invitation depth level', null=True)
488

    
489
    class Meta:
490
        verbose_name = u'Invitation'
491

    
492
    def __unicode__(self):
493
        return "From: %s, To: %s" % (self.source, self.target)
494

    
495

    
496
class NetworkInterface(models.Model):
497
    FIREWALL_PROFILES = (
498
        ('ENABLED', 'Enabled'),
499
        ('DISABLED', 'Disabled'),
500
        ('PROTECTED', 'Protected')
501
    )
502
    
503
    machine = models.ForeignKey(VirtualMachine, related_name='nics')
504
    network = models.ForeignKey(Network, related_name='nics')
505
    created = models.DateTimeField(auto_now_add=True)
506
    updated = models.DateTimeField(auto_now=True)
507
    index = models.IntegerField(null=True)
508
    mac = models.CharField(max_length=17, null=True)
509
    ipv4 = models.CharField(max_length=15, null=True)
510
    ipv6 = models.CharField(max_length=100, null=True)
511
    firewall_profile = models.CharField(choices=FIREWALL_PROFILES,
512
                                        max_length=30, null=True)
513
    
514
    def __unicode__(self):
515
        return '%s@%s' % (self.machine.name, self.network.name)
516

    
517

    
518
class NetworkLink(models.Model):
519
    network = models.ForeignKey(Network, null=True, related_name='+')
520
    index = models.IntegerField()
521
    name = models.CharField(max_length=255)
522
    available = models.BooleanField(default=True)
523
    
524
    def __unicode__(self):
525
        return self.name