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

18
from __future__ import absolute_import, unicode_literals
Dudás Ádám committed
19
from logging import getLogger
20
from re import search
Dudás Ádám committed
21

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

from celery.exceptions import TimeLimitExceeded
27

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


logger = getLogger(__name__)
40 41


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

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

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

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

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

63 64
    def create_activity(self, parent, user, kwargs):
        name = self.get_activity_name(kwargs)
65 66 67 68 69 70 71 72 73 74
        if parent:
            if parent.instance != self.instance:
                raise ValueError("The instance associated with the specified "
                                 "parent activity does not match the instance "
                                 "bound to the operation.")
            if parent.user != user:
                raise ValueError("The user associated with the specified "
                                 "parent activity does not match the user "
                                 "provided as parameter.")

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

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

88

89 90 91 92 93 94
class AddInterfaceOperation(InstanceOperation):
    activity_code_suffix = 'add_interface'
    id = 'add_interface'
    name = _("add interface")
    description = _("Add a new network interface for the specified VLAN to "
                    "the VM.")
95
    required_perms = ()
96 97 98 99 100 101 102 103 104 105 106 107 108

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

        return net

109 110 111 112
    def get_activity_name(self, kwargs):
        return create_readable(ugettext_noop("add %(vlan)s interface"),
                               vlan=kwargs['vlan'])

113

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


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

    def check_precond(self):
125
        super(CreateDiskOperation, self).check_precond()
126
        # TODO remove check when hot-attach is implemented
127
        if self.instance.status not in ['STOPPED', 'PENDING']:
128 129
            raise self.instance.WrongStateError(self.instance)

130
    def _operation(self, user, size, name=None):
131
        # TODO implement with hot-attach when it'll be available
Bach Dániel committed
132 133
        from storage.models import Disk

134 135 136
        if not name:
            name = "new disk"
        disk = Disk.create(size=size, name=name, type="qcow2-norm")
137
        disk.full_clean()
138 139
        self.instance.disks.add(disk)

140 141 142 143 144
    def get_activity_name(self, kwargs):
        return create_readable(ugettext_noop("create %(size)s disk"),
                               size=kwargs['size'])


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

    def check_precond(self):
158
        super(DownloadDiskOperation, self).check_precond()
159
        # TODO remove check when hot-attach is implemented
160
        if self.instance.status not in ['STOPPED', 'PENDING']:
161 162
            raise self.instance.WrongStateError(self.instance)

163 164
    def _operation(self, user, url, task, activity, name=None):
        activity.result = url
165
        # TODO implement with hot-attach when it'll be available
Bach Dániel committed
166 167
        from storage.models import Disk

168
        disk = Disk.download(url=url, name=name, task=task)
169
        disk.full_clean()
170
        self.instance.disks.add(disk)
171 172
        activity.readable_name = create_readable(
            ugettext_noop("download %(name)s"), name=disk.name)
173 174 175 176

register_operation(DownloadDiskOperation)


177
class DeployOperation(InstanceOperation):
Dudás Ádám committed
178 179 180
    activity_code_suffix = 'deploy'
    id = 'deploy'
    name = _("deploy")
181
    description = _("Deploy new virtual machine with network.")
182
    required_perms = ()
Dudás Ádám committed
183

184 185 186 187
    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
188

189 190
    def is_preferred(self):
        return self.instance.status in (self.instance.STATUS.STOPPED,
191
                                        self.instance.STATUS.PENDING,
192 193
                                        self.instance.STATUS.ERROR)

Dudás Ádám committed
194 195 196
    def on_commit(self, activity):
        activity.resultant_state = 'RUNNING'

197
    def _operation(self, activity, timeout=15):
Dudás Ádám committed
198 199 200
        # Allocate VNC port and host node
        self.instance.allocate_vnc_port()
        self.instance.allocate_node()
Dudás Ádám committed
201 202

        # Deploy virtual images
203 204 205
        with activity.sub_activity(
            'deploying_disks', readable_name=ugettext_noop(
                "deploy disks")):
Dudás Ádám committed
206 207 208
            self.instance.deploy_disks()

        # Deploy VM on remote machine
209
        if self.instance.state not in ['PAUSED']:
210 211 212
            with activity.sub_activity(
                'deploying_vm', readable_name=ugettext_noop(
                    "deploy virtual machine")) as deploy_act:
213
                deploy_act.result = self.instance.deploy_vm(timeout=timeout)
Dudás Ádám committed
214 215

        # Establish network connection (vmdriver)
216 217 218
        with activity.sub_activity(
            'deploying_net', readable_name=ugettext_noop(
                "deploy network")):
Dudás Ádám committed
219 220 221
            self.instance.deploy_net()

        # Resume vm
222 223 224
        with activity.sub_activity(
            'booting', readable_name=ugettext_noop(
                "boot virtual machine")):
Dudás Ádám committed
225
            self.instance.resume_vm(timeout=timeout)
Dudás Ádám committed
226

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


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


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

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

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

252
        if self.instance.node:
Dudás Ádám committed
253
            # Delete virtual machine
254 255 256
            with activity.sub_activity(
                    'destroying_vm',
                    readable_name=ugettext_noop("destroy virtual machine")):
Dudás Ádám committed
257
                self.instance.delete_vm()
Dudás Ádám committed
258 259

        # Destroy disks
260 261 262
        with activity.sub_activity(
                'destroying_disks',
                readable_name=ugettext_noop("destroy disks")):
Dudás Ádám committed
263
            self.instance.destroy_disks()
Dudás Ádám committed
264

Dudás Ádám committed
265 266 267 268 269 270 271 272 273
        # 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
274 275 276 277 278

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


279
register_operation(DestroyOperation)
Dudás Ádám committed
280 281


282
class MigrateOperation(InstanceOperation):
Dudás Ádám committed
283 284 285
    activity_code_suffix = 'migrate'
    id = 'migrate'
    name = _("migrate")
286
    description = _("Live migrate running VM to another node.")
287
    required_perms = ()
Dudás Ádám committed
288

289
    def rollback(self, activity):
290 291 292
        with activity.sub_activity(
            'rollback_net', readable_name=ugettext_noop(
                "redeploy network (rollback)")):
293 294
            self.instance.deploy_net()

295 296 297 298 299
    def check_precond(self):
        super(MigrateOperation, self).check_precond()
        if self.instance.status not in ['RUNNING']:
            raise self.instance.WrongStateError(self.instance)

300 301 302 303 304 305
    def check_auth(self, user):
        if not user.is_superuser:
            raise PermissionDenied()

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

306
    def _operation(self, activity, to_node=None, timeout=120):
Dudás Ádám committed
307
        if not to_node:
308 309 310
            with activity.sub_activity('scheduling',
                                       readable_name=ugettext_noop(
                                           "schedule")) as sa:
Dudás Ádám committed
311 312 313
                to_node = self.instance.select_node()
                sa.result = to_node

314
        try:
315 316 317
            with activity.sub_activity(
                'migrate_vm', readable_name=create_readable(
                    ugettext_noop("migrate to %(node)s"), node=to_node)):
318 319 320 321
                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
322
            raise
Dudás Ádám committed
323

324
        # Shutdown networks
325 326 327
        with activity.sub_activity(
            'shutdown_net', readable_name=ugettext_noop(
                "shutdown network")):
328 329
            self.instance.shutdown_net()

Dudás Ádám committed
330 331 332 333
        # Refresh node information
        self.instance.node = to_node
        self.instance.save()
        # Estabilish network connection (vmdriver)
334 335 336
        with activity.sub_activity(
            'deploying_net', readable_name=ugettext_noop(
                "deploy network")):
Dudás Ádám committed
337
            self.instance.deploy_net()
Dudás Ádám committed
338 339


340
register_operation(MigrateOperation)
Dudás Ádám committed
341 342


343
class RebootOperation(InstanceOperation):
Dudás Ádám committed
344 345 346
    activity_code_suffix = 'reboot'
    id = 'reboot'
    name = _("reboot")
347
    description = _("Reboot virtual machine with Ctrl+Alt+Del signal.")
348
    required_perms = ()
Dudás Ádám committed
349

350 351 352 353
    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
354

355
    def _operation(self, timeout=5):
Dudás Ádám committed
356
        self.instance.reboot_vm(timeout=timeout)
Dudás Ádám committed
357 358


359
register_operation(RebootOperation)
Dudás Ádám committed
360 361


362 363 364 365 366
class RemoveInterfaceOperation(InstanceOperation):
    activity_code_suffix = 'remove_interface'
    id = 'remove_interface'
    name = _("remove interface")
    description = _("Remove the specified network interface from the VM.")
367
    required_perms = ()
368 369 370 371 372 373 374 375 376

    def _operation(self, activity, user, system, interface):
        if self.instance.is_running:
            interface.shutdown()

        interface.destroy()
        interface.delete()


Bach Dániel committed
377
register_operation(RemoveInterfaceOperation)
378 379


380 381 382 383 384
class RemoveDiskOperation(InstanceOperation):
    activity_code_suffix = 'remove_disk'
    id = 'remove_disk'
    name = _("remove disk")
    description = _("Remove the specified disk from the VM.")
385
    required_perms = ()
386 387 388 389 390 391 392 393 394 395 396 397

    def check_precond(self):
        super(RemoveDiskOperation, self).check_precond()
        # TODO remove check when hot-detach is implemented
        if self.instance.status not in ['STOPPED']:
            raise self.instance.WrongStateError(self.instance)

    def _operation(self, activity, user, system, disk):
        # TODO implement with hot-detach when it'll be available
        return self.instance.disks.remove(disk)


Guba Sándor committed
398
register_operation(RemoveDiskOperation)
399 400


401
class ResetOperation(InstanceOperation):
Dudás Ádám committed
402 403 404
    activity_code_suffix = 'reset'
    id = 'reset'
    name = _("reset")
405
    description = _("Reset virtual machine (reset button).")
406
    required_perms = ()
Dudás Ádám committed
407

408 409 410 411
    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
412

413
    def _operation(self, timeout=5):
Dudás Ádám committed
414
        self.instance.reset_vm(timeout=timeout)
Dudás Ádám committed
415

416
register_operation(ResetOperation)
Dudás Ádám committed
417 418


419
class SaveAsTemplateOperation(InstanceOperation):
Dudás Ádám committed
420 421 422 423 424 425 426 427
    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.
        """)
428
    abortable = True
429
    required_perms = ('vm.create_template', )
Dudás Ádám committed
430

431 432 433 434
    def is_preferred(self):
        return (self.instance.is_base and
                self.instance.status == self.instance.STATUS.RUNNING)

435 436 437 438 439 440
    @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)
441
        else:
442 443
            v = 1
        return "%s v%d" % (name, v)
444

445
    def on_abort(self, activity, error):
446
        if hasattr(self, 'disks'):
447 448 449
            for disk in self.disks:
                disk.destroy()

450 451 452 453 454
    def check_precond(self):
        super(SaveAsTemplateOperation, self).check_precond()
        if self.instance.status not in ['RUNNING', 'PENDING', 'STOPPED']:
            raise self.instance.WrongStateError(self.instance)

455
    def _operation(self, activity, user, system, timeout=300, name=None,
456
                   with_shutdown=True, task=None, **kwargs):
457
        if with_shutdown:
458 459
            try:
                ShutdownOperation(self.instance).call(parent_activity=activity,
460
                                                      user=user, task=task)
461 462 463
            except Instance.WrongStateError:
                pass

Dudás Ádám committed
464 465 466 467 468 469 470 471
        # 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,
472
            'name': name or self._rename(self.instance.name),
Dudás Ádám committed
473 474 475 476 477 478 479 480 481
            '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
482
        params.pop("parent_activity", None)
Dudás Ádám committed
483

484 485
        from storage.models import Disk

Dudás Ádám committed
486 487
        def __try_save_disk(disk):
            try:
488
                return disk.save_as(task)
Dudás Ádám committed
489 490 491
            except Disk.WrongDiskTypeError:
                return disk

492
        self.disks = []
493 494
        with activity.sub_activity('saving_disks',
                                   readable_name=ugettext_noop("save disks")):
495 496 497 498 499
            for disk in self.instance.disks.all():
                self.disks.append(__try_save_disk(disk))

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

Dudás Ádám committed
501 502 503 504 505
        # create template and do additional setup
        tmpl = InstanceTemplate(**params)
        tmpl.full_clean()  # Avoiding database errors.
        tmpl.save()
        try:
506
            tmpl.disks.add(*self.disks)
Dudás Ádám committed
507 508 509 510 511 512 513 514 515 516
            # create interface templates
            for i in self.instance.interface_set.all():
                i.save_as_template(tmpl)
        except:
            tmpl.delete()
            raise
        else:
            return tmpl


517
register_operation(SaveAsTemplateOperation)
Dudás Ádám committed
518 519


520
class ShutdownOperation(InstanceOperation):
Dudás Ádám committed
521 522 523
    activity_code_suffix = 'shutdown'
    id = 'shutdown'
    name = _("shutdown")
524
    description = _("Shutdown virtual machine with ACPI signal.")
Kálmán Viktor committed
525
    abortable = True
526
    required_perms = ()
Dudás Ádám committed
527

528 529 530 531 532
    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
533 534 535
    def on_commit(self, activity):
        activity.resultant_state = 'STOPPED'

536 537
    def _operation(self, task=None):
        self.instance.shutdown_vm(task=task)
Dudás Ádám committed
538 539
        self.instance.yield_node()
        self.instance.yield_vnc_port()
Dudás Ádám committed
540 541


542
register_operation(ShutdownOperation)
Dudás Ádám committed
543 544


545
class ShutOffOperation(InstanceOperation):
Dudás Ádám committed
546 547 548
    activity_code_suffix = 'shut_off'
    id = 'shut_off'
    name = _("shut off")
549
    description = _("Shut off VM (plug-out).")
550
    required_perms = ()
Dudás Ádám committed
551

552 553 554 555
    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
556

557
    def on_commit(self, activity):
Dudás Ádám committed
558 559
        activity.resultant_state = 'STOPPED'

560
    def _operation(self, activity):
Dudás Ádám committed
561 562 563
        # Shutdown networks
        with activity.sub_activity('shutdown_net'):
            self.instance.shutdown_net()
Dudás Ádám committed
564

Dudás Ádám committed
565 566 567 568 569 570 571
        # 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
572 573


574
register_operation(ShutOffOperation)
Dudás Ádám committed
575 576


577
class SleepOperation(InstanceOperation):
Dudás Ádám committed
578 579 580
    activity_code_suffix = 'sleep'
    id = 'sleep'
    name = _("sleep")
581
    description = _("Suspend virtual machine with memory dump.")
582
    required_perms = ()
Dudás Ádám committed
583

584 585 586 587
    def is_preferred(self):
        return (not self.instance.is_base and
                self.instance.status == self.instance.STATUS.RUNNING)

Dudás Ádám committed
588
    def check_precond(self):
589
        super(SleepOperation, self).check_precond()
Dudás Ádám committed
590 591 592 593 594 595 596 597 598 599 600 601
        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'

602
    def _operation(self, activity, timeout=240):
Dudás Ádám committed
603
        # Destroy networks
604 605
        with activity.sub_activity('shutdown_net', readable_name=ugettext_noop(
                "shutdown network")):
Dudás Ádám committed
606
            self.instance.shutdown_net()
Dudás Ádám committed
607 608

        # Suspend vm
609 610 611
        with activity.sub_activity('suspending',
                                   readable_name=ugettext_noop(
                                       "suspend virtual machine")):
Dudás Ádám committed
612 613 614 615
            self.instance.suspend_vm(timeout=timeout)

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


618
register_operation(SleepOperation)
Dudás Ádám committed
619 620


621
class WakeUpOperation(InstanceOperation):
Dudás Ádám committed
622 623 624 625 626 627 628
    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.
        """)
629
    required_perms = ()
Dudás Ádám committed
630

631 632 633 634
    def is_preferred(self):
        return (self.instance.is_base and
                self.instance.status == self.instance.STATUS.SUSPENDED)

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

646
    def _operation(self, activity, timeout=60):
Dudás Ádám committed
647
        # Schedule vm
Dudás Ádám committed
648 649
        self.instance.allocate_vnc_port()
        self.instance.allocate_node()
Dudás Ádám committed
650 651

        # Resume vm
652 653 654
        with activity.sub_activity(
            'resuming', readable_name=ugettext_noop(
                "resume virtual machine")):
Dudás Ádám committed
655
            self.instance.wake_up_vm(timeout=timeout)
Dudás Ádám committed
656 657

        # Estabilish network connection (vmdriver)
658 659 660
        with activity.sub_activity(
            'deploying_net', readable_name=ugettext_noop(
                "deploy network")):
Dudás Ádám committed
661
            self.instance.deploy_net()
Dudás Ádám committed
662 663

        # Renew vm
664
        self.instance.renew(parent_activity=activity)
Dudás Ádám committed
665 666


667
register_operation(WakeUpOperation)
668 669


670 671 672 673 674 675
class RenewOperation(InstanceOperation):
    activity_code_suffix = 'renew'
    id = 'renew'
    name = _("renew")
    description = _("Renew expiration times")
    acl_level = "operator"
676
    required_perms = ()
677
    concurrency_check = False
678 679 680 681

    def _operation(self, lease=None):
        (self.instance.time_of_suspend,
         self.instance.time_of_delete) = self.instance.get_renew_times(lease)
682
        self.instance.save()
683 684 685 686 687


register_operation(RenewOperation)


688
class NodeOperation(Operation):
689
    async_operation = abortable_async_node_operation
690
    host_cls = Node
691 692 693 694 695

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

696 697
    def create_activity(self, parent, user, kwargs):
        name = self.get_activity_name(kwargs)
698 699 700 701 702 703 704 705 706 707
        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.")

708 709
            return parent.create_sub(code_suffix=self.activity_code_suffix,
                                     readable_name=name)
710 711
        else:
            return NodeActivity.create(code_suffix=self.activity_code_suffix,
712 713
                                       node=self.node, user=user,
                                       readable_name=name)
714 715 716 717 718 719


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

723 724 725 726 727 728
    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)

729 730 731 732 733 734
    def check_auth(self, user):
        if not user.is_superuser:
            raise PermissionDenied()

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

735
    def _operation(self, activity, user):
736
        self.node_enabled = self.node.enabled
737 738
        self.node.disable(user, activity)
        for i in self.node.instance_set.all():
739 740 741 742
            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
743
                i.migrate(user=user)
744 745


746
register_operation(FlushOperation)
747 748 749 750 751 752 753 754


class ScreenshotOperation(InstanceOperation):
    activity_code_suffix = 'screenshot'
    id = 'screenshot'
    name = _("screenshot")
    description = _("Get screenshot")
    acl_level = "owner"
755
    required_perms = ()
756 757 758 759 760 761

    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
762
    def _operation(self):
763 764 765 766
        return self.instance.get_screenshot(timeout=20)


register_operation(ScreenshotOperation)
Bach Dániel committed
767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793


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)
794 795


796 797 798 799 800 801 802
class ResourcesOperation(InstanceOperation):
    activity_code_suffix = 'Resources change'
    id = 'resources_change'
    name = _("resources change")
    description = _("Change resources")
    acl_level = "owner"
    concurrency_check = False
803
    required_perms = ('vm.change_resources', )
804 805 806

    def check_precond(self):
        super(ResourcesOperation, self).check_precond()
807
        if self.instance.status not in ["STOPPED", "PENDING"]:
808 809
            raise self.instance.WrongStateError(self.instance)

810 811
    def _operation(self, user, num_cores, ram_size, max_ram_size, priority):

812 813 814 815 816
        self.instance.num_cores = num_cores
        self.instance.ram_size = ram_size
        self.instance.max_ram_size = max_ram_size
        self.instance.priority = priority

817
        self.instance.full_clean()
818 819 820 821
        self.instance.save()


register_operation(ResourcesOperation)