1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00
webvirtcloud/computes/models.py
catborise cf991adad5 fix(computes): a VM migrates to its host's panel address when it has no migration address
Without a migration address libvirt sends a migrating VM to the
destination's own host name, which the source host often cannot resolve:
a live migration to a host added without one failed ("Unable to resolve
address ..."), also when the other host had one. The destination's
address on the Computes page, which the panel reaches it at, is now the
default; the migration address stays for another network. A host the
panel reaches on its loopback keeps libvirt's default.
2026-11-01 18:41:07 +03:00

134 lines
4.9 KiB
Python

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)