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 129 130
            net.deploy()

        return net

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

135

Bach Dániel committed
136
register_operation(AddInterfaceOperation)
137 138


139
class CreateDiskOperation(InstanceOperation):
140

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

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

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

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

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

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


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

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

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

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

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

214 215 216
register_operation(DownloadDiskOperation)


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

224 225 226 227
    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
228

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

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

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

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

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

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

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

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


270
register_operation(DeployOperation)
Dudás Ádám committed
271 272


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

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

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

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

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

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

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


319
register_operation(DestroyOperation)
Dudás Ádám committed
320 321


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

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

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

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

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

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

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

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

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


380
register_operation(MigrateOperation)
Dudás Ádám committed
381 382


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

390 391 392 393
    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
394

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


399
register_operation(RebootOperation)
Dudás Ádám committed
400 401


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

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

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

        interface.destroy()
        interface.delete()


Bach Dániel committed
424
register_operation(RemoveInterfaceOperation)
425 426


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

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

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


Guba Sándor committed
446
register_operation(RemoveDiskOperation)
447 448


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

456 457 458 459
    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
460

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

464
register_operation(ResetOperation)
Dudás Ádám committed
465 466


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

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

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

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

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

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

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

532 533
        from storage.models import Disk

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

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

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


562
register_operation(SaveAsTemplateOperation)
Dudás Ádám committed
563 564


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

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

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


587
register_operation(ShutdownOperation)
Dudás Ádám committed
588 589


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

597 598 599 600
    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
601

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

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

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


619
register_operation(ShutOffOperation)
Dudás Ádám committed
620 621


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

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

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

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

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

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


663
register_operation(SleepOperation)
Dudás Ádám committed
664 665


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

676
    def is_preferred(self):
677
        return self.instance.status == self.instance.STATUS.SUSPENDED
678

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

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

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

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

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


711
register_operation(WakeUpOperation)
712 713


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

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

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


register_operation(RenewOperation)


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

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


register_operation(ChangeStateOperation)


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

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

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

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


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

787 788 789 790 791 792
    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)

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

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

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


810
register_operation(FlushOperation)
811 812 813 814 815 816 817 818


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

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


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


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)
858 859


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

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

873 874
    def _operation(self, user, num_cores, ram_size, max_ram_size, priority):

875 876 877 878 879
        self.instance.num_cores = num_cores
        self.instance.ram_size = ram_size
        self.instance.max_ram_size = max_ram_size
        self.instance.priority = priority

880
        self.instance.full_clean()
881 882 883 884
        self.instance.save()


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


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):
901 902 903 904 905
        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()
906 907 908


register_operation(PasswordResetOperation)