Commit a1f08100 authored by Tigran Mkrtchyan's avatar Tigran Mkrtchyan
Browse files

nfs: add initial implementation of xattr support

Motivation:

Modification:
pass nfs requests to backend file system implementation. The list operation
for now assumes that reply will fit into a single reply.

Result:
minimal support of file extended attribute over NFS

Acked-by: Paul Millar
Target: master
parent 5b41bd7b
......@@ -407,7 +407,8 @@ public class OperationGETATTR extends AbstractNFSv4Operation {
case nfs4_prot.FATTR4_SUPPATTR_EXCLCREAT:
return Optional.of(new fattr4_supported_attrs(NFSv4FileAttributes.EXCLCREAT_ATTR));
case nfs4_prot.FATTR4_XATTR_SUPPORT:
return Optional.of(new fattr4_xattr_support(false));
// REVISIT: we should query file system for corresponding capability
return Optional.of(new fattr4_xattr_support(true));
case nfs4_prot.FATTR4_TIME_MODIFY_SET:
case nfs4_prot.FATTR4_TIME_ACCESS_SET:
throw new InvalException("getattr of write-only attributes");
......
......@@ -21,10 +21,14 @@ package org.dcache.nfs.v4;
import java.io.IOException;
import org.dcache.nfs.ChimeraNFSException;
import org.dcache.nfs.nfsstat;
import org.dcache.nfs.status.NoXattrException;
import org.dcache.nfs.status.NotSuppException;
import org.dcache.nfs.v4.xdr.nfs_argop4;
import org.dcache.nfs.v4.xdr.nfs_opnum4;
import org.dcache.nfs.v4.xdr.nfs_resop4;
import org.dcache.nfs.v4.xdr.xattrvalue4;
import org.dcache.nfs.vfs.Inode;
import org.dcache.oncrpc4j.rpc.OncRpcException;
public class OperationGETXATTR extends AbstractNFSv4Operation {
......@@ -35,7 +39,16 @@ public class OperationGETXATTR extends AbstractNFSv4Operation {
@Override
public void process(CompoundContext context, nfs_resop4 result) throws ChimeraNFSException, IOException, OncRpcException {
throw new NotSuppException("Not implemented yet.");
}
try {
Inode inode = context.currentInode();
byte[] value = context.getFs().getXattr(inode, _args.opgetxattr.gxa_name);
result.opgetxattr.gxr_value = new xattrvalue4(value);
result.setStatus(nfsstat.NFS_OK);
} catch (NotSuppException e) {
throw e;
} catch (IOException e) {
throw new NoXattrException();
}
}
}
......@@ -21,10 +21,13 @@ package org.dcache.nfs.v4;
import java.io.IOException;
import org.dcache.nfs.ChimeraNFSException;
import org.dcache.nfs.status.NotSuppException;
import org.dcache.nfs.nfsstat;
import org.dcache.nfs.v4.xdr.LISTXATTRS4resok;
import org.dcache.nfs.v4.xdr.nfs_argop4;
import org.dcache.nfs.v4.xdr.nfs_cookie4;
import org.dcache.nfs.v4.xdr.nfs_opnum4;
import org.dcache.nfs.v4.xdr.nfs_resop4;
import org.dcache.nfs.vfs.Inode;
import org.dcache.oncrpc4j.rpc.OncRpcException;
public class OperationLISTXATTRS extends AbstractNFSv4Operation {
......@@ -35,7 +38,16 @@ public class OperationLISTXATTRS extends AbstractNFSv4Operation {
@Override
public void process(CompoundContext context, nfs_resop4 result) throws ChimeraNFSException, IOException, OncRpcException {
throw new NotSuppException("Not implemented yet.");
Inode inode = context.currentInode();
String[] attrs = context.getFs().listXattrs(inode);
result.oplistxattrs.lxr_value = new LISTXATTRS4resok();
// FIXME: for now send all in one go.
result.oplistxattrs.lxr_value.lxr_eof = true;
result.oplistxattrs.lxr_value.lxr_cookie = new nfs_cookie4(0);
result.oplistxattrs.lxr_value.lxr_names = attrs;
result.setStatus(nfsstat.NFS_OK);
}
}
......@@ -21,10 +21,11 @@ package org.dcache.nfs.v4;
import java.io.IOException;
import org.dcache.nfs.ChimeraNFSException;
import org.dcache.nfs.status.NotSuppException;
import org.dcache.nfs.nfsstat;
import org.dcache.nfs.v4.xdr.nfs_argop4;
import org.dcache.nfs.v4.xdr.nfs_opnum4;
import org.dcache.nfs.v4.xdr.nfs_resop4;
import org.dcache.nfs.vfs.Inode;
import org.dcache.oncrpc4j.rpc.OncRpcException;
public class OperationREMOVEXATTR extends AbstractNFSv4Operation {
......@@ -35,7 +36,10 @@ public class OperationREMOVEXATTR extends AbstractNFSv4Operation {
@Override
public void process(CompoundContext context, nfs_resop4 result) throws ChimeraNFSException, IOException, OncRpcException {
throw new NotSuppException("Not implemented yet.");
Inode inode = context.currentInode();
context.getFs().removeXattr(inode, _args.opremovexattr.rxa_name);
result.setStatus(nfsstat.NFS_OK);
}
}
......@@ -21,10 +21,16 @@ package org.dcache.nfs.v4;
import java.io.IOException;
import org.dcache.nfs.ChimeraNFSException;
import org.dcache.nfs.status.NotSuppException;
import org.dcache.nfs.nfsstat;
import org.dcache.nfs.status.BadXdrException;
import org.dcache.nfs.v4.xdr.change_info4;
import org.dcache.nfs.v4.xdr.changeid4;
import org.dcache.nfs.v4.xdr.nfs_argop4;
import org.dcache.nfs.v4.xdr.nfs_opnum4;
import org.dcache.nfs.v4.xdr.nfs_resop4;
import org.dcache.nfs.vfs.Inode;
import org.dcache.nfs.vfs.Stat;
import org.dcache.nfs.vfs.VirtualFileSystem;
import org.dcache.oncrpc4j.rpc.OncRpcException;
public class OperationSETXATTR extends AbstractNFSv4Operation {
......@@ -35,7 +41,35 @@ public class OperationSETXATTR extends AbstractNFSv4Operation {
@Override
public void process(CompoundContext context, nfs_resop4 result) throws ChimeraNFSException, IOException, OncRpcException {
throw new NotSuppException("Not implemented yet.");
Inode inode = context.currentInode();
result.opsetxattr.sxr_info = new change_info4();
result.opsetxattr.sxr_info.atomic = true;
Stat stat = context.getFs().getattr(inode);
result.opsetxattr.sxr_info.before = new changeid4(stat.getGeneration());
context.getFs().setXattr(inode, _args.opsetxattr.sxa_name,
_args.opsetxattr.sxa_value.value,
toXatterSetMode(_args.opsetxattr.sxa_option));
stat = context.getFs().getattr(inode);
result.opsetxattr.sxr_info.after = new changeid4(stat.getGeneration());
result.setStatus(nfsstat.NFS_OK);
}
private final VirtualFileSystem.SetXattrMode toXatterSetMode(int i) throws BadXdrException {
switch(i) {
case 0:
return VirtualFileSystem.SetXattrMode.EITHER;
case 1:
return VirtualFileSystem.SetXattrMode.CREATE;
case 2:
return VirtualFileSystem.SetXattrMode.REPLACE;
default:
throw new BadXdrException("Unknown setxattr mode " + i);
}
}
}
......@@ -93,16 +93,21 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.OptionalLong;
import org.dcache.nfs.v4.xdr.GETXATTR4args;
import org.dcache.nfs.v4.xdr.LAYOUTCOMMIT4args;
import org.dcache.nfs.v4.xdr.LAYOUTERROR4args;
import org.dcache.nfs.v4.xdr.LAYOUTSTATS4args;
import org.dcache.nfs.v4.xdr.LISTXATTRS4args;
import org.dcache.nfs.v4.xdr.LOCKU4args;
import org.dcache.nfs.v4.xdr.RECLAIM_COMPLETE4args;
import org.dcache.nfs.v4.xdr.REMOVEXATTR4args;
import org.dcache.nfs.v4.xdr.SETXATTR4args;
import org.dcache.nfs.v4.xdr.device_error4;
import org.dcache.nfs.v4.xdr.io_info4;
import org.dcache.nfs.v4.xdr.layoutupdate4;
import org.dcache.nfs.v4.xdr.newoffset4;
import org.dcache.nfs.v4.xdr.newtime4;
import org.dcache.nfs.v4.xdr.xattrvalue4;
import org.dcache.oncrpc4j.util.Bytes;
import org.dcache.oncrpc4j.xdr.Xdr;
......@@ -663,6 +668,51 @@ public class CompoundBuilder {
return this;
}
public CompoundBuilder withGetXattr(String name) {
nfs_argop4 op = new nfs_argop4();
op.argop = nfs_opnum4.OP_GETXATTR;
op.opgetxattr = new GETXATTR4args();
op.opgetxattr.gxa_name = name;
ops.add(op);
return this;
}
public CompoundBuilder withSetXattr(String name, byte[] value, int mode) {
nfs_argop4 op = new nfs_argop4();
op.argop = nfs_opnum4.OP_SETXATTR;
op.opsetxattr = new SETXATTR4args();
op.opsetxattr.sxa_name = name;
op.opsetxattr.sxa_value = new xattrvalue4(value);
op.opsetxattr.sxa_option = mode;
ops.add(op);
return this;
}
public CompoundBuilder withListXattrs(int cookie, int count) {
nfs_argop4 op = new nfs_argop4();
op.argop = nfs_opnum4.OP_LISTXATTRS;
op.oplistxattrs = new LISTXATTRS4args();
op.oplistxattrs.lxa_cookie = new nfs_cookie4(cookie);
op.oplistxattrs.lxa_maxcount = new count4(count);
ops.add(op);
return this;
}
public CompoundBuilder withRemoveXattr(String name) {
nfs_argop4 op = new nfs_argop4();
op.argop = nfs_opnum4.OP_REMOVEXATTR;
op.opremovexattr = new REMOVEXATTR4args();
op.opremovexattr.rxa_name = name;
ops.add(op);
return this;
}
public COMPOUND4args build() {
final COMPOUND4args compound4args = new COMPOUND4args();
compound4args.tag = new utf8str_cs(tag);
......
package org.dcache.nfs.v4;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.dcache.nfs.v4.client.CompoundBuilder;
import org.dcache.nfs.v4.xdr.COMPOUND4args;
import org.dcache.nfs.v4.xdr.COMPOUND4res;
import org.dcache.nfs.v4.xdr.nfs_fh4;
import org.dcache.nfs.vfs.Inode;
import org.dcache.nfs.vfs.VirtualFileSystem;
import org.junit.Before;
import org.junit.Test;
import static org.dcache.nfs.v4.NfsTestUtils.execute;
import static org.dcache.nfs.v4.NfsTestUtils.generateRpcCall;
import static org.junit.Assert.assertArrayEquals;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class OperationGETXATTRTest {
private VirtualFileSystem vfs;
private final Inode inode = Inode.forFile(new byte[]{1, 2, 3, 4});
private final nfs_fh4 fh = new nfs_fh4(inode.toNfsHandle());
@Before
public void setUp() {
vfs = mock(VirtualFileSystem.class);
}
@Test
public void testPropageteGetXattr() throws IOException {
String key = "xattr1";
byte[] expectedValue = "value1".getBytes(StandardCharsets.UTF_8);
COMPOUND4args getxattrArgs = new CompoundBuilder()
.withPutfh(fh)
.withGetXattr(key)
.build();
CompoundContext context = new CompoundContextBuilder()
.withFs(vfs)
.withCall(generateRpcCall())
.build();
when(vfs.getXattr(inode, key)).thenReturn(expectedValue);
COMPOUND4res res = execute(context, getxattrArgs);
verify(vfs).getXattr(inode, key);
assertArrayEquals(expectedValue, res.resarray.get(1).opgetxattr.gxr_value.value);
}
}
package org.dcache.nfs.v4;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.dcache.nfs.v4.client.CompoundBuilder;
import org.dcache.nfs.v4.xdr.COMPOUND4args;
import org.dcache.nfs.v4.xdr.COMPOUND4res;
import org.dcache.nfs.v4.xdr.nfs_fh4;
import org.dcache.nfs.vfs.Inode;
import org.dcache.nfs.vfs.VirtualFileSystem;
import org.junit.Before;
import org.junit.Test;
import static org.dcache.nfs.v4.NfsTestUtils.execute;
import static org.dcache.nfs.v4.NfsTestUtils.generateRpcCall;
import static org.junit.Assert.assertArrayEquals;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class OperationLISTXATTRSTest {
private VirtualFileSystem vfs;
private final Inode inode = Inode.forFile(new byte[]{1, 2, 3, 4});
private final nfs_fh4 fh = new nfs_fh4(inode.toNfsHandle());
@Before
public void setUp() {
vfs = mock(VirtualFileSystem.class);
}
@Test
public void testPropageteListXattrs() throws IOException {
String[] expectedAttrs = {
"xattr1", "xattr2", "xattr3"
};
COMPOUND4args listxattrsArgs = new CompoundBuilder()
.withPutfh(fh)
.withListXattrs(0, 100)
.build();
CompoundContext context = new CompoundContextBuilder()
.withFs(vfs)
.withCall(generateRpcCall())
.build();
when(vfs.listXattrs(inode)).thenReturn(expectedAttrs);
COMPOUND4res res = execute(context, listxattrsArgs);
verify(vfs).listXattrs(inode);
assertArrayEquals(expectedAttrs, res.resarray.get(1).oplistxattrs.lxr_value.lxr_names);
}
}
package org.dcache.nfs.v4;
import java.io.IOException;
import org.dcache.nfs.v4.client.CompoundBuilder;
import org.dcache.nfs.v4.xdr.COMPOUND4args;
import org.dcache.nfs.v4.xdr.COMPOUND4res;
import org.dcache.nfs.v4.xdr.nfs_fh4;
import org.dcache.nfs.vfs.Inode;
import org.dcache.nfs.vfs.VirtualFileSystem;
import org.junit.Before;
import org.junit.Test;
import static org.dcache.nfs.v4.NfsTestUtils.execute;
import static org.dcache.nfs.v4.NfsTestUtils.generateRpcCall;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class OperationREMOVEXATTRTest {
private VirtualFileSystem vfs;
private final Inode inode = Inode.forFile(new byte[]{1, 2, 3, 4});
private final nfs_fh4 fh = new nfs_fh4(inode.toNfsHandle());
@Before
public void setUp() {
vfs = mock(VirtualFileSystem.class);
}
@Test
public void testPropageteRemoveXattr() throws IOException {
String key = "xattr1";
COMPOUND4args removexattrArgs = new CompoundBuilder()
.withPutfh(fh)
.withRemoveXattr(key)
.build();
CompoundContext context = new CompoundContextBuilder()
.withFs(vfs)
.withCall(generateRpcCall())
.build();
execute(context, removexattrArgs);
verify(vfs).removeXattr(inode, key);
}
}
package org.dcache.nfs.v4;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.dcache.nfs.status.BadXdrException;
import org.dcache.nfs.v4.client.CompoundBuilder;
import org.dcache.nfs.v4.xdr.COMPOUND4args;
import org.dcache.nfs.v4.xdr.nfs_fh4;
import org.dcache.nfs.v4.xdr.setxattr_option4;
import org.dcache.nfs.vfs.Inode;
import org.dcache.nfs.vfs.VirtualFileSystem;
import org.junit.Before;
import org.junit.Test;
import static org.dcache.nfs.v4.NfsTestUtils.execute;
import static org.dcache.nfs.v4.NfsTestUtils.generateRpcCall;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class OperationSETXATTRTest {
private VirtualFileSystem vfs;
private final Inode inode = Inode.forFile(new byte[]{1, 2, 3, 4});
private final nfs_fh4 fh = new nfs_fh4(inode.toNfsHandle());
@Before
public void setUp() {
vfs = mock(VirtualFileSystem.class);
}
@Test
public void testPropageteSetXattrModeEither() throws IOException {
String key = "xattr1";
byte[] expectedValue = "value1".getBytes(StandardCharsets.UTF_8);
COMPOUND4args setxattrArgs = new CompoundBuilder()
.withPutfh(fh)
.withSetXattr(key, expectedValue, setxattr_option4.SETXATTR4_EITHER)
.build();
CompoundContext context = new CompoundContextBuilder()
.withFs(vfs)
.withCall(generateRpcCall())
.build();
execute(context, setxattrArgs);
verify(vfs).setXattr(inode, key, expectedValue, VirtualFileSystem.SetXattrMode.EITHER);
}
@Test
public void testPropageteSetXattrModeReplace() throws IOException {
String key = "xattr1";
byte[] expectedValue = "value1".getBytes(StandardCharsets.UTF_8);
COMPOUND4args setxattrArgs = new CompoundBuilder()
.withPutfh(fh)
.withSetXattr(key, expectedValue, setxattr_option4.SETXATTR4_REPLACE)
.build();
CompoundContext context = new CompoundContextBuilder()
.withFs(vfs)
.withCall(generateRpcCall())
.build();
execute(context, setxattrArgs);
verify(vfs).setXattr(inode, key, expectedValue, VirtualFileSystem.SetXattrMode.REPLACE);
}
@Test
public void testPropageteSetXattrModeCreate() throws IOException {
String key = "xattr1";
byte[] expectedValue = "value1".getBytes(StandardCharsets.UTF_8);
COMPOUND4args setxattrArgs = new CompoundBuilder()
.withPutfh(fh)
.withSetXattr(key, expectedValue, setxattr_option4.SETXATTR4_CREATE)
.build();
CompoundContext context = new CompoundContextBuilder()
.withFs(vfs)
.withCall(generateRpcCall())
.build();
execute(context, setxattrArgs);
verify(vfs).setXattr(inode, key, expectedValue, VirtualFileSystem.SetXattrMode.CREATE);
}
@Test(expected = BadXdrException.class)
public void testPropageteSetXattrModeInval() throws IOException {
String key = "xattr1";
byte[] expectedValue = "value1".getBytes(StandardCharsets.UTF_8);
COMPOUND4args setxattrArgs = new CompoundBuilder()
.withPutfh(fh)
.withSetXattr(key, expectedValue, 7)
.build();
CompoundContext context = new CompoundContextBuilder()
.withFs(vfs)
.withCall(generateRpcCall())
.build();
execute(context, setxattrArgs);
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment