from ipaddress import ip_address from django.db import transaction from django.db.models import CharField, IntegerField, Model from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ from libvirt import libvirtError, virConnect from computes.validators import validate_gstfsd_key, validate_hostname, validate_login, validate_migration_address from vrtManager.connection import connection_manager from vrtManager.hostdetails import wvmHostDetails class Compute(Model): name = CharField(_("name"), max_length=64, unique=True) hostname = CharField(_("hostname"), max_length=64, validators=[validate_hostname]) login = CharField(_("login"), max_length=32, blank=True, validators=[validate_login]) password = CharField(_("password"), max_length=255, blank=True, null=True) details = CharField(_("details"), max_length=64, null=True, blank=True) type = IntegerField() migration_address = CharField( _("migration address"), max_length=64, blank=True, default="", validators=[validate_migration_address], help_text=_( "IP address or host name the other hosts send migrating VMs to; " "empty: the address the panel reaches the host at" ), ) gstfsd_key = CharField( _("gstfsd key"), max_length=64, blank=True, default="", validators=[validate_gstfsd_key], help_text=_( "GSTFSD_KEY in the host's /etc/default/webvirt-gstfsd: the panel sets the root password and SSH key " "of a shut-off VM with it; empty: they cannot be set there" ), ) @property def migration_uri(self): """The native migration URI for VMs migrated to this host: its migration address, or else the address the panel reaches it at, so that the other hosts need not resolve its own name. None, libvirt's default (that name), for a host the panel reaches on its loopback.""" address = self.migration_address or self.hostname try: ip = ip_address(address) except ValueError: ip = None if not self.migration_address and (address.lower().rstrip(".") == "localhost" or ip and ip.is_loopback): return None return f"tcp://[{address}]" if ip and ip.version == 6 else f"tcp://{address}" @cached_property def status(self): """True if WebVirtCloud can connect to the host (within the connect timeout; a host that failed recently is not tried again at once).""" return isinstance(self.connection, virConnect) @cached_property def connection_error(self): """Why the host cannot be reached, or None.""" return None if self.status else str(self.connection) @cached_property def connection(self): try: return connection_manager.get_connection( self.hostname, self.login, self.password, self.type, ) except Exception as e: return e @cached_property def proxy(self): return wvmHostDetails( self.hostname, self.login, self.password, self.type, ) @cached_property def cpu_count(self): return self.proxy.get_node_info()[3] @cached_property def ram_size(self): return self.proxy.get_node_info()[2] @cached_property def ram_usage(self): return self.proxy.get_memory_usage()["percent"] @cached_property def capacity(self): """What the host has given its VMs (wvmHostDetails.get_capacity) and its vCPUs per CPU; or None when the host cannot be read. Not its pools: their space is measured by a refresh that scans each pool, too slow for pages that read every host (computes.views.host_pools).""" if self.status is not True: return None try: given = self.proxy.get_capacity() except libvirtError: return None given["ratio"] = given["vcpus"]["all"] / given["cpus"] if given["cpus"] else 0 return given def __str__(self): return self.name def delete(self, *args, **kwargs): """Its VMs keep running on the host: their owners wait in tombstones, or follow a VM already listed on another compute. The compute is held exclusive, so no reconcile, VM operation or job on it runs meanwhile (Busy if one does); its row is locked before its VMs', as the reconcile does.""" from computes.utils import forget, libvirt_compute_lock, lock_ownership with libvirt_compute_lock(self), transaction.atomic(): lock_ownership() Compute.objects.select_for_update().filter(pk=self.pk).first() for inst in self.instance_set.select_for_update(): forget(inst) return super().delete(*args, **kwargs)