libvirt: virtlogd: use virtlogd for char devices

This change makes actual usage of the "logd" sub-element for char devices.
The two REST APIs ``os-getConsoleOutput`` and ``os-getSerialConsole`` can
now be satisfied at the same time. This is valid for any combination of:
* char device element: "console", "serial"
* char device type: "tcp", "pty"
There is also no need to create multiple different device types anymore.
If we have a tcp device, we don't need the pty device anymore. The logging
will be done in the tcp device.

Implements blueprint libvirt-virtlogd
Closes-Bug: 832507
Change-Id: Ia412f55bd988f6e11cd78c4c5a50a86389e648b0
This commit is contained in:
Markus Zoeller 2016-12-06 11:40:25 +01:00
parent e718aadeb4
commit 1f659251c7
2 changed files with 114 additions and 10 deletions

View File

@ -3829,6 +3829,45 @@ class LibvirtConnTestCase(test.NoDBTestCase):
self.assertEqual("tcp", cfg.devices[3].type) self.assertEqual("tcp", cfg.devices[3].type)
self.assertEqual("tcp", cfg.devices[4].type) self.assertEqual("tcp", cfg.devices[4].type)
@mock.patch.object(host.Host, 'has_min_version', return_value=True)
@mock.patch('nova.console.serial.acquire_port')
@mock.patch('nova.virt.hardware.get_number_of_serial_ports',
return_value=1)
@mock.patch.object(libvirt_driver.libvirt_utils, 'get_arch',)
def test_guest_config_char_device_logd(self, mock_get_arch,
mock_get_number_serial_ports,
mock_acquire_port,
mock_host_has_min_version):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
def _test_consoles(arch_to_mock, serial_enabled,
expected_device_type, expected_device_cls):
guest_cfg = vconfig.LibvirtConfigGuest()
mock_get_arch.return_value = arch_to_mock
self.flags(enabled=serial_enabled, group='serial_console')
instance = objects.Instance(**self.test_instance)
drvr._create_consoles("qemu", guest_cfg, instance=instance,
flavor=None, image_meta=None)
self.assertEqual(1, len(guest_cfg.devices))
device = guest_cfg.devices[0]
self.assertEqual(expected_device_type, device.type)
self.assertIsInstance(device, expected_device_cls)
self.assertIsInstance(device.log,
vconfig.LibvirtConfigGuestCharDeviceLog)
self.assertEqual("off", device.log.append)
self.assertTrue(device.log.file.endswith("console.log"))
_test_consoles(fields.Architecture.X86_64, True,
"tcp", vconfig.LibvirtConfigGuestSerial)
_test_consoles(fields.Architecture.X86_64, False,
"pty", vconfig.LibvirtConfigGuestSerial)
_test_consoles(fields.Architecture.S390, True,
"tcp", vconfig.LibvirtConfigGuestConsole)
_test_consoles(fields.Architecture.S390X, False,
"pty", vconfig.LibvirtConfigGuestConsole)
@mock.patch('nova.console.serial.acquire_port') @mock.patch('nova.console.serial.acquire_port')
def test_get_guest_config_serial_console_through_port_rng_exhausted( def test_get_guest_config_serial_console_through_port_rng_exhausted(
self, acquire_port): self, acquire_port):

View File

@ -269,6 +269,10 @@ MIN_QEMU_S390_VERSION = (2, 3, 0)
# libvirt 1.3 fix f391889f4e942e22b9ef8ecca492de05106ce41e # libvirt 1.3 fix f391889f4e942e22b9ef8ecca492de05106ce41e
MIN_LIBVIRT_PF_WITH_NO_VFS_CAP_VERSION = (1, 3, 0) MIN_LIBVIRT_PF_WITH_NO_VFS_CAP_VERSION = (1, 3, 0)
# Use the "logd" backend for handling stdout/stderr from QEMU processes.
MIN_LIBVIRT_VIRTLOGD = (1, 3, 3)
MIN_QEMU_VIRTLOGD = (2, 7, 0)
# ppc64/ppc64le architectures with KVM # ppc64/ppc64le architectures with KVM
# NOTE(rfolco): Same levels for Libvirt/Qemu on Big Endian and Little # NOTE(rfolco): Same levels for Libvirt/Qemu on Big Endian and Little
# Endian giving the nuance around guest vs host architectures # Endian giving the nuance around guest vs host architectures
@ -595,6 +599,10 @@ class LibvirtDriver(driver.ComputeDriver):
return True return True
return False return False
def _is_virtlogd_available(self):
return self._host.has_min_version(MIN_LIBVIRT_VIRTLOGD,
MIN_QEMU_VIRTLOGD)
def _handle_live_migration_post_copy(self, migration_flags): def _handle_live_migration_post_copy(self, migration_flags):
if CONF.libvirt.live_migration_permit_post_copy: if CONF.libvirt.live_migration_permit_post_copy:
if self._is_post_copy_available(): if self._is_post_copy_available():
@ -4364,6 +4372,21 @@ class LibvirtDriver(driver.ComputeDriver):
def _create_consoles(self, virt_type, guest_cfg, instance, flavor, def _create_consoles(self, virt_type, guest_cfg, instance, flavor,
image_meta): image_meta):
# NOTE(markus_z): Beware! Below are so many conditionals that it is
# easy to lose track. Use this chart to figure out your case:
#
# case | is serial | has | is qemu | resulting
# | enabled? | virtlogd? | or kvm? | devices
# --------------------------------------------------
# 1 | no | no | no | pty*
# 2 | no | no | yes | file + pty
# 3 | no | yes | no | see case 1
# 4 | no | yes | yes | pty with logd
# 5 | yes | no | no | see case 1
# 6 | yes | no | yes | tcp + pty
# 7 | yes | yes | no | see case 1
# 8 | yes | yes | yes | tcp with logd
# * exception: virt_type "parallels" doesn't create a device
if virt_type == 'parallels': if virt_type == 'parallels':
pass pass
elif virt_type not in ("qemu", "kvm"): elif virt_type not in ("qemu", "kvm"):
@ -4384,38 +4407,73 @@ class LibvirtDriver(driver.ComputeDriver):
def _create_consoles_qemu_kvm(self, guest_cfg, instance, flavor, def _create_consoles_qemu_kvm(self, guest_cfg, instance, flavor,
image_meta): image_meta):
char_dev_cls = vconfig.LibvirtConfigGuestSerial char_dev_cls = vconfig.LibvirtConfigGuestSerial
log_path = self._get_console_log_path(instance)
if CONF.serial_console.enabled: if CONF.serial_console.enabled:
if not self._serial_ports_already_defined(instance): if not self._serial_ports_already_defined(instance):
num_ports = hardware.get_number_of_serial_ports(flavor, num_ports = hardware.get_number_of_serial_ports(flavor,
image_meta) image_meta)
self._check_number_of_serial_console(num_ports) self._check_number_of_serial_console(num_ports)
self._create_serial_consoles(guest_cfg, num_ports, self._create_serial_consoles(guest_cfg, num_ports,
char_dev_cls) char_dev_cls, log_path)
else: else:
self._create_file_device(guest_cfg, instance, char_dev_cls) self._create_file_device(guest_cfg, instance, char_dev_cls)
self._create_pty_device(guest_cfg, char_dev_cls) self._create_pty_device(guest_cfg, char_dev_cls, log_path=log_path)
def _create_consoles_s390x(self, guest_cfg, instance, flavor, image_meta): def _create_consoles_s390x(self, guest_cfg, instance, flavor, image_meta):
char_dev_cls = vconfig.LibvirtConfigGuestConsole char_dev_cls = vconfig.LibvirtConfigGuestConsole
log_path = self._get_console_log_path(instance)
if CONF.serial_console.enabled: if CONF.serial_console.enabled:
if not self._serial_ports_already_defined(instance): if not self._serial_ports_already_defined(instance):
num_ports = hardware.get_number_of_serial_ports(flavor, num_ports = hardware.get_number_of_serial_ports(flavor,
image_meta) image_meta)
self._create_serial_consoles(guest_cfg, num_ports, self._create_serial_consoles(guest_cfg, num_ports,
char_dev_cls) char_dev_cls, log_path)
else: else:
self._create_file_device(guest_cfg, instance, char_dev_cls, self._create_file_device(guest_cfg, instance, char_dev_cls,
"sclplm") "sclplm")
self._create_pty_device(guest_cfg, char_dev_cls, "sclp") self._create_pty_device(guest_cfg, char_dev_cls, "sclp", log_path)
def _create_pty_device(self, guest_cfg, char_dev_cls, target_type=None): def _create_pty_device(self, guest_cfg, char_dev_cls, target_type=None,
consolepty = char_dev_cls() log_path=None):
consolepty.target_type = target_type def _create_base_dev():
consolepty.type = "pty" consolepty = char_dev_cls()
guest_cfg.add_device(consolepty) consolepty.target_type = target_type
consolepty.type = "pty"
return consolepty
def _create_logd_dev():
consolepty = _create_base_dev()
log = vconfig.LibvirtConfigGuestCharDeviceLog()
log.file = log_path
consolepty.log = log
return consolepty
if CONF.serial_console.enabled:
if self._is_virtlogd_available():
return
else:
# NOTE(markus_z): You may wonder why this is necessary and
# so do I. I'm certain that this is *not* needed in any
# real use case. It is, however, useful if you want to
# pypass the Nova API and use "virsh console <guest>" on
# an hypervisor, as this CLI command doesn't work with TCP
# devices (like the serial console is).
# https://bugzilla.redhat.com/show_bug.cgi?id=781467
# Pypassing the Nova API however is a thing we don't want.
# Future changes should remove this and fix the unit tests
# which ask for the existence.
guest_cfg.add_device(_create_base_dev())
else:
if self._is_virtlogd_available():
guest_cfg.add_device(_create_logd_dev())
else:
guest_cfg.add_device(_create_base_dev())
def _create_file_device(self, guest_cfg, instance, char_dev_cls, def _create_file_device(self, guest_cfg, instance, char_dev_cls,
target_type=None): target_type=None):
if self._is_virtlogd_available():
return
consolelog = char_dev_cls() consolelog = char_dev_cls()
consolelog.target_type = target_type consolelog.target_type = target_type
consolelog.type = "file" consolelog.type = "file"
@ -4436,7 +4494,8 @@ class LibvirtDriver(driver.ComputeDriver):
"ports in its domain XML", instance=instance) "ports in its domain XML", instance=instance)
return False return False
def _create_serial_consoles(self, guest_cfg, num_ports, char_dev_cls): def _create_serial_consoles(self, guest_cfg, num_ports, char_dev_cls,
log_path):
for port in six.moves.range(num_ports): for port in six.moves.range(num_ports):
console = char_dev_cls() console = char_dev_cls()
console.port = port console.port = port
@ -4444,6 +4503,12 @@ class LibvirtDriver(driver.ComputeDriver):
console.listen_host = CONF.serial_console.proxyclient_address console.listen_host = CONF.serial_console.proxyclient_address
listen_port = serial_console.acquire_port(console.listen_host) listen_port = serial_console.acquire_port(console.listen_host)
console.listen_port = listen_port console.listen_port = listen_port
# NOTE: only the first serial console gets the boot messages,
# that's why we attach the logd subdevice only to that.
if port == 0 and self._is_virtlogd_available():
log = vconfig.LibvirtConfigGuestCharDeviceLog()
log.file = log_path
console.log = log
guest_cfg.add_device(console) guest_cfg.add_device(console)
def _cpu_config_to_vcpu_model(self, cpu_config, vcpu_model): def _cpu_config_to_vcpu_model(self, cpu_config, vcpu_model):