Statistics
| Branch: | Tag: | Revision:

root / db / models.py @ 26563957

History | View | Annotate | Download (15.4 kB)

1
# vim: ts=4 sts=4 et ai sw=4 fileencoding=utf-8
2

    
3
from django.conf import settings
4
from django.db import models
5

    
6
import datetime
7

    
8
class SynnefoUser(models.Model):
9

    
10
    #TODO: Amend this when we have groups
11
    ACCOUNT_TYPE = (
12
        ('STUDENT', 'Student'),
13
        ('PROFESSOR', 'Professor'),
14
        ('USER', 'Generic User')
15
    )
16

    
17
    name = models.CharField('Synnefo Username', max_length=255, default='')
18
    realname = models.CharField('Real Name', max_length=255, default='')
19
    uniq = models.CharField('External Unique ID', max_length=255,null=True)
20
    credit = models.IntegerField('Credit Balance')
21
    auth_token = models.CharField('Authentication Token', max_length=32, null=True)
22
    auth_token_created = models.DateTimeField('Time of auth token creation', auto_now_add=True, null = True)
23
    auth_token_expires = models.DateTimeField('Time of auth token expiration', auto_now_add=True, null = True)
24
    type = models.CharField('Current Image State', choices=ACCOUNT_TYPE, max_length=30)
25
    created = models.DateTimeField('Time of creation', auto_now_add=True)
26
    updated = models.DateTimeField('Time of last update', auto_now=True)
27

    
28
    class Meta:
29
        verbose_name = u'Synnefo User'
30

    
31
    def __unicode__(self):
32
        return self.name
33

    
34
    def get_limit(self, limit_name):
35
        """Returns the limit value for the specified limit"""
36
        limit_objs = Limit.objects.filter(name=limit_name, user=self)
37
        if len(limit_objs) == 1:
38
            return limit_objs[0].value
39
        
40
        return 0
41
        
42
    def _get_credit_quota(self):
43
        """Internal getter function for credit quota"""
44
        return self.get_limit('QUOTA_CREDIT')
45
        
46
    credit_quota = property(_get_credit_quota)
47
    
48
    def _get_monthly_rate(self):
49
        """Internal getter function for monthly credit issue rate"""
50
        return self.get_limit('MONTHLY_RATE')
51
        
52
    monthly_rate = property(_get_monthly_rate)
53
    
54
    def _get_min_credits(self):
55
        """Internal getter function for maximum number of violations"""
56
        return self.get_limit('MIN_CREDITS')
57
        
58
    min_credits = property(_get_min_credits)
59

    
60

    
61
class Image(models.Model):
62
    # This is WIP, FIXME
63
    IMAGE_STATES = (
64
        ('ACTIVE', 'Active'),
65
        ('SAVING', 'Saving'),
66
        ('DELETED', 'Deleted')
67
    )
68

    
69
    # The list of supported Image formats
70
    FORMATS = (
71
        ('dump', 'ext2 dump'),
72
        ('lvm', 'lvm snapshot')
73
    )
74

    
75
    name = models.CharField('Image name', max_length=255)
76
    state = models.CharField('Current Image State', choices=IMAGE_STATES,
77
                                max_length=30)
78
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)
79
    created = models.DateTimeField('Time of creation', auto_now_add=True)
80
    updated = models.DateTimeField('Time of last update', auto_now=True)
81
    sourcevm = models.ForeignKey("VirtualMachine", null=True)
82
    backend_id = models.CharField(max_length=50, default='debian_base')
83
    format = models.CharField(choices=FORMATS, max_length=30, default='dump')
84
    public = models.BooleanField(default=False)
85

    
86
    class Meta:
87
        verbose_name = u'Image'
88

    
89
    def __unicode__(self):
90
        return u'%s' % ( self.name, )
91

    
92

    
93
class ImageMetadata(models.Model):
94
    meta_key = models.CharField('Image metadata key name', max_length=50)
95
    meta_value = models.CharField('Image metadata value', max_length=500)
96
    image = models.ForeignKey(Image)
97
    
98
    class Meta:
99
        verbose_name = u'Key-value pair of Image metadata.'
100
    
101
    def __unicode__(self):
102
        return u'%s, %s for %s' % (self.meta_key, self.meta_value, self.image.name)
103

    
104

    
105
class Limit(models.Model):
106
    LIMITS = (
107
        ('QUOTA_CREDIT', 'Maximum amount of credits per user'),
108
        ('MIN_CREDITS', 'Minimum amount of credits per user'),
109
        ('MONTHLY_RATE', 'Monthly credit issue rate')
110
    )
111
    user = models.ForeignKey(SynnefoUser)
112
    name = models.CharField('Limit key name', choices=LIMITS, max_length=30, null=False)
113
    value = models.IntegerField('Limit current value')
114
    
115
    class Meta:
116
        verbose_name = u'Enforced limit for user'
117
    
118
    def __unicode__(self):
119
        return u'Limit %s for user %s: %d' % (self.value, self.user, self.value)
120

    
121

    
122
class Flavor(models.Model):
123
    cpu = models.IntegerField('Number of CPUs', default=0)
124
    ram = models.IntegerField('Size of RAM', default=0)             # Size in MiB
125
    disk = models.IntegerField('Size of Disk space', default=0)     # Size in GiB
126
    
127
    class Meta:
128
        verbose_name = u'Virtual machine flavor'
129
        unique_together = ("cpu","ram","disk")
130
            
131
    def _get_name(self):
132
        """Returns flavor name (generated)"""
133
        return u'C%dR%dD%d' % (self.cpu, self.ram, self.disk)
134

    
135
    def _current_cost(self, active):
136
        """Returns active/inactive cost value
137

138
        set active = True to get active cost and False for the inactive.
139

140
        """
141
        fch_list = FlavorCost.objects.filter(flavor=self).order_by('-effective_from')
142
        if len(fch_list) > 0:
143
            if active:
144
                return fch_list[0].cost_active
145
            else:
146
                return fch_list[0].cost_inactive
147

    
148
        return 0
149

    
150
    def _current_cost_active(self):
151
        """Returns current active cost (property method)"""
152
        return self._current_cost(True)
153

    
154
    def _current_cost_inactive(self):
155
        """Returns current inactive cost (property method)"""
156
        return self._current_cost(False)
157

    
158
    name = property(_get_name)
159
    current_cost_active = property(_current_cost_active)
160
    current_cost_inactive = property(_current_cost_inactive)
161

    
162
    def __unicode__(self):
163
        return self.name
164

    
165

    
166
class FlavorCost(models.Model):
167
    cost_active = models.PositiveIntegerField('Active Cost')
168
    cost_inactive = models.PositiveIntegerField('Inactive Cost')
169
    effective_from = models.DateTimeField()
170
    flavor = models.ForeignKey(Flavor)
171
    
172
    class Meta:
173
        verbose_name = u'Pricing history for flavors'
174
    
175
    def __unicode__(self):
176
        return u'Costs (up, down)=(%d, %d) for %s since %s' % \
177
                (int(self.cost_active), int(self.cost_inactive),
178
                 self.flavor.name, self.effective_from)
179

    
180

    
181
class VirtualMachine(models.Model):
182
    # The list of possible actions for a VM
183
    ACTIONS = (
184
       ('CREATE', 'Create VM'),
185
       ('START', 'Start VM'),
186
       ('STOP', 'Shutdown VM'),
187
       ('SUSPEND', 'Admin Suspend VM'),
188
       ('REBOOT', 'Reboot VM'),
189
       ('DESTROY', 'Destroy VM')
190
    )
191
    # The internal operating state of a VM
192
    OPER_STATES = (
193
        ('BUILD', 'Queued for creation'),
194
        ('ERROR', 'Creation failed'),
195
        ('STOPPED', 'Stopped'),
196
        ('STARTED', 'Started'),
197
        ('DESTROYED', 'Destroyed')
198
    )
199
    # The list of possible operations on the backend
200
    BACKEND_OPCODES = (
201
        ('OP_INSTANCE_CREATE', 'Create Instance'),
202
        ('OP_INSTANCE_REMOVE', 'Remove Instance'),
203
        ('OP_INSTANCE_STARTUP', 'Startup Instance'),
204
        ('OP_INSTANCE_SHUTDOWN', 'Shutdown Instance'),
205
        ('OP_INSTANCE_REBOOT', 'Reboot Instance'),
206

    
207
        # These are listed here for completeness,
208
        # and are ignored for the time being
209
        ('OP_INSTANCE_SET_PARAMS', 'Set Instance Parameters'),
210
        ('OP_INSTANCE_QUERY_DATA', 'Query Instance Data'),
211
    )
212
    # A backend job may be in one of the following possible states
213
    BACKEND_STATUSES = (
214
        ('queued', 'request queued'),
215
        ('waiting', 'request waiting for locks'),
216
        ('canceling', 'request being canceled'),
217
        ('running', 'request running'),
218
        ('canceled', 'request canceled'),
219
        ('success', 'request completed successfully'),
220
        ('error', 'request returned error')
221
    )
222

    
223
    # The operating state of a VM,
224
    # upon the successful completion of a backend operation.
225
    OPER_STATE_FROM_OPCODE = {
226
        'OP_INSTANCE_CREATE': 'STARTED',
227
        'OP_INSTANCE_REMOVE': 'DESTROYED',
228
        'OP_INSTANCE_STARTUP': 'STARTED',
229
        'OP_INSTANCE_SHUTDOWN': 'STOPPED',
230
        'OP_INSTANCE_REBOOT': 'STARTED',
231
        'OP_INSTANCE_SET_PARAMS': None,
232
        'OP_INSTANCE_QUERY_DATA': None
233
    }
234

    
235
    # This dictionary contains the correspondence between
236
    # internal operating states and Server States as defined
237
    # by the Rackspace API.
238
    RSAPI_STATE_FROM_OPER_STATE = {
239
        "BUILD": "BUILD",
240
        "ERROR": "ERROR",
241
        "STOPPED": "STOPPED",
242
        "STARTED": "ACTIVE",
243
        "DESTROYED": "DELETED"
244
    }
245

    
246
    name = models.CharField('Virtual Machine Name', max_length=255)
247
    owner = models.ForeignKey(SynnefoUser)
248
    created = models.DateTimeField(auto_now_add=True)
249
    updated = models.DateTimeField(auto_now=True)
250
    charged = models.DateTimeField(default=datetime.datetime.now())
251
    sourceimage = models.ForeignKey("Image", null=False) 
252
    hostid = models.CharField(max_length=100)
253
    flavor = models.ForeignKey(Flavor)
254
    deleted = models.BooleanField('Deleted', default=False)
255
    suspended = models.BooleanField('Administratively Suspended', default=False)
256

    
257
    # VM State 
258
    # The following fields are volatile data, in the sense
259
    # that they need not be persistent in the DB, but rather
260
    # get generated at runtime by quering Ganeti and applying
261
    # updates received from Ganeti.
262
    
263
    # In the future they could be moved to a separate caching layer
264
    # and removed from the database.
265
    # [vkoukis] after discussion with [faidon].
266
    action = models.CharField(choices=ACTIONS, max_length=30, null=True)
267
    operstate = models.CharField(choices=OPER_STATES, max_length=30, null=True)
268
    backendjobid = models.PositiveIntegerField(null=True)
269
    backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30,
270
                                     null=True)
271
    backendjobstatus = models.CharField(choices=BACKEND_STATUSES, max_length=30,
272
                                        null=True)
273
    backendlogmsg = models.TextField(null=True)
274

    
275
    # Error classes
276
    class InvalidBackendIdError(Exception):
277
         def __init__(self, value):
278
            self.value = value
279
         def __str__(self):
280
            return repr(self.value)
281

    
282
    class InvalidBackendMsgError(Exception):
283
         def __init__(self, opcode, status):
284
            self.opcode = opcode
285
            self.status = status
286
         def __str__(self):
287
            return repr("<opcode: %s, status: %s>" % (str(self.opcode),
288
                                                      str(self.status)))
289

    
290
    class InvalidActionError(Exception):
291
         def __init__(self, action):
292
            self._action = action
293
         def __str__(self):
294
            return repr(str(self._action))
295
    
296
    class DeletedError(Exception):
297
        pass
298
    
299
    class BuildingError(Exception):
300
        pass
301
    
302
    def __init__(self, *args, **kw):
303
        """Initialize state for just created VM instances."""
304
        super(VirtualMachine, self).__init__(*args, **kw)
305
        # This gets called BEFORE an instance gets save()d for
306
        # the first time.
307
        if not self.pk: 
308
            self.action = None
309
            self.backendjobid = None
310
            self.backendjobstatus = None
311
            self.backendopcode = None
312
            self.backendlogmsg = None
313
            self.operstate = 'BUILD'
314

    
315
    def _get_backend_id(self):
316
        """Returns the backend id for this VM by prepending backend-prefix."""
317
        if not self.id:
318
            raise VirtualMachine.InvalidBackendIdError("self.id is None")
319
        return '%s%s' % (settings.BACKEND_PREFIX_ID, str(self.id))
320

    
321
    backend_id = property(_get_backend_id)
322

    
323
    class Meta:
324
        verbose_name = u'Virtual machine instance'
325
        get_latest_by = 'created'
326
    
327
    def __unicode__(self):
328
        return self.name
329

    
330

    
331
class VirtualMachineGroup(models.Model):
332
    """Groups of VMs for SynnefoUsers"""
333
    name = models.CharField(max_length=255)
334
    created = models.DateTimeField('Time of creation', auto_now_add=True)
335
    updated = models.DateTimeField('Time of last update', auto_now=True)
336
    owner = models.ForeignKey(SynnefoUser)
337
    machines = models.ManyToManyField(VirtualMachine)
338

    
339
    class Meta:
340
        verbose_name = u'Virtual Machine Group'
341
        verbose_name_plural = 'Virtual Machine Groups'
342
        ordering = ['name']
343
    
344
    def __unicode__(self):
345
        return self.name
346

    
347

    
348
class VirtualMachineMetadata(models.Model):
349
    meta_key = models.CharField(max_length=50)
350
    meta_value = models.CharField(max_length=500)
351
    vm = models.ForeignKey(VirtualMachine)
352
    
353
    class Meta:
354
        verbose_name = u'Key-value pair of metadata for a VM.'
355
    
356
    def __unicode__(self):
357
        return u'%s, %s for %s' % (self.meta_key, self.meta_value, self.vm.name)
358

    
359

    
360
class Debit(models.Model):
361
    when = models.DateTimeField()
362
    user = models.ForeignKey(SynnefoUser)
363
    vm = models.ForeignKey(VirtualMachine)
364
    description = models.TextField()
365
    
366
    class Meta:
367
        verbose_name = u'Accounting log'
368

    
369
    def __unicode__(self):
370
        return u'%s - %s - %s - %s' % (self.user.id, self.vm.name,
371
                                       str(self.when), self.description)
372

    
373

    
374
class Disk(models.Model):
375
    name = models.CharField(max_length=255)
376
    created = models.DateTimeField('Time of creation', auto_now_add=True)
377
    updated = models.DateTimeField('Time of last update', auto_now=True)
378
    size = models.PositiveIntegerField('Disk size in GBs')
379
    vm = models.ForeignKey(VirtualMachine, blank=True, null=True)
380
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)  
381

    
382
    class Meta:
383
        verbose_name = u'Disk instance'
384

    
385
    def __unicode__(self):
386
        return self.name
387

    
388

    
389
class Network(models.Model):
390
    NETWORK_STATES = (
391
        ('ACTIVE', 'Active'),
392
        ('DELETED', 'Deleted')
393
    )
394
    
395
    name = models.CharField(max_length=255)
396
    created = models.DateTimeField(auto_now_add=True)
397
    updated = models.DateTimeField(auto_now=True)
398
    owner = models.ForeignKey(SynnefoUser, null=True)
399
    state = models.CharField(choices=NETWORK_STATES, max_length=30)
400
    public = models.BooleanField(default=False)
401
    link = models.ForeignKey('NetworkLink', related_name='+')
402
    machines = models.ManyToManyField(VirtualMachine,
403
                                        through='NetworkInterface')
404
    
405
    def __unicode__(self):
406
        return self.name
407

    
408

    
409
class Invitations(models.Model):
410
    source = models.ForeignKey(SynnefoUser, related_name="source")
411
    target = models.ForeignKey(SynnefoUser, related_name="target")
412
    accepted = models.BooleanField('Is the invitation accepted?', default=False)
413
    created = models.DateTimeField(auto_now_add=True)
414
    updated = models.DateTimeField(auto_now=True)
415

    
416
    class Meta:
417
        verbose_name = u'Invitation'
418

    
419
    def __unicode__(self):
420
        return self.name
421

    
422

    
423
class NetworkInterface(models.Model):
424
    FIREWALL_PROFILES = (
425
        ('ENABLED', 'Enabled'),
426
        ('DISABLED', 'Disabled'),
427
        ('PROTECTED', 'Protected')
428
    )
429
    
430
    machine = models.ForeignKey(VirtualMachine, related_name='nics')
431
    network = models.ForeignKey(Network, related_name='nics')
432
    created = models.DateTimeField(auto_now_add=True)
433
    updated = models.DateTimeField(auto_now=True)
434
    index = models.IntegerField(null=True)
435
    mac = models.CharField(max_length=17, null=True)
436
    ipv4 = models.CharField(max_length=15, null=True)
437
    ipv6 = models.CharField(max_length=100, null=True)
438
    firewall_profile = models.CharField(choices=FIREWALL_PROFILES,
439
                                        max_length=30, null=True)
440

    
441

    
442
class NetworkLink(models.Model):
443
    network = models.ForeignKey(Network, null=True, related_name='+')
444
    index = models.IntegerField()
445
    name = models.CharField(max_length=255)
446
    available = models.BooleanField(default=True)