Skip to content

Commit e2f556c

Browse files
authored
[backend] fix(multitenancy): fix incorrect minio migration (#109)
1 parent 9a3e8f8 commit e2f556c

9 files changed

Lines changed: 205 additions & 32 deletions

File tree

.drone.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ steps:
1717
SPRING_FLYWAY_USER: openaev
1818
SPRING_FLYWAY_PASSWORD: openaev
1919
MINIO_ENDPOINT: minio
20-
MINIO_PORT: 9000
20+
MINIO_PORT: 11000
2121
ENGINE_URL: http://elastic:9200
2222
OPENBAS_RABBITMQ_HOSTNAME: rabbitmq
2323
commands:
@@ -53,7 +53,7 @@ steps:
5353
SPRING_DATASOURCE_USERNAME: openaev
5454
SPRING_DATASOURCE_PASSWORD: openaev
5555
MINIO_ENDPOINT: minio-e2e
56-
MINIO_PORT: 9000
56+
MINIO_PORT: 11000
5757
ENGINE_URL: http://elastic:9200
5858
MINIO_ACCESS_KEY: minioadmin
5959
MINIO_ACCESS_SECRET: minioadmin
@@ -180,7 +180,7 @@ services:
180180
environment:
181181
MINIO_ROOT_USER: minioadmin
182182
MINIO_ROOT_PASSWORD: minioadmin
183-
command: [server, /data]
183+
command: [server, /data, --address, ":11000"]
184184
- name: pgsql
185185
image: postgres:17-alpine
186186
environment:
@@ -199,7 +199,7 @@ services:
199199
environment:
200200
MINIO_ROOT_USER: minioadmin
201201
MINIO_ROOT_PASSWORD: minioadmin
202-
command: [server, /data]
202+
command: [server, /data, --address, ":11000"]
203203
- name: pgsql-e2e
204204
image: postgres:17-alpine
205205
environment:

.github/actions/api-tests/action.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ runs:
4949
-e RABBITMQ_DEFAULT_PASS=guest \
5050
rabbitmq:4.1-management
5151
docker run -d --name minio \
52-
-p 9000:9000 \
52+
-p 11000:9000 \
5353
-e MINIO_ROOT_USER=minioadmin \
5454
-e MINIO_ROOT_PASSWORD=minioadmin \
5555
minio/minio:RELEASE.2025-06-13T11-33-47Z server /data
@@ -193,10 +193,10 @@ runs:
193193
194194
echo "⏳ Waiting for MinIO..."
195195
for i in $(seq 1 30); do
196-
curl -sf http://localhost:9000/minio/health/live && break
196+
curl -sf http://localhost:11000/minio/health/live && break
197197
sleep 1
198198
done
199-
curl -sf http://localhost:9000/minio/health/live || { echo "❌ MinIO failed to start"; exit 1; }
199+
curl -sf http://localhost:11000/minio/health/live || { echo "❌ MinIO failed to start"; exit 1; }
200200
201201
echo "✅ All services ready"
202202

.github/actions/e2e-tests/action.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ runs:
4444
-e RABBITMQ_DEFAULT_PASS=guest \
4545
rabbitmq:4.1-management
4646
docker run -d --name minio \
47-
-p 9000:9000 \
47+
-p 11000:9000 \
4848
-e MINIO_ROOT_USER=minioadmin \
4949
-e MINIO_ROOT_PASSWORD=minioadmin \
5050
minio/minio:RELEASE.2025-06-13T11-33-47Z server /data
@@ -175,10 +175,10 @@ runs:
175175
176176
echo "⏳ Waiting for MinIO..."
177177
for i in $(seq 1 30); do
178-
curl -sf http://localhost:9000/minio/health/live && break
178+
curl -sf http://localhost:11000/minio/health/live && break
179179
sleep 1
180180
done
181-
curl -sf http://localhost:9000/minio/health/live || { echo "❌ MinIO failed to start"; exit 1; }
181+
curl -sf http://localhost:11000/minio/health/live || { echo "❌ MinIO failed to start"; exit 1; }
182182
183183
echo "✅ All services ready"
184184

.github/workflows/_quality-gates.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ jobs:
100100
SPRING_DATASOURCE_USERNAME: openaev
101101
SPRING_DATASOURCE_PASSWORD: openaev
102102
MINIO_ENDPOINT: localhost
103-
MINIO_PORT: 9000
103+
MINIO_PORT: 11000
104104
ENGINE_URL: http://localhost:9200
105105
OPENBAS_RABBITMQ_HOSTNAME: localhost
106106
GH_TOKEN: ${{ github.token }}
@@ -122,7 +122,7 @@ jobs:
122122
SPRING_DATASOURCE_USERNAME: openaev
123123
SPRING_DATASOURCE_PASSWORD: openaev
124124
MINIO_ENDPOINT: localhost
125-
MINIO_PORT: 9000
125+
MINIO_PORT: 11000
126126
MINIO_ACCESS_KEY: minioadmin
127127
MINIO_ACCESS_SECRET: minioadmin
128128
ENGINE_URL: http://localhost:9200

.github/workflows/nightly-ci.yml

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ jobs:
8383
SPRING_DATASOURCE_USERNAME: openaev
8484
SPRING_DATASOURCE_PASSWORD: openaev
8585
MINIO_ENDPOINT: localhost
86-
MINIO_PORT: 9000
86+
MINIO_PORT: 11000
8787
ENGINE_URL: http://localhost:9200
8888
OPENBAS_RABBITMQ_HOSTNAME: localhost
8989
steps:
@@ -99,17 +99,17 @@ jobs:
9999
- name: Start MinIO
100100
run: |
101101
docker run -d --name minio \
102-
-p 9000:9000 \
102+
-p 11000:9000 \
103103
-e MINIO_ROOT_USER=minioadmin \
104104
-e MINIO_ROOT_PASSWORD=minioadmin \
105105
minio/minio:RELEASE.2025-06-13T11-33-47Z server /data
106106
107107
echo "⏳ Waiting for MinIO..."
108108
for i in $(seq 1 30); do
109-
curl -sf http://localhost:9000/minio/health/live && break
109+
curl -sf http://localhost:11000/minio/health/live && break
110110
sleep 1
111111
done
112-
curl -sf http://localhost:9000/minio/health/live || { echo "❌ MinIO failed to start"; exit 1; }
112+
curl -sf http://localhost:11000/minio/health/live || { echo "❌ MinIO failed to start"; exit 1; }
113113
echo "✅ MinIO ready"
114114
115115
- name: Build all modules (skip tests)
@@ -172,7 +172,7 @@ jobs:
172172
SPRING_DATASOURCE_USERNAME: openaev
173173
SPRING_DATASOURCE_PASSWORD: openaev
174174
MINIO_ENDPOINT: localhost
175-
MINIO_PORT: 9000
175+
MINIO_PORT: 11000
176176
ENGINE_URL: http://localhost:9200
177177
ENGINE_ENGINESELECTOR: opensearch
178178
OPENBAS_RABBITMQ_HOSTNAME: localhost
@@ -189,17 +189,17 @@ jobs:
189189
- name: Start MinIO
190190
run: |
191191
docker run -d --name minio \
192-
-p 9000:9000 \
192+
-p 11000:9000 \
193193
-e MINIO_ROOT_USER=minioadmin \
194194
-e MINIO_ROOT_PASSWORD=minioadmin \
195195
minio/minio:RELEASE.2025-06-13T11-33-47Z server /data
196196
197197
echo "⏳ Waiting for MinIO..."
198198
for i in $(seq 1 30); do
199-
curl -sf http://localhost:9000/minio/health/live && break
199+
curl -sf http://localhost:11000/minio/health/live && break
200200
sleep 1
201201
done
202-
curl -sf http://localhost:9000/minio/health/live || { echo "❌ MinIO failed to start"; exit 1; }
202+
curl -sf http://localhost:11000/minio/health/live || { echo "❌ MinIO failed to start"; exit 1; }
203203
echo "✅ MinIO ready"
204204
205205
- name: Build all modules (skip tests)
@@ -278,7 +278,7 @@ jobs:
278278
SPRING_DATASOURCE_USERNAME: openaev
279279
SPRING_DATASOURCE_PASSWORD: openaev
280280
MINIO_ENDPOINT: localhost
281-
MINIO_PORT: 9000
281+
MINIO_PORT: 11000
282282
MINIO_ACCESS_KEY: minioadmin
283283
MINIO_ACCESS_SECRET: minioadmin
284284
ENGINE_URL: http://localhost:9200
@@ -319,15 +319,15 @@ jobs:
319319
- name: Start MinIO
320320
run: |
321321
docker run -d --name minio \
322-
-p 9000:9000 \
322+
-p 11000:9000 \
323323
-e MINIO_ROOT_USER=minioadmin \
324324
-e MINIO_ROOT_PASSWORD=minioadmin \
325325
minio/minio:RELEASE.2025-06-13T11-33-47Z server /data
326326
for i in $(seq 1 30); do
327-
curl -sf http://localhost:9000/minio/health/live && break
327+
curl -sf http://localhost:11000/minio/health/live && break
328328
sleep 1
329329
done
330-
curl -sf http://localhost:9000/minio/health/live || { echo "❌ MinIO failed"; exit 1; }
330+
curl -sf http://localhost:11000/minio/health/live || { echo "❌ MinIO failed"; exit 1; }
331331
332332
- name: Set up Java 21
333333
uses: actions/setup-java@v5
@@ -421,7 +421,7 @@ jobs:
421421
echo "── Service connectivity ──"
422422
echo -n " PostgreSQL (5432): "; curl -sf http://localhost:5432 2>&1 | head -1 || pg_isready -h localhost -p 5432 2>&1 || echo "unreachable"
423423
echo -n " Elasticsearch/OpenSearch (9200): "; curl -sf http://localhost:9200 | head -1 || echo "unreachable"
424-
echo -n " MinIO (9000): "; curl -sf http://localhost:9000/minio/health/live && echo "ok" || echo "unreachable"
424+
echo -n " MinIO (11000): "; curl -sf http://localhost:11000/minio/health/live && echo "ok" || echo "unreachable"
425425
echo -n " RabbitMQ (5672): "; nc -z localhost 5672 2>&1 && echo "ok" || echo "unreachable"
426426
427427
echo ""
@@ -515,7 +515,7 @@ jobs:
515515
SPRING_DATASOURCE_USERNAME: openaev
516516
SPRING_DATASOURCE_PASSWORD: openaev
517517
MINIO_ENDPOINT: localhost
518-
MINIO_PORT: 9000
518+
MINIO_PORT: 11000
519519
MINIO_ACCESS_KEY: minioadmin
520520
MINIO_ACCESS_SECRET: minioadmin
521521
ENGINE_URL: http://localhost:9200
@@ -554,15 +554,15 @@ jobs:
554554
- name: Start MinIO
555555
run: |
556556
docker run -d --name minio \
557-
-p 9000:9000 \
557+
-p 11000:9000 \
558558
-e MINIO_ROOT_USER=minioadmin \
559559
-e MINIO_ROOT_PASSWORD=minioadmin \
560560
minio/minio:RELEASE.2025-06-13T11-33-47Z server /data
561561
for i in $(seq 1 30); do
562-
curl -sf http://localhost:9000/minio/health/live && break
562+
curl -sf http://localhost:11000/minio/health/live && break
563563
sleep 1
564564
done
565-
curl -sf http://localhost:9000/minio/health/live || { echo "❌ MinIO failed"; exit 1; }
565+
curl -sf http://localhost:11000/minio/health/live || { echo "❌ MinIO failed"; exit 1; }
566566
567567
- name: Set up Java 21
568568
uses: actions/setup-java@v5
@@ -655,7 +655,7 @@ jobs:
655655
echo "── Service connectivity ──"
656656
echo -n " PostgreSQL (5432): "; curl -sf http://localhost:5432 2>&1 | head -1 || pg_isready -h localhost -p 5432 2>&1 || echo "unreachable"
657657
echo -n " Elasticsearch/OpenSearch (9200): "; curl -sf http://localhost:9200 | head -1 || echo "unreachable"
658-
echo -n " MinIO (9000): "; curl -sf http://localhost:9000/minio/health/live && echo "ok" || echo "unreachable"
658+
echo -n " MinIO (11000): "; curl -sf http://localhost:11000/minio/health/live && echo "ok" || echo "unreachable"
659659
echo -n " RabbitMQ (5672): "; nc -z localhost 5672 2>&1 && echo "ok" || echo "unreachable"
660660
661661
echo ""
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package io.openaev.driver;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import io.minio.*;
6+
import io.minio.messages.Item;
7+
import io.openaev.IntegrationTest;
8+
import io.openaev.config.MinioConfig;
9+
import io.openaev.config.S3Config;
10+
import io.openaev.database.model.Tenant;
11+
import io.openaev.utils.mockUser.WithMockUser;
12+
import java.io.ByteArrayInputStream;
13+
import java.nio.charset.StandardCharsets;
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
import java.util.UUID;
17+
import org.junit.jupiter.api.*;
18+
import org.springframework.beans.factory.annotation.Autowired;
19+
import org.springframework.transaction.annotation.Transactional;
20+
21+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
22+
@Transactional
23+
@DisplayName("MinioDriver")
24+
@WithMockUser
25+
class MinioDriverTest extends IntegrationTest {
26+
27+
@Autowired private MinioDriver minioDriver;
28+
@Autowired private MinioConfig minioConfig;
29+
@Autowired private S3Config s3Config;
30+
31+
private MinioClient minioClient;
32+
33+
@BeforeAll
34+
void setUpAll() {
35+
minioClient = minioDriver.getMinioClient();
36+
}
37+
38+
@Nested
39+
@DisplayName("getMinioClient")
40+
class GetMinioClient {
41+
42+
@Test
43+
@DisplayName("Given valid config, should return a non-null MinioClient")
44+
void given_validConfig_should_returnNonNullClient() {
45+
// -- ARRANGE --
46+
// Config is provided by test application.properties (openaev-test-minio on port 11000)
47+
48+
// -- ACT --
49+
MinioClient result = minioDriver.getMinioClient();
50+
51+
// -- ASSERT --
52+
assertThat(result).isNotNull();
53+
}
54+
}
55+
56+
@Nested
57+
@DisplayName("minioClient bean")
58+
class MinioClientBean {
59+
60+
@Test
61+
@DisplayName("Given application started, should have created the bucket")
62+
void given_applicationStarted_should_haveCreatedBucket() throws Exception {
63+
// -- ARRANGE --
64+
String bucket = minioConfig.getBucket();
65+
66+
// -- ACT --
67+
boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
68+
69+
// -- ASSERT --
70+
assertThat(exists).isTrue();
71+
}
72+
73+
@Test
74+
@DisplayName("Given root-level file in bucket, should migrate it to default tenant path")
75+
void given_rootLevelFile_should_migrateToDefaultTenantPath() throws Exception {
76+
// -- ARRANGE --
77+
String bucket = minioConfig.getBucket();
78+
String rootFileName = "test-migrate-" + UUID.randomUUID() + ".txt";
79+
byte[] content = "test content".getBytes(StandardCharsets.UTF_8);
80+
81+
minioClient.putObject(
82+
PutObjectArgs.builder().bucket(bucket).object(rootFileName).stream(
83+
new ByteArrayInputStream(content), content.length, -1)
84+
.contentType("text/plain")
85+
.build());
86+
87+
// -- ACT --
88+
// Trigger the migration logic via minioClient() bean creation
89+
MinioDriver freshDriver = new MinioDriver(minioConfig, s3Config, tenantRepository);
90+
freshDriver.minioClient();
91+
92+
// -- ASSERT --
93+
String expectedPath = Tenant.DEFAULT_TENANT_UUID + "/" + rootFileName;
94+
StatObjectResponse stat =
95+
minioClient.statObject(
96+
StatObjectArgs.builder().bucket(bucket).object(expectedPath).build());
97+
assertThat(stat).isNotNull();
98+
assertThat(stat.size()).isEqualTo(content.length);
99+
100+
// Cleanup
101+
minioClient.removeObject(
102+
RemoveObjectArgs.builder().bucket(bucket).object(expectedPath).build());
103+
}
104+
105+
@Test
106+
@DisplayName("Given file already under tenant path, should not move it")
107+
void given_fileUnderTenantPath_should_notMoveIt() throws Exception {
108+
// -- ARRANGE --
109+
String bucket = minioConfig.getBucket();
110+
String tenantId = Tenant.DEFAULT_TENANT_UUID;
111+
String fileName = tenantId + "/test-no-move-" + UUID.randomUUID() + ".txt";
112+
byte[] content = "tenant content".getBytes(StandardCharsets.UTF_8);
113+
114+
minioClient.putObject(
115+
PutObjectArgs.builder().bucket(bucket).object(fileName).stream(
116+
new ByteArrayInputStream(content), content.length, -1)
117+
.contentType("text/plain")
118+
.build());
119+
120+
// -- ACT --
121+
MinioDriver freshDriver = new MinioDriver(minioConfig, s3Config, tenantRepository);
122+
freshDriver.minioClient();
123+
124+
// -- ASSERT --
125+
// File should still be at original path (not double-nested)
126+
StatObjectResponse stat =
127+
minioClient.statObject(StatObjectArgs.builder().bucket(bucket).object(fileName).build());
128+
assertThat(stat).isNotNull();
129+
130+
// Verify no double-nested copy was created
131+
String doubleNested = Tenant.DEFAULT_TENANT_UUID + "/" + fileName;
132+
List<String> objects = new ArrayList<>();
133+
for (Result<Item> r :
134+
minioClient.listObjects(
135+
ListObjectsArgs.builder().bucket(bucket).prefix(doubleNested).build())) {
136+
objects.add(r.get().objectName());
137+
}
138+
assertThat(objects).isEmpty();
139+
140+
// Cleanup
141+
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucket).object(fileName).build());
142+
}
143+
}
144+
}

openaev-api/src/test/resources/application.properties

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,12 @@ engine.url=http://localhost:9201
9494

9595
# Minio Properties
9696
minio.endpoint=localhost
97-
minio.port=10000
97+
minio.port=11000
9898
minio.bucket=openaev
9999
minio.access-key=minioadmin
100100
minio.access-secret=minioadmin
101+
openaev.s3.use-aws-role=false
102+
openaev.s3.sts-endpoint=
101103

102104
# Telemetry
103105
telemetry.oaev.endpoint=http://localhost

0 commit comments

Comments
 (0)