• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package utils.test.util;
17 
18 import org.slf4j.Logger;
19 import org.slf4j.LoggerFactory;
20 import software.amazon.awssdk.core.exception.SdkClientException;
21 import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
22 import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
23 import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest;
24 import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest;
25 import software.amazon.awssdk.services.dynamodb.model.ResourceInUseException;
26 import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException;
27 import software.amazon.awssdk.services.dynamodb.model.TableDescription;
28 import software.amazon.awssdk.services.dynamodb.model.TableStatus;
29 
30 /**
31  * Utility methods for working with DynamoDB tables.
32  *
33  * <pre class="brush: java">
34  * // ... create DynamoDB table ...
35  * try {
36  *     waitUntilActive(dynamoDB, myTableName());
37  * } catch (SdkClientException e) {
38  *     // table didn't become active
39  * }
40  * // ... start making calls to table ...
41  * </pre>
42  */
43 public class TableUtils {
44 
45     private static final int DEFAULT_WAIT_TIMEOUT = 20 * 60 * 1000;
46     private static final int DEFAULT_WAIT_INTERVAL = 10 * 1000;
47     /**
48      * The logging utility.
49      */
50     private static final Logger log = LoggerFactory.getLogger(TableUtils.class);
51 
52     /**
53      * Waits up to 10 minutes for a specified DynamoDB table to resolve,
54      * indicating that it exists. If the table doesn't return a result after
55      * this time, a SdkClientException is thrown.
56      *
57      * @param dynamo
58      *            The DynamoDB client to use to make requests.
59      * @param tableName
60      *            The name of the table being resolved.
61      *
62      * @throws SdkClientException
63      *             If the specified table does not resolve before this method
64      *             times out and stops polling.
65      * @throws InterruptedException
66      *             If the thread is interrupted while waiting for the table to
67      *             resolve.
68      */
waitUntilExists(final DynamoDbClient dynamo, final String tableName)69     public static void waitUntilExists(final DynamoDbClient dynamo, final String tableName)
70             throws InterruptedException {
71         waitUntilExists(dynamo, tableName, DEFAULT_WAIT_TIMEOUT, DEFAULT_WAIT_INTERVAL);
72     }
73 
74     /**
75      * Waits up to a specified amount of time for a specified DynamoDB table to
76      * resolve, indicating that it exists. If the table doesn't return a result
77      * after this time, a SdkClientException is thrown.
78      *
79      * @param dynamo
80      *            The DynamoDB client to use to make requests.
81      * @param tableName
82      *            The name of the table being resolved.
83      * @param timeout
84      *            The maximum number of milliseconds to wait.
85      * @param interval
86      *            The poll interval in milliseconds.
87      *
88      * @throws SdkClientException
89      *             If the specified table does not resolve before this method
90      *             times out and stops polling.
91      * @throws InterruptedException
92      *             If the thread is interrupted while waiting for the table to
93      *             resolve.
94      */
waitUntilExists(final DynamoDbClient dynamo, final String tableName, final int timeout, final int interval)95     public static void waitUntilExists(final DynamoDbClient dynamo, final String tableName, final int timeout,
96                                        final int interval) throws InterruptedException {
97         TableDescription table = waitForTableDescription(dynamo, tableName, null, timeout, interval);
98 
99         if (table == null) {
100             throw SdkClientException.builder().message("Table " + tableName + " never returned a result").build();
101         }
102     }
103 
104     /**
105      * Waits up to 10 minutes for a specified DynamoDB table to move into the
106      * <code>ACTIVE</code> state. If the table does not exist or does not
107      * transition to the <code>ACTIVE</code> state after this time, then
108      * SdkClientException is thrown.
109      *
110      * @param dynamo
111      *            The DynamoDB client to use to make requests.
112      * @param tableName
113      *            The name of the table whose status is being checked.
114      *
115      * @throws TableNeverTransitionedToStateException
116      *             If the specified table does not exist or does not transition
117      *             into the <code>ACTIVE</code> state before this method times
118      *             out and stops polling.
119      * @throws InterruptedException
120      *             If the thread is interrupted while waiting for the table to
121      *             transition into the <code>ACTIVE</code> state.
122      */
waitUntilActive(final DynamoDbClient dynamo, final String tableName)123     public static void waitUntilActive(final DynamoDbClient dynamo, final String tableName)
124             throws InterruptedException, TableNeverTransitionedToStateException {
125         waitUntilActive(dynamo, tableName, DEFAULT_WAIT_TIMEOUT, DEFAULT_WAIT_INTERVAL);
126     }
127 
128     /**
129      * Waits up to a specified amount of time for a specified DynamoDB table to
130      * move into the <code>ACTIVE</code> state. If the table does not exist or
131      * does not transition to the <code>ACTIVE</code> state after this time,
132      * then a SdkClientException is thrown.
133      *
134      * @param dynamo
135      *            The DynamoDB client to use to make requests.
136      * @param tableName
137      *            The name of the table whose status is being checked.
138      * @param timeout
139      *            The maximum number of milliseconds to wait.
140      * @param interval
141      *            The poll interval in milliseconds.
142      *
143      * @throws TableNeverTransitionedToStateException
144      *             If the specified table does not exist or does not transition
145      *             into the <code>ACTIVE</code> state before this method times
146      *             out and stops polling.
147      * @throws InterruptedException
148      *             If the thread is interrupted while waiting for the table to
149      *             transition into the <code>ACTIVE</code> state.
150      */
waitUntilActive(final DynamoDbClient dynamo, final String tableName, final int timeout, final int interval)151     public static void waitUntilActive(final DynamoDbClient dynamo, final String tableName, final int timeout,
152                                        final int interval) throws InterruptedException, TableNeverTransitionedToStateException {
153         TableDescription table = waitForTableDescription(dynamo, tableName, TableStatus.ACTIVE, timeout, interval);
154 
155         if (table == null || !table.tableStatus().equals(TableStatus.ACTIVE)) {
156             throw new TableNeverTransitionedToStateException(tableName, TableStatus.ACTIVE);
157         }
158     }
159 
160     /**
161      * Wait for the table to reach the desired status and returns the table
162      * description
163      *
164      * @param dynamo
165      *            Dynamo client to use
166      * @param tableName
167      *            Table name to poll status of
168      * @param desiredStatus
169      *            Desired {@link TableStatus} to wait for. If null this method
170      *            simply waits until DescribeTable returns something non-null
171      *            (i.e. any status)
172      * @param timeout
173      *            Timeout in milliseconds to continue to poll for desired status
174      * @param interval
175      *            Time to wait in milliseconds between poll attempts
176      * @return Null if DescribeTables never returns a result, otherwise the
177      *         result of the last poll attempt (which may or may not have the
178      *         desired state)
179      * @throws {@link
180      *             IllegalArgumentException} If timeout or interval is invalid
181      */
waitForTableDescription(final DynamoDbClient dynamo, final String tableName, TableStatus desiredStatus, final int timeout, final int interval)182     private static TableDescription waitForTableDescription(final DynamoDbClient dynamo, final String tableName,
183                                                             TableStatus desiredStatus, final int timeout, final int interval)
184             throws InterruptedException, IllegalArgumentException {
185         if (timeout < 0) {
186             throw new IllegalArgumentException("Timeout must be >= 0");
187         }
188         if (interval <= 0 || interval >= timeout) {
189             throw new IllegalArgumentException("Interval must be > 0 and < timeout");
190         }
191         long startTime = System.currentTimeMillis();
192         long endTime = startTime + timeout;
193 
194         TableDescription table = null;
195         while (System.currentTimeMillis() < endTime) {
196             try {
197                 table = dynamo.describeTable(DescribeTableRequest.builder().tableName(tableName).build()).table();
198                 if (desiredStatus == null || table.tableStatus().equals(desiredStatus)) {
199                     return table;
200 
201                 }
202             } catch (ResourceNotFoundException rnfe) {
203                 // ResourceNotFound means the table doesn't exist yet,
204                 // so ignore this error and just keep polling.
205             }
206 
207             Thread.sleep(interval);
208         }
209         return table;
210     }
211 
212     /**
213      * Creates the table and ignores any errors if it already exists.
214      * @param dynamo The Dynamo client to use.
215      * @param createTableRequest The create table request.
216      * @return True if created, false otherwise.
217      */
createTableIfNotExists(final DynamoDbClient dynamo, final CreateTableRequest createTableRequest)218     public static boolean createTableIfNotExists(final DynamoDbClient dynamo, final CreateTableRequest createTableRequest) {
219         try {
220             dynamo.createTable(createTableRequest);
221             return true;
222         } catch (final ResourceInUseException e) {
223             if (log.isTraceEnabled()) {
224                 log.trace("Table " + createTableRequest.tableName() + " already exists", e);
225             }
226         }
227         return false;
228     }
229 
230     /**
231      * Deletes the table and ignores any errors if it doesn't exist.
232      * @param dynamo The Dynamo client to use.
233      * @param deleteTableRequest The delete table request.
234      * @return True if deleted, false otherwise.
235      */
deleteTableIfExists(final DynamoDbClient dynamo, final DeleteTableRequest deleteTableRequest)236     public static boolean deleteTableIfExists(final DynamoDbClient dynamo, final DeleteTableRequest deleteTableRequest) {
237         try {
238             dynamo.deleteTable(deleteTableRequest);
239             return true;
240         } catch (final ResourceNotFoundException e) {
241             if (log.isTraceEnabled()) {
242                 log.trace("Table " + deleteTableRequest.tableName() + " does not exist", e);
243             }
244         }
245         return false;
246     }
247 
248     /**
249      * Thrown by {@link TableUtils} when a table never reaches a desired state
250      */
251     public static class TableNeverTransitionedToStateException extends SdkClientException {
252 
253         private static final long serialVersionUID = 8920567021104846647L;
254 
TableNeverTransitionedToStateException(String tableName, TableStatus desiredStatus)255         public TableNeverTransitionedToStateException(String tableName, TableStatus desiredStatus) {
256             super(SdkClientException.builder()
257                                     .message("Table " + tableName + " never transitioned to desired state of " +
258                                              desiredStatus.toString()));
259         }
260 
261     }
262 
263 }
264