Statistics
| Branch: | Tag: | Revision:

root / db / models.py @ 111b2cda

History | View | Annotate | Download (12.9 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
from logic import utils, credits
9

    
10
class SynnefoUser(models.Model):
11
    name = models.CharField('Synnefo Username', max_length=255)
12
    credit = models.IntegerField('Credit Balance')
13
    created = models.DateTimeField('Time of creation', auto_now_add=True)
14
    updated = models.DateTimeField('Time of last update', auto_now=True)
15

    
16
    class Meta:
17
        verbose_name = u'Synnefo User'
18
    
19
    def __unicode__(self):
20
        return self.name 
21

    
22
    def get_limit(self, limit_name):
23
        """Returns the limit value for the specified limit"""
24
        limit_objs = Limit.objects.filter(name=limit_name, user=self)        
25
        if len(limit_objs) == 1:
26
            return limit_objs[0].value
27
        
28
        return 0
29
        
30
    def _get_credit_quota(self):
31
        """Internal getter function for credit quota"""
32
        return self.get_limit('QUOTA_CREDIT')
33
        
34
    credit_quota = property(_get_credit_quota)
35
    
36
    def _get_monthly_rate(self):
37
        """Internal getter function for monthly credit issue rate"""
38
        return self.get_limit('MONTHLY_RATE')
39
        
40
    monthly_rate = property(_get_monthly_rate)
41
    
42
    def _get_min_credits(self):
43
        """Internal getter function for maximum number of violations"""
44
        return self.get_limit('MIN_CREDITS')
45
        
46
    min_credits = property(_get_min_credits)
47

    
48

    
49
class Image(models.Model):
50
    # This is WIP, FIXME
51
    IMAGE_STATES = (
52
        ('ACTIVE', 'Active'),
53
        ('SAVING', 'Saving'),
54
        ('DELETED', 'Deleted')
55
    )
56

    
57
    name = models.CharField('Image name', max_length=255)
58
    state = models.CharField('Current Image State', choices=IMAGE_STATES, max_length=30)
59
    description = models.TextField('General description')
60
    size = models.PositiveIntegerField('Image size in MBs')
61
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)
62
    created = models.DateTimeField('Time of creation', auto_now_add=True)
63
    updated = models.DateTimeField('Time of last update', auto_now=True)
64
    sourcevm = models.ForeignKey("VirtualMachine", null=True)
65

    
66
    class Meta:
67
        verbose_name = u'Image'
68

    
69
    def __unicode__(self):
70
        return u'%s' % ( self.name, )
71

    
72

    
73
class ImageMetadata(models.Model):
74
    meta_key = models.CharField('Image metadata key name', max_length=50)
75
    meta_value = models.CharField('Image metadata value', max_length=500)
76
    image = models.ForeignKey(Image)
77
    
78
    class Meta:
79
        verbose_name = u'Key-value pair of Image metadata.'
80
    
81
    def __unicode__(self):
82
        return u'%s, %s for %s' % (self.meta_key, self.meta_value, self.image.name)
83

    
84

    
85
class Limit(models.Model):
86
    LIMITS = (
87
        ('QUOTA_CREDIT', 'Maximum amount of credits per user'),
88
        ('MIN_CREDITS', 'Minimum amount of credits per user'),
89
        ('MONTHLY_RATE', 'Monthly credit issue rate')
90
    )
91
    user = models.ForeignKey(SynnefoUser)
92
    name = models.CharField('Limit key name', choices=LIMITS, max_length=30, null=False)
93
    value = models.IntegerField('Limit current value')
94
    
95
    class Meta:
96
        verbose_name = u'Enforced limit for user'
97
    
98
    def __unicode__(self):
99
        return u'Limit %s for user %s: %d' % (self.value, self.user, self.value)
100

    
101

    
102
class Flavor(models.Model):
103
    cpu = models.IntegerField('Number of CPUs', default=0)
104
    ram = models.IntegerField('Size of RAM', default=0)
105
    disk = models.IntegerField('Size of Disk space', default=0)
106
    
107
    class Meta:
108
        verbose_name = u'Virtual machine flavor'
109
        unique_together = ("cpu","ram","disk")
110
            
111
    def _get_name(self):
112
        """Returns flavor name (generated)"""
113
        return u'C%dR%dD%d' % (self.cpu, self.ram, self.disk)
114

    
115
    def _current_cost(self, active):
116
        """Returns active/inactive cost value
117

118
        set active = True to get active cost and False for the inactive.
119

120
        """
121
        fch_list = FlavorCost.objects.filter(flavor=self).order_by('-effective_from')
122
        if len(fch_list) > 0:
123
            if active:
124
                return fch_list[0].cost_active
125
            else:
126
                return fch_list[0].cost_inactive
127

    
128
        return 0
129

    
130
    def _current_cost_active(self):
131
        """Returns current active cost (property method)"""
132
        return self._current_cost(True)
133

    
134
    def _current_cost_inactive(self):
135
        """Returns current inactive cost (property method)"""
136
        return self._current_cost(False)
137

    
138
    name = property(_get_name)
139
    current_cost_active = property(_current_cost_active)
140
    current_cost_inactive = property(_current_cost_inactive)
141

    
142
    def __unicode__(self):
143
        return self.name
144

    
145
    def get_cost_active(self, start_datetime, end_datetime):
146
        """Returns a list with the active costs for the specified duration"""
147
        return credits.get_costs(self, start_datetime, end_datetime, True)
148

    
149
    def get_cost_inactive(self, start_datetime, end_datetime):
150
        """Returns a list with the inactive costs for the specified duration"""
151
        return credits.get_costs(self, start_datetime, end_datetime, False)
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' % (self.cost_active, 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
    description = models.TextField()
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
            # Do not use _update_state() for this, 
287
            # as this may cause save() to get called in __init__(),
288
            # breaking VirtualMachine.object.create() among other things.
289
            self._operstate = 'BUILD'
290

    
291
    # FIXME: leave this here to preserve the property rsapistate
292
    def _get_rsapi_state(self):
293
        return utils.get_rsapi_state(self)
294

    
295
    rsapi_state = property(_get_rsapi_state)
296

    
297
    def _get_backend_id(self):
298
        """Returns the backend id for this VM by prepending backend-prefix."""
299
        return '%s%s' % (settings.BACKEND_PREFIX_ID, str(self.id))
300

    
301
    backend_id = property(_get_backend_id)
302

    
303
    class Meta:
304
        verbose_name = u'Virtual machine instance'
305
        get_latest_by = 'created'
306
    
307
    def __unicode__(self):
308
        return self.name
309

    
310
    def _update_state(self, new_operstate):
311
        """Wrapper around updates of the _operstate field
312

313
        Currently calls the charge() method when necessary.
314

315
        """
316

    
317
        # Call charge() unconditionally before any change of
318
        # internal state.
319
        credits.charge(self)
320
        self._operstate = new_operstate
321

    
322

    
323
class VirtualMachineGroup(models.Model):
324
    """Groups of VMs for SynnefoUsers"""
325
    name = models.CharField(max_length=255)
326
    created = models.DateTimeField('Time of creation', auto_now_add=True)
327
    updated = models.DateTimeField('Time of last update', auto_now=True)
328
    owner = models.ForeignKey(SynnefoUser)
329
    machines = models.ManyToManyField(VirtualMachine)
330

    
331
    class Meta:
332
        verbose_name = u'Virtual Machine Group'
333
        verbose_name_plural = 'Virtual Machine Groups'
334
        ordering = ['name']
335
    
336
    def __unicode__(self):
337
        return self.name
338

    
339

    
340
class VirtualMachineMetadata(models.Model):
341
    meta_key = models.CharField(max_length=50)
342
    meta_value = models.CharField(max_length=500)
343
    vm = models.ForeignKey(VirtualMachine)
344
    
345
    class Meta:
346
        verbose_name = u'Key-value pair of metadata for a VM.'
347
    
348
    def __unicode__(self):
349
        return u'%s, %s for %s' % (self.meta_key, self.meta_value, self.vm.name)
350

    
351

    
352
class Debit(models.Model):
353
    when = models.DateTimeField()
354
    user = models.ForeignKey(SynnefoUser)
355
    vm = models.ForeignKey(VirtualMachine)
356
    description = models.TextField()
357
    
358
    class Meta:
359
        verbose_name = u'Accounting log'
360

    
361
    def __unicode__(self):
362
        return u'%s - %s - %s - %s' % ( self.user.id, self.vm.name, str(self.when), self.description)
363

    
364

    
365
class Disk(models.Model):
366
    name = models.CharField(max_length=255)
367
    created = models.DateTimeField('Time of creation', auto_now_add=True)
368
    updated = models.DateTimeField('Time of last update', auto_now=True)
369
    size = models.PositiveIntegerField('Disk size in GBs')
370
    vm = models.ForeignKey(VirtualMachine, blank=True, null=True)
371
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)  
372

    
373
    class Meta:
374
        verbose_name = u'Disk instance'
375

    
376
    def __unicode__(self):
377
        return self.name