All Virtuozzo development lists (kernel + QEMU)
 help / color / mirror / Atom feed
From: "Denis V. Lunev" <den@openvz.org>
To: svt-core@virtuozzo.com
Cc: andrey.drobyshev@virtuozzo.com, den@openvz.org
Subject: [PATCH hci-8.0 v2 3/3] iotests/migrate-cpr-exec: cover chardevs with logfile= #VSTOR-148304
Date: Mon,  5 Oct 2026 11:26:35 +0200	[thread overview]
Message-ID: <20261005092635.3564485-4-den@openvz.org> (raw)
In-Reply-To: <20261005092635.3564485-1-den@openvz.org>

From: Denis V. Lunev <den@openvz.org>

Run a console pty with its log in an fdset, a guest agent socket and a
second QMP monitor, each with a log, through cpr-exec with a command
line whose logfile paths cannot be created, as libvirt's is. Cover
chardev-change to a new log for pty and socket, and a failed
chardev-change, whose rollback must keep the old log preserved.

Signed-off-by: Denis V. Lunev <den@openvz.org>
---
 tests/qemu-iotests/tests/migrate-cpr-exec     | 154 ++++++++++++++++++
 tests/qemu-iotests/tests/migrate-cpr-exec.out |   4 +-
 2 files changed, 156 insertions(+), 2 deletions(-)

diff --git a/tests/qemu-iotests/tests/migrate-cpr-exec b/tests/qemu-iotests/tests/migrate-cpr-exec
index 51e6cbcd659..2b30929561c 100755
--- a/tests/qemu-iotests/tests/migrate-cpr-exec
+++ b/tests/qemu-iotests/tests/migrate-cpr-exec
@@ -288,6 +288,160 @@ class TestCprExec(iotests.QMPTestCase):
             '-device', 'isa-serial,chardev=char0')
         self._test_cpr(pre_cpr_hook=change_socket)
 
+    BAD_LOG = os.path.join(iotests.test_dir, 'nonexistent', 'chr.log')
+
+    def _serial_write(self, data: str) -> None:
+        for c in data:
+            self.vm.qtest(f'outb 0x3f8 {ord(c)}')
+
+    def _open_files(self) -> set:
+        fd_dir = f'/proc/{self.vm.get_pid()}/fd'
+        files = set()
+        for fd in os.listdir(fd_dir):
+            try:
+                files.add(os.readlink(os.path.join(fd_dir, fd)))
+            except FileNotFoundError:
+                pass
+        return files
+
+    def _replace_arg(self, old: str, new: str) -> None:
+        args = list(self.vm._qemu_full_args)
+        args[args.index(old)] = new
+        self.vm._qemu_full_args = tuple(args)
+
+    def _read(self, path: str) -> str:
+        with open(path, encoding='utf-8') as f:
+            return f.read()
+
+    def _log_path(self, name: str) -> str:
+        path = os.path.join(iotests.test_dir, name)
+        self.addCleanup(lambda: os.path.exists(path) and os.remove(path))
+        return path
+
+    def test_chardev_logfile(self) -> None:
+        """Log fds survive cpr-exec; the new logfile= paths are never opened"""
+        console_log = self._log_path('console.log')
+        agent_log = self._log_path('agent.log')
+        mon_log = self._log_path('mon.log')
+        agent_sock = iotests.file_path('agent.sock')
+        mon_sock = iotests.file_path('mon.sock')
+
+        console = 'pty,id=charserial0,logfile=/dev/fdset/1,logappend=on'
+        agent = (f'socket,id=charchannel0,path={agent_sock},server=on,'
+                 f'wait=off,logfile={agent_log}')
+        mon = (f'socket,id=charmonitor1,path={mon_sock},server=on,'
+               f'wait=off,logfile={mon_log}')
+
+        def pre_cpr():
+            self._serial_write('before\n')
+            for c in (console, agent, mon):
+                self._replace_arg(c, c.replace(c[c.index('logfile='):],
+                                               f'logfile={self.BAD_LOG}'))
+            args = list(self.vm._qemu_full_args)
+            i = args.index('-add-fd')
+            self.vm._qemu_full_args = tuple(args[:i] + args[i + 2:])
+
+        def post_cpr():
+            self._serial_write('after\n')
+            self.assertEqual(self._read(console_log), 'before\nafter\n')
+            self.assertLessEqual({agent_log, mon_log}, self._open_files())
+            self.assertFalse(os.path.exists(os.path.dirname(self.BAD_LOG)))
+
+        fd = os.open(console_log, os.O_WRONLY | os.O_CREAT | os.O_APPEND)
+        try:
+            self.vm.add_fd(fd, 1, 'console')
+            self.vm.add_args(
+                '-chardev', console,
+                '-device', 'isa-serial,chardev=charserial0',
+                '-chardev', agent, '-device', 'virtio-serial',
+                '-device', 'virtserialport,chardev=charchannel0,'
+                           'name=org.qemu.guest_agent.0',
+                '-chardev', mon, '-mon', 'chardev=charmonitor1,mode=control')
+            self._test_cpr(pre_cpr_hook=pre_cpr, post_cpr_hook=post_cpr)
+        finally:
+            os.close(fd)
+
+    def _test_chardev_change_logfile(self, backend: str, data_new: dict,
+                                     opts_old: str) -> None:
+        """cpr-exec after chardev-change preserves the new log, not the old"""
+        log_old = self._log_path('chr-old.log')
+        log_new = self._log_path('chr-new.log')
+        arg = f'{backend},id=char0,{opts_old}logfile={log_old}'
+
+        def change():
+            self._serial_write('before\n')
+            res = self.vm.qmp('chardev-change', id='char0',
+                              backend={'type': backend,
+                                       'data': {**data_new,
+                                                'logfile': log_new}})
+            assert 'return' in res, res
+            self._replace_arg(arg, f'{backend},id=char0,{opts_old}'
+                                   f'logfile={self.BAD_LOG}')
+
+        def post_cpr():
+            self._serial_write('after\n')
+            self.assertEqual(self._read(log_old), 'before\n')
+            self.assertEqual(self._read(log_new), 'after\n')
+            self.assertNotIn(log_old, self._open_files())
+
+        self.vm.add_args('-chardev', arg,
+                         '-device', 'isa-serial,chardev=char0')
+        self._test_cpr(pre_cpr_hook=change, post_cpr_hook=post_cpr)
+
+    def test_chardev_change_logfile_pty(self) -> None:
+        self._test_chardev_change_logfile('pty', {}, '')
+
+    def test_chardev_change_logfile_socket(self) -> None:
+        sock1 = iotests.file_path('chr-log-sock1')
+        sock2 = iotests.file_path('chr-log-sock2')
+        self._test_chardev_change_logfile(
+            'socket',
+            {'addr': {'type': 'unix', 'data': {'path': sock2}},
+             'server': True, 'wait': False},
+            f'path={sock1},server=on,wait=off,')
+
+    def test_chardev_change_logfile_rollback(self) -> None:
+        """A rolled back chardev-change keeps the old log preserved"""
+        log = self._log_path('chr.log')
+        log_new = self._log_path('chr-new.log')
+        link = self._log_path('pty-rollback.link')
+        open(link, 'w').close()
+        arg = f'pty,id=char0,logfile={log}'
+
+        def failed_change():
+            self._serial_write('before\n')
+            res = self.vm.qmp('chardev-change', id='char0',
+                              backend={'type': 'pty',
+                                       'data': {'path': link,
+                                                'logfile': log_new}})
+            assert 'error' in res, res
+            self._replace_arg(arg, f'pty,id=char0,logfile={self.BAD_LOG}')
+
+        def post_cpr():
+            self._serial_write('after\n')
+            self.assertEqual(self._read(log), 'before\nafter\n')
+
+        self.vm.add_args('-chardev', arg,
+                         '-device', 'isa-serial,chardev=char0')
+        self._test_cpr(pre_cpr_hook=failed_change, post_cpr_hook=post_cpr)
+
+    def test_chardev_logfile_not_preserved(self) -> None:
+        """A log the source did not save is dropped, not reopened by path"""
+        log = self._log_path('unsaved.log')
+        arg = 'pty,id=char0'
+
+        def add_log():
+            self._replace_arg(arg, f'{arg},logfile={log}')
+
+        def post_cpr():
+            self.assertFalse(os.path.exists(log))
+
+        self.vm.add_args('-chardev', arg,
+                         '-device', 'isa-serial,chardev=char0')
+        self._test_cpr(pre_cpr_hook=add_log, post_cpr_hook=post_cpr)
+        self.assertIn("chardev 'char0': log file was not preserved, "
+                      "continuing without it", self.vm.get_log())
+
     def test_nbd_attached_hd(self) -> None:
         nbd_img = os.path.join(iotests.test_dir, 'nbd_disk')
         iotests.qemu_img_create('-f', 'qcow2', nbd_img, '64M')
diff --git a/tests/qemu-iotests/tests/migrate-cpr-exec.out b/tests/qemu-iotests/tests/migrate-cpr-exec.out
index 5ce2f9a2ed9..cfa5c0d0e66 100644
--- a/tests/qemu-iotests/tests/migrate-cpr-exec.out
+++ b/tests/qemu-iotests/tests/migrate-cpr-exec.out
@@ -1,5 +1,5 @@
-..............................
+...................................
 ----------------------------------------------------------------------
-Ran 30 tests
+Ran 35 tests
 
 OK
-- 
2.53.0


  parent reply	other threads:[~2026-10-05  9:26 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-05  9:26 [PATCH hci-8.0 v2 0/3] chardev: fix qemu-update destroying VMs with a console log #VSTOR-148304 Denis V. Lunev
2026-10-05  9:26 ` [PATCH hci-8.0 v2 1/3] chardev: implement cpr_claim_fds() in the base class #VSTOR-148304 Denis V. Lunev
2026-10-05  9:28   ` no-reply
2026-10-05  9:26 ` [PATCH hci-8.0 v2 2/3] chardev: preserve the logfile fd across cpr-exec #VSTOR-148304 Denis V. Lunev
2026-10-05  9:30   ` no-reply
2026-10-05  9:26 ` Denis V. Lunev [this message]
2026-10-05  9:35   ` [PATCH hci-8.0 v2 3/3] iotests/migrate-cpr-exec: cover chardevs with logfile= #VSTOR-148304 no-reply
2026-10-05  9:47 ` [PATCH hci-8.0 v2 0/3] chardev: fix qemu-update destroying VMs with a console log #VSTOR-148304 Andrey Drobyshev

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261005092635.3564485-4-den@openvz.org \
    --to=den@openvz.org \
    --cc=andrey.drobyshev@virtuozzo.com \
    --cc=svt-core@virtuozzo.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.