operations.py 14.8 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.")
Dudás Ádám committed
88 89 90 91

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

Dudás Ádám committed
92 93 94 95
    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
96 97 98

        # Deploy virtual images
        with activity.sub_activity('deploying_disks'):
Dudás Ádám committed
99 100 101 102 103 104 105 106 107 108 109 110 111
            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
112

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


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


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

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

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

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

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

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

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


157
register_operation(DestroyOperation)
Dudás Ádám committed
158 159


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

166
    def _operation(self, activity, user, system, to_node=None, timeout=120):
Dudás Ádám committed
167 168 169 170 171
        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
172 173 174
        # Shutdown networks
        with activity.sub_activity('shutdown_net'):
            self.instance.shutdown_net()
Dudás Ádám committed
175 176

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

Dudás Ádám committed
179 180 181 182 183
        # 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
184
            self.instance.deploy_net()
Dudás Ádám committed
185 186


187
register_operation(MigrateOperation)
Dudás Ádám committed
188 189


190
class RebootOperation(InstanceOperation):
Dudás Ádám committed
191 192 193
    activity_code_suffix = 'reboot'
    id = 'reboot'
    name = _("reboot")
194
    description = _("Reboot virtual machine with Ctrl+Alt+Del signal.")
Dudás Ádám committed
195

196
    def _operation(self, activity, user, system, timeout=5):
Dudás Ádám committed
197
        self.instance.reboot_vm(timeout=timeout)
Dudás Ádám committed
198 199


200
register_operation(RebootOperation)
Dudás Ádám committed
201 202


203 204 205 206 207 208 209 210 211 212 213 214 215 216
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
217
register_operation(RemoveInterfaceOperation)
218 219


220
class ResetOperation(InstanceOperation):
Dudás Ádám committed
221 222 223
    activity_code_suffix = 'reset'
    id = 'reset'
    name = _("reset")
224
    description = _("Reset virtual machine (reset button).")
Dudás Ádám committed
225

226
    def _operation(self, activity, user, system, timeout=5):
Dudás Ádám committed
227
        self.instance.reset_vm(timeout=timeout)
Dudás Ádám committed
228

229
register_operation(ResetOperation)
Dudás Ádám committed
230 231


232
class SaveAsTemplateOperation(InstanceOperation):
Dudás Ádám committed
233 234 235 236 237 238 239 240 241
    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.
        """)

242 243 244 245 246 247
    @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)
248
        else:
249 250
            v = 1
        return "%s v%d" % (name, v)
251 252

    def _operation(self, activity, user, system, timeout=300,
253 254
                   with_shutdown=True, **kwargs):
        if with_shutdown:
255 256 257 258 259 260
            try:
                ShutdownOperation(self.instance).call(parent_activity=activity,
                                                      user=user)
            except Instance.WrongStateError:
                pass

Dudás Ádám committed
261 262 263 264 265 266 267 268
        # 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,
269
            'name': self._rename(self.instance.name),
Dudás Ádám committed
270 271 272 273 274 275 276 277 278 279
            '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)

280 281
        from storage.models import Disk

Dudás Ádám committed
282 283
        def __try_save_disk(disk):
            try:
Dudás Ádám committed
284
                return disk.save_as()
Dudás Ádám committed
285 286 287
            except Disk.WrongDiskTypeError:
                return disk

288 289 290 291
        with activity.sub_activity('saving_disks'):
            disks = [__try_save_disk(disk)
                     for disk in self.instance.disks.all()]

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


308
register_operation(SaveAsTemplateOperation)
Dudás Ádám committed
309 310


311
class ShutdownOperation(InstanceOperation):
Dudás Ádám committed
312 313 314
    activity_code_suffix = 'shutdown'
    id = 'shutdown'
    name = _("shutdown")
315
    description = _("Shutdown virtual machine with ACPI signal.")
Dudás Ádám committed
316

317 318 319 320 321
    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
322 323 324 325 326 327 328 329 330
    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'

331
    def _operation(self, activity, user, system, timeout=120):
Dudás Ádám committed
332 333 334
        self.instance.shutdown_vm(timeout=timeout)
        self.instance.yield_node()
        self.instance.yield_vnc_port()
Dudás Ádám committed
335 336


337
register_operation(ShutdownOperation)
Dudás Ádám committed
338 339


340
class ShutOffOperation(InstanceOperation):
Dudás Ádám committed
341 342 343
    activity_code_suffix = 'shut_off'
    id = 'shut_off'
    name = _("shut off")
344
    description = _("Shut off VM (plug-out).")
Dudás Ádám committed
345

346
    def on_commit(self, activity):
Dudás Ádám committed
347 348
        activity.resultant_state = 'STOPPED'

349
    def _operation(self, activity, user, system):
Dudás Ádám committed
350 351 352
        # Shutdown networks
        with activity.sub_activity('shutdown_net'):
            self.instance.shutdown_net()
Dudás Ádám committed
353

Dudás Ádám committed
354 355 356 357 358 359 360
        # 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
361 362


363
register_operation(ShutOffOperation)
Dudás Ádám committed
364 365


366
class SleepOperation(InstanceOperation):
Dudás Ádám committed
367 368 369
    activity_code_suffix = 'sleep'
    id = 'sleep'
    name = _("sleep")
370
    description = _("Suspend virtual machine with memory dump.")
Dudás Ádám committed
371 372

    def check_precond(self):
373
        super(SleepOperation, self).check_precond()
Dudás Ádám committed
374 375 376 377 378 379 380 381 382 383 384 385
        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'

386
    def _operation(self, activity, user, system, timeout=60):
Dudás Ádám committed
387
        # Destroy networks
Dudás Ádám committed
388 389
        with activity.sub_activity('shutdown_net'):
            self.instance.shutdown_net()
Dudás Ádám committed
390 391 392

        # Suspend vm
        with activity.sub_activity('suspending'):
Dudás Ádám committed
393 394 395 396
            self.instance.suspend_vm(timeout=timeout)

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


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


402
class WakeUpOperation(InstanceOperation):
Dudás Ádám committed
403 404 405 406 407 408 409 410 411
    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.
        """)

    def check_precond(self):
412
        super(WakeUpOperation, self).check_precond()
Dudás Ádám committed
413 414 415 416 417 418 419 420 421
        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'

422
    def _operation(self, activity, user, system, timeout=60):
Dudás Ádám committed
423
        # Schedule vm
Dudás Ádám committed
424 425
        self.instance.allocate_vnc_port()
        self.instance.allocate_node()
Dudás Ádám committed
426 427 428

        # Resume vm
        with activity.sub_activity('resuming'):
Dudás Ádám committed
429
            self.instance.wake_up_vm(timeout=timeout)
Dudás Ádám committed
430 431 432

        # Estabilish network connection (vmdriver)
        with activity.sub_activity('deploying_net'):
Dudás Ádám committed
433
            self.instance.deploy_net()
Dudás Ádám committed
434 435 436 437 438

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


439
register_operation(WakeUpOperation)
440 441 442 443


class NodeOperation(Operation):
    async_operation = async_node_operation
444
    host_cls = Node
445 446 447 448 449

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

450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
    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)
465 466 467 468 469 470


class FlushOperation(NodeOperation):
    activity_code_suffix = 'flush'
    id = 'flush'
    name = _("flush")
471
    description = _("Disable node and move all instances to other ones.")
472 473 474 475 476 477 478 479

    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()


480
register_operation(FlushOperation)