From: Scott Mayhew <smayhew@redhat.com>
To: calum.mackay@oracle.com
Cc: linux-nfs@vger.kernel.org
Subject: Re: [PATCH v2] pynfs: add delegation test for CB_GETATTR after sync WRITE
Date: Fri, 3 Apr 2026 20:55:13 -0400 [thread overview]
Message-ID: <adBhcY8lfimH_7G6@aion> (raw)
In-Reply-To: <20260404003050.1560149-6-smayhew@redhat.com>
My bad, the subject should say [PATCH v2 5/5]...
On Fri, 03 Apr 2026, Scott Mayhew wrote:
> DELEG27 tests the scenario where a client has written data to the server
> while holding a write delegation, but is not *currently* holding
> modified data in its cache.
>
> In this case, the CB_GETATTR should not trigger an mtime update (the
> time_modify that client1 gets in the GETATTR after the WRITE should
> match the time_modify it gets in the GETATTR in the DELEGRETURN
> compound).
>
> Signed-off-by: Scott Mayhew <smayhew@redhat.com>
> ---
> nfs4.1/server41tests/st_delegation.py | 151 ++++++++++++++++++++++++++
> 1 file changed, 151 insertions(+)
>
> diff --git a/nfs4.1/server41tests/st_delegation.py b/nfs4.1/server41tests/st_delegation.py
> index bbf6925..6a08950 100644
> --- a/nfs4.1/server41tests/st_delegation.py
> +++ b/nfs4.1/server41tests/st_delegation.py
> @@ -8,6 +8,8 @@ import nfs_ops
> op = nfs_ops.NFS4ops()
> import nfs4lib
> import threading
> +import copy
> +import time
>
> def _got_deleg(deleg):
> return (deleg.delegation_type != OPEN_DELEGATE_NONE and
> @@ -476,3 +478,152 @@ def testDelegReadAfterClose(t, env):
> # cleanup: return delegation
> res = sess1.compound([op.putfh(fh), op.delegreturn(delegstateid)])
> check(res)
> +
> +def testCbGetattrAfterSyncWrite(t, env):
> + """Test CB_GETATTR after a FILE_SYNC4 WRITE
> +
> + 1. Client 1 opens a file (getting a write deleg or a write attrs deleg) and
> + does a GETATTR
> + 2. Client 1 does a FILE_SYNC4 WRITE. If we got a write delegation, it
> + follows this up with a GETATTR. Otherwise we got a write attrs deleg
> + and we construct the attrs ourself.
> + 3. Client 2 does a GETATTR, triggering a CB_GETATTR to client 1. Client 2
> + then does an OPEN, triggering a CB_RECALL to client 1.
> + 4. Client 1 does a PUTFH|SETATTR|GETATTR|DELEGRETURN if we have a write
> + attrs deleg, otherwise it does a PUTFH|GETATTR|DELEGRETURN.
> +
> + time_modify should only change between steps 1 and 2. It should not change
> + from steps 2 thru 4.
> +
> + FLAGS: deleg all
> + CODE: DELEG27
> + """
> + cb = threading.Event()
> + cbattrs = {}
> + def getattr_post_hook(arg, env, res):
> + res.obj_attributes = cbattrs
> + env.notify = cb.set
> + return res
> +
> + recall = threading.Event()
> + def recall_pre_hook(arg, env):
> + recall.stateid = arg.stateid
> + recall.cred = env.cred.raw_cred
> + env.notify = recall.set
> + def recall_post_hook(arg, env, res):
> + return res
> +
> + size = 5
> +
> + sess1 = env.c1.new_client_session(b"%s_1" % env.testname(t))
> + sess1.client.cb_post_hook(OP_CB_GETATTR, getattr_post_hook)
> + sess1.client.cb_pre_hook(OP_CB_RECALL, recall_pre_hook)
> + sess1.client.cb_post_hook(OP_CB_RECALL, recall_post_hook)
> +
> + res = sess1.compound([op.putrootfh(),
> + op.getattr(nfs4lib.list2bitmap([FATTR4_SUPPORTED_ATTRS,
> + FATTR4_OPEN_ARGUMENTS]))])
> + check(res)
> + caps = res.resarray[-1].obj_attributes
> +
> + openmask = (OPEN4_SHARE_ACCESS_READ |
> + OPEN4_SHARE_ACCESS_WRITE |
> + OPEN4_SHARE_ACCESS_WANT_WRITE_DELEG)
> +
> + if caps[FATTR4_SUPPORTED_ATTRS] & (1 << FATTR4_OPEN_ARGUMENTS):
> + if caps[FATTR4_OPEN_ARGUMENTS].oa_share_access_want & OPEN_ARGS_SHARE_ACCESS_WANT_DELEG_TIMESTAMPS:
> + openmask |= 1<<OPEN_ARGS_SHARE_ACCESS_WANT_DELEG_TIMESTAMPS
> +
> + fh, stateid, deleg = __create_file_with_deleg(sess1, env.testname(t), openmask)
> + delegtype = deleg.delegation_type
> + if delegtype != OPEN_DELEGATE_WRITE_ATTRS_DELEG and delegtype != OPEN_DELEGATE_WRITE:
> + fail("Didn't get a write delegation.")
> + delegstateid = deleg.write.stateid
> +
> + attrs1 = do_getattrdict(sess1, fh, [FATTR4_CHANGE, FATTR4_SIZE,
> + FATTR4_TIME_ACCESS, FATTR4_TIME_MODIFY])
> +
> + cbattrs[FATTR4_CHANGE] = attrs1[FATTR4_CHANGE]
> + cbattrs[FATTR4_SIZE] = attrs1[FATTR4_SIZE]
> +
> + env.sleep(1)
> + res = write_file(sess1, fh, b'z' * size, 0, delegstateid)
> + check(res)
> +
> + if delegtype == OPEN_DELEGATE_WRITE_ATTRS_DELEG:
> + attrs2 = copy.deepcopy(attrs1)
> + now = divmod(time.time_ns(), 1000000000)
> + attrs2[FATTR4_TIME_ACCESS] = nfstime4(*now)
> + attrs2[FATTR4_TIME_MODIFY] = nfstime4(*now)
> + cbattrs[FATTR4_TIME_DELEG_ACCESS] = nfstime4(*now)
> + cbattrs[FATTR4_TIME_DELEG_MODIFY] = nfstime4(*now)
> + else:
> + attrs2 = do_getattrdict(sess1, fh, [FATTR4_CHANGE, FATTR4_SIZE,
> + FATTR4_TIME_ACCESS, FATTR4_TIME_MODIFY])
> +
> + # No need to bump FATTR4_CHANGE because we've already flushed our data
> + cbattrs[FATTR4_SIZE] = size
> +
> + sess2 = env.c1.new_client_session(b"%s_2" % env.testname(t))
> + slot = sess2.compound_async([op.putfh(fh),
> + op.getattr(1<<FATTR4_CHANGE |
> + 1<<FATTR4_SIZE |
> + 1<<FATTR4_TIME_ACCESS |
> + 1<<FATTR4_TIME_MODIFY)])
> +
> + completed = cb.wait(2)
> + if not completed:
> + fail("CB_GETATTR not received")
> +
> + res = sess2.listen(slot)
> + check(res)
> + attrs3 = res.resarray[-1].obj_attributes
> +
> + claim = open_claim4(CLAIM_NULL, env.testname(t))
> + owner = open_owner4(0, b"owner")
> + how = openflag4(OPEN4_NOCREATE)
> + open_op = op.open(0, OPEN4_SHARE_ACCESS_WRITE,
> + OPEN4_SHARE_DENY_NONE, owner, how, claim)
> + slot = sess2.compound_async(env.home + [open_op, op.getfh()])
> + completed = recall.wait(2)
> + if not completed:
> + fail("CB_RECALL not received")
> +
> + env.sleep(.1)
> +
> + # Note if we have a write attrs deleg we should do a setattr before the
> + # delegreturn (see RFC 9754, section 5)
> + res = sess1.compound([op.putfh(fh),
> + *([op.setattr(delegstateid,
> + {FATTR4_TIME_DELEG_ACCESS: cbattrs[FATTR4_TIME_DELEG_ACCESS],
> + FATTR4_TIME_DELEG_MODIFY: cbattrs[FATTR4_TIME_DELEG_MODIFY]})]
> + if delegtype == OPEN_DELEGATE_WRITE_ATTRS_DELEG else []),
> + op.getattr(1<<FATTR4_CHANGE |
> + 1<<FATTR4_SIZE |
> + 1<<FATTR4_TIME_ACCESS |
> + 1<<FATTR4_TIME_MODIFY),
> + op.delegreturn(delegstateid)])
> + check(res)
> + attrs4 = res.resarray[-2].obj_attributes
> +
> + res = sess2.listen(slot)
> + check(res, [NFS4_OK, NFS4ERR_DELAY])
> + if res.status == NFS4_OK:
> + fh2 = res.resarray[-1].object
> + stateid2 = res.resarray[-2].stateid
> + else:
> + fh2 = None
> + stateid2 = None
> +
> + close_file(sess1, fh, stateid=stateid)
> + if fh2 is not None and stateid2 is not None:
> + close_file(sess2, fh2, stateid=stateid2)
> +
> + #print(f"attrs1: size {attrs1[FATTR4_SIZE]} change {attrs1[FATTR4_CHANGE]} mtime {attrs1[FATTR4_TIME_MODIFY]}")
> + #print(f"attrs2: size {attrs2[FATTR4_SIZE]} change {attrs2[FATTR4_CHANGE]} mtime {attrs2[FATTR4_TIME_MODIFY]}")
> + #print(f"attrs3: size {attrs3[FATTR4_SIZE]} change {attrs3[FATTR4_CHANGE]} mtime {attrs3[FATTR4_TIME_MODIFY]}")
> + #print(f"attrs4: size {attrs4[FATTR4_SIZE]} change {attrs4[FATTR4_CHANGE]} mtime {attrs4[FATTR4_TIME_MODIFY]}")
> +
> + if compareTimes(attrs2[FATTR4_TIME_MODIFY], attrs4[FATTR4_TIME_MODIFY]) != 0:
> + fail(f"mtime after write ({attrs2[FATTR4_TIME_MODIFY]}) != "
> + f"mtime from delegreturn ({attrs4[FATTR4_TIME_MODIFY]})")
> --
> 2.53.0
>
>
prev parent reply other threads:[~2026-04-04 0:55 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-04 0:30 [PATCH v2 0/5] pynfs: a handful of fixes and a new CB_GETATTR test Scott Mayhew
2026-04-04 0:30 ` [PATCH v2 1/5] pynfs: ensure tests clean up after themselves Scott Mayhew
2026-04-04 0:30 ` [PATCH v2 2/5] pynfs: fix the check for delegated attribute support Scott Mayhew
2026-04-04 0:30 ` [PATCH v2 3/5] pynfs: _testCbGetattr() should check the delegation that was received Scott Mayhew
2026-04-04 0:30 ` [PATCH v2 4/5] pynfs: fix erroneous test in DELEG24 and DELEG25 Scott Mayhew
2026-04-04 0:30 ` [PATCH v2] pynfs: add delegation test for CB_GETATTR after sync WRITE Scott Mayhew
2026-04-04 0:55 ` Scott Mayhew [this message]
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=adBhcY8lfimH_7G6@aion \
--to=smayhew@redhat.com \
--cc=calum.mackay@oracle.com \
--cc=linux-nfs@vger.kernel.org \
/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.