• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *       http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.cloud.dns;
18 
19 import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
20 
21 import com.google.api.client.googleapis.json.GoogleJsonError;
22 import com.google.api.gax.paging.Page;
23 import com.google.api.services.dns.model.Change;
24 import com.google.api.services.dns.model.ChangesListResponse;
25 import com.google.api.services.dns.model.ManagedZone;
26 import com.google.api.services.dns.model.ManagedZonesListResponse;
27 import com.google.api.services.dns.model.Project;
28 import com.google.api.services.dns.model.ResourceRecordSet;
29 import com.google.api.services.dns.model.ResourceRecordSetsListResponse;
30 import com.google.cloud.PageImpl;
31 import com.google.cloud.dns.spi.v1.DnsRpc;
32 import com.google.cloud.dns.spi.v1.RpcBatch;
33 import com.google.common.annotations.VisibleForTesting;
34 import com.google.common.collect.ImmutableList;
35 import com.google.common.collect.Iterables;
36 import java.util.List;
37 import java.util.Map;
38 
39 /** A batch of operations to be submitted to Google Cloud DNS using a single RPC request. */
40 public class DnsBatch {
41 
42   private final RpcBatch batch;
43   private final DnsRpc dnsRpc;
44   private final DnsOptions options;
45 
DnsBatch(DnsOptions options)46   DnsBatch(DnsOptions options) {
47     this.options = options;
48     this.dnsRpc = options.getDnsRpcV1();
49     this.batch = dnsRpc.createBatch();
50   }
51 
52   @VisibleForTesting
getBatch()53   Object getBatch() {
54     return batch;
55   }
56 
57   @VisibleForTesting
getDnsRpc()58   DnsRpc getDnsRpc() {
59     return dnsRpc;
60   }
61 
62   @VisibleForTesting
getOptions()63   DnsOptions getOptions() {
64     return options;
65   }
66 
67   /**
68    * Adds a request representing the "list zones" operation to this batch. The {@code options} can
69    * be used to restrict the fields returned or provide page size limits in the same way as for
70    * {@link Dns#listZones(Dns.ZoneListOption...)}. Calling {@link DnsBatchResult#get()} on the
71    * return value yields a page of zones if successful and throws a {@link DnsException} otherwise.
72    */
listZones(Dns.ZoneListOption... options)73   public DnsBatchResult<Page<Zone>> listZones(Dns.ZoneListOption... options) {
74     DnsBatchResult<Page<Zone>> result = new DnsBatchResult<>();
75     Map<DnsRpc.Option, ?> optionMap = DnsImpl.optionMap(options);
76     RpcBatch.Callback<ManagedZonesListResponse> callback =
77         createListZonesCallback(result, optionMap);
78     batch.addListZones(callback, optionMap);
79     return result;
80   }
81 
82   /**
83    * Adds a request representing the "create zone" operation to this batch. The {@code options} can
84    * be used to restrict the fields returned in the same way as for {@link Dns#create(ZoneInfo,
85    * Dns.ZoneOption...)}. Calling {@link DnsBatchResult#get()} on the return value yields the
86    * created {@link Zone} if successful and throws a {@link DnsException} otherwise.
87    */
createZone(ZoneInfo zone, Dns.ZoneOption... options)88   public DnsBatchResult<Zone> createZone(ZoneInfo zone, Dns.ZoneOption... options) {
89     DnsBatchResult<Zone> result = new DnsBatchResult<>();
90     // todo this can cause misleading report of a failure, intended to be fixed within #924
91     RpcBatch.Callback<ManagedZone> callback = createZoneCallback(this.options, result, false, true);
92     Map<DnsRpc.Option, ?> optionMap = DnsImpl.optionMap(options);
93     batch.addCreateZone(zone.toPb(), callback, optionMap);
94     return result;
95   }
96 
97   /**
98    * Adds a request representing the "delete zone" operation to this batch. Calling {@link
99    * DnsBatchResult#get()} on the return value yields {@code true} upon successful deletion, {@code
100    * false} if the zone was not found, or throws a {@link DnsException} if the operation failed.
101    */
deleteZone(String zoneName)102   public DnsBatchResult<Boolean> deleteZone(String zoneName) {
103     DnsBatchResult<Boolean> result = new DnsBatchResult<>();
104     RpcBatch.Callback<Void> callback = createDeleteZoneCallback(result);
105     batch.addDeleteZone(zoneName, callback);
106     return result;
107   }
108 
109   /**
110    * Adds a request representing the "get zone" operation to this batch. The {@code options} can be
111    * used to restrict the fields returned in the same way as for {@link Dns#getZone(String,
112    * Dns.ZoneOption...)}. Calling {@link DnsBatchResult#get()} on the return value yields the
113    * requested {@link Zone} if successful, {@code null} if no such zone exists, or throws a {@link
114    * DnsException} if the operation failed.
115    */
getZone(String zoneName, Dns.ZoneOption... options)116   public DnsBatchResult<Zone> getZone(String zoneName, Dns.ZoneOption... options) {
117     DnsBatchResult<Zone> result = new DnsBatchResult<>();
118     RpcBatch.Callback<ManagedZone> callback = createZoneCallback(this.options, result, true, true);
119     Map<DnsRpc.Option, ?> optionMap = DnsImpl.optionMap(options);
120     batch.addGetZone(zoneName, callback, optionMap);
121     return result;
122   }
123 
124   /**
125    * Adds a request representing the "get project" operation to this batch. The {@code options} can
126    * be used to restrict the fields returned in the same way as for {@link
127    * Dns#getProject(Dns.ProjectOption...)}. Calling {@link DnsBatchResult#get()} on the return value
128    * yields the created {@link ProjectInfo} if successful and throws a {@link DnsException} if the
129    * operation failed.
130    */
getProject(Dns.ProjectOption... options)131   public DnsBatchResult<ProjectInfo> getProject(Dns.ProjectOption... options) {
132     DnsBatchResult<ProjectInfo> result = new DnsBatchResult<>();
133     RpcBatch.Callback<Project> callback = createProjectCallback(result);
134     Map<DnsRpc.Option, ?> optionMap = DnsImpl.optionMap(options);
135     batch.addGetProject(callback, optionMap);
136     return result;
137   }
138 
139   /**
140    * Adds a request representing the "list record sets" operation in the zone specified by {@code
141    * zoneName} to this batch. The {@code options} can be used to restrict the fields returned or
142    * provide page size limits in the same way as for {@link Dns#listRecordSets(String,
143    * Dns.RecordSetListOption...)}. Calling {@link DnsBatchResult#get()} on the return value yields a
144    * page of record sets if successful and throws a {@link DnsException} if the operation failed or
145    * the zone does not exist.
146    */
listRecordSets( String zoneName, Dns.RecordSetListOption... options)147   public DnsBatchResult<Page<RecordSet>> listRecordSets(
148       String zoneName, Dns.RecordSetListOption... options) {
149     DnsBatchResult<Page<RecordSet>> result = new DnsBatchResult<>();
150     Map<DnsRpc.Option, ?> optionMap = DnsImpl.optionMap(options);
151     RpcBatch.Callback<ResourceRecordSetsListResponse> callback =
152         createListRecordSetsCallback(zoneName, result, optionMap);
153     batch.addListRecordSets(zoneName, callback, optionMap);
154     return result;
155   }
156 
157   /**
158    * Adds a request representing the "list change requests" operation in the zone specified by
159    * {@code zoneName} to this batch. The {@code options} can be used to restrict the fields returned
160    * or provide page size limits in the same way as for {@link Dns#listChangeRequests(String,
161    * Dns.ChangeRequestListOption...)}. Calling {@link DnsBatchResult#get()} on the return value
162    * yields a page of change requests if successful and throws a {@link DnsException} if the
163    * operation failed or the zone does not exist.
164    */
listChangeRequests( String zoneName, Dns.ChangeRequestListOption... options)165   public DnsBatchResult<Page<ChangeRequest>> listChangeRequests(
166       String zoneName, Dns.ChangeRequestListOption... options) {
167     DnsBatchResult<Page<ChangeRequest>> result = new DnsBatchResult<>();
168     Map<DnsRpc.Option, ?> optionMap = DnsImpl.optionMap(options);
169     RpcBatch.Callback<ChangesListResponse> callback =
170         createListChangeRequestsCallback(zoneName, result, optionMap);
171     batch.addListChangeRequests(zoneName, callback, optionMap);
172     return result;
173   }
174 
175   /**
176    * Adds a request representing the "get change request" operation for the zone specified by {@code
177    * zoneName} to this batch. The {@code options} can be used to restrict the fields returned in the
178    * same way as for {@link Dns#getChangeRequest(String, String, Dns.ChangeRequestOption...)}.
179    * Calling {@link DnsBatchResult#get()} on the return value yields the requested {@link
180    * ChangeRequest} if successful, {@code null} if the change request does not exist, or throws a
181    * {@link DnsException} if the operation failed or the zone does not exist.
182    */
getChangeRequest( String zoneName, String changeRequestId, Dns.ChangeRequestOption... options)183   public DnsBatchResult<ChangeRequest> getChangeRequest(
184       String zoneName, String changeRequestId, Dns.ChangeRequestOption... options) {
185     DnsBatchResult<ChangeRequest> result = new DnsBatchResult<>();
186     RpcBatch.Callback<Change> callback = createChangeRequestCallback(zoneName, result, true, true);
187     Map<DnsRpc.Option, ?> optionMap = DnsImpl.optionMap(options);
188     batch.addGetChangeRequest(zoneName, changeRequestId, callback, optionMap);
189     return result;
190   }
191 
192   /**
193    * Adds a request representing the "apply change request" operation to the zone specified by
194    * {@code zoneName} to this batch. The {@code options} can be used to restrict the fields returned
195    * in the same way as for {@link Dns#applyChangeRequest(String, ChangeRequestInfo,
196    * Dns.ChangeRequestOption...)}. Calling {@link DnsBatchResult#get()} on the return value yields
197    * the created {@link ChangeRequest} if successful or throws a {@link DnsException} if the
198    * operation failed or the zone does not exist.
199    */
applyChangeRequest( String zoneName, ChangeRequestInfo changeRequest, Dns.ChangeRequestOption... options)200   public DnsBatchResult<ChangeRequest> applyChangeRequest(
201       String zoneName, ChangeRequestInfo changeRequest, Dns.ChangeRequestOption... options) {
202     DnsBatchResult<ChangeRequest> result = new DnsBatchResult<>();
203     RpcBatch.Callback<Change> callback =
204         createChangeRequestCallback(zoneName, result, false, false);
205     Map<DnsRpc.Option, ?> optionMap = DnsImpl.optionMap(options);
206     batch.addApplyChangeRequest(zoneName, changeRequest.toPb(), callback, optionMap);
207     return result;
208   }
209 
210   /** Submits this batch for processing using a single RPC request. */
submit()211   public void submit() {
212     batch.submit();
213   }
214 
createListZonesCallback( final DnsBatchResult<Page<Zone>> result, final Map<DnsRpc.Option, ?> optionMap)215   private RpcBatch.Callback<ManagedZonesListResponse> createListZonesCallback(
216       final DnsBatchResult<Page<Zone>> result, final Map<DnsRpc.Option, ?> optionMap) {
217     return new RpcBatch.Callback<ManagedZonesListResponse>() {
218       @Override
219       public void onSuccess(ManagedZonesListResponse response) {
220         List<ManagedZone> zones = response.getManagedZones();
221         Page<Zone> zonePage =
222             new PageImpl<>(
223                 new DnsImpl.ZonePageFetcher(options, response.getNextPageToken(), optionMap),
224                 response.getNextPageToken(),
225                 zones == null
226                     ? ImmutableList.<Zone>of()
227                     : Iterables.transform(zones, DnsImpl.zoneFromPb(options)));
228         result.success(zonePage);
229       }
230 
231       @Override
232       public void onFailure(GoogleJsonError googleJsonError) {
233         result.error(new DnsException(googleJsonError, true));
234       }
235     };
236   }
237 
238   private RpcBatch.Callback<Void> createDeleteZoneCallback(final DnsBatchResult<Boolean> result) {
239     return new RpcBatch.Callback<Void>() {
240       @Override
241       public void onSuccess(Void response) {
242         result.success(true);
243       }
244 
245       @Override
246       public void onFailure(GoogleJsonError googleJsonError) {
247         DnsException serviceException = new DnsException(googleJsonError, false);
248         if (serviceException.getCode() == HTTP_NOT_FOUND) {
249           result.success(false);
250         } else {
251           result.error(serviceException);
252         }
253       }
254     };
255   }
256 
257   /** A joint callback for both "get zone" and "create zone" operations. */
258   private RpcBatch.Callback<ManagedZone> createZoneCallback(
259       final DnsOptions serviceOptions,
260       final DnsBatchResult<Zone> result,
261       final boolean nullForNotFound,
262       final boolean idempotent) {
263     return new RpcBatch.Callback<ManagedZone>() {
264       @Override
265       public void onSuccess(ManagedZone response) {
266         result.success(
267             response == null ? null : Zone.fromPb(serviceOptions.getService(), response));
268       }
269 
270       @Override
271       public void onFailure(GoogleJsonError googleJsonError) {
272         DnsException serviceException = new DnsException(googleJsonError, idempotent);
273         if (nullForNotFound && serviceException.getCode() == HTTP_NOT_FOUND) {
274           result.success(null);
275         } else {
276           result.error(serviceException);
277         }
278       }
279     };
280   }
281 
282   private RpcBatch.Callback<Project> createProjectCallback(
283       final DnsBatchResult<ProjectInfo> result) {
284     return new RpcBatch.Callback<Project>() {
285       @Override
286       public void onSuccess(Project response) {
287         result.success(response == null ? null : ProjectInfo.fromPb(response));
288       }
289 
290       @Override
291       public void onFailure(GoogleJsonError googleJsonError) {
292         result.error(new DnsException(googleJsonError, true));
293       }
294     };
295   }
296 
297   private RpcBatch.Callback<ResourceRecordSetsListResponse> createListRecordSetsCallback(
298       final String zoneName,
299       final DnsBatchResult<Page<RecordSet>> result,
300       final Map<DnsRpc.Option, ?> optionMap) {
301     return new RpcBatch.Callback<ResourceRecordSetsListResponse>() {
302       @Override
303       public void onSuccess(ResourceRecordSetsListResponse response) {
304         List<ResourceRecordSet> recordSets = response.getRrsets();
305         Page<RecordSet> page =
306             new PageImpl<>(
307                 new DnsImpl.RecordSetPageFetcher(
308                     zoneName, options, response.getNextPageToken(), optionMap),
309                 response.getNextPageToken(),
310                 recordSets == null
311                     ? ImmutableList.<RecordSet>of()
312                     : Iterables.transform(recordSets, RecordSet.FROM_PB_FUNCTION));
313         result.success(page);
314       }
315 
316       @Override
317       public void onFailure(GoogleJsonError googleJsonError) {
318         result.error(new DnsException(googleJsonError, true));
319       }
320     };
321   }
322 
323   private RpcBatch.Callback<ChangesListResponse> createListChangeRequestsCallback(
324       final String zoneName,
325       final DnsBatchResult<Page<ChangeRequest>> result,
326       final Map<DnsRpc.Option, ?> optionMap) {
327     return new RpcBatch.Callback<ChangesListResponse>() {
328       @Override
329       public void onSuccess(ChangesListResponse response) {
330         List<Change> changes = response.getChanges();
331         Page<ChangeRequest> page =
332             new PageImpl<>(
333                 new DnsImpl.ChangeRequestPageFetcher(
334                     zoneName, options, response.getNextPageToken(), optionMap),
335                 response.getNextPageToken(),
336                 changes == null
337                     ? ImmutableList.<ChangeRequest>of()
338                     : Iterables.transform(
339                         changes, ChangeRequest.fromPbFunction(options.getService(), zoneName)));
340         result.success(page);
341       }
342 
343       @Override
344       public void onFailure(GoogleJsonError googleJsonError) {
345         result.error(new DnsException(googleJsonError, true));
346       }
347     };
348   }
349 
350   /** A joint callback for both "get change request" and "create change request" operations. */
351   private RpcBatch.Callback<Change> createChangeRequestCallback(
352       final String zoneName,
353       final DnsBatchResult<ChangeRequest> result,
354       final boolean nullForNotFound,
355       final boolean idempotent) {
356     return new RpcBatch.Callback<Change>() {
357       @Override
358       public void onSuccess(Change response) {
359         result.success(
360             response == null
361                 ? null
362                 : ChangeRequest.fromPb(options.getService(), zoneName, response));
363       }
364 
365       @Override
366       public void onFailure(GoogleJsonError googleJsonError) {
367         DnsException serviceException = new DnsException(googleJsonError, idempotent);
368         if (serviceException.getCode() == HTTP_NOT_FOUND) {
369           if ("entity.parameters.changeId".equals(serviceException.getLocation())
370               || (serviceException.getMessage() != null
371                   && serviceException.getMessage().contains("parameters.changeId"))) {
372             // the change id was not found, but the zone exists
373             result.success(null);
374             return;
375           }
376           // the zone does not exist, so throw an exception
377         }
378         result.error(serviceException);
379       }
380     };
381   }
382 }
383