16
16
17
17
package io .aiven .kafka .tieredstorage .storage .s3 ;
18
18
19
- import java .io .ByteArrayInputStream ;
20
19
import java .io .IOException ;
21
- import java .util .ArrayList ;
22
- import java .util .List ;
20
+ import java .util .HashMap ;
21
+ import java .util .Map ;
23
22
import java .util .Random ;
23
+ import java .util .concurrent .ConcurrentHashMap ;
24
+ import java .util .stream .Collectors ;
24
25
25
26
import com .amazonaws .services .s3 .AmazonS3 ;
26
27
import com .amazonaws .services .s3 .model .AbortMultipartUploadRequest ;
@@ -83,7 +84,7 @@ void sendAbortForAnyExceptionWhileWriting() {
83
84
new S3MultiPartOutputStream (BUCKET_NAME , FILE_KEY , 100 , mockedS3 )) {
84
85
out .write (new byte [] {1 , 2 , 3 });
85
86
}
86
- }).isInstanceOf (IOException .class ).hasCause (testException );
87
+ }).isInstanceOf (IOException .class ).hasRootCause (testException );
87
88
88
89
verify (mockedS3 ).initiateMultipartUpload (any (InitiateMultipartUploadRequest .class ));
89
90
verify (mockedS3 ).uploadPart (any (UploadPartRequest .class ));
@@ -101,7 +102,7 @@ void sendAbortForAnyExceptionWhenClose() throws Exception {
101
102
when (mockedS3 .uploadPart (any (UploadPartRequest .class )))
102
103
.thenThrow (RuntimeException .class );
103
104
104
- final S3MultiPartOutputStream out = new S3MultiPartOutputStream (BUCKET_NAME , FILE_KEY , 10 , mockedS3 );
105
+ final var out = new S3MultiPartOutputStream (BUCKET_NAME , FILE_KEY , 10 , mockedS3 );
105
106
106
107
final byte [] buffer = new byte [5 ];
107
108
random .nextBytes (buffer );
@@ -127,110 +128,124 @@ void writesOneByte() throws Exception {
127
128
when (mockedS3 .completeMultipartUpload (completeMultipartUploadRequestCaptor .capture ()))
128
129
.thenReturn (new CompleteMultipartUploadResult ());
129
130
130
- try (final S3MultiPartOutputStream out = new S3MultiPartOutputStream (BUCKET_NAME , FILE_KEY , 100 , mockedS3 )) {
131
+ try (final var out = new S3MultiPartOutputStream (BUCKET_NAME , FILE_KEY , 100 , mockedS3 )) {
131
132
out .write (1 );
132
133
}
133
134
134
135
verify (mockedS3 ).initiateMultipartUpload (any (InitiateMultipartUploadRequest .class ));
135
136
verify (mockedS3 ).uploadPart (any (UploadPartRequest .class ));
136
137
verify (mockedS3 ).completeMultipartUpload (any (CompleteMultipartUploadRequest .class ));
137
138
139
+ final UploadPartRequest value = uploadPartRequestCaptor .getValue ();
138
140
assertUploadPartRequest (
139
- uploadPartRequestCaptor .getValue (),
141
+ value ,
142
+ value .getInputStream ().readAllBytes (),
140
143
1 ,
141
144
1 ,
142
145
new byte [] {1 });
143
146
assertCompleteMultipartUploadRequest (
144
147
completeMultipartUploadRequestCaptor .getValue (),
145
- List .of (new PartETag ( 1 , "SOME_ETAG" ) )
148
+ Map .of (1 , "SOME_ETAG" )
146
149
);
147
150
}
148
151
149
152
@ Test
150
153
void writesMultipleMessages () throws Exception {
151
154
final int bufferSize = 10 ;
152
- final byte [] message = new byte [bufferSize ];
153
155
154
156
when (mockedS3 .initiateMultipartUpload (any (InitiateMultipartUploadRequest .class )))
155
157
.thenReturn (newInitiateMultipartUploadResult ());
156
- when (mockedS3 .uploadPart (uploadPartRequestCaptor .capture ()))
157
- .thenAnswer (a -> {
158
- final UploadPartRequest up = a .getArgument (0 );
158
+
159
+ // capturing requests and contents from concurrent threads
160
+ final Map <Integer , UploadPartRequest > uploadPartRequests = new ConcurrentHashMap <>();
161
+ final Map <Integer , byte []> uploadPartContents = new ConcurrentHashMap <>();
162
+ when (mockedS3 .uploadPart (any (UploadPartRequest .class )))
163
+ .thenAnswer (answer -> {
164
+ final UploadPartRequest up = answer .getArgument (0 );
165
+ //emulate behave of S3 client otherwise we will get wrong array in the memory
166
+ uploadPartRequests .put (up .getPartNumber (), up );
167
+ uploadPartContents .put (up .getPartNumber (), up .getInputStream ().readAllBytes ());
168
+
159
169
return newUploadPartResult (up .getPartNumber (), "SOME_TAG#" + up .getPartNumber ());
160
170
});
161
- when (mockedS3 .completeMultipartUpload (completeMultipartUploadRequestCaptor . capture ( )))
171
+ when (mockedS3 .completeMultipartUpload (any ( CompleteMultipartUploadRequest . class )))
162
172
.thenReturn (new CompleteMultipartUploadResult ());
163
173
164
- final List <byte []> expectedMessagesList = new ArrayList <>();
165
- try (final S3MultiPartOutputStream out =
166
- new S3MultiPartOutputStream (BUCKET_NAME , FILE_KEY , bufferSize , mockedS3 )) {
174
+ final Map <Integer , byte []> expectedMessageParts = new HashMap <>();
175
+ try (final var out = new S3MultiPartOutputStream (BUCKET_NAME , FILE_KEY , bufferSize , mockedS3 )) {
167
176
for (int i = 0 ; i < 3 ; i ++) {
177
+ final byte [] message = new byte [bufferSize ];
168
178
random .nextBytes (message );
169
179
out .write (message , 0 , message .length );
170
- expectedMessagesList . add ( message );
180
+ expectedMessageParts . put ( i + 1 , message );
171
181
}
172
182
}
173
183
174
184
verify (mockedS3 ).initiateMultipartUpload (any (InitiateMultipartUploadRequest .class ));
175
185
verify (mockedS3 , times (3 )).uploadPart (any (UploadPartRequest .class ));
176
186
verify (mockedS3 ).completeMultipartUpload (any (CompleteMultipartUploadRequest .class ));
177
187
178
- final List <UploadPartRequest > uploadRequests = uploadPartRequestCaptor .getAllValues ();
179
- int counter = 0 ;
180
- for (final byte [] expectedMessage : expectedMessagesList ) {
188
+ for (final Integer part : expectedMessageParts .keySet ()) {
181
189
assertUploadPartRequest (
182
- uploadRequests .get (counter ),
190
+ uploadPartRequests .get (part ),
191
+ uploadPartContents .get (part ),
183
192
bufferSize ,
184
- counter + 1 ,
185
- expectedMessage );
186
- counter ++ ;
193
+ part ,
194
+ expectedMessageParts . get ( part )
195
+ ) ;
187
196
}
188
197
assertCompleteMultipartUploadRequest (
189
198
completeMultipartUploadRequestCaptor .getValue (),
190
- List .of (new PartETag ( 1 , "SOME_TAG#1" ) ,
191
- new PartETag ( 2 , "SOME_TAG#2" ) ,
192
- new PartETag ( 3 , "SOME_TAG#3" ) )
199
+ Map .of (1 , "SOME_TAG#1" ,
200
+ 2 , "SOME_TAG#2" ,
201
+ 3 , "SOME_TAG#3" )
193
202
);
194
203
}
195
204
196
205
@ Test
197
206
void writesTailMessages () throws Exception {
198
207
final int messageSize = 20 ;
199
208
200
- final List <UploadPartRequest > uploadPartRequests = new ArrayList <>();
201
-
202
209
when (mockedS3 .initiateMultipartUpload (any (InitiateMultipartUploadRequest .class )))
203
210
.thenReturn (newInitiateMultipartUploadResult ());
211
+
212
+ // capturing requests and contents from concurrent threads
213
+ final Map <Integer , UploadPartRequest > uploadPartRequests = new ConcurrentHashMap <>();
214
+ final Map <Integer , byte []> uploadPartContents = new ConcurrentHashMap <>();
204
215
when (mockedS3 .uploadPart (any (UploadPartRequest .class )))
205
- .thenAnswer (a -> {
206
- final UploadPartRequest up = a .getArgument (0 );
216
+ .thenAnswer (answer -> {
217
+ final UploadPartRequest up = answer .getArgument (0 );
207
218
//emulate behave of S3 client otherwise we will get wrong array in the memory
208
- up . setInputStream ( new ByteArrayInputStream ( up .getInputStream (). readAllBytes ()) );
209
- uploadPartRequests . add (up );
219
+ uploadPartRequests . put ( up .getPartNumber (), up );
220
+ uploadPartContents . put (up . getPartNumber (), up . getInputStream (). readAllBytes () );
210
221
211
222
return newUploadPartResult (up .getPartNumber (), "SOME_TAG#" + up .getPartNumber ());
212
223
});
213
- when (mockedS3 .completeMultipartUpload (completeMultipartUploadRequestCaptor . capture ( )))
224
+ when (mockedS3 .completeMultipartUpload (any ( CompleteMultipartUploadRequest . class )))
214
225
.thenReturn (new CompleteMultipartUploadResult ());
215
226
216
- final byte [] message = new byte [messageSize ];
217
-
218
227
final byte [] expectedFullMessage = new byte [messageSize + 10 ];
219
228
final byte [] expectedTailMessage = new byte [10 ];
220
229
221
- final S3MultiPartOutputStream
222
- out = new S3MultiPartOutputStream (BUCKET_NAME , FILE_KEY , messageSize + 10 , mockedS3 );
223
- random .nextBytes (message );
224
- out .write (message );
225
- System .arraycopy (message , 0 , expectedFullMessage , 0 , message .length );
226
- random .nextBytes (message );
227
- out .write (message );
228
- System .arraycopy (message , 0 , expectedFullMessage , 20 , 10 );
229
- System .arraycopy (message , 10 , expectedTailMessage , 0 , 10 );
230
+ final var out = new S3MultiPartOutputStream (BUCKET_NAME , FILE_KEY , messageSize + 10 , mockedS3 );
231
+ {
232
+ final byte [] message = new byte [messageSize ];
233
+ random .nextBytes (message );
234
+ out .write (message );
235
+ System .arraycopy (message , 0 , expectedFullMessage , 0 , message .length );
236
+ }
237
+ {
238
+ final byte [] message = new byte [messageSize ];
239
+ random .nextBytes (message );
240
+ out .write (message );
241
+ System .arraycopy (message , 0 , expectedFullMessage , 20 , 10 );
242
+ System .arraycopy (message , 10 , expectedTailMessage , 0 , 10 );
243
+ }
230
244
out .close ();
231
245
232
- assertUploadPartRequest (uploadPartRequests .get (0 ), 30 , 1 , expectedFullMessage );
233
- assertUploadPartRequest (uploadPartRequests .get (1 ), 10 , 2 , expectedTailMessage );
246
+ assertThat (uploadPartRequests ).hasSize (2 );
247
+ assertUploadPartRequest (uploadPartRequests .get (1 ), uploadPartContents .get (1 ), 30 , 1 , expectedFullMessage );
248
+ assertUploadPartRequest (uploadPartRequests .get (2 ), uploadPartContents .get (2 ), 10 , 2 , expectedTailMessage );
234
249
235
250
verify (mockedS3 ).initiateMultipartUpload (any (InitiateMultipartUploadRequest .class ));
236
251
verify (mockedS3 , times (2 )).uploadPart (any (UploadPartRequest .class ));
@@ -251,6 +266,7 @@ private static UploadPartResult newUploadPartResult(final int partNumber, final
251
266
}
252
267
253
268
private static void assertUploadPartRequest (final UploadPartRequest uploadPartRequest ,
269
+ final byte [] bytes ,
254
270
final int expectedPartSize ,
255
271
final int expectedPartNumber ,
256
272
final byte [] expectedBytes ) {
@@ -259,23 +275,17 @@ private static void assertUploadPartRequest(final UploadPartRequest uploadPartRe
259
275
assertThat (uploadPartRequest .getPartNumber ()).isEqualTo (expectedPartNumber );
260
276
assertThat (uploadPartRequest .getBucketName ()).isEqualTo (BUCKET_NAME );
261
277
assertThat (uploadPartRequest .getKey ()).isEqualTo (FILE_KEY );
262
- assertThat (uploadPartRequest . getInputStream ()). hasBinaryContent (expectedBytes );
278
+ assertThat (bytes ). isEqualTo (expectedBytes );
263
279
}
264
280
265
281
private static void assertCompleteMultipartUploadRequest (final CompleteMultipartUploadRequest request ,
266
- final List < PartETag > expectedETags ) {
282
+ final Map < Integer , String > expectedETags ) {
267
283
assertThat (request .getBucketName ()).isEqualTo (BUCKET_NAME );
268
284
assertThat (request .getKey ()).isEqualTo (FILE_KEY );
269
285
assertThat (request .getUploadId ()).isEqualTo (UPLOAD_ID );
270
- assertThat (request .getPartETags ()).hasSameSizeAs (expectedETags );
271
-
272
- for (int i = 0 ; i < expectedETags .size (); i ++) {
273
- final PartETag expectedETag = expectedETags .get (i );
274
- final PartETag etag = request .getPartETags ().get (i );
275
-
276
- assertThat (etag .getPartNumber ()).isEqualTo (expectedETag .getPartNumber ());
277
- assertThat (etag .getETag ()).isEqualTo (expectedETag .getETag ());
278
- }
286
+ final Map <Integer , String > tags = request .getPartETags ().stream ()
287
+ .collect (Collectors .toMap (PartETag ::getPartNumber , PartETag ::getETag ));
288
+ assertThat (tags ).containsExactlyInAnyOrderEntriesOf (expectedETags );
279
289
}
280
290
281
291
private static void assertAbortMultipartUploadRequest (final AbortMultipartUploadRequest request ) {
0 commit comments