Skip to content

Commit

Permalink
rquota: add possibility to set quota.
Browse files Browse the repository at this point in the history
Modification:
added `org.dcache.rquota.QuotaVfs#setQuota`
added unit test

Fixes: #133
Acked-by: Lea Morschel
Target: master
  • Loading branch information
kofemann committed Sep 11, 2024
1 parent 0597235 commit c58a552
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 19 deletions.
56 changes: 48 additions & 8 deletions rquota/src/main/java/org/dcache/rquota/QuotaSvc.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

import static org.dcache.rquota.QuotaVfs.GROUP_QUOTA;
import static org.dcache.rquota.QuotaVfs.USER_QUOTA;
import org.dcache.nfs.ExportTable;
import org.dcache.nfs.FsExport;
import org.dcache.nfs.util.SubjectHolder;
import org.dcache.nfs.util.UnixSubjects;
import org.dcache.oncrpc4j.rpc.RpcCall;
Expand All @@ -33,17 +35,20 @@
import org.dcache.rquota.xdr.setquota_args;
import org.dcache.rquota.xdr.setquota_rslt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.security.auth.Subject;

public class QuotaSvc extends rquotaServerStub {

private final static Logger LOGGER = org.slf4j.LoggerFactory.getLogger(QuotaSvc.class);
private final static Logger LOGGER = LoggerFactory.getLogger(QuotaSvc.class);

private final QuotaVfs _qfs;
private final QuotaVfs qfs;
private final ExportTable exportTable;

public QuotaSvc(QuotaVfs _qfs) {
this._qfs = _qfs;
public QuotaSvc(QuotaVfs qfs, ExportTable exportTable) {
this.qfs = qfs;
this.exportTable = exportTable;
}

@Override
Expand Down Expand Up @@ -76,7 +81,7 @@ public getquota_rslt RQUOTAPROC_GETQUOTA_2(RpcCall call$, ext_getquota_args arg1
}

r.status = qr_status.Q_OK;
r.gqr_rquota = _qfs.getQuota(arg1.gqa_id, arg1.gqa_type);
r.gqr_rquota = qfs.getQuota(arg1.gqa_id, arg1.gqa_type);
return r;
}

Expand All @@ -90,18 +95,53 @@ public getquota_rslt RQUOTAPROC_GETACTIVEQUOTA_2(RpcCall call$, ext_getquota_arg
}

r.status = qr_status.Q_OK;
r.gqr_rquota = _qfs.getQuota(arg1.gqa_id, arg1.gqa_type);
r.gqr_rquota = qfs.getQuota(arg1.gqa_id, arg1.gqa_type);
return r;
}

@Override
public setquota_rslt RQUOTAPROC_SETQUOTA_2(RpcCall call$, ext_setquota_args arg1) {
throw new UnsupportedOperationException("Not supported yet.");

setquota_rslt result = new setquota_rslt();
String path = "/" + arg1.sqa_pathp; // leading slash is never sent by the client


FsExport export = exportTable.getExport(path, call$.getTransport().getRemoteSocketAddress().getAddress());
if (export == null) {
result.status = qr_status.Q_EPERM;
return result;
}

if (!(UnixSubjects.isRootSubject(call$.getCredential().getSubject()) && export.isTrusted())) {
result.status = qr_status.Q_EPERM;
return result;
}

result.sqr_rquota = qfs.setQuota(arg1.sqa_id, arg1.sqa_type, arg1.sqa_dqblk);
result.status = qr_status.Q_OK;
return result;
}

@Override
public setquota_rslt RQUOTAPROC_SETACTIVEQUOTA_2(RpcCall call$, ext_setquota_args arg1) {
throw new UnsupportedOperationException("Not supported yet.");
setquota_rslt result = new setquota_rslt();
String path = "/" + arg1.sqa_pathp; // leading slash is never sent by the client


FsExport export = exportTable.getExport(path, call$.getTransport().getRemoteSocketAddress().getAddress());
if (export == null) {
result.status = qr_status.Q_EPERM;
return result;
}

if (!(UnixSubjects.isRootSubject(call$.getCredential().getSubject()) && export.isTrusted())) {
result.status = qr_status.Q_EPERM;
return result;
}

result.sqr_rquota = qfs.setQuota(arg1.sqa_id, arg1.sqa_type, arg1.sqa_dqblk);
result.status = qr_status.Q_OK;
return result;
}

/**
Expand Down
13 changes: 12 additions & 1 deletion rquota/src/main/java/org/dcache/rquota/QuotaVfs.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package org.dcache.rquota;

import org.dcache.rquota.xdr.rquota;
import org.dcache.rquota.xdr.sq_dqblk;

/**
* Interface for querying quotas.
Expand All @@ -41,7 +42,17 @@ public interface QuotaVfs {
*
* @param id numeric id of user or group to get quota for.
* @param type type of quota to get, either {@link #USER_QUOTA} or {@link #GROUP_QUOTA}.
* @return the quota for the given subject
* @return the quota for the given id and type
*/
rquota getQuota(int id, int type);

/**
* Set the quota for the given id.
*
* @param id numeric id of user or group to set quota for.
* @param type type of quota to set, either {@link #USER_QUOTA} or {@link #GROUP_QUOTA}.
* @param quota the quota to set
* @return the new quota for the given id and type
*/
rquota setQuota(int id, int type, sq_dqblk quota);
}
90 changes: 80 additions & 10 deletions rquota/src/test/java/org/dcache/rquota/QuotaSvcTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,48 @@
import static org.dcache.rquota.QuotaVfs.GROUP_QUOTA;
import static org.dcache.rquota.QuotaVfs.USER_QUOTA;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.dcache.nfs.ExportTable;
import org.dcache.nfs.FsExport;
import org.dcache.nfs.util.UnixSubjects;
import org.dcache.oncrpc4j.rpc.RpcAuth;
import org.dcache.oncrpc4j.rpc.RpcCall;
import org.dcache.oncrpc4j.rpc.RpcTransport;
import org.dcache.rquota.xdr.ext_getquota_args;
import org.dcache.rquota.xdr.ext_setquota_args;
import org.dcache.rquota.xdr.getquota_rslt;
import org.dcache.rquota.xdr.qr_status;
import org.dcache.rquota.xdr.setquota_rslt;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

import java.net.InetSocketAddress;
import java.net.UnknownHostException;

public class QuotaSvcTest {

private QuotaVfs quotaVfs;
private QuotaSvc quotaSvc;
private RpcCall call;
private RpcAuth auth;
private ExportTable exportTable;
private RpcTransport transport;

@Before
public void setUp() {
quotaVfs = mock(QuotaVfs.class);
quotaSvc = new QuotaSvc(quotaVfs);
exportTable = mock(ExportTable.class);
quotaSvc = new QuotaSvc(quotaVfs, exportTable);
call = mock(RpcCall.class);
auth = mock(RpcAuth.class);
when(call.getCredential()).thenReturn(auth);

transport = mock(RpcTransport.class);
when(transport.getRemoteSocketAddress()).thenReturn(new InetSocketAddress(0));
when(call.getTransport()).thenReturn(transport);
}

@Test
Expand All @@ -35,8 +53,7 @@ public void testGetQuotaWrongUser() {
args.gqa_id = 1;
args.gqa_type = USER_QUOTA;

Mockito.when(call.getCredential()).thenReturn(auth);
Mockito.when(auth.getSubject()).thenReturn(UnixSubjects.toSubject(2, 2));
when(auth.getSubject()).thenReturn(UnixSubjects.toSubject(2, 2));
getquota_rslt result = quotaSvc.RQUOTAPROC_GETQUOTA_2(call, args);
assertEquals(qr_status.Q_EPERM, result.status);
}
Expand All @@ -47,8 +64,7 @@ public void testGetQuotaWrongGroup() {
args.gqa_id = 1;
args.gqa_type = GROUP_QUOTA;

Mockito.when(call.getCredential()).thenReturn(auth);
Mockito.when(auth.getSubject()).thenReturn(UnixSubjects.toSubject(2, 2));
when(auth.getSubject()).thenReturn(UnixSubjects.toSubject(2, 2));
getquota_rslt result = quotaSvc.RQUOTAPROC_GETQUOTA_2(call, args);
assertEquals(qr_status.Q_EPERM, result.status);
}
Expand All @@ -59,8 +75,7 @@ public void testGetUserQuota() {
args.gqa_id = 1;
args.gqa_type = USER_QUOTA;

Mockito.when(call.getCredential()).thenReturn(auth);
Mockito.when(auth.getSubject()).thenReturn(UnixSubjects.toSubject(1, 1));
when(auth.getSubject()).thenReturn(UnixSubjects.toSubject(1, 1));
getquota_rslt result = quotaSvc.RQUOTAPROC_GETQUOTA_2(call, args);
assertEquals(qr_status.Q_OK, result.status);
}
Expand All @@ -71,9 +86,64 @@ public void testGetGroupQuota() {
args.gqa_id = 1;
args.gqa_type = GROUP_QUOTA;

Mockito.when(call.getCredential()).thenReturn(auth);
Mockito.when(auth.getSubject()).thenReturn(UnixSubjects.toSubject(1, 1));
when(auth.getSubject()).thenReturn(UnixSubjects.toSubject(1, 1));
getquota_rslt result = quotaSvc.RQUOTAPROC_GETQUOTA_2(call, args);
assertEquals(qr_status.Q_OK, result.status);
}

@Test
public void testSetQuotaNoExport() {
ext_setquota_args args = new ext_setquota_args();
args.sqa_id = 1;
args.sqa_type = GROUP_QUOTA;

when(exportTable.getExport(anyString(), any())).thenReturn(null);
when(auth.getSubject()).thenReturn(UnixSubjects.toSubject(0, 0));
setquota_rslt result = quotaSvc.RQUOTAPROC_SETQUOTA_2(call, args);
assertEquals(qr_status.Q_EPERM, result.status);
}

@Test
public void testSetQuotaNotRoot() {
ext_setquota_args args = new ext_setquota_args();
args.sqa_id = 1;
args.sqa_type = GROUP_QUOTA;

when(exportTable.getExport(anyString(), any())).thenReturn(mock(FsExport.class));
when(auth.getSubject()).thenReturn(UnixSubjects.toSubject(2, 2));
setquota_rslt result = quotaSvc.RQUOTAPROC_SETQUOTA_2(call, args);
assertEquals(qr_status.Q_EPERM, result.status);
}

@Test
public void testSetQuotaRootSquashed() throws UnknownHostException {
ext_setquota_args args = new ext_setquota_args();
args.sqa_id = 1;
args.sqa_type = GROUP_QUOTA;

when(exportTable.getExport(anyString(), any())).thenReturn(new FsExport.FsExportBuilder()
.notTrusted()
.forClient("*")
.build("/"));

when(auth.getSubject()).thenReturn(UnixSubjects.toSubject(0, 0));
setquota_rslt result = quotaSvc.RQUOTAPROC_SETQUOTA_2(call, args);
assertEquals(qr_status.Q_EPERM, result.status);
}

@Test
public void testSetQuotaAsRoot() throws UnknownHostException {
ext_setquota_args args = new ext_setquota_args();
args.sqa_id = 1;
args.sqa_type = GROUP_QUOTA;

when(exportTable.getExport(anyString(), any())).thenReturn(new FsExport.FsExportBuilder()
.trusted()
.forClient("*")
.build("/"));

when(auth.getSubject()).thenReturn(UnixSubjects.toSubject(0, 0));
setquota_rslt result = quotaSvc.RQUOTAPROC_SETQUOTA_2(call, args);
assertEquals(qr_status.Q_OK, result.status);
}
}

0 comments on commit c58a552

Please sign in to comment.