Statistics
| Branch: | Tag: | Revision:

root / db / models.py @ ef39e7ee

History | View | Annotate | Download (11.7 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
from django.contrib.auth.models import User
6

    
7
import datetime
8

    
9
class SynnefoUser(User):
10
    name = models.CharField('Synnefo Username', max_length=255)
11
    credit = models.IntegerField('Credit Balance')
12
    auth_token = models.CharField('Authentication Token', max_length=32)
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

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

    
158

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

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

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

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

    
230
    # VM State 
231
    # The following fields are volatile data, in the sense
232
    # that they need not be persistent in the DB, but rather
233
    # get generated at runtime by quering Ganeti and applying
234
    # updates received from Ganeti.
235
    action = models.CharField(choices=ACTIONS, max_length=30, null=True)
236
    operstate = models.CharField(choices=OPER_STATES, max_length=30, null=True)
237
    backendjobid = models.PositiveIntegerField(null=True)
238
    backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30, null=True)
239
    backendjobstatus = models.CharField(choices=BACKEND_STATUSES, max_length=30, null=True)
240
    backendlogmsg = models.TextField(null=True)
241

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

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

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

    
262
    def __init__(self, *args, **kw):
263
        """Initialize state for just created VM instances."""
264
        super(VirtualMachine, self).__init__(*args, **kw)
265
        # This gets called BEFORE an instance gets save()d for
266
        # the first time.
267
        if not self.pk: 
268
            self.action = None
269
            self.backendjobid = None
270
            self.backendjobstatus = None
271
            self.backendopcode = None
272
            self.backendlogmsg = None
273
            self.operstate = 'BUILD'
274

    
275
    def _get_backend_id(self):
276
        """Returns the backend id for this VM by prepending backend-prefix."""
277
        return '%s%s' % (settings.BACKEND_PREFIX_ID, str(self.id))
278

    
279
    backend_id = property(_get_backend_id)
280

    
281
    class Meta:
282
        verbose_name = u'Virtual machine instance'
283
        get_latest_by = 'created'
284
    
285
    def __unicode__(self):
286
        return self.name
287

    
288

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

    
297
    class Meta:
298
        verbose_name = u'Virtual Machine Group'
299
        verbose_name_plural = 'Virtual Machine Groups'
300
        ordering = ['name']
301
    
302
    def __unicode__(self):
303
        return self.name
304

    
305

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

    
317

    
318
class Debit(models.Model):
319
    when = models.DateTimeField()
320
    user = models.ForeignKey(SynnefoUser)
321
    vm = models.ForeignKey(VirtualMachine)
322
    description = models.TextField()
323
    
324
    class Meta:
325
        verbose_name = u'Accounting log'
326

    
327
    def __unicode__(self):
328
        return u'%s - %s - %s - %s' % ( self.user.id, self.vm.name, str(self.when), self.description)
329

    
330

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

    
339
    class Meta:
340
        verbose_name = u'Disk instance'
341

    
342
    def __unicode__(self):
343
        return self.name