From e781123e50ac2225694f9ce8f3a46a9734c6f8bd Mon Sep 17 00:00:00 2001 From: Markus Zoeller Date: Tue, 11 Oct 2016 18:05:51 +0200 Subject: [PATCH] libvirt: read rotated "console.log" files A libvirt guest can output its console into a "console.log" file. This change enhances the reading of this file in case there is log rotation on this file. This will be the case in a following change which make use of the "virtlogd" deamon of libvirt 1.3.0 and beyond. If no log-rotation is used, the method will behave as before. This change makes the later virtlogd change easier to review. Change-Id: Ica2c91ad309df8e66433fe0c7fe2b799a23c0096 --- nova/tests/unit/virt/libvirt/test_driver.py | 63 ++++++++++++++++++++- nova/virt/libvirt/driver.py | 23 +++++--- 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 8252cdde7cce..4d72410af875 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -10798,7 +10798,8 @@ class LibvirtConnTestCase(test.NoDBTestCase): self.assertEqual('', output) - def test_get_console_output_pty(self): + @mock.patch('os.path.exists', return_value=True) + def test_get_console_output_pty(self, mocked_path_exists): fake_libvirt_utils.files['pty'] = '01234567890' with utils.tempdir() as tmpdir: @@ -10872,6 +10873,66 @@ class LibvirtConnTestCase(test.NoDBTestCase): self.assertRaises(exception.ConsoleNotAvailable, drvr.get_console_output, self.context, instance) + @mock.patch('nova.virt.libvirt.host.Host.get_domain') + @mock.patch.object(libvirt_guest.Guest, "get_xml_desc") + def test_get_console_output_logrotate(self, mock_get_xml, get_domain): + fake_libvirt_utils.files['console.log'] = 'uvwxyz' + fake_libvirt_utils.files['console.log.0'] = 'klmnopqrst' + fake_libvirt_utils.files['console.log.1'] = 'abcdefghij' + + def mock_path_exists(path): + return os.path.basename(path) in fake_libvirt_utils.files + + xml = """ + + + + + + + + + + + + """ + mock_get_xml.return_value = xml + get_domain.return_value = mock.MagicMock() + + drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) + instance = objects.Instance(**self.test_instance) + + def _get_logd_output(bytes_to_read): + with utils.tempdir() as tmp_dir: + self.flags(instances_path=tmp_dir) + log_data = "" + try: + prev_max = libvirt_driver.MAX_CONSOLE_BYTES + libvirt_driver.MAX_CONSOLE_BYTES = bytes_to_read + with mock.patch('os.path.exists', + side_effect=mock_path_exists): + log_data = drvr.get_console_output(self.context, + instance) + finally: + libvirt_driver.MAX_CONSOLE_BYTES = prev_max + return log_data + + # span across only 1 file (with remaining bytes) + self.assertEqual('wxyz', _get_logd_output(4)) + # span across only 1 file (exact bytes) + self.assertEqual('uvwxyz', _get_logd_output(6)) + # span across 2 files (with remaining bytes) + self.assertEqual('opqrstuvwxyz', _get_logd_output(12)) + # span across all files (exact bytes) + self.assertEqual('abcdefghijklmnopqrstuvwxyz', _get_logd_output(26)) + # span across all files with more bytes than available + self.assertEqual('abcdefghijklmnopqrstuvwxyz', _get_logd_output(30)) + # files are not available + fake_libvirt_utils.files = {} + self.assertEqual('', _get_logd_output(30)) + # reset the file for other tests + fake_libvirt_utils.files['console.log'] = '01234567890' + def test_get_host_ip_addr(self): drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) ip = drvr.get_host_ip_addr() diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 76b590e3f38b..3eaf976161a8 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -2646,17 +2646,26 @@ class LibvirtDriver(driver.ComputeDriver): return fpath - def _get_console_output_file(self, instance, path): - libvirt_utils.chown(path, os.getuid()) - - with libvirt_utils.file_open(path, 'rb') as fp: - log_data, remaining = utils.last_bytes(fp, - MAX_CONSOLE_BYTES) + def _get_console_output_file(self, instance, console_log): + bytes_to_read = MAX_CONSOLE_BYTES + log_data = "" # The last N read bytes + i = 0 # in case there is a log rotation (like "virtlogd") + path = console_log + while bytes_to_read > 0 and os.path.exists(path): + libvirt_utils.chown(path, os.getuid()) + with libvirt_utils.file_open(path, 'rb') as fp: + read_log_data, remaining = utils.last_bytes(fp, bytes_to_read) + # We need the log file content in chronological order, + # that's why we *prepend* the log data. + log_data = read_log_data + log_data + bytes_to_read -= len(read_log_data) + path = console_log + "." + str(i) + i += 1 if remaining > 0: LOG.info(_LI('Truncated console log returned, ' '%d bytes ignored'), remaining, instance=instance) - return log_data + return log_data def get_console_output(self, context, instance): guest = self._host.get_guest(instance)