Browse Source

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
tags/15.0.0.0b2
Markus Zoeller 2 years ago
parent
commit
1f659251c7
2 changed files with 114 additions and 10 deletions
  1. 39
    0
      nova/tests/unit/virt/libvirt/test_driver.py
  2. 75
    10
      nova/virt/libvirt/driver.py

+ 39
- 0
nova/tests/unit/virt/libvirt/test_driver.py View File

@@ -3829,6 +3829,45 @@ class LibvirtConnTestCase(test.NoDBTestCase):
3829 3829
         self.assertEqual("tcp", cfg.devices[3].type)
3830 3830
         self.assertEqual("tcp", cfg.devices[4].type)
3831 3831
 
3832
+    @mock.patch.object(host.Host, 'has_min_version', return_value=True)
3833
+    @mock.patch('nova.console.serial.acquire_port')
3834
+    @mock.patch('nova.virt.hardware.get_number_of_serial_ports',
3835
+                return_value=1)
3836
+    @mock.patch.object(libvirt_driver.libvirt_utils, 'get_arch',)
3837
+    def test_guest_config_char_device_logd(self, mock_get_arch,
3838
+                                           mock_get_number_serial_ports,
3839
+                                           mock_acquire_port,
3840
+                                           mock_host_has_min_version):
3841
+        drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
3842
+
3843
+        def _test_consoles(arch_to_mock, serial_enabled,
3844
+                           expected_device_type, expected_device_cls):
3845
+            guest_cfg = vconfig.LibvirtConfigGuest()
3846
+            mock_get_arch.return_value = arch_to_mock
3847
+            self.flags(enabled=serial_enabled, group='serial_console')
3848
+            instance = objects.Instance(**self.test_instance)
3849
+
3850
+            drvr._create_consoles("qemu", guest_cfg, instance=instance,
3851
+                                  flavor=None, image_meta=None)
3852
+
3853
+            self.assertEqual(1, len(guest_cfg.devices))
3854
+            device = guest_cfg.devices[0]
3855
+            self.assertEqual(expected_device_type, device.type)
3856
+            self.assertIsInstance(device, expected_device_cls)
3857
+            self.assertIsInstance(device.log,
3858
+                                  vconfig.LibvirtConfigGuestCharDeviceLog)
3859
+            self.assertEqual("off", device.log.append)
3860
+            self.assertTrue(device.log.file.endswith("console.log"))
3861
+
3862
+        _test_consoles(fields.Architecture.X86_64, True,
3863
+                       "tcp", vconfig.LibvirtConfigGuestSerial)
3864
+        _test_consoles(fields.Architecture.X86_64, False,
3865
+                       "pty", vconfig.LibvirtConfigGuestSerial)
3866
+        _test_consoles(fields.Architecture.S390, True,
3867
+                       "tcp", vconfig.LibvirtConfigGuestConsole)
3868
+        _test_consoles(fields.Architecture.S390X, False,
3869
+                       "pty", vconfig.LibvirtConfigGuestConsole)
3870
+
3832 3871
     @mock.patch('nova.console.serial.acquire_port')
3833 3872
     def test_get_guest_config_serial_console_through_port_rng_exhausted(
3834 3873
             self, acquire_port):

+ 75
- 10
nova/virt/libvirt/driver.py View File

@@ -269,6 +269,10 @@ MIN_QEMU_S390_VERSION = (2, 3, 0)
269 269
 # libvirt 1.3 fix f391889f4e942e22b9ef8ecca492de05106ce41e
270 270
 MIN_LIBVIRT_PF_WITH_NO_VFS_CAP_VERSION = (1, 3, 0)
271 271
 
272
+# Use the "logd" backend for handling stdout/stderr from QEMU processes.
273
+MIN_LIBVIRT_VIRTLOGD = (1, 3, 3)
274
+MIN_QEMU_VIRTLOGD = (2, 7, 0)
275
+
272 276
 # ppc64/ppc64le architectures with KVM
273 277
 # NOTE(rfolco): Same levels for Libvirt/Qemu on Big Endian and Little
274 278
 # Endian giving the nuance around guest vs host architectures
@@ -595,6 +599,10 @@ class LibvirtDriver(driver.ComputeDriver):
595 599
             return True
596 600
         return False
597 601
 
602
+    def _is_virtlogd_available(self):
603
+        return self._host.has_min_version(MIN_LIBVIRT_VIRTLOGD,
604
+                                          MIN_QEMU_VIRTLOGD)
605
+
598 606
     def _handle_live_migration_post_copy(self, migration_flags):
599 607
         if CONF.libvirt.live_migration_permit_post_copy:
600 608
             if self._is_post_copy_available():
@@ -4364,6 +4372,21 @@ class LibvirtDriver(driver.ComputeDriver):
4364 4372
 
4365 4373
     def _create_consoles(self, virt_type, guest_cfg, instance, flavor,
4366 4374
                          image_meta):
4375
+        # NOTE(markus_z): Beware! Below are so many conditionals that it is
4376
+        # easy to lose track. Use this chart to figure out your case:
4377
+        #
4378
+        # case | is serial | has       | is qemu | resulting
4379
+        #      | enabled?  | virtlogd? | or kvm? | devices
4380
+        # --------------------------------------------------
4381
+        #    1 |        no |        no |     no  | pty*
4382
+        #    2 |        no |        no |     yes | file + pty
4383
+        #    3 |        no |       yes |      no | see case 1
4384
+        #    4 |        no |       yes |     yes | pty with logd
4385
+        #    5 |       yes |        no |      no | see case 1
4386
+        #    6 |       yes |        no |     yes | tcp + pty
4387
+        #    7 |       yes |       yes |      no | see case 1
4388
+        #    8 |       yes |       yes |     yes | tcp with logd
4389
+        #    * exception: virt_type "parallels" doesn't create a device
4367 4390
         if virt_type == 'parallels':
4368 4391
             pass
4369 4392
         elif virt_type not in ("qemu", "kvm"):
@@ -4384,38 +4407,73 @@ class LibvirtDriver(driver.ComputeDriver):
4384 4407
     def _create_consoles_qemu_kvm(self, guest_cfg, instance, flavor,
4385 4408
                                   image_meta):
4386 4409
         char_dev_cls = vconfig.LibvirtConfigGuestSerial
4410
+        log_path = self._get_console_log_path(instance)
4387 4411
         if CONF.serial_console.enabled:
4388 4412
             if not self._serial_ports_already_defined(instance):
4389 4413
                 num_ports = hardware.get_number_of_serial_ports(flavor,
4390 4414
                                                                 image_meta)
4391 4415
                 self._check_number_of_serial_console(num_ports)
4392 4416
                 self._create_serial_consoles(guest_cfg, num_ports,
4393
-                                             char_dev_cls)
4417
+                                             char_dev_cls, log_path)
4394 4418
         else:
4395 4419
             self._create_file_device(guest_cfg, instance, char_dev_cls)
4396
-        self._create_pty_device(guest_cfg, char_dev_cls)
4420
+        self._create_pty_device(guest_cfg, char_dev_cls, log_path=log_path)
4397 4421
 
4398 4422
     def _create_consoles_s390x(self, guest_cfg, instance, flavor, image_meta):
4399 4423
         char_dev_cls = vconfig.LibvirtConfigGuestConsole
4424
+        log_path = self._get_console_log_path(instance)
4400 4425
         if CONF.serial_console.enabled:
4401 4426
             if not self._serial_ports_already_defined(instance):
4402 4427
                 num_ports = hardware.get_number_of_serial_ports(flavor,
4403 4428
                                                                 image_meta)
4404 4429
                 self._create_serial_consoles(guest_cfg, num_ports,
4405
-                                             char_dev_cls)
4430
+                                             char_dev_cls, log_path)
4406 4431
         else:
4407 4432
             self._create_file_device(guest_cfg, instance, char_dev_cls,
4408 4433
                                      "sclplm")
4409
-        self._create_pty_device(guest_cfg, char_dev_cls, "sclp")
4434
+        self._create_pty_device(guest_cfg, char_dev_cls, "sclp", log_path)
4435
+
4436
+    def _create_pty_device(self, guest_cfg, char_dev_cls, target_type=None,
4437
+                           log_path=None):
4438
+        def _create_base_dev():
4439
+            consolepty = char_dev_cls()
4440
+            consolepty.target_type = target_type
4441
+            consolepty.type = "pty"
4442
+            return consolepty
4443
+
4444
+        def _create_logd_dev():
4445
+            consolepty = _create_base_dev()
4446
+            log = vconfig.LibvirtConfigGuestCharDeviceLog()
4447
+            log.file = log_path
4448
+            consolepty.log = log
4449
+            return consolepty
4410 4450
 
4411
-    def _create_pty_device(self, guest_cfg, char_dev_cls, target_type=None):
4412
-        consolepty = char_dev_cls()
4413
-        consolepty.target_type = target_type
4414
-        consolepty.type = "pty"
4415
-        guest_cfg.add_device(consolepty)
4451
+        if CONF.serial_console.enabled:
4452
+            if self._is_virtlogd_available():
4453
+                return
4454
+            else:
4455
+                # NOTE(markus_z): You may wonder why this is necessary and
4456
+                # so do I. I'm certain that this is *not* needed in any
4457
+                # real use case. It is, however, useful if you want to
4458
+                # pypass the Nova API and use "virsh console <guest>" on
4459
+                # an hypervisor, as this CLI command doesn't work with TCP
4460
+                # devices (like the serial console is).
4461
+                #     https://bugzilla.redhat.com/show_bug.cgi?id=781467
4462
+                # Pypassing the Nova API however is a thing we don't want.
4463
+                # Future changes should remove this and fix the unit tests
4464
+                # which ask for the existence.
4465
+                guest_cfg.add_device(_create_base_dev())
4466
+        else:
4467
+            if self._is_virtlogd_available():
4468
+                guest_cfg.add_device(_create_logd_dev())
4469
+            else:
4470
+                guest_cfg.add_device(_create_base_dev())
4416 4471
 
4417 4472
     def _create_file_device(self, guest_cfg, instance, char_dev_cls,
4418 4473
                             target_type=None):
4474
+        if self._is_virtlogd_available():
4475
+            return
4476
+
4419 4477
         consolelog = char_dev_cls()
4420 4478
         consolelog.target_type = target_type
4421 4479
         consolelog.type = "file"
@@ -4436,7 +4494,8 @@ class LibvirtDriver(driver.ComputeDriver):
4436 4494
                 "ports in its domain XML", instance=instance)
4437 4495
         return False
4438 4496
 
4439
-    def _create_serial_consoles(self, guest_cfg, num_ports, char_dev_cls):
4497
+    def _create_serial_consoles(self, guest_cfg, num_ports, char_dev_cls,
4498
+                                log_path):
4440 4499
         for port in six.moves.range(num_ports):
4441 4500
             console = char_dev_cls()
4442 4501
             console.port = port
@@ -4444,6 +4503,12 @@ class LibvirtDriver(driver.ComputeDriver):
4444 4503
             console.listen_host = CONF.serial_console.proxyclient_address
4445 4504
             listen_port = serial_console.acquire_port(console.listen_host)
4446 4505
             console.listen_port = listen_port
4506
+            # NOTE: only the first serial console gets the boot messages,
4507
+            # that's why we attach the logd subdevice only to that.
4508
+            if port == 0 and self._is_virtlogd_available():
4509
+                log = vconfig.LibvirtConfigGuestCharDeviceLog()
4510
+                log.file = log_path
4511
+                console.log = log
4447 4512
             guest_cfg.add_device(console)
4448 4513
 
4449 4514
     def _cpu_config_to_vcpu_model(self, cpu_config, vcpu_model):

Loading…
Cancel
Save