operations.py 22.6 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 173
    def check_precond(self):
        super(DeployOperation, self).check_precond()
        if self.instance.status in ['RUNNING', 'SUSPENDED']:
            raise self.instance.WrongStateError(self.instance)

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

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

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

        # Deploy virtual images
        with activity.sub_activity('deploying_disks'):
Dudás Ádám committed
188 189 190 191 192 193 194 195 196 197 198 199 200
            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
201

Dudás Ádám committed
202
        self.instance.renew(which='both', base_activity=activity)
Dudás Ádám committed
203 204


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


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

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

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

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

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

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

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


247
register_operation(DestroyOperation)
Dudás Ádám committed
248 249


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

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

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

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

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

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

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

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

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


298
register_operation(MigrateOperation)
Dudás Ádám committed
299 300


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

308 309 310 311 312
    def check_precond(self):
        super(RebootOperation, self).check_precond()
        if self.instance.status not in ['RUNNING']:
            raise self.instance.WrongStateError(self.instance)

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


317
register_operation(RebootOperation)
Dudás Ádám committed
318 319


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

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

        interface.destroy()
        interface.delete()


Bach Dániel committed
335
register_operation(RemoveInterfaceOperation)
336 337


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

    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
356
register_operation(RemoveDiskOperation)
357 358


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

366 367 368 369 370
    def check_precond(self):
        super(ResetOperation, self).check_precond()
        if self.instance.status not in ['RUNNING']:
            raise self.instance.WrongStateError(self.instance)

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

374
register_operation(ResetOperation)
Dudás Ádám committed
375 376


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

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

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

403 404 405 406 407
    def on_abort(self, activity, error):
        if getattr(self, 'disks'):
            for disk in self.disks:
                disk.destroy()

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

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

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

442 443
        from storage.models import Disk

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

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

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

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


474
register_operation(SaveAsTemplateOperation)
Dudás Ádám committed
475 476


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

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

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


499
register_operation(ShutdownOperation)
Dudás Ádám committed
500 501


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

509 510 511 512 513
    def check_precond(self):
        super(ShutOffOperation, self).check_precond()
        if self.instance.status not in ['RUNNING']:
            raise self.instance.WrongStateError(self.instance)

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

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

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


531
register_operation(ShutOffOperation)
Dudás Ádám committed
532 533


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

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

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

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

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

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


572
register_operation(SleepOperation)
Dudás Ádám committed
573 574


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

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

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

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

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

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

        # Renew vm
        self.instance.renew(which='both', base_activity=activity)


617
register_operation(WakeUpOperation)
618 619 620


class NodeOperation(Operation):
621
    async_operation = abortable_async_node_operation
622
    host_cls = Node
623 624 625 626 627

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

628 629 630 631 632 633 634 635 636 637 638 639 640 641 642
    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)
643 644 645 646 647 648


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

652 653 654 655 656 657
    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)

658 659 660 661 662 663
    def check_auth(self, user):
        if not user.is_superuser:
            raise PermissionDenied()

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

664
    def _operation(self, activity, user):
665
        self.node_enabled = self.node.enabled
666 667 668
        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
669
                i.migrate(user=user)
670 671


672
register_operation(FlushOperation)
673 674 675 676 677 678 679 680


class ScreenshotOperation(InstanceOperation):
    activity_code_suffix = 'screenshot'
    id = 'screenshot'
    name = _("screenshot")
    description = _("Get screenshot")
    acl_level = "owner"
681
    required_perms = ()
682 683 684 685 686 687

    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
688
    def _operation(self):
689 690 691 692
        return self.instance.get_screenshot(timeout=20)


register_operation(ScreenshotOperation)
693 694 695 696 697 698 699 700 701


class ResourcesOperation(InstanceOperation):
    activity_code_suffix = 'Resources change'
    id = 'resources_change'
    name = _("resources change")
    description = _("Change resources")
    acl_level = "owner"
    concurrency_check = False
702
    required_perms = ('vm.change_resources', )
703 704 705

    def check_precond(self):
        super(ResourcesOperation, self).check_precond()
706
        if self.instance.status not in ["STOPPED", "PENDING"]:
707 708
            raise self.instance.WrongStateError(self.instance)

709 710
    def _operation(self, user, num_cores, ram_size, max_ram_size, priority):

711 712 713 714 715
        self.instance.num_cores = num_cores
        self.instance.ram_size = ram_size
        self.instance.max_ram_size = max_ram_size
        self.instance.priority = priority

716
        self.instance.full_clean()
717 718 719 720
        self.instance.save()


register_operation(ResourcesOperation)