Statistics
| Branch: | Tag: | Revision:

root / snf-app / synnefo / db / models.py @ 54ae949d

History | View | Annotate | Download (14.4 kB)

1
# Copyright 2011 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or without
4
# modification, are permitted provided that the following conditions
5
# are met:
6
#
7
#   1. Redistributions of source code must retain the above copyright
8
#      notice, this list of conditions and the following disclaimer.
9
#
10
#  2. Redistributions in binary form must reproduce the above copyright
11
#     notice, this list of conditions and the following disclaimer in the
12
#     documentation and/or other materials provided with the distribution.
13
#
14
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24
# SUCH DAMAGE.
25
#
26
# The views and conclusions contained in the software and documentation are
27
# those of the authors and should not be interpreted as representing official
28
# policies, either expressed or implied, of GRNET S.A.
29

    
30
import datetime
31

    
32
from django.conf import settings
33
from django.db import models
34

    
35

    
36
class SynnefoUser(models.Model):
37
    #TODO: Amend this when we have groups
38
    ACCOUNT_TYPE = (
39
        ('STUDENT', 'Student'),
40
        ('PROFESSOR', 'Professor'),
41
        ('USER', 'Generic User'),
42
        ('HELPDESK', 'Helpdesk User'),
43
        ('ADMIN', 'Admin User')
44
    )
45
    
46
    ACCOUNT_STATE = (
47
        ('ACTIVE', 'Active'),
48
        ('DELETED', 'Deleted'),
49
        ('SUSPENDED', 'Suspended')
50
    )
51
    
52
    name = models.CharField('Synnefo Username', max_length=255, default='')
53
    realname = models.CharField('Real Name', max_length=255, default='')
54
    uniq = models.CharField('External Unique ID', max_length=255,null=True)
55
    auth_token = models.CharField('Authentication Token', max_length=32,
56
            null=True)
57
    auth_token_created = models.DateTimeField('Time of auth token creation',
58
            auto_now_add=True, null=True)
59
    auth_token_expires = models.DateTimeField('Time of auth token expiration',
60
            auto_now_add=True, null=True)
61
    tmp_auth_token = models.CharField('Temporary authentication token',
62
            max_length=32, null=True)
63
    tmp_auth_token_expires = models.DateTimeField('Time of temporary auth '
64
            'token expiration', auto_now_add=True, null=True)
65
    type = models.CharField('Account type', choices=ACCOUNT_TYPE,
66
            max_length=30)
67
    state = models.CharField('Account state', choices=ACCOUNT_STATE,
68
            max_length=30, default='ACTIVE')
69
    created = models.DateTimeField('Time of creation', auto_now_add=True)
70
    updated = models.DateTimeField('Time of last update', auto_now=True)
71
    
72
    def __unicode__(self):
73
        return self.name
74

    
75

    
76
class Image(models.Model):
77
    IMAGE_STATES = (
78
        ('ACTIVE', 'Active'),
79
        ('SAVING', 'Saving'),
80
        ('DELETED', 'Deleted')
81
    )
82

    
83
    # The list of supported Image formats
84
    FORMATS = (
85
        ('dump', 'ext3 dump'),
86
        ('extdump', 'Raw ext2/3/4 dump'),
87
        ('lvm', 'lvm snapshot'),
88
        ('ntfsclone', 'Windows Image produced by ntfsclone'),
89
        ('ntfsdump', 'Raw NTFS dump'),
90
        ('diskdump', 'Raw dump of a hard disk')
91
    )
92

    
93
    name = models.CharField('Image name', max_length=255)
94
    state = models.CharField('Current Image State', choices=IMAGE_STATES,
95
            max_length=30)
96
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)
97
    created = models.DateTimeField('Time of creation', auto_now_add=True)
98
    updated = models.DateTimeField('Time of last update', auto_now=True)
99
    sourcevm = models.ForeignKey('VirtualMachine', null=True)
100
    backend_id = models.CharField(max_length=50, default='debian_base')
101
    format = models.CharField(choices=FORMATS, max_length=30, default='dump')
102
    public = models.BooleanField(default=False)
103
    
104
    def __unicode__(self):
105
        return self.name
106

    
107

    
108
class ImageMetadata(models.Model):
109
    meta_key = models.CharField('Image metadata key name', max_length=50)
110
    meta_value = models.CharField('Image metadata value', max_length=500)
111
    image = models.ForeignKey(Image, related_name='metadata')
112
    
113
    class Meta:
114
        unique_together = (('meta_key', 'image'),)
115
        verbose_name = u'Key-value pair of Image metadata.'
116
    
117
    def __unicode__(self):
118
        return u'%s: %s' % (self.meta_key, self.meta_value)
119

    
120

    
121
class Flavor(models.Model):
122
    cpu = models.IntegerField('Number of CPUs', default=0)
123
    ram = models.IntegerField('RAM size in MiB', default=0)
124
    disk = models.IntegerField('Disk size in GiB', default=0)
125
    disk_template = models.CharField('Disk template', max_length=32,
126
            default=settings.DEFAULT_GANETI_DISK_TEMPLATE)
127
    deleted = models.BooleanField('Deleted', default=False)
128
    
129
    class Meta:
130
        verbose_name = u'Virtual machine flavor'
131
        unique_together = ('cpu', 'ram', 'disk')
132
    
133
    @property
134
    def name(self):
135
        """Returns flavor name (generated)"""
136
        return u'C%dR%dD%d' % (self.cpu, self.ram, self.disk)
137
    
138
    def __unicode__(self):
139
        return self.name
140

    
141

    
142
class VirtualMachine(models.Model):
143
    # The list of possible actions for a VM
144
    ACTIONS = (
145
       ('CREATE', 'Create VM'),
146
       ('START', 'Start VM'),
147
       ('STOP', 'Shutdown VM'),
148
       ('SUSPEND', 'Admin Suspend VM'),
149
       ('REBOOT', 'Reboot VM'),
150
       ('DESTROY', 'Destroy VM')
151
    )
152
    
153
    # The internal operating state of a VM
154
    OPER_STATES = (
155
        ('BUILD', 'Queued for creation'),
156
        ('ERROR', 'Creation failed'),
157
        ('STOPPED', 'Stopped'),
158
        ('STARTED', 'Started'),
159
        ('DESTROYED', 'Destroyed')
160
    )
161
    
162
    # The list of possible operations on the backend
163
    BACKEND_OPCODES = (
164
        ('OP_INSTANCE_CREATE', 'Create Instance'),
165
        ('OP_INSTANCE_REMOVE', 'Remove Instance'),
166
        ('OP_INSTANCE_STARTUP', 'Startup Instance'),
167
        ('OP_INSTANCE_SHUTDOWN', 'Shutdown Instance'),
168
        ('OP_INSTANCE_REBOOT', 'Reboot Instance'),
169

    
170
        # These are listed here for completeness,
171
        # and are ignored for the time being
172
        ('OP_INSTANCE_SET_PARAMS', 'Set Instance Parameters'),
173
        ('OP_INSTANCE_QUERY_DATA', 'Query Instance Data'),
174
        ('OP_INSTANCE_REINSTALL', 'Reinstall Instance'),
175
        ('OP_INSTANCE_ACTIVATE_DISKS', 'Activate Disks'),
176
        ('OP_INSTANCE_DEACTIVATE_DISKS', 'Deactivate Disks'),
177
        ('OP_INSTANCE_REPLACE_DISKS', 'Replace Disks'),
178
        ('OP_INSTANCE_MIGRATE', 'Migrate Instance'),
179
        ('OP_INSTANCE_CONSOLE', 'Get Instance Console'),
180
        ('OP_INSTANCE_RECREATE_DISKS', 'Recreate Disks'),
181
        ('OP_INSTANCE_FAILOVER', 'Failover Instance')
182
    )
183
    
184
    # A backend job may be in one of the following possible states
185
    BACKEND_STATUSES = (
186
        ('queued', 'request queued'),
187
        ('waiting', 'request waiting for locks'),
188
        ('canceling', 'request being canceled'),
189
        ('running', 'request running'),
190
        ('canceled', 'request canceled'),
191
        ('success', 'request completed successfully'),
192
        ('error', 'request returned error')
193
    )
194

    
195
    # The operating state of a VM,
196
    # upon the successful completion of a backend operation.
197
    # IMPORTANT: Make sure all keys have a corresponding
198
    # entry in BACKEND_OPCODES if you update this field, see #1035, #1111.
199
    OPER_STATE_FROM_OPCODE = {
200
        'OP_INSTANCE_CREATE': 'STARTED',
201
        'OP_INSTANCE_REMOVE': 'DESTROYED',
202
        'OP_INSTANCE_STARTUP': 'STARTED',
203
        'OP_INSTANCE_SHUTDOWN': 'STOPPED',
204
        'OP_INSTANCE_REBOOT': 'STARTED',
205
        'OP_INSTANCE_SET_PARAMS': None,
206
        'OP_INSTANCE_QUERY_DATA': None,
207
        'OP_INSTANCE_REINSTALL' : None,
208
        'OP_INSTANCE_ACTIVATE_DISKS' : None,
209
        'OP_INSTANCE_DEACTIVATE_DISKS': None,
210
        'OP_INSTANCE_REPLACE_DISKS' : None,
211
        'OP_INSTANCE_MIGRATE': None,
212
        'OP_INSTANCE_CONSOLE': None,
213
        'OP_INSTANCE_RECREATE_DISKS': None,
214
        'OP_INSTANCE_FAILOVER': None
215
    }
216

    
217
    # This dictionary contains the correspondence between
218
    # internal operating states and Server States as defined
219
    # by the Rackspace API.
220
    RSAPI_STATE_FROM_OPER_STATE = {
221
        "BUILD": "BUILD",
222
        "ERROR": "ERROR",
223
        "STOPPED": "STOPPED",
224
        "STARTED": "ACTIVE",
225
        "DESTROYED": "DELETED"
226
    }
227

    
228
    name = models.CharField('Virtual Machine Name', max_length=255)
229
    owner = models.ForeignKey(SynnefoUser)
230
    created = models.DateTimeField(auto_now_add=True)
231
    updated = models.DateTimeField(auto_now=True)
232
    charged = models.DateTimeField(default=datetime.datetime.now())
233
    imageid = models.CharField(max_length=100, null=False)
234
    hostid = models.CharField(max_length=100)
235
    flavor = models.ForeignKey(Flavor)
236
    deleted = models.BooleanField('Deleted', default=False)
237
    suspended = models.BooleanField('Administratively Suspended',
238
            default=False)
239

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

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

    
266
    class InvalidBackendMsgError(Exception):
267
         def __init__(self, opcode, status):
268
            self.opcode = opcode
269
            self.status = status
270
         def __str__(self):
271
            return repr('<opcode: %s, status: %s>' % (self.opcode,
272
                    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
    @property
300
    def backend_id(self):
301
        """Returns the backend id for this VM by prepending backend-prefix."""
302
        if not self.id:
303
            raise VirtualMachine.InvalidBackendIdError("self.id is None")
304
        return '%s%s' % (settings.BACKEND_PREFIX_ID, self.id)
305
    
306
    class Meta:
307
        verbose_name = u'Virtual machine instance'
308
        get_latest_by = 'created'
309
    
310
    def __unicode__(self):
311
        return self.name
312

    
313

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

    
326

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

    
335
    class Meta:
336
        verbose_name = u'Disk instance'
337

    
338
    def __unicode__(self):
339
        return self.name
340

    
341

    
342
class Network(models.Model):
343
    NETWORK_STATES = (
344
        ('ACTIVE', 'Active'),
345
        ('DELETED', 'Deleted')
346
    )
347
    
348
    name = models.CharField(max_length=255)
349
    created = models.DateTimeField(auto_now_add=True)
350
    updated = models.DateTimeField(auto_now=True)
351
    owner = models.ForeignKey(SynnefoUser, null=True)
352
    state = models.CharField(choices=NETWORK_STATES, max_length=30)
353
    public = models.BooleanField(default=False)
354
    link = models.ForeignKey('NetworkLink', related_name='+')
355
    machines = models.ManyToManyField(VirtualMachine,
356
            through='NetworkInterface')
357
    
358
    def __unicode__(self):
359
        return self.name
360

    
361

    
362
class NetworkInterface(models.Model):
363
    FIREWALL_PROFILES = (
364
        ('ENABLED', 'Enabled'),
365
        ('DISABLED', 'Disabled'),
366
        ('PROTECTED', 'Protected')
367
    )
368
    
369
    machine = models.ForeignKey(VirtualMachine, related_name='nics')
370
    network = models.ForeignKey(Network, related_name='nics')
371
    created = models.DateTimeField(auto_now_add=True)
372
    updated = models.DateTimeField(auto_now=True)
373
    index = models.IntegerField(null=True)
374
    mac = models.CharField(max_length=17, null=True)
375
    ipv4 = models.CharField(max_length=15, null=True)
376
    ipv6 = models.CharField(max_length=100, null=True)
377
    firewall_profile = models.CharField(choices=FIREWALL_PROFILES,
378
            max_length=30, null=True)
379
    
380
    def __unicode__(self):
381
        return '%s@%s' % (self.machine.name, self.network.name)
382

    
383

    
384
class NetworkLink(models.Model):
385
    network = models.ForeignKey(Network, null=True, related_name='+')
386
    index = models.IntegerField()
387
    name = models.CharField(max_length=255)
388
    available = models.BooleanField(default=True)
389
    
390
    def __unicode__(self):
391
        return self.name