* [PATCH pynfs v3 01/26] nfs4.1: add proposed NOTIFY4_GFLAG_EXTEND flag
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 02/26] nfs4.1: add a getfh() to the end of create_obj() compound Jeff Layton
` (25 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
This flag has been proposed as part of RFC8881bis. This flag is used to
negotiate extensions to the original directory delegations originally
specified in RFC8881.
In practice, the Linux nfs server requires that the client support this
flag if it's requesting anything other than a recall-only delegation.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/xdrdef/nfs4.x | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/nfs4.1/xdrdef/nfs4.x b/nfs4.1/xdrdef/nfs4.x
index ee3da8aa7a34..f03eb538a298 100644
--- a/nfs4.1/xdrdef/nfs4.x
+++ b/nfs4.1/xdrdef/nfs4.x
@@ -3611,7 +3611,8 @@ enum notify_type4 {
NOTIFY4_REMOVE_ENTRY = 2,
NOTIFY4_ADD_ENTRY = 3,
NOTIFY4_RENAME_ENTRY = 4,
- NOTIFY4_CHANGE_COOKIE_VERIFIER = 5
+ NOTIFY4_CHANGE_COOKIE_VERIFIER = 5,
+ NOTIFY4_GFLAG_EXTEND = 6 /* proposed in rfc8881bis */
};
/* Changed entry information. */
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 02/26] nfs4.1: add a getfh() to the end of create_obj() compound
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 01/26] nfs4.1: add proposed NOTIFY4_GFLAG_EXTEND flag Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 03/26] server41tests: add a basic GET_DIR_DELEGATION test Jeff Layton
` (24 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
A later patch will use the create_obj() function to create files, but
some of the tests require us to know the filehandle of the created
object. Add a GETFH operation to the end of the create_obj() compound.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/environment.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/nfs4.1/server41tests/environment.py b/nfs4.1/server41tests/environment.py
index 3c77153631ae..f5b1fea4a64c 100644
--- a/nfs4.1/server41tests/environment.py
+++ b/nfs4.1/server41tests/environment.py
@@ -474,7 +474,7 @@ def create_obj(sess, path, kind=NF4DIR, attrs={FATTR4_MODE:0o755}):
# Ensure using createtype4
if not hasattr(kind, "type"):
kind = createtype4(kind)
- ops = use_obj(path[:-1]) + [op.create(kind, path[-1], attrs)]
+ ops = use_obj(path[:-1]) + [op.create(kind, path[-1], attrs), op.getfh()]
return sess.compound(ops)
def open_create_file(sess, owner, path=None, attrs={FATTR4_MODE: 0o644},
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 03/26] server41tests: add a basic GET_DIR_DELEGATION test
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 01/26] nfs4.1: add proposed NOTIFY4_GFLAG_EXTEND flag Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 02/26] nfs4.1: add a getfh() to the end of create_obj() compound Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 04/26] server41tests: add a test for duplicate GET_DIR_DELEGATION requests Jeff Layton
` (23 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Test basic dir delegation handout, recall and return
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/nfs4client.py | 6 ++
nfs4.1/server41tests/__init__.py | 1 +
nfs4.1/server41tests/st_dir_deleg.py | 109 +++++++++++++++++++++++++++++++++++
3 files changed, 116 insertions(+)
diff --git a/nfs4.1/nfs4client.py b/nfs4.1/nfs4client.py
index 25d7fd16f12b..dbe7af761f16 100644
--- a/nfs4.1/nfs4client.py
+++ b/nfs4.1/nfs4client.py
@@ -283,6 +283,12 @@ class NFS4Client(rpc.Client, rpc.Server):
res = self.posthook(arg, env, res=NFS4_OK)
return encode_status(res)
+ def op_cb_notify(self, arg, env):
+ log_cb.info("In CB_NOTIFY")
+ self.prehook(arg, env)
+ res = self.posthook(arg, env, res=NFS4_OK)
+ return encode_status(res)
+
def op_cb_notify_lock(self, arg, env):
log_cb.info("In CB_NOTIFY_LOCK")
self.prehook(arg, env)
diff --git a/nfs4.1/server41tests/__init__.py b/nfs4.1/server41tests/__init__.py
index 156c5e3082eb..c654212c617e 100644
--- a/nfs4.1/server41tests/__init__.py
+++ b/nfs4.1/server41tests/__init__.py
@@ -28,4 +28,5 @@ __all__ = ["st_exchange_id.py", # draft 21
"st_xattr.py",
"st_courtesy.py",
"st_callback.py",
+ "st_dir_deleg.py",
]
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
new file mode 100644
index 000000000000..99768389cfa8
--- /dev/null
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -0,0 +1,109 @@
+from .st_create_session import create_session
+from .st_open import open_claim4
+from xdrdef.nfs4_const import *
+from xdrdef.nfs4_pack import NFS4Unpacker
+
+from .environment import check, fail, create_obj, use_obj
+from xdrdef.nfs4_type import *
+import nfs_ops
+op = nfs_ops.NFS4ops()
+import nfs4lib
+import threading
+
+zerotime = nfstime4(seconds=0, nseconds=0)
+
+def decode_notify_event(change):
+ """Decode a notify4 into its typed event structure."""
+ mask = nfs4lib.bitmap2list(change.notify_mask)
+ unpacker = NFS4Unpacker(change.notify_vals)
+ if NOTIFY4_REMOVE_ENTRY in mask:
+ return (NOTIFY4_REMOVE_ENTRY, unpacker.unpack_notify_remove4())
+ elif NOTIFY4_ADD_ENTRY in mask:
+ return (NOTIFY4_ADD_ENTRY, unpacker.unpack_notify_add4())
+ elif NOTIFY4_RENAME_ENTRY in mask:
+ return (NOTIFY4_RENAME_ENTRY, unpacker.unpack_notify_rename4())
+ elif NOTIFY4_CHANGE_DIR_ATTRS in mask:
+ return (NOTIFY4_CHANGE_DIR_ATTRS, unpacker.unpack_notify_attr4())
+ return (None, None)
+
+def bitmap4_to_int(bitmap):
+ """Convert a bitmap4 (list of uint32 words) to a single integer."""
+ result = 0
+ for i, word in enumerate(bitmap):
+ result |= word << (32 * i)
+ return result
+
+def _getDirDeleg(t, env, notify_mask, cb):
+ def recall_pre_hook(arg, env):
+ cb.stateid = arg.stateid # NOTE this must be done before set()
+ cb.cred = env.cred.raw_cred
+ cb.got_recall = True
+ env.notify = cb.set # This is called after compound sent to queue
+ def recall_post_hook(arg, env, res):
+ return res
+ def notify_pre_hook(arg, env):
+ cb.stateid = arg.cna_stateid
+ cb.fh = arg.cna_fh
+ cb.changes = arg.cna_changes
+ cb.got_notify = True
+ env.notify = cb.set # This is called after compound sent to queue
+ def notify_post_hook(arg, env, res):
+ return res
+
+ cb.got_recall = False
+ cb.got_notify = False
+
+ c = env.c1
+ sess1 = c.new_client_session(b"%s_1" % env.testname(t))
+ sess1.client.cb_pre_hook(OP_CB_RECALL, recall_pre_hook)
+ sess1.client.cb_post_hook(OP_CB_RECALL, recall_post_hook)
+ sess1.client.cb_pre_hook(OP_CB_NOTIFY, notify_pre_hook)
+ sess1.client.cb_post_hook(OP_CB_NOTIFY, notify_post_hook)
+
+ topdir = c.homedir + [t.code.encode('utf8')]
+ res = create_obj(sess1, topdir)
+ check(res)
+ fh = res.resarray[-1].object
+
+ ops = [ op.putfh(fh), op.get_dir_delegation(False,
+ nfs4lib.list2bitmap(notify_mask),
+ zerotime, zerotime,
+ nfs4lib.list2bitmap([]),
+ nfs4lib.list2bitmap([]))]
+ res = sess1.compound(ops)
+ check(res)
+ deleg = res.resarray[-1].gddrnf_resok4.gddr_stateid
+ return (sess1, fh, deleg)
+
+def testDirDelegSimple(t, env):
+ """Test basic dir delegation handout, recall and return
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG1
+ """
+ c = env.c1
+ recall = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env, [], recall)
+
+ # new client -- create a file in the dir
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ slot = sess2.compound_async(open_op)
+ completed = recall.wait(2)
+ env.sleep(.1)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
+
+ # Reap the async open and close any file it created
+ res = sess2.listen(slot)
+ if res.status == NFS4_OK:
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess2, file_fh, stateid=open_stateid)
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 04/26] server41tests: add a test for duplicate GET_DIR_DELEGATION requests
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (2 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 03/26] server41tests: add a basic GET_DIR_DELEGATION test Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 05/26] server41tests: pass_warn() when server doesn't support dir delegations Jeff Layton
` (22 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
---
nfs4.1/server41tests/st_dir_deleg.py | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 99768389cfa8..f47d1f6ac053 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -107,3 +107,29 @@ def testDirDelegSimple(t, env):
open_stateid = res.resarray[-2].stateid
file_fh = res.resarray[-1].object
close_file(sess2, file_fh, stateid=open_stateid)
+
+def testDirDelegDuplicate(t, env):
+ """Test that server returns GDD4_UNAVAIL on duplicate GDD4 request
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG2
+ """
+ c = env.c1
+ recall = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env, [], recall)
+
+ # get a dir deleg with no notifications
+ ops = [ op.putfh(fh), op.get_dir_delegation(False,
+ nfs4lib.list2bitmap([]),
+ zerotime, zerotime,
+ nfs4lib.list2bitmap([]),
+ nfs4lib.list2bitmap([]))]
+ res = sess1.compound(ops)
+ check(res)
+ nfstatus = res.resarray[-1].gddr_res_non_fatal4.gddrnf_status
+ if (nfstatus != GDD4_UNAVAIL):
+ fail("Server replied to duplicate request with %d" % nfstatus)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 05/26] server41tests: pass_warn() when server doesn't support dir delegations
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (3 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 04/26] server41tests: add a test for duplicate GET_DIR_DELEGATION requests Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 06/26] server41tests: test remove triggers dir delegation recall Jeff Layton
` (21 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Instead of just failing the test when GET_DIR_DELEGATION isn't supported
or the server doesn't hand out a directory delegation, have it pass with
a warning instead. This should make it safe to keep the directory
delegation tests in the "all" group.
Also, when receiving a directory delegation, vet that it got the
requested notifications. Just pass_warn() if it didn't.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 21 ++++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index f47d1f6ac053..5f46c08316e3 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -65,14 +65,29 @@ def _getDirDeleg(t, env, notify_mask, cb):
check(res)
fh = res.resarray[-1].object
- ops = [ op.putfh(fh), op.get_dir_delegation(False,
- nfs4lib.list2bitmap(notify_mask),
+ mask_bm = nfs4lib.list2bitmap(notify_mask)
+ ops = [ op.putfh(fh), op.get_dir_delegation(False, nfs4lib.list2bitmap(notify_mask),
zerotime, zerotime,
nfs4lib.list2bitmap([]),
nfs4lib.list2bitmap([]))]
res = sess1.compound(ops)
- check(res)
+ check(res, [NFS4_OK, NFS4ERR_NOTSUPP])
+ if (res.status == NFS4ERR_NOTSUPP):
+ t.pass_warn("Server doesn't support GET_DIR_DELEGATION")
+
+ nf = res.resarray[-1].gddr_res_non_fatal4
+ if nf.gddrnf_status == GDD4_UNAVAIL:
+ t.pass_warn("Server reported that delegation on new dir was unavailable.")
+ elif nf.gddrnf_status != GDD4_OK:
+ t.fail("Server returned unknown non-fatal status code.")
+
deleg = res.resarray[-1].gddrnf_resok4.gddr_stateid
+ if NOTIFY4_GFLAG_EXTEND in notify_mask and \
+ nf.gddrnf_resok4.gddr_notification != mask_bm:
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ t.pass_warn("Server didn't offer the necessary directory notifications for this test")
+
return (sess1, fh, deleg)
def testDirDelegSimple(t, env):
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 06/26] server41tests: test remove triggers dir delegation recall
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (4 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 05/26] server41tests: pass_warn() when server doesn't support dir delegations Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 07/26] server41tests: test rename " Jeff Layton
` (20 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Get a dir delegation with no notification mask, create a file, then
remove it from a second client. Verify that the server issues a
CB_RECALL.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 36 +++++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 5f46c08316e3..b2afc7cccb01 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -3,7 +3,7 @@ from .st_open import open_claim4
from xdrdef.nfs4_const import *
from xdrdef.nfs4_pack import NFS4Unpacker
-from .environment import check, fail, create_obj, use_obj
+from .environment import check, fail, create_obj, use_obj, close_file, rename_obj
from xdrdef.nfs4_type import *
import nfs_ops
op = nfs_ops.NFS4ops()
@@ -148,3 +148,37 @@ def testDirDelegDuplicate(t, env):
ops = [ op.putfh(fh), op.delegreturn(deleg) ]
res = sess1.compound(ops)
check(res)
+
+def testDirDelegRemoveRecall(t, env):
+ """Verify remove triggers dir delegation recall
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG3
+ """
+ c = env.c1
+ recall = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env, [], recall)
+
+ # Create a file from sess1
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ # Remove the file from sess2 -- should trigger recall
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ remove_op = [ op.putfh(fh), op.remove(env.testname(t)) ]
+ slot = sess2.compound_async(remove_op)
+ completed = recall.wait(2)
+ env.sleep(.1)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 07/26] server41tests: test rename triggers dir delegation recall
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (5 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 06/26] server41tests: test remove triggers dir delegation recall Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 08/26] server41tests: test mkdir " Jeff Layton
` (19 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Get a dir delegation with no notification mask, create a file, then
rename it from a second client. Verify that the server issues a
CB_RECALL.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 37 ++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index b2afc7cccb01..b968035d0446 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -182,3 +182,40 @@ def testDirDelegRemoveRecall(t, env):
ops = [ op.putfh(fh), op.delegreturn(deleg) ]
res = sess1.compound(ops)
check(res)
+
+def testDirDelegRenameRecall(t, env):
+ """Verify rename triggers dir delegation recall
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG4
+ """
+ c = env.c1
+ recall = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env, [], recall)
+
+ # Create a file from sess1
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+
+ # Rename the file from sess2 -- should trigger recall
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ rename_op = [ op.putfh(fh), op.savefh(),
+ op.putfh(fh),
+ op.rename(env.testname(t), b"%s_2" % env.testname(t)) ]
+ slot = sess2.compound_async(rename_op)
+ completed = recall.wait(2)
+ env.sleep(.1)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
+
+ close_file(sess1, file_fh, stateid=open_stateid)
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 08/26] server41tests: test mkdir triggers dir delegation recall
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (6 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 07/26] server41tests: test rename " Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 09/26] server41tests: test link " Jeff Layton
` (18 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Get a dir delegation with no notification mask, then create a
subdirectory from a second client. Verify that the server issues
a CB_RECALL.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index b968035d0446..b27e68eea5f6 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -219,3 +219,26 @@ def testDirDelegRenameRecall(t, env):
check(res)
close_file(sess1, file_fh, stateid=open_stateid)
+
+def testDirDelegMkdirRecall(t, env):
+ """Verify mkdir triggers dir delegation recall
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG5
+ """
+ c = env.c1
+ recall = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env, [], recall)
+
+ # Create a subdirectory from sess2 -- should trigger recall
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ create_op = [ op.putfh(fh),
+ op.create(createtype4(NF4DIR), env.testname(t),
+ {FATTR4_MODE: 0o755}) ]
+ slot = sess2.compound_async(create_op)
+ completed = recall.wait(2)
+ env.sleep(.1)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 09/26] server41tests: test link triggers dir delegation recall
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (7 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 08/26] server41tests: test mkdir " Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 10/26] server41tests: test no notifications without GFLAG_EXTEND Jeff Layton
` (17 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Get a dir delegation with no notification mask, create a file, then
hard-link it to a new name from a second client. Verify that the
server issues a CB_RECALL.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index b27e68eea5f6..008e45d4cf64 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -242,3 +242,39 @@ def testDirDelegMkdirRecall(t, env):
ops = [ op.putfh(fh), op.delegreturn(deleg) ]
res = sess1.compound(ops)
check(res)
+
+def testDirDelegLinkRecall(t, env):
+ """Verify link triggers dir delegation recall
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG6
+ """
+ c = env.c1
+ recall = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env, [], recall)
+
+ # Create a file from sess1
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ # Link the file to a new name from sess2 -- should trigger recall
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ link_op = [ op.putfh(file_fh), op.savefh(),
+ op.putfh(fh),
+ op.link(b"%s_link" % env.testname(t)) ]
+ slot = sess2.compound_async(link_op)
+ completed = recall.wait(2)
+ env.sleep(.1)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 10/26] server41tests: test no notifications without GFLAG_EXTEND
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (8 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 09/26] server41tests: test link " Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 11/26] server41tests: test unrequested notification type triggers recall Jeff Layton
` (16 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Request a dir delegation with NOTIFY4_ADD_ENTRY but without
NOTIFY4_GFLAG_EXTEND. Verify the server issues a CB_RECALL
instead of CB_NOTIFY when a file is created.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 37 ++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 008e45d4cf64..4afcc40f515a 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -278,3 +278,40 @@ def testDirDelegLinkRecall(t, env):
ops = [ op.putfh(fh), op.delegreturn(deleg) ]
res = sess1.compound(ops)
check(res)
+
+def testDirDelegNoGflag(t, env):
+ """Verify recall instead of notification without NOTIFY4_GFLAG_EXTEND
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG7
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env, [NOTIFY4_ADD_ENTRY], cb)
+
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ slot = sess2.compound_async(open_op)
+ completed = cb.wait(2)
+ env.sleep(.1)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
+
+ # Reap the async open and close any file it created
+ res = sess2.listen(slot)
+ if res.status == NFS4_OK:
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess2, file_fh, stateid=open_stateid)
+
+ if cb.got_notify:
+ fail("Got CB_NOTIFY without GFLAG_EXTEND")
+ if not cb.got_recall:
+ fail("Expected CB_RECALL without GFLAG_EXTEND, but didn't get one")
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 11/26] server41tests: test unrequested notification type triggers recall
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (9 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 10/26] server41tests: test no notifications without GFLAG_EXTEND Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 12/26] server41tests: add a test for removal from dir with dir delegation Jeff Layton
` (15 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Request a dir delegation with only REMOVE notifications. Trigger
an ADD event from a second client. Verify the server issues a
CB_RECALL instead of CB_NOTIFY since ADD was not requested.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 39 ++++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 4afcc40f515a..8fe2ba54fc67 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -315,3 +315,42 @@ def testDirDelegNoGflag(t, env):
fail("Got CB_NOTIFY without GFLAG_EXTEND")
if not cb.got_recall:
fail("Expected CB_RECALL without GFLAG_EXTEND, but didn't get one")
+
+def testDirDelegFiltering(t, env):
+ """Verify unrequested notification type triggers recall
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG8
+ """
+ c = env.c1
+ cb = threading.Event()
+ # Only request REMOVE notifications
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_REMOVE_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ # Trigger an ADD event (not requested) from a second client
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ slot = sess2.compound_async(open_op)
+ completed = cb.wait(2)
+ env.sleep(.1)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
+
+ # Reap the async open and close any file it created
+ res = sess2.listen(slot)
+ if res.status == NFS4_OK:
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess2, file_fh, stateid=open_stateid)
+
+ if not cb.got_recall:
+ fail("Expected CB_RECALL for unrequested notification type")
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 12/26] server41tests: add a test for removal from dir with dir delegation
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (10 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 11/26] server41tests: test unrequested notification type triggers recall Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 13/26] server41tests: add a test for directory add notifications Jeff Layton
` (14 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Request a dir delegation with REMOVE_ENTRY notifications. Create a
file, then remove it from a second client. Verify the server sends
a CB_NOTIFY with the correct REMOVE event.
Also add full child and directory attribute bitmaps to the
GET_DIR_DELEGATION request for all CB_NOTIFY tests.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 65 ++++++++++++++++++++++++++++++++++--
1 file changed, 63 insertions(+), 2 deletions(-)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 8fe2ba54fc67..5b39f38a478c 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -68,8 +68,26 @@ def _getDirDeleg(t, env, notify_mask, cb):
mask_bm = nfs4lib.list2bitmap(notify_mask)
ops = [ op.putfh(fh), op.get_dir_delegation(False, nfs4lib.list2bitmap(notify_mask),
zerotime, zerotime,
- nfs4lib.list2bitmap([]),
- nfs4lib.list2bitmap([]))]
+ nfs4lib.list2bitmap([FATTR4_TYPE,
+ FATTR4_CHANGE,
+ FATTR4_SIZE,
+ FATTR4_FILEID,
+ FATTR4_FILEHANDLE,
+ FATTR4_MODE,
+ FATTR4_NUMLINKS,
+ FATTR4_RAWDEV,
+ FATTR4_SPACE_USED,
+ FATTR4_TIME_ACCESS,
+ FATTR4_TIME_METADATA,
+ FATTR4_TIME_MODIFY,
+ FATTR4_TIME_CREATE]),
+ nfs4lib.list2bitmap([FATTR4_CHANGE,
+ FATTR4_SIZE,
+ FATTR4_NUMLINKS,
+ FATTR4_SPACE_USED,
+ FATTR4_TIME_ACCESS,
+ FATTR4_TIME_METADATA,
+ FATTR4_TIME_MODIFY]))]
res = sess1.compound(ops)
check(res, [NFS4_OK, NFS4ERR_NOTSUPP])
if (res.status == NFS4ERR_NOTSUPP):
@@ -354,3 +372,46 @@ def testDirDelegFiltering(t, env):
if not cb.got_recall:
fail("Expected CB_RECALL for unrequested notification type")
+
+def testDirDelegRemove(t, env):
+ """Create a dir_deleg that accepts notification of REMOVE events
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG9
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_CHANGE_DIR_ATTRS,
+ NOTIFY4_REMOVE_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ remove_op = [ op.putfh(fh), op.remove(env.testname(t)) ]
+ res = sess2.compound(remove_op)
+ check(res)
+
+ completed = cb.wait(5)
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_REMOVE_ENTRY:
+ fail("Expected REMOVE notification, got %d" % evt_type)
+ if evt.nrm_old_entry.ne_file != env.testname(t):
+ fail("Wrong entry name in REMOVE notification")
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 13/26] server41tests: add a test for directory add notifications
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (11 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 12/26] server41tests: add a test for removal from dir with dir delegation Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 14/26] server41tests: add test for RENAME event notifications Jeff Layton
` (13 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Request a dir delegation with ADD_ENTRY notifications. Create a file
from a second client. Verify the server sends a CB_NOTIFY with the
correct ADD event.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 45 ++++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 5b39f38a478c..e906633a7972 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -415,3 +415,48 @@ def testDirDelegRemove(t, env):
fail("Expected REMOVE notification, got %d" % evt_type)
if evt.nrm_old_entry.ne_file != env.testname(t):
fail("Wrong entry name in REMOVE notification")
+
+def testDirDelegAdd(t, env):
+ """Create a dir_deleg that accepts notification of ADD events
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG10
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess2.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ close_file(sess2, file_fh, stateid=open_stateid)
+
+ remove_op = [ op.putfh(fh), op.remove(env.testname(t)) ]
+ res = sess2.compound(remove_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_ADD_ENTRY:
+ fail("Expected ADD notification, got %d" % evt_type)
+ if evt.nad_new_entry.ne_file != env.testname(t):
+ fail("Wrong entry name in ADD notification")
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 14/26] server41tests: add test for RENAME event notifications
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (12 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 13/26] server41tests: add a test for directory add notifications Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 15/26] server41tests: verify child attributes in ADD notification Jeff Layton
` (12 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Request a dir delegation with RENAME_ENTRY notifications. Create a
file, then rename it from a second client. Verify the server sends
a CB_NOTIFY with the correct RENAME event.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 47 ++++++++++++++++++++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index e906633a7972..00d74b2c7300 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -460,3 +460,50 @@ def testDirDelegAdd(t, env):
fail("Expected ADD notification, got %d" % evt_type)
if evt.nad_new_entry.ne_file != env.testname(t):
fail("Wrong entry name in ADD notification")
+
+def testDirDelegRename(t, env):
+ """Create a dir_deleg that accepts notification of RENAME events
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG11
+ """
+ c = env.c1
+ cb = threading.Event()
+
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_RENAME_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ topdir = c.homedir + [t.code.encode('utf8')]
+ oldpath = topdir + [env.testname(t)]
+ newpath = topdir + [b"%s_2" % env.testname(t)]
+ res = rename_obj(sess2, oldpath, newpath)
+ check(res)
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_RENAME_ENTRY:
+ fail("Expected RENAME notification, got %d" % evt_type)
+ if evt.nrn_old_entry.nrm_old_entry.ne_file != env.testname(t):
+ fail("Wrong old entry name in RENAME notification")
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 15/26] server41tests: verify child attributes in ADD notification
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (13 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 14/26] server41tests: add test for RENAME event notifications Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 16/26] server41tests: test CHANGE_DIR_ATTRS notification Jeff Layton
` (11 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Request a dir delegation with ADD_ENTRY notifications. Create a file
from a second client. Verify the ADD notification includes child
attributes with a correct size.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 51 ++++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 00d74b2c7300..a12fa13878e4 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -507,3 +507,54 @@ def testDirDelegRename(t, env):
fail("Expected RENAME notification, got %d" % evt_type)
if evt.nrn_old_entry.nrm_old_entry.ne_file != env.testname(t):
fail("Wrong old entry name in RENAME notification")
+
+def testDirDelegChildAttrs(t, env):
+ """Verify child attributes are present in ADD notification
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG12
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess2.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ close_file(sess2, file_fh, stateid=open_stateid)
+
+ remove_op = [ op.putfh(fh), op.remove(env.testname(t)) ]
+ res = sess2.compound(remove_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_ADD_ENTRY:
+ fail("Expected ADD notification, got %d" % evt_type)
+
+ attrs = evt.nad_new_entry.ne_attrs
+ if not any(attrs.attrmask):
+ fail("No child attributes in ADD notification")
+ attrs.attrmask = bitmap4_to_int(attrs.attrmask)
+ attr_dict = nfs4lib.fattr2dict(attrs)
+ if FATTR4_SIZE in attr_dict and attr_dict[FATTR4_SIZE] != 0:
+ fail("Expected size 0 for new file, got %d" % attr_dict[FATTR4_SIZE])
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 16/26] server41tests: test CHANGE_DIR_ATTRS notification
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (14 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 15/26] server41tests: verify child attributes in ADD notification Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 17/26] server41tests: test mkdir triggers ADD notification Jeff Layton
` (10 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Request a dir delegation with CHANGE_DIR_ATTRS and ADD_ENTRY
notifications. Create a file from a second client. Verify the
server sends a CHANGE_DIR_ATTRS notification with directory
attributes.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 54 ++++++++++++++++++++++++++++++++++++
1 file changed, 54 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index a12fa13878e4..752fec3df185 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -558,3 +558,57 @@ def testDirDelegChildAttrs(t, env):
attr_dict = nfs4lib.fattr2dict(attrs)
if FATTR4_SIZE in attr_dict and attr_dict[FATTR4_SIZE] != 0:
fail("Expected size 0 for new file, got %d" % attr_dict[FATTR4_SIZE])
+
+def testDirDelegDirAttrs(t, env):
+ """Verify CHANGE_DIR_ATTRS notification on directory change
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG13
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_CHANGE_DIR_ATTRS,
+ NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess2.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ close_file(sess2, file_fh, stateid=open_stateid)
+
+ remove_op = [ op.putfh(fh), op.remove(env.testname(t)) ]
+ res = sess2.compound(remove_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ # Look for a CHANGE_DIR_ATTRS event among the changes
+ found_dir_attrs = False
+ for change in cb.changes:
+ evt_type, evt = decode_notify_event(change)
+ if evt_type == NOTIFY4_CHANGE_DIR_ATTRS:
+ found_dir_attrs = True
+ attrs = evt.na_changed_entry.ne_attrs
+ if not any(attrs.attrmask):
+ fail("No directory attributes in CHANGE_DIR_ATTRS notification")
+ break
+
+ if not found_dir_attrs:
+ fail("No CHANGE_DIR_ATTRS notification found")
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 17/26] server41tests: test mkdir triggers ADD notification
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (15 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 16/26] server41tests: test CHANGE_DIR_ATTRS notification Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 18/26] server41tests: test DELEGRETURN stops notifications Jeff Layton
` (9 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Request a dir delegation with ADD_ENTRY notifications. Create a
subdirectory from a second client. Verify the server sends a
CB_NOTIFY with the correct ADD event.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 752fec3df185..ec4e2bc0e961 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -612,3 +612,36 @@ def testDirDelegDirAttrs(t, env):
if not found_dir_attrs:
fail("No CHANGE_DIR_ATTRS notification found")
+
+def testDirDelegMkdir(t, env):
+ """Verify mkdir triggers ADD notification
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG14
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ topdir = c.homedir + [t.code.encode('utf8')]
+ subdir = topdir + [env.testname(t)]
+ res = create_obj(sess2, subdir)
+ check(res)
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_ADD_ENTRY:
+ fail("Expected ADD notification, got %d" % evt_type)
+ if evt.nad_new_entry.ne_file != env.testname(t):
+ fail("Wrong directory name in ADD notification")
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 18/26] server41tests: test DELEGRETURN stops notifications
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (16 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 17/26] server41tests: test mkdir triggers ADD notification Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 19/26] server41tests: verify filehandle in ADD notification Jeff Layton
` (8 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Get a dir delegation with ADD_ENTRY notifications, immediately
return it with DELEGRETURN, then create a file from a second
client. Verify no callbacks are received after the delegation
was returned.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 42 ++++++++++++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index ec4e2bc0e961..c4778da61dcd 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -645,3 +645,45 @@ def testDirDelegMkdir(t, env):
fail("Expected ADD notification, got %d" % evt_type)
if evt.nad_new_entry.ne_file != env.testname(t):
fail("Wrong directory name in ADD notification")
+
+def testDirDelegReturnStopsNotify(t, env):
+ """Verify DELEGRETURN stops notifications
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG15
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ # Immediately return the delegation
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
+
+ # Now create a file from a second client
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess2.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+
+ # Wait briefly -- should NOT get any callback
+ completed = cb.wait(2)
+
+ close_file(sess2, file_fh, stateid=open_stateid)
+
+ remove_op = [ op.putfh(fh), op.remove(env.testname(t)) ]
+ res = sess2.compound(remove_op)
+ check(res)
+
+ if cb.got_notify or cb.got_recall:
+ fail("Received callback after DELEGRETURN")
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 19/26] server41tests: verify filehandle in ADD notification
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (17 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 18/26] server41tests: test DELEGRETURN stops notifications Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 20/26] server41tests: test cross-directory rename REMOVE notification Jeff Layton
` (7 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Request a dir delegation with ADD_ENTRY notifications. Create a file
from a second client, recording its filehandle via GETFH. Verify the
ADD notification includes a filehandle attribute that matches.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 51 ++++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index c4778da61dcd..5d7c1a8fbaca 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -687,3 +687,54 @@ def testDirDelegReturnStopsNotify(t, env):
if cb.got_notify or cb.got_recall:
fail("Received callback after DELEGRETURN")
+
+def testDirDelegFilehandle(t, env):
+ """Verify filehandle in ADD notification matches GETFH result
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG16
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess2.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ close_file(sess2, file_fh, stateid=open_stateid)
+
+ remove_op = [ op.putfh(fh), op.remove(env.testname(t)) ]
+ res = sess2.compound(remove_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_ADD_ENTRY:
+ fail("Expected ADD notification, got %d" % evt_type)
+
+ attrs = evt.nad_new_entry.ne_attrs
+ attrs.attrmask = bitmap4_to_int(attrs.attrmask)
+ attr_dict = nfs4lib.fattr2dict(attrs)
+ if FATTR4_FILEHANDLE not in attr_dict:
+ fail("No filehandle in ADD notification attributes")
+ if attr_dict[FATTR4_FILEHANDLE] != file_fh:
+ fail("Filehandle in notification doesn't match GETFH result")
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 20/26] server41tests: test cross-directory rename REMOVE notification
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (18 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 19/26] server41tests: verify filehandle in ADD notification Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 21/26] server41tests: test cross-directory rename ADD notification on target Jeff Layton
` (6 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Verify that a cross-directory rename generates a NOTIFY4_REMOVE_ENTRY
notification on the source directory's delegation, rather than a
NOTIFY4_RENAME_ENTRY (which is only for within-directory renames).
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 57 ++++++++++++++++++++++++++++++++++++
1 file changed, 57 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 5d7c1a8fbaca..0751cf1ec47e 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -738,3 +738,60 @@ def testDirDelegFilehandle(t, env):
fail("No filehandle in ADD notification attributes")
if attr_dict[FATTR4_FILEHANDLE] != file_fh:
fail("Filehandle in notification doesn't match GETFH result")
+
+def testDirDelegCrossRename(t, env):
+ """Verify cross-directory rename generates REMOVE notification on source
+
+ Per RFC 8881bis Section 27.4.6, a rename across directories sends
+ a REMOVE notification to the source directory and an ADD notification
+ to the target directory, rather than a RENAME notification.
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG17
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_REMOVE_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ # Create a file in the delegated directory
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ # Create a target directory (sibling of the delegated directory)
+ topdir = c.homedir + [t.code.encode('utf8')]
+ targetdir = c.homedir + [b"%s_target" % t.code.encode('utf8')]
+ res = create_obj(sess1, targetdir)
+ check(res)
+
+ # Rename the file from the delegated dir to the target dir from sess2
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ oldpath = topdir + [env.testname(t)]
+ newpath = targetdir + [env.testname(t)]
+ res = rename_obj(sess2, oldpath, newpath)
+ check(res)
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_REMOVE_ENTRY:
+ fail("Expected REMOVE notification for cross-dir rename, got %d" % evt_type)
+ if evt.nrm_old_entry.ne_file != env.testname(t):
+ fail("Wrong entry name in REMOVE notification")
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 21/26] server41tests: test cross-directory rename ADD notification on target
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (19 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 20/26] server41tests: test cross-directory rename REMOVE notification Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 22/26] server41tests: test link triggers ADD notification Jeff Layton
` (5 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Verify that a cross-directory rename generates a NOTIFY4_ADD_ENTRY
notification on the target directory's delegation.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 60 ++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 0751cf1ec47e..43e9ce4cb4d2 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -795,3 +795,63 @@ def testDirDelegCrossRename(t, env):
fail("Expected REMOVE notification for cross-dir rename, got %d" % evt_type)
if evt.nrm_old_entry.ne_file != env.testname(t):
fail("Wrong entry name in REMOVE notification")
+
+def testDirDelegCrossRenameTarget(t, env):
+ """Verify cross-directory rename generates ADD notification on target
+
+ Per RFC 8881bis Section 27.4.6, a rename across directories sends
+ a REMOVE notification to the source directory and an ADD notification
+ to the target directory.
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG18
+ """
+ c = env.c1
+ cb = threading.Event()
+
+ # Create a source directory (not delegated) and a file in it
+ srcdir = c.homedir + [b"%s_src" % t.code.encode('utf8')]
+ sess1 = c.new_client_session(b"%s_1" % env.testname(t))
+ res = create_obj(sess1, srcdir)
+ check(res)
+
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ src_fh = res.resarray[-1].object
+ open_op = [ op.putfh(src_fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ # Get a dir delegation on the target directory
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ # Rename the file into the delegated target directory from sess2
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ topdir = c.homedir + [t.code.encode('utf8')]
+ oldpath = srcdir + [env.testname(t)]
+ newpath = topdir + [env.testname(t)]
+ res = rename_obj(sess2, oldpath, newpath)
+ check(res)
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_ADD_ENTRY:
+ fail("Expected ADD notification for cross-dir rename target, got %d" % evt_type)
+ if evt.nad_new_entry.ne_file != env.testname(t):
+ fail("Wrong entry name in ADD notification")
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 22/26] server41tests: test link triggers ADD notification
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (20 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 21/26] server41tests: test cross-directory rename ADD notification on target Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 23/26] server41tests: test same-client changes don't trigger notifications Jeff Layton
` (4 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Verify that creating a hard link triggers a NOTIFY4_ADD_ENTRY
notification on the directory's delegation.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 56 ++++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 43e9ce4cb4d2..973ac5b8f934 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -855,3 +855,59 @@ def testDirDelegCrossRenameTarget(t, env):
fail("Expected ADD notification for cross-dir rename target, got %d" % evt_type)
if evt.nad_new_entry.ne_file != env.testname(t):
fail("Wrong entry name in ADD notification")
+
+def testDirDelegLinkNotify(t, env):
+ """Verify hard link triggers ADD notification
+
+ Per RFC 8881bis Section 27.4.4, the server sends an ADD notification
+ when a hard link is being created to an existing file.
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG19
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ # Create a file in the delegated directory from sess1
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ # Clear the notification state from the create
+ cb.clear()
+ cb.got_notify = False
+
+ # Link the file to a new name from sess2
+ link_name = b"%s_link" % env.testname(t)
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ link_op = [ op.putfh(file_fh), op.savefh(),
+ op.putfh(fh),
+ op.link(link_name) ]
+ res = sess2.compound(link_op)
+ check(res)
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_ADD_ENTRY:
+ fail("Expected ADD notification for link, got %d" % evt_type)
+ if evt.nad_new_entry.ne_file != link_name:
+ fail("Wrong entry name in ADD notification")
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 23/26] server41tests: test same-client changes don't trigger notifications
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (21 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 22/26] server41tests: test link triggers ADD notification Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 24/26] server41tests: test cross-directory rename-over nad_old_entry Jeff Layton
` (3 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Verify that changes made by the delegation-holding session itself
do not trigger CB_NOTIFY or CB_RECALL callbacks.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 41 ++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 973ac5b8f934..8da51bb53cd6 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -911,3 +911,44 @@ def testDirDelegLinkNotify(t, env):
fail("Expected ADD notification for link, got %d" % evt_type)
if evt.nad_new_entry.ne_file != link_name:
fail("Wrong entry name in ADD notification")
+
+def testDirDelegSameClientNoNotify(t, env):
+ """Verify delegation holder's own changes don't trigger notifications
+
+ Per RFC 8881bis Section 16.2.11.2, order-unaware clients should not
+ receive notifications for changes they made themselves.
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG20
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ # Create a file from the delegation-holding session itself
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+
+ # Wait briefly -- should NOT get any callback
+ completed = cb.wait(2)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
+
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ if cb.got_notify:
+ fail("Got CB_NOTIFY for delegation holder's own change")
+ if cb.got_recall:
+ fail("Got CB_RECALL for delegation holder's own change")
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 24/26] server41tests: test cross-directory rename-over nad_old_entry
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (22 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 23/26] server41tests: test same-client changes don't trigger notifications Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 25/26] server41tests: test within-directory " Jeff Layton
` (2 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Test that when a cross-directory rename overwrites an existing file in
a delegated directory, the server reports the overwritten entry via
nad_old_entry in the NOTIFY4_ADD_ENTRY notification rather than
generating a separate NOTIFY4_REMOVE_ENTRY notification.
Per RFC 8881 Section 18.26.4, when the removal is done atomically with
the rename, a separate NOTIFY4_REMOVE_ENTRY notification will not be
generated. Instead, the deletion of the file will be reported as part
of the NOTIFY4_ADD_ENTRY notification via nad_old_entry.
Also fix _getDirDeleg to initialize cb.changes as a list and use
extend() to accumulate notifications, so tests that receive multiple
CB_NOTIFY callbacks can see all notifications.
---
nfs4.1/server41tests/st_dir_deleg.py | 100 ++++++++++++++++++++++++++++++++++-
1 file changed, 99 insertions(+), 1 deletion(-)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 8da51bb53cd6..36873675cedd 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -44,7 +44,7 @@ def _getDirDeleg(t, env, notify_mask, cb):
def notify_pre_hook(arg, env):
cb.stateid = arg.cna_stateid
cb.fh = arg.cna_fh
- cb.changes = arg.cna_changes
+ cb.changes.extend(arg.cna_changes)
cb.got_notify = True
env.notify = cb.set # This is called after compound sent to queue
def notify_post_hook(arg, env, res):
@@ -52,6 +52,7 @@ def _getDirDeleg(t, env, notify_mask, cb):
cb.got_recall = False
cb.got_notify = False
+ cb.changes = []
c = env.c1
sess1 = c.new_client_session(b"%s_1" % env.testname(t))
@@ -952,3 +953,100 @@ def testDirDelegSameClientNoNotify(t, env):
fail("Got CB_NOTIFY for delegation holder's own change")
if cb.got_recall:
fail("Got CB_RECALL for delegation holder's own change")
+
+def testDirDelegCrossRenameOver(t, env):
+ """Verify cross-directory rename-over reports overwritten entry in nad_old_entry
+
+ Per RFC 8881 Section 18.26.4, when a cross-directory rename
+ overwrites an existing file in the target directory, a
+ NOTIFY4_ADD_ENTRY is generated. When the removal is done
+ atomically with the rename, a separate NOTIFY4_REMOVE_ENTRY
+ notification will not be generated. Instead, the deletion of the
+ file will be reported as part of the NOTIFY4_ADD_ENTRY notification
+ via nad_old_entry.
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG21
+ """
+ c = env.c1
+ cb = threading.Event()
+
+ # Get a dir delegation on the target directory
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_REMOVE_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ topdir = c.homedir + [t.code.encode('utf8')]
+
+ # Create a file in the delegated directory (will be renamed over)
+ victim_name = b"%s_victim" % env.testname(t)
+ claim = open_claim4(CLAIM_NULL, victim_name)
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ # Create a source directory and a file in it
+ srcdir = c.homedir + [b"%s_src" % t.code.encode('utf8')]
+ res = create_obj(sess1, srcdir)
+ check(res)
+ src_fh = res.resarray[-1].object
+
+ src_name = env.testname(t)
+ claim2 = open_claim4(CLAIM_NULL, src_name)
+ owner2 = open_owner4(0, b"owner2")
+ how2 = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(src_fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner2, how2, claim2), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid2 = res.resarray[-2].stateid
+ file_fh2 = res.resarray[-1].object
+ close_file(sess1, file_fh2, stateid=open_stateid2)
+
+ # Clear notification state from creates above
+ cb.clear()
+ cb.got_notify = False
+
+ # Rename the source file over the victim in the delegated dir from sess2
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ oldpath = srcdir + [src_name]
+ newpath = topdir + [victim_name]
+ res = rename_obj(sess2, oldpath, newpath)
+ check(res)
+
+ completed = cb.wait(2)
+ if completed:
+ cb.clear()
+ cb.wait(1)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ # Look for ADD notification with nad_old_entry for the overwritten file
+ got_add = False
+ for change in cb.changes:
+ evt_type, evt = decode_notify_event(change)
+ if evt_type == NOTIFY4_ADD_ENTRY:
+ got_add = True
+ if evt.nad_new_entry.ne_file != victim_name:
+ fail("Wrong entry name in ADD notification")
+ if len(evt.nad_old_entry) != 1:
+ fail("Expected nad_old_entry to contain the overwritten entry")
+ if evt.nad_old_entry[0].nrm_old_entry.ne_file != victim_name:
+ fail("Wrong overwritten entry name in nad_old_entry")
+
+ if not got_add:
+ fail("Missing ADD notification for cross-dir rename-over")
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 25/26] server41tests: test within-directory rename-over nad_old_entry
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (23 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 24/26] server41tests: test cross-directory rename-over nad_old_entry Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-19 19:22 ` [PATCH pynfs v3 26/26] nfs4.1: move a lot of log/log_cb.info messages to log/log_cb.debug Jeff Layton
2026-06-23 21:33 ` [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Calum Mackay
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Test that when a within-directory rename overwrites an existing entry,
the server populates nrn_new_entry.nad_old_entry in the
NOTIFY4_RENAME_ENTRY notification with the overwritten entry's info.
---
nfs4.1/server41tests/st_dir_deleg.py | 65 ++++++++++++++++++++++++++++++++++++
1 file changed, 65 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 36873675cedd..7561d57cbdf3 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -1050,3 +1050,68 @@ def testDirDelegCrossRenameOver(t, env):
if not got_add:
fail("Missing ADD notification for cross-dir rename-over")
+
+def testDirDelegRenameOver(t, env):
+ """Verify within-directory rename-over populates nad_old_entry
+
+ Per RFC 8881bis Section 27.4.6, when a within-directory rename
+ overwrites an existing entry, the overwritten entry's info is
+ reported in nrn_new_entry.nad_old_entry.
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG22
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_RENAME_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ # Create two files in the delegated directory from sess1
+ src_name = env.testname(t)
+ victim_name = b"%s_victim" % env.testname(t)
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+
+ for name in [src_name, victim_name]:
+ claim = open_claim4(CLAIM_NULL, name)
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ # Rename src over victim from sess2
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ rename_op = [ op.putfh(fh), op.savefh(),
+ op.putfh(fh),
+ op.rename(src_name, victim_name) ]
+ res = sess2.compound(rename_op)
+ check(res)
+
+ completed = cb.wait(2)
+ if completed:
+ cb.clear()
+ cb.wait(1)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_RENAME_ENTRY:
+ fail("Expected RENAME notification, got %d" % evt_type)
+ if evt.nrn_old_entry.nrm_old_entry.ne_file != src_name:
+ fail("Wrong old entry name in RENAME notification")
+ if evt.nrn_new_entry.nad_new_entry.ne_file != victim_name:
+ fail("Wrong new entry name in RENAME notification")
+ if len(evt.nrn_new_entry.nad_old_entry) != 1:
+ fail("Expected nad_old_entry to contain the overwritten entry")
+ if evt.nrn_new_entry.nad_old_entry[0].nrm_old_entry.ne_file != victim_name:
+ fail("Wrong overwritten entry name in nad_old_entry")
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v3 26/26] nfs4.1: move a lot of log/log_cb.info messages to log/log_cb.debug
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (24 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 25/26] server41tests: test within-directory " Jeff Layton
@ 2026-06-19 19:22 ` Jeff Layton
2026-06-23 21:33 ` [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Calum Mackay
26 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-06-19 19:22 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Scott Mayhew, linux-nfs,
Jeff Layton
Most of these are note terribly useful in the normal case. Change them
from info level to debug.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/nfs4client.py | 38 +++++++++++++++++------------------
nfs4.1/server41tests/environment.py | 4 ++--
nfs4.1/server41tests/st_delegation.py | 3 ---
3 files changed, 21 insertions(+), 24 deletions(-)
diff --git a/nfs4.1/nfs4client.py b/nfs4.1/nfs4client.py
index dbe7af761f16..79df26ea1cda 100644
--- a/nfs4.1/nfs4client.py
+++ b/nfs4.1/nfs4client.py
@@ -83,7 +83,7 @@ class NFS4Client(rpc.Client, rpc.Server):
p = packer(check_enum=checks, check_array=checks)
c4 = COMPOUND4args(tag, version, ops)
if SHOW_TRAFFIC:
- log_cb.info("compound args = %r" % (c4,))
+ log_cb.debug("compound args = %r" % (c4,))
p.pack_COMPOUND4args(c4)
return self.send_call(pipe, 1, p.get_buffer(), credinfo)
@@ -93,7 +93,7 @@ class NFS4Client(rpc.Client, rpc.Server):
pipe = kwargs.get("pipe", None)
res = self.listen(xid, pipe=pipe)
if SHOW_TRAFFIC:
- log_cb.info("compound result = %r" % (res,))
+ log_cb.debug("compound result = %r" % (res,))
if self.summary:
self.summary.show_op('call v4.1 %s:%s' % self.server_address,
[ nfs_opnum4[a.argop].lower()[3:] for a in args[0] ],
@@ -112,8 +112,8 @@ class NFS4Client(rpc.Client, rpc.Server):
def handle_0(self, data, cred):
"""NULL procedure"""
allow_null_data = True
- log_cb.info("*" * 20)
- log_cb.info("Handling CB_NULL")
+ log_cb.debug("*" * 20)
+ log_cb.debug("Handling CB_NULL")
if data and not allow_null_data:
return rpc.GARBAGE_ARGS, None
else:
@@ -121,16 +121,16 @@ class NFS4Client(rpc.Client, rpc.Server):
def handle_1(self, data, cred):
# STUB
- log_cb.info("*" * 20)
- log_cb.info("Handling CB_COMPOUND")
+ log_cb.debug("*" * 20)
+ log_cb.debug("Handling CB_COMPOUND")
p = nfs4lib.FancyNFS4Packer()
res = CB_COMPOUND4res(NFS4ERR_BACK_CHAN_BUSY, "STUB CB_REPLY", [])
p.pack_CB_COMPOUND4res(res)
return rpc.SUCCESS, p.get_buffer()
def handle_1(self, data, cred):
- log_cb.info("*" * 20)
- log_cb.info("Handling COMPOUND")
+ log_cb.debug("*" * 20)
+ log_cb.debug("Handling COMPOUND")
# data is an XDR packed string. Unpack it.
unpacker = nfs4lib.FancyNFS4Unpacker(data)
try:
@@ -146,7 +146,7 @@ class NFS4Client(rpc.Client, rpc.Server):
args.req_size = len(data)
# Handle the request
env = self.op_cb_compound(args, cred)
- log_cb.info(repr(env.results.reply.results))
+ log_cb.debug(repr(env.results.reply.results))
# Pack the results back into an XDR string
p = nfs4lib.FancyNFS4Packer()
p.pack_CB_COMPOUND4res(CB_COMPOUND4res(env.results.reply.status,
@@ -162,9 +162,9 @@ class NFS4Client(rpc.Client, rpc.Server):
env.cache.data = p.get_buffer()
env.cache.valid.set()
except NFS4Replay as e:
- log_cb.info("Replay...waiting for valid data")
+ log_cb.debug("Replay...waiting for valid data")
e.cache.valid.wait()
- log_cb.info("Replay...sending data")
+ log_cb.debug("Replay...sending data")
data = e.cache.data
return rpc.SUCCESS, data, getattr(env, "notify", None)
@@ -190,7 +190,7 @@ class NFS4Client(rpc.Client, rpc.Server):
status = NFS4_OK
for arg in args.argarray:
opname = nfs_cb_opnum4.get(arg.argop, 'op_cb_illegal')
- log_cb.info("*** %s (%d) ***" % (opname, arg.argop))
+ log_cb.debug("*** %s (%d) ***" % (opname, arg.argop))
env.index += 1
# Look for function self.op_<name>
funct = getattr(self, opname.lower(), None)
@@ -220,7 +220,7 @@ class NFS4Client(rpc.Client, rpc.Server):
status = result.status
if status != NFS4_OK:
break
- log_cb.info("Replying. Status %s (%d)" % (nfsstat4[status], status))
+ log_cb.debug("Replying. Status %s (%d)" % (nfsstat4[status], status))
return env
def prehook(self, arg, env):
@@ -248,7 +248,7 @@ class NFS4Client(rpc.Client, rpc.Server):
return funct(arg, env, res)
def op_cb_sequence(self, arg, env):
- log_cb.info("In CB_SEQUENCE")
+ log_cb.debug("In CB_SEQUENCE")
if env.index != 0:
return encode_status(NFS4ERR_SEQUENCE_POS)
session = self.sessions.get(arg.csa_sessionid, None)
@@ -272,31 +272,31 @@ class NFS4Client(rpc.Client, rpc.Server):
return encode_status(NFS4_OK, res)
def op_cb_getattr(self, arg, env):
- log_cb.info("In CB_GETATTR")
+ log_cb.debug("In CB_GETATTR")
self.prehook(arg, env)
res = self.posthook(arg, env, res=CB_GETATTR4resok())
return encode_status(NFS4_OK, res)
def op_cb_recall(self, arg, env):
- log_cb.info("In CB_RECALL")
+ log_cb.debug("In CB_RECALL")
self.prehook(arg, env)
res = self.posthook(arg, env, res=NFS4_OK)
return encode_status(res)
def op_cb_notify(self, arg, env):
- log_cb.info("In CB_NOTIFY")
+ log_cb.debug("In CB_NOTIFY")
self.prehook(arg, env)
res = self.posthook(arg, env, res=NFS4_OK)
return encode_status(res)
def op_cb_notify_lock(self, arg, env):
- log_cb.info("In CB_NOTIFY_LOCK")
+ log_cb.debug("In CB_NOTIFY_LOCK")
self.prehook(arg, env)
res = self.posthook(arg, env, res=NFS4_OK)
return encode_status(res)
def op_cb_layoutrecall(self, arg, env):
- log_cb.info("In CB_LAYOUTRECALL")
+ log_cb.debug("In CB_LAYOUTRECALL")
self.prehook(arg, env)
res = self.posthook(arg, env, res=NFS4_OK)
if res is not NFS4_OK:
diff --git a/nfs4.1/server41tests/environment.py b/nfs4.1/server41tests/environment.py
index f5b1fea4a64c..c14b12cbfca5 100644
--- a/nfs4.1/server41tests/environment.py
+++ b/nfs4.1/server41tests/environment.py
@@ -443,7 +443,7 @@ def do_readdir(sess, file, cookie=0, cookieverf=b'', attrs=0,
# Since we may not get whole directory listing in one readdir request,
# loop until we do. For each request result, create a flat list
# with <entry4> objects.
- log.info("Called do_readdir()")
+ log.debug("Called do_readdir()")
entries = []
baseops = use_obj(file)
while True:
@@ -458,7 +458,7 @@ def do_readdir(sess, file, cookie=0, cookieverf=b'', attrs=0,
break
cookie = entries[-1].cookie
cookieverf = res.resarray[-1].cookieverf
- log.info("do_readdir() = %r" % entries)
+ log.debug("do_readdir() = %r" % entries)
return entries
def do_getattrdict(sess, file, attrlist):
diff --git a/nfs4.1/server41tests/st_delegation.py b/nfs4.1/server41tests/st_delegation.py
index 41095b98a7a4..a67a3ba9e73f 100644
--- a/nfs4.1/server41tests/st_delegation.py
+++ b/nfs4.1/server41tests/st_delegation.py
@@ -266,10 +266,8 @@ def testServerSelfConflict3(t, env):
fh, deleg = __create_file_with_deleg(sess1, env.testname(t),
OPEN4_SHARE_ACCESS_READ | OPEN4_SHARE_ACCESS_WANT_READ_DELEG)
- print("__create_file_with_deleg: ", fh, deleg)
delegstateid = deleg.read.stateid
res = open_file(sess1, env.testname(t), access = OPEN4_SHARE_ACCESS_WRITE)
- print("open_file res: ", res)
check(res)
# XXX: cut-n-paste from _testDeleg; make helper instead:
@@ -319,7 +317,6 @@ def _testCbGetattr(t, env, change=0, size=0):
openmask |= 1<<OPEN_ARGS_SHARE_ACCESS_WANT_DELEG_TIMESTAMPS
fh, deleg = __create_file_with_deleg(sess1, env.testname(t), openmask)
- print("__create_file_with_deleg: ", fh, deleg)
attrs1 = do_getattrdict(sess1, fh, [FATTR4_CHANGE, FATTR4_SIZE,
FATTR4_TIME_ACCESS, FATTR4_TIME_MODIFY])
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases
2026-06-19 19:22 [PATCH pynfs v3 00/26] nfs4.1: add some directory delegation testcases Jeff Layton
` (25 preceding siblings ...)
2026-06-19 19:22 ` [PATCH pynfs v3 26/26] nfs4.1: move a lot of log/log_cb.info messages to log/log_cb.debug Jeff Layton
@ 2026-06-23 21:33 ` Calum Mackay
26 siblings, 0 replies; 28+ messages in thread
From: Calum Mackay @ 2026-06-23 21:33 UTC (permalink / raw)
To: Jeff Layton
Cc: Calum Mackay, Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo,
Tom Talpey, Trond Myklebust, Anna Schumaker, Scott Mayhew,
linux-nfs
Thanks very much Jeff,
I have a bit of a backlog, again, but will get to these asap.
cheers,
c.
On 19/06/2026 8:22 pm, Jeff Layton wrote:
> Long delay since v2, but the CB_NOTIFY patches only recently got merged
> into Chuck's nfsd-testing branch. They're currently slated to make v7.3.
> This version of the series fixes some potential state leaks that Scott
> pointed out, and adds a patch to make the output a bit less chatty with
> normal settings.
>
> Original cover letter follows:
>
> ---------------------8<-------------------
>
> This patchset adds some new testcases for directory delegations.
>
> DIRDELEG1-7 should pass on current mainline kernels, with recall-only
> support. The rest require CB_NOTIFY support. If the server doesn't
> offer notifications, then the tests pass_warn (so there should be no
> failures in those cases):
>
> https://lore.kernel.org/linux-nfs/20260416-dir-deleg-v2-0-851426a550f6@kernel.org/
>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
>
> ---
> Changes in v3:
> - Ensure we clean up state after tests
> - Demote some extra-chatty messages to debug level
> - Link to v2: https://lore.kernel.org/r/20260416-dir-deleg-v2-0-fad510db5941@kernel.org
>
> Changes in v2:
> - Added more tests for CB_NOTIFY behavior
> - Link to v1: https://lore.kernel.org/r/20260407-dir-deleg-v1-0-54c998eab72b@kernel.org
>
> ---
> Jeff Layton (26):
> nfs4.1: add proposed NOTIFY4_GFLAG_EXTEND flag
> nfs4.1: add a getfh() to the end of create_obj() compound
> server41tests: add a basic GET_DIR_DELEGATION test
> server41tests: add a test for duplicate GET_DIR_DELEGATION requests
> server41tests: pass_warn() when server doesn't support dir delegations
> server41tests: test remove triggers dir delegation recall
> server41tests: test rename triggers dir delegation recall
> server41tests: test mkdir triggers dir delegation recall
> server41tests: test link triggers dir delegation recall
> server41tests: test no notifications without GFLAG_EXTEND
> server41tests: test unrequested notification type triggers recall
> server41tests: add a test for removal from dir with dir delegation
> server41tests: add a test for directory add notifications
> server41tests: add test for RENAME event notifications
> server41tests: verify child attributes in ADD notification
> server41tests: test CHANGE_DIR_ATTRS notification
> server41tests: test mkdir triggers ADD notification
> server41tests: test DELEGRETURN stops notifications
> server41tests: verify filehandle in ADD notification
> server41tests: test cross-directory rename REMOVE notification
> server41tests: test cross-directory rename ADD notification on target
> server41tests: test link triggers ADD notification
> server41tests: test same-client changes don't trigger notifications
> server41tests: test cross-directory rename-over nad_old_entry
> server41tests: test within-directory rename-over nad_old_entry
> nfs4.1: move a lot of log/log_cb.info messages to log/log_cb.debug
>
> nfs4.1/nfs4client.py | 42 +-
> nfs4.1/server41tests/__init__.py | 1 +
> nfs4.1/server41tests/environment.py | 6 +-
> nfs4.1/server41tests/st_delegation.py | 3 -
> nfs4.1/server41tests/st_dir_deleg.py | 1117 +++++++++++++++++++++++++++++++++
> nfs4.1/xdrdef/nfs4.x | 3 +-
> 6 files changed, 1147 insertions(+), 25 deletions(-)
> ---
> base-commit: cd4701827a8261fedbfb4c6e39029fb9671321a6
> change-id: 20260331-dir-deleg-a1b3475f8385
>
> Best regards,
^ permalink raw reply [flat|nested] 28+ messages in thread