Statistics
| Branch: | Tag: | Revision:

root / db / models.py @ dfd19c2d

History | View | Annotate | Download (11.6 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
    action = models.CharField(choices=ACTIONS, max_length=30, null=True)
234
    operstate = models.CharField(choices=OPER_STATES, max_length=30, null=True)
235
    backendjobid = models.PositiveIntegerField(null=True)
236
    backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30, null=True)
237
    backendjobstatus = models.CharField(choices=BACKEND_STATUSES, max_length=30, null=True)
238
    backendlogmsg = models.TextField(null=True)
239

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

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

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

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

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

    
277
    backend_id = property(_get_backend_id)
278

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

    
286

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

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

    
303

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

    
315

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

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

    
328

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

    
337
    class Meta:
338
        verbose_name = u'Disk instance'
339

    
340
    def __unicode__(self):
341
        return self.name