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
next prev 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