Skip to content
Snippets Groups Projects
Commit ef1a1a5a authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Handled quota limit on PutFileController

parent 509c8674
Branches
Tags
No related merge requests found
Pipeline #2031 passed
......@@ -6,6 +6,7 @@
package it.inaf.ia2.transfer.controller;
import it.inaf.ia2.transfer.exception.FileNotFoundException;
import it.inaf.ia2.transfer.exception.InsufficientStorageException;
import it.inaf.ia2.transfer.exception.InvalidArgumentException;
import it.inaf.ia2.transfer.persistence.model.FileInfo;
import it.inaf.ia2.transfer.persistence.FileDAO;
......@@ -64,14 +65,24 @@ public class PutFileController extends FileController {
Optional<FileInfo> optFileInfo = fileDAO.getFileInfo(path);
if (optFileInfo.isPresent()) {
try (InputStream in = file != null ? file.getInputStream() : request.getInputStream()) {
FileInfo fileInfo = optFileInfo.get();
String parentPath = fileInfo.getVirtualPath().substring(0, fileInfo.getVirtualPath().lastIndexOf("/"));
Long remainingQuota = fileDAO.getRemainingQuota(parentPath);
// if MultipartFile provides file size it is possible to check
// quota limit before reading the stream
if (remainingQuota != null && file != null && file.getSize() > remainingQuota) {
throw new InsufficientStorageException(fileInfo.getVirtualPath());
}
if (file != null) {
fileInfo.setContentType(file.getContentType());
}
fileInfo.setContentEncoding(contentEncoding);
storeGenericFile(fileInfo, in, jobId);
try (InputStream in = file != null ? file.getInputStream() : request.getInputStream()) {
storeGenericFile(fileInfo, in, jobId, remainingQuota);
} catch (IOException | NoSuchAlgorithmException ex) {
throw new RuntimeException(ex);
}
......@@ -81,7 +92,7 @@ public class PutFileController extends FileController {
}, jobId);
}
private void storeGenericFile(FileInfo fileInfo, InputStream is, String jobId) throws IOException, NoSuchAlgorithmException {
private void storeGenericFile(FileInfo fileInfo, InputStream is, String jobId, Long remainingQuota) throws IOException, NoSuchAlgorithmException {
File file = new File(fileInfo.getOsPath());
......@@ -112,6 +123,13 @@ public class PutFileController extends FileController {
}
Long fileSize = Files.size(file.toPath());
// Quota limit is checked again to handle cases where MultipartFile is not used
if (remainingQuota != null && fileSize > remainingQuota) {
file.delete();
throw new InsufficientStorageException(fileInfo.getVirtualPath());
}
String md5Checksum = makeMD5Checksum(file);
fileDAO.updateFileAttributes(fileInfo.getNodeId(),
......
/*
* This file is part of vospace-file-service
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package it.inaf.ia2.transfer.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.INSUFFICIENT_STORAGE)
public class InsufficientStorageException extends JobException {
public InsufficientStorageException(String path) {
super(Type.FATAL, "Quota Exceeded");
setErrorDetail("QuotaExceeded Path: " + path);
}
}
......@@ -5,14 +5,18 @@
*/
package it.inaf.ia2.transfer.controller;
import it.inaf.ia2.transfer.exception.InsufficientStorageException;
import it.inaf.ia2.transfer.persistence.model.FileInfo;
import it.inaf.ia2.transfer.persistence.FileDAO;
import it.inaf.ia2.transfer.persistence.JobDAO;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Optional;
import java.util.UUID;
import javax.servlet.ServletInputStream;
import net.ivoa.xml.uws.v1.ExecutionPhase;
import org.assertj.core.util.Files;
import org.junit.jupiter.api.AfterAll;
......@@ -22,6 +26,8 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import org.mockito.Mockito;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
......@@ -70,6 +76,8 @@ public class PutFileControllerTest {
@Test
public void putGenericFile() throws Exception {
when(fileDao.getRemainingQuota(any())).thenReturn(null);
String randomFileName = UUID.randomUUID().toString();
createBaseFileInfo(randomFileName);
......@@ -99,6 +107,8 @@ public class PutFileControllerTest {
private void putGenericFileWithNameConflict(String name1, String name2, String name3) throws Exception {
when(fileDao.getRemainingQuota(any())).thenReturn(null);
createBaseFileInfo(name1);
MockMultipartFile fakeFile = new MockMultipartFile("file", "test.txt", "text/plain", "content".getBytes());
......@@ -143,6 +153,8 @@ public class PutFileControllerTest {
@Test
public void putGenericFileWithJobId() throws Exception {
when(fileDao.getRemainingQuota(any())).thenReturn(null);
when(jobDAO.isJobExisting("pippo10")).thenReturn(false);
when(jobDAO.isJobExisting("pippo5")).thenReturn(true);
......@@ -222,6 +234,59 @@ public class PutFileControllerTest {
verify(jobDAO, times(1)).setJobError(eq("abcdef"), any());
}
@Test
public void testQuotaExceededMultipart() throws Exception {
when(fileDao.getRemainingQuota(eq("/path/to"))).thenReturn(0l);
createBaseFileInfo();
MockMultipartFile fakeFile = new MockMultipartFile("file", "test.txt", null, "content".getBytes());
Exception ex = mockMvc.perform(putMultipart("/path/to/test.txt")
.file(fakeFile))
.andDo(print())
.andExpect(status().is5xxServerError())
.andReturn().getResolvedException();
verify(fileDao, times(1)).getRemainingQuota(eq("/path/to"));
assertTrue(ex instanceof InsufficientStorageException);
}
@Test
public void testQuotaExceededStream() throws Exception {
when(fileDao.getRemainingQuota(eq("/path/to"))).thenReturn(0l);
createBaseFileInfo();
MockHttpServletRequestBuilder streamBuilder = put("/path/to/test.txt");
streamBuilder.with(new RequestPostProcessor() {
@Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
MockHttpServletRequest spyRequest = spy(request);
ByteArrayInputStream bais = new ByteArrayInputStream("some data".getBytes());
ServletInputStream sis = mock(ServletInputStream.class);
try {
when(sis.transferTo(any())).thenAnswer(i -> bais.transferTo(i.getArgument(0)));
} catch (IOException ex) {
}
Mockito.doReturn(sis).when(spyRequest).getInputStream();
return spyRequest;
}
});
Exception ex = mockMvc.perform(streamBuilder)
.andDo(print())
.andExpect(status().is5xxServerError())
.andReturn().getResolvedException();
verify(fileDao, times(1)).getRemainingQuota(eq("/path/to"));
assertTrue(ex instanceof InsufficientStorageException);
}
private FileInfo createBaseFileInfo() {
String randomFileName = UUID.randomUUID().toString();
return createBaseFileInfo(randomFileName);
......@@ -230,6 +295,7 @@ public class PutFileControllerTest {
private FileInfo createBaseFileInfo(String fileName) {
FileInfo fileInfo = new FileInfo();
fileInfo.setOsPath(getTestFilePath(fileName));
fileInfo.setVirtualPath("/path/to/" + fileName);
fileInfo.setPublic(false);
when(fileDao.getFileInfo(any())).thenReturn(Optional.of(fileInfo));
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment