Statistics
| Branch: | Tag: | Revision:

root / db / models.py @ 26fef2b9

History | View | Annotate | Download (13 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, default='')
17
    realname = models.CharField('Real Name', max_length=255, default='')
18
    uniq = models.CharField('External Unique ID', max_length=255,null=True)
19
    credit = models.IntegerField('Credit Balance')
20
    auth_token = models.CharField('Authentication Token', max_length=32, null=True)
21
    auth_token_created = models.DateTimeField('Time of auth token creation', auto_now_add=True)
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
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)
70
    created = models.DateTimeField('Time of creation', auto_now_add=True)
71
    updated = models.DateTimeField('Time of last update', auto_now=True)
72
    sourcevm = models.ForeignKey("VirtualMachine", null=True)
73

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

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

    
80

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

    
92

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

    
109

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

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

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

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

    
136
        return 0
137

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

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

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

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

    
153

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

    
166

    
167
class VirtualMachine(models.Model):
168
    # The list of possible actions for a VM
169
    ACTIONS = (
170
       ('CREATE', 'Create VM'),
171
       ('START', 'Start VM'),
172
       ('STOP', 'Shutdown VM'),
173
       ('SUSPEND', 'Admin Suspend VM'),
174
       ('REBOOT', 'Reboot VM'),
175
       ('DESTROY', 'Destroy VM')
176
    )
177
    # The internal operating state of a VM
178
    OPER_STATES = (
179
        ('BUILD', 'Queued for creation'),
180
        ('ERROR', 'Creation failed'),
181
        ('STOPPED', 'Stopped'),
182
        ('STARTED', 'Started'),
183
        ('DESTROYED', 'Destroyed')
184
    )
185
    # The list of possible operations on the backend
186
    BACKEND_OPCODES = (
187
        ('OP_INSTANCE_CREATE', 'Create Instance'),
188
        ('OP_INSTANCE_REMOVE', 'Remove Instance'),
189
        ('OP_INSTANCE_STARTUP', 'Startup Instance'),
190
        ('OP_INSTANCE_SHUTDOWN', 'Shutdown Instance'),
191
        ('OP_INSTANCE_REBOOT', 'Reboot Instance'),
192

    
193
        # These are listed here for completeness,
194
        # and are ignored for the time being
195
        ('OP_INSTANCE_SET_PARAMS', 'Set Instance Parameters'),
196
        ('OP_INSTANCE_QUERY_DATA', 'Query Instance Data'),
197
    )
198
    # A backend job may be in one of the following possible states
199
    BACKEND_STATUSES = (
200
        ('queued', 'request queued'),
201
        ('waiting', 'request waiting for locks'),
202
        ('canceling', 'request being canceled'),
203
        ('running', 'request running'),
204
        ('canceled', 'request canceled'),
205
        ('success', 'request completed successfully'),
206
        ('error', 'request returned error')
207
    )
208

    
209
    # The operating state of a VM,
210
    # upon the successful completion of a backend operation.
211
    OPER_STATE_FROM_OPCODE = {
212
        'OP_INSTANCE_CREATE': 'STARTED',
213
        'OP_INSTANCE_REMOVE': 'DESTROYED',
214
        'OP_INSTANCE_STARTUP': 'STARTED',
215
        'OP_INSTANCE_SHUTDOWN': 'STOPPED',
216
        'OP_INSTANCE_REBOOT': 'STARTED',
217
        'OP_INSTANCE_SET_PARAMS': None,
218
        'OP_INSTANCE_QUERY_DATA': None
219
    }
220

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

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

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

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

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

    
275
    class InvalidActionError(Exception):
276
         def __init__(self, action):
277
            self._action = action
278
         def __str__(self):
279
            return repr(str(self._action))
280
    
281
    class DeletedError(Exception):
282
        pass
283
    
284
    class BuildingError(Exception):
285
        pass
286
    
287
    def __init__(self, *args, **kw):
288
        """Initialize state for just created VM instances."""
289
        super(VirtualMachine, self).__init__(*args, **kw)
290
        # This gets called BEFORE an instance gets save()d for
291
        # the first time.
292
        if not self.pk: 
293
            self.action = None
294
            self.backendjobid = None
295
            self.backendjobstatus = None
296
            self.backendopcode = None
297
            self.backendlogmsg = None
298
            self.operstate = 'BUILD'
299

    
300
    def _get_backend_id(self):
301
        """Returns the backend id for this VM by prepending backend-prefix."""
302
        if not self.id:
303
            raise VirtualMachine.InvalidBackendIdError("self.id is None")
304
        return '%s%s' % (settings.BACKEND_PREFIX_ID, str(self.id))
305

    
306
    backend_id = property(_get_backend_id)
307

    
308
    class Meta:
309
        verbose_name = u'Virtual machine instance'
310
        get_latest_by = 'created'
311
    
312
    def __unicode__(self):
313
        return self.name
314

    
315

    
316
class VirtualMachineGroup(models.Model):
317
    """Groups of VMs for SynnefoUsers"""
318
    name = models.CharField(max_length=255)
319
    created = models.DateTimeField('Time of creation', auto_now_add=True)
320
    updated = models.DateTimeField('Time of last update', auto_now=True)
321
    owner = models.ForeignKey(SynnefoUser)
322
    machines = models.ManyToManyField(VirtualMachine)
323

    
324
    class Meta:
325
        verbose_name = u'Virtual Machine Group'
326
        verbose_name_plural = 'Virtual Machine Groups'
327
        ordering = ['name']
328
    
329
    def __unicode__(self):
330
        return self.name
331

    
332

    
333
class VirtualMachineMetadata(models.Model):
334
    meta_key = models.CharField(max_length=50)
335
    meta_value = models.CharField(max_length=500)
336
    vm = models.ForeignKey(VirtualMachine)
337
    
338
    class Meta:
339
        verbose_name = u'Key-value pair of metadata for a VM.'
340
    
341
    def __unicode__(self):
342
        return u'%s, %s for %s' % (self.meta_key, self.meta_value, self.vm.name)
343

    
344

    
345
class Debit(models.Model):
346
    when = models.DateTimeField()
347
    user = models.ForeignKey(SynnefoUser)
348
    vm = models.ForeignKey(VirtualMachine)
349
    description = models.TextField()
350
    
351
    class Meta:
352
        verbose_name = u'Accounting log'
353

    
354
    def __unicode__(self):
355
        return u'%s - %s - %s - %s' % ( self.user.id, self.vm.name, str(self.when), self.description)
356

    
357

    
358
class Disk(models.Model):
359
    name = models.CharField(max_length=255)
360
    created = models.DateTimeField('Time of creation', auto_now_add=True)
361
    updated = models.DateTimeField('Time of last update', auto_now=True)
362
    size = models.PositiveIntegerField('Disk size in GBs')
363
    vm = models.ForeignKey(VirtualMachine, blank=True, null=True)
364
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)  
365

    
366
    class Meta:
367
        verbose_name = u'Disk instance'
368

    
369
    def __unicode__(self):
370
        return self.name
371

    
372

    
373
class Network(models.Model):
374
    name = models.CharField(max_length=255)
375
    created = models.DateTimeField(auto_now_add=True)
376
    updated = models.DateTimeField(auto_now=True)
377
    owner = models.ForeignKey(SynnefoUser)
378
    machines = models.ManyToManyField(VirtualMachine)
379
    
380
    def __unicode__(self):
381
        return self.name