From c95e1ec86248ed100abc95a753c2e46221e96fda Mon Sep 17 00:00:00 2001 From: Martin Derka Date: Wed, 13 Apr 2016 16:45:47 -0400 Subject: [PATCH] Third concept of DNS batch. (#787) * Added third concept of DNS batch. * Simplified the internals. Removed DnsBatch.Request. * Work in progress. Implemented more calls and added tests. * Turned BatchResult into an abstract class. * Created RpcBatch interface for opacity of batch. Added core unit test for BatchResult. Added javadoc for BatchResult generics. Changed prefix of newCallback methods. Fixed method javadoc. * Fixed documentation. * Removed addToBatch methods from RPC. * Removed conflicts with master branch. * Implemented batch processing for change requests and record sets. * Implemented the rest of the batch functions and tests. - Renames several forgotten DnsRecord variables and functions in tests - Moved the Callback interface from DnsRpc to RpcBatch * Added tests for callbacks. Fixed documentation. * Added onFailure callback tests and fixed one doc string. * Extracted GoogleJsonError to a final attribute. * Fixed imports and implemented notify. * Fixed import orders * Annotated getters as @VisibleForTesting. * Fixed docs and renamed a few methods. Also consolidated mocks batch in tests. * Added notify test * Renamed submitted() to completed(). * Consolidated more tests and added exception to notify. * Added test for null result in BatchResult. --- .../java/com/google/cloud/BatchResult.java | 106 +++ .../com/google/cloud/BatchResultTest.java | 107 +++ .../com/google/cloud/dns/ChangeRequest.java | 4 +- .../main/java/com/google/cloud/dns/Dns.java | 5 + .../java/com/google/cloud/dns/DnsBatch.java | 354 ++++++++++ .../com/google/cloud/dns/DnsBatchResult.java | 38 ++ .../com/google/cloud/dns/DnsException.java | 5 + .../java/com/google/cloud/dns/DnsImpl.java | 39 +- .../java/com/google/cloud/dns/RecordSet.java | 2 +- .../google/cloud/dns/spi/DefaultDnsRpc.java | 268 ++++++-- .../java/com/google/cloud/dns/spi/DnsRpc.java | 5 + .../com/google/cloud/dns/spi/RpcBatch.java | 117 ++++ .../google/cloud/dns/DnsBatchResultTest.java | 110 +++ .../com/google/cloud/dns/DnsBatchTest.java | 632 ++++++++++++++++++ .../com/google/cloud/dns/DnsImplTest.java | 22 +- .../google/cloud/dns/SerializationTest.java | 2 + .../com/google/cloud/dns/it/ITDnsTest.java | 28 +- 17 files changed, 1772 insertions(+), 72 deletions(-) create mode 100644 gcloud-java-core/src/main/java/com/google/cloud/BatchResult.java create mode 100644 gcloud-java-core/src/test/java/com/google/cloud/BatchResultTest.java create mode 100644 gcloud-java-dns/src/main/java/com/google/cloud/dns/DnsBatch.java create mode 100644 gcloud-java-dns/src/main/java/com/google/cloud/dns/DnsBatchResult.java create mode 100644 gcloud-java-dns/src/main/java/com/google/cloud/dns/spi/RpcBatch.java create mode 100644 gcloud-java-dns/src/test/java/com/google/cloud/dns/DnsBatchResultTest.java create mode 100644 gcloud-java-dns/src/test/java/com/google/cloud/dns/DnsBatchTest.java diff --git a/gcloud-java-core/src/main/java/com/google/cloud/BatchResult.java b/gcloud-java-core/src/main/java/com/google/cloud/BatchResult.java new file mode 100644 index 000000000000..388551d5aaea --- /dev/null +++ b/gcloud-java-core/src/main/java/com/google/cloud/BatchResult.java @@ -0,0 +1,106 @@ +/* + * 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.checkState; + +import java.util.LinkedList; +import java.util.List; + +/** + * This class holds a single result of a batch call. {@code T} is the type of the result and {@code + * E} is the type of the service-dependent exception thrown when a processing error occurs. + */ +public abstract class BatchResult { + + private T result; + private boolean completed = false; + private E error; + private List> toBeNotified = new LinkedList<>(); + + /** + * Returns {@code true} if the batch has been completed and the result is available; {@code false} + * otherwise. + */ + public boolean completed() { + return completed; + } + + /** + * Returns the result of this call. + * + * @throws IllegalStateException if the batch has not been completed yet + * @throws E if an error occurred when processing this request + */ + public T get() throws E { + checkState(completed(), "Batch has not been completed yet"); + if (error != null) { + throw error; + } + return result; + } + + /** + * Adds a callback for the batch operation. + * + * @throws IllegalStateException if the batch has been completed already + */ + public void notify(Callback callback) { + if (completed) { + throw new IllegalStateException("The batch has been completed. All the calls to the notify()" + + " method should be done prior to submitting the batch."); + } + toBeNotified.add(callback); + } + + /** + * Sets an error and status as completed. Notifies all callbacks. + */ + protected void error(E error) { + this.error = error; + this.completed = true; + for (Callback callback : toBeNotified) { + callback.error(error); + } + } + + /** + * Sets a result and status as completed. Notifies all callbacks. + */ + protected void success(T result) { + this.result = result; + this.completed = true; + for (Callback callback : toBeNotified) { + callback.success(result); + } + } + + /** + * An interface for the batch callbacks. + */ + public interface Callback { + /** + * The method to be called when the batched operation succeeds. + */ + void success(T result); + + /** + * The method to be called when the batched operation fails. + */ + void error(E exception); + } +} diff --git a/gcloud-java-core/src/test/java/com/google/cloud/BatchResultTest.java b/gcloud-java-core/src/test/java/com/google/cloud/BatchResultTest.java new file mode 100644 index 000000000000..85172170144d --- /dev/null +++ b/gcloud-java-core/src/test/java/com/google/cloud/BatchResultTest.java @@ -0,0 +1,107 @@ +/* + * 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.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.easymock.EasyMock; +import org.junit.Before; +import org.junit.Test; + +public class BatchResultTest { + + private BatchResult result; + + @Before + public void setUp() { + result = new BatchResult() {}; + } + + @Test + public void testSuccess() { + assertFalse(result.completed()); + try { + result.get(); + fail("This was not completed yet."); + } catch (IllegalStateException ex) { + // expected + } + result.success(true); + assertTrue(result.get()); + // test that null is allowed + result.success(null); + } + + @Test + public void testError() { + assertFalse(result.completed()); + try { + result.get(); + fail("This was not completed yet."); + } catch (IllegalStateException ex) { + // expected + } + BaseServiceException ex = new BaseServiceException(0, "message", "reason", false); + result.error(ex); + try { + result.get(); + fail("This is a failed operation and should have thrown a DnsException."); + } catch (BaseServiceException real) { + assertSame(ex, real); + } + } + + @Test + public void testNotifyError() { + final BaseServiceException ex = new BaseServiceException(0, "message", "reason", false); + assertFalse(result.completed()); + BatchResult.Callback callback = + EasyMock.createStrictMock(BatchResult.Callback.class); + callback.error(ex); + EasyMock.replay(callback); + result.notify(callback); + result.error(ex); + try { + result.notify(callback); + fail("The batch has been completed."); + } catch (IllegalStateException exception) { + // expected + } + EasyMock.verify(callback); + } + + @Test + public void testNotifySuccess() { + assertFalse(result.completed()); + BatchResult.Callback callback = + EasyMock.createStrictMock(BatchResult.Callback.class); + callback.success(true); + EasyMock.replay(callback); + result.notify(callback); + result.success(true); + try { + result.notify(callback); + fail("The batch has been completed."); + } catch (IllegalStateException exception) { + // expected + } + EasyMock.verify(callback); + } +} diff --git a/gcloud-java-dns/src/main/java/com/google/cloud/dns/ChangeRequest.java b/gcloud-java-dns/src/main/java/com/google/cloud/dns/ChangeRequest.java index 3538a5c411e9..267acf199b87 100644 --- a/gcloud-java-dns/src/main/java/com/google/cloud/dns/ChangeRequest.java +++ b/gcloud-java-dns/src/main/java/com/google/cloud/dns/ChangeRequest.java @@ -173,8 +173,8 @@ public ChangeRequest reload(Dns.ChangeRequestOption... options) { /** * Returns {@code true} if the change request has been completed. If the status is not {@link - * Status#DONE} already, the method makes an API call to Google Cloud DNS to update the change - * request first. + * ChangeRequestInfo.Status#DONE} already, the method makes an API call to Google Cloud DNS to + * update the change request first. * * @throws DnsException upon failure of the API call or if the associated zone was not found */ diff --git a/gcloud-java-dns/src/main/java/com/google/cloud/dns/Dns.java b/gcloud-java-dns/src/main/java/com/google/cloud/dns/Dns.java index 2aa080fb63d3..38faad7854e7 100644 --- a/gcloud-java-dns/src/main/java/com/google/cloud/dns/Dns.java +++ b/gcloud-java-dns/src/main/java/com/google/cloud/dns/Dns.java @@ -503,4 +503,9 @@ ChangeRequest getChangeRequest(String zoneName, String changeRequestId, * @see Cloud DNS Chages: list */ Page listChangeRequests(String zoneName, ChangeRequestListOption... options); + + /** + * Creates a new empty batch for grouping multiple service calls in one underlying RPC call. + */ + DnsBatch batch(); } diff --git a/gcloud-java-dns/src/main/java/com/google/cloud/dns/DnsBatch.java b/gcloud-java-dns/src/main/java/com/google/cloud/dns/DnsBatch.java new file mode 100644 index 000000000000..8e6c98d36c7a --- /dev/null +++ b/gcloud-java-dns/src/main/java/com/google/cloud/dns/DnsBatch.java @@ -0,0 +1,354 @@ +/* + * 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.dns; + +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; + +import com.google.api.client.googleapis.json.GoogleJsonError; +import com.google.api.services.dns.model.Change; +import com.google.api.services.dns.model.ChangesListResponse; +import com.google.api.services.dns.model.ManagedZone; +import com.google.api.services.dns.model.ManagedZonesListResponse; +import com.google.api.services.dns.model.Project; +import com.google.api.services.dns.model.ResourceRecordSet; +import com.google.api.services.dns.model.ResourceRecordSetsListResponse; +import com.google.cloud.Page; +import com.google.cloud.PageImpl; +import com.google.cloud.dns.spi.DnsRpc; +import com.google.cloud.dns.spi.RpcBatch; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +import java.util.List; +import java.util.Map; + +/** + * A batch of operations to be submitted to Google Cloud DNS using a single RPC request. + */ +public class DnsBatch { + + private RpcBatch batch; + private final DnsRpc dnsRpc; + private final DnsOptions options; + + DnsBatch(DnsOptions options) { + this.options = options; + this.dnsRpc = options.rpc(); + this.batch = dnsRpc.createBatch(); + } + + @VisibleForTesting + Object batch() { + return batch; + } + + @VisibleForTesting + DnsRpc dnsRpc() { + return dnsRpc; + } + + @VisibleForTesting + DnsOptions options() { + return options; + } + + /** + * Adds a request representing the "list zones" operation to this batch. The {@code options} can + * be used to restrict the fields returned or provide page size limits in the same way as for + * {@link Dns#listZones(Dns.ZoneListOption...)}. Calling {@link DnsBatchResult#get()} on the + * return value yields a page of zones if successful and throws a {@link DnsException} otherwise. + */ + public DnsBatchResult> listZones(Dns.ZoneListOption... options) { + DnsBatchResult> result = new DnsBatchResult<>(); + Map optionMap = DnsImpl.optionMap(options); + RpcBatch.Callback callback = + createListZonesCallback(result, optionMap); + batch.addListZones(callback, optionMap); + return result; + } + + /** + * Adds a request representing the "create zone" operation to this batch. The {@code options} can + * be used to restrict the fields returned in the same way as for {@link Dns#create(ZoneInfo, + * Dns.ZoneOption...)}. Calling {@link DnsBatchResult#get()} on the return value yields the + * created {@link Zone} if successful and throws a {@link DnsException} otherwise. + */ + public DnsBatchResult createZone(ZoneInfo zone, Dns.ZoneOption... options) { + DnsBatchResult result = new DnsBatchResult<>(); + // todo this can cause misleading report of a failure, intended to be fixed within #924 + RpcBatch.Callback callback = createZoneCallback(this.options, result, true); + Map optionMap = DnsImpl.optionMap(options); + batch.addCreateZone(zone.toPb(), callback, optionMap); + return result; + } + + /** + * Adds a request representing the "delete zone" operation to this batch. Calling {@link + * DnsBatchResult#get()} on the return value yields {@code true} upon successful deletion, {@code + * false} if the zone was not found, or throws a {@link DnsException} if the operation failed. + */ + public DnsBatchResult deleteZone(String zoneName) { + DnsBatchResult result = new DnsBatchResult<>(); + RpcBatch.Callback callback = createDeleteZoneCallback(result); + batch.addDeleteZone(zoneName, callback); + return result; + } + + /** + * Adds a request representing the "get zone" operation to this batch. The {@code options} can be + * used to restrict the fields returned in the same way as for {@link Dns#getZone(String, + * Dns.ZoneOption...)}. Calling {@link DnsBatchResult#get()} on the return value yields the + * requested {@link Zone} if successful, {@code null} if no such zone exists, or throws a + * {@link DnsException} if the operation failed. + */ + public DnsBatchResult getZone(String zoneName, Dns.ZoneOption... options) { + DnsBatchResult result = new DnsBatchResult<>(); + RpcBatch.Callback callback = createZoneCallback(this.options, result, true); + Map optionMap = DnsImpl.optionMap(options); + batch.addGetZone(zoneName, callback, optionMap); + return result; + } + + /** + * Adds a request representing the "get project" operation to this batch. The {@code options} can + * be used to restrict the fields returned in the same way as for {@link + * Dns#getProject(Dns.ProjectOption...)}. Calling {@link DnsBatchResult#get()} on the return value + * yields the created {@link ProjectInfo} if successful and throws a {@link DnsException} if the + * operation failed. + */ + public DnsBatchResult getProject(Dns.ProjectOption... options) { + DnsBatchResult result = new DnsBatchResult<>(); + RpcBatch.Callback callback = createProjectCallback(result); + Map optionMap = DnsImpl.optionMap(options); + batch.addGetProject(callback, optionMap); + return result; + } + + /** + * Adds a request representing the "list record sets" operation in the zone specified by {@code + * zoneName} to this batch. The {@code options} can be used to restrict the fields returned or + * provide page size limits in the same way as for {@link Dns#listRecordSets(String, + * Dns.RecordSetListOption...)}. Calling {@link DnsBatchResult#get()} on the return value yields a + * page of record sets if successful and throws a {@link DnsException} if the operation failed or + * the zone does not exist. + */ + public DnsBatchResult> listRecordSets(String zoneName, + Dns.RecordSetListOption... options) { + DnsBatchResult> result = new DnsBatchResult<>(); + Map optionMap = DnsImpl.optionMap(options); + RpcBatch.Callback callback = + createListRecordSetsCallback(zoneName, result, optionMap); + batch.addListRecordSets(zoneName, callback, optionMap); + return result; + } + + /** + * Adds a request representing the "list change requests" operation in the zone specified by + * {@code zoneName} to this batch. The {@code options} can be used to restrict the fields returned + * or provide page size limits in the same way as for {@link Dns#listChangeRequests(String, + * Dns.ChangeRequestListOption...)}. Calling {@link DnsBatchResult#get()} on the return value + * yields a page of change requests if successful and throws a {@link DnsException} if the + * operation failed or the zone does not exist. + */ + public DnsBatchResult> listChangeRequests(String zoneName, + Dns.ChangeRequestListOption... options) { + DnsBatchResult> result = new DnsBatchResult<>(); + Map optionMap = DnsImpl.optionMap(options); + RpcBatch.Callback callback = + createListChangeRequestsCallback(zoneName, result, optionMap); + batch.addListChangeRequests(zoneName, callback, optionMap); + return result; + } + + /** + * Adds a request representing the "get change request" operation for the zone specified by {@code + * zoneName} to this batch. The {@code options} can be used to restrict the fields returned in the + * same way as for {@link Dns#getChangeRequest(String, String, Dns.ChangeRequestOption...)}. + * Calling {@link DnsBatchResult#get()} on the return value yields the requested {@link + * ChangeRequest} if successful, {@code null} if the change request does not exist, or throws a + * {@link DnsException} if the operation failed or the zone does not exist. + */ + public DnsBatchResult getChangeRequest(String zoneName, String changeRequestId, + Dns.ChangeRequestOption... options) { + DnsBatchResult result = new DnsBatchResult<>(); + RpcBatch.Callback callback = createChangeRequestCallback(zoneName, result, true); + Map optionMap = DnsImpl.optionMap(options); + batch.addGetChangeRequest(zoneName, changeRequestId, callback, optionMap); + return result; + } + + /** + * Adds a request representing the "apply change request" operation to the zone specified by + * {@code zoneName} to this batch. The {@code options} can be used to restrict the fields returned + * in the same way as for {@link Dns#applyChangeRequest(String, ChangeRequestInfo, + * Dns.ChangeRequestOption...)}. Calling {@link DnsBatchResult#get()} on the return value yields + * the created {@link ChangeRequest} if successful, {@code null} if the change request does not + * exist, or throws a {@link DnsException} if the operation failed or the zone does not exist. + */ + public DnsBatchResult applyChangeRequest(String zoneName, + ChangeRequestInfo changeRequest, Dns.ChangeRequestOption... options) { + DnsBatchResult result = new DnsBatchResult<>(); + RpcBatch.Callback callback = createChangeRequestCallback(zoneName, result, false); + Map optionMap = DnsImpl.optionMap(options); + batch.addApplyChangeRequest(zoneName, changeRequest.toPb(), callback, optionMap); + return result; + } + + /** + * Submits this batch for processing using a single HTTP request. + */ + public void submit() { + batch.submit(); + } + + private RpcBatch.Callback createListZonesCallback( + final DnsBatchResult> result, final Map optionMap) { + return new RpcBatch.Callback() { + @Override + public void onSuccess(ManagedZonesListResponse response) { + List zones = response.getManagedZones(); + Page zonePage = new PageImpl<>( + new DnsImpl.ZonePageFetcher(options, response.getNextPageToken(), optionMap), + response.getNextPageToken(), zones == null ? ImmutableList.of() + : Iterables.transform(zones, DnsImpl.zoneFromPb(options))); + result.success(zonePage); + } + + @Override + public void onFailure(GoogleJsonError googleJsonError) { + result.error(new DnsException(googleJsonError, true)); + } + }; + } + + private RpcBatch.Callback createDeleteZoneCallback(final DnsBatchResult result) { + return new RpcBatch.Callback() { + @Override + public void onSuccess(Void response) { + result.success(true); + } + + @Override + public void onFailure(GoogleJsonError googleJsonError) { + DnsException serviceException = new DnsException(googleJsonError, false); + if (serviceException.code() == HTTP_NOT_FOUND) { + result.success(false); + } else { + result.error(serviceException); + } + } + }; + } + + /** + * A joint callback for both "get zone" and "create zone" operations. + */ + private RpcBatch.Callback createZoneCallback(final DnsOptions serviceOptions, + final DnsBatchResult result, final boolean idempotent) { + return new RpcBatch.Callback() { + @Override + public void onSuccess(ManagedZone response) { + result.success(response == null ? null : Zone.fromPb(serviceOptions.service(), response)); + } + + @Override + public void onFailure(GoogleJsonError googleJsonError) { + result.error(new DnsException(googleJsonError, idempotent)); + } + }; + } + + private RpcBatch.Callback createProjectCallback( + final DnsBatchResult result) { + return new RpcBatch.Callback() { + @Override + public void onSuccess(Project response) { + result.success(response == null ? null : ProjectInfo.fromPb(response)); + } + + @Override + public void onFailure(GoogleJsonError googleJsonError) { + result.error(new DnsException(googleJsonError, true)); + } + }; + } + + private RpcBatch.Callback createListRecordSetsCallback( + final String zoneName, final DnsBatchResult> result, + final Map optionMap) { + return new RpcBatch.Callback() { + @Override + public void onSuccess(ResourceRecordSetsListResponse response) { + List recordSets = response.getRrsets(); + Page page = new PageImpl<>( + new DnsImpl.RecordSetPageFetcher(zoneName, options, response.getNextPageToken(), + optionMap), + response.getNextPageToken(), recordSets == null ? ImmutableList.of() + : Iterables.transform(recordSets, RecordSet.FROM_PB_FUNCTION)); + result.success(page); + } + + @Override + public void onFailure(GoogleJsonError googleJsonError) { + result.error(new DnsException(googleJsonError, true)); + } + }; + } + + private RpcBatch.Callback createListChangeRequestsCallback( + final String zoneName, final DnsBatchResult> result, + final Map optionMap) { + return new RpcBatch.Callback() { + @Override + public void onSuccess(ChangesListResponse response) { + List changes = response.getChanges(); + Page page = new PageImpl<>( + new DnsImpl.ChangeRequestPageFetcher(zoneName, options, response.getNextPageToken(), + optionMap), + response.getNextPageToken(), changes == null ? ImmutableList.of() + : Iterables.transform(changes, ChangeRequest.fromPbFunction(options.service(), + zoneName))); + result.success(page); + } + + @Override + public void onFailure(GoogleJsonError googleJsonError) { + result.error(new DnsException(googleJsonError, true)); + } + }; + } + + /** + * A joint callback for both "get change request" and "create change request" operations. + */ + private RpcBatch.Callback createChangeRequestCallback(final String zoneName, + final DnsBatchResult result, final boolean idempotent) { + return new RpcBatch.Callback() { + @Override + public void onSuccess(Change response) { + result.success(response == null ? null : ChangeRequest.fromPb(options.service(), + zoneName, response)); + } + + @Override + public void onFailure(GoogleJsonError googleJsonError) { + result.error(new DnsException(googleJsonError, idempotent)); + } + }; + } +} diff --git a/gcloud-java-dns/src/main/java/com/google/cloud/dns/DnsBatchResult.java b/gcloud-java-dns/src/main/java/com/google/cloud/dns/DnsBatchResult.java new file mode 100644 index 000000000000..41c217ddc60e --- /dev/null +++ b/gcloud-java-dns/src/main/java/com/google/cloud/dns/DnsBatchResult.java @@ -0,0 +1,38 @@ +/* + * 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.dns; + +import com.google.cloud.BatchResult; + +/** + * This class holds a single result of a batch call to the Cloud DNS. + */ +public class DnsBatchResult extends BatchResult { + + DnsBatchResult() { + } + + @Override + protected void error(DnsException error) { + super.error(error); + } + + @Override + protected void success(T result) { + super.success(result); + } +} diff --git a/gcloud-java-dns/src/main/java/com/google/cloud/dns/DnsException.java b/gcloud-java-dns/src/main/java/com/google/cloud/dns/DnsException.java index f725984b6661..274ff91b3bd0 100644 --- a/gcloud-java-dns/src/main/java/com/google/cloud/dns/DnsException.java +++ b/gcloud-java-dns/src/main/java/com/google/cloud/dns/DnsException.java @@ -16,6 +16,7 @@ package com.google.cloud.dns; +import com.google.api.client.googleapis.json.GoogleJsonError; import com.google.cloud.BaseServiceException; import com.google.cloud.RetryHelper.RetryHelperException; import com.google.cloud.RetryHelper.RetryInterruptedException; @@ -43,6 +44,10 @@ public DnsException(IOException exception, boolean idempotent) { super(exception, idempotent); } + public DnsException(GoogleJsonError error, boolean idempotent) { + super(error, idempotent); + } + private DnsException(int code, String message) { super(code, message, null, true); } diff --git a/gcloud-java-dns/src/main/java/com/google/cloud/dns/DnsImpl.java b/gcloud-java-dns/src/main/java/com/google/cloud/dns/DnsImpl.java index cdb311adc823..7f657cb912f1 100644 --- a/gcloud-java-dns/src/main/java/com/google/cloud/dns/DnsImpl.java +++ b/gcloud-java-dns/src/main/java/com/google/cloud/dns/DnsImpl.java @@ -44,7 +44,7 @@ final class DnsImpl extends BaseService implements Dns { private final DnsRpc dnsRpc; - private static class ZonePageFetcher implements PageImpl.NextPageFetcher { + static class ZonePageFetcher implements PageImpl.NextPageFetcher { private static final long serialVersionUID = 2158209410430566961L; private final Map requestOptions; @@ -63,7 +63,7 @@ public Page nextPage() { } } - private static class ChangeRequestPageFetcher implements PageImpl.NextPageFetcher { + static class ChangeRequestPageFetcher implements PageImpl.NextPageFetcher { private static final long serialVersionUID = 4473265130673029139L; private final String zoneName; @@ -84,14 +84,14 @@ public Page nextPage() { } } - private static class DnsRecordPageFetcher implements PageImpl.NextPageFetcher { + static class RecordSetPageFetcher implements PageImpl.NextPageFetcher { private static final long serialVersionUID = -6039369212511530846L; private final Map requestOptions; private final DnsOptions serviceOptions; private final String zoneName; - DnsRecordPageFetcher(String zoneName, DnsOptions serviceOptions, String cursor, + RecordSetPageFetcher(String zoneName, DnsOptions serviceOptions, String cursor, Map optionMap) { this.zoneName = zoneName; this.requestOptions = @@ -110,6 +110,16 @@ public Page nextPage() { dnsRpc = options.rpc(); } + static Function zoneFromPb(final DnsOptions options) { + return new Function() { + @Override + public Zone apply( + com.google.api.services.dns.model.ManagedZone zonePb) { + return Zone.fromPb(options.service(), zonePb); + } + }; + } + @Override public Page listZones(ZoneListOption... options) { return listZones(options(), optionMap(options)); @@ -117,14 +127,6 @@ public Page listZones(ZoneListOption... options) { private static Page listZones(final DnsOptions serviceOptions, final Map optionsMap) { - // define transformation function - // this differs from the other list operations since zone is functional and requires dns service - Function pbToZoneFunction = new Function() { - @Override - public Zone apply(ManagedZone zonePb) { - return Zone.fromPb(serviceOptions.service(), zonePb); - } - }; try { // get a list of managed zones final DnsRpc rpc = serviceOptions.rpc(); @@ -137,8 +139,8 @@ public DnsRpc.ListResult call() { }, serviceOptions.retryParams(), EXCEPTION_HANDLER); String cursor = result.pageToken(); // transform that list into zone objects - Iterable zones = result.results() == null - ? ImmutableList.of() : Iterables.transform(result.results(), pbToZoneFunction); + Iterable zones = result.results() == null ? ImmutableList.of() + : Iterables.transform(result.results(), zoneFromPb(serviceOptions)); return new PageImpl<>(new ZonePageFetcher(serviceOptions, cursor, optionsMap), cursor, zones); } catch (RetryHelper.RetryHelperException e) { @@ -198,7 +200,7 @@ public DnsRpc.ListResult call() { Iterable recordSets = result.results() == null ? ImmutableList.of() : Iterables.transform(result.results(), RecordSet.FROM_PB_FUNCTION); - return new PageImpl<>(new DnsRecordPageFetcher(zoneName, serviceOptions, cursor, optionsMap), + return new PageImpl<>(new RecordSetPageFetcher(zoneName, serviceOptions, cursor, optionsMap), cursor, recordSets); } catch (RetryHelper.RetryHelperException e) { throw DnsException.translateAndThrow(e); @@ -306,7 +308,12 @@ public Change call() { } } - private Map optionMap(Option... options) { + @Override + public DnsBatch batch() { + return new DnsBatch(this.options()); + } + + static Map optionMap(Option... options) { Map temp = Maps.newEnumMap(DnsRpc.Option.class); for (Option option : options) { Object prev = temp.put(option.rpcOption(), option.value()); diff --git a/gcloud-java-dns/src/main/java/com/google/cloud/dns/RecordSet.java b/gcloud-java-dns/src/main/java/com/google/cloud/dns/RecordSet.java index 5e061b5164e8..e9d2e1b2527e 100644 --- a/gcloud-java-dns/src/main/java/com/google/cloud/dns/RecordSet.java +++ b/gcloud-java-dns/src/main/java/com/google/cloud/dns/RecordSet.java @@ -154,7 +154,7 @@ private Builder(RecordSet record) { * RFC 1034 (section 3.6.1). Examples of records are available in Google DNS documentation. * * @see Google - * DNS documentation . + * DNS documentation */ public Builder addRecord(String record) { this.rrdatas.add(checkNotNull(record)); diff --git a/gcloud-java-dns/src/main/java/com/google/cloud/dns/spi/DefaultDnsRpc.java b/gcloud-java-dns/src/main/java/com/google/cloud/dns/spi/DefaultDnsRpc.java index 08f14a0ba254..518aa749733c 100644 --- a/gcloud-java-dns/src/main/java/com/google/cloud/dns/spi/DefaultDnsRpc.java +++ b/gcloud-java-dns/src/main/java/com/google/cloud/dns/spi/DefaultDnsRpc.java @@ -1,3 +1,19 @@ +/* + * 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.dns.spi; import static com.google.cloud.dns.spi.DnsRpc.ListResult.of; @@ -10,6 +26,10 @@ import static com.google.cloud.dns.spi.DnsRpc.Option.SORTING_ORDER; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; +import com.google.api.client.googleapis.batch.BatchRequest; +import com.google.api.client.googleapis.batch.json.JsonBatchCallback; +import com.google.api.client.googleapis.json.GoogleJsonError; +import com.google.api.client.http.HttpHeaders; import com.google.api.client.http.HttpRequestInitializer; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.jackson.JacksonFactory; @@ -36,6 +56,129 @@ public class DefaultDnsRpc implements DnsRpc { private final Dns dns; private final DnsOptions options; + private class DefaultRpcBatch implements RpcBatch { + + private BatchRequest batch; + + private DefaultRpcBatch(BatchRequest batch) { + this.batch = batch; + } + + @Override + public void addListZones(RpcBatch.Callback callback, + Map options) { + try { + listZonesCall(options).queue(batch, toJsonCallback(callback)); + } catch (IOException ex) { + throw translate(ex, false); + } + } + + @Override + public void addCreateZone(ManagedZone zone, RpcBatch.Callback callback, + Map options) { + try { + createZoneCall(zone, options).queue(batch, toJsonCallback(callback)); + } catch (IOException ex) { + throw translate(ex, false); + } + } + + @Override + public void addGetZone(String zoneName, RpcBatch.Callback callback, + Map options) { + try { + getZoneCall(zoneName, options).queue(batch, toJsonCallback(callback)); + } catch (IOException ex) { + throw translate(ex, false); + } + } + + @Override + public void addDeleteZone(String zoneName, RpcBatch.Callback callback) { + try { + deleteZoneCall(zoneName).queue(batch, toJsonCallback(callback)); + } catch (IOException ex) { + throw translate(ex, false); + } + } + + @Override + public void addGetProject(RpcBatch.Callback callback, + Map options) { + try { + getProjectCall(options).queue(batch, toJsonCallback(callback)); + } catch (IOException ex) { + throw translate(ex, false); + } + } + + @Override + public void addListRecordSets(String zoneName, + RpcBatch.Callback callback, Map options) { + try { + listRecordSetsCall(zoneName, options).queue(batch, toJsonCallback(callback)); + } catch (IOException ex) { + throw translate(ex, false); + } + } + + @Override + public void addListChangeRequests(String zoneName, + RpcBatch.Callback callback, Map options) { + try { + listChangeRequestsCall(zoneName, options).queue(batch, toJsonCallback(callback)); + } catch (IOException ex) { + throw translate(ex, false); + } + } + + @Override + public void addGetChangeRequest(String zoneName, String changeRequestId, + RpcBatch.Callback callback, Map options) { + try { + getChangeRequestCall(zoneName, changeRequestId, options).queue(batch, + toJsonCallback(callback)); + } catch (IOException ex) { + throw translate(ex, false); + } + } + + @Override + public void addApplyChangeRequest(String zoneName, Change change, + RpcBatch.Callback callback, Map options) { + try { + applyChangeRequestCall(zoneName, change, options).queue(batch, toJsonCallback(callback)); + } catch (IOException ex) { + throw translate(ex, false); + } + } + + @Override + public void submit() { + try { + batch.execute(); + } catch (IOException ex) { + throw translate(ex, false); + } + } + } + + private static JsonBatchCallback toJsonCallback(final RpcBatch.Callback callback) { + return new JsonBatchCallback() { + @Override + public void onSuccess(T response, HttpHeaders httpHeaders) throws IOException { + callback.onSuccess(response); + } + + @Override + public void onFailure(GoogleJsonError googleJsonError, HttpHeaders httpHeaders) + throws IOException { + callback.onFailure(googleJsonError); + } + }; + } + private static DnsException translate(IOException exception, boolean idempotent) { return new DnsException(exception, idempotent); } @@ -56,23 +199,25 @@ public DefaultDnsRpc(DnsOptions options) { @Override public ManagedZone create(ManagedZone zone, Map options) throws DnsException { try { - return dns.managedZones() - .create(this.options.projectId(), zone) - .setFields(FIELDS.getString(options)) - .execute(); + return createZoneCall(zone, options).execute(); } catch (IOException ex) { // todo this can cause misleading report of a failure, intended to be fixed within #924 throw translate(ex, true); } } + private Dns.ManagedZones.Create createZoneCall(ManagedZone zone, Map options) + throws IOException { + return dns.managedZones() + .create(this.options.projectId(), zone) + .setFields(FIELDS.getString(options)); + } + @Override public ManagedZone getZone(String zoneName, Map options) throws DnsException { // just fields option try { - return dns.managedZones().get(this.options.projectId(), zoneName) - .setFields(FIELDS.getString(options)) - .execute(); + return getZoneCall(zoneName, options).execute(); } catch (IOException ex) { DnsException serviceException = translate(ex, true); if (serviceException.code() == HTTP_NOT_FOUND) { @@ -82,26 +227,36 @@ public ManagedZone getZone(String zoneName, Map options) throws DnsEx } } + private Dns.ManagedZones.Get getZoneCall(String zoneName, Map options) + throws IOException { + return dns.managedZones() + .get(this.options.projectId(), zoneName) + .setFields(FIELDS.getString(options)); + } + @Override public ListResult listZones(Map options) throws DnsException { // fields, page token, page size try { - ManagedZonesListResponse zoneList = dns.managedZones().list(this.options.projectId()) - .setFields(FIELDS.getString(options)) - .setMaxResults(PAGE_SIZE.getInt(options)) - .setDnsName(DNS_NAME.getString(options)) - .setPageToken(PAGE_TOKEN.getString(options)) - .execute(); + ManagedZonesListResponse zoneList = listZonesCall(options).execute(); return of(zoneList.getNextPageToken(), zoneList.getManagedZones()); } catch (IOException ex) { throw translate(ex, true); } } + private Dns.ManagedZones.List listZonesCall(Map options) throws IOException { + return dns.managedZones().list(this.options.projectId()) + .setFields(FIELDS.getString(options)) + .setMaxResults(PAGE_SIZE.getInt(options)) + .setDnsName(DNS_NAME.getString(options)) + .setPageToken(PAGE_TOKEN.getString(options)); + } + @Override public boolean deleteZone(String zoneName) throws DnsException { try { - dns.managedZones().delete(this.options.projectId(), zoneName).execute(); + deleteZoneCall(zoneName).execute(); return true; } catch (IOException ex) { DnsException serviceException = translate(ex, false); @@ -112,54 +267,68 @@ public boolean deleteZone(String zoneName) throws DnsException { } } + private Dns.ManagedZones.Delete deleteZoneCall(String zoneName) throws IOException { + return dns.managedZones().delete(this.options.projectId(), zoneName); + } + @Override public ListResult listRecordSets(String zoneName, Map options) throws DnsException { - // options are fields, page token, dns name, type try { - ResourceRecordSetsListResponse response = dns.resourceRecordSets() - .list(this.options.projectId(), zoneName) - .setFields(FIELDS.getString(options)) - .setPageToken(PAGE_TOKEN.getString(options)) - .setMaxResults(PAGE_SIZE.getInt(options)) - .setName(NAME.getString(options)) - .setType(DNS_TYPE.getString(options)) - .execute(); + ResourceRecordSetsListResponse response = listRecordSetsCall(zoneName, options).execute(); return of(response.getNextPageToken(), response.getRrsets()); } catch (IOException ex) { throw translate(ex, true); } } + private Dns.ResourceRecordSets.List listRecordSetsCall(String zoneName, Map options) + throws IOException { + // options are fields, page token, dns name, type + return dns.resourceRecordSets() + .list(this.options.projectId(), zoneName) + .setFields(FIELDS.getString(options)) + .setPageToken(PAGE_TOKEN.getString(options)) + .setMaxResults(PAGE_SIZE.getInt(options)) + .setName(NAME.getString(options)) + .setType(DNS_TYPE.getString(options)); + } + @Override public Project getProject(Map options) throws DnsException { try { - return dns.projects().get(this.options.projectId()) - .setFields(FIELDS.getString(options)).execute(); + return getProjectCall(options).execute(); } catch (IOException ex) { throw translate(ex, true); } } + private Dns.Projects.Get getProjectCall(Map options) throws IOException { + return dns.projects().get(this.options.projectId()).setFields(FIELDS.getString(options)); + } + @Override public Change applyChangeRequest(String zoneName, Change changeRequest, Map options) throws DnsException { try { - return dns.changes().create(this.options.projectId(), zoneName, changeRequest) - .setFields(FIELDS.getString(options)) - .execute(); + return applyChangeRequestCall(zoneName, changeRequest, options).execute(); } catch (IOException ex) { throw translate(ex, false); } } + private Dns.Changes.Create applyChangeRequestCall(String zoneName, Change changeRequest, + Map options) throws IOException { + return dns.changes() + .create(this.options.projectId(), zoneName, changeRequest) + .setFields(FIELDS.getString(options)); + } + @Override public Change getChangeRequest(String zoneName, String changeRequestId, Map options) throws DnsException { try { - return dns.changes().get(this.options.projectId(), zoneName, changeRequestId) - .setFields(FIELDS.getString(options)) - .execute(); + return getChangeRequestCall(zoneName, changeRequestId, options).execute(); } catch (IOException ex) { DnsException serviceException = translate(ex, true); if (serviceException.code() == HTTP_NOT_FOUND) { @@ -175,23 +344,40 @@ public Change getChangeRequest(String zoneName, String changeRequestId, Map options) throws IOException { + return dns.changes() + .get(this.options.projectId(), zoneName, changeRequestId) + .setFields(FIELDS.getString(options)); + } + @Override public ListResult listChangeRequests(String zoneName, Map options) throws DnsException { - // options are fields, page token, page size, sort order try { - Dns.Changes.List request = dns.changes().list(this.options.projectId(), zoneName) - .setFields(FIELDS.getString(options)) - .setMaxResults(PAGE_SIZE.getInt(options)) - .setPageToken(PAGE_TOKEN.getString(options)); - if (SORTING_ORDER.getString(options) != null) { - // todo check and change if more sorting options are implemented, issue #604 - request = request.setSortBy(SORT_BY).setSortOrder(SORTING_ORDER.getString(options)); - } - ChangesListResponse response = request.execute(); + ChangesListResponse response = listChangeRequestsCall(zoneName, options).execute(); return of(response.getNextPageToken(), response.getChanges()); } catch (IOException ex) { throw translate(ex, true); } } + + private Dns.Changes.List listChangeRequestsCall(String zoneName, Map options) + throws IOException { + // options are fields, page token, page size, sort order + Dns.Changes.List request = dns.changes().list(this.options.projectId(), zoneName) + .setFields(FIELDS.getString(options)) + .setMaxResults(PAGE_SIZE.getInt(options)) + .setPageToken(PAGE_TOKEN.getString(options)); + if (SORTING_ORDER.getString(options) != null) { + // todo check and change if more sorting options are implemented, issue #604 + request = request.setSortBy(SORT_BY).setSortOrder(SORTING_ORDER.getString(options)); + } + return request; + } + + @Override + public RpcBatch createBatch() { + return new DefaultRpcBatch(dns.batch()); + } } diff --git a/gcloud-java-dns/src/main/java/com/google/cloud/dns/spi/DnsRpc.java b/gcloud-java-dns/src/main/java/com/google/cloud/dns/spi/DnsRpc.java index ba74f14f0527..5d0054d86379 100644 --- a/gcloud-java-dns/src/main/java/com/google/cloud/dns/spi/DnsRpc.java +++ b/gcloud-java-dns/src/main/java/com/google/cloud/dns/spi/DnsRpc.java @@ -171,4 +171,9 @@ Change getChangeRequest(String zoneName, String changeRequestId, Map */ ListResult listChangeRequests(String zoneName, Map options) throws DnsException; + + /** + * Creates an empty batch. + */ + RpcBatch createBatch(); } diff --git a/gcloud-java-dns/src/main/java/com/google/cloud/dns/spi/RpcBatch.java b/gcloud-java-dns/src/main/java/com/google/cloud/dns/spi/RpcBatch.java new file mode 100644 index 000000000000..154ffa4a3c99 --- /dev/null +++ b/gcloud-java-dns/src/main/java/com/google/cloud/dns/spi/RpcBatch.java @@ -0,0 +1,117 @@ +/* + * 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.dns.spi; + +import com.google.api.client.googleapis.json.GoogleJsonError; +import com.google.api.services.dns.model.Change; +import com.google.api.services.dns.model.ChangesListResponse; +import com.google.api.services.dns.model.ManagedZone; +import com.google.api.services.dns.model.ManagedZonesListResponse; +import com.google.api.services.dns.model.Project; +import com.google.api.services.dns.model.ResourceRecordSetsListResponse; + +import java.util.Map; + +/** + * An interface for the collection of batch operations. + */ +public interface RpcBatch { + + /** + * An interface for batch callbacks. + */ + interface Callback { + + /** + * This method will be called upon success of the batch operation. + */ + void onSuccess(T response); + + /** + * This method will be called upon failure of the batch operation. + */ + void onFailure(GoogleJsonError googleJsonError); + } + + /** + * Adds a call to "list zones" to the batch with the provided {@code callback} and {@code + * options}. + */ + void addListZones(Callback callback, Map options); + + /** + * Adds a call to "create zone" to the batch with the provided {@code callback} and {@code + * options}. + */ + void addCreateZone(ManagedZone zone, Callback callback, + Map options); + + /** + * Adds a call to "get zone" to the batch with the provided {@code callback} and {@code options}. + * The zone to be retrieved is identified by {@code zoneName}. + */ + void addGetZone(String zoneName, Callback callback, Map options); + + /** + * Adds a call to "get project" to the batch with the provided {@code callback} and {@code + * options}. + */ + void addGetProject(Callback callback, Map options); + + /** + * Adds a call to "delete zone" to the batch with the provided {@code callback}. The zone to be + * deleted is identified by {@code zoneName}. + */ + void addDeleteZone(String zoneName, Callback callback); + + /** + * Adds a call to "list record sets" to the batch with the provided {@code callback} and {@code + * options}. The zone whose record sets are to be listed is identified by {@code zoneName}. + */ + void addListRecordSets(String zoneName, Callback callback, + Map options); + + /** + * Adds a call to "list change requests" to the batch with the provided {@code callback} and + * {@code options}. The zone whose change requests are to be listed is identified by {@code + * zoneName}. + */ + void addListChangeRequests(String zoneName, Callback callback, + Map options); + + /** + * Adds a call to "get change request" to the batch with the provided {@code callback} and {@code + * options}. The change request to be retrieved is identified by {@code changeRequestId}. The zone + * to which the change request was applied is identified by {@code zoneName}. + */ + void addGetChangeRequest(String zoneName, String changeRequestId, Callback callback, + Map options); + + /** + * Adds a call to "apply change request" to the batch with the provided {@code callback} and + * {@code options}. The parameter {@code change} is the change request to be applied. The zone to + * which the change request should be applied is identified by {@code zoneName}. + */ + void addApplyChangeRequest(String zoneName, Change change, Callback callback, + Map options); + + /** + * Submits a batch of requests for processing using a single HTTP request to Cloud DNS. + */ + void submit(); +} + diff --git a/gcloud-java-dns/src/test/java/com/google/cloud/dns/DnsBatchResultTest.java b/gcloud-java-dns/src/test/java/com/google/cloud/dns/DnsBatchResultTest.java new file mode 100644 index 000000000000..c2b1c10b4741 --- /dev/null +++ b/gcloud-java-dns/src/test/java/com/google/cloud/dns/DnsBatchResultTest.java @@ -0,0 +1,110 @@ +/* + * 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.dns; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.cloud.BaseServiceException; +import com.google.cloud.BatchResult; + +import org.easymock.EasyMock; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; + +public class DnsBatchResultTest { + + private DnsBatchResult result; + + @Before + public void setUp() { + result = new DnsBatchResult<>(); + } + + @Test + public void testSuccess() { + assertFalse(result.completed()); + try { + result.get(); + fail("This was not completed yet."); + } catch (IllegalStateException ex) { + // expected + } + result.success(true); + assertTrue(result.get()); + } + + @Test + public void testError() { + assertFalse(result.completed()); + try { + result.get(); + fail("This was not completed yet."); + } catch (IllegalStateException ex) { + // expected + } + DnsException ex = new DnsException(new IOException("some error"), true); + result.error(ex); + try { + result.get(); + fail("This is a failed operation and should have thrown a DnsException."); + } catch (DnsException real) { + assertSame(ex, real); + } + } + + @Test + public void testNotifyError() { + DnsException ex = new DnsException(new IOException("some error"), false); + assertFalse(result.completed()); + BatchResult.Callback callback = + EasyMock.createStrictMock(BatchResult.Callback.class); + callback.error(ex); + EasyMock.replay(callback); + result.notify(callback); + result.error(ex); + try { + result.notify(callback); + fail("The batch has been completed."); + } catch (IllegalStateException exception) { + // expected + } + EasyMock.verify(callback); + } + + @Test + public void testNotifySuccess() { + assertFalse(result.completed()); + BatchResult.Callback callback = + EasyMock.createStrictMock(BatchResult.Callback.class); + callback.success(true); + EasyMock.replay(callback); + result.notify(callback); + result.success(true); + try { + result.notify(callback); + fail("The batch has been completed."); + } catch (IllegalStateException exception) { + // expected + } + EasyMock.verify(callback); + } +} diff --git a/gcloud-java-dns/src/test/java/com/google/cloud/dns/DnsBatchTest.java b/gcloud-java-dns/src/test/java/com/google/cloud/dns/DnsBatchTest.java new file mode 100644 index 000000000000..3f6605ff2d76 --- /dev/null +++ b/gcloud-java-dns/src/test/java/com/google/cloud/dns/DnsBatchTest.java @@ -0,0 +1,632 @@ +/* + * 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.dns; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.api.client.googleapis.json.GoogleJsonError; +import com.google.api.services.dns.model.Change; +import com.google.api.services.dns.model.ChangesListResponse; +import com.google.api.services.dns.model.ManagedZone; +import com.google.api.services.dns.model.ManagedZonesListResponse; +import com.google.api.services.dns.model.Project; +import com.google.api.services.dns.model.ResourceRecordSet; +import com.google.api.services.dns.model.ResourceRecordSetsListResponse; +import com.google.cloud.Page; +import com.google.cloud.dns.spi.DnsRpc; +import com.google.cloud.dns.spi.RpcBatch; +import com.google.common.collect.ImmutableList; + +import org.easymock.Capture; +import org.easymock.EasyMock; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class DnsBatchTest { + + private static final String ZONE_NAME = "somezonename"; + private static final String DNS_NAME = "example.com."; + private static final String DESCRIPTION = "desc"; + private static final Integer MAX_SIZE = 20; + private static final String PAGE_TOKEN = "some token"; + + private static final ZoneInfo ZONE_INFO = ZoneInfo.of(ZONE_NAME, DNS_NAME, DESCRIPTION); + private static final Dns.ZoneOption ZONE_FIELDS = + Dns.ZoneOption.fields(Dns.ZoneField.CREATION_TIME); + private static final Dns.ProjectOption PROJECT_FIELDS = + Dns.ProjectOption.fields(Dns.ProjectField.QUOTA); + private static final Dns.ZoneListOption[] ZONE_LIST_OPTIONS = { + Dns.ZoneListOption.pageSize(MAX_SIZE), Dns.ZoneListOption.pageToken(PAGE_TOKEN), + Dns.ZoneListOption.fields(Dns.ZoneField.DESCRIPTION), + Dns.ZoneListOption.dnsName(DNS_NAME)}; + private static final ProjectInfo PROJECT_INFO = ProjectInfo.builder().build(); + private static final Dns.RecordSetListOption[] RECORD_SET_LIST_OPTIONS = { + Dns.RecordSetListOption.pageSize(MAX_SIZE), + Dns.RecordSetListOption.pageToken(PAGE_TOKEN), + Dns.RecordSetListOption.fields(Dns.RecordSetField.TTL), + Dns.RecordSetListOption.dnsName(DNS_NAME), + Dns.RecordSetListOption.type(RecordSet.Type.AAAA)}; + private static final RecordSet RECORD_SET = + RecordSet.builder("Something", RecordSet.Type.AAAA).build(); + private static final ChangeRequestInfo CHANGE_REQUEST_PARTIAL = ChangeRequestInfo.builder() + .add(RECORD_SET) + .build(); + private static final String CHANGE_ID = "some change id"; + private static final ChangeRequestInfo CHANGE_REQUEST_COMPLETE = ChangeRequestInfo.builder() + .add(RECORD_SET) + .startTimeMillis(123L) + .status(ChangeRequest.Status.PENDING) + .generatedId(CHANGE_ID) + .build(); + private static final Dns.ChangeRequestListOption[] CHANGE_LIST_OPTIONS = { + Dns.ChangeRequestListOption.pageSize(MAX_SIZE), + Dns.ChangeRequestListOption.pageToken(PAGE_TOKEN), + Dns.ChangeRequestListOption.fields(Dns.ChangeRequestField.STATUS), + Dns.ChangeRequestListOption.sortOrder(Dns.SortingOrder.ASCENDING)}; + private static final Dns.ChangeRequestOption CHANGE_GET_FIELDS = + Dns.ChangeRequestOption.fields(Dns.ChangeRequestField.STATUS); + private static final List RECORD_SET_LIST = ImmutableList.of( + RECORD_SET.toPb(), RECORD_SET.toPb(), RECORD_SET.toPb(), RECORD_SET.toPb()); + private static final List CHANGE_LIST = ImmutableList.of(CHANGE_REQUEST_COMPLETE.toPb(), + CHANGE_REQUEST_COMPLETE.toPb(), CHANGE_REQUEST_COMPLETE.toPb()); + private static final List ZONE_LIST = ImmutableList.of(ZONE_INFO.toPb(), + ZONE_INFO.toPb()); + private static final GoogleJsonError GOOGLE_JSON_ERROR = new GoogleJsonError(); + + private DnsOptions optionsMock; + private DnsRpc dnsRpcMock; + private RpcBatch batchMock; + private DnsBatch dnsBatch; + private Dns dns = EasyMock.createStrictMock(Dns.class); + + @Before + public void setUp() { + optionsMock = EasyMock.createMock(DnsOptions.class); + dnsRpcMock = EasyMock.createMock(DnsRpc.class); + batchMock = EasyMock.createMock(RpcBatch.class); + EasyMock.expect(optionsMock.rpc()).andReturn(dnsRpcMock); + EasyMock.expect(dnsRpcMock.createBatch()).andReturn(batchMock); + EasyMock.replay(optionsMock, dnsRpcMock, batchMock, dns); + dnsBatch = new DnsBatch(optionsMock); + } + + @After + public void tearDown() { + EasyMock.verify(batchMock, dnsRpcMock, optionsMock, dns); + } + + @Test + public void testConstructor() { + assertSame(batchMock, dnsBatch.batch()); + assertSame(optionsMock, dnsBatch.options()); + assertSame(dnsRpcMock, dnsBatch.dnsRpc()); + } + + @Test + public void testListZones() { + EasyMock.reset(batchMock); + Capture> callback = Capture.newInstance(); + Capture> capturedOptions = Capture.newInstance(); + batchMock.addListZones(EasyMock.capture(callback), EasyMock.capture(capturedOptions)); + EasyMock.replay(batchMock); + DnsBatchResult> batchResult = dnsBatch.listZones(); + assertEquals(0, capturedOptions.getValue().size()); + assertNotNull(callback.getValue()); + try { + batchResult.get(); + fail("No result available yet."); + } catch (IllegalStateException ex) { + // expected + } + RpcBatch.Callback capturedCallback = callback.getValue(); + capturedCallback.onFailure(GOOGLE_JSON_ERROR); + try { + batchResult.get(); + fail("Should throw a DnsException on error."); + } catch (DnsException ex) { + // expected + assertTrue(ex.idempotent()); + } + } + + @Test + public void testListZonesWithOptions() { + EasyMock.reset(batchMock); + Capture> callback = Capture.newInstance(); + Capture> capturedOptions = Capture.newInstance(); + batchMock.addListZones(EasyMock.capture(callback), EasyMock.capture(capturedOptions)); + EasyMock.replay(batchMock); + DnsBatchResult> batchResult = dnsBatch.listZones(ZONE_LIST_OPTIONS); + assertNotNull(callback.getValue()); + Integer size = (Integer) capturedOptions.getValue().get(ZONE_LIST_OPTIONS[0].rpcOption()); + assertEquals(MAX_SIZE, size); + String selector = (String) capturedOptions.getValue().get(ZONE_LIST_OPTIONS[1].rpcOption()); + assertEquals(PAGE_TOKEN, selector); + selector = (String) capturedOptions.getValue().get(ZONE_LIST_OPTIONS[2].rpcOption()); + assertTrue(selector.contains(Dns.ZoneField.DESCRIPTION.selector())); + assertTrue(selector.contains(Dns.ZoneField.NAME.selector())); + selector = (String) capturedOptions.getValue().get(ZONE_LIST_OPTIONS[3].rpcOption()); + assertEquals(DNS_NAME, selector); + // check the callback + ManagedZonesListResponse response = new ManagedZonesListResponse() + .setManagedZones(ZONE_LIST) + .setNextPageToken(PAGE_TOKEN); + RpcBatch.Callback capturedCallback = callback.getValue(); + EasyMock.verify(optionsMock); + EasyMock.reset(optionsMock); + EasyMock.expect(optionsMock.service()).andReturn(dns).times(ZONE_LIST.size()); + EasyMock.replay(optionsMock); + capturedCallback.onSuccess(response); + Page page = batchResult.get(); + assertEquals(PAGE_TOKEN, page.nextPageCursor()); + Iterator iterator = page.values().iterator(); + int resultSize = 0; + EasyMock.verify(dns); + EasyMock.reset(dns); + EasyMock.expect(dns.options()).andReturn(optionsMock).times(ZONE_LIST.size() + 1); + EasyMock.replay(dns); + Zone zoneInfoFunctional = new Zone(dns, new ZoneInfo.BuilderImpl(ZONE_INFO)); + while (iterator.hasNext()) { + assertEquals(zoneInfoFunctional, iterator.next()); + resultSize++; + } + assertEquals(ZONE_LIST.size(), resultSize); + } + + @Test + public void testCreateZone() { + EasyMock.reset(batchMock); + Capture> callback = Capture.newInstance(); + Capture> capturedOptions = Capture.newInstance(); + Capture capturedZone = Capture.newInstance(); + batchMock.addCreateZone(EasyMock.capture(capturedZone), EasyMock.capture(callback), + EasyMock.capture(capturedOptions)); + EasyMock.replay(batchMock); + DnsBatchResult batchResult = dnsBatch.createZone(ZONE_INFO); + assertEquals(0, capturedOptions.getValue().size()); + assertEquals(ZONE_INFO.toPb(), capturedZone.getValue()); + assertNotNull(callback.getValue()); + try { + batchResult.get(); + fail("No result available yet."); + } catch (IllegalStateException ex) { + // expected + } + // testing error here, success is tested with options + RpcBatch.Callback capturedCallback = callback.getValue(); + capturedCallback.onFailure(GOOGLE_JSON_ERROR); + try { + batchResult.get(); + fail("Should throw a DnsException on error."); + } catch (DnsException ex) { + // expected + assertTrue(ex.idempotent()); + } + } + + @Test + public void testCreateZoneWithOptions() { + EasyMock.reset(dns, batchMock, optionsMock); + EasyMock.expect(dns.options()).andReturn(optionsMock); + EasyMock.expect(optionsMock.service()).andReturn(dns); + Capture> callback = Capture.newInstance(); + Capture> capturedOptions = Capture.newInstance(); + Capture capturedZone = Capture.newInstance(); + batchMock.addCreateZone(EasyMock.capture(capturedZone), EasyMock.capture(callback), + EasyMock.capture(capturedOptions)); + EasyMock.replay(dns, batchMock, optionsMock); + DnsBatchResult batchResult = dnsBatch.createZone(ZONE_INFO, ZONE_FIELDS); + assertEquals(ZONE_INFO.toPb(), capturedZone.getValue()); + assertNotNull(callback.getValue()); + String selector = (String) capturedOptions.getValue().get(ZONE_FIELDS.rpcOption()); + assertTrue(selector.contains(Dns.ZoneField.CREATION_TIME.selector())); + assertTrue(selector.contains(Dns.ZoneField.NAME.selector())); + RpcBatch.Callback capturedCallback = callback.getValue(); + capturedCallback.onSuccess(ZONE_INFO.toPb()); + assertEquals(ZONE_INFO.toPb(), batchResult.get().toPb()); + } + + @Test + public void testGetZone() { + EasyMock.reset(batchMock); + Capture> callback = Capture.newInstance(); + Capture> capturedOptions = Capture.newInstance(); + batchMock.addGetZone(EasyMock.eq(ZONE_NAME), EasyMock.capture(callback), + EasyMock.capture(capturedOptions)); + EasyMock.replay(batchMock); + DnsBatchResult batchResult = dnsBatch.getZone(ZONE_NAME); + assertEquals(0, capturedOptions.getValue().size()); + assertNotNull(callback.getValue()); + try { + batchResult.get(); + fail("No result available yet."); + } catch (IllegalStateException ex) { + // expected + } + // testing error here, success is tested with options + RpcBatch.Callback capturedCallback = callback.getValue(); + capturedCallback.onFailure(GOOGLE_JSON_ERROR); + try { + batchResult.get(); + fail("Should throw a DnsException on error."); + } catch (DnsException ex) { + // expected + assertTrue(ex.idempotent()); + } + } + + @Test + public void testGetZoneWithOptions() { + EasyMock.reset(dns, batchMock, optionsMock); + EasyMock.expect(dns.options()).andReturn(optionsMock); + Capture> callback = Capture.newInstance(); + Capture> capturedOptions = Capture.newInstance(); + batchMock.addGetZone(EasyMock.eq(ZONE_NAME), EasyMock.capture(callback), + EasyMock.capture(capturedOptions)); + EasyMock.expect(optionsMock.service()).andReturn(dns); + EasyMock.replay(dns, batchMock, optionsMock); + DnsBatchResult batchResult = dnsBatch.getZone(ZONE_NAME, ZONE_FIELDS); + assertNotNull(callback.getValue()); + String selector = (String) capturedOptions.getValue().get(ZONE_FIELDS.rpcOption()); + assertTrue(selector.contains(Dns.ZoneField.CREATION_TIME.selector())); + assertTrue(selector.contains(Dns.ZoneField.NAME.selector())); + RpcBatch.Callback capturedCallback = callback.getValue(); + capturedCallback.onSuccess(ZONE_INFO.toPb()); + assertEquals(ZONE_INFO.toPb(), batchResult.get().toPb()); + } + + @Test + public void testDeleteZone() { + EasyMock.reset(batchMock); + Capture> callback = Capture.newInstance(); + batchMock.addDeleteZone(EasyMock.eq(ZONE_NAME), EasyMock.capture(callback)); + EasyMock.replay(batchMock); + DnsBatchResult batchResult = dnsBatch.deleteZone(ZONE_NAME); + assertNotNull(callback.getValue()); + try { + batchResult.get(); + fail("No result available yet."); + } catch (IllegalStateException ex) { + // expected + } + // testing error here, success is tested with options + RpcBatch.Callback capturedCallback = callback.getValue(); + capturedCallback.onFailure(GOOGLE_JSON_ERROR); + try { + batchResult.get(); + fail("Should throw a DnsException on error."); + } catch (DnsException ex) { + // expected + assertFalse(ex.idempotent()); + } + } + + @Test + public void testDeleteZoneOnSuccess() { + EasyMock.reset(batchMock); + Capture> callback = Capture.newInstance(); + batchMock.addDeleteZone(EasyMock.eq(ZONE_NAME), EasyMock.capture(callback)); + EasyMock.replay(batchMock); + DnsBatchResult batchResult = dnsBatch.deleteZone(ZONE_NAME); + assertNotNull(callback.getValue()); + RpcBatch.Callback capturedCallback = callback.getValue(); + Void result = null; + capturedCallback.onSuccess(result); + assertTrue(batchResult.get()); + } + + @Test + public void testGetProject() { + EasyMock.reset(batchMock); + Capture> callback = Capture.newInstance(); + Capture> capturedOptions = Capture.newInstance(); + batchMock.addGetProject(EasyMock.capture(callback), EasyMock.capture(capturedOptions)); + EasyMock.replay(batchMock); + DnsBatchResult batchResult = dnsBatch.getProject(); + assertEquals(0, capturedOptions.getValue().size()); + assertNotNull(callback.getValue()); + try { + batchResult.get(); + fail("No result available yet."); + } catch (IllegalStateException ex) { + // expected + } + // testing error here, success is tested with options + RpcBatch.Callback capturedCallback = callback.getValue(); + capturedCallback.onFailure(GOOGLE_JSON_ERROR); + try { + batchResult.get(); + fail("Should throw a DnsException on error."); + } catch (DnsException ex) { + // expected + assertTrue(ex.idempotent()); + } + } + + @Test + public void testGetProjectWithOptions() { + EasyMock.reset(batchMock); + Capture> callback = Capture.newInstance(); + Capture> capturedOptions = Capture.newInstance(); + batchMock.addGetProject(EasyMock.capture(callback), EasyMock.capture(capturedOptions)); + EasyMock.replay(batchMock); + DnsBatchResult batchResult = dnsBatch.getProject(PROJECT_FIELDS); + assertNotNull(callback.getValue()); + String selector = (String) capturedOptions.getValue().get(PROJECT_FIELDS.rpcOption()); + assertTrue(selector.contains(Dns.ProjectField.QUOTA.selector())); + assertTrue(selector.contains(Dns.ProjectField.PROJECT_ID.selector())); + RpcBatch.Callback capturedCallback = callback.getValue(); + capturedCallback.onSuccess(PROJECT_INFO.toPb()); + assertEquals(PROJECT_INFO, batchResult.get()); + } + + @Test + public void testListRecordSets() { + EasyMock.reset(batchMock); + Capture> callback = Capture.newInstance(); + Capture> capturedOptions = Capture.newInstance(); + batchMock.addListRecordSets(EasyMock.eq(ZONE_NAME), EasyMock.capture(callback), + EasyMock.capture(capturedOptions)); + EasyMock.replay(batchMock); + DnsBatchResult> batchResult = dnsBatch.listRecordSets(ZONE_NAME); + assertEquals(0, capturedOptions.getValue().size()); + assertNotNull(callback.getValue()); + try { + batchResult.get(); + fail("No result available yet."); + } catch (IllegalStateException ex) { + // expected + } + RpcBatch.Callback capturedCallback = callback.getValue(); + capturedCallback.onFailure(GOOGLE_JSON_ERROR); + try { + batchResult.get(); + fail("Should throw a DnsException on error."); + } catch (DnsException ex) { + // expected + assertTrue(ex.idempotent()); + } + } + + @Test + public void testListRecordSetsWithOptions() { + EasyMock.reset(batchMock); + Capture> callback = Capture.newInstance(); + Capture> capturedOptions = Capture.newInstance(); + batchMock.addListRecordSets(EasyMock.eq(ZONE_NAME), EasyMock.capture(callback), + EasyMock.capture(capturedOptions)); + EasyMock.replay(batchMock); + DnsBatchResult> batchResult = + dnsBatch.listRecordSets(ZONE_NAME, RECORD_SET_LIST_OPTIONS); + assertNotNull(callback.getValue()); + Integer size = (Integer) capturedOptions.getValue().get(RECORD_SET_LIST_OPTIONS[0].rpcOption()); + assertEquals(MAX_SIZE, size); + String selector = (String) capturedOptions.getValue() + .get(RECORD_SET_LIST_OPTIONS[1].rpcOption()); + assertEquals(PAGE_TOKEN, selector); + selector = (String) capturedOptions.getValue().get(RECORD_SET_LIST_OPTIONS[2].rpcOption()); + assertTrue(selector.contains(Dns.RecordSetField.NAME.selector())); + assertTrue(selector.contains(Dns.RecordSetField.TTL.selector())); + selector = (String) capturedOptions.getValue().get(RECORD_SET_LIST_OPTIONS[3].rpcOption()); + assertEquals(RECORD_SET_LIST_OPTIONS[3].value(), selector); + String type = (String) capturedOptions.getValue().get(RECORD_SET_LIST_OPTIONS[4] + .rpcOption()); + assertEquals(RECORD_SET_LIST_OPTIONS[4].value(), type); + RpcBatch.Callback capturedCallback = callback.getValue(); + ResourceRecordSetsListResponse response = new ResourceRecordSetsListResponse() + .setRrsets(RECORD_SET_LIST) + .setNextPageToken(PAGE_TOKEN); + capturedCallback.onSuccess(response); + Page page = batchResult.get(); + assertEquals(PAGE_TOKEN, page.nextPageCursor()); + Iterator iterator = page.values().iterator(); + int resultSize = 0; + while (iterator.hasNext()) { + assertEquals(RECORD_SET, iterator.next()); + resultSize++; + } + assertEquals(RECORD_SET_LIST.size(), resultSize); + } + + @Test + public void testListChangeRequests() { + EasyMock.reset(batchMock); + Capture> callback = Capture.newInstance(); + Capture> capturedOptions = Capture.newInstance(); + batchMock.addListChangeRequests(EasyMock.eq(ZONE_NAME), EasyMock.capture(callback), + EasyMock.capture(capturedOptions)); + EasyMock.replay(batchMock); + DnsBatchResult> batchResult = dnsBatch.listChangeRequests(ZONE_NAME); + assertNotNull(callback.getValue()); + assertEquals(0, capturedOptions.getValue().size()); + try { + batchResult.get(); + fail("No result available yet."); + } catch (IllegalStateException ex) { + // expected + } + RpcBatch.Callback capturedCallback = callback.getValue(); + capturedCallback.onFailure(GOOGLE_JSON_ERROR); + try { + batchResult.get(); + fail("Should throw a DnsException on error."); + } catch (DnsException ex) { + // expected + assertTrue(ex.idempotent()); + } + } + + @Test + public void testListChangeRequestsWithOptions() { + EasyMock.reset(batchMock); + Capture> callback = Capture.newInstance(); + Capture> capturedOptions = Capture.newInstance(); + batchMock.addListChangeRequests(EasyMock.eq(ZONE_NAME), EasyMock.capture(callback), + EasyMock.capture(capturedOptions)); + EasyMock.replay(batchMock); + DnsBatchResult> batchResult = + dnsBatch.listChangeRequests(ZONE_NAME, CHANGE_LIST_OPTIONS); + assertNotNull(callback.getValue()); + Integer size = (Integer) capturedOptions.getValue().get(CHANGE_LIST_OPTIONS[0].rpcOption()); + assertEquals(MAX_SIZE, size); + String selector = (String) capturedOptions.getValue().get(CHANGE_LIST_OPTIONS[1].rpcOption()); + assertEquals(PAGE_TOKEN, selector); + selector = (String) capturedOptions.getValue().get(CHANGE_LIST_OPTIONS[2].rpcOption()); + assertTrue(selector.contains(Dns.ChangeRequestField.STATUS.selector())); + assertTrue(selector.contains(Dns.ChangeRequestField.ID.selector())); + selector = (String) capturedOptions.getValue().get(CHANGE_LIST_OPTIONS[3].rpcOption()); + assertTrue(selector.contains(Dns.SortingOrder.ASCENDING.selector())); + // check the callback + ChangesListResponse response = new ChangesListResponse() + .setChanges(CHANGE_LIST) + .setNextPageToken(PAGE_TOKEN); + RpcBatch.Callback capturedCallback = callback.getValue(); + EasyMock.verify(optionsMock); + EasyMock.reset(optionsMock); + EasyMock.expect(optionsMock.service()).andReturn(dns); + EasyMock.replay(optionsMock); + capturedCallback.onSuccess(response); + Page page = batchResult.get(); + assertEquals(PAGE_TOKEN, page.nextPageCursor()); + Iterator iterator = page.values().iterator(); + int resultSize = 0; + EasyMock.verify(dns); + EasyMock.reset(dns); + EasyMock.expect(dns.options()).andReturn(optionsMock).times(CHANGE_LIST.size()); + EasyMock.replay(dns); + while (iterator.hasNext()) { + assertEquals(CHANGE_REQUEST_COMPLETE.toPb(), iterator.next().toPb()); + resultSize++; + } + assertEquals(CHANGE_LIST.size(), resultSize); + } + + @Test + public void testGetChangeRequest() { + EasyMock.reset(batchMock); + Capture> callback = Capture.newInstance(); + Capture> capturedOptions = Capture.newInstance(); + batchMock.addGetChangeRequest(EasyMock.eq(ZONE_NAME), + EasyMock.eq(CHANGE_REQUEST_COMPLETE.generatedId()), EasyMock.capture(callback), + EasyMock.capture(capturedOptions)); + EasyMock.replay(batchMock); + DnsBatchResult batchResult = dnsBatch.getChangeRequest(ZONE_NAME, + CHANGE_REQUEST_COMPLETE.generatedId()); + assertEquals(0, capturedOptions.getValue().size()); + assertNotNull(callback.getValue()); + try { + batchResult.get(); + fail("No result available yet."); + } catch (IllegalStateException ex) { + // expected + } + // testing error here, success is tested with options + RpcBatch.Callback capturedCallback = callback.getValue(); + capturedCallback.onFailure(GOOGLE_JSON_ERROR); + try { + batchResult.get(); + fail("Should throw a DnsException on error."); + } catch (DnsException ex) { + // expected + assertTrue(ex.idempotent()); + } + } + + @Test + public void testGetChangeRequestWithOptions() { + EasyMock.reset(dns, batchMock, optionsMock); + EasyMock.expect(dns.options()).andReturn(optionsMock); + EasyMock.expect(optionsMock.service()).andReturn(dns); + Capture> callback = Capture.newInstance(); + Capture> capturedOptions = Capture.newInstance(); + batchMock.addGetChangeRequest(EasyMock.eq(ZONE_NAME), + EasyMock.eq(CHANGE_REQUEST_COMPLETE.generatedId()), EasyMock.capture(callback), + EasyMock.capture(capturedOptions)); + EasyMock.replay(dns, batchMock, optionsMock); + DnsBatchResult batchResult = dnsBatch.getChangeRequest(ZONE_NAME, + CHANGE_REQUEST_COMPLETE.generatedId(), CHANGE_GET_FIELDS); + assertNotNull(callback.getValue()); + String selector = (String) capturedOptions.getValue().get(CHANGE_GET_FIELDS.rpcOption()); + assertTrue(selector.contains(Dns.ChangeRequestField.STATUS.selector())); + assertTrue(selector.contains(Dns.ChangeRequestField.ID.selector())); + RpcBatch.Callback capturedCallback = callback.getValue(); + capturedCallback.onSuccess(CHANGE_REQUEST_COMPLETE.toPb()); + assertEquals(CHANGE_REQUEST_COMPLETE.toPb(), batchResult.get().toPb()); + } + + @Test + public void testApplyChangeRequest() { + EasyMock.reset(batchMock); + Capture> callback = Capture.newInstance(); + Capture> capturedOptions = Capture.newInstance(); + batchMock.addApplyChangeRequest(EasyMock.eq(ZONE_NAME), + EasyMock.eq(CHANGE_REQUEST_PARTIAL.toPb()), EasyMock.capture(callback), + EasyMock.capture(capturedOptions)); + EasyMock.replay(batchMock); + DnsBatchResult batchResult = dnsBatch.applyChangeRequest(ZONE_INFO.name(), + CHANGE_REQUEST_PARTIAL); + assertEquals(0, capturedOptions.getValue().size()); + assertNotNull(callback.getValue()); + try { + batchResult.get(); + fail("No result available yet."); + } catch (IllegalStateException ex) { + // expected + } + // testing error here, success is tested with options + RpcBatch.Callback capturedCallback = callback.getValue(); + capturedCallback.onFailure(GOOGLE_JSON_ERROR); + try { + batchResult.get(); + fail("Should throw a DnsException on error."); + } catch (DnsException ex) { + // expected + assertFalse(ex.idempotent()); + } + } + + @Test + public void testApplyChangeRequestWithOptions() { + EasyMock.reset(dns, batchMock, optionsMock); + EasyMock.expect(dns.options()).andReturn(optionsMock); + Capture> callback = Capture.newInstance(); + Capture> capturedOptions = Capture.newInstance(); + batchMock.addApplyChangeRequest(EasyMock.eq(ZONE_NAME), + EasyMock.eq(CHANGE_REQUEST_PARTIAL.toPb()), EasyMock.capture(callback), + EasyMock.capture(capturedOptions)); + EasyMock.expect(optionsMock.service()).andReturn(dns); + EasyMock.replay(dns, batchMock, optionsMock); + DnsBatchResult batchResult = dnsBatch.applyChangeRequest(ZONE_INFO.name(), + CHANGE_REQUEST_PARTIAL, CHANGE_GET_FIELDS); + String selector = (String) capturedOptions.getValue().get(CHANGE_GET_FIELDS.rpcOption()); + assertTrue(selector.contains(Dns.ChangeRequestField.STATUS.selector())); + assertTrue(selector.contains(Dns.ChangeRequestField.ID.selector())); + RpcBatch.Callback capturedCallback = callback.getValue(); + capturedCallback.onSuccess(CHANGE_REQUEST_COMPLETE.toPb()); + assertEquals(CHANGE_REQUEST_COMPLETE.toPb(), batchResult.get().toPb()); + } +} diff --git a/gcloud-java-dns/src/test/java/com/google/cloud/dns/DnsImplTest.java b/gcloud-java-dns/src/test/java/com/google/cloud/dns/DnsImplTest.java index 457f5ed3adb3..d34cb340e6e3 100644 --- a/gcloud-java-dns/src/test/java/com/google/cloud/dns/DnsImplTest.java +++ b/gcloud-java-dns/src/test/java/com/google/cloud/dns/DnsImplTest.java @@ -91,7 +91,7 @@ public class DnsImplTest { Dns.ChangeRequestListOption.pageToken(PAGE_TOKEN), Dns.ChangeRequestListOption.fields(Dns.ChangeRequestField.STATUS), Dns.ChangeRequestListOption.sortOrder(Dns.SortingOrder.ASCENDING)}; - private static final Dns.RecordSetListOption[] DNS_RECORD_LIST_OPTIONS = { + private static final Dns.RecordSetListOption[] RECORD_SET_LIST_OPTIONS = { Dns.RecordSetListOption.pageSize(MAX_SIZE), Dns.RecordSetListOption.pageToken(PAGE_TOKEN), Dns.RecordSetListOption.fields(Dns.RecordSetField.TTL), @@ -350,7 +350,7 @@ public void testListZonesWithOptions() { } @Test - public void testListDnsRecords() { + public void testListRecordSets() { EasyMock.expect(dnsRpcMock.listRecordSets(ZONE_INFO.name(), EMPTY_RPC_OPTIONS)) .andReturn(LIST_OF_PB_DNS_RECORDS); EasyMock.replay(dnsRpcMock); @@ -362,28 +362,28 @@ public void testListDnsRecords() { } @Test - public void testListDnsRecordsWithOptions() { + public void testListRecordSetsWithOptions() { Capture> capturedOptions = Capture.newInstance(); EasyMock.expect(dnsRpcMock.listRecordSets(EasyMock.eq(ZONE_NAME), EasyMock.capture(capturedOptions))).andReturn(LIST_OF_PB_DNS_RECORDS); EasyMock.replay(dnsRpcMock); dns = options.service(); // creates DnsImpl - Page dnsPage = dns.listRecordSets(ZONE_NAME, DNS_RECORD_LIST_OPTIONS); + Page dnsPage = dns.listRecordSets(ZONE_NAME, RECORD_SET_LIST_OPTIONS); assertEquals(2, Lists.newArrayList(dnsPage.values()).size()); assertTrue(Lists.newArrayList(dnsPage.values()).contains(DNS_RECORD1)); assertTrue(Lists.newArrayList(dnsPage.values()).contains(DNS_RECORD2)); - Integer size = (Integer) capturedOptions.getValue().get(DNS_RECORD_LIST_OPTIONS[0].rpcOption()); + Integer size = (Integer) capturedOptions.getValue().get(RECORD_SET_LIST_OPTIONS[0].rpcOption()); assertEquals(MAX_SIZE, size); String selector = (String) capturedOptions.getValue() - .get(DNS_RECORD_LIST_OPTIONS[1].rpcOption()); + .get(RECORD_SET_LIST_OPTIONS[1].rpcOption()); assertEquals(PAGE_TOKEN, selector); - selector = (String) capturedOptions.getValue().get(DNS_RECORD_LIST_OPTIONS[2].rpcOption()); + selector = (String) capturedOptions.getValue().get(RECORD_SET_LIST_OPTIONS[2].rpcOption()); assertTrue(selector.contains(Dns.RecordSetField.NAME.selector())); assertTrue(selector.contains(Dns.RecordSetField.TTL.selector())); - selector = (String) capturedOptions.getValue().get(DNS_RECORD_LIST_OPTIONS[3].rpcOption()); - assertEquals(DNS_RECORD_LIST_OPTIONS[3].value(), selector); - String type = (String) capturedOptions.getValue().get(DNS_RECORD_LIST_OPTIONS[4] + selector = (String) capturedOptions.getValue().get(RECORD_SET_LIST_OPTIONS[3].rpcOption()); + assertEquals(RECORD_SET_LIST_OPTIONS[3].value(), selector); + String type = (String) capturedOptions.getValue().get(RECORD_SET_LIST_OPTIONS[4] .rpcOption()); - assertEquals(DNS_RECORD_LIST_OPTIONS[4].value(), type); + assertEquals(RECORD_SET_LIST_OPTIONS[4].value(), type); } } diff --git a/gcloud-java-dns/src/test/java/com/google/cloud/dns/SerializationTest.java b/gcloud-java-dns/src/test/java/com/google/cloud/dns/SerializationTest.java index e510850f62ab..2115c9dd98bf 100644 --- a/gcloud-java-dns/src/test/java/com/google/cloud/dns/SerializationTest.java +++ b/gcloud-java-dns/src/test/java/com/google/cloud/dns/SerializationTest.java @@ -22,6 +22,8 @@ import com.google.cloud.RetryParams; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList; + import java.io.Serializable; import java.math.BigInteger; import java.util.concurrent.TimeUnit; diff --git a/gcloud-java-dns/src/test/java/com/google/cloud/dns/it/ITDnsTest.java b/gcloud-java-dns/src/test/java/com/google/cloud/dns/it/ITDnsTest.java index dd8eafa775cd..c511ef3672e2 100644 --- a/gcloud-java-dns/src/test/java/com/google/cloud/dns/it/ITDnsTest.java +++ b/gcloud-java-dns/src/test/java/com/google/cloud/dns/it/ITDnsTest.java @@ -27,6 +27,8 @@ import com.google.cloud.dns.ChangeRequest; import com.google.cloud.dns.ChangeRequestInfo; import com.google.cloud.dns.Dns; +import com.google.cloud.dns.DnsBatch; +import com.google.cloud.dns.DnsBatchResult; import com.google.cloud.dns.DnsException; import com.google.cloud.dns.DnsOptions; import com.google.cloud.dns.ProjectInfo; @@ -34,6 +36,7 @@ import com.google.cloud.dns.Zone; import com.google.cloud.dns.ZoneInfo; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterators; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -87,7 +90,7 @@ public class ITDnsTest { .build(); private static final List ZONE_NAMES = ImmutableList.of(ZONE_NAME1, ZONE_NAME_EMPTY_DESCRIPTION); - + @Rule public Timeout globalTimeout = Timeout.seconds(300); @@ -962,4 +965,27 @@ public void testListDnsRecords() { clear(); } } + + @Test + public void testListZoneBatch() { + DNS.create(ZONE1); + DNS.create(ZONE_EMPTY_DESCRIPTION); + try { + DnsBatch batch = DNS.batch(); + DnsBatchResult> batchResult = batch.listZones(); + batch.submit(); + assertTrue(batchResult.completed()); + Iterator iteratorBatch = batchResult.get().iterateAll(); + Iterator iteratorList = DNS.listZones().iterateAll(); + assertEquals(Iterators.size(iteratorList), Iterators.size(iteratorBatch)); + while (iteratorBatch.hasNext()) { + assertTrue(Iterators.contains(iteratorList, iteratorBatch.next())); + } + } finally { + DNS.delete(ZONE1.name()); + DNS.delete(ZONE_EMPTY_DESCRIPTION.name()); + } + } + + // todo(mderka) implement tests for other batch calls, issue #874 }