s3mock 도입

더보기
더보기

aws 프리티어 계정을 사용하는데 사용량 초과로 문제를 겪고 있었습니다.
이를 위한 해결책을 찾기 위해 s3를 대신하는 s3mock에 대해서 찾아보았고 다음 리파지토리를 발견하였습니다
s3mock 깃허브 링크

적용방법

1. gradle.build의 dependencies에 다음을 추가해 줍니다

implementation 'io.findify:s3mock_2.12:0.2.4'

(현 시점에서 가장 최신버전) 0.2.6버전에서는 서버가 종료되지 않는 문제가 발생하여서 0.2.4 버전을 사용하였습니다.

2. s3mock config파일 추가

EmbeddedS3Config를 test 디렉터리 하위에 추가해줍니다

@Slf4j
@Profile({"test"})
@Configuration
public class EmbeddedS3Config {
    @Value("${aws.s3.region}")
    private String region;
    @Value("${aws.s3.mock.port}")
    private int port;
    private S3Mock s3Mock;
    @Bean
    public S3Mock s3Mock()  {
        s3Mock = new S3Mock.Builder()
                .withPort(port) // 해당 포트에 프로세스가 생성된다.
                .withInMemoryBackend() // 인메모리에서 활성화.
                .build();
        s3Mock.start();
        return s3Mock;
    }
    @PostConstruct
    public void init(){
        port = ProcessUtils.isRunningPort(port) ? ProcessUtils.findAvailableRandomPort() : port;
    }
    @PreDestroy
    public void destroyS3Mock(){
        this.s3Mock.stop();

        log.info("인메모리 S3 Mock 서버가 종료됩니다. port: {}", port);
    }
    @Bean
    public AmazonS3 amazonS3Client() {
        AwsClientBuilder.EndpointConfiguration endpoint = new AwsClientBuilder.EndpointConfiguration(getUri(), region);
        AmazonS3 client =  AmazonS3ClientBuilder
                .standard()
                .withPathStyleAccessEnabled(true)
                .withEndpointConfiguration(endpoint)
                .withCredentials(new AWSStaticCredentialsProvider(new AnonymousAWSCredentials()))
                .build();
        return client;
    }
    private String getUri() {
        return "http://localhost:" + port;
    }
}

3. util파일 추가
사용중이지 않는 port를 찾아주는 ProcessUtil class도 Test 디렉터리 하위에 추가시켜줍니다

public final class ProcessUtils {

    private ProcessUtils() {
    }

    public static boolean isRunningPort(int port) {
        return availablePort(port);
    }

    public static int findAvailableRandomPort() {
        for (int port = 10000; port <= 65535; port++) {
            if (availablePort(port)) {
                return port;
            }
        }
        throw new IllegalArgumentException("사용가능한 포트를 찾을 수 없습니다. (10000 ~ 65535)");
    }

    private static boolean availablePort(int port) {
        try {
            new java.net.Socket("127.0.0.1", port).close();
            return false;
        } catch (IOException e) {
            return true;
        }
    }
}

4. profile 설정
s3Config등의 다른 설정파일은 @Profile("prod")로 설정해주어 test환경에서는 사용되지 않게 설정해줍니다.

 

5. 디렉터리 생성

마지막으로 디렉터리를 테스트 시작전에 생성해주면 올바르게 돌아가는것을 확인할 수 있습니다

결과

  • 사용량 초과로 실패하던 테스트가 100% 성공하였습니다.

한계

s3mock은 실제로 file을 올리는게 아니기 때문에 url도 만들어지지 않습니다.

따라서 다음 s3Uplaoder에 구현되어있는 doesObjectExist 메서드의 경우작동하지 않습니다

    @Override
    public boolean doesObjectExist(String objectUrl) {
        try {
            String key = extractKeyFromUrl(objectUrl);

            return amazonS3Client.doesObjectExist(bucketName, key);
        } catch (RuntimeException e) {
            throw new AmazonClientException("Amazon client exception", e);
        }
    }