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

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

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

from celery.exceptions import TimeLimitExceeded
28

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

logger = getLogger(__name__)
40 41


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

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

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

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

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

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

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

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

88

89 90 91 92 93 94
class AddInterfaceOperation(InstanceOperation):
    activity_code_suffix = 'add_interface'
    id = 'add_interface'
    name = _("add interface")
    description = _("Add a new network interface for the specified VLAN to "
                    "the VM.")
95
    required_perms = ()
96

97 98 99 100 101
    def check_precond(self):
        super(AddInterfaceOperation, self).check_precond()
        if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
            raise self.instance.WrongStateError(self.instance)

102 103 104 105 106 107 108 109
    def _operation(self, activity, user, system, vlan, managed=None):
        if managed is None:
            managed = vlan.managed

        net = Interface.create(base_activity=activity, instance=self.instance,
                               managed=managed, owner=user, vlan=vlan)

        if self.instance.is_running:
110 111
            with activity.sub_activity('attach_network'):
                self.instance.attach_network(net)
112 113 114 115
            net.deploy()

        return net

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

120

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


124
class CreateDiskOperation(InstanceOperation):
125

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

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

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

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

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

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


162 163 164 165 166 167 168 169 170
register_operation(CreateDiskOperation)


class DownloadDiskOperation(InstanceOperation):
    activity_code_suffix = 'download_disk'
    id = 'download_disk'
    name = _("download disk")
    description = _("Download disk for the VM.")
    abortable = True
171
    has_percentage = True
172
    required_perms = ('storage.download_disk', )
173 174

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

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

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

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

199 200 201
register_operation(DownloadDiskOperation)


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

209 210 211 212
    def check_precond(self):
        super(DeployOperation, self).check_precond()
        if self.instance.status in ['RUNNING', 'SUSPENDED']:
            raise self.instance.WrongStateError(self.instance)
Dudás Ádám committed
213

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

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

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

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

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

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

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

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


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


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

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

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

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

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

Dudás Ádám committed
290 291 292 293 294 295 296 297 298
        # Delete mem. dump if exists
        try:
            self.instance.delete_mem_dump()
        except:
            pass

        # Clear node and VNC port association
        self.instance.yield_node()
        self.instance.yield_vnc_port()
Dudás Ádám committed
299 300 301 302 303

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


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


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

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

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

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

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

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

339
        try:
340 341 342
            with activity.sub_activity(
                'migrate_vm', readable_name=create_readable(
                    ugettext_noop("migrate to %(node)s"), node=to_node)):
343 344 345 346
                self.instance.migrate_vm(to_node=to_node, timeout=timeout)
        except Exception as e:
            if hasattr(e, 'libvirtError'):
                self.rollback(activity)
Bach Dániel committed
347
            raise
Dudás Ádám committed
348

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

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


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


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

375 376 377 378
    def check_precond(self):
        super(RebootOperation, self).check_precond()
        if self.instance.status not in ['RUNNING']:
            raise self.instance.WrongStateError(self.instance)
Dudás Ádám committed
379

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


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


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

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

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

        interface.destroy()
        interface.delete()


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


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

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

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


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


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

441 442 443 444
    def check_precond(self):
        super(ResetOperation, self).check_precond()
        if self.instance.status not in ['RUNNING']:
            raise self.instance.WrongStateError(self.instance)
Dudás Ádám committed
445

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

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


452
class SaveAsTemplateOperation(InstanceOperation):
Dudás Ádám committed
453 454 455 456 457 458 459 460
    activity_code_suffix = 'save_as_template'
    id = 'save_as_template'
    name = _("save as template")
    description = _("""Save Virtual Machine as a Template.

        Template can be shared with groups and users.
        Users can instantiate Virtual Machines from Templates.
        """)
461
    abortable = True
462
    required_perms = ('vm.create_template', )
Dudás Ádám committed
463

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

468 469 470 471 472 473
    @staticmethod
    def _rename(name):
        m = search(r" v(\d+)$", name)
        if m:
            v = int(m.group(1)) + 1
            name = search(r"^(.*) v(\d+)$", name).group(1)
474
        else:
475 476
            v = 1
        return "%s v%d" % (name, v)
477

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

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

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

Dudás Ádám committed
497 498 499 500 501 502 503 504
        # prepare parameters
        params = {
            'access_method': self.instance.access_method,
            'arch': self.instance.arch,
            'boot_menu': self.instance.boot_menu,
            'description': self.instance.description,
            'lease': self.instance.lease,  # Can be problem in new VM
            'max_ram_size': self.instance.max_ram_size,
505
            'name': name or self._rename(self.instance.name),
Dudás Ádám committed
506 507 508 509 510 511 512 513 514
            'num_cores': self.instance.num_cores,
            'owner': user,
            'parent': self.instance.template,  # Can be problem
            'priority': self.instance.priority,
            'ram_size': self.instance.ram_size,
            'raw_data': self.instance.raw_data,
            'system': self.instance.system,
        }
        params.update(kwargs)
Bach Dániel committed
515
        params.pop("parent_activity", None)
Dudás Ádám committed
516

517 518
        from storage.models import Disk

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

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

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


547
register_operation(SaveAsTemplateOperation)
Dudás Ádám committed
548 549


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

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

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


572
register_operation(ShutdownOperation)
Dudás Ádám committed
573 574


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

582 583 584 585
    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
586

587
    def on_commit(self, activity):
Dudás Ádám committed
588 589
        activity.resultant_state = 'STOPPED'

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

Dudás Ádám committed
595 596 597 598 599 600 601
        # 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
602 603


604
register_operation(ShutOffOperation)
Dudás Ádám committed
605 606


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

614 615 616 617
    def is_preferred(self):
        return (not self.instance.is_base and
                self.instance.status == self.instance.STATUS.RUNNING)

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

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

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

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


648
register_operation(SleepOperation)
Dudás Ádám committed
649 650


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

661
    def is_preferred(self):
662
        return self.instance.status == self.instance.STATUS.SUSPENDED
663

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

675
    def _operation(self, activity, timeout=60):
Dudás Ádám committed
676
        # Schedule vm
Dudás Ádám committed
677 678
        self.instance.allocate_vnc_port()
        self.instance.allocate_node()
Dudás Ádám committed
679 680

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

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

        # Renew vm
693
        self.instance.renew(parent_activity=activity)
Dudás Ádám committed
694 695


696
register_operation(WakeUpOperation)
697 698


699 700 701 702 703 704
class RenewOperation(InstanceOperation):
    activity_code_suffix = 'renew'
    id = 'renew'
    name = _("renew")
    description = _("Renew expiration times")
    acl_level = "operator"
705
    required_perms = ()
706
    concurrency_check = False
707

708 709 710 711 712
    def check_precond(self):
        super(RenewOperation, self).check_precond()
        if self.instance.status == 'DESTROYED':
            raise self.instance.WrongStateError(self.instance)

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


register_operation(RenewOperation)


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

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


register_operation(ChangeStateOperation)


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

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

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

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


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

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

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

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

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


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


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

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


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


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


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

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

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

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

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


register_operation(ResourcesOperation)
870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889


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):
        self.instance.change_password()


register_operation(PasswordResetOperation)