Blame SOURCES/kvm-nbd-server-Allow-MULTI_CONN-for-shared-writable-expo.patch

586cba
From 4a9ddf42788d3f924bdad7746f7aca615f03d7c1 Mon Sep 17 00:00:00 2001
586cba
From: Eric Blake <eblake@redhat.com>
586cba
Date: Wed, 11 May 2022 19:49:24 -0500
586cba
Subject: [PATCH 2/2] nbd/server: Allow MULTI_CONN for shared writable exports
586cba
MIME-Version: 1.0
586cba
Content-Type: text/plain; charset=UTF-8
586cba
Content-Transfer-Encoding: 8bit
586cba
586cba
RH-Author: Eric Blake <eblake@redhat.com>
586cba
RH-MergeRequest: 90: Advertise MULTI_CONN on writeable NBD servers
586cba
RH-Commit: [2/2] 53f0e885a5ed7f6e4bb14e74fe8e7957e6afe90f (ebblake/centos-qemu-kvm)
586cba
RH-Bugzilla: 1708300
586cba
RH-Acked-by: Nir Soffer <nsoffer@redhat.com>
586cba
RH-Acked-by: Kevin Wolf <kwolf@redhat.com>
586cba
RH-Acked-by: Daniel P. Berrangé <berrange@redhat.com>
586cba
586cba
According to the NBD spec, a server that advertises
586cba
NBD_FLAG_CAN_MULTI_CONN promises that multiple client connections will
586cba
not see any cache inconsistencies: when properly separated by a single
586cba
flush, actions performed by one client will be visible to another
586cba
client, regardless of which client did the flush.
586cba
586cba
We always satisfy these conditions in qemu - even when we support
586cba
multiple clients, ALL clients go through a single point of reference
586cba
into the block layer, with no local caching.  The effect of one client
586cba
is instantly visible to the next client.  Even if our backend were a
586cba
network device, we argue that any multi-path caching effects that
586cba
would cause inconsistencies in back-to-back actions not seeing the
586cba
effect of previous actions would be a bug in that backend, and not the
586cba
fault of caching in qemu.  As such, it is safe to unconditionally
586cba
advertise CAN_MULTI_CONN for any qemu NBD server situation that
586cba
supports parallel clients.
586cba
586cba
Note, however, that we don't want to advertise CAN_MULTI_CONN when we
586cba
know that a second client cannot connect (for historical reasons,
586cba
qemu-nbd defaults to a single connection while nbd-server-add and QMP
586cba
commands default to unlimited connections; but we already have
586cba
existing means to let either style of NBD server creation alter those
586cba
defaults).  This is visible by no longer advertising MULTI_CONN for
586cba
'qemu-nbd -r' without -e, as in the iotest nbd-qemu-allocation.
586cba
586cba
The harder part of this patch is setting up an iotest to demonstrate
586cba
behavior of multiple NBD clients to a single server.  It might be
586cba
possible with parallel qemu-io processes, but I found it easier to do
586cba
in python with the help of libnbd, and help from Nir and Vladimir in
586cba
writing the test.
586cba
586cba
Signed-off-by: Eric Blake <eblake@redhat.com>
586cba
Suggested-by: Nir Soffer <nsoffer@redhat.com>
586cba
Suggested-by: Vladimir Sementsov-Ogievskiy <v.sementsov-og@mail.ru>
586cba
Message-Id: <20220512004924.417153-3-eblake@redhat.com>
586cba
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
586cba
586cba
(cherry picked from commit 58a6fdcc9efb2a7c1ef4893dca4aa5e8020ca3dc)
586cba
Conflicts:
586cba
	nbd/server.c - context, e5fb29d5 not backported
586cba
Signed-off-by: Eric Blake <eblake@redhat.com>
586cba
---
586cba
 MAINTAINERS                                   |   1 +
586cba
 blockdev-nbd.c                                |   5 +
586cba
 docs/interop/nbd.txt                          |   1 +
586cba
 docs/tools/qemu-nbd.rst                       |   3 +-
586cba
 include/block/nbd.h                           |   3 +-
586cba
 nbd/server.c                                  |  10 +-
586cba
 qapi/block-export.json                        |   8 +-
586cba
 tests/qemu-iotests/tests/nbd-multiconn        | 145 ++++++++++++++++++
586cba
 tests/qemu-iotests/tests/nbd-multiconn.out    |   5 +
586cba
 .../tests/nbd-qemu-allocation.out             |   2 +-
586cba
 10 files changed, 172 insertions(+), 11 deletions(-)
586cba
 create mode 100755 tests/qemu-iotests/tests/nbd-multiconn
586cba
 create mode 100644 tests/qemu-iotests/tests/nbd-multiconn.out
586cba
586cba
diff --git a/MAINTAINERS b/MAINTAINERS
586cba
index 4ad2451e03..2fe20a49ab 100644
586cba
--- a/MAINTAINERS
586cba
+++ b/MAINTAINERS
586cba
@@ -3370,6 +3370,7 @@ F: qemu-nbd.*
586cba
 F: blockdev-nbd.c
586cba
 F: docs/interop/nbd.txt
586cba
 F: docs/tools/qemu-nbd.rst
586cba
+F: tests/qemu-iotests/tests/*nbd*
586cba
 T: git https://repo.or.cz/qemu/ericb.git nbd
586cba
 T: git https://src.openvz.org/scm/~vsementsov/qemu.git nbd
586cba
 
586cba
diff --git a/blockdev-nbd.c b/blockdev-nbd.c
586cba
index add41a23af..c6d9b0324c 100644
586cba
--- a/blockdev-nbd.c
586cba
+++ b/blockdev-nbd.c
586cba
@@ -44,6 +44,11 @@ bool nbd_server_is_running(void)
586cba
     return nbd_server || qemu_nbd_connections >= 0;
586cba
 }
586cba
 
586cba
+int nbd_server_max_connections(void)
586cba
+{
586cba
+    return nbd_server ? nbd_server->max_connections : qemu_nbd_connections;
586cba
+}
586cba
+
586cba
 static void nbd_blockdev_client_closed(NBDClient *client, bool ignored)
586cba
 {
586cba
     nbd_client_put(client);
586cba
diff --git a/docs/interop/nbd.txt b/docs/interop/nbd.txt
586cba
index bdb0f2a41a..f5ca25174a 100644
586cba
--- a/docs/interop/nbd.txt
586cba
+++ b/docs/interop/nbd.txt
586cba
@@ -68,3 +68,4 @@ NBD_CMD_BLOCK_STATUS for "qemu:dirty-bitmap:", NBD_CMD_CACHE
586cba
 * 4.2: NBD_FLAG_CAN_MULTI_CONN for shareable read-only exports,
586cba
 NBD_CMD_FLAG_FAST_ZERO
586cba
 * 5.2: NBD_CMD_BLOCK_STATUS for "qemu:allocation-depth"
586cba
+* 7.1: NBD_FLAG_CAN_MULTI_CONN for shareable writable exports
586cba
diff --git a/docs/tools/qemu-nbd.rst b/docs/tools/qemu-nbd.rst
586cba
index 4c950f6199..8e08a29e89 100644
586cba
--- a/docs/tools/qemu-nbd.rst
586cba
+++ b/docs/tools/qemu-nbd.rst
586cba
@@ -139,8 +139,7 @@ driver options if :option:`--image-opts` is specified.
586cba
 .. option:: -e, --shared=NUM
586cba
 
586cba
   Allow up to *NUM* clients to share the device (default
586cba
-  ``1``), 0 for unlimited. Safe for readers, but for now,
586cba
-  consistency is not guaranteed between multiple writers.
586cba
+  ``1``), 0 for unlimited.
586cba
 
586cba
 .. option:: -t, --persistent
586cba
 
586cba
diff --git a/include/block/nbd.h b/include/block/nbd.h
586cba
index c5a29ce1c6..c74b7a9d2e 100644
586cba
--- a/include/block/nbd.h
586cba
+++ b/include/block/nbd.h
586cba
@@ -1,5 +1,5 @@
586cba
 /*
586cba
- *  Copyright (C) 2016-2020 Red Hat, Inc.
586cba
+ *  Copyright (C) 2016-2022 Red Hat, Inc.
586cba
  *  Copyright (C) 2005  Anthony Liguori <anthony@codemonkey.ws>
586cba
  *
586cba
  *  Network Block Device
586cba
@@ -346,6 +346,7 @@ void nbd_client_put(NBDClient *client);
586cba
 
586cba
 void nbd_server_is_qemu_nbd(int max_connections);
586cba
 bool nbd_server_is_running(void);
586cba
+int nbd_server_max_connections(void);
586cba
 void nbd_server_start(SocketAddress *addr, const char *tls_creds,
586cba
                       const char *tls_authz, uint32_t max_connections,
586cba
                       Error **errp);
586cba
diff --git a/nbd/server.c b/nbd/server.c
586cba
index c5644fd3f6..6e2157acfa 100644
586cba
--- a/nbd/server.c
586cba
+++ b/nbd/server.c
586cba
@@ -1,5 +1,5 @@
586cba
 /*
586cba
- *  Copyright (C) 2016-2021 Red Hat, Inc.
586cba
+ *  Copyright (C) 2016-2022 Red Hat, Inc.
586cba
  *  Copyright (C) 2005  Anthony Liguori <anthony@codemonkey.ws>
586cba
  *
586cba
  *  Network Block Device Server Side
586cba
@@ -1642,7 +1642,6 @@ static int nbd_export_create(BlockExport *blk_exp, BlockExportOptions *exp_args,
586cba
     int64_t size;
586cba
     uint64_t perm, shared_perm;
586cba
     bool readonly = !exp_args->writable;
586cba
-    bool shared = !exp_args->writable;
586cba
     strList *bitmaps;
586cba
     size_t i;
586cba
     int ret;
586cba
@@ -1693,11 +1692,12 @@ static int nbd_export_create(BlockExport *blk_exp, BlockExportOptions *exp_args,
586cba
     exp->description = g_strdup(arg->description);
586cba
     exp->nbdflags = (NBD_FLAG_HAS_FLAGS | NBD_FLAG_SEND_FLUSH |
586cba
                      NBD_FLAG_SEND_FUA | NBD_FLAG_SEND_CACHE);
586cba
+
586cba
+    if (nbd_server_max_connections() != 1) {
586cba
+        exp->nbdflags |= NBD_FLAG_CAN_MULTI_CONN;
586cba
+    }
586cba
     if (readonly) {
586cba
         exp->nbdflags |= NBD_FLAG_READ_ONLY;
586cba
-        if (shared) {
586cba
-            exp->nbdflags |= NBD_FLAG_CAN_MULTI_CONN;
586cba
-        }
586cba
     } else {
586cba
         exp->nbdflags |= (NBD_FLAG_SEND_TRIM | NBD_FLAG_SEND_WRITE_ZEROES |
586cba
                           NBD_FLAG_SEND_FAST_ZERO);
586cba
diff --git a/qapi/block-export.json b/qapi/block-export.json
586cba
index 1e34927f85..755ccc89b1 100644
586cba
--- a/qapi/block-export.json
586cba
+++ b/qapi/block-export.json
586cba
@@ -21,7 +21,9 @@
586cba
 #             recreated on the fly while the NBD server is active.
586cba
 #             If missing, it will default to denying access (since 4.0).
586cba
 # @max-connections: The maximum number of connections to allow at the same
586cba
-#                   time, 0 for unlimited. (since 5.2; default: 0)
586cba
+#                   time, 0 for unlimited. Setting this to 1 also stops
586cba
+#                   the server from advertising multiple client support
586cba
+#                   (since 5.2; default: 0)
586cba
 #
586cba
 # Since: 4.2
586cba
 ##
586cba
@@ -50,7 +52,9 @@
586cba
 #             recreated on the fly while the NBD server is active.
586cba
 #             If missing, it will default to denying access (since 4.0).
586cba
 # @max-connections: The maximum number of connections to allow at the same
586cba
-#                   time, 0 for unlimited. (since 5.2; default: 0)
586cba
+#                   time, 0 for unlimited. Setting this to 1 also stops
586cba
+#                   the server from advertising multiple client support
586cba
+#                   (since 5.2; default: 0).
586cba
 #
586cba
 # Returns: error if the server is already running.
586cba
 #
586cba
diff --git a/tests/qemu-iotests/tests/nbd-multiconn b/tests/qemu-iotests/tests/nbd-multiconn
586cba
new file mode 100755
586cba
index 0000000000..b121f2e363
586cba
--- /dev/null
586cba
+++ b/tests/qemu-iotests/tests/nbd-multiconn
586cba
@@ -0,0 +1,145 @@
586cba
+#!/usr/bin/env python3
586cba
+# group: rw auto quick
586cba
+#
586cba
+# Test cases for NBD multi-conn advertisement
586cba
+#
586cba
+# Copyright (C) 2022 Red Hat, Inc.
586cba
+#
586cba
+# This program is free software; you can redistribute it and/or modify
586cba
+# it under the terms of the GNU General Public License as published by
586cba
+# the Free Software Foundation; either version 2 of the License, or
586cba
+# (at your option) any later version.
586cba
+#
586cba
+# This program is distributed in the hope that it will be useful,
586cba
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
586cba
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
586cba
+# GNU General Public License for more details.
586cba
+#
586cba
+# You should have received a copy of the GNU General Public License
586cba
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
586cba
+
586cba
+import os
586cba
+from contextlib import contextmanager
586cba
+import iotests
586cba
+from iotests import qemu_img_create, qemu_io
586cba
+
586cba
+
586cba
+disk = os.path.join(iotests.test_dir, 'disk')
586cba
+size = '4M'
586cba
+nbd_sock = os.path.join(iotests.sock_dir, 'nbd_sock')
586cba
+nbd_uri = 'nbd+unix:///{}?socket=' + nbd_sock
586cba
+
586cba
+
586cba
+@contextmanager
586cba
+def open_nbd(export_name):
586cba
+    h = nbd.NBD()
586cba
+    try:
586cba
+        h.connect_uri(nbd_uri.format(export_name))
586cba
+        yield h
586cba
+    finally:
586cba
+        h.shutdown()
586cba
+
586cba
+class TestNbdMulticonn(iotests.QMPTestCase):
586cba
+    def setUp(self):
586cba
+        qemu_img_create('-f', iotests.imgfmt, disk, size)
586cba
+        qemu_io('-c', 'w -P 1 0 2M', '-c', 'w -P 2 2M 2M', disk)
586cba
+
586cba
+        self.vm = iotests.VM()
586cba
+        self.vm.launch()
586cba
+        result = self.vm.qmp('blockdev-add', {
586cba
+            'driver': 'qcow2',
586cba
+            'node-name': 'n',
586cba
+            'file': {'driver': 'file', 'filename': disk}
586cba
+        })
586cba
+        self.assert_qmp(result, 'return', {})
586cba
+
586cba
+    def tearDown(self):
586cba
+        self.vm.shutdown()
586cba
+        os.remove(disk)
586cba
+        try:
586cba
+            os.remove(nbd_sock)
586cba
+        except OSError:
586cba
+            pass
586cba
+
586cba
+    @contextmanager
586cba
+    def run_server(self, max_connections=None):
586cba
+        args = {
586cba
+            'addr': {
586cba
+                'type': 'unix',
586cba
+                'data': {'path': nbd_sock}
586cba
+            }
586cba
+        }
586cba
+        if max_connections is not None:
586cba
+            args['max-connections'] = max_connections
586cba
+
586cba
+        result = self.vm.qmp('nbd-server-start', args)
586cba
+        self.assert_qmp(result, 'return', {})
586cba
+        yield
586cba
+
586cba
+        result = self.vm.qmp('nbd-server-stop')
586cba
+        self.assert_qmp(result, 'return', {})
586cba
+
586cba
+    def add_export(self, name, writable=None):
586cba
+        args = {
586cba
+            'type': 'nbd',
586cba
+            'id': name,
586cba
+            'node-name': 'n',
586cba
+            'name': name,
586cba
+        }
586cba
+        if writable is not None:
586cba
+            args['writable'] = writable
586cba
+
586cba
+        result = self.vm.qmp('block-export-add', args)
586cba
+        self.assert_qmp(result, 'return', {})
586cba
+
586cba
+    def test_default_settings(self):
586cba
+        with self.run_server():
586cba
+            self.add_export('r')
586cba
+            self.add_export('w', writable=True)
586cba
+            with open_nbd('r') as h:
586cba
+                self.assertTrue(h.can_multi_conn())
586cba
+            with open_nbd('w') as h:
586cba
+                self.assertTrue(h.can_multi_conn())
586cba
+
586cba
+    def test_limited_connections(self):
586cba
+        with self.run_server(max_connections=1):
586cba
+            self.add_export('r')
586cba
+            self.add_export('w', writable=True)
586cba
+            with open_nbd('r') as h:
586cba
+                self.assertFalse(h.can_multi_conn())
586cba
+            with open_nbd('w') as h:
586cba
+                self.assertFalse(h.can_multi_conn())
586cba
+
586cba
+    def test_parallel_writes(self):
586cba
+        with self.run_server():
586cba
+            self.add_export('w', writable=True)
586cba
+
586cba
+            clients = [nbd.NBD() for _ in range(3)]
586cba
+            for c in clients:
586cba
+                c.connect_uri(nbd_uri.format('w'))
586cba
+                self.assertTrue(c.can_multi_conn())
586cba
+
586cba
+            initial_data = clients[0].pread(1024 * 1024, 0)
586cba
+            self.assertEqual(initial_data, b'\x01' * 1024 * 1024)
586cba
+
586cba
+            updated_data = b'\x03' * 1024 * 1024
586cba
+            clients[1].pwrite(updated_data, 0)
586cba
+            clients[2].flush()
586cba
+            current_data = clients[0].pread(1024 * 1024, 0)
586cba
+
586cba
+            self.assertEqual(updated_data, current_data)
586cba
+
586cba
+            for i in range(3):
586cba
+                clients[i].shutdown()
586cba
+
586cba
+
586cba
+if __name__ == '__main__':
586cba
+    try:
586cba
+        # Easier to use libnbd than to try and set up parallel
586cba
+        # 'qemu-nbd --list' or 'qemu-io' processes, but not all systems
586cba
+        # have libnbd installed.
586cba
+        import nbd  # type: ignore
586cba
+
586cba
+        iotests.main(supported_fmts=['qcow2'])
586cba
+    except ImportError:
586cba
+        iotests.notrun('libnbd not installed')
586cba
diff --git a/tests/qemu-iotests/tests/nbd-multiconn.out b/tests/qemu-iotests/tests/nbd-multiconn.out
586cba
new file mode 100644
586cba
index 0000000000..8d7e996700
586cba
--- /dev/null
586cba
+++ b/tests/qemu-iotests/tests/nbd-multiconn.out
586cba
@@ -0,0 +1,5 @@
586cba
+...
586cba
+----------------------------------------------------------------------
586cba
+Ran 3 tests
586cba
+
586cba
+OK
586cba
diff --git a/tests/qemu-iotests/tests/nbd-qemu-allocation.out b/tests/qemu-iotests/tests/nbd-qemu-allocation.out
586cba
index 0bf1abb063..9d938db24e 100644
586cba
--- a/tests/qemu-iotests/tests/nbd-qemu-allocation.out
586cba
+++ b/tests/qemu-iotests/tests/nbd-qemu-allocation.out
586cba
@@ -17,7 +17,7 @@ wrote 2097152/2097152 bytes at offset 1048576
586cba
 exports available: 1
586cba
  export: ''
586cba
   size:  4194304
586cba
-  flags: 0x58f ( readonly flush fua df multi cache )
586cba
+  flags: 0x48f ( readonly flush fua df cache )
586cba
   min block: 1
586cba
   opt block: 4096
586cba
   max block: 33554432
586cba
-- 
586cba
2.31.1
586cba