Statistics
| Branch: | Tag: | Revision:

root / db / models.py @ 420f2c20

History | View | Annotate | Download (12.3 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
from django.contrib.auth.models import User
6

    
7
import datetime
8

    
9
class SynnefoUser(models.Model):
10

    
11
    ACCOUNT_TYPE = (
12
        ('STUDENT', 'Student'),
13
        ('PROFESSOR', 'Professor')
14
    )
15

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

    
28
    def __unicode__(self):
29
        return self.name
30

    
31
    def __init__(self, *args, **kw):
32
        """Initialize state for just created VM instances."""
33
        super(SynnefoUser, self).__init__(*args, **kw)
34
        # This gets called BEFORE an instance gets save()d for
35
        # the first time.
36
        if not self.pk:
37
            self.name = kw['name']
38
            self.realname = kw['realname']
39

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

    
66

    
67
class Image(models.Model):
68
    # This is WIP, FIXME
69
    IMAGE_STATES = (
70
        ('ACTIVE', 'Active'),
71
        ('SAVING', 'Saving'),
72
        ('DELETED', 'Deleted')
73
    )
74

    
75
    name = models.CharField('Image name', max_length=255)
76
    state = models.CharField('Current Image State', choices=IMAGE_STATES, max_length=30)
77
    description = models.TextField('General description')
78
    size = models.PositiveIntegerField('Image size in MBs')
79
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)
80
    created = models.DateTimeField('Time of creation', auto_now_add=True)
81
    updated = models.DateTimeField('Time of last update', auto_now=True)
82
    sourcevm = models.ForeignKey("VirtualMachine", null=True)
83

    
84
    class Meta:
85
        verbose_name = u'Image'
86

    
87
    def __unicode__(self):
88
        return u'%s' % ( self.name, )
89

    
90

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

    
102

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

    
119

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

    
133
    def _current_cost(self, active):
134
        """Returns active/inactive cost value
135

136
        set active = True to get active cost and False for the inactive.
137

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

    
146
        return 0
147

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

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

    
156
    name = property(_get_name)
157
    current_cost_active = property(_current_cost_active)
158
    current_cost_inactive = property(_current_cost_inactive)
159

    
160
    def __unicode__(self):
161
        return self.name
162

    
163

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

    
176

    
177
class VirtualMachine(models.Model):
178
    # The list of possible actions for a VM
179
    ACTIONS = (
180
       ('CREATE', 'Create VM'),
181
       ('START', 'Start VM'),
182
       ('STOP', 'Shutdown VM'),
183
       ('SUSPEND', 'Admin Suspend VM'),
184
       ('REBOOT', 'Reboot VM'),
185
       ('DESTROY', 'Destroy VM')
186
    )
187
    # The internal operating state of a VM
188
    OPER_STATES = (
189
        ('BUILD', 'Queued for creation'),
190
        ('ERROR', 'Creation failed'),
191
        ('STOPPED', 'Stopped'),
192
        ('STARTED', 'Started'),
193
        ('DESTROYED', 'Destroyed')
194
    )
195
    # The list of possible operations on the backend
196
    BACKEND_OPCODES = (
197
        ('OP_INSTANCE_CREATE', 'Create Instance'),
198
        ('OP_INSTANCE_REMOVE', 'Remove Instance'),
199
        ('OP_INSTANCE_STARTUP', 'Startup Instance'),
200
        ('OP_INSTANCE_SHUTDOWN', 'Shutdown Instance'),
201
        ('OP_INSTANCE_REBOOT', 'Reboot Instance')
202
    )
203
    # A backend job may be in one of the following possible states
204
    BACKEND_STATUSES = (
205
        ('queued', 'request queued'),
206
        ('waiting', 'request waiting for locks'),
207
        ('canceling', 'request being canceled'),
208
        ('running', 'request running'),
209
        ('canceled', 'request canceled'),
210
        ('success', 'request completed successfully'),
211
        ('error', 'request returned error')
212
    )
213

    
214
    # The operating state of a VM,
215
    # upon the successful completion of a backend operation.
216
    OPER_STATE_FROM_OPCODE = {
217
        'OP_INSTANCE_CREATE': 'STARTED',
218
        'OP_INSTANCE_REMOVE': 'DESTROYED',
219
        'OP_INSTANCE_STARTUP': 'STARTED',
220
        'OP_INSTANCE_SHUTDOWN': 'STOPPED',
221
        'OP_INSTANCE_REBOOT': 'STARTED'
222
    }
223

    
224
    # This dictionary contains the correspondence between
225
    # internal operating states and Server States as defined
226
    # by the Rackspace API.
227
    RSAPI_STATE_FROM_OPER_STATE = {
228
        "BUILD": "BUILD",
229
        "ERROR": "ERROR",
230
        "STOPPED": "STOPPED",
231
        "STARTED": "ACTIVE",
232
        "DESTROYED": "DELETED"
233
    }
234

    
235
    name = models.CharField('Virtual Machine Name', max_length=255)
236
    owner = models.ForeignKey(SynnefoUser)
237
    created = models.DateTimeField(auto_now_add=True)
238
    updated = models.DateTimeField(auto_now=True)
239
    charged = models.DateTimeField(default=datetime.datetime.now())
240
    sourceimage = models.ForeignKey("Image", null=False) 
241
    hostid = models.CharField(max_length=100)
242
    ipfour = models.IPAddressField()
243
    ipsix = models.CharField(max_length=100)
244
    flavor = models.ForeignKey(Flavor)
245
    deleted = models.BooleanField('Deleted', default=False)
246
    suspended = models.BooleanField('Administratively Suspended', default=False)
247

    
248
    # VM State 
249
    # The following fields are volatile data, in the sense
250
    # that they need not be persistent in the DB, but rather
251
    # get generated at runtime by quering Ganeti and applying
252
    # updates received from Ganeti.
253
    action = models.CharField(choices=ACTIONS, max_length=30, null=True)
254
    operstate = models.CharField(choices=OPER_STATES, max_length=30, null=True)
255
    backendjobid = models.PositiveIntegerField(null=True)
256
    backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30, null=True)
257
    backendjobstatus = models.CharField(choices=BACKEND_STATUSES, max_length=30, null=True)
258
    backendlogmsg = models.TextField(null=True)
259

    
260
    # Error classes
261
    class InvalidBackendIdError(Exception):
262
         def __init__(self, value):
263
            self.value = value
264
         def __str__(self):
265
            return repr(self.value)
266

    
267
    class InvalidBackendMsgError(Exception):
268
         def __init__(self, opcode, status):
269
            self.opcode = opcode
270
            self.status = status
271
         def __str__(self):
272
            return repr("<opcode: %s, status: %s>" % (str(self.opcode), str(self.status)))
273

    
274
    class InvalidActionError(Exception):
275
         def __init__(self, action):
276
            self._action = action
277
         def __str__(self):
278
            return repr(str(self._action))
279

    
280
    def __init__(self, *args, **kw):
281
        """Initialize state for just created VM instances."""
282
        super(VirtualMachine, self).__init__(*args, **kw)
283
        # This gets called BEFORE an instance gets save()d for
284
        # the first time.
285
        if not self.pk: 
286
            self.action = None
287
            self.backendjobid = None
288
            self.backendjobstatus = None
289
            self.backendopcode = None
290
            self.backendlogmsg = None
291
            self.operstate = 'BUILD'
292

    
293
    def _get_backend_id(self):
294
        """Returns the backend id for this VM by prepending backend-prefix."""
295
        return '%s%s' % (settings.BACKEND_PREFIX_ID, str(self.id))
296

    
297
    backend_id = property(_get_backend_id)
298

    
299
    class Meta:
300
        verbose_name = u'Virtual machine instance'
301
        get_latest_by = 'created'
302
    
303
    def __unicode__(self):
304
        return self.name
305

    
306

    
307
class VirtualMachineGroup(models.Model):
308
    """Groups of VMs for SynnefoUsers"""
309
    name = models.CharField(max_length=255)
310
    created = models.DateTimeField('Time of creation', auto_now_add=True)
311
    updated = models.DateTimeField('Time of last update', auto_now=True)
312
    owner = models.ForeignKey(SynnefoUser)
313
    machines = models.ManyToManyField(VirtualMachine)
314

    
315
    class Meta:
316
        verbose_name = u'Virtual Machine Group'
317
        verbose_name_plural = 'Virtual Machine Groups'
318
        ordering = ['name']
319
    
320
    def __unicode__(self):
321
        return self.name
322

    
323

    
324
class VirtualMachineMetadata(models.Model):
325
    meta_key = models.CharField(max_length=50)
326
    meta_value = models.CharField(max_length=500)
327
    vm = models.ForeignKey(VirtualMachine)
328
    
329
    class Meta:
330
        verbose_name = u'Key-value pair of metadata for a VM.'
331
    
332
    def __unicode__(self):
333
        return u'%s, %s for %s' % (self.meta_key, self.meta_value, self.vm.name)
334

    
335

    
336
class Debit(models.Model):
337
    when = models.DateTimeField()
338
    user = models.ForeignKey(SynnefoUser)
339
    vm = models.ForeignKey(VirtualMachine)
340
    description = models.TextField()
341
    
342
    class Meta:
343
        verbose_name = u'Accounting log'
344

    
345
    def __unicode__(self):
346
        return u'%s - %s - %s - %s' % ( self.user.id, self.vm.name, str(self.when), self.description)
347

    
348

    
349
class Disk(models.Model):
350
    name = models.CharField(max_length=255)
351
    created = models.DateTimeField('Time of creation', auto_now_add=True)
352
    updated = models.DateTimeField('Time of last update', auto_now=True)
353
    size = models.PositiveIntegerField('Disk size in GBs')
354
    vm = models.ForeignKey(VirtualMachine, blank=True, null=True)
355
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)  
356

    
357
    class Meta:
358
        verbose_name = u'Disk instance'
359

    
360
    def __unicode__(self):
361
        return self.name