Statistics
| Branch: | Tag: | Revision:

root / db / models.py @ 19da4325

History | View | Annotate | Download (17.7 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
    )
46

    
47
    name = models.CharField('Synnefo Username', max_length=255, default='')
48
    realname = models.CharField('Real Name', max_length=255, default='')
49
    uniq = models.CharField('External Unique ID', max_length=255,null=True)
50
    credit = models.IntegerField('Credit Balance')
51
    auth_token = models.CharField('Authentication Token', max_length=32, null=True)
52
    auth_token_created = models.DateTimeField('Time of auth token creation', auto_now_add=True, null = True)
53
    auth_token_expires = models.DateTimeField('Time of auth token expiration', auto_now_add=True, null = True)
54
    tmp_auth_token = models.CharField('Temporary authentication token', max_length=32, null=True)
55
    tmp_auth_token_expires = models.DateTimeField('Time of temporary auth token expiration', auto_now_add=True, null = True)
56
    type = models.CharField('Account type', choices=ACCOUNT_TYPE,
57
                            max_length=30)
58
    created = models.DateTimeField('Time of creation', auto_now_add=True)
59
    updated = models.DateTimeField('Time of last update', auto_now=True)
60
    max_invitations = models.IntegerField('Max number of invitations',
61
                                          null=True)
62

    
63
    class Meta:
64
        verbose_name = u'Synnefo User'
65

    
66
    def __unicode__(self):
67
        return self.name
68

    
69
    def get_limit(self, limit_name):
70
        """Returns the limit value for the specified limit"""
71
        limit_objs = Limit.objects.filter(name=limit_name, user=self)
72
        if len(limit_objs) == 1:
73
            return limit_objs[0].value
74
        
75
        return 0
76
        
77
    def _get_credit_quota(self):
78
        """Internal getter function for credit quota"""
79
        return self.get_limit('QUOTA_CREDIT')
80
        
81
    credit_quota = property(_get_credit_quota)
82
    
83
    def _get_monthly_rate(self):
84
        """Internal getter function for monthly credit issue rate"""
85
        return self.get_limit('MONTHLY_RATE')
86
        
87
    monthly_rate = property(_get_monthly_rate)
88
    
89
    def _get_min_credits(self):
90
        """Internal getter function for maximum number of violations"""
91
        return self.get_limit('MIN_CREDITS')
92
        
93
    min_credits = property(_get_min_credits)
94

    
95

    
96
class Image(models.Model):
97
    # This is WIP, FIXME
98
    IMAGE_STATES = (
99
        ('ACTIVE', 'Active'),
100
        ('SAVING', 'Saving'),
101
        ('DELETED', 'Deleted')
102
    )
103

    
104
    # The list of supported Image formats
105
    FORMATS = (
106
        ('dump', 'ext3 dump'),
107
        ('lvm', 'lvm snapshot'),
108
        ('ntfsclone', 'Windows Image produced by ntfsclone')
109
    )
110

    
111
    name = models.CharField('Image name', max_length=255)
112
    state = models.CharField('Current Image State', choices=IMAGE_STATES,
113
                                max_length=30)
114
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)
115
    created = models.DateTimeField('Time of creation', auto_now_add=True)
116
    updated = models.DateTimeField('Time of last update', auto_now=True)
117
    sourcevm = models.ForeignKey("VirtualMachine", null=True)
118
    backend_id = models.CharField(max_length=50, default='debian_base')
119
    format = models.CharField(choices=FORMATS, max_length=30, default='dump')
120
    public = models.BooleanField(default=False)
121

    
122
    class Meta:
123
        verbose_name = u'Image'
124

    
125
    def __unicode__(self):
126
        return u'%s' % ( self.name, )
127

    
128

    
129
class ImageMetadata(models.Model):
130
    meta_key = models.CharField('Image metadata key name', max_length=50)
131
    meta_value = models.CharField('Image metadata value', max_length=500)
132
    image = models.ForeignKey(Image)
133
    
134
    class Meta:
135
        verbose_name = u'Key-value pair of Image metadata.'
136
    
137
    def __unicode__(self):
138
        return u'%s, %s for %s' % (self.meta_key, self.meta_value, self.image.name)
139

    
140

    
141
class Limit(models.Model):
142
    LIMITS = (
143
        ('QUOTA_CREDIT', 'Maximum amount of credits per user'),
144
        ('MIN_CREDITS', 'Minimum amount of credits per user'),
145
        ('MONTHLY_RATE', 'Monthly credit issue rate')
146
    )
147
    user = models.ForeignKey(SynnefoUser)
148
    name = models.CharField('Limit key name', choices=LIMITS, max_length=30, null=False)
149
    value = models.IntegerField('Limit current value')
150
    
151
    class Meta:
152
        verbose_name = u'Enforced limit for user'
153
    
154
    def __unicode__(self):
155
        return u'Limit %s for user %s: %d' % (self.value, self.user, self.value)
156

    
157

    
158
class Flavor(models.Model):
159
    cpu = models.IntegerField('Number of CPUs', default=0)
160
    ram = models.IntegerField('Size of RAM', default=0)             # Size in MiB
161
    disk = models.IntegerField('Size of Disk space', default=0)     # Size in GiB
162
    
163
    class Meta:
164
        verbose_name = u'Virtual machine flavor'
165
        unique_together = ("cpu","ram","disk")
166
            
167
    def _get_name(self):
168
        """Returns flavor name (generated)"""
169
        return u'C%dR%dD%d' % (self.cpu, self.ram, self.disk)
170

    
171
    def _current_cost(self, active):
172
        """Returns active/inactive cost value
173

174
        set active = True to get active cost and False for the inactive.
175

176
        """
177
        fch_list = FlavorCost.objects.filter(flavor=self).order_by('-effective_from')
178
        if len(fch_list) > 0:
179
            if active:
180
                return fch_list[0].cost_active
181
            else:
182
                return fch_list[0].cost_inactive
183

    
184
        return 0
185

    
186
    def _current_cost_active(self):
187
        """Returns current active cost (property method)"""
188
        return self._current_cost(True)
189

    
190
    def _current_cost_inactive(self):
191
        """Returns current inactive cost (property method)"""
192
        return self._current_cost(False)
193

    
194
    name = property(_get_name)
195
    current_cost_active = property(_current_cost_active)
196
    current_cost_inactive = property(_current_cost_inactive)
197

    
198
    def __unicode__(self):
199
        return self.name
200

    
201

    
202
class FlavorCost(models.Model):
203
    cost_active = models.PositiveIntegerField('Active Cost')
204
    cost_inactive = models.PositiveIntegerField('Inactive Cost')
205
    effective_from = models.DateTimeField()
206
    flavor = models.ForeignKey(Flavor)
207
    
208
    class Meta:
209
        verbose_name = u'Pricing history for flavors'
210
    
211
    def __unicode__(self):
212
        return u'Costs (up, down)=(%d, %d) for %s since %s' % \
213
                (int(self.cost_active), int(self.cost_inactive),
214
                 self.flavor.name, self.effective_from)
215

    
216

    
217
class VirtualMachine(models.Model):
218
    # The list of possible actions for a VM
219
    ACTIONS = (
220
       ('CREATE', 'Create VM'),
221
       ('START', 'Start VM'),
222
       ('STOP', 'Shutdown VM'),
223
       ('SUSPEND', 'Admin Suspend VM'),
224
       ('REBOOT', 'Reboot VM'),
225
       ('DESTROY', 'Destroy VM')
226
    )
227
    # The internal operating state of a VM
228
    OPER_STATES = (
229
        ('BUILD', 'Queued for creation'),
230
        ('ERROR', 'Creation failed'),
231
        ('STOPPED', 'Stopped'),
232
        ('STARTED', 'Started'),
233
        ('DESTROYED', 'Destroyed')
234
    )
235
    # The list of possible operations on the backend
236
    BACKEND_OPCODES = (
237
        ('OP_INSTANCE_CREATE', 'Create Instance'),
238
        ('OP_INSTANCE_REMOVE', 'Remove Instance'),
239
        ('OP_INSTANCE_STARTUP', 'Startup Instance'),
240
        ('OP_INSTANCE_SHUTDOWN', 'Shutdown Instance'),
241
        ('OP_INSTANCE_REBOOT', 'Reboot Instance'),
242

    
243
        # These are listed here for completeness,
244
        # and are ignored for the time being
245
        ('OP_INSTANCE_SET_PARAMS', 'Set Instance Parameters'),
246
        ('OP_INSTANCE_QUERY_DATA', 'Query Instance Data'),
247
    )
248
    # A backend job may be in one of the following possible states
249
    BACKEND_STATUSES = (
250
        ('queued', 'request queued'),
251
        ('waiting', 'request waiting for locks'),
252
        ('canceling', 'request being canceled'),
253
        ('running', 'request running'),
254
        ('canceled', 'request canceled'),
255
        ('success', 'request completed successfully'),
256
        ('error', 'request returned error')
257
    )
258

    
259
    # The operating state of a VM,
260
    # upon the successful completion of a backend operation.
261
    OPER_STATE_FROM_OPCODE = {
262
        'OP_INSTANCE_CREATE': 'STARTED',
263
        'OP_INSTANCE_REMOVE': 'DESTROYED',
264
        'OP_INSTANCE_STARTUP': 'STARTED',
265
        'OP_INSTANCE_SHUTDOWN': 'STOPPED',
266
        'OP_INSTANCE_REBOOT': 'STARTED',
267
        'OP_INSTANCE_SET_PARAMS': None,
268
        'OP_INSTANCE_QUERY_DATA': None
269
    }
270

    
271
    # This dictionary contains the correspondence between
272
    # internal operating states and Server States as defined
273
    # by the Rackspace API.
274
    RSAPI_STATE_FROM_OPER_STATE = {
275
        "BUILD": "BUILD",
276
        "ERROR": "ERROR",
277
        "STOPPED": "STOPPED",
278
        "STARTED": "ACTIVE",
279
        "DESTROYED": "DELETED"
280
    }
281

    
282
    name = models.CharField('Virtual Machine Name', max_length=255)
283
    owner = models.ForeignKey(SynnefoUser)
284
    created = models.DateTimeField(auto_now_add=True)
285
    updated = models.DateTimeField(auto_now=True)
286
    charged = models.DateTimeField(default=datetime.datetime.now())
287
    sourceimage = models.ForeignKey("Image", null=False) 
288
    hostid = models.CharField(max_length=100)
289
    flavor = models.ForeignKey(Flavor)
290
    deleted = models.BooleanField('Deleted', default=False)
291
    suspended = models.BooleanField('Administratively Suspended', default=False)
292

    
293
    # VM State 
294
    # The following fields are volatile data, in the sense
295
    # that they need not be persistent in the DB, but rather
296
    # get generated at runtime by quering Ganeti and applying
297
    # updates received from Ganeti.
298
    
299
    # In the future they could be moved to a separate caching layer
300
    # and removed from the database.
301
    # [vkoukis] after discussion with [faidon].
302
    action = models.CharField(choices=ACTIONS, max_length=30, null=True)
303
    operstate = models.CharField(choices=OPER_STATES, max_length=30, null=True)
304
    backendjobid = models.PositiveIntegerField(null=True)
305
    backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30,
306
                                     null=True)
307
    backendjobstatus = models.CharField(choices=BACKEND_STATUSES, max_length=30,
308
                                        null=True)
309
    backendlogmsg = models.TextField(null=True)
310
    buildpercentage = models.IntegerField(default=0)
311

    
312
    # Error classes
313
    class InvalidBackendIdError(Exception):
314
         def __init__(self, value):
315
            self.value = value
316
         def __str__(self):
317
            return repr(self.value)
318

    
319
    class InvalidBackendMsgError(Exception):
320
         def __init__(self, opcode, status):
321
            self.opcode = opcode
322
            self.status = status
323
         def __str__(self):
324
            return repr("<opcode: %s, status: %s>" % (str(self.opcode),
325
                                                      str(self.status)))
326

    
327
    class InvalidActionError(Exception):
328
         def __init__(self, action):
329
            self._action = action
330
         def __str__(self):
331
            return repr(str(self._action))
332
    
333
    class DeletedError(Exception):
334
        pass
335
    
336
    class BuildingError(Exception):
337
        pass
338

    
339
    #class IllegalState(Exception):
340
    #    pass
341
    
342
    def __init__(self, *args, **kw):
343
        """Initialize state for just created VM instances."""
344
        super(VirtualMachine, self).__init__(*args, **kw)
345
        # This gets called BEFORE an instance gets save()d for
346
        # the first time.
347
        if not self.pk: 
348
            self.action = None
349
            self.backendjobid = None
350
            self.backendjobstatus = None
351
            self.backendopcode = None
352
            self.backendlogmsg = None
353
            self.operstate = 'BUILD'
354

    
355
    def _get_backend_id(self):
356
        """Returns the backend id for this VM by prepending backend-prefix."""
357
        if not self.id:
358
            raise VirtualMachine.InvalidBackendIdError("self.id is None")
359
        return '%s%s' % (settings.BACKEND_PREFIX_ID, str(self.id))
360

    
361
    backend_id = property(_get_backend_id)
362

    
363
    class Meta:
364
        verbose_name = u'Virtual machine instance'
365
        get_latest_by = 'created'
366
    
367
    def __unicode__(self):
368
        return self.name
369

    
370

    
371
class VirtualMachineGroup(models.Model):
372
    """Groups of VMs for SynnefoUsers"""
373
    name = models.CharField(max_length=255)
374
    created = models.DateTimeField('Time of creation', auto_now_add=True)
375
    updated = models.DateTimeField('Time of last update', auto_now=True)
376
    owner = models.ForeignKey(SynnefoUser)
377
    machines = models.ManyToManyField(VirtualMachine)
378

    
379
    class Meta:
380
        verbose_name = u'Virtual Machine Group'
381
        verbose_name_plural = 'Virtual Machine Groups'
382
        ordering = ['name']
383
    
384
    def __unicode__(self):
385
        return self.name
386

    
387

    
388
class VirtualMachineMetadata(models.Model):
389
    meta_key = models.CharField(max_length=50)
390
    meta_value = models.CharField(max_length=500)
391
    vm = models.ForeignKey(VirtualMachine)
392
    
393
    class Meta:
394
        verbose_name = u'Key-value pair of metadata for a VM.'
395
    
396
    def __unicode__(self):
397
        return u'%s, %s for %s' % (self.meta_key, self.meta_value, self.vm.name)
398

    
399

    
400
class Debit(models.Model):
401
    when = models.DateTimeField()
402
    user = models.ForeignKey(SynnefoUser)
403
    vm = models.ForeignKey(VirtualMachine)
404
    description = models.TextField()
405
    
406
    class Meta:
407
        verbose_name = u'Accounting log'
408

    
409
    def __unicode__(self):
410
        return u'%s - %s - %s - %s' % (self.user.id, self.vm.name,
411
                                       str(self.when), self.description)
412

    
413

    
414
class Disk(models.Model):
415
    name = models.CharField(max_length=255)
416
    created = models.DateTimeField('Time of creation', auto_now_add=True)
417
    updated = models.DateTimeField('Time of last update', auto_now=True)
418
    size = models.PositiveIntegerField('Disk size in GBs')
419
    vm = models.ForeignKey(VirtualMachine, blank=True, null=True)
420
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)  
421

    
422
    class Meta:
423
        verbose_name = u'Disk instance'
424

    
425
    def __unicode__(self):
426
        return self.name
427

    
428

    
429
class Network(models.Model):
430
    NETWORK_STATES = (
431
        ('ACTIVE', 'Active'),
432
        ('DELETED', 'Deleted')
433
    )
434
    
435
    name = models.CharField(max_length=255)
436
    created = models.DateTimeField(auto_now_add=True)
437
    updated = models.DateTimeField(auto_now=True)
438
    owner = models.ForeignKey(SynnefoUser, null=True)
439
    state = models.CharField(choices=NETWORK_STATES, max_length=30)
440
    public = models.BooleanField(default=False)
441
    link = models.ForeignKey('NetworkLink', related_name='+')
442
    machines = models.ManyToManyField(VirtualMachine,
443
                                        through='NetworkInterface')
444
    
445
    def __unicode__(self):
446
        return self.name
447

    
448

    
449
class Invitations(models.Model):
450
    source = models.ForeignKey(SynnefoUser, related_name="source")
451
    target = models.ForeignKey(SynnefoUser, related_name="target")
452
    accepted = models.BooleanField('Is the invitation accepted?', default=False)
453
    created = models.DateTimeField(auto_now_add=True)
454
    updated = models.DateTimeField(auto_now=True)
455
    level = models.IntegerField('Invitation depth level', null=True)
456

    
457
    class Meta:
458
        verbose_name = u'Invitation'
459

    
460
    def __unicode__(self):
461
        return "From: %s, To: %s" % (self.source, self.target)
462

    
463

    
464
class NetworkInterface(models.Model):
465
    FIREWALL_PROFILES = (
466
        ('ENABLED', 'Enabled'),
467
        ('DISABLED', 'Disabled'),
468
        ('PROTECTED', 'Protected')
469
    )
470
    
471
    machine = models.ForeignKey(VirtualMachine, related_name='nics')
472
    network = models.ForeignKey(Network, related_name='nics')
473
    created = models.DateTimeField(auto_now_add=True)
474
    updated = models.DateTimeField(auto_now=True)
475
    index = models.IntegerField(null=True)
476
    mac = models.CharField(max_length=17, null=True)
477
    ipv4 = models.CharField(max_length=15, null=True)
478
    ipv6 = models.CharField(max_length=100, null=True)
479
    firewall_profile = models.CharField(choices=FIREWALL_PROFILES,
480
                                        max_length=30, null=True)
481
    
482
    def __unicode__(self):
483
        return '%s@%s' % (self.machine.name, self.network.name)
484

    
485

    
486
class NetworkLink(models.Model):
487
    network = models.ForeignKey(Network, null=True, related_name='+')
488
    index = models.IntegerField()
489
    name = models.CharField(max_length=255)
490
    available = models.BooleanField(default=True)
491
    
492
    def __unicode__(self):
493
        return self.name