[PATCH pynfs 01/13] server41tests: add helpers and basic synchronous COPY test

Jeff Layton <[email protected]>
Newsgroups gmane.linux.nfs
Message-ID <[email protected]>
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 <[email protected]>
---
 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.