[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