Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (10.8 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
from django.conf import settings
31
from django.db import models
32

    
33

    
34
class Flavor(models.Model):
35
    cpu = models.IntegerField('Number of CPUs', default=0)
36
    ram = models.IntegerField('RAM size in MiB', default=0)
37
    disk = models.IntegerField('Disk size in GiB', default=0)
38
    disk_template = models.CharField('Disk template', max_length=32,
39
            default=settings.DEFAULT_GANETI_DISK_TEMPLATE)
40
    deleted = models.BooleanField('Deleted', default=False)
41

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

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

    
51
    def __unicode__(self):
52
        return self.name
53

    
54

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

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

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

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

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

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

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

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

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

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

    
171
    # Error classes
172
    class InvalidBackendIdError(Exception):
173
         def __init__(self, value):
174
            self.value = value
175
         def __str__(self):
176
            return repr(self.value)
177

    
178
    class InvalidBackendMsgError(Exception):
179
         def __init__(self, opcode, status):
180
            self.opcode = opcode
181
            self.status = status
182
         def __str__(self):
183
            return repr('<opcode: %s, status: %s>' % (self.opcode,
184
                    self.status))
185

    
186
    class InvalidActionError(Exception):
187
         def __init__(self, action):
188
            self._action = action
189
         def __str__(self):
190
            return repr(str(self._action))
191

    
192
    class DeletedError(Exception):
193
        pass
194

    
195
    class BuildingError(Exception):
196
        pass
197

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

    
211
    @property
212
    def backend_id(self):
213
        """Returns the backend id for this VM by prepending backend-prefix."""
214
        if not self.id:
215
            raise VirtualMachine.InvalidBackendIdError("self.id is None")
216
        return '%s%s' % (settings.BACKEND_PREFIX_ID, self.id)
217

    
218
    class Meta:
219
        verbose_name = u'Virtual machine instance'
220
        get_latest_by = 'created'
221

    
222
    def __unicode__(self):
223
        return self.name
224

    
225

    
226
class VirtualMachineMetadata(models.Model):
227
    meta_key = models.CharField(max_length=50)
228
    meta_value = models.CharField(max_length=500)
229
    vm = models.ForeignKey(VirtualMachine, related_name='metadata')
230

    
231
    class Meta:
232
        unique_together = (('meta_key', 'vm'),)
233
        verbose_name = u'Key-value pair of metadata for a VM.'
234

    
235
    def __unicode__(self):
236
        return u'%s: %s' % (self.meta_key, self.meta_value)
237

    
238

    
239
class Network(models.Model):
240
    NETWORK_STATES = (
241
        ('ACTIVE', 'Active'),
242
        ('DELETED', 'Deleted')
243
    )
244

    
245
    name = models.CharField(max_length=255)
246
    created = models.DateTimeField(auto_now_add=True)
247
    updated = models.DateTimeField(auto_now=True)
248
    userid = models.CharField('User ID of the owner', max_length=100,
249
                              null=True)
250
    state = models.CharField(choices=NETWORK_STATES, max_length=30)
251
    public = models.BooleanField(default=False)
252
    link = models.ForeignKey('NetworkLink', related_name='+')
253
    machines = models.ManyToManyField(VirtualMachine,
254
                                      through='NetworkInterface')
255

    
256
    def __unicode__(self):
257
        return self.name
258

    
259

    
260
class NetworkInterface(models.Model):
261
    FIREWALL_PROFILES = (
262
        ('ENABLED', 'Enabled'),
263
        ('DISABLED', 'Disabled'),
264
        ('PROTECTED', 'Protected')
265
    )
266

    
267
    machine = models.ForeignKey(VirtualMachine, related_name='nics')
268
    network = models.ForeignKey(Network, related_name='nics')
269
    created = models.DateTimeField(auto_now_add=True)
270
    updated = models.DateTimeField(auto_now=True)
271
    index = models.IntegerField(null=True)
272
    mac = models.CharField(max_length=17, null=True)
273
    ipv4 = models.CharField(max_length=15, null=True)
274
    ipv6 = models.CharField(max_length=100, null=True)
275
    firewall_profile = models.CharField(choices=FIREWALL_PROFILES,
276
                                        max_length=30, null=True)
277

    
278
    def __unicode__(self):
279
        return '%s@%s' % (self.machine.name, self.network.name)
280

    
281

    
282
class NetworkLink(models.Model):
283
    network = models.ForeignKey(Network, null=True, related_name='+')
284
    index = models.IntegerField()
285
    name = models.CharField(max_length=255)
286
    available = models.BooleanField(default=True)
287

    
288
    def __unicode__(self):
289
        return self.name
290

    
291
    class NotAvailable(Exception):
292
        pass
293