Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (25.6 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
from django.db import transaction
36

    
37
import utils
38

    
39
from hashlib import sha1
40
from synnefo.api.faults import ServiceUnavailable
41
from synnefo.util.rapi import GanetiRapiClient
42
from synnefo import settings as snf_settings
43
from aes_encrypt import encrypt_db_charfield, decrypt_db_charfield
44

    
45
from synnefo.db.managers import ForUpdateManager, ProtectedDeleteManager
46
from synnefo.db import pools
47

    
48
BACKEND_CLIENTS = {}  # {hash:Backend client}
49
BACKEND_HASHES = {}   # {Backend.id:hash}
50

    
51

    
52
def get_client(hash, backend):
53
    """Get a cached backend client or create a new one.
54

55
    @param hash: The hash of the backend
56
    @param backend: Either a backend object or backend ID
57
    """
58

    
59
    if backend is None:
60
        raise Exception("Backend is None. Cannot create a client.")
61

    
62
    if hash in BACKEND_CLIENTS:
63
        # Return cached client
64
        return BACKEND_CLIENTS[hash]
65

    
66
    # Always get a new instance to ensure latest credentials
67
    if isinstance(backend, Backend):
68
        backend = backend.id
69

    
70
    backend = Backend.objects.get(id=backend)
71
    hash = backend.hash
72
    clustername = backend.clustername
73
    port = backend.port
74
    user = backend.username
75
    password = backend.password
76

    
77
    # Check client for updated hash
78
    if hash in BACKEND_CLIENTS:
79
        return BACKEND_CLIENTS[hash]
80

    
81
    # Delete old version of the client
82
    if backend in BACKEND_HASHES:
83
        del BACKEND_CLIENTS[BACKEND_HASHES[backend]]
84

    
85
    # Create the new client
86
    client = GanetiRapiClient(clustername, port, user, password)
87

    
88
    # Store the client and the hash
89
    BACKEND_CLIENTS[hash] = client
90
    BACKEND_HASHES[backend] = hash
91

    
92
    return client
93

    
94

    
95
def clear_client_cache():
96
    BACKEND_CLIENTS.clear()
97
    BACKEND_HASHES.clear()
98

    
99

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

    
108
    class Meta:
109
        verbose_name = u'Virtual machine flavor'
110
        unique_together = ('cpu', 'ram', 'disk', 'disk_template')
111

    
112
    @property
113
    def name(self):
114
        """Returns flavor name (generated)"""
115
        return u'C%dR%dD%d' % (self.cpu, self.ram, self.disk)
116

    
117
    def __unicode__(self):
118
        return self.name
119

    
120

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

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

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

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

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

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

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

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

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

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

    
202
    def __init__(self, *args, **kwargs):
203
        super(Backend, self).__init__(*args, **kwargs)
204
        if not self.pk:
205
            # Generate a unique index for the Backend
206
            indexes = Backend.objects.all().values_list('index', flat=True)
207
            first_free = [x for x in xrange(0, 16) if x not in indexes][0]
208
            self.index = first_free
209

    
210

    
211
# A backend job may be in one of the following possible states
212
BACKEND_STATUSES = (
213
    ('queued', 'request queued'),
214
    ('waiting', 'request waiting for locks'),
215
    ('canceling', 'request being canceled'),
216
    ('running', 'request running'),
217
    ('canceled', 'request canceled'),
218
    ('success', 'request completed successfully'),
219
    ('error', 'request returned error')
220
)
221

    
222

    
223
class VirtualMachine(models.Model):
224
    # The list of possible actions for a VM
225
    ACTIONS = (
226
       ('CREATE', 'Create VM'),
227
       ('START', 'Start VM'),
228
       ('STOP', 'Shutdown VM'),
229
       ('SUSPEND', 'Admin Suspend VM'),
230
       ('REBOOT', 'Reboot VM'),
231
       ('DESTROY', 'Destroy VM')
232
    )
233

    
234
    # The internal operating state of a VM
235
    OPER_STATES = (
236
        ('BUILD', 'Queued for creation'),
237
        ('ERROR', 'Creation failed'),
238
        ('STOPPED', 'Stopped'),
239
        ('STARTED', 'Started'),
240
        ('DESTROYED', 'Destroyed')
241
    )
242

    
243
    # The list of possible operations on the backend
244
    BACKEND_OPCODES = (
245
        ('OP_INSTANCE_CREATE', 'Create Instance'),
246
        ('OP_INSTANCE_REMOVE', 'Remove Instance'),
247
        ('OP_INSTANCE_STARTUP', 'Startup Instance'),
248
        ('OP_INSTANCE_SHUTDOWN', 'Shutdown Instance'),
249
        ('OP_INSTANCE_REBOOT', 'Reboot Instance'),
250

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

    
265

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

    
288
    # This dictionary contains the correspondence between
289
    # internal operating states and Server States as defined
290
    # by the Rackspace API.
291
    RSAPI_STATE_FROM_OPER_STATE = {
292
        "BUILD": "BUILD",
293
        "ERROR": "ERROR",
294
        "STOPPED": "STOPPED",
295
        "STARTED": "ACTIVE",
296
        "DESTROYED": "DELETED"
297
    }
298

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

    
313
    # VM State
314
    # The following fields are volatile data, in the sense
315
    # that they need not be persistent in the DB, but rather
316
    # get generated at runtime by quering Ganeti and applying
317
    # updates received from Ganeti.
318

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

    
333
    @property
334
    def client(self):
335
        if self.backend and not self.backend.offline:
336
            return get_client(self.backend_hash, self.backend_id)
337
        else:
338
            raise ServiceUnavailable
339

    
340
    # Error classes
341
    class InvalidBackendIdError(Exception):
342
        def __init__(self, value):
343
            self.value = value
344

    
345
        def __str__(self):
346
            return repr(self.value)
347

    
348
    class InvalidBackendMsgError(Exception):
349
        def __init__(self, opcode, status):
350
            self.opcode = opcode
351
            self.status = status
352

    
353
        def __str__(self):
354
            return repr('<opcode: %s, status: %s>' % (self.opcode,
355
                        self.status))
356

    
357
    class InvalidActionError(Exception):
358
        def __init__(self, action):
359
            self._action = action
360

    
361
        def __str__(self):
362
            return repr(str(self._action))
363

    
364
    class DeletedError(Exception):
365
        pass
366

    
367
    class BuildingError(Exception):
368
        pass
369

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

    
383
    def save(self, *args, **kwargs):
384
        # Store hash for first time saved vm
385
        if (self.id is None or self.backend_hash == '') and self.backend:
386
            self.backend_hash = self.backend.hash
387
        super(VirtualMachine, self).save(*args, **kwargs)
388

    
389
    @property
390
    def backend_vm_id(self):
391
        """Returns the backend id for this VM by prepending backend-prefix."""
392
        if not self.id:
393
            raise VirtualMachine.InvalidBackendIdError("self.id is None")
394
        return "%s%s" % (settings.BACKEND_PREFIX_ID, str(self.id))
395

    
396
    class Meta:
397
        verbose_name = u'Virtual machine instance'
398
        get_latest_by = 'created'
399

    
400
    def __unicode__(self):
401
        return self.name
402

    
403

    
404
class VirtualMachineMetadata(models.Model):
405
    meta_key = models.CharField(max_length=50)
406
    meta_value = models.CharField(max_length=500)
407
    vm = models.ForeignKey(VirtualMachine, related_name='metadata')
408

    
409
    class Meta:
410
        unique_together = (('meta_key', 'vm'),)
411
        verbose_name = u'Key-value pair of metadata for a VM.'
412

    
413
    def __unicode__(self):
414
        return u'%s: %s' % (self.meta_key, self.meta_value)
415

    
416

    
417
class Network(models.Model):
418
    OPER_STATES = (
419
        ('PENDING', 'Pending'),
420
        ('ACTIVE', 'Active'),
421
        ('DELETED', 'Deleted'),
422
        ('ERROR', 'Error')
423
    )
424

    
425
    ACTIONS = (
426
       ('CREATE', 'Create Network'),
427
       ('DESTROY', 'Destroy Network'),
428
    )
429

    
430
    RSAPI_STATE_FROM_OPER_STATE = {
431
        'PENDING': 'PENDING',
432
        'ACTIVE': 'ACTIVE',
433
        'DELETED': 'DELETED',
434
        'ERROR': 'ERROR'
435
    }
436

    
437
    NETWORK_TYPES = (
438
        ('PUBLIC_ROUTED', 'Public routed network'),
439
        ('PRIVATE_PHYSICAL_VLAN', 'Private vlan network'),
440
        ('PRIVATE_MAC_FILTERED', 'Private network with mac-filtering'),
441
        ('CUSTOM_ROUTED', 'Custom routed network'),
442
        ('CUSTOM_BRIDGED', 'Custom bridged network')
443
    )
444

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

    
467
    reservations = models.TextField(default='')
468

    
469
    ip_pool = None
470

    
471
    objects = ForUpdateManager()
472

    
473
    class InvalidBackendIdError(Exception):
474
        def __init__(self, value):
475
            self.value = value
476

    
477
        def __str__(self):
478
            return repr(self.value)
479

    
480
    class InvalidBackendMsgError(Exception):
481
        def __init__(self, opcode, status):
482
            self.opcode = opcode
483
            self.status = status
484

    
485
        def __str__(self):
486
            return repr('<opcode: %s, status: %s>' % (self.opcode,
487
                    self.status))
488

    
489
    class InvalidActionError(Exception):
490
        def __init__(self, action):
491
            self._action = action
492

    
493
        def __str__(self):
494
            return repr(str(self._action))
495

    
496
    @property
497
    def backend_id(self):
498
        """Return the backend id by prepending backend-prefix."""
499
        if not self.id:
500
            raise Network.InvalidBackendIdError("self.id is None")
501
        return "%snet-%s" % (settings.BACKEND_PREFIX_ID, str(self.id))
502

    
503
    @property
504
    def backend_tag(self):
505
        """Return the network tag to be used in backend
506

507
        """
508
        return getattr(snf_settings, self.type + '_TAGS')
509

    
510
    def __unicode__(self):
511
        return self.name
512

    
513
    @transaction.commit_on_success
514
    def update_state(self):
515
        """Update state of the Network.
516

517
        Update the state of the Network depending on the related
518
        backend_networks. When backend networks do not have the same operstate,
519
        the Network's state is PENDING. Otherwise it is the same with
520
        the BackendNetworks operstate.
521

522
        """
523

    
524
        old_state = self.state
525

    
526
        backend_states = [s.operstate for s in self.backend_networks.all()]
527
        if not backend_states:
528
            self.state = 'PENDING'
529
            self.save()
530
            return
531

    
532
        all_equal = len(set(backend_states)) <= 1
533
        self.state = all_equal and backend_states[0] or 'PENDING'
534

    
535
        # Release the resources on the deletion of the Network
536
        if old_state != 'DELETED' and self.state == 'DELETED':
537
            self.deleted = True
538

    
539
            if self.mac_prefix:
540
                mac_pool = MacPrefixPoolTable.get_pool()
541
                mac_pool.put(self.mac_prefix)
542
                mac_pool.save()
543

    
544
            if self.link and self.type == 'PRIVATE_VLAN':
545
                bridge_pool = BridgePoolTable.get_pool()
546
                bridge_pool.put(self.link)
547
                bridge_pool.save()
548

    
549
        self.save()
550

    
551
    def __init__(self, *args, **kwargs):
552
        super(Network, self).__init__(*args, **kwargs)
553
        if not self.mac_prefix:
554
            # Allocate a MAC prefix for just created Network instances
555
            mac_pool = MacPrefixPoolTable.get_pool()
556
            mac_prefix = mac_pool.get()
557
            mac_pool.save()
558
            self.mac_prefix = mac_prefix
559

    
560
    def create_backend_network(self, backend=None):
561
        """Create corresponding BackendNetwork entries."""
562

    
563
        backends = [backend] if backend else Backend.objects.all()
564
        for backend in backends:
565
            BackendNetwork.objects.create(backend=backend, network=self)
566

    
567
    @property
568
    def pool(self):
569
        if self.ip_pool:
570
            return self.ip_pool
571
        else:
572
            self.ip_pool = pools.IPPool(self)
573
            return self.ip_pool
574

    
575
    def reserve_address(self, address, pool=None):
576
        pool = pool or self.pool
577
        pool.reserve(address)
578
        pool._update_network()
579
        self.save()
580

    
581
    def release_address(self, address, pool=None):
582
        pool = pool or self.pool
583
        pool.release(address)
584
        pool._update_network()
585
        self.save()
586

    
587

    
588
class BackendNetwork(models.Model):
589
    OPER_STATES = (
590
        ('PENDING', 'Pending'),
591
        ('ACTIVE', 'Active'),
592
        ('DELETED', 'Deleted'),
593
        ('ERROR', 'Error')
594
    )
595

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

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

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

    
638
    class Meta:
639
        # Ensure one entry for each network in each backend
640
        unique_together = (("network", "backend"))
641

    
642
    def __init__(self, *args, **kwargs):
643
        """Initialize state for just created BackendNetwork instances."""
644
        super(BackendNetwork, self).__init__(*args, **kwargs)
645
        if not self.mac_prefix:
646
            # Generate the MAC prefix of the BackendNetwork, by combining
647
            # the Network prefix with the index of the Backend
648
            net_prefix = self.network.mac_prefix
649
            backend_suffix = hex(self.backend.index).replace('0x', '')
650
            mac_prefix = net_prefix + backend_suffix
651
            try:
652
                utils.validate_mac(mac_prefix + ":00:00:00")
653
            except utils.InvalidMacAddress:
654
                raise utils.InvalidMacAddress("Invalid MAC prefix '%s'" % \
655
                                               mac_prefix)
656
            self.mac_prefix = mac_prefix
657

    
658
    def save(self, *args, **kwargs):
659
        super(BackendNetwork, self).save(*args, **kwargs)
660
        self.network.update_state()
661

    
662
    def delete(self, *args, **kwargs):
663
        super(BackendNetwork, self).delete(*args, **kwargs)
664
        self.network.update_state()
665

    
666

    
667
class NetworkInterface(models.Model):
668
    FIREWALL_PROFILES = (
669
        ('ENABLED', 'Enabled'),
670
        ('DISABLED', 'Disabled'),
671
        ('PROTECTED', 'Protected')
672
    )
673

    
674
    machine = models.ForeignKey(VirtualMachine, related_name='nics')
675
    network = models.ForeignKey(Network, related_name='nics')
676
    created = models.DateTimeField(auto_now_add=True)
677
    updated = models.DateTimeField(auto_now=True)
678
    index = models.IntegerField(null=False)
679
    mac = models.CharField(max_length=32, null=False, unique=True)
680
    ipv4 = models.CharField(max_length=15, null=True)
681
    ipv6 = models.CharField(max_length=100, null=True)
682
    firewall_profile = models.CharField(choices=FIREWALL_PROFILES,
683
                                        max_length=30, null=True)
684
    dirty = models.BooleanField(default=False)
685

    
686
    def __unicode__(self):
687
        return '%s@%s' % (self.machine.name, self.network.name)
688

    
689

    
690
class PoolTable(models.Model):
691
    available_map = models.TextField(default="", null=False)
692
    reserved_map = models.TextField(default="", null=False)
693
    size = models.IntegerField(null=False)
694

    
695
    # Optional Fields
696
    base = models.CharField(null=True, max_length=32)
697
    offset = models.IntegerField(null=True)
698

    
699
    objects = ForUpdateManager()
700

    
701
    class Meta:
702
        abstract = True
703

    
704
    @classmethod
705
    def get_pool(cls):
706
        try:
707
            pool_row = cls.objects.select_for_update().all()[0]
708
            return pool_row.pool
709
        except IndexError:
710
            raise pools.EmptyPool
711

    
712
    @property
713
    def pool(self):
714
        return self.manager(self)
715

    
716

    
717
class BridgePoolTable(PoolTable):
718
    manager = pools.BridgePool
719

    
720
class MacPrefixPoolTable(PoolTable):
721
    manager = pools.MacPrefixPool