Skip to content

Commit d0e99ac

Browse files
committed
test: 💍 add LOCK tests
1 parent 30ce6f5 commit d0e99ac

File tree

9 files changed

+2123
-4
lines changed

9 files changed

+2123
-4
lines changed

src/nfs/v4/server/__demos__/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ PORT=8777 npx ts-node src/nfs/v4/server/__demos__/tcp-server.ts
3737
Then mount an NFSv4 share from another terminal or machine:
3838

3939
```bash
40-
mount -t nfs -o vers=4,nfsvers=4,port=8777,mountport=8777,proto=tcp,sec=none 127.0.0.1:/export ~/mnt/test
40+
mount -t nfs -o vers=4,nfsvers=4,port=8777,mountport=8777,proto=tcp,sec=none,noowners 127.0.0.1:/export ~/mnt/test
4141
```
4242

4343
Unmount with:

src/nfs/v4/server/operations/node/Nfsv4OperationsNode.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -973,7 +973,7 @@ export class Nfsv4OperationsNode implements Nfsv4Operations {
973973
if (this.hasConflictingLock(currentPath, locktype, offset, length, existingLockOwnerKey)) {
974974
const conflictOwner = new struct.Nfsv4LockOwner(BigInt(0), new Uint8Array());
975975
const denied = new msg.Nfsv4LockResDenied(offset, length, locktype, conflictOwner);
976-
return new msg.Nfsv4LockResponse(Nfsv4Stat.NFS4ERR_LOCKED, undefined, denied);
976+
return new msg.Nfsv4LockResponse(Nfsv4Stat.NFS4ERR_DENIED, undefined, denied);
977977
}
978978
const lockStateid = this.getOrCreateLockStateid(existingLockOwnerKey, currentPath);
979979
const stateid = lockStateid.incrementAndGetStateid();
@@ -1031,7 +1031,7 @@ export class Nfsv4OperationsNode implements Nfsv4Operations {
10311031
if (this.hasConflictingLock(currentPath, locktype, offset, length, lockOwnerKey)) {
10321032
const conflictOwner = new struct.Nfsv4LockOwner(BigInt(0), new Uint8Array());
10331033
const denied = new msg.Nfsv4LockResDenied(offset, length, locktype, conflictOwner);
1034-
return new msg.Nfsv4LockResponse(Nfsv4Stat.NFS4ERR_LOCKED, undefined, denied);
1034+
return new msg.Nfsv4LockResponse(Nfsv4Stat.NFS4ERR_DENIED, undefined, denied);
10351035
}
10361036
let lockOwnerState = this.lockOwners.get(lockOwnerKey);
10371037
if (!lockOwnerState) {
@@ -1073,7 +1073,7 @@ export class Nfsv4OperationsNode implements Nfsv4Operations {
10731073
if (this.hasConflictingLock(currentPath, locktype, offset, length, ownerKey)) {
10741074
const conflictOwner = new struct.Nfsv4LockOwner(BigInt(0), new Uint8Array());
10751075
const denied = new msg.Nfsv4LocktResDenied(offset, length, locktype, conflictOwner);
1076-
return new msg.Nfsv4LocktResponse(Nfsv4Stat.NFS4ERR_LOCKED, denied);
1076+
return new msg.Nfsv4LocktResponse(Nfsv4Stat.NFS4ERR_DENIED, denied);
10771077
}
10781078
return new msg.Nfsv4LocktResponse(Nfsv4Stat.NFS4_OK);
10791079
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import {setupNfsClientServerTestbed} from '../../../__tests__/setup';
2+
import * as msg from '../../../../messages';
3+
import {Nfsv4Stat, Nfsv4LockType} from '../../../../constants';
4+
import {nfs} from '../../../../builder';
5+
6+
/**
7+
* Multi-client lock conflict tests based on RFC 7530 Section 9
8+
* Tests lock compatibility matrix and conflict resolution
9+
*/
10+
describe('Lock conflicts between multiple clients (RFC 7530 §9)', () => {
11+
describe('READ lock compatibility (shared locks)', () => {
12+
test.todo('should allow multiple READ locks from different clients on same range');
13+
test.todo('should allow multiple READ locks from different lock-owners on same range');
14+
test.todo('should allow overlapping READ locks');
15+
});
16+
17+
describe('WRITE lock exclusivity', () => {
18+
test.todo('should prevent WRITE lock when READ lock held by another client');
19+
test.todo('should prevent WRITE lock when WRITE lock held by another client');
20+
test.todo('should allow WRITE lock when no conflicting locks exist');
21+
});
22+
23+
describe('READ vs WRITE conflicts', () => {
24+
test.todo('should prevent READ lock when WRITE lock held by another client');
25+
test.todo('should return NFS4ERR_DENIED for READ when WRITE held');
26+
test.todo('should return NFS4ERR_DENIED for WRITE when READ held');
27+
});
28+
29+
describe('WRITE vs WRITE conflicts', () => {
30+
test.todo('should prevent WRITE lock when another WRITE lock held');
31+
test.todo('should return NFS4ERR_DENIED with conflict details');
32+
});
33+
34+
describe('Lock-owner isolation', () => {
35+
test.todo('should isolate locks between different lock-owners');
36+
test.todo('should not conflict with own locks from same lock-owner');
37+
test.todo('should conflict with locks from different lock-owners');
38+
});
39+
40+
describe('LOCK4denied structure', () => {
41+
test.todo('should return correct offset in LOCK4denied');
42+
test.todo('should return correct length in LOCK4denied');
43+
test.todo('should return correct locktype in LOCK4denied');
44+
test.todo('should return lock_owner4 of conflicting lock in LOCK4denied');
45+
test.todo('should return approximate values if exact conflict unknown');
46+
});
47+
48+
describe('Non-overlapping locks', () => {
49+
test.todo('should allow non-overlapping locks from different clients');
50+
test.todo('should allow adjacent locks from different clients');
51+
});
52+
53+
describe('Blocking lock fairness', () => {
54+
test.todo('should queue blocking locks from multiple clients');
55+
test.todo('should grant locks in order of request');
56+
test.todo('should maintain fairness across multiple clients');
57+
});
58+
});
59+
60+
/**
61+
* Lease renewal via lock operations based on RFC 7530 Section 9.5
62+
*/
63+
describe('Lease renewal via lock operations (RFC 7530 §9.5)', () => {
64+
describe('Implicit lease renewal', () => {
65+
test.todo('should renew lease on LOCK operation');
66+
test.todo('should renew lease on LOCKU operation');
67+
test.todo('should renew lease on LOCKT operation');
68+
test.todo('should renew all leases for client together');
69+
});
70+
71+
describe('Operations that renew lease', () => {
72+
test.todo('should renew lease with valid clientid');
73+
test.todo('should renew lease with valid stateid (not special)');
74+
test.todo('should not renew lease with anonymous stateid');
75+
test.todo('should not renew lease with READ bypass stateid');
76+
});
77+
78+
describe('SETCLIENTID behavior', () => {
79+
test.todo('should not renew lease with SETCLIENTID');
80+
test.todo('should not renew lease with SETCLIENTID_CONFIRM');
81+
test.todo('should drop locking state on client verifier change');
82+
});
83+
84+
describe('Lease expiration prevention', () => {
85+
test.todo('should not expire lease during active locking operations');
86+
test.todo('should extend lease time on each operation');
87+
test.todo('should maintain common expiration time for all client state');
88+
});
89+
90+
describe('Lease time management', () => {
91+
test.todo('should update single lease expiration for all state on renewal');
92+
test.todo('should allow low-overhead renewal via normal operations');
93+
test.todo('should not require explicit RENEW for active clients');
94+
});
95+
96+
describe('Stale state detection', () => {
97+
test.todo('should return NFS4ERR_STALE_STATEID after server reboot');
98+
test.todo('should return NFS4ERR_STALE_CLIENTID after server reboot');
99+
test.todo('should prevent spurious renewals after reboot');
100+
});
101+
});
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import {setupNfsClientServerTestbed} from '../../../__tests__/setup';
2+
import * as msg from '../../../../messages';
3+
import {Nfsv4Stat, Nfsv4OpenAccess, Nfsv4OpenDeny, Nfsv4LockType} from '../../../../constants';
4+
import {nfs} from '../../../../builder';
5+
6+
/**
7+
* Comprehensive error condition tests for locking operations
8+
* Based on RFC 7530 Sections 9 and 16.10-16.12
9+
*/
10+
describe('Lock operation error conditions (RFC 7530)', () => {
11+
describe('NFS4ERR_INVAL', () => {
12+
test.todo('should return NFS4ERR_INVAL for zero-length lock');
13+
test.todo('should return NFS4ERR_INVAL when offset + length overflows UINT64');
14+
test.todo('should return NFS4ERR_INVAL for invalid parameters');
15+
});
16+
17+
describe('NFS4ERR_BAD_RANGE', () => {
18+
test.todo('should return NFS4ERR_BAD_RANGE on 32-bit server for offset > UINT32_MAX');
19+
test.todo('should return NFS4ERR_BAD_RANGE for range overlapping UINT32_MAX boundary on 32-bit');
20+
test.todo('should accept ranges up to UINT32_MAX on 32-bit servers');
21+
test.todo('should accept range to UINT64_MAX (all bits 1) on any server');
22+
});
23+
24+
describe('NFS4ERR_LOCK_RANGE', () => {
25+
test.todo('should return NFS4ERR_LOCK_RANGE for sub-range lock when not supported');
26+
test.todo('should return NFS4ERR_LOCK_RANGE for overlapping range from same owner when not supported');
27+
test.todo('should return NFS4ERR_LOCK_RANGE from LOCKT if checking own overlapping locks');
28+
});
29+
30+
describe('NFS4ERR_LOCK_NOTSUPP', () => {
31+
test.todo('should return NFS4ERR_LOCK_NOTSUPP for atomic downgrade if not supported');
32+
test.todo('should return NFS4ERR_LOCK_NOTSUPP for atomic upgrade if not supported');
33+
});
34+
35+
describe('NFS4ERR_DENIED', () => {
36+
test.todo('should return NFS4ERR_DENIED with LOCK4denied for conflicting lock');
37+
test.todo('should return NFS4ERR_DENIED for upgrade blocked by other lock');
38+
test.todo('should include conflicting lock details in LOCK4denied');
39+
});
40+
41+
describe('NFS4ERR_DEADLOCK', () => {
42+
test.todo('should return NFS4ERR_DEADLOCK for WRITEW_LT when deadlock detected');
43+
test.todo('should return NFS4ERR_DEADLOCK for READW_LT when deadlock detected');
44+
test.todo('should detect deadlock in lock upgrade scenario');
45+
});
46+
47+
describe('NFS4ERR_BAD_STATEID', () => {
48+
test.todo('should return NFS4ERR_BAD_STATEID for unknown stateid');
49+
test.todo('should return NFS4ERR_BAD_STATEID for wrong filehandle');
50+
test.todo('should return NFS4ERR_BAD_STATEID for wrong stateid type');
51+
test.todo('should return NFS4ERR_BAD_STATEID for future seqid');
52+
test.todo('should return NFS4ERR_BAD_STATEID for invalid special stateid combo');
53+
test.todo('should return NFS4ERR_BAD_STATEID when new_lock_owner=true but state exists');
54+
});
55+
56+
describe('NFS4ERR_OLD_STATEID', () => {
57+
test.todo('should return NFS4ERR_OLD_STATEID for outdated stateid seqid');
58+
test.todo('should accept current stateid seqid');
59+
});
60+
61+
describe('NFS4ERR_STALE_STATEID', () => {
62+
test.todo('should return NFS4ERR_STALE_STATEID after server restart');
63+
test.todo('should return NFS4ERR_STALE_STATEID for revoked state');
64+
});
65+
66+
describe('NFS4ERR_BAD_SEQID', () => {
67+
test.todo('should return NFS4ERR_BAD_SEQID for incorrect lock-owner seqid');
68+
test.todo('should return NFS4ERR_BAD_SEQID for incorrect open-owner seqid');
69+
test.todo('should return NFS4ERR_BAD_SEQID when seqid not last + 1 or last (replay)');
70+
test.todo('should prioritize NFS4ERR_BAD_SEQID over other stateid errors');
71+
});
72+
73+
describe('NFS4ERR_STALE_CLIENTID', () => {
74+
test.todo('should return NFS4ERR_STALE_CLIENTID after server restart with invalid clientid');
75+
test.todo('should require SETCLIENTID after receiving NFS4ERR_STALE_CLIENTID');
76+
});
77+
78+
describe('NFS4ERR_EXPIRED', () => {
79+
test.todo('should return NFS4ERR_EXPIRED for lease-expired locks');
80+
test.todo('should mark state as expired not deleted');
81+
});
82+
83+
describe('NFS4ERR_ADMIN_REVOKED', () => {
84+
test.todo('should return NFS4ERR_ADMIN_REVOKED for administratively revoked locks');
85+
});
86+
87+
describe('NFS4ERR_LOCKED', () => {
88+
test.todo('should return NFS4ERR_LOCKED for READ conflicting with mandatory lock');
89+
test.todo('should return NFS4ERR_LOCKED for WRITE conflicting with mandatory lock');
90+
test.todo('should return NFS4ERR_LOCKED for OPEN_DOWNGRADE with locks held');
91+
});
92+
93+
describe('NFS4ERR_OPENMODE', () => {
94+
test.todo('should return NFS4ERR_OPENMODE for WRITE with read-only stateid');
95+
test.todo('should validate access mode matches operation');
96+
});
97+
98+
describe('NFS4ERR_GRACE', () => {
99+
test.todo('should return NFS4ERR_GRACE for non-reclaim LOCK during grace period');
100+
test.todo('should return NFS4ERR_GRACE for READ/WRITE during grace period if conflicts possible');
101+
test.todo('should allow reclaim LOCK during grace period');
102+
});
103+
104+
describe('NFS4ERR_RESOURCE', () => {
105+
test.todo('should return NFS4ERR_RESOURCE when server resources exhausted');
106+
});
107+
108+
describe('NFS4ERR_NOFILEHANDLE', () => {
109+
test.todo('should return NFS4ERR_NOFILEHANDLE when no current filehandle set');
110+
});
111+
112+
describe('Error combinations', () => {
113+
test.todo('should prioritize errors according to RFC specifications');
114+
test.todo('should return most specific error for condition');
115+
});
116+
});
117+
118+
/**
119+
* Server restart and recovery tests based on RFC 7530 Section 9.6.2
120+
*/
121+
describe('Lock reclaim after server restart (RFC 7530 §9.6.2)', () => {
122+
describe('Grace period', () => {
123+
test.todo('should establish grace period equal to lease period after restart');
124+
test.todo('should reject non-reclaim LOCK during grace period with NFS4ERR_GRACE');
125+
test.todo('should reject READ during grace period if conflicts possible');
126+
test.todo('should reject WRITE during grace period if conflicts possible');
127+
test.todo('should allow reclaim LOCK during grace period');
128+
});
129+
130+
describe('Reclaim parameter usage', () => {
131+
test.todo('should accept LOCK with reclaim=true during grace period');
132+
test.todo('should accept LOCK with reclaim=false after grace period');
133+
test.todo('should restore locks with reclaim=true');
134+
});
135+
136+
describe('Client restart detection', () => {
137+
test.todo('should detect client restart via verifier change');
138+
test.todo('should break old client leases on verifier change');
139+
test.todo('should release all locks for old client ID on verifier change');
140+
});
141+
142+
describe('Server restart detection', () => {
143+
test.todo('should return NFS4ERR_STALE_STATEID for stateids after restart');
144+
test.todo('should return NFS4ERR_STALE_CLIENTID for client IDs after restart');
145+
test.todo('should require client to establish new client ID');
146+
});
147+
148+
describe('Lock recovery', () => {
149+
test.todo('should allow clients to reclaim locks during grace period');
150+
test.todo('should track reclaimed locks');
151+
test.todo('should deny conflicting locks during grace period');
152+
});
153+
154+
describe('CLAIM_PREVIOUS', () => {
155+
test.todo('should accept CLAIM_PREVIOUS for opens during grace period');
156+
test.todo('should not require OPEN_CONFIRM for CLAIM_PREVIOUS');
157+
});
158+
159+
describe('Grace period end', () => {
160+
test.todo('should allow normal operations after grace period');
161+
test.todo('should reject late reclaim attempts after grace period');
162+
});
163+
164+
describe('Stable storage considerations', () => {
165+
test.todo('should optionally use stable storage to track granted locks');
166+
test.todo('should optionally allow non-reclaim I/O if no conflicts possible');
167+
});
168+
});

0 commit comments

Comments
 (0)