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 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.