operations.py 28.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
Ő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
    def check_precond(self):
        super(AddInterfaceOperation, self).check_precond()
        if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
            raise self.instance.WrongStateError(self.instance)

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

        return net

116 117 118 119
    def get_activity_name(self, kwargs):
        return create_readable(ugettext_noop("add %(vlan)s interface"),
                               vlan=kwargs['vlan'])

120

Bach Dániel committed
121
register_operation(AddInterfaceOperation)
122 123


124
class CreateDiskOperation(InstanceOperation):
125

126 127 128 129
    activity_code_suffix = 'create_disk'
    id = 'create_disk'
    name = _("create disk")
    description = _("Create empty disk for the VM.")
130
    required_perms = ('storage.create_empty_disk', )
131 132

    def check_precond(self):
133
        super(CreateDiskOperation, self).check_precond()
134
        if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
135 136
            raise self.instance.WrongStateError(self.instance)

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

140 141 142
        if not name:
            name = "new disk"
        disk = Disk.create(size=size, name=name, type="qcow2-norm")
143
        disk.full_clean()
144 145 146 147
        devnums = list(ascii_lowercase)
        for d in self.instance.disks.all():
            devnums.remove(d.dev_num)
        disk.dev_num = devnums.pop(0)
148
        disk.save()
149 150
        self.instance.disks.add(disk)

151 152 153 154 155 156
        if self.instance.is_running:
            with activity.sub_activity('deploying_disk'):
                disk.deploy()
            with activity.sub_activity('attach_disk'):
                self.instance.attach_disk(disk)

157 158 159 160 161
    def get_activity_name(self, kwargs):
        return create_readable(ugettext_noop("create %(size)s disk"),
                               size=kwargs['size'])


162 163 164 165 166 167 168 169 170
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
171
    has_percentage = True
172
    required_perms = ('storage.download_disk', )
173 174

    def check_precond(self):
175
        super(DownloadDiskOperation, self).check_precond()
176
        if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
177 178
            raise self.instance.WrongStateError(self.instance)

179 180
    def _operation(self, user, url, task, activity, name=None):
        activity.result = url
Bach Dániel committed
181 182
        from storage.models import Disk

183
        disk = Disk.download(url=url, name=name, task=task)
184 185 186 187
        devnums = list(ascii_lowercase)
        for d in self.instance.disks.all():
            devnums.remove(d.dev_num)
        disk.dev_num = devnums.pop(0)
188
        disk.full_clean()
189
        disk.save()
190
        self.instance.disks.add(disk)
191 192
        activity.readable_name = create_readable(
            ugettext_noop("download %(name)s"), name=disk.name)
193

Őry Máté committed
194
        # TODO iso (cd) hot-plug is not supported by kvm/guests
195 196 197
        if self.instance.is_running and disk.type not in ["iso"]:
            with activity.sub_activity('attach_disk'):
                self.instance.attach_disk(disk)
198 199 200 201

register_operation(DownloadDiskOperation)


202
class DeployOperation(InstanceOperation):
Dudás Ádám committed
203 204 205
    activity_code_suffix = 'deploy'
    id = 'deploy'
    name = _("deploy")
206
    description = _("Deploy new virtual machine with network.")
207
    required_perms = ()
Dudás Ádám committed
208

209 210 211 212
    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
213

214 215
    def is_preferred(self):
        return self.instance.status in (self.instance.STATUS.STOPPED,
216
                                        self.instance.STATUS.PENDING,
217 218
                                        self.instance.STATUS.ERROR)

Dudás Ádám committed
219 220 221
    def on_commit(self, activity):
        activity.resultant_state = 'RUNNING'

222
    def _operation(self, activity, timeout=15):
Dudás Ádám committed
223 224 225
        # Allocate VNC port and host node
        self.instance.allocate_vnc_port()
        self.instance.allocate_node()
Dudás Ádám committed
226 227

        # Deploy virtual images
228 229 230
        with activity.sub_activity(
            'deploying_disks', readable_name=ugettext_noop(
                "deploy disks")):
Dudás Ádám committed
231 232 233
            self.instance.deploy_disks()

        # Deploy VM on remote machine
234
        if self.instance.state not in ['PAUSED']:
235 236 237
            with activity.sub_activity(
                'deploying_vm', readable_name=ugettext_noop(
                    "deploy virtual machine")) as deploy_act:
238
                deploy_act.result = self.instance.deploy_vm(timeout=timeout)
Dudás Ádám committed
239 240

        # Establish network connection (vmdriver)
241 242 243
        with activity.sub_activity(
            'deploying_net', readable_name=ugettext_noop(
                "deploy network")):
Dudás Ádám committed
244 245 246
            self.instance.deploy_net()

        # Resume vm
247 248 249
        with activity.sub_activity(
            'booting', readable_name=ugettext_noop(
                "boot virtual machine")):
Dudás Ádám committed
250
            self.instance.resume_vm(timeout=timeout)
Dudás Ádám committed
251

252
        self.instance.renew(parent_activity=activity)
Dudás Ádám committed
253 254


255
register_operation(DeployOperation)
Dudás Ádám committed
256 257


258
class DestroyOperation(InstanceOperation):
Dudás Ádám committed
259 260 261
    activity_code_suffix = 'destroy'
    id = 'destroy'
    name = _("destroy")
262
    description = _("Destroy virtual machine and its networks.")
263
    required_perms = ()
Dudás Ádám committed
264 265 266 267

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

268
    def _operation(self, activity):
269
        # Destroy networks
270 271 272
        with activity.sub_activity(
                'destroying_net',
                readable_name=ugettext_noop("destroy network")):
273
            if self.instance.node:
274
                self.instance.shutdown_net()
275
            self.instance.destroy_net()
Dudás Ádám committed
276

277
        if self.instance.node:
Dudás Ádám committed
278
            # Delete virtual machine
279 280 281
            with activity.sub_activity(
                    'destroying_vm',
                    readable_name=ugettext_noop("destroy virtual machine")):
Dudás Ádám committed
282
                self.instance.delete_vm()
Dudás Ádám committed
283 284

        # Destroy disks
285 286 287
        with activity.sub_activity(
                'destroying_disks',
                readable_name=ugettext_noop("destroy disks")):
Dudás Ádám committed
288
            self.instance.destroy_disks()
Dudás Ádám committed
289

Dudás Ádám committed
290 291 292 293 294 295 296 297 298
        # 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
299 300 301 302 303

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


304
register_operation(DestroyOperation)
Dudás Ádám committed
305 306


307
class MigrateOperation(InstanceOperation):
Dudás Ádám committed
308 309 310
    activity_code_suffix = 'migrate'
    id = 'migrate'
    name = _("migrate")
311
    description = _("Live migrate running VM to another node.")
312
    required_perms = ()
Dudás Ádám committed
313

314
    def rollback(self, activity):
315 316 317
        with activity.sub_activity(
            'rollback_net', readable_name=ugettext_noop(
                "redeploy network (rollback)")):
318 319
            self.instance.deploy_net()

320 321 322 323 324
    def check_precond(self):
        super(MigrateOperation, self).check_precond()
        if self.instance.status not in ['RUNNING']:
            raise self.instance.WrongStateError(self.instance)

325 326 327 328 329 330
    def check_auth(self, user):
        if not user.is_superuser:
            raise PermissionDenied()

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

331
    def _operation(self, activity, to_node=None, timeout=120):
Dudás Ádám committed
332
        if not to_node:
333 334 335
            with activity.sub_activity('scheduling',
                                       readable_name=ugettext_noop(
                                           "schedule")) as sa:
Dudás Ádám committed
336 337 338
                to_node = self.instance.select_node()
                sa.result = to_node

339
        try:
340 341 342
            with activity.sub_activity(
                'migrate_vm', readable_name=create_readable(
                    ugettext_noop("migrate to %(node)s"), node=to_node)):
343 344 345 346
                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
347
            raise
Dudás Ádám committed
348

349
        # Shutdown networks
350 351 352
        with activity.sub_activity(
            'shutdown_net', readable_name=ugettext_noop(
                "shutdown network")):
353 354
            self.instance.shutdown_net()

Dudás Ádám committed
355 356 357 358
        # Refresh node information
        self.instance.node = to_node
        self.instance.save()
        # Estabilish network connection (vmdriver)
359 360 361
        with activity.sub_activity(
            'deploying_net', readable_name=ugettext_noop(
                "deploy network")):
Dudás Ádám committed
362
            self.instance.deploy_net()
Dudás Ádám committed
363 364


365
register_operation(MigrateOperation)
Dudás Ádám committed
366 367


368
class RebootOperation(InstanceOperation):
Dudás Ádám committed
369 370 371
    activity_code_suffix = 'reboot'
    id = 'reboot'
    name = _("reboot")
372
    description = _("Reboot virtual machine with Ctrl+Alt+Del signal.")
373
    required_perms = ()
Dudás Ádám committed
374

375 376 377 378
    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
379

380
    def _operation(self, timeout=5):
Dudás Ádám committed
381
        self.instance.reboot_vm(timeout=timeout)
Dudás Ádám committed
382 383


384
register_operation(RebootOperation)
Dudás Ádám committed
385 386


387 388 389 390 391
class RemoveInterfaceOperation(InstanceOperation):
    activity_code_suffix = 'remove_interface'
    id = 'remove_interface'
    name = _("remove interface")
    description = _("Remove the specified network interface from the VM.")
392
    required_perms = ()
393

394 395 396 397 398
    def check_precond(self):
        super(RemoveInterfaceOperation, self).check_precond()
        if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
            raise self.instance.WrongStateError(self.instance)

399 400
    def _operation(self, activity, user, system, interface):
        if self.instance.is_running:
401 402
            with activity.sub_activity('detach_network'):
                self.instance.detach_network(interface)
403 404 405 406 407 408
            interface.shutdown()

        interface.destroy()
        interface.delete()


Bach Dániel committed
409
register_operation(RemoveInterfaceOperation)
410 411


412 413 414 415 416
class RemoveDiskOperation(InstanceOperation):
    activity_code_suffix = 'remove_disk'
    id = 'remove_disk'
    name = _("remove disk")
    description = _("Remove the specified disk from the VM.")
417
    required_perms = ()
418 419 420

    def check_precond(self):
        super(RemoveDiskOperation, self).check_precond()
421
        if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
422 423 424
            raise self.instance.WrongStateError(self.instance)

    def _operation(self, activity, user, system, disk):
425 426 427
        if self.instance.is_running and disk.type not in ["iso"]:
            with activity.sub_activity('detach_disk'):
                self.instance.detach_disk(disk)
428 429 430
        return self.instance.disks.remove(disk)


Guba Sándor committed
431
register_operation(RemoveDiskOperation)
432 433


434
class ResetOperation(InstanceOperation):
Dudás Ádám committed
435 436 437
    activity_code_suffix = 'reset'
    id = 'reset'
    name = _("reset")
438
    description = _("Reset virtual machine (reset button).")
439
    required_perms = ()
Dudás Ádám committed
440

441 442 443 444
    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
445

446
    def _operation(self, timeout=5):
Dudás Ádám committed
447
        self.instance.reset_vm(timeout=timeout)
Dudás Ádám committed
448

449
register_operation(ResetOperation)
Dudás Ádám committed
450 451


452
class SaveAsTemplateOperation(InstanceOperation):
Dudás Ádám committed
453 454 455 456 457 458 459 460
    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.
        """)
461
    abortable = True
462
    required_perms = ('vm.create_template', )
Dudás Ádám committed
463

464 465 466 467
    def is_preferred(self):
        return (self.instance.is_base and
                self.instance.status == self.instance.STATUS.RUNNING)

468 469 470 471 472 473
    @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)
474
        else:
475 476
            v = 1
        return "%s v%d" % (name, v)
477

478
    def on_abort(self, activity, error):
479
        if hasattr(self, 'disks'):
480 481 482
            for disk in self.disks:
                disk.destroy()

483 484 485 486 487
    def check_precond(self):
        super(SaveAsTemplateOperation, self).check_precond()
        if self.instance.status not in ['RUNNING', 'PENDING', 'STOPPED']:
            raise self.instance.WrongStateError(self.instance)

488
    def _operation(self, activity, user, system, timeout=300, name=None,
489
                   with_shutdown=True, task=None, **kwargs):
490
        if with_shutdown:
491 492
            try:
                ShutdownOperation(self.instance).call(parent_activity=activity,
493
                                                      user=user, task=task)
494 495 496
            except Instance.WrongStateError:
                pass

Dudás Ádám committed
497 498 499 500 501 502 503 504
        # 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,
505
            'name': name or self._rename(self.instance.name),
Dudás Ádám committed
506 507 508 509 510 511 512 513 514
            '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
515
        params.pop("parent_activity", None)
Dudás Ádám committed
516

517 518
        from storage.models import Disk

Dudás Ádám committed
519 520
        def __try_save_disk(disk):
            try:
521
                return disk.save_as(task)
Dudás Ádám committed
522 523 524
            except Disk.WrongDiskTypeError:
                return disk

525
        self.disks = []
526 527
        with activity.sub_activity('saving_disks',
                                   readable_name=ugettext_noop("save disks")):
528 529 530 531 532
            for disk in self.instance.disks.all():
                self.disks.append(__try_save_disk(disk))

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

Dudás Ádám committed
534 535 536 537 538
        # create template and do additional setup
        tmpl = InstanceTemplate(**params)
        tmpl.full_clean()  # Avoiding database errors.
        tmpl.save()
        try:
539
            tmpl.disks.add(*self.disks)
Dudás Ádám committed
540 541 542 543 544 545 546 547 548 549
            # create interface templates
            for i in self.instance.interface_set.all():
                i.save_as_template(tmpl)
        except:
            tmpl.delete()
            raise
        else:
            return tmpl


550
register_operation(SaveAsTemplateOperation)
Dudás Ádám committed
551 552


553
class ShutdownOperation(InstanceOperation):
Dudás Ádám committed
554 555 556
    activity_code_suffix = 'shutdown'
    id = 'shutdown'
    name = _("shutdown")
557
    description = _("Shutdown virtual machine with ACPI signal.")
Kálmán Viktor committed
558
    abortable = True
559
    required_perms = ()
Dudás Ádám committed
560

561 562 563 564 565
    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
566 567 568
    def on_commit(self, activity):
        activity.resultant_state = 'STOPPED'

569 570
    def _operation(self, task=None):
        self.instance.shutdown_vm(task=task)
Dudás Ádám committed
571 572
        self.instance.yield_node()
        self.instance.yield_vnc_port()
Dudás Ádám committed
573 574


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


578
class ShutOffOperation(InstanceOperation):
Dudás Ádám committed
579 580 581
    activity_code_suffix = 'shut_off'
    id = 'shut_off'
    name = _("shut off")
582
    description = _("Shut off VM (plug-out).")
583
    required_perms = ()
Dudás Ádám committed
584

585 586 587 588
    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
589

590
    def on_commit(self, activity):
Dudás Ádám committed
591 592
        activity.resultant_state = 'STOPPED'

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

Dudás Ádám committed
598 599 600 601 602 603 604
        # 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
605 606


607
register_operation(ShutOffOperation)
Dudás Ádám committed
608 609


610
class SleepOperation(InstanceOperation):
Dudás Ádám committed
611 612 613
    activity_code_suffix = 'sleep'
    id = 'sleep'
    name = _("sleep")
614
    description = _("Suspend virtual machine with memory dump.")
615
    required_perms = ()
Dudás Ádám committed
616

617 618 619 620
    def is_preferred(self):
        return (not self.instance.is_base and
                self.instance.status == self.instance.STATUS.RUNNING)

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

635
    def _operation(self, activity, timeout=240):
Dudás Ádám committed
636
        # Destroy networks
637 638
        with activity.sub_activity('shutdown_net', readable_name=ugettext_noop(
                "shutdown network")):
Dudás Ádám committed
639
            self.instance.shutdown_net()
Dudás Ádám committed
640 641

        # Suspend vm
642 643 644
        with activity.sub_activity('suspending',
                                   readable_name=ugettext_noop(
                                       "suspend virtual machine")):
Dudás Ádám committed
645 646 647 648
            self.instance.suspend_vm(timeout=timeout)

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


651
register_operation(SleepOperation)
Dudás Ádám committed
652 653


654
class WakeUpOperation(InstanceOperation):
Dudás Ádám committed
655 656 657 658 659 660 661
    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.
        """)
662
    required_perms = ()
Dudás Ádám committed
663

664 665 666 667
    def is_preferred(self):
        return (self.instance.is_base and
                self.instance.status == self.instance.STATUS.SUSPENDED)

Dudás Ádám committed
668
    def check_precond(self):
669
        super(WakeUpOperation, self).check_precond()
Dudás Ádám committed
670 671 672 673 674 675 676 677 678
        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'

679
    def _operation(self, activity, timeout=60):
Dudás Ádám committed
680
        # Schedule vm
Dudás Ádám committed
681 682
        self.instance.allocate_vnc_port()
        self.instance.allocate_node()
Dudás Ádám committed
683 684

        # Resume vm
685 686 687
        with activity.sub_activity(
            'resuming', readable_name=ugettext_noop(
                "resume virtual machine")):
Dudás Ádám committed
688
            self.instance.wake_up_vm(timeout=timeout)
Dudás Ádám committed
689 690

        # Estabilish network connection (vmdriver)
691 692 693
        with activity.sub_activity(
            'deploying_net', readable_name=ugettext_noop(
                "deploy network")):
Dudás Ádám committed
694
            self.instance.deploy_net()
Dudás Ádám committed
695 696

        # Renew vm
697
        self.instance.renew(parent_activity=activity)
Dudás Ádám committed
698 699


700
register_operation(WakeUpOperation)
701 702


703 704 705 706 707 708
class RenewOperation(InstanceOperation):
    activity_code_suffix = 'renew'
    id = 'renew'
    name = _("renew")
    description = _("Renew expiration times")
    acl_level = "operator"
709
    required_perms = ()
710
    concurrency_check = False
711 712 713 714

    def _operation(self, lease=None):
        (self.instance.time_of_suspend,
         self.instance.time_of_delete) = self.instance.get_renew_times(lease)
715
        self.instance.save()
716 717 718 719 720


register_operation(RenewOperation)


721
class ChangeStateOperation(InstanceOperation):
Guba Sándor committed
722 723 724
    activity_code_suffix = 'emergency_change_state'
    id = 'emergency_change_state'
    name = _("emergency change state")
725 726
    description = _("Change the virtual machine state to NOSTATE")
    acl_level = "owner"
Guba Sándor committed
727
    required_perms = ('vm.emergency_change_state', )
728

Guba Sándor committed
729
    def _operation(self, user, activity, new_state="NOSTATE"):
730 731 732 733 734 735
        activity.resultant_state = new_state


register_operation(ChangeStateOperation)


736
class NodeOperation(Operation):
737
    async_operation = abortable_async_node_operation
738
    host_cls = Node
739 740 741 742 743

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

744 745
    def create_activity(self, parent, user, kwargs):
        name = self.get_activity_name(kwargs)
746 747 748 749 750 751 752 753 754 755
        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.")

756 757
            return parent.create_sub(code_suffix=self.activity_code_suffix,
                                     readable_name=name)
758 759
        else:
            return NodeActivity.create(code_suffix=self.activity_code_suffix,
760 761
                                       node=self.node, user=user,
                                       readable_name=name)
762 763 764 765 766 767


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

771 772 773 774 775 776
    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)

777 778 779 780 781 782
    def check_auth(self, user):
        if not user.is_superuser:
            raise PermissionDenied()

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

783
    def _operation(self, activity, user):
784
        self.node_enabled = self.node.enabled
785 786
        self.node.disable(user, activity)
        for i in self.node.instance_set.all():
787 788 789 790
            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
791
                i.migrate(user=user)
792 793


794
register_operation(FlushOperation)
795 796 797 798 799 800 801 802


class ScreenshotOperation(InstanceOperation):
    activity_code_suffix = 'screenshot'
    id = 'screenshot'
    name = _("screenshot")
    description = _("Get screenshot")
    acl_level = "owner"
803
    required_perms = ()
804 805 806 807 808 809

    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
810
    def _operation(self):
811 812 813 814
        return self.instance.get_screenshot(timeout=20)


register_operation(ScreenshotOperation)
Bach Dániel committed
815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841


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)
842 843


844 845 846 847 848 849 850
class ResourcesOperation(InstanceOperation):
    activity_code_suffix = 'Resources change'
    id = 'resources_change'
    name = _("resources change")
    description = _("Change resources")
    acl_level = "owner"
    concurrency_check = False
851
    required_perms = ('vm.change_resources', )
852 853 854

    def check_precond(self):
        super(ResourcesOperation, self).check_precond()
855
        if self.instance.status not in ["STOPPED", "PENDING"]:
856 857
            raise self.instance.WrongStateError(self.instance)

858 859
    def _operation(self, user, num_cores, ram_size, max_ram_size, priority):

860 861 862 863 864
        self.instance.num_cores = num_cores
        self.instance.ram_size = ram_size
        self.instance.max_ram_size = max_ram_size
        self.instance.priority = priority

865
        self.instance.full_clean()
866 867 868 869
        self.instance.save()


register_operation(ResourcesOperation)