1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-11 04:36:48 +00:00

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.
This commit is contained in:
catborise 2026-11-01 17:59:39 +03:00
parent 940e639149
commit cf991adad5
8 changed files with 74 additions and 23 deletions

View file

@ -0,0 +1,19 @@
# Generated by Django 5.2.18 on 2026-11-01 14:52
import computes.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('computes', '0007_gstfsd_key'),
]
operations = [
migrations.AlterField(
model_name='compute',
name='migration_address',
field=models.CharField(blank=True, default='', help_text='IP address or host name the other hosts send migrating VMs to; empty: the address the panel reaches the host at', max_length=64, validators=[computes.validators.validate_migration_address], verbose_name='migration address'),
),
]

View file

@ -26,7 +26,7 @@ class Compute(Model):
validators=[validate_migration_address],
help_text=_(
"IP address or host name the other hosts send migrating VMs to; "
"empty: the host's own host name, which they must be able to resolve"
"empty: the address the panel reaches the host at"
),
)
gstfsd_key = CharField(
@ -43,16 +43,18 @@ class Compute(Model):
@property
def migration_uri(self):
"""The native migration URI for VMs migrated to this host, or None for
libvirt's default."""
if not self.migration_address:
return None
"""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:
if ip_address(self.migration_address).version == 6:
return f"tcp://[{self.migration_address}]"
ip = ip_address(address)
except ValueError:
pass
return f"tcp://{self.migration_address}"
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):

View file

@ -26,11 +26,19 @@ class MigrationAddressTests(TestCase):
validate_migration_address(value)
def test_migration_uri(self):
cases = {"": None, "192.0.2.12": "tcp://192.0.2.12", "kvm102.example.org": "tcp://kvm102.example.org",
cases = {"192.0.2.12": "tcp://192.0.2.12", "kvm102.example.org": "tcp://kvm102.example.org",
"2001:db8::12": "tcp://[2001:db8::12]"}
for address, uri in cases.items():
with self.subTest(address=address):
self.assertEqual(Compute(migration_address=address).migration_uri, uri)
self.assertEqual(Compute(hostname="192.0.2.40", migration_address=address).migration_uri, uri)
def test_without_a_migration_address_the_panel_s_address_of_the_host_is_used(self):
# no DNS name of the host's own; a host the panel reaches on its loopback: libvirt's default
cases = {"192.0.2.40": "tcp://192.0.2.40", "kvm103.example.org": "tcp://kvm103.example.org",
"localhost": None, "LOCALHOST": None, "localhost.": None, "127.0.0.1": None}
for hostname, uri in cases.items():
with self.subTest(hostname=hostname):
self.assertEqual(Compute(hostname=hostname, migration_address="").migration_uri, uri)
def test_the_compute_form_saves_it(self):
self.client.force_login(get_user_model().objects.create_superuser("ma-admin", "ma@example.com", "pw"))