Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / db / models.py @ 9c0ac5af

History | View | Annotate | Download (18.6 kB)

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

    
30
import datetime
31

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

    
35

    
36
class SynnefoUser(models.Model):
37
    #TODO: Amend this when we have groups
38
    ACCOUNT_TYPE = (
39
        ('STUDENT', 'Student'),
40
        ('PROFESSOR', 'Professor'),
41
        ('USER', 'Generic User'),
42
        ('HELPDESK', 'Helpdesk User'),
43
        ('ADMIN', 'Admin User')
44
    )
45
    
46
    ACCOUNT_STATE = (
47
        ('ACTIVE', 'Active'),
48
        ('DELETED', 'Deleted'),
49
        ('SUSPENDED', 'Suspended')
50
    )
51
    
52
    name = models.CharField('Synnefo Username', max_length=255, default='')
53
    realname = models.CharField('Real Name', max_length=255, default='')
54
    uniq = models.CharField('External Unique ID', max_length=255,null=True)
55
    credit = models.IntegerField('Credit Balance')
56
    auth_token = models.CharField('Authentication Token', max_length=32,
57
            null=True)
58
    auth_token_created = models.DateTimeField('Time of auth token creation',
59
            auto_now_add=True, null=True)
60
    auth_token_expires = models.DateTimeField('Time of auth token expiration',
61
            auto_now_add=True, null=True)
62
    tmp_auth_token = models.CharField('Temporary authentication token',
63
            max_length=32, null=True)
64
    tmp_auth_token_expires = models.DateTimeField('Time of temporary auth '
65
            'token expiration', auto_now_add=True, null=True)
66
    type = models.CharField('Account type', choices=ACCOUNT_TYPE,
67
            max_length=30)
68
    state = models.CharField('Account state', choices=ACCOUNT_STATE,
69
            max_length=30, default='ACTIVE')
70
    created = models.DateTimeField('Time of creation', auto_now_add=True)
71
    updated = models.DateTimeField('Time of last update', auto_now=True)
72
    max_invitations = models.IntegerField('Max number of invitations',
73
            null=True)
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
        return 0
84
    
85
    @property
86
    def credit_quota(self):
87
        """Internal getter function for credit quota"""
88
        return self.get_limit('QUOTA_CREDIT')
89
    
90
    @property
91
    def monthly_rate(self):
92
        """Internal getter function for monthly credit issue rate"""
93
        return self.get_limit('MONTHLY_RATE')
94
    
95
    @property
96
    def min_credits(self):
97
        """Internal getter function for maximum number of violations"""
98
        return self.get_limit('MIN_CREDITS')
99

    
100

    
101
class Image(models.Model):
102
    IMAGE_STATES = (
103
        ('ACTIVE', 'Active'),
104
        ('SAVING', 'Saving'),
105
        ('DELETED', 'Deleted')
106
    )
107

    
108
    # The list of supported Image formats
109
    FORMATS = (
110
        ('dump', 'ext3 dump'),
111
        ('extdump', 'Raw ext2/3/4 dump'),
112
        ('lvm', 'lvm snapshot'),
113
        ('ntfsclone', 'Windows Image produced by ntfsclone'),
114
        ('ntfsdump', 'Raw NTFS dump'),
115
        ('diskdump', 'Raw dump of a hard disk')
116
    )
117

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

    
132

    
133
class ImageMetadata(models.Model):
134
    meta_key = models.CharField('Image metadata key name', max_length=50)
135
    meta_value = models.CharField('Image metadata value', max_length=500)
136
    image = models.ForeignKey(Image, related_name='metadata')
137
    
138
    class Meta:
139
        unique_together = (('meta_key', 'image'),)
140
        verbose_name = u'Key-value pair of Image metadata.'
141
    
142
    def __unicode__(self):
143
        return u'%s: %s' % (self.meta_key, self.meta_value)
144

    
145

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

    
164

    
165
class Flavor(models.Model):
166
    cpu = models.IntegerField('Number of CPUs', default=0)
167
    ram = models.IntegerField('RAM size in MiB', default=0)
168
    disk = models.IntegerField('Disk size in GiB', default=0)
169
    disk_template = models.CharField('Disk template', max_length=32,
170
            default=settings.DEFAULT_GANETI_DISK_TEMPLATE)
171
    deleted = models.BooleanField('Deleted', default=False)
172
    
173
    class Meta:
174
        verbose_name = u'Virtual machine flavor'
175
        unique_together = ('cpu', 'ram', 'disk')
176
    
177
    @property
178
    def name(self):
179
        """Returns flavor name (generated)"""
180
        return u'C%dR%dD%d' % (self.cpu, self.ram, self.disk)
181

    
182
    def _current_cost(self, active):
183
        """Returns active/inactive cost value
184

185
        set active = True to get active cost and False for the inactive.
186
        """
187
        
188
        costs = FlavorCost.objects.filter(flavor=self)
189
        fch_list = costs.order_by('-effective_from')
190
        if len(fch_list) > 0:
191
            if active:
192
                return fch_list[0].cost_active
193
            else:
194
                return fch_list[0].cost_inactive
195

    
196
        return 0
197
    
198
    @property
199
    def current_cost_active(self):
200
        """Returns current active cost (property method)"""
201
        return self._current_cost(True)
202
    
203
    @property
204
    def current_cost_inactive(self):
205
        """Returns current inactive cost (property method)"""
206
        return self._current_cost(False)
207
    
208
    def __unicode__(self):
209
        return self.name
210

    
211

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

    
226

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

    
255
        # These are listed here for completeness,
256
        # and are ignored for the time being
257
        ('OP_INSTANCE_SET_PARAMS', 'Set Instance Parameters'),
258
        ('OP_INSTANCE_QUERY_DATA', 'Query Instance Data'),
259
        ('OP_INSTANCE_REINSTALL', 'Reinstall Instance'),
260
        ('OP_INSTANCE_ACTIVATE_DISKS', 'Activate Disks'),
261
        ('OP_INSTANCE_DEACTIVATE_DISKS', 'Deactivate Disks'),
262
        ('OP_INSTANCE_REPLACE_DISKS', 'Replace Disks'),
263
        ('OP_INSTANCE_MIGRATE', 'Migrate Instance'),
264
        ('OP_INSTANCE_CONSOLE', 'Get Instance Console'),
265
        ('OP_INSTANCE_RECREATE_DISKS', 'Recreate Disks'),
266
        ('OP_INSTANCE_FAILOVER', 'Failover Instance')
267
    )
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
    imageid = models.CharField(max_length=100, 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',
323
            default=False)
324

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

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

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

    
359
    class InvalidActionError(Exception):
360
         def __init__(self, action):
361
            self._action = action
362
         def __str__(self):
363
            return repr(str(self._action))
364
    
365
    class DeletedError(Exception):
366
        pass
367
    
368
    class BuildingError(Exception):
369
        pass
370
    
371
    def __init__(self, *args, **kw):
372
        """Initialize state for just created VM instances."""
373
        super(VirtualMachine, self).__init__(*args, **kw)
374
        # This gets called BEFORE an instance gets save()d for
375
        # the first time.
376
        if not self.pk: 
377
            self.action = None
378
            self.backendjobid = None
379
            self.backendjobstatus = None
380
            self.backendopcode = None
381
            self.backendlogmsg = None
382
            self.operstate = 'BUILD'
383
    
384
    @property
385
    def backend_id(self):
386
        """Returns the backend id for this VM by prepending backend-prefix."""
387
        if not self.id:
388
            raise VirtualMachine.InvalidBackendIdError("self.id is None")
389
        return '%s%s' % (settings.BACKEND_PREFIX_ID, self.id)
390
    
391
    class Meta:
392
        verbose_name = u'Virtual machine instance'
393
        get_latest_by = 'created'
394
    
395
    def __unicode__(self):
396
        return self.name
397

    
398

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

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

    
415

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

    
428

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

    
438
    def __unicode__(self):
439
        return u'%s - %s - %s - %s' % (self.user.id, self.vm.name,
440
                self.when, self.description)
441

    
442

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

    
451
    class Meta:
452
        verbose_name = u'Disk instance'
453

    
454
    def __unicode__(self):
455
        return self.name
456

    
457

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

    
477

    
478
class Invitations(models.Model):
479
    source = models.ForeignKey(SynnefoUser, related_name="source")
480
    target = models.ForeignKey(SynnefoUser, related_name="target")
481
    accepted = models.BooleanField('Is the invitation accepted?',
482
            default=False)
483
    created = models.DateTimeField(auto_now_add=True)
484
    updated = models.DateTimeField(auto_now=True)
485
    level = models.IntegerField('Invitation depth level', null=True)
486
    
487
    def __unicode__(self):
488
        return "From: %s, To: %s" % (self.source, self.target)
489

    
490

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

    
512

    
513
class NetworkLink(models.Model):
514
    network = models.ForeignKey(Network, null=True, related_name='+')
515
    index = models.IntegerField()
516
    name = models.CharField(max_length=255)
517
    available = models.BooleanField(default=True)
518
    
519
    def __unicode__(self):
520
        return self.name