Statistics
| Branch: | Tag: | Revision:

root / db / models.py @ 76ba77c1

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

    
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
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)
58
    created = models.DateTimeField('Time of creation', auto_now_add=True)
59
    updated = models.DateTimeField('Time of last update', auto_now=True)
60
    sourcevm = models.ForeignKey("VirtualMachine", null=True)
61

    
62
    class Meta:
63
        verbose_name = u'Image'
64

    
65
    def __unicode__(self):
66
        return u'%s' % ( self.name, )
67

    
68

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

    
80

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

    
97

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

    
111
    def _current_cost(self, active):
112
        """Returns active/inactive cost value
113

114
        set active = True to get active cost and False for the inactive.
115

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

    
124
        return 0
125

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

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

    
134
    name = property(_get_name)
135
    current_cost_active = property(_current_cost_active)
136
    current_cost_inactive = property(_current_cost_inactive)
137

    
138
    def __unicode__(self):
139
        return self.name
140

    
141

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

    
154

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

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

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

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

    
226
    # VM State 
227
    # The following fields are volatile data, in the sense
228
    # that they need not be persistent in the DB, but rather
229
    # get generated at runtime by quering Ganeti and applying
230
    # updates received from Ganeti.
231
    
232
    # In the future they could be moved to a separate caching layer
233
    # and removed from the database.
234
    # [vkoukis] after discussion with [faidon].
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
        if not self.id:
278
            raise VirtualMachine.InvalidBackendIdError("self.id is None")
279
        return '%s%s' % (settings.BACKEND_PREFIX_ID, str(self.id))
280

    
281
    backend_id = property(_get_backend_id)
282

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

    
290

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

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

    
307

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

    
319

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

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

    
332

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

    
341
    class Meta:
342
        verbose_name = u'Disk instance'
343

    
344
    def __unicode__(self):
345
        return self.name