diff --git a/README.md b/README.md index 76dff042d27d..f851112f415a 100644 --- a/README.md +++ b/README.md @@ -173,9 +173,7 @@ if (table == null) { } System.out.println("Loading data into table " + tableId); Job loadJob = table.load(FormatOptions.csv(), "gs://bucket/path"); -while (!loadJob.isDone()) { - Thread.sleep(1000L); -} +loadJob = loadJob.waitFor(); if (loadJob.status().error() != null) { System.out.println("Job completed with errors"); } else { @@ -203,7 +201,6 @@ import com.google.cloud.compute.Compute; import com.google.cloud.compute.ComputeOptions; import com.google.cloud.compute.Disk; import com.google.cloud.compute.DiskId; -import com.google.cloud.compute.Operation; import com.google.cloud.compute.Snapshot; Compute compute = ComputeOptions.defaultInstance().service(); @@ -212,12 +209,10 @@ Disk disk = compute.getDisk(diskId, Compute.DiskOption.fields()); if (disk != null) { String snapshotName = "disk-name-snapshot"; Operation operation = disk.createSnapshot(snapshotName); - while (!operation.isDone()) { - Thread.sleep(1000L); - } + operation = operation.waitFor(); if (operation.errors() == null) { // use snapshot - Snapshot snapshot = compute.getSnapshot("disk-name-snapshot"); + Snapshot snapshot = compute.getSnapshot(snapshotName); } } ``` @@ -234,8 +229,6 @@ import com.google.cloud.compute.InstanceId; import com.google.cloud.compute.InstanceInfo; import com.google.cloud.compute.MachineTypeId; import com.google.cloud.compute.NetworkId; -import com.google.cloud.compute.NetworkInterface; -import com.google.cloud.compute.Operation; Compute compute = ComputeOptions.defaultInstance().service(); ImageId imageId = ImageId.of("debian-cloud", "debian-8-jessie-v20160329"); @@ -246,9 +239,7 @@ InstanceId instanceId = InstanceId.of("us-central1-a", "instance-name"); MachineTypeId machineTypeId = MachineTypeId.of("us-central1-a", "n1-standard-1"); Operation operation = compute.create(InstanceInfo.of(instanceId, machineTypeId, attachedDisk, networkInterface)); -while (!operation.isDone()) { - Thread.sleep(1000L); -} +operation = operation.waitFor(); if (operation.errors() == null) { // use instance Instance instance = compute.getInstance(instanceId); diff --git a/gcloud-java-bigquery/src/main/java/com/google/cloud/bigquery/Job.java b/gcloud-java-bigquery/src/main/java/com/google/cloud/bigquery/Job.java index bfcca5b5388a..df0849d4b6f4 100644 --- a/gcloud-java-bigquery/src/main/java/com/google/cloud/bigquery/Job.java +++ b/gcloud-java-bigquery/src/main/java/com/google/cloud/bigquery/Job.java @@ -18,9 +18,16 @@ import static com.google.common.base.Preconditions.checkNotNull; +import com.google.cloud.Clock; +import com.google.cloud.WaitForOption; +import com.google.cloud.WaitForOption.CheckingPeriod; +import com.google.cloud.WaitForOption.Timeout; + import java.io.IOException; import java.io.ObjectInputStream; import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * A Google BigQuery Job. @@ -143,6 +150,59 @@ public boolean isDone() { return job == null || job.status().state() == JobStatus.State.DONE; } + /** + * Blocks until this job completes its execution, either failing or succeeding. This method + * returns current job's latest information. If the job no longer exists, this method returns + * {@code null}. By default, the job status is checked every 500 milliseconds, to configure this + * value use {@link WaitForOption#checkEvery(long, TimeUnit)}. Use + * {@link WaitForOption#timeout(long, TimeUnit)} to set the maximum time to wait. + * + *

Example usage of {@code waitFor()}: + *

 {@code
+   * Job completedJob = job.waitFor();
+   * if (completedJob == null) {
+   *   // job no longer exists
+   * } else if (completedJob.status().error() != null) {
+   *   // job failed, handle error
+   * } else {
+   *   // job completed successfully
+   * }}
+ * + *

Example usage of {@code waitFor()} with checking period and timeout: + *

 {@code
+   * Job completedJob = job.waitFor(WaitForOption.checkEvery(1, TimeUnit.SECONDS),
+   *     WaitForOption.timeout(60, TimeUnit.SECONDS));
+   * if (completedJob == null) {
+   *   // job no longer exists
+   * } else if (completedJob.status().error() != null) {
+   *   // job failed, handle error
+   * } else {
+   *   // job completed successfully
+   * }}
+ * + * @param waitOptions options to configure checking period and timeout + * @throws BigQueryException upon failure + * @throws InterruptedException if the current thread gets interrupted while waiting for the job + * to complete + * @throws TimeoutException if the timeout provided with + * {@link WaitForOption#timeout(long, TimeUnit)} is exceeded. If no such option is provided + * this exception is never thrown. + */ + public Job waitFor(WaitForOption... waitOptions) throws InterruptedException, TimeoutException { + Timeout timeout = Timeout.getOrDefault(waitOptions); + CheckingPeriod checkingPeriod = CheckingPeriod.getOrDefault(waitOptions); + long timeoutMillis = timeout.timeoutMillis(); + Clock clock = options.clock(); + long startTime = clock.millis(); + while (!isDone()) { + if (timeoutMillis != -1 && (clock.millis() - startTime) >= timeoutMillis) { + throw new TimeoutException(); + } + checkingPeriod.sleep(); + } + return reload(); + } + /** * Fetches current job's latest information. Returns {@code null} if the job does not exist. * @@ -151,7 +211,7 @@ public boolean isDone() { * @throws BigQueryException upon failure */ public Job reload(BigQuery.JobOption... options) { - return bigquery.getJob(jobId().job(), options); + return bigquery.getJob(jobId(), options); } /** diff --git a/gcloud-java-bigquery/src/main/java/com/google/cloud/bigquery/package-info.java b/gcloud-java-bigquery/src/main/java/com/google/cloud/bigquery/package-info.java index a701b82c1c2c..3b4d392d26dc 100644 --- a/gcloud-java-bigquery/src/main/java/com/google/cloud/bigquery/package-info.java +++ b/gcloud-java-bigquery/src/main/java/com/google/cloud/bigquery/package-info.java @@ -33,9 +33,7 @@ * } * System.out.println("Loading data into table " + tableId); * Job loadJob = table.load(FormatOptions.csv(), "gs://bucket/path"); - * while (!loadJob.isDone()) { - * Thread.sleep(1000L); - * } + * loadJob = loadJob.waitFor(); * if (loadJob.status().error() != null) { * System.out.println("Job completed with errors"); * } else { diff --git a/gcloud-java-bigquery/src/test/java/com/google/cloud/bigquery/JobTest.java b/gcloud-java-bigquery/src/test/java/com/google/cloud/bigquery/JobTest.java index 44e5e201e95c..fb47b54428c0 100644 --- a/gcloud-java-bigquery/src/test/java/com/google/cloud/bigquery/JobTest.java +++ b/gcloud-java-bigquery/src/test/java/com/google/cloud/bigquery/JobTest.java @@ -27,10 +27,18 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import com.google.cloud.Clock; +import com.google.cloud.WaitForOption; import com.google.cloud.bigquery.JobStatistics.CopyStatistics; +import org.easymock.EasyMock; import org.junit.After; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; public class JobTest { @@ -66,6 +74,9 @@ public class JobTest { private Job expectedJob; private Job job; + @Rule + public final ExpectedException thrown = ExpectedException.none(); + private void initializeExpectedJob(int optionsCalls) { expect(serviceMockReturnsOptions.options()).andReturn(mockOptions).times(optionsCalls); replay(serviceMockReturnsOptions); @@ -177,13 +188,113 @@ public void testIsDone_NotExists() throws Exception { assertTrue(job.isDone()); } + @Test + public void testWaitFor() throws InterruptedException, TimeoutException { + initializeExpectedJob(2); + BigQuery.JobOption[] expectedOptions = {BigQuery.JobOption.fields(BigQuery.JobField.STATUS)}; + JobStatus status = createStrictMock(JobStatus.class); + expect(status.state()).andReturn(JobStatus.State.DONE); + expect(bigquery.options()).andReturn(mockOptions); + expect(mockOptions.clock()).andReturn(Clock.defaultClock()); + Job completedJob = expectedJob.toBuilder().status(status).build(); + expect(bigquery.getJob(JOB_INFO.jobId(), expectedOptions)).andReturn(completedJob); + expect(bigquery.getJob(JOB_INFO.jobId())).andReturn(completedJob); + replay(status, bigquery, mockOptions); + initializeJob(); + assertSame(completedJob, job.waitFor()); + verify(status, mockOptions); + } + + @Test + public void testWaitFor_Null() throws InterruptedException, TimeoutException { + initializeExpectedJob(1); + BigQuery.JobOption[] expectedOptions = {BigQuery.JobOption.fields(BigQuery.JobField.STATUS)}; + expect(bigquery.options()).andReturn(mockOptions); + expect(mockOptions.clock()).andReturn(Clock.defaultClock()); + expect(bigquery.getJob(JOB_INFO.jobId(), expectedOptions)).andReturn(null); + expect(bigquery.getJob(JOB_INFO.jobId())).andReturn(null); + replay(bigquery, mockOptions); + initializeJob(); + assertNull(job.waitFor()); + verify(mockOptions); + } + + @Test + public void testWaitForWithCheckingPeriod() throws InterruptedException, TimeoutException { + initializeExpectedJob(3); + BigQuery.JobOption[] expectedOptions = {BigQuery.JobOption.fields(BigQuery.JobField.STATUS)}; + TimeUnit timeUnit = createStrictMock(TimeUnit.class); + timeUnit.sleep(42); + EasyMock.expectLastCall(); + JobStatus status = createStrictMock(JobStatus.class); + expect(status.state()).andReturn(JobStatus.State.RUNNING); + expect(status.state()).andReturn(JobStatus.State.DONE); + expect(bigquery.options()).andReturn(mockOptions); + expect(mockOptions.clock()).andReturn(Clock.defaultClock()); + Job runningJob = expectedJob.toBuilder().status(status).build(); + Job completedJob = expectedJob.toBuilder().status(status).build(); + expect(bigquery.getJob(JOB_INFO.jobId(), expectedOptions)).andReturn(runningJob); + expect(bigquery.getJob(JOB_INFO.jobId(), expectedOptions)).andReturn(completedJob); + expect(bigquery.getJob(JOB_INFO.jobId())).andReturn(completedJob); + replay(status, bigquery, timeUnit, mockOptions); + initializeJob(); + assertSame(completedJob, job.waitFor(WaitForOption.checkEvery(42, timeUnit))); + verify(status, timeUnit, mockOptions); + } + + @Test + public void testWaitForWithCheckingPeriod_Null() throws InterruptedException, TimeoutException { + initializeExpectedJob(2); + BigQuery.JobOption[] expectedOptions = {BigQuery.JobOption.fields(BigQuery.JobField.STATUS)}; + TimeUnit timeUnit = createStrictMock(TimeUnit.class); + timeUnit.sleep(42); + EasyMock.expectLastCall(); + expect(bigquery.options()).andReturn(mockOptions); + expect(mockOptions.clock()).andReturn(Clock.defaultClock()); + Job runningJob = expectedJob.toBuilder().status(new JobStatus(JobStatus.State.RUNNING)).build(); + expect(bigquery.getJob(JOB_INFO.jobId(), expectedOptions)).andReturn(runningJob); + expect(bigquery.getJob(JOB_INFO.jobId(), expectedOptions)).andReturn(null); + expect(bigquery.getJob(JOB_INFO.jobId())).andReturn(null); + replay(bigquery, timeUnit, mockOptions); + initializeJob(); + assertNull(job.waitFor(WaitForOption.checkEvery(42, timeUnit))); + verify(bigquery, timeUnit, mockOptions); + } + + @Test + public void testWaitForWithTimeout() throws InterruptedException, TimeoutException { + initializeExpectedJob(2); + BigQuery.JobOption[] expectedOptions = {BigQuery.JobOption.fields(BigQuery.JobField.STATUS)}; + TimeUnit timeUnit = createStrictMock(TimeUnit.class); + timeUnit.sleep(1); + EasyMock.expectLastCall(); + Clock clock = createStrictMock(Clock.class); + expect(clock.millis()).andReturn(0L); + expect(clock.millis()).andReturn(1L); + expect(clock.millis()).andReturn(3L); + JobStatus status = createStrictMock(JobStatus.class); + expect(status.state()).andReturn(JobStatus.State.RUNNING); + expect(status.state()).andReturn(JobStatus.State.RUNNING); + expect(bigquery.options()).andReturn(mockOptions); + expect(mockOptions.clock()).andReturn(clock); + Job runningJob = expectedJob.toBuilder().status(status).build(); + expect(bigquery.getJob(JOB_INFO.jobId(), expectedOptions)).andReturn(runningJob); + expect(bigquery.getJob(JOB_INFO.jobId(), expectedOptions)).andReturn(runningJob); + replay(status, bigquery, timeUnit, clock, mockOptions); + initializeJob(); + thrown.expect(TimeoutException.class); + job.waitFor(WaitForOption.checkEvery(1, timeUnit), + WaitForOption.timeout(3, TimeUnit.MILLISECONDS)); + verify(status, timeUnit, clock, mockOptions); + } + @Test public void testReload() throws Exception { initializeExpectedJob(4); JobInfo updatedInfo = JOB_INFO.toBuilder().etag("etag").build(); Job expectedJob = new Job(serviceMockReturnsOptions, new JobInfo.BuilderImpl(updatedInfo)); expect(bigquery.options()).andReturn(mockOptions); - expect(bigquery.getJob(JOB_INFO.jobId().job())).andReturn(expectedJob); + expect(bigquery.getJob(JOB_INFO.jobId())).andReturn(expectedJob); replay(bigquery); initializeJob(); Job updatedJob = job.reload(); @@ -194,7 +305,7 @@ public void testReload() throws Exception { public void testReloadNull() throws Exception { initializeExpectedJob(1); expect(bigquery.options()).andReturn(mockOptions); - expect(bigquery.getJob(JOB_INFO.jobId().job())).andReturn(null); + expect(bigquery.getJob(JOB_INFO.jobId())).andReturn(null); replay(bigquery); initializeJob(); assertNull(job.reload()); @@ -206,7 +317,7 @@ public void testReloadWithOptions() throws Exception { JobInfo updatedInfo = JOB_INFO.toBuilder().etag("etag").build(); Job expectedJob = new Job(serviceMockReturnsOptions, new JobInfo.BuilderImpl(updatedInfo)); expect(bigquery.options()).andReturn(mockOptions); - expect(bigquery.getJob(JOB_INFO.jobId().job(), BigQuery.JobOption.fields())) + expect(bigquery.getJob(JOB_INFO.jobId(), BigQuery.JobOption.fields())) .andReturn(expectedJob); replay(bigquery); initializeJob(); diff --git a/gcloud-java-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java b/gcloud-java-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java index 5007c73b69ce..dde170f87859 100644 --- a/gcloud-java-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java +++ b/gcloud-java-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java @@ -71,7 +71,6 @@ import org.junit.AfterClass; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.Timeout; @@ -83,6 +82,7 @@ import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; @@ -170,7 +170,7 @@ public class ITBigQueryTest { public Timeout globalTimeout = Timeout.seconds(300); @BeforeClass - public static void beforeClass() throws InterruptedException { + public static void beforeClass() throws InterruptedException, TimeoutException { RemoteBigQueryHelper bigqueryHelper = RemoteBigQueryHelper.create(); RemoteStorageHelper storageHelper = RemoteStorageHelper.create(); bigquery = bigqueryHelper.options().service(); @@ -188,9 +188,7 @@ public static void beforeClass() throws InterruptedException { .schema(TABLE_SCHEMA) .build(); Job job = bigquery.create(JobInfo.of(configuration)); - while (!job.isDone()) { - Thread.sleep(1000); - } + job = job.waitFor(); assertNull(job.status().error()); } @@ -786,7 +784,7 @@ public void testCreateAndGetJobWithSelectedFields() { } @Test - public void testCopyJob() throws InterruptedException { + public void testCopyJob() throws InterruptedException, TimeoutException { String sourceTableName = "test_copy_job_source_table"; String destinationTableName = "test_copy_job_destination_table"; TableId sourceTable = TableId.of(DATASET, sourceTableName); @@ -799,9 +797,7 @@ public void testCopyJob() throws InterruptedException { TableId destinationTable = TableId.of(DATASET, destinationTableName); CopyJobConfiguration configuration = CopyJobConfiguration.of(destinationTable, sourceTable); Job remoteJob = bigquery.create(JobInfo.of(configuration)); - while (!remoteJob.isDone()) { - Thread.sleep(1000); - } + remoteJob = remoteJob.waitFor(); assertNull(remoteJob.status().error()); Table remoteTable = bigquery.getTable(DATASET, destinationTableName); assertNotNull(remoteTable); @@ -813,7 +809,7 @@ public void testCopyJob() throws InterruptedException { } @Test - public void testQueryJob() throws InterruptedException { + public void testQueryJob() throws InterruptedException, TimeoutException { String tableName = "test_query_job_table"; String query = new StringBuilder() .append("SELECT TimestampField, StringField, BooleanField FROM ") @@ -825,9 +821,7 @@ public void testQueryJob() throws InterruptedException { .destinationTable(destinationTable) .build(); Job remoteJob = bigquery.create(JobInfo.of(configuration)); - while (!remoteJob.isDone()) { - Thread.sleep(1000); - } + remoteJob = remoteJob.waitFor(); assertNull(remoteJob.status().error()); QueryResponse response = bigquery.getQueryResults(remoteJob.jobId()); @@ -858,7 +852,7 @@ public void testQueryJob() throws InterruptedException { } @Test - public void testExtractJob() throws InterruptedException { + public void testExtractJob() throws InterruptedException, TimeoutException { String tableName = "test_export_job_table"; TableId destinationTable = TableId.of(DATASET, tableName); LoadJobConfiguration configuration = @@ -866,9 +860,7 @@ public void testExtractJob() throws InterruptedException { .schema(SIMPLE_SCHEMA) .build(); Job remoteLoadJob = bigquery.create(JobInfo.of(configuration)); - while (!remoteLoadJob.isDone()) { - Thread.sleep(1000); - } + remoteLoadJob = remoteLoadJob.waitFor(); assertNull(remoteLoadJob.status().error()); ExtractJobConfiguration extractConfiguration = @@ -876,9 +868,7 @@ public void testExtractJob() throws InterruptedException { .printHeader(false) .build(); Job remoteExtractJob = bigquery.create(JobInfo.of(extractConfiguration)); - while (!remoteExtractJob.isDone()) { - Thread.sleep(1000); - } + remoteExtractJob = remoteExtractJob.waitFor(); assertNull(remoteExtractJob.status().error()); assertEquals(CSV_CONTENT, new String(storage.readAllBytes(BUCKET, EXTRACT_FILE), StandardCharsets.UTF_8)); @@ -886,7 +876,7 @@ public void testExtractJob() throws InterruptedException { } @Test - public void testCancelJob() throws InterruptedException { + public void testCancelJob() throws InterruptedException, TimeoutException { String destinationTableName = "test_cancel_query_job_table"; String query = "SELECT TimestampField, StringField, BooleanField FROM " + TABLE_ID.table(); TableId destinationTable = TableId.of(DATASET, destinationTableName); @@ -896,9 +886,7 @@ public void testCancelJob() throws InterruptedException { .build(); Job remoteJob = bigquery.create(JobInfo.of(configuration)); assertTrue(remoteJob.cancel()); - while (!remoteJob.isDone()) { - Thread.sleep(1000); - } + remoteJob = remoteJob.waitFor(); assertNull(remoteJob.status().error()); } diff --git a/gcloud-java-compute/README.md b/gcloud-java-compute/README.md index 81d46fd1270d..19c0d56b5b41 100644 --- a/gcloud-java-compute/README.md +++ b/gcloud-java-compute/README.md @@ -114,10 +114,8 @@ succeeded: ```java RegionAddressId addressId = RegionAddressId.of("us-central1", "test-address"); Operation operation = compute.create(AddressInfo.of(addressId)); -while (!operation.isDone()) { - Thread.sleep(1000L); -} -operation = operation.reload(); +// Wait for operation to complete +operation = operation.waitFor(); if (operation.errors() == null) { System.out.println("Address " + addressId + " was successfully created"); } else { @@ -150,10 +148,8 @@ DiskId diskId = DiskId.of("us-central1-a", "test-disk"); ImageDiskConfiguration diskConfiguration = ImageDiskConfiguration.of(imageId); DiskInfo disk = DiskInfo.of(diskId, diskConfiguration); Operation operation = compute.create(disk); -while (!operation.isDone()) { - Thread.sleep(1000L); -} -operation = operation.reload(); +// Wait for operation to complete +operation = operation.waitFor(); if (operation.errors() == null) { System.out.println("Disk " + diskId + " was successfully created"); } else { @@ -198,10 +194,8 @@ MachineTypeId machineTypeId = MachineTypeId.of("us-central1-a", "n1-standard-1") InstanceInfo instance = InstanceInfo.of(instanceId, machineTypeId, attachedDisk, networkInterface); Operation operation = compute.create(instance); -while (!operation.isDone()) { - Thread.sleep(1000L); -} -operation = operation.reload(); +// Wait for operation to complete +operation = operation.waitFor(); if (operation.errors() == null) { System.out.println("Instance " + instanceId + " was successfully created"); } else { diff --git a/gcloud-java-compute/src/main/java/com/google/cloud/compute/Operation.java b/gcloud-java-compute/src/main/java/com/google/cloud/compute/Operation.java index 326b681098a6..78752e9cdaeb 100644 --- a/gcloud-java-compute/src/main/java/com/google/cloud/compute/Operation.java +++ b/gcloud-java-compute/src/main/java/com/google/cloud/compute/Operation.java @@ -18,6 +18,9 @@ import static com.google.common.base.Preconditions.checkNotNull; +import com.google.cloud.Clock; +import com.google.cloud.WaitForOption; +import com.google.cloud.WaitForOption.CheckingPeriod; import com.google.cloud.compute.Compute.OperationOption; import com.google.common.base.Function; import com.google.common.base.MoreObjects; @@ -36,13 +39,15 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * Google Compute Engine operations. Operation identity can be obtained via {@link #operationId()}. * {@link #operationId()} returns {@link GlobalOperationId} for global operations, * {@link RegionOperationId} for region operations, and {@link ZoneOperationId} for zone operations. * To get an {@code Operation} object with the most recent information, use - * {@link #reload(OperationOption...)}. + * {@link #reload(Compute.OperationOption...)}. */ public class Operation implements Serializable { @@ -635,7 +640,7 @@ public String description() { * @return {@code true} if this operation exists, {@code false} otherwise * @throws ComputeException upon failure */ - public boolean exists() throws ComputeException { + public boolean exists() { return reload(OperationOption.fields()) != null; } @@ -652,12 +657,67 @@ public boolean exists() throws ComputeException { * not exist, {@code false} if the state is not {@link Operation.Status#DONE} * @throws ComputeException upon failure */ - public boolean isDone() throws ComputeException { + public boolean isDone() { Operation operation = compute.getOperation(operationId, OperationOption.fields(Compute.OperationField.STATUS)); return operation == null || operation.status() == Status.DONE; } + /** + * Blocks until this operation completes its execution, either failing or succeeding. This method + * returns current operation's latest information. If the operation no longer exists, this method + * returns {@code null}. By default, the operation status is checked every 500 milliseconds, to + * configure this value use {@link WaitForOption#checkEvery(long, TimeUnit)}. Use + * {@link WaitForOption#timeout(long, TimeUnit)} to set the maximum time to wait. + * + *

Example usage of {@code waitFor()}: + *

 {@code
+   * Operation completedOperation = operation.waitFor();
+   * if (completedOperation == null) {
+   *   // operation no longer exists
+   * } else if (completedOperation.errors() != null) {
+   *   // operation failed, handle error
+   * } else {
+   *   // operation completed successfully
+   * }}
+ * + *

Example usage of {@code waitFor()} with checking period and timeout: + *

 {@code
+   * Operation completedOperation =
+   *     operation.waitFor(WaitForOption.checkEvery(1, TimeUnit.SECONDS),
+   *         WaitForOption.timeout(60, TimeUnit.SECONDS));
+   * if (completedOperation == null) {
+   *   // operation no longer exists
+   * } else if (completedOperation.errors() != null) {
+   *   // operation failed, handle error
+   * } else {
+   *   // operation completed successfully
+   * }}
+ * + * @param waitOptions options to configure checking period and timeout + * @throws ComputeException upon failure + * @throws InterruptedException if the current thread gets interrupted while waiting for the + * operation to complete + * @throws TimeoutException if the timeout provided with + * {@link WaitForOption#timeout(long, TimeUnit)} is exceeded. If no such option is provided + * this exception is never thrown. + */ + public Operation waitFor(WaitForOption... waitOptions) + throws InterruptedException, TimeoutException { + WaitForOption.Timeout timeout = WaitForOption.Timeout.getOrDefault(waitOptions); + CheckingPeriod checkingPeriod = CheckingPeriod.getOrDefault(waitOptions); + long timeoutMillis = timeout.timeoutMillis(); + Clock clock = options.clock(); + long startTime = clock.millis(); + while (!isDone()) { + if (timeoutMillis != -1 && (clock.millis() - startTime) >= timeoutMillis) { + throw new TimeoutException(); + } + checkingPeriod.sleep(); + } + return reload(); + } + /** * Fetches current operation's latest information. Returns {@code null} if the operation does not * exist. @@ -666,7 +726,7 @@ public boolean isDone() throws ComputeException { * @return an {@code Operation} object with latest information or {@code null} if not found * @throws ComputeException upon failure */ - public Operation reload(OperationOption... options) throws ComputeException { + public Operation reload(OperationOption... options) { return compute.getOperation(operationId, options); } @@ -677,7 +737,7 @@ public Operation reload(OperationOption... options) throws ComputeException { * @return {@code true} if operation was deleted, {@code false} if it was not found * @throws ComputeException upon failure */ - public boolean delete() throws ComputeException { + public boolean delete() { return compute.deleteOperation(operationId); } diff --git a/gcloud-java-compute/src/main/java/com/google/cloud/compute/package-info.java b/gcloud-java-compute/src/main/java/com/google/cloud/compute/package-info.java index b7f589ea3b3f..aff2e4254b57 100644 --- a/gcloud-java-compute/src/main/java/com/google/cloud/compute/package-info.java +++ b/gcloud-java-compute/src/main/java/com/google/cloud/compute/package-info.java @@ -28,12 +28,10 @@ * if (disk != null) { * String snapshotName = "disk-name-snapshot"; * Operation operation = disk.createSnapshot(snapshotName); - * while (!operation.isDone()) { - * Thread.sleep(1000L); - * } + * operation = operation.waitFor(); * if (operation.errors() == null) { * // use snapshot - * Snapshot snapshot = compute.getSnapshot("disk-name-snapshot"); + * Snapshot snapshot = compute.getSnapshot(snapshotName); * } * }} *

This second example shows how to create a virtual machine instance. Complete source code can @@ -49,10 +47,8 @@ * InstanceId instanceId = InstanceId.of("us-central1-a", "instance-name"); * MachineTypeId machineTypeId = MachineTypeId.of("us-central1-a", "n1-standard-1"); * Operation operation = - * compute.create(InstanceInfo.of(instanceId, machineTypeId, attachedDisk, networkInterface)); - * while (!operation.isDone()) { - * Thread.sleep(1000L); - * } + * compute.create(InstanceInfo.of(instanceId, machineTypeId, attachedDisk, networkInterface)); + * operation = operation.waitFor(); * if (operation.errors() == null) { * // use instance * Instance instance = compute.getInstance(instanceId); diff --git a/gcloud-java-compute/src/test/java/com/google/cloud/compute/OperationTest.java b/gcloud-java-compute/src/test/java/com/google/cloud/compute/OperationTest.java index d45fe48c1134..975610f93609 100644 --- a/gcloud-java-compute/src/test/java/com/google/cloud/compute/OperationTest.java +++ b/gcloud-java-compute/src/test/java/com/google/cloud/compute/OperationTest.java @@ -28,24 +28,33 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import com.google.cloud.Clock; +import com.google.cloud.WaitForOption; +import com.google.cloud.compute.Operation.OperationError; +import com.google.cloud.compute.Operation.OperationWarning; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import org.easymock.EasyMock; import org.junit.After; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; public class OperationTest { - private static final Operation.OperationError OPERATION_ERROR1 = - new Operation.OperationError("code1", "location1", "message1"); - private static final Operation.OperationError OPERATION_ERROR2 = - new Operation.OperationError("code2", "location2", "message2"); - private static final Operation.OperationWarning OPERATION_WARNING1 = - new Operation.OperationWarning("code1", "message1", ImmutableMap.of("k1", "v1")); - private static final Operation.OperationWarning OPERATION_WARNING2 = - new Operation.OperationWarning("code2", "location2", ImmutableMap.of("k2", "v2")); + private static final OperationError OPERATION_ERROR1 = + new OperationError("code1", "location1", "message1"); + private static final OperationError OPERATION_ERROR2 = + new OperationError("code2", "location2", "message2"); + private static final OperationWarning OPERATION_WARNING1 = + new OperationWarning("code1", "message1", ImmutableMap.of("k1", "v1")); + private static final OperationWarning OPERATION_WARNING2 = + new OperationWarning("code2", "location2", ImmutableMap.of("k2", "v2")); private static final String GENERATED_ID = "1"; private static final String CLIENT_OPERATION_ID = "clientOperationId"; private static final String OPERATION_TYPE = "delete"; @@ -58,9 +67,9 @@ public class OperationTest { private static final Long INSERT_TIME = 1453293540000L; private static final Long START_TIME = 1453293420000L; private static final Long END_TIME = 1453293480000L; - private static final List ERRORS = + private static final List ERRORS = ImmutableList.of(OPERATION_ERROR1, OPERATION_ERROR2); - private static final List WARNINGS = + private static final List WARNINGS = ImmutableList.of(OPERATION_WARNING1, OPERATION_WARNING2); private static final Integer HTTP_ERROR_STATUS_CODE = 404; private static final String HTTP_ERROR_MESSAGE = "NOT FOUND"; @@ -72,6 +81,9 @@ public class OperationTest { private static final RegionOperationId REGION_OPERATION_ID = RegionOperationId.of("project", "region", "op"); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + private final Compute serviceMockReturnsOptions = createStrictMock(Compute.class); private final ComputeOptions mockOptions = createMock(ComputeOptions.class); private Compute compute; @@ -352,6 +364,109 @@ public void testIsDone_NotExists() throws Exception { verify(compute); } + @Test + public void testWaitFor() throws InterruptedException, TimeoutException { + initializeExpectedOperation(4); + Compute.OperationOption[] expectedOptions = + {Compute.OperationOption.fields(Compute.OperationField.STATUS)}; + Operation successOperation = + Operation.fromPb(serviceMockReturnsOptions, globalOperation.toPb().setError(null)); + expect(compute.options()).andReturn(mockOptions); + expect(mockOptions.clock()).andReturn(Clock.defaultClock()); + expect(compute.getOperation(GLOBAL_OPERATION_ID, expectedOptions)).andReturn(successOperation); + expect(compute.getOperation(GLOBAL_OPERATION_ID)).andReturn(successOperation); + replay(compute, mockOptions); + initializeOperation(); + assertSame(successOperation, operation.waitFor()); + verify(mockOptions); + } + + @Test + public void testWaitFor_Null() throws InterruptedException, TimeoutException { + initializeExpectedOperation(3); + Compute.OperationOption[] expectedOptions = + {Compute.OperationOption.fields(Compute.OperationField.STATUS)}; + expect(compute.options()).andReturn(mockOptions); + expect(mockOptions.clock()).andReturn(Clock.defaultClock()); + expect(compute.getOperation(GLOBAL_OPERATION_ID, expectedOptions)).andReturn(null); + expect(compute.getOperation(GLOBAL_OPERATION_ID)).andReturn(null); + replay(compute, mockOptions); + initializeOperation(); + assertNull(operation.waitFor()); + verify(mockOptions); + } + + @Test + public void testWaitForCheckingPeriod() throws InterruptedException, TimeoutException { + initializeExpectedOperation(5); + Compute.OperationOption[] expectedOptions = + {Compute.OperationOption.fields(Compute.OperationField.STATUS)}; + TimeUnit timeUnit = createStrictMock(TimeUnit.class); + timeUnit.sleep(42); + EasyMock.expectLastCall(); + Operation runningOperation = Operation.fromPb(serviceMockReturnsOptions, + globalOperation.toPb().setError(null).setStatus("RUNNING")); + Operation completedOperation = + Operation.fromPb(serviceMockReturnsOptions, globalOperation.toPb().setError(null)); + expect(compute.options()).andReturn(mockOptions); + expect(mockOptions.clock()).andReturn(Clock.defaultClock()); + expect(compute.getOperation(GLOBAL_OPERATION_ID, expectedOptions)).andReturn(runningOperation); + expect(compute.getOperation(GLOBAL_OPERATION_ID, expectedOptions)) + .andReturn(completedOperation); + expect(compute.getOperation(GLOBAL_OPERATION_ID)).andReturn(completedOperation); + replay(compute, timeUnit, mockOptions); + initializeOperation(); + assertSame(completedOperation, operation.waitFor(WaitForOption.checkEvery(42, timeUnit))); + verify(timeUnit, mockOptions); + } + + @Test + public void testWaitForCheckingPeriod_Null() throws InterruptedException, TimeoutException { + initializeExpectedOperation(4); + Compute.OperationOption[] expectedOptions = + {Compute.OperationOption.fields(Compute.OperationField.STATUS)}; + TimeUnit timeUnit = createStrictMock(TimeUnit.class); + timeUnit.sleep(42); + EasyMock.expectLastCall(); + Operation runningOperation = Operation.fromPb(serviceMockReturnsOptions, + globalOperation.toPb().setError(null).setStatus("RUNNING")); + expect(compute.options()).andReturn(mockOptions); + expect(mockOptions.clock()).andReturn(Clock.defaultClock()); + expect(compute.getOperation(GLOBAL_OPERATION_ID, expectedOptions)).andReturn(runningOperation); + expect(compute.getOperation(GLOBAL_OPERATION_ID, expectedOptions)).andReturn(null); + expect(compute.getOperation(GLOBAL_OPERATION_ID)).andReturn(null); + replay(compute, timeUnit, mockOptions); + initializeOperation(); + assertNull(operation.waitFor(WaitForOption.checkEvery(42, timeUnit))); + verify(compute, timeUnit, mockOptions); + } + + @Test + public void testWaitForWithTimeout() throws InterruptedException, TimeoutException { + initializeExpectedOperation(4); + Compute.OperationOption[] expectedOptions = + {Compute.OperationOption.fields(Compute.OperationField.STATUS)}; + TimeUnit timeUnit = createStrictMock(TimeUnit.class); + timeUnit.sleep(1); + EasyMock.expectLastCall(); + Clock clock = createStrictMock(Clock.class); + expect(clock.millis()).andReturn(0L); + expect(clock.millis()).andReturn(1L); + expect(clock.millis()).andReturn(3L); + Operation runningOperation = Operation.fromPb(serviceMockReturnsOptions, + globalOperation.toPb().setError(null).setStatus("RUNNING")); + expect(compute.options()).andReturn(mockOptions); + expect(mockOptions.clock()).andReturn(clock); + expect(compute.getOperation(GLOBAL_OPERATION_ID, expectedOptions)).andReturn(runningOperation); + expect(compute.getOperation(GLOBAL_OPERATION_ID, expectedOptions)).andReturn(runningOperation); + replay(compute, timeUnit, clock, mockOptions); + initializeOperation(); + thrown.expect(TimeoutException.class); + operation.waitFor(WaitForOption.checkEvery(1, timeUnit), + WaitForOption.timeout(3, TimeUnit.MILLISECONDS)); + verify(compute, timeUnit, clock, mockOptions); + } + @Test public void testReload() throws Exception { initializeExpectedOperation(5); diff --git a/gcloud-java-compute/src/test/java/com/google/cloud/compute/it/ITComputeTest.java b/gcloud-java-compute/src/test/java/com/google/cloud/compute/it/ITComputeTest.java index 71013a3c1120..5ee58c6bfda4 100644 --- a/gcloud-java-compute/src/test/java/com/google/cloud/compute/it/ITComputeTest.java +++ b/gcloud-java-compute/src/test/java/com/google/cloud/compute/it/ITComputeTest.java @@ -88,6 +88,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeoutException; public class ITComputeTest { @@ -681,14 +682,12 @@ public void testListZoneOperationsWithFilter() { } @Test - public void testCreateGetAndDeleteRegionAddress() throws InterruptedException { + public void testCreateGetAndDeleteRegionAddress() throws InterruptedException, TimeoutException { String name = BASE_RESOURCE_NAME + "create-and-get-region-address"; AddressId addressId = RegionAddressId.of(REGION, name); AddressInfo addressInfo = AddressInfo.of(addressId); Operation operation = compute.create(addressInfo); - while (!operation.isDone()) { - Thread.sleep(1000L); - } + operation.waitFor(); // test get Address remoteAddress = compute.getAddress(addressId); assertNotNull(remoteAddress); @@ -709,26 +708,20 @@ public void testCreateGetAndDeleteRegionAddress() throws InterruptedException { assertNull(remoteAddress.creationTimestamp()); assertNull(remoteAddress.generatedId()); operation = remoteAddress.delete(); - while (!operation.isDone()) { - Thread.sleep(1000L); - } + operation.waitFor(); assertNull(compute.getAddress(addressId)); } @Test - public void testListRegionAddresses() throws InterruptedException { + public void testListRegionAddresses() throws InterruptedException, TimeoutException { String prefix = BASE_RESOURCE_NAME + "list-region-address"; String[] addressNames = {prefix + "1", prefix + "2"}; AddressId firstAddressId = RegionAddressId.of(REGION, addressNames[0]); AddressId secondAddressId = RegionAddressId.of(REGION, addressNames[1]); Operation firstOperation = compute.create(AddressInfo.of(firstAddressId)); Operation secondOperation = compute.create(AddressInfo.of(secondAddressId)); - while (!firstOperation.isDone()) { - Thread.sleep(1000L); - } - while (!secondOperation.isDone()) { - Thread.sleep(1000L); - } + firstOperation.waitFor(); + secondOperation.waitFor(); Set addressSet = ImmutableSet.copyOf(addressNames); // test list Compute.AddressFilter filter = @@ -772,19 +765,15 @@ public void testListRegionAddresses() throws InterruptedException { } @Test - public void testAggregatedListAddresses() throws InterruptedException { + public void testAggregatedListAddresses() throws InterruptedException, TimeoutException { String prefix = BASE_RESOURCE_NAME + "aggregated-list-address"; String[] addressNames = {prefix + "1", prefix + "2"}; AddressId firstAddressId = RegionAddressId.of(REGION, addressNames[0]); AddressId secondAddressId = GlobalAddressId.of(REGION, addressNames[1]); Operation firstOperation = compute.create(AddressInfo.of(firstAddressId)); Operation secondOperation = compute.create(AddressInfo.of(secondAddressId)); - while (!firstOperation.isDone()) { - Thread.sleep(1000L); - } - while (!secondOperation.isDone()) { - Thread.sleep(1000L); - } + firstOperation.waitFor(); + secondOperation.waitFor(); Set addressSet = ImmutableSet.copyOf(addressNames); Compute.AddressFilter filter = Compute.AddressFilter.equals(Compute.AddressField.NAME, prefix + "\\d"); @@ -807,14 +796,12 @@ public void testAggregatedListAddresses() throws InterruptedException { } @Test - public void testCreateGetAndDeleteGlobalAddress() throws InterruptedException { + public void testCreateGetAndDeleteGlobalAddress() throws InterruptedException, TimeoutException { String name = BASE_RESOURCE_NAME + "create-and-get-global-address"; AddressId addressId = GlobalAddressId.of(name); AddressInfo addressInfo = AddressInfo.of(addressId); Operation operation = compute.create(addressInfo); - while (!operation.isDone()) { - Thread.sleep(1000L); - } + operation.waitFor(); // test get Address remoteAddress = compute.getAddress(addressId); assertNotNull(remoteAddress); @@ -833,26 +820,20 @@ public void testCreateGetAndDeleteGlobalAddress() throws InterruptedException { assertNull(remoteAddress.creationTimestamp()); assertNull(remoteAddress.generatedId()); operation = remoteAddress.delete(); - while (!operation.isDone()) { - Thread.sleep(1000L); - } + operation.waitFor(); assertNull(compute.getAddress(addressId)); } @Test - public void testListGlobalAddresses() throws InterruptedException { + public void testListGlobalAddresses() throws InterruptedException, TimeoutException { String prefix = BASE_RESOURCE_NAME + "list-global-address"; String[] addressNames = {prefix + "1", prefix + "2"}; AddressId firstAddressId = GlobalAddressId.of(addressNames[0]); AddressId secondAddressId = GlobalAddressId.of(addressNames[1]); Operation firstOperation = compute.create(AddressInfo.of(firstAddressId)); Operation secondOperation = compute.create(AddressInfo.of(secondAddressId)); - while (!firstOperation.isDone()) { - Thread.sleep(1000L); - } - while (!secondOperation.isDone()) { - Thread.sleep(1000L); - } + firstOperation.waitFor(); + secondOperation.waitFor(); Set addressSet = ImmutableSet.copyOf(addressNames); // test list Compute.AddressFilter filter = @@ -894,15 +875,13 @@ public void testListGlobalAddresses() throws InterruptedException { } @Test - public void testCreateGetResizeAndDeleteStandardDisk() throws InterruptedException { + public void testCreateGetResizeAndDeleteStandardDisk() throws InterruptedException, TimeoutException { String name = BASE_RESOURCE_NAME + "create-and-get-standard-disk"; DiskId diskId = DiskId.of(ZONE, name); DiskInfo diskInfo = DiskInfo.of(diskId, StandardDiskConfiguration.of(DiskTypeId.of(ZONE, "pd-ssd"), 100L)); Operation operation = compute.create(diskInfo); - while (!operation.isDone()) { - Thread.sleep(1000L); - } + operation.waitFor(); // test get Disk remoteDisk = compute.getDisk(diskId); assertNotNull(remoteDisk); @@ -918,9 +897,7 @@ public void testCreateGetResizeAndDeleteStandardDisk() throws InterruptedExcepti assertNull(remoteDisk.lastAttachTimestamp()); assertNull(remoteDisk.lastDetachTimestamp()); operation = remoteDisk.resize(200L); - while (!operation.isDone()) { - Thread.sleep(1000L); - } + operation.waitFor(); // test resize and get with selected fields remoteDisk = compute.getDisk(diskId, Compute.DiskOption.fields(Compute.DiskField.SIZE_GB)); assertNotNull(remoteDisk); @@ -936,21 +913,17 @@ public void testCreateGetResizeAndDeleteStandardDisk() throws InterruptedExcepti assertNull(remoteDisk.lastAttachTimestamp()); assertNull(remoteDisk.lastDetachTimestamp()); operation = remoteDisk.delete(); - while (!operation.isDone()) { - Thread.sleep(1000L); - } + operation.waitFor(); assertNull(compute.getDisk(diskId)); } @Test - public void testCreateGetAndDeleteImageDisk() throws InterruptedException { + public void testCreateGetAndDeleteImageDisk() throws InterruptedException, TimeoutException { String name = BASE_RESOURCE_NAME + "create-and-get-image-disk"; DiskId diskId = DiskId.of(ZONE, name); DiskInfo diskInfo = DiskInfo.of(diskId, ImageDiskConfiguration.of(IMAGE_ID)); Operation operation = compute.create(diskInfo); - while (!operation.isDone()) { - Thread.sleep(1000L); - } + operation.waitFor(); // test get Disk remoteDisk = compute.getDisk(diskId); assertNotNull(remoteDisk); @@ -985,14 +958,12 @@ public void testCreateGetAndDeleteImageDisk() throws InterruptedException { assertNull(remoteDisk.lastAttachTimestamp()); assertNull(remoteDisk.lastDetachTimestamp()); operation = remoteDisk.delete(); - while (!operation.isDone()) { - Thread.sleep(1000L); - } + operation.waitFor(); assertNull(compute.getDisk(diskId)); } @Test - public void testCreateGetAndDeleteSnapshotAndSnapshotDisk() throws InterruptedException { + public void testCreateGetAndDeleteSnapshotAndSnapshotDisk() throws InterruptedException, TimeoutException { String diskName = BASE_RESOURCE_NAME + "create-and-get-snapshot-disk1"; String snapshotDiskName = BASE_RESOURCE_NAME + "create-and-get-snapshot-disk2"; DiskId diskId = DiskId.of(ZONE, diskName); @@ -1001,14 +972,10 @@ public void testCreateGetAndDeleteSnapshotAndSnapshotDisk() throws InterruptedEx DiskInfo diskInfo = DiskInfo.of(diskId, StandardDiskConfiguration.of(DiskTypeId.of(ZONE, "pd-ssd"), 100L)); Operation operation = compute.create(diskInfo); - while (!operation.isDone()) { - Thread.sleep(1000L); - } + operation.waitFor(); Disk remoteDisk = compute.getDisk(diskId); operation = remoteDisk.createSnapshot(snapshotName); - while (!operation.isDone()) { - Thread.sleep(1000L); - } + operation.waitFor(); // test get snapshot with selected fields Snapshot snapshot = compute.getSnapshot(snapshotName, Compute.SnapshotOption.fields(Compute.SnapshotField.CREATION_TIMESTAMP)); @@ -1038,9 +1005,7 @@ public void testCreateGetAndDeleteSnapshotAndSnapshotDisk() throws InterruptedEx diskInfo = DiskInfo.of(snapshotDiskId, SnapshotDiskConfiguration.of(SnapshotId.of(snapshotName))); operation = compute.create(diskInfo); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); // test get disk remoteDisk = compute.getDisk(snapshotDiskId); assertNotNull(remoteDisk); @@ -1076,19 +1041,15 @@ public void testCreateGetAndDeleteSnapshotAndSnapshotDisk() throws InterruptedEx assertNull(remoteDisk.lastAttachTimestamp()); assertNull(remoteDisk.lastDetachTimestamp()); operation = remoteDisk.delete(); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); assertNull(compute.getDisk(snapshotDiskId)); operation = snapshot.delete(); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); assertNull(compute.getSnapshot(snapshotName)); } @Test - public void testListDisksAndSnapshots() throws InterruptedException { + public void testListDisksAndSnapshots() throws InterruptedException, TimeoutException { String prefix = BASE_RESOURCE_NAME + "list-disks-and-snapshots-disk"; String[] diskNames = {prefix + "1", prefix + "2"}; DiskId firstDiskId = DiskId.of(ZONE, diskNames[0]); @@ -1097,12 +1058,8 @@ public void testListDisksAndSnapshots() throws InterruptedException { StandardDiskConfiguration.of(DiskTypeId.of(ZONE, "pd-ssd"), 100L); Operation firstOperation = compute.create(DiskInfo.of(firstDiskId, configuration)); Operation secondOperation = compute.create(DiskInfo.of(secondDiskId, configuration)); - while (!firstOperation.isDone()) { - Thread.sleep(1000L); - } - while (!secondOperation.isDone()) { - Thread.sleep(1000L); - } + firstOperation.waitFor(); + secondOperation.waitFor(); Set diskSet = ImmutableSet.copyOf(diskNames); // test list disks Compute.DiskFilter diskFilter = @@ -1154,12 +1111,8 @@ public void testListDisksAndSnapshots() throws InterruptedException { SnapshotId secondSnapshotId = SnapshotId.of(diskNames[1]); firstOperation = compute.create(SnapshotInfo.of(firstSnapshotId, firstDiskId)); secondOperation = compute.create(SnapshotInfo.of(secondSnapshotId, secondDiskId)); - while (!firstOperation.isDone()) { - Thread.sleep(1000L); - } - while (!secondOperation.isDone()) { - Thread.sleep(1000L); - } + firstOperation.waitFor(); + secondOperation.waitFor(); // test list snapshots Compute.SnapshotFilter snapshotFilter = Compute.SnapshotFilter.equals(Compute.SnapshotField.NAME, prefix + "\\d"); @@ -1207,7 +1160,7 @@ public void testListDisksAndSnapshots() throws InterruptedException { } @Test - public void testAggregatedListDisks() throws InterruptedException { + public void testAggregatedListDisks() throws InterruptedException, TimeoutException { String prefix = BASE_RESOURCE_NAME + "list-aggregated-disk"; String[] diskZones = {"us-central1-a", "us-east1-c"}; String[] diskNames = {prefix + "1", prefix + "2"}; @@ -1217,12 +1170,8 @@ public void testAggregatedListDisks() throws InterruptedException { StandardDiskConfiguration.of(DiskTypeId.of(ZONE, "pd-ssd"), 100L); Operation firstOperation = compute.create(DiskInfo.of(firstDiskId, configuration)); Operation secondOperation = compute.create(DiskInfo.of(secondDiskId, configuration)); - while (!firstOperation.isDone()) { - Thread.sleep(1000L); - } - while (!secondOperation.isDone()) { - Thread.sleep(1000L); - } + firstOperation.waitFor(); + secondOperation.waitFor(); Set zoneSet = ImmutableSet.copyOf(diskZones); Set diskSet = ImmutableSet.copyOf(diskNames); Compute.DiskFilter diskFilter = @@ -1250,7 +1199,7 @@ public void testAggregatedListDisks() throws InterruptedException { } @Test - public void testCreateGetAndDeprecateImage() throws InterruptedException { + public void testCreateGetAndDeprecateImage() throws InterruptedException, TimeoutException { String diskName = BASE_RESOURCE_NAME + "create-and-get-image-disk"; String imageName = BASE_RESOURCE_NAME + "create-and-get-image"; DiskId diskId = DiskId.of(ZONE, diskName); @@ -1258,15 +1207,11 @@ public void testCreateGetAndDeprecateImage() throws InterruptedException { DiskInfo diskInfo = DiskInfo.of(diskId, StandardDiskConfiguration.of(DiskTypeId.of(ZONE, "pd-ssd"), 100L)); Operation operation = compute.create(diskInfo); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); Disk remoteDisk = compute.getDisk(diskId); ImageInfo imageInfo = ImageInfo.of(imageId, DiskImageConfiguration.of(diskId)); operation = compute.create(imageInfo); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); // test get image with selected fields Image image = compute.getImage(imageId, Compute.ImageOption.fields(Compute.ImageField.CREATION_TIMESTAMP)); @@ -1302,16 +1247,12 @@ public void testCreateGetAndDeprecateImage() throws InterruptedException { .deprecated(System.currentTimeMillis()) .build(); operation = image.deprecate(deprecationStatus); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); image = compute.getImage(imageId); assertEquals(deprecationStatus, image.deprecationStatus()); remoteDisk.delete(); operation = image.delete(); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); assertNull(compute.getImage(imageId)); } @@ -1376,15 +1317,13 @@ public void testListImagesWithFilter() { } @Test - public void testCreateAndGetNetwork() throws InterruptedException { + public void testCreateAndGetNetwork() throws InterruptedException, TimeoutException { String name = BASE_RESOURCE_NAME + "create-and-get-network"; NetworkId networkId = NetworkId.of(name); NetworkInfo networkInfo = NetworkInfo.of(networkId, StandardNetworkConfiguration.of("192.168.0.0/16")); Operation operation = compute.create(networkInfo); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); // test get network with selected fields Network network = compute.getNetwork(networkId.network(), Compute.NetworkOption.fields(Compute.NetworkField.CREATION_TIMESTAMP)); @@ -1404,22 +1343,18 @@ public void testCreateAndGetNetwork() throws InterruptedException { remoteConfiguration = network.configuration(); assertEquals("192.168.0.0/16", remoteConfiguration.ipRange()); operation = network.delete(); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); assertNull(compute.getNetwork(name)); } @Test - public void testListNetworks() throws InterruptedException { + public void testListNetworks() throws InterruptedException, TimeoutException { String name = BASE_RESOURCE_NAME + "list-network"; NetworkId networkId = NetworkId.of(name); NetworkInfo networkInfo = NetworkInfo.of(networkId, StandardNetworkConfiguration.of("192.168.0.0/16")); Operation operation = compute.create(networkInfo); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); // test list Compute.NetworkFilter filter = Compute.NetworkFilter.equals(Compute.NetworkField.NAME, name); Page networkPage = compute.listNetworks(Compute.NetworkListOption.filter(filter)); @@ -1454,21 +1389,17 @@ public void testListNetworks() throws InterruptedException { } assertEquals(1, count); operation = compute.deleteNetwork(networkId); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); assertNull(compute.getNetwork(name)); } @Test - public void testCreateNetworkAndSubnetwork() throws InterruptedException { + public void testCreateNetworkAndSubnetwork() throws InterruptedException, TimeoutException { String networkName = BASE_RESOURCE_NAME + "create-subnetwork-network"; NetworkId networkId = NetworkId.of(networkName); NetworkInfo networkInfo = NetworkInfo.of(networkId, SubnetNetworkConfiguration.of(false)); Operation operation = compute.create(networkInfo); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); // test get network Network network = compute.getNetwork(networkId.network()); assertEquals(networkId.network(), network.networkId().network()); @@ -1481,9 +1412,7 @@ public void testCreateNetworkAndSubnetwork() throws InterruptedException { SubnetworkId subnetworkId = SubnetworkId.of(REGION, subnetworkName); SubnetworkInfo subnetworkInfo = SubnetworkInfo.of(subnetworkId, networkId, "192.168.0.0/16"); operation = compute.create(subnetworkInfo); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); // test get subnetwork with selected fields Subnetwork subnetwork = compute.getSubnetwork(subnetworkId, Compute.SubnetworkOption.fields(Compute.SubnetworkField.CREATION_TIMESTAMP)); @@ -1538,26 +1467,20 @@ public void testCreateNetworkAndSubnetwork() throws InterruptedException { } assertEquals(1, count); operation = subnetwork.delete(); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); operation = compute.deleteNetwork(networkId); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); assertNull(compute.getSubnetwork(subnetworkId)); assertNull(compute.getNetwork(networkName)); } @Test - public void testAggregatedListSubnetworks() throws InterruptedException { + public void testAggregatedListSubnetworks() throws InterruptedException, TimeoutException { String networkName = BASE_RESOURCE_NAME + "list-subnetwork-network"; NetworkId networkId = NetworkId.of(networkName); NetworkInfo networkInfo = NetworkInfo.of(networkId, SubnetNetworkConfiguration.of(false)); Operation operation = compute.create(networkInfo); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); String prefix = BASE_RESOURCE_NAME + "list-subnetwork"; String[] regionNames = {"us-central1", "us-east1"}; String[] subnetworkNames = {prefix + "1", prefix + "2"}; @@ -1570,12 +1493,8 @@ public void testAggregatedListSubnetworks() throws InterruptedException { SubnetworkInfo.of(secondSubnetworkId, networkId, ipRanges[1]); Operation firstOperation = compute.create(firstSubnetworkInfo); Operation secondOperation = compute.create(secondSubnetworkInfo); - while (!firstOperation.isDone()) { - Thread.sleep(1000L); - } - while (!secondOperation.isDone()) { - Thread.sleep(1000L); - } + firstOperation.waitFor(); + secondOperation.waitFor(); Set regionSet = ImmutableSet.copyOf(regionNames); Set subnetworkSet = ImmutableSet.copyOf(subnetworkNames); Set rangeSet = ImmutableSet.copyOf(ipRanges); @@ -1599,30 +1518,22 @@ public void testAggregatedListSubnetworks() throws InterruptedException { assertEquals(2, count); firstOperation = compute.deleteSubnetwork(firstSubnetworkId); secondOperation = compute.deleteSubnetwork(secondSubnetworkId); - while (!firstOperation.isDone()) { - Thread.sleep(1000L); - } - while (!secondOperation.isDone()) { - Thread.sleep(1000L); - } + firstOperation.waitFor(); + secondOperation.waitFor(); operation = compute.deleteNetwork(networkId); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); assertNull(compute.getNetwork(networkName)); } @Test - public void testCreateGetAndDeleteInstance() throws InterruptedException { + public void testCreateGetAndDeleteInstance() throws InterruptedException, TimeoutException { String instanceName = BASE_RESOURCE_NAME + "create-and-get-instance"; String addressName = BASE_RESOURCE_NAME + "create-and-get-instance-address"; // Create an address to assign to the instance AddressId addressId = RegionAddressId.of(REGION, addressName); AddressInfo addressInfo = AddressInfo.of(addressId); Operation operation = compute.create(addressInfo); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); Address address = compute.getAddress(addressId); // Create an instance InstanceId instanceId = InstanceId.of(ZONE, instanceName); @@ -1640,9 +1551,7 @@ public void testCreateGetAndDeleteInstance() throws InterruptedException { .networkInterfaces(networkInterface) .build(); operation = compute.create(instanceInfo); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); // test get Instance remoteInstance = compute.getInstance(instanceId); assertEquals(instanceName, remoteInstance.instanceId().instance()); @@ -1694,15 +1603,13 @@ public void testCreateGetAndDeleteInstance() throws InterruptedException { String newSerialPortOutput = remoteInstance.getSerialPortOutput(1); assertTrue(newSerialPortOutput.contains(serialPortOutput)); operation = remoteInstance.delete(); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); assertNull(compute.getInstance(instanceId)); address.delete(); } @Test - public void testStartStopAndResetInstance() throws InterruptedException { + public void testStartStopAndResetInstance() throws InterruptedException, TimeoutException { String instanceName = BASE_RESOURCE_NAME + "start-stop-reset-instance"; InstanceId instanceId = InstanceId.of(ZONE, instanceName); NetworkId networkId = NetworkId.of("default"); @@ -1715,30 +1622,22 @@ public void testStartStopAndResetInstance() throws InterruptedException { .networkInterfaces(networkInterface) .build(); Operation operation = compute.create(instanceInfo); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); Instance remoteInstance = compute.getInstance(instanceId, Compute.InstanceOption.fields(Compute.InstanceField.STATUS)); assertEquals(InstanceInfo.Status.RUNNING, remoteInstance.status()); operation = remoteInstance.stop(); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); remoteInstance = compute.getInstance(instanceId, Compute.InstanceOption.fields(Compute.InstanceField.STATUS)); assertEquals(InstanceInfo.Status.TERMINATED, remoteInstance.status()); operation = remoteInstance.start(); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); remoteInstance = compute.getInstance(instanceId, Compute.InstanceOption.fields(Compute.InstanceField.STATUS)); assertEquals(InstanceInfo.Status.RUNNING, remoteInstance.status()); operation = remoteInstance.reset(); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); remoteInstance = compute.getInstance(instanceId, Compute.InstanceOption.fields(Compute.InstanceField.STATUS)); assertEquals(InstanceInfo.Status.RUNNING, remoteInstance.status()); @@ -1746,7 +1645,7 @@ public void testStartStopAndResetInstance() throws InterruptedException { } @Test - public void testSetInstanceProperties() throws InterruptedException { + public void testSetInstanceProperties() throws InterruptedException, TimeoutException { String instanceName = BASE_RESOURCE_NAME + "set-properties-instance"; InstanceId instanceId = InstanceId.of(ZONE, instanceName); NetworkId networkId = NetworkId.of("default"); @@ -1759,51 +1658,39 @@ public void testSetInstanceProperties() throws InterruptedException { .networkInterfaces(networkInterface) .build(); Operation operation = compute.create(instanceInfo); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); Instance remoteInstance = compute.getInstance(instanceId); // test set tags List tags = ImmutableList.of("tag1", "tag2"); operation = remoteInstance.setTags(tags); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); remoteInstance = compute.getInstance(instanceId); assertEquals(tags, remoteInstance.tags().values()); // test set metadata Map metadata = ImmutableMap.of("key", "value"); operation = remoteInstance.setMetadata(metadata); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); remoteInstance = compute.getInstance(instanceId); assertEquals(metadata, remoteInstance.metadata().values()); // test set machine type operation = remoteInstance.stop(); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); operation = remoteInstance.setMachineType(MachineTypeId.of(ZONE, "n1-standard-1")); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); remoteInstance = compute.getInstance(instanceId); assertEquals("n1-standard-1", remoteInstance.machineType().type()); assertEquals(ZONE, remoteInstance.machineType().zone()); // test set scheduling options SchedulingOptions options = SchedulingOptions.standard(false, SchedulingOptions.Maintenance.TERMINATE); operation = remoteInstance.setSchedulingOptions(options); - while (!operation.isDone()) { - Thread.sleep(1000L); - } +operation.waitFor(); remoteInstance = compute.getInstance(instanceId); assertEquals(options, remoteInstance.schedulingOptions()); remoteInstance.delete(); } @Test - public void testAttachAndDetachDisk() throws InterruptedException { + public void testAttachAndDetachDisk() throws InterruptedException, TimeoutException { String instanceName = BASE_RESOURCE_NAME + "attach-and-detach-disk-instance"; String diskName = BASE_RESOURCE_NAME + "attach-and-detach-disk"; InstanceId instanceId = InstanceId.of(ZONE, instanceName); @@ -1820,19 +1707,13 @@ public void testAttachAndDetachDisk() throws InterruptedException { DiskId diskId = DiskId.of(ZONE, diskName); Operation diskOperation = compute.create(DiskInfo.of(diskId, StandardDiskConfiguration.of(DiskTypeId.of(ZONE, "pd-ssd")))); - while (!instanceOperation.isDone()) { - Thread.sleep(1000L); - } - while (!diskOperation.isDone()) { - Thread.sleep(1000L); - } + instanceOperation.waitFor(); + diskOperation.waitFor(); Instance remoteInstance = compute.getInstance(instanceId); // test attach disk instanceOperation = remoteInstance.attachDisk("dev1", AttachedDisk.PersistentDiskConfiguration.builder(diskId).build()); - while (!instanceOperation.isDone()) { - Thread.sleep(1000L); - } + instanceOperation.waitFor(); remoteInstance = compute.getInstance(instanceId); Set deviceSet = ImmutableSet.of("dev0", "dev1"); assertEquals(2, remoteInstance.attachedDisks().size()); @@ -1841,9 +1722,7 @@ public void testAttachAndDetachDisk() throws InterruptedException { } // test set disk auto-delete instanceOperation = remoteInstance.setDiskAutoDelete("dev1", true); - while (!instanceOperation.isDone()) { - Thread.sleep(1000L); - } + instanceOperation.waitFor(); remoteInstance = compute.getInstance(instanceId); assertEquals(2, remoteInstance.attachedDisks().size()); for (AttachedDisk remoteAttachedDisk : remoteInstance.attachedDisks()) { @@ -1852,9 +1731,7 @@ public void testAttachAndDetachDisk() throws InterruptedException { } // test detach disk instanceOperation = remoteInstance.detachDisk("dev1"); - while (!instanceOperation.isDone()) { - Thread.sleep(1000L); - } + instanceOperation.waitFor(); remoteInstance = compute.getInstance(instanceId); assertEquals(1, remoteInstance.attachedDisks().size()); assertEquals("dev0", remoteInstance.attachedDisks().get(0).deviceName()); @@ -1863,7 +1740,7 @@ public void testAttachAndDetachDisk() throws InterruptedException { } @Test - public void testAddAndRemoveAccessConfig() throws InterruptedException { + public void testAddAndRemoveAccessConfig() throws InterruptedException, TimeoutException { String instanceName = BASE_RESOURCE_NAME + "add-and-remove-access-instance"; String addressName = BASE_RESOURCE_NAME + "add-and-remove-access-address"; InstanceId instanceId = InstanceId.of(ZONE, instanceName); @@ -1880,15 +1757,8 @@ public void testAddAndRemoveAccessConfig() throws InterruptedException { AddressId addressId = RegionAddressId.of(REGION, addressName); AddressInfo addressInfo = AddressInfo.of(addressId); Operation addressOperation = compute.create(addressInfo); - while (!addressOperation.isDone()) { - Thread.sleep(1000L); - } - while (!instanceOperation.isDone()) { - Thread.sleep(1000L); - } - while (!addressOperation.isDone()) { - Thread.sleep(1000L); - } + addressOperation.waitFor(); + instanceOperation.waitFor(); Address remoteAddress = compute.getAddress(addressId); Instance remoteInstance = compute.getInstance(instanceId); String networkInterfaceName = remoteInstance.networkInterfaces().get(0).name(); @@ -1898,9 +1768,7 @@ public void testAddAndRemoveAccessConfig() throws InterruptedException { .name("NAT") .build(); instanceOperation = remoteInstance.addAccessConfig(networkInterfaceName, accessConfig); - while (!instanceOperation.isDone()) { - Thread.sleep(1000L); - } + instanceOperation.waitFor(); remoteInstance = compute.getInstance(instanceId); List accessConfigurations = remoteInstance.networkInterfaces().get(0).accessConfigurations(); @@ -1908,9 +1776,7 @@ public void testAddAndRemoveAccessConfig() throws InterruptedException { assertEquals("NAT", accessConfigurations.get(0).name()); // test delete access config instanceOperation = remoteInstance.deleteAccessConfig(networkInterfaceName, "NAT"); - while (!instanceOperation.isDone()) { - Thread.sleep(1000L); - } + instanceOperation.waitFor(); remoteInstance = compute.getInstance(instanceId); assertTrue(remoteInstance.networkInterfaces().get(0).accessConfigurations().isEmpty()); remoteInstance.delete(); diff --git a/gcloud-java-core/src/main/java/com/google/cloud/WaitForOption.java b/gcloud-java-core/src/main/java/com/google/cloud/WaitForOption.java new file mode 100644 index 000000000000..8af7a074ab4d --- /dev/null +++ b/gcloud-java-core/src/main/java/com/google/cloud/WaitForOption.java @@ -0,0 +1,225 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.MoreObjects; + +import java.io.Serializable; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * This class represents options for methods that wait for changes in the status of a resource. + */ +public abstract class WaitForOption implements Serializable { + + private static final long serialVersionUID = 8443451708032349243L; + + private final OptionType optionType; + + enum OptionType { + CHECKING_PERIOD, + TIMEOUT + } + + private WaitForOption(OptionType optionType) { + this.optionType = optionType; + } + + /** + * This class represents an option to set how frequently the resource status should be checked. + * Objects of this class keep the actual period and related time unit for the checking period. + */ + public static final class CheckingPeriod extends WaitForOption { + + private static final long serialVersionUID = -2481062893220539210L; + private static final CheckingPeriod DEFAULT = new CheckingPeriod(500, TimeUnit.MILLISECONDS); + + private final long period; + private final TimeUnit unit; + + private CheckingPeriod(long period, TimeUnit unit) { + super(OptionType.CHECKING_PERIOD); + this.period = period; + this.unit = unit; + } + + /** + * Returns the checking period. + */ + public long period() { + return period; + } + + /** + * Returns the time unit for {@link #period()}. + */ + public TimeUnit unit() { + return unit; + } + + /** + * Blocks the current thread for the amount of time specified by this object. + * + * @throws InterruptedException if the current thread was interrupted + */ + public void sleep() throws InterruptedException { + unit.sleep(period); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || !(obj instanceof CheckingPeriod)) { + return false; + } + CheckingPeriod other = (CheckingPeriod) obj; + return baseEquals(other) + && Objects.equals(period, other.period) + && Objects.equals(unit, other.unit); + } + + @Override + public int hashCode() { + return Objects.hash(baseHashCode(), period, unit); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("period", period) + .add("unit", unit) + .toString(); + } + + /** + * Returns the {@code CheckingPeriod} option specified in {@code options}. If no + * {@code CheckingPeriod} could be found among {@code options}, the default checking period (500 + * milliseconds) is used. + */ + public static CheckingPeriod getOrDefault(WaitForOption... options) { + return getOrDefaultInternal(OptionType.CHECKING_PERIOD, DEFAULT, options); + } + } + + /** + * This class represents an option to set the maximum time to wait for the resource's status to + * reach the desired state. + */ + public static final class Timeout extends WaitForOption { + + private static final long serialVersionUID = -7120401111985321932L; + private static final Timeout DEFAULT = new Timeout(-1); + + private final long timeoutMillis; + + private Timeout(long timeoutMillis) { + super(OptionType.TIMEOUT); + this.timeoutMillis = timeoutMillis; + } + + /** + * Returns the timeout in milliseconds. + */ + public long timeoutMillis() { + return timeoutMillis; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || !(obj instanceof Timeout)) { + return false; + } + Timeout other = (Timeout) obj; + return baseEquals(other) && Objects.equals(timeoutMillis, other.timeoutMillis); + } + + @Override + public int hashCode() { + return Objects.hash(baseHashCode(), timeoutMillis); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("timeoutMillis", timeoutMillis) + .toString(); + } + + /** + * Returns the {@code Timeout} option specified in {@code options}. If no {@code Timeout} could + * be found among {@code options}, no timeout will be used. + */ + public static Timeout getOrDefault(WaitForOption... options) { + return getOrDefaultInternal(OptionType.TIMEOUT, DEFAULT, options); + } + } + + OptionType optionType() { + return optionType; + } + + final boolean baseEquals(WaitForOption option) { + return Objects.equals(option.optionType, option.optionType); + } + + final int baseHashCode() { + return Objects.hash(optionType); + } + + @SuppressWarnings("unchecked") + private static T getOrDefaultInternal(OptionType optionType, + T defaultValue, WaitForOption... options) { + T foundOption = null; + for (WaitForOption option : options) { + if (option.optionType.equals(optionType)) { + checkArgument(foundOption == null, "Duplicate option %s", option); + foundOption = (T) option; + } + } + return foundOption != null ? foundOption : defaultValue; + } + + /** + * Returns an option to set how frequently the resource status should be checked. + * + * @param checkEvery the checking period + * @param unit the time unit of the checking period + */ + public static CheckingPeriod checkEvery(long checkEvery, TimeUnit unit) { + checkArgument(checkEvery >= 0, "checkEvery must be >= 0"); + return new CheckingPeriod(checkEvery, unit); + } + + /** + * Returns an option to set the maximum time to wait. + * + * @param timeout the maximum time to wait, expressed in {@code unit} + * @param unit the time unit of the timeout + */ + public static Timeout timeout(long timeout, TimeUnit unit) { + checkArgument(timeout >= 0, "timeout must be >= 0"); + return new Timeout(unit.toMillis(timeout)); + } +} diff --git a/gcloud-java-core/src/test/java/com/google/cloud/SerializationTest.java b/gcloud-java-core/src/test/java/com/google/cloud/SerializationTest.java index 7fa778a524eb..53baa57b96ca 100644 --- a/gcloud-java-core/src/test/java/com/google/cloud/SerializationTest.java +++ b/gcloud-java-core/src/test/java/com/google/cloud/SerializationTest.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.io.Serializable; import java.util.Date; +import java.util.concurrent.TimeUnit; public class SerializationTest extends BaseSerializationTest { @@ -36,6 +37,8 @@ public class SerializationTest extends BaseSerializationTest { new SigningException("message", BASE_SERVICE_EXCEPTION); private static final RetryParams RETRY_PARAMS = RetryParams.defaultInstance(); private static final SomeIamPolicy SOME_IAM_POLICY = new SomeIamPolicy.Builder().build(); + private static final WaitForOption CHECKING_PERIOD = + WaitForOption.checkEvery(42, TimeUnit.SECONDS); private static final String JSON_KEY = "{\n" + " \"private_key_id\": \"somekeyid\",\n" + " \"private_key\": \"-----BEGIN PRIVATE KEY-----\\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggS" @@ -88,7 +91,7 @@ public Builder toBuilder() { @Override protected Serializable[] serializableObjects() { return new Serializable[]{BASE_SERVICE_EXCEPTION, EXCEPTION_HANDLER, IDENTITY, PAGE, - RETRY_PARAMS, SOME_IAM_POLICY, SIGNING_EXCEPTION}; + RETRY_PARAMS, SOME_IAM_POLICY, SIGNING_EXCEPTION, CHECKING_PERIOD}; } @Override diff --git a/gcloud-java-core/src/test/java/com/google/cloud/WaitForOptionTest.java b/gcloud-java-core/src/test/java/com/google/cloud/WaitForOptionTest.java new file mode 100644 index 000000000000..82996e1ca3f8 --- /dev/null +++ b/gcloud-java-core/src/test/java/com/google/cloud/WaitForOptionTest.java @@ -0,0 +1,124 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import com.google.cloud.WaitForOption.CheckingPeriod; +import com.google.cloud.WaitForOption.OptionType; +import com.google.cloud.WaitForOption.Timeout; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.concurrent.TimeUnit; + +public class WaitForOptionTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private static final CheckingPeriod CHECKING_PERIOD_OPTION = + WaitForOption.checkEvery(42, TimeUnit.MILLISECONDS); + private static final Timeout TIMEOUT_OPTION = WaitForOption.timeout(43, TimeUnit.MILLISECONDS); + + @Test + public void testCheckEvery() { + assertEquals(OptionType.CHECKING_PERIOD, CHECKING_PERIOD_OPTION.optionType()); + assertEquals(42, CHECKING_PERIOD_OPTION.period()); + assertEquals(TimeUnit.MILLISECONDS, CHECKING_PERIOD_OPTION.unit()); + } + + @Test + public void testCheckEvery_InvalidPeriod() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("checkEvery must be >= 0"); + WaitForOption.checkEvery(-1, TimeUnit.MILLISECONDS); + } + + @Test + public void testTimeout() { + assertEquals(OptionType.TIMEOUT, TIMEOUT_OPTION.optionType()); + assertEquals(43, TIMEOUT_OPTION.timeoutMillis()); + Timeout timeoutOption = WaitForOption.timeout(43, TimeUnit.SECONDS); + assertEquals(43_000, timeoutOption.timeoutMillis()); + } + + @Test + public void testTimeout_InvalidTimeout() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("timeout must be >= 0"); + WaitForOption.timeout(-1, TimeUnit.MILLISECONDS); + } + + @Test + public void testEqualsAndHashCode() { + assertEquals(CHECKING_PERIOD_OPTION, CHECKING_PERIOD_OPTION); + assertEquals(TIMEOUT_OPTION, TIMEOUT_OPTION); + assertEquals(CHECKING_PERIOD_OPTION.hashCode(), CHECKING_PERIOD_OPTION.hashCode()); + assertEquals(TIMEOUT_OPTION.hashCode(), TIMEOUT_OPTION.hashCode()); + WaitForOption checkingPeriodOption = WaitForOption.checkEvery(42, TimeUnit.MILLISECONDS); + assertEquals(CHECKING_PERIOD_OPTION, checkingPeriodOption); + assertEquals(CHECKING_PERIOD_OPTION.hashCode(), checkingPeriodOption.hashCode()); + WaitForOption timeoutOption = WaitForOption.timeout(43, TimeUnit.MILLISECONDS); + assertEquals(TIMEOUT_OPTION, timeoutOption); + assertEquals(TIMEOUT_OPTION.hashCode(), timeoutOption.hashCode()); + assertNotEquals(CHECKING_PERIOD_OPTION, TIMEOUT_OPTION); + assertNotEquals(CHECKING_PERIOD_OPTION.hashCode(), TIMEOUT_OPTION.hashCode()); + checkingPeriodOption = WaitForOption.checkEvery(43, TimeUnit.MILLISECONDS); + assertNotEquals(CHECKING_PERIOD_OPTION, checkingPeriodOption); + assertNotEquals(CHECKING_PERIOD_OPTION.hashCode(), checkingPeriodOption.hashCode()); + checkingPeriodOption = WaitForOption.checkEvery(42, TimeUnit.SECONDS); + assertNotEquals(CHECKING_PERIOD_OPTION, checkingPeriodOption); + assertNotEquals(CHECKING_PERIOD_OPTION.hashCode(), checkingPeriodOption.hashCode()); + timeoutOption = WaitForOption.timeout(42, TimeUnit.MILLISECONDS); + assertNotEquals(TIMEOUT_OPTION, timeoutOption); + assertNotEquals(TIMEOUT_OPTION.hashCode(), timeoutOption.hashCode()); + timeoutOption = WaitForOption.timeout(43, TimeUnit.SECONDS); + assertNotEquals(TIMEOUT_OPTION, timeoutOption); + assertNotEquals(TIMEOUT_OPTION.hashCode(), timeoutOption.hashCode()); + } + + @Test + public void testGetOrDefault() { + assertEquals(CHECKING_PERIOD_OPTION, + CheckingPeriod.getOrDefault(CHECKING_PERIOD_OPTION, TIMEOUT_OPTION)); + assertEquals(TIMEOUT_OPTION, + Timeout.getOrDefault(CHECKING_PERIOD_OPTION, TIMEOUT_OPTION)); + CheckingPeriod checkingPeriod = CheckingPeriod.getOrDefault(TIMEOUT_OPTION); + assertEquals(500, checkingPeriod.period()); + assertEquals(TimeUnit.MILLISECONDS, checkingPeriod.unit()); + Timeout timeout = Timeout.getOrDefault(CHECKING_PERIOD_OPTION); + assertEquals(-1, timeout.timeoutMillis()); + } + + @Test + public void testCheckingPeriodGetOrDefault_DuplicateOption() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage(String.format("Duplicate option %s", CHECKING_PERIOD_OPTION)); + CheckingPeriod.getOrDefault(CHECKING_PERIOD_OPTION, CHECKING_PERIOD_OPTION); + } + + @Test + public void testTimeoutGetOrDefault_DuplicateOption() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage(String.format("Duplicate option %s", TIMEOUT_OPTION)); + Timeout.getOrDefault(TIMEOUT_OPTION, TIMEOUT_OPTION); + } +} diff --git a/gcloud-java-examples/src/main/java/com/google/cloud/examples/bigquery/BigQueryExample.java b/gcloud-java-examples/src/main/java/com/google/cloud/examples/bigquery/BigQueryExample.java index 6acfb5bbef6d..156e475a5ac9 100644 --- a/gcloud-java-examples/src/main/java/com/google/cloud/examples/bigquery/BigQueryExample.java +++ b/gcloud-java-examples/src/main/java/com/google/cloud/examples/bigquery/BigQueryExample.java @@ -528,6 +528,7 @@ void run(BigQuery bigquery, JobInfo job) throws Exception { System.out.println("Waiting for job " + startedJob.jobId().job() + " to complete"); Thread.sleep(1000L); } + startedJob = startedJob.reload(); if (startedJob.status().error() == null) { System.out.println("Job " + startedJob.jobId().job() + " succeeded"); } else { diff --git a/gcloud-java-examples/src/main/java/com/google/cloud/examples/bigquery/snippets/CreateTableAndLoadData.java b/gcloud-java-examples/src/main/java/com/google/cloud/examples/bigquery/snippets/CreateTableAndLoadData.java index 01290ec8b491..6772d79a73ca 100644 --- a/gcloud-java-examples/src/main/java/com/google/cloud/examples/bigquery/snippets/CreateTableAndLoadData.java +++ b/gcloud-java-examples/src/main/java/com/google/cloud/examples/bigquery/snippets/CreateTableAndLoadData.java @@ -33,6 +33,8 @@ import com.google.cloud.bigquery.TableId; import com.google.cloud.bigquery.TableInfo; +import java.util.concurrent.TimeoutException; + /** * A snippet for Google Cloud BigQuery showing how to get a BigQuery table or create it if it does * not exist. The snippet also starts a BigQuery job to load data into the table from a Cloud @@ -40,7 +42,7 @@ */ public class CreateTableAndLoadData { - public static void main(String... args) throws InterruptedException { + public static void main(String... args) throws InterruptedException, TimeoutException { BigQuery bigquery = BigQueryOptions.defaultInstance().service(); TableId tableId = TableId.of("dataset", "table"); Table table = bigquery.getTable(tableId); @@ -52,9 +54,7 @@ public static void main(String... args) throws InterruptedException { } System.out.println("Loading data into table " + tableId); Job loadJob = table.load(FormatOptions.csv(), "gs://bucket/path"); - while (!loadJob.isDone()) { - Thread.sleep(1000L); - } + loadJob = loadJob.waitFor(); if (loadJob.status().error() != null) { System.out.println("Job completed with errors"); } else { diff --git a/gcloud-java-examples/src/main/java/com/google/cloud/examples/compute/snippets/CreateAddressDiskAndInstance.java b/gcloud-java-examples/src/main/java/com/google/cloud/examples/compute/snippets/CreateAddressDiskAndInstance.java index 5334f746c95b..0a63865904e0 100644 --- a/gcloud-java-examples/src/main/java/com/google/cloud/examples/compute/snippets/CreateAddressDiskAndInstance.java +++ b/gcloud-java-examples/src/main/java/com/google/cloud/examples/compute/snippets/CreateAddressDiskAndInstance.java @@ -35,13 +35,15 @@ import com.google.cloud.compute.Operation; import com.google.cloud.compute.RegionAddressId; +import java.util.concurrent.TimeoutException; + /** * A snippet for Google Cloud Compute Engine showing how to create a disk and an address. The * snippet also shows how to create a virtual machine instance using the created disk and address. */ public class CreateAddressDiskAndInstance { - public static void main(String... args) throws InterruptedException { + public static void main(String... args) throws InterruptedException, TimeoutException { // Create a service object // Credentials are inferred from the environment. Compute compute = ComputeOptions.defaultInstance().service(); @@ -50,11 +52,7 @@ public static void main(String... args) throws InterruptedException { RegionAddressId addressId = RegionAddressId.of("us-central1", "test-address"); Operation operation = compute.create(AddressInfo.of(addressId)); // Wait for operation to complete - while (!operation.isDone()) { - Thread.sleep(1000L); - } - // Check operation errors - operation = operation.reload(); + operation = operation.waitFor(); if (operation.errors() == null) { System.out.println("Address " + addressId + " was successfully created"); } else { @@ -69,11 +67,7 @@ public static void main(String... args) throws InterruptedException { DiskInfo disk = DiskInfo.of(diskId, diskConfiguration); operation = compute.create(disk); // Wait for operation to complete - while (!operation.isDone()) { - Thread.sleep(1000L); - } - // Check operation errors - operation = operation.reload(); + operation = operation.waitFor(); if (operation.errors() == null) { System.out.println("Disk " + diskId + " was successfully created"); } else { @@ -96,11 +90,7 @@ public static void main(String... args) throws InterruptedException { InstanceInfo.of(instanceId, machineTypeId, attachedDisk, networkInterface); operation = compute.create(instance); // Wait for operation to complete - while (!operation.isDone()) { - Thread.sleep(1000L); - } - // Check operation errors - operation = operation.reload(); + operation = operation.waitFor(); if (operation.errors() == null) { System.out.println("Instance " + instanceId + " was successfully created"); } else { diff --git a/gcloud-java-examples/src/main/java/com/google/cloud/examples/compute/snippets/CreateInstance.java b/gcloud-java-examples/src/main/java/com/google/cloud/examples/compute/snippets/CreateInstance.java index d8162908d133..66c10bace269 100644 --- a/gcloud-java-examples/src/main/java/com/google/cloud/examples/compute/snippets/CreateInstance.java +++ b/gcloud-java-examples/src/main/java/com/google/cloud/examples/compute/snippets/CreateInstance.java @@ -28,12 +28,14 @@ import com.google.cloud.compute.NetworkInterface; import com.google.cloud.compute.Operation; +import java.util.concurrent.TimeoutException; + /** * A snippet for Google Cloud Compute Engine showing how to create a virtual machine instance. */ public class CreateInstance { - public static void main(String... args) throws InterruptedException { + public static void main(String... args) throws InterruptedException, TimeoutException { Compute compute = ComputeOptions.defaultInstance().service(); ImageId imageId = ImageId.of("debian-cloud", "debian-8-jessie-v20160329"); NetworkId networkId = NetworkId.of("default"); @@ -43,9 +45,7 @@ public static void main(String... args) throws InterruptedException { MachineTypeId machineTypeId = MachineTypeId.of("us-central1-a", "n1-standard-1"); Operation operation = compute.create(InstanceInfo.of(instanceId, machineTypeId, attachedDisk, networkInterface)); - while (!operation.isDone()) { - Thread.sleep(1000L); - } + operation = operation.waitFor(); if (operation.errors() == null) { // use instance Instance instance = compute.getInstance(instanceId); diff --git a/gcloud-java-examples/src/main/java/com/google/cloud/examples/compute/snippets/CreateSnapshot.java b/gcloud-java-examples/src/main/java/com/google/cloud/examples/compute/snippets/CreateSnapshot.java index cc8029936186..35d19e38e18e 100644 --- a/gcloud-java-examples/src/main/java/com/google/cloud/examples/compute/snippets/CreateSnapshot.java +++ b/gcloud-java-examples/src/main/java/com/google/cloud/examples/compute/snippets/CreateSnapshot.java @@ -23,25 +23,25 @@ import com.google.cloud.compute.Operation; import com.google.cloud.compute.Snapshot; +import java.util.concurrent.TimeoutException; + /** * A snippet for Google Cloud Compute Engine showing how to create a snapshot of a disk if the disk * exists. */ public class CreateSnapshot { - public static void main(String... args) throws InterruptedException { + public static void main(String... args) throws InterruptedException, TimeoutException { Compute compute = ComputeOptions.defaultInstance().service(); DiskId diskId = DiskId.of("us-central1-a", "disk-name"); Disk disk = compute.getDisk(diskId, Compute.DiskOption.fields()); if (disk != null) { String snapshotName = "disk-name-snapshot"; Operation operation = disk.createSnapshot(snapshotName); - while (!operation.isDone()) { - Thread.sleep(1000L); - } + operation = operation.waitFor(); if (operation.errors() == null) { // use snapshot - Snapshot snapshot = compute.getSnapshot("disk-name-snapshot"); + Snapshot snapshot = compute.getSnapshot(snapshotName); } } }