operations.py 23.9 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 192 193 194 195 196 197 198 199 200 201
            self.instance.deploy_disks()

        # Deploy VM on remote machine
        with activity.sub_activity('deploying_vm') as deploy_act:
            deploy_act.result = self.instance.deploy_vm(timeout=timeout)

        # 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
202

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


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


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

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

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

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

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

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

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


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


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

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

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

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

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

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

280 281 282 283 284 285
        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
286
            raise
Dudás Ádám committed
287

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

Dudás Ádám committed
292 293 294 295 296
        # 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
297
            self.instance.deploy_net()
Dudás Ádám committed
298 299


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


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

310 311 312 313
    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
314

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


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


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

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

        interface.destroy()
        interface.delete()


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


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

    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
358
register_operation(RemoveDiskOperation)
359 360


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

368 369 370 371
    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
372

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

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


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

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

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

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

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

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

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

444 445
        from storage.models import Disk

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

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

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

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


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


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

487 488 489 490 491
    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
492 493 494
    def on_commit(self, activity):
        activity.resultant_state = 'STOPPED'

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


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


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

511 512 513 514
    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
515

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

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

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


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


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

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

Dudás Ádám committed
547
    def check_precond(self):
548
        super(SleepOperation, self).check_precond()
Dudás Ádám committed
549 550 551 552 553 554 555 556 557 558 559 560
        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'

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

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

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


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


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

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

Dudás Ádám committed
591
    def check_precond(self):
592
        super(WakeUpOperation, self).check_precond()
Dudás Ádám committed
593 594 595 596 597 598 599 600 601
        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'

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

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

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

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


619
register_operation(WakeUpOperation)
620 621


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

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


register_operation(RenewOperation)


640
class NodeOperation(Operation):
641
    async_operation = abortable_async_node_operation
642
    host_cls = Node
643 644 645 646 647

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

648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
    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)
663 664 665 666 667 668


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

672 673 674 675 676 677
    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)

678 679 680 681 682 683
    def check_auth(self, user):
        if not user.is_superuser:
            raise PermissionDenied()

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

684
    def _operation(self, activity, user):
685
        self.node_enabled = self.node.enabled
686 687 688
        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
689
                i.migrate(user=user)
690 691


692
register_operation(FlushOperation)
693 694 695 696 697 698 699 700


class ScreenshotOperation(InstanceOperation):
    activity_code_suffix = 'screenshot'
    id = 'screenshot'
    name = _("screenshot")
    description = _("Get screenshot")
    acl_level = "owner"
701
    required_perms = ()
702 703 704 705 706 707

    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
708
    def _operation(self):
709 710 711 712
        return self.instance.get_screenshot(timeout=20)


register_operation(ScreenshotOperation)
Bach Dániel committed
713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739


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)
740 741


742 743 744 745 746 747 748
class ResourcesOperation(InstanceOperation):
    activity_code_suffix = 'Resources change'
    id = 'resources_change'
    name = _("resources change")
    description = _("Change resources")
    acl_level = "owner"
    concurrency_check = False
749
    required_perms = ('vm.change_resources', )
750 751 752

    def check_precond(self):
        super(ResourcesOperation, self).check_precond()
753
        if self.instance.status not in ["STOPPED", "PENDING"]:
754 755
            raise self.instance.WrongStateError(self.instance)

756 757
    def _operation(self, user, num_cores, ram_size, max_ram_size, priority):

758 759 760 761 762
        self.instance.num_cores = num_cores
        self.instance.ram_size = ram_size
        self.instance.max_ram_size = max_ram_size
        self.instance.priority = priority

763
        self.instance.full_clean()
764 765 766 767
        self.instance.save()


register_operation(ResourcesOperation)