Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (25.1 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
from django.db import IntegrityError
35

    
36
from hashlib import sha1
37
from synnefo.api.faults import ServiceUnavailable
38
from synnefo.util.rapi import GanetiRapiClient
39
from synnefo.logic.ippool import IPPool
40
from synnefo import settings as snf_settings
41
from aes_encrypt import encrypt_db_charfield, decrypt_db_charfield
42

    
43
BACKEND_CLIENTS = {}  # {hash:Backend client}
44
BACKEND_HASHES = {}   # {Backend.id:hash}
45

    
46

    
47
def get_client(hash, backend):
48
    """Get a cached backend client or create a new one.
49

50
    @param hash: The hash of the backend
51
    @param backend: Either a backend object or backend ID
52
    """
53

    
54
    if backend is None:
55
        raise Exception("Backend is None. Cannot create a client.")
56

    
57
    if hash in BACKEND_CLIENTS:
58
        # Return cached client
59
        return BACKEND_CLIENTS[hash]
60

    
61
    # Always get a new instance to ensure latest credentials
62
    if isinstance(backend, Backend):
63
        backend = backend.id
64

    
65
    backend = Backend.objects.get(id=backend)
66
    hash = backend.hash
67
    clustername = backend.clustername
68
    port = backend.port
69
    user = backend.username
70
    password = backend.password
71

    
72
    # Check client for updated hash
73
    if hash in BACKEND_CLIENTS:
74
        return BACKEND_CLIENTS[hash]
75

    
76
    # Delete old version of the client
77
    if backend in BACKEND_HASHES:
78
        del BACKEND_CLIENTS[BACKEND_HASHES[backend]]
79

    
80
    # Create the new client
81
    client = GanetiRapiClient(clustername, port, user, password)
82

    
83
    # Store the client and the hash
84
    BACKEND_CLIENTS[hash] = client
85
    BACKEND_HASHES[backend] = hash
86

    
87
    return client
88

    
89

    
90
def clear_client_cache():
91
    BACKEND_CLIENTS.clear()
92
    BACKEND_HASHES.clear()
93

    
94

    
95
class Flavor(models.Model):
96
    cpu = models.IntegerField('Number of CPUs', default=0)
97
    ram = models.IntegerField('RAM size in MiB', default=0)
98
    disk = models.IntegerField('Disk size in GiB', default=0)
99
    disk_template = models.CharField('Disk template', max_length=32,
100
            default=settings.DEFAULT_GANETI_DISK_TEMPLATE)
101
    deleted = models.BooleanField('Deleted', default=False)
102

    
103
    class Meta:
104
        verbose_name = u'Virtual machine flavor'
105
        unique_together = ('cpu', 'ram', 'disk', 'disk_template')
106

    
107
    @property
108
    def name(self):
109
        """Returns flavor name (generated)"""
110
        return u'C%dR%dD%d' % (self.cpu, self.ram, self.disk)
111

    
112
    def __unicode__(self):
113
        return self.name
114

    
115

    
116
class BackendQuerySet(models.query.QuerySet):
117
    def delete(self):
118
        for backend in self._clone():
119
            backend.delete()
120

    
121

    
122
class ProtectDeleteManager(models.Manager):
123
    def get_query_set(self):
124
        return BackendQuerySet(self.model, using=self._db)
125

    
126

    
127
class Backend(models.Model):
128
    clustername = models.CharField('Cluster Name', max_length=128, unique=True)
129
    port = models.PositiveIntegerField('Port', default=5080)
130
    username = models.CharField('Username', max_length=64, blank=True,
131
                                null=True)
132
    password_hash = models.CharField('Password', max_length=64, blank=True,
133
                                null=True)
134
    # Sha1 is up to 40 characters long
135
    hash = models.CharField('Hash', max_length=40, editable=False, null=False)
136
    drained = models.BooleanField('Drained', default=False, null=False)
137
    offline = models.BooleanField('Offline', default=False, null=False)
138
    # Last refresh of backend resources
139
    updated = models.DateTimeField(auto_now_add=True)
140
    # Backend resources
141
    mfree = models.PositiveIntegerField('Free Memory', default=0, null=False)
142
    mtotal = models.PositiveIntegerField('Total Memory', default=0, null=False)
143
    dfree = models.PositiveIntegerField('Free Disk', default=0, null=False)
144
    dtotal = models.PositiveIntegerField('Total Disk', default=0, null=False)
145
    pinst_cnt = models.PositiveIntegerField('Primary Instances', default=0,
146
                                            null=False)
147
    ctotal = models.PositiveIntegerField('Total number of logical processors',
148
                                         default=0, null=False)
149
    # Custom object manager to protect from cascade delete
150
    objects = ProtectDeleteManager()
151

    
152
    class Meta:
153
        verbose_name = u'Backend'
154
        ordering = ["clustername"]
155

    
156
    def __unicode__(self):
157
        return self.clustername
158

    
159
    @property
160
    def backend_id(self):
161
        return self.id
162

    
163
    @property
164
    def client(self):
165
        """Get or create a client. """
166
        if not self.offline:
167
            return get_client(self.hash, self)
168
        else:
169
            raise ServiceUnavailable
170

    
171
    def create_hash(self):
172
        """Create a hash for this backend. """
173
        return sha1('%s%s%s%s' % \
174
                (self.clustername, self.port, self.username, self.password)) \
175
                .hexdigest()
176

    
177
    @property
178
    def password(self):
179
        return decrypt_db_charfield(self.password_hash)
180

    
181
    @password.setter
182
    def password(self, value):
183
        self.password_hash = encrypt_db_charfield(value)
184

    
185
    def save(self, *args, **kwargs):
186
        # Create a new hash each time a Backend is saved
187
        old_hash = self.hash
188
        self.hash = self.create_hash()
189
        super(Backend, self).save(*args, **kwargs)
190
        if self.hash != old_hash:
191
            # Populate the new hash to the new instances
192
            self.virtual_machines.filter(deleted=False).update(backend_hash=self.hash)
193

    
194
    def delete(self, *args, **kwargs):
195
        # Integrity Error if non-deleted VMs are associated with Backend
196
        if self.virtual_machines.filter(deleted=False).count():
197
            raise IntegrityError("Non-deleted virtual machines are associated "
198
                                 "with backend: %s" % self)
199
        else:
200
            # ON_DELETE = SET NULL
201
            self.virtual_machines.all().backend = None
202
            super(Backend, self).delete(*args, **kwargs)
203

    
204

    
205
# A backend job may be in one of the following possible states
206
BACKEND_STATUSES = (
207
    ('queued', 'request queued'),
208
    ('waiting', 'request waiting for locks'),
209
    ('canceling', 'request being canceled'),
210
    ('running', 'request running'),
211
    ('canceled', 'request canceled'),
212
    ('success', 'request completed successfully'),
213
    ('error', 'request returned error')
214
)
215

    
216

    
217
class VirtualMachine(models.Model):
218
    # The list of possible actions for a VM
219
    ACTIONS = (
220
       ('CREATE', 'Create VM'),
221
       ('START', 'Start VM'),
222
       ('STOP', 'Shutdown VM'),
223
       ('SUSPEND', 'Admin Suspend VM'),
224
       ('REBOOT', 'Reboot VM'),
225
       ('DESTROY', 'Destroy VM')
226
    )
227

    
228
    # The internal operating state of a VM
229
    OPER_STATES = (
230
        ('BUILD', 'Queued for creation'),
231
        ('ERROR', 'Creation failed'),
232
        ('STOPPED', 'Stopped'),
233
        ('STARTED', 'Started'),
234
        ('DESTROYED', 'Destroyed')
235
    )
236

    
237
    # The list of possible operations on the backend
238
    BACKEND_OPCODES = (
239
        ('OP_INSTANCE_CREATE', 'Create Instance'),
240
        ('OP_INSTANCE_REMOVE', 'Remove Instance'),
241
        ('OP_INSTANCE_STARTUP', 'Startup Instance'),
242
        ('OP_INSTANCE_SHUTDOWN', 'Shutdown Instance'),
243
        ('OP_INSTANCE_REBOOT', 'Reboot Instance'),
244

    
245
        # These are listed here for completeness,
246
        # and are ignored for the time being
247
        ('OP_INSTANCE_SET_PARAMS', 'Set Instance Parameters'),
248
        ('OP_INSTANCE_QUERY_DATA', 'Query Instance Data'),
249
        ('OP_INSTANCE_REINSTALL', 'Reinstall Instance'),
250
        ('OP_INSTANCE_ACTIVATE_DISKS', 'Activate Disks'),
251
        ('OP_INSTANCE_DEACTIVATE_DISKS', 'Deactivate Disks'),
252
        ('OP_INSTANCE_REPLACE_DISKS', 'Replace Disks'),
253
        ('OP_INSTANCE_MIGRATE', 'Migrate Instance'),
254
        ('OP_INSTANCE_CONSOLE', 'Get Instance Console'),
255
        ('OP_INSTANCE_RECREATE_DISKS', 'Recreate Disks'),
256
        ('OP_INSTANCE_FAILOVER', 'Failover Instance')
257
    )
258

    
259

    
260
    # The operating state of a VM,
261
    # upon the successful completion of a backend operation.
262
    # IMPORTANT: Make sure all keys have a corresponding
263
    # entry in BACKEND_OPCODES if you update this field, see #1035, #1111.
264
    OPER_STATE_FROM_OPCODE = {
265
        'OP_INSTANCE_CREATE': 'STARTED',
266
        'OP_INSTANCE_REMOVE': 'DESTROYED',
267
        'OP_INSTANCE_STARTUP': 'STARTED',
268
        'OP_INSTANCE_SHUTDOWN': 'STOPPED',
269
        'OP_INSTANCE_REBOOT': 'STARTED',
270
        'OP_INSTANCE_SET_PARAMS': None,
271
        'OP_INSTANCE_QUERY_DATA': None,
272
        'OP_INSTANCE_REINSTALL': None,
273
        'OP_INSTANCE_ACTIVATE_DISKS': None,
274
        'OP_INSTANCE_DEACTIVATE_DISKS': None,
275
        'OP_INSTANCE_REPLACE_DISKS': None,
276
        'OP_INSTANCE_MIGRATE': None,
277
        'OP_INSTANCE_CONSOLE': None,
278
        'OP_INSTANCE_RECREATE_DISKS': None,
279
        'OP_INSTANCE_FAILOVER': None
280
    }
281

    
282
    # This dictionary contains the correspondence between
283
    # internal operating states and Server States as defined
284
    # by the Rackspace API.
285
    RSAPI_STATE_FROM_OPER_STATE = {
286
        "BUILD": "BUILD",
287
        "ERROR": "ERROR",
288
        "STOPPED": "STOPPED",
289
        "STARTED": "ACTIVE",
290
        "DESTROYED": "DELETED"
291
    }
292

    
293
    name = models.CharField('Virtual Machine Name', max_length=255)
294
    userid = models.CharField('User ID of the owner', max_length=100)
295
    backend = models.ForeignKey(Backend, null=True,
296
                                related_name="virtual_machines",)
297
    backend_hash = models.CharField(max_length=128, null=True, editable=False)
298
    created = models.DateTimeField(auto_now_add=True)
299
    updated = models.DateTimeField(auto_now=True)
300
    imageid = models.CharField(max_length=100, null=False)
301
    hostid = models.CharField(max_length=100)
302
    flavor = models.ForeignKey(Flavor)
303
    deleted = models.BooleanField('Deleted', default=False)
304
    suspended = models.BooleanField('Administratively Suspended',
305
                                    default=False)
306

    
307
    # VM State
308
    # The following fields are volatile data, in the sense
309
    # that they need not be persistent in the DB, but rather
310
    # get generated at runtime by quering Ganeti and applying
311
    # updates received from Ganeti.
312

    
313
    # In the future they could be moved to a separate caching layer
314
    # and removed from the database.
315
    # [vkoukis] after discussion with [faidon].
316
    action = models.CharField(choices=ACTIONS, max_length=30, null=True)
317
    operstate = models.CharField(choices=OPER_STATES, max_length=30, null=True)
318
    backendjobid = models.PositiveIntegerField(null=True)
319
    backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30,
320
                                     null=True)
321
    backendjobstatus = models.CharField(choices=BACKEND_STATUSES,
322
                                        max_length=30, null=True)
323
    backendlogmsg = models.TextField(null=True)
324
    buildpercentage = models.IntegerField(default=0)
325
    backendtime = models.DateTimeField(default=datetime.datetime.min)
326

    
327
    @property
328
    def client(self):
329
        if self.backend and not self.backend.offline:
330
            return get_client(self.backend_hash, self.backend_id)
331
        else:
332
            raise ServiceUnavailable
333

    
334
    # Error classes
335
    class InvalidBackendIdError(Exception):
336
        def __init__(self, value):
337
            self.value = value
338

    
339
        def __str__(self):
340
            return repr(self.value)
341

    
342
    class InvalidBackendMsgError(Exception):
343
        def __init__(self, opcode, status):
344
            self.opcode = opcode
345
            self.status = status
346

    
347
        def __str__(self):
348
            return repr('<opcode: %s, status: %s>' % (self.opcode,
349
                        self.status))
350

    
351
    class InvalidActionError(Exception):
352
        def __init__(self, action):
353
            self._action = action
354

    
355
        def __str__(self):
356
            return repr(str(self._action))
357

    
358
    class DeletedError(Exception):
359
        pass
360

    
361
    class BuildingError(Exception):
362
        pass
363

    
364
    def __init__(self, *args, **kw):
365
        """Initialize state for just created VM instances."""
366
        super(VirtualMachine, self).__init__(*args, **kw)
367
        # This gets called BEFORE an instance gets save()d for
368
        # the first time.
369
        if not self.pk:
370
            self.action = None
371
            self.backendjobid = None
372
            self.backendjobstatus = None
373
            self.backendopcode = None
374
            self.backendlogmsg = None
375
            self.operstate = 'BUILD'
376

    
377
    def save(self, *args, **kwargs):
378
        # Store hash for first time saved vm
379
        if (self.id is None or self.backend_hash == '') and self.backend:
380
            self.backend_hash = self.backend.hash
381
        super(VirtualMachine, self).save(*args, **kwargs)
382

    
383
    @property
384
    def backend_vm_id(self):
385
        """Returns the backend id for this VM by prepending backend-prefix."""
386
        if not self.id:
387
            raise VirtualMachine.InvalidBackendIdError("self.id is None")
388
        return "%s%s" % (settings.BACKEND_PREFIX_ID, str(self.id))
389

    
390
    class Meta:
391
        verbose_name = u'Virtual machine instance'
392
        get_latest_by = 'created'
393

    
394
    def __unicode__(self):
395
        return self.name
396

    
397

    
398
class VirtualMachineMetadata(models.Model):
399
    meta_key = models.CharField(max_length=50)
400
    meta_value = models.CharField(max_length=500)
401
    vm = models.ForeignKey(VirtualMachine, related_name='metadata')
402

    
403
    class Meta:
404
        unique_together = (('meta_key', 'vm'),)
405
        verbose_name = u'Key-value pair of metadata for a VM.'
406

    
407
    def __unicode__(self):
408
        return u'%s: %s' % (self.meta_key, self.meta_value)
409

    
410

    
411
class Network(models.Model):
412
    OPER_STATES = (
413
        ('PENDING', 'Pending'),
414
        ('ACTIVE', 'Active'),
415
        ('DELETED', 'Deleted'),
416
        ('ERROR', 'Error')
417
    )
418

    
419
    ACTIONS = (
420
       ('CREATE', 'Create Network'),
421
       ('DESTROY', 'Destroy Network'),
422
    )
423

    
424
    RSAPI_STATE_FROM_OPER_STATE = {
425
        'PENDING': 'PENDING',
426
        'ACTIVE': 'ACTIVE',
427
        'DELETED': 'DELETED',
428
        'ERROR': 'ERROR'
429
    }
430

    
431
    NETWORK_TYPES = (
432
        ('PUBLIC_ROUTED', 'Public routed network'),
433
        ('PRIVATE_PHYSICAL_VLAN', 'Private vlan network'),
434
        ('PRIVATE_MAC_FILTERED', 'Private network with mac-filtering'),
435
        ('CUSTOM_ROUTED', 'Custom routed network'),
436
        ('CUSTOM_BRIDGED', 'Custom bridged network')
437
    )
438

    
439
    name = models.CharField('Network Name', max_length=128)
440
    userid = models.CharField('User ID of the owner', max_length=128, null=True)
441
    subnet = models.CharField('Subnet', max_length=32, default='10.0.0.0/24')
442
    subnet6 = models.CharField('IPv6 Subnet', max_length=64, null=True)
443
    gateway = models.CharField('Gateway', max_length=32, null=True)
444
    gateway6 = models.CharField('IPv6 Gateway', max_length=64, null=True)
445
    dhcp = models.BooleanField('DHCP', default=True)
446
    type = models.CharField(choices=NETWORK_TYPES, max_length=50,
447
                            default='PRIVATE_PHYSICAL_VLAN')
448
    link = models.CharField('Network Link', max_length=128, null=True)
449
    mac_prefix = models.CharField('MAC Prefix', max_length=32, null=True)
450
    public = models.BooleanField(default=False)
451
    created = models.DateTimeField(auto_now_add=True)
452
    updated = models.DateTimeField(auto_now=True)
453
    deleted = models.BooleanField('Deleted', default=False)
454
    state = models.CharField(choices=OPER_STATES, max_length=32,
455
                             default='PENDING')
456
    machines = models.ManyToManyField(VirtualMachine,
457
                                      through='NetworkInterface')
458
    action = models.CharField(choices=ACTIONS, max_length=32, null=True,
459
                              default=None)
460

    
461
    reservations = models.TextField(default='')
462

    
463
    ip_pool = None
464

    
465

    
466
    class InvalidBackendIdError(Exception):
467
        def __init__(self, value):
468
            self.value = value
469

    
470
        def __str__(self):
471
            return repr(self.value)
472

    
473

    
474
    class InvalidBackendMsgError(Exception):
475
        def __init__(self, opcode, status):
476
            self.opcode = opcode
477
            self.status = status
478

    
479
        def __str__(self):
480
            return repr('<opcode: %s, status: %s>' % (self.opcode,
481
                    self.status))
482

    
483
    class InvalidActionError(Exception):
484
        def __init__(self, action):
485
            self._action = action
486

    
487
        def __str__(self):
488
            return repr(str(self._action))
489

    
490
    @property
491
    def backend_id(self):
492
        """Return the backend id by prepending backend-prefix."""
493
        if not self.id:
494
            raise Network.InvalidBackendIdError("self.id is None")
495
        return "%snet-%s" % (settings.BACKEND_PREFIX_ID, str(self.id))
496

    
497
    @property
498
    def backend_tag(self):
499
        """Return the network tag to be used in backend
500

501
        """
502
        return getattr(snf_settings, self.type + '_TAGS')
503

    
504
    def __unicode__(self):
505
        return self.name
506

    
507
    def update_state(self):
508
        """Update state of the Network.
509

510
        Update the state of the Network depending on the related
511
        backend_networks. When backend networks do not have the same operstate,
512
        the Network's state is PENDING. Otherwise it is the same with
513
        the BackendNetworks operstate.
514

515
        """
516
        backend_states = [s.operstate for s in self.backend_networks.all()]
517
        if not backend_states:
518
            self.state = 'PENDING'
519
            self.save()
520
            return
521

    
522
        all_equal = len(set(backend_states)) <= 1
523
        self.state = all_equal and backend_states[0] or 'PENDING'
524

    
525
        if self.state == 'DELETED':
526
            self.deleted = True
527

    
528
            if self.mac_prefix:
529
                MacPrefixPool.set_available(self.mac_prefix)
530

    
531
            if self.link and self.type == 'PRIVATE_VLAN':
532
                BridgePool.set_available(self.link)
533

    
534
        self.save()
535

    
536
    def save(self, *args, **kwargs):
537
        pk = self.pk
538
        super(Network, self).save(*args, **kwargs)
539
        if not pk:
540
            # In case of a new Network, corresponding BackendNetwork's must
541
            # be created!
542
            for back in Backend.objects.all():
543
                BackendNetwork.objects.create(backend=back, network=self)
544

    
545
    @property
546
    def pool(self):
547
        if self.ip_pool:
548
            return self.ip_pool
549
        else:
550
            self.ip_pool = IPPool(self)
551
            return self.ip_pool
552

    
553
    def reserve_address(self, address, pool=None):
554
        pool = pool or self.pool
555
        pool.reserve(address)
556
        pool._update_network()
557
        self.save()
558

    
559
    def release_address(self, address, pool=None):
560
        pool = pool or self.pool
561
        pool.release(address)
562
        pool._update_network()
563
        self.save()
564

    
565
    # def get_free_address(self):
566
    #     # Get yourself inside a transaction
567
    #     network = Network.objects.get(id=self.id)
568
    #     # Get the pool object
569
    #     pool = network.pool
570
    #     print network is self
571
    #     try:
572
    #         address = pool.get_free_address()
573
    #     except IPPoolExhausted:
574
    #         raise Network.NetworkIsFull
575

    
576
    #     pool._update_network()
577
    #     network.save()
578
    #     return address
579

    
580
    # class NetworkIsFull(Exception):
581
    #     pass
582

    
583

    
584
class BackendNetwork(models.Model):
585
    OPER_STATES = (
586
        ('PENDING', 'Pending'),
587
        ('ACTIVE', 'Active'),
588
        ('DELETED', 'Deleted'),
589
        ('ERROR', 'Error')
590
    )
591

    
592
    # The list of possible operations on the backend
593
    BACKEND_OPCODES = (
594
        ('OP_NETWORK_ADD', 'Create Network'),
595
        ('OP_NETWORK_CONNECT', 'Activate Network'),
596
        ('OP_NETWORK_DISCONNECT', 'Deactivate Network'),
597
        ('OP_NETWORK_REMOVE', 'Remove Network'),
598
        # These are listed here for completeness,
599
        # and are ignored for the time being
600
        ('OP_NETWORK_SET_PARAMS', 'Set Network Parameters'),
601
        ('OP_NETWORK_QUERY_DATA', 'Query Network Data')
602
    )
603

    
604
    # The operating state of a Netowork,
605
    # upon the successful completion of a backend operation.
606
    # IMPORTANT: Make sure all keys have a corresponding
607
    # entry in BACKEND_OPCODES if you update this field, see #1035, #1111.
608
    OPER_STATE_FROM_OPCODE = {
609
        'OP_NETWORK_ADD': 'PENDING',
610
        'OP_NETWORK_CONNECT': 'ACTIVE',
611
        'OP_NETWORK_DISCONNECT': 'PENDING',
612
        'OP_NETWORK_REMOVE': 'DELETED',
613
        'OP_NETWORK_SET_PARAMS': None,
614
        'OP_NETWORK_QUERY_DATA': None
615
    }
616

    
617
    network = models.ForeignKey(Network, related_name='backend_networks')
618
    backend = models.ForeignKey(Backend, related_name='networks')
619
    created = models.DateTimeField(auto_now_add=True)
620
    updated = models.DateTimeField(auto_now=True)
621
    deleted = models.BooleanField('Deleted', default=False)
622
    operstate = models.CharField(choices=OPER_STATES, max_length=30,
623
                                 default='PENDING')
624
    backendjobid = models.PositiveIntegerField(null=True)
625
    backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30,
626
                                     null=True)
627
    backendjobstatus = models.CharField(choices=BACKEND_STATUSES,
628
                                        max_length=30, null=True)
629
    backendlogmsg = models.TextField(null=True)
630
    backendtime = models.DateTimeField(null=False,
631
                                       default=datetime.datetime.min)
632

    
633
    def save(self, *args, **kwargs):
634
        super(BackendNetwork, self).save(*args, **kwargs)
635
        self.network.update_state()
636

    
637
    def delete(self, *args, **kwargs):
638
        super(BackendNetwork, self).delete(*args, **kwargs)
639
        self.network.update_state()
640

    
641

    
642
class NetworkInterface(models.Model):
643
    FIREWALL_PROFILES = (
644
        ('ENABLED', 'Enabled'),
645
        ('DISABLED', 'Disabled'),
646
        ('PROTECTED', 'Protected')
647
    )
648

    
649
    machine = models.ForeignKey(VirtualMachine, related_name='nics')
650
    network = models.ForeignKey(Network, related_name='nics')
651
    created = models.DateTimeField(auto_now_add=True)
652
    updated = models.DateTimeField(auto_now=True)
653
    index = models.IntegerField(null=False)
654
    mac = models.CharField(max_length=32, null=True)
655
    ipv4 = models.CharField(max_length=15, null=True)
656
    ipv6 = models.CharField(max_length=100, null=True)
657
    firewall_profile = models.CharField(choices=FIREWALL_PROFILES,
658
                                        max_length=30, null=True)
659
    dirty = models.BooleanField(default=False)
660

    
661
    def __unicode__(self):
662
        return '%s@%s' % (self.machine.name, self.network.name)
663

    
664

    
665
class Pool(models.Model):
666
    available = models.BooleanField(default=True, null=False)
667
    index = models.IntegerField(null=False, unique=True)
668
    value = models.CharField(max_length=128, null=False, unique=True)
669
    max_index = 0
670

    
671
    class Meta:
672
        abstract = True
673
        ordering = ['index']
674

    
675
    @classmethod
676
    def get_available(cls):
677
        try:
678
            entry = cls.objects.filter(available=True)[0]
679
            entry.available = False
680
            entry.save()
681
            return entry
682
        except IndexError:
683
            return cls.generate_new()
684

    
685
    @classmethod
686
    def generate_new(cls):
687
        try:
688
            last = cls.objects.order_by('-index')[0]
689
            index = last.index + 1
690
        except IndexError:
691
            index = 1
692

    
693
        if index <= cls.max_index:
694
            return cls.objects.create(index=index,
695
                                      value=cls.value_from_index(index),
696
                                      available=False)
697

    
698
        raise Pool.PoolExhausted()
699

    
700
    @classmethod
701
    def set_available(cls, value):
702
        entry = cls.objects.get(value=value)
703
        entry.available = True
704
        entry.save()
705

    
706

    
707
    class PoolExhausted(Exception):
708
        pass
709

    
710

    
711
class BridgePool(Pool):
712
    max_index = snf_settings.PRIVATE_PHYSICAL_VLAN_MAX_NUMBER
713

    
714
    @staticmethod
715
    def value_from_index(index):
716
        return snf_settings.PRIVATE_PHYSICAL_VLAN_BRIDGE_PREFIX + str(index)
717

    
718

    
719
class MacPrefixPool(Pool):
720
    max_index = snf_settings.PRIVATE_MAC_FILTERED_MAX_PREFIX_NUMBER
721

    
722
    @staticmethod
723
    def value_from_index(index):
724
        """Convert number to mac prefix
725

726
        """
727
        high = snf_settings.PRIVATE_MAC_FILTERED_BASE_MAC_PREFIX
728
        a = hex(int(high.replace(":", ""), 16) + index).replace("0x", '')
729
        mac_prefix = ":".join([a[x:x + 2] for x in xrange(0, len(a), 2)])
730
        return mac_prefix