instance.py 27.2 KB
Newer Older
1
from __future__ import absolute_import, unicode_literals
2
from datetime import timedelta
3
from logging import getLogger
Őry Máté committed
4
from importlib import import_module
5

6
import django.conf
7 8 9
from django.db.models import (BooleanField, CharField, DateTimeField,
                              IntegerField, ForeignKey, Manager,
                              ManyToManyField, permalink, TextField)
Őry Máté committed
10 11
from django.contrib.auth.models import User
from django.core import signing
12
from django.dispatch import Signal
13 14
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
Dudás Ádám committed
15

16
from model_utils.models import TimeStampedModel
17
from taggit.managers import TaggableManager
18

Dudás Ádám committed
19
from acl.models import AclBase
20
from storage.models import Disk
Őry Máté committed
21 22
from ..tasks import local_tasks, vm_tasks
from .activity import instance_activity
23
from .common import BaseResourceConfigModel, Lease
24 25
from .network import Interface
from .node import Node, Trait
26

27
logger = getLogger(__name__)
Őry Máté committed
28 29
pre_state_changed = Signal(providing_args=["new_state"])
post_state_changed = Signal(providing_args=["new_state"])
30
pwgen = User.objects.make_random_password
31
scheduler = import_module(name=django.conf.settings.VM_SCHEDULER)
Őry Máté committed
32

33
ACCESS_PROTOCOLS = django.conf.settings.VM_ACCESS_PROTOCOLS
Őry Máté committed
34 35
ACCESS_METHODS = [(key, name) for key, (name, port, transport)
                  in ACCESS_PROTOCOLS.iteritems()]
36
VNC_PORT_RANGE = (2000, 65536)  # inclusive start, exclusive end
37 38


39
def find_unused_vnc_port():
40
    used = set(Instance.objects.values_list('vnc_port', flat=True))
41 42 43 44 45 46 47
    for p in xrange(*VNC_PORT_RANGE):
        if p not in used:
            return p
    else:
        raise Exception("No unused port could be found for VNC.")


48
class InstanceActiveManager(Manager):
Dudás Ádám committed
49

50 51 52 53 54
    def get_query_set(self):
        return super(InstanceActiveManager,
                     self).get_query_set().filter(destroyed=None)


55
class VirtualMachineDescModel(BaseResourceConfigModel):
56

57 58 59 60 61 62 63 64
    """Abstract base for virtual machine describing models.
    """
    access_method = CharField(max_length=10, choices=ACCESS_METHODS,
                              verbose_name=_('access method'),
                              help_text=_('Primary remote access method.'))
    boot_menu = BooleanField(verbose_name=_('boot menu'), default=False,
                             help_text=_(
                                 'Show boot device selection menu on boot.'))
65
    lease = ForeignKey(Lease, help_text=_("Preferred expiration periods."))
66 67
    raw_data = TextField(verbose_name=_('raw_data'), blank=True, help_text=_(
        'Additional libvirt domain parameters in XML format.'))
Dudás Ádám committed
68 69 70 71 72
    req_traits = ManyToManyField(Trait, blank=True,
                                 help_text=_("A set of traits required for a "
                                             "node to declare to be suitable "
                                             "for hosting the VM."),
                                 verbose_name=_("required traits"))
Dudás Ádám committed
73
    tags = TaggableManager(blank=True, verbose_name=_("tags"))
74 75 76 77 78 79

    class Meta:
        abstract = True


class InstanceTemplate(VirtualMachineDescModel, TimeStampedModel):
tarokkk committed
80

81 82 83 84 85 86 87 88 89 90 91 92 93 94
    """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
95
    STATES = [('NEW', _('new')),        # template has just been created
96
              ('SAVING', _('saving')),  # changes are being saved
Őry Máté committed
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
              ('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'))
    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.'))
114 115

    class Meta:
Őry Máté committed
116 117
        app_label = 'vm'
        db_table = 'vm_instancetemplate'
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
        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':
136
            return 'win'
137
        else:
138
            return 'linux'
139 140


141
class Instance(AclBase, VirtualMachineDescModel, TimeStampedModel):
tarokkk committed
142

143 144 145 146 147 148 149 150 151 152 153
    """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
154
      * current state (libvirt domain state)
155 156
      * time of creation and last modification
      * base resource configuration values
157
      * owner and privilege information
158 159 160 161 162 163 164 165 166
    """
    STATES = [('NOSTATE', _('nostate')),
              ('RUNNING', _('running')),
              ('BLOCKED', _('blocked')),
              ('PAUSED', _('paused')),
              ('SHUTDOWN', _('shutdown')),
              ('SHUTOFF', _('shutoff')),
              ('CRASHED', _('crashed')),
              ('PMSUSPENDED', _('pmsuspended'))]  # libvirt domain states
167 168 169 170 171
    ACL_LEVELS = (
        ('user', _('user')),          # see all details
        ('operator', _('operator')),  # console, networking, change state
        ('owner', _('owner')),        # superuser, can delete, delegate perms
    )
Őry Máté committed
172
    name = CharField(blank=True, max_length=100, verbose_name=_('name'),
173
                     help_text=_("Human readable name of instance."))
Őry Máté committed
174 175 176
    description = TextField(blank=True, verbose_name=_('description'))
    template = ForeignKey(InstanceTemplate, blank=True, null=True,
                          related_name='instance_set',
177
                          help_text=_("Template the instance derives from."),
Őry Máté committed
178
                          verbose_name=_('template'))
179
    pw = CharField(help_text=_("Original password of the instance."),
Őry Máté committed
180 181 182
                   max_length=20, verbose_name=_('password'))
    time_of_suspend = DateTimeField(blank=True, default=None, null=True,
                                    verbose_name=_('time of suspend'),
183 184
                                    help_text=_("Proposed time of automatic "
                                                "suspension."))
Őry Máté committed
185 186
    time_of_delete = DateTimeField(blank=True, default=None, null=True,
                                   verbose_name=_('time of delete'),
187 188
                                   help_text=_("Proposed time of automatic "
                                               "deletion."))
Őry Máté committed
189
    active_since = DateTimeField(blank=True, null=True,
190 191
                                 help_text=_("Time stamp of successful "
                                             "boot report."),
Őry Máté committed
192 193 194
                                 verbose_name=_('active since'))
    node = ForeignKey(Node, blank=True, null=True,
                      related_name='instance_set',
195
                      help_text=_("Current hypervisor of this instance."),
Őry Máté committed
196 197 198
                      verbose_name=_('host node'))
    state = CharField(choices=STATES, default='NOSTATE', max_length=20)
    disks = ManyToManyField(Disk, related_name='instance_set',
199
                            help_text=_("Set of mounted disks."),
Őry Máté committed
200
                            verbose_name=_('disks'))
201 202 203
    vnc_port = IntegerField(blank=True, default=None, null=True,
                            help_text=_("TCP port where VNC console listens."),
                            unique=True, verbose_name=_('vnc_port'))
Őry Máté committed
204
    owner = ForeignKey(User)
Dudás Ádám committed
205
    destroyed = DateTimeField(blank=True, null=True,
206 207
                              help_text=_("The virtual machine's time of "
                                          "destruction."))
208 209
    objects = Manager()
    active = InstanceActiveManager()
210 211

    class Meta:
Őry Máté committed
212 213
        app_label = 'vm'
        db_table = 'vm_instance'
214
        ordering = ['pk', ]
215 216 217 218 219 220
        permissions = (
            ('access_console', _('Can access the graphical console of a VM.')),
            ('change_resources', _('Can change resources of a running VM.')),
            ('set_resources', _('Can change resources of a new VM.')),
            ('config_ports', _('Can configure port forwards.')),
        )
221 222 223 224
        verbose_name = _('instance')
        verbose_name_plural = _('instances')

    def __unicode__(self):
225 226
        parts = [self.name, "(" + str(self.id) + ")"]
        return " ".join([s for s in parts if s != ""])
227

228 229 230 231 232
    def clean(self, *args, **kwargs):
        if self.time_of_delete is None:
            self.renew(which='delete')
        super(Instance, self).clean(*args, **kwargs)

233
    @classmethod
234
    def create_from_template(cls, template, owner, disks=None, networks=None,
Dudás Ádám committed
235
                             req_traits=None, tags=None, **kwargs):
236 237 238 239 240
        """Create a new instance based on an InstanceTemplate.

        Can also specify parameters as keyword arguments which should override
        template settings.
        """
241 242 243 244 245 246 247 248 249 250 251 252 253 254
        insts = cls.mass_create_from_template(template, owner, disks=disks,
                                              networks=networks, tags=tags,
                                              req_traits=req_traits, **kwargs)
        return insts[0]

    @classmethod
    def mass_create_from_template(cls, template, owner, amount=1, disks=None,
                                  networks=None, req_traits=None, tags=None,
                                  **kwargs):
        """Mass-create new instances based on an InstanceTemplate.

        Can also specify parameters as keyword arguments which should override
        template settings.
        """
255
        disks = template.disks.all() if disks is None else disks
256

257 258 259
        networks = (template.interface_set.all() if networks is None
                    else networks)

Dudás Ádám committed
260 261 262
        req_traits = (template.req_traits.all() if req_traits is None
                      else req_traits)

Dudás Ádám committed
263 264
        tags = template.tags.all() if tags is None else tags

265
        # prepare parameters
Dudás Ádám committed
266 267 268 269 270 271 272
        common_fields = ['name', 'description', 'num_cores', 'ram_size',
                         'max_ram_size', 'arch', 'priority', 'boot_menu',
                         'raw_data', 'lease', 'access_method']
        params = dict(template=template, owner=owner, pw=pwgen())
        params.update([(f, getattr(template, f)) for f in common_fields])
        params.update(kwargs)  # override defaults w/ user supplied values

273 274 275 276 277
        return [cls.__create_instance(params, disks, networks, req_traits,
                                      tags) for i in xrange(amount)]

    @classmethod
    def __create_instance(cls, params, disks, networks, req_traits, tags):
278
        # create instance and do additional setup
Dudás Ádám committed
279 280
        inst = cls(**params)

281
        # save instance
282
        inst.clean()
283
        inst.save()
Dudás Ádám committed
284

285
        # create related entities
Dudás Ádám committed
286
        inst.disks.add(*[disk.get_exclusive() for disk in disks])
287

288
        for net in networks:
289 290
            i = Interface.create(instance=inst, vlan=net.vlan,
                                 owner=inst.owner, managed=net.managed)
291 292
            if i.host:
                i.host.enable_net()
293 294 295
                port, proto = ACCESS_PROTOCOLS[i.instance.access_method][1:3]
                # TODO fix this port fw
                i.host.add_port(proto, private=port)
296

Dudás Ádám committed
297
        inst.req_traits.add(*req_traits)
Dudás Ádám committed
298 299
        inst.tags.add(*tags)

300 301
        return inst

Őry Máté committed
302
    @permalink
303
    def get_absolute_url(self):
304
        return ('dashboard.views.detail', None, {'pk': self.id})
305 306

    @property
307 308 309 310 311 312 313 314 315
    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
316 317 318 319 320 321
    def mem_dump(self):
        """Return the path for the memory dump.

        It is always on the first hard drive storage named cloud-<id>.dump
        """
        path = self.disks.all()[0].datastore.path
Dudás Ádám committed
322
        return path + '/' + self.vm_name + '.dump'
323 324

    @property
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
    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):
340 341
        """Primary IPv4 address of the instance.
        """
342 343 344 345
        return self.primary_host.ipv4 if self.primary_host else None

    @property
    def ipv6(self):
346 347
        """Primary IPv6 address of the instance.
        """
348 349 350 351
        return self.primary_host.ipv6 if self.primary_host else None

    @property
    def mac(self):
352 353
        """Primary MAC address of the instance.
        """
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
        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.
        """
376
        return self.activity_log.filter(finished__isnull=True).exists()
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391

    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.
        """
392
        if not self.interface_set.exclude(host=None):
393 394
            return _('None')
        proto = 'ipv6' if use_ipv6 else 'ipv4'
395 396
        return self.interface_set.exclude(host=None)[0].host.get_hostname(
            proto=proto)
397 398 399 400 401 402 403 404 405 406

    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'
407 408 409
            return ('%(proto)s:cloud:%(pw)s:%(host)s:%(port)d' %
                    {'port': port, 'proto': proto, 'pw': self.pw,
                     'host': host})
410 411 412
        except:
            return

tarokkk committed
413 414
    def get_vm_desc(self):
        return {
415
            'name': self.vm_name,
416
            'vcpu': self.num_cores,
417
            'memory': int(self.ram_size) * 1024,  # convert from MiB to KiB
Őry Máté committed
418
            'memory_max': int(self.max_ram_size) * 1024,  # convert MiB to KiB
419 420 421 422 423
            '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()],
424 425 426 427 428
            '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
429
                'port': self.vnc_port
430
            },
431
            'boot_token': signing.dumps(self.id, salt='activate'),
Guba Sándor committed
432
            'raw_data': "" if not self.raw_data else self.raw_data
433
        }
tarokkk committed
434

435 436 437 438
    def get_remote_queue_name(self, queue_id):
        """Get the remote worker queue name of this instance with the specified
           queue ID.
        """
439
        return self.node.get_remote_queue_name(queue_id)
440

Dudás Ádám committed
441 442 443 444 445 446 447 448 449 450 451
    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()

452
    def deploy(self, user=None, task_uuid=None):
Dudás Ádám committed
453 454 455 456 457 458 459 460 461 462 463
        """Deploy new virtual machine with network

        :param self: The virtual machine to deploy.
        :type self: vm.models.Instance

        :param user: The user who's issuing the command.
        :type user: django.contrib.auth.models.User

        :param task_uuid: The task's UUID, if the command is being executed
                          asynchronously.
        :type task_uuid: str
464
        """
465 466
        with instance_activity(code_suffix='deploy', instance=self,
                               task_uuid=task_uuid, user=user) as act:
467

468 469 470
            # Clear destroyed flag
            self.destroyed = None

471 472
            # Find unused port for VNC
            if self.vnc_port is None:
473
                self.vnc_port = find_unused_vnc_port()
474

475
            # Schedule
476
            if self.node is None:
477
                self.node = scheduler.select_node(self, Node.objects.all())
Dudás Ádám committed
478

479
            self.save()
tarokkk committed
480

481 482 483 484
            # Deploy virtual images
            with act.sub_activity('deploying_disks'):
                for disk in self.disks.all():
                    disk.deploy()
485

486
            queue_name = self.get_remote_queue_name('vm')
Dudás Ádám committed
487

488 489
            # Deploy VM on remote machine
            with act.sub_activity('deploying_vm'):
490
                vm_tasks.deploy.apply_async(args=[self.get_vm_desc()],
491
                                            queue=queue_name).get()
tarokkk committed
492

493 494 495 496
            # Estabilish network connection (vmdriver)
            with act.sub_activity('deploying_net'):
                for net in self.interface_set.all():
                    net.deploy()
tarokkk committed
497

498 499 500
            # Generate context
            # TODO

501 502 503 504
            # Resume vm
            with act.sub_activity('booting'):
                vm_tasks.resume.apply_async(args=[self.vm_name],
                                            queue=queue_name).get()
505

506 507
            self.renew('suspend')

508 509 510
    def deploy_async(self, user=None):
        """Execute deploy asynchronously.
        """
511 512
        logger.debug('Calling async local_tasks.deploy(%s, %s)',
                     unicode(self), unicode(user))
513 514
        return local_tasks.deploy.apply_async(args=[self, user],
                                              queue="localhost.man")
515

516
    def destroy(self, user=None, task_uuid=None):
Dudás Ádám committed
517 518 519 520 521 522 523 524 525 526 527
        """Remove virtual machine and its networks.

        :param self: The virtual machine to destroy.
        :type self: vm.models.Instance

        :param user: The user who's issuing the command.
        :type user: django.contrib.auth.models.User

        :param task_uuid: The task's UUID, if the command is being executed
                          asynchronously.
        :type task_uuid: str
528
        """
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
        with instance_activity(code_suffix='destroy', instance=self,
                               task_uuid=task_uuid, user=user) as act:

            # Destroy networks
            with act.sub_activity('destroying_net'):
                for net in self.interface_set.all():
                    net.destroy()

            # Destroy virtual machine
            with act.sub_activity('destroying_vm'):
                queue_name = self.get_remote_queue_name('vm')
                vm_tasks.destroy.apply_async(args=[self.vm_name],
                                             queue=queue_name).get()

            # Destroy disks
            with act.sub_activity('destroying_disks'):
                for disk in self.disks.all():
                    disk.destroy()

548 549 550 551
            # Clear node and VNC port association
            self.node = None
            self.vnc_port = None

Dudás Ádám committed
552
            self.destroyed = timezone.now()
553
            self.save()
554 555

    def destroy_async(self, user=None):
Dudás Ádám committed
556
        """Execute destroy asynchronously.
557
        """
558 559
        return local_tasks.destroy.apply_async(args=[self, user],
                                               queue="localhost.man")
560 561 562 563

    def sleep(self, user=None, task_uuid=None):
        """Suspend virtual machine with memory dump.
        """
564 565
        with instance_activity(code_suffix='sleep', instance=self,
                               task_uuid=task_uuid, user=user):
566

567 568 569
            queue_name = self.get_remote_queue_name('vm')
            vm_tasks.sleep.apply_async(args=[self.vm_name, self.mem_dump],
                                       queue=queue_name).get()
Guba Sándor committed
570

571
    def sleep_async(self, user=None):
Dudás Ádám committed
572
        """Execute sleep asynchronously.
573
        """
574 575
        return local_tasks.sleep.apply_async(args=[self, user],
                                             queue="localhost.man")
Guba Sándor committed
576

577
    def wake_up(self, user=None, task_uuid=None):
578 579
        with instance_activity(code_suffix='wake_up', instance=self,
                               task_uuid=task_uuid, user=user):
580

581 582 583
            queue_name = self.get_remote_queue_name('vm')
            vm_tasks.resume.apply_async(args=[self.vm_name, self.dump_mem],
                                        queue=queue_name).get()
584

585
    def wake_up_async(self, user=None):
Dudás Ádám committed
586
        """Execute wake_up asynchronously.
587
        """
588 589
        return local_tasks.wake_up.apply_async(args=[self, user],
                                               queue="localhost.man")
590

591 592 593
    def shutdown(self, user=None, task_uuid=None):
        """Shutdown virtual machine with ACPI signal.
        """
594 595
        with instance_activity(code_suffix='shutdown', instance=self,
                               task_uuid=task_uuid, user=user):
596

597 598 599
            queue_name = self.get_remote_queue_name('vm')
            vm_tasks.shutdown.apply_async(args=[self.vm_name],
                                          queue=queue_name).get()
Guba Sándor committed
600

601 602
    def shutdown_async(self, user=None):
        """Execute shutdown asynchronously.
603
        """
604 605
        return local_tasks.shutdown.apply_async(args=[self, user],
                                                queue="localhost.man")
Guba Sándor committed
606

607 608 609
    def reset(self, user=None, task_uuid=None):
        """Reset virtual machine (reset button)
        """
610 611
        with instance_activity(code_suffix='reset', instance=self,
                               task_uuid=task_uuid, user=user):
612

613 614 615
            queue_name = self.get_remote_queue_name('vm')
            vm_tasks.restart.apply_async(args=[self.vm_name],
                                         queue=queue_name).get()
Guba Sándor committed
616

617 618
    def reset_async(self, user=None):
        """Execute reset asynchronously.
619
        """
620 621
        return local_tasks.restart.apply_async(args=[self, user],
                                               queue="localhost.man")
Guba Sándor committed
622

623
    def reboot(self, user=None, task_uuid=None):
Dudás Ádám committed
624
        """Reboot virtual machine with Ctrl+Alt+Del signal.
625
        """
626 627
        with instance_activity(code_suffix='reboot', instance=self,
                               task_uuid=task_uuid, user=user):
628

629 630 631
            queue_name = self.get_remote_queue_name('vm')
            vm_tasks.reboot.apply_async(args=[self.vm_name],
                                        queue=queue_name).get()
632

633
    def reboot_async(self, user=None):
634
        """Execute reboot asynchronously. """
635 636
        return local_tasks.reboot.apply_async(args=[self, user],
                                              queue="localhost.man")
637

638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
    def migrate_async(self, to_node, user=None):
        """Execute migrate asynchronously. """
        return local_tasks.migrate.apply_async(args=[self, to_node, user],
                                               queue="localhost.man")

    def migrate(self, to_node, user=None, task_uuid=None):
        """Live migrate running vm to another node. """
        with instance_activity(code_suffix='migrate', instance=self,
                               task_uuid=task_uuid, user=user) as act:
            # Destroy networks
            with act.sub_activity('destroying_net'):
                for net in self.interface_set.all():
                    net.destroy()

            with act.sub_activity('migrate_vm'):
                queue_name = self.get_remote_queue_name('vm')
                vm_tasks.migrate.apply_async(args=[self.vm_name,
                                             to_node.host.hostname],
                                             queue=queue_name).get()
            # Refresh node information
            self.node = to_node
            self.save()
            # Estabilish network connection (vmdriver)
            with act.sub_activity('deploying_net'):
                for net in self.interface_set.all():
                    net.deploy()

665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697
    def save_as_template(self, name, **kwargs):
        # prepare parameters
        kwargs.setdefault('name', name)
        kwargs.setdefault('description', self.description)
        kwargs.setdefault('parent', self.template)
        kwargs.setdefault('num_cores', self.num_cores)
        kwargs.setdefault('ram_size', self.ram_size)
        kwargs.setdefault('max_ram_size', self.max_ram_size)
        kwargs.setdefault('arch', self.arch)
        kwargs.setdefault('priority', self.priority)
        kwargs.setdefault('boot_menu', self.boot_menu)
        kwargs.setdefault('raw_data', self.raw_data)
        kwargs.setdefault('lease', self.lease)
        kwargs.setdefault('access_method', self.access_method)
        kwargs.setdefault('system', self.template.system
                          if self.template else None)
        # create template and do additional setup
        tmpl = InstanceTemplate(**kwargs)
        # save template
        tmpl.save()
        # create related entities
        for disk in self.disks.all():
            try:
                d = disk.save_as()
            except Disk.WrongDiskTypeError:
                d = disk

            tmpl.disks.add(d)

        for i in self.interface_set.all():
            i.save_as_template(tmpl)

        return tmpl
tarokkk committed
698

699 700 701 702
    def state_changed(self, new_state):
        logger.debug('Instance %s state changed '
                     '(db: %s, new: %s)',
                     self, self.state, new_state)
703 704 705 706 707 708 709 710 711
        try:
            pre_state_changed.send(sender=self, new_state=new_state)
        except Exception as e:
            logger.info('Instance %s state change ignored: %s',
                        unicode(self), unicode(e))
        else:
            self.state = new_state
            self.save()
            post_state_changed.send(sender=self, new_state=new_state)