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@virtuozzo.com
Subject: [PATCH 3/3] iotests/migrate-cpr-exec: cover chardevs with logfile= #VSTOR-148304
Date: Fri,  2 Oct 2026 18:48:53 +0200	[thread overview]
Message-ID: <20261002164853.2130337-4-den@openvz.org> (raw)
In-Reply-To: <20261002164853.2130337-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     | 132 ++++++++++++++++++
 tests/qemu-iotests/tests/migrate-cpr-exec.out |   4 +-
 2 files changed, 134 insertions(+), 2 deletions(-)

diff --git a/tests/qemu-iotests/tests/migrate-cpr-exec b/tests/qemu-iotests/tests/migrate-cpr-exec
index 51e6cbcd659..e8a6c502c84 100755
--- a/tests/qemu-iotests/tests/migrate-cpr-exec
+++ b/tests/qemu-iotests/tests/migrate-cpr-exec
@@ -288,6 +288,138 @@ 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'
+        return {os.readlink(os.path.join(fd_dir, fd))
+                for fd in os.listdir(fd_dir)}
+
+    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_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..88bf7fa73a5 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 34 tests
 
 OK
-- 
2.53.0


  parent reply	other threads:[~2026-10-02 16:49 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-02 16:48 [PATCH 0/3] chardev: fix qemu-update destroying VMs with a console log #VSTOR-148304 Denis V. Lunev
2026-10-02 16:48 ` [PATCH 1/3] chardev: implement cpr_claim_fds() in the base class #VSTOR-148304 Denis V. Lunev
2026-10-02 16:50   ` no-reply
2026-10-05  9:05   ` Andrey Drobyshev
2026-10-02 16:48 ` [PATCH 2/3] chardev: preserve the logfile fd across cpr-exec #VSTOR-148304 Denis V. Lunev
2026-10-02 16:54   ` no-reply
2026-10-05  9:05   ` Andrey Drobyshev
2026-10-02 16:48 ` Denis V. Lunev [this message]
2026-10-02 16:55   ` [PATCH 3/3] iotests/migrate-cpr-exec: cover chardevs with logfile= #VSTOR-148304 no-reply
2026-10-05  9:05   ` 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=20261002164853.2130337-4-den@openvz.org \
    --to=den@openvz.org \
    --cc=andrey.drobyshev@virtuozzo.com \
    --cc=den@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.