operations.py 15 KB
Newer Older
1
from __future__ import absolute_import, unicode_literals
Dudás Ádám committed
2
from logging import getLogger
3
from re import search
Dudás Ádám committed
4

5
from django.core.exceptions import PermissionDenied
Dudás Ádám committed
6 7 8 9
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _

from celery.exceptions import TimeLimitExceeded
10

11
from common.operations import Operation, register_operation
12 13
from .tasks.local_tasks import async_instance_operation, async_node_operation
from .models import (
14 15
    Instance, InstanceActivity, InstanceTemplate, Interface, Node,
    NodeActivity,
16
)
Dudás Ádám committed
17 18 19


logger = getLogger(__name__)
20 21


22
class InstanceOperation(Operation):
23
    acl_level = 'owner'
24
    async_operation = async_instance_operation
25
    host_cls = Instance
Dudás Ádám committed
26

27
    def __init__(self, instance):
28
        super(InstanceOperation, self).__init__(subject=instance)
29 30 31
        self.instance = instance

    def check_precond(self):
32 33
        if self.instance.destroyed_at:
            raise self.instance.InstanceDestroyedError(self.instance)
34 35

    def check_auth(self, user):
36 37 38 39
        if not self.instance.has_level(user, self.acl_level):
            raise PermissionDenied("%s doesn't have the required ACL level." %
                                   user)

40
        super(InstanceOperation, self).check_auth(user=user)
41

42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
    def create_activity(self, parent, user):
        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.")

            return parent.create_sub(code_suffix=self.activity_code_suffix)
        else:
            return InstanceActivity.create(
                code_suffix=self.activity_code_suffix, instance=self.instance,
                user=user)
58

59

60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
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.")

    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


Bach Dániel committed
80
register_operation(AddInterfaceOperation)
81 82


83
class DeployOperation(InstanceOperation):
Dudás Ádám committed
84 85 86
    activity_code_suffix = 'deploy'
    id = 'deploy'
    name = _("deploy")
87
    description = _("Deploy new virtual machine with network.")
88
    icon = 'play'
Dudás Ádám committed
89 90 91 92

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

Dudás Ádám committed
93 94 95 96
    def _operation(self, activity, user, system, timeout=15):
        # Allocate VNC port and host node
        self.instance.allocate_vnc_port()
        self.instance.allocate_node()
Dudás Ádám committed
97 98 99

        # Deploy virtual images
        with activity.sub_activity('deploying_disks'):
Dudás Ádám committed
100 101 102 103 104 105 106 107 108 109 110 111 112
            self.instance.deploy_disks()

        # Deploy VM on remote machine
        with activity.sub_activity('deploying_vm') as deploy_act:
            deploy_act.result = self.instance.deploy_vm(timeout=timeout)

        # Establish network connection (vmdriver)
        with activity.sub_activity('deploying_net'):
            self.instance.deploy_net()

        # Resume vm
        with activity.sub_activity('booting'):
            self.instance.resume_vm(timeout=timeout)
Dudás Ádám committed
113

Dudás Ádám committed
114
        self.instance.renew(which='both', base_activity=activity)
Dudás Ádám committed
115 116


117
register_operation(DeployOperation)
Dudás Ádám committed
118 119


120
class DestroyOperation(InstanceOperation):
Dudás Ádám committed
121 122 123
    activity_code_suffix = 'destroy'
    id = 'destroy'
    name = _("destroy")
124
    description = _("Destroy virtual machine and its networks.")
125
    icon = 'remove'
Dudás Ádám committed
126 127 128 129

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

130
    def _operation(self, activity, user, system):
Dudás Ádám committed
131
        if self.instance.node:
Dudás Ádám committed
132 133
            # Destroy networks
            with activity.sub_activity('destroying_net'):
134
                self.instance.shutdown_net()
Dudás Ádám committed
135 136 137 138 139
                self.instance.destroy_net()

            # Delete virtual machine
            with activity.sub_activity('destroying_vm'):
                self.instance.delete_vm()
Dudás Ádám committed
140 141 142

        # Destroy disks
        with activity.sub_activity('destroying_disks'):
Dudás Ádám committed
143
            self.instance.destroy_disks()
Dudás Ádám committed
144

Dudás Ádám committed
145 146 147 148 149 150 151 152 153
        # 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
154 155 156 157 158

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


159
register_operation(DestroyOperation)
Dudás Ádám committed
160 161


162
class MigrateOperation(InstanceOperation):
Dudás Ádám committed
163 164 165
    activity_code_suffix = 'migrate'
    id = 'migrate'
    name = _("migrate")
166
    description = _("Live migrate running VM to another node.")
167
    icon = 'truck'
Dudás Ádám committed
168

169
    def _operation(self, activity, user, system, to_node=None, timeout=120):
Dudás Ádám committed
170 171 172 173 174
        if not to_node:
            with activity.sub_activity('scheduling') as sa:
                to_node = self.instance.select_node()
                sa.result = to_node

Dudás Ádám committed
175 176 177
        # Shutdown networks
        with activity.sub_activity('shutdown_net'):
            self.instance.shutdown_net()
Dudás Ádám committed
178 179

        with activity.sub_activity('migrate_vm'):
Dudás Ádám committed
180 181
            self.instance.migrate_vm(to_node=to_node, timeout=timeout)

Dudás Ádám committed
182 183 184 185 186
        # Refresh node information
        self.instance.node = to_node
        self.instance.save()
        # Estabilish network connection (vmdriver)
        with activity.sub_activity('deploying_net'):
Dudás Ádám committed
187
            self.instance.deploy_net()
Dudás Ádám committed
188 189


190
register_operation(MigrateOperation)
Dudás Ádám committed
191 192


193
class RebootOperation(InstanceOperation):
Dudás Ádám committed
194 195 196
    activity_code_suffix = 'reboot'
    id = 'reboot'
    name = _("reboot")
197
    description = _("Reboot virtual machine with Ctrl+Alt+Del signal.")
198
    icon = 'refresh'
Dudás Ádám committed
199

200
    def _operation(self, activity, user, system, timeout=5):
Dudás Ádám committed
201
        self.instance.reboot_vm(timeout=timeout)
Dudás Ádám committed
202 203


204
register_operation(RebootOperation)
Dudás Ádám committed
205 206


207 208 209 210 211 212 213 214 215 216 217 218 219 220
class RemoveInterfaceOperation(InstanceOperation):
    activity_code_suffix = 'remove_interface'
    id = 'remove_interface'
    name = _("remove interface")
    description = _("Remove the specified network interface from the VM.")

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

        interface.destroy()
        interface.delete()


Bach Dániel committed
221
register_operation(RemoveInterfaceOperation)
222 223


224
class ResetOperation(InstanceOperation):
Dudás Ádám committed
225 226 227
    activity_code_suffix = 'reset'
    id = 'reset'
    name = _("reset")
228
    description = _("Reset virtual machine (reset button).")
229
    icon = 'bolt'
Dudás Ádám committed
230

231
    def _operation(self, activity, user, system, timeout=5):
Dudás Ádám committed
232
        self.instance.reset_vm(timeout=timeout)
Dudás Ádám committed
233

234
register_operation(ResetOperation)
Dudás Ádám committed
235 236


237
class SaveAsTemplateOperation(InstanceOperation):
Dudás Ádám committed
238 239 240 241 242 243 244 245
    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.
        """)
246
    icon = 'save'
Dudás Ádám committed
247

248 249 250 251 252 253
    @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)
254
        else:
255 256
            v = 1
        return "%s v%d" % (name, v)
257 258

    def _operation(self, activity, user, system, timeout=300,
259 260
                   with_shutdown=True, **kwargs):
        if with_shutdown:
261 262 263 264 265 266
            try:
                ShutdownOperation(self.instance).call(parent_activity=activity,
                                                      user=user)
            except Instance.WrongStateError:
                pass

Dudás Ádám committed
267 268 269 270 271 272 273 274
        # 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,
275
            'name': self._rename(self.instance.name),
Dudás Ádám committed
276 277 278 279 280 281 282 283 284 285
            '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)

286 287
        from storage.models import Disk

Dudás Ádám committed
288 289
        def __try_save_disk(disk):
            try:
Dudás Ádám committed
290
                return disk.save_as()
Dudás Ádám committed
291 292 293
            except Disk.WrongDiskTypeError:
                return disk

294 295 296 297
        with activity.sub_activity('saving_disks'):
            disks = [__try_save_disk(disk)
                     for disk in self.instance.disks.all()]

Dudás Ádám committed
298 299 300 301 302
        # create template and do additional setup
        tmpl = InstanceTemplate(**params)
        tmpl.full_clean()  # Avoiding database errors.
        tmpl.save()
        try:
303
            tmpl.disks.add(*disks)
Dudás Ádám committed
304 305 306 307 308 309 310 311 312 313
            # create interface templates
            for i in self.instance.interface_set.all():
                i.save_as_template(tmpl)
        except:
            tmpl.delete()
            raise
        else:
            return tmpl


314
register_operation(SaveAsTemplateOperation)
Dudás Ádám committed
315 316


317
class ShutdownOperation(InstanceOperation):
Dudás Ádám committed
318 319 320
    activity_code_suffix = 'shutdown'
    id = 'shutdown'
    name = _("shutdown")
321
    description = _("Shutdown virtual machine with ACPI signal.")
322
    icon = 'off'
Dudás Ádám committed
323

324 325 326 327 328
    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
329 330 331 332 333 334 335 336 337
    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 = 'STOPPED'

338
    def _operation(self, activity, user, system, timeout=120):
Dudás Ádám committed
339 340 341
        self.instance.shutdown_vm(timeout=timeout)
        self.instance.yield_node()
        self.instance.yield_vnc_port()
Dudás Ádám committed
342 343


344
register_operation(ShutdownOperation)
Dudás Ádám committed
345 346


347
class ShutOffOperation(InstanceOperation):
Dudás Ádám committed
348 349 350
    activity_code_suffix = 'shut_off'
    id = 'shut_off'
    name = _("shut off")
351
    description = _("Shut off VM (plug-out).")
352
    icon = 'ban-circle'
Dudás Ádám committed
353

354
    def on_commit(self, activity):
Dudás Ádám committed
355 356
        activity.resultant_state = 'STOPPED'

357
    def _operation(self, activity, user, system):
Dudás Ádám committed
358 359 360
        # Shutdown networks
        with activity.sub_activity('shutdown_net'):
            self.instance.shutdown_net()
Dudás Ádám committed
361

Dudás Ádám committed
362 363 364 365 366 367 368
        # 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
369 370


371
register_operation(ShutOffOperation)
Dudás Ádám committed
372 373


374
class SleepOperation(InstanceOperation):
Dudás Ádám committed
375 376 377
    activity_code_suffix = 'sleep'
    id = 'sleep'
    name = _("sleep")
378
    description = _("Suspend virtual machine with memory dump.")
379
    icon = 'moon'
Dudás Ádám committed
380 381

    def check_precond(self):
382
        super(SleepOperation, self).check_precond()
Dudás Ádám committed
383 384 385 386 387 388 389 390 391 392 393 394
        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'

395
    def _operation(self, activity, user, system, timeout=60):
Dudás Ádám committed
396
        # Destroy networks
Dudás Ádám committed
397 398
        with activity.sub_activity('shutdown_net'):
            self.instance.shutdown_net()
Dudás Ádám committed
399 400 401

        # Suspend vm
        with activity.sub_activity('suspending'):
Dudás Ádám committed
402 403 404 405
            self.instance.suspend_vm(timeout=timeout)

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


408
register_operation(SleepOperation)
Dudás Ádám committed
409 410


411
class WakeUpOperation(InstanceOperation):
Dudás Ádám committed
412 413 414 415 416 417 418
    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.
        """)
419
    icon = 'sun'
Dudás Ádám committed
420 421

    def check_precond(self):
422
        super(WakeUpOperation, self).check_precond()
Dudás Ádám committed
423 424 425 426 427 428 429 430 431
        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'

432
    def _operation(self, activity, user, system, timeout=60):
Dudás Ádám committed
433
        # Schedule vm
Dudás Ádám committed
434 435
        self.instance.allocate_vnc_port()
        self.instance.allocate_node()
Dudás Ádám committed
436 437 438

        # Resume vm
        with activity.sub_activity('resuming'):
Dudás Ádám committed
439
            self.instance.wake_up_vm(timeout=timeout)
Dudás Ádám committed
440 441 442

        # Estabilish network connection (vmdriver)
        with activity.sub_activity('deploying_net'):
Dudás Ádám committed
443
            self.instance.deploy_net()
Dudás Ádám committed
444 445 446 447 448

        # Renew vm
        self.instance.renew(which='both', base_activity=activity)


449
register_operation(WakeUpOperation)
450 451 452 453


class NodeOperation(Operation):
    async_operation = async_node_operation
454
    host_cls = Node
455 456 457 458 459

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

460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
    def create_activity(self, parent, user):
        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.")

            return parent.create_sub(code_suffix=self.activity_code_suffix)
        else:
            return NodeActivity.create(code_suffix=self.activity_code_suffix,
                                       node=self.node, user=user)
475 476 477 478 479 480


class FlushOperation(NodeOperation):
    activity_code_suffix = 'flush'
    id = 'flush'
    name = _("flush")
481
    description = _("Disable node and move all instances to other ones.")
482 483 484 485 486 487 488 489

    def _operation(self, activity, user, system):
        self.node.disable(user, activity)
        for i in self.node.instance_set.all():
            with activity.sub_activity('migrate_instance_%d' % i.pk):
                i.migrate()


490
register_operation(FlushOperation)