from django.db import models from django.conf import settings from django.contrib.auth.models import User from django.utils import timezone from datetime import timedelta from image.models import Disk from interface_openstack.implementation.vm.instance import ( OSVirtualMachineManager ) import logging logger = logging.getLogger(__name__) interface = OSVirtualMachineManager(settings.CONNECTION) ACTIONS = { "start": interface.start_vm, "stop": interface.stop_vm, "suspend": interface.suspend_vm, "wake_up": interface.wake_up_vm, "reset": interface.reset_vm, "reboot": interface.reboot_vm, } class Lease(models.Model): """ Users can use the virtual machine until the lease dates. After suspend interval the vm suspends and after delete it will be destroyed """ name = models.CharField(max_length=100) description = models.CharField(blank=True, max_length=100) suspend_interval_in_sec = models.IntegerField(default=3600) delete_interval_in_sec = models.IntegerField(default=7200) class Flavor(models.Model): """ Flavors are reasource packages, contains all information about resources accociated with the virtual machine """ name = models.CharField(blank=False, max_length=100) description = models.CharField(blank=True, max_length=200) remote_id = models.CharField( max_length=100, help_text="ID of the instance on the backend" ) ram = models.PositiveIntegerField(default=0) vcpu = models.PositiveIntegerField(default=0) initial_disk = models.PositiveIntegerField(default=0) priority = models.PositiveIntegerField(blank=True, null=True) @classmethod def create(cls, name, description, ram=0, vcpu=0, initial_disk=0, priority=0): try: remote_flavor = interface.create_flavor(name, ram, vcpu, initial_disk) flavor = cls(name=name, description=description, remote_id=remote_flavor.id, ram=ram, vcpu=vcpu, initial_disk=initial_disk, priority=priority) flavor.save() return flavor except Exception as e: logger.error(str(e)) raise ValueError("Can't create Flavor in remote cloud.") def delete(self): try: interface.delete_flavor(self.remote_id) super(Flavor, self).delete() except Exception as e: if e.OpenStackError: logger.error("Can not delete the flavor in remote cloud") class BaseMachineDescriptor(models.Model): """Virtual machine template. """ OSTypes = ( ('LINUX', 'The system based on Linux'), ('WINDOWS', 'Windows based system'), ) CONN_PROTOCOL = tuple( [(key, val[0]) for key, val in settings.VM_ACCESS_PROTOCOLS.items()] ) name = models.CharField(max_length=100, verbose_name="name", help_text="Human readable name of template.") description = models.TextField(verbose_name="description", blank=True, help_text="Description of the template.") system_type = models.CharField(max_length=50, choices=OSTypes, verbose_name="operating_system", help_text="The base name of the operating system" ) distro = models.CharField(max_length=100, blank=True, verbose_name='system distribution', help_text="The specific name and version of the installed OS" "e. g. Win 7, Ubuntu 18.04" ) access_protocol = models.CharField(max_length=50, choices=CONN_PROTOCOL, verbose_name="remote_access_protocol", help_text="The protocol which used to connect to the machine" "that created from this template" ) created_at = models.DateTimeField(auto_now_add=True, editable=False, help_text="Date, when the template created.") created_by = models.ForeignKey(User, on_delete=models.DO_NOTHING, related_name="created_templates", help_text="The user, who create the template") flavor = models.ForeignKey(Flavor, help_text="Resources given to the vm", verbose_name="flavor", on_delete=models.CASCADE, related_name='templates') lease = models.ForeignKey(Lease, on_delete=models.CASCADE, related_name='templates') network_id = models.CharField(max_length=100, verbose_name="network_id", help_text="The new instance will be in this network.", null=True, blank=True) class Instance(BaseMachineDescriptor): """Virtual machine instance. """ from template.models import ImageTemplate class Meta: default_permissions = () permissions = ( ('create_instance', 'Can create a new VM.'), ('create_template_from_instance', 'Can create template from instance.'), ('use_instance', 'Can access the VM connection info.'), ('operate_instance', 'Can use basic lifecycle methods of the VM.'), ('administer_instance', 'Can delete VM.'), ('access_console', 'Can access the graphical console of a VM.'), ('change_resources', 'Can change resources of a VM.'), ('manage_access', 'Can manage access rights for the VM.'), ('config_ports', 'Can configure port forwards.'), ) remote_id = models.CharField( max_length=100, help_text="ID of the instance on the backend" ) password = models.CharField( max_length=50, help_text="Original password of the instance" ) time_of_suspend = models.DateTimeField( blank=True, help_text="After this point in time, the instance will be suspended" ) time_of_delete = models.DateTimeField( blank=True, help_text="After this point in time, the instance will be deleted!" ) status = models.CharField(max_length=50, verbose_name="instance_status", default="NO_STATE") deleted = models.BooleanField( help_text="Indicates if the instance is ready for garbage collection", default=False, ) template = models.ForeignKey(ImageTemplate, related_name="vm", null=True, help_text="The base image of the vm", on_delete=models.DO_NOTHING) disks = models.ManyToManyField(Disk, related_name="instance", blank=True, help_text="Disks attached to instance", verbose_name="disks") @classmethod def create(cls, lease, owner, flavor, template, remote_id, params): params["password"] = cls.generate_password() inst = cls(lease=lease, flavor=flavor, created_by=owner, system_type=template.system_type, distro=template.distro, access_protocol=template.access_protocol, remote_id=remote_id, template=template, status="CREATING", **params) inst.full_clean() inst.save() logger.info("New instance created") return inst @classmethod def create_instance_from_template(cls, params, template, owner, lease, networks, flavor): try: remote_id = interface.create_vm_from_template(params["name"], template.image.remote_id, flavor.remote_id, networks, ) new_inst = cls.create(lease, owner, flavor, template, remote_id, params) return new_inst except Exception as e: logger.error(str(e)) raise ValueError("Can't create Instance in remote cloud." "Search the logs for more detail.") def clean(self, *args, **kwargs): self.time_of_suspend, self.time_of_delete = self.get_renew_times() super(Instance, self).clean(*args, **kwargs) def get_renew_times(self, lease=None): """Returns new suspend and delete times if renew would be called. """ if lease is None: lease = self.lease return ( timezone.now() + timedelta( seconds=lease.suspend_interval_in_sec), timezone.now() + timedelta( seconds=lease.delete_interval_in_sec) ) def renew(self, lease=None): """Renew virtual machine, if a new lease is provided it changes it as well. """ if lease is None: lease = self.lease else: self.lease = lease self.time_of_suspend, self.time_of_delete = self.get_renew_times(lease) self.save() def delete(self): try: interface.destroy_vm(self.remote_id) super(Instance, self).delete() except Exception as e: if e.OpenStackError: logger.error("Can not delete the instance in remote cloud") def execute_common_action(self, action): if ACTIONS[action]: return ACTIONS[action](self.remote_id) else: raise ValueError("This action is not supported!") def get_remote_instance(self): return interface.get_vm(self.remote_id) def update_status(self): remote = self.get_remote_instance() self.status = remote.status self.save() @classmethod def generate_password(self): return User.objects.make_random_password( allowed_chars='abcdefghijklmnopqrstuvwx' 'ABCDEFGHIJKLMNOPQRSTUVWX123456789') def reset_password(self): self.password = self.generate_password() self.save() def change_name(self, new_name): self.name = new_name self.save() def change_description(self, new_description): self.description = new_description self.save() def destroy(self): self.deleted = True self.save()