Statistics
| Branch: | Tag: | Revision:

root / db / models.py @ 75768d0e

History | View | Annotate | Download (14.3 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

    
10
    #TODO: Amend this when we have groups
11
    ACCOUNT_TYPE = (
12
        ('STUDENT', 'Student'),
13
        ('PROFESSOR', 'Professor')
14
    )
15

    
16
    name = models.CharField('Synnefo Username', max_length=255, default='')
17
    realname = models.CharField('Real Name', max_length=255, default='')
18
    uniq = models.CharField('External Unique ID', max_length=255,null=True)
19
    credit = models.IntegerField('Credit Balance')
20
    auth_token = models.CharField('Authentication Token', max_length=32, null=True)
21
    auth_token_created = models.DateTimeField('Time of auth token creation', auto_now_add=True)
22
    type = models.CharField('Current Image State', choices=ACCOUNT_TYPE, max_length=30)
23
    created = models.DateTimeField('Time of creation', auto_now_add=True)
24
    updated = models.DateTimeField('Time of last update', auto_now=True)
25

    
26
    class Meta:
27
        verbose_name = u'Synnefo User'
28

    
29
    def __unicode__(self):
30
        return self.name
31

    
32
    def get_limit(self, limit_name):
33
        """Returns the limit value for the specified limit"""
34
        limit_objs = Limit.objects.filter(name=limit_name, user=self)
35
        if len(limit_objs) == 1:
36
            return limit_objs[0].value
37
        
38
        return 0
39
        
40
    def _get_credit_quota(self):
41
        """Internal getter function for credit quota"""
42
        return self.get_limit('QUOTA_CREDIT')
43
        
44
    credit_quota = property(_get_credit_quota)
45
    
46
    def _get_monthly_rate(self):
47
        """Internal getter function for monthly credit issue rate"""
48
        return self.get_limit('MONTHLY_RATE')
49
        
50
    monthly_rate = property(_get_monthly_rate)
51
    
52
    def _get_min_credits(self):
53
        """Internal getter function for maximum number of violations"""
54
        return self.get_limit('MIN_CREDITS')
55
        
56
    min_credits = property(_get_min_credits)
57

    
58

    
59
class Image(models.Model):
60
    # This is WIP, FIXME
61
    IMAGE_STATES = (
62
        ('ACTIVE', 'Active'),
63
        ('SAVING', 'Saving'),
64
        ('DELETED', 'Deleted')
65
    )
66

    
67
    name = models.CharField('Image name', max_length=255)
68
    state = models.CharField('Current Image State', choices=IMAGE_STATES, max_length=30)
69
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)
70
    created = models.DateTimeField('Time of creation', auto_now_add=True)
71
    updated = models.DateTimeField('Time of last update', auto_now=True)
72
    sourcevm = models.ForeignKey("VirtualMachine", null=True)
73
    public = models.BooleanField(default=False)
74

    
75
    class Meta:
76
        verbose_name = u'Image'
77

    
78
    def __unicode__(self):
79
        return u'%s' % ( self.name, )
80

    
81

    
82
class ImageMetadata(models.Model):
83
    meta_key = models.CharField('Image metadata key name', max_length=50)
84
    meta_value = models.CharField('Image metadata value', max_length=500)
85
    image = models.ForeignKey(Image)
86
    
87
    class Meta:
88
        verbose_name = u'Key-value pair of Image metadata.'
89
    
90
    def __unicode__(self):
91
        return u'%s, %s for %s' % (self.meta_key, self.meta_value, self.image.name)
92

    
93

    
94
class Limit(models.Model):
95
    LIMITS = (
96
        ('QUOTA_CREDIT', 'Maximum amount of credits per user'),
97
        ('MIN_CREDITS', 'Minimum amount of credits per user'),
98
        ('MONTHLY_RATE', 'Monthly credit issue rate')
99
    )
100
    user = models.ForeignKey(SynnefoUser)
101
    name = models.CharField('Limit key name', choices=LIMITS, max_length=30, null=False)
102
    value = models.IntegerField('Limit current value')
103
    
104
    class Meta:
105
        verbose_name = u'Enforced limit for user'
106
    
107
    def __unicode__(self):
108
        return u'Limit %s for user %s: %d' % (self.value, self.user, self.value)
109

    
110

    
111
class Flavor(models.Model):
112
    cpu = models.IntegerField('Number of CPUs', default=0)
113
    ram = models.IntegerField('Size of RAM', default=0)             # Size in MiB
114
    disk = models.IntegerField('Size of Disk space', default=0)     # Size in GiB
115
    
116
    class Meta:
117
        verbose_name = u'Virtual machine flavor'
118
        unique_together = ("cpu","ram","disk")
119
            
120
    def _get_name(self):
121
        """Returns flavor name (generated)"""
122
        return u'C%dR%dD%d' % (self.cpu, self.ram, self.disk)
123

    
124
    def _current_cost(self, active):
125
        """Returns active/inactive cost value
126

127
        set active = True to get active cost and False for the inactive.
128

129
        """
130
        fch_list = FlavorCost.objects.filter(flavor=self).order_by('-effective_from')
131
        if len(fch_list) > 0:
132
            if active:
133
                return fch_list[0].cost_active
134
            else:
135
                return fch_list[0].cost_inactive
136

    
137
        return 0
138

    
139
    def _current_cost_active(self):
140
        """Returns current active cost (property method)"""
141
        return self._current_cost(True)
142

    
143
    def _current_cost_inactive(self):
144
        """Returns current inactive cost (property method)"""
145
        return self._current_cost(False)
146

    
147
    name = property(_get_name)
148
    current_cost_active = property(_current_cost_active)
149
    current_cost_inactive = property(_current_cost_inactive)
150

    
151
    def __unicode__(self):
152
        return self.name
153

    
154

    
155
class FlavorCost(models.Model):
156
    cost_active = models.PositiveIntegerField('Active Cost')
157
    cost_inactive = models.PositiveIntegerField('Inactive Cost')
158
    effective_from = models.DateTimeField()
159
    flavor = models.ForeignKey(Flavor)
160
    
161
    class Meta:
162
        verbose_name = u'Pricing history for flavors'
163
    
164
    def __unicode__(self):
165
        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)
166

    
167

    
168
class VirtualMachine(models.Model):
169
    # The list of possible actions for a VM
170
    ACTIONS = (
171
       ('CREATE', 'Create VM'),
172
       ('START', 'Start VM'),
173
       ('STOP', 'Shutdown VM'),
174
       ('SUSPEND', 'Admin Suspend VM'),
175
       ('REBOOT', 'Reboot VM'),
176
       ('DESTROY', 'Destroy VM')
177
    )
178
    # The internal operating state of a VM
179
    OPER_STATES = (
180
        ('BUILD', 'Queued for creation'),
181
        ('ERROR', 'Creation failed'),
182
        ('STOPPED', 'Stopped'),
183
        ('STARTED', 'Started'),
184
        ('DESTROYED', 'Destroyed')
185
    )
186
    # The list of possible operations on the backend
187
    BACKEND_OPCODES = (
188
        ('OP_INSTANCE_CREATE', 'Create Instance'),
189
        ('OP_INSTANCE_REMOVE', 'Remove Instance'),
190
        ('OP_INSTANCE_STARTUP', 'Startup Instance'),
191
        ('OP_INSTANCE_SHUTDOWN', 'Shutdown Instance'),
192
        ('OP_INSTANCE_REBOOT', 'Reboot Instance'),
193

    
194
        # These are listed here for completeness,
195
        # and are ignored for the time being
196
        ('OP_INSTANCE_SET_PARAMS', 'Set Instance Parameters'),
197
        ('OP_INSTANCE_QUERY_DATA', 'Query Instance Data'),
198
    )
199
    # A backend job may be in one of the following possible states
200
    BACKEND_STATUSES = (
201
        ('queued', 'request queued'),
202
        ('waiting', 'request waiting for locks'),
203
        ('canceling', 'request being canceled'),
204
        ('running', 'request running'),
205
        ('canceled', 'request canceled'),
206
        ('success', 'request completed successfully'),
207
        ('error', 'request returned error')
208
    )
209

    
210
    # The operating state of a VM,
211
    # upon the successful completion of a backend operation.
212
    OPER_STATE_FROM_OPCODE = {
213
        'OP_INSTANCE_CREATE': 'STARTED',
214
        'OP_INSTANCE_REMOVE': 'DESTROYED',
215
        'OP_INSTANCE_STARTUP': 'STARTED',
216
        'OP_INSTANCE_SHUTDOWN': 'STOPPED',
217
        'OP_INSTANCE_REBOOT': 'STARTED',
218
        'OP_INSTANCE_SET_PARAMS': None,
219
        'OP_INSTANCE_QUERY_DATA': None
220
    }
221

    
222
    # This dictionary contains the correspondence between
223
    # internal operating states and Server States as defined
224
    # by the Rackspace API.
225
    RSAPI_STATE_FROM_OPER_STATE = {
226
        "BUILD": "BUILD",
227
        "ERROR": "ERROR",
228
        "STOPPED": "STOPPED",
229
        "STARTED": "ACTIVE",
230
        "DESTROYED": "DELETED"
231
    }
232

    
233
    name = models.CharField('Virtual Machine Name', max_length=255)
234
    owner = models.ForeignKey(SynnefoUser)
235
    created = models.DateTimeField(auto_now_add=True)
236
    updated = models.DateTimeField(auto_now=True)
237
    charged = models.DateTimeField(default=datetime.datetime.now())
238
    sourceimage = models.ForeignKey("Image", null=False) 
239
    hostid = models.CharField(max_length=100)
240
    flavor = models.ForeignKey(Flavor)
241
    deleted = models.BooleanField('Deleted', default=False)
242
    suspended = models.BooleanField('Administratively Suspended', default=False)
243

    
244
    # VM State 
245
    # The following fields are volatile data, in the sense
246
    # that they need not be persistent in the DB, but rather
247
    # get generated at runtime by quering Ganeti and applying
248
    # updates received from Ganeti.
249
    
250
    # In the future they could be moved to a separate caching layer
251
    # and removed from the database.
252
    # [vkoukis] after discussion with [faidon].
253
    action = models.CharField(choices=ACTIONS, max_length=30, null=True)
254
    operstate = models.CharField(choices=OPER_STATES, max_length=30, null=True)
255
    backendjobid = models.PositiveIntegerField(null=True)
256
    backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30, null=True)
257
    backendjobstatus = models.CharField(choices=BACKEND_STATUSES, max_length=30, null=True)
258
    backendlogmsg = models.TextField(null=True)
259

    
260
    # Error classes
261
    class InvalidBackendIdError(Exception):
262
         def __init__(self, value):
263
            self.value = value
264
         def __str__(self):
265
            return repr(self.value)
266

    
267
    class InvalidBackendMsgError(Exception):
268
         def __init__(self, opcode, status):
269
            self.opcode = opcode
270
            self.status = status
271
         def __str__(self):
272
            return repr("<opcode: %s, status: %s>" % (str(self.opcode), str(self.status)))
273

    
274
    class InvalidActionError(Exception):
275
         def __init__(self, action):
276
            self._action = action
277
         def __str__(self):
278
            return repr(str(self._action))
279
    
280
    class DeletedError(Exception):
281
        pass
282
    
283
    class BuildingError(Exception):
284
        pass
285
    
286
    def __init__(self, *args, **kw):
287
        """Initialize state for just created VM instances."""
288
        super(VirtualMachine, self).__init__(*args, **kw)
289
        # This gets called BEFORE an instance gets save()d for
290
        # the first time.
291
        if not self.pk: 
292
            self.action = None
293
            self.backendjobid = None
294
            self.backendjobstatus = None
295
            self.backendopcode = None
296
            self.backendlogmsg = None
297
            self.operstate = 'BUILD'
298

    
299
    def _get_backend_id(self):
300
        """Returns the backend id for this VM by prepending backend-prefix."""
301
        if not self.id:
302
            raise VirtualMachine.InvalidBackendIdError("self.id is None")
303
        return '%s%s' % (settings.BACKEND_PREFIX_ID, str(self.id))
304

    
305
    backend_id = property(_get_backend_id)
306

    
307
    class Meta:
308
        verbose_name = u'Virtual machine instance'
309
        get_latest_by = 'created'
310
    
311
    def __unicode__(self):
312
        return self.name
313

    
314

    
315
class VirtualMachineGroup(models.Model):
316
    """Groups of VMs for SynnefoUsers"""
317
    name = models.CharField(max_length=255)
318
    created = models.DateTimeField('Time of creation', auto_now_add=True)
319
    updated = models.DateTimeField('Time of last update', auto_now=True)
320
    owner = models.ForeignKey(SynnefoUser)
321
    machines = models.ManyToManyField(VirtualMachine)
322

    
323
    class Meta:
324
        verbose_name = u'Virtual Machine Group'
325
        verbose_name_plural = 'Virtual Machine Groups'
326
        ordering = ['name']
327
    
328
    def __unicode__(self):
329
        return self.name
330

    
331

    
332
class VirtualMachineMetadata(models.Model):
333
    meta_key = models.CharField(max_length=50)
334
    meta_value = models.CharField(max_length=500)
335
    vm = models.ForeignKey(VirtualMachine)
336
    
337
    class Meta:
338
        verbose_name = u'Key-value pair of metadata for a VM.'
339
    
340
    def __unicode__(self):
341
        return u'%s, %s for %s' % (self.meta_key, self.meta_value, self.vm.name)
342

    
343

    
344
class Debit(models.Model):
345
    when = models.DateTimeField()
346
    user = models.ForeignKey(SynnefoUser)
347
    vm = models.ForeignKey(VirtualMachine)
348
    description = models.TextField()
349
    
350
    class Meta:
351
        verbose_name = u'Accounting log'
352

    
353
    def __unicode__(self):
354
        return u'%s - %s - %s - %s' % ( self.user.id, self.vm.name, str(self.when), self.description)
355

    
356

    
357
class Disk(models.Model):
358
    name = models.CharField(max_length=255)
359
    created = models.DateTimeField('Time of creation', auto_now_add=True)
360
    updated = models.DateTimeField('Time of last update', auto_now=True)
361
    size = models.PositiveIntegerField('Disk size in GBs')
362
    vm = models.ForeignKey(VirtualMachine, blank=True, null=True)
363
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)  
364

    
365
    class Meta:
366
        verbose_name = u'Disk instance'
367

    
368
    def __unicode__(self):
369
        return self.name
370

    
371

    
372
class Network(models.Model):
373
    NETWORK_STATES = (
374
        ('ACTIVE', 'Active'),
375
        ('DELETED', 'Deleted')
376
    )
377
    
378
    name = models.CharField(max_length=255)
379
    created = models.DateTimeField(auto_now_add=True)
380
    updated = models.DateTimeField(auto_now=True)
381
    owner = models.ForeignKey(SynnefoUser, null=True)
382
    state = models.CharField(choices=NETWORK_STATES, max_length=30)
383
    public = models.BooleanField(default=False)
384
    link = models.ForeignKey('NetworkLink', related_name='+')
385
    machines = models.ManyToManyField(VirtualMachine,
386
                                        through='NetworkInterface')
387
    
388
    def __unicode__(self):
389
        return self.name
390

    
391

    
392
class NetworkInterface(models.Model):
393
    FIREWALL_PROFILES = (
394
        ('ENABLED', 'Enabled'),
395
        ('DISABLED', 'Disabled')
396
    )
397
    
398
    machine = models.ForeignKey(VirtualMachine, related_name='nics')
399
    network = models.ForeignKey(Network, related_name='nics')
400
    created = models.DateTimeField(auto_now_add=True)
401
    updated = models.DateTimeField(auto_now=True)
402
    index = models.IntegerField(null=True)
403
    mac = models.CharField(max_length=17, null=True)
404
    ipv4 = models.CharField(max_length=15, null=True)
405
    ipv6 = models.CharField(max_length=100, null=True)
406
    firewall_profile = models.CharField(choices=FIREWALL_PROFILES,
407
                                        max_length=30, null=True)
408

    
409

    
410
class NetworkLink(models.Model):
411
    network = models.ForeignKey(Network, null=True, related_name='+')
412
    index = models.IntegerField()
413
    name = models.CharField(max_length=255)
414
    available = models.BooleanField(default=True)