Skip to content

Commit

Permalink
Added batch concept supporting list zones calls only.
Browse files Browse the repository at this point in the history
  • Loading branch information
mderka committed Mar 18, 2016
1 parent 17442b0 commit 1855733
Show file tree
Hide file tree
Showing 9 changed files with 466 additions and 9 deletions.
15 changes: 15 additions & 0 deletions gcloud-java-dns/src/main/java/com/google/gcloud/dns/Dns.java
Original file line number Diff line number Diff line change
Expand Up @@ -533,4 +533,19 @@ ChangeRequest getChangeRequest(String zoneName, String changeRequestId,
* @see <a href="https://cloud.google.com/dns/api/v1/changes/list">Cloud DNS Chages: list</a>
*/
Page<ChangeRequest> listChangeRequests(String zoneName, ChangeRequestListOption... options);

/**
* Submits a {@code batch} for processing to the Google Cloud DNS. The operations in the batch
* will be called using a single HTTP request. For each successfully executed operation, its
* {@link DnsBatch.Callback#success(Object, DnsBatch.Request)} callback method will be invoked.
* For each operation which returned an error, its {@link DnsBatch.Callback#error(DnsException,
* DnsBatch.Request)} callback method will be invoked.
*/
void submitBatch(DnsBatch batch);

/**
* Initiates a new empty batch ready to be populated with service calls, which will use this
* {@code Dns} instance when submitted for processing to Google Cloud DNS.
*/
DnsBatch batch();
}
231 changes: 231 additions & 0 deletions gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsBatch.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
/*
* 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.gcloud.dns;

import com.google.gcloud.Page;

import java.util.LinkedHashMap;
import java.util.Map;

/**
* A batch of operations to be submitted to Google Cloud DNS using a single HTTP request.
*/
public class DnsBatch {

private Map<Request, Callback> requests = new LinkedHashMap<>();
private Dns dns;

/**
* An interface for the callback which will be invoked when the operation has been executed. The
* parameter {@code <T>} represents the type of the result of the operation and thus depends on
* the {@link DnsBatch.Request} that this call back belongs to and it should be as follows:
*
* <ul>
* <li>{@link Zone} for creating and getting a zone</li>
* <li>{@link Boolean} for deleting a zone</li>
* <li>{@link ChangeRequest} for creating and getting a change request</li>
* <li>{@link ProjectInfo} for getting a project</li>
* <li>{@code Page<Zone>} for listing zones</li>
* <li>{@code Page<DnsRecord>} for listing {@link DnsRecord}s inside a zone</li>
* <li>{@code Page<ChangeRequest>} for listing {@link ChangeRequest}s for a zone</li>
* </ul>
*/
public interface Callback<T> {
/**
* A method which will be called if the {@link DnsBatch.Request} succeeds. See the {@link
* Callback} documentation for details on type {@code T}.
*
* @param output the result of the operation
* @param request the request which succeeded
*/
void success(T output, DnsBatch.Request request);

/**
* A method which will be called if the {@link DnsBatch.Request} fails.
*
* @param ex the error
* @param request the request which failed
*/
void error(DnsException ex, DnsBatch.Request request);
}

/**
* An operation to be submitted to Google Cloud DNS within this batch. Only an subset of the class
* attributes appropriate for the represented operation is initialized. Refer to the class method
* and attribute documentation for the specific fields.
*/
public static class Request {

private final String zoneName;
private final String changeId;
private final ChangeRequest changeRequest;
private final ZoneInfo zoneInfo;
private final Operation operation;
private final AbstractOption[] options;

private Request(RequestBuilder builder) {
this.zoneName = builder.zoneName;
this.changeId = builder.changeId;
this.changeRequest = builder.changeRequest;
this.zoneInfo = builder.zoneInfo;
this.operation = builder.operation;
this.options = builder.options;
}

private static RequestBuilder builder(Operation operation, AbstractOption... options) {
return new RequestBuilder(operation, options);
}

/**
* Returns the name of the zone to which the operation is applied. This field is initialized for
* zone create, get and delete operation, and listing DNS records and changes within a zone.
* Returns {@code null} in other cases.
*/
public String zoneName() {
return zoneName;
}

/**
* Returns the id of the change request which is being retrieved. Getting a change request is
* the only operation when this attribute is initialized. The method returns {@code null} in the
* remaining cases.
*/
public String changeId() {
return changeId;
}

/**
* Returns the change request which is being created. Creating a change request is the only
* operation when this attribute is initialized. The method returns {@code null} in the
* remaining cases.
*/
public ChangeRequest changeRequest() {
return changeRequest;
}

/**
* Returns the zone which is being created. Creating a zone is the only operation when this
* attribute is initialized. The method returns {@code null} in the remaining cases.
*/
public ZoneInfo zoneInfo() {
return zoneInfo;
}

/**
* Returns the type of the operation represented by this {@link DnsBatch.Request}. This field is
* always initialized.
*/
public Operation operation() {
return operation;
}

/**
* Returns options provided to the operation. Returns an empty array if no options were
* provided.
*/
public AbstractOption[] options() {
return options == null ? new AbstractOption[0] : options;
}
}

static class RequestBuilder {
private final AbstractOption[] options;
private String zoneName;
private String changeId;
private ChangeRequest changeRequest;
private ZoneInfo zoneInfo;
private final Operation operation;

RequestBuilder(Operation operation, AbstractOption... options) {
this.operation = operation;
this.options = options;
}

RequestBuilder zoneName(String zoneName) {
this.zoneName = zoneName;
return this;
}

RequestBuilder changeId(String changeId) {
this.changeId = changeId;
return this;
}

RequestBuilder changeRequest(ChangeRequest changeRequest) {
this.changeRequest = changeRequest;
return this;
}

RequestBuilder zoneInfo(ZoneInfo zoneInfo) {
this.zoneInfo = zoneInfo;
return this;
}

Request build() {
return new Request(this);
}
}

/**
* Represents the type of the batch operation.
*/
public enum Operation {
CREATE_ZONE,
DELETE_ZONE,
GET_ZONE,
LIST_ZONES,
APPLY_CHANGE_REQUEST,
GET_CHANGE_REQUEST,
LIST_CHANGES_REQUESTS,
LIST_DNS_RECORDS
}

DnsBatch(Dns dns) {
this.dns = dns;
}

public Dns service() {
return dns;
}

Map<Request, Callback> requests() {
return requests;
}

/**
* Adds a {@code DnsBatch.Request} represeting the list zones operation to this batch. The request
* will not have initialized any fields except for the operation type and options (if provided).
* The {@code callback} will receive a page of {@link Zone}s upon success of the request. 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...)}.
*/
public DnsBatch listZones(Callback<Page<Zone>> callback, Dns.ZoneListOption... options) {
Request request = Request.builder(Operation.LIST_ZONES, options).build();
requests.put(request, callback);
return this;
}

// todo(mderka) add the rest of the operations

/**
* Submits this batch for processing using a single HTTP request. This will invoke all callbacks
* for the invidual {@link DnsBatch.Request}s contained in this batch.
*/
public void submit() {
dns.submitBatch(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.gcloud.dns;

import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.common.collect.ImmutableSet;
import com.google.gcloud.BaseServiceException;
import com.google.gcloud.RetryHelper.RetryHelperException;
Expand Down Expand Up @@ -43,6 +44,10 @@ public DnsException(IOException exception) {
super(exception, true);
}

public DnsException(GoogleJsonError error) {
super(error, true);
}

private DnsException(int code, String message) {
super(code, message, null, true);
}
Expand Down
87 changes: 84 additions & 3 deletions gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,13 @@
import static com.google.gcloud.RetryHelper.runWithRetries;
import static com.google.gcloud.dns.ChangeRequest.fromPb;

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.services.dns.model.Change;
import com.google.api.services.dns.model.ManagedZone;
import com.google.api.services.dns.model.ManagedZonesListResponse;
import com.google.api.services.dns.model.ResourceRecordSet;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
Expand All @@ -35,6 +40,7 @@
import com.google.gcloud.RetryHelper;
import com.google.gcloud.dns.spi.DnsRpc;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.Callable;

Expand Down Expand Up @@ -309,11 +315,86 @@ public com.google.api.services.dns.model.Change call() {
}
}

@Override
public void submitBatch(DnsBatch toSubmit) {
try {
BatchRequest batchRequest = prepareBatch(toSubmit);
batchRequest.execute();
} catch (IOException ex) {
throw new DnsException(ex);
}
}

/**
* Since {@code BatchRequest} is a final class, it cannot be mocked with easy mock and the call of
* {@code execute()} cannot be tested. Thus, most of the functionality of {@link
* #submitBatch(DnsBatch)} is extracted to this method which does not make the call so it does not
* communicate with the service.
*/
BatchRequest prepareBatch(DnsBatch toSubmit) throws IOException {
BatchRequest batch = null;
for (Map.Entry<DnsBatch.Request, DnsBatch.Callback> entry : toSubmit.requests().entrySet()) {
DnsBatch.Request request = entry.getKey();
DnsBatch.Callback callback = entry.getValue();
switch (request.operation()) {
case LIST_ZONES:
JsonBatchCallback rpcCallback = listZonesCallback(callback, request);
batch =
dnsRpc.enqueueListZones(batch, request, rpcCallback, optionMap(request.options()));
break;
default:
// todo(mderka) implement the rest of the operations
throw new UnsupportedOperationException("Not implemented yet");
}
}
return batch;
}

@Override
public DnsBatch batch() {
return new DnsBatch(this);
}

private JsonBatchCallback listZonesCallback(final DnsBatch.Callback callback,
final DnsBatch.Request request) {
return new JsonBatchCallback<ManagedZonesListResponse>() {
@Override
public void onFailure(GoogleJsonError error, HttpHeaders httpHeaders) {
if (callback != null) {
callback.error(new DnsException(error), request);
}
}

@Override
public void onSuccess(ManagedZonesListResponse zoneList, HttpHeaders httpHeaders) {
if (callback != null) {
DnsRpc.ListResult<ManagedZone> listResult =
DnsRpc.ListResult.of(zoneList.getNextPageToken(), zoneList.getManagedZones());
String cursor = listResult.pageToken();
Iterable<Zone> zones = listResult.results() == null ? ImmutableList.<Zone>of()
: Iterables.transform(listResult.results(), new Function<ManagedZone, Zone>() {
@Override
public Zone apply(ManagedZone managedZone) {
return new Zone(options().service(),
new ZoneInfo.BuilderImpl(ZoneInfo.fromPb(managedZone)));
}
}
);
Page<Zone> page = new PageImpl<>(
new ZonePageFetcher(options(), cursor, optionMap(request.options())), cursor, zones);
callback.success(page, request);
}
}
};
}

private Map<DnsRpc.Option, ?> optionMap(AbstractOption... options) {
Map<DnsRpc.Option, Object> temp = Maps.newEnumMap(DnsRpc.Option.class);
for (AbstractOption option : options) {
Object prev = temp.put(option.rpcOption(), option.value());
checkArgument(prev == null, "Duplicate option %s", option);
if (options != null) {
for (AbstractOption option : options) {
Object prev = temp.put(option.rpcOption(), option.value());
checkArgument(prev == null, "Duplicate option %s", option);
}
}
return ImmutableMap.copyOf(temp);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.api.services.dns.model.ManagedZone;
import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
Expand Down
Loading

0 comments on commit 1855733

Please sign in to comment.