Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (33.2 kB)

1 244c552b Giorgos Verigakis
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2 48130e66 Georgios Gousios
#
3 48130e66 Georgios Gousios
# Redistribution and use in source and binary forms, with or without
4 48130e66 Georgios Gousios
# modification, are permitted provided that the following conditions
5 48130e66 Georgios Gousios
# are met:
6 48130e66 Georgios Gousios
#
7 48130e66 Georgios Gousios
#   1. Redistributions of source code must retain the above copyright
8 48130e66 Georgios Gousios
#      notice, this list of conditions and the following disclaimer.
9 48130e66 Georgios Gousios
#
10 48130e66 Georgios Gousios
#  2. Redistributions in binary form must reproduce the above copyright
11 48130e66 Georgios Gousios
#     notice, this list of conditions and the following disclaimer in the
12 48130e66 Georgios Gousios
#     documentation and/or other materials provided with the distribution.
13 48130e66 Georgios Gousios
#
14 48130e66 Georgios Gousios
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15 48130e66 Georgios Gousios
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 48130e66 Georgios Gousios
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 48130e66 Georgios Gousios
# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18 48130e66 Georgios Gousios
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 48130e66 Georgios Gousios
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 48130e66 Georgios Gousios
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 48130e66 Georgios Gousios
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 48130e66 Georgios Gousios
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 48130e66 Georgios Gousios
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 48130e66 Georgios Gousios
# SUCH DAMAGE.
25 48130e66 Georgios Gousios
#
26 48130e66 Georgios Gousios
# The views and conclusions contained in the software and documentation are
27 48130e66 Georgios Gousios
# those of the authors and should not be interpreted as representing official
28 48130e66 Georgios Gousios
# policies, either expressed or implied, of GRNET S.A.
29 48130e66 Georgios Gousios
30 c4e55622 Christos Stavrakakis
import datetime
31 c4e55622 Christos Stavrakakis
32 bd87213f Christos Stavrakakis
from copy import deepcopy
33 9dbe70f8 Markos Gogoulos
from django.conf import settings
34 17935f41 Vassilios Karakoidas
from django.db import models
35 3165f027 Christos Stavrakakis
36 3165f027 Christos Stavrakakis
import utils
37 3524241a Christos Stavrakakis
from contextlib import contextmanager
38 aed9b901 Christos Stavrakakis
from hashlib import sha1
39 bd40abfa Christos Stavrakakis
from snf_django.lib.api import faults
40 0c09b1c0 Christos Stavrakakis
from django.conf import settings as snf_settings
41 30e0ed74 Christos Stavrakakis
from aes_encrypt import encrypt_db_charfield, decrypt_db_charfield
42 aed9b901 Christos Stavrakakis
43 c9976c84 Christos Stavrakakis
from synnefo.db import pools, fields
44 864bed43 Christos Stavrakakis
45 3524241a Christos Stavrakakis
from synnefo.logic.rapi_pool import (get_rapi_client,
46 3524241a Christos Stavrakakis
                                     put_rapi_client)
47 aed9b901 Christos Stavrakakis
48 b533e9b2 Christos Stavrakakis
import logging
49 b533e9b2 Christos Stavrakakis
log = logging.getLogger(__name__)
50 b533e9b2 Christos Stavrakakis
51 aed9b901 Christos Stavrakakis
52 e7164ebc Vassilios Karakoidas
class Flavor(models.Model):
53 13b3c5ff Vassilios Karakoidas
    cpu = models.IntegerField('Number of CPUs', default=0)
54 c909cbbd Giorgos Verigakis
    ram = models.IntegerField('RAM size in MiB', default=0)
55 c909cbbd Giorgos Verigakis
    disk = models.IntegerField('Disk size in GiB', default=0)
56 c802789d Christos Stavrakakis
    disk_template = models.CharField('Disk template', max_length=32)
57 2c60cbef Giorgos Verigakis
    deleted = models.BooleanField('Deleted', default=False)
58 ce55f211 Kostas Papadimitriou
59 be7b8d37 Vassilios Karakoidas
    class Meta:
60 dcfc6c2d Vangelis Koukis
        verbose_name = u'Virtual machine flavor'
61 b1af40e2 Giorgos Verigakis
        unique_together = ('cpu', 'ram', 'disk', 'disk_template')
62 ce55f211 Kostas Papadimitriou
63 9ff7ca6f Giorgos Verigakis
    @property
64 9ff7ca6f Giorgos Verigakis
    def name(self):
65 f0f4edac Vassilios Karakoidas
        """Returns flavor name (generated)"""
66 b65a3b04 Christos Stavrakakis
        return u'C%dR%dD%d%s' % (self.cpu, self.ram, self.disk,
67 b65a3b04 Christos Stavrakakis
                                 self.disk_template)
68 ce55f211 Kostas Papadimitriou
69 46547e14 Faidon Liambotis
    def __unicode__(self):
70 df36a125 Christos Stavrakakis
        return "<%s:%s>" % (str(self.id), self.name)
71 452d2391 Vassilios Karakoidas
72 46547e14 Faidon Liambotis
73 aed9b901 Christos Stavrakakis
class Backend(models.Model):
74 aed9b901 Christos Stavrakakis
    clustername = models.CharField('Cluster Name', max_length=128, unique=True)
75 aed9b901 Christos Stavrakakis
    port = models.PositiveIntegerField('Port', default=5080)
76 aed9b901 Christos Stavrakakis
    username = models.CharField('Username', max_length=64, blank=True,
77 aed9b901 Christos Stavrakakis
                                null=True)
78 2a8a60d5 Christos Stavrakakis
    password_hash = models.CharField('Password', max_length=128, blank=True,
79 68b952f9 Christos Stavrakakis
                                     null=True)
80 aed9b901 Christos Stavrakakis
    # Sha1 is up to 40 characters long
81 aed9b901 Christos Stavrakakis
    hash = models.CharField('Hash', max_length=40, editable=False, null=False)
82 3165f027 Christos Stavrakakis
    # Unique index of the Backend, used for the mac-prefixes of the
83 3165f027 Christos Stavrakakis
    # BackendNetworks
84 3165f027 Christos Stavrakakis
    index = models.PositiveIntegerField('Index', null=False, unique=True,
85 3165f027 Christos Stavrakakis
                                        default=0)
86 aed9b901 Christos Stavrakakis
    drained = models.BooleanField('Drained', default=False, null=False)
87 aed9b901 Christos Stavrakakis
    offline = models.BooleanField('Offline', default=False, null=False)
88 bd87213f Christos Stavrakakis
    # Type of hypervisor
89 bd87213f Christos Stavrakakis
    hypervisor = models.CharField('Hypervisor', max_length=32, default="kvm",
90 bd87213f Christos Stavrakakis
                                  null=False)
91 c9976c84 Christos Stavrakakis
    disk_templates = fields.SeparatedValuesField("Disk Templates", null=True)
92 aed9b901 Christos Stavrakakis
    # Last refresh of backend resources
93 aed9b901 Christos Stavrakakis
    updated = models.DateTimeField(auto_now_add=True)
94 aed9b901 Christos Stavrakakis
    # Backend resources
95 aed9b901 Christos Stavrakakis
    mfree = models.PositiveIntegerField('Free Memory', default=0, null=False)
96 aed9b901 Christos Stavrakakis
    mtotal = models.PositiveIntegerField('Total Memory', default=0, null=False)
97 aed9b901 Christos Stavrakakis
    dfree = models.PositiveIntegerField('Free Disk', default=0, null=False)
98 aed9b901 Christos Stavrakakis
    dtotal = models.PositiveIntegerField('Total Disk', default=0, null=False)
99 aed9b901 Christos Stavrakakis
    pinst_cnt = models.PositiveIntegerField('Primary Instances', default=0,
100 aed9b901 Christos Stavrakakis
                                            null=False)
101 aed9b901 Christos Stavrakakis
    ctotal = models.PositiveIntegerField('Total number of logical processors',
102 aed9b901 Christos Stavrakakis
                                         default=0, null=False)
103 aed9b901 Christos Stavrakakis
104 4c9ac139 Christos Stavrakakis
    HYPERVISORS = (
105 4c9ac139 Christos Stavrakakis
        ("kvm", "Linux KVM hypervisor"),
106 4c9ac139 Christos Stavrakakis
        ("xen-pvm", "Xen PVM hypervisor"),
107 4c9ac139 Christos Stavrakakis
        ("xen-hvm", "Xen KVM hypervisor"),
108 4c9ac139 Christos Stavrakakis
    )
109 4c9ac139 Christos Stavrakakis
110 aed9b901 Christos Stavrakakis
    class Meta:
111 aed9b901 Christos Stavrakakis
        verbose_name = u'Backend'
112 aed9b901 Christos Stavrakakis
        ordering = ["clustername"]
113 aed9b901 Christos Stavrakakis
114 aed9b901 Christos Stavrakakis
    def __unicode__(self):
115 bf5c82dc Christos Stavrakakis
        return self.clustername + "(id=" + str(self.id) + ")"
116 aed9b901 Christos Stavrakakis
117 aed9b901 Christos Stavrakakis
    @property
118 aed9b901 Christos Stavrakakis
    def backend_id(self):
119 aed9b901 Christos Stavrakakis
        return self.id
120 aed9b901 Christos Stavrakakis
121 3524241a Christos Stavrakakis
    def get_client(self):
122 aed9b901 Christos Stavrakakis
        """Get or create a client. """
123 3524241a Christos Stavrakakis
        if self.offline:
124 bd40abfa Christos Stavrakakis
            raise faults.ServiceUnavailable
125 3524241a Christos Stavrakakis
        return get_rapi_client(self.id, self.hash,
126 cc50e51a Christos Stavrakakis
                               self.clustername,
127 cc50e51a Christos Stavrakakis
                               self.port,
128 3524241a Christos Stavrakakis
                               self.username,
129 3524241a Christos Stavrakakis
                               self.password)
130 3524241a Christos Stavrakakis
131 3524241a Christos Stavrakakis
    @staticmethod
132 3524241a Christos Stavrakakis
    def put_client(client):
133 3524241a Christos Stavrakakis
            put_rapi_client(client)
134 aed9b901 Christos Stavrakakis
135 aed9b901 Christos Stavrakakis
    def create_hash(self):
136 aed9b901 Christos Stavrakakis
        """Create a hash for this backend. """
137 68b952f9 Christos Stavrakakis
        sha = sha1('%s%s%s%s' %
138 68b952f9 Christos Stavrakakis
                   (self.clustername, self.port, self.username, self.password))
139 68b952f9 Christos Stavrakakis
        return sha.hexdigest()
140 aed9b901 Christos Stavrakakis
141 30e0ed74 Christos Stavrakakis
    @property
142 30e0ed74 Christos Stavrakakis
    def password(self):
143 30e0ed74 Christos Stavrakakis
        return decrypt_db_charfield(self.password_hash)
144 30e0ed74 Christos Stavrakakis
145 30e0ed74 Christos Stavrakakis
    @password.setter
146 30e0ed74 Christos Stavrakakis
    def password(self, value):
147 30e0ed74 Christos Stavrakakis
        self.password_hash = encrypt_db_charfield(value)
148 30e0ed74 Christos Stavrakakis
149 aed9b901 Christos Stavrakakis
    def save(self, *args, **kwargs):
150 aed9b901 Christos Stavrakakis
        # Create a new hash each time a Backend is saved
151 aed9b901 Christos Stavrakakis
        old_hash = self.hash
152 aed9b901 Christos Stavrakakis
        self.hash = self.create_hash()
153 aed9b901 Christos Stavrakakis
        super(Backend, self).save(*args, **kwargs)
154 aed9b901 Christos Stavrakakis
        if self.hash != old_hash:
155 aed9b901 Christos Stavrakakis
            # Populate the new hash to the new instances
156 68b952f9 Christos Stavrakakis
            self.virtual_machines.filter(deleted=False)\
157 68b952f9 Christos Stavrakakis
                                 .update(backend_hash=self.hash)
158 aed9b901 Christos Stavrakakis
159 3165f027 Christos Stavrakakis
    def __init__(self, *args, **kwargs):
160 3165f027 Christos Stavrakakis
        super(Backend, self).__init__(*args, **kwargs)
161 3165f027 Christos Stavrakakis
        if not self.pk:
162 3165f027 Christos Stavrakakis
            # Generate a unique index for the Backend
163 3165f027 Christos Stavrakakis
            indexes = Backend.objects.all().values_list('index', flat=True)
164 ce9abd26 Christos Stavrakakis
            try:
165 ce9abd26 Christos Stavrakakis
                first_free = [x for x in xrange(0, 16) if x not in indexes][0]
166 ce9abd26 Christos Stavrakakis
                self.index = first_free
167 ce9abd26 Christos Stavrakakis
            except IndexError:
168 ce9abd26 Christos Stavrakakis
                raise Exception("Can not create more than 16 backends")
169 3165f027 Christos Stavrakakis
170 bd87213f Christos Stavrakakis
    def use_hotplug(self):
171 bd87213f Christos Stavrakakis
        return self.hypervisor == "kvm" and snf_settings.GANETI_USE_HOTPLUG
172 bd87213f Christos Stavrakakis
173 bd87213f Christos Stavrakakis
    def get_create_params(self):
174 bd87213f Christos Stavrakakis
        params = deepcopy(snf_settings.GANETI_CREATEINSTANCE_KWARGS)
175 bd87213f Christos Stavrakakis
        params["hvparams"] = params.get("hvparams", {})\
176 bd87213f Christos Stavrakakis
                                   .get(self.hypervisor, {})
177 bd87213f Christos Stavrakakis
        return params
178 bd87213f Christos Stavrakakis
179 aed9b901 Christos Stavrakakis
180 fd65ab41 Christos Stavrakakis
# A backend job may be in one of the following possible states
181 fd65ab41 Christos Stavrakakis
BACKEND_STATUSES = (
182 fd65ab41 Christos Stavrakakis
    ('queued', 'request queued'),
183 fd65ab41 Christos Stavrakakis
    ('waiting', 'request waiting for locks'),
184 fd65ab41 Christos Stavrakakis
    ('canceling', 'request being canceled'),
185 fd65ab41 Christos Stavrakakis
    ('running', 'request running'),
186 fd65ab41 Christos Stavrakakis
    ('canceled', 'request canceled'),
187 fd65ab41 Christos Stavrakakis
    ('success', 'request completed successfully'),
188 fd65ab41 Christos Stavrakakis
    ('error', 'request returned error')
189 fd65ab41 Christos Stavrakakis
)
190 fd65ab41 Christos Stavrakakis
191 fd65ab41 Christos Stavrakakis
192 3416e629 Christos Stavrakakis
class QuotaHolderSerial(models.Model):
193 97fffe10 Christos Stavrakakis
    """Model representing a serial for a Quotaholder Commission.
194 97fffe10 Christos Stavrakakis

195 97fffe10 Christos Stavrakakis
    serial:   The serial that Quotaholder assigned to this commission
196 97fffe10 Christos Stavrakakis
    pending:  Whether it has been decided to accept or reject this commission
197 97fffe10 Christos Stavrakakis
    accept:   If pending is False, this attribute indicates whether to accept
198 97fffe10 Christos Stavrakakis
              or reject this commission
199 97fffe10 Christos Stavrakakis
    resolved: Whether this commission has been accepted or rejected to
200 97fffe10 Christos Stavrakakis
              Quotaholder.
201 97fffe10 Christos Stavrakakis

202 97fffe10 Christos Stavrakakis
    """
203 68b952f9 Christos Stavrakakis
    serial = models.BigIntegerField(null=False, primary_key=True,
204 68b952f9 Christos Stavrakakis
                                    db_index=True)
205 3416e629 Christos Stavrakakis
    pending = models.BooleanField(default=True, db_index=True)
206 97fffe10 Christos Stavrakakis
    accept = models.BooleanField(default=False)
207 97fffe10 Christos Stavrakakis
    resolved = models.BooleanField(default=False)
208 3416e629 Christos Stavrakakis
209 3416e629 Christos Stavrakakis
    class Meta:
210 3416e629 Christos Stavrakakis
        verbose_name = u'Quota Serial'
211 3416e629 Christos Stavrakakis
        ordering = ["serial"]
212 3416e629 Christos Stavrakakis
213 41a7fae7 Christos Stavrakakis
    def __unicode__(self):
214 41a7fae7 Christos Stavrakakis
        return u"<serial: %s>" % self.serial
215 41a7fae7 Christos Stavrakakis
216 3416e629 Christos Stavrakakis
217 07f3219d Vassilios Karakoidas
class VirtualMachine(models.Model):
218 c92af313 Vangelis Koukis
    # The list of possible actions for a VM
219 d08a5f6f Vangelis Koukis
    ACTIONS = (
220 68b952f9 Christos Stavrakakis
        ('CREATE', 'Create VM'),
221 68b952f9 Christos Stavrakakis
        ('START', 'Start VM'),
222 68b952f9 Christos Stavrakakis
        ('STOP', 'Shutdown VM'),
223 68b952f9 Christos Stavrakakis
        ('SUSPEND', 'Admin Suspend VM'),
224 68b952f9 Christos Stavrakakis
        ('REBOOT', 'Reboot VM'),
225 1af851fd Christos Stavrakakis
        ('DESTROY', 'Destroy VM'),
226 1af851fd Christos Stavrakakis
        ('RESIZE', 'Resize a VM'),
227 9ba6bb95 Christos Stavrakakis
        ('ADDFLOATINGIP', 'Add floating IP to VM'),
228 9ba6bb95 Christos Stavrakakis
        ('REMOVEFLOATINGIP', 'Add floating IP to VM'),
229 d08a5f6f Vangelis Koukis
    )
230 ce55f211 Kostas Papadimitriou
231 c92af313 Vangelis Koukis
    # The internal operating state of a VM
232 d08a5f6f Vangelis Koukis
    OPER_STATES = (
233 d08a5f6f Vangelis Koukis
        ('BUILD', 'Queued for creation'),
234 d08a5f6f Vangelis Koukis
        ('ERROR', 'Creation failed'),
235 d08a5f6f Vangelis Koukis
        ('STOPPED', 'Stopped'),
236 d08a5f6f Vangelis Koukis
        ('STARTED', 'Started'),
237 1af851fd Christos Stavrakakis
        ('DESTROYED', 'Destroyed'),
238 1af851fd Christos Stavrakakis
        ('RESIZE', 'Resizing')
239 d08a5f6f Vangelis Koukis
    )
240 ce55f211 Kostas Papadimitriou
241 c92af313 Vangelis Koukis
    # The list of possible operations on the backend
242 d08a5f6f Vangelis Koukis
    BACKEND_OPCODES = (
243 d08a5f6f Vangelis Koukis
        ('OP_INSTANCE_CREATE', 'Create Instance'),
244 d08a5f6f Vangelis Koukis
        ('OP_INSTANCE_REMOVE', 'Remove Instance'),
245 d08a5f6f Vangelis Koukis
        ('OP_INSTANCE_STARTUP', 'Startup Instance'),
246 d08a5f6f Vangelis Koukis
        ('OP_INSTANCE_SHUTDOWN', 'Shutdown Instance'),
247 26fef2b9 Vangelis Koukis
        ('OP_INSTANCE_REBOOT', 'Reboot Instance'),
248 26fef2b9 Vangelis Koukis
249 26fef2b9 Vangelis Koukis
        # These are listed here for completeness,
250 26fef2b9 Vangelis Koukis
        # and are ignored for the time being
251 26fef2b9 Vangelis Koukis
        ('OP_INSTANCE_SET_PARAMS', 'Set Instance Parameters'),
252 26fef2b9 Vangelis Koukis
        ('OP_INSTANCE_QUERY_DATA', 'Query Instance Data'),
253 21d8adbf Vangelis Koukis
        ('OP_INSTANCE_REINSTALL', 'Reinstall Instance'),
254 21d8adbf Vangelis Koukis
        ('OP_INSTANCE_ACTIVATE_DISKS', 'Activate Disks'),
255 21d8adbf Vangelis Koukis
        ('OP_INSTANCE_DEACTIVATE_DISKS', 'Deactivate Disks'),
256 21d8adbf Vangelis Koukis
        ('OP_INSTANCE_REPLACE_DISKS', 'Replace Disks'),
257 21d8adbf Vangelis Koukis
        ('OP_INSTANCE_MIGRATE', 'Migrate Instance'),
258 21d8adbf Vangelis Koukis
        ('OP_INSTANCE_CONSOLE', 'Get Instance Console'),
259 41303ed0 Vangelis Koukis
        ('OP_INSTANCE_RECREATE_DISKS', 'Recreate Disks'),
260 41303ed0 Vangelis Koukis
        ('OP_INSTANCE_FAILOVER', 'Failover Instance')
261 d08a5f6f Vangelis Koukis
    )
262 ce55f211 Kostas Papadimitriou
263 d08a5f6f Vangelis Koukis
    # The operating state of a VM,
264 d08a5f6f Vangelis Koukis
    # upon the successful completion of a backend operation.
265 21d8adbf Vangelis Koukis
    # IMPORTANT: Make sure all keys have a corresponding
266 21d8adbf Vangelis Koukis
    # entry in BACKEND_OPCODES if you update this field, see #1035, #1111.
267 d08a5f6f Vangelis Koukis
    OPER_STATE_FROM_OPCODE = {
268 d08a5f6f Vangelis Koukis
        'OP_INSTANCE_CREATE': 'STARTED',
269 d08a5f6f Vangelis Koukis
        'OP_INSTANCE_REMOVE': 'DESTROYED',
270 d08a5f6f Vangelis Koukis
        'OP_INSTANCE_STARTUP': 'STARTED',
271 d08a5f6f Vangelis Koukis
        'OP_INSTANCE_SHUTDOWN': 'STOPPED',
272 26fef2b9 Vangelis Koukis
        'OP_INSTANCE_REBOOT': 'STARTED',
273 26fef2b9 Vangelis Koukis
        'OP_INSTANCE_SET_PARAMS': None,
274 8490bf84 Georgios Gousios
        'OP_INSTANCE_QUERY_DATA': None,
275 1bce82c0 Christos Stavrakakis
        'OP_INSTANCE_REINSTALL': None,
276 1bce82c0 Christos Stavrakakis
        'OP_INSTANCE_ACTIVATE_DISKS': None,
277 8490bf84 Georgios Gousios
        'OP_INSTANCE_DEACTIVATE_DISKS': None,
278 1bce82c0 Christos Stavrakakis
        'OP_INSTANCE_REPLACE_DISKS': None,
279 8490bf84 Georgios Gousios
        'OP_INSTANCE_MIGRATE': None,
280 8490bf84 Georgios Gousios
        'OP_INSTANCE_CONSOLE': None,
281 41303ed0 Vangelis Koukis
        'OP_INSTANCE_RECREATE_DISKS': None,
282 41303ed0 Vangelis Koukis
        'OP_INSTANCE_FAILOVER': None
283 d08a5f6f Vangelis Koukis
    }
284 d08a5f6f Vangelis Koukis
285 c92af313 Vangelis Koukis
    # This dictionary contains the correspondence between
286 c92af313 Vangelis Koukis
    # internal operating states and Server States as defined
287 c92af313 Vangelis Koukis
    # by the Rackspace API.
288 d08a5f6f Vangelis Koukis
    RSAPI_STATE_FROM_OPER_STATE = {
289 d08a5f6f Vangelis Koukis
        "BUILD": "BUILD",
290 d08a5f6f Vangelis Koukis
        "ERROR": "ERROR",
291 d08a5f6f Vangelis Koukis
        "STOPPED": "STOPPED",
292 d08a5f6f Vangelis Koukis
        "STARTED": "ACTIVE",
293 41a7fae7 Christos Stavrakakis
        'RESIZE': 'RESIZE',
294 41a7fae7 Christos Stavrakakis
        'DESTROYED': 'DELETED',
295 d08a5f6f Vangelis Koukis
    }
296 d08a5f6f Vangelis Koukis
297 4daac449 Vassilios Karakoidas
    name = models.CharField('Virtual Machine Name', max_length=255)
298 190d155f Christos Stavrakakis
    userid = models.CharField('User ID of the owner', max_length=100,
299 e18c1749 Christos Stavrakakis
                              db_index=True, null=False)
300 aed9b901 Christos Stavrakakis
    backend = models.ForeignKey(Backend, null=True,
301 26515bc1 Christos Stavrakakis
                                related_name="virtual_machines",
302 26515bc1 Christos Stavrakakis
                                on_delete=models.PROTECT)
303 aed9b901 Christos Stavrakakis
    backend_hash = models.CharField(max_length=128, null=True, editable=False)
304 d174b105 Vassilios Karakoidas
    created = models.DateTimeField(auto_now_add=True)
305 d174b105 Vassilios Karakoidas
    updated = models.DateTimeField(auto_now=True)
306 936afb7b Giorgos Verigakis
    imageid = models.CharField(max_length=100, null=False)
307 22571df6 Vassilios Karakoidas
    hostid = models.CharField(max_length=100)
308 3bb0b117 Christos Stavrakakis
    flavor = models.ForeignKey(Flavor, on_delete=models.PROTECT)
309 190d155f Christos Stavrakakis
    deleted = models.BooleanField('Deleted', default=False, db_index=True)
310 9ff7ca6f Giorgos Verigakis
    suspended = models.BooleanField('Administratively Suspended',
311 244c552b Giorgos Verigakis
                                    default=False)
312 004967e4 Christos Stavrakakis
    serial = models.ForeignKey(QuotaHolderSerial,
313 3bb0b117 Christos Stavrakakis
                               related_name='virtual_machine', null=True,
314 3bb0b117 Christos Stavrakakis
                               on_delete=models.SET_NULL)
315 d08a5f6f Vangelis Koukis
316 ce55f211 Kostas Papadimitriou
    # VM State
317 52194743 Vangelis Koukis
    # The following fields are volatile data, in the sense
318 52194743 Vangelis Koukis
    # that they need not be persistent in the DB, but rather
319 52194743 Vangelis Koukis
    # get generated at runtime by quering Ganeti and applying
320 52194743 Vangelis Koukis
    # updates received from Ganeti.
321 ce55f211 Kostas Papadimitriou
322 73b2b65e Vassilios Karakoidas
    # In the future they could be moved to a separate caching layer
323 73b2b65e Vassilios Karakoidas
    # and removed from the database.
324 73b2b65e Vassilios Karakoidas
    # [vkoukis] after discussion with [faidon].
325 1cfd5d4d Christos Stavrakakis
    action = models.CharField(choices=ACTIONS, max_length=30, null=True,
326 1cfd5d4d Christos Stavrakakis
                              default=None)
327 1cfd5d4d Christos Stavrakakis
    operstate = models.CharField(choices=OPER_STATES, max_length=30,
328 1cfd5d4d Christos Stavrakakis
                                 null=False, default="BUILD")
329 dfd19c2d Vassilios Karakoidas
    backendjobid = models.PositiveIntegerField(null=True)
330 f533f224 Vangelis Koukis
    backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30,
331 aed9b901 Christos Stavrakakis
                                     null=True)
332 9ff7ca6f Giorgos Verigakis
    backendjobstatus = models.CharField(choices=BACKEND_STATUSES,
333 aed9b901 Christos Stavrakakis
                                        max_length=30, null=True)
334 dfd19c2d Vassilios Karakoidas
    backendlogmsg = models.TextField(null=True)
335 19da4325 Vangelis Koukis
    buildpercentage = models.IntegerField(default=0)
336 c4e55622 Christos Stavrakakis
    backendtime = models.DateTimeField(default=datetime.datetime.min)
337 9068cd85 Georgios Gousios
338 41a7fae7 Christos Stavrakakis
    # Latest action and corresponding Ganeti job ID, for actions issued
339 41a7fae7 Christos Stavrakakis
    # by the API
340 41a7fae7 Christos Stavrakakis
    task = models.CharField(max_length=64, null=True)
341 e8d31c74 Christos Stavrakakis
    task_job_id = models.BigIntegerField(null=True)
342 41a7fae7 Christos Stavrakakis
343 3524241a Christos Stavrakakis
    def get_client(self):
344 3524241a Christos Stavrakakis
        if self.backend:
345 2cea7f38 Christos Stavrakakis
            return self.backend.get_client()
346 aed9b901 Christos Stavrakakis
        else:
347 bd40abfa Christos Stavrakakis
            raise faults.ServiceUnavailable
348 aed9b901 Christos Stavrakakis
349 6ce1fc56 Kostas Papadimitriou
    def get_last_diagnostic(self, **filters):
350 6ce1fc56 Kostas Papadimitriou
        try:
351 6ce1fc56 Kostas Papadimitriou
            return self.diagnostics.filter()[0]
352 6ce1fc56 Kostas Papadimitriou
        except IndexError:
353 6ce1fc56 Kostas Papadimitriou
            return None
354 6ce1fc56 Kostas Papadimitriou
355 3524241a Christos Stavrakakis
    @staticmethod
356 3524241a Christos Stavrakakis
    def put_client(client):
357 3524241a Christos Stavrakakis
            put_rapi_client(client)
358 3524241a Christos Stavrakakis
359 aed9b901 Christos Stavrakakis
    def save(self, *args, **kwargs):
360 aed9b901 Christos Stavrakakis
        # Store hash for first time saved vm
361 aed9b901 Christos Stavrakakis
        if (self.id is None or self.backend_hash == '') and self.backend:
362 aed9b901 Christos Stavrakakis
            self.backend_hash = self.backend.hash
363 aed9b901 Christos Stavrakakis
        super(VirtualMachine, self).save(*args, **kwargs)
364 aed9b901 Christos Stavrakakis
365 9ff7ca6f Giorgos Verigakis
    @property
366 924d8085 Christos Stavrakakis
    def backend_vm_id(self):
367 224b5e19 Markos Gogoulos
        """Returns the backend id for this VM by prepending backend-prefix."""
368 76ba77c1 Vangelis Koukis
        if not self.id:
369 76ba77c1 Vangelis Koukis
            raise VirtualMachine.InvalidBackendIdError("self.id is None")
370 d30f29aa Christos Stavrakakis
        return "%s%s" % (settings.BACKEND_PREFIX_ID, str(self.id))
371 ce55f211 Kostas Papadimitriou
372 d08a5f6f Vangelis Koukis
    class Meta:
373 d08a5f6f Vangelis Koukis
        verbose_name = u'Virtual machine instance'
374 d08a5f6f Vangelis Koukis
        get_latest_by = 'created'
375 ce55f211 Kostas Papadimitriou
376 d08a5f6f Vangelis Koukis
    def __unicode__(self):
377 7c714455 Christos Stavrakakis
        return u"<vm:%s@backend:%s>" % (self.id, self.backend_id)
378 d08a5f6f Vangelis Koukis
379 048fab1e Christos Stavrakakis
    # Error classes
380 048fab1e Christos Stavrakakis
    class InvalidBackendIdError(Exception):
381 048fab1e Christos Stavrakakis
        def __init__(self, value):
382 048fab1e Christos Stavrakakis
            self.value = value
383 048fab1e Christos Stavrakakis
384 048fab1e Christos Stavrakakis
        def __str__(self):
385 048fab1e Christos Stavrakakis
            return repr(self.value)
386 048fab1e Christos Stavrakakis
387 048fab1e Christos Stavrakakis
    class InvalidBackendMsgError(Exception):
388 048fab1e Christos Stavrakakis
        def __init__(self, opcode, status):
389 048fab1e Christos Stavrakakis
            self.opcode = opcode
390 048fab1e Christos Stavrakakis
            self.status = status
391 048fab1e Christos Stavrakakis
392 048fab1e Christos Stavrakakis
        def __str__(self):
393 048fab1e Christos Stavrakakis
            return repr('<opcode: %s, status: %s>' % (self.opcode,
394 048fab1e Christos Stavrakakis
                        self.status))
395 048fab1e Christos Stavrakakis
396 048fab1e Christos Stavrakakis
    class InvalidActionError(Exception):
397 048fab1e Christos Stavrakakis
        def __init__(self, action):
398 048fab1e Christos Stavrakakis
            self._action = action
399 048fab1e Christos Stavrakakis
400 048fab1e Christos Stavrakakis
        def __str__(self):
401 048fab1e Christos Stavrakakis
            return repr(str(self._action))
402 048fab1e Christos Stavrakakis
403 4ffb82dc Vassilios Karakoidas
404 be7b8d37 Vassilios Karakoidas
class VirtualMachineMetadata(models.Model):
405 be7b8d37 Vassilios Karakoidas
    meta_key = models.CharField(max_length=50)
406 be7b8d37 Vassilios Karakoidas
    meta_value = models.CharField(max_length=500)
407 3bb0b117 Christos Stavrakakis
    vm = models.ForeignKey(VirtualMachine, related_name='metadata',
408 3bb0b117 Christos Stavrakakis
                           on_delete=models.CASCADE)
409 ce55f211 Kostas Papadimitriou
410 be7b8d37 Vassilios Karakoidas
    class Meta:
411 1bd24df6 Giorgos Verigakis
        unique_together = (('meta_key', 'vm'),)
412 dcfc6c2d Vangelis Koukis
        verbose_name = u'Key-value pair of metadata for a VM.'
413 ce55f211 Kostas Papadimitriou
414 be7b8d37 Vassilios Karakoidas
    def __unicode__(self):
415 9ff7ca6f Giorgos Verigakis
        return u'%s: %s' % (self.meta_key, self.meta_value)
416 be7b8d37 Vassilios Karakoidas
417 be7b8d37 Vassilios Karakoidas
418 0269afd6 Giorgos Verigakis
class Network(models.Model):
419 e6a42a96 Christos Stavrakakis
    OPER_STATES = (
420 99af08a4 Christos Stavrakakis
        ('PENDING', 'Pending'),  # Unused because of lazy networks
421 f533f224 Vangelis Koukis
        ('ACTIVE', 'Active'),
422 e6a42a96 Christos Stavrakakis
        ('DELETED', 'Deleted'),
423 e6a42a96 Christos Stavrakakis
        ('ERROR', 'Error')
424 e6a42a96 Christos Stavrakakis
    )
425 e6a42a96 Christos Stavrakakis
426 e6a42a96 Christos Stavrakakis
    ACTIONS = (
427 68b952f9 Christos Stavrakakis
        ('CREATE', 'Create Network'),
428 68b952f9 Christos Stavrakakis
        ('DESTROY', 'Destroy Network'),
429 ece5581b Christos Stavrakakis
        ('ADD', 'Add server to Network'),
430 ece5581b Christos Stavrakakis
        ('REMOVE', 'Remove server from Network'),
431 e6a42a96 Christos Stavrakakis
    )
432 e6a42a96 Christos Stavrakakis
433 e6a42a96 Christos Stavrakakis
    RSAPI_STATE_FROM_OPER_STATE = {
434 e6a42a96 Christos Stavrakakis
        'PENDING': 'PENDING',
435 e6a42a96 Christos Stavrakakis
        'ACTIVE': 'ACTIVE',
436 e6a42a96 Christos Stavrakakis
        'DELETED': 'DELETED',
437 e6a42a96 Christos Stavrakakis
        'ERROR': 'ERROR'
438 e6a42a96 Christos Stavrakakis
    }
439 e6a42a96 Christos Stavrakakis
440 b7d38981 Dimitris Aragiorgis
    FLAVORS = {
441 b7d38981 Dimitris Aragiorgis
        'CUSTOM': {
442 68b952f9 Christos Stavrakakis
            'mode': 'bridged',
443 68b952f9 Christos Stavrakakis
            'link': settings.DEFAULT_BRIDGE,
444 68b952f9 Christos Stavrakakis
            'mac_prefix': settings.DEFAULT_MAC_PREFIX,
445 68b952f9 Christos Stavrakakis
            'tags': None,
446 68b952f9 Christos Stavrakakis
            'desc': "Basic flavor used for a bridged network",
447 b7d38981 Dimitris Aragiorgis
        },
448 b7d38981 Dimitris Aragiorgis
        'IP_LESS_ROUTED': {
449 68b952f9 Christos Stavrakakis
            'mode': 'routed',
450 68b952f9 Christos Stavrakakis
            'link': settings.DEFAULT_ROUTING_TABLE,
451 68b952f9 Christos Stavrakakis
            'mac_prefix': settings.DEFAULT_MAC_PREFIX,
452 68b952f9 Christos Stavrakakis
            'tags': 'ip-less-routed',
453 68b952f9 Christos Stavrakakis
            'desc': "Flavor used for an IP-less routed network using"
454 68b952f9 Christos Stavrakakis
                    " Proxy ARP",
455 b7d38981 Dimitris Aragiorgis
        },
456 b7d38981 Dimitris Aragiorgis
        'MAC_FILTERED': {
457 68b952f9 Christos Stavrakakis
            'mode': 'bridged',
458 68b952f9 Christos Stavrakakis
            'link': settings.DEFAULT_MAC_FILTERED_BRIDGE,
459 68b952f9 Christos Stavrakakis
            'mac_prefix': 'pool',
460 68b952f9 Christos Stavrakakis
            'tags': 'private-filtered',
461 68b952f9 Christos Stavrakakis
            'desc': "Flavor used for bridged networks that offer isolation"
462 68b952f9 Christos Stavrakakis
                    " via filtering packets based on their src "
463 68b952f9 Christos Stavrakakis
                    " MAC (ebtables)",
464 b7d38981 Dimitris Aragiorgis
        },
465 b7d38981 Dimitris Aragiorgis
        'PHYSICAL_VLAN': {
466 68b952f9 Christos Stavrakakis
            'mode': 'bridged',
467 68b952f9 Christos Stavrakakis
            'link': 'pool',
468 68b952f9 Christos Stavrakakis
            'mac_prefix': settings.DEFAULT_MAC_PREFIX,
469 68b952f9 Christos Stavrakakis
            'tags': 'physical-vlan',
470 68b952f9 Christos Stavrakakis
            'desc': "Flavor used for bridged network that offer isolation"
471 68b952f9 Christos Stavrakakis
                    " via dedicated physical vlan",
472 b7d38981 Dimitris Aragiorgis
        },
473 b7d38981 Dimitris Aragiorgis
    }
474 ce55f211 Kostas Papadimitriou
475 cddc2b2f Dionysis Grigoropoulos
    NETWORK_NAME_LENGTH = 128
476 cddc2b2f Dionysis Grigoropoulos
477 cddc2b2f Dionysis Grigoropoulos
    name = models.CharField('Network Name', max_length=NETWORK_NAME_LENGTH)
478 190d155f Christos Stavrakakis
    userid = models.CharField('User ID of the owner', max_length=128,
479 190d155f Christos Stavrakakis
                              null=True, db_index=True)
480 b7d38981 Dimitris Aragiorgis
    flavor = models.CharField('Flavor', max_length=32, null=False)
481 b7d38981 Dimitris Aragiorgis
    mode = models.CharField('Network Mode', max_length=16, null=True)
482 b7d38981 Dimitris Aragiorgis
    link = models.CharField('Network Link', max_length=32, null=True)
483 3165f027 Christos Stavrakakis
    mac_prefix = models.CharField('MAC Prefix', max_length=32, null=False)
484 b7d38981 Dimitris Aragiorgis
    tags = models.CharField('Network Tags', max_length=128, null=True)
485 190d155f Christos Stavrakakis
    public = models.BooleanField(default=False, db_index=True)
486 0269afd6 Giorgos Verigakis
    created = models.DateTimeField(auto_now_add=True)
487 0269afd6 Giorgos Verigakis
    updated = models.DateTimeField(auto_now=True)
488 190d155f Christos Stavrakakis
    deleted = models.BooleanField('Deleted', default=False, db_index=True)
489 fd65ab41 Christos Stavrakakis
    state = models.CharField(choices=OPER_STATES, max_length=32,
490 fd65ab41 Christos Stavrakakis
                             default='PENDING')
491 f533f224 Vangelis Koukis
    machines = models.ManyToManyField(VirtualMachine,
492 244c552b Giorgos Verigakis
                                      through='NetworkInterface')
493 fd65ab41 Christos Stavrakakis
    action = models.CharField(choices=ACTIONS, max_length=32, null=True,
494 fd65ab41 Christos Stavrakakis
                              default=None)
495 6dafedf6 Christos Stavrakakis
    drained = models.BooleanField("Drained", default=False, null=False)
496 9115d567 Christos Stavrakakis
    floating_ip_pool = models.BooleanField('Floating IP Pool', null=False,
497 b2d0830b Christos Stavrakakis
                                           default=False)
498 7b72b50d Christos Stavrakakis
    external_router = models.BooleanField(default=False)
499 004967e4 Christos Stavrakakis
    serial = models.ForeignKey(QuotaHolderSerial, related_name='network',
500 3bb0b117 Christos Stavrakakis
                               null=True, on_delete=models.SET_NULL)
501 77f0fa63 Christos Stavrakakis
502 cfd70896 Christos Stavrakakis
    def __unicode__(self):
503 0989632e Christos Stavrakakis
        return "<Network: %s>" % str(self.id)
504 cfd70896 Christos Stavrakakis
505 e6a42a96 Christos Stavrakakis
    @property
506 e6a42a96 Christos Stavrakakis
    def backend_id(self):
507 fd65ab41 Christos Stavrakakis
        """Return the backend id by prepending backend-prefix."""
508 e6a42a96 Christos Stavrakakis
        if not self.id:
509 e6a42a96 Christos Stavrakakis
            raise Network.InvalidBackendIdError("self.id is None")
510 d30f29aa Christos Stavrakakis
        return "%snet-%s" % (settings.BACKEND_PREFIX_ID, str(self.id))
511 e6a42a96 Christos Stavrakakis
512 e6a42a96 Christos Stavrakakis
    @property
513 e6a42a96 Christos Stavrakakis
    def backend_tag(self):
514 e6a42a96 Christos Stavrakakis
        """Return the network tag to be used in backend
515 e6a42a96 Christos Stavrakakis

516 e6a42a96 Christos Stavrakakis
        """
517 b7d38981 Dimitris Aragiorgis
        if self.tags:
518 b7d38981 Dimitris Aragiorgis
            return self.tags.split(',')
519 b7d38981 Dimitris Aragiorgis
        else:
520 b7d38981 Dimitris Aragiorgis
            return []
521 e6a42a96 Christos Stavrakakis
522 7cfbbf32 Christos Stavrakakis
    def create_backend_network(self, backend=None):
523 7cfbbf32 Christos Stavrakakis
        """Create corresponding BackendNetwork entries."""
524 7cfbbf32 Christos Stavrakakis
525 b2d0830b Christos Stavrakakis
        backends = [backend] if backend else\
526 b2d0830b Christos Stavrakakis
            Backend.objects.filter(offline=False)
527 7cfbbf32 Christos Stavrakakis
        for backend in backends:
528 68b952f9 Christos Stavrakakis
            backend_exists =\
529 68b952f9 Christos Stavrakakis
                BackendNetwork.objects.filter(backend=backend, network=self)\
530 68b952f9 Christos Stavrakakis
                                      .exists()
531 68b952f9 Christos Stavrakakis
            if not backend_exists:
532 ce9abd26 Christos Stavrakakis
                BackendNetwork.objects.create(backend=backend, network=self)
533 fd65ab41 Christos Stavrakakis
534 92d2d1ce Christos Stavrakakis
    def get_pool(self, locked=True):
535 b4695420 Christos Stavrakakis
        try:
536 b4695420 Christos Stavrakakis
            subnet = self.subnets.get(ipversion=4, deleted=False)
537 b4695420 Christos Stavrakakis
        except Subnet.DoesNotExist:
538 b4695420 Christos Stavrakakis
            raise pools.EmptyPool
539 92d2d1ce Christos Stavrakakis
        return subnet.get_pool(locked=locked)
540 92d2d1ce Christos Stavrakakis
541 92d2d1ce Christos Stavrakakis
    def allocate_address(self, userid):
542 b4695420 Christos Stavrakakis
        try:
543 92d2d1ce Christos Stavrakakis
            subnet = self.subnets.get(ipversion=4, deleted=False)
544 92d2d1ce Christos Stavrakakis
        except Subnet.DoesNotExist:
545 92d2d1ce Christos Stavrakakis
            raise pools.EmptyPool
546 92d2d1ce Christos Stavrakakis
        return subnet.allocate_address(userid)
547 77f0fa63 Christos Stavrakakis
548 fdc94944 Christos Stavrakakis
    def reserve_address(self, address):
549 fdc94944 Christos Stavrakakis
        pool = self.get_pool()
550 77f0fa63 Christos Stavrakakis
        pool.reserve(address)
551 fdc94944 Christos Stavrakakis
        pool.save()
552 77f0fa63 Christos Stavrakakis
553 fdc94944 Christos Stavrakakis
    def release_address(self, address):
554 fdc94944 Christos Stavrakakis
        pool = self.get_pool()
555 fdc94944 Christos Stavrakakis
        pool.put(address)
556 fdc94944 Christos Stavrakakis
        pool.save()
557 77f0fa63 Christos Stavrakakis
558 048fab1e Christos Stavrakakis
    class InvalidBackendIdError(Exception):
559 048fab1e Christos Stavrakakis
        def __init__(self, value):
560 048fab1e Christos Stavrakakis
            self.value = value
561 048fab1e Christos Stavrakakis
562 048fab1e Christos Stavrakakis
        def __str__(self):
563 048fab1e Christos Stavrakakis
            return repr(self.value)
564 048fab1e Christos Stavrakakis
565 048fab1e Christos Stavrakakis
    class InvalidBackendMsgError(Exception):
566 048fab1e Christos Stavrakakis
        def __init__(self, opcode, status):
567 048fab1e Christos Stavrakakis
            self.opcode = opcode
568 048fab1e Christos Stavrakakis
            self.status = status
569 048fab1e Christos Stavrakakis
570 048fab1e Christos Stavrakakis
        def __str__(self):
571 68b952f9 Christos Stavrakakis
            return repr('<opcode: %s, status: %s>'
572 68b952f9 Christos Stavrakakis
                        % (self.opcode, self.status))
573 048fab1e Christos Stavrakakis
574 048fab1e Christos Stavrakakis
    class InvalidActionError(Exception):
575 048fab1e Christos Stavrakakis
        def __init__(self, action):
576 048fab1e Christos Stavrakakis
            self._action = action
577 048fab1e Christos Stavrakakis
578 048fab1e Christos Stavrakakis
        def __str__(self):
579 048fab1e Christos Stavrakakis
            return repr(str(self._action))
580 048fab1e Christos Stavrakakis
581 864bed43 Christos Stavrakakis
582 4e3a6674 Christos Stavrakakis
class Subnet(models.Model):
583 4e3a6674 Christos Stavrakakis
    SUBNET_NAME_LENGTH = 128
584 4e3a6674 Christos Stavrakakis
585 4e3a6674 Christos Stavrakakis
    network = models.ForeignKey('Network', null=False, db_index=True,
586 4e3a6674 Christos Stavrakakis
                                related_name="subnets")
587 4e3a6674 Christos Stavrakakis
    name = models.CharField('Subnet Name', max_length=SUBNET_NAME_LENGTH,
588 4e3a6674 Christos Stavrakakis
                            null=True)
589 4e3a6674 Christos Stavrakakis
    ipversion = models.IntegerField('IP Version', default=4, null=False)
590 74d936dc Christos Stavrakakis
    cidr = models.CharField('Subnet', max_length=64, null=False)
591 4e3a6674 Christos Stavrakakis
    gateway = models.CharField('Gateway', max_length=64, null=True)
592 74d936dc Christos Stavrakakis
    dhcp = models.BooleanField('DHCP', default=True, null=False)
593 74d936dc Christos Stavrakakis
    deleted = models.BooleanField('Deleted', default=False, db_index=True,
594 74d936dc Christos Stavrakakis
                                  null=False)
595 4e3a6674 Christos Stavrakakis
    host_routes = fields.SeparatedValuesField('Host Routes', null=True)
596 4e3a6674 Christos Stavrakakis
    dns_nameservers = fields.SeparatedValuesField('DNS Nameservers', null=True)
597 4e3a6674 Christos Stavrakakis
598 4e3a6674 Christos Stavrakakis
    def __unicode__(self):
599 4e3a6674 Christos Stavrakakis
        return "<Subnet %s, Network: %s>" % (self.id, self.network_id)
600 4e3a6674 Christos Stavrakakis
601 92d2d1ce Christos Stavrakakis
    def get_pool(self, locked=True):
602 92d2d1ce Christos Stavrakakis
        if self.ipversion == 6:
603 92d2d1ce Christos Stavrakakis
            raise Exception("IPv6 Subnets have no IP Pool.")
604 92d2d1ce Christos Stavrakakis
        ip_pools = self.ip_pools
605 92d2d1ce Christos Stavrakakis
        if locked:
606 92d2d1ce Christos Stavrakakis
            ip_pools = ip_pools.select_for_update()
607 92d2d1ce Christos Stavrakakis
        return ip_pools.all()[0].pool
608 92d2d1ce Christos Stavrakakis
609 92d2d1ce Christos Stavrakakis
    def allocate_address(self, userid):
610 92d2d1ce Christos Stavrakakis
        pool = self.get_pool(locked=True)
611 92d2d1ce Christos Stavrakakis
        address = pool.get()
612 92d2d1ce Christos Stavrakakis
        pool.save()
613 92d2d1ce Christos Stavrakakis
        return IPAddress.objects.create(network=self.network, subnet=self,
614 92d2d1ce Christos Stavrakakis
                                        address=address, userid=userid)
615 92d2d1ce Christos Stavrakakis
616 4e3a6674 Christos Stavrakakis
617 fd65ab41 Christos Stavrakakis
class BackendNetwork(models.Model):
618 fd65ab41 Christos Stavrakakis
    OPER_STATES = (
619 fd65ab41 Christos Stavrakakis
        ('PENDING', 'Pending'),
620 fd65ab41 Christos Stavrakakis
        ('ACTIVE', 'Active'),
621 fd65ab41 Christos Stavrakakis
        ('DELETED', 'Deleted'),
622 fd65ab41 Christos Stavrakakis
        ('ERROR', 'Error')
623 fd65ab41 Christos Stavrakakis
    )
624 fd65ab41 Christos Stavrakakis
625 fd65ab41 Christos Stavrakakis
    # The list of possible operations on the backend
626 fd65ab41 Christos Stavrakakis
    BACKEND_OPCODES = (
627 fd65ab41 Christos Stavrakakis
        ('OP_NETWORK_ADD', 'Create Network'),
628 fd65ab41 Christos Stavrakakis
        ('OP_NETWORK_CONNECT', 'Activate Network'),
629 fd65ab41 Christos Stavrakakis
        ('OP_NETWORK_DISCONNECT', 'Deactivate Network'),
630 fd65ab41 Christos Stavrakakis
        ('OP_NETWORK_REMOVE', 'Remove Network'),
631 fd65ab41 Christos Stavrakakis
        # These are listed here for completeness,
632 fd65ab41 Christos Stavrakakis
        # and are ignored for the time being
633 fd65ab41 Christos Stavrakakis
        ('OP_NETWORK_SET_PARAMS', 'Set Network Parameters'),
634 fd65ab41 Christos Stavrakakis
        ('OP_NETWORK_QUERY_DATA', 'Query Network Data')
635 fd65ab41 Christos Stavrakakis
    )
636 fd65ab41 Christos Stavrakakis
637 fd65ab41 Christos Stavrakakis
    # The operating state of a Netowork,
638 fd65ab41 Christos Stavrakakis
    # upon the successful completion of a backend operation.
639 fd65ab41 Christos Stavrakakis
    # IMPORTANT: Make sure all keys have a corresponding
640 fd65ab41 Christos Stavrakakis
    # entry in BACKEND_OPCODES if you update this field, see #1035, #1111.
641 fd65ab41 Christos Stavrakakis
    OPER_STATE_FROM_OPCODE = {
642 fd65ab41 Christos Stavrakakis
        'OP_NETWORK_ADD': 'PENDING',
643 fd65ab41 Christos Stavrakakis
        'OP_NETWORK_CONNECT': 'ACTIVE',
644 fd65ab41 Christos Stavrakakis
        'OP_NETWORK_DISCONNECT': 'PENDING',
645 fd65ab41 Christos Stavrakakis
        'OP_NETWORK_REMOVE': 'DELETED',
646 fd65ab41 Christos Stavrakakis
        'OP_NETWORK_SET_PARAMS': None,
647 fd65ab41 Christos Stavrakakis
        'OP_NETWORK_QUERY_DATA': None
648 fd65ab41 Christos Stavrakakis
    }
649 fd65ab41 Christos Stavrakakis
650 3bb0b117 Christos Stavrakakis
    network = models.ForeignKey(Network, related_name='backend_networks',
651 3bb0b117 Christos Stavrakakis
                                on_delete=models.CASCADE)
652 26515bc1 Christos Stavrakakis
    backend = models.ForeignKey(Backend, related_name='networks',
653 26515bc1 Christos Stavrakakis
                                on_delete=models.PROTECT)
654 fd65ab41 Christos Stavrakakis
    created = models.DateTimeField(auto_now_add=True)
655 fd65ab41 Christos Stavrakakis
    updated = models.DateTimeField(auto_now=True)
656 fd65ab41 Christos Stavrakakis
    deleted = models.BooleanField('Deleted', default=False)
657 3165f027 Christos Stavrakakis
    mac_prefix = models.CharField('MAC Prefix', max_length=32, null=False)
658 fd65ab41 Christos Stavrakakis
    operstate = models.CharField(choices=OPER_STATES, max_length=30,
659 fd65ab41 Christos Stavrakakis
                                 default='PENDING')
660 fd65ab41 Christos Stavrakakis
    backendjobid = models.PositiveIntegerField(null=True)
661 fd65ab41 Christos Stavrakakis
    backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30,
662 fd65ab41 Christos Stavrakakis
                                     null=True)
663 fd65ab41 Christos Stavrakakis
    backendjobstatus = models.CharField(choices=BACKEND_STATUSES,
664 fd65ab41 Christos Stavrakakis
                                        max_length=30, null=True)
665 fd65ab41 Christos Stavrakakis
    backendlogmsg = models.TextField(null=True)
666 fd65ab41 Christos Stavrakakis
    backendtime = models.DateTimeField(null=False,
667 fd65ab41 Christos Stavrakakis
                                       default=datetime.datetime.min)
668 fd65ab41 Christos Stavrakakis
669 7cfbbf32 Christos Stavrakakis
    class Meta:
670 7cfbbf32 Christos Stavrakakis
        # Ensure one entry for each network in each backend
671 7cfbbf32 Christos Stavrakakis
        unique_together = (("network", "backend"))
672 7cfbbf32 Christos Stavrakakis
673 3165f027 Christos Stavrakakis
    def __init__(self, *args, **kwargs):
674 3165f027 Christos Stavrakakis
        """Initialize state for just created BackendNetwork instances."""
675 3165f027 Christos Stavrakakis
        super(BackendNetwork, self).__init__(*args, **kwargs)
676 3165f027 Christos Stavrakakis
        if not self.mac_prefix:
677 3165f027 Christos Stavrakakis
            # Generate the MAC prefix of the BackendNetwork, by combining
678 3165f027 Christos Stavrakakis
            # the Network prefix with the index of the Backend
679 3165f027 Christos Stavrakakis
            net_prefix = self.network.mac_prefix
680 3165f027 Christos Stavrakakis
            backend_suffix = hex(self.backend.index).replace('0x', '')
681 3165f027 Christos Stavrakakis
            mac_prefix = net_prefix + backend_suffix
682 3165f027 Christos Stavrakakis
            try:
683 3165f027 Christos Stavrakakis
                utils.validate_mac(mac_prefix + ":00:00:00")
684 3165f027 Christos Stavrakakis
            except utils.InvalidMacAddress:
685 68b952f9 Christos Stavrakakis
                raise utils.InvalidMacAddress("Invalid MAC prefix '%s'" %
686 68b952f9 Christos Stavrakakis
                                              mac_prefix)
687 3165f027 Christos Stavrakakis
            self.mac_prefix = mac_prefix
688 3165f027 Christos Stavrakakis
689 99af08a4 Christos Stavrakakis
    def __unicode__(self):
690 99af08a4 Christos Stavrakakis
        return '<%s@%s>' % (self.network, self.backend)
691 99af08a4 Christos Stavrakakis
692 f533f224 Vangelis Koukis
693 bdd0f1a6 Christos Stavrakakis
class IPAddress(models.Model):
694 bdd0f1a6 Christos Stavrakakis
    subnet = models.ForeignKey("Subnet", related_name="ips", null=False,
695 bdd0f1a6 Christos Stavrakakis
                               on_delete=models.CASCADE)
696 bdd0f1a6 Christos Stavrakakis
    network = models.ForeignKey(Network, related_name="ips", null=False,
697 bdd0f1a6 Christos Stavrakakis
                                on_delete=models.CASCADE)
698 bdd0f1a6 Christos Stavrakakis
    nic = models.ForeignKey("NetworkInterface", related_name="ips", null=True,
699 bdd0f1a6 Christos Stavrakakis
                            on_delete=models.SET_NULL)
700 bdd0f1a6 Christos Stavrakakis
    userid = models.CharField("UUID of the owner", max_length=128, null=False,
701 bdd0f1a6 Christos Stavrakakis
                              db_index=True)
702 bdd0f1a6 Christos Stavrakakis
    address = models.CharField("IP Address", max_length=64, null=False)
703 bdd0f1a6 Christos Stavrakakis
    floating_ip = models.BooleanField("Floating IP", null=False, default=False)
704 bdd0f1a6 Christos Stavrakakis
    created = models.DateTimeField(auto_now_add=True)
705 bdd0f1a6 Christos Stavrakakis
    updated = models.DateTimeField(auto_now=True)
706 bdd0f1a6 Christos Stavrakakis
    deleted = models.BooleanField(default=False, null=False)
707 bdd0f1a6 Christos Stavrakakis
708 bdd0f1a6 Christos Stavrakakis
    serial = models.ForeignKey(QuotaHolderSerial,
709 bdd0f1a6 Christos Stavrakakis
                               related_name="ips", null=True,
710 bdd0f1a6 Christos Stavrakakis
                               on_delete=models.SET_NULL)
711 bdd0f1a6 Christos Stavrakakis
712 bdd0f1a6 Christos Stavrakakis
    def __unicode__(self):
713 bdd0f1a6 Christos Stavrakakis
        ip_type = "floating" if self.floating_ip else "static"
714 bdd0f1a6 Christos Stavrakakis
        return u"<IPAddress: %s, Network: %s, Subnet: %s, Type: %s>"\
715 bdd0f1a6 Christos Stavrakakis
               % (self.address, self.network_id, self.subnet_id, ip_type)
716 bdd0f1a6 Christos Stavrakakis
717 bdd0f1a6 Christos Stavrakakis
    def in_use(self):
718 bdd0f1a6 Christos Stavrakakis
        if self.machine is None:
719 bdd0f1a6 Christos Stavrakakis
            return False
720 bdd0f1a6 Christos Stavrakakis
        else:
721 bdd0f1a6 Christos Stavrakakis
            return (not self.machine.deleted)
722 bdd0f1a6 Christos Stavrakakis
723 bdd0f1a6 Christos Stavrakakis
    class Meta:
724 bdd0f1a6 Christos Stavrakakis
        unique_together = ("network", "address")
725 bdd0f1a6 Christos Stavrakakis
726 bdd0f1a6 Christos Stavrakakis
727 f533f224 Vangelis Koukis
class NetworkInterface(models.Model):
728 f533f224 Vangelis Koukis
    FIREWALL_PROFILES = (
729 f533f224 Vangelis Koukis
        ('ENABLED', 'Enabled'),
730 26563957 Giorgos Verigakis
        ('DISABLED', 'Disabled'),
731 26563957 Giorgos Verigakis
        ('PROTECTED', 'Protected')
732 f533f224 Vangelis Koukis
    )
733 ce55f211 Kostas Papadimitriou
734 939d71dd Christos Stavrakakis
    STATES = (
735 939d71dd Christos Stavrakakis
        ("ACTIVE", "Active"),
736 939d71dd Christos Stavrakakis
        ("BUILDING", "Building"),
737 9dcfad23 Christos Stavrakakis
        ("ERROR", "Error"),
738 939d71dd Christos Stavrakakis
    )
739 939d71dd Christos Stavrakakis
740 cddc2b2f Dionysis Grigoropoulos
    NETWORK_IFACE_NAME_LENGTH = 128
741 cddc2b2f Dionysis Grigoropoulos
742 bdd0f1a6 Christos Stavrakakis
    name = models.CharField('NIC name', max_length=128, null=True)
743 cddc2b2f Dionysis Grigoropoulos
    userid = models.CharField("UUID of the owner",
744 cddc2b2f Dionysis Grigoropoulos
                              max_length=NETWORK_IFACE_NAME_LENGTH,
745 bdd0f1a6 Christos Stavrakakis
                              null=True, db_index=True)
746 3bb0b117 Christos Stavrakakis
    machine = models.ForeignKey(VirtualMachine, related_name='nics',
747 3bb0b117 Christos Stavrakakis
                                on_delete=models.CASCADE)
748 3bb0b117 Christos Stavrakakis
    network = models.ForeignKey(Network, related_name='nics',
749 3bb0b117 Christos Stavrakakis
                                on_delete=models.CASCADE)
750 f533f224 Vangelis Koukis
    created = models.DateTimeField(auto_now_add=True)
751 f533f224 Vangelis Koukis
    updated = models.DateTimeField(auto_now=True)
752 051e47f8 Christos Stavrakakis
    index = models.IntegerField(null=True)
753 939d71dd Christos Stavrakakis
    mac = models.CharField(max_length=32, null=True, unique=True)
754 f533f224 Vangelis Koukis
    firewall_profile = models.CharField(choices=FIREWALL_PROFILES,
755 244c552b Giorgos Verigakis
                                        max_length=30, null=True)
756 bdd0f1a6 Christos Stavrakakis
    security_groups = models.ManyToManyField("SecurityGroup", null=True)
757 ef54eee4 Christos Stavrakakis
    state = models.CharField(max_length=32, null=False, default="ACTIVE",
758 939d71dd Christos Stavrakakis
                             choices=STATES)
759 bdd0f1a6 Christos Stavrakakis
    device_owner = models.CharField('Device owner', max_length=128, null=True)
760 bdd0f1a6 Christos Stavrakakis
761 bdd0f1a6 Christos Stavrakakis
    def __unicode__(self):
762 bdd0f1a6 Christos Stavrakakis
        return "<%s:vm:%s network:%s>" % (self.id, self.machine_id,
763 bdd0f1a6 Christos Stavrakakis
                                          self.network_id)
764 ce55f211 Kostas Papadimitriou
765 7c714455 Christos Stavrakakis
    @property
766 7c714455 Christos Stavrakakis
    def backend_uuid(self):
767 7c714455 Christos Stavrakakis
        """Return the backend id by prepending backend-prefix."""
768 7c714455 Christos Stavrakakis
        return "%snic-%s" % (settings.BACKEND_PREFIX_ID, str(self.id))
769 7c714455 Christos Stavrakakis
770 92d2d1ce Christos Stavrakakis
    @property
771 92d2d1ce Christos Stavrakakis
    def ipv4_address(self):
772 92d2d1ce Christos Stavrakakis
        try:
773 92d2d1ce Christos Stavrakakis
            return self.ips.get(subnet__ipversion=4).address
774 92d2d1ce Christos Stavrakakis
        except IPAddress.DoesNotExist:
775 92d2d1ce Christos Stavrakakis
            return None
776 92d2d1ce Christos Stavrakakis
777 df8a7015 Christos Stavrakakis
778 bdd0f1a6 Christos Stavrakakis
class SecurityGroup(models.Model):
779 cddc2b2f Dionysis Grigoropoulos
    SECURITY_GROUP_NAME_LENGTH = 128
780 cddc2b2f Dionysis Grigoropoulos
    name = models.CharField('group name',
781 cddc2b2f Dionysis Grigoropoulos
                            max_length=SECURITY_GROUP_NAME_LENGTH)
782 cb7b1c23 Christos Stavrakakis
783 f533f224 Vangelis Koukis
784 03992c72 Christos Stavrakakis
class PoolTable(models.Model):
785 03992c72 Christos Stavrakakis
    available_map = models.TextField(default="", null=False)
786 03992c72 Christos Stavrakakis
    reserved_map = models.TextField(default="", null=False)
787 03992c72 Christos Stavrakakis
    size = models.IntegerField(null=False)
788 e1334eb6 Christos Stavrakakis
789 03992c72 Christos Stavrakakis
    # Optional Fields
790 03992c72 Christos Stavrakakis
    base = models.CharField(null=True, max_length=32)
791 03992c72 Christos Stavrakakis
    offset = models.IntegerField(null=True)
792 ce55f211 Kostas Papadimitriou
793 e6a42a96 Christos Stavrakakis
    class Meta:
794 e6a42a96 Christos Stavrakakis
        abstract = True
795 e6a42a96 Christos Stavrakakis
796 e6a42a96 Christos Stavrakakis
    @classmethod
797 03992c72 Christos Stavrakakis
    def get_pool(cls):
798 e6a42a96 Christos Stavrakakis
        try:
799 21c9042c Christos Stavrakakis
            pool_row = cls.objects.select_for_update().get()
800 03992c72 Christos Stavrakakis
            return pool_row.pool
801 3bdfc1be Christos Stavrakakis
        except cls.DoesNotExist:
802 03992c72 Christos Stavrakakis
            raise pools.EmptyPool
803 e6a42a96 Christos Stavrakakis
804 03992c72 Christos Stavrakakis
    @property
805 03992c72 Christos Stavrakakis
    def pool(self):
806 03992c72 Christos Stavrakakis
        return self.manager(self)
807 e6a42a96 Christos Stavrakakis
808 e6a42a96 Christos Stavrakakis
809 03992c72 Christos Stavrakakis
class BridgePoolTable(PoolTable):
810 03992c72 Christos Stavrakakis
    manager = pools.BridgePool
811 e6a42a96 Christos Stavrakakis
812 0ccb6461 Christos Stavrakakis
    def __unicode__(self):
813 d793786b Christos Stavrakakis
        return u"<BridgePool id:%s>" % self.id
814 0ccb6461 Christos Stavrakakis
815 fdc94944 Christos Stavrakakis
816 03992c72 Christos Stavrakakis
class MacPrefixPoolTable(PoolTable):
817 03992c72 Christos Stavrakakis
    manager = pools.MacPrefixPool
818 fdc94944 Christos Stavrakakis
819 0ccb6461 Christos Stavrakakis
    def __unicode__(self):
820 d793786b Christos Stavrakakis
        return u"<MACPrefixPool id:%s>" % self.id
821 0ccb6461 Christos Stavrakakis
822 fdc94944 Christos Stavrakakis
823 fdc94944 Christos Stavrakakis
class IPPoolTable(PoolTable):
824 fdc94944 Christos Stavrakakis
    manager = pools.IPPool
825 3524241a Christos Stavrakakis
826 4e3a6674 Christos Stavrakakis
    subnet = models.ForeignKey('Subnet', related_name="ip_pools",
827 4e3a6674 Christos Stavrakakis
                               db_index=True, null=True)
828 d793786b Christos Stavrakakis
829 4e3a6674 Christos Stavrakakis
    def __unicode__(self):
830 4e3a6674 Christos Stavrakakis
        return u"<IPv4AdressPool, Subnet: %s>" % self.subnet_id
831 3524241a Christos Stavrakakis
832 7b72b50d Christos Stavrakakis
833 3524241a Christos Stavrakakis
@contextmanager
834 3524241a Christos Stavrakakis
def pooled_rapi_client(obj):
835 198d91c3 Christos Stavrakakis
        if isinstance(obj, (VirtualMachine, BackendNetwork)):
836 3524241a Christos Stavrakakis
            backend = obj.backend
837 3524241a Christos Stavrakakis
        else:
838 3524241a Christos Stavrakakis
            backend = obj
839 3524241a Christos Stavrakakis
840 3524241a Christos Stavrakakis
        if backend.offline:
841 99af08a4 Christos Stavrakakis
            log.warning("Trying to connect with offline backend: %s", backend)
842 0129231a Christos Stavrakakis
            raise faults.ServiceUnavailable("Can not connect to offline"
843 0129231a Christos Stavrakakis
                                            " backend: %s" % backend)
844 3524241a Christos Stavrakakis
845 3524241a Christos Stavrakakis
        b = backend
846 3524241a Christos Stavrakakis
        client = get_rapi_client(b.id, b.hash, b.clustername, b.port,
847 3524241a Christos Stavrakakis
                                 b.username, b.password)
848 4216cd83 Christos Stavrakakis
        try:
849 4216cd83 Christos Stavrakakis
            yield client
850 4216cd83 Christos Stavrakakis
        finally:
851 4216cd83 Christos Stavrakakis
            put_rapi_client(client)
852 6ce1fc56 Kostas Papadimitriou
853 6ce1fc56 Kostas Papadimitriou
854 6ce1fc56 Kostas Papadimitriou
class VirtualMachineDiagnosticManager(models.Manager):
855 6ce1fc56 Kostas Papadimitriou
    """
856 6ce1fc56 Kostas Papadimitriou
    Custom manager for :class:`VirtualMachineDiagnostic` model.
857 6ce1fc56 Kostas Papadimitriou
    """
858 6ce1fc56 Kostas Papadimitriou
859 6ce1fc56 Kostas Papadimitriou
    # diagnostic creation helpers
860 6ce1fc56 Kostas Papadimitriou
    def create_for_vm(self, vm, level, message, **kwargs):
861 6ce1fc56 Kostas Papadimitriou
        attrs = {'machine': vm, 'level': level, 'message': message}
862 6ce1fc56 Kostas Papadimitriou
        attrs.update(kwargs)
863 6ce1fc56 Kostas Papadimitriou
        # update instance updated time
864 6ce1fc56 Kostas Papadimitriou
        self.create(**attrs)
865 6ce1fc56 Kostas Papadimitriou
        vm.save()
866 6ce1fc56 Kostas Papadimitriou
867 6ce1fc56 Kostas Papadimitriou
    def create_error(self, vm, **kwargs):
868 6ce1fc56 Kostas Papadimitriou
        self.create_for_vm(vm, 'ERROR', **kwargs)
869 6ce1fc56 Kostas Papadimitriou
870 6ce1fc56 Kostas Papadimitriou
    def create_debug(self, vm, **kwargs):
871 6ce1fc56 Kostas Papadimitriou
        self.create_for_vm(vm, 'DEBUG', **kwargs)
872 6ce1fc56 Kostas Papadimitriou
873 6ce1fc56 Kostas Papadimitriou
    def since(self, vm, created_since, **kwargs):
874 6ce1fc56 Kostas Papadimitriou
        return self.get_query_set().filter(vm=vm, created__gt=created_since,
875 68b952f9 Christos Stavrakakis
                                           **kwargs)
876 6ce1fc56 Kostas Papadimitriou
877 6ce1fc56 Kostas Papadimitriou
878 6ce1fc56 Kostas Papadimitriou
class VirtualMachineDiagnostic(models.Model):
879 6ce1fc56 Kostas Papadimitriou
    """
880 6ce1fc56 Kostas Papadimitriou
    Model to store backend information messages that relate to the state of
881 6ce1fc56 Kostas Papadimitriou
    the virtual machine.
882 6ce1fc56 Kostas Papadimitriou
    """
883 6ce1fc56 Kostas Papadimitriou
884 6ce1fc56 Kostas Papadimitriou
    TYPES = (
885 6ce1fc56 Kostas Papadimitriou
        ('ERROR', 'Error'),
886 6ce1fc56 Kostas Papadimitriou
        ('WARNING', 'Warning'),
887 6ce1fc56 Kostas Papadimitriou
        ('INFO', 'Info'),
888 6ce1fc56 Kostas Papadimitriou
        ('DEBUG', 'Debug'),
889 6ce1fc56 Kostas Papadimitriou
    )
890 6ce1fc56 Kostas Papadimitriou
891 6ce1fc56 Kostas Papadimitriou
    objects = VirtualMachineDiagnosticManager()
892 6ce1fc56 Kostas Papadimitriou
893 6ce1fc56 Kostas Papadimitriou
    created = models.DateTimeField(auto_now_add=True)
894 3bb0b117 Christos Stavrakakis
    machine = models.ForeignKey('VirtualMachine', related_name="diagnostics",
895 3bb0b117 Christos Stavrakakis
                                on_delete=models.CASCADE)
896 6ce1fc56 Kostas Papadimitriou
    level = models.CharField(max_length=20, choices=TYPES)
897 6ce1fc56 Kostas Papadimitriou
    source = models.CharField(max_length=100)
898 6ce1fc56 Kostas Papadimitriou
    source_date = models.DateTimeField(null=True)
899 6ce1fc56 Kostas Papadimitriou
    message = models.CharField(max_length=255)
900 6ce1fc56 Kostas Papadimitriou
    details = models.TextField(null=True)
901 6ce1fc56 Kostas Papadimitriou
902 6ce1fc56 Kostas Papadimitriou
    class Meta:
903 6ce1fc56 Kostas Papadimitriou
        ordering = ['-created']