Statistics
| Branch: | Tag: | Revision:

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

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
import logging
52
log = logging.getLogger(__name__)
53

    
54

    
55
def get_client(hash, backend):
56
    """Get a cached backend client or create a new one.
57

58
    @param hash: The hash of the backend
59
    @param backend: Either a backend object or backend ID
60
    """
61

    
62
    if backend is None:
63
        raise Exception("Backend is None. Cannot create a client.")
64

    
65
    if hash in BACKEND_CLIENTS:
66
        # Return cached client
67
        return BACKEND_CLIENTS[hash]
68

    
69
    # Always get a new instance to ensure latest credentials
70
    if isinstance(backend, Backend):
71
        backend = backend.id
72

    
73
    backend = Backend.objects.get(id=backend)
74
    hash = backend.hash
75
    clustername = backend.clustername
76
    port = backend.port
77
    user = backend.username
78
    password = backend.password
79

    
80
    # Check client for updated hash
81
    if hash in BACKEND_CLIENTS:
82
        return BACKEND_CLIENTS[hash]
83

    
84
    # Delete old version of the client
85
    if backend in BACKEND_HASHES:
86
        del BACKEND_CLIENTS[BACKEND_HASHES[backend]]
87

    
88
    # Create the new client
89
    client = GanetiRapiClient(clustername, port, user, password)
90

    
91
    # Store the client and the hash
92
    BACKEND_CLIENTS[hash] = client
93
    BACKEND_HASHES[backend] = hash
94

    
95
    return client
96

    
97

    
98
def clear_client_cache():
99
    BACKEND_CLIENTS.clear()
100
    BACKEND_HASHES.clear()
101

    
102

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

    
111
    class Meta:
112
        verbose_name = u'Virtual machine flavor'
113
        unique_together = ('cpu', 'ram', 'disk', 'disk_template')
114

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

    
120
    def __unicode__(self):
121
        return self.name
122

    
123

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

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

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

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

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

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

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

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

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

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

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

    
213

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

    
225

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

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

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

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

    
268

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

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

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

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

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

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

    
343
    # Error classes
344
    class InvalidBackendIdError(Exception):
345
        def __init__(self, value):
346
            self.value = value
347

    
348
        def __str__(self):
349
            return repr(self.value)
350

    
351
    class InvalidBackendMsgError(Exception):
352
        def __init__(self, opcode, status):
353
            self.opcode = opcode
354
            self.status = status
355

    
356
        def __str__(self):
357
            return repr('<opcode: %s, status: %s>' % (self.opcode,
358
                        self.status))
359

    
360
    class InvalidActionError(Exception):
361
        def __init__(self, action):
362
            self._action = action
363

    
364
        def __str__(self):
365
            return repr(str(self._action))
366

    
367
    class DeletedError(Exception):
368
        pass
369

    
370
    class BuildingError(Exception):
371
        pass
372

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

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

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

    
399
    class Meta:
400
        verbose_name = u'Virtual machine instance'
401
        get_latest_by = 'created'
402

    
403
    def __unicode__(self):
404
        return self.name
405

    
406

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

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

    
416
    def __unicode__(self):
417
        return u'%s: %s' % (self.meta_key, self.meta_value)
418

    
419

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

    
428
    ACTIONS = (
429
       ('CREATE', 'Create Network'),
430
       ('DESTROY', 'Destroy Network'),
431
    )
432

    
433
    RSAPI_STATE_FROM_OPER_STATE = {
434
        'PENDING': 'PENDING',
435
        'ACTIVE': 'ACTIVE',
436
        'DELETED': 'DELETED',
437
        'ERROR': 'ERROR'
438
    }
439

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

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

    
470
    pool = models.OneToOneField('IPPoolTable', related_name='network',
471
                                null=True)
472

    
473
    objects = ForUpdateManager()
474

    
475
    class InvalidBackendIdError(Exception):
476
        def __init__(self, value):
477
            self.value = value
478

    
479
        def __str__(self):
480
            return repr(self.value)
481

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

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

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

    
495
        def __str__(self):
496
            return repr(str(self._action))
497

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

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

509
        """
510
        return getattr(snf_settings, self.type + '_TAGS')
511

    
512
    def __unicode__(self):
513
        return self.name
514

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

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

524
        """
525

    
526
        old_state = self.state
527

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

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

    
537
        # Release the resources on the deletion of the Network
538
        if old_state != 'DELETED' and self.state == 'DELETED':
539
            log.info("Network %r deleted. Releasing link %r mac_prefix %r",
540
                     self.id, self.mac_prefix, self.link)
541
            self.deleted = True
542
            if self.mac_prefix:
543
                mac_pool = MacPrefixPoolTable.get_pool()
544
                mac_pool.put(self.mac_prefix)
545
                mac_pool.save()
546

    
547
            if self.link and self.type == 'PRIVATE_VLAN':
548
                bridge_pool = BridgePoolTable.get_pool()
549
                bridge_pool.put(self.link)
550
                bridge_pool.save()
551

    
552
        self.save()
553

    
554
    def create_backend_network(self, backend=None):
555
        """Create corresponding BackendNetwork entries."""
556

    
557
        backends = [backend] if backend else Backend.objects.all()
558
        for backend in backends:
559
            BackendNetwork.objects.create(backend=backend, network=self)
560

    
561
    def get_pool(self):
562
        if not self.pool_id:
563
            self.pool = IPPoolTable.objects.create(available_map='',
564
                                                   reserved_map='',
565
                                                   size=0)
566
            self.save()
567
        return IPPoolTable.objects.select_for_update().get(id=self.pool_id).pool
568

    
569
    def reserve_address(self, address):
570
        pool = self.get_pool()
571
        pool.reserve(address)
572
        pool.save()
573

    
574
    def release_address(self, address):
575
        pool = self.get_pool()
576
        pool.put(address)
577
        pool.save()
578

    
579

    
580
class BackendNetwork(models.Model):
581
    OPER_STATES = (
582
        ('PENDING', 'Pending'),
583
        ('ACTIVE', 'Active'),
584
        ('DELETED', 'Deleted'),
585
        ('ERROR', 'Error')
586
    )
587

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

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

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

    
630
    class Meta:
631
        # Ensure one entry for each network in each backend
632
        unique_together = (("network", "backend"))
633

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

    
650
    def save(self, *args, **kwargs):
651
        super(BackendNetwork, self).save(*args, **kwargs)
652
        self.network.update_state()
653

    
654
    def delete(self, *args, **kwargs):
655
        super(BackendNetwork, self).delete(*args, **kwargs)
656
        self.network.update_state()
657

    
658

    
659
class NetworkInterface(models.Model):
660
    FIREWALL_PROFILES = (
661
        ('ENABLED', 'Enabled'),
662
        ('DISABLED', 'Disabled'),
663
        ('PROTECTED', 'Protected')
664
    )
665

    
666
    machine = models.ForeignKey(VirtualMachine, related_name='nics')
667
    network = models.ForeignKey(Network, related_name='nics')
668
    created = models.DateTimeField(auto_now_add=True)
669
    updated = models.DateTimeField(auto_now=True)
670
    index = models.IntegerField(null=False)
671
    mac = models.CharField(max_length=32, null=False, unique=True)
672
    ipv4 = models.CharField(max_length=15, null=True)
673
    ipv6 = models.CharField(max_length=100, null=True)
674
    firewall_profile = models.CharField(choices=FIREWALL_PROFILES,
675
                                        max_length=30, null=True)
676
    dirty = models.BooleanField(default=False)
677

    
678
    def __unicode__(self):
679
        return '%s@%s' % (self.machine.name, self.network.name)
680

    
681

    
682
class PoolTable(models.Model):
683
    available_map = models.TextField(default="", null=False)
684
    reserved_map = models.TextField(default="", null=False)
685
    size = models.IntegerField(null=False)
686

    
687
    # Optional Fields
688
    base = models.CharField(null=True, max_length=32)
689
    offset = models.IntegerField(null=True)
690

    
691
    objects = ForUpdateManager()
692

    
693
    class Meta:
694
        abstract = True
695

    
696
    @classmethod
697
    def get_pool(cls):
698
        try:
699
            pool_row = cls.objects.select_for_update().all()[0]
700
            return pool_row.pool
701
        except IndexError:
702
            raise pools.EmptyPool
703

    
704
    @property
705
    def pool(self):
706
        return self.manager(self)
707

    
708

    
709
class BridgePoolTable(PoolTable):
710
    manager = pools.BridgePool
711

    
712

    
713
class MacPrefixPoolTable(PoolTable):
714
    manager = pools.MacPrefixPool
715

    
716

    
717
class IPPoolTable(PoolTable):
718
    manager = pools.IPPool