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

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

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

from celery.exceptions import TimeLimitExceeded
28

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

logger = getLogger(__name__)
40 41


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

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

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

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

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

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

75 76
            return parent.create_sub(code_suffix=self.activity_code_suffix,
                                     readable_name=name)
77 78 79
        else:
            return InstanceActivity.create(
                code_suffix=self.activity_code_suffix, instance=self.instance,
80 81
                readable_name=name, user=user,
                concurrency_check=self.concurrency_check)
82

83 84 85 86 87
    def is_preferred(self):
        """If this is the recommended op in the current state of the instance.
        """
        return False

88

89 90 91 92 93 94
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.")
95
    required_perms = ()
96

97 98 99 100 101 102 103
    def rollback(self, net, activity):
        with activity.sub_activity(
            'destroying_net',
                readable_name=ugettext_noop("destroy network (rollback)")):
            net.destroy()
            net.delete()

104 105 106 107 108
    def check_precond(self):
        super(AddInterfaceOperation, self).check_precond()
        if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
            raise self.instance.WrongStateError(self.instance)

109
    def _operation(self, activity, user, system, vlan, managed=None):
110 111
        if not vlan.has_level(user, 'user'):
            raise PermissionDenied()
112 113 114 115 116 117 118
        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:
119 120 121 122 123 124 125
            try:
                with activity.sub_activity('attach_network'):
                    self.instance.attach_network(net)
            except Exception as e:
                if hasattr(e, 'libvirtError'):
                    self.rollback(net, activity)
                raise
126 127
            net.deploy()

128 129 130 131
    def get_activity_name(self, kwargs):
        return create_readable(ugettext_noop("add %(vlan)s interface"),
                               vlan=kwargs['vlan'])

132

Bach Dániel committed
133
register_operation(AddInterfaceOperation)
134 135


136
class CreateDiskOperation(InstanceOperation):
137

138 139 140 141
    activity_code_suffix = 'create_disk'
    id = 'create_disk'
    name = _("create disk")
    description = _("Create empty disk for the VM.")
142
    required_perms = ('storage.create_empty_disk', )
143 144

    def check_precond(self):
145
        super(CreateDiskOperation, self).check_precond()
146
        if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
147 148
            raise self.instance.WrongStateError(self.instance)

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

152 153 154
        if not name:
            name = "new disk"
        disk = Disk.create(size=size, name=name, type="qcow2-norm")
155
        disk.full_clean()
156 157 158 159
        devnums = list(ascii_lowercase)
        for d in self.instance.disks.all():
            devnums.remove(d.dev_num)
        disk.dev_num = devnums.pop(0)
160
        disk.save()
161 162
        self.instance.disks.add(disk)

163 164 165 166 167 168
        if self.instance.is_running:
            with activity.sub_activity('deploying_disk'):
                disk.deploy()
            with activity.sub_activity('attach_disk'):
                self.instance.attach_disk(disk)

169 170 171 172 173
    def get_activity_name(self, kwargs):
        return create_readable(ugettext_noop("create %(size)s disk"),
                               size=kwargs['size'])


174 175 176 177 178 179 180 181 182
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
183
    has_percentage = True
184
    required_perms = ('storage.download_disk', )
185 186

    def check_precond(self):
187
        super(DownloadDiskOperation, self).check_precond()
188
        if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
189 190
            raise self.instance.WrongStateError(self.instance)

191 192
    def _operation(self, user, url, task, activity, name=None):
        activity.result = url
Bach Dániel committed
193 194
        from storage.models import Disk

195
        disk = Disk.download(url=url, name=name, task=task)
196 197 198 199
        devnums = list(ascii_lowercase)
        for d in self.instance.disks.all():
            devnums.remove(d.dev_num)
        disk.dev_num = devnums.pop(0)
200
        disk.full_clean()
201
        disk.save()
202
        self.instance.disks.add(disk)
203 204
        activity.readable_name = create_readable(
            ugettext_noop("download %(name)s"), name=disk.name)
205

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

211 212 213
register_operation(DownloadDiskOperation)


214
class DeployOperation(InstanceOperation):
Dudás Ádám committed
215 216 217
    activity_code_suffix = 'deploy'
    id = 'deploy'
    name = _("deploy")
218
    description = _("Deploy new virtual machine with network.")
219
    required_perms = ()
Dudás Ádám committed
220

221 222 223 224
    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
225

226 227
    def is_preferred(self):
        return self.instance.status in (self.instance.STATUS.STOPPED,
228
                                        self.instance.STATUS.PENDING,
229 230
                                        self.instance.STATUS.ERROR)

Dudás Ádám committed
231 232 233
    def on_commit(self, activity):
        activity.resultant_state = 'RUNNING'

234
    def _operation(self, activity, timeout=15):
Dudás Ádám committed
235 236 237
        # Allocate VNC port and host node
        self.instance.allocate_vnc_port()
        self.instance.allocate_node()
Dudás Ádám committed
238 239

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

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

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

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

264
        self.instance.renew(parent_activity=activity)
Dudás Ádám committed
265 266


267
register_operation(DeployOperation)
Dudás Ádám committed
268 269


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

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

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

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

        # Destroy disks
297 298 299
        with activity.sub_activity(
                'destroying_disks',
                readable_name=ugettext_noop("destroy disks")):
Dudás Ádám committed
300
            self.instance.destroy_disks()
Dudás Ádám committed
301

Dudás Ádám committed
302 303 304 305 306 307 308 309 310
        # 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
311 312 313 314 315

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


316
register_operation(DestroyOperation)
Dudás Ádám committed
317 318


319
class MigrateOperation(InstanceOperation):
Dudás Ádám committed
320 321 322
    activity_code_suffix = 'migrate'
    id = 'migrate'
    name = _("migrate")
323
    description = _("Live migrate running VM to another node.")
324
    required_perms = ()
Dudás Ádám committed
325

326
    def rollback(self, activity):
327 328 329
        with activity.sub_activity(
            'rollback_net', readable_name=ugettext_noop(
                "redeploy network (rollback)")):
330 331
            self.instance.deploy_net()

332 333 334 335 336
    def check_precond(self):
        super(MigrateOperation, self).check_precond()
        if self.instance.status not in ['RUNNING']:
            raise self.instance.WrongStateError(self.instance)

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

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

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

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

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

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


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


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

387 388 389 390
    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
391

392
    def _operation(self, timeout=5):
Dudás Ádám committed
393
        self.instance.reboot_vm(timeout=timeout)
Dudás Ádám committed
394 395


396
register_operation(RebootOperation)
Dudás Ádám committed
397 398


399 400 401 402 403
class RemoveInterfaceOperation(InstanceOperation):
    activity_code_suffix = 'remove_interface'
    id = 'remove_interface'
    name = _("remove interface")
    description = _("Remove the specified network interface from the VM.")
404
    required_perms = ()
405

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

411 412
    def _operation(self, activity, user, system, interface):
        if self.instance.is_running:
413 414
            with activity.sub_activity('detach_network'):
                self.instance.detach_network(interface)
415 416 417 418 419 420
            interface.shutdown()

        interface.destroy()
        interface.delete()


Bach Dániel committed
421
register_operation(RemoveInterfaceOperation)
422 423


424 425 426 427 428
class RemoveDiskOperation(InstanceOperation):
    activity_code_suffix = 'remove_disk'
    id = 'remove_disk'
    name = _("remove disk")
    description = _("Remove the specified disk from the VM.")
429
    required_perms = ()
430 431 432

    def check_precond(self):
        super(RemoveDiskOperation, self).check_precond()
433
        if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
434 435 436
            raise self.instance.WrongStateError(self.instance)

    def _operation(self, activity, user, system, disk):
437 438 439
        if self.instance.is_running and disk.type not in ["iso"]:
            with activity.sub_activity('detach_disk'):
                self.instance.detach_disk(disk)
440 441 442
        return self.instance.disks.remove(disk)


Guba Sándor committed
443
register_operation(RemoveDiskOperation)
444 445


446
class ResetOperation(InstanceOperation):
Dudás Ádám committed
447 448 449
    activity_code_suffix = 'reset'
    id = 'reset'
    name = _("reset")
450
    description = _("Reset virtual machine (reset button).")
451
    required_perms = ()
Dudás Ádám committed
452

453 454 455 456
    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
457

458
    def _operation(self, timeout=5):
Dudás Ádám committed
459
        self.instance.reset_vm(timeout=timeout)
Dudás Ádám committed
460

461
register_operation(ResetOperation)
Dudás Ádám committed
462 463


464
class SaveAsTemplateOperation(InstanceOperation):
Dudás Ádám committed
465 466 467 468 469 470 471 472
    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.
        """)
473
    abortable = True
474
    required_perms = ('vm.create_template', )
Dudás Ádám committed
475

476 477 478 479
    def is_preferred(self):
        return (self.instance.is_base and
                self.instance.status == self.instance.STATUS.RUNNING)

480 481 482 483 484 485
    @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)
486
        else:
487 488
            v = 1
        return "%s v%d" % (name, v)
489

490
    def on_abort(self, activity, error):
491
        if hasattr(self, 'disks'):
492 493 494
            for disk in self.disks:
                disk.destroy()

495 496 497 498 499
    def check_precond(self):
        super(SaveAsTemplateOperation, self).check_precond()
        if self.instance.status not in ['RUNNING', 'PENDING', 'STOPPED']:
            raise self.instance.WrongStateError(self.instance)

500
    def _operation(self, activity, user, system, timeout=300, name=None,
501
                   with_shutdown=True, task=None, **kwargs):
502
        if with_shutdown:
503 504
            try:
                ShutdownOperation(self.instance).call(parent_activity=activity,
505
                                                      user=user, task=task)
506 507 508
            except Instance.WrongStateError:
                pass

Dudás Ádám committed
509 510 511 512 513 514 515 516
        # 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,
517
            'name': name or self._rename(self.instance.name),
Dudás Ádám committed
518 519 520 521 522 523 524 525 526
            '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
527
        params.pop("parent_activity", None)
Dudás Ádám committed
528

529 530
        from storage.models import Disk

Dudás Ádám committed
531 532
        def __try_save_disk(disk):
            try:
533
                return disk.save_as(task)
Dudás Ádám committed
534 535 536
            except Disk.WrongDiskTypeError:
                return disk

537
        self.disks = []
538 539
        with activity.sub_activity('saving_disks',
                                   readable_name=ugettext_noop("save disks")):
540 541 542
            for disk in self.instance.disks.all():
                self.disks.append(__try_save_disk(disk))

Dudás Ádám committed
543 544 545 546 547
        # create template and do additional setup
        tmpl = InstanceTemplate(**params)
        tmpl.full_clean()  # Avoiding database errors.
        tmpl.save()
        try:
548
            tmpl.disks.add(*self.disks)
Dudás Ádám committed
549 550 551 552 553 554 555 556 557 558
            # create interface templates
            for i in self.instance.interface_set.all():
                i.save_as_template(tmpl)
        except:
            tmpl.delete()
            raise
        else:
            return tmpl


559
register_operation(SaveAsTemplateOperation)
Dudás Ádám committed
560 561


562
class ShutdownOperation(InstanceOperation):
Dudás Ádám committed
563 564 565
    activity_code_suffix = 'shutdown'
    id = 'shutdown'
    name = _("shutdown")
566
    description = _("Shutdown virtual machine with ACPI signal.")
Kálmán Viktor committed
567
    abortable = True
568
    required_perms = ()
Dudás Ádám committed
569

570 571 572 573 574
    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
575 576 577
    def on_commit(self, activity):
        activity.resultant_state = 'STOPPED'

578 579
    def _operation(self, task=None):
        self.instance.shutdown_vm(task=task)
Dudás Ádám committed
580 581
        self.instance.yield_node()
        self.instance.yield_vnc_port()
Dudás Ádám committed
582 583


584
register_operation(ShutdownOperation)
Dudás Ádám committed
585 586


587
class ShutOffOperation(InstanceOperation):
Dudás Ádám committed
588 589 590
    activity_code_suffix = 'shut_off'
    id = 'shut_off'
    name = _("shut off")
591
    description = _("Shut off VM (plug-out).")
592
    required_perms = ()
Dudás Ádám committed
593

594 595 596 597
    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
598

599
    def on_commit(self, activity):
Dudás Ádám committed
600 601
        activity.resultant_state = 'STOPPED'

602
    def _operation(self, activity):
Dudás Ádám committed
603 604 605
        # Shutdown networks
        with activity.sub_activity('shutdown_net'):
            self.instance.shutdown_net()
Dudás Ádám committed
606

Dudás Ádám committed
607 608 609 610 611 612 613
        # 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
614 615


616
register_operation(ShutOffOperation)
Dudás Ádám committed
617 618


619
class SleepOperation(InstanceOperation):
Dudás Ádám committed
620 621 622
    activity_code_suffix = 'sleep'
    id = 'sleep'
    name = _("sleep")
623
    description = _("Suspend virtual machine with memory dump.")
624
    required_perms = ()
Dudás Ádám committed
625

626 627 628 629
    def is_preferred(self):
        return (not self.instance.is_base and
                self.instance.status == self.instance.STATUS.RUNNING)

Dudás Ádám committed
630
    def check_precond(self):
631
        super(SleepOperation, self).check_precond()
Dudás Ádám committed
632 633 634 635 636 637 638 639 640 641 642 643
        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'

644
    def _operation(self, activity, timeout=240):
Dudás Ádám committed
645
        # Destroy networks
646 647
        with activity.sub_activity('shutdown_net', readable_name=ugettext_noop(
                "shutdown network")):
Dudás Ádám committed
648
            self.instance.shutdown_net()
Dudás Ádám committed
649 650

        # Suspend vm
651 652 653
        with activity.sub_activity('suspending',
                                   readable_name=ugettext_noop(
                                       "suspend virtual machine")):
Dudás Ádám committed
654 655 656 657
            self.instance.suspend_vm(timeout=timeout)

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


660
register_operation(SleepOperation)
Dudás Ádám committed
661 662


663
class WakeUpOperation(InstanceOperation):
Dudás Ádám committed
664 665 666 667 668 669 670
    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.
        """)
671
    required_perms = ()
Dudás Ádám committed
672

673
    def is_preferred(self):
674
        return self.instance.status == self.instance.STATUS.SUSPENDED
675

Dudás Ádám committed
676
    def check_precond(self):
677
        super(WakeUpOperation, self).check_precond()
Dudás Ádám committed
678 679 680 681 682 683 684 685 686
        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'

687
    def _operation(self, activity, timeout=60):
Dudás Ádám committed
688
        # Schedule vm
Dudás Ádám committed
689 690
        self.instance.allocate_vnc_port()
        self.instance.allocate_node()
Dudás Ádám committed
691 692

        # Resume vm
693 694 695
        with activity.sub_activity(
            'resuming', readable_name=ugettext_noop(
                "resume virtual machine")):
Dudás Ádám committed
696
            self.instance.wake_up_vm(timeout=timeout)
Dudás Ádám committed
697 698

        # Estabilish network connection (vmdriver)
699 700 701
        with activity.sub_activity(
            'deploying_net', readable_name=ugettext_noop(
                "deploy network")):
Dudás Ádám committed
702
            self.instance.deploy_net()
Dudás Ádám committed
703 704

        # Renew vm
705
        self.instance.renew(parent_activity=activity)
Dudás Ádám committed
706 707


708
register_operation(WakeUpOperation)
709 710


711 712 713 714 715 716
class RenewOperation(InstanceOperation):
    activity_code_suffix = 'renew'
    id = 'renew'
    name = _("renew")
    description = _("Renew expiration times")
    acl_level = "operator"
717
    required_perms = ()
718
    concurrency_check = False
719

720 721 722 723 724
    def check_precond(self):
        super(RenewOperation, self).check_precond()
        if self.instance.status == 'DESTROYED':
            raise self.instance.WrongStateError(self.instance)

725 726 727
    def _operation(self, lease=None):
        (self.instance.time_of_suspend,
         self.instance.time_of_delete) = self.instance.get_renew_times(lease)
728
        self.instance.save()
729 730 731 732 733


register_operation(RenewOperation)


734
class ChangeStateOperation(InstanceOperation):
Guba Sándor committed
735 736 737
    activity_code_suffix = 'emergency_change_state'
    id = 'emergency_change_state'
    name = _("emergency change state")
738 739
    description = _("Change the virtual machine state to NOSTATE")
    acl_level = "owner"
Guba Sándor committed
740
    required_perms = ('vm.emergency_change_state', )
741

Guba Sándor committed
742
    def _operation(self, user, activity, new_state="NOSTATE"):
743 744 745 746 747 748
        activity.resultant_state = new_state


register_operation(ChangeStateOperation)


749
class NodeOperation(Operation):
750
    async_operation = abortable_async_node_operation
751
    host_cls = Node
752 753 754 755 756

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

757 758
    def create_activity(self, parent, user, kwargs):
        name = self.get_activity_name(kwargs)
759 760 761 762 763 764 765 766 767 768
        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.")

769 770
            return parent.create_sub(code_suffix=self.activity_code_suffix,
                                     readable_name=name)
771 772
        else:
            return NodeActivity.create(code_suffix=self.activity_code_suffix,
773 774
                                       node=self.node, user=user,
                                       readable_name=name)
775 776 777 778 779 780


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

784 785 786 787 788 789
    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)

790 791 792 793 794 795
    def check_auth(self, user):
        if not user.is_superuser:
            raise PermissionDenied()

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

796
    def _operation(self, activity, user):
797
        self.node_enabled = self.node.enabled
798 799
        self.node.disable(user, activity)
        for i in self.node.instance_set.all():
800 801 802 803
            name = create_readable(ugettext_noop(
                "migrate %(instance)s (%(pk)s)"), instance=i.name, pk=i.pk)
            with activity.sub_activity('migrate_instance_%d' % i.pk,
                                       readable_name=name):
Bach Dániel committed
804
                i.migrate(user=user)
805 806


807
register_operation(FlushOperation)
808 809 810 811 812 813 814 815


class ScreenshotOperation(InstanceOperation):
    activity_code_suffix = 'screenshot'
    id = 'screenshot'
    name = _("screenshot")
    description = _("Get screenshot")
    acl_level = "owner"
816
    required_perms = ()
817 818 819 820 821 822

    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
823
    def _operation(self):
824 825 826 827
        return self.instance.get_screenshot(timeout=20)


register_operation(ScreenshotOperation)
Bach Dániel committed
828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854


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)
855 856


857 858 859 860 861 862
class ResourcesOperation(InstanceOperation):
    activity_code_suffix = 'Resources change'
    id = 'resources_change'
    name = _("resources change")
    description = _("Change resources")
    acl_level = "owner"
863
    required_perms = ('vm.change_resources', )
864 865 866

    def check_precond(self):
        super(ResourcesOperation, self).check_precond()
867
        if self.instance.status not in ["STOPPED", "PENDING"]:
868 869
            raise self.instance.WrongStateError(self.instance)

870 871
    def _operation(self, user, num_cores, ram_size, max_ram_size, priority):

872 873 874 875 876
        self.instance.num_cores = num_cores
        self.instance.ram_size = ram_size
        self.instance.max_ram_size = max_ram_size
        self.instance.priority = priority

877
        self.instance.full_clean()
878 879 880 881
        self.instance.save()


register_operation(ResourcesOperation)