operations.py 27.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE.  If not, see <http://www.gnu.org/licenses/>.

18
from __future__ import absolute_import, unicode_literals
Dudás Ádám committed
19
from logging import getLogger
20
from re import search
Őry Máté committed
21
from string import ascii_lowercase
Dudás Ádám committed
22

23
from django.core.exceptions import PermissionDenied
Dudás Ádám committed
24
from django.utils import timezone
25
from django.utils.translation import ugettext_lazy as _, ugettext_noop
Dudás Ádám committed
26 27

from celery.exceptions import TimeLimitExceeded
28

29
from common.models import create_readable, humanize_exception
30
from common.operations import Operation, register_operation
31 32 33
from .tasks.local_tasks import (
    abortable_async_instance_operation, abortable_async_node_operation,
)
34
from .models import (
35
    Instance, InstanceActivity, InstanceTemplate, Interface, Node,
36
    NodeActivity, pwgen
37
)
38
from .tasks import agent_tasks
Dudás Ádám committed
39 40

logger = getLogger(__name__)
41 42


43
class InstanceOperation(Operation):
44
    acl_level = 'owner'
45
    async_operation = abortable_async_instance_operation
46
    host_cls = Instance
47
    concurrency_check = True
48 49
    accept_states = None
    deny_states = None
Dudás Ádám committed
50

51
    def __init__(self, instance):
52
        super(InstanceOperation, self).__init__(subject=instance)
53 54 55
        self.instance = instance

    def check_precond(self):
56 57
        if self.instance.destroyed_at:
            raise self.instance.InstanceDestroyedError(self.instance)
58 59 60 61 62 63 64 65 66 67 68 69 70 71
        if self.accept_states:
            if self.instance.status not in self.accept_states:
                logger.debug("precond failed for %s: %s not in %s",
                             unicode(self.__class__),
                             unicode(self.instance.status),
                             unicode(self.accept_states))
                raise self.instance.WrongStateError(self.instance)
        if self.deny_states:
            if self.instance.status in self.deny_states:
                logger.debug("precond failed for %s: %s in %s",
                             unicode(self.__class__),
                             unicode(self.instance.status),
                             unicode(self.accept_states))
                raise self.instance.WrongStateError(self.instance)
72 73

    def check_auth(self, user):
74
        if not self.instance.has_level(user, self.acl_level):
75 76 77
            raise humanize_exception(ugettext_noop(
                "%(acl_level)s level is required for this operation."),
                PermissionDenied(), acl_level=self.acl_level)
78

79
        super(InstanceOperation, self).check_auth(user=user)
80

81 82
    def create_activity(self, parent, user, kwargs):
        name = self.get_activity_name(kwargs)
83 84 85 86 87 88 89 90 91 92
        if parent:
            if parent.instance != self.instance:
                raise ValueError("The instance associated with the specified "
                                 "parent activity does not match the instance "
                                 "bound to the operation.")
            if parent.user != user:
                raise ValueError("The user associated with the specified "
                                 "parent activity does not match the user "
                                 "provided as parameter.")

93 94
            return parent.create_sub(code_suffix=self.activity_code_suffix,
                                     readable_name=name)
95 96 97
        else:
            return InstanceActivity.create(
                code_suffix=self.activity_code_suffix, instance=self.instance,
98 99
                readable_name=name, user=user,
                concurrency_check=self.concurrency_check)
100

101 102 103 104 105
    def is_preferred(self):
        """If this is the recommended op in the current state of the instance.
        """
        return False

106

107 108 109 110 111 112
class AddInterfaceOperation(InstanceOperation):
    activity_code_suffix = 'add_interface'
    id = 'add_interface'
    name = _("add interface")
    description = _("Add a new network interface for the specified VLAN to "
                    "the VM.")
113
    required_perms = ()
114
    accept_states = ('STOPPED', 'PENDING', 'RUNNING')
115

116 117 118 119 120 121 122
    def rollback(self, net, activity):
        with activity.sub_activity(
            'destroying_net',
                readable_name=ugettext_noop("destroy network (rollback)")):
            net.destroy()
            net.delete()

123
    def _operation(self, activity, user, system, vlan, managed=None):
124
        if not vlan.has_level(user, 'user'):
125 126 127
            raise humanize_exception(ugettext_noop(
                "User acces to vlan %(vlan)s is required."),
                PermissionDenied(), vlan=vlan)
128 129 130 131 132 133 134
        if managed is None:
            managed = vlan.managed

        net = Interface.create(base_activity=activity, instance=self.instance,
                               managed=managed, owner=user, vlan=vlan)

        if self.instance.is_running:
135 136 137 138 139 140 141
            try:
                with activity.sub_activity('attach_network'):
                    self.instance.attach_network(net)
            except Exception as e:
                if hasattr(e, 'libvirtError'):
                    self.rollback(net, activity)
                raise
142 143
            net.deploy()

144 145 146 147
    def get_activity_name(self, kwargs):
        return create_readable(ugettext_noop("add %(vlan)s interface"),
                               vlan=kwargs['vlan'])

148

Bach Dániel committed
149
register_operation(AddInterfaceOperation)
150 151


152
class CreateDiskOperation(InstanceOperation):
153

154 155 156 157
    activity_code_suffix = 'create_disk'
    id = 'create_disk'
    name = _("create disk")
    description = _("Create empty disk for the VM.")
158
    required_perms = ('storage.create_empty_disk', )
159
    accept_states = ('STOPPED', 'PENDING', 'RUNNING')
160

161
    def _operation(self, user, size, activity, name=None):
Bach Dániel committed
162 163
        from storage.models import Disk

164 165 166
        if not name:
            name = "new disk"
        disk = Disk.create(size=size, name=name, type="qcow2-norm")
167
        disk.full_clean()
168 169 170 171
        devnums = list(ascii_lowercase)
        for d in self.instance.disks.all():
            devnums.remove(d.dev_num)
        disk.dev_num = devnums.pop(0)
172
        disk.save()
173 174
        self.instance.disks.add(disk)

175 176 177 178 179 180
        if self.instance.is_running:
            with activity.sub_activity('deploying_disk'):
                disk.deploy()
            with activity.sub_activity('attach_disk'):
                self.instance.attach_disk(disk)

181 182 183 184 185
    def get_activity_name(self, kwargs):
        return create_readable(ugettext_noop("create %(size)s disk"),
                               size=kwargs['size'])


186 187 188 189 190 191 192 193 194
register_operation(CreateDiskOperation)


class DownloadDiskOperation(InstanceOperation):
    activity_code_suffix = 'download_disk'
    id = 'download_disk'
    name = _("download disk")
    description = _("Download disk for the VM.")
    abortable = True
195
    has_percentage = True
196
    required_perms = ('storage.download_disk', )
197
    accept_states = ('STOPPED', 'PENDING', 'RUNNING')
198

199 200
    def _operation(self, user, url, task, activity, name=None):
        activity.result = url
Bach Dániel committed
201 202
        from storage.models import Disk

203
        disk = Disk.download(url=url, name=name, task=task)
204 205 206 207
        devnums = list(ascii_lowercase)
        for d in self.instance.disks.all():
            devnums.remove(d.dev_num)
        disk.dev_num = devnums.pop(0)
208
        disk.full_clean()
209
        disk.save()
210
        self.instance.disks.add(disk)
211 212
        activity.readable_name = create_readable(
            ugettext_noop("download %(name)s"), name=disk.name)
213

Őry Máté committed
214
        # TODO iso (cd) hot-plug is not supported by kvm/guests
215 216 217 218
        if self.instance.is_running and disk.type not in ["iso"]:
            with activity.sub_activity('attach_disk'):
                self.instance.attach_disk(disk)

219 220 221
register_operation(DownloadDiskOperation)


222
class DeployOperation(InstanceOperation):
Dudás Ádám committed
223 224 225
    activity_code_suffix = 'deploy'
    id = 'deploy'
    name = _("deploy")
226
    description = _("Deploy new virtual machine with network.")
227
    required_perms = ()
228
    deny_states = ('SUSPENDED', 'RUNNING')
Dudás Ádám committed
229

230 231
    def is_preferred(self):
        return self.instance.status in (self.instance.STATUS.STOPPED,
232
                                        self.instance.STATUS.PENDING,
233 234
                                        self.instance.STATUS.ERROR)

Dudás Ádám committed
235 236 237
    def on_commit(self, activity):
        activity.resultant_state = 'RUNNING'

238
    def _operation(self, activity, timeout=15):
Dudás Ádám committed
239 240 241
        # Allocate VNC port and host node
        self.instance.allocate_vnc_port()
        self.instance.allocate_node()
Dudás Ádám committed
242 243

        # Deploy virtual images
244 245 246
        with activity.sub_activity(
            'deploying_disks', readable_name=ugettext_noop(
                "deploy disks")):
Dudás Ádám committed
247 248 249
            self.instance.deploy_disks()

        # Deploy VM on remote machine
250
        if self.instance.state not in ['PAUSED']:
251 252 253
            with activity.sub_activity(
                'deploying_vm', readable_name=ugettext_noop(
                    "deploy virtual machine")) as deploy_act:
254
                deploy_act.result = self.instance.deploy_vm(timeout=timeout)
Dudás Ádám committed
255 256

        # Establish network connection (vmdriver)
257 258 259
        with activity.sub_activity(
            'deploying_net', readable_name=ugettext_noop(
                "deploy network")):
Dudás Ádám committed
260 261 262
            self.instance.deploy_net()

        # Resume vm
263 264 265
        with activity.sub_activity(
            'booting', readable_name=ugettext_noop(
                "boot virtual machine")):
Dudás Ádám committed
266
            self.instance.resume_vm(timeout=timeout)
Dudás Ádám committed
267

268
        self.instance.renew(parent_activity=activity)
Dudás Ádám committed
269 270


271
register_operation(DeployOperation)
Dudás Ádám committed
272 273


274
class DestroyOperation(InstanceOperation):
Dudás Ádám committed
275 276 277
    activity_code_suffix = 'destroy'
    id = 'destroy'
    name = _("destroy")
278
    description = _("Destroy virtual machine and its networks.")
279
    required_perms = ()
Dudás Ádám committed
280 281 282 283

    def on_commit(self, activity):
        activity.resultant_state = 'DESTROYED'

284
    def _operation(self, activity):
285
        # Destroy networks
286 287 288
        with activity.sub_activity(
                'destroying_net',
                readable_name=ugettext_noop("destroy network")):
289
            if self.instance.node:
290
                self.instance.shutdown_net()
291
            self.instance.destroy_net()
Dudás Ádám committed
292

293
        if self.instance.node:
Dudás Ádám committed
294
            # Delete virtual machine
295 296 297
            with activity.sub_activity(
                    'destroying_vm',
                    readable_name=ugettext_noop("destroy virtual machine")):
Dudás Ádám committed
298
                self.instance.delete_vm()
Dudás Ádám committed
299 300

        # Destroy disks
301 302 303
        with activity.sub_activity(
                'destroying_disks',
                readable_name=ugettext_noop("destroy disks")):
Dudás Ádám committed
304
            self.instance.destroy_disks()
Dudás Ádám committed
305

Dudás Ádám committed
306 307 308 309 310 311 312 313 314
        # Delete mem. dump if exists
        try:
            self.instance.delete_mem_dump()
        except:
            pass

        # Clear node and VNC port association
        self.instance.yield_node()
        self.instance.yield_vnc_port()
Dudás Ádám committed
315 316 317 318 319

        self.instance.destroyed_at = timezone.now()
        self.instance.save()


320
register_operation(DestroyOperation)
Dudás Ádám committed
321 322


323
class MigrateOperation(InstanceOperation):
Dudás Ádám committed
324 325 326
    activity_code_suffix = 'migrate'
    id = 'migrate'
    name = _("migrate")
327
    description = _("Live migrate running VM to another node.")
328
    required_perms = ()
329
    accept_states = ('RUNNING', )
Dudás Ádám committed
330

331
    def rollback(self, activity):
332 333 334
        with activity.sub_activity(
            'rollback_net', readable_name=ugettext_noop(
                "redeploy network (rollback)")):
335 336
            self.instance.deploy_net()

337 338 339 340 341 342
    def check_auth(self, user):
        if not user.is_superuser:
            raise PermissionDenied()

        super(MigrateOperation, self).check_auth(user=user)

343
    def _operation(self, activity, to_node=None, timeout=120):
Dudás Ádám committed
344
        if not to_node:
345 346 347
            with activity.sub_activity('scheduling',
                                       readable_name=ugettext_noop(
                                           "schedule")) as sa:
Dudás Ádám committed
348 349 350
                to_node = self.instance.select_node()
                sa.result = to_node

351
        try:
352 353 354
            with activity.sub_activity(
                'migrate_vm', readable_name=create_readable(
                    ugettext_noop("migrate to %(node)s"), node=to_node)):
355 356 357 358
                self.instance.migrate_vm(to_node=to_node, timeout=timeout)
        except Exception as e:
            if hasattr(e, 'libvirtError'):
                self.rollback(activity)
Bach Dániel committed
359
            raise
Dudás Ádám committed
360

361
        # Shutdown networks
362 363 364
        with activity.sub_activity(
            'shutdown_net', readable_name=ugettext_noop(
                "shutdown network")):
365 366
            self.instance.shutdown_net()

Dudás Ádám committed
367 368 369 370
        # Refresh node information
        self.instance.node = to_node
        self.instance.save()
        # Estabilish network connection (vmdriver)
371 372 373
        with activity.sub_activity(
            'deploying_net', readable_name=ugettext_noop(
                "deploy network")):
Dudás Ádám committed
374
            self.instance.deploy_net()
Dudás Ádám committed
375 376


377
register_operation(MigrateOperation)
Dudás Ádám committed
378 379


380
class RebootOperation(InstanceOperation):
Dudás Ádám committed
381 382 383
    activity_code_suffix = 'reboot'
    id = 'reboot'
    name = _("reboot")
384
    description = _("Reboot virtual machine with Ctrl+Alt+Del signal.")
385
    required_perms = ()
386
    accept_states = ('RUNNING', )
Dudás Ádám committed
387

388
    def _operation(self, timeout=5):
Dudás Ádám committed
389
        self.instance.reboot_vm(timeout=timeout)
Dudás Ádám committed
390 391


392
register_operation(RebootOperation)
Dudás Ádám committed
393 394


395 396 397 398 399
class RemoveInterfaceOperation(InstanceOperation):
    activity_code_suffix = 'remove_interface'
    id = 'remove_interface'
    name = _("remove interface")
    description = _("Remove the specified network interface from the VM.")
400
    required_perms = ()
401
    accept_states = ('STOPPED', 'PENDING', 'RUNNING')
402

403 404
    def _operation(self, activity, user, system, interface):
        if self.instance.is_running:
405 406
            with activity.sub_activity('detach_network'):
                self.instance.detach_network(interface)
407 408 409 410 411 412
            interface.shutdown()

        interface.destroy()
        interface.delete()


Bach Dániel committed
413
register_operation(RemoveInterfaceOperation)
414 415


416 417 418 419 420
class RemoveDiskOperation(InstanceOperation):
    activity_code_suffix = 'remove_disk'
    id = 'remove_disk'
    name = _("remove disk")
    description = _("Remove the specified disk from the VM.")
421
    required_perms = ()
422
    accept_states = ('STOPPED', 'PENDING', 'RUNNING')
423 424

    def _operation(self, activity, user, system, disk):
425 426 427
        if self.instance.is_running and disk.type not in ["iso"]:
            with activity.sub_activity('detach_disk'):
                self.instance.detach_disk(disk)
428 429 430
        return self.instance.disks.remove(disk)


Guba Sándor committed
431
register_operation(RemoveDiskOperation)
432 433


434
class ResetOperation(InstanceOperation):
Dudás Ádám committed
435 436 437
    activity_code_suffix = 'reset'
    id = 'reset'
    name = _("reset")
438
    description = _("Reset virtual machine (reset button).")
439
    required_perms = ()
440
    accept_states = ('RUNNING', )
Dudás Ádám committed
441

442
    def _operation(self, timeout=5):
Dudás Ádám committed
443
        self.instance.reset_vm(timeout=timeout)
Dudás Ádám committed
444

445
register_operation(ResetOperation)
Dudás Ádám committed
446 447


448
class SaveAsTemplateOperation(InstanceOperation):
Dudás Ádám committed
449 450 451 452 453 454 455 456
    activity_code_suffix = 'save_as_template'
    id = 'save_as_template'
    name = _("save as template")
    description = _("""Save Virtual Machine as a Template.

        Template can be shared with groups and users.
        Users can instantiate Virtual Machines from Templates.
        """)
457
    abortable = True
458
    required_perms = ('vm.create_template', )
459
    accept_states = ('RUNNING', 'PENDING', 'STOPPED')
Dudás Ádám committed
460

461 462 463 464
    def is_preferred(self):
        return (self.instance.is_base and
                self.instance.status == self.instance.STATUS.RUNNING)

465 466 467 468 469 470
    @staticmethod
    def _rename(name):
        m = search(r" v(\d+)$", name)
        if m:
            v = int(m.group(1)) + 1
            name = search(r"^(.*) v(\d+)$", name).group(1)
471
        else:
472 473
            v = 1
        return "%s v%d" % (name, v)
474

475
    def on_abort(self, activity, error):
476
        if hasattr(self, 'disks'):
477 478 479
            for disk in self.disks:
                disk.destroy()

480
    def _operation(self, activity, user, system, timeout=300, name=None,
481
                   with_shutdown=True, task=None, **kwargs):
482
        if with_shutdown:
483 484
            try:
                ShutdownOperation(self.instance).call(parent_activity=activity,
485
                                                      user=user, task=task)
486 487 488
            except Instance.WrongStateError:
                pass

Dudás Ádám committed
489 490 491 492 493 494 495 496
        # prepare parameters
        params = {
            'access_method': self.instance.access_method,
            'arch': self.instance.arch,
            'boot_menu': self.instance.boot_menu,
            'description': self.instance.description,
            'lease': self.instance.lease,  # Can be problem in new VM
            'max_ram_size': self.instance.max_ram_size,
497
            'name': name or self._rename(self.instance.name),
Dudás Ádám committed
498 499 500 501 502 503 504 505 506
            'num_cores': self.instance.num_cores,
            'owner': user,
            'parent': self.instance.template,  # Can be problem
            'priority': self.instance.priority,
            'ram_size': self.instance.ram_size,
            'raw_data': self.instance.raw_data,
            'system': self.instance.system,
        }
        params.update(kwargs)
Bach Dániel committed
507
        params.pop("parent_activity", None)
Dudás Ádám committed
508

509 510
        from storage.models import Disk

Dudás Ádám committed
511 512
        def __try_save_disk(disk):
            try:
513
                return disk.save_as(task)
Dudás Ádám committed
514 515 516
            except Disk.WrongDiskTypeError:
                return disk

517
        self.disks = []
518 519
        with activity.sub_activity('saving_disks',
                                   readable_name=ugettext_noop("save disks")):
520 521 522
            for disk in self.instance.disks.all():
                self.disks.append(__try_save_disk(disk))

Dudás Ádám committed
523 524 525 526 527
        # create template and do additional setup
        tmpl = InstanceTemplate(**params)
        tmpl.full_clean()  # Avoiding database errors.
        tmpl.save()
        try:
528
            tmpl.disks.add(*self.disks)
Dudás Ádám committed
529 530 531 532 533 534 535 536 537 538
            # create interface templates
            for i in self.instance.interface_set.all():
                i.save_as_template(tmpl)
        except:
            tmpl.delete()
            raise
        else:
            return tmpl


539
register_operation(SaveAsTemplateOperation)
Dudás Ádám committed
540 541


542
class ShutdownOperation(InstanceOperation):
Dudás Ádám committed
543 544 545
    activity_code_suffix = 'shutdown'
    id = 'shutdown'
    name = _("shutdown")
546
    description = _("Shutdown virtual machine with ACPI signal.")
Kálmán Viktor committed
547
    abortable = True
548
    required_perms = ()
549
    accept_states = ('RUNNING', )
550

Dudás Ádám committed
551 552 553
    def on_commit(self, activity):
        activity.resultant_state = 'STOPPED'

554 555
    def _operation(self, task=None):
        self.instance.shutdown_vm(task=task)
Dudás Ádám committed
556 557
        self.instance.yield_node()
        self.instance.yield_vnc_port()
Dudás Ádám committed
558 559


560
register_operation(ShutdownOperation)
Dudás Ádám committed
561 562


563
class ShutOffOperation(InstanceOperation):
Dudás Ádám committed
564 565 566
    activity_code_suffix = 'shut_off'
    id = 'shut_off'
    name = _("shut off")
567
    description = _("Shut off VM (plug-out).")
568
    required_perms = ()
569
    accept_states = ('RUNNING', )
Dudás Ádám committed
570

571
    def on_commit(self, activity):
Dudás Ádám committed
572 573
        activity.resultant_state = 'STOPPED'

574
    def _operation(self, activity):
Dudás Ádám committed
575 576 577
        # Shutdown networks
        with activity.sub_activity('shutdown_net'):
            self.instance.shutdown_net()
Dudás Ádám committed
578

Dudás Ádám committed
579 580 581 582 583 584 585
        # Delete virtual machine
        with activity.sub_activity('delete_vm'):
            self.instance.delete_vm()

        # Clear node and VNC port association
        self.instance.yield_node()
        self.instance.yield_vnc_port()
Dudás Ádám committed
586 587


588
register_operation(ShutOffOperation)
Dudás Ádám committed
589 590


591
class SleepOperation(InstanceOperation):
Dudás Ádám committed
592 593 594
    activity_code_suffix = 'sleep'
    id = 'sleep'
    name = _("sleep")
595
    description = _("Suspend virtual machine with memory dump.")
596
    required_perms = ()
597
    accept_states = ('RUNNING', )
Dudás Ádám committed
598

599 600 601 602
    def is_preferred(self):
        return (not self.instance.is_base and
                self.instance.status == self.instance.STATUS.RUNNING)

Dudás Ádám committed
603 604 605 606 607 608 609 610 611
    def on_abort(self, activity, error):
        if isinstance(error, TimeLimitExceeded):
            activity.resultant_state = None
        else:
            activity.resultant_state = 'ERROR'

    def on_commit(self, activity):
        activity.resultant_state = 'SUSPENDED'

612
    def _operation(self, activity, timeout=240):
Dudás Ádám committed
613
        # Destroy networks
614 615
        with activity.sub_activity('shutdown_net', readable_name=ugettext_noop(
                "shutdown network")):
Dudás Ádám committed
616
            self.instance.shutdown_net()
Dudás Ádám committed
617 618

        # Suspend vm
619 620 621
        with activity.sub_activity('suspending',
                                   readable_name=ugettext_noop(
                                       "suspend virtual machine")):
Dudás Ádám committed
622 623 624 625
            self.instance.suspend_vm(timeout=timeout)

        self.instance.yield_node()
        # VNC port needs to be kept
Dudás Ádám committed
626 627


628
register_operation(SleepOperation)
Dudás Ádám committed
629 630


631
class WakeUpOperation(InstanceOperation):
Dudás Ádám committed
632 633 634 635 636 637 638
    activity_code_suffix = 'wake_up'
    id = 'wake_up'
    name = _("wake up")
    description = _("""Wake up Virtual Machine from SUSPENDED state.

        Power on Virtual Machine and load its memory from dump.
        """)
639
    required_perms = ()
640
    accept_states = ('SUSPENDED', )
Dudás Ádám committed
641

642
    def is_preferred(self):
643
        return self.instance.status == self.instance.STATUS.SUSPENDED
644

Dudás Ádám committed
645 646 647 648 649 650
    def on_abort(self, activity, error):
        activity.resultant_state = 'ERROR'

    def on_commit(self, activity):
        activity.resultant_state = 'RUNNING'

651
    def _operation(self, activity, timeout=60):
Dudás Ádám committed
652
        # Schedule vm
Dudás Ádám committed
653 654
        self.instance.allocate_vnc_port()
        self.instance.allocate_node()
Dudás Ádám committed
655 656

        # Resume vm
657 658 659
        with activity.sub_activity(
            'resuming', readable_name=ugettext_noop(
                "resume virtual machine")):
Dudás Ádám committed
660
            self.instance.wake_up_vm(timeout=timeout)
Dudás Ádám committed
661 662

        # Estabilish network connection (vmdriver)
663 664 665
        with activity.sub_activity(
            'deploying_net', readable_name=ugettext_noop(
                "deploy network")):
Dudás Ádám committed
666
            self.instance.deploy_net()
Dudás Ádám committed
667 668

        # Renew vm
669
        self.instance.renew(parent_activity=activity)
Dudás Ádám committed
670 671


672
register_operation(WakeUpOperation)
673 674


675 676 677 678 679 680
class RenewOperation(InstanceOperation):
    activity_code_suffix = 'renew'
    id = 'renew'
    name = _("renew")
    description = _("Renew expiration times")
    acl_level = "operator"
681
    required_perms = ()
682
    concurrency_check = False
683 684 685 686

    def _operation(self, lease=None):
        (self.instance.time_of_suspend,
         self.instance.time_of_delete) = self.instance.get_renew_times(lease)
687
        self.instance.save()
688 689 690 691 692


register_operation(RenewOperation)


693
class ChangeStateOperation(InstanceOperation):
Guba Sándor committed
694 695 696
    activity_code_suffix = 'emergency_change_state'
    id = 'emergency_change_state'
    name = _("emergency change state")
697 698
    description = _("Change the virtual machine state to NOSTATE")
    acl_level = "owner"
Guba Sándor committed
699
    required_perms = ('vm.emergency_change_state', )
700

Guba Sándor committed
701
    def _operation(self, user, activity, new_state="NOSTATE"):
702 703 704 705 706 707
        activity.resultant_state = new_state


register_operation(ChangeStateOperation)


708
class NodeOperation(Operation):
709
    async_operation = abortable_async_node_operation
710
    host_cls = Node
711 712 713 714 715

    def __init__(self, node):
        super(NodeOperation, self).__init__(subject=node)
        self.node = node

716 717
    def create_activity(self, parent, user, kwargs):
        name = self.get_activity_name(kwargs)
718 719 720 721 722 723 724 725 726 727
        if parent:
            if parent.node != self.node:
                raise ValueError("The node associated with the specified "
                                 "parent activity does not match the node "
                                 "bound to the operation.")
            if parent.user != user:
                raise ValueError("The user associated with the specified "
                                 "parent activity does not match the user "
                                 "provided as parameter.")

728 729
            return parent.create_sub(code_suffix=self.activity_code_suffix,
                                     readable_name=name)
730 731
        else:
            return NodeActivity.create(code_suffix=self.activity_code_suffix,
732 733
                                       node=self.node, user=user,
                                       readable_name=name)
734 735 736 737 738 739


class FlushOperation(NodeOperation):
    activity_code_suffix = 'flush'
    id = 'flush'
    name = _("flush")
740
    description = _("Disable node and move all instances to other ones.")
741
    required_perms = ()
742

743 744 745 746 747 748
    def on_abort(self, activity, error):
        from manager.scheduler import TraitsUnsatisfiableException
        if isinstance(error, TraitsUnsatisfiableException):
            if self.node_enabled:
                self.node.enable(activity.user, activity)

749 750
    def check_auth(self, user):
        if not user.is_superuser:
751 752
            raise humanize_exception(ugettext_noop(
                "Superuser privileges are required."), PermissionDenied())
753 754 755

        super(FlushOperation, self).check_auth(user=user)

756
    def _operation(self, activity, user):
757
        self.node_enabled = self.node.enabled
758 759
        self.node.disable(user, activity)
        for i in self.node.instance_set.all():
760 761 762 763
            name = create_readable(ugettext_noop(
                "migrate %(instance)s (%(pk)s)"), instance=i.name, pk=i.pk)
            with activity.sub_activity('migrate_instance_%d' % i.pk,
                                       readable_name=name):
Bach Dániel committed
764
                i.migrate(user=user)
765 766


767
register_operation(FlushOperation)
768 769 770 771 772 773 774 775


class ScreenshotOperation(InstanceOperation):
    activity_code_suffix = 'screenshot'
    id = 'screenshot'
    name = _("screenshot")
    description = _("Get screenshot")
    acl_level = "owner"
776
    required_perms = ()
777
    accept_states = ('RUNNING', )
778

Kálmán Viktor committed
779
    def _operation(self):
780 781 782 783
        return self.instance.get_screenshot(timeout=20)


register_operation(ScreenshotOperation)
Bach Dániel committed
784 785 786 787 788 789 790 791 792


class RecoverOperation(InstanceOperation):
    activity_code_suffix = 'recover'
    id = 'recover'
    name = _("recover")
    description = _("Recover virtual machine from destroyed state.")
    acl_level = "owner"
    required_perms = ('vm.recover', )
793
    accept_states = ('DESTROYED', )
Bach Dániel committed
794 795

    def check_precond(self):
796 797 798 799
        try:
            super(RecoverOperation, self).check_precond()
        except Instance.InstanceDestroyedError:
            pass
Bach Dániel committed
800 801 802 803 804 805 806 807 808 809 810 811 812 813

    def on_commit(self, activity):
        activity.resultant_state = 'PENDING'

    def _operation(self):
        for disk in self.instance.disks.all():
            disk.destroyed = None
            disk.restore()
            disk.save()
        self.instance.destroyed_at = None
        self.instance.save()


register_operation(RecoverOperation)
814 815


816 817 818 819 820 821
class ResourcesOperation(InstanceOperation):
    activity_code_suffix = 'Resources change'
    id = 'resources_change'
    name = _("resources change")
    description = _("Change resources")
    acl_level = "owner"
822
    required_perms = ('vm.change_resources', )
823
    accept_states = ('STOPPED', 'PENDING', )
824

825 826
    def _operation(self, user, num_cores, ram_size, max_ram_size, priority):

827 828 829 830 831
        self.instance.num_cores = num_cores
        self.instance.ram_size = ram_size
        self.instance.max_ram_size = max_ram_size
        self.instance.priority = priority

832
        self.instance.full_clean()
833 834 835 836
        self.instance.save()


register_operation(ResourcesOperation)
837 838 839 840 841 842 843 844 845


class PasswordResetOperation(InstanceOperation):
    activity_code_suffix = 'Password reset'
    id = 'password_reset'
    name = _("password reset")
    description = _("Password reset")
    acl_level = "owner"
    required_perms = ()
846
    accept_states = ('RUNNING', )
847 848

    def _operation(self):
849 850 851 852 853
        self.instance.pw = pwgen()
        queue = self.instance.get_remote_queue_name("agent")
        agent_tasks.change_password.apply_async(
            queue=queue, args=(self.instance.vm_name, self.instance.pw))
        self.instance.save()
854 855 856


register_operation(PasswordResetOperation)