Linux NFS development
 help / color / mirror / Atom feed
From: Jeff Layton <jlayton@kernel.org>
To: Calum Mackay <calum.mackay@oracle.com>
Cc: Chuck Lever <cel@kernel.org>, NeilBrown <neil@brown.name>,
	 Olga Kornievskaia <okorniev@redhat.com>,
	Dai Ngo <Dai.Ngo@oracle.com>,  Tom Talpey <tom@talpey.com>,
	linux-nfs@vger.kernel.org,  Jeff Layton <jlayton@kernel.org>
Subject: [PATCH pynfs 12/13] server41tests: support a second server for inter-server copy
Date: Thu, 09 Jul 2026 15:02:40 -0400	[thread overview]
Message-ID: <20260709-copy-v1-12-849bf581d7cb@kernel.org> (raw)
In-Reply-To: <20260709-copy-v1-0-849bf581d7cb@kernel.org>

Inter-server (server-to-server) COPY tests need to talk to two servers.
Add a --server2 SERVER:/PATH option, parsed with parse_nfs_url, and an
optional second client env.c2 in the test environment.

The environment now creates, initializes, and cleans up the test tree on
both servers, and factors credential creation into a helper so c2 gets a
credential built the same way as c1.  When --server2 is not given, c2 is
None and single-server runs are unaffected.

Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
 nfs4.1/server41tests/environment.py | 127 ++++++++++++++++++++++++------------
 nfs4.1/testserver.py                |  14 ++++
 2 files changed, 98 insertions(+), 43 deletions(-)

diff --git a/nfs4.1/server41tests/environment.py b/nfs4.1/server41tests/environment.py
index c14b12cbfca5..38cd5a7a7185 100644
--- a/nfs4.1/server41tests/environment.py
+++ b/nfs4.1/server41tests/environment.py
@@ -119,23 +119,23 @@ class Environment(testmod.Environment):
         self._lock = Lock()
         self.opts = opts
         self.c1 = nfs4client.NFS4Client(opts.server, opts.port, opts.minorversion, secure=opts.secure)
-        s1 = rpc.security.instance(opts.flavor)
-        if opts.flavor == rpc.AUTH_NONE:
-            self.cred1 = s1.init_cred()
-        elif opts.flavor == rpc.AUTH_SYS:
-            self.cred1 = s1.init_cred(uid=opts.uid, gid=opts.gid, name=opts.machinename)
-        elif opts.flavor == rpc.RPCSEC_GSS:
-            call = self.c1.make_call_function(self.c1.c1, 0,
-                                              self.c1.default_prog,
-                                              self.c1.default_vers)
-            krb5_cred = AuthGss().init_cred(call, target="nfs@%s" % opts.server)
-            krb5_cred.service = opts.service
-            self.cred1 = krb5_cred
-        self.c1.set_cred(self.cred1)
+        self.cred1 = self._make_cred(self.c1, opts.server)
         self.cred2 = AuthSys().init_cred(uid=1111, gid=37, name=b"shampoo")
 
         opts.home = opts.path + [b'tmp']
         self.c1.homedir = opts.home
+
+        # Optional second server, used only by inter-server (server-to-server)
+        # COPY tests.  Enabled by passing --server2 SERVER:/PATH on the
+        # command line; None otherwise so single-server runs are unaffected.
+        self.c2 = None
+        if getattr(opts, "server2", None):
+            self.c2 = nfs4client.NFS4Client(opts.server2_host, opts.server2_port,
+                                            opts.minorversion, secure=opts.secure)
+            opts.server2_home = opts.server2_path + [b'tmp']
+            self.c2.homedir = opts.server2_home
+            self._make_cred(self.c2, opts.server2_host)
+
         # Put this after client creation, to ensure _last_verf bigger than
         # any natural client verifiers
         self.timestamp = int(time.time())
@@ -146,38 +146,72 @@ class Environment(testmod.Environment):
         self.stateid1 = stateid4(0xffffffff, b'\xff'*12)
 
         log.info("Created client to %s, %i" % (opts.server, opts.port))
+        if self.c2 is not None:
+            log.info("Created second client to %s, %i" %
+                     (opts.server2_host, opts.server2_port))
+
+    def _make_cred(self, client, server_host):
+        """Build and install an RPC credential on client for server_host."""
+        s = rpc.security.instance(self.opts.flavor)
+        if self.opts.flavor == rpc.AUTH_NONE:
+            cred = s.init_cred()
+        elif self.opts.flavor == rpc.AUTH_SYS:
+            cred = s.init_cred(uid=self.opts.uid, gid=self.opts.gid,
+                               name=self.opts.machinename)
+        elif self.opts.flavor == rpc.RPCSEC_GSS:
+            call = client.make_call_function(client.c1, 0,
+                                             client.default_prog,
+                                             client.default_vers)
+            cred = AuthGss().init_cred(call, target="nfs@%s" % server_host)
+            cred.service = self.opts.service
+        client.set_cred(cred)
+        return cred
+
+    def _all_clients(self):
+        """All configured clients (c1, and c2 if a second server was given)."""
+        return [c for c in (self.c1, self.c2) if c is not None]
 
     def init(self):
         """Run once before any test is run"""
         if self.opts.noinit:
             return
-        sess = self.c1.new_client_session(b"Environment.init_%i" %
-                                                    self.timestamp)
+        self._init_server(self.c1, self.opts.path, self.opts.home)
+        if self.c2 is not None:
+            self._init_server(self.c2, self.opts.server2_path,
+                              self.opts.server2_home)
+        self.clean_sessions()
+        self.clean_clients()
+
+    def _init_server(self, c, path, home):
+        """Prepare (and optionally build) the test tree on a single server."""
+        sess = c.new_client_session(b"Environment.init_%i" % self.timestamp)
         if self.opts.maketree:
-            self._maketree(sess)
-        # Make sure opts.home exists
-        res = sess.compound(use_obj(self.opts.home))
-        check(res, msg="Could not LOOKUP /%s," % b'/'.join(self.opts.home))
+            self._maketree(sess, path, home)
+        # Make sure home exists
+        res = sess.compound(use_obj(home))
+        check(res, msg="Could not LOOKUP /%s," % b'/'.join(home))
         # Make sure it is empty
-        clean_dir(sess, self.opts.home)
+        clean_dir(sess, home)
         sess.c.null()
-        self.clean_sessions()
-        self.clean_clients()
 
-    def _maketree(self, sess):
+    def _maketree(self, sess, path=None, home=None):
         """Make test tree"""
+        if path is None:
+            path = self.opts.path
+        if home is None:
+            home = self.opts.home
         # ensure /tmp (and path leading up) exists
-        path = []
-        for comp in self.opts.home:
-            path.append(comp)
-            res = sess.compound(use_obj(path))
+        p = []
+        for comp in home:
+            p.append(comp)
+            res = sess.compound(use_obj(p))
             check(res, [NFS4_OK, NFS4ERR_NOENT],
-                      "LOOKUP /%s," % b'/'.join(path))
+                      "LOOKUP /%s," % b'/'.join(p))
             if res.status == NFS4ERR_NOENT:
-                res = create_obj(sess, path, NF4DIR)
-                check(res, msg="Trying to create /%s," % b'/'.join(path))
+                res = create_obj(sess, p, NF4DIR)
+                check(res, msg="Trying to create /%s," % b'/'.join(p))
         # ensure /tree exists and is empty
-        tree = self.opts.path + [b'tree']
+        tree = path + [b'tree']
         res = sess.compound(use_obj(tree))
         check(res, [NFS4_OK, NFS4ERR_NOENT])
         if res.status == NFS4ERR_NOENT:
@@ -210,16 +244,21 @@ class Environment(testmod.Environment):
         """Run once after all tests are run"""
         if self.opts.nocleanup:
             return
-        sess = self.c1.new_client_session(b"Environment.init_%i" % self.timestamp)
-        clean_dir(sess, self.opts.home)
-        sess.c.null()
+        homes = [(self.c1, self.opts.home)]
+        if self.c2 is not None:
+            homes.append((self.c2, self.opts.server2_home))
+        for c, home in homes:
+            sess = c.new_client_session(b"Environment.init_%i" % self.timestamp)
+            clean_dir(sess, home)
+            sess.c.null()
         self.clean_sessions()
         self.clean_clients()
 
     def startUp(self):
         """Run before each test"""
         log.debug("Sending pretest NULL")
-        self.c1.null()
+        for c in self._all_clients():
+            c.null()
         log.debug("Got pretest NULL response")
 
     def sleep(self, sec, msg=''):
@@ -260,16 +299,18 @@ class Environment(testmod.Environment):
         return b"%s_%i" % (os.fsencode(t.code), self.timestamp)
 
     def clean_sessions(self):
-        """Destroy client name env.c1"""
-        for sessionid in list(self.c1.sessions):
-            self.c1.compound([op.destroy_session(sessionid)])
-            del(self.c1.sessions[sessionid])
+        """Destroy all sessions on all configured clients"""
+        for c in self._all_clients():
+            for sessionid in list(c.sessions):
+                c.compound([op.destroy_session(sessionid)])
+                del(c.sessions[sessionid])
 
     def clean_clients(self):
-        """Destroy client name env.c1"""
-        for clientid in list(self.c1.clients):
-            self.c1.compound([op.destroy_clientid(clientid)])
-            del(self.c1.clients[clientid])
+        """Destroy all client ids on all configured clients"""
+        for c in self._all_clients():
+            for clientid in list(c.clients):
+                c.compound([op.destroy_clientid(clientid)])
+                del(c.clients[clientid])
 
 #########################################
 debug_fail = False
diff --git a/nfs4.1/testserver.py b/nfs4.1/testserver.py
index 0970c64efe34..f38279b36cba 100755
--- a/nfs4.1/testserver.py
+++ b/nfs4.1/testserver.py
@@ -153,6 +153,13 @@ def scan_options(p):
                  help="Use FH for certain specialized tests")
     p.add_option_group(g)
 
+    g = OptionGroup(p, "Inter-server copy options",
+                    "A second server enables NFSv4.2 inter-server "
+                    "(server-to-server) COPY tests.")
+    g.add_option("--server2", default=None, metavar="SERVER:/PATH",
+                 help="Second server and export for inter-server copy tests")
+    p.add_option_group(g)
+
     g = OptionGroup(p, "Server workaround options",
                     "Certain servers handle certain things in unexpected ways."
                     " These options allow you to alter test behavior so that "
@@ -259,6 +266,13 @@ def main():
 
     opt.server, opt.port = server_list[0]
 
+    # Optional second server for inter-server copy tests
+    if opt.server2 is not None:
+        server2_list, opt.server2_path = nfs4lib.parse_nfs_url(opt.server2)
+        if not server2_list:
+            p.error("%s not a valid server name" % opt.server2)
+        opt.server2_host, opt.server2_port = server2_list[0]
+
     if not args:
         p.error("No tests given")
 

-- 
2.55.0


  parent reply	other threads:[~2026-07-09 19:02 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-09 19:02 [PATCH pynfs 00/13] server41tests: add some tests for copy offload Jeff Layton
2026-07-09 19:02 ` [PATCH pynfs 01/13] server41tests: add helpers and basic synchronous COPY test Jeff Layton
2026-07-09 19:02 ` [PATCH pynfs 02/13] server41tests: test COPY with non-zero offsets Jeff Layton
2026-07-09 19:02 ` [PATCH pynfs 03/13] server41tests: test async COPY with OFFLOAD_STATUS polling Jeff Layton
2026-07-09 19:02 ` [PATCH pynfs 04/13] server41tests: test OFFLOAD_STATUS persists after async copy completes Jeff Layton
2026-07-09 19:02 ` [PATCH pynfs 05/13] server41tests: test OFFLOAD_CANCEL on async copy Jeff Layton
2026-07-09 19:02 ` [PATCH pynfs 06/13] server41tests: test COPY with bad source stateid Jeff Layton
2026-07-09 19:02 ` [PATCH pynfs 07/13] server41tests: test COPY with bad destination stateid Jeff Layton
2026-07-09 19:02 ` [PATCH pynfs 08/13] server41tests: test OFFLOAD_STATUS with fabricated stateid Jeff Layton
2026-07-09 19:02 ` [PATCH pynfs 09/13] server41tests: test COPY within same file Jeff Layton
2026-07-09 19:02 ` [PATCH pynfs 10/13] nfs4.1: fix COPY_NOTIFY args union arm name in XDR Jeff Layton
2026-07-09 19:02 ` [PATCH pynfs 11/13] server41tests: test COPY_NOTIFY Jeff Layton
2026-07-09 19:02 ` Jeff Layton [this message]
2026-07-09 19:02 ` [PATCH pynfs 13/13] server41tests: test inter-server COPY Jeff Layton

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=20260709-copy-v1-12-849bf581d7cb@kernel.org \
    --to=jlayton@kernel.org \
    --cc=Dai.Ngo@oracle.com \
    --cc=calum.mackay@oracle.com \
    --cc=cel@kernel.org \
    --cc=linux-nfs@vger.kernel.org \
    --cc=neil@brown.name \
    --cc=okorniev@redhat.com \
    --cc=tom@talpey.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox