Skip to content

Commit e6e1941

Browse files
committed
nfs42: allow sync copy only if byte count is less than max IO size
Motivation: The server-side-copy allows two modes sync and async. The spec states that server is not allows to change the mode that was requested by client. However, current implementation will fall-back to async, if COPY takes longer that 1 second. As server has no idea how log a copy might take place, allow sync copy only for chunks that are not bigger than max I/O block. Modification: Update COPY to return an error if requested number of bytes larger than max I/O. Add unit tests to cover the behavior. Add missing exception. Result: better spec compliance Acked-by: Marina Sahakyan Target: master
1 parent e146568 commit e6e1941

File tree

5 files changed

+299
-99
lines changed

5 files changed

+299
-99
lines changed

core/src/main/java/org/dcache/nfs/nfsstat.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,8 @@ public static void throwIfNeeded(int errorCode) throws ChimeraNFSException {
615615
throw new OffloadDeniedExeption();
616616
case nfsstat.NFS4ERR_DELEG_REVOKED:
617617
throw new DelegRevokedException();
618+
case nfsstat.NFS4ERR_OFFLOAD_NO_REQS:
619+
throw new OffloadNoReqsException();
618620
default:
619621
throw new BadXdrException();
620622
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (c) 2025 Deutsches Elektronen-Synchroton,
3+
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY
4+
*
5+
* This library is free software; you can redistribute it and/or modify
6+
* it under the terms of the GNU Library General Public License as
7+
* published by the Free Software Foundation; either version 2 of the
8+
* License, or (at your option) any later version.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Library General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Library General Public
16+
* License along with this program (see the file COPYING.LIB for more
17+
* details); if not, write to the Free Software Foundation, Inc.,
18+
* 675 Mass Ave, Cambridge, MA 02139, USA.
19+
*/
20+
package org.dcache.nfs.status;
21+
22+
import static org.dcache.nfs.nfsstat.NFS4ERR_OFFLOAD_NO_REQS;
23+
import org.dcache.nfs.ChimeraNFSException;
24+
25+
public class OffloadNoReqsException extends ChimeraNFSException {
26+
27+
private static final long serialVersionUID = -5377367456693294221L;
28+
29+
public OffloadNoReqsException() {
30+
super(NFS4ERR_OFFLOAD_NO_REQS);
31+
}
32+
33+
public OffloadNoReqsException(String msg) {
34+
super(NFS4ERR_OFFLOAD_NO_REQS, msg);
35+
}
36+
37+
public OffloadNoReqsException(String msg, Throwable cause) {
38+
super(NFS4ERR_OFFLOAD_NO_REQS, msg, cause);
39+
}
40+
}

core/src/main/java/org/dcache/nfs/v4/CompoundBuilder.java

Lines changed: 90 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -19,94 +19,97 @@
1919
*/
2020
package org.dcache.nfs.v4;
2121

22-
import org.dcache.nfs.v4.xdr.layoutreturn_file4;
23-
import org.dcache.nfs.v4.xdr.nfs_fh4;
24-
import org.dcache.nfs.v4.xdr.CREATE_SESSION4args;
25-
import org.dcache.nfs.v4.xdr.CREATE4args;
26-
import org.dcache.nfs.v4.xdr.WRITE4args;
27-
import org.dcache.nfs.v4.xdr.attrlist4;
28-
import org.dcache.nfs.v4.xdr.open_claim_type4;
29-
import org.dcache.nfs.v4.xdr.seqid4;
30-
import org.dcache.nfs.v4.xdr.READDIR4args;
31-
import org.dcache.nfs.v4.xdr.nfs_opnum4;
32-
import org.dcache.nfs.v4.xdr.nfs_argop4;
33-
import org.dcache.nfs.v4.xdr.nfs_impl_id4;
34-
import org.dcache.nfs.v4.xdr.callback_sec_parms4;
35-
import org.dcache.nfs.v4.xdr.nfs4_prot;
36-
import org.dcache.nfs.v4.xdr.layoutreturn4;
37-
import org.dcache.nfs.v4.xdr.slotid4;
38-
import org.dcache.nfs.v4.xdr.sequenceid4;
39-
import org.dcache.nfs.v4.xdr.LOOKUP4args;
40-
import org.dcache.nfs.v4.xdr.state_protect4_a;
41-
import org.dcache.nfs.v4.xdr.layoutiomode4;
42-
import org.dcache.nfs.v4.xdr.PUTFH4args;
43-
import org.dcache.nfs.v4.xdr.uint32_t;
44-
import org.dcache.nfs.v4.xdr.GETDEVICELIST4args;
45-
import org.dcache.nfs.v4.xdr.GETATTR4args;
46-
import org.dcache.nfs.v4.xdr.open_claim4;
47-
import org.dcache.nfs.v4.xdr.stateid4;
48-
import org.dcache.nfs.v4.xdr.GETDEVICEINFO4args;
49-
import org.dcache.nfs.v4.xdr.createhow4;
50-
import org.dcache.nfs.v4.xdr.createtype4;
51-
import org.dcache.nfs.v4.xdr.deviceid4;
52-
import org.dcache.nfs.v4.xdr.SEQUENCE4args;
53-
import org.dcache.nfs.v4.xdr.nfs_ftype4;
54-
import org.dcache.nfs.v4.xdr.client_owner4;
55-
import org.dcache.nfs.v4.xdr.count4;
56-
import org.dcache.nfs.v4.xdr.stable_how4;
57-
import org.dcache.nfs.v4.xdr.utf8str_cis;
58-
import org.dcache.nfs.v4.xdr.CLOSE4args;
59-
import org.dcache.nfs.v4.xdr.fattr4_mode;
60-
import org.dcache.nfs.v4.xdr.DESTROY_CLIENTID4args;
61-
import org.dcache.nfs.v4.xdr.clientid4;
62-
import org.dcache.nfs.v4.xdr.channel_attrs4;
63-
import org.dcache.nfs.v4.xdr.EXCHANGE_ID4args;
64-
import org.dcache.nfs.v4.xdr.opentype4;
65-
import org.dcache.nfs.v4.xdr.length4;
66-
import org.dcache.nfs.v4.xdr.COMPOUND4args;
67-
import org.dcache.nfs.v4.xdr.fattr4_size;
68-
import org.dcache.nfs.v4.xdr.bitmap4;
69-
import org.dcache.nfs.v4.xdr.REMOVE4args;
70-
import org.dcache.nfs.v4.xdr.component4;
71-
import org.dcache.nfs.v4.xdr.LAYOUTGET4args;
72-
import org.dcache.nfs.v4.xdr.OPEN4args;
73-
import org.dcache.nfs.v4.xdr.fattr4;
74-
import org.dcache.nfs.v4.xdr.READ4args;
75-
import org.dcache.nfs.v4.xdr.state_owner4;
76-
import org.dcache.nfs.v4.xdr.offset4;
77-
import org.dcache.nfs.v4.xdr.DESTROY_SESSION4args;
78-
import org.dcache.nfs.v4.xdr.LAYOUTRETURN4args;
79-
import org.dcache.nfs.v4.xdr.utf8str_cs;
80-
import org.dcache.nfs.v4.xdr.nfstime4;
81-
import org.dcache.nfs.v4.xdr.layoutreturn_type4;
82-
import org.dcache.nfs.v4.xdr.nfs_cookie4;
83-
import org.dcache.nfs.v4.xdr.layouttype4;
84-
import org.dcache.nfs.v4.xdr.verifier4;
85-
import org.dcache.nfs.v4.xdr.sessionid4;
86-
import org.dcache.nfs.v4.xdr.openflag4;
87-
import org.dcache.nfs.v4.xdr.createmode4;
88-
import org.dcache.nfs.v4.xdr.open_owner4;
8922
import com.google.common.base.Splitter;
9023
import java.io.IOException;
9124
import java.nio.ByteBuffer;
9225
import java.nio.charset.StandardCharsets;
9326
import java.util.ArrayList;
9427
import java.util.List;
9528
import java.util.OptionalLong;
29+
30+
import org.dcache.nfs.v4.xdr.CLOSE4args;
31+
import org.dcache.nfs.v4.xdr.COMPOUND4args;
32+
import org.dcache.nfs.v4.xdr.COPY4args;
33+
import org.dcache.nfs.v4.xdr.CREATE4args;
34+
import org.dcache.nfs.v4.xdr.CREATE_SESSION4args;
35+
import org.dcache.nfs.v4.xdr.DESTROY_CLIENTID4args;
36+
import org.dcache.nfs.v4.xdr.DESTROY_SESSION4args;
37+
import org.dcache.nfs.v4.xdr.EXCHANGE_ID4args;
38+
import org.dcache.nfs.v4.xdr.GETATTR4args;
39+
import org.dcache.nfs.v4.xdr.GETDEVICEINFO4args;
40+
import org.dcache.nfs.v4.xdr.GETDEVICELIST4args;
9641
import org.dcache.nfs.v4.xdr.GETXATTR4args;
9742
import org.dcache.nfs.v4.xdr.LAYOUTCOMMIT4args;
9843
import org.dcache.nfs.v4.xdr.LAYOUTERROR4args;
44+
import org.dcache.nfs.v4.xdr.LAYOUTGET4args;
45+
import org.dcache.nfs.v4.xdr.LAYOUTRETURN4args;
9946
import org.dcache.nfs.v4.xdr.LAYOUTSTATS4args;
10047
import org.dcache.nfs.v4.xdr.LISTXATTRS4args;
10148
import org.dcache.nfs.v4.xdr.LOCKU4args;
49+
import org.dcache.nfs.v4.xdr.LOOKUP4args;
50+
import org.dcache.nfs.v4.xdr.OPEN4args;
51+
import org.dcache.nfs.v4.xdr.PUTFH4args;
52+
import org.dcache.nfs.v4.xdr.READ4args;
53+
import org.dcache.nfs.v4.xdr.READDIR4args;
10254
import org.dcache.nfs.v4.xdr.RECLAIM_COMPLETE4args;
55+
import org.dcache.nfs.v4.xdr.REMOVE4args;
10356
import org.dcache.nfs.v4.xdr.REMOVEXATTR4args;
57+
import org.dcache.nfs.v4.xdr.SEQUENCE4args;
10458
import org.dcache.nfs.v4.xdr.SETXATTR4args;
59+
import org.dcache.nfs.v4.xdr.WRITE4args;
60+
import org.dcache.nfs.v4.xdr.attrlist4;
61+
import org.dcache.nfs.v4.xdr.bitmap4;
62+
import org.dcache.nfs.v4.xdr.callback_sec_parms4;
63+
import org.dcache.nfs.v4.xdr.channel_attrs4;
64+
import org.dcache.nfs.v4.xdr.client_owner4;
65+
import org.dcache.nfs.v4.xdr.clientid4;
66+
import org.dcache.nfs.v4.xdr.component4;
67+
import org.dcache.nfs.v4.xdr.count4;
68+
import org.dcache.nfs.v4.xdr.createhow4;
69+
import org.dcache.nfs.v4.xdr.createmode4;
70+
import org.dcache.nfs.v4.xdr.createtype4;
10571
import org.dcache.nfs.v4.xdr.device_error4;
72+
import org.dcache.nfs.v4.xdr.deviceid4;
73+
import org.dcache.nfs.v4.xdr.fattr4;
74+
import org.dcache.nfs.v4.xdr.fattr4_mode;
75+
import org.dcache.nfs.v4.xdr.fattr4_size;
10676
import org.dcache.nfs.v4.xdr.io_info4;
77+
import org.dcache.nfs.v4.xdr.layoutiomode4;
78+
import org.dcache.nfs.v4.xdr.layoutreturn4;
79+
import org.dcache.nfs.v4.xdr.layoutreturn_file4;
80+
import org.dcache.nfs.v4.xdr.layoutreturn_type4;
81+
import org.dcache.nfs.v4.xdr.layouttype4;
10782
import org.dcache.nfs.v4.xdr.layoutupdate4;
83+
import org.dcache.nfs.v4.xdr.length4;
84+
import org.dcache.nfs.v4.xdr.netloc4;
10885
import org.dcache.nfs.v4.xdr.newoffset4;
10986
import org.dcache.nfs.v4.xdr.newtime4;
87+
import org.dcache.nfs.v4.xdr.nfs4_prot;
88+
import org.dcache.nfs.v4.xdr.nfs_argop4;
89+
import org.dcache.nfs.v4.xdr.nfs_cookie4;
90+
import org.dcache.nfs.v4.xdr.nfs_fh4;
91+
import org.dcache.nfs.v4.xdr.nfs_ftype4;
92+
import org.dcache.nfs.v4.xdr.nfs_impl_id4;
93+
import org.dcache.nfs.v4.xdr.nfs_opnum4;
94+
import org.dcache.nfs.v4.xdr.nfstime4;
95+
import org.dcache.nfs.v4.xdr.offset4;
96+
import org.dcache.nfs.v4.xdr.open_claim4;
97+
import org.dcache.nfs.v4.xdr.open_claim_type4;
98+
import org.dcache.nfs.v4.xdr.open_owner4;
99+
import org.dcache.nfs.v4.xdr.openflag4;
100+
import org.dcache.nfs.v4.xdr.opentype4;
101+
import org.dcache.nfs.v4.xdr.seqid4;
102+
import org.dcache.nfs.v4.xdr.sequenceid4;
103+
import org.dcache.nfs.v4.xdr.sessionid4;
104+
import org.dcache.nfs.v4.xdr.slotid4;
105+
import org.dcache.nfs.v4.xdr.stable_how4;
106+
import org.dcache.nfs.v4.xdr.state_owner4;
107+
import org.dcache.nfs.v4.xdr.state_protect4_a;
108+
import org.dcache.nfs.v4.xdr.stateid4;
109+
import org.dcache.nfs.v4.xdr.uint32_t;
110+
import org.dcache.nfs.v4.xdr.utf8str_cis;
111+
import org.dcache.nfs.v4.xdr.utf8str_cs;
112+
import org.dcache.nfs.v4.xdr.verifier4;
110113
import org.dcache.nfs.v4.xdr.xattrvalue4;
111114
import org.dcache.oncrpc4j.util.Bytes;
112115
import org.dcache.oncrpc4j.xdr.Xdr;
@@ -713,6 +716,26 @@ public CompoundBuilder withRemoveXattr(String name) {
713716
return this;
714717
}
715718

719+
public CompoundBuilder withIntraServerCopy(stateid4 srcStateid, stateid4 dstStateid, long srcOffset, long dstOffset, long length, boolean sync, boolean consecutive) {
720+
721+
nfs_argop4 op = new nfs_argop4();
722+
op.argop = nfs_opnum4.OP_COPY;
723+
op.opcopy = new COPY4args();
724+
op.opcopy.ca_consecutive = consecutive;
725+
op.opcopy.ca_synchronous = sync;
726+
op.opcopy.ca_source_server = new netloc4[]{};
727+
op.opcopy.ca_src_stateid = srcStateid;
728+
op.opcopy.ca_dst_stateid = dstStateid;
729+
op.opcopy.ca_src_offset = new offset4(srcOffset);
730+
op.opcopy.ca_dst_offset = new offset4(dstOffset);
731+
op.opcopy.ca_count = new length4(length);
732+
733+
ops.add(op);
734+
735+
return this;
736+
}
737+
738+
716739
public COMPOUND4args build() {
717740
final COMPOUND4args compound4args = new COMPOUND4args();
718741
compound4args.tag = new utf8str_cs(tag);

core/src/main/java/org/dcache/nfs/v4/OperationCOPY.java

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@
2121

2222
import java.io.IOException;
2323
import java.util.concurrent.CompletableFuture;
24-
import java.util.concurrent.ExecutionException;
25-
import java.util.concurrent.TimeUnit;
26-
import java.util.concurrent.TimeoutException;
2724

2825
import com.google.common.annotations.Beta;
2926
import com.google.common.base.Throwables;
@@ -70,22 +67,23 @@ public void process(CompoundContext context, nfs_resop4 result) throws ChimeraNF
7067
throw new NotSuppException("Inter-server copy is not supported");
7168
}
7269

73-
// only consecutive copy is supported
74-
if (!_args.opcopy.ca_consecutive) {
75-
res.cr_requirements = new copy_requirements4();
76-
res.cr_requirements.cr_consecutive = true;
77-
res.cr_requirements.cr_synchronous = _args.opcopy.ca_synchronous;
78-
res.cr_status = nfsstat.NFS4ERR_OFFLOAD_NO_REQS;
79-
return;
80-
}
81-
8270
Inode srcInode = context.savedInode();
8371
Inode dstInode = context.currentInode();
8472

8573
long srcPos = _args.opcopy.ca_src_offset.value;
8674
long dstPos = _args.opcopy.ca_dst_offset.value;
8775
long len = _args.opcopy.ca_count.value;
8876

77+
78+
// Only consecutive copy is supported. Synchronous copy is allowed if the byte count is smaller than max IO size.
79+
if (!_args.opcopy.ca_consecutive || (_args.opcopy.ca_synchronous && len > NFSv4Defaults.NFS4_MAXIOBUFFERSIZE)) {
80+
res.cr_requirements = new copy_requirements4();
81+
res.cr_requirements.cr_consecutive = true;
82+
res.cr_requirements.cr_synchronous = true;
83+
res.cr_status = nfsstat.NFS4ERR_OFFLOAD_NO_REQS;
84+
return;
85+
}
86+
8987
NFS4Client client = context.getSession().getClient();
9088

9189
NFS4State srcState = client.state(_args.opcopy.ca_src_stateid);
@@ -108,40 +106,34 @@ public void process(CompoundContext context, nfs_resop4 result) throws ChimeraNF
108106
res.cr_resok4 = new COPY4resok();
109107
res.cr_resok4.cr_response = new write_response4();
110108
res.cr_resok4.cr_response.wr_writeverf = context.getRebootVerifier();
111-
res.cr_resok4.cr_response.wr_callback_id = new stateid4[]{};
112-
res.cr_resok4.cr_response.wr_committed = stable_how4.FILE_SYNC4;
113-
res.cr_resok4.cr_response.wr_count = new length4(0);
114-
115-
res.cr_resok4.cr_requirements = new copy_requirements4();
116-
res.cr_resok4.cr_requirements.cr_consecutive = true;
117109
res.cr_status = nfsstat.NFS_OK;
118110

111+
119112
CompletableFuture<Long> copyFuture = context.getFs().copyFileRange(srcInode, srcPos, dstInode, dstPos, len);
120-
boolean isSync = _args.opcopy.ca_synchronous;
121-
if (isSync) {
113+
if (_args.opcopy.ca_synchronous) {
114+
long bytes = 0L;
122115
try {
123-
// try sync copy and fall-back to async
124-
long n = copyFuture.get(1, TimeUnit.SECONDS);
125-
res.cr_resok4.cr_response.wr_count = new length4(n);
126-
} catch (InterruptedException | ExecutionException e) {
127-
116+
bytes = copyFuture.get();
117+
} catch (Exception e) {
128118
Throwable cause = Throwables.getRootCause(e);
129119
Throwables.propagateIfPossible(cause, ChimeraNFSException.class);
130120

131121
LOGGER.error("Copy-offload failed: {}", e.getMessage());
132122
res.cr_status = nfsstat.NFSERR_IO;
133-
} catch (TimeoutException e) {
134-
// continue as async copy
135-
isSync = false;
136123
}
137-
}
138124

139-
if (!isSync) {
140-
// copy asynchronously
125+
res.cr_resok4.cr_response.wr_count = new length4(bytes);
126+
res.cr_resok4.cr_response.wr_callback_id = new stateid4[]{};
127+
} else {
141128
var copyState = notifyWhenComplete(client, dstInode, context.getRebootVerifier(), copyFuture);
142129
res.cr_resok4.cr_response.wr_callback_id = new stateid4[]{copyState};
130+
res.cr_resok4.cr_response.wr_count = new length4(0);
143131
}
144-
res.cr_resok4.cr_requirements.cr_synchronous = isSync;
132+
133+
res.cr_resok4.cr_response.wr_committed = stable_how4.FILE_SYNC4;
134+
res.cr_resok4.cr_requirements = new copy_requirements4();
135+
res.cr_resok4.cr_requirements.cr_consecutive = true;
136+
res.cr_resok4.cr_requirements.cr_synchronous = _args.opcopy.ca_synchronous;
145137
}
146138

147139
private stateid4 notifyWhenComplete(NFS4Client client, Inode dstInode, verifier4 verifier, CompletableFuture<Long> copyFuture) throws ChimeraNFSException {

0 commit comments

Comments
 (0)