operations.py 24.4 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
Dudás Ádám committed
21

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

from celery.exceptions import TimeLimitExceeded
27

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


logger = getLogger(__name__)
39 40


41
class InstanceOperation(Operation):
42
    acl_level = 'owner'
43
    async_operation = abortable_async_instance_operation
44
    host_cls = Instance
45
    concurrency_check = True
Dudás Ádám committed
46

47
    def __init__(self, instance):
48
        super(InstanceOperation, self).__init__(subject=instance)
49 50 51
        self.instance = instance

    def check_precond(self):
52 53
        if self.instance.destroyed_at:
            raise self.instance.InstanceDestroyedError(self.instance)
54 55

    def check_auth(self, user):
56 57 58 59
        if not self.instance.has_level(user, self.acl_level):
            raise PermissionDenied("%s doesn't have the required ACL level." %
                                   user)

60
        super(InstanceOperation, self).check_auth(user=user)
61

62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
    def create_activity(self, parent, user):
        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.")

            return parent.create_sub(code_suffix=self.activity_code_suffix)
        else:
            return InstanceActivity.create(
                code_suffix=self.activity_code_suffix, instance=self.instance,
77
                user=user, concurrency_check=self.concurrency_check)
78

79 80 81 82 83
    def is_preferred(self):
        """If this is the recommended op in the current state of the instance.
        """
        return False

84

85 86 87 88 89 90
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.")
91
    required_perms = ()
92 93 94 95 96 97 98 99 100 101 102 103 104 105

    def _operation(self, activity, user, system, vlan, managed=None):
        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:
            net.deploy()

        return net


Bach Dániel committed
106
register_operation(AddInterfaceOperation)
107 108


109 110 111 112 113
class CreateDiskOperation(InstanceOperation):
    activity_code_suffix = 'create_disk'
    id = 'create_disk'
    name = _("create disk")
    description = _("Create empty disk for the VM.")
114
    required_perms = ('storage.create_empty_disk', )
115 116

    def check_precond(self):
117
        super(CreateDiskOperation, self).check_precond()
118
        # TODO remove check when hot-attach is implemented
119
        if self.instance.status not in ['STOPPED', 'PENDING']:
120 121
            raise self.instance.WrongStateError(self.instance)

122
    def _operation(self, user, size, name=None):
123
        # TODO implement with hot-attach when it'll be available
Bach Dániel committed
124 125
        from storage.models import Disk

126 127 128
        if not name:
            name = "new disk"
        disk = Disk.create(size=size, name=name, type="qcow2-norm")
129
        disk.full_clean()
130 131 132 133 134 135 136 137 138 139 140
        self.instance.disks.add(disk)

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
141
    has_percentage = True
142
    required_perms = ('storage.download_disk', )
143 144

    def check_precond(self):
145
        super(DownloadDiskOperation, self).check_precond()
146
        # TODO remove check when hot-attach is implemented
147
        if self.instance.status not in ['STOPPED', 'PENDING']:
148 149
            raise self.instance.WrongStateError(self.instance)

150 151
    def _operation(self, user, url, task, activity, name=None):
        activity.result = url
152
        # TODO implement with hot-attach when it'll be available
Bach Dániel committed
153 154
        from storage.models import Disk

155
        disk = Disk.download(url=url, name=name, task=task)
156
        disk.full_clean()
157 158 159 160 161
        self.instance.disks.add(disk)

register_operation(DownloadDiskOperation)


162
class DeployOperation(InstanceOperation):
Dudás Ádám committed
163 164 165
    activity_code_suffix = 'deploy'
    id = 'deploy'
    name = _("deploy")
166
    description = _("Deploy new virtual machine with network.")
167
    required_perms = ()
Dudás Ádám committed
168

169 170 171 172
    def check_precond(self):
        super(DeployOperation, self).check_precond()
        if self.instance.status in ['RUNNING', 'SUSPENDED']:
            raise self.instance.WrongStateError(self.instance)
Dudás Ádám committed
173

174 175
    def is_preferred(self):
        return self.instance.status in (self.instance.STATUS.STOPPED,
176
                                        self.instance.STATUS.PENDING,
177 178
                                        self.instance.STATUS.ERROR)

Dudás Ádám committed
179 180 181
    def on_commit(self, activity):
        activity.resultant_state = 'RUNNING'

182
    def _operation(self, activity, timeout=15):
Dudás Ádám committed
183 184 185
        # Allocate VNC port and host node
        self.instance.allocate_vnc_port()
        self.instance.allocate_node()
Dudás Ádám committed
186 187 188

        # Deploy virtual images
        with activity.sub_activity('deploying_disks'):
Dudás Ádám committed
189 190 191
            self.instance.deploy_disks()

        # Deploy VM on remote machine
192 193 194
        if self.instance.state not in ['PAUSED']:
            with activity.sub_activity('deploying_vm') as deploy_act:
                deploy_act.result = self.instance.deploy_vm(timeout=timeout)
Dudás Ádám committed
195 196 197 198 199 200 201 202

        # Establish network connection (vmdriver)
        with activity.sub_activity('deploying_net'):
            self.instance.deploy_net()

        # Resume vm
        with activity.sub_activity('booting'):
            self.instance.resume_vm(timeout=timeout)
Dudás Ádám committed
203

204
        self.instance.renew(parent_activity=activity)
Dudás Ádám committed
205 206


207
register_operation(DeployOperation)
Dudás Ádám committed
208 209


210
class DestroyOperation(InstanceOperation):
Dudás Ádám committed
211 212 213
    activity_code_suffix = 'destroy'
    id = 'destroy'
    name = _("destroy")
214
    description = _("Destroy virtual machine and its networks.")
215
    required_perms = ()
Dudás Ádám committed
216 217 218 219

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

220
    def _operation(self, activity):
221 222 223
        # Destroy networks
        with activity.sub_activity('destroying_net'):
            if self.instance.node:
224
                self.instance.shutdown_net()
225
            self.instance.destroy_net()
Dudás Ádám committed
226

227
        if self.instance.node:
Dudás Ádám committed
228 229 230
            # Delete virtual machine
            with activity.sub_activity('destroying_vm'):
                self.instance.delete_vm()
Dudás Ádám committed
231 232 233

        # Destroy disks
        with activity.sub_activity('destroying_disks'):
Dudás Ádám committed
234
            self.instance.destroy_disks()
Dudás Ádám committed
235

Dudás Ádám committed
236 237 238 239 240 241 242 243 244
        # 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
245 246 247 248 249

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


250
register_operation(DestroyOperation)
Dudás Ádám committed
251 252


253
class MigrateOperation(InstanceOperation):
Dudás Ádám committed
254 255 256
    activity_code_suffix = 'migrate'
    id = 'migrate'
    name = _("migrate")
257
    description = _("Live migrate running VM to another node.")
258
    required_perms = ()
Dudás Ádám committed
259

260 261 262 263
    def rollback(self, activity):
        with activity.sub_activity('rollback_net'):
            self.instance.deploy_net()

264 265 266 267 268
    def check_precond(self):
        super(MigrateOperation, self).check_precond()
        if self.instance.status not in ['RUNNING']:
            raise self.instance.WrongStateError(self.instance)

269 270 271 272 273 274
    def check_auth(self, user):
        if not user.is_superuser:
            raise PermissionDenied()

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

275
    def _operation(self, activity, to_node=None, timeout=120):
Dudás Ádám committed
276 277 278 279 280
        if not to_node:
            with activity.sub_activity('scheduling') as sa:
                to_node = self.instance.select_node()
                sa.result = to_node

281 282 283 284 285 286
        try:
            with activity.sub_activity('migrate_vm'):
                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
287
            raise
Dudás Ádám committed
288

289 290 291 292
        # Shutdown networks
        with activity.sub_activity('shutdown_net'):
            self.instance.shutdown_net()

Dudás Ádám committed
293 294 295 296 297
        # Refresh node information
        self.instance.node = to_node
        self.instance.save()
        # Estabilish network connection (vmdriver)
        with activity.sub_activity('deploying_net'):
Dudás Ádám committed
298
            self.instance.deploy_net()
Dudás Ádám committed
299 300


301
register_operation(MigrateOperation)
Dudás Ádám committed
302 303


304
class RebootOperation(InstanceOperation):
Dudás Ádám committed
305 306 307
    activity_code_suffix = 'reboot'
    id = 'reboot'
    name = _("reboot")
308
    description = _("Reboot virtual machine with Ctrl+Alt+Del signal.")
309
    required_perms = ()
Dudás Ádám committed
310

311 312 313 314
    def check_precond(self):
        super(RebootOperation, self).check_precond()
        if self.instance.status not in ['RUNNING']:
            raise self.instance.WrongStateError(self.instance)
Dudás Ádám committed
315

316
    def _operation(self, timeout=5):
Dudás Ádám committed
317
        self.instance.reboot_vm(timeout=timeout)
Dudás Ádám committed
318 319


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


323 324 325 326 327
class RemoveInterfaceOperation(InstanceOperation):
    activity_code_suffix = 'remove_interface'
    id = 'remove_interface'
    name = _("remove interface")
    description = _("Remove the specified network interface from the VM.")
328
    required_perms = ()
329 330 331 332 333 334 335 336 337

    def _operation(self, activity, user, system, interface):
        if self.instance.is_running:
            interface.shutdown()

        interface.destroy()
        interface.delete()


Bach Dániel committed
338
register_operation(RemoveInterfaceOperation)
339 340


341 342 343 344 345
class RemoveDiskOperation(InstanceOperation):
    activity_code_suffix = 'remove_disk'
    id = 'remove_disk'
    name = _("remove disk")
    description = _("Remove the specified disk from the VM.")
346
    required_perms = ()
347 348 349 350 351 352 353 354 355 356 357 358

    def check_precond(self):
        super(RemoveDiskOperation, self).check_precond()
        # TODO remove check when hot-detach is implemented
        if self.instance.status not in ['STOPPED']:
            raise self.instance.WrongStateError(self.instance)

    def _operation(self, activity, user, system, disk):
        # TODO implement with hot-detach when it'll be available
        return self.instance.disks.remove(disk)


Guba Sándor committed
359
register_operation(RemoveDiskOperation)
360 361


362
class ResetOperation(InstanceOperation):
Dudás Ádám committed
363 364 365
    activity_code_suffix = 'reset'
    id = 'reset'
    name = _("reset")
366
    description = _("Reset virtual machine (reset button).")
367
    required_perms = ()
Dudás Ádám committed
368

369 370 371 372
    def check_precond(self):
        super(ResetOperation, self).check_precond()
        if self.instance.status not in ['RUNNING']:
            raise self.instance.WrongStateError(self.instance)
Dudás Ádám committed
373

374
    def _operation(self, timeout=5):
Dudás Ádám committed
375
        self.instance.reset_vm(timeout=timeout)
Dudás Ádám committed
376

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


380
class SaveAsTemplateOperation(InstanceOperation):
Dudás Ádám committed
381 382 383 384 385 386 387 388
    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.
        """)
389
    abortable = True
390
    required_perms = ('vm.create_template', )
Dudás Ádám committed
391

392 393 394 395
    def is_preferred(self):
        return (self.instance.is_base and
                self.instance.status == self.instance.STATUS.RUNNING)

396 397 398 399 400 401
    @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)
402
        else:
403 404
            v = 1
        return "%s v%d" % (name, v)
405

406
    def on_abort(self, activity, error):
407
        if hasattr(self, 'disks'):
408 409 410
            for disk in self.disks:
                disk.destroy()

411 412 413 414 415
    def check_precond(self):
        super(SaveAsTemplateOperation, self).check_precond()
        if self.instance.status not in ['RUNNING', 'PENDING', 'STOPPED']:
            raise self.instance.WrongStateError(self.instance)

416
    def _operation(self, activity, user, system, timeout=300, name=None,
417
                   with_shutdown=True, task=None, **kwargs):
418
        if with_shutdown:
419 420
            try:
                ShutdownOperation(self.instance).call(parent_activity=activity,
421
                                                      user=user, task=task)
422 423 424
            except Instance.WrongStateError:
                pass

Dudás Ádám committed
425 426 427 428 429 430 431 432
        # 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,
433
            'name': name or self._rename(self.instance.name),
Dudás Ádám committed
434 435 436 437 438 439 440 441 442
            '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
443
        params.pop("parent_activity", None)
Dudás Ádám committed
444

445 446
        from storage.models import Disk

Dudás Ádám committed
447 448
        def __try_save_disk(disk):
            try:
449
                return disk.save_as(task)
Dudás Ádám committed
450 451 452
            except Disk.WrongDiskTypeError:
                return disk

453
        self.disks = []
454
        with activity.sub_activity('saving_disks'):
455 456 457 458 459
            for disk in self.instance.disks.all():
                self.disks.append(__try_save_disk(disk))

        for disk in self.disks:
            disk.set_level(user, 'owner')
460

Dudás Ádám committed
461 462 463 464 465
        # create template and do additional setup
        tmpl = InstanceTemplate(**params)
        tmpl.full_clean()  # Avoiding database errors.
        tmpl.save()
        try:
466
            tmpl.disks.add(*self.disks)
Dudás Ádám committed
467 468 469 470 471 472 473 474 475 476
            # create interface templates
            for i in self.instance.interface_set.all():
                i.save_as_template(tmpl)
        except:
            tmpl.delete()
            raise
        else:
            return tmpl


477
register_operation(SaveAsTemplateOperation)
Dudás Ádám committed
478 479


480
class ShutdownOperation(InstanceOperation):
Dudás Ádám committed
481 482 483
    activity_code_suffix = 'shutdown'
    id = 'shutdown'
    name = _("shutdown")
484
    description = _("Shutdown virtual machine with ACPI signal.")
Kálmán Viktor committed
485
    abortable = True
486
    required_perms = ()
Dudás Ádám committed
487

488 489 490 491 492
    def check_precond(self):
        super(ShutdownOperation, self).check_precond()
        if self.instance.status not in ['RUNNING']:
            raise self.instance.WrongStateError(self.instance)

Dudás Ádám committed
493 494 495
    def on_commit(self, activity):
        activity.resultant_state = 'STOPPED'

496 497
    def _operation(self, task=None):
        self.instance.shutdown_vm(task=task)
Dudás Ádám committed
498 499
        self.instance.yield_node()
        self.instance.yield_vnc_port()
Dudás Ádám committed
500 501


502
register_operation(ShutdownOperation)
Dudás Ádám committed
503 504


505
class ShutOffOperation(InstanceOperation):
Dudás Ádám committed
506 507 508
    activity_code_suffix = 'shut_off'
    id = 'shut_off'
    name = _("shut off")
509
    description = _("Shut off VM (plug-out).")
510
    required_perms = ()
Dudás Ádám committed
511

512 513 514 515
    def check_precond(self):
        super(ShutOffOperation, self).check_precond()
        if self.instance.status not in ['RUNNING']:
            raise self.instance.WrongStateError(self.instance)
Dudás Ádám committed
516

517
    def on_commit(self, activity):
Dudás Ádám committed
518 519
        activity.resultant_state = 'STOPPED'

520
    def _operation(self, activity):
Dudás Ádám committed
521 522 523
        # Shutdown networks
        with activity.sub_activity('shutdown_net'):
            self.instance.shutdown_net()
Dudás Ádám committed
524

Dudás Ádám committed
525 526 527 528 529 530 531
        # 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
532 533


534
register_operation(ShutOffOperation)
Dudás Ádám committed
535 536


537
class SleepOperation(InstanceOperation):
Dudás Ádám committed
538 539 540
    activity_code_suffix = 'sleep'
    id = 'sleep'
    name = _("sleep")
541
    description = _("Suspend virtual machine with memory dump.")
542
    required_perms = ()
Dudás Ádám committed
543

544 545 546 547
    def is_preferred(self):
        return (not self.instance.is_base and
                self.instance.status == self.instance.STATUS.RUNNING)

Dudás Ádám committed
548
    def check_precond(self):
549
        super(SleepOperation, self).check_precond()
Dudás Ádám committed
550 551 552 553 554 555 556 557 558 559 560 561
        if self.instance.status not in ['RUNNING']:
            raise self.instance.WrongStateError(self.instance)

    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'

562
    def _operation(self, activity, timeout=240):
Dudás Ádám committed
563
        # Destroy networks
Dudás Ádám committed
564 565
        with activity.sub_activity('shutdown_net'):
            self.instance.shutdown_net()
Dudás Ádám committed
566 567 568

        # Suspend vm
        with activity.sub_activity('suspending'):
Dudás Ádám committed
569 570 571 572
            self.instance.suspend_vm(timeout=timeout)

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


575
register_operation(SleepOperation)
Dudás Ádám committed
576 577


578
class WakeUpOperation(InstanceOperation):
Dudás Ádám committed
579 580 581 582 583 584 585
    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.
        """)
586
    required_perms = ()
Dudás Ádám committed
587

588 589 590 591
    def is_preferred(self):
        return (self.instance.is_base and
                self.instance.status == self.instance.STATUS.SUSPENDED)

Dudás Ádám committed
592
    def check_precond(self):
593
        super(WakeUpOperation, self).check_precond()
Dudás Ádám committed
594 595 596 597 598 599 600 601 602
        if self.instance.status not in ['SUSPENDED']:
            raise self.instance.WrongStateError(self.instance)

    def on_abort(self, activity, error):
        activity.resultant_state = 'ERROR'

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

603
    def _operation(self, activity, timeout=60):
Dudás Ádám committed
604
        # Schedule vm
Dudás Ádám committed
605 606
        self.instance.allocate_vnc_port()
        self.instance.allocate_node()
Dudás Ádám committed
607 608 609

        # Resume vm
        with activity.sub_activity('resuming'):
Dudás Ádám committed
610
            self.instance.wake_up_vm(timeout=timeout)
Dudás Ádám committed
611 612 613

        # Estabilish network connection (vmdriver)
        with activity.sub_activity('deploying_net'):
Dudás Ádám committed
614
            self.instance.deploy_net()
Dudás Ádám committed
615 616

        # Renew vm
617
        self.instance.renew(parent_activity=activity)
Dudás Ádám committed
618 619


620
register_operation(WakeUpOperation)
621 622


623 624 625 626 627 628
class RenewOperation(InstanceOperation):
    activity_code_suffix = 'renew'
    id = 'renew'
    name = _("renew")
    description = _("Renew expiration times")
    acl_level = "operator"
629
    required_perms = ()
630
    concurrency_check = False
631 632 633 634

    def _operation(self, lease=None):
        (self.instance.time_of_suspend,
         self.instance.time_of_delete) = self.instance.get_renew_times(lease)
635
        self.instance.save()
636 637 638 639 640


register_operation(RenewOperation)


641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656
class ChangeStateOperation(InstanceOperation):
    activity_code_suffix = 'change_state'
    id = 'change_state'
    name = _("change state")
    description = _("Change the virtual machine state to NOSTATE")
    acl_level = "owner"
    required_perms = ('vm.change_state', )

    def _operation(self, user, activity, new_state="NOSTATE",
                   reason=None, lease=None):
        activity.resultant_state = new_state


register_operation(ChangeStateOperation)


657
class NodeOperation(Operation):
658
    async_operation = abortable_async_node_operation
659
    host_cls = Node
660 661 662 663 664

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

665 666 667 668 669 670 671 672 673 674 675 676 677 678 679
    def create_activity(self, parent, user):
        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.")

            return parent.create_sub(code_suffix=self.activity_code_suffix)
        else:
            return NodeActivity.create(code_suffix=self.activity_code_suffix,
                                       node=self.node, user=user)
680 681 682 683 684 685


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

689 690 691 692 693 694
    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)

695 696 697 698 699 700
    def check_auth(self, user):
        if not user.is_superuser:
            raise PermissionDenied()

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

701
    def _operation(self, activity, user):
702
        self.node_enabled = self.node.enabled
703 704 705
        self.node.disable(user, activity)
        for i in self.node.instance_set.all():
            with activity.sub_activity('migrate_instance_%d' % i.pk):
Bach Dániel committed
706
                i.migrate(user=user)
707 708


709
register_operation(FlushOperation)
710 711 712 713 714 715 716 717


class ScreenshotOperation(InstanceOperation):
    activity_code_suffix = 'screenshot'
    id = 'screenshot'
    name = _("screenshot")
    description = _("Get screenshot")
    acl_level = "owner"
718
    required_perms = ()
719 720 721 722 723 724

    def check_precond(self):
        super(ScreenshotOperation, self).check_precond()
        if self.instance.status not in ['RUNNING']:
            raise self.instance.WrongStateError(self.instance)

Kálmán Viktor committed
725
    def _operation(self):
726 727 728 729
        return self.instance.get_screenshot(timeout=20)


register_operation(ScreenshotOperation)
Bach Dániel committed
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756


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', )

    def check_precond(self):
        if not self.instance.destroyed_at:
            raise self.instance.WrongStateError(self.instance)

    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)
757 758


759 760 761 762 763 764 765
class ResourcesOperation(InstanceOperation):
    activity_code_suffix = 'Resources change'
    id = 'resources_change'
    name = _("resources change")
    description = _("Change resources")
    acl_level = "owner"
    concurrency_check = False
766
    required_perms = ('vm.change_resources', )
767 768 769

    def check_precond(self):
        super(ResourcesOperation, self).check_precond()
770
        if self.instance.status not in ["STOPPED", "PENDING"]:
771 772
            raise self.instance.WrongStateError(self.instance)

773 774
    def _operation(self, user, num_cores, ram_size, max_ram_size, priority):

775 776 777 778 779
        self.instance.num_cores = num_cores
        self.instance.ram_size = ram_size
        self.instance.max_ram_size = max_ram_size
        self.instance.priority = priority

780
        self.instance.full_clean()
781 782 783 784
        self.instance.save()


register_operation(ResourcesOperation)