Statistics
| Branch: | Tag: | Revision:

root / db / models.py @ faa26af8

History | View | Annotate | Download (12.1 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, 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)
113
    disk = models.IntegerField('Size of Disk space', default=0)
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
    # A backend job may be in one of the following possible states
194
    BACKEND_STATUSES = (
195
        ('queued', 'request queued'),
196
        ('waiting', 'request waiting for locks'),
197
        ('canceling', 'request being canceled'),
198
        ('running', 'request running'),
199
        ('canceled', 'request canceled'),
200
        ('success', 'request completed successfully'),
201
        ('error', 'request returned error')
202
    )
203

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

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

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

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

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

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

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

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

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

    
291
    backend_id = property(_get_backend_id)
292

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

    
300

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

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

    
317

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

    
329

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

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

    
342

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

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

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