|
20 | 20 | package org.elasticsearch.repositories.s3; |
21 | 21 |
|
22 | 22 | import com.amazonaws.AmazonClientException; |
23 | | -import com.amazonaws.AmazonServiceException; |
24 | 23 | import com.amazonaws.SdkClientException; |
25 | 24 | import com.amazonaws.services.s3.AbstractAmazonS3; |
26 | 25 | import com.amazonaws.services.s3.model.AmazonS3Exception; |
27 | 26 | import com.amazonaws.services.s3.model.CopyObjectRequest; |
28 | 27 | import com.amazonaws.services.s3.model.CopyObjectResult; |
29 | 28 | import com.amazonaws.services.s3.model.DeleteObjectRequest; |
30 | | -import com.amazonaws.services.s3.model.GetObjectMetadataRequest; |
| 29 | +import com.amazonaws.services.s3.model.DeleteObjectsRequest; |
| 30 | +import com.amazonaws.services.s3.model.DeleteObjectsResult; |
31 | 31 | import com.amazonaws.services.s3.model.GetObjectRequest; |
32 | 32 | import com.amazonaws.services.s3.model.ListObjectsRequest; |
33 | 33 | import com.amazonaws.services.s3.model.ObjectListing; |
|
37 | 37 | import com.amazonaws.services.s3.model.S3Object; |
38 | 38 | import com.amazonaws.services.s3.model.S3ObjectInputStream; |
39 | 39 | import com.amazonaws.services.s3.model.S3ObjectSummary; |
| 40 | +import org.elasticsearch.common.Strings; |
| 41 | +import org.elasticsearch.common.io.Streams; |
40 | 42 |
|
| 43 | +import java.io.ByteArrayInputStream; |
| 44 | +import java.io.ByteArrayOutputStream; |
41 | 45 | import java.io.IOException; |
42 | | -import java.io.InputStream; |
43 | | -import java.io.UncheckedIOException; |
44 | | -import java.net.InetAddress; |
45 | | -import java.net.Socket; |
46 | 46 | import java.util.ArrayList; |
47 | 47 | import java.util.List; |
48 | 48 | import java.util.Map; |
49 | | -import java.util.concurrent.ConcurrentHashMap; |
| 49 | +import java.util.Objects; |
| 50 | +import java.util.concurrent.ConcurrentMap; |
50 | 51 |
|
51 | | -import static org.junit.Assert.assertTrue; |
| 52 | +import static org.hamcrest.MatcherAssert.assertThat; |
| 53 | +import static org.hamcrest.Matchers.equalTo; |
| 54 | +import static org.hamcrest.Matchers.notNullValue; |
| 55 | +import static org.hamcrest.Matchers.nullValue; |
52 | 56 |
|
53 | 57 | class MockAmazonS3 extends AbstractAmazonS3 { |
54 | 58 |
|
55 | | - private final int mockSocketPort; |
56 | | - |
57 | | - private Map<String, InputStream> blobs = new ConcurrentHashMap<>(); |
58 | | - |
59 | | - // in ESBlobStoreContainerTestCase.java, the maximum |
60 | | - // length of the input data is 100 bytes |
61 | | - private byte[] byteCounter = new byte[100]; |
62 | | - |
63 | | - |
64 | | - MockAmazonS3(int mockSocketPort) { |
65 | | - this.mockSocketPort = mockSocketPort; |
| 59 | + private final ConcurrentMap<String, byte[]> blobs; |
| 60 | + private final String bucket; |
| 61 | + private final boolean serverSideEncryption; |
| 62 | + private final String cannedACL; |
| 63 | + private final String storageClass; |
| 64 | + |
| 65 | + MockAmazonS3(final ConcurrentMap<String, byte[]> blobs, |
| 66 | + final String bucket, |
| 67 | + final boolean serverSideEncryption, |
| 68 | + final String cannedACL, |
| 69 | + final String storageClass) { |
| 70 | + this.blobs = Objects.requireNonNull(blobs); |
| 71 | + this.bucket = Objects.requireNonNull(bucket); |
| 72 | + this.serverSideEncryption = serverSideEncryption; |
| 73 | + this.cannedACL = cannedACL; |
| 74 | + this.storageClass = storageClass; |
66 | 75 | } |
67 | 76 |
|
68 | | - // Simulate a socket connection to check that SocketAccess.doPrivileged() is used correctly. |
69 | | - // Any method of AmazonS3 might potentially open a socket to the S3 service. Firstly, a call |
70 | | - // to any method of AmazonS3 has to be wrapped by SocketAccess.doPrivileged(). |
71 | | - // Secondly, each method on the stack from doPrivileged to opening the socket has to be |
72 | | - // located in a jar that is provided by the plugin. |
73 | | - // Thirdly, a SocketPermission has to be configured in plugin-security.policy. |
74 | | - // By opening a socket in each method of MockAmazonS3 it is ensured that in production AmazonS3 |
75 | | - // is able to to open a socket to the S3 Service without causing a SecurityException |
76 | | - private void simulateS3SocketConnection() { |
77 | | - try (Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), mockSocketPort)) { |
78 | | - assertTrue(socket.isConnected()); // NOOP to keep static analysis happy |
79 | | - } catch (IOException e) { |
80 | | - throw new UncheckedIOException(e); |
81 | | - } |
82 | | - } |
83 | | - |
84 | | - |
85 | 77 | @Override |
86 | | - public boolean doesBucketExist(String bucket) { |
87 | | - return true; |
| 78 | + public boolean doesBucketExist(final String bucket) { |
| 79 | + return this.bucket.equalsIgnoreCase(bucket); |
88 | 80 | } |
89 | 81 |
|
90 | 82 | @Override |
91 | | - public boolean doesObjectExist(String bucketName, String objectName) throws AmazonServiceException, SdkClientException { |
92 | | - simulateS3SocketConnection(); |
| 83 | + public boolean doesObjectExist(final String bucketName, final String objectName) throws SdkClientException { |
| 84 | + assertThat(bucketName, equalTo(bucket)); |
93 | 85 | return blobs.containsKey(objectName); |
94 | 86 | } |
95 | 87 |
|
96 | 88 | @Override |
97 | | - public ObjectMetadata getObjectMetadata( |
98 | | - GetObjectMetadataRequest getObjectMetadataRequest) |
99 | | - throws AmazonClientException, AmazonServiceException { |
100 | | - simulateS3SocketConnection(); |
101 | | - String blobName = getObjectMetadataRequest.getKey(); |
102 | | - |
103 | | - if (!blobs.containsKey(blobName)) { |
104 | | - throw new AmazonS3Exception("[" + blobName + "] does not exist."); |
105 | | - } |
106 | | - |
107 | | - return new ObjectMetadata(); // nothing is done with it |
108 | | - } |
109 | | - |
110 | | - @Override |
111 | | - public PutObjectResult putObject(PutObjectRequest putObjectRequest) |
112 | | - throws AmazonClientException, AmazonServiceException { |
113 | | - simulateS3SocketConnection(); |
114 | | - String blobName = putObjectRequest.getKey(); |
115 | | - |
116 | | - if (blobs.containsKey(blobName)) { |
117 | | - throw new AmazonS3Exception("[" + blobName + "] already exists."); |
| 89 | + public PutObjectResult putObject(final PutObjectRequest request) throws AmazonClientException { |
| 90 | + assertThat(request.getBucketName(), equalTo(bucket)); |
| 91 | + assertThat(request.getMetadata().getSSEAlgorithm(), serverSideEncryption ? equalTo("AES256") : nullValue()); |
| 92 | + assertThat(request.getCannedAcl(), notNullValue()); |
| 93 | + assertThat(request.getCannedAcl().toString(), cannedACL != null ? equalTo(cannedACL) : equalTo("private")); |
| 94 | + assertThat(request.getStorageClass(), storageClass != null ? equalTo(storageClass) : equalTo("STANDARD")); |
| 95 | + |
| 96 | + |
| 97 | + final String blobName = request.getKey(); |
| 98 | + final ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| 99 | + try { |
| 100 | + Streams.copy(request.getInputStream(), out); |
| 101 | + blobs.put(blobName, out.toByteArray()); |
| 102 | + } catch (IOException e) { |
| 103 | + throw new AmazonClientException(e); |
118 | 104 | } |
119 | | - |
120 | | - blobs.put(blobName, putObjectRequest.getInputStream()); |
121 | 105 | return new PutObjectResult(); |
122 | 106 | } |
123 | 107 |
|
124 | 108 | @Override |
125 | | - public S3Object getObject(GetObjectRequest getObjectRequest) |
126 | | - throws AmazonClientException, AmazonServiceException { |
127 | | - simulateS3SocketConnection(); |
128 | | - // in ESBlobStoreContainerTestCase.java, the prefix is empty, |
129 | | - // so the key and blobName are equivalent to each other |
130 | | - String blobName = getObjectRequest.getKey(); |
131 | | - |
132 | | - if (!blobs.containsKey(blobName)) { |
133 | | - final AmazonS3Exception e = new AmazonS3Exception("[" + blobName + "] does not exist."); |
134 | | - e.setStatusCode(404); |
135 | | - throw e; |
| 109 | + public S3Object getObject(final GetObjectRequest request) throws AmazonClientException { |
| 110 | + assertThat(request.getBucketName(), equalTo(bucket)); |
| 111 | + |
| 112 | + final String blobName = request.getKey(); |
| 113 | + final byte[] content = blobs.get(blobName); |
| 114 | + if (content == null) { |
| 115 | + AmazonS3Exception exception = new AmazonS3Exception("[" + blobName + "] does not exist."); |
| 116 | + exception.setStatusCode(404); |
| 117 | + throw exception; |
136 | 118 | } |
137 | 119 |
|
138 | | - // the HTTP request attribute is irrelevant for reading |
139 | | - S3ObjectInputStream stream = new S3ObjectInputStream( |
140 | | - blobs.get(blobName), null, false); |
| 120 | + ObjectMetadata metadata = new ObjectMetadata(); |
| 121 | + metadata.setContentLength(content.length); |
| 122 | + |
141 | 123 | S3Object s3Object = new S3Object(); |
142 | | - s3Object.setObjectContent(stream); |
| 124 | + s3Object.setObjectContent(new S3ObjectInputStream(new ByteArrayInputStream(content), null, false)); |
| 125 | + s3Object.setKey(blobName); |
| 126 | + s3Object.setObjectMetadata(metadata); |
| 127 | + |
143 | 128 | return s3Object; |
144 | 129 | } |
145 | 130 |
|
146 | 131 | @Override |
147 | | - public ObjectListing listObjects(ListObjectsRequest listObjectsRequest) |
148 | | - throws AmazonClientException, AmazonServiceException { |
149 | | - simulateS3SocketConnection(); |
150 | | - MockObjectListing list = new MockObjectListing(); |
151 | | - list.setTruncated(false); |
152 | | - |
153 | | - String blobName; |
154 | | - String prefix = listObjectsRequest.getPrefix(); |
155 | | - |
156 | | - ArrayList<S3ObjectSummary> mockObjectSummaries = new ArrayList<>(); |
157 | | - |
158 | | - for (Map.Entry<String, InputStream> blob : blobs.entrySet()) { |
159 | | - blobName = blob.getKey(); |
160 | | - S3ObjectSummary objectSummary = new S3ObjectSummary(); |
161 | | - |
162 | | - if (prefix.isEmpty() || blobName.startsWith(prefix)) { |
163 | | - objectSummary.setKey(blobName); |
164 | | - |
165 | | - try { |
166 | | - objectSummary.setSize(getSize(blob.getValue())); |
167 | | - } catch (IOException e) { |
168 | | - throw new AmazonS3Exception("Object listing " + |
169 | | - "failed for blob [" + blob.getKey() + "]"); |
170 | | - } |
171 | | - |
172 | | - mockObjectSummaries.add(objectSummary); |
| 132 | + public ObjectListing listObjects(final ListObjectsRequest request) throws AmazonClientException { |
| 133 | + assertThat(request.getBucketName(), equalTo(bucket)); |
| 134 | + |
| 135 | + final ObjectListing listing = new ObjectListing(); |
| 136 | + listing.setBucketName(request.getBucketName()); |
| 137 | + listing.setPrefix(request.getPrefix()); |
| 138 | + |
| 139 | + for (Map.Entry<String, byte[]> blob : blobs.entrySet()) { |
| 140 | + if (Strings.isEmpty(request.getPrefix()) || blob.getKey().startsWith(request.getPrefix())) { |
| 141 | + S3ObjectSummary summary = new S3ObjectSummary(); |
| 142 | + summary.setBucketName(request.getBucketName()); |
| 143 | + summary.setKey(blob.getKey()); |
| 144 | + summary.setSize(blob.getValue().length); |
| 145 | + listing.getObjectSummaries().add(summary); |
173 | 146 | } |
174 | 147 | } |
175 | | - |
176 | | - list.setObjectSummaries(mockObjectSummaries); |
177 | | - return list; |
| 148 | + return listing; |
178 | 149 | } |
179 | 150 |
|
180 | 151 | @Override |
181 | | - public CopyObjectResult copyObject(CopyObjectRequest copyObjectRequest) |
182 | | - throws AmazonClientException, AmazonServiceException { |
183 | | - simulateS3SocketConnection(); |
184 | | - String sourceBlobName = copyObjectRequest.getSourceKey(); |
185 | | - String targetBlobName = copyObjectRequest.getDestinationKey(); |
186 | | - |
187 | | - if (!blobs.containsKey(sourceBlobName)) { |
188 | | - final AmazonS3Exception e = new AmazonS3Exception("Source blob [" + |
189 | | - sourceBlobName + "] does not exist."); |
190 | | - e.setStatusCode(404); |
191 | | - throw e; |
192 | | - } |
| 152 | + public CopyObjectResult copyObject(final CopyObjectRequest request) throws AmazonClientException { |
| 153 | + assertThat(request.getSourceBucketName(), equalTo(bucket)); |
| 154 | + assertThat(request.getDestinationBucketName(), equalTo(bucket)); |
193 | 155 |
|
194 | | - if (blobs.containsKey(targetBlobName)) { |
195 | | - throw new AmazonS3Exception("Target blob [" + |
196 | | - targetBlobName + "] already exists."); |
| 156 | + final String sourceBlobName = request.getSourceKey(); |
| 157 | + |
| 158 | + final byte[] content = blobs.get(sourceBlobName); |
| 159 | + if (content == null) { |
| 160 | + AmazonS3Exception exception = new AmazonS3Exception("[" + sourceBlobName + "] does not exist."); |
| 161 | + exception.setStatusCode(404); |
| 162 | + throw exception; |
197 | 163 | } |
198 | 164 |
|
199 | | - blobs.put(targetBlobName, blobs.get(sourceBlobName)); |
200 | | - return new CopyObjectResult(); // nothing is done with it |
| 165 | + blobs.put(request.getDestinationKey(), content); |
| 166 | + return new CopyObjectResult(); |
201 | 167 | } |
202 | 168 |
|
203 | 169 | @Override |
204 | | - public void deleteObject(DeleteObjectRequest deleteObjectRequest) |
205 | | - throws AmazonClientException, AmazonServiceException { |
206 | | - simulateS3SocketConnection(); |
207 | | - String blobName = deleteObjectRequest.getKey(); |
208 | | - |
209 | | - if (!blobs.containsKey(blobName)) { |
210 | | - final AmazonS3Exception e = new AmazonS3Exception("[" + blobName + "] does not exist."); |
211 | | - e.setStatusCode(404); |
212 | | - throw e; |
| 170 | + public void deleteObject(final DeleteObjectRequest request) throws AmazonClientException { |
| 171 | + assertThat(request.getBucketName(), equalTo(bucket)); |
| 172 | + |
| 173 | + final String blobName = request.getKey(); |
| 174 | + if (blobs.remove(blobName) == null) { |
| 175 | + AmazonS3Exception exception = new AmazonS3Exception("[" + blobName + "] does not exist."); |
| 176 | + exception.setStatusCode(404); |
| 177 | + throw exception; |
213 | 178 | } |
214 | | - |
215 | | - blobs.remove(blobName); |
216 | 179 | } |
217 | 180 |
|
218 | | - private int getSize(InputStream stream) throws IOException { |
219 | | - int size = stream.read(byteCounter); |
220 | | - stream.reset(); // in case we ever need the size again |
221 | | - return size; |
222 | | - } |
223 | | - |
224 | | - private class MockObjectListing extends ObjectListing { |
225 | | - // the objectSummaries attribute in ObjectListing.java |
226 | | - // is read-only, but we need to be able to write to it, |
227 | | - // so we create a mock of it to work around this |
228 | | - private List<S3ObjectSummary> mockObjectSummaries; |
229 | | - |
230 | | - @Override |
231 | | - public List<S3ObjectSummary> getObjectSummaries() { |
232 | | - return mockObjectSummaries; |
233 | | - } |
234 | | - |
235 | | - private void setObjectSummaries(List<S3ObjectSummary> objectSummaries) { |
236 | | - mockObjectSummaries = objectSummaries; |
| 181 | + @Override |
| 182 | + public DeleteObjectsResult deleteObjects(DeleteObjectsRequest request) throws SdkClientException { |
| 183 | + assertThat(request.getBucketName(), equalTo(bucket)); |
| 184 | + |
| 185 | + final List<DeleteObjectsResult.DeletedObject> deletions = new ArrayList<>(); |
| 186 | + for (DeleteObjectsRequest.KeyVersion key : request.getKeys()) { |
| 187 | + if (blobs.remove(key.getKey()) == null) { |
| 188 | + AmazonS3Exception exception = new AmazonS3Exception("[" + key + "] does not exist."); |
| 189 | + exception.setStatusCode(404); |
| 190 | + throw exception; |
| 191 | + } else { |
| 192 | + DeleteObjectsResult.DeletedObject deletion = new DeleteObjectsResult.DeletedObject(); |
| 193 | + deletion.setKey(key.getKey()); |
| 194 | + deletions.add(deletion); |
| 195 | + } |
237 | 196 | } |
| 197 | + return new DeleteObjectsResult(deletions); |
238 | 198 | } |
239 | 199 | } |
0 commit comments