Statistics
| Branch: | Tag: | Revision:

root / db / models.py @ 76cc889c

History | View | Annotate | Download (12.2 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
    #TODO: Amend this when we have groups
12
    ACCOUNT_TYPE = (
13
        ('STUDENT', 'Student'),
14
        ('PROFESSOR', 'Professor')
15
    )
16

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

    
26
    class Meta:
27
        verbose_name = u'Synnefo User'
28

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

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

    
58

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

    
67
    name = models.CharField('Image name', max_length=255)
68
    state = models.CharField('Current Image State', choices=IMAGE_STATES, max_length=30)
69
    description = models.TextField('General description')
70
    size = models.PositiveIntegerField('Image size in MBs')
71
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)
72
    created = models.DateTimeField('Time of creation', auto_now_add=True)
73
    updated = models.DateTimeField('Time of last update', auto_now=True)
74
    sourcevm = models.ForeignKey("VirtualMachine", null=True)
75

    
76
    class Meta:
77
        verbose_name = u'Image'
78

    
79
    def __unicode__(self):
80
        return u'%s' % ( self.name, )
81

    
82

    
83
class ImageMetadata(models.Model):
84
    meta_key = models.CharField('Image metadata key name', max_length=50)
85
    meta_value = models.CharField('Image metadata value', max_length=500)
86
    image = models.ForeignKey(Image)
87
    
88
    class Meta:
89
        verbose_name = u'Key-value pair of Image metadata.'
90
    
91
    def __unicode__(self):
92
        return u'%s, %s for %s' % (self.meta_key, self.meta_value, self.image.name)
93

    
94

    
95
class Limit(models.Model):
96
    LIMITS = (
97
        ('QUOTA_CREDIT', 'Maximum amount of credits per user'),
98
        ('MIN_CREDITS', 'Minimum amount of credits per user'),
99
        ('MONTHLY_RATE', 'Monthly credit issue rate')
100
    )
101
    user = models.ForeignKey(SynnefoUser)
102
    name = models.CharField('Limit key name', choices=LIMITS, max_length=30, null=False)
103
    value = models.IntegerField('Limit current value')
104
    
105
    class Meta:
106
        verbose_name = u'Enforced limit for user'
107
    
108
    def __unicode__(self):
109
        return u'Limit %s for user %s: %d' % (self.value, self.user, self.value)
110

    
111

    
112
class Flavor(models.Model):
113
    cpu = models.IntegerField('Number of CPUs', default=0)
114
    ram = models.IntegerField('Size of RAM', default=0)
115
    disk = models.IntegerField('Size of Disk space', default=0)
116
    
117
    class Meta:
118
        verbose_name = u'Virtual machine flavor'
119
        unique_together = ("cpu","ram","disk")
120
            
121
    def _get_name(self):
122
        """Returns flavor name (generated)"""
123
        return u'C%dR%dD%d' % (self.cpu, self.ram, self.disk)
124

    
125
    def _current_cost(self, active):
126
        """Returns active/inactive cost value
127

128
        set active = True to get active cost and False for the inactive.
129

130
        """
131
        fch_list = FlavorCost.objects.filter(flavor=self).order_by('-effective_from')
132
        if len(fch_list) > 0:
133
            if active:
134
                return fch_list[0].cost_active
135
            else:
136
                return fch_list[0].cost_inactive
137

    
138
        return 0
139

    
140
    def _current_cost_active(self):
141
        """Returns current active cost (property method)"""
142
        return self._current_cost(True)
143

    
144
    def _current_cost_inactive(self):
145
        """Returns current inactive cost (property method)"""
146
        return self._current_cost(False)
147

    
148
    name = property(_get_name)
149
    current_cost_active = property(_current_cost_active)
150
    current_cost_inactive = property(_current_cost_inactive)
151

    
152
    def __unicode__(self):
153
        return self.name
154

    
155

    
156
class FlavorCost(models.Model):
157
    cost_active = models.PositiveIntegerField('Active Cost')
158
    cost_inactive = models.PositiveIntegerField('Inactive Cost')
159
    effective_from = models.DateTimeField()
160
    flavor = models.ForeignKey(Flavor)
161
    
162
    class Meta:
163
        verbose_name = u'Pricing history for flavors'
164
    
165
    def __unicode__(self):
166
        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)
167

    
168

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

    
206
    # The operating state of a VM,
207
    # upon the successful completion of a backend operation.
208
    OPER_STATE_FROM_OPCODE = {
209
        'OP_INSTANCE_CREATE': 'STARTED',
210
        'OP_INSTANCE_REMOVE': 'DESTROYED',
211
        'OP_INSTANCE_STARTUP': 'STARTED',
212
        'OP_INSTANCE_SHUTDOWN': 'STOPPED',
213
        'OP_INSTANCE_REBOOT': 'STARTED'
214
    }
215

    
216
    # This dictionary contains the correspondence between
217
    # internal operating states and Server States as defined
218
    # by the Rackspace API.
219
    RSAPI_STATE_FROM_OPER_STATE = {
220
        "BUILD": "BUILD",
221
        "ERROR": "ERROR",
222
        "STOPPED": "STOPPED",
223
        "STARTED": "ACTIVE",
224
        "DESTROYED": "DELETED"
225
    }
226

    
227
    name = models.CharField('Virtual Machine Name', max_length=255)
228
    owner = models.ForeignKey(SynnefoUser)
229
    created = models.DateTimeField(auto_now_add=True)
230
    updated = models.DateTimeField(auto_now=True)
231
    charged = models.DateTimeField(default=datetime.datetime.now())
232
    sourceimage = models.ForeignKey("Image", null=False) 
233
    hostid = models.CharField(max_length=100)
234
    ipfour = models.IPAddressField()
235
    ipsix = models.CharField(max_length=100)
236
    flavor = models.ForeignKey(Flavor)
237
    deleted = models.BooleanField('Deleted', default=False)
238
    suspended = models.BooleanField('Administratively Suspended', default=False)
239

    
240
    # VM State 
241
    # The following fields are volatile data, in the sense
242
    # that they need not be persistent in the DB, but rather
243
    # get generated at runtime by quering Ganeti and applying
244
    # updates received from Ganeti.
245
    
246
    # In the future they could be moved to a separate caching layer
247
    # and removed from the database.
248
    # [vkoukis] after discussion with [faidon].
249
    action = models.CharField(choices=ACTIONS, max_length=30, null=True)
250
    operstate = models.CharField(choices=OPER_STATES, max_length=30, null=True)
251
    backendjobid = models.PositiveIntegerField(null=True)
252
    backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30, null=True)
253
    backendjobstatus = models.CharField(choices=BACKEND_STATUSES, max_length=30, null=True)
254
    backendlogmsg = models.TextField(null=True)
255

    
256
    # Error classes
257
    class InvalidBackendIdError(Exception):
258
         def __init__(self, value):
259
            self.value = value
260
         def __str__(self):
261
            return repr(self.value)
262

    
263
    class InvalidBackendMsgError(Exception):
264
         def __init__(self, opcode, status):
265
            self.opcode = opcode
266
            self.status = status
267
         def __str__(self):
268
            return repr("<opcode: %s, status: %s>" % (str(self.opcode), str(self.status)))
269

    
270
    class InvalidActionError(Exception):
271
         def __init__(self, action):
272
            self._action = action
273
         def __str__(self):
274
            return repr(str(self._action))
275

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

    
289
    def _get_backend_id(self):
290
        """Returns the backend id for this VM by prepending backend-prefix."""
291
        return '%s%s' % (settings.BACKEND_PREFIX_ID, str(self.id))
292

    
293
    backend_id = property(_get_backend_id)
294

    
295
    class Meta:
296
        verbose_name = u'Virtual machine instance'
297
        get_latest_by = 'created'
298
    
299
    def __unicode__(self):
300
        return self.name
301

    
302

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

    
311
    class Meta:
312
        verbose_name = u'Virtual Machine Group'
313
        verbose_name_plural = 'Virtual Machine Groups'
314
        ordering = ['name']
315
    
316
    def __unicode__(self):
317
        return self.name
318

    
319

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

    
331

    
332
class Debit(models.Model):
333
    when = models.DateTimeField()
334
    user = models.ForeignKey(SynnefoUser)
335
    vm = models.ForeignKey(VirtualMachine)
336
    description = models.TextField()
337
    
338
    class Meta:
339
        verbose_name = u'Accounting log'
340

    
341
    def __unicode__(self):
342
        return u'%s - %s - %s - %s' % ( self.user.id, self.vm.name, str(self.when), self.description)
343

    
344

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

    
353
    class Meta:
354
        verbose_name = u'Disk instance'
355

    
356
    def __unicode__(self):
357
        return self.name