Statistics
| Branch: | Tag: | Revision:

root / db / models.py @ b8d1a948

History | View | Annotate | Download (17.6 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
    # Error classes
312
    class InvalidBackendIdError(Exception):
313
         def __init__(self, value):
314
            self.value = value
315
         def __str__(self):
316
            return repr(self.value)
317

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

    
326
    class InvalidActionError(Exception):
327
         def __init__(self, action):
328
            self._action = action
329
         def __str__(self):
330
            return repr(str(self._action))
331
    
332
    class DeletedError(Exception):
333
        pass
334
    
335
    class BuildingError(Exception):
336
        pass
337
    
338
    def __init__(self, *args, **kw):
339
        """Initialize state for just created VM instances."""
340
        super(VirtualMachine, self).__init__(*args, **kw)
341
        # This gets called BEFORE an instance gets save()d for
342
        # the first time.
343
        if not self.pk: 
344
            self.action = None
345
            self.backendjobid = None
346
            self.backendjobstatus = None
347
            self.backendopcode = None
348
            self.backendlogmsg = None
349
            self.operstate = 'BUILD'
350

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

    
357
    backend_id = property(_get_backend_id)
358

    
359
    class Meta:
360
        verbose_name = u'Virtual machine instance'
361
        get_latest_by = 'created'
362
    
363
    def __unicode__(self):
364
        return self.name
365

    
366

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

    
375
    class Meta:
376
        verbose_name = u'Virtual Machine Group'
377
        verbose_name_plural = 'Virtual Machine Groups'
378
        ordering = ['name']
379
    
380
    def __unicode__(self):
381
        return self.name
382

    
383

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

    
395

    
396
class Debit(models.Model):
397
    when = models.DateTimeField()
398
    user = models.ForeignKey(SynnefoUser)
399
    vm = models.ForeignKey(VirtualMachine)
400
    description = models.TextField()
401
    
402
    class Meta:
403
        verbose_name = u'Accounting log'
404

    
405
    def __unicode__(self):
406
        return u'%s - %s - %s - %s' % (self.user.id, self.vm.name,
407
                                       str(self.when), self.description)
408

    
409

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

    
418
    class Meta:
419
        verbose_name = u'Disk instance'
420

    
421
    def __unicode__(self):
422
        return self.name
423

    
424

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

    
444

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

    
453
    class Meta:
454
        verbose_name = u'Invitation'
455

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

    
459

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

    
481

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