Commit ef1a1a5a authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Handled quota limit on PutFileController

parent 509c8674
Loading
Loading
Loading
Loading
Loading
+26 −8
Original line number Diff line number Diff line
@@ -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(),
+18 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+70 −4
Original line number Diff line number Diff line
@@ -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));