Statistics
| Branch: | Tag: | Revision:

root / db / models.py @ c25cc9ec

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

    
311
    buildpercentage = models.IntegerField(null=True)
312

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

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

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

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

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

    
362
    backend_id = property(_get_backend_id)
363

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

    
371

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

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

    
388

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

    
400

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

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

    
414

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

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

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

    
429

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

    
449

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

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

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

    
464

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

    
486

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