Statistics
| Branch: | Tag: | Revision:

root / db / models.py @ dd53338a

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

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

    
57

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

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

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

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

    
81

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

    
93

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

    
110

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

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

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

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

    
137
        return 0
138

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

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

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

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

    
154

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

    
167

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

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

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

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

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

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

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

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

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

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

    
292
    backend_id = property(_get_backend_id)
293

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

    
301

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

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

    
318

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

    
330

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

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

    
343

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

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

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