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 01/13] server41tests: add helpers and basic synchronous COPY test
Date: Thu, 09 Jul 2026 15:02:29 -0400 [thread overview]
Message-ID: <20260709-copy-v1-1-849bf581d7cb@kernel.org> (raw)
In-Reply-To: <20260709-copy-v1-0-849bf581d7cb@kernel.org>
Add helpers to reduce boilerplate in COPY tests: _do_copy(),
_create_and_open(), _poll_offload_status(), and chunked _write_data()/
_verify_data() that split I/O into pieces bounded by the session's
negotiated ca_maxrequestsize/ca_maxresponsesize (a single large WRITE or
READ would otherwise fail with NFS4ERR_REQ_TOO_BIG).
Add testSyncCopy (COPY1): write 64KB to a source file, copy it, and
verify the destination contents. RFC 7862 permits a server to perform
the copy asynchronously even when a synchronous copy is requested, so
honor cr_resok4.cr_requirements.cr_synchronous: poll OFFLOAD_STATUS to
completion when the server downgrades to async instead of failing on a
zero wr_count.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_copy.py | 90 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 89 insertions(+), 1 deletion(-)
diff --git a/nfs4.1/server41tests/st_copy.py b/nfs4.1/server41tests/st_copy.py
index a4bdb77ca407..c7e48adf6fbe 100644
--- a/nfs4.1/server41tests/st_copy.py
+++ b/nfs4.1/server41tests/st_copy.py
@@ -1,14 +1,102 @@
+import time
+
from .st_create_session import create_session
from xdrdef.nfs4_const import *
from .environment import check, fail, create_file, open_file, close_file
-from .environment import open_create_file_op, use_obj, write_file
+from .environment import open_create_file_op, use_obj, write_file, read_file
from xdrdef.nfs4_type import open_owner4, openflag4, createhow4, open_claim4
from xdrdef.nfs4_type import creatverfattr, fattr4, stateid4, locker4, lock_owner4
from xdrdef.nfs4_type import open_to_lock_owner4
import nfs_ops
op = nfs_ops.NFS4ops()
+def _do_copy(sess, src_fh, src_stateid, dst_fh, dst_stateid,
+ src_offset=0, dst_offset=0, count=0,
+ consecutive=0, synchronous=1):
+ ops = [op.putfh(src_fh), op.savefh(), op.putfh(dst_fh),
+ op.copy(src_stateid, dst_stateid, src_offset, dst_offset,
+ count, consecutive, synchronous, [])]
+ return sess.compound(ops)
+
+def _poll_offload_status(sess, dst_fh, copy_stateid, timeout=120):
+ deadline = time.time() + timeout
+ while time.time() < deadline:
+ ops = [op.putfh(dst_fh), op.offload_status(copy_stateid)]
+ res = sess.compound(ops)
+ check(res)
+ status_res = res.resarray[-1]
+ if status_res.osr_complete:
+ return status_res
+ time.sleep(1)
+ fail("OFFLOAD_STATUS did not complete within %d seconds" % timeout)
+
+def _create_and_open(sess, name):
+ res = create_file(sess, name)
+ check(res)
+ fh = res.resarray[-1].object
+ stateid = res.resarray[-2].stateid
+ return fh, stateid
+
+def _write_data(sess, fh, stateid, data, offset=0):
+ """Write data in chunks bounded by the session's max request size."""
+ chunk = sess.fore_channel.maxrequestsize - 1024
+ pos = 0
+ while pos < len(data):
+ res = write_file(sess, fh, data[pos:pos + chunk], offset + pos, stateid)
+ check(res, msg="WRITE at offset %d" % (offset + pos))
+ pos += res.count
+
+def _verify_data(sess, fh, stateid, data, offset=0):
+ """Read back and compare data in chunks bounded by max response size."""
+ chunk = sess.fore_channel.maxresponsesize - 1024
+ pos = 0
+ while pos < len(data):
+ res = read_file(sess, fh, offset + pos, min(chunk, len(data) - pos),
+ stateid)
+ check(res)
+ if not res.data:
+ fail("Short read at offset %d" % (offset + pos))
+ if res.data != data[pos:pos + len(res.data)]:
+ fail("Data mismatch at offset %d" % (offset + pos))
+ pos += len(res.data)
+
+def testSyncCopy(t, env):
+ """synchronous copy of a file and verify contents
+
+ FLAGS: copy
+ CODE: COPY1
+ """
+ sess = env.c1.new_client_session(env.testname(t))
+ src_fh, src_stateid = _create_and_open(sess, env.testname(t))
+ data = b"A" * 65536
+ _write_data(sess, src_fh, src_stateid, data)
+
+ dst_fh, dst_stateid = _create_and_open(sess, env.testname(t) + b"_dst")
+
+ res = _do_copy(sess, src_fh, src_stateid, dst_fh, dst_stateid,
+ count=len(data), synchronous=1)
+ check(res)
+ cr = res.resarray[-1]
+
+ # A synchronous COPY was requested, but RFC 7862 permits the server to
+ # perform the copy asynchronously anyway; cr_requirements.cr_synchronous
+ # reports what actually happened. Honor either, but verify the byte count.
+ if cr.cr_resok4.cr_requirements.cr_synchronous:
+ if cr.cr_response.wr_count != len(data):
+ fail("Synchronous copy expected %d bytes, got %d" %
+ (len(data), cr.cr_response.wr_count))
+ else:
+ copy_stateid = cr.cr_response.wr_callback_id[0]
+ status = _poll_offload_status(sess, dst_fh, copy_stateid)
+ if status.osr_complete[0] != NFS4_OK:
+ fail("Async copy completed with error: %d" % status.osr_complete[0])
+ if status.osr_count != len(data):
+ fail("Expected %d bytes copied, got %d" %
+ (len(data), status.osr_count))
+
+ _verify_data(sess, dst_fh, dst_stateid, data)
+
def testZeroLengthCopy(t, env):
"""test that zero-length copy copies to EOF
--
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 ` Jeff Layton [this message]
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 ` [PATCH pynfs 12/13] server41tests: support a second server for inter-server copy Jeff Layton
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-1-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.