Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (24 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 import settings as snf_settings
40
from aes_encrypt import encrypt_db_charfield, decrypt_db_charfield
41

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

    
45

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

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

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

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

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

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

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

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

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

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

    
86
    return client
87

    
88

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

    
93

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

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

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

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

    
114

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

    
120

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

    
125

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

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

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

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

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

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

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

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

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

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

    
203

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

    
215

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

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

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

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

    
258

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

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

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

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

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

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

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

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

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

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

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

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

    
357
    class DeletedError(Exception):
358
        pass
359

    
360
    class BuildingError(Exception):
361
        pass
362

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

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

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

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

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

    
396

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

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

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

    
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

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

    
462
    class InvalidBackendIdError(Exception):
463
        def __init__(self, value):
464
            self.value = value
465

    
466
        def __str__(self):
467
            return repr(self.value)
468

    
469

    
470
    class InvalidBackendMsgError(Exception):
471
        def __init__(self, opcode, status):
472
            self.opcode = opcode
473
            self.status = status
474

    
475
        def __str__(self):
476
            return repr('<opcode: %s, status: %s>' % (self.opcode,
477
                    self.status))
478

    
479
    class InvalidActionError(Exception):
480
        def __init__(self, action):
481
            self._action = action
482

    
483
        def __str__(self):
484
            return repr(str(self._action))
485

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

    
493
    @property
494
    def backend_tag(self):
495
        """Return the network tag to be used in backend
496

497
        """
498
        return getattr(snf_settings, self.type + '_TAGS')
499

    
500
    def __unicode__(self):
501
        return self.name
502

    
503
    def update_state(self):
504
        """Update state of the Network.
505

506
        Update the state of the Network depending on the related
507
        backend_networks. When backend networks do not have the same operstate,
508
        the Network's state is PENDING. Otherwise it is the same with
509
        the BackendNetworks operstate.
510

511
        """
512
        backend_states = [s.operstate for s in self.backend_networks.all()]
513
        if not backend_states:
514
            self.state = 'PENDING'
515
            self.save()
516
            return
517

    
518
        all_equal = len(set(backend_states)) <= 1
519
        self.state = all_equal and backend_states[0] or 'PENDING'
520

    
521
        if self.state == 'DELETED':
522
            self.deleted = True
523

    
524
            if self.mac_prefix:
525
                MacPrefixPool.set_available(self.mac_prefix)
526

    
527
            if self.link and self.type == 'PRIVATE_VLAN':
528
                BridgePool.set_available(self.link)
529

    
530
        self.save()
531

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

    
541

    
542
class BackendNetwork(models.Model):
543
    OPER_STATES = (
544
        ('PENDING', 'Pending'),
545
        ('ACTIVE', 'Active'),
546
        ('DELETED', 'Deleted'),
547
        ('ERROR', 'Error')
548
    )
549

    
550
    # The list of possible operations on the backend
551
    BACKEND_OPCODES = (
552
        ('OP_NETWORK_ADD', 'Create Network'),
553
        ('OP_NETWORK_CONNECT', 'Activate Network'),
554
        ('OP_NETWORK_DISCONNECT', 'Deactivate Network'),
555
        ('OP_NETWORK_REMOVE', 'Remove Network'),
556
        # These are listed here for completeness,
557
        # and are ignored for the time being
558
        ('OP_NETWORK_SET_PARAMS', 'Set Network Parameters'),
559
        ('OP_NETWORK_QUERY_DATA', 'Query Network Data')
560
    )
561

    
562
    # The operating state of a Netowork,
563
    # upon the successful completion of a backend operation.
564
    # IMPORTANT: Make sure all keys have a corresponding
565
    # entry in BACKEND_OPCODES if you update this field, see #1035, #1111.
566
    OPER_STATE_FROM_OPCODE = {
567
        'OP_NETWORK_ADD': 'PENDING',
568
        'OP_NETWORK_CONNECT': 'ACTIVE',
569
        'OP_NETWORK_DISCONNECT': 'PENDING',
570
        'OP_NETWORK_REMOVE': 'DELETED',
571
        'OP_NETWORK_SET_PARAMS': None,
572
        'OP_NETWORK_QUERY_DATA': None
573
    }
574

    
575
    network = models.ForeignKey(Network, related_name='backend_networks')
576
    backend = models.ForeignKey(Backend, related_name='networks')
577
    created = models.DateTimeField(auto_now_add=True)
578
    updated = models.DateTimeField(auto_now=True)
579
    deleted = models.BooleanField('Deleted', default=False)
580
    operstate = models.CharField(choices=OPER_STATES, max_length=30,
581
                                 default='PENDING')
582
    backendjobid = models.PositiveIntegerField(null=True)
583
    backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30,
584
                                     null=True)
585
    backendjobstatus = models.CharField(choices=BACKEND_STATUSES,
586
                                        max_length=30, null=True)
587
    backendlogmsg = models.TextField(null=True)
588
    backendtime = models.DateTimeField(null=False,
589
                                       default=datetime.datetime.min)
590

    
591
    def save(self, *args, **kwargs):
592
        super(BackendNetwork, self).save(*args, **kwargs)
593
        self.network.update_state()
594

    
595
    def delete(self, *args, **kwargs):
596
        super(BackendNetwork, self).delete(*args, **kwargs)
597
        self.network.update_state()
598

    
599

    
600
class NetworkInterface(models.Model):
601
    FIREWALL_PROFILES = (
602
        ('ENABLED', 'Enabled'),
603
        ('DISABLED', 'Disabled'),
604
        ('PROTECTED', 'Protected')
605
    )
606

    
607
    machine = models.ForeignKey(VirtualMachine, related_name='nics')
608
    network = models.ForeignKey(Network, related_name='nics')
609
    created = models.DateTimeField(auto_now_add=True)
610
    updated = models.DateTimeField(auto_now=True)
611
    index = models.IntegerField(null=False)
612
    mac = models.CharField(max_length=17, null=True)
613
    ipv4 = models.CharField(max_length=15, null=True)
614
    ipv6 = models.CharField(max_length=100, null=True)
615
    firewall_profile = models.CharField(choices=FIREWALL_PROFILES,
616
                                        max_length=30, null=True)
617
    dirty = models.BooleanField(default=False)
618

    
619
    def __unicode__(self):
620
        return '%s@%s' % (self.machine.name, self.network.name)
621

    
622

    
623
class Pool(models.Model):
624
    available = models.BooleanField(default=True, null=False)
625
    index = models.IntegerField(null=False, unique=True)
626
    value = models.CharField(max_length=128, null=False, unique=True)
627
    max_index = 0
628

    
629
    class Meta:
630
        abstract = True
631
        ordering = ['index']
632

    
633
    @classmethod
634
    def get_available(cls):
635
        try:
636
            entry = cls.objects.filter(available=True)[0]
637
            entry.available = False
638
            entry.save()
639
            return entry
640
        except IndexError:
641
            return cls.generate_new()
642

    
643
    @classmethod
644
    def generate_new(cls):
645
        try:
646
            last = cls.objects.order_by('-index')[0]
647
            index = last.index + 1
648
        except IndexError:
649
            index = 1
650

    
651
        if index <= cls.max_index:
652
            return cls.objects.create(index=index,
653
                                      value=cls.value_from_index(index),
654
                                      available=False)
655

    
656
        raise Pool.PoolExhausted()
657

    
658
    @classmethod
659
    def set_available(cls, value):
660
        entry = cls.objects.get(value=value)
661
        entry.available = True
662
        entry.save()
663

    
664

    
665
    class PoolExhausted(Exception):
666
        pass
667

    
668

    
669
class BridgePool(Pool):
670
    max_index = snf_settings.PRIVATE_PHYSICAL_VLAN_MAX_NUMBER
671

    
672
    @staticmethod
673
    def value_from_index(index):
674
        return snf_settings.PRIVATE_PHYSICAL_VLAN_BRIDGE_PREFIX + str(index)
675

    
676

    
677
class MacPrefixPool(Pool):
678
    max_index = snf_settings.PRIVATE_MAC_FILTERED_MAX_PREFIX_NUMBER
679

    
680
    @staticmethod
681
    def value_from_index(index):
682
        """Convert number to mac prefix
683

684
        """
685
        high = snf_settings.PRIVATE_MAC_FILTERED_BASE_MAC_PREFIX
686
        a = hex(int(high.replace(":", ""), 16) + index).replace("0x", '')
687
        mac_prefix = ":".join([a[x:x + 2] for x in xrange(0, len(a), 2)])
688
        return mac_prefix