Statistics
| Branch: | Tag: | Revision:

root / db / models.py @ 8d97deff

History | View | Annotate | Download (12 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
    name = models.CharField('Synnefo Username', max_length=255)
10
    credit = models.IntegerField('Credit Balance')
11
    created = models.DateTimeField('Time of creation', auto_now_add=True)
12
    updated = models.DateTimeField('Time of last update', auto_now=True)
13

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

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

    
46

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

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

    
64
    class Meta:
65
        verbose_name = u'Image'
66

    
67
    def __unicode__(self):
68
        return u'%s' % ( self.name, )
69

    
70

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

    
82

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

    
99

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

    
113
    def _current_cost(self, active):
114
        """Returns active/inactive cost value
115

116
        set active = True to get active cost and False for the inactive.
117

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

    
126
        return 0
127

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

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

    
136
    name = property(_get_name)
137
    current_cost_active = property(_current_cost_active)
138
    current_cost_inactive = property(_current_cost_inactive)
139

    
140
    def __unicode__(self):
141
        return self.name
142

    
143

    
144
class FlavorCost(models.Model):
145
    cost_active = models.PositiveIntegerField('Active Cost')
146
    cost_inactive = models.PositiveIntegerField('Inactive Cost')
147
    effective_from = models.DateTimeField()
148
    flavor = models.ForeignKey(Flavor)
149
    
150
    class Meta:
151
        verbose_name = u'Pricing history for flavors'
152
    
153
    def __unicode__(self):
154
        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)
155

    
156

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

    
194
    # The operating state of a VM,
195
    # upon the successful completion of a backend operation.
196
    OPER_STATE_FROM_OPCODE = {
197
        'OP_INSTANCE_CREATE': 'STARTED',
198
        'OP_INSTANCE_REMOVE': 'DESTROYED',
199
        'OP_INSTANCE_STARTUP': 'STARTED',
200
        'OP_INSTANCE_SHUTDOWN': 'STOPPED',
201
        'OP_INSTANCE_REBOOT': 'STARTED'
202
    }
203

    
204
    # This dictionary contains the correspondence between
205
    # internal operating states and Server States as defined
206
    # by the Rackspace API.
207
    RSAPI_STATE_FROM_OPER_STATE = {
208
        "BUILD": "BUILD",
209
        "ERROR": "ERROR",
210
        "STOPPED": "STOPPED",
211
        "STARTED": "ACTIVE",
212
        "DESTROYED": "DELETED"
213
    }
214

    
215
    name = models.CharField('Virtual Machine Name', max_length=255)
216
    owner = models.ForeignKey(SynnefoUser)
217
    created = models.DateTimeField(auto_now_add=True)
218
    updated = models.DateTimeField(auto_now=True)
219
    charged = models.DateTimeField(default=datetime.datetime.now())
220
    sourceimage = models.ForeignKey("Image", null=False) 
221
    hostid = models.CharField(max_length=100)
222
    ipfour = models.IPAddressField()
223
    ipsix = models.CharField(max_length=100)
224
    flavor = models.ForeignKey(Flavor)
225
    deleted = models.BooleanField('Deleted', default=False)
226
    suspended = models.BooleanField('Administratively Suspended', default=False)
227

    
228
    # VM State 
229
    # The following fields are volatile data, in the sense
230
    # that they need not be persistent in the DB, but rather
231
    # get generated at runtime by quering Ganeti and applying
232
    # updates received from Ganeti.
233
    #
234
    # In the future they could be moved to a separate caching layer
235
    # and removed from the database.
236
    # [vkoukis] after discussion with [faidon].
237
    _action = models.CharField(choices=ACTIONS, max_length=30, null=True)
238
    _operstate = models.CharField(choices=OPER_STATES, max_length=30, null=True)
239
    _backendjobid = models.PositiveIntegerField(null=True)
240
    _backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30, null=True)
241
    _backendjobstatus = models.CharField(choices=BACKEND_STATUSES, max_length=30, null=True)
242
    _backendlogmsg = models.TextField(null=True)
243

    
244
    # Error classes
245
    class InvalidBackendIdError(Exception):
246
         def __init__(self, value):
247
            self.value = value
248
         def __str__(self):
249
            return repr(self.value)
250

    
251
    class InvalidBackendMsgError(Exception):
252
         def __init__(self, opcode, status):
253
            self.opcode = opcode
254
            self.status = status
255
         def __str__(self):
256
            return repr("<opcode: %s, status: %s>" % (str(self.opcode), str(self.status)))
257

    
258
    class InvalidActionError(Exception):
259
         def __init__(self, action):
260
            self._action = action
261
         def __str__(self):
262
            return repr(str(self._action))
263

    
264
    def __init__(self, *args, **kw):
265
        """Initialize state for just created VM instances."""
266
        super(VirtualMachine, self).__init__(*args, **kw)
267
        # This gets called BEFORE an instance gets save()d for
268
        # the first time.
269
        if not self.pk: 
270
            self._action = None
271
            self._backendjobid = None
272
            self._backendjobstatus = None
273
            self._backendopcode = None
274
            self._backendlogmsg = None
275
            # Do not use _update_state() for this, 
276
            # as this may cause save() to get called in __init__(),
277
            # breaking VirtualMachine.object.create() among other things.
278
            self._operstate = 'BUILD'
279

    
280
    def _get_backend_id(self):
281
        """Returns the backend id for this VM by prepending backend-prefix."""
282
        return '%s%s' % (settings.BACKEND_PREFIX_ID, str(self.id))
283

    
284
    backend_id = property(_get_backend_id)
285

    
286
    class Meta:
287
        verbose_name = u'Virtual machine instance'
288
        get_latest_by = 'created'
289
    
290
    def __unicode__(self):
291
        return self.name
292

    
293

    
294
class VirtualMachineGroup(models.Model):
295
    """Groups of VMs for SynnefoUsers"""
296
    name = models.CharField(max_length=255)
297
    created = models.DateTimeField('Time of creation', auto_now_add=True)
298
    updated = models.DateTimeField('Time of last update', auto_now=True)
299
    owner = models.ForeignKey(SynnefoUser)
300
    machines = models.ManyToManyField(VirtualMachine)
301

    
302
    class Meta:
303
        verbose_name = u'Virtual Machine Group'
304
        verbose_name_plural = 'Virtual Machine Groups'
305
        ordering = ['name']
306
    
307
    def __unicode__(self):
308
        return self.name
309

    
310

    
311
class VirtualMachineMetadata(models.Model):
312
    meta_key = models.CharField(max_length=50)
313
    meta_value = models.CharField(max_length=500)
314
    vm = models.ForeignKey(VirtualMachine)
315
    
316
    class Meta:
317
        verbose_name = u'Key-value pair of metadata for a VM.'
318
    
319
    def __unicode__(self):
320
        return u'%s, %s for %s' % (self.meta_key, self.meta_value, self.vm.name)
321

    
322

    
323
class Debit(models.Model):
324
    when = models.DateTimeField()
325
    user = models.ForeignKey(SynnefoUser)
326
    vm = models.ForeignKey(VirtualMachine)
327
    description = models.TextField()
328
    
329
    class Meta:
330
        verbose_name = u'Accounting log'
331

    
332
    def __unicode__(self):
333
        return u'%s - %s - %s - %s' % ( self.user.id, self.vm.name, str(self.when), self.description)
334

    
335

    
336
class Disk(models.Model):
337
    name = models.CharField(max_length=255)
338
    created = models.DateTimeField('Time of creation', auto_now_add=True)
339
    updated = models.DateTimeField('Time of last update', auto_now=True)
340
    size = models.PositiveIntegerField('Disk size in GBs')
341
    vm = models.ForeignKey(VirtualMachine, blank=True, null=True)
342
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)  
343

    
344
    class Meta:
345
        verbose_name = u'Disk instance'
346

    
347
    def __unicode__(self):
348
        return self.name