libvirt: prepare domain XML update for serial ports
The serial console feature uses "ports" on a compute node to enable
a websocket connection. On a host, one port can only be used by one
single instance. If another instance gets live-migrated to that host,
an update of the ports in the domain XML is needed. Otherwise we will
get an exception from libvirt/qemu which states:
Failed to bind socket: Cannot assign requested address
This change here *prepares* the update of the domain XML. Another change
is necessary which actually provides the ports to use for this update
function within the LibvirtLiveMigrateData object.
This change is a preparation for the fix of bug 1455252.
Co-Authored-By: Sahid Orentino Ferdjaoui <sahid.ferdjaoui@redhat.com>
Change-Id: I531ec76bc82beaea89f3a94a72e8a75627a9aa23
(cherry picked from commit 6a85a98822
)
This commit is contained in:
parent
3197c157fa
commit
d663437aca
@ -83,6 +83,17 @@ class UtilityMigrationTestCase(test.NoDBTestCase):
|
|||||||
addr = migration.serial_listen_addr(data)
|
addr = migration.serial_listen_addr(data)
|
||||||
self.assertIsNone(addr)
|
self.assertIsNone(addr)
|
||||||
|
|
||||||
|
def test_serial_listen_ports(self):
|
||||||
|
data = objects.LibvirtLiveMigrateData(
|
||||||
|
serial_listen_ports=[1, 2, 3])
|
||||||
|
ports = migration.serial_listen_ports(data)
|
||||||
|
self.assertEqual([1, 2, 3], ports)
|
||||||
|
|
||||||
|
def test_serial_listen_ports_emtpy(self):
|
||||||
|
data = objects.LibvirtLiveMigrateData()
|
||||||
|
ports = migration.serial_listen_ports(data)
|
||||||
|
self.assertEqual([], ports)
|
||||||
|
|
||||||
@mock.patch('lxml.etree.tostring')
|
@mock.patch('lxml.etree.tostring')
|
||||||
@mock.patch.object(migration, '_update_perf_events_xml')
|
@mock.patch.object(migration, '_update_perf_events_xml')
|
||||||
@mock.patch.object(migration, '_update_graphics_xml')
|
@mock.patch.object(migration, '_update_graphics_xml')
|
||||||
@ -105,26 +116,60 @@ class UtilityMigrationTestCase(test.NoDBTestCase):
|
|||||||
|
|
||||||
def test_update_serial_xml_serial(self):
|
def test_update_serial_xml_serial(self):
|
||||||
data = objects.LibvirtLiveMigrateData(
|
data = objects.LibvirtLiveMigrateData(
|
||||||
serial_listen_addr='127.0.0.100')
|
serial_listen_addr='127.0.0.100',
|
||||||
|
serial_listen_ports=[2001])
|
||||||
xml = """<domain>
|
xml = """<domain>
|
||||||
<devices>
|
<devices>
|
||||||
<serial type="tcp">
|
<serial type="tcp">
|
||||||
<source host="127.0.0.1"/>
|
<source host="127.0.0.1" service="2000"/>
|
||||||
|
<target type="serial" port="0"/>
|
||||||
</serial>
|
</serial>
|
||||||
</devices>
|
</devices>
|
||||||
</domain>"""
|
</domain>"""
|
||||||
doc = etree.fromstring(xml)
|
doc = etree.fromstring(xml)
|
||||||
res = etree.tostring(migration._update_serial_xml(doc, data))
|
res = etree.tostring(migration._update_serial_xml(doc, data))
|
||||||
new_xml = xml.replace("127.0.0.1", "127.0.0.100")
|
new_xml = xml.replace("127.0.0.1", "127.0.0.100").replace(
|
||||||
|
"2000", "2001")
|
||||||
self.assertThat(res, matchers.XMLMatches(new_xml))
|
self.assertThat(res, matchers.XMLMatches(new_xml))
|
||||||
|
|
||||||
def test_update_serial_xml_console(self):
|
def test_update_serial_xml_console(self):
|
||||||
data = objects.LibvirtLiveMigrateData(
|
data = objects.LibvirtLiveMigrateData(
|
||||||
serial_listen_addr='127.0.0.100')
|
serial_listen_addr='127.0.0.100',
|
||||||
|
serial_listen_ports=[299, 300])
|
||||||
xml = """<domain>
|
xml = """<domain>
|
||||||
<devices>
|
<devices>
|
||||||
<console type="tcp">
|
<console type="tcp">
|
||||||
<source host="127.0.0.1"/>
|
<source host="127.0.0.1" service="2001"/>
|
||||||
|
<target type="serial" port="0"/>
|
||||||
|
</console>
|
||||||
|
<console type="tcp">
|
||||||
|
<source host="127.0.0.1" service="2002"/>
|
||||||
|
<target type="serial" port="1"/>
|
||||||
|
</console>
|
||||||
|
</devices>
|
||||||
|
</domain>"""
|
||||||
|
doc = etree.fromstring(xml)
|
||||||
|
res = etree.tostring(migration._update_serial_xml(doc, data))
|
||||||
|
new_xml = xml.replace("127.0.0.1", "127.0.0.100").replace(
|
||||||
|
"2001", "299").replace("2002", "300")
|
||||||
|
|
||||||
|
self.assertThat(res, matchers.XMLMatches(new_xml))
|
||||||
|
|
||||||
|
def test_update_serial_xml_without_ports(self):
|
||||||
|
# This test is for backwards compatibility when we don't
|
||||||
|
# get the serial ports from the target node.
|
||||||
|
data = objects.LibvirtLiveMigrateData(
|
||||||
|
serial_listen_addr='127.0.0.100',
|
||||||
|
serial_listen_ports=[])
|
||||||
|
xml = """<domain>
|
||||||
|
<devices>
|
||||||
|
<console type="tcp">
|
||||||
|
<source host="127.0.0.1" service="2001"/>
|
||||||
|
<target type="serial" port="0"/>
|
||||||
|
</console>
|
||||||
|
<console type="tcp">
|
||||||
|
<source host="127.0.0.1" service="2002"/>
|
||||||
|
<target type="serial" port="1"/>
|
||||||
</console>
|
</console>
|
||||||
</devices>
|
</devices>
|
||||||
</domain>"""
|
</domain>"""
|
||||||
|
@ -65,6 +65,15 @@ def serial_listen_addr(migrate_data):
|
|||||||
return listen_addr
|
return listen_addr
|
||||||
|
|
||||||
|
|
||||||
|
# TODO(sahid): remove me for Q*
|
||||||
|
def serial_listen_ports(migrate_data):
|
||||||
|
"""Returns ports serial from a LibvirtLiveMigrateData"""
|
||||||
|
ports = []
|
||||||
|
if migrate_data.obj_attr_is_set('serial_listen_ports'):
|
||||||
|
ports = migrate_data.serial_listen_ports
|
||||||
|
return ports
|
||||||
|
|
||||||
|
|
||||||
def get_updated_guest_xml(guest, migrate_data, get_volume_config):
|
def get_updated_guest_xml(guest, migrate_data, get_volume_config):
|
||||||
xml_doc = etree.fromstring(guest.get_xml_desc(dump_migratable=True))
|
xml_doc = etree.fromstring(guest.get_xml_desc(dump_migratable=True))
|
||||||
xml_doc = _update_graphics_xml(xml_doc, migrate_data)
|
xml_doc = _update_graphics_xml(xml_doc, migrate_data)
|
||||||
@ -91,12 +100,31 @@ def _update_graphics_xml(xml_doc, migrate_data):
|
|||||||
|
|
||||||
def _update_serial_xml(xml_doc, migrate_data):
|
def _update_serial_xml(xml_doc, migrate_data):
|
||||||
listen_addr = serial_listen_addr(migrate_data)
|
listen_addr = serial_listen_addr(migrate_data)
|
||||||
for dev in xml_doc.findall("./devices/serial[@type='tcp']/source"):
|
listen_ports = serial_listen_ports(migrate_data)
|
||||||
if dev.get('host') is not None:
|
|
||||||
dev.set('host', listen_addr)
|
def set_listen_addr_and_port(source, listen_addr, serial_listen_ports):
|
||||||
for dev in xml_doc.findall("./devices/console[@type='tcp']/source"):
|
# The XML nodes can be empty, which would make checks like
|
||||||
if dev.get('host') is not None:
|
# "if source.get('host'):" different to an explicit check for
|
||||||
dev.set('host', listen_addr)
|
# None. That's why we have to check for None in this method.
|
||||||
|
if source.get('host') is not None:
|
||||||
|
source.set('host', listen_addr)
|
||||||
|
device = source.getparent()
|
||||||
|
target = device.find("target")
|
||||||
|
if target is not None and source.get('service') is not None:
|
||||||
|
port_index = int(target.get('port'))
|
||||||
|
# NOTE (markus_z): Previous releases might not give us the
|
||||||
|
# ports yet, that's why we have this check here.
|
||||||
|
if len(serial_listen_ports) > port_index:
|
||||||
|
source.set('service', str(serial_listen_ports[port_index]))
|
||||||
|
|
||||||
|
# This updates all "LibvirtConfigGuestSerial" devices
|
||||||
|
for source in xml_doc.findall("./devices/serial[@type='tcp']/source"):
|
||||||
|
set_listen_addr_and_port(source, listen_addr, listen_ports)
|
||||||
|
|
||||||
|
# This updates all "LibvirtConfigGuestConsole" devices
|
||||||
|
for source in xml_doc.findall("./devices/console[@type='tcp']/source"):
|
||||||
|
set_listen_addr_and_port(source, listen_addr, listen_ports)
|
||||||
|
|
||||||
return xml_doc
|
return xml_doc
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user