From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AD4B1145355 for ; Sat, 4 Apr 2026 00:55:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775264122; cv=none; b=X1WchqjGtNdVA/3vmRjeGEWAPAv6GEhlS6TPcXnAmTYIS4c+8iNiSMhmvZ0FUf/JlI/n+hVDPcszPxlHOTgJr8HltG1Oo8lMpm5PzR9BDm3m8YVz7Q5L0v1d1mGXMIxjuHXowym23deoJXn4PECrptxJt8BY3KRZKJxc5Dyh5EE= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775264122; c=relaxed/simple; bh=teXOa1Ke+p9edw5OqzabMvTZzrenD3ZcfSh6cTlFwX8=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=fpTR6SzBF5jCgExfmyitaMVXnbpcQD7McT8fNkUA/edCmgOqDgIIAQdZCaMfD5myTDB7DYCzhBYHfgawO3nsauWjl2by19aEV12WUdwednEUeifBiSizzywJy2F+n+KCFAWOiRS/w1sQzPbFEG68tUJ0PivcKlo4l+jEWOuez5c= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=JxioZUq3; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="JxioZUq3" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1775264119; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=wqcgOPRcZnr7Q6ElhQQOn4OwWqU7m4Q8lT5w0wGM4SM=; b=JxioZUq3TBomxlyoBPgN0+ZogN/Qr8o2K68TPjMiybsxOBiP12bjJxOelkFn87Lz6pkDKL 00OgA58hIBCcmxUzSgSy75M5Cm5nGrmITZOm+pYUamSB7p6eXQr4aRrhQxWSlA/gNBzY/Q PZ1Kh86DPnUtbOiGwwpRWZcstzqa6RQ= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-676-dWrbHH7wMm-Qmp_APsON-w-1; Fri, 03 Apr 2026 20:55:16 -0400 X-MC-Unique: dWrbHH7wMm-Qmp_APsON-w-1 X-Mimecast-MFC-AGG-ID: dWrbHH7wMm-Qmp_APsON-w_1775264115 Received: from mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.111]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 9CD831956046; Sat, 4 Apr 2026 00:55:15 +0000 (UTC) Received: from aion.redhat.com (unknown [10.22.88.38]) by mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 160121800361; Sat, 4 Apr 2026 00:55:15 +0000 (UTC) Received: by aion.redhat.com (Postfix, from userid 1000) id C9559749EBA; Fri, 03 Apr 2026 20:55:13 -0400 (EDT) Date: Fri, 3 Apr 2026 20:55:13 -0400 From: Scott Mayhew 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 Message-ID: References: <20260404003050.1560149-1-smayhew@redhat.com> <20260404003050.1560149-6-smayhew@redhat.com> Precedence: bulk X-Mailing-List: linux-nfs@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20260404003050.1560149-6-smayhew@redhat.com> X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.111 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 > --- > 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< + > + 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< + 1< + 1< + 1< + > + 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< + 1< + 1< + 1< + 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 > >