operations.py 29.5 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
    Instance, InstanceActivity, InstanceTemplate, Interface, Node,
36
    NodeActivity, pwgen
37
)
38
from .tasks import agent_tasks
Dudás Ádám committed
39 40

logger = getLogger(__name__)
41 42


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

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

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

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

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

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

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

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

89

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

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

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

110
    def _operation(self, activity, user, system, vlan, managed=None):
111 112
        if not vlan.has_level(user, 'user'):
            raise PermissionDenied()
113 114 115 116 117 118 119
        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:
120 121 122 123 124 125 126
            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
127 128
            net.deploy()

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

133

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


137
class CreateDiskOperation(InstanceOperation):
138

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

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

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

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

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

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


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

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

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

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

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

212 213 214
register_operation(DownloadDiskOperation)


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

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

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

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

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

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

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

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

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

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


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


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

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

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

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

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

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

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


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


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

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

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

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

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

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

352
        try:
353 354 355
            with activity.sub_activity(
                'migrate_vm', readable_name=create_readable(
                    ugettext_noop("migrate to %(node)s"), node=to_node)):
356 357 358 359
                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
360
            raise
Dudás Ádám committed
361

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

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


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


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

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

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


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


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

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

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

        interface.destroy()
        interface.delete()


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


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

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

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


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


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

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

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

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


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

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

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

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

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

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

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

530 531
        from storage.models import Disk

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

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

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


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


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

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

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


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


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

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

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

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

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


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


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

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

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

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

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

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


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


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

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

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

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

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

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

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


709
register_operation(WakeUpOperation)
710 711


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

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

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


register_operation(RenewOperation)


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

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


register_operation(ChangeStateOperation)


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

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

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

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


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

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

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

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

797
    def _operation(self, activity, user):
798
        self.node_enabled = self.node.enabled
799 800
        self.node.disable(user, activity)
        for i in self.node.instance_set.all():
801 802 803 804
            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
805
                i.migrate(user=user)
806 807


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


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

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


register_operation(ScreenshotOperation)
Bach Dániel committed
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 855


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


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

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

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

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

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


register_operation(ResourcesOperation)
883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898


class PasswordResetOperation(InstanceOperation):
    activity_code_suffix = 'Password reset'
    id = 'password_reset'
    name = _("password reset")
    description = _("Password reset")
    acl_level = "owner"
    required_perms = ()

    def check_precond(self):
        super(PasswordResetOperation, self).check_precond()
        if self.instance.status not in ["RUNNING"]:
            raise self.instance.WrongStateError(self.instance)

    def _operation(self):
899 900 901 902 903
        self.instance.pw = pwgen()
        queue = self.instance.get_remote_queue_name("agent")
        agent_tasks.change_password.apply_async(
            queue=queue, args=(self.instance.vm_name, self.instance.pw))
        self.instance.save()
904 905 906


register_operation(PasswordResetOperation)