operations.py 25.2 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
)
36
from string import ascii_lowercase
Dudás Ádám committed
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
    def check_precond(self):
        super(AddInterfaceOperation, self).check_precond()
        if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
            raise self.instance.WrongStateError(self.instance)

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:
106 107
            with activity.sub_activity('attach_network'):
                self.instance.attach_network(net)
108 109 110 111 112
            net.deploy()

        return net


Bach Dániel committed
113
register_operation(AddInterfaceOperation)
114 115


116
class CreateDiskOperation(InstanceOperation):
117

118 119 120 121
    activity_code_suffix = 'create_disk'
    id = 'create_disk'
    name = _("create disk")
    description = _("Create empty disk for the VM.")
122
    required_perms = ('storage.create_empty_disk', )
123 124

    def check_precond(self):
125
        super(CreateDiskOperation, self).check_precond()
126
        if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
127 128
            raise self.instance.WrongStateError(self.instance)

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

132 133 134
        if not name:
            name = "new disk"
        disk = Disk.create(size=size, name=name, type="qcow2-norm")
135
        disk.full_clean()
136 137 138 139
        devnums = list(ascii_lowercase)
        for d in self.instance.disks.all():
            devnums.remove(d.dev_num)
        disk.dev_num = devnums.pop(0)
140 141
        self.instance.disks.add(disk)

142 143 144 145 146 147
        if self.instance.is_running:
            with activity.sub_activity('deploying_disk'):
                disk.deploy()
            with activity.sub_activity('attach_disk'):
                self.instance.attach_disk(disk)

148 149 150 151 152 153 154 155 156
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
157
    has_percentage = True
158
    required_perms = ('storage.download_disk', )
159 160

    def check_precond(self):
161
        super(DownloadDiskOperation, self).check_precond()
162
        if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
163 164
            raise self.instance.WrongStateError(self.instance)

165 166
    def _operation(self, user, url, task, activity, name=None):
        activity.result = url
Bach Dániel committed
167 168
        from storage.models import Disk

169
        disk = Disk.download(url=url, name=name, task=task)
170 171 172 173
        devnums = list(ascii_lowercase)
        for d in self.instance.disks.all():
            devnums.remove(d.dev_num)
        disk.dev_num = devnums.pop(0)
174
        disk.full_clean()
175
        disk.save()
176 177
        self.instance.disks.add(disk)

178 179 180 181
        if self.instance.is_running and disk.type not in ["iso"]:
            with activity.sub_activity('attach_disk'):
                self.instance.attach_disk(disk)

182 183 184
register_operation(DownloadDiskOperation)


185
class DeployOperation(InstanceOperation):
Dudás Ádám committed
186 187 188
    activity_code_suffix = 'deploy'
    id = 'deploy'
    name = _("deploy")
189
    description = _("Deploy new virtual machine with network.")
190
    required_perms = ()
Dudás Ádám committed
191

192 193 194 195
    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
196

197 198
    def is_preferred(self):
        return self.instance.status in (self.instance.STATUS.STOPPED,
199
                                        self.instance.STATUS.PENDING,
200 201
                                        self.instance.STATUS.ERROR)

Dudás Ádám committed
202 203 204
    def on_commit(self, activity):
        activity.resultant_state = 'RUNNING'

205
    def _operation(self, activity, timeout=15):
Dudás Ádám committed
206 207 208
        # Allocate VNC port and host node
        self.instance.allocate_vnc_port()
        self.instance.allocate_node()
Dudás Ádám committed
209 210 211

        # Deploy virtual images
        with activity.sub_activity('deploying_disks'):
Dudás Ádám committed
212 213 214
            self.instance.deploy_disks()

        # Deploy VM on remote machine
215 216 217
        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
218 219 220 221 222 223 224 225

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

227
        self.instance.renew(parent_activity=activity)
Dudás Ádám committed
228 229


230
register_operation(DeployOperation)
Dudás Ádám committed
231 232


233
class DestroyOperation(InstanceOperation):
Dudás Ádám committed
234 235 236
    activity_code_suffix = 'destroy'
    id = 'destroy'
    name = _("destroy")
237
    description = _("Destroy virtual machine and its networks.")
238
    required_perms = ()
Dudás Ádám committed
239 240 241 242

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

243
    def _operation(self, activity):
244 245 246
        # Destroy networks
        with activity.sub_activity('destroying_net'):
            if self.instance.node:
247
                self.instance.shutdown_net()
248
            self.instance.destroy_net()
Dudás Ádám committed
249

250
        if self.instance.node:
Dudás Ádám committed
251 252 253
            # Delete virtual machine
            with activity.sub_activity('destroying_vm'):
                self.instance.delete_vm()
Dudás Ádám committed
254 255 256

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

Dudás Ádám committed
259 260 261 262 263 264 265 266 267
        # 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
268 269 270 271 272

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


273
register_operation(DestroyOperation)
Dudás Ádám committed
274 275


276
class MigrateOperation(InstanceOperation):
Dudás Ádám committed
277 278 279
    activity_code_suffix = 'migrate'
    id = 'migrate'
    name = _("migrate")
280
    description = _("Live migrate running VM to another node.")
281
    required_perms = ()
Dudás Ádám committed
282

283 284 285 286
    def rollback(self, activity):
        with activity.sub_activity('rollback_net'):
            self.instance.deploy_net()

287 288 289 290 291
    def check_precond(self):
        super(MigrateOperation, self).check_precond()
        if self.instance.status not in ['RUNNING']:
            raise self.instance.WrongStateError(self.instance)

292 293 294 295 296 297
    def check_auth(self, user):
        if not user.is_superuser:
            raise PermissionDenied()

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

298
    def _operation(self, activity, to_node=None, timeout=120):
Dudás Ádám committed
299 300 301 302 303
        if not to_node:
            with activity.sub_activity('scheduling') as sa:
                to_node = self.instance.select_node()
                sa.result = to_node

304 305 306 307 308 309
        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
310
            raise
Dudás Ádám committed
311

312 313 314 315
        # Shutdown networks
        with activity.sub_activity('shutdown_net'):
            self.instance.shutdown_net()

Dudás Ádám committed
316 317 318 319 320
        # 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
321
            self.instance.deploy_net()
Dudás Ádám committed
322 323


324
register_operation(MigrateOperation)
Dudás Ádám committed
325 326


327
class RebootOperation(InstanceOperation):
Dudás Ádám committed
328 329 330
    activity_code_suffix = 'reboot'
    id = 'reboot'
    name = _("reboot")
331
    description = _("Reboot virtual machine with Ctrl+Alt+Del signal.")
332
    required_perms = ()
Dudás Ádám committed
333

334 335 336 337
    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
338

339
    def _operation(self, timeout=5):
Dudás Ádám committed
340
        self.instance.reboot_vm(timeout=timeout)
Dudás Ádám committed
341 342


343
register_operation(RebootOperation)
Dudás Ádám committed
344 345


346 347 348 349 350
class RemoveInterfaceOperation(InstanceOperation):
    activity_code_suffix = 'remove_interface'
    id = 'remove_interface'
    name = _("remove interface")
    description = _("Remove the specified network interface from the VM.")
351
    required_perms = ()
352

353 354 355 356 357
    def check_precond(self):
        super(RemoveInterfaceOperation, self).check_precond()
        if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
            raise self.instance.WrongStateError(self.instance)

358 359
    def _operation(self, activity, user, system, interface):
        if self.instance.is_running:
360 361
            with activity.sub_activity('detach_network'):
                self.instance.detach_network(interface)
362 363 364 365 366 367
            interface.shutdown()

        interface.destroy()
        interface.delete()


Bach Dániel committed
368
register_operation(RemoveInterfaceOperation)
369 370


371 372 373 374 375
class RemoveDiskOperation(InstanceOperation):
    activity_code_suffix = 'remove_disk'
    id = 'remove_disk'
    name = _("remove disk")
    description = _("Remove the specified disk from the VM.")
376
    required_perms = ()
377 378 379

    def check_precond(self):
        super(RemoveDiskOperation, self).check_precond()
380
        if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
381 382 383
            raise self.instance.WrongStateError(self.instance)

    def _operation(self, activity, user, system, disk):
384 385 386
        if self.instance.is_running and disk.type not in ["iso"]:
            with activity.sub_activity('detach_disk'):
                self.instance.detach_disk(disk)
387 388 389
        return self.instance.disks.remove(disk)


Guba Sándor committed
390
register_operation(RemoveDiskOperation)
391 392


393
class ResetOperation(InstanceOperation):
Dudás Ádám committed
394 395 396
    activity_code_suffix = 'reset'
    id = 'reset'
    name = _("reset")
397
    description = _("Reset virtual machine (reset button).")
398
    required_perms = ()
Dudás Ádám committed
399

400 401 402 403
    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
404

405
    def _operation(self, timeout=5):
Dudás Ádám committed
406
        self.instance.reset_vm(timeout=timeout)
Dudás Ádám committed
407

408
register_operation(ResetOperation)
Dudás Ádám committed
409 410


411
class SaveAsTemplateOperation(InstanceOperation):
Dudás Ádám committed
412 413 414 415 416 417 418 419
    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.
        """)
420
    abortable = True
421
    required_perms = ('vm.create_template', )
Dudás Ádám committed
422

423 424 425 426
    def is_preferred(self):
        return (self.instance.is_base and
                self.instance.status == self.instance.STATUS.RUNNING)

427 428 429 430 431 432
    @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)
433
        else:
434 435
            v = 1
        return "%s v%d" % (name, v)
436

437
    def on_abort(self, activity, error):
438
        if hasattr(self, 'disks'):
439 440 441
            for disk in self.disks:
                disk.destroy()

442 443 444 445 446
    def check_precond(self):
        super(SaveAsTemplateOperation, self).check_precond()
        if self.instance.status not in ['RUNNING', 'PENDING', 'STOPPED']:
            raise self.instance.WrongStateError(self.instance)

447
    def _operation(self, activity, user, system, timeout=300, name=None,
448
                   with_shutdown=True, task=None, **kwargs):
449
        if with_shutdown:
450 451
            try:
                ShutdownOperation(self.instance).call(parent_activity=activity,
452
                                                      user=user, task=task)
453 454 455
            except Instance.WrongStateError:
                pass

Dudás Ádám committed
456 457 458 459 460 461 462 463
        # 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,
464
            'name': name or self._rename(self.instance.name),
Dudás Ádám committed
465 466 467 468 469 470 471 472 473
            '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
474
        params.pop("parent_activity", None)
Dudás Ádám committed
475

476 477
        from storage.models import Disk

Dudás Ádám committed
478 479
        def __try_save_disk(disk):
            try:
480
                return disk.save_as(task)
Dudás Ádám committed
481 482 483
            except Disk.WrongDiskTypeError:
                return disk

484
        self.disks = []
485
        with activity.sub_activity('saving_disks'):
486 487 488 489 490
            for disk in self.instance.disks.all():
                self.disks.append(__try_save_disk(disk))

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

Dudás Ádám committed
492 493 494 495 496
        # create template and do additional setup
        tmpl = InstanceTemplate(**params)
        tmpl.full_clean()  # Avoiding database errors.
        tmpl.save()
        try:
497
            tmpl.disks.add(*self.disks)
Dudás Ádám committed
498 499 500 501 502 503 504 505 506 507
            # create interface templates
            for i in self.instance.interface_set.all():
                i.save_as_template(tmpl)
        except:
            tmpl.delete()
            raise
        else:
            return tmpl


508
register_operation(SaveAsTemplateOperation)
Dudás Ádám committed
509 510


511
class ShutdownOperation(InstanceOperation):
Dudás Ádám committed
512 513 514
    activity_code_suffix = 'shutdown'
    id = 'shutdown'
    name = _("shutdown")
515
    description = _("Shutdown virtual machine with ACPI signal.")
Kálmán Viktor committed
516
    abortable = True
517
    required_perms = ()
Dudás Ádám committed
518

519 520 521 522 523
    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
524 525 526
    def on_commit(self, activity):
        activity.resultant_state = 'STOPPED'

527 528
    def _operation(self, task=None):
        self.instance.shutdown_vm(task=task)
Dudás Ádám committed
529 530
        self.instance.yield_node()
        self.instance.yield_vnc_port()
Dudás Ádám committed
531 532


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


536
class ShutOffOperation(InstanceOperation):
Dudás Ádám committed
537 538 539
    activity_code_suffix = 'shut_off'
    id = 'shut_off'
    name = _("shut off")
540
    description = _("Shut off VM (plug-out).")
541
    required_perms = ()
Dudás Ádám committed
542

543 544 545 546
    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
547

548
    def on_commit(self, activity):
Dudás Ádám committed
549 550
        activity.resultant_state = 'STOPPED'

551
    def _operation(self, activity):
Dudás Ádám committed
552 553 554
        # Shutdown networks
        with activity.sub_activity('shutdown_net'):
            self.instance.shutdown_net()
Dudás Ádám committed
555

Dudás Ádám committed
556 557 558 559 560 561 562
        # 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
563 564


565
register_operation(ShutOffOperation)
Dudás Ádám committed
566 567


568
class SleepOperation(InstanceOperation):
Dudás Ádám committed
569 570 571
    activity_code_suffix = 'sleep'
    id = 'sleep'
    name = _("sleep")
572
    description = _("Suspend virtual machine with memory dump.")
573
    required_perms = ()
Dudás Ádám committed
574

575 576 577 578
    def is_preferred(self):
        return (not self.instance.is_base and
                self.instance.status == self.instance.STATUS.RUNNING)

Dudás Ádám committed
579
    def check_precond(self):
580
        super(SleepOperation, self).check_precond()
Dudás Ádám committed
581 582 583 584 585 586 587 588 589 590 591 592
        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'

593
    def _operation(self, activity, timeout=240):
Dudás Ádám committed
594
        # Destroy networks
Dudás Ádám committed
595 596
        with activity.sub_activity('shutdown_net'):
            self.instance.shutdown_net()
Dudás Ádám committed
597 598 599

        # Suspend vm
        with activity.sub_activity('suspending'):
Dudás Ádám committed
600 601 602 603
            self.instance.suspend_vm(timeout=timeout)

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


606
register_operation(SleepOperation)
Dudás Ádám committed
607 608


609
class WakeUpOperation(InstanceOperation):
Dudás Ádám committed
610 611 612 613 614 615 616
    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.
        """)
617
    required_perms = ()
Dudás Ádám committed
618

619 620 621 622
    def is_preferred(self):
        return (self.instance.is_base and
                self.instance.status == self.instance.STATUS.SUSPENDED)

Dudás Ádám committed
623
    def check_precond(self):
624
        super(WakeUpOperation, self).check_precond()
Dudás Ádám committed
625 626 627 628 629 630 631 632 633
        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'

634
    def _operation(self, activity, timeout=60):
Dudás Ádám committed
635
        # Schedule vm
Dudás Ádám committed
636 637
        self.instance.allocate_vnc_port()
        self.instance.allocate_node()
Dudás Ádám committed
638 639 640

        # Resume vm
        with activity.sub_activity('resuming'):
Dudás Ádám committed
641
            self.instance.wake_up_vm(timeout=timeout)
Dudás Ádám committed
642 643 644

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

        # Renew vm
648
        self.instance.renew(parent_activity=activity)
Dudás Ádám committed
649 650


651
register_operation(WakeUpOperation)
652 653


654 655 656 657 658 659
class RenewOperation(InstanceOperation):
    activity_code_suffix = 'renew'
    id = 'renew'
    name = _("renew")
    description = _("Renew expiration times")
    acl_level = "operator"
660
    required_perms = ()
661
    concurrency_check = False
662 663 664 665

    def _operation(self, lease=None):
        (self.instance.time_of_suspend,
         self.instance.time_of_delete) = self.instance.get_renew_times(lease)
666
        self.instance.save()
667 668 669 670 671


register_operation(RenewOperation)


672
class NodeOperation(Operation):
673
    async_operation = abortable_async_node_operation
674
    host_cls = Node
675 676 677 678 679

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

680 681 682 683 684 685 686 687 688 689 690 691 692 693 694
    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)
695 696 697 698 699 700


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

704 705 706 707 708 709
    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)

710 711 712 713 714 715
    def check_auth(self, user):
        if not user.is_superuser:
            raise PermissionDenied()

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

716
    def _operation(self, activity, user):
717
        self.node_enabled = self.node.enabled
718 719 720
        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
721
                i.migrate(user=user)
722 723


724
register_operation(FlushOperation)
725 726 727 728 729 730 731 732


class ScreenshotOperation(InstanceOperation):
    activity_code_suffix = 'screenshot'
    id = 'screenshot'
    name = _("screenshot")
    description = _("Get screenshot")
    acl_level = "owner"
733
    required_perms = ()
734 735 736 737 738 739

    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
740
    def _operation(self):
741 742 743 744
        return self.instance.get_screenshot(timeout=20)


register_operation(ScreenshotOperation)
Bach Dániel committed
745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771


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)
772 773


774 775 776 777 778 779 780
class ResourcesOperation(InstanceOperation):
    activity_code_suffix = 'Resources change'
    id = 'resources_change'
    name = _("resources change")
    description = _("Change resources")
    acl_level = "owner"
    concurrency_check = False
781
    required_perms = ('vm.change_resources', )
782 783 784

    def check_precond(self):
        super(ResourcesOperation, self).check_precond()
785
        if self.instance.status not in ["STOPPED", "PENDING"]:
786 787
            raise self.instance.WrongStateError(self.instance)

788 789
    def _operation(self, user, num_cores, ram_size, max_ram_size, priority):

790 791 792 793 794
        self.instance.num_cores = num_cores
        self.instance.ram_size = ram_size
        self.instance.max_ram_size = max_ram_size
        self.instance.priority = priority

795
        self.instance.full_clean()
796 797 798 799
        self.instance.save()


register_operation(ResourcesOperation)