Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / db / models.py @ 1bce82c0

History | View | Annotate | Download (10.9 kB)

1
# Copyright 2011-2012 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 Flavor(models.Model):
37
    cpu = models.IntegerField('Number of CPUs', default=0)
38
    ram = models.IntegerField('RAM size in MiB', default=0)
39
    disk = models.IntegerField('Disk size in GiB', default=0)
40
    disk_template = models.CharField('Disk template', max_length=32,
41
            default=settings.DEFAULT_GANETI_DISK_TEMPLATE)
42
    deleted = models.BooleanField('Deleted', default=False)
43

    
44
    class Meta:
45
        verbose_name = u'Virtual machine flavor'
46
        unique_together = ('cpu', 'ram', 'disk', 'disk_template')
47

    
48
    @property
49
    def name(self):
50
        """Returns flavor name (generated)"""
51
        return u'C%dR%dD%d' % (self.cpu, self.ram, self.disk)
52

    
53
    def __unicode__(self):
54
        return self.name
55

    
56

    
57
class VirtualMachine(models.Model):
58
    # The list of possible actions for a VM
59
    ACTIONS = (
60
       ('CREATE', 'Create VM'),
61
       ('START', 'Start VM'),
62
       ('STOP', 'Shutdown VM'),
63
       ('SUSPEND', 'Admin Suspend VM'),
64
       ('REBOOT', 'Reboot VM'),
65
       ('DESTROY', 'Destroy VM')
66
    )
67

    
68
    # The internal operating state of a VM
69
    OPER_STATES = (
70
        ('BUILD', 'Queued for creation'),
71
        ('ERROR', 'Creation failed'),
72
        ('STOPPED', 'Stopped'),
73
        ('STARTED', 'Started'),
74
        ('DESTROYED', 'Destroyed')
75
    )
76

    
77
    # The list of possible operations on the backend
78
    BACKEND_OPCODES = (
79
        ('OP_INSTANCE_CREATE', 'Create Instance'),
80
        ('OP_INSTANCE_REMOVE', 'Remove Instance'),
81
        ('OP_INSTANCE_STARTUP', 'Startup Instance'),
82
        ('OP_INSTANCE_SHUTDOWN', 'Shutdown Instance'),
83
        ('OP_INSTANCE_REBOOT', 'Reboot Instance'),
84

    
85
        # These are listed here for completeness,
86
        # and are ignored for the time being
87
        ('OP_INSTANCE_SET_PARAMS', 'Set Instance Parameters'),
88
        ('OP_INSTANCE_QUERY_DATA', 'Query Instance Data'),
89
        ('OP_INSTANCE_REINSTALL', 'Reinstall Instance'),
90
        ('OP_INSTANCE_ACTIVATE_DISKS', 'Activate Disks'),
91
        ('OP_INSTANCE_DEACTIVATE_DISKS', 'Deactivate Disks'),
92
        ('OP_INSTANCE_REPLACE_DISKS', 'Replace Disks'),
93
        ('OP_INSTANCE_MIGRATE', 'Migrate Instance'),
94
        ('OP_INSTANCE_CONSOLE', 'Get Instance Console'),
95
        ('OP_INSTANCE_RECREATE_DISKS', 'Recreate Disks'),
96
        ('OP_INSTANCE_FAILOVER', 'Failover Instance')
97
    )
98

    
99
    # A backend job may be in one of the following possible states
100
    BACKEND_STATUSES = (
101
        ('queued', 'request queued'),
102
        ('waiting', 'request waiting for locks'),
103
        ('canceling', 'request being canceled'),
104
        ('running', 'request running'),
105
        ('canceled', 'request canceled'),
106
        ('success', 'request completed successfully'),
107
        ('error', 'request returned error')
108
    )
109

    
110
    # The operating state of a VM,
111
    # upon the successful completion of a backend operation.
112
    # IMPORTANT: Make sure all keys have a corresponding
113
    # entry in BACKEND_OPCODES if you update this field, see #1035, #1111.
114
    OPER_STATE_FROM_OPCODE = {
115
        'OP_INSTANCE_CREATE': 'STARTED',
116
        'OP_INSTANCE_REMOVE': 'DESTROYED',
117
        'OP_INSTANCE_STARTUP': 'STARTED',
118
        'OP_INSTANCE_SHUTDOWN': 'STOPPED',
119
        'OP_INSTANCE_REBOOT': 'STARTED',
120
        'OP_INSTANCE_SET_PARAMS': None,
121
        'OP_INSTANCE_QUERY_DATA': None,
122
        'OP_INSTANCE_REINSTALL': None,
123
        'OP_INSTANCE_ACTIVATE_DISKS': None,
124
        'OP_INSTANCE_DEACTIVATE_DISKS': None,
125
        'OP_INSTANCE_REPLACE_DISKS': None,
126
        'OP_INSTANCE_MIGRATE': None,
127
        'OP_INSTANCE_CONSOLE': None,
128
        'OP_INSTANCE_RECREATE_DISKS': None,
129
        'OP_INSTANCE_FAILOVER': None
130
    }
131

    
132
    # This dictionary contains the correspondence between
133
    # internal operating states and Server States as defined
134
    # by the Rackspace API.
135
    RSAPI_STATE_FROM_OPER_STATE = {
136
        "BUILD": "BUILD",
137
        "ERROR": "ERROR",
138
        "STOPPED": "STOPPED",
139
        "STARTED": "ACTIVE",
140
        "DESTROYED": "DELETED"
141
    }
142

    
143
    name = models.CharField('Virtual Machine Name', max_length=255)
144
    userid = models.CharField('User ID of the owner', max_length=100)
145
    created = models.DateTimeField(auto_now_add=True)
146
    updated = models.DateTimeField(auto_now=True)
147
    imageid = models.CharField(max_length=100, null=False)
148
    hostid = models.CharField(max_length=100)
149
    flavor = models.ForeignKey(Flavor)
150
    deleted = models.BooleanField('Deleted', default=False)
151
    suspended = models.BooleanField('Administratively Suspended',
152
                                    default=False)
153

    
154
    # VM State
155
    # The following fields are volatile data, in the sense
156
    # that they need not be persistent in the DB, but rather
157
    # get generated at runtime by quering Ganeti and applying
158
    # updates received from Ganeti.
159

    
160
    # In the future they could be moved to a separate caching layer
161
    # and removed from the database.
162
    # [vkoukis] after discussion with [faidon].
163
    action = models.CharField(choices=ACTIONS, max_length=30, null=True)
164
    operstate = models.CharField(choices=OPER_STATES, max_length=30, null=True)
165
    backendjobid = models.PositiveIntegerField(null=True)
166
    backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30,
167
            null=True)
168
    backendjobstatus = models.CharField(choices=BACKEND_STATUSES,
169
            max_length=30, null=True)
170
    backendlogmsg = models.TextField(null=True)
171
    buildpercentage = models.IntegerField(default=0)
172
    backendtime = models.DateTimeField(default=datetime.datetime.min)
173

    
174
    # Error classes
175
    class InvalidBackendIdError(Exception):
176
        def __init__(self, value):
177
            self.value = value
178

    
179
        def __str__(self):
180
            return repr(self.value)
181

    
182
    class InvalidBackendMsgError(Exception):
183
        def __init__(self, opcode, status):
184
            self.opcode = opcode
185
            self.status = status
186

    
187
        def __str__(self):
188
            return repr('<opcode: %s, status: %s>' % (self.opcode,
189
                        self.status))
190

    
191
    class InvalidActionError(Exception):
192
        def __init__(self, action):
193
            self._action = action
194

    
195
        def __str__(self):
196
            return repr(str(self._action))
197

    
198
    class DeletedError(Exception):
199
        pass
200

    
201
    class BuildingError(Exception):
202
        pass
203

    
204
    def __init__(self, *args, **kw):
205
        """Initialize state for just created VM instances."""
206
        super(VirtualMachine, self).__init__(*args, **kw)
207
        # This gets called BEFORE an instance gets save()d for
208
        # the first time.
209
        if not self.pk:
210
            self.action = None
211
            self.backendjobid = None
212
            self.backendjobstatus = None
213
            self.backendopcode = None
214
            self.backendlogmsg = None
215
            self.operstate = 'BUILD'
216

    
217
    @property
218
    def backend_vm_id(self):
219
        """Returns the backend id for this VM by prepending backend-prefix."""
220
        if not self.id:
221
            raise VirtualMachine.InvalidBackendIdError("self.id is None")
222
        return '%s%s' % (settings.BACKEND_PREFIX_ID, self.id)
223

    
224
    class Meta:
225
        verbose_name = u'Virtual machine instance'
226
        get_latest_by = 'created'
227

    
228
    def __unicode__(self):
229
        return self.name
230

    
231

    
232
class VirtualMachineMetadata(models.Model):
233
    meta_key = models.CharField(max_length=50)
234
    meta_value = models.CharField(max_length=500)
235
    vm = models.ForeignKey(VirtualMachine, related_name='metadata')
236

    
237
    class Meta:
238
        unique_together = (('meta_key', 'vm'),)
239
        verbose_name = u'Key-value pair of metadata for a VM.'
240

    
241
    def __unicode__(self):
242
        return u'%s: %s' % (self.meta_key, self.meta_value)
243

    
244

    
245
class Network(models.Model):
246
    NETWORK_STATES = (
247
        ('ACTIVE', 'Active'),
248
        ('DELETED', 'Deleted')
249
    )
250

    
251
    name = models.CharField(max_length=255)
252
    created = models.DateTimeField(auto_now_add=True)
253
    updated = models.DateTimeField(auto_now=True)
254
    userid = models.CharField('User ID of the owner', max_length=100,
255
                              null=True)
256
    state = models.CharField(choices=NETWORK_STATES, max_length=30)
257
    public = models.BooleanField(default=False)
258
    link = models.ForeignKey('NetworkLink', related_name='+')
259
    machines = models.ManyToManyField(VirtualMachine,
260
                                      through='NetworkInterface')
261

    
262
    def __unicode__(self):
263
        return self.name
264

    
265

    
266
class NetworkInterface(models.Model):
267
    FIREWALL_PROFILES = (
268
        ('ENABLED', 'Enabled'),
269
        ('DISABLED', 'Disabled'),
270
        ('PROTECTED', 'Protected')
271
    )
272

    
273
    machine = models.ForeignKey(VirtualMachine, related_name='nics')
274
    network = models.ForeignKey(Network, related_name='nics')
275
    created = models.DateTimeField(auto_now_add=True)
276
    updated = models.DateTimeField(auto_now=True)
277
    index = models.IntegerField(null=True)
278
    mac = models.CharField(max_length=17, null=True)
279
    ipv4 = models.CharField(max_length=15, null=True)
280
    ipv6 = models.CharField(max_length=100, null=True)
281
    firewall_profile = models.CharField(choices=FIREWALL_PROFILES,
282
                                        max_length=30, null=True)
283

    
284
    def __unicode__(self):
285
        return '%s@%s' % (self.machine.name, self.network.name)
286

    
287

    
288
class NetworkLink(models.Model):
289
    network = models.ForeignKey(Network, null=True, related_name='+')
290
    index = models.IntegerField()
291
    name = models.CharField(max_length=255)
292
    available = models.BooleanField(default=True)
293

    
294
    def __unicode__(self):
295
        return self.name
296

    
297
    class NotAvailable(Exception):
298
        pass
299