models.py 25.8 KB
Newer Older
1
from datetime import timedelta
2
from importlib import import_module
Őry Máté committed
3
import logging
4

5
import django.conf
6
from django.contrib.auth.models import User
Őry Máté committed
7 8 9
from django.db.models import (Model, ForeignKey, ManyToManyField, IntegerField,
                              DateTimeField, BooleanField, TextField,
                              CharField, permalink)
10 11 12 13 14
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from model_utils.models import TimeStampedModel
Őry Máté committed
15
from netaddr import EUI
16

Őry Máté committed
17
from . import tasks
18
from firewall.models import Vlan, Host
19
from manager import vm_manager
20
from storage.models import Disk
21

22 23 24

logger = logging.getLogger(__name__)
pwgen = User.objects.make_random_password
25
scheduler = import_module(name=django.conf.settings.VM_SCHEDULER)
26
ACCESS_PROTOCOLS = django.conf.settings.VM_ACCESS_PROTOCOLS
Őry Máté committed
27 28 29 30
ACCESS_METHODS = [(key, name) for key, (name, port, transport)
                  in ACCESS_PROTOCOLS.iteritems()]
ARCHITECTURES = (('x86_64', 'x86-64 (64 bit)'),
                 ('i686', 'x86 (32 bit)'))
31 32


Őry Máté committed
33
class BaseResourceConfigModel(Model):
tarokkk committed
34

35 36 37
    """Abstract base class for models with base resource configuration
       parameters.
    """
Őry Máté committed
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
    num_cores = IntegerField(verbose_name=_('number of cores'),
                             help_text=_('Number of virtual CPU cores '
                                         'available to the virtual machine.'))
    ram_size = IntegerField(verbose_name=_('RAM size'),
                            help_text=_('Mebibytes of memory.'))
    max_ram_size = IntegerField(verbose_name=_('maximal RAM size'),
                                help_text=_('Upper memory size limit '
                                            'for balloning.'))
    arch = CharField(max_length=10, verbose_name=_('architecture'),
                     choices=ARCHITECTURES)
    priority = IntegerField(verbose_name=_('priority'),
                            help_text=_('CPU priority.'))
    boot_menu = BooleanField(verbose_name=_('boot menu'), default=False,
                             help_text=_(
                                 'Show boot device selection menu on boot.'))
    raw_data = TextField(verbose_name=_('raw_data'), blank=True, help_text=_(
        'Additional libvirt domain parameters in XML format.'))
55 56 57 58 59 60

    class Meta:
        abstract = True


class NamedBaseResourceConfig(BaseResourceConfigModel, TimeStampedModel):
tarokkk committed
61

62 63
    """Pre-created, named base resource configurations.
    """
Őry Máté committed
64 65 66
    name = CharField(max_length=50, unique=True,
                     verbose_name=_('name'), help_text=
                     _('Name of base resource configuration.'))
67 68 69 70 71 72

    def __unicode__(self):
        return self.name


class Node(TimeStampedModel):
tarokkk committed
73

Őry Máté committed
74
    """A VM host machine, a hypervisor.
75
    """
Őry Máté committed
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
    name = CharField(max_length=50, unique=True,
                     verbose_name=_('name'),
                     help_text=_('Human readable name of node.'))
    num_cores = IntegerField(verbose_name=_('number of cores'),
                             help_text=_('Number of CPU threads '
                                         'available to the virtual machines.'))
    ram_size = IntegerField(verbose_name=_('RAM size'),
                            help_text=_('Mebibytes of memory.'))
    priority = IntegerField(verbose_name=_('priority'),
                            help_text=_('Node usage priority.'))
    host = ForeignKey(Host, verbose_name=_('host'),
                      help_text=_('Host in firewall.'))
    enabled = BooleanField(verbose_name=_('enabled'), default=False,
                           help_text=_('Indicates whether the node can '
                                       'be used for hosting.'))
91 92 93 94 95 96 97 98 99 100

    class Meta:
        permissions = ()

    @property
    def online(self):
        """Indicates whether the node is connected and functional.
        """
        pass  # TODO implement check

101 102 103
    def __unicode__(self):
        return self.name

104

105
class NodeActivity(TimeStampedModel):
Őry Máté committed
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
    activity_code = CharField(verbose_name=_('activity code'),
                              max_length=100)  # TODO
    task_uuid = CharField(verbose_name=_('task_uuid'), blank=True,
                          max_length=50, null=True, unique=True, help_text=_(
                              'Celery task unique identifier.'))
    node = ForeignKey(Node, verbose_name=_('node'),
                      related_name='activity_log',
                      help_text=_('Node this activity works on.'))
    user = ForeignKey(User, verbose_name=_('user'), blank=True, null=True,
                      help_text=_('The person who started this activity.'))
    started = DateTimeField(verbose_name=_('started at'),
                            blank=True, null=True,
                            help_text=_('Time of activity initiation.'))
    finished = DateTimeField(verbose_name=_('finished at'),
                             blank=True, null=True,
                             help_text=_('Time of activity finalization.'))
    result = TextField(verbose_name=_('result'), blank=True, null=True,
                       help_text=_('Human readable result of activity.'))
    status = CharField(verbose_name=_('status'), default='PENDING',
                       max_length=50, help_text=_('Actual state of activity'))


class Lease(Model):
tarokkk committed
129

130 131 132 133 134
    """Lease times for VM instances.

    Specifies a time duration until suspension and deletion of a VM
    instance.
    """
Őry Máté committed
135 136 137 138
    name = CharField(max_length=100, unique=True,
                     verbose_name=_('name'))
    suspend_interval_seconds = IntegerField(verbose_name=_('suspend interval'))
    delete_interval_seconds = IntegerField(verbose_name=_('delete interval'))
139

140 141 142
    class Meta:
        ordering = ['name', ]

143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
    @property
    def suspend_interval(self):
        return timedelta(seconds=self.suspend_interval_seconds)

    @suspend_interval.setter
    def suspend_interval(self, value):
        self.suspend_interval_seconds = value.seconds

    @property
    def delete_interval(self):
        return timedelta(seconds=self.delete_interval_seconds)

    @delete_interval.setter
    def delete_interval(self, value):
        self.delete_interval_seconds = value.seconds

159 160 161
    def __unicode__(self):
        return self.name

162 163

class InstanceTemplate(BaseResourceConfigModel, TimeStampedModel):
tarokkk committed
164

165 166 167 168 169 170 171 172 173 174 175 176 177 178
    """Virtual machine template.

    Every template has:
      * a name and a description
      * an optional parent template
      * state of the template
      * an OS name/description
      * a method of access to the system
      * default values of base resource configuration
      * list of attached images
      * set of interfaces
      * lease times (suspension & deletion)
      * time of creation and last modification
    """
Őry Máté committed
179
    STATES = [('NEW', _('new')),        # template has just been created
180
              ('SAVING', _('saving')),  # changes are being saved
Őry Máté committed
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
              ('READY', _('ready'))]    # template is ready for instantiation
    name = CharField(max_length=100, unique=True,
                     verbose_name=_('name'),
                     help_text=_('Human readable name of template.'))
    description = TextField(verbose_name=_('description'), blank=True)
    parent = ForeignKey('self', null=True, blank=True,
                        verbose_name=_('parent template'),
                        help_text=_('Template which this one is derived of.'))
    system = TextField(verbose_name=_('operating system'),
                       blank=True,
                       help_text=(_('Name of operating system in '
                                    'format like "%s".') %
                                  'Ubuntu 12.04 LTS Desktop amd64'))
    access_method = CharField(max_length=10, choices=ACCESS_METHODS,
                              verbose_name=_('access method'),
                              help_text=_('Primary remote access method.'))
    state = CharField(max_length=10, choices=STATES, default='NEW')
    disks = ManyToManyField(Disk, verbose_name=_('disks'),
                            related_name='template_set',
                            help_text=_('Disks which are to be mounted.'))
    lease = ForeignKey(Lease, related_name='template_set',
                       verbose_name=_('lease'),
                       help_text=_('Expiration times.'))
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223

    class Meta:
        ordering = ['name', ]
        permissions = ()
        verbose_name = _('template')
        verbose_name_plural = _('templates')

    def __unicode__(self):
        return self.name

    def running_instances(self):
        """Returns the number of running instances of the template.
        """
        return self.instance_set.filter(state='RUNNING').count()

    @property
    def os_type(self):
        """Get the type of the template's operating system.
        """
        if self.access_method == 'rdp':
224
            return 'win'
225
        else:
226
            return 'linux'
227 228


Őry Máté committed
229
class InterfaceTemplate(Model):
tarokkk committed
230

231 232 233 234
    """Network interface template for an instance template.

    If the interface is managed, a host will be created for it.
    """
Őry Máté committed
235 236 237 238 239 240 241 242
    vlan = ForeignKey(Vlan, verbose_name=_('vlan'),
                      help_text=_('Network the interface belongs to.'))
    managed = BooleanField(verbose_name=_('managed'), default=True,
                           help_text=_('If a firewall host (i.e. IP address '
                                       'association) should be generated.'))
    template = ForeignKey(InstanceTemplate, verbose_name=_('template'),
                          related_name='interface_set',
                          help_text=_())
243 244 245 246 247 248 249 250

    class Meta:
        permissions = ()
        verbose_name = _('interface template')
        verbose_name_plural = _('interface templates')


class Instance(BaseResourceConfigModel, TimeStampedModel):
tarokkk committed
251

252 253 254 255 256 257 258 259 260 261 262
    """Virtual machine instance.

    Every instance has:
      * a name and a description
      * an optional parent template
      * associated share
      * a generated password for login authentication
      * time of deletion and time of suspension
      * lease times (suspension & deletion)
      * last boot timestamp
      * host node
263
      * current state (libvirt domain state)
264 265
      * time of creation and last modification
      * base resource configuration values
266
      * owner and privilege information
267 268 269 270 271 272 273 274 275
    """
    STATES = [('NOSTATE', _('nostate')),
              ('RUNNING', _('running')),
              ('BLOCKED', _('blocked')),
              ('PAUSED', _('paused')),
              ('SHUTDOWN', _('shutdown')),
              ('SHUTOFF', _('shutoff')),
              ('CRASHED', _('crashed')),
              ('PMSUSPENDED', _('pmsuspended'))]  # libvirt domain states
Őry Máté committed
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
    name = CharField(blank=True, max_length=100, verbose_name=_('name'),
                     help_text=_('Human readable name of instance.'))
    description = TextField(blank=True, verbose_name=_('description'))
    template = ForeignKey(InstanceTemplate, blank=True, null=True,
                          related_name='instance_set',
                          help_text=_('Template the instance derives from.'),
                          verbose_name=_('template'))
    pw = CharField(help_text=_('Original password of the instance.'),
                   max_length=20, verbose_name=_('password'))
    time_of_suspend = DateTimeField(blank=True, default=None, null=True,
                                    verbose_name=_('time of suspend'),
                                    help_text=_('Proposed time of automatic '
                                                'suspension.'))
    time_of_delete = DateTimeField(blank=True, default=None, null=True,
                                   verbose_name=_('time of delete'),
                                   help_text=_('Proposed time of automatic '
                                               'suspension.'))
    active_since = DateTimeField(blank=True, null=True,
                                 help_text=_('Time stamp of successful '
                                             'boot report.'),
                                 verbose_name=_('active since'))
    node = ForeignKey(Node, blank=True, null=True,
                      related_name='instance_set',
                      help_text=_('Current hypervisor of this instance.'),
                      verbose_name=_('host node'))
    state = CharField(choices=STATES, default='NOSTATE', max_length=20)
    disks = ManyToManyField(Disk, related_name='instance_set',
                            help_text=_('Set of mounted disks.'),
                            verbose_name=_('disks'))
    lease = ForeignKey(Lease, help_text=_('Preferred expiration periods.'))
    access_method = CharField(max_length=10, choices=ACCESS_METHODS,
                              help_text=_('Primary remote access method.'),
                              verbose_name=_('access method'))
    vnc_port = IntegerField(verbose_name=_('vnc_port'),
                            help_text=_('TCP port where VNC console listens.'))
    owner = ForeignKey(User)
312 313 314 315 316 317 318 319 320 321 322

    class Meta:
        ordering = ['pk', ]
        permissions = ()
        verbose_name = _('instance')
        verbose_name_plural = _('instances')

    def __unicode__(self):
        return self.name

    @classmethod
323
    def create_from_template(cls, template, owner, **kwargs):
324 325 326 327 328 329 330
        """Create a new instance based on an InstanceTemplate.

        Can also specify parameters as keyword arguments which should override
        template settings.
        """
        # prepare parameters
        kwargs['template'] = template
331
        kwargs['owner'] = owner
332 333 334 335 336 337 338 339
        kwargs.setdefault('name', template.name)
        kwargs.setdefault('description', template.description)
        kwargs.setdefault('pw', pwgen())
        kwargs.setdefault('num_cores', template.num_cores)
        kwargs.setdefault('ram_size', template.ram_size)
        kwargs.setdefault('max_ram_size', template.max_ram_size)
        kwargs.setdefault('arch', template.arch)
        kwargs.setdefault('priority', template.priority)
340 341
        kwargs.setdefault('boot_menu', template.boot_menu)
        kwargs.setdefault('raw_data', template.raw_data)
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
        kwargs.setdefault('lease', template.lease)
        kwargs.setdefault('access_method', template.access_method)
        # create instance and do additional setup
        inst = cls(**kwargs)
        for disk in template.disks:
            inst.disks.add(disk.get_exclusive())
        # save instance
        inst.save()
        # create related entities
        for iftmpl in template.interface_set.all():
            i = Interface.create_from_template(instance=inst, template=iftmpl)
            if i.host:
                i.host.enable_net()
                port, proto = ACCESS_PROTOCOLS[i.access_method][1:3]
                i.host.add_port(proto, i.get_port(), port)

        return inst

Őry Máté committed
360
    @permalink
361
    def get_absolute_url(self):
Bach Dániel committed
362
        return ('dashboard.views.detail', None, {'id': self.id})
363 364

    @property
365 366 367 368 369 370 371 372 373
    def vm_name(self):
        """Name of the VM instance.

        This is a unique identifier as opposed to the 'name' attribute, which
        is just for display.
        """
        return 'cloud-' + str(self.id)

    @property
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
    def primary_host(self):
        interfaces = self.interface_set.select_related('host')
        hosts = [i.host for i in interfaces if i.host]
        if not hosts:
            return None
        hs = [h for h in hosts if h.ipv6]
        if hs:
            return hs[0]
        hs = [h for h in hosts if not h.shared_ip]
        if hs:
            return hs[0]
        return hosts[0]

    @property
    def ipv4(self):
389 390
        """Primary IPv4 address of the instance.
        """
391 392 393 394
        return self.primary_host.ipv4 if self.primary_host else None

    @property
    def ipv6(self):
395 396
        """Primary IPv6 address of the instance.
        """
397 398 399 400
        return self.primary_host.ipv6 if self.primary_host else None

    @property
    def mac(self):
401 402
        """Primary MAC address of the instance.
        """
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
        return self.primary_host.mac if self.primary_host else None

    @property
    def uptime(self):
        """Uptime of the instance.
        """
        if self.active_since:
            return timezone.now() - self.active_since
        else:
            return timedelta()  # zero

    def get_age(self):
        """Deprecated. Use uptime instead.

        Get age of VM in seconds.
        """
        return self.uptime.seconds

    @property
    def waiting(self):
        """Indicates whether the instance's waiting for an operation to finish.
        """
425
        return self.activity_log.filter(finished__isnull=True).exists()
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454

    def get_connect_port(self, use_ipv6=False):
        """Get public port number for default access method.
        """
        port, proto = ACCESS_PROTOCOLS[self.access_method][1:3]
        if self.primary_host:
            endpoints = self.primary_host.get_public_endpoints(port, proto)
            endpoint = endpoints['ipv6'] if use_ipv6 else endpoints['ipv4']
            return endpoint[1] if endpoint else None
        else:
            return None

    def get_connect_host(self, use_ipv6=False):
        """Get public hostname.
        """
        if not self.firewall_host:
            return _('None')
        proto = 'ipv6' if use_ipv6 else 'ipv4'
        return self.firewall_host.get_hostname(proto=proto)

    def get_connect_uri(self, use_ipv6=False):
        """Get access parameters in URI format.
        """
        try:
            port = self.get_connect_port(use_ipv6=use_ipv6)
            host = self.get_connect_host(use_ipv6=use_ipv6)
            proto = self.access_method
            if proto == 'ssh':
                proto = 'sshterm'
455 456 457
            return ('%(proto)s:cloud:%(pw)s:%(host)s:%(port)d' %
                    {'port': port, 'proto': proto, 'pw': self.pw,
                     'host': host})
458 459 460
        except:
            return

tarokkk committed
461 462
    def get_vm_desc(self):
        return {
463
            'name': self.vm_name,
464 465 466 467 468 469 470 471
            'vcpu': self.num_cores,
            'memory': self.ram_size,
            'memory_max': self.max_ram_size,
            'cpu_share': self.priority,
            'arch': self.arch,
            'boot_menu': self.boot_menu,
            'network_list': [n.get_vmnetwork_desc()
                             for n in self.interface_set.all()],
472 473 474 475 476
            'disk_list': [d.get_vmdisk_desc() for d in self.disks.all()],
            'graphics': {
                'type': 'vnc',
                'listen': '0.0.0.0',
                'passwd': '',
Guba Sándor committed
477
                'port': self.vnc_port
478
            },
Guba Sándor committed
479
            'raw_data': "" if not self.raw_data else self.raw_data
480
        }
tarokkk committed
481

482 483 484
    def deploy_async(self, user=None):
        """ Launch celery task to handle the job asynchronously.
        """
Guba Sándor committed
485
        vm_manager.deploy.apply_async(args=[self, user], queue="localhost.man")
tarokkk committed
486

487 488
    def deploy(self, user=None, task_uuid=None):
        """ Deploy new virtual machine with network
tarokkk committed
489
        1. Schedule
490
        """
Guba Sándor committed
491 492 493 494 495
        act = InstanceActivity(activity_code='vm.Instance.deploy')
        act.instance = self
        act.user = user
        act.started = timezone.now()
        act.task_uuid = task_uuid
496 497
        act.save()

tarokkk committed
498 499
        # Schedule
        act.update_state("PENDING")
500
        self.node = scheduler.get_node(self, Node.objects.all())
501
        self.save()
tarokkk committed
502 503 504

        # Create virtual images
        act.update_state("PREPARING DISKS")
505
        for disk in self.disks.all():
tarokkk committed
506 507 508 509
            disk.deploy()

        # Deploy VM on remote machine
        act.update_state("DEPLOYING VM")
Guba Sándor committed
510 511
        tasks.create.apply_async(args=[self.get_vm_desc()],
                                 queue=self.node.host.hostname + ".vm").get()
tarokkk committed
512 513 514 515 516 517 518 519

        # Estabilish network connection (vmdriver)
        act.update_state("DEPLOYING NET")
        for net in self.interface_set.all():
            net.deploy()

        # Resume vm
        act.update_state("BOOTING")
Guba Sándor committed
520 521
        tasks.resume.apply_async(args=[self.vm_name],
                                 queue=self.node + ".vm").get()
tarokkk committed
522

523
        act.finish(result='SUCCESS')
524

Guba Sándor committed
525 526
    def stop_async(self, user=None):
        vm_manager.stop.apply_async(args=[self, user], queue="localhost.man")
527

Guba Sándor committed
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
    def stop(self, user=None, task_uuid=None):
        act = InstanceActivity(activity_code='vm.Instance.stop')
        act.instance = self
        act.user = user
        act.started = timezone.now()
        act.task_uuid = task_uuid
        act.save()
        tasks.stop.apply_async(args=[self.get_vm_desc()],
                               queue=self.node.host.hostname + ".vm").get()

    def resume_async(self, user=None):
        vm_manager.resume.apply_async(args=[self, user], queue="localhost.man")

    def resume(self, user=None, task_uuid=None):
        act = InstanceActivity(activity_code='vm.Instance.resume')
        act.instance = self
        act.user = user
        act.started = timezone.now()
        act.task_uuid = task_uuid
        act.save()
        tasks.resume.apply_async(args=[self.get_vm_desc()],
                                 queue=self.node.host.hostname + ".vm").get()
550

Guba Sándor committed
551 552 553
    def poweroff_async(self, user=None):
        vm_manager.power_off.apply_async(args=[self, user],
                                         queue="localhost.man")
554

Guba Sándor committed
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
    def poweroff(self, user=None, task_uuid=None):
        act = InstanceActivity(activity_code='vm.Instance.power_off')
        act.instance = self
        act.user = user
        act.started = timezone.now()
        act.task_uuid = task_uuid
        act.save()
        tasks.power_off.apply_async(args=[self.get_vm_desc()],
                                    queue=self.node.host.hostname + ".vm"
                                    ).get()

    def restart_async(self, user=None):
        vm_manager.restart.apply_async(args=[self, user],
                                       queue="localhost.man")

    def restart(self, user=None, task_uuid=None):
        act = InstanceActivity(activity_code='vm.Instance.restart')
        act.instance = self
        act.user = user
        act.started = timezone.now()
        act.task_uuid = task_uuid
        act.save()
        tasks.restart.apply_async(args=[self.get_vm_desc()],
                                  queue=self.node.host.hostname + ".vm").get()

    def save_as_async(self, user=None):
        vm_manager.save_as.apply_async(
            args=[self, user], queue="localhost.man")

    def save_as(self, user=None, task_uuid=None):
        act = InstanceActivity(activity_code='vm.Instance.restart')
        act.instance = self
        act.user = user
        act.started = timezone.now()
        act.task_uuid = task_uuid
        act.save()
        tasks.save_as.apply_async(args=[self.get_vm_desc()],
                                  queue=self.node.host.hostname + ".vm").get()
593 594 595 596 597 598 599 600 601 602 603 604 605

    def renew(self, which='both'):
        """Renew virtual machine instance leases.
        """
        if which not in ['suspend', 'delete', 'both']:
            raise ValueError('No such expiration type.')
        if which in ['suspend', 'both']:
            self.time_of_suspend = timezone.now() + self.lease.suspend_interval
        if which in ['delete', 'both']:
            self.time_of_delete = timezone.now() + self.lease.delete_interval
        self.save()


606
@receiver(pre_delete, sender=Instance, dispatch_uid='delete_instance_pre')
607 608 609 610 611
def delete_instance_pre(sender, instance, using, **kwargs):
    # TODO implement
    pass


612
class InstanceActivity(TimeStampedModel):
Őry Máté committed
613 614 615 616 617 618 619 620 621 622 623
    activity_code = CharField(verbose_name=_('activity_code'), max_length=100)
    task_uuid = CharField(verbose_name=_('task_uuid'), blank=True,
                          max_length=50, null=True, unique=True)
    instance = ForeignKey(Instance, verbose_name=_('instance'),
                          related_name='activity_log')
    user = ForeignKey(User, verbose_name=_('user'), blank=True, null=True)
    started = DateTimeField(verbose_name=_('started'), blank=True, null=True)
    finished = DateTimeField(verbose_name=_('finished'), blank=True, null=True)
    result = TextField(verbose_name=_('result'), blank=True, null=True)
    state = CharField(verbose_name=_('state'),
                      default='PENDING', max_length=50)
tarokkk committed
624

625 626 627
    def update_state(self, new_state):
        self.state = new_state
        self.save()
tarokkk committed
628

629 630 631 632 633
    def finish(self, result=None):
        if not self.finished:
            self.finished = timezone.now()
            self.result = result
            self.save()
tarokkk committed
634

635

Őry Máté committed
636
class Interface(Model):
637 638 639

    """Network interface for an instance.
    """
Őry Máté committed
640 641 642 643 644
    vlan = ForeignKey(Vlan, verbose_name=_('vlan'),
                      related_name="vm_interface")
    host = ForeignKey(Host, verbose_name=_('host'),  blank=True, null=True)
    instance = ForeignKey(Instance, verbose_name=_('instance'),
                          related_name='interface_set')
645

646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663
    @property
    def mac(self):
        try:
            return self.host.mac
        except:
            return Interface.generate_mac(self.instance, self.vlan)

    @classmethod
    def generate_mac(cls, instance, vlan):
        """Generate MAC address for a VM instance on a VLAN.
        """
        # MAC 02:XX:XX:XX:XX:XX
        #        \________/\__/
        #           VM ID   VLAN ID
        i = instance.id & 0xfffffff
        v = vlan.vid & 0xfff
        m = (0x02 << 40) | (i << 12) | v
        return EUI(m)
664 665 666 667 668

    def get_vmnetwork_desc(self):
        return {
            'name': 'cloud-' + self.instance.id + '-' + self.vlan.vid,
            'bridge': 'cloud',
669
            'mac': self.mac,
670 671 672 673 674 675 676 677 678 679 680
            'ipv4': self.host.ipv4 if self.host is not None else None,
            'ipv6': self.host.ipv6 if self.host is not None else None,
            'vlan': self.vlan.vid,
            'managed': self.host is not None
        }

    @classmethod
    def create_from_template(cls, instance, template):
        """Create a new interface for an instance based on an
           InterfaceTemplate.
        """
681 682 683
        host = (Host(vlan=template.vlan, mac=cls.generate_mac(instance,
                                                              template.vlan))
                if template.managed else None)
684 685 686
        iface = cls(vlan=template.vlan, host=host, instance=instance)
        iface.save()
        return iface