From a1ec7f46c9e4e53ad185039011f49113de48c53a Mon Sep 17 00:00:00 2001 From: Martin Derka Date: Fri, 19 Feb 2016 10:32:17 -0800 Subject: [PATCH 1/9] Added local implementation of the service and tests. --- .../google/gcloud/testing/LocalDnsHelper.java | 1426 +++++++++++++++++ .../testing/OptionParsersAndExtractors.java | 279 ++++ .../gcloud/testing/LocalDnsHelperTest.java | 955 +++++++++++ 3 files changed, 2660 insertions(+) create mode 100644 gcloud-java-dns/src/main/java/com/google/gcloud/testing/LocalDnsHelper.java create mode 100644 gcloud-java-dns/src/main/java/com/google/gcloud/testing/OptionParsersAndExtractors.java create mode 100644 gcloud-java-dns/src/test/java/com/google/gcloud/testing/LocalDnsHelperTest.java diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/testing/LocalDnsHelper.java b/gcloud-java-dns/src/main/java/com/google/gcloud/testing/LocalDnsHelper.java new file mode 100644 index 000000000000..2d30cf9e5d90 --- /dev/null +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/testing/LocalDnsHelper.java @@ -0,0 +1,1426 @@ +/* + * 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.testing; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.net.HttpURLConnection.HTTP_NO_CONTENT; +import static java.net.HttpURLConnection.HTTP_OK; + +import com.google.api.client.json.JsonFactory; +import com.google.api.client.repackaged.com.google.common.base.Joiner; +import com.google.api.services.dns.model.Change; +import com.google.api.services.dns.model.ManagedZone; +import com.google.api.services.dns.model.Project; +import com.google.api.services.dns.model.Quota; +import com.google.api.services.dns.model.ResourceRecordSet; +import com.google.common.base.Function; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.common.io.ByteStreams; +import com.google.gcloud.dns.DnsOptions; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +import org.joda.time.format.ISODateTimeFormat; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.Objects; +import java.util.Random; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.GZIPInputStream; + +import javax.annotation.Nullable; + +/** + * A utility to create local Google Cloud DNS mock. + * + *

The mock runs in a separate thread, listening for HTTP requests on the local machine at an + * ephemeral port. + * + *

While the mock attempts to simulate the service, there are some differences in the behaviour. + * The mock will accept any project ID and never returns a notFound or another error because of + * project ID. It assumes that all project IDs exists and that the user has all the necessary + * privileges to manipulate any project. Similarly, the local simulation does not work with any + * verification of domain name ownership. Any request for creating a managed zone will be approved. + * The mock does not track quota and will allow the user to exceed it. The mock provides only basic + * validation of the DNS data for records of type A and AAAA. It does not validate any other record + * types. + */ +public class LocalDnsHelper { + + private final ConcurrentSkipListMap projects + = new ConcurrentSkipListMap<>(); + private static final URI BASE_CONTEXT; + private static final Logger log = Logger.getLogger(LocalDnsHelper.class.getName()); + private static final JsonFactory jsonFactory = + new com.google.api.client.json.jackson.JacksonFactory(); + private static final Random ID_GENERATOR = new Random(); + private static final String VERSION = "v1"; + private static final String CONTEXT = "/" + VERSION + "/projects"; + private static final Set SUPPORTED_COMPRESSION_ENCODINGS = + ImmutableSet.of("gzip", "x-gzip"); + private static final List TYPES = ImmutableList.of("A", "AAAA", "CNAME", "MX", "NAPTR", + "NS", "PTR", "SOA", "SPF", "SRV", "TXT"); + + static { + try { + BASE_CONTEXT = new URI(CONTEXT); + } catch (URISyntaxException e) { + throw new RuntimeException( + "Could not initialize LocalDnsHelper due to URISyntaxException.", e); + } + } + + private long delayChange; + private final HttpServer server; + private final int port; + + /** + * For matching URLs to operations. + */ + private enum CallRegex { + CHANGE_CREATE("POST", "/[^/]+/managedZones/[^/]+/changes"), + CHANGE_GET("GET", "/[^/]+/managedZones/[^/]+/changes/[^/]+"), + CHANGE_LIST("GET", "/[^/]+/managedZones/[^/]+/changes"), + ZONE_CREATE("POST", "/[^/]+/managedZones"), + ZONE_DELETE("DELETE", "/[^/]+/managedZones/[^/]+"), + ZONE_GET("GET", "/[^/]+/managedZones/[^/]+"), + ZONE_LIST("GET", "/[^/]+/managedZones"), + PROJECT_GET("GET", "/[^/]+"), + RECORD_LIST("GET", "/[^/]+/managedZones/[^/]+/rrsets"); + + private String method; + private String pathRegex; + + CallRegex(String method, String pathRegex) { + this.pathRegex = pathRegex; + this.method = method; + } + } + + /** + * Wraps DNS data by adding a timestamp and id which is used for paging and listing. + */ + static class RrsetWrapper { + static final Function WRAP_FUNCTION = + new Function() { + @Nullable + @Override + public RrsetWrapper apply(@Nullable ResourceRecordSet input) { + return new RrsetWrapper(input); + } + }; + private final ResourceRecordSet rrset; + private final Long timestamp = System.currentTimeMillis(); + private String id; + + RrsetWrapper(ResourceRecordSet rrset) { + // The constructor creates a copy in order to prevent side effects. + this.rrset = new ResourceRecordSet(); + this.rrset.setName(rrset.getName()); + this.rrset.setTtl(rrset.getTtl()); + this.rrset.setRrdatas(ImmutableList.copyOf(rrset.getRrdatas())); + this.rrset.setType(rrset.getType()); + } + + void setId(String id) { + this.id = id; + } + + String id() { + return id; + } + + /** + * Equals does not care about the listing id and timestamp metadata, just the rrset. + */ + @Override + public boolean equals(Object other) { + return (other instanceof RrsetWrapper) && Objects.equals(rrset, ((RrsetWrapper) other).rrset); + } + + @Override + public int hashCode() { + return Objects.hash(rrset); + } + + ResourceRecordSet rrset() { + return rrset; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("rrset", rrset) + .add("timestamp", timestamp) + .add("id", id) + .toString(); + } + } + + /** + * Associates a project with a collection of ManagedZones. Thread safe. + */ + static class ProjectContainer { + private final Project project; + private final ConcurrentSkipListMap zones = + new ConcurrentSkipListMap<>(); + + ProjectContainer(Project project) { + this.project = project; + } + + Project project() { + return project; + } + + ConcurrentSkipListMap zones() { + return zones; + } + } + + /** + * Associates a zone with a collection of changes and dns record. Thread safe. + */ + static class ZoneContainer { + private final ManagedZone zone; + /** + * DNS records are held in a map to allow for atomic replacement of record sets when applying + * changes. The key for the map is always the zone name. The collection of records is immutable + * and must always exist, i.e., dnsRecords.get(zone.getName()) is never null. + */ + private final ConcurrentSkipListMap> + dnsRecords = new ConcurrentSkipListMap<>(); + private final ConcurrentLinkedQueue changes = new ConcurrentLinkedQueue<>(); + + ZoneContainer(ManagedZone zone) { + this.zone = zone; + this.dnsRecords.put(zone.getName(), ImmutableList.of()); + } + + ManagedZone zone() { + return zone; + } + + ConcurrentSkipListMap> dnsRecords() { + return dnsRecords; + } + + ConcurrentLinkedQueue changes() { + return changes; + } + + Change findChange(String changeId) { + for (Change current : changes) { + if (changeId.equals(current.getId())) { + return current; + } + } + return null; + } + } + + static class Response { + private final int code; + private final String body; + + Response(int code, String body) { + this.code = code; + this.body = body; + } + + int code() { + return code; + } + + String body() { + return body; + } + } + + private enum Error { + REQUIRED(400, "global", "required", "REQUIRED"), + INTERNAL_ERROR(500, "global", "internalError", "INTERNAL_ERROR"), + BAD_REQUEST(400, "global", "badRequest", "BAD_REQUEST"), + INVALID(400, "global", "invalid", "INVALID"), + NOT_AVAILABLE(400, "global", "managedZoneDnsNameNotAvailable", "NOT_AVAILABLE"), + NOT_FOUND(404, "global", "notFound", "NOT_FOUND"), + ALREADY_EXISTS(409, "global", "alreadyExists", "ALREADY_EXISTS"), + CONDITION_NOT_MET(412, "global", "conditionNotMet", "CONDITION_NOT_MET"), + INVALID_ZONE_APEX(400, "global", "invalidZoneApex", "INVALID_ZONE_APEX"); + + private final int code; + private final String domain; + private final String reason; + private final String status; + + Error(int code, String domain, String reason, String status) { + this.code = code; + this.domain = domain; + this.reason = reason; + this.status = status; + } + + Response response(String message) { + try { + return new Response(code, toJson(message)); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response("Error when generating JSON error response."); + } + } + + private String toJson(String message) throws IOException { + Map errors = new HashMap<>(); + errors.put("domain", domain); + errors.put("message", message); + errors.put("reason", reason); + Map args = new HashMap<>(); + args.put("errors", ImmutableList.of(errors)); + args.put("code", code); + args.put("message", message); + args.put("status", status); + return jsonFactory.toString(ImmutableMap.of("error", args)); + } + } + + private class RequestHandler implements HttpHandler { + + /** + * Chooses the proper handler for a request. + */ + private Response pickHandler(HttpExchange exchange, CallRegex regex) { + switch (regex) { + case CHANGE_GET: + return handleChangeGet(exchange); + case CHANGE_LIST: + return handleChangeList(exchange); + case ZONE_GET: + return handleZoneGet(exchange); + case ZONE_DELETE: + return handleZoneDelete(exchange); + case ZONE_LIST: + return handleZoneList(exchange); + case PROJECT_GET: + return handleProjectGet(exchange); + case RECORD_LIST: + return handleDnsRecordList(exchange); + case ZONE_CREATE: + try { + return handleZoneCreate(exchange); + } catch (IOException ex) { + return Error.BAD_REQUEST.response(ex.getMessage()); + } + case CHANGE_CREATE: + try { + return handleChangeCreate(exchange); + } catch (IOException ex) { + return Error.BAD_REQUEST.response(ex.getMessage()); + } + default: + return Error.INTERNAL_ERROR.response("Operation without a handler."); + } + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + String requestMethod = exchange.getRequestMethod(); + String rawPath = exchange.getRequestURI().getRawPath(); + for (CallRegex regex : CallRegex.values()) { + if (requestMethod.equals(regex.method) && rawPath.matches(regex.pathRegex)) { + // there is a match, pass the handling accordingly + Response response = pickHandler(exchange, regex); + writeResponse(exchange, response); + return; // only one match is possible + } + } + // could not be matched, the service returns 404 page not found here + writeResponse(exchange, Error.NOT_FOUND.response("The url does not match any API call.")); + } + + // todo(mderka) Test handlers using gcloud-java-dns. Issue #665. + private Response handleZoneDelete(HttpExchange exchange) { + String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); + String[] tokens = path.split("/"); + String projectId = tokens[1]; + String zoneName = tokens[3]; + return deleteZone(projectId, zoneName); + } + + private Response handleZoneGet(HttpExchange exchange) { + String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); + String[] tokens = path.split("/"); + String projectId = tokens[1]; + String zoneName = tokens[3]; + String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); + String[] fields = OptionParsersAndExtractors.parseGetOptions(query); + return getZone(projectId, zoneName, fields); + } + + private Response handleZoneList(HttpExchange exchange) { + String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); + String[] tokens = path.split("/"); + String projectId = tokens[1]; + String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); + Map options = OptionParsersAndExtractors.parseListZonesOptions(query); + return listZones(projectId, options); + } + + private Response handleProjectGet(HttpExchange exchange) { + String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); + String[] tokens = path.split("/"); + String projectId = tokens[1]; + String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); + String[] fields = OptionParsersAndExtractors.parseGetOptions(query); + return getProject(projectId, fields); + } + + /** + * @throws IOException if the request cannot be parsed. + */ + private Response handleChangeCreate(HttpExchange exchange) throws IOException { + String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); + String[] tokens = path.split("/"); + String projectId = tokens[1]; + String zoneName = tokens[3]; + String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); + String[] fields = OptionParsersAndExtractors.parseGetOptions(query); + String requestBody = decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); + Change change = jsonFactory.fromString(requestBody, Change.class); + return createChange(projectId, zoneName, change, fields); + } + + private Response handleChangeGet(HttpExchange exchange) { + String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); + String[] tokens = path.split("/"); + String projectId = tokens[1]; + String zoneName = tokens[3]; + String changeId = tokens[5]; + String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); + String[] fields = OptionParsersAndExtractors.parseGetOptions(query); + return getChange(projectId, zoneName, changeId, fields); + } + + private Response handleChangeList(HttpExchange exchange) { + String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); + String[] tokens = path.split("/"); + String projectId = tokens[1]; + String zoneName = tokens[3]; + String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); + Map options = OptionParsersAndExtractors.parseListChangesOptions(query); + return listChanges(projectId, zoneName, options); + } + + private Response handleDnsRecordList(HttpExchange exchange) { + String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); + String[] tokens = path.split("/"); + String projectId = tokens[1]; + String zoneName = tokens[3]; + String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); + Map options = OptionParsersAndExtractors.parseListDnsRecordsOptions(query); + return listDnsRecords(projectId, zoneName, options); + } + + /** + * @throws IOException if the request cannot be parsed. + */ + private Response handleZoneCreate(HttpExchange exchange) throws IOException { + String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); + String[] tokens = path.split("/"); + String projectId = tokens[1]; + String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); + String[] options = OptionParsersAndExtractors.parseGetOptions(query); + String requestBody = decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); + ManagedZone zone; + try { + // IllegalArgumentException if the request body is an empty string + zone = jsonFactory.fromString(requestBody, ManagedZone.class); + } catch (IllegalArgumentException ex) { + return Error.REQUIRED.response( + "The 'entity.managedZone' parameter is required but was missing."); + } + return createZone(projectId, zone, options); + } + } + + private LocalDnsHelper(long delay) { + this.delayChange = delay; // 0 makes this synchronous + try { + server = HttpServer.create(new InetSocketAddress(0), 0); + port = server.getAddress().getPort(); + server.createContext(CONTEXT, new RequestHandler()); + } catch (IOException e) { + throw new RuntimeException("Could not bind the mock DNS server.", e); + } + } + + /** + * Accessor for testing purposes. + */ + ConcurrentSkipListMap projects() { + return projects; + } + + /** + * Creates new {@link LocalDnsHelper} instance that listens to requests on the local machine. This + * instance processes changes separate threads. The parameter determines how long a thread should + * wait before processing a change. If it is set to 0, the threading is turned off and the mock + * will behave synchronously. + * + * @param delay delay for processing changes in ms or 0 for synchronous processing + */ + public static LocalDnsHelper create(Long delay) { + return new LocalDnsHelper(delay); + } + + /** + * Returns a DnsOptions instance that sets the host to use the mock server. + */ + public DnsOptions options() { + return DnsOptions.builder().host("http://localhost:" + port).build(); + } + + /** + * Starts the thread that runs the local DNS server. + */ + public void start() { + server.start(); + } + + /** + * Stops the thread that runs the mock DNS server. + */ + public void stop() { + server.stop(1); + } + + private static void writeResponse(HttpExchange exchange, Response response) { + exchange.getResponseHeaders().set("Content-type", "application/json; charset=UTF-8"); + OutputStream outputStream = exchange.getResponseBody(); + try { + exchange.getResponseHeaders().add("Connection", "close"); + exchange.sendResponseHeaders(response.code(), response.body().length()); + outputStream.write(response.body().getBytes(StandardCharsets.UTF_8)); + outputStream.close(); + } catch (IOException e) { + log.log(Level.WARNING, "IOException encountered when sending response.", e); + } + } + + /** + * Decodes content of the HttpRequest. + */ + private static String decodeContent(Headers headers, InputStream inputStream) throws IOException { + List contentEncoding = headers.get("Content-encoding"); + InputStream input = inputStream; + try { + if (contentEncoding != null && !contentEncoding.isEmpty()) { + String encoding = contentEncoding.get(0); + if (SUPPORTED_COMPRESSION_ENCODINGS.contains(encoding)) { + input = new GZIPInputStream(inputStream); + } else if (!encoding.equals("identity")) { + throw new IOException( + "The request has the following unsupported HTTP content encoding: " + encoding); + } + } + return new String(ByteStreams.toByteArray(input), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new IOException("Exception encountered when decoding request content.", e); + } + } + + /** + * Generates a JSON response. Tested. + */ + static Response toListResponse(List serializedObjects, String pageToken, + boolean includePageToken) { + // start building response + StringBuilder responseBody = new StringBuilder(); + responseBody.append("{\"projects\": ["); + Joiner.on(",").appendTo(responseBody, serializedObjects); + responseBody.append("]"); + // add page token only if exists and is asked for + if (pageToken != null && includePageToken) { + responseBody.append(",\"pageToken\": \"").append(pageToken).append("\""); + } + responseBody.append("}"); + return new Response(HTTP_OK, responseBody.toString()); + } + + /** + * Prepares DNS records that are created by default for each zone. + */ + private static ImmutableList defaultRecords(ManagedZone zone) { + ResourceRecordSet soa = new ResourceRecordSet(); + soa.setTtl(21600); + soa.setName(zone.getDnsName()); + soa.setRrdatas(ImmutableList.of( + // taken from the service + "ns-cloud-c1.googledomains.com. cloud-dns-hostmaster.google.com. 0 21600 3600 1209600 312" + )); + soa.setType("SOA"); + ResourceRecordSet ns = new ResourceRecordSet(); + ns.setTtl(21600); + ns.setName(zone.getDnsName()); + ns.setRrdatas(zone.getNameServers()); + ns.setType("NS"); + RrsetWrapper nsWrapper = new RrsetWrapper(ns); + RrsetWrapper soaWrapper = new RrsetWrapper(soa); + ImmutableList results = ImmutableList.of(nsWrapper, soaWrapper); + nsWrapper.setId(getUniqueId(results)); + soaWrapper.setId(getUniqueId(results)); + return results; + } + + /** + * Returns a list of four nameservers randomly chosen from the predefined set. + */ + static List randomNameservers() { + ArrayList nameservers = Lists.newArrayList( + "dns1.googlecloud.com", "dns2.googlecloud.com", "dns3.googlecloud.com", + "dns4.googlecloud.com", "dns5.googlecloud.com", "dns6.googlecloud.com" + ); + while (nameservers.size() != 4) { + int index = ID_GENERATOR.nextInt(nameservers.size()); + nameservers.remove(index); + } + return nameservers; + } + + /** + * Returns a hex string id (used for a dns record) unique withing the set of wrappers. + */ + static String getUniqueId(List wrappers) { + TreeSet ids = Sets.newTreeSet(Lists.transform(wrappers, + new Function() { + @Nullable + @Override + public String apply(@Nullable RrsetWrapper input) { + return input.id() == null ? "null" : input.id(); + } + })); + String id; + do { + id = Long.toHexString(System.currentTimeMillis()) + + Long.toHexString(Math.abs(ID_GENERATOR.nextLong())); + if (!ids.contains(id)) { + return id; + } + } while (ids.contains(id)); + return id; + } + + /** + * Tests if a DNS record matches name and type (if provided). Used for filtering. + */ + static boolean matchesCriteria(ResourceRecordSet record, String name, String type) { + if (type != null && !record.getType().equals(type)) { + return false; + } + if (name != null && !record.getName().equals(name)) { + return false; + } + return true; + } + + /** + * Returns a project container. Never returns null because we assume that all projects exists. + */ + ProjectContainer findProject(String projectId) { + ProjectContainer defaultProject = createProject(projectId); + projects.putIfAbsent(projectId, defaultProject); + return projects.get(projectId); + } + + /** + * Returns a zone container. Returns null if zone does not exist within project. + */ + ZoneContainer findZone(String projectId, String zoneName) { + ProjectContainer projectContainer = findProject(projectId); // never null + return projectContainer.zones().get(zoneName); + } + + /** + * Returns a change found by its id. Returns null if such a change does not exist. + */ + Change findChange(String projectId, String zoneName, String changeId) { + ZoneContainer wrapper = findZone(projectId, zoneName); + return wrapper == null ? null : wrapper.findChange(changeId); + } + + /** + * Returns a response to get change call. + */ + Response getChange(String projectId, String zoneName, String changeId, String[] fields) { + Change change = findChange(projectId, zoneName, changeId); + if (change == null) { + ZoneContainer zone = findZone(projectId, zoneName); + if (zone == null) { + // zone does not exist + return Error.NOT_FOUND.response(String.format( + "The 'parameters.managedZone' resource named '%s' does not exist.", zoneName)); + } + // zone exists but change does not + return Error.NOT_FOUND.response(String.format( + "The 'parameters.changeId' resource named '%s' does not exist.", changeId)); + } + Change result = OptionParsersAndExtractors.extractFields(change, fields); + try { + return new Response(HTTP_OK, jsonFactory.toString(result)); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response(String.format( + "Error when serializing change %s in managed zone %s in project %s.", + changeId, zoneName, projectId)); + } + // todo(mderka) Test field options within #665. + } + + /** + * Returns a response to get zone call. + */ + Response getZone(String projectId, String zoneName, String[] fields) { + ZoneContainer container = findZone(projectId, zoneName); + if (container == null) { + return Error.NOT_FOUND.response(String.format( + "The 'parameters.managedZone' resource named '%s' does not exist.", zoneName)); + } + ManagedZone result = OptionParsersAndExtractors.extractFields(container.zone(), fields); + try { + return new Response(HTTP_OK, jsonFactory.toString(result)); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response(String.format( + "Error when serializing managed zone %s in project %s.", zoneName, projectId)); + } + // todo(mderka) Test field options within #665. + } + + /** + * We assume that every project exists. If we do not have it in the collection yet, we just create + * a new default project instance with default quota. + */ + Response getProject(String projectId, String[] fields) { + ProjectContainer defaultProject = createProject(projectId); + projects.putIfAbsent(projectId, defaultProject); + Project project = projects.get(projectId).project(); // project is now guaranteed to exist + Project result = OptionParsersAndExtractors.extractFields(project, fields); + try { + return new Response(HTTP_OK, jsonFactory.toString(result)); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response( + String.format("Error when serializing project %s.", projectId)); + } + // todo(mderka) Test field options within #665. + } + + /** + * Creates a project. It generates a project number randomly (we do not have project numbers + * available). + */ + private ProjectContainer createProject(String projectId) { + Quota quota = new Quota(); + quota.setManagedZones(10000); + quota.setRrsetsPerManagedZone(10000); + quota.setRrsetAdditionsPerChange(100); + quota.setRrsetDeletionsPerChange(100); + quota.setTotalRrdataSizePerChange(10000); + quota.setResourceRecordsPerRrset(100); + Project project = new Project(); + project.setId(projectId); + project.setNumber(new BigInteger(String.valueOf( + Math.abs(ID_GENERATOR.nextLong() % Long.MAX_VALUE)))); + project.setQuota(quota); + return new ProjectContainer(project); + } + + /** + * Deletes a zone. + */ + Response deleteZone(String projectId, String zoneName) { + ProjectContainer projectContainer = projects.get(projectId); + ZoneContainer previous = projectContainer.zones.remove(zoneName); + return previous == null + // map was not in the collection + ? Error.NOT_FOUND.response(String.format( + "The 'parameters.managedZone' resource named '%s' does not exist.", zoneName)) + : new Response(HTTP_NO_CONTENT, "{}"); + } + + /** + * Creates new managed zone and stores it in the collection. Assumes that project exists. + */ + Response createZone(String projectId, ManagedZone zone, String[] fields) { + checkNotNull(zone, "Zone to create cannot be null"); + // check if the provided data is valid + Response errorResponse = checkZone(zone); + if (errorResponse != null) { + return errorResponse; + } + // create a copy of the managed zone in order to avoid side effects + ManagedZone completeZone = new ManagedZone(); + completeZone.setName(zone.getName()); + completeZone.setDnsName(zone.getDnsName()); + completeZone.setNameServerSet(zone.getNameServerSet()); + completeZone.setCreationTime(ISODateTimeFormat.dateTime().withZoneUTC() + .print(System.currentTimeMillis())); + completeZone.setId( + new BigInteger(String.valueOf(Math.abs(ID_GENERATOR.nextLong() % Long.MAX_VALUE)))); + completeZone.setNameServers(randomNameservers()); + ZoneContainer zoneContainer = new ZoneContainer(completeZone); + // create the default NS and SOA records + zoneContainer.dnsRecords().put(zone.getName(), defaultRecords(completeZone)); + // place the zone in the data collection + ProjectContainer projectContainer = findProject(projectId); + ZoneContainer oldValue = projectContainer.zones().putIfAbsent( + completeZone.getName(), zoneContainer); + if (oldValue != null) { + return Error.ALREADY_EXISTS.response(String.format( + "The resource 'entity.managedZone' named '%s' already exists", completeZone.getName())); + } + // now return the desired attributes + ManagedZone result = OptionParsersAndExtractors.extractFields(completeZone, fields); + try { + return new Response(HTTP_OK, jsonFactory.toString(result)); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response( + String.format("Error when serializing managed zone %s.", result.getName())); + } + // todo(mderka) Test field options within #665. + } + + /** + * Creates a new change, stores it, and invokes processing in a new thread. + */ + Response createChange(String projectId, String zoneName, Change change, String[] fields) { + ZoneContainer zoneContainer = findZone(projectId, zoneName); + if (zoneContainer == null) { + return Error.NOT_FOUND.response(String.format( + "The 'parameters.managedZone' resource named %s does not exist.", zoneName)); + } + // check that the change to be applied is valid + Response response = checkChange(change, zoneContainer); + if (response != null) { + return response; + } + // start applying + Change completeChange = new Change(); // copy to avoid side effects + if (change.getAdditions() != null) { + completeChange.setAdditions(ImmutableList.copyOf(change.getAdditions())); + } + if (change.getDeletions() != null) { + completeChange.setDeletions(ImmutableList.copyOf(change.getDeletions())); + } + /* we need to get the proper ID in concurrent environment + the element fell on an index between 0 and maxId + we will reset all IDs in this range (all of them are valid) */ + ConcurrentLinkedQueue changeSequence = zoneContainer.changes(); + changeSequence.add(completeChange); + int maxId = changeSequence.size(); + int index = 0; + for (Change c : changeSequence) { + if (index == maxId) { + break; + } + c.setId(String.valueOf(++index)); // indexing from 1 + } + completeChange.setStatus("pending"); // not finished yet + completeChange.setStartTime(ISODateTimeFormat.dateTime().withZoneUTC() + .print(System.currentTimeMillis())); // accepted + invokeChange(projectId, zoneName, completeChange.getId()); + Change result = OptionParsersAndExtractors.extractFields(completeChange, fields); + try { + return new Response(HTTP_OK, jsonFactory.toString(result)); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response( + String.format("Error when serializing change %s in managed zone %s in project %s.", + result.getId(), zoneName, projectId)); + } + // todo(mderka) Test field options within #665. + } + + /** + * Applies change. Uses a new thread which applies the change only if DELAY_CHANGE is > 0. + */ + private Thread invokeChange(final String projectId, final String zoneName, + final String changeId) { + if (delayChange > 0) { + Thread thread = new Thread() { + @Override + public void run() { + try { + Thread.sleep(delayChange); // simulate delayed execution + } catch (InterruptedException ex) { + log.log(Level.WARNING, "Thread was interrupted while sleeping.", ex); + } + // start applying the changes + applyExistingChange(projectId, zoneName, changeId); + } + }; + thread.start(); + return thread; + } else { + applyExistingChange(projectId, zoneName, changeId); + return null; + } + } + + /** + * Applies changes to a zone. Repeatedly tries until succeeds. Thread safe and deadlock safe. + */ + private void applyExistingChange(String projectId, String zoneName, String changeId) { + Change change = findChange(projectId, zoneName, changeId); + if (change == null) { + return; // no such change exists, nothing to do + } + ZoneContainer wrapper = findZone(projectId, zoneName); + ConcurrentSkipListMap> dnsRecords = wrapper.dnsRecords(); + while (true) { + // managed zone must have a set of records which is not null + ImmutableList original = dnsRecords.get(zoneName); + assert original != null; + List copy = Lists.newLinkedList(original); + // apply deletions first + List deletions = change.getDeletions(); + if (deletions != null) { + List transformedDeletions = Lists.transform(deletions, + RrsetWrapper.WRAP_FUNCTION); + copy.removeAll(transformedDeletions); + } + // apply additions + List additions = change.getAdditions(); + if (additions != null) { + for (ResourceRecordSet addition : additions) { + String id = getUniqueId(copy); + RrsetWrapper rrsetWrapper = new RrsetWrapper(addition); + rrsetWrapper.setId(id); + copy.add(rrsetWrapper); + } + } + // make it immutable and replace + boolean success = dnsRecords.replace(zoneName, original, ImmutableList.copyOf(copy)); + if (success) { + break; // success if no other thread modified the value in the meantime + } + } + // set status to done + change.setStatus("done"); + } + + /** + * Lists zones. Next page token is the last listed zone name and is returned only of there is more + * to list. + */ + Response listZones(String projectId, Map options) { + Response response = checkListOptions(options); + if (response != null) { + return response; + } + ConcurrentSkipListMap containers = findProject(projectId).zones(); + String[] fields = (String[]) options.get("fields"); + String dnsName = (String) options.get("dnsName"); + String pageToken = (String) options.get("pageToken"); + Integer maxResults = options.get("maxResults") == null + ? null : Integer.valueOf((String) options.get("maxResults")); + // matches will be included in the result if true + boolean listing = (pageToken == null || !containers.containsKey(pageToken)); + boolean sizeReached = false; // maximum result size was reached, we should not return more + boolean hasMorePages = false; // should next page token be included in the response? + LinkedList serializedZones = new LinkedList<>(); + String lastZoneName = null; + for (ZoneContainer zoneContainer : containers.values()) { + ManagedZone zone = zoneContainer.zone(); + if (listing) { + if (dnsName == null || zone.getDnsName().equals(dnsName)) { + lastZoneName = zone.getName(); + if (sizeReached) { + // we do not add this, just note that there would be more and there should be a token + hasMorePages = true; + break; + } else { + try { + serializedZones.addLast(jsonFactory.toString( + OptionParsersAndExtractors.extractFields(zone, fields))); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response(String.format( + "Error when serializing managed zone %s in project %s", zone.getName(), + projectId)); + } + } + } + } + // either we are listing already, or we check if we should start in the next iteration + listing = zone.getName().equals(pageToken) || listing; + sizeReached = (maxResults != null) && maxResults.equals(serializedZones.size()); + } + boolean includePageToken = + hasMorePages && (fields == null || ImmutableList.copyOf(fields).contains("pageToken")); + return toListResponse(serializedZones, lastZoneName, includePageToken); + // todo(mderka) Test field and listing options within #665. + } + + /** + * Lists DNS records for a zone. Next page token is ID of the last record listed. + */ + Response listDnsRecords(String projectId, String zoneName, Map options) { + Response response = checkListOptions(options); + if (response != null) { + return response; + } + ZoneContainer zoneContainer = findZone(projectId, zoneName); + if (zoneContainer == null) { + return Error.NOT_FOUND.response(String.format( + "The 'parameters.managedZone' resource named '%s' does not exist.", zoneName)); + } + List dnsRecords = zoneContainer.dnsRecords().get(zoneName); + String[] fields = (String[]) options.get("fields"); + String name = (String) options.get("name"); + String type = (String) options.get("type"); + String pageToken = (String) options.get("pageToken"); + Integer maxResults = options.get("maxResults") == null + ? null : Integer.valueOf((String) options.get("maxResults")); + boolean listing = (pageToken == null); // matches will be included in the result if true + boolean sizeReached = false; // maximum result size was reached, we should not return more + boolean hasMorePages = false; // should next page token be included in the response? + LinkedList serializedRrsets = new LinkedList<>(); + String lastRecordId = null; + for (RrsetWrapper recordWrapper : dnsRecords) { + ResourceRecordSet record = recordWrapper.rrset(); + if (listing) { + if (matchesCriteria(record, name, type)) { + lastRecordId = recordWrapper.id(); + if (sizeReached) { + // we do not add this, just note that there would be more and there should be a token + hasMorePages = true; + break; + } else { + try { + serializedRrsets.addLast(jsonFactory.toString( + OptionParsersAndExtractors.extractFields(record, fields))); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response(String.format( + "Error when serializing resource record set in managed zone %s in project %s", + zoneName, projectId)); + } + } + } + } + // either we are listing already, or we check if we should start in the next iteration + listing = recordWrapper.id().equals(pageToken) || listing; + sizeReached = (maxResults != null) && maxResults.equals(serializedRrsets.size()); + } + boolean includePageToken = + hasMorePages && (fields == null || ImmutableList.copyOf(fields).contains("pageToken")); + return toListResponse(serializedRrsets, lastRecordId, includePageToken); + // todo(mderka) Test field and listing options within #665. + } + + /** + * Lists changes. Next page token is ID of the last change listed. + */ + Response listChanges(String projectId, String zoneName, Map options) { + Response response = checkListOptions(options); + if (response != null) { + return response; + } + ZoneContainer zoneContainer = findZone(projectId, zoneName); + // take a sorted snapshot of the current change list + TreeMap changes = new TreeMap<>(); + for (Change c : zoneContainer.changes()) { + if (c.getId() != null) { + changes.put(Integer.valueOf(c.getId()), c); + } + } + String[] fields = (String[]) options.get("fields"); + String sortOrder = (String) options.get("sortOrder"); + String pageToken = (String) options.get("pageToken"); + Integer maxResults = options.get("maxResults") == null + ? null : Integer.valueOf((String) options.get("maxResults")); + // we are not reading sort by as it the only key is the change sequence + NavigableSet keys; + if ("descending".equals(sortOrder)) { + keys = changes.descendingKeySet(); + } else { + keys = changes.navigableKeySet(); + } + boolean listing = (pageToken == null); // matches will be included in the result if true + boolean sizeReached = false; // maximum result size was reached, we should not return more + boolean hasMorePages = false; // should next page token be included in the response? + LinkedList serializedResults = new LinkedList<>(); + String lastChangeId = null; + for (Integer key : keys) { + Change change = changes.get(key); + if (listing) { + lastChangeId = change.getId(); + if (sizeReached) { + // we do not add this, just note that there would be more and there should be a token + hasMorePages = true; + break; + } else { + try { + serializedResults.addLast(jsonFactory.toString( + OptionParsersAndExtractors.extractFields(change, fields))); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response(String.format( + "Error when serializing change %s in managed zone %s in project %s", + change.getId(), zoneName, projectId)); + } + } + } + + // either we are listing already, or we check if we should start in the next iteration + listing = change.getId().equals(pageToken) || listing; + sizeReached = (maxResults != null) && maxResults.equals(serializedResults.size()); + } + boolean includePageToken = + hasMorePages && (fields == null || ImmutableList.copyOf(fields).contains("pageToken")); + return toListResponse(serializedResults, lastChangeId, includePageToken); + // todo(mderka) Test field and listing options within #665. + } + + /** + * Validates a zone to be created. + */ + static Response checkZone(ManagedZone zone) { + if (zone.getName() == null) { + return Error.REQUIRED.response( + "The 'entity.managedZone.name' parameter is required but was missing."); + } + if (zone.getDnsName() == null) { + return Error.REQUIRED.response( + "The 'entity.managedZone.dnsName' parameter is required but was missing."); + } + if (zone.getDescription() == null) { + return Error.REQUIRED.response( + "The 'entity.managedZone.description' parameter is required but was missing."); + } + try { + int number = Integer.valueOf(zone.getName()); + return Error.INVALID.response( + String.format("Invalid value for 'entity.managedZone.name': '%s'", number)); + } catch (NumberFormatException ex) { + // expected + } + if (zone.getName().isEmpty()) { + return Error.INVALID.response( + String.format("Invalid value for 'entity.managedZone.name': '%s'", zone.getName())); + } + if (zone.getDnsName().isEmpty() || !zone.getDnsName().endsWith(".")) { + return Error.INVALID.response( + String.format("Invalid value for 'entity.managedZone.dnsName': '%s'", zone.getDnsName())); + } + TreeSet forbidden = Sets.newTreeSet( + ImmutableList.of("google.com.", "com.", "example.com.", "net.", "org.")); + if (forbidden.contains(zone.getDnsName())) { + return Error.NOT_AVAILABLE.response(String.format( + "The '%s' managed zone is not available to be created.", zone.getDnsName())); + } + return null; + } + + /** + * Validates a change to be created. + */ + static Response checkChange(Change change, ZoneContainer zone) { + checkNotNull(zone); + if ((change.getDeletions() != null && change.getDeletions().size() > 0) + || (change.getAdditions() != null && change.getAdditions().size() > 0)) { + // ok, this is what we want + } else { + return Error.REQUIRED.response("The 'entity.change' parameter is required but was missing."); + } + if (change.getAdditions() != null) { + int counter = 0; + for (ResourceRecordSet addition : change.getAdditions()) { + Response response = checkRrset(addition, zone, counter, "additions"); + if (response != null) { + return response; + } + counter++; + } + } + if (change.getDeletions() != null) { + int counter = 0; + for (ResourceRecordSet deletion : change.getDeletions()) { + Response response = checkRrset(deletion, zone, counter, "deletions"); + if (response != null) { + return response; + } + counter++; + } + } + return additionsMeetDeletions(change.getAdditions(), change.getDeletions(), zone); + // null if everything is ok + } + + /** + * Checks a rrset within a change. + * + * @param type [additions|deletions] + * @param index the index or addition or deletion in the list + * @param zone the zone that this change is applied to + */ + static Response checkRrset(ResourceRecordSet rrset, ZoneContainer zone, int index, String type) { + if (rrset.getName() == null || !rrset.getName().endsWith(zone.zone().getDnsName())) { + return Error.INVALID.response(String.format( + "Invalid value for 'entity.change.%s[%s].name': '%s'", type, index, rrset.getName())); + } + if (rrset.getType() == null || !TYPES.contains(rrset.getType())) { + return Error.INVALID.response(String.format( + "Invalid value for 'entity.change.%s[%s].type': '%s'", type, index, rrset.getType())); + } + if (rrset.getTtl() != null && rrset.getTtl() != 0 && rrset.getTtl() < 0) { + return Error.INVALID.response(String.format( + "Invalid value for 'entity.change.%s[%s].ttl': '%s'", type, index, rrset.getTtl())); + } + if (rrset.getRrdatas() == null || rrset.isEmpty()) { + return Error.INVALID.response(String.format( + "Invalid value for 'entity.change.%s[%s].rrdata': '%s'", type, index, "")); + } + int counter = 0; + for (String record : rrset.getRrdatas()) { + if (!checkRrData(record, rrset.getType())) { + return Error.INVALID.response(String.format( + "Invalid value for 'entity.change.%s[%s].rrdata[%s]': '%s'", + type, index, counter, record)); + } + counter++; + } + if ("deletions".equals(type)) { + // check that deletion has a match by name and type + boolean found = false; + for (RrsetWrapper rrsetWrapper : zone.dnsRecords().get(zone.zone().getName())) { + ResourceRecordSet wrappedRrset = rrsetWrapper.rrset(); + if (rrset.getName().equals(wrappedRrset.getName()) + && rrset.getType().equals(wrappedRrset.getType())) { + found = true; + break; + } + } + if (!found) { + return Error.NOT_FOUND.response(String.format( + "The 'entity.change.deletions[%s]' resource named '%s (%s)' does not exist.", + index, rrset.getName(), rrset.getType())); + } + // if found, we still need an exact match + if ("deletions".equals(type) + && !zone.dnsRecords().get(zone.zone().getName()).contains(new RrsetWrapper(rrset))) { + // such a record does not exist + return Error.CONDITION_NOT_MET.response(String.format( + "Precondition not met for 'entity.change.deletions[%s]", index)); + } + } + return null; + } + + /** + * Checks that for each record that already exists, we have a matching deletion. Furthermore, + * check that mandatory SOA and NS records stay. + */ + static Response additionsMeetDeletions(List additions, + List deletions, ZoneContainer zone) { + if (additions != null) { + int index = 0; + for (ResourceRecordSet rrset : additions) { + for (RrsetWrapper wrapper : zone.dnsRecords().get(zone.zone().getName())) { + ResourceRecordSet wrappedRrset = wrapper.rrset(); + if (rrset.getName().equals(wrappedRrset.getName()) + && rrset.getType().equals(wrappedRrset.getType())) { + // such a record exist and we must have a deletion + if (deletions == null || !deletions.contains(wrappedRrset)) { + return Error.ALREADY_EXISTS.response(String.format( + "The 'entity.change.additions[%s]' resource named '%s (%s)' already exists.", + index, rrset.getName(), rrset.getType())); + } + } + } + if (rrset.getType().equals("SOA") && findByNameAndType(deletions, null, "SOA") == null) { + return Error.INVALID_ZONE_APEX.response(String.format("The resource record set 'entity" + + ".change.additions[%s]' is invalid because a zone must contain exactly one resource" + + " record set of type 'SOA' at the apex.", index)); + } + if (rrset.getType().equals("NS") && findByNameAndType(deletions, null, "NS") == null) { + return Error.INVALID_ZONE_APEX.response(String.format("The resource record set 'entity" + + ".change.additions[%s]' is invalid because a zone must contain exactly one resource" + + " record set of type 'NS' at the apex.", index)); + } + index++; + } + } + if (deletions != null) { + int index = 0; + for (ResourceRecordSet rrset : deletions) { + if (rrset.getType().equals("SOA") && findByNameAndType(additions, null, "SOA") == null) { + return Error.INVALID_ZONE_APEX.response(String.format("The resource record set 'entity" + + ".change.deletions[%s]' is invalid because a zone must contain exactly one resource" + + " record set of type 'SOA' at the apex.", index)); + } + if (rrset.getType().equals("NS") && findByNameAndType(additions, null, "NS") == null) { + return Error.INVALID_ZONE_APEX.response(String.format("The resource record set 'entity" + + ".change.deletions[%s]' is invalid because a zone must contain exactly one resource" + + " record set of type 'NS' at the apex.", index)); + } + index++; + } + } + return null; + } + + /** + * Helper for searching rrsets in a collection. + */ + private static ResourceRecordSet findByNameAndType(Iterable records, + String name, String type) { + if (records != null) { + for (ResourceRecordSet rrset : records) { + if ((name == null || name.equals(rrset.getName())) + && (type == null || type.equals(rrset.getType()))) { + return rrset; + } + } + } + return null; + } + + /** + * We only provide the most basic validation for A and AAAA records. + */ + static boolean checkRrData(String data, String type) { + // todo add validation for other records + String[] tokens; + switch (type) { + case "A": + tokens = data.split("\\."); + if (tokens.length != 4) { + return false; + } + for (String token : tokens) { + try { + Integer number = Integer.valueOf(token); + if (number < 0 || number > 255) { + return false; + } + } catch (NumberFormatException ex) { + return false; + } + } + return true; + case "AAAA": + tokens = data.split(":", -1); + if (tokens.length != 8) { + return false; + } + for (String token : tokens) { + try { + if (!token.isEmpty()) { + // empty is ok + Long number = Long.parseLong(token, 16); + if (number < 0 || number > 0xFFFF) { + return false; + } + } + } catch (NumberFormatException ex) { + return false; + } + } + return true; + default: + return true; + } + } + + /** + * Check supplied listing options. + */ + static Response checkListOptions(Map options) { + // for general listing + String maxResultsString = (String) options.get("maxResults"); + if (maxResultsString != null) { + Integer maxResults = null; + try { + maxResults = Integer.valueOf(maxResultsString); + } catch (NumberFormatException ex) { + return Error.INVALID.response(String.format( + "Invalid integer value': '%s'.", maxResultsString)); + } + if (maxResults <= 0) { + return Error.INVALID.response(String.format( + "Invalid value for 'parameters.maxResults': '%s'", maxResults)); + } + } + String dnsName = (String) options.get("dnsName"); + if (dnsName != null) { + if (!dnsName.endsWith(".")) { + return Error.INVALID.response(String.format( + "Invalid value for 'parameters.dnsName': '%s'", dnsName)); + } + } + // for listing dns records, name must be fully qualified + String name = (String) options.get("name"); + if (name != null) { + if (!name.endsWith(".")) { + return Error.INVALID.response(String.format( + "Invalid value for 'parameters.name': '%s'", name)); + } + } + String type = (String) options.get("type"); // must be provided with name + if (type != null) { + if (name == null) { + return Error.INVALID.response("Invalid value for 'parameters.name': ''"); + } + if (!TYPES.contains(type)) { + return Error.INVALID.response(String.format( + "Invalid value for 'parameters.type': '%s'", type)); + } + } + // for listing changes + String order = (String) options.get("sortOrder"); + if (order != null && !"ascending".equals(order) && !"descending".equals(order)) { + return Error.INVALID.response(String.format( + "Invalid value for 'parameters.sortOrder': '%s'", order)); + } + String sortBy = (String) options.get("sortBy"); // case insensitive + if (sortBy != null && !"changesequence".equals(sortBy.toLowerCase())) { + return Error.INVALID.response(String.format( + "Invalid string value: '%s'. Allowed values: [changesequence]", sortBy)); + } + return null; + } +} diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/testing/OptionParsersAndExtractors.java b/gcloud-java-dns/src/main/java/com/google/gcloud/testing/OptionParsersAndExtractors.java new file mode 100644 index 000000000000..f94cb15779ca --- /dev/null +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/testing/OptionParsersAndExtractors.java @@ -0,0 +1,279 @@ +/* + * 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.testing; + +import com.google.api.services.dns.model.Change; +import com.google.api.services.dns.model.ManagedZone; +import com.google.api.services.dns.model.Project; +import com.google.api.services.dns.model.ResourceRecordSet; + +import java.util.HashMap; +import java.util.Map; + +/** + * Utility helpers for LocalDnsHelper. + */ +class OptionParsersAndExtractors { + + /** + * Makes a map of list options. Expects query to be only query part of the url (i.e., what follows + * the '?'). + */ + static Map parseListZonesOptions(String query) { + Map options = new HashMap<>(); + if (query != null) { + String[] args = query.split("&"); + for (String arg : args) { + String[] argEntry = arg.split("="); + switch (argEntry[0]) { + case "fields": + // List fields are in the form "managedZones(field1, field2, ...)" + options.put( + "fields", + argEntry[1].substring("managedZones(".length(), argEntry[1].length() - 1) + .split(",")); + break; + case "dnsName": + options.put("dnsName", argEntry[1]); + break; + case "pageToken": + options.put("pageToken", argEntry[1]); + break; + case "maxResults": + // parsing to int is done while handling + options.put("maxResults", argEntry[1]); + break; + default: + break; + } + } + } + return options; + } + + /** + * Makes a map of list options. Expects query to be only query part of the url (i.e., what follows + * the '?'). This format is common for all of zone, change and project. + */ + static String[] parseGetOptions(String query) { + if (query != null) { + String[] args = query.split("&"); + for (String arg : args) { + String[] argEntry = arg.split("="); + if (argEntry[0].equals("fields")) { + // List fields are in the form "fields=field1, field2,..." + return argEntry[1].split(","); + } + } + } + return null; + } + + /** + * Extracts only request fields. + */ + static ManagedZone extractFields(ManagedZone fullZone, String[] fields) { + if (fields == null) { + return fullZone; + } + ManagedZone managedZone = new ManagedZone(); + for (String field : fields) { + switch (field) { + case "creationTime": + managedZone.setCreationTime(fullZone.getCreationTime()); + break; + case "description": + managedZone.setDescription(fullZone.getDescription()); + break; + case "dnsName": + managedZone.setDnsName(fullZone.getDnsName()); + break; + case "id": + managedZone.setId(fullZone.getId()); + break; + case "name": + managedZone.setName(fullZone.getName()); + break; + case "nameServerSet": + managedZone.setNameServerSet(fullZone.getNameServerSet()); + break; + case "nameServers": + managedZone.setNameServers(fullZone.getNameServers()); + break; + default: + break; + } + } + return managedZone; + } + + /** + * Extracts only request fields. + */ + static Change extractFields(Change fullChange, String[] fields) { + if (fields == null) { + return fullChange; + } + Change change = new Change(); + for (String field : fields) { + switch (field) { + case "additions": + // todo the fragmentation is ignored here as our api does not support it + change.setAdditions(fullChange.getAdditions()); + break; + case "deletions": + // todo the fragmentation is ignored here as our api does not support it + change.setDeletions(fullChange.getDeletions()); + break; + case "id": + change.setId(fullChange.getId()); + break; + case "startTime": + change.setStartTime(fullChange.getStartTime()); + break; + case "status": + change.setStatus(fullChange.getStatus()); + break; + default: + break; + } + } + return change; + } + + /** + * Extracts only request fields. + */ + static Project extractFields(Project fullProject, String[] fields) { + if (fields == null) { + return fullProject; + } + Project project = new Project(); + for (String field : fields) { + switch (field) { + case "id": + project.setId(fullProject.getId()); + break; + case "number": + project.setNumber(fullProject.getNumber()); + break; + case "quota": + project.setQuota(fullProject.getQuota()); + break; + default: + break; + } + } + return project; + } + + /** + * Extracts only request fields. + */ + static ResourceRecordSet extractFields(ResourceRecordSet fullRecord, String[] fields) { + if (fields == null) { + return fullRecord; + } + ResourceRecordSet record = new ResourceRecordSet(); + for (String field : fields) { + switch (field) { + case "name": + record.setName(fullRecord.getName()); + break; + case "rrdatas": + record.setRrdatas(fullRecord.getRrdatas()); + break; + case "type": + record.setType(fullRecord.getType()); + break; + case "ttl": + record.setTtl(fullRecord.getTtl()); + break; + default: + break; + } + } + return record; + } + + static Map parseListChangesOptions(String query) { + Map options = new HashMap<>(); + if (query != null) { + String[] args = query.split("&"); + for (String arg : args) { + String[] argEntry = arg.split("="); + switch (argEntry[0]) { + case "fields": + // todo we do not support fragmentation in deletions and additions + options.put( + "fields", + argEntry[1].substring("changes(".length(), argEntry[1].length() - 1).split(",")); + break; + case "name": + options.put("name", argEntry[1]); + break; + case "pageToken": + options.put("pageToken", argEntry[1]); + break; + case "sortBy": + options.put("sortBy", argEntry[1]); + break; + case "sortOrder": + options.put("sortOrder", argEntry[1]); + break; + case "maxResults": + // parsing to int is done while handling + options.put("maxResults", argEntry[1]); + break; + default: + break; + } + } + } + return options; + } + + static Map parseListDnsRecordsOptions(String query) { + Map options = new HashMap<>(); + if (query != null) { + String[] args = query.split("&"); + for (String arg : args) { + String[] argEntry = arg.split("="); + switch (argEntry[0]) { + case "fields": + options.put( + "fields", + argEntry[1].substring("rrsets(".length(), argEntry[1].length() - 1).split(",")); + break; + case "name": + options.put("name", argEntry[1]); + break; + case "pageToken": + options.put("pageToken", argEntry[1]); + break; + case "maxResults": + // parsing to int is done while handling + options.put("maxResults", argEntry[1]); + break; + default: + break; + } + } + } + return options; + } +} diff --git a/gcloud-java-dns/src/test/java/com/google/gcloud/testing/LocalDnsHelperTest.java b/gcloud-java-dns/src/test/java/com/google/gcloud/testing/LocalDnsHelperTest.java new file mode 100644 index 000000000000..1f851045659c --- /dev/null +++ b/gcloud-java-dns/src/test/java/com/google/gcloud/testing/LocalDnsHelperTest.java @@ -0,0 +1,955 @@ +/* + * 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.testing; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.api.services.dns.model.Change; +import com.google.api.services.dns.model.ManagedZone; +import com.google.api.services.dns.model.ResourceRecordSet; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class LocalDnsHelperTest { + + private static final String RRSET_TYPE = "A"; + private static final ResourceRecordSet RRSET1 = new ResourceRecordSet(); + private static final ResourceRecordSet RRSET2 = new ResourceRecordSet(); + private static final ResourceRecordSet RRSET_KEEP = new ResourceRecordSet(); + private static final String PROJECT_ID1 = "2135436541254"; + private static final String PROJECT_ID2 = "882248761325"; + private static final String ZONE_NAME1 = "my little zone"; + private static final String ZONE_NAME2 = "another zone name"; + private static final ManagedZone ZONE1 = new ManagedZone(); + private static final ManagedZone ZONE2 = new ManagedZone(); + private static final String DNS_NAME = "www.example.com."; + private static final Change CHANGE1 = new Change(); + private static final Change CHANGE2 = new Change(); + private static final Change CHANGE_KEEP = new Change(); + private Map optionsMap; + private LocalDnsHelper localDns; + private ManagedZone minimalZone = new ManagedZone(); // to be adjusted as needed + + @BeforeClass + public static void before() { + RRSET1.setName(DNS_NAME); + RRSET1.setType(RRSET_TYPE); + RRSET1.setRrdatas(ImmutableList.of("1.1.1.1")); + ZONE1.setName(ZONE_NAME1); + ZONE1.setDescription(""); + ZONE1.setDnsName(DNS_NAME); + ZONE2.setName(ZONE_NAME2); + ZONE2.setDescription(""); + ZONE2.setDnsName(DNS_NAME); + RRSET2.setName(DNS_NAME); + RRSET2.setType(RRSET_TYPE); + RRSET_KEEP.setName(DNS_NAME); + RRSET_KEEP.setType("MX"); + RRSET_KEEP.setRrdatas(ImmutableList.of("255.255.255.254")); + RRSET2.setRrdatas(ImmutableList.of("123.132.153.156")); + CHANGE1.setAdditions(ImmutableList.of(RRSET1, RRSET2)); + CHANGE2.setDeletions(ImmutableList.of(RRSET2)); + CHANGE_KEEP.setAdditions(ImmutableList.of(RRSET_KEEP)); + } + + @Before + public void setUp() { + localDns = LocalDnsHelper.create(0L); // synchronous + optionsMap = new HashMap<>(); + minimalZone = copyZone(ZONE1); + } + + @After + public void after() { + localDns = null; + } + + @Test + public void testMatchesCriteria() { + assertTrue(LocalDnsHelper.matchesCriteria(RRSET1, RRSET1.getName(), RRSET1.getType())); + assertFalse(LocalDnsHelper.matchesCriteria(RRSET1, RRSET1.getName(), "anothertype")); + assertTrue(LocalDnsHelper.matchesCriteria(RRSET1, null, RRSET1.getType())); + assertTrue(LocalDnsHelper.matchesCriteria(RRSET1, RRSET1.getName(), null)); + assertFalse(LocalDnsHelper.matchesCriteria(RRSET1, "anothername", RRSET1.getType())); + } + + @Test + public void testGetUniqueId() { + assertNotNull(LocalDnsHelper.getUniqueId(Lists.newLinkedList())); + } + + @Test + public void testFindProject() { + assertEquals(0, localDns.projects().size()); + LocalDnsHelper.ProjectContainer project = localDns.findProject(PROJECT_ID1); + assertNotNull(project); + assertTrue(localDns.projects().containsKey(PROJECT_ID1)); + assertNotNull(localDns.findProject(PROJECT_ID2)); + assertTrue(localDns.projects().containsKey(PROJECT_ID2)); + assertTrue(localDns.projects().containsKey(PROJECT_ID1)); + assertNotNull(project.zones()); + assertEquals(0, project.zones().size()); + assertNotNull(project.project()); + assertNotNull(project.project().getQuota()); + } + + @Test + public void testCreateAndFindZone() { + LocalDnsHelper.ZoneContainer zone1 = localDns.findZone(PROJECT_ID1, ZONE_NAME1); + assertTrue(localDns.projects().containsKey(PROJECT_ID1)); + assertNull(zone1); + localDns.createZone(PROJECT_ID1, ZONE1, null); // we do not care about options + zone1 = localDns.findZone(PROJECT_ID1, ZONE1.getName()); + assertNotNull(zone1); + // cannot call equals because id and timestamp got assigned + assertEquals(ZONE_NAME1, zone1.zone().getName()); + assertNotNull(zone1.changes()); + assertTrue(zone1.changes().isEmpty()); + assertNotNull(zone1.dnsRecords()); + assertEquals(2, zone1.dnsRecords().get(ZONE_NAME1).size()); // default SOA and NS + localDns.createZone(PROJECT_ID2, ZONE1, null); // project does not exits yet + assertEquals(ZONE1.getName(), localDns.findZone(PROJECT_ID2, ZONE_NAME1).zone().getName()); + } + + @Test + public void testDeleteZone() { + localDns.createZone(PROJECT_ID1, ZONE1, null); + LocalDnsHelper.Response response = localDns.deleteZone(PROJECT_ID1, ZONE1.getName()); + assertEquals(204, response.code()); + // deleting non-existent zone + response = localDns.deleteZone(PROJECT_ID1, ZONE1.getName()); + assertEquals(404, response.code()); + assertNull(localDns.findZone(PROJECT_ID1, ZONE1.getName())); + localDns.createZone(PROJECT_ID1, ZONE1, null); + localDns.createZone(PROJECT_ID1, ZONE2, null); + assertNotNull(localDns.findZone(PROJECT_ID1, ZONE1.getName())); + assertNotNull(localDns.findZone(PROJECT_ID1, ZONE2.getName())); + // delete in reverse order + response = localDns.deleteZone(PROJECT_ID1, ZONE1.getName()); + assertEquals(204, response.code()); + assertNull(localDns.findZone(PROJECT_ID1, ZONE1.getName())); + assertNotNull(localDns.findZone(PROJECT_ID1, ZONE2.getName())); + localDns.deleteZone(PROJECT_ID1, ZONE2.getName()); + assertEquals(204, response.code()); + assertNull(localDns.findZone(PROJECT_ID1, ZONE1.getName())); + assertNull(localDns.findZone(PROJECT_ID1, ZONE2.getName())); + } + + @Test + public void testCreateAndApplyChange() { + localDns = LocalDnsHelper.create(5 * 1000L); // we will be using threads here + localDns.createZone(PROJECT_ID1, ZONE1, null); + assertNull(localDns.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); + LocalDnsHelper.Response response + = localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add + assertEquals(200, response.code()); + assertNotNull(localDns.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); + assertNull(localDns.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); + localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add + response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add + assertEquals(200, response.code()); + assertNotNull(localDns.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); + assertNotNull(localDns.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); + localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); // delete + assertNotNull(localDns.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); + assertNotNull(localDns.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); + assertNotNull(localDns.findZone(PROJECT_ID1, ZONE_NAME1).findChange("3")); + localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE_KEEP, null); // id is "4" + // check execution + Change change = localDns.findChange(PROJECT_ID1, ZONE_NAME1, "4"); + for (int i = 0; i < 10 && !change.getStatus().equals("done"); i++) { + // change has not been finished yet; wait at most 20 seconds + // it takes 5 seconds for the thread to kick in in the first place + try { + Thread.sleep(2 * 1000); + } catch (InterruptedException e) { + fail("Test was interrupted"); + } + } + assertEquals("done", change.getStatus()); + List list = + localDns.findZone(PROJECT_ID1, ZONE_NAME1).dnsRecords().get(ZONE_NAME1); + assertTrue(list.contains(new LocalDnsHelper.RrsetWrapper(RRSET_KEEP))); + } + + @Test + public void testFindChange() { + localDns.createZone(PROJECT_ID1, ZONE1, null); + Change change = localDns.findChange(PROJECT_ID1, ZONE1.getName(), "somerandomchange"); + assertNull(change); + localDns.createChange(PROJECT_ID1, ZONE1.getName(), CHANGE1, null); + // changes are sequential so we should find ID 1 + assertNotNull(localDns.findChange(PROJECT_ID1, ZONE1.getName(), "1")); + // add another + localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); + assertNotNull(localDns.findChange(PROJECT_ID1, ZONE1.getName(), "1")); + assertNotNull(localDns.findChange(PROJECT_ID1, ZONE1.getName(), "2")); + // try to find non-existent change + assertNull(localDns.findChange(PROJECT_ID1, ZONE1.getName(), "3")); + // try to find a change in yet non-existent project + assertNull(localDns.findChange(PROJECT_ID2, ZONE1.getName(), "3")); + } + + @Test + public void testRandomNameServers() { + assertEquals(4, LocalDnsHelper.randomNameservers().size()); + assertEquals(4, LocalDnsHelper.randomNameservers().size()); + assertEquals(4, LocalDnsHelper.randomNameservers().size()); + assertEquals(4, LocalDnsHelper.randomNameservers().size()); + } + + @Test + public void testGetProject() { + // only interested in no exceptions and non-null response here + assertNotNull(localDns.getProject(PROJECT_ID1, null)); + assertNotNull(localDns.getProject(PROJECT_ID2, null)); + } + + @Test + public void testGetZone() { + // non-existent + LocalDnsHelper.Response response = localDns.getZone(PROJECT_ID1, ZONE_NAME1, null); + assertEquals(404, response.code()); + assertTrue(response.body().contains("does not exist")); + // existent + localDns.createZone(PROJECT_ID1, ZONE1, null); + response = localDns.getZone(PROJECT_ID1, ZONE1.getName(), null); + assertEquals(200, response.code()); + } + + @Test + public void testCreateZone() { + // only interested in no exceptions and non-null response here + LocalDnsHelper.Response response = localDns.createZone(PROJECT_ID1, ZONE1, null); + assertEquals(200, response.code()); + assertEquals(1, localDns.projects().get(PROJECT_ID1).zones().size()); + try { + localDns.createZone(PROJECT_ID1, null, null); + fail("Zone cannot be null"); + } catch (NullPointerException ex) { + // expected + } + // create zone twice + response = localDns.createZone(PROJECT_ID1, ZONE1, null); + assertEquals(409, response.code()); + assertTrue(response.body().contains("already exists")); + } + + @Test + public void testCreateChange() { + // non-existent zone + LocalDnsHelper.Response response = + localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); + assertEquals(404, response.code()); + + // existent zone + assertNotNull(localDns.createZone(PROJECT_ID1, ZONE1, null)); + assertNull(localDns.findChange(PROJECT_ID1, ZONE_NAME1, "1")); + response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); + assertEquals(200, response.code()); + assertNotNull(localDns.findChange(PROJECT_ID1, ZONE_NAME1, "1")); + } + + @Test + public void testGetChange() { + // existent + assertEquals(200, localDns.createZone(PROJECT_ID1, ZONE1, null).code()); + assertEquals(200, localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null).code()); + assertEquals(200, localDns.getChange(PROJECT_ID1, ZONE_NAME1, "1", null).code()); + // non-existent + LocalDnsHelper.Response response = localDns.getChange(PROJECT_ID1, ZONE_NAME1, "2", null); + assertEquals(404, response.code()); + assertTrue(response.body().contains("parameters.changeId")); + // non-existent zone + response = localDns.getChange(PROJECT_ID1, ZONE_NAME2, "1", null); + assertEquals(404, response.code()); + assertTrue(response.body().contains("parameters.managedZone")); + } + + @Test + public void testListZones() { + // only interested in no exceptions and non-null response here + optionsMap.put("dnsName", null); + optionsMap.put("fields", null); + optionsMap.put("pageToken", null); + optionsMap.put("maxResults", null); + LocalDnsHelper.Response response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(200, response.code()); + // some zones exists + localDns.createZone(PROJECT_ID1, ZONE1, null); + response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(200, response.code()); + localDns.createZone(PROJECT_ID1, ZONE2, null); + response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(200, response.code()); + // error in options + optionsMap.put("maxResults", "aaa"); + response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(400, response.code()); + optionsMap.put("maxResults", "0"); + response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(400, response.code()); + optionsMap.put("maxResults", "-1"); + response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(400, response.code()); + optionsMap.put("maxResults", "15"); + response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(200, response.code()); + optionsMap.put("dnsName", "aaa"); + response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(400, response.code()); + optionsMap.put("dnsName", "aaa."); + response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(200, response.code()); + } + + @Test + public void testListDnsRecords() { + // only interested in no exceptions and non-null response here + optionsMap.put("name", null); + optionsMap.put("fields", null); + optionsMap.put("type", null); + optionsMap.put("pageToken", null); + optionsMap.put("maxResults", null); + // no zone exists + LocalDnsHelper.Response response = localDns.listDnsRecords(PROJECT_ID1, ZONE_NAME1, + optionsMap); + assertEquals(404, response.code()); + // zone exists but has no records + localDns.createZone(PROJECT_ID1, ZONE1, null); + localDns.listDnsRecords(PROJECT_ID1, ZONE_NAME1, optionsMap); + // zone has records + localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); + response = localDns.listDnsRecords(PROJECT_ID1, ZONE_NAME1, optionsMap); + assertEquals(200, response.code()); + // error in options + optionsMap.put("maxResults", "aaa"); + response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(400, response.code()); + optionsMap.put("maxResults", "0"); + response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(400, response.code()); + optionsMap.put("maxResults", "-1"); + response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(400, response.code()); + optionsMap.put("maxResults", "15"); + response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(200, response.code()); + optionsMap.put("name", "aaa"); + response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(400, response.code()); + optionsMap.put("name", "aaa."); + response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(200, response.code()); + optionsMap.put("name", null); + optionsMap.put("type", "A"); + response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(400, response.code()); + optionsMap.put("name", "aaa."); + optionsMap.put("type", "a"); + response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(400, response.code()); + optionsMap.put("name", "aaaa."); + optionsMap.put("type", "A"); + response = localDns.listZones(PROJECT_ID1, optionsMap); + assertEquals(200, response.code()); + } + + @Test + public void testListChanges() { + optionsMap.put("sortBy", null); + optionsMap.put("sortOrder", null); + optionsMap.put("fields", null); + optionsMap.put("pageToken", null); + optionsMap.put("maxResults", null); + // no such zone exists + LocalDnsHelper.Response response = localDns.listDnsRecords(PROJECT_ID1, ZONE_NAME1, optionsMap); + assertEquals(404, response.code()); + assertTrue(response.body().contains("managedZone")); + // zone exists but has no changes + localDns.createZone(PROJECT_ID1, ZONE1, null); + assertNotNull(localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap)); + // zone has changes + localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); + assertNotNull(localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap)); + localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); + localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); + localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); + assertNotNull(localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap)); + // error in options + optionsMap.put("maxResults", "aaa"); + response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + assertEquals(400, response.code()); + optionsMap.put("maxResults", "0"); + response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + assertEquals(400, response.code()); + optionsMap.put("maxResults", "-1"); + response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + assertEquals(400, response.code()); + optionsMap.put("maxResults", "15"); + response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + assertEquals(200, response.code()); + optionsMap.put("dnsName", "aaa"); + response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + assertEquals(400, response.code()); + optionsMap.put("dnsName", "aaa."); + response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + assertEquals(200, response.code()); + optionsMap.put("sortBy", "changeSequence"); + response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + assertEquals(200, response.code()); + optionsMap.put("sortBy", "something else"); + response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + assertEquals(400, response.code()); + assertTrue(response.body().contains("Allowed values: [changesequence]")); + optionsMap.put("sortBy", "ChAnGeSeQuEnCe"); // is not case sensitive + response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + assertEquals(200, response.code()); + optionsMap.put("sortOrder", "ascending"); + response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + assertEquals(200, response.code()); + optionsMap.put("sortBy", null); + optionsMap.put("sortOrder", "descending"); + response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + assertEquals(200, response.code()); + optionsMap.put("sortOrder", "somethingelse"); + response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + assertEquals(400, response.code()); + assertTrue(response.body().contains("parameters.sortOrder")); + } + + @Test + public void testToListResponse() { + LocalDnsHelper.Response response = LocalDnsHelper.toListResponse( + Lists.newArrayList("some", "multiple", "words"), "IncludeThisPageToken", true); + assertTrue(response.body().contains("IncludeThisPageToken")); + response = LocalDnsHelper.toListResponse( + Lists.newArrayList("some", "multiple", "words"), "IncludeThisPageToken", false); + assertFalse(response.body().contains("IncludeThisPageToken")); + response = LocalDnsHelper.toListResponse( + Lists.newArrayList("some", "multiple", "words"), null, true); + assertFalse(response.body().contains("pageToken")); + } + + @Test + public void testCheckZone() { + // no name + ManagedZone copy = copyZone(minimalZone); + copy.setName(null); + LocalDnsHelper.Response response = LocalDnsHelper.checkZone(copy); + assertEquals(400, response.code()); + assertTrue(response.body().contains("entity.managedZone.name")); + // no description + copy = copyZone(minimalZone); + copy.setDescription(null); + response = LocalDnsHelper.checkZone(copy); + assertEquals(400, response.code()); + assertTrue(response.body().contains("entity.managedZone.description")); + // no description + copy = copyZone(minimalZone); + copy.setDnsName(null); + response = LocalDnsHelper.checkZone(copy); + assertEquals(400, response.code()); + assertTrue(response.body().contains("entity.managedZone.dnsName")); + // zone name is a number + copy = copyZone(minimalZone); + copy.setName("123456"); + response = LocalDnsHelper.checkZone(copy); + assertEquals(400, response.code()); + assertTrue(response.body().contains("entity.managedZone.name")); + assertTrue(response.body().contains("Invalid")); + // dns name does not end with period + copy = copyZone(minimalZone); + copy.setDnsName("aaaaaa.com"); + response = LocalDnsHelper.checkZone(copy); + assertEquals(400, response.code()); + assertTrue(response.body().contains("entity.managedZone.dnsName")); + assertTrue(response.body().contains("Invalid")); + // dns name is reserved + copy = copyZone(minimalZone); + copy.setDnsName("com."); + response = LocalDnsHelper.checkZone(copy); + assertEquals(400, response.code()); + assertTrue(response.body().contains("not available to be created.")); + // empty description should pass + copy = copyZone(minimalZone); + copy.setDescription(""); + assertNull(LocalDnsHelper.checkZone(copy)); + } + + @Test + public void testCreateZoneValidatesZone() { + // no name + ManagedZone copy = copyZone(minimalZone); + copy.setName(null); + LocalDnsHelper.Response response = localDns.createZone(PROJECT_ID1, copy, null); + assertEquals(400, response.code()); + assertTrue(response.body().contains("entity.managedZone.name")); + // no description + copy = copyZone(minimalZone); + copy.setDescription(null); + response = localDns.createZone(PROJECT_ID1, copy, null); + assertEquals(400, response.code()); + assertTrue(response.body().contains("entity.managedZone.description")); + // no dns name + copy = copyZone(minimalZone); + copy.setDnsName(null); + response = localDns.createZone(PROJECT_ID1, copy, null); + assertEquals(400, response.code()); + assertTrue(response.body().contains("entity.managedZone.dnsName")); + // zone name is a number + copy = copyZone(minimalZone); + copy.setName("123456"); + response = localDns.createZone(PROJECT_ID1, copy, null); + assertEquals(400, response.code()); + assertTrue(response.body().contains("entity.managedZone.name")); + assertTrue(response.body().contains("Invalid")); + // dns name does not end with period + copy = copyZone(minimalZone); + copy.setDnsName("aaaaaa.com"); + response = localDns.createZone(PROJECT_ID1, copy, null); + assertEquals(400, response.code()); + assertTrue(response.body().contains("entity.managedZone.dnsName")); + assertTrue(response.body().contains("Invalid")); + // dns name is reserved + copy = copyZone(minimalZone); + copy.setDnsName("com."); + response = localDns.createZone(PROJECT_ID1, copy, null); + assertEquals(400, response.code()); + assertTrue(response.body().contains("not available to be created.")); + // empty description should pass + copy = copyZone(minimalZone); + copy.setDescription(""); + response = localDns.createZone(PROJECT_ID1, copy, null); + assertEquals(200, response.code()); + } + + @Test + public void testCheckListOptions() { + // listing zones + optionsMap.put("maxResults", "-1"); + LocalDnsHelper.Response response = LocalDnsHelper.checkListOptions(optionsMap); + assertEquals(400, response.code()); + assertTrue(response.body().contains("parameters.maxResults")); + optionsMap.put("maxResults", "0"); + response = LocalDnsHelper.checkListOptions(optionsMap); + assertEquals(400, response.code()); + assertTrue(response.body().contains("parameters.maxResults")); + optionsMap.put("maxResults", "aaaa"); + response = LocalDnsHelper.checkListOptions(optionsMap); + assertEquals(400, response.code()); + assertTrue(response.body().contains("integer")); + optionsMap.put("maxResults", "15"); + response = LocalDnsHelper.checkListOptions(optionsMap); + assertNull(response); + optionsMap.put("dnsName", "aaa"); + response = LocalDnsHelper.checkListOptions(optionsMap); + assertEquals(400, response.code()); + assertTrue(response.body().contains("parameters.dnsName")); + optionsMap.put("dnsName", "aaa."); + response = LocalDnsHelper.checkListOptions(optionsMap); + assertNull(response); + // listing dns records + optionsMap.put("name", "aaa"); + response = LocalDnsHelper.checkListOptions(optionsMap); + assertEquals(400, response.code()); + assertTrue(response.body().contains("parameters.name")); + optionsMap.put("name", "aaa."); + response = LocalDnsHelper.checkListOptions(optionsMap); + assertNull(response); + optionsMap.put("name", null); + optionsMap.put("type", "A"); + response = LocalDnsHelper.checkListOptions(optionsMap); + assertEquals(400, response.code()); + assertTrue(response.body().contains("parameters.name")); + optionsMap.put("name", "aaa."); + optionsMap.put("type", "a"); + response = LocalDnsHelper.checkListOptions(optionsMap); + assertEquals(400, response.code()); + assertTrue(response.body().contains("parameters.type")); + optionsMap.put("name", "aaaa."); + optionsMap.put("type", "A"); + response = LocalDnsHelper.checkListOptions(optionsMap); + assertNull(response); + // listing changes + optionsMap.put("sortBy", "changeSequence"); + response = LocalDnsHelper.checkListOptions(optionsMap); + assertNull(response); + optionsMap.put("sortBy", "something else"); + response = LocalDnsHelper.checkListOptions(optionsMap); + assertEquals(400, response.code()); + assertTrue(response.body().contains("Allowed values: [changesequence]")); + optionsMap.put("sortBy", "ChAnGeSeQuEnCe"); // is not case sensitive + response = LocalDnsHelper.checkListOptions(optionsMap); + assertNull(response); + optionsMap.put("sortOrder", "ascending"); + response = LocalDnsHelper.checkListOptions(optionsMap); + assertNull(response); + optionsMap.put("sortOrder", "descending"); + response = LocalDnsHelper.checkListOptions(optionsMap); + assertNull(response); + optionsMap.put("sortOrder", "somethingelse"); + response = LocalDnsHelper.checkListOptions(optionsMap); + assertEquals(400, response.code()); + assertTrue(response.body().contains("parameters.sortOrder")); + } + + @Test + public void testCheckRrset() { + ResourceRecordSet valid = new ResourceRecordSet(); + valid.setName(ZONE1.getDnsName()); + valid.setType("A"); + valid.setRrdatas(ImmutableList.of("0.255.1.5")); + valid.setTtl(500); + Change validChange = new Change(); + validChange.setAdditions(ImmutableList.of(valid)); + localDns.createZone(PROJECT_ID1, ZONE1, null); + localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + // delete with field mismatch + LocalDnsHelper.ZoneContainer zone = localDns.findZone(PROJECT_ID1, ZONE_NAME1); + valid.setTtl(valid.getTtl() + 20); + LocalDnsHelper.Response response = LocalDnsHelper.checkRrset(valid, zone, 0, "deletions"); + assertEquals(412, response.code()); + assertTrue(response.body().contains("entity.change.deletions[0]")); + } + + @Test + public void testCheckRrdata() { + // A + assertTrue(LocalDnsHelper.checkRrData("255.255.255.255", "A")); + assertTrue(LocalDnsHelper.checkRrData("13.15.145.165", "A")); + assertTrue(LocalDnsHelper.checkRrData("0.0.0.0", "A")); + assertFalse(LocalDnsHelper.checkRrData("255.255.255.256", "A")); + assertFalse(LocalDnsHelper.checkRrData("-1.255.255.255", "A")); + assertFalse(LocalDnsHelper.checkRrData(".255.255.254", "A")); + assertFalse(LocalDnsHelper.checkRrData("111.255.255.", "A")); + assertFalse(LocalDnsHelper.checkRrData("111.255..22", "A")); + assertFalse(LocalDnsHelper.checkRrData("111.255.aa.22", "A")); + assertFalse(LocalDnsHelper.checkRrData("", "A")); + assertFalse(LocalDnsHelper.checkRrData("...", "A")); + assertFalse(LocalDnsHelper.checkRrData("111.255.12", "A")); + assertFalse(LocalDnsHelper.checkRrData("111.255.12.11.11", "A")); + // AAAA + assertTrue(LocalDnsHelper.checkRrData(":::::::", "AAAA")); + assertTrue(LocalDnsHelper.checkRrData("1F:fa:09fd::343:aaaa:AAAA:", "AAAA")); + assertTrue(LocalDnsHelper.checkRrData("0000:FFFF:09fd::343:aaaa:AAAA:", "AAAA")); + assertFalse(LocalDnsHelper.checkRrData("-2:::::::", "AAAA")); + assertTrue(LocalDnsHelper.checkRrData("0:::::::", "AAAA")); + assertFalse(LocalDnsHelper.checkRrData("::1FFFF:::::", "AAAA")); + assertFalse(LocalDnsHelper.checkRrData("::aqaa:::::", "AAAA")); + assertFalse(LocalDnsHelper.checkRrData("::::::::", "AAAA")); // too long + assertFalse(LocalDnsHelper.checkRrData("::::::", "AAAA")); // too short + } + + @Test + public void testCheckChange() { + ResourceRecordSet validA = new ResourceRecordSet(); + validA.setName(ZONE1.getDnsName()); + validA.setType("A"); + validA.setRrdatas(ImmutableList.of("0.255.1.5")); + ResourceRecordSet invalidA = new ResourceRecordSet(); + invalidA.setName(ZONE1.getDnsName()); + invalidA.setType("A"); + invalidA.setRrdatas(ImmutableList.of("0.-255.1.5")); + Change validChange = new Change(); + validChange.setAdditions(ImmutableList.of(validA)); + Change invalidChange = new Change(); + invalidChange.setAdditions(ImmutableList.of(invalidA)); + LocalDnsHelper.ZoneContainer zoneContainer = new LocalDnsHelper.ZoneContainer(ZONE1); + LocalDnsHelper.Response response = LocalDnsHelper.checkChange(validChange, zoneContainer); + assertNull(response); + response = LocalDnsHelper.checkChange(invalidChange, zoneContainer); + assertEquals(400, response.code()); + assertTrue(response.body().contains("additions[0].rrdata[0]")); + // only empty additions/deletions + Change empty = new Change(); + empty.setAdditions(ImmutableList.of()); + empty.setDeletions(ImmutableList.of()); + response = LocalDnsHelper.checkChange(empty, zoneContainer); + assertEquals(400, response.code()); + assertTrue(response.body().contains( + "The 'entity.change' parameter is required but was missing.")); + // null additions/deletions + empty = new Change(); + response = LocalDnsHelper.checkChange(empty, zoneContainer); + assertEquals(400, response.code()); + assertTrue(response.body().contains( + "The 'entity.change' parameter is required but was missing.")); + // non-matching name + validA.setName(ZONE1.getDnsName() + ".aaa."); + response = LocalDnsHelper.checkChange(validChange, zoneContainer); + assertEquals(400, response.code()); + assertTrue(response.body().contains("additions[0].name")); + // wrong type + validA.setName(ZONE1.getDnsName()); // revert + validA.setType("ABCD"); + response = LocalDnsHelper.checkChange(validChange, zoneContainer); + assertEquals(400, response.code()); + assertTrue(response.body().contains("additions[0].type")); + // wrong ttl + validA.setType("A"); // revert + validA.setTtl(-1); + response = LocalDnsHelper.checkChange(validChange, zoneContainer); + assertEquals(400, response.code()); + assertTrue(response.body().contains("additions[0].ttl")); + validA.setTtl(null); + // null name + validA.setName(null); + response = LocalDnsHelper.checkChange(validChange, zoneContainer); + assertEquals(400, response.code()); + assertTrue(response.body().contains("additions[0].name")); + validA.setName(ZONE1.getDnsName()); + // null type + validA.setType(null); + response = LocalDnsHelper.checkChange(validChange, zoneContainer); + assertEquals(400, response.code()); + assertTrue(response.body().contains("additions[0].type")); + validA.setType("A"); + // null rrdata + List temp = validA.getRrdatas(); // preserve + validA.setRrdatas(null); + response = LocalDnsHelper.checkChange(validChange, zoneContainer); + assertEquals(400, response.code()); + assertTrue(response.body().contains("additions[0].rrdata")); + validA.setRrdatas(temp); + // delete non-existent + ResourceRecordSet nonExistent = new ResourceRecordSet(); + nonExistent.setName(ZONE1.getDnsName()); + nonExistent.setType("AAAA"); + nonExistent.setRrdatas(ImmutableList.of(":::::::")); + Change delete = new Change(); + delete.setDeletions(ImmutableList.of(nonExistent)); + response = LocalDnsHelper.checkChange(delete, zoneContainer); + assertEquals(404, response.code()); + assertTrue(response.body().contains("deletions[0]")); + + } + + @Test + public void testAdditionsMeetDeletions() { + ResourceRecordSet validA = new ResourceRecordSet(); + validA.setName(ZONE1.getDnsName()); + validA.setType("A"); + validA.setRrdatas(ImmutableList.of("0.255.1.5")); + Change validChange = new Change(); + validChange.setAdditions(ImmutableList.of(validA)); + localDns.createZone(PROJECT_ID1, ZONE1, null); + localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + LocalDnsHelper.ZoneContainer container = localDns.findZone(PROJECT_ID1, ZONE_NAME1); + LocalDnsHelper.Response response = + LocalDnsHelper.additionsMeetDeletions(ImmutableList.of(validA), null, container); + assertEquals(409, response.code()); + assertTrue(response.body().contains("already exists")); + + } + + @Test + public void testCreateChangeValidatesChangeContent() { + ResourceRecordSet validA = new ResourceRecordSet(); + validA.setName(ZONE1.getDnsName()); + validA.setType("A"); + validA.setRrdatas(ImmutableList.of("0.255.1.5")); + Change validChange = new Change(); + validChange.setAdditions(ImmutableList.of(validA)); + localDns.createZone(PROJECT_ID1, ZONE1, null); + localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + LocalDnsHelper.Response response = + localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + assertEquals(409, response.code()); + assertTrue(response.body().contains("already exists")); + // delete with field mismatch + Change delete = new Change(); + validA.setTtl(20); // mismatch + delete.setDeletions(ImmutableList.of(validA)); + response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, delete, null); + assertEquals(412, response.code()); + assertTrue(response.body().contains("entity.change.deletions[0]")); + // delete and add SOA + Change addition = new Change(); + ImmutableList rrsetWrappers + = localDns.findZone(PROJECT_ID1, ZONE_NAME1).dnsRecords().get(ZONE_NAME1); + LinkedList deletions = new LinkedList<>(); + LinkedList additions = new LinkedList<>(); + for (LocalDnsHelper.RrsetWrapper wrapper : rrsetWrappers) { + ResourceRecordSet rrset = wrapper.rrset(); + if (rrset.getType().equals("SOA")) { + deletions.add(rrset); + ResourceRecordSet copy = copyRrset(rrset); + copy.setName("x." + copy.getName()); + additions.add(copy); + break; + } + } + delete.setDeletions(deletions); + addition.setAdditions(additions); + response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, delete, null); + assertEquals(400, response.code()); + assertTrue(response.body().contains( + "zone must contain exactly one resource record set of type 'SOA' at the apex")); + assertTrue(response.body().contains("deletions[0]")); + response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, addition, null); + assertEquals(400, response.code()); + assertTrue(response.body().contains( + "zone must contain exactly one resource record set of type 'SOA' at the apex")); + assertTrue(response.body().contains("additions[0]")); + // delete NS + deletions = new LinkedList<>(); + additions = new LinkedList<>(); + for (LocalDnsHelper.RrsetWrapper wrapper : rrsetWrappers) { + ResourceRecordSet rrset = wrapper.rrset(); + if (rrset.getType().equals("NS")) { + deletions.add(rrset); + ResourceRecordSet copy = copyRrset(rrset); + copy.setName("x." + copy.getName()); + additions.add(copy); + break; + } + } + delete.setDeletions(deletions); + addition.setAdditions(additions); + response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, delete, null); + assertEquals(400, response.code()); + assertTrue(response.body().contains( + "zone must contain exactly one resource record set of type 'NS' at the apex")); + response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, addition, null); + assertEquals(400, response.code()); + assertTrue(response.body().contains( + "zone must contain exactly one resource record set of type 'NS' at the apex")); + assertTrue(response.body().contains("additions[0]")); + // change (delete + add) + addition.setDeletions(deletions); + response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, addition, null); + assertEquals(200, response.code()); + } + + @Test + public void testCreateChangeValidatesChange() { + localDns.createZone(PROJECT_ID1, ZONE1, null); + ResourceRecordSet validA = new ResourceRecordSet(); + validA.setName(ZONE1.getDnsName()); + validA.setType("A"); + validA.setRrdatas(ImmutableList.of("0.255.1.5")); + ResourceRecordSet invalidA = new ResourceRecordSet(); + invalidA.setName(ZONE1.getDnsName()); + invalidA.setType("A"); + invalidA.setRrdatas(ImmutableList.of("0.-255.1.5")); + Change validChange = new Change(); + validChange.setAdditions(ImmutableList.of(validA)); + Change invalidChange = new Change(); + invalidChange.setAdditions(ImmutableList.of(invalidA)); + LocalDnsHelper.Response response = + localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + assertEquals(200, response.code()); + response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, invalidChange, null); + assertEquals(400, response.code()); + // only empty additions/deletions + Change empty = new Change(); + empty.setAdditions(ImmutableList.of()); + empty.setDeletions(ImmutableList.of()); + response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, empty, null); + assertEquals(400, response.code()); + assertTrue(response.body().contains( + "The 'entity.change' parameter is required but was missing.")); + // non-matching name + validA.setName(ZONE1.getDnsName() + ".aaa."); + response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + assertEquals(400, response.code()); + assertTrue(response.body().contains("additions[0].name")); + // wrong type + validA.setName(ZONE1.getDnsName()); // revert + validA.setType("ABCD"); + response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + assertEquals(400, response.code()); + assertTrue(response.body().contains("additions[0].type")); + // wrong ttl + validA.setType("A"); // revert + validA.setTtl(-1); + response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + assertEquals(400, response.code()); + assertTrue(response.body().contains("additions[0].ttl")); + validA.setTtl(null); // revert + // null name + validA.setName(null); + response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + assertEquals(400, response.code()); + assertTrue(response.body().contains("additions[0].name")); + validA.setName(ZONE1.getDnsName()); + // null type + validA.setType(null); + response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + assertEquals(400, response.code()); + assertTrue(response.body().contains("additions[0].type")); + validA.setType("A"); + // null rrdata + List temp = validA.getRrdatas(); // preserve + validA.setRrdatas(null); + response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + assertEquals(400, response.code()); + assertTrue(response.body().contains("additions[0].rrdata")); + validA.setRrdatas(temp); + // delete non-existent + ResourceRecordSet nonExistent = new ResourceRecordSet(); + nonExistent.setName(ZONE1.getDnsName()); + nonExistent.setType("AAAA"); + nonExistent.setRrdatas(ImmutableList.of(":::::::")); + Change delete = new Change(); + delete.setDeletions(ImmutableList.of(nonExistent)); + response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, delete, null); + assertEquals(404, response.code()); + assertTrue(response.body().contains("deletions[0]")); + } + + private static ManagedZone copyZone(ManagedZone original) { + ManagedZone copy = new ManagedZone(); + copy.setDescription(original.getDescription()); + copy.setName(original.getName()); + copy.setCreationTime(original.getCreationTime()); + copy.setId(original.getId()); + copy.setNameServerSet(original.getNameServerSet()); + copy.setDnsName(original.getDnsName()); + if (original.getNameServers() != null) { + copy.setNameServers(ImmutableList.copyOf(original.getNameServers())); + } + return copy; + } + + private static ResourceRecordSet copyRrset(ResourceRecordSet set) { + ResourceRecordSet copy = new ResourceRecordSet(); + if (set.getRrdatas() != null) { + copy.setRrdatas(ImmutableList.copyOf(set.getRrdatas())); + } + copy.setTtl(set.getTtl()); + copy.setName(set.getName()); + copy.setType(set.getType()); + return copy; + } +} From 04f354a16a8759af38d3e883b9bb9dcc467744f3 Mon Sep 17 00:00:00 2001 From: Martin Derka Date: Tue, 23 Feb 2016 11:38:51 -0800 Subject: [PATCH 2/9] Added RPC layer of tests for local helpers. Closes #665. Debugged parsing for options. Fixed context for the server URLs and regular expression parsing. Fixed paging. Improved error detection for missing zones vs. non-existent changes. --- .../com/google/gcloud/spi/DefaultDnsRpc.java | 4 +- .../google/gcloud/testing/LocalDnsHelper.java | 126 +- .../testing/OptionParsersAndExtractors.java | 31 +- .../gcloud/testing/LocalDnsHelperTest.java | 1176 ++++++++++++++--- 4 files changed, 1097 insertions(+), 240 deletions(-) diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/spi/DefaultDnsRpc.java b/gcloud-java-dns/src/main/java/com/google/gcloud/spi/DefaultDnsRpc.java index 6ed9c7e0f216..b72a21445a80 100644 --- a/gcloud-java-dns/src/main/java/com/google/gcloud/spi/DefaultDnsRpc.java +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/spi/DefaultDnsRpc.java @@ -162,7 +162,9 @@ public Change getChangeRequest(String zoneName, String changeRequestId, MapWhile the mock attempts to simulate the service, there are some differences in the behaviour. * The mock will accept any project ID and never returns a notFound or another error because of - * project ID. It assumes that all project IDs exists and that the user has all the necessary + * project ID. It assumes that all project IDs exist and that the user has all the necessary * privileges to manipulate any project. Similarly, the local simulation does not work with any * verification of domain name ownership. Any request for creating a managed zone will be approved. * The mock does not track quota and will allow the user to exceed it. The mock provides only basic @@ -92,11 +93,10 @@ public class LocalDnsHelper { = new ConcurrentSkipListMap<>(); private static final URI BASE_CONTEXT; private static final Logger log = Logger.getLogger(LocalDnsHelper.class.getName()); - private static final JsonFactory jsonFactory = - new com.google.api.client.json.jackson.JacksonFactory(); + private static final JsonFactory jsonFactory = new JacksonFactory(); private static final Random ID_GENERATOR = new Random(); private static final String VERSION = "v1"; - private static final String CONTEXT = "/" + VERSION + "/projects"; + private static final String CONTEXT = "/dns/" + VERSION + "/projects"; private static final Set SUPPORTED_COMPRESSION_ENCODINGS = ImmutableSet.of("gzip", "x-gzip"); private static final List TYPES = ImmutableList.of("A", "AAAA", "CNAME", "MX", "NAPTR", @@ -119,15 +119,15 @@ public class LocalDnsHelper { * For matching URLs to operations. */ private enum CallRegex { - CHANGE_CREATE("POST", "/[^/]+/managedZones/[^/]+/changes"), - CHANGE_GET("GET", "/[^/]+/managedZones/[^/]+/changes/[^/]+"), - CHANGE_LIST("GET", "/[^/]+/managedZones/[^/]+/changes"), - ZONE_CREATE("POST", "/[^/]+/managedZones"), - ZONE_DELETE("DELETE", "/[^/]+/managedZones/[^/]+"), - ZONE_GET("GET", "/[^/]+/managedZones/[^/]+"), - ZONE_LIST("GET", "/[^/]+/managedZones"), - PROJECT_GET("GET", "/[^/]+"), - RECORD_LIST("GET", "/[^/]+/managedZones/[^/]+/rrsets"); + CHANGE_CREATE("POST", CONTEXT + "/[^/]+/managedZones/[^/]+/changes"), + CHANGE_GET("GET", CONTEXT + "/[^/]+/managedZones/[^/]+/changes/[^/]+"), + CHANGE_LIST("GET", CONTEXT + "/[^/]+/managedZones/[^/]+/changes"), + ZONE_CREATE("POST", CONTEXT + "/[^/]+/managedZones"), + ZONE_DELETE("DELETE", CONTEXT + "/[^/]+/managedZones/[^/]+"), + ZONE_GET("GET", CONTEXT + "/[^/]+/managedZones/[^/]+"), + ZONE_LIST("GET", CONTEXT + "/[^/]+/managedZones"), + PROJECT_GET("GET", CONTEXT + "/[^/]+"), + RECORD_LIST("GET", CONTEXT + "/[^/]+/managedZones/[^/]+/rrsets"); private String method; private String pathRegex; @@ -220,14 +220,14 @@ ConcurrentSkipListMap zones() { } /** - * Associates a zone with a collection of changes and dns record. Thread safe. + * Associates a zone with a collection of changes and dns records. Thread safe. */ static class ZoneContainer { private final ManagedZone zone; /** * DNS records are held in a map to allow for atomic replacement of record sets when applying * changes. The key for the map is always the zone name. The collection of records is immutable - * and must always exist, i.e., dnsRecords.get(zone.getName()) is never null. + * and must always exist, i.e., dnsRecords.get(zone.getName()) is never {@code null}. */ private final ConcurrentSkipListMap> dnsRecords = new ConcurrentSkipListMap<>(); @@ -377,20 +377,19 @@ public void handle(HttpExchange exchange) throws IOException { writeResponse(exchange, Error.NOT_FOUND.response("The url does not match any API call.")); } - // todo(mderka) Test handlers using gcloud-java-dns. Issue #665. private Response handleZoneDelete(HttpExchange exchange) { String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); String[] tokens = path.split("/"); - String projectId = tokens[1]; - String zoneName = tokens[3]; + String projectId = tokens[0]; + String zoneName = tokens[2]; return deleteZone(projectId, zoneName); } private Response handleZoneGet(HttpExchange exchange) { String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); String[] tokens = path.split("/"); - String projectId = tokens[1]; - String zoneName = tokens[3]; + String projectId = tokens[0]; + String zoneName = tokens[2]; String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); String[] fields = OptionParsersAndExtractors.parseGetOptions(query); return getZone(projectId, zoneName, fields); @@ -399,7 +398,7 @@ private Response handleZoneGet(HttpExchange exchange) { private Response handleZoneList(HttpExchange exchange) { String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); String[] tokens = path.split("/"); - String projectId = tokens[1]; + String projectId = tokens[0]; String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); Map options = OptionParsersAndExtractors.parseListZonesOptions(query); return listZones(projectId, options); @@ -408,7 +407,7 @@ private Response handleZoneList(HttpExchange exchange) { private Response handleProjectGet(HttpExchange exchange) { String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); String[] tokens = path.split("/"); - String projectId = tokens[1]; + String projectId = tokens[0]; String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); String[] fields = OptionParsersAndExtractors.parseGetOptions(query); return getProject(projectId, fields); @@ -420,8 +419,8 @@ private Response handleProjectGet(HttpExchange exchange) { private Response handleChangeCreate(HttpExchange exchange) throws IOException { String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); String[] tokens = path.split("/"); - String projectId = tokens[1]; - String zoneName = tokens[3]; + String projectId = tokens[0]; + String zoneName = tokens[2]; String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); String[] fields = OptionParsersAndExtractors.parseGetOptions(query); String requestBody = decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); @@ -432,9 +431,9 @@ private Response handleChangeCreate(HttpExchange exchange) throws IOException { private Response handleChangeGet(HttpExchange exchange) { String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); String[] tokens = path.split("/"); - String projectId = tokens[1]; - String zoneName = tokens[3]; - String changeId = tokens[5]; + String projectId = tokens[0]; + String zoneName = tokens[2]; + String changeId = tokens[4]; String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); String[] fields = OptionParsersAndExtractors.parseGetOptions(query); return getChange(projectId, zoneName, changeId, fields); @@ -443,8 +442,8 @@ private Response handleChangeGet(HttpExchange exchange) { private Response handleChangeList(HttpExchange exchange) { String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); String[] tokens = path.split("/"); - String projectId = tokens[1]; - String zoneName = tokens[3]; + String projectId = tokens[0]; + String zoneName = tokens[2]; String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); Map options = OptionParsersAndExtractors.parseListChangesOptions(query); return listChanges(projectId, zoneName, options); @@ -453,8 +452,8 @@ private Response handleChangeList(HttpExchange exchange) { private Response handleDnsRecordList(HttpExchange exchange) { String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); String[] tokens = path.split("/"); - String projectId = tokens[1]; - String zoneName = tokens[3]; + String projectId = tokens[0]; + String zoneName = tokens[2]; String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); Map options = OptionParsersAndExtractors.parseListDnsRecordsOptions(query); return listDnsRecords(projectId, zoneName, options); @@ -466,7 +465,7 @@ private Response handleDnsRecordList(HttpExchange exchange) { private Response handleZoneCreate(HttpExchange exchange) throws IOException { String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); String[] tokens = path.split("/"); - String projectId = tokens[1]; + String projectId = tokens[0]; String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); String[] options = OptionParsersAndExtractors.parseGetOptions(query); String requestBody = decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); @@ -539,7 +538,9 @@ private static void writeResponse(HttpExchange exchange, Response response) { try { exchange.getResponseHeaders().add("Connection", "close"); exchange.sendResponseHeaders(response.code(), response.body().length()); - outputStream.write(response.body().getBytes(StandardCharsets.UTF_8)); + if (response.code() != 204) { + outputStream.write(response.body().getBytes(StandardCharsets.UTF_8)); + } outputStream.close(); } catch (IOException e) { log.log(Level.WARNING, "IOException encountered when sending response.", e); @@ -569,18 +570,20 @@ private static String decodeContent(Headers headers, InputStream inputStream) th } /** - * Generates a JSON response. Tested. + * Generates a JSON response. + * + * @param context managedZones | projects | rrsets | changes */ - static Response toListResponse(List serializedObjects, String pageToken, + static Response toListResponse(List serializedObjects, String context, String pageToken, boolean includePageToken) { // start building response StringBuilder responseBody = new StringBuilder(); - responseBody.append("{\"projects\": ["); + responseBody.append("{\"").append(context).append("\": ["); Joiner.on(",").appendTo(responseBody, serializedObjects); responseBody.append("]"); // add page token only if exists and is asked for if (pageToken != null && includePageToken) { - responseBody.append(",\"pageToken\": \"").append(pageToken).append("\""); + responseBody.append(",\"nextPageToken\": \"").append(pageToken).append("\""); } responseBody.append("}"); return new Response(HTTP_OK, responseBody.toString()); @@ -627,14 +630,13 @@ static List randomNameservers() { } /** - * Returns a hex string id (used for a dns record) unique withing the set of wrappers. + * Returns a hex string id (used for a dns record) unique within the set of wrappers. */ static String getUniqueId(List wrappers) { TreeSet ids = Sets.newTreeSet(Lists.transform(wrappers, new Function() { - @Nullable @Override - public String apply(@Nullable RrsetWrapper input) { + public String apply(RrsetWrapper input) { return input.id() == null ? "null" : input.id(); } })); @@ -663,7 +665,8 @@ static boolean matchesCriteria(ResourceRecordSet record, String name, String typ } /** - * Returns a project container. Never returns null because we assume that all projects exists. + * Returns a project container. Never returns {@code null} because we assume that all projects + * exists. */ ProjectContainer findProject(String projectId) { ProjectContainer defaultProject = createProject(projectId); @@ -672,7 +675,7 @@ ProjectContainer findProject(String projectId) { } /** - * Returns a zone container. Returns null if zone does not exist within project. + * Returns a zone container. Returns {@code null} if zone does not exist within project. */ ZoneContainer findZone(String projectId, String zoneName) { ProjectContainer projectContainer = findProject(projectId); // never null @@ -680,7 +683,7 @@ ZoneContainer findZone(String projectId, String zoneName) { } /** - * Returns a change found by its id. Returns null if such a change does not exist. + * Returns a change found by its id. Returns {@code null} if such a change does not exist. */ Change findChange(String projectId, String zoneName, String changeId) { ZoneContainer wrapper = findZone(projectId, zoneName); @@ -688,7 +691,7 @@ Change findChange(String projectId, String zoneName, String changeId) { } /** - * Returns a response to get change call. + * Returns a response to getChange service call. */ Response getChange(String projectId, String zoneName, String changeId, String[] fields) { Change change = findChange(projectId, zoneName, changeId); @@ -711,11 +714,10 @@ Response getChange(String projectId, String zoneName, String changeId, String[] "Error when serializing change %s in managed zone %s in project %s.", changeId, zoneName, projectId)); } - // todo(mderka) Test field options within #665. } /** - * Returns a response to get zone call. + * Returns a response to getZone service call. */ Response getZone(String projectId, String zoneName, String[] fields) { ZoneContainer container = findZone(projectId, zoneName); @@ -730,7 +732,6 @@ Response getZone(String projectId, String zoneName, String[] fields) { return Error.INTERNAL_ERROR.response(String.format( "Error when serializing managed zone %s in project %s.", zoneName, projectId)); } - // todo(mderka) Test field options within #665. } /** @@ -748,7 +749,6 @@ Response getProject(String projectId, String[] fields) { return Error.INTERNAL_ERROR.response( String.format("Error when serializing project %s.", projectId)); } - // todo(mderka) Test field options within #665. } /** @@ -798,6 +798,7 @@ Response createZone(String projectId, ManagedZone zone, String[] fields) { ManagedZone completeZone = new ManagedZone(); completeZone.setName(zone.getName()); completeZone.setDnsName(zone.getDnsName()); + completeZone.setDescription(zone.getDescription()); completeZone.setNameServerSet(zone.getNameServerSet()); completeZone.setCreationTime(ISODateTimeFormat.dateTime().withZoneUTC() .print(System.currentTimeMillis())); @@ -823,7 +824,6 @@ Response createZone(String projectId, ManagedZone zone, String[] fields) { return Error.INTERNAL_ERROR.response( String.format("Error when serializing managed zone %s.", result.getName())); } - // todo(mderka) Test field options within #665. } /** @@ -873,7 +873,6 @@ we will reset all IDs in this range (all of them are valid) */ String.format("Error when serializing change %s in managed zone %s in project %s.", result.getId(), zoneName, projectId)); } - // todo(mderka) Test field options within #665. } /** @@ -969,13 +968,13 @@ Response listZones(String projectId, Map options) { ManagedZone zone = zoneContainer.zone(); if (listing) { if (dnsName == null || zone.getDnsName().equals(dnsName)) { - lastZoneName = zone.getName(); if (sizeReached) { // we do not add this, just note that there would be more and there should be a token hasMorePages = true; break; } else { try { + lastZoneName = zone.getName(); serializedZones.addLast(jsonFactory.toString( OptionParsersAndExtractors.extractFields(zone, fields))); } catch (IOException e) { @@ -991,9 +990,8 @@ Response listZones(String projectId, Map options) { sizeReached = (maxResults != null) && maxResults.equals(serializedZones.size()); } boolean includePageToken = - hasMorePages && (fields == null || ImmutableList.copyOf(fields).contains("pageToken")); - return toListResponse(serializedZones, lastZoneName, includePageToken); - // todo(mderka) Test field and listing options within #665. + hasMorePages && (fields == null || ImmutableList.copyOf(fields).contains("nextPageToken")); + return toListResponse(serializedZones, "managedZones", lastZoneName, includePageToken); } /** @@ -1025,12 +1023,12 @@ Response listDnsRecords(String projectId, String zoneName, Map o ResourceRecordSet record = recordWrapper.rrset(); if (listing) { if (matchesCriteria(record, name, type)) { - lastRecordId = recordWrapper.id(); if (sizeReached) { // we do not add this, just note that there would be more and there should be a token hasMorePages = true; break; } else { + lastRecordId = recordWrapper.id(); try { serializedRrsets.addLast(jsonFactory.toString( OptionParsersAndExtractors.extractFields(record, fields))); @@ -1047,9 +1045,8 @@ Response listDnsRecords(String projectId, String zoneName, Map o sizeReached = (maxResults != null) && maxResults.equals(serializedRrsets.size()); } boolean includePageToken = - hasMorePages && (fields == null || ImmutableList.copyOf(fields).contains("pageToken")); - return toListResponse(serializedRrsets, lastRecordId, includePageToken); - // todo(mderka) Test field and listing options within #665. + hasMorePages && (fields == null || ImmutableList.copyOf(fields).contains("nextPageToken")); + return toListResponse(serializedRrsets, "rrsets", lastRecordId, includePageToken); } /** @@ -1061,6 +1058,10 @@ Response listChanges(String projectId, String zoneName, Map opti return response; } ZoneContainer zoneContainer = findZone(projectId, zoneName); + if (zoneContainer == null) { + return Error.NOT_FOUND.response(String.format( + "The 'parameters.managedZone' resource named '%s' does not exist", zoneName)); + } // take a sorted snapshot of the current change list TreeMap changes = new TreeMap<>(); for (Change c : zoneContainer.changes()) { @@ -1088,12 +1089,12 @@ Response listChanges(String projectId, String zoneName, Map opti for (Integer key : keys) { Change change = changes.get(key); if (listing) { - lastChangeId = change.getId(); if (sizeReached) { // we do not add this, just note that there would be more and there should be a token hasMorePages = true; break; } else { + lastChangeId = change.getId(); try { serializedResults.addLast(jsonFactory.toString( OptionParsersAndExtractors.extractFields(change, fields))); @@ -1110,9 +1111,8 @@ Response listChanges(String projectId, String zoneName, Map opti sizeReached = (maxResults != null) && maxResults.equals(serializedResults.size()); } boolean includePageToken = - hasMorePages && (fields == null || ImmutableList.copyOf(fields).contains("pageToken")); - return toListResponse(serializedResults, lastChangeId, includePageToken); - // todo(mderka) Test field and listing options within #665. + hasMorePages && (fields == null || ImmutableList.copyOf(fields).contains("nextPageToken")); + return toListResponse(serializedResults, "changes", lastChangeId, includePageToken); } /** diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/testing/OptionParsersAndExtractors.java b/gcloud-java-dns/src/main/java/com/google/gcloud/testing/OptionParsersAndExtractors.java index f94cb15779ca..26759f7e3ccc 100644 --- a/gcloud-java-dns/src/main/java/com/google/gcloud/testing/OptionParsersAndExtractors.java +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/testing/OptionParsersAndExtractors.java @@ -42,14 +42,19 @@ static Map parseListZonesOptions(String query) { switch (argEntry[0]) { case "fields": // List fields are in the form "managedZones(field1, field2, ...)" + String replaced = argEntry[1].replace("managedZones(", ","); + replaced = replaced.replace(")", ","); + // we will get empty strings, but it does not matter, they will be ignored options.put( "fields", - argEntry[1].substring("managedZones(".length(), argEntry[1].length() - 1) - .split(",")); + replaced.split(",")); break; case "dnsName": options.put("dnsName", argEntry[1]); break; + case "nextPageToken": + options.put("nextPageToken", argEntry[1]); + break; case "pageToken": options.put("pageToken", argEntry[1]); break; @@ -218,14 +223,16 @@ static Map parseListChangesOptions(String query) { String[] argEntry = arg.split("="); switch (argEntry[0]) { case "fields": - // todo we do not support fragmentation in deletions and additions - options.put( - "fields", - argEntry[1].substring("changes(".length(), argEntry[1].length() - 1).split(",")); + // todo we do not support fragmentation in deletions and additions in the library + String replaced = argEntry[1].replace("changes(", ",").replace(")", ","); + options.put("fields", replaced.split(",")); // empty strings will be ignored break; case "name": options.put("name", argEntry[1]); break; + case "nextPageToken": + options.put("nextPageToken", argEntry[1]); + break; case "pageToken": options.put("pageToken", argEntry[1]); break; @@ -255,16 +262,22 @@ static Map parseListDnsRecordsOptions(String query) { String[] argEntry = arg.split("="); switch (argEntry[0]) { case "fields": - options.put( - "fields", - argEntry[1].substring("rrsets(".length(), argEntry[1].length() - 1).split(",")); + String replace = argEntry[1].replace("rrsets(", ","); + replace = replace.replace(")", ","); + options.put("fields", replace.split(",")); // empty strings do not matter break; case "name": options.put("name", argEntry[1]); break; + case "type": + options.put("type", argEntry[1]); + break; case "pageToken": options.put("pageToken", argEntry[1]); break; + case "nextPageToken": + options.put("nextPageToken", argEntry[1]); + break; case "maxResults": // parsing to int is done while handling options.put("maxResults", argEntry[1]); diff --git a/gcloud-java-dns/src/test/java/com/google/gcloud/testing/LocalDnsHelperTest.java b/gcloud-java-dns/src/test/java/com/google/gcloud/testing/LocalDnsHelperTest.java index 1f851045659c..7a97b69846ef 100644 --- a/gcloud-java-dns/src/test/java/com/google/gcloud/testing/LocalDnsHelperTest.java +++ b/gcloud-java-dns/src/test/java/com/google/gcloud/testing/LocalDnsHelperTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -25,11 +26,16 @@ import com.google.api.services.dns.model.Change; import com.google.api.services.dns.model.ManagedZone; +import com.google.api.services.dns.model.Project; import com.google.api.services.dns.model.ResourceRecordSet; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; +import com.google.gcloud.dns.DnsException; +import com.google.gcloud.spi.DefaultDnsRpc; +import com.google.gcloud.spi.DnsRpc; -import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -55,8 +61,14 @@ public class LocalDnsHelperTest { private static final Change CHANGE1 = new Change(); private static final Change CHANGE2 = new Change(); private static final Change CHANGE_KEEP = new Change(); + private static final Change CHANGE_COMPLEX = new Change(); + private static final LocalDnsHelper LOCAL_DNS_HELPER = LocalDnsHelper.create(0L); // synchronous + private static final Map EMPTY_RPC_OPTIONS = ImmutableMap.of(); + private static final DnsRpc RPC = + new DefaultDnsRpc(LOCAL_DNS_HELPER.options()); + private static final String REAL_PROJECT_ID = LOCAL_DNS_HELPER.options().projectId(); private Map optionsMap; - private LocalDnsHelper localDns; + private ManagedZone minimalZone = new ManagedZone(); // to be adjusted as needed @BeforeClass @@ -67,9 +79,11 @@ public static void before() { ZONE1.setName(ZONE_NAME1); ZONE1.setDescription(""); ZONE1.setDnsName(DNS_NAME); + ZONE1.setNameServerSet("somenameserveset"); ZONE2.setName(ZONE_NAME2); ZONE2.setDescription(""); ZONE2.setDnsName(DNS_NAME); + ZONE2.setNameServerSet("somenameserveset"); RRSET2.setName(DNS_NAME); RRSET2.setType(RRSET_TYPE); RRSET_KEEP.setName(DNS_NAME); @@ -79,18 +93,27 @@ public static void before() { CHANGE1.setAdditions(ImmutableList.of(RRSET1, RRSET2)); CHANGE2.setDeletions(ImmutableList.of(RRSET2)); CHANGE_KEEP.setAdditions(ImmutableList.of(RRSET_KEEP)); + CHANGE_COMPLEX.setAdditions(ImmutableList.of(RRSET_KEEP)); + CHANGE_COMPLEX.setDeletions(ImmutableList.of(RRSET_KEEP)); + LOCAL_DNS_HELPER.start(); } @Before public void setUp() { - localDns = LocalDnsHelper.create(0L); // synchronous + resetProjects(); optionsMap = new HashMap<>(); minimalZone = copyZone(ZONE1); } - @After - public void after() { - localDns = null; + private static void resetProjects() { + for (String project : LOCAL_DNS_HELPER.projects().keySet()) { + LOCAL_DNS_HELPER.projects().remove(project); + } + } + + @AfterClass + public static void after() { + LOCAL_DNS_HELPER.stop(); } @Test @@ -109,13 +132,13 @@ public void testGetUniqueId() { @Test public void testFindProject() { - assertEquals(0, localDns.projects().size()); - LocalDnsHelper.ProjectContainer project = localDns.findProject(PROJECT_ID1); + assertEquals(0, LOCAL_DNS_HELPER.projects().size()); + LocalDnsHelper.ProjectContainer project = LOCAL_DNS_HELPER.findProject(PROJECT_ID1); assertNotNull(project); - assertTrue(localDns.projects().containsKey(PROJECT_ID1)); - assertNotNull(localDns.findProject(PROJECT_ID2)); - assertTrue(localDns.projects().containsKey(PROJECT_ID2)); - assertTrue(localDns.projects().containsKey(PROJECT_ID1)); + assertTrue(LOCAL_DNS_HELPER.projects().containsKey(PROJECT_ID1)); + assertNotNull(LOCAL_DNS_HELPER.findProject(PROJECT_ID2)); + assertTrue(LOCAL_DNS_HELPER.projects().containsKey(PROJECT_ID2)); + assertTrue(LOCAL_DNS_HELPER.projects().containsKey(PROJECT_ID1)); assertNotNull(project.zones()); assertEquals(0, project.zones().size()); assertNotNull(project.project()); @@ -124,11 +147,11 @@ public void testFindProject() { @Test public void testCreateAndFindZone() { - LocalDnsHelper.ZoneContainer zone1 = localDns.findZone(PROJECT_ID1, ZONE_NAME1); - assertTrue(localDns.projects().containsKey(PROJECT_ID1)); + LocalDnsHelper.ZoneContainer zone1 = LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE_NAME1); + assertTrue(LOCAL_DNS_HELPER.projects().containsKey(PROJECT_ID1)); assertNull(zone1); - localDns.createZone(PROJECT_ID1, ZONE1, null); // we do not care about options - zone1 = localDns.findZone(PROJECT_ID1, ZONE1.getName()); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); // we do not care about options + zone1 = LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE1.getName()); assertNotNull(zone1); // cannot call equals because id and timestamp got assigned assertEquals(ZONE_NAME1, zone1.zone().getName()); @@ -136,56 +159,99 @@ public void testCreateAndFindZone() { assertTrue(zone1.changes().isEmpty()); assertNotNull(zone1.dnsRecords()); assertEquals(2, zone1.dnsRecords().get(ZONE_NAME1).size()); // default SOA and NS - localDns.createZone(PROJECT_ID2, ZONE1, null); // project does not exits yet - assertEquals(ZONE1.getName(), localDns.findZone(PROJECT_ID2, ZONE_NAME1).zone().getName()); + LOCAL_DNS_HELPER.createZone(PROJECT_ID2, ZONE1, null); // project does not exits yet + assertEquals(ZONE1.getName(), + LOCAL_DNS_HELPER.findZone(PROJECT_ID2, ZONE_NAME1).zone().getName()); + } + + @Test + public void testCreateAndFindZoneUsingRpc() { + // zone does not exist yet + ManagedZone zone1 = RPC.getZone(ZONE_NAME1, EMPTY_RPC_OPTIONS); + assertTrue(LOCAL_DNS_HELPER.projects().containsKey(REAL_PROJECT_ID)); // check internal state + assertNull(zone1); + // create zone + ManagedZone createdZone = RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + assertEquals(ZONE1.getName(), createdZone.getName()); + assertEquals(ZONE1.getDescription(), createdZone.getDescription()); + assertEquals(ZONE1.getDnsName(), createdZone.getDnsName()); + assertEquals(4, createdZone.getNameServers().size()); + // get the same zone zone + ManagedZone zone = RPC.getZone(ZONE1.getName(), EMPTY_RPC_OPTIONS); + assertEquals(createdZone, zone); + // check that default records were created + DnsRpc.ListResult resourceRecordSetListResult + = RPC.listDnsRecords(ZONE1.getName(), EMPTY_RPC_OPTIONS); + assertEquals(2, Lists.newLinkedList(resourceRecordSetListResult.results()).size()); } @Test public void testDeleteZone() { - localDns.createZone(PROJECT_ID1, ZONE1, null); - LocalDnsHelper.Response response = localDns.deleteZone(PROJECT_ID1, ZONE1.getName()); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); + LocalDnsHelper.Response response = LOCAL_DNS_HELPER.deleteZone(PROJECT_ID1, ZONE1.getName()); assertEquals(204, response.code()); // deleting non-existent zone - response = localDns.deleteZone(PROJECT_ID1, ZONE1.getName()); + response = LOCAL_DNS_HELPER.deleteZone(PROJECT_ID1, ZONE1.getName()); assertEquals(404, response.code()); - assertNull(localDns.findZone(PROJECT_ID1, ZONE1.getName())); - localDns.createZone(PROJECT_ID1, ZONE1, null); - localDns.createZone(PROJECT_ID1, ZONE2, null); - assertNotNull(localDns.findZone(PROJECT_ID1, ZONE1.getName())); - assertNotNull(localDns.findZone(PROJECT_ID1, ZONE2.getName())); + assertNull(LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE1.getName())); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE2, null); + assertNotNull(LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE1.getName())); + assertNotNull(LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE2.getName())); // delete in reverse order - response = localDns.deleteZone(PROJECT_ID1, ZONE1.getName()); + response = LOCAL_DNS_HELPER.deleteZone(PROJECT_ID1, ZONE1.getName()); assertEquals(204, response.code()); - assertNull(localDns.findZone(PROJECT_ID1, ZONE1.getName())); - assertNotNull(localDns.findZone(PROJECT_ID1, ZONE2.getName())); - localDns.deleteZone(PROJECT_ID1, ZONE2.getName()); + assertNull(LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE1.getName())); + assertNotNull(LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE2.getName())); + LOCAL_DNS_HELPER.deleteZone(PROJECT_ID1, ZONE2.getName()); assertEquals(204, response.code()); - assertNull(localDns.findZone(PROJECT_ID1, ZONE1.getName())); - assertNull(localDns.findZone(PROJECT_ID1, ZONE2.getName())); + assertNull(LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE1.getName())); + assertNull(LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE2.getName())); + } + + @Test + public void testDeleteZoneUsingRpc() { + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + assertTrue(RPC.deleteZone(ZONE1.getName())); + assertNull(RPC.getZone(ZONE1.getName(), EMPTY_RPC_OPTIONS)); + // deleting non-existent zone + assertFalse(RPC.deleteZone(ZONE1.getName())); + assertNull(RPC.getZone(ZONE1.getName(), EMPTY_RPC_OPTIONS)); + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + RPC.create(ZONE2, EMPTY_RPC_OPTIONS); + assertNotNull(RPC.getZone(ZONE1.getName(), EMPTY_RPC_OPTIONS)); + assertNotNull(RPC.getZone(ZONE2.getName(), EMPTY_RPC_OPTIONS)); + // delete in reverse order + assertTrue(RPC.deleteZone(ZONE1.getName())); + assertNull(RPC.getZone(ZONE1.getName(), EMPTY_RPC_OPTIONS)); + assertNotNull(RPC.getZone(ZONE2.getName(), EMPTY_RPC_OPTIONS)); + assertTrue(RPC.deleteZone(ZONE2.getName())); + assertNull(RPC.getZone(ZONE1.getName(), EMPTY_RPC_OPTIONS)); + assertNull(RPC.getZone(ZONE2.getName(), EMPTY_RPC_OPTIONS)); } @Test public void testCreateAndApplyChange() { - localDns = LocalDnsHelper.create(5 * 1000L); // we will be using threads here - localDns.createZone(PROJECT_ID1, ZONE1, null); - assertNull(localDns.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); + LocalDnsHelper localDnsThreaded = LocalDnsHelper.create(5 * 1000L); // using threads here + localDnsThreaded.createZone(PROJECT_ID1, ZONE1, null); + assertNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); LocalDnsHelper.Response response - = localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add + = localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add assertEquals(200, response.code()); - assertNotNull(localDns.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); - assertNull(localDns.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); - localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add - response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add + assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); + assertNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); + localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add + response = localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add assertEquals(200, response.code()); - assertNotNull(localDns.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); - assertNotNull(localDns.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); - localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); // delete - assertNotNull(localDns.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); - assertNotNull(localDns.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); - assertNotNull(localDns.findZone(PROJECT_ID1, ZONE_NAME1).findChange("3")); - localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE_KEEP, null); // id is "4" + assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); + assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); + localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); // delete + assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); + assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); + assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("3")); + localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE_KEEP, null); // id is "4" // check execution - Change change = localDns.findChange(PROJECT_ID1, ZONE_NAME1, "4"); + Change change = localDnsThreaded.findChange(PROJECT_ID1, ZONE_NAME1, "4"); for (int i = 0; i < 10 && !change.getStatus().equals("done"); i++) { // change has not been finished yet; wait at most 20 seconds // it takes 5 seconds for the thread to kick in in the first place @@ -197,26 +263,70 @@ public void testCreateAndApplyChange() { } assertEquals("done", change.getStatus()); List list = - localDns.findZone(PROJECT_ID1, ZONE_NAME1).dnsRecords().get(ZONE_NAME1); + localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).dnsRecords().get(ZONE_NAME1); assertTrue(list.contains(new LocalDnsHelper.RrsetWrapper(RRSET_KEEP))); + localDnsThreaded.stop(); + } + + @Test + public void testCreateAndApplyChangeUsingRpc() { + // not using threads + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + assertNull(RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS)); + //add + Change createdChange = RPC.applyChangeRequest(ZONE1.getName(), CHANGE1, EMPTY_RPC_OPTIONS); + assertEquals(createdChange.getAdditions(), CHANGE1.getAdditions()); + assertEquals(createdChange.getDeletions(), CHANGE1.getDeletions()); + assertNotNull(createdChange.getStartTime()); + assertEquals("1", createdChange.getId()); + Change retrievedChange = RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS); + assertEquals(createdChange, retrievedChange); + assertNull(RPC.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); + try { + Change anotherChange = RPC.applyChangeRequest(ZONE1.getName(), CHANGE1, EMPTY_RPC_OPTIONS); + } catch (DnsException ex) { + assertEquals(409, ex.code()); + } + assertNotNull(RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS)); + assertNull(RPC.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); + // delete + RPC.applyChangeRequest(ZONE1.getName(), CHANGE2, EMPTY_RPC_OPTIONS); + assertNotNull(RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS)); + assertNotNull(RPC.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); + Change last = RPC.applyChangeRequest(ZONE1.getName(), CHANGE_KEEP, EMPTY_RPC_OPTIONS); + assertEquals("done", last.getStatus()); + // todo(mderka) replace with real call + List list = + LOCAL_DNS_HELPER.findZone(REAL_PROJECT_ID, ZONE_NAME1).dnsRecords().get(ZONE_NAME1); + assertTrue(list.contains(new LocalDnsHelper.RrsetWrapper(RRSET_KEEP))); + Iterable results = + RPC.listDnsRecords(ZONE1.getName(), EMPTY_RPC_OPTIONS).results(); + boolean ok = false; + for (ResourceRecordSet dnsRecord : results) { + if (dnsRecord.getName().equals(RRSET_KEEP.getName()) + && dnsRecord.getType().equals(RRSET_KEEP.getType())) { + ok = true; + } + } + assertTrue(ok); } @Test public void testFindChange() { - localDns.createZone(PROJECT_ID1, ZONE1, null); - Change change = localDns.findChange(PROJECT_ID1, ZONE1.getName(), "somerandomchange"); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); + Change change = LOCAL_DNS_HELPER.findChange(PROJECT_ID1, ZONE1.getName(), "somerandomchange"); assertNull(change); - localDns.createChange(PROJECT_ID1, ZONE1.getName(), CHANGE1, null); + LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE1.getName(), CHANGE1, null); // changes are sequential so we should find ID 1 - assertNotNull(localDns.findChange(PROJECT_ID1, ZONE1.getName(), "1")); + assertNotNull(LOCAL_DNS_HELPER.findChange(PROJECT_ID1, ZONE1.getName(), "1")); // add another - localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); - assertNotNull(localDns.findChange(PROJECT_ID1, ZONE1.getName(), "1")); - assertNotNull(localDns.findChange(PROJECT_ID1, ZONE1.getName(), "2")); + LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); + assertNotNull(LOCAL_DNS_HELPER.findChange(PROJECT_ID1, ZONE1.getName(), "1")); + assertNotNull(LOCAL_DNS_HELPER.findChange(PROJECT_ID1, ZONE1.getName(), "2")); // try to find non-existent change - assertNull(localDns.findChange(PROJECT_ID1, ZONE1.getName(), "3")); + assertNull(LOCAL_DNS_HELPER.findChange(PROJECT_ID1, ZONE1.getName(), "3")); // try to find a change in yet non-existent project - assertNull(localDns.findChange(PROJECT_ID2, ZONE1.getName(), "3")); + assertNull(LOCAL_DNS_HELPER.findChange(PROJECT_ID2, ZONE1.getName(), "3")); } @Test @@ -230,71 +340,375 @@ public void testRandomNameServers() { @Test public void testGetProject() { // only interested in no exceptions and non-null response here - assertNotNull(localDns.getProject(PROJECT_ID1, null)); - assertNotNull(localDns.getProject(PROJECT_ID2, null)); + assertNotNull(LOCAL_DNS_HELPER.getProject(PROJECT_ID1, null)); + assertNotNull(LOCAL_DNS_HELPER.getProject(PROJECT_ID2, null)); + Project project = RPC.getProject(EMPTY_RPC_OPTIONS); + assertNotNull(project.getQuota()); + assertEquals(REAL_PROJECT_ID, project.getId()); + // fields options + Map options = new HashMap<>(); + options.put(DnsRpc.Option.FIELDS, "number"); + project = RPC.getProject(options); + assertNull(project.getId()); + assertNotNull(project.getNumber()); + assertNull(project.getQuota()); + options.put(DnsRpc.Option.FIELDS, "id"); + project = RPC.getProject(options); + assertNotNull(project.getId()); + assertNull(project.getNumber()); + assertNull(project.getQuota()); + options.put(DnsRpc.Option.FIELDS, "quota"); + project = RPC.getProject(options); + assertNull(project.getId()); + assertNull(project.getNumber()); + assertNotNull(project.getQuota()); } @Test public void testGetZone() { // non-existent - LocalDnsHelper.Response response = localDns.getZone(PROJECT_ID1, ZONE_NAME1, null); + LocalDnsHelper.Response response = LOCAL_DNS_HELPER.getZone(PROJECT_ID1, ZONE_NAME1, null); assertEquals(404, response.code()); assertTrue(response.body().contains("does not exist")); // existent - localDns.createZone(PROJECT_ID1, ZONE1, null); - response = localDns.getZone(PROJECT_ID1, ZONE1.getName(), null); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); + response = LOCAL_DNS_HELPER.getZone(PROJECT_ID1, ZONE1.getName(), null); assertEquals(200, response.code()); } + @Test + public void testGetZoneUsingRpc() { + // non-existent + assertNull(RPC.getZone(ZONE_NAME1, EMPTY_RPC_OPTIONS)); + // existent + ManagedZone created = RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + ManagedZone zone = RPC.getZone(ZONE_NAME1, EMPTY_RPC_OPTIONS); + assertEquals(created, zone); + assertEquals(ZONE1.getName(), zone.getName()); + // field options + Map options = new HashMap<>(); + options.put(DnsRpc.Option.FIELDS, "id"); + zone = RPC.getZone(ZONE1.getName(), options); + assertNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNull(zone.getNameServerSet()); + assertNotNull(zone.getId()); + options.put(DnsRpc.Option.FIELDS, "creationTime"); + zone = RPC.getZone(ZONE1.getName(), options); + assertNotNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNull(zone.getNameServerSet()); + assertNull(zone.getId()); + options.put(DnsRpc.Option.FIELDS, "dnsName"); + zone = RPC.getZone(ZONE1.getName(), options); + assertNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNotNull(zone.getDnsName()); + assertNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNull(zone.getNameServerSet()); + assertNull(zone.getId()); + options.put(DnsRpc.Option.FIELDS, "description"); + zone = RPC.getZone(ZONE1.getName(), options); + assertNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNotNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNull(zone.getNameServerSet()); + assertNull(zone.getId()); + options.put(DnsRpc.Option.FIELDS, "nameServers"); + zone = RPC.getZone(ZONE1.getName(), options); + assertNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNull(zone.getDescription()); + assertNotNull(zone.getNameServers()); + assertNull(zone.getNameServerSet()); + assertNull(zone.getId()); + options.put(DnsRpc.Option.FIELDS, "nameServerSet"); + zone = RPC.getZone(ZONE1.getName(), options); + assertNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNotNull(zone.getNameServerSet()); + assertNull(zone.getId()); + // several combined + options.put(DnsRpc.Option.FIELDS, "nameServerSet,description,id,name"); + zone = RPC.getZone(ZONE1.getName(), options); + assertNull(zone.getCreationTime()); + assertNotNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNotNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNotNull(zone.getNameServerSet()); + assertNotNull(zone.getId()); + } + @Test public void testCreateZone() { // only interested in no exceptions and non-null response here - LocalDnsHelper.Response response = localDns.createZone(PROJECT_ID1, ZONE1, null); + LocalDnsHelper.Response response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); assertEquals(200, response.code()); - assertEquals(1, localDns.projects().get(PROJECT_ID1).zones().size()); + assertEquals(1, LOCAL_DNS_HELPER.projects().get(PROJECT_ID1).zones().size()); try { - localDns.createZone(PROJECT_ID1, null, null); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, null, null); fail("Zone cannot be null"); } catch (NullPointerException ex) { // expected } // create zone twice - response = localDns.createZone(PROJECT_ID1, ZONE1, null); + response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); assertEquals(409, response.code()); assertTrue(response.body().contains("already exists")); } + @Test + public void testCreateZoneUsingRpc() { + ManagedZone created = RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + assertEquals(created, LOCAL_DNS_HELPER.findZone(REAL_PROJECT_ID, ZONE1.getName()).zone()); + ManagedZone zone = RPC.getZone(ZONE_NAME1, EMPTY_RPC_OPTIONS); + assertEquals(created, zone); + try { + RPC.create(null, EMPTY_RPC_OPTIONS); + fail("Zone cannot be null"); + } catch (DnsException ex) { + // expected + assertEquals(400, ex.code()); + assertTrue(ex.getMessage().contains("entity.managedZone")); + } + // create zone twice + try { + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + } catch (DnsException ex) { + // expected + assertEquals(409, ex.code()); + assertTrue(ex.getMessage().contains("already exists")); + } + // field options + resetProjects(); + Map options = new HashMap<>(); + options.put(DnsRpc.Option.FIELDS, "id"); + zone = RPC.create(ZONE1, options); + assertNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNull(zone.getNameServerSet()); + assertNotNull(zone.getId()); + resetProjects(); + options.put(DnsRpc.Option.FIELDS, "creationTime"); + zone = RPC.create(ZONE1, options); + assertNotNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNull(zone.getNameServerSet()); + assertNull(zone.getId()); + options.put(DnsRpc.Option.FIELDS, "dnsName"); + resetProjects(); + zone = RPC.create(ZONE1, options); + assertNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNotNull(zone.getDnsName()); + assertNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNull(zone.getNameServerSet()); + assertNull(zone.getId()); + options.put(DnsRpc.Option.FIELDS, "description"); + resetProjects(); + zone = RPC.create(ZONE1, options); + assertNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNotNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNull(zone.getNameServerSet()); + assertNull(zone.getId()); + options.put(DnsRpc.Option.FIELDS, "nameServers"); + resetProjects(); + zone = RPC.create(ZONE1, options); + assertNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNull(zone.getDescription()); + assertNotNull(zone.getNameServers()); + assertNull(zone.getNameServerSet()); + assertNull(zone.getId()); + options.put(DnsRpc.Option.FIELDS, "nameServerSet"); + resetProjects(); + zone = RPC.create(ZONE1, options); + assertNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNotNull(zone.getNameServerSet()); + assertNull(zone.getId()); + // several combined + options.put(DnsRpc.Option.FIELDS, "nameServerSet,description,id,name"); + resetProjects(); + zone = RPC.create(ZONE1, options); + assertNull(zone.getCreationTime()); + assertNotNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNotNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNotNull(zone.getNameServerSet()); + assertNotNull(zone.getId()); + } + @Test public void testCreateChange() { // non-existent zone LocalDnsHelper.Response response = - localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); + LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); assertEquals(404, response.code()); - // existent zone - assertNotNull(localDns.createZone(PROJECT_ID1, ZONE1, null)); - assertNull(localDns.findChange(PROJECT_ID1, ZONE_NAME1, "1")); - response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); + assertNotNull(LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null)); + assertNull(LOCAL_DNS_HELPER.findChange(PROJECT_ID1, ZONE_NAME1, "1")); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); assertEquals(200, response.code()); - assertNotNull(localDns.findChange(PROJECT_ID1, ZONE_NAME1, "1")); + assertNotNull(LOCAL_DNS_HELPER.findChange(PROJECT_ID1, ZONE_NAME1, "1")); + } + + @Test + public void testCreateChangeUsingRpc() { + // non-existent zone + try { + RPC.applyChangeRequest(ZONE_NAME1, CHANGE1, EMPTY_RPC_OPTIONS); + } catch (DnsException ex) { + assertEquals(404, ex.code()); + } + // existent zone + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + assertNull(RPC.getChangeRequest(ZONE_NAME1, "1", EMPTY_RPC_OPTIONS)); + Change created = RPC.applyChangeRequest(ZONE1.getName(), CHANGE1, EMPTY_RPC_OPTIONS); + assertEquals(created, RPC.getChangeRequest(ZONE_NAME1, "1", EMPTY_RPC_OPTIONS)); + // field options + RPC.applyChangeRequest(ZONE1.getName(), CHANGE_KEEP, EMPTY_RPC_OPTIONS); + Map options = new HashMap<>(); + options.put(DnsRpc.Option.FIELDS, "additions"); + Change complex = RPC.applyChangeRequest(ZONE1.getName(), CHANGE_COMPLEX, options); + assertNotNull(complex.getAdditions()); + assertNull(complex.getDeletions()); + assertNull(complex.getId()); + assertNull(complex.getStartTime()); + assertNull(complex.getStatus()); + options.put(DnsRpc.Option.FIELDS, "deletions"); + complex = RPC.applyChangeRequest(ZONE1.getName(), CHANGE_COMPLEX, options); + assertNull(complex.getAdditions()); + assertNotNull(complex.getDeletions()); + assertNull(complex.getId()); + assertNull(complex.getStartTime()); + assertNull(complex.getStatus()); + options.put(DnsRpc.Option.FIELDS, "id"); + complex = RPC.applyChangeRequest(ZONE1.getName(), CHANGE_COMPLEX, options); + assertNull(complex.getAdditions()); + assertNull(complex.getDeletions()); + assertNotNull(complex.getId()); + assertNull(complex.getStartTime()); + assertNull(complex.getStatus()); + options.put(DnsRpc.Option.FIELDS, "startTime"); + complex = RPC.applyChangeRequest(ZONE1.getName(), CHANGE_COMPLEX, options); + assertNull(complex.getAdditions()); + assertNull(complex.getDeletions()); + assertNull(complex.getId()); + assertNotNull(complex.getStartTime()); + assertNull(complex.getStatus()); + options.put(DnsRpc.Option.FIELDS, "status"); + complex = RPC.applyChangeRequest(ZONE1.getName(), CHANGE_COMPLEX, options); + assertNull(complex.getAdditions()); + assertNull(complex.getDeletions()); + assertNull(complex.getId()); + assertNull(complex.getStartTime()); + assertNotNull(complex.getStatus()); } @Test public void testGetChange() { // existent - assertEquals(200, localDns.createZone(PROJECT_ID1, ZONE1, null).code()); - assertEquals(200, localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null).code()); - assertEquals(200, localDns.getChange(PROJECT_ID1, ZONE_NAME1, "1", null).code()); + assertEquals(200, LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null).code()); + assertEquals(200, LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null).code()); + assertEquals(200, LOCAL_DNS_HELPER.getChange(PROJECT_ID1, ZONE_NAME1, "1", null).code()); // non-existent - LocalDnsHelper.Response response = localDns.getChange(PROJECT_ID1, ZONE_NAME1, "2", null); + LocalDnsHelper.Response response = + LOCAL_DNS_HELPER.getChange(PROJECT_ID1, ZONE_NAME1, "2", null); assertEquals(404, response.code()); assertTrue(response.body().contains("parameters.changeId")); // non-existent zone - response = localDns.getChange(PROJECT_ID1, ZONE_NAME2, "1", null); + response = LOCAL_DNS_HELPER.getChange(PROJECT_ID1, ZONE_NAME2, "1", null); assertEquals(404, response.code()); assertTrue(response.body().contains("parameters.managedZone")); } + @Test + public void testGetChangeUsingRpc() { + // existent + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + Change created = RPC.applyChangeRequest(ZONE1.getName(), CHANGE1, EMPTY_RPC_OPTIONS); + Change retrieved = RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS); + assertEquals(created, retrieved); + // non-existent + assertNull(RPC.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); + // non-existent zone + try { + RPC.getChangeRequest(ZONE_NAME2, "1", EMPTY_RPC_OPTIONS); + } catch (DnsException ex) { + // expected + assertEquals(404, ex.code()); + } + // field options + RPC.applyChangeRequest(ZONE1.getName(), CHANGE_KEEP, EMPTY_RPC_OPTIONS); + Change change = RPC.applyChangeRequest(ZONE1.getName(), CHANGE_COMPLEX, EMPTY_RPC_OPTIONS); + Map options = new HashMap<>(); + options.put(DnsRpc.Option.FIELDS, "additions"); + Change complex = RPC.getChangeRequest(ZONE1.getName(), change.getId(), options); + assertNotNull(complex.getAdditions()); + assertNull(complex.getDeletions()); + assertNull(complex.getId()); + assertNull(complex.getStartTime()); + assertNull(complex.getStatus()); + options.put(DnsRpc.Option.FIELDS, "deletions"); + complex = RPC.getChangeRequest(ZONE1.getName(), change.getId(), options); + assertNull(complex.getAdditions()); + assertNotNull(complex.getDeletions()); + assertNull(complex.getId()); + assertNull(complex.getStartTime()); + assertNull(complex.getStatus()); + options.put(DnsRpc.Option.FIELDS, "id"); + complex = RPC.getChangeRequest(ZONE1.getName(), change.getId(), options); + assertNull(complex.getAdditions()); + assertNull(complex.getDeletions()); + assertNotNull(complex.getId()); + assertNull(complex.getStartTime()); + assertNull(complex.getStatus()); + options.put(DnsRpc.Option.FIELDS, "startTime"); + complex = RPC.getChangeRequest(ZONE1.getName(), change.getId(), options); + assertNull(complex.getAdditions()); + assertNull(complex.getDeletions()); + assertNull(complex.getId()); + assertNotNull(complex.getStartTime()); + assertNull(complex.getStatus()); + options.put(DnsRpc.Option.FIELDS, "status"); + complex = RPC.getChangeRequest(ZONE1.getName(), change.getId(), options); + assertNull(complex.getAdditions()); + assertNull(complex.getDeletions()); + assertNull(complex.getId()); + assertNull(complex.getStartTime()); + assertNotNull(complex.getStatus()); + } + @Test public void testListZones() { // only interested in no exceptions and non-null response here @@ -302,36 +716,175 @@ public void testListZones() { optionsMap.put("fields", null); optionsMap.put("pageToken", null); optionsMap.put("maxResults", null); - LocalDnsHelper.Response response = localDns.listZones(PROJECT_ID1, optionsMap); + LocalDnsHelper.Response response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(200, response.code()); // some zones exists - localDns.createZone(PROJECT_ID1, ZONE1, null); - response = localDns.listZones(PROJECT_ID1, optionsMap); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); + response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(200, response.code()); - localDns.createZone(PROJECT_ID1, ZONE2, null); - response = localDns.listZones(PROJECT_ID1, optionsMap); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE2, null); + response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(200, response.code()); // error in options optionsMap.put("maxResults", "aaa"); - response = localDns.listZones(PROJECT_ID1, optionsMap); + response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(400, response.code()); optionsMap.put("maxResults", "0"); - response = localDns.listZones(PROJECT_ID1, optionsMap); + response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(400, response.code()); optionsMap.put("maxResults", "-1"); - response = localDns.listZones(PROJECT_ID1, optionsMap); + response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(400, response.code()); optionsMap.put("maxResults", "15"); - response = localDns.listZones(PROJECT_ID1, optionsMap); + response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(200, response.code()); optionsMap.put("dnsName", "aaa"); - response = localDns.listZones(PROJECT_ID1, optionsMap); + response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(400, response.code()); optionsMap.put("dnsName", "aaa."); - response = localDns.listZones(PROJECT_ID1, optionsMap); + response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(200, response.code()); } + @Test + public void testListZonesUsingRpc() { + Iterable results = RPC.listZones(EMPTY_RPC_OPTIONS).results(); + ImmutableList zones = ImmutableList.copyOf(results); + assertEquals(0, zones.size()); + // some zones exists + ManagedZone created = RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + results = RPC.listZones(EMPTY_RPC_OPTIONS).results(); + zones = ImmutableList.copyOf(results); + assertEquals(created, zones.get(0)); + assertEquals(1, zones.size()); + created = RPC.create(ZONE2, EMPTY_RPC_OPTIONS); + results = RPC.listZones(EMPTY_RPC_OPTIONS).results(); + zones = ImmutableList.copyOf(results); + assertEquals(2, zones.size()); + assertTrue(zones.contains(created)); + // error in options + Map options = new HashMap<>(); + options.put(DnsRpc.Option.PAGE_SIZE, 0); + try { + RPC.listZones(options); + } catch (DnsException ex) { + // expected + assertEquals(400, ex.code()); + } + options = new HashMap<>(); + options.put(DnsRpc.Option.PAGE_SIZE, -1); + try { + RPC.listZones(options); + } catch (DnsException ex) { + // expected + assertEquals(400, ex.code()); + } + // ok size + options = new HashMap<>(); + options.put(DnsRpc.Option.PAGE_SIZE, 1); + results = RPC.listZones(options).results(); + zones = ImmutableList.copyOf(results); + assertEquals(1, zones.size()); + // dns name problems + options = new HashMap<>(); + options.put(DnsRpc.Option.DNS_NAME, "aaa"); + try { + RPC.listZones(options); + } catch (DnsException ex) { + // expected + assertEquals(400, ex.code()); + } + // ok name + options = new HashMap<>(); + options.put(DnsRpc.Option.DNS_NAME, "aaaa."); + results = RPC.listZones(options).results(); + zones = ImmutableList.copyOf(results); + assertEquals(0, zones.size()); + // field options + options = new HashMap<>(); + options.put(DnsRpc.Option.FIELDS, "managedZones(id)"); + ManagedZone zone = RPC.listZones(options).results().iterator().next(); + assertNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNull(zone.getNameServerSet()); + assertNotNull(zone.getId()); + options.put(DnsRpc.Option.FIELDS, "managedZones(creationTime)"); + zone = RPC.listZones(options).results().iterator().next(); + assertNotNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNull(zone.getNameServerSet()); + assertNull(zone.getId()); + options.put(DnsRpc.Option.FIELDS, "managedZones(dnsName)"); + zone = RPC.listZones(options).results().iterator().next(); + assertNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNotNull(zone.getDnsName()); + assertNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNull(zone.getNameServerSet()); + assertNull(zone.getId()); + options.put(DnsRpc.Option.FIELDS, "managedZones(description)"); + zone = RPC.listZones(options).results().iterator().next(); + assertNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNotNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNull(zone.getNameServerSet()); + assertNull(zone.getId()); + options.put(DnsRpc.Option.FIELDS, "managedZones(nameServers)"); + zone = RPC.listZones(options).results().iterator().next(); + assertNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNull(zone.getDescription()); + assertNotNull(zone.getNameServers()); + assertNull(zone.getNameServerSet()); + assertNull(zone.getId()); + options.put(DnsRpc.Option.FIELDS, "managedZones(nameServerSet)"); + DnsRpc.ListResult managedZoneListResult = RPC.listZones(options); + zone = managedZoneListResult.results().iterator().next(); + assertNull(managedZoneListResult.pageToken()); + assertNull(zone.getCreationTime()); + assertNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNotNull(zone.getNameServerSet()); + assertNull(zone.getId()); + // several combined + options.put(DnsRpc.Option.FIELDS, + "managedZones(nameServerSet,description,id,name),nextPageToken"); + options.put(DnsRpc.Option.PAGE_SIZE, 1); + managedZoneListResult = RPC.listZones(options); + zone = managedZoneListResult.results().iterator().next(); + assertNull(zone.getCreationTime()); + assertNotNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNotNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNotNull(zone.getNameServerSet()); + assertNotNull(zone.getId()); + assertEquals(zone.getName(), managedZoneListResult.pageToken()); + // paging + options = new HashMap<>(); + options.put(DnsRpc.Option.PAGE_SIZE, 1); + managedZoneListResult = RPC.listZones(options); + ImmutableList page1 = ImmutableList.copyOf(managedZoneListResult.results()); + assertEquals(1, page1.size()); + options.put(DnsRpc.Option.PAGE_TOKEN, managedZoneListResult.pageToken()); + managedZoneListResult = RPC.listZones(options); + ImmutableList page2 = ImmutableList.copyOf(managedZoneListResult.results()); + assertEquals(1, page2.size()); + assertNotEquals(page1.get(0), page2.get(0)); + } + @Test public void testListDnsRecords() { // only interested in no exceptions and non-null response here @@ -341,49 +894,196 @@ public void testListDnsRecords() { optionsMap.put("pageToken", null); optionsMap.put("maxResults", null); // no zone exists - LocalDnsHelper.Response response = localDns.listDnsRecords(PROJECT_ID1, ZONE_NAME1, + LocalDnsHelper.Response response = LOCAL_DNS_HELPER.listDnsRecords(PROJECT_ID1, ZONE_NAME1, optionsMap); assertEquals(404, response.code()); // zone exists but has no records - localDns.createZone(PROJECT_ID1, ZONE1, null); - localDns.listDnsRecords(PROJECT_ID1, ZONE_NAME1, optionsMap); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); + LOCAL_DNS_HELPER.listDnsRecords(PROJECT_ID1, ZONE_NAME1, optionsMap); // zone has records - localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); - response = localDns.listDnsRecords(PROJECT_ID1, ZONE_NAME1, optionsMap); + LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); + response = LOCAL_DNS_HELPER.listDnsRecords(PROJECT_ID1, ZONE_NAME1, optionsMap); assertEquals(200, response.code()); // error in options optionsMap.put("maxResults", "aaa"); - response = localDns.listZones(PROJECT_ID1, optionsMap); + response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(400, response.code()); optionsMap.put("maxResults", "0"); - response = localDns.listZones(PROJECT_ID1, optionsMap); + response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(400, response.code()); optionsMap.put("maxResults", "-1"); - response = localDns.listZones(PROJECT_ID1, optionsMap); + response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(400, response.code()); optionsMap.put("maxResults", "15"); - response = localDns.listZones(PROJECT_ID1, optionsMap); + response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(200, response.code()); optionsMap.put("name", "aaa"); - response = localDns.listZones(PROJECT_ID1, optionsMap); + response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(400, response.code()); optionsMap.put("name", "aaa."); - response = localDns.listZones(PROJECT_ID1, optionsMap); + response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(200, response.code()); optionsMap.put("name", null); optionsMap.put("type", "A"); - response = localDns.listZones(PROJECT_ID1, optionsMap); + response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(400, response.code()); optionsMap.put("name", "aaa."); optionsMap.put("type", "a"); - response = localDns.listZones(PROJECT_ID1, optionsMap); + response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(400, response.code()); optionsMap.put("name", "aaaa."); optionsMap.put("type", "A"); - response = localDns.listZones(PROJECT_ID1, optionsMap); + response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); assertEquals(200, response.code()); } + @Test + public void testListDnsRecordsUsingRpc() { + // no zone exists + try { + RPC.listDnsRecords(ZONE_NAME1, EMPTY_RPC_OPTIONS); + } catch (DnsException ex) { + // expected + assertEquals(404, ex.code()); + } + // zone exists but has no records + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + Iterable results = + RPC.listDnsRecords(ZONE_NAME1, EMPTY_RPC_OPTIONS).results(); + ImmutableList records = ImmutableList.copyOf(results); + assertEquals(2, records.size()); // contains default NS and SOA + // zone has records + RPC.applyChangeRequest(ZONE_NAME1, CHANGE_KEEP, EMPTY_RPC_OPTIONS); + results = RPC.listDnsRecords(ZONE_NAME1, EMPTY_RPC_OPTIONS).results(); + records = ImmutableList.copyOf(results); + assertEquals(3, records.size()); + // error in options + Map options = new HashMap<>(); + options.put(DnsRpc.Option.PAGE_SIZE, 0); + try { + RPC.listDnsRecords(ZONE1.getName(), options); + } catch (DnsException ex) { + // expected + assertEquals(400, ex.code()); + } + options.put(DnsRpc.Option.PAGE_SIZE, -1); + try { + RPC.listDnsRecords(ZONE1.getName(), options); + } catch (DnsException ex) { + // expected + assertEquals(400, ex.code()); + } + options.put(DnsRpc.Option.PAGE_SIZE, 1); + results = RPC.listDnsRecords(ZONE1.getName(), options).results(); + records = ImmutableList.copyOf(results); + assertEquals(1, records.size()); + options.put(DnsRpc.Option.PAGE_SIZE, 15); + results = RPC.listDnsRecords(ZONE1.getName(), options).results(); + records = ImmutableList.copyOf(results); + assertEquals(3, records.size()); + + // dnsName filter + options = new HashMap<>(); + options.put(DnsRpc.Option.NAME, "aaa"); + try { + RPC.listDnsRecords(ZONE1.getName(), options); + } catch (DnsException ex) { + // expected + assertEquals(400, ex.code()); + } + options.put(DnsRpc.Option.NAME, "aaa."); + results = RPC.listDnsRecords(ZONE1.getName(), options).results(); + records = ImmutableList.copyOf(results); + assertEquals(0, records.size()); + options.put(DnsRpc.Option.NAME, null); + options.put(DnsRpc.Option.DNS_TYPE, "A"); + try { + RPC.listDnsRecords(ZONE1.getName(), options); + } catch (DnsException ex) { + // expected + assertEquals(400, ex.code()); + } + options.put(DnsRpc.Option.NAME, "aaa."); + options.put(DnsRpc.Option.DNS_TYPE, "a"); + try { + RPC.listDnsRecords(ZONE1.getName(), options); + } catch (DnsException ex) { + // expected + assertEquals(400, ex.code()); + } + options.put(DnsRpc.Option.NAME, DNS_NAME); + options.put(DnsRpc.Option.DNS_TYPE, "SOA"); + results = RPC.listDnsRecords(ZONE1.getName(), options).results(); + records = ImmutableList.copyOf(results); + assertEquals(1, records.size()); + // field options + options = new HashMap<>(); + options.put(DnsRpc.Option.FIELDS, "rrsets(name)"); + DnsRpc.ListResult resourceRecordSetListResult = + RPC.listDnsRecords(ZONE1.getName(), options); + records = ImmutableList.copyOf(resourceRecordSetListResult.results()); + ResourceRecordSet record = records.get(0); + assertNotNull(record.getName()); + assertNull(record.getRrdatas()); + assertNull(record.getType()); + assertNull(record.getTtl()); + assertNull(resourceRecordSetListResult.pageToken()); + options.put(DnsRpc.Option.FIELDS, "rrsets(rrdatas)"); + resourceRecordSetListResult = RPC.listDnsRecords(ZONE1.getName(), options); + records = ImmutableList.copyOf(resourceRecordSetListResult.results()); + record = records.get(0); + assertNull(record.getName()); + assertNotNull(record.getRrdatas()); + assertNull(record.getType()); + assertNull(record.getTtl()); + assertNull(resourceRecordSetListResult.pageToken()); + options.put(DnsRpc.Option.FIELDS, "rrsets(ttl)"); + resourceRecordSetListResult = RPC.listDnsRecords(ZONE1.getName(), options); + records = ImmutableList.copyOf(resourceRecordSetListResult.results()); + record = records.get(0); + assertNull(record.getName()); + assertNull(record.getRrdatas()); + assertNull(record.getType()); + assertNotNull(record.getTtl()); + assertNull(resourceRecordSetListResult.pageToken()); + options.put(DnsRpc.Option.FIELDS, "rrsets(type)"); + resourceRecordSetListResult = RPC.listDnsRecords(ZONE1.getName(), options); + records = ImmutableList.copyOf(resourceRecordSetListResult.results()); + record = records.get(0); + assertNull(record.getName()); + assertNull(record.getRrdatas()); + assertNotNull(record.getType()); + assertNull(record.getTtl()); + assertNull(resourceRecordSetListResult.pageToken()); + options.put(DnsRpc.Option.FIELDS, "nextPageToken"); + resourceRecordSetListResult = RPC.listDnsRecords(ZONE1.getName(), options); + records = ImmutableList.copyOf(resourceRecordSetListResult.results()); + record = records.get(0); + assertNull(record.getName()); + assertNull(record.getRrdatas()); + assertNull(record.getType()); + assertNull(record.getTtl()); + assertNull(resourceRecordSetListResult.pageToken()); + options.put(DnsRpc.Option.FIELDS, "nextPageToken,rrsets(name,rrdatas)"); + options.put(DnsRpc.Option.PAGE_SIZE, 1); + resourceRecordSetListResult = RPC.listDnsRecords(ZONE1.getName(), options); + records = ImmutableList.copyOf(resourceRecordSetListResult.results()); + assertEquals(1, records.size()); + record = records.get(0); + assertNotNull(record.getName()); + assertNotNull(record.getRrdatas()); + assertNull(record.getType()); + assertNull(record.getTtl()); + assertNotNull(resourceRecordSetListResult.pageToken()); + // paging + options.put(DnsRpc.Option.PAGE_TOKEN, resourceRecordSetListResult.pageToken()); + resourceRecordSetListResult = RPC.listDnsRecords(ZONE1.getName(), options); + records = ImmutableList.copyOf(resourceRecordSetListResult.results()); + assertEquals(1, records.size()); + ResourceRecordSet nextRecord = records.get(0); + assertNotEquals(record, nextRecord); + } + @Test public void testListChanges() { optionsMap.put("sortBy", null); @@ -392,72 +1092,214 @@ public void testListChanges() { optionsMap.put("pageToken", null); optionsMap.put("maxResults", null); // no such zone exists - LocalDnsHelper.Response response = localDns.listDnsRecords(PROJECT_ID1, ZONE_NAME1, optionsMap); + LocalDnsHelper.Response response = + LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); assertEquals(404, response.code()); assertTrue(response.body().contains("managedZone")); // zone exists but has no changes - localDns.createZone(PROJECT_ID1, ZONE1, null); - assertNotNull(localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap)); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); + assertNotNull(LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap)); // zone has changes - localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); - assertNotNull(localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap)); - localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); - localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); - localDns.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); - assertNotNull(localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap)); + LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); + assertNotNull(LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap)); + LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); + LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); + LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); + assertNotNull(LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap)); // error in options optionsMap.put("maxResults", "aaa"); - response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); assertEquals(400, response.code()); optionsMap.put("maxResults", "0"); - response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); assertEquals(400, response.code()); optionsMap.put("maxResults", "-1"); - response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); assertEquals(400, response.code()); optionsMap.put("maxResults", "15"); - response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); - assertEquals(200, response.code()); - optionsMap.put("dnsName", "aaa"); - response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); - assertEquals(400, response.code()); - optionsMap.put("dnsName", "aaa."); - response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); assertEquals(200, response.code()); optionsMap.put("sortBy", "changeSequence"); - response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); assertEquals(200, response.code()); optionsMap.put("sortBy", "something else"); - response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); assertEquals(400, response.code()); assertTrue(response.body().contains("Allowed values: [changesequence]")); optionsMap.put("sortBy", "ChAnGeSeQuEnCe"); // is not case sensitive - response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); assertEquals(200, response.code()); optionsMap.put("sortOrder", "ascending"); - response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); assertEquals(200, response.code()); optionsMap.put("sortBy", null); optionsMap.put("sortOrder", "descending"); - response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); assertEquals(200, response.code()); optionsMap.put("sortOrder", "somethingelse"); - response = localDns.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); + response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); assertEquals(400, response.code()); assertTrue(response.body().contains("parameters.sortOrder")); } + @Test + public void testListChangesUsingRpc() { + // no such zone exists + try { + RPC.listChangeRequests(ZONE_NAME1, EMPTY_RPC_OPTIONS); + } catch (DnsException ex) { + // expected + assertEquals(404, ex.code()); + } + // zone exists but has no changes + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + Iterable results = RPC.listChangeRequests(ZONE1.getName(), EMPTY_RPC_OPTIONS).results(); + ImmutableList changes = ImmutableList.copyOf(results); + assertEquals(0, changes.size()); + // zone has changes + RPC.applyChangeRequest(ZONE1.getName(), CHANGE1, EMPTY_RPC_OPTIONS); + RPC.applyChangeRequest(ZONE1.getName(), CHANGE2, EMPTY_RPC_OPTIONS); + RPC.applyChangeRequest(ZONE1.getName(), CHANGE_KEEP, EMPTY_RPC_OPTIONS); + results = RPC.listChangeRequests(ZONE1.getName(), EMPTY_RPC_OPTIONS).results(); + changes = ImmutableList.copyOf(results); + assertEquals(3, changes.size()); + // error in options + Map options = new HashMap<>(); + options.put(DnsRpc.Option.PAGE_SIZE, 0); + try { + RPC.listChangeRequests(ZONE1.getName(), options); + } catch (DnsException ex) { + // expected + assertEquals(400, ex.code()); + } + options.put(DnsRpc.Option.PAGE_SIZE, -1); + try { + RPC.listChangeRequests(ZONE1.getName(), options); + } catch (DnsException ex) { + // expected + assertEquals(400, ex.code()); + } + options.put(DnsRpc.Option.PAGE_SIZE, 15); + try { + RPC.listChangeRequests(ZONE1.getName(), options); + } catch (DnsException ex) { + // expected + assertEquals(400, ex.code()); + } + options = new HashMap<>(); + options.put(DnsRpc.Option.SORTING_ORDER, "descending"); + results = RPC.listChangeRequests(ZONE1.getName(), options).results(); + ImmutableList descending = ImmutableList.copyOf(results); + results = RPC.listChangeRequests(ZONE1.getName(), EMPTY_RPC_OPTIONS).results(); + ImmutableList ascending = ImmutableList.copyOf(results); + int size = 3; + assertEquals(size, descending.size()); + for (int i = 0; i < size; i++) { + assertEquals(descending.get(i), ascending.get(size - i - 1)); + } + options.put(DnsRpc.Option.SORTING_ORDER, "something else"); + try { + RPC.listChangeRequests(ZONE1.getName(), options); + } catch (DnsException ex) { + // expected + assertEquals(400, ex.code()); + } + // field options + RPC.applyChangeRequest(ZONE1.getName(), CHANGE_COMPLEX, EMPTY_RPC_OPTIONS); + options = new HashMap<>(); + options.put(DnsRpc.Option.SORTING_ORDER, "descending"); + options.put(DnsRpc.Option.FIELDS, "changes(additions)"); + DnsRpc.ListResult changeListResult = + RPC.listChangeRequests(ZONE1.getName(), options); + changes = ImmutableList.copyOf(changeListResult.results()); + Change complex = changes.get(0); + assertNotNull(complex.getAdditions()); + assertNull(complex.getDeletions()); + assertNull(complex.getId()); + assertNull(complex.getStartTime()); + assertNull(complex.getStatus()); + assertNull(changeListResult.pageToken()); + options.put(DnsRpc.Option.FIELDS, "changes(deletions)"); + changeListResult = RPC.listChangeRequests(ZONE1.getName(), options); + changes = ImmutableList.copyOf(changeListResult.results()); + complex = changes.get(0); + assertNull(complex.getAdditions()); + assertNotNull(complex.getDeletions()); + assertNull(complex.getId()); + assertNull(complex.getStartTime()); + assertNull(complex.getStatus()); + assertNull(changeListResult.pageToken()); + options.put(DnsRpc.Option.FIELDS, "changes(id)"); + changeListResult = RPC.listChangeRequests(ZONE1.getName(), options); + changes = ImmutableList.copyOf(changeListResult.results()); + complex = changes.get(0); + assertNull(complex.getAdditions()); + assertNull(complex.getDeletions()); + assertNotNull(complex.getId()); + assertNull(complex.getStartTime()); + assertNull(complex.getStatus()); + assertNull(changeListResult.pageToken()); + options.put(DnsRpc.Option.FIELDS, "changes(startTime)"); + changeListResult = RPC.listChangeRequests(ZONE1.getName(), options); + changes = ImmutableList.copyOf(changeListResult.results()); + complex = changes.get(0); + assertNull(complex.getAdditions()); + assertNull(complex.getDeletions()); + assertNull(complex.getId()); + assertNotNull(complex.getStartTime()); + assertNull(complex.getStatus()); + assertNull(changeListResult.pageToken()); + options.put(DnsRpc.Option.FIELDS, "changes(status)"); + changeListResult = RPC.listChangeRequests(ZONE1.getName(), options); + changes = ImmutableList.copyOf(changeListResult.results()); + complex = changes.get(0); + assertNull(complex.getAdditions()); + assertNull(complex.getDeletions()); + assertNull(complex.getId()); + assertNull(complex.getStartTime()); + assertNotNull(complex.getStatus()); + assertNull(changeListResult.pageToken()); + options.put(DnsRpc.Option.FIELDS, "nextPageToken"); + options.put(DnsRpc.Option.PAGE_SIZE, 1); + changeListResult = RPC.listChangeRequests(ZONE1.getName(), options); + changes = ImmutableList.copyOf(changeListResult.results()); + complex = changes.get(0); + assertNull(complex.getAdditions()); + assertNull(complex.getDeletions()); + assertNull(complex.getId()); + assertNull(complex.getStartTime()); + assertNull(complex.getStatus()); + assertNotNull(changeListResult.pageToken()); + // paging + options.put(DnsRpc.Option.FIELDS, "nextPageToken,changes(id)"); + options.put(DnsRpc.Option.PAGE_SIZE, 1); + changeListResult = RPC.listChangeRequests(ZONE1.getName(), options); + changes = ImmutableList.copyOf(changeListResult.results()); + assertEquals(1, changes.size()); + final Change first = changes.get(0); + assertNotNull(changeListResult.pageToken()); + options.put(DnsRpc.Option.PAGE_TOKEN, changeListResult.pageToken()); + changeListResult = RPC.listChangeRequests(ZONE1.getName(), options); + changes = ImmutableList.copyOf(changeListResult.results()); + assertEquals(1, changes.size()); + Change second = changes.get(0); + assertNotEquals(first, second); + } + @Test public void testToListResponse() { LocalDnsHelper.Response response = LocalDnsHelper.toListResponse( - Lists.newArrayList("some", "multiple", "words"), "IncludeThisPageToken", true); + Lists.newArrayList("some", "multiple", "words"), "contextA", "IncludeThisPageToken", true); assertTrue(response.body().contains("IncludeThisPageToken")); + assertTrue(response.body().contains("contextA")); response = LocalDnsHelper.toListResponse( - Lists.newArrayList("some", "multiple", "words"), "IncludeThisPageToken", false); + Lists.newArrayList("some", "multiple", "words"), "contextB", "IncludeThisPageToken", false); assertFalse(response.body().contains("IncludeThisPageToken")); + assertTrue(response.body().contains("contextB")); response = LocalDnsHelper.toListResponse( - Lists.newArrayList("some", "multiple", "words"), null, true); + Lists.newArrayList("some", "multiple", "words"), "contextC", null, true); assertFalse(response.body().contains("pageToken")); + assertTrue(response.body().contains("contextC")); } @Test @@ -511,45 +1353,45 @@ public void testCreateZoneValidatesZone() { // no name ManagedZone copy = copyZone(minimalZone); copy.setName(null); - LocalDnsHelper.Response response = localDns.createZone(PROJECT_ID1, copy, null); + LocalDnsHelper.Response response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy, null); assertEquals(400, response.code()); assertTrue(response.body().contains("entity.managedZone.name")); // no description copy = copyZone(minimalZone); copy.setDescription(null); - response = localDns.createZone(PROJECT_ID1, copy, null); + response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy, null); assertEquals(400, response.code()); assertTrue(response.body().contains("entity.managedZone.description")); // no dns name copy = copyZone(minimalZone); copy.setDnsName(null); - response = localDns.createZone(PROJECT_ID1, copy, null); + response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy, null); assertEquals(400, response.code()); assertTrue(response.body().contains("entity.managedZone.dnsName")); // zone name is a number copy = copyZone(minimalZone); copy.setName("123456"); - response = localDns.createZone(PROJECT_ID1, copy, null); + response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy, null); assertEquals(400, response.code()); assertTrue(response.body().contains("entity.managedZone.name")); assertTrue(response.body().contains("Invalid")); // dns name does not end with period copy = copyZone(minimalZone); copy.setDnsName("aaaaaa.com"); - response = localDns.createZone(PROJECT_ID1, copy, null); + response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy, null); assertEquals(400, response.code()); assertTrue(response.body().contains("entity.managedZone.dnsName")); assertTrue(response.body().contains("Invalid")); // dns name is reserved copy = copyZone(minimalZone); copy.setDnsName("com."); - response = localDns.createZone(PROJECT_ID1, copy, null); + response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy, null); assertEquals(400, response.code()); assertTrue(response.body().contains("not available to be created.")); // empty description should pass copy = copyZone(minimalZone); copy.setDescription(""); - response = localDns.createZone(PROJECT_ID1, copy, null); + response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy, null); assertEquals(200, response.code()); } @@ -632,10 +1474,10 @@ public void testCheckRrset() { valid.setTtl(500); Change validChange = new Change(); validChange.setAdditions(ImmutableList.of(valid)); - localDns.createZone(PROJECT_ID1, ZONE1, null); - localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); + LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); // delete with field mismatch - LocalDnsHelper.ZoneContainer zone = localDns.findZone(PROJECT_ID1, ZONE_NAME1); + LocalDnsHelper.ZoneContainer zone = LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE_NAME1); valid.setTtl(valid.getTtl() + 20); LocalDnsHelper.Response response = LocalDnsHelper.checkRrset(valid, zone, 0, "deletions"); assertEquals(412, response.code()); @@ -735,7 +1577,7 @@ public void testCheckChange() { assertTrue(response.body().contains("additions[0].type")); validA.setType("A"); // null rrdata - List temp = validA.getRrdatas(); // preserve + final List temp = validA.getRrdatas(); // preserve validA.setRrdatas(null); response = LocalDnsHelper.checkChange(validChange, zoneContainer); assertEquals(400, response.code()); @@ -762,9 +1604,9 @@ public void testAdditionsMeetDeletions() { validA.setRrdatas(ImmutableList.of("0.255.1.5")); Change validChange = new Change(); validChange.setAdditions(ImmutableList.of(validA)); - localDns.createZone(PROJECT_ID1, ZONE1, null); - localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); - LocalDnsHelper.ZoneContainer container = localDns.findZone(PROJECT_ID1, ZONE_NAME1); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); + LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + LocalDnsHelper.ZoneContainer container = LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE_NAME1); LocalDnsHelper.Response response = LocalDnsHelper.additionsMeetDeletions(ImmutableList.of(validA), null, container); assertEquals(409, response.code()); @@ -780,23 +1622,23 @@ public void testCreateChangeValidatesChangeContent() { validA.setRrdatas(ImmutableList.of("0.255.1.5")); Change validChange = new Change(); validChange.setAdditions(ImmutableList.of(validA)); - localDns.createZone(PROJECT_ID1, ZONE1, null); - localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); + LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); LocalDnsHelper.Response response = - localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); assertEquals(409, response.code()); assertTrue(response.body().contains("already exists")); // delete with field mismatch Change delete = new Change(); validA.setTtl(20); // mismatch delete.setDeletions(ImmutableList.of(validA)); - response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, delete, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, delete, null); assertEquals(412, response.code()); assertTrue(response.body().contains("entity.change.deletions[0]")); // delete and add SOA Change addition = new Change(); ImmutableList rrsetWrappers - = localDns.findZone(PROJECT_ID1, ZONE_NAME1).dnsRecords().get(ZONE_NAME1); + = LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE_NAME1).dnsRecords().get(ZONE_NAME1); LinkedList deletions = new LinkedList<>(); LinkedList additions = new LinkedList<>(); for (LocalDnsHelper.RrsetWrapper wrapper : rrsetWrappers) { @@ -811,12 +1653,12 @@ public void testCreateChangeValidatesChangeContent() { } delete.setDeletions(deletions); addition.setAdditions(additions); - response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, delete, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, delete, null); assertEquals(400, response.code()); assertTrue(response.body().contains( "zone must contain exactly one resource record set of type 'SOA' at the apex")); assertTrue(response.body().contains("deletions[0]")); - response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, addition, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, addition, null); assertEquals(400, response.code()); assertTrue(response.body().contains( "zone must contain exactly one resource record set of type 'SOA' at the apex")); @@ -836,24 +1678,24 @@ public void testCreateChangeValidatesChangeContent() { } delete.setDeletions(deletions); addition.setAdditions(additions); - response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, delete, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, delete, null); assertEquals(400, response.code()); assertTrue(response.body().contains( "zone must contain exactly one resource record set of type 'NS' at the apex")); - response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, addition, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, addition, null); assertEquals(400, response.code()); assertTrue(response.body().contains( "zone must contain exactly one resource record set of type 'NS' at the apex")); assertTrue(response.body().contains("additions[0]")); // change (delete + add) addition.setDeletions(deletions); - response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, addition, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, addition, null); assertEquals(200, response.code()); } @Test public void testCreateChangeValidatesChange() { - localDns.createZone(PROJECT_ID1, ZONE1, null); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); ResourceRecordSet validA = new ResourceRecordSet(); validA.setName(ZONE1.getDnsName()); validA.setType("A"); @@ -867,52 +1709,52 @@ public void testCreateChangeValidatesChange() { Change invalidChange = new Change(); invalidChange.setAdditions(ImmutableList.of(invalidA)); LocalDnsHelper.Response response = - localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); assertEquals(200, response.code()); - response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, invalidChange, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, invalidChange, null); assertEquals(400, response.code()); // only empty additions/deletions Change empty = new Change(); empty.setAdditions(ImmutableList.of()); empty.setDeletions(ImmutableList.of()); - response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, empty, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, empty, null); assertEquals(400, response.code()); assertTrue(response.body().contains( "The 'entity.change' parameter is required but was missing.")); // non-matching name validA.setName(ZONE1.getDnsName() + ".aaa."); - response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); assertEquals(400, response.code()); assertTrue(response.body().contains("additions[0].name")); // wrong type validA.setName(ZONE1.getDnsName()); // revert validA.setType("ABCD"); - response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); assertEquals(400, response.code()); assertTrue(response.body().contains("additions[0].type")); // wrong ttl validA.setType("A"); // revert validA.setTtl(-1); - response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); assertEquals(400, response.code()); assertTrue(response.body().contains("additions[0].ttl")); validA.setTtl(null); // revert // null name validA.setName(null); - response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); assertEquals(400, response.code()); assertTrue(response.body().contains("additions[0].name")); validA.setName(ZONE1.getDnsName()); // null type validA.setType(null); - response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); assertEquals(400, response.code()); assertTrue(response.body().contains("additions[0].type")); validA.setType("A"); // null rrdata - List temp = validA.getRrdatas(); // preserve + final List temp = validA.getRrdatas(); // preserve validA.setRrdatas(null); - response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); assertEquals(400, response.code()); assertTrue(response.body().contains("additions[0].rrdata")); validA.setRrdatas(temp); @@ -923,7 +1765,7 @@ public void testCreateChangeValidatesChange() { nonExistent.setRrdatas(ImmutableList.of(":::::::")); Change delete = new Change(); delete.setDeletions(ImmutableList.of(nonExistent)); - response = localDns.createChange(PROJECT_ID1, ZONE_NAME1, delete, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, delete, null); assertEquals(404, response.code()); assertTrue(response.body().contains("deletions[0]")); } From 38000570eaa34f45a28dfe72ca5366ca8fd3e147 Mon Sep 17 00:00:00 2001 From: Martin Derka Date: Wed, 24 Feb 2016 10:11:02 -0800 Subject: [PATCH 3/9] Refactored URL parsing and removed unnecessary doc in local helper. Refactored to use AtomicReference instead of singleton Map. Adjusted tests of paging to test that pages contain expected objects. A few other minor test adjustmnets. Added check and test for deleting non-empy zone. --- .../{ => dns}/testing/LocalDnsHelper.java | 264 +++++++----------- .../testing/OptionParsers.java} | 42 +-- .../com/google/gcloud/spi/DefaultDnsRpc.java | 6 +- .../{ => dns}/testing/LocalDnsHelperTest.java | 123 ++++---- 4 files changed, 178 insertions(+), 257 deletions(-) rename gcloud-java-dns/src/main/java/com/google/gcloud/{ => dns}/testing/LocalDnsHelper.java (85%) rename gcloud-java-dns/src/main/java/com/google/gcloud/{testing/OptionParsersAndExtractors.java => dns/testing/OptionParsers.java} (87%) rename gcloud-java-dns/src/test/java/com/google/gcloud/{ => dns}/testing/LocalDnsHelperTest.java (96%) diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/testing/LocalDnsHelper.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java similarity index 85% rename from gcloud-java-dns/src/main/java/com/google/gcloud/testing/LocalDnsHelper.java rename to gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java index b8483bc4fcd7..493811e78301 100644 --- a/gcloud-java-dns/src/main/java/com/google/gcloud/testing/LocalDnsHelper.java +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.google.gcloud.testing; +package com.google.gcloud.dns.testing; -import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.net.InetAddresses.isInetAddress; import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static java.net.HttpURLConnection.HTTP_OK; @@ -66,12 +66,11 @@ import java.util.TreeSet; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.GZIPInputStream; -import javax.annotation.Nullable; - /** * A utility to create local Google Cloud DNS mock. * @@ -101,6 +100,8 @@ public class LocalDnsHelper { ImmutableSet.of("gzip", "x-gzip"); private static final List TYPES = ImmutableList.of("A", "AAAA", "CNAME", "MX", "NAPTR", "NS", "PTR", "SOA", "SPF", "SRV", "TXT"); + private static final TreeSet FORBIDDEN = Sets.newTreeSet( + ImmutableList.of("google.com.", "com.", "example.com.", "net.", "org.")); static { try { @@ -144,9 +145,8 @@ private enum CallRegex { static class RrsetWrapper { static final Function WRAP_FUNCTION = new Function() { - @Nullable @Override - public RrsetWrapper apply(@Nullable ResourceRecordSet input) { + public RrsetWrapper apply(ResourceRecordSet input) { return new RrsetWrapper(input); } }; @@ -229,20 +229,20 @@ static class ZoneContainer { * changes. The key for the map is always the zone name. The collection of records is immutable * and must always exist, i.e., dnsRecords.get(zone.getName()) is never {@code null}. */ - private final ConcurrentSkipListMap> - dnsRecords = new ConcurrentSkipListMap<>(); + private final AtomicReference> + dnsRecords = new AtomicReference<>(ImmutableList.of()); private final ConcurrentLinkedQueue changes = new ConcurrentLinkedQueue<>(); ZoneContainer(ManagedZone zone) { this.zone = zone; - this.dnsRecords.put(zone.getName(), ImmutableList.of()); + this.dnsRecords.set(ImmutableList.of()); } ManagedZone zone() { return zone; } - ConcurrentSkipListMap> dnsRecords() { + AtomicReference> dnsRecords() { return dnsRecords; } @@ -283,6 +283,7 @@ private enum Error { INTERNAL_ERROR(500, "global", "internalError", "INTERNAL_ERROR"), BAD_REQUEST(400, "global", "badRequest", "BAD_REQUEST"), INVALID(400, "global", "invalid", "INVALID"), + CONTAINER_NOT_EMPTY(400, "global", "containerNotEmpty", "CONTAINER_NOT_EMPTY"), NOT_AVAILABLE(400, "global", "managedZoneDnsNameNotAvailable", "NOT_AVAILABLE"), NOT_FOUND(404, "global", "notFound", "NOT_FOUND"), ALREADY_EXISTS(409, "global", "alreadyExists", "ALREADY_EXISTS"), @@ -329,30 +330,36 @@ private class RequestHandler implements HttpHandler { * Chooses the proper handler for a request. */ private Response pickHandler(HttpExchange exchange, CallRegex regex) { + String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); + String[] tokens = path.split("/"); + String projectId = tokens != null && tokens.length > 0 ? tokens[0] : null; + String zoneName = tokens != null && tokens.length > 2 ? tokens[2] : null; + String changeId = tokens != null && tokens.length > 4 ? tokens[4] : null; + String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); switch (regex) { case CHANGE_GET: - return handleChangeGet(exchange); + return handleChangeGet(projectId, zoneName, changeId, query); case CHANGE_LIST: - return handleChangeList(exchange); + return handleChangeList(projectId, zoneName, query); case ZONE_GET: - return handleZoneGet(exchange); + return handleZoneGet(projectId, zoneName, query); case ZONE_DELETE: - return handleZoneDelete(exchange); + return handleZoneDelete(projectId, zoneName); case ZONE_LIST: - return handleZoneList(exchange); + return handleZoneList(projectId, query); case PROJECT_GET: - return handleProjectGet(exchange); + return handleProjectGet(projectId, query); case RECORD_LIST: - return handleDnsRecordList(exchange); + return handleDnsRecordList(projectId, zoneName, query); case ZONE_CREATE: try { - return handleZoneCreate(exchange); + return handleZoneCreate(exchange, projectId, query); } catch (IOException ex) { return Error.BAD_REQUEST.response(ex.getMessage()); } case CHANGE_CREATE: try { - return handleChangeCreate(exchange); + return handleChangeCreate(exchange, projectId, zoneName, query); } catch (IOException ex) { return Error.BAD_REQUEST.response(ex.getMessage()); } @@ -374,100 +381,62 @@ public void handle(HttpExchange exchange) throws IOException { } } // could not be matched, the service returns 404 page not found here - writeResponse(exchange, Error.NOT_FOUND.response("The url does not match any API call.")); + writeResponse(exchange, Error.NOT_FOUND.response(String.format( + "The url %s does not match any API call.", exchange.getRequestURI()))); } - private Response handleZoneDelete(HttpExchange exchange) { - String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); - String[] tokens = path.split("/"); - String projectId = tokens[0]; - String zoneName = tokens[2]; + private Response handleZoneDelete(String projectId, String zoneName) { return deleteZone(projectId, zoneName); } - private Response handleZoneGet(HttpExchange exchange) { - String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); - String[] tokens = path.split("/"); - String projectId = tokens[0]; - String zoneName = tokens[2]; - String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); - String[] fields = OptionParsersAndExtractors.parseGetOptions(query); + private Response handleZoneGet(String projectId, String zoneName, String query) { + String[] fields = OptionParsers.parseGetOptions(query); return getZone(projectId, zoneName, fields); } - private Response handleZoneList(HttpExchange exchange) { - String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); - String[] tokens = path.split("/"); - String projectId = tokens[0]; - String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); - Map options = OptionParsersAndExtractors.parseListZonesOptions(query); + private Response handleZoneList(String projectId, String query) { + Map options = OptionParsers.parseListZonesOptions(query); return listZones(projectId, options); } - private Response handleProjectGet(HttpExchange exchange) { - String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); - String[] tokens = path.split("/"); - String projectId = tokens[0]; - String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); - String[] fields = OptionParsersAndExtractors.parseGetOptions(query); + private Response handleProjectGet(String projectId, String query) { + String[] fields = OptionParsers.parseGetOptions(query); return getProject(projectId, fields); } /** * @throws IOException if the request cannot be parsed. */ - private Response handleChangeCreate(HttpExchange exchange) throws IOException { - String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); - String[] tokens = path.split("/"); - String projectId = tokens[0]; - String zoneName = tokens[2]; - String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); - String[] fields = OptionParsersAndExtractors.parseGetOptions(query); + private Response handleChangeCreate(HttpExchange exchange, String projectId, String zoneName, + String query) throws IOException { + String[] fields = OptionParsers.parseGetOptions(query); String requestBody = decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); Change change = jsonFactory.fromString(requestBody, Change.class); return createChange(projectId, zoneName, change, fields); } - private Response handleChangeGet(HttpExchange exchange) { - String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); - String[] tokens = path.split("/"); - String projectId = tokens[0]; - String zoneName = tokens[2]; - String changeId = tokens[4]; - String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); - String[] fields = OptionParsersAndExtractors.parseGetOptions(query); + private Response handleChangeGet(String projectId, String zoneName, + String changeId, String query) { + String[] fields = OptionParsers.parseGetOptions(query); return getChange(projectId, zoneName, changeId, fields); } - private Response handleChangeList(HttpExchange exchange) { - String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); - String[] tokens = path.split("/"); - String projectId = tokens[0]; - String zoneName = tokens[2]; - String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); - Map options = OptionParsersAndExtractors.parseListChangesOptions(query); + private Response handleChangeList(String projectId, String zoneName, String query) { + Map options = OptionParsers.parseListChangesOptions(query); return listChanges(projectId, zoneName, options); } - private Response handleDnsRecordList(HttpExchange exchange) { - String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); - String[] tokens = path.split("/"); - String projectId = tokens[0]; - String zoneName = tokens[2]; - String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); - Map options = OptionParsersAndExtractors.parseListDnsRecordsOptions(query); + private Response handleDnsRecordList(String projectId, String zoneName, String query) { + Map options = OptionParsers.parseListDnsRecordsOptions(query); return listDnsRecords(projectId, zoneName, options); } /** * @throws IOException if the request cannot be parsed. */ - private Response handleZoneCreate(HttpExchange exchange) throws IOException { - String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); - String[] tokens = path.split("/"); - String projectId = tokens[0]; - String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); - String[] options = OptionParsersAndExtractors.parseGetOptions(query); + private Response handleZoneCreate(HttpExchange exchange, String projectId, String query) + throws IOException { + String[] options = OptionParsers.parseGetOptions(query); String requestBody = decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); ManagedZone zone; try { @@ -501,9 +470,9 @@ ConcurrentSkipListMap projects() { /** * Creates new {@link LocalDnsHelper} instance that listens to requests on the local machine. This - * instance processes changes separate threads. The parameter determines how long a thread should - * wait before processing a change. If it is set to 0, the threading is turned off and the mock - * will behave synchronously. + * instance processes changes in separate thread. The parameter determines how long a thread + * should wait before processing a change. If it is set to 0, the threading is turned off and the + * mock will behave synchronously. * * @param delay delay for processing changes in ms or 0 for synchronous processing */ @@ -512,7 +481,7 @@ public static LocalDnsHelper create(Long delay) { } /** - * Returns a DnsOptions instance that sets the host to use the mock server. + * Returns a {@link DnsOptions} instance that sets the host to use the mock server. */ public DnsOptions options() { return DnsOptions.builder().host("http://localhost:" + port).build(); @@ -547,9 +516,6 @@ private static void writeResponse(HttpExchange exchange, Response response) { } } - /** - * Decodes content of the HttpRequest. - */ private static String decodeContent(Headers headers, InputStream inputStream) throws IOException { List contentEncoding = headers.get("Content-encoding"); InputStream input = inputStream; @@ -558,7 +524,7 @@ private static String decodeContent(Headers headers, InputStream inputStream) th String encoding = contentEncoding.get(0); if (SUPPORTED_COMPRESSION_ENCODINGS.contains(encoding)) { input = new GZIPInputStream(inputStream); - } else if (!encoding.equals("identity")) { + } else if (!"identity".equals(encoding)) { throw new IOException( "The request has the following unsupported HTTP content encoding: " + encoding); } @@ -580,12 +546,12 @@ static Response toListResponse(List serializedObjects, String context, S StringBuilder responseBody = new StringBuilder(); responseBody.append("{\"").append(context).append("\": ["); Joiner.on(",").appendTo(responseBody, serializedObjects); - responseBody.append("]"); + responseBody.append(']'); // add page token only if exists and is asked for if (pageToken != null && includePageToken) { - responseBody.append(",\"nextPageToken\": \"").append(pageToken).append("\""); + responseBody.append(",\"nextPageToken\": \"").append(pageToken).append('"'); } - responseBody.append("}"); + responseBody.append('}'); return new Response(HTTP_OK, responseBody.toString()); } @@ -658,10 +624,7 @@ static boolean matchesCriteria(ResourceRecordSet record, String name, String typ if (type != null && !record.getType().equals(type)) { return false; } - if (name != null && !record.getName().equals(name)) { - return false; - } - return true; + return name == null || record.getName().equals(name); } /** @@ -706,7 +669,7 @@ Response getChange(String projectId, String zoneName, String changeId, String[] return Error.NOT_FOUND.response(String.format( "The 'parameters.changeId' resource named '%s' does not exist.", changeId)); } - Change result = OptionParsersAndExtractors.extractFields(change, fields); + Change result = OptionParsers.extractFields(change, fields); try { return new Response(HTTP_OK, jsonFactory.toString(result)); } catch (IOException e) { @@ -725,7 +688,7 @@ Response getZone(String projectId, String zoneName, String[] fields) { return Error.NOT_FOUND.response(String.format( "The 'parameters.managedZone' resource named '%s' does not exist.", zoneName)); } - ManagedZone result = OptionParsersAndExtractors.extractFields(container.zone(), fields); + ManagedZone result = OptionParsers.extractFields(container.zone(), fields); try { return new Response(HTTP_OK, jsonFactory.toString(result)); } catch (IOException e) { @@ -742,7 +705,7 @@ Response getProject(String projectId, String[] fields) { ProjectContainer defaultProject = createProject(projectId); projects.putIfAbsent(projectId, defaultProject); Project project = projects.get(projectId).project(); // project is now guaranteed to exist - Project result = OptionParsersAndExtractors.extractFields(project, fields); + Project result = OptionParsers.extractFields(project, fields); try { return new Response(HTTP_OK, jsonFactory.toString(result)); } catch (IOException e) { @@ -771,11 +734,18 @@ private ProjectContainer createProject(String projectId) { return new ProjectContainer(project); } - /** - * Deletes a zone. - */ Response deleteZone(String projectId, String zoneName) { ProjectContainer projectContainer = projects.get(projectId); + // check if empty + ZoneContainer zone = findZone(projectId, zoneName); + ImmutableList wrappers = zone == null + ? ImmutableList.of() : zone.dnsRecords().get(); + for (RrsetWrapper current : wrappers) { + if (!ImmutableList.of("NS", "SOA").contains(current.rrset().getType())) { + return Error.CONTAINER_NOT_EMPTY.response(String.format( + "The resource named '%s' cannot be deleted because it is not empty", zoneName)); + } + } ZoneContainer previous = projectContainer.zones.remove(zoneName); return previous == null // map was not in the collection @@ -788,7 +758,6 @@ Response deleteZone(String projectId, String zoneName) { * Creates new managed zone and stores it in the collection. Assumes that project exists. */ Response createZone(String projectId, ManagedZone zone, String[] fields) { - checkNotNull(zone, "Zone to create cannot be null"); // check if the provided data is valid Response errorResponse = checkZone(zone); if (errorResponse != null) { @@ -802,12 +771,11 @@ Response createZone(String projectId, ManagedZone zone, String[] fields) { completeZone.setNameServerSet(zone.getNameServerSet()); completeZone.setCreationTime(ISODateTimeFormat.dateTime().withZoneUTC() .print(System.currentTimeMillis())); - completeZone.setId( - new BigInteger(String.valueOf(Math.abs(ID_GENERATOR.nextLong() % Long.MAX_VALUE)))); + completeZone.setId(BigInteger.valueOf(Math.abs(ID_GENERATOR.nextLong() % Long.MAX_VALUE))); completeZone.setNameServers(randomNameservers()); ZoneContainer zoneContainer = new ZoneContainer(completeZone); // create the default NS and SOA records - zoneContainer.dnsRecords().put(zone.getName(), defaultRecords(completeZone)); + zoneContainer.dnsRecords().set(defaultRecords(completeZone)); // place the zone in the data collection ProjectContainer projectContainer = findProject(projectId); ZoneContainer oldValue = projectContainer.zones().putIfAbsent( @@ -817,7 +785,7 @@ Response createZone(String projectId, ManagedZone zone, String[] fields) { "The resource 'entity.managedZone' named '%s' already exists", completeZone.getName())); } // now return the desired attributes - ManagedZone result = OptionParsersAndExtractors.extractFields(completeZone, fields); + ManagedZone result = OptionParsers.extractFields(completeZone, fields); try { return new Response(HTTP_OK, jsonFactory.toString(result)); } catch (IOException e) { @@ -827,7 +795,7 @@ Response createZone(String projectId, ManagedZone zone, String[] fields) { } /** - * Creates a new change, stores it, and invokes processing in a new thread. + * Creates a new change, stores it, and if delayChange > 0, invokes processing in a new thread. */ Response createChange(String projectId, String zoneName, Change change, String[] fields) { ZoneContainer zoneContainer = findZone(projectId, zoneName); @@ -848,9 +816,9 @@ Response createChange(String projectId, String zoneName, Change change, String[] if (change.getDeletions() != null) { completeChange.setDeletions(ImmutableList.copyOf(change.getDeletions())); } - /* we need to get the proper ID in concurrent environment - the element fell on an index between 0 and maxId - we will reset all IDs in this range (all of them are valid) */ + /* We need set ID for the change. We are working in concurrent environment. We know that the + element fell on an index between 0 and maxId, so we will reset all IDs in this range (all of + them are valid for the respective objects). */ ConcurrentLinkedQueue changeSequence = zoneContainer.changes(); changeSequence.add(completeChange); int maxId = changeSequence.size(); @@ -865,7 +833,7 @@ we will reset all IDs in this range (all of them are valid) */ completeChange.setStartTime(ISODateTimeFormat.dateTime().withZoneUTC() .print(System.currentTimeMillis())); // accepted invokeChange(projectId, zoneName, completeChange.getId()); - Change result = OptionParsersAndExtractors.extractFields(completeChange, fields); + Change result = OptionParsers.extractFields(completeChange, fields); try { return new Response(HTTP_OK, jsonFactory.toString(result)); } catch (IOException e) { @@ -910,11 +878,13 @@ private void applyExistingChange(String projectId, String zoneName, String chang return; // no such change exists, nothing to do } ZoneContainer wrapper = findZone(projectId, zoneName); - ConcurrentSkipListMap> dnsRecords = wrapper.dnsRecords(); + if (wrapper == null) { + return; // no such zone exists; it might have been deleted by another thread + } + AtomicReference> dnsRecords = wrapper.dnsRecords(); while (true) { // managed zone must have a set of records which is not null - ImmutableList original = dnsRecords.get(zoneName); - assert original != null; + ImmutableList original = dnsRecords.get(); List copy = Lists.newLinkedList(original); // apply deletions first List deletions = change.getDeletions(); @@ -934,7 +904,8 @@ private void applyExistingChange(String projectId, String zoneName, String chang } } // make it immutable and replace - boolean success = dnsRecords.replace(zoneName, original, ImmutableList.copyOf(copy)); +// boolean success = dnsRecords.replace(zoneName, original, ImmutableList.copyOf(copy)); + boolean success = dnsRecords.compareAndSet(original, ImmutableList.copyOf(copy)); if (success) { break; // success if no other thread modified the value in the meantime } @@ -976,7 +947,7 @@ Response listZones(String projectId, Map options) { try { lastZoneName = zone.getName(); serializedZones.addLast(jsonFactory.toString( - OptionParsersAndExtractors.extractFields(zone, fields))); + OptionParsers.extractFields(zone, fields))); } catch (IOException e) { return Error.INTERNAL_ERROR.response(String.format( "Error when serializing managed zone %s in project %s", zone.getName(), @@ -1007,7 +978,7 @@ Response listDnsRecords(String projectId, String zoneName, Map o return Error.NOT_FOUND.response(String.format( "The 'parameters.managedZone' resource named '%s' does not exist.", zoneName)); } - List dnsRecords = zoneContainer.dnsRecords().get(zoneName); + List dnsRecords = zoneContainer.dnsRecords().get(); String[] fields = (String[]) options.get("fields"); String name = (String) options.get("name"); String type = (String) options.get("type"); @@ -1031,7 +1002,7 @@ Response listDnsRecords(String projectId, String zoneName, Map o lastRecordId = recordWrapper.id(); try { serializedRrsets.addLast(jsonFactory.toString( - OptionParsersAndExtractors.extractFields(record, fields))); + OptionParsers.extractFields(record, fields))); } catch (IOException e) { return Error.INTERNAL_ERROR.response(String.format( "Error when serializing resource record set in managed zone %s in project %s", @@ -1097,7 +1068,7 @@ Response listChanges(String projectId, String zoneName, Map opti lastChangeId = change.getId(); try { serializedResults.addLast(jsonFactory.toString( - OptionParsersAndExtractors.extractFields(change, fields))); + OptionParsers.extractFields(change, fields))); } catch (IOException e) { return Error.INTERNAL_ERROR.response(String.format( "Error when serializing change %s in managed zone %s in project %s", @@ -1146,9 +1117,7 @@ static Response checkZone(ManagedZone zone) { return Error.INVALID.response( String.format("Invalid value for 'entity.managedZone.dnsName': '%s'", zone.getDnsName())); } - TreeSet forbidden = Sets.newTreeSet( - ImmutableList.of("google.com.", "com.", "example.com.", "net.", "org.")); - if (forbidden.contains(zone.getDnsName())) { + if (FORBIDDEN.contains(zone.getDnsName())) { return Error.NOT_AVAILABLE.response(String.format( "The '%s' managed zone is not available to be created.", zone.getDnsName())); } @@ -1159,11 +1128,8 @@ static Response checkZone(ManagedZone zone) { * Validates a change to be created. */ static Response checkChange(Change change, ZoneContainer zone) { - checkNotNull(zone); - if ((change.getDeletions() != null && change.getDeletions().size() > 0) - || (change.getAdditions() != null && change.getAdditions().size() > 0)) { - // ok, this is what we want - } else { + if ((change.getDeletions() == null || change.getDeletions().size() <= 0) + && (change.getAdditions() == null || change.getAdditions().size() <= 0)) { return Error.REQUIRED.response("The 'entity.change' parameter is required but was missing."); } if (change.getAdditions() != null) { @@ -1226,7 +1192,7 @@ static Response checkRrset(ResourceRecordSet rrset, ZoneContainer zone, int inde if ("deletions".equals(type)) { // check that deletion has a match by name and type boolean found = false; - for (RrsetWrapper rrsetWrapper : zone.dnsRecords().get(zone.zone().getName())) { + for (RrsetWrapper rrsetWrapper : zone.dnsRecords().get()) { ResourceRecordSet wrappedRrset = rrsetWrapper.rrset(); if (rrset.getName().equals(wrappedRrset.getName()) && rrset.getType().equals(wrappedRrset.getType())) { @@ -1241,7 +1207,7 @@ static Response checkRrset(ResourceRecordSet rrset, ZoneContainer zone, int inde } // if found, we still need an exact match if ("deletions".equals(type) - && !zone.dnsRecords().get(zone.zone().getName()).contains(new RrsetWrapper(rrset))) { + && !zone.dnsRecords().get().contains(new RrsetWrapper(rrset))) { // such a record does not exist return Error.CONDITION_NOT_MET.response(String.format( "Precondition not met for 'entity.change.deletions[%s]", index)); @@ -1259,7 +1225,7 @@ static Response additionsMeetDeletions(List additions, if (additions != null) { int index = 0; for (ResourceRecordSet rrset : additions) { - for (RrsetWrapper wrapper : zone.dnsRecords().get(zone.zone().getName())) { + for (RrsetWrapper wrapper : zone.dnsRecords().get()) { ResourceRecordSet wrappedRrset = wrapper.rrset(); if (rrset.getName().equals(wrappedRrset.getName()) && rrset.getType().equals(wrappedRrset.getType())) { @@ -1323,44 +1289,11 @@ private static ResourceRecordSet findByNameAndType(Iterable r * We only provide the most basic validation for A and AAAA records. */ static boolean checkRrData(String data, String type) { - // todo add validation for other records - String[] tokens; switch (type) { case "A": - tokens = data.split("\\."); - if (tokens.length != 4) { - return false; - } - for (String token : tokens) { - try { - Integer number = Integer.valueOf(token); - if (number < 0 || number > 255) { - return false; - } - } catch (NumberFormatException ex) { - return false; - } - } - return true; + return !data.contains(":") && isInetAddress(data); case "AAAA": - tokens = data.split(":", -1); - if (tokens.length != 8) { - return false; - } - for (String token : tokens) { - try { - if (!token.isEmpty()) { - // empty is ok - Long number = Long.parseLong(token, 16); - if (number < 0 || number > 0xFFFF) { - return false; - } - } - } catch (NumberFormatException ex) { - return false; - } - } - return true; + return data.contains(":") && isInetAddress(data); default: return true; } @@ -1403,7 +1336,8 @@ static Response checkListOptions(Map options) { String type = (String) options.get("type"); // must be provided with name if (type != null) { if (name == null) { - return Error.INVALID.response("Invalid value for 'parameters.name': ''"); + return Error.INVALID.response("Invalid value for 'parameters.name': '' " + + "(name must be specified if type is specified)"); } if (!TYPES.contains(type)) { return Error.INVALID.response(String.format( diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/testing/OptionParsersAndExtractors.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/OptionParsers.java similarity index 87% rename from gcloud-java-dns/src/main/java/com/google/gcloud/testing/OptionParsersAndExtractors.java rename to gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/OptionParsers.java index 26759f7e3ccc..f77f05123dcc 100644 --- a/gcloud-java-dns/src/main/java/com/google/gcloud/testing/OptionParsersAndExtractors.java +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/OptionParsers.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.gcloud.testing; +package com.google.gcloud.dns.testing; import com.google.api.services.dns.model.Change; import com.google.api.services.dns.model.ManagedZone; @@ -27,12 +27,8 @@ /** * Utility helpers for LocalDnsHelper. */ -class OptionParsersAndExtractors { +class OptionParsers { - /** - * Makes a map of list options. Expects query to be only query part of the url (i.e., what follows - * the '?'). - */ static Map parseListZonesOptions(String query) { Map options = new HashMap<>(); if (query != null) { @@ -41,20 +37,15 @@ static Map parseListZonesOptions(String query) { String[] argEntry = arg.split("="); switch (argEntry[0]) { case "fields": - // List fields are in the form "managedZones(field1, field2, ...)" + // List fields are in the form "managedZones(field1, field2, ...),nextPageToken" String replaced = argEntry[1].replace("managedZones(", ","); replaced = replaced.replace(")", ","); // we will get empty strings, but it does not matter, they will be ignored - options.put( - "fields", - replaced.split(",")); + options.put("fields", replaced.split(",")); break; case "dnsName": options.put("dnsName", argEntry[1]); break; - case "nextPageToken": - options.put("nextPageToken", argEntry[1]); - break; case "pageToken": options.put("pageToken", argEntry[1]); break; @@ -70,10 +61,6 @@ static Map parseListZonesOptions(String query) { return options; } - /** - * Makes a map of list options. Expects query to be only query part of the url (i.e., what follows - * the '?'). This format is common for all of zone, change and project. - */ static String[] parseGetOptions(String query) { if (query != null) { String[] args = query.split("&"); @@ -88,9 +75,6 @@ static String[] parseGetOptions(String query) { return null; } - /** - * Extracts only request fields. - */ static ManagedZone extractFields(ManagedZone fullZone, String[] fields) { if (fields == null) { return fullZone; @@ -126,9 +110,6 @@ static ManagedZone extractFields(ManagedZone fullZone, String[] fields) { return managedZone; } - /** - * Extracts only request fields. - */ static Change extractFields(Change fullChange, String[] fields) { if (fields == null) { return fullChange; @@ -160,9 +141,6 @@ static Change extractFields(Change fullChange, String[] fields) { return change; } - /** - * Extracts only request fields. - */ static Project extractFields(Project fullProject, String[] fields) { if (fields == null) { return fullProject; @@ -186,9 +164,6 @@ static Project extractFields(Project fullProject, String[] fields) { return project; } - /** - * Extracts only request fields. - */ static ResourceRecordSet extractFields(ResourceRecordSet fullRecord, String[] fields) { if (fields == null) { return fullRecord; @@ -227,12 +202,6 @@ static Map parseListChangesOptions(String query) { String replaced = argEntry[1].replace("changes(", ",").replace(")", ","); options.put("fields", replaced.split(",")); // empty strings will be ignored break; - case "name": - options.put("name", argEntry[1]); - break; - case "nextPageToken": - options.put("nextPageToken", argEntry[1]); - break; case "pageToken": options.put("pageToken", argEntry[1]); break; @@ -275,9 +244,6 @@ static Map parseListDnsRecordsOptions(String query) { case "pageToken": options.put("pageToken", argEntry[1]); break; - case "nextPageToken": - options.put("nextPageToken", argEntry[1]); - break; case "maxResults": // parsing to int is done while handling options.put("maxResults", argEntry[1]); diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/spi/DefaultDnsRpc.java b/gcloud-java-dns/src/main/java/com/google/gcloud/spi/DefaultDnsRpc.java index b72a21445a80..1df0a8a2f831 100644 --- a/gcloud-java-dns/src/main/java/com/google/gcloud/spi/DefaultDnsRpc.java +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/spi/DefaultDnsRpc.java @@ -162,9 +162,9 @@ public Change getChangeRequest(String zoneName, String changeRequestId, Map list = - localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).dnsRecords().get(ZONE_NAME1); + localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).dnsRecords().get(); assertTrue(list.contains(new LocalDnsHelper.RrsetWrapper(RRSET_KEEP))); localDnsThreaded.stop(); } @@ -284,6 +291,7 @@ public void testCreateAndApplyChangeUsingRpc() { assertNull(RPC.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); try { Change anotherChange = RPC.applyChangeRequest(ZONE1.getName(), CHANGE1, EMPTY_RPC_OPTIONS); + fail(); } catch (DnsException ex) { assertEquals(409, ex.code()); } @@ -295,10 +303,6 @@ public void testCreateAndApplyChangeUsingRpc() { assertNotNull(RPC.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); Change last = RPC.applyChangeRequest(ZONE1.getName(), CHANGE_KEEP, EMPTY_RPC_OPTIONS); assertEquals("done", last.getStatus()); - // todo(mderka) replace with real call - List list = - LOCAL_DNS_HELPER.findZone(REAL_PROJECT_ID, ZONE_NAME1).dnsRecords().get(ZONE_NAME1); - assertTrue(list.contains(new LocalDnsHelper.RrsetWrapper(RRSET_KEEP))); Iterable results = RPC.listDnsRecords(ZONE1.getName(), EMPTY_RPC_OPTIONS).results(); boolean ok = false; @@ -332,9 +336,6 @@ public void testFindChange() { @Test public void testRandomNameServers() { assertEquals(4, LocalDnsHelper.randomNameservers().size()); - assertEquals(4, LocalDnsHelper.randomNameservers().size()); - assertEquals(4, LocalDnsHelper.randomNameservers().size()); - assertEquals(4, LocalDnsHelper.randomNameservers().size()); } @Test @@ -459,12 +460,6 @@ public void testCreateZone() { LocalDnsHelper.Response response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); assertEquals(200, response.code()); assertEquals(1, LOCAL_DNS_HELPER.projects().get(PROJECT_ID1).zones().size()); - try { - LOCAL_DNS_HELPER.createZone(PROJECT_ID1, null, null); - fail("Zone cannot be null"); - } catch (NullPointerException ex) { - // expected - } // create zone twice response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); assertEquals(409, response.code()); @@ -711,7 +706,6 @@ public void testGetChangeUsingRpc() { @Test public void testListZones() { - // only interested in no exceptions and non-null response here optionsMap.put("dnsName", null); optionsMap.put("fields", null); optionsMap.put("pageToken", null); @@ -872,22 +866,31 @@ public void testListZonesUsingRpc() { assertNotNull(zone.getNameServerSet()); assertNotNull(zone.getId()); assertEquals(zone.getName(), managedZoneListResult.pageToken()); - // paging - options = new HashMap<>(); + } + + @Test + public void testZonePaging() { + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + RPC.create(ZONE2, EMPTY_RPC_OPTIONS); + ImmutableList complete = ImmutableList.copyOf( + RPC.listZones(EMPTY_RPC_OPTIONS).results()); + Map options = new HashMap<>(); options.put(DnsRpc.Option.PAGE_SIZE, 1); - managedZoneListResult = RPC.listZones(options); + DnsRpc.ListResult managedZoneListResult = RPC.listZones(options); ImmutableList page1 = ImmutableList.copyOf(managedZoneListResult.results()); assertEquals(1, page1.size()); + assertEquals(complete.get(0), page1.get(0)); + assertEquals(page1.get(0).getName(), managedZoneListResult.pageToken()); options.put(DnsRpc.Option.PAGE_TOKEN, managedZoneListResult.pageToken()); managedZoneListResult = RPC.listZones(options); ImmutableList page2 = ImmutableList.copyOf(managedZoneListResult.results()); assertEquals(1, page2.size()); - assertNotEquals(page1.get(0), page2.get(0)); + assertEquals(complete.get(1), page2.get(0)); + assertNull(managedZoneListResult.pageToken()); } @Test public void testListDnsRecords() { - // only interested in no exceptions and non-null response here optionsMap.put("name", null); optionsMap.put("fields", null); optionsMap.put("type", null); @@ -981,7 +984,6 @@ public void testListDnsRecordsUsingRpc() { results = RPC.listDnsRecords(ZONE1.getName(), options).results(); records = ImmutableList.copyOf(results); assertEquals(3, records.size()); - // dnsName filter options = new HashMap<>(); options.put(DnsRpc.Option.NAME, "aaa"); @@ -1075,13 +1077,26 @@ record = records.get(0); assertNull(record.getType()); assertNull(record.getTtl()); assertNotNull(resourceRecordSetListResult.pageToken()); - // paging + } + + @Test + public void testDnsRecordPaging() { + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + List complete = ImmutableList.copyOf( + RPC.listDnsRecords(ZONE1.getName(), EMPTY_RPC_OPTIONS).results()); + Map options = new HashMap<>(); + options.put(DnsRpc.Option.PAGE_SIZE, 1); + DnsRpc.ListResult resourceRecordSetListResult = + RPC.listDnsRecords(ZONE1.getName(), options); + ImmutableList records = + ImmutableList.copyOf(resourceRecordSetListResult.results()); + assertEquals(1, records.size()); + assertEquals(complete.get(0), records.get(0)); options.put(DnsRpc.Option.PAGE_TOKEN, resourceRecordSetListResult.pageToken()); resourceRecordSetListResult = RPC.listDnsRecords(ZONE1.getName(), options); records = ImmutableList.copyOf(resourceRecordSetListResult.results()); assertEquals(1, records.size()); - ResourceRecordSet nextRecord = records.get(0); - assertNotEquals(record, nextRecord); + assertEquals(complete.get(1), records.get(0)); } @Test @@ -1270,20 +1285,29 @@ public void testListChangesUsingRpc() { assertNull(complex.getStartTime()); assertNull(complex.getStatus()); assertNotNull(changeListResult.pageToken()); - // paging - options.put(DnsRpc.Option.FIELDS, "nextPageToken,changes(id)"); + } + + @Test + public void testChangePaging() { + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + RPC.applyChangeRequest(ZONE1.getName(), CHANGE1, EMPTY_RPC_OPTIONS); + RPC.applyChangeRequest(ZONE1.getName(), CHANGE2, EMPTY_RPC_OPTIONS); + RPC.applyChangeRequest(ZONE1.getName(), CHANGE_KEEP, EMPTY_RPC_OPTIONS); + ImmutableList complete = + ImmutableList.copyOf(RPC.listChangeRequests(ZONE1.getName(), EMPTY_RPC_OPTIONS).results()); + Map options = new HashMap<>(); options.put(DnsRpc.Option.PAGE_SIZE, 1); - changeListResult = RPC.listChangeRequests(ZONE1.getName(), options); - changes = ImmutableList.copyOf(changeListResult.results()); + DnsRpc.ListResult changeListResult = RPC.listChangeRequests(ZONE1.getName(), options); + List changes = ImmutableList.copyOf(changeListResult.results()); assertEquals(1, changes.size()); - final Change first = changes.get(0); - assertNotNull(changeListResult.pageToken()); + assertEquals(complete.get(0), changes.get(0)); + assertEquals(complete.get(0).getId(), changeListResult.pageToken()); options.put(DnsRpc.Option.PAGE_TOKEN, changeListResult.pageToken()); changeListResult = RPC.listChangeRequests(ZONE1.getName(), options); changes = ImmutableList.copyOf(changeListResult.results()); assertEquals(1, changes.size()); - Change second = changes.get(0); - assertNotEquals(first, second); + assertEquals(complete.get(1), changes.get(0)); + assertEquals(complete.get(1).getId(), changeListResult.pageToken()); } @Test @@ -1501,11 +1525,10 @@ public void testCheckRrdata() { assertFalse(LocalDnsHelper.checkRrData("111.255.12", "A")); assertFalse(LocalDnsHelper.checkRrData("111.255.12.11.11", "A")); // AAAA - assertTrue(LocalDnsHelper.checkRrData(":::::::", "AAAA")); - assertTrue(LocalDnsHelper.checkRrData("1F:fa:09fd::343:aaaa:AAAA:", "AAAA")); - assertTrue(LocalDnsHelper.checkRrData("0000:FFFF:09fd::343:aaaa:AAAA:", "AAAA")); + assertTrue(LocalDnsHelper.checkRrData("1F:fa:09fd::343:aaaa:AAAA:0", "AAAA")); + assertTrue(LocalDnsHelper.checkRrData("0000:FFFF:09fd::343:aaaa:AAAA:0", "AAAA")); assertFalse(LocalDnsHelper.checkRrData("-2:::::::", "AAAA")); - assertTrue(LocalDnsHelper.checkRrData("0:::::::", "AAAA")); + assertTrue(LocalDnsHelper.checkRrData("0::0", "AAAA")); assertFalse(LocalDnsHelper.checkRrData("::1FFFF:::::", "AAAA")); assertFalse(LocalDnsHelper.checkRrData("::aqaa:::::", "AAAA")); assertFalse(LocalDnsHelper.checkRrData("::::::::", "AAAA")); // too long @@ -1587,13 +1610,12 @@ public void testCheckChange() { ResourceRecordSet nonExistent = new ResourceRecordSet(); nonExistent.setName(ZONE1.getDnsName()); nonExistent.setType("AAAA"); - nonExistent.setRrdatas(ImmutableList.of(":::::::")); + nonExistent.setRrdatas(ImmutableList.of("0:0:0:0:5::6")); Change delete = new Change(); delete.setDeletions(ImmutableList.of(nonExistent)); response = LocalDnsHelper.checkChange(delete, zoneContainer); assertEquals(404, response.code()); assertTrue(response.body().contains("deletions[0]")); - } @Test @@ -1611,7 +1633,6 @@ public void testAdditionsMeetDeletions() { LocalDnsHelper.additionsMeetDeletions(ImmutableList.of(validA), null, container); assertEquals(409, response.code()); assertTrue(response.body().contains("already exists")); - } @Test @@ -1638,7 +1659,7 @@ public void testCreateChangeValidatesChangeContent() { // delete and add SOA Change addition = new Change(); ImmutableList rrsetWrappers - = LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE_NAME1).dnsRecords().get(ZONE_NAME1); + = LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE_NAME1).dnsRecords().get(); LinkedList deletions = new LinkedList<>(); LinkedList additions = new LinkedList<>(); for (LocalDnsHelper.RrsetWrapper wrapper : rrsetWrappers) { @@ -1762,7 +1783,7 @@ public void testCreateChangeValidatesChange() { ResourceRecordSet nonExistent = new ResourceRecordSet(); nonExistent.setName(ZONE1.getDnsName()); nonExistent.setType("AAAA"); - nonExistent.setRrdatas(ImmutableList.of(":::::::")); + nonExistent.setRrdatas(ImmutableList.of("2607:f8b0:400a:801::1005")); Change delete = new Change(); delete.setDeletions(ImmutableList.of(nonExistent)); response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, delete, null); From 6d335cdcc9fb2215783b9cfe866e528f38e974b5 Mon Sep 17 00:00:00 2001 From: Martin Derka Date: Fri, 26 Feb 2016 13:34:31 -0500 Subject: [PATCH 4/9] LocalDnsHelperTest clean up. --- .../dns/testing/LocalDnsHelperTest.java | 899 +++++------------- 1 file changed, 243 insertions(+), 656 deletions(-) diff --git a/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java index 52d2deae96c3..b03f6b56dfb3 100644 --- a/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java +++ b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java @@ -68,8 +68,6 @@ public class LocalDnsHelperTest { private static final String REAL_PROJECT_ID = LOCAL_DNS_HELPER.options().projectId(); private Map optionsMap; - private ManagedZone minimalZone = new ManagedZone(); // to be adjusted as needed - @BeforeClass public static void before() { ZONE1.setName(ZONE_NAME1); @@ -101,7 +99,6 @@ public static void before() { public void setUp() { resetProjects(); optionsMap = new HashMap<>(); - minimalZone = copyZone(ZONE1); } private static void resetProjects() { @@ -116,280 +113,36 @@ public static void after() { } @Test - public void testMatchesCriteria() { - assertTrue(LocalDnsHelper.matchesCriteria(RRSET1, RRSET1.getName(), RRSET1.getType())); - assertFalse(LocalDnsHelper.matchesCriteria(RRSET1, RRSET1.getName(), "anothertype")); - assertTrue(LocalDnsHelper.matchesCriteria(RRSET1, null, RRSET1.getType())); - assertTrue(LocalDnsHelper.matchesCriteria(RRSET1, RRSET1.getName(), null)); - assertFalse(LocalDnsHelper.matchesCriteria(RRSET1, "anothername", RRSET1.getType())); - } - - @Test - public void testGetUniqueId() { - assertNotNull(LocalDnsHelper.getUniqueId(Lists.newLinkedList())); - } - - @Test - public void testFindProject() { - assertEquals(0, LOCAL_DNS_HELPER.projects().size()); - LocalDnsHelper.ProjectContainer project = LOCAL_DNS_HELPER.findProject(PROJECT_ID1); - assertNotNull(project); - assertTrue(LOCAL_DNS_HELPER.projects().containsKey(PROJECT_ID1)); - assertNotNull(LOCAL_DNS_HELPER.findProject(PROJECT_ID2)); - assertTrue(LOCAL_DNS_HELPER.projects().containsKey(PROJECT_ID2)); - assertTrue(LOCAL_DNS_HELPER.projects().containsKey(PROJECT_ID1)); - assertNotNull(project.zones()); - assertEquals(0, project.zones().size()); - assertNotNull(project.project()); - assertNotNull(project.project().getQuota()); - } - - @Test - public void testCreateAndFindZone() { - LocalDnsHelper.ZoneContainer zone1 = LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE_NAME1); - assertTrue(LOCAL_DNS_HELPER.projects().containsKey(PROJECT_ID1)); - assertNull(zone1); - LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); // we do not care about options - zone1 = LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE1.getName()); - assertNotNull(zone1); - // cannot call equals because id and timestamp got assigned - assertEquals(ZONE_NAME1, zone1.zone().getName()); - assertNotNull(zone1.changes()); - assertTrue(zone1.changes().isEmpty()); - assertNotNull(zone1.dnsRecords()); - assertEquals(2, zone1.dnsRecords().get().size()); // default SOA and NS - LOCAL_DNS_HELPER.createZone(PROJECT_ID2, ZONE1, null); // project does not exist yet - assertEquals(ZONE1.getName(), - LOCAL_DNS_HELPER.findZone(PROJECT_ID2, ZONE_NAME1).zone().getName()); - } - - @Test - public void testCreateAndFindZoneUsingRpc() { - // zone does not exist yet - ManagedZone zone1 = RPC.getZone(ZONE_NAME1, EMPTY_RPC_OPTIONS); - assertTrue(LOCAL_DNS_HELPER.projects().containsKey(REAL_PROJECT_ID)); // check internal state - assertNull(zone1); - // create zone - ManagedZone createdZone = RPC.create(ZONE1, EMPTY_RPC_OPTIONS); - assertEquals(ZONE1.getName(), createdZone.getName()); - assertEquals(ZONE1.getDescription(), createdZone.getDescription()); - assertEquals(ZONE1.getDnsName(), createdZone.getDnsName()); - assertEquals(4, createdZone.getNameServers().size()); - ManagedZone zone = RPC.getZone(ZONE1.getName(), EMPTY_RPC_OPTIONS); - assertEquals(createdZone, zone); + public void testCreateZone() { + ManagedZone created = RPC.create(ZONE1, EMPTY_RPC_OPTIONS); // check that default records were created DnsRpc.ListResult resourceRecordSetListResult = RPC.listDnsRecords(ZONE1.getName(), EMPTY_RPC_OPTIONS); assertEquals(2, Lists.newLinkedList(resourceRecordSetListResult.results()).size()); - } - - @Test - public void testDeleteZone() { - LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); - LocalDnsHelper.Response response = LOCAL_DNS_HELPER.deleteZone(PROJECT_ID1, ZONE1.getName()); - assertEquals(204, response.code()); - // deleting non-existent zone - response = LOCAL_DNS_HELPER.deleteZone(PROJECT_ID1, ZONE1.getName()); - assertEquals(404, response.code()); - assertNull(LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE1.getName())); - LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); - LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE2, null); - assertNotNull(LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE1.getName())); - assertNotNull(LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE2.getName())); - // delete in reverse order - response = LOCAL_DNS_HELPER.deleteZone(PROJECT_ID1, ZONE1.getName()); - assertEquals(204, response.code()); - assertNull(LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE1.getName())); - assertNotNull(LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE2.getName())); - LOCAL_DNS_HELPER.deleteZone(PROJECT_ID1, ZONE2.getName()); - assertEquals(204, response.code()); - assertNull(LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE1.getName())); - assertNull(LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE2.getName())); - } - - @Test - public void testDeleteZoneUsingRpc() { - RPC.create(ZONE1, EMPTY_RPC_OPTIONS); - assertTrue(RPC.deleteZone(ZONE1.getName())); - assertNull(RPC.getZone(ZONE1.getName(), EMPTY_RPC_OPTIONS)); - // deleting non-existent zone - assertFalse(RPC.deleteZone(ZONE1.getName())); - assertNull(RPC.getZone(ZONE1.getName(), EMPTY_RPC_OPTIONS)); - RPC.create(ZONE1, EMPTY_RPC_OPTIONS); - RPC.create(ZONE2, EMPTY_RPC_OPTIONS); - assertNotNull(RPC.getZone(ZONE1.getName(), EMPTY_RPC_OPTIONS)); - assertNotNull(RPC.getZone(ZONE2.getName(), EMPTY_RPC_OPTIONS)); - // delete in reverse order - assertTrue(RPC.deleteZone(ZONE1.getName())); - assertNull(RPC.getZone(ZONE1.getName(), EMPTY_RPC_OPTIONS)); - assertNotNull(RPC.getZone(ZONE2.getName(), EMPTY_RPC_OPTIONS)); - assertTrue(RPC.deleteZone(ZONE2.getName())); - assertNull(RPC.getZone(ZONE1.getName(), EMPTY_RPC_OPTIONS)); - assertNull(RPC.getZone(ZONE2.getName(), EMPTY_RPC_OPTIONS)); - RPC.create(ZONE1, EMPTY_RPC_OPTIONS); - RPC.applyChangeRequest(ZONE1.getName(), CHANGE_KEEP, EMPTY_RPC_OPTIONS); + assertEquals(created, LOCAL_DNS_HELPER.findZone(REAL_PROJECT_ID, ZONE1.getName()).zone()); + ManagedZone zone = RPC.getZone(ZONE_NAME1, EMPTY_RPC_OPTIONS); + assertEquals(created, zone); try { - assertFalse(RPC.deleteZone(ZONE1.getName())); + RPC.create(null, EMPTY_RPC_OPTIONS); + fail("Zone cannot be null"); } catch (DnsException ex) { // expected assertEquals(400, ex.code()); + assertTrue(ex.getMessage().contains("entity.managedZone")); } - - } - - @Test - public void testCreateAndApplyChange() { - LocalDnsHelper localDnsThreaded = LocalDnsHelper.create(1 * 1000L); // using threads here - localDnsThreaded.createZone(PROJECT_ID1, ZONE1, null); - assertNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); - LocalDnsHelper.Response response - = localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add - assertEquals(200, response.code()); - assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); - assertNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); - localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add - response = localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add - assertEquals(200, response.code()); - assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); - assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); - localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); // delete - assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); - assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); - assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("3")); - localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE_KEEP, null); // id is "4" - // check execution - Change change = localDnsThreaded.findChange(PROJECT_ID1, ZONE_NAME1, "4"); - for (int i = 0; i < 10 && !change.getStatus().equals("done"); i++) { - // change has not been finished yet; wait at most 20 seconds - // it takes 5 seconds for the thread to kick in in the first place - try { - Thread.sleep(2 * 1000); - } catch (InterruptedException e) { - fail("Test was interrupted"); - } - } - assertEquals("done", change.getStatus()); - List list = - localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).dnsRecords().get(); - assertTrue(list.contains(new LocalDnsHelper.RrsetWrapper(RRSET_KEEP))); - localDnsThreaded.stop(); - } - - @Test - public void testCreateAndApplyChangeUsingRpc() { - // not using threads - RPC.create(ZONE1, EMPTY_RPC_OPTIONS); - assertNull(RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS)); - //add - Change createdChange = RPC.applyChangeRequest(ZONE1.getName(), CHANGE1, EMPTY_RPC_OPTIONS); - assertEquals(createdChange.getAdditions(), CHANGE1.getAdditions()); - assertEquals(createdChange.getDeletions(), CHANGE1.getDeletions()); - assertNotNull(createdChange.getStartTime()); - assertEquals("1", createdChange.getId()); - Change retrievedChange = RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS); - assertEquals(createdChange, retrievedChange); - assertNull(RPC.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); + // create zone twice try { - Change anotherChange = RPC.applyChangeRequest(ZONE1.getName(), CHANGE1, EMPTY_RPC_OPTIONS); - fail(); + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); } catch (DnsException ex) { + // expected assertEquals(409, ex.code()); + assertTrue(ex.getMessage().contains("already exists")); } - assertNotNull(RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS)); - assertNull(RPC.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); - // delete - RPC.applyChangeRequest(ZONE1.getName(), CHANGE2, EMPTY_RPC_OPTIONS); - assertNotNull(RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS)); - assertNotNull(RPC.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); - Change last = RPC.applyChangeRequest(ZONE1.getName(), CHANGE_KEEP, EMPTY_RPC_OPTIONS); - assertEquals("done", last.getStatus()); - Iterable results = - RPC.listDnsRecords(ZONE1.getName(), EMPTY_RPC_OPTIONS).results(); - boolean ok = false; - for (ResourceRecordSet dnsRecord : results) { - if (dnsRecord.getName().equals(RRSET_KEEP.getName()) - && dnsRecord.getType().equals(RRSET_KEEP.getType())) { - ok = true; - } - } - assertTrue(ok); - } - - @Test - public void testFindChange() { - LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); - Change change = LOCAL_DNS_HELPER.findChange(PROJECT_ID1, ZONE1.getName(), "somerandomchange"); - assertNull(change); - LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE1.getName(), CHANGE1, null); - // changes are sequential so we should find ID 1 - assertNotNull(LOCAL_DNS_HELPER.findChange(PROJECT_ID1, ZONE1.getName(), "1")); - // add another - LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); - assertNotNull(LOCAL_DNS_HELPER.findChange(PROJECT_ID1, ZONE1.getName(), "1")); - assertNotNull(LOCAL_DNS_HELPER.findChange(PROJECT_ID1, ZONE1.getName(), "2")); - // try to find non-existent change - assertNull(LOCAL_DNS_HELPER.findChange(PROJECT_ID1, ZONE1.getName(), "3")); - // try to find a change in yet non-existent project - assertNull(LOCAL_DNS_HELPER.findChange(PROJECT_ID2, ZONE1.getName(), "3")); - } - - @Test - public void testRandomNameServers() { - assertEquals(4, LocalDnsHelper.randomNameservers().size()); - } - - @Test - public void testGetProject() { - // only interested in no exceptions and non-null response here - assertNotNull(LOCAL_DNS_HELPER.getProject(PROJECT_ID1, null)); - assertNotNull(LOCAL_DNS_HELPER.getProject(PROJECT_ID2, null)); - Project project = RPC.getProject(EMPTY_RPC_OPTIONS); - assertNotNull(project.getQuota()); - assertEquals(REAL_PROJECT_ID, project.getId()); - // fields options - Map options = new HashMap<>(); - options.put(DnsRpc.Option.FIELDS, "number"); - project = RPC.getProject(options); - assertNull(project.getId()); - assertNotNull(project.getNumber()); - assertNull(project.getQuota()); - options.put(DnsRpc.Option.FIELDS, "id"); - project = RPC.getProject(options); - assertNotNull(project.getId()); - assertNull(project.getNumber()); - assertNull(project.getQuota()); - options.put(DnsRpc.Option.FIELDS, "quota"); - project = RPC.getProject(options); - assertNull(project.getId()); - assertNull(project.getNumber()); - assertNotNull(project.getQuota()); - } - - @Test - public void testGetZone() { - // non-existent - LocalDnsHelper.Response response = LOCAL_DNS_HELPER.getZone(PROJECT_ID1, ZONE_NAME1, null); - assertEquals(404, response.code()); - assertTrue(response.body().contains("does not exist")); - // existent - LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); - response = LOCAL_DNS_HELPER.getZone(PROJECT_ID1, ZONE1.getName(), null); - assertEquals(200, response.code()); - } - - @Test - public void testGetZoneUsingRpc() { - // non-existent - assertNull(RPC.getZone(ZONE_NAME1, EMPTY_RPC_OPTIONS)); - // existent - ManagedZone created = RPC.create(ZONE1, EMPTY_RPC_OPTIONS); - ManagedZone zone = RPC.getZone(ZONE_NAME1, EMPTY_RPC_OPTIONS); - assertEquals(created, zone); - assertEquals(ZONE1.getName(), zone.getName()); // field options + resetProjects(); Map options = new HashMap<>(); options.put(DnsRpc.Option.FIELDS, "id"); - zone = RPC.getZone(ZONE1.getName(), options); + zone = RPC.create(ZONE1, options); assertNull(zone.getCreationTime()); assertNull(zone.getName()); assertNull(zone.getDnsName()); @@ -397,8 +150,9 @@ public void testGetZoneUsingRpc() { assertNull(zone.getNameServers()); assertNull(zone.getNameServerSet()); assertNotNull(zone.getId()); + resetProjects(); options.put(DnsRpc.Option.FIELDS, "creationTime"); - zone = RPC.getZone(ZONE1.getName(), options); + zone = RPC.create(ZONE1, options); assertNotNull(zone.getCreationTime()); assertNull(zone.getName()); assertNull(zone.getDnsName()); @@ -407,7 +161,8 @@ public void testGetZoneUsingRpc() { assertNull(zone.getNameServerSet()); assertNull(zone.getId()); options.put(DnsRpc.Option.FIELDS, "dnsName"); - zone = RPC.getZone(ZONE1.getName(), options); + resetProjects(); + zone = RPC.create(ZONE1, options); assertNull(zone.getCreationTime()); assertNull(zone.getName()); assertNotNull(zone.getDnsName()); @@ -416,7 +171,8 @@ public void testGetZoneUsingRpc() { assertNull(zone.getNameServerSet()); assertNull(zone.getId()); options.put(DnsRpc.Option.FIELDS, "description"); - zone = RPC.getZone(ZONE1.getName(), options); + resetProjects(); + zone = RPC.create(ZONE1, options); assertNull(zone.getCreationTime()); assertNull(zone.getName()); assertNull(zone.getDnsName()); @@ -425,7 +181,8 @@ public void testGetZoneUsingRpc() { assertNull(zone.getNameServerSet()); assertNull(zone.getId()); options.put(DnsRpc.Option.FIELDS, "nameServers"); - zone = RPC.getZone(ZONE1.getName(), options); + resetProjects(); + zone = RPC.create(ZONE1, options); assertNull(zone.getCreationTime()); assertNull(zone.getName()); assertNull(zone.getDnsName()); @@ -434,7 +191,8 @@ public void testGetZoneUsingRpc() { assertNull(zone.getNameServerSet()); assertNull(zone.getId()); options.put(DnsRpc.Option.FIELDS, "nameServerSet"); - zone = RPC.getZone(ZONE1.getName(), options); + resetProjects(); + zone = RPC.create(ZONE1, options); assertNull(zone.getCreationTime()); assertNull(zone.getName()); assertNull(zone.getDnsName()); @@ -444,7 +202,8 @@ public void testGetZoneUsingRpc() { assertNull(zone.getId()); // several combined options.put(DnsRpc.Option.FIELDS, "nameServerSet,description,id,name"); - zone = RPC.getZone(ZONE1.getName(), options); + resetProjects(); + zone = RPC.create(ZONE1, options); assertNull(zone.getCreationTime()); assertNotNull(zone.getName()); assertNull(zone.getDnsName()); @@ -455,44 +214,18 @@ public void testGetZoneUsingRpc() { } @Test - public void testCreateZone() { - // only interested in no exceptions and non-null response here - LocalDnsHelper.Response response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); - assertEquals(200, response.code()); - assertEquals(1, LOCAL_DNS_HELPER.projects().get(PROJECT_ID1).zones().size()); - // create zone twice - response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); - assertEquals(409, response.code()); - assertTrue(response.body().contains("already exists")); - } - - @Test - public void testCreateZoneUsingRpc() { + public void testGetZone() { + // non-existent + assertNull(RPC.getZone(ZONE_NAME1, EMPTY_RPC_OPTIONS)); + // existent ManagedZone created = RPC.create(ZONE1, EMPTY_RPC_OPTIONS); - assertEquals(created, LOCAL_DNS_HELPER.findZone(REAL_PROJECT_ID, ZONE1.getName()).zone()); ManagedZone zone = RPC.getZone(ZONE_NAME1, EMPTY_RPC_OPTIONS); assertEquals(created, zone); - try { - RPC.create(null, EMPTY_RPC_OPTIONS); - fail("Zone cannot be null"); - } catch (DnsException ex) { - // expected - assertEquals(400, ex.code()); - assertTrue(ex.getMessage().contains("entity.managedZone")); - } - // create zone twice - try { - RPC.create(ZONE1, EMPTY_RPC_OPTIONS); - } catch (DnsException ex) { - // expected - assertEquals(409, ex.code()); - assertTrue(ex.getMessage().contains("already exists")); - } + assertEquals(ZONE1.getName(), zone.getName()); // field options - resetProjects(); Map options = new HashMap<>(); options.put(DnsRpc.Option.FIELDS, "id"); - zone = RPC.create(ZONE1, options); + zone = RPC.getZone(ZONE1.getName(), options); assertNull(zone.getCreationTime()); assertNull(zone.getName()); assertNull(zone.getDnsName()); @@ -500,9 +233,8 @@ public void testCreateZoneUsingRpc() { assertNull(zone.getNameServers()); assertNull(zone.getNameServerSet()); assertNotNull(zone.getId()); - resetProjects(); options.put(DnsRpc.Option.FIELDS, "creationTime"); - zone = RPC.create(ZONE1, options); + zone = RPC.getZone(ZONE1.getName(), options); assertNotNull(zone.getCreationTime()); assertNull(zone.getName()); assertNull(zone.getDnsName()); @@ -511,8 +243,7 @@ public void testCreateZoneUsingRpc() { assertNull(zone.getNameServerSet()); assertNull(zone.getId()); options.put(DnsRpc.Option.FIELDS, "dnsName"); - resetProjects(); - zone = RPC.create(ZONE1, options); + zone = RPC.getZone(ZONE1.getName(), options); assertNull(zone.getCreationTime()); assertNull(zone.getName()); assertNotNull(zone.getDnsName()); @@ -521,8 +252,7 @@ public void testCreateZoneUsingRpc() { assertNull(zone.getNameServerSet()); assertNull(zone.getId()); options.put(DnsRpc.Option.FIELDS, "description"); - resetProjects(); - zone = RPC.create(ZONE1, options); + zone = RPC.getZone(ZONE1.getName(), options); assertNull(zone.getCreationTime()); assertNull(zone.getName()); assertNull(zone.getDnsName()); @@ -531,8 +261,7 @@ public void testCreateZoneUsingRpc() { assertNull(zone.getNameServerSet()); assertNull(zone.getId()); options.put(DnsRpc.Option.FIELDS, "nameServers"); - resetProjects(); - zone = RPC.create(ZONE1, options); + zone = RPC.getZone(ZONE1.getName(), options); assertNull(zone.getCreationTime()); assertNull(zone.getName()); assertNull(zone.getDnsName()); @@ -541,8 +270,7 @@ public void testCreateZoneUsingRpc() { assertNull(zone.getNameServerSet()); assertNull(zone.getId()); options.put(DnsRpc.Option.FIELDS, "nameServerSet"); - resetProjects(); - zone = RPC.create(ZONE1, options); + zone = RPC.getZone(ZONE1.getName(), options); assertNull(zone.getCreationTime()); assertNull(zone.getName()); assertNull(zone.getDnsName()); @@ -552,8 +280,7 @@ public void testCreateZoneUsingRpc() { assertNull(zone.getId()); // several combined options.put(DnsRpc.Option.FIELDS, "nameServerSet,description,id,name"); - resetProjects(); - zone = RPC.create(ZONE1, options); + zone = RPC.getZone(ZONE1.getName(), options); assertNull(zone.getCreationTime()); assertNotNull(zone.getName()); assertNull(zone.getDnsName()); @@ -564,21 +291,142 @@ public void testCreateZoneUsingRpc() { } @Test - public void testCreateChange() { - // non-existent zone + public void testDeleteZone() { + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + assertTrue(RPC.deleteZone(ZONE1.getName())); + assertNull(RPC.getZone(ZONE1.getName(), EMPTY_RPC_OPTIONS)); + // deleting non-existent zone + assertFalse(RPC.deleteZone(ZONE1.getName())); + assertNull(RPC.getZone(ZONE1.getName(), EMPTY_RPC_OPTIONS)); + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + RPC.create(ZONE2, EMPTY_RPC_OPTIONS); + assertNotNull(RPC.getZone(ZONE1.getName(), EMPTY_RPC_OPTIONS)); + assertNotNull(RPC.getZone(ZONE2.getName(), EMPTY_RPC_OPTIONS)); + // delete in reverse order + assertTrue(RPC.deleteZone(ZONE1.getName())); + assertNull(RPC.getZone(ZONE1.getName(), EMPTY_RPC_OPTIONS)); + assertNotNull(RPC.getZone(ZONE2.getName(), EMPTY_RPC_OPTIONS)); + assertTrue(RPC.deleteZone(ZONE2.getName())); + assertNull(RPC.getZone(ZONE1.getName(), EMPTY_RPC_OPTIONS)); + assertNull(RPC.getZone(ZONE2.getName(), EMPTY_RPC_OPTIONS)); + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + RPC.applyChangeRequest(ZONE1.getName(), CHANGE_KEEP, EMPTY_RPC_OPTIONS); + try { + assertFalse(RPC.deleteZone(ZONE1.getName())); + } catch (DnsException ex) { + // expected + assertEquals(400, ex.code()); + } + } + + @Test + public void testCreateAndApplyChangeWithThreads() { + LocalDnsHelper localDnsThreaded = LocalDnsHelper.create(1000L); // using threads here + localDnsThreaded.createZone(PROJECT_ID1, ZONE1, null); + assertNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); LocalDnsHelper.Response response = - LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); - assertEquals(404, response.code()); - // existent zone - assertNotNull(LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null)); - assertNull(LOCAL_DNS_HELPER.findChange(PROJECT_ID1, ZONE_NAME1, "1")); - response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); + localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add + assertEquals(200, response.code()); + assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); + assertNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); + localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add + response = localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add assertEquals(200, response.code()); - assertNotNull(LOCAL_DNS_HELPER.findChange(PROJECT_ID1, ZONE_NAME1, "1")); + assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); + assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); + localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); // delete + assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); + assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); + assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("3")); + localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE_KEEP, null); // id is "4" + // check execution + Change change = localDnsThreaded.findChange(PROJECT_ID1, ZONE_NAME1, "4"); + for (int i = 0; i < 10 && !change.getStatus().equals("done"); i++) { + // change has not been finished yet; wait at most 20 seconds + // it takes 5 seconds for the thread to kick in in the first place + try { + Thread.sleep(2 * 1000); + } catch (InterruptedException e) { + fail("Test was interrupted"); + } + } + assertEquals("done", change.getStatus()); + List list = + localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).dnsRecords().get(); + assertTrue(list.contains(new LocalDnsHelper.RrsetWrapper(RRSET_KEEP))); + localDnsThreaded.stop(); + } + + @Test + public void testCreateAndApplyChange() { + // not using threads + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + assertNull(RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS)); + //add + Change createdChange = RPC.applyChangeRequest(ZONE1.getName(), CHANGE1, EMPTY_RPC_OPTIONS); + assertEquals(createdChange.getAdditions(), CHANGE1.getAdditions()); + assertEquals(createdChange.getDeletions(), CHANGE1.getDeletions()); + assertNotNull(createdChange.getStartTime()); + assertEquals("1", createdChange.getId()); + Change retrievedChange = RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS); + assertEquals(createdChange, retrievedChange); + assertNull(RPC.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); + try { + RPC.applyChangeRequest(ZONE1.getName(), CHANGE1, EMPTY_RPC_OPTIONS); + fail(); + } catch (DnsException ex) { + assertEquals(409, ex.code()); + } + assertNotNull(RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS)); + assertNull(RPC.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); + // delete + RPC.applyChangeRequest(ZONE1.getName(), CHANGE2, EMPTY_RPC_OPTIONS); + assertNotNull(RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS)); + assertNotNull(RPC.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); + Change last = RPC.applyChangeRequest(ZONE1.getName(), CHANGE_KEEP, EMPTY_RPC_OPTIONS); + assertEquals("done", last.getStatus()); + Iterable results = + RPC.listDnsRecords(ZONE1.getName(), EMPTY_RPC_OPTIONS).results(); + boolean ok = false; + for (ResourceRecordSet dnsRecord : results) { + if (dnsRecord.getName().equals(RRSET_KEEP.getName()) + && dnsRecord.getType().equals(RRSET_KEEP.getType())) { + ok = true; + } + } + assertTrue(ok); + } + + @Test + public void testGetProject() { + // the projects are automatically created when getProject is called + assertNotNull(LOCAL_DNS_HELPER.getProject(PROJECT_ID1, null)); + assertNotNull(LOCAL_DNS_HELPER.getProject(PROJECT_ID2, null)); + Project project = RPC.getProject(EMPTY_RPC_OPTIONS); + assertNotNull(project.getQuota()); + assertEquals(REAL_PROJECT_ID, project.getId()); + // fields options + Map options = new HashMap<>(); + options.put(DnsRpc.Option.FIELDS, "number"); + project = RPC.getProject(options); + assertNull(project.getId()); + assertNotNull(project.getNumber()); + assertNull(project.getQuota()); + options.put(DnsRpc.Option.FIELDS, "id"); + project = RPC.getProject(options); + assertNotNull(project.getId()); + assertNull(project.getNumber()); + assertNull(project.getQuota()); + options.put(DnsRpc.Option.FIELDS, "quota"); + project = RPC.getProject(options); + assertNull(project.getId()); + assertNull(project.getNumber()); + assertNotNull(project.getQuota()); } + @Test - public void testCreateChangeUsingRpc() { + public void testCreateChange() { // non-existent zone try { RPC.applyChangeRequest(ZONE_NAME1, CHANGE1, EMPTY_RPC_OPTIONS); @@ -632,23 +480,6 @@ public void testCreateChangeUsingRpc() { @Test public void testGetChange() { - // existent - assertEquals(200, LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null).code()); - assertEquals(200, LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null).code()); - assertEquals(200, LOCAL_DNS_HELPER.getChange(PROJECT_ID1, ZONE_NAME1, "1", null).code()); - // non-existent - LocalDnsHelper.Response response = - LOCAL_DNS_HELPER.getChange(PROJECT_ID1, ZONE_NAME1, "2", null); - assertEquals(404, response.code()); - assertTrue(response.body().contains("parameters.changeId")); - // non-existent zone - response = LOCAL_DNS_HELPER.getChange(PROJECT_ID1, ZONE_NAME2, "1", null); - assertEquals(404, response.code()); - assertTrue(response.body().contains("parameters.managedZone")); - } - - @Test - public void testGetChangeUsingRpc() { // existent RPC.create(ZONE1, EMPTY_RPC_OPTIONS); Change created = RPC.applyChangeRequest(ZONE1.getName(), CHANGE1, EMPTY_RPC_OPTIONS); @@ -706,42 +537,6 @@ public void testGetChangeUsingRpc() { @Test public void testListZones() { - optionsMap.put("dnsName", null); - optionsMap.put("fields", null); - optionsMap.put("pageToken", null); - optionsMap.put("maxResults", null); - LocalDnsHelper.Response response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(200, response.code()); - // some zones exists - LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); - response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(200, response.code()); - LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE2, null); - response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(200, response.code()); - // error in options - optionsMap.put("maxResults", "aaa"); - response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(400, response.code()); - optionsMap.put("maxResults", "0"); - response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(400, response.code()); - optionsMap.put("maxResults", "-1"); - response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(400, response.code()); - optionsMap.put("maxResults", "15"); - response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(200, response.code()); - optionsMap.put("dnsName", "aaa"); - response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(400, response.code()); - optionsMap.put("dnsName", "aaa."); - response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(200, response.code()); - } - - @Test - public void testListZonesUsingRpc() { Iterable results = RPC.listZones(EMPTY_RPC_OPTIONS).results(); ImmutableList zones = ImmutableList.copyOf(results); assertEquals(0, zones.size()); @@ -854,94 +649,23 @@ public void testListZonesUsingRpc() { assertNull(zone.getId()); // several combined options.put(DnsRpc.Option.FIELDS, - "managedZones(nameServerSet,description,id,name),nextPageToken"); - options.put(DnsRpc.Option.PAGE_SIZE, 1); - managedZoneListResult = RPC.listZones(options); - zone = managedZoneListResult.results().iterator().next(); - assertNull(zone.getCreationTime()); - assertNotNull(zone.getName()); - assertNull(zone.getDnsName()); - assertNotNull(zone.getDescription()); - assertNull(zone.getNameServers()); - assertNotNull(zone.getNameServerSet()); - assertNotNull(zone.getId()); - assertEquals(zone.getName(), managedZoneListResult.pageToken()); - } - - @Test - public void testZonePaging() { - RPC.create(ZONE1, EMPTY_RPC_OPTIONS); - RPC.create(ZONE2, EMPTY_RPC_OPTIONS); - ImmutableList complete = ImmutableList.copyOf( - RPC.listZones(EMPTY_RPC_OPTIONS).results()); - Map options = new HashMap<>(); - options.put(DnsRpc.Option.PAGE_SIZE, 1); - DnsRpc.ListResult managedZoneListResult = RPC.listZones(options); - ImmutableList page1 = ImmutableList.copyOf(managedZoneListResult.results()); - assertEquals(1, page1.size()); - assertEquals(complete.get(0), page1.get(0)); - assertEquals(page1.get(0).getName(), managedZoneListResult.pageToken()); - options.put(DnsRpc.Option.PAGE_TOKEN, managedZoneListResult.pageToken()); + "managedZones(nameServerSet,description,id,name),nextPageToken"); + options.put(DnsRpc.Option.PAGE_SIZE, 1); managedZoneListResult = RPC.listZones(options); - ImmutableList page2 = ImmutableList.copyOf(managedZoneListResult.results()); - assertEquals(1, page2.size()); - assertEquals(complete.get(1), page2.get(0)); - assertNull(managedZoneListResult.pageToken()); + zone = managedZoneListResult.results().iterator().next(); + assertNull(zone.getCreationTime()); + assertNotNull(zone.getName()); + assertNull(zone.getDnsName()); + assertNotNull(zone.getDescription()); + assertNull(zone.getNameServers()); + assertNotNull(zone.getNameServerSet()); + assertNotNull(zone.getId()); + assertEquals(zone.getName(), managedZoneListResult.pageToken()); } - @Test - public void testListDnsRecords() { - optionsMap.put("name", null); - optionsMap.put("fields", null); - optionsMap.put("type", null); - optionsMap.put("pageToken", null); - optionsMap.put("maxResults", null); - // no zone exists - LocalDnsHelper.Response response = LOCAL_DNS_HELPER.listDnsRecords(PROJECT_ID1, ZONE_NAME1, - optionsMap); - assertEquals(404, response.code()); - // zone exists but has no records - LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); - LOCAL_DNS_HELPER.listDnsRecords(PROJECT_ID1, ZONE_NAME1, optionsMap); - // zone has records - LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); - response = LOCAL_DNS_HELPER.listDnsRecords(PROJECT_ID1, ZONE_NAME1, optionsMap); - assertEquals(200, response.code()); - // error in options - optionsMap.put("maxResults", "aaa"); - response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(400, response.code()); - optionsMap.put("maxResults", "0"); - response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(400, response.code()); - optionsMap.put("maxResults", "-1"); - response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(400, response.code()); - optionsMap.put("maxResults", "15"); - response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(200, response.code()); - optionsMap.put("name", "aaa"); - response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(400, response.code()); - optionsMap.put("name", "aaa."); - response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(200, response.code()); - optionsMap.put("name", null); - optionsMap.put("type", "A"); - response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(400, response.code()); - optionsMap.put("name", "aaa."); - optionsMap.put("type", "a"); - response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(400, response.code()); - optionsMap.put("name", "aaaa."); - optionsMap.put("type", "A"); - response = LOCAL_DNS_HELPER.listZones(PROJECT_ID1, optionsMap); - assertEquals(200, response.code()); - } @Test - public void testListDnsRecordsUsingRpc() { + public void testListDnsRecords() { // no zone exists try { RPC.listDnsRecords(ZONE_NAME1, EMPTY_RPC_OPTIONS); @@ -1079,86 +803,10 @@ record = records.get(0); assertNotNull(resourceRecordSetListResult.pageToken()); } - @Test - public void testDnsRecordPaging() { - RPC.create(ZONE1, EMPTY_RPC_OPTIONS); - List complete = ImmutableList.copyOf( - RPC.listDnsRecords(ZONE1.getName(), EMPTY_RPC_OPTIONS).results()); - Map options = new HashMap<>(); - options.put(DnsRpc.Option.PAGE_SIZE, 1); - DnsRpc.ListResult resourceRecordSetListResult = - RPC.listDnsRecords(ZONE1.getName(), options); - ImmutableList records = - ImmutableList.copyOf(resourceRecordSetListResult.results()); - assertEquals(1, records.size()); - assertEquals(complete.get(0), records.get(0)); - options.put(DnsRpc.Option.PAGE_TOKEN, resourceRecordSetListResult.pageToken()); - resourceRecordSetListResult = RPC.listDnsRecords(ZONE1.getName(), options); - records = ImmutableList.copyOf(resourceRecordSetListResult.results()); - assertEquals(1, records.size()); - assertEquals(complete.get(1), records.get(0)); - } - @Test - public void testListChanges() { - optionsMap.put("sortBy", null); - optionsMap.put("sortOrder", null); - optionsMap.put("fields", null); - optionsMap.put("pageToken", null); - optionsMap.put("maxResults", null); - // no such zone exists - LocalDnsHelper.Response response = - LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); - assertEquals(404, response.code()); - assertTrue(response.body().contains("managedZone")); - // zone exists but has no changes - LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); - assertNotNull(LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap)); - // zone has changes - LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); - assertNotNull(LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap)); - LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); - LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); - LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); - assertNotNull(LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap)); - // error in options - optionsMap.put("maxResults", "aaa"); - response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); - assertEquals(400, response.code()); - optionsMap.put("maxResults", "0"); - response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); - assertEquals(400, response.code()); - optionsMap.put("maxResults", "-1"); - response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); - assertEquals(400, response.code()); - optionsMap.put("maxResults", "15"); - response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); - assertEquals(200, response.code()); - optionsMap.put("sortBy", "changeSequence"); - response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); - assertEquals(200, response.code()); - optionsMap.put("sortBy", "something else"); - response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); - assertEquals(400, response.code()); - assertTrue(response.body().contains("Allowed values: [changesequence]")); - optionsMap.put("sortBy", "ChAnGeSeQuEnCe"); // is not case sensitive - response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); - assertEquals(200, response.code()); - optionsMap.put("sortOrder", "ascending"); - response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); - assertEquals(200, response.code()); - optionsMap.put("sortBy", null); - optionsMap.put("sortOrder", "descending"); - response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); - assertEquals(200, response.code()); - optionsMap.put("sortOrder", "somethingelse"); - response = LOCAL_DNS_HELPER.listChanges(PROJECT_ID1, ZONE_NAME1, optionsMap); - assertEquals(400, response.code()); - assertTrue(response.body().contains("parameters.sortOrder")); - } @Test - public void testListChangesUsingRpc() { + public void testListChanges() { // no such zone exists try { RPC.listChangeRequests(ZONE_NAME1, EMPTY_RPC_OPTIONS); @@ -1287,6 +935,47 @@ public void testListChangesUsingRpc() { assertNotNull(changeListResult.pageToken()); } + @Test + public void testDnsRecordPaging() { + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + List complete = ImmutableList.copyOf( + RPC.listDnsRecords(ZONE1.getName(), EMPTY_RPC_OPTIONS).results()); + Map options = new HashMap<>(); + options.put(DnsRpc.Option.PAGE_SIZE, 1); + DnsRpc.ListResult resourceRecordSetListResult = + RPC.listDnsRecords(ZONE1.getName(), options); + ImmutableList records = + ImmutableList.copyOf(resourceRecordSetListResult.results()); + assertEquals(1, records.size()); + assertEquals(complete.get(0), records.get(0)); + options.put(DnsRpc.Option.PAGE_TOKEN, resourceRecordSetListResult.pageToken()); + resourceRecordSetListResult = RPC.listDnsRecords(ZONE1.getName(), options); + records = ImmutableList.copyOf(resourceRecordSetListResult.results()); + assertEquals(1, records.size()); + assertEquals(complete.get(1), records.get(0)); + } + + @Test + public void testZonePaging() { + RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + RPC.create(ZONE2, EMPTY_RPC_OPTIONS); + ImmutableList complete = ImmutableList.copyOf( + RPC.listZones(EMPTY_RPC_OPTIONS).results()); + Map options = new HashMap<>(); + options.put(DnsRpc.Option.PAGE_SIZE, 1); + DnsRpc.ListResult managedZoneListResult = RPC.listZones(options); + ImmutableList page1 = ImmutableList.copyOf(managedZoneListResult.results()); + assertEquals(1, page1.size()); + assertEquals(complete.get(0), page1.get(0)); + assertEquals(page1.get(0).getName(), managedZoneListResult.pageToken()); + options.put(DnsRpc.Option.PAGE_TOKEN, managedZoneListResult.pageToken()); + managedZoneListResult = RPC.listZones(options); + ImmutableList page2 = ImmutableList.copyOf(managedZoneListResult.results()); + assertEquals(1, page2.size()); + assertEquals(complete.get(1), page2.get(0)); + assertNull(managedZoneListResult.pageToken()); + } + @Test public void testChangePaging() { RPC.create(ZONE1, EMPTY_RPC_OPTIONS); @@ -1327,53 +1016,8 @@ public void testToListResponse() { } @Test - public void testCheckZone() { - // no name - ManagedZone copy = copyZone(minimalZone); - copy.setName(null); - LocalDnsHelper.Response response = LocalDnsHelper.checkZone(copy); - assertEquals(400, response.code()); - assertTrue(response.body().contains("entity.managedZone.name")); - // no description - copy = copyZone(minimalZone); - copy.setDescription(null); - response = LocalDnsHelper.checkZone(copy); - assertEquals(400, response.code()); - assertTrue(response.body().contains("entity.managedZone.description")); - // no description - copy = copyZone(minimalZone); - copy.setDnsName(null); - response = LocalDnsHelper.checkZone(copy); - assertEquals(400, response.code()); - assertTrue(response.body().contains("entity.managedZone.dnsName")); - // zone name is a number - copy = copyZone(minimalZone); - copy.setName("123456"); - response = LocalDnsHelper.checkZone(copy); - assertEquals(400, response.code()); - assertTrue(response.body().contains("entity.managedZone.name")); - assertTrue(response.body().contains("Invalid")); - // dns name does not end with period - copy = copyZone(minimalZone); - copy.setDnsName("aaaaaa.com"); - response = LocalDnsHelper.checkZone(copy); - assertEquals(400, response.code()); - assertTrue(response.body().contains("entity.managedZone.dnsName")); - assertTrue(response.body().contains("Invalid")); - // dns name is reserved - copy = copyZone(minimalZone); - copy.setDnsName("com."); - response = LocalDnsHelper.checkZone(copy); - assertEquals(400, response.code()); - assertTrue(response.body().contains("not available to be created.")); - // empty description should pass - copy = copyZone(minimalZone); - copy.setDescription(""); - assertNull(LocalDnsHelper.checkZone(copy)); - } - - @Test - public void testCreateZoneValidatesZone() { + public void testCreateZoneValidation() { + ManagedZone minimalZone = copyZone(ZONE1); // no name ManagedZone copy = copyZone(minimalZone); copy.setName(null); @@ -1489,6 +1133,7 @@ public void testCheckListOptions() { assertTrue(response.body().contains("parameters.sortOrder")); } + // help @Test public void testCheckRrset() { ResourceRecordSet valid = new ResourceRecordSet(); @@ -1636,7 +1281,7 @@ public void testAdditionsMeetDeletions() { } @Test - public void testCreateChangeValidatesChangeContent() { + public void testCreateChangeContentValidation() { ResourceRecordSet validA = new ResourceRecordSet(); validA.setName(ZONE1.getDnsName()); validA.setType("A"); @@ -1715,80 +1360,22 @@ public void testCreateChangeValidatesChangeContent() { } @Test - public void testCreateChangeValidatesChange() { - LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); - ResourceRecordSet validA = new ResourceRecordSet(); - validA.setName(ZONE1.getDnsName()); - validA.setType("A"); - validA.setRrdatas(ImmutableList.of("0.255.1.5")); - ResourceRecordSet invalidA = new ResourceRecordSet(); - invalidA.setName(ZONE1.getDnsName()); - invalidA.setType("A"); - invalidA.setRrdatas(ImmutableList.of("0.-255.1.5")); - Change validChange = new Change(); - validChange.setAdditions(ImmutableList.of(validA)); - Change invalidChange = new Change(); - invalidChange.setAdditions(ImmutableList.of(invalidA)); - LocalDnsHelper.Response response = - LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); - assertEquals(200, response.code()); - response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, invalidChange, null); - assertEquals(400, response.code()); - // only empty additions/deletions - Change empty = new Change(); - empty.setAdditions(ImmutableList.of()); - empty.setDeletions(ImmutableList.of()); - response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, empty, null); - assertEquals(400, response.code()); - assertTrue(response.body().contains( - "The 'entity.change' parameter is required but was missing.")); - // non-matching name - validA.setName(ZONE1.getDnsName() + ".aaa."); - response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); - assertEquals(400, response.code()); - assertTrue(response.body().contains("additions[0].name")); - // wrong type - validA.setName(ZONE1.getDnsName()); // revert - validA.setType("ABCD"); - response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); - assertEquals(400, response.code()); - assertTrue(response.body().contains("additions[0].type")); - // wrong ttl - validA.setType("A"); // revert - validA.setTtl(-1); - response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); - assertEquals(400, response.code()); - assertTrue(response.body().contains("additions[0].ttl")); - validA.setTtl(null); // revert - // null name - validA.setName(null); - response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); - assertEquals(400, response.code()); - assertTrue(response.body().contains("additions[0].name")); - validA.setName(ZONE1.getDnsName()); - // null type - validA.setType(null); - response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); - assertEquals(400, response.code()); - assertTrue(response.body().contains("additions[0].type")); - validA.setType("A"); - // null rrdata - final List temp = validA.getRrdatas(); // preserve - validA.setRrdatas(null); - response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); - assertEquals(400, response.code()); - assertTrue(response.body().contains("additions[0].rrdata")); - validA.setRrdatas(temp); - // delete non-existent - ResourceRecordSet nonExistent = new ResourceRecordSet(); - nonExistent.setName(ZONE1.getDnsName()); - nonExistent.setType("AAAA"); - nonExistent.setRrdatas(ImmutableList.of("2607:f8b0:400a:801::1005")); - Change delete = new Change(); - delete.setDeletions(ImmutableList.of(nonExistent)); - response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, delete, null); - assertEquals(404, response.code()); - assertTrue(response.body().contains("deletions[0]")); + public void testMatchesCriteria() { + assertTrue(LocalDnsHelper.matchesCriteria(RRSET1, RRSET1.getName(), RRSET1.getType())); + assertFalse(LocalDnsHelper.matchesCriteria(RRSET1, RRSET1.getName(), "anothertype")); + assertTrue(LocalDnsHelper.matchesCriteria(RRSET1, null, RRSET1.getType())); + assertTrue(LocalDnsHelper.matchesCriteria(RRSET1, RRSET1.getName(), null)); + assertFalse(LocalDnsHelper.matchesCriteria(RRSET1, "anothername", RRSET1.getType())); + } + + @Test + public void testGetUniqueId() { + assertNotNull(LocalDnsHelper.getUniqueId(Lists.newLinkedList())); + } + + @Test + public void testRandomNameServers() { + assertEquals(4, LocalDnsHelper.randomNameservers().size()); } private static ManagedZone copyZone(ManagedZone original) { From 92b5525eb3317b7875a9d040562b1c8224d71e7e Mon Sep 17 00:00:00 2001 From: Martin Derka Date: Mon, 29 Feb 2016 12:17:35 -0800 Subject: [PATCH 5/9] Removed RrsetWapper and used tailMap in listing. Also fixed some codacy issues and added @VisibleForTesting annotations. --- .../gcloud/dns/testing/LocalDnsHelper.java | 418 +++++++----------- .../gcloud/dns/testing/OptionParsers.java | 10 +- .../dns/testing/LocalDnsHelperTest.java | 80 ++-- 3 files changed, 210 insertions(+), 298 deletions(-) diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java index 493811e78301..44dd2f5ec19b 100644 --- a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java @@ -27,12 +27,12 @@ import com.google.api.services.dns.model.Project; import com.google.api.services.dns.model.Quota; import com.google.api.services.dns.model.ResourceRecordSet; -import com.google.common.base.Function; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; -import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.io.ByteStreams; @@ -58,13 +58,15 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.NavigableMap; import java.util.NavigableSet; -import java.util.Objects; import java.util.Random; import java.util.Set; +import java.util.SortedMap; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; @@ -107,7 +109,7 @@ public class LocalDnsHelper { try { BASE_CONTEXT = new URI(CONTEXT); } catch (URISyntaxException e) { - throw new RuntimeException( + throw new IllegalArgumentException( "Could not initialize LocalDnsHelper due to URISyntaxException.", e); } } @@ -140,66 +142,7 @@ private enum CallRegex { } /** - * Wraps DNS data by adding a timestamp and id which is used for paging and listing. - */ - static class RrsetWrapper { - static final Function WRAP_FUNCTION = - new Function() { - @Override - public RrsetWrapper apply(ResourceRecordSet input) { - return new RrsetWrapper(input); - } - }; - private final ResourceRecordSet rrset; - private final Long timestamp = System.currentTimeMillis(); - private String id; - - RrsetWrapper(ResourceRecordSet rrset) { - // The constructor creates a copy in order to prevent side effects. - this.rrset = new ResourceRecordSet(); - this.rrset.setName(rrset.getName()); - this.rrset.setTtl(rrset.getTtl()); - this.rrset.setRrdatas(ImmutableList.copyOf(rrset.getRrdatas())); - this.rrset.setType(rrset.getType()); - } - - void setId(String id) { - this.id = id; - } - - String id() { - return id; - } - - /** - * Equals does not care about the listing id and timestamp metadata, just the rrset. - */ - @Override - public boolean equals(Object other) { - return (other instanceof RrsetWrapper) && Objects.equals(rrset, ((RrsetWrapper) other).rrset); - } - - @Override - public int hashCode() { - return Objects.hash(rrset); - } - - ResourceRecordSet rrset() { - return rrset; - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("rrset", rrset) - .add("timestamp", timestamp) - .add("id", id) - .toString(); - } - } - - /** - * Associates a project with a collection of ManagedZones. Thread safe. + * Associates a project with a collection of ManagedZones. */ static class ProjectContainer { private final Project project; @@ -220,29 +163,24 @@ ConcurrentSkipListMap zones() { } /** - * Associates a zone with a collection of changes and dns records. Thread safe. + * Associates a zone with a collection of changes and dns records. */ static class ZoneContainer { private final ManagedZone zone; - /** - * DNS records are held in a map to allow for atomic replacement of record sets when applying - * changes. The key for the map is always the zone name. The collection of records is immutable - * and must always exist, i.e., dnsRecords.get(zone.getName()) is never {@code null}. - */ - private final AtomicReference> - dnsRecords = new AtomicReference<>(ImmutableList.of()); + private final AtomicReference> + dnsRecords = new AtomicReference<>(ImmutableSortedMap.of()); private final ConcurrentLinkedQueue changes = new ConcurrentLinkedQueue<>(); ZoneContainer(ManagedZone zone) { this.zone = zone; - this.dnsRecords.set(ImmutableList.of()); + this.dnsRecords.set(ImmutableSortedMap.of()); } ManagedZone zone() { return zone; } - AtomicReference> dnsRecords() { + AtomicReference> dnsRecords() { return dnsRecords; } @@ -326,31 +264,28 @@ private String toJson(String message) throws IOException { private class RequestHandler implements HttpHandler { - /** - * Chooses the proper handler for a request. - */ private Response pickHandler(HttpExchange exchange, CallRegex regex) { String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); String[] tokens = path.split("/"); - String projectId = tokens != null && tokens.length > 0 ? tokens[0] : null; - String zoneName = tokens != null && tokens.length > 2 ? tokens[2] : null; - String changeId = tokens != null && tokens.length > 4 ? tokens[4] : null; + String projectId = tokens.length > 0 ? tokens[0] : null; + String zoneName = tokens.length > 2 ? tokens[2] : null; + String changeId = tokens.length > 4 ? tokens[4] : null; String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); switch (regex) { case CHANGE_GET: - return handleChangeGet(projectId, zoneName, changeId, query); + return getChange(projectId, zoneName, changeId, query); case CHANGE_LIST: - return handleChangeList(projectId, zoneName, query); + return listChanges(projectId, zoneName, query); case ZONE_GET: - return handleZoneGet(projectId, zoneName, query); + return getZone(projectId, zoneName, query); case ZONE_DELETE: - return handleZoneDelete(projectId, zoneName); + return deleteZone(projectId, zoneName); case ZONE_LIST: - return handleZoneList(projectId, query); + return listZones(projectId, query); case PROJECT_GET: - return handleProjectGet(projectId, query); + return getProject(projectId, query); case RECORD_LIST: - return handleDnsRecordList(projectId, zoneName, query); + return listDnsRecords(projectId, zoneName, query); case ZONE_CREATE: try { return handleZoneCreate(exchange, projectId, query); @@ -374,63 +309,32 @@ public void handle(HttpExchange exchange) throws IOException { String rawPath = exchange.getRequestURI().getRawPath(); for (CallRegex regex : CallRegex.values()) { if (requestMethod.equals(regex.method) && rawPath.matches(regex.pathRegex)) { - // there is a match, pass the handling accordingly Response response = pickHandler(exchange, regex); writeResponse(exchange, response); - return; // only one match is possible + return; } } - // could not be matched, the service returns 404 page not found here writeResponse(exchange, Error.NOT_FOUND.response(String.format( "The url %s does not match any API call.", exchange.getRequestURI()))); } - private Response handleZoneDelete(String projectId, String zoneName) { - return deleteZone(projectId, zoneName); - } - - private Response handleZoneGet(String projectId, String zoneName, String query) { - String[] fields = OptionParsers.parseGetOptions(query); - return getZone(projectId, zoneName, fields); - } - - private Response handleZoneList(String projectId, String query) { - Map options = OptionParsers.parseListZonesOptions(query); - return listZones(projectId, options); - } - - private Response handleProjectGet(String projectId, String query) { - String[] fields = OptionParsers.parseGetOptions(query); - return getProject(projectId, fields); - } - /** - * @throws IOException if the request cannot be parsed. + * @throws IOException if the request cannot be parsed */ private Response handleChangeCreate(HttpExchange exchange, String projectId, String zoneName, String query) throws IOException { String[] fields = OptionParsers.parseGetOptions(query); String requestBody = decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); - Change change = jsonFactory.fromString(requestBody, Change.class); + Change change; + try { + change = jsonFactory.fromString(requestBody, Change.class); + } catch (IllegalArgumentException ex) { + return Error.REQUIRED.response( + "The 'entity.change' parameter is required but was missing."); + } return createChange(projectId, zoneName, change, fields); } - private Response handleChangeGet(String projectId, String zoneName, - String changeId, String query) { - String[] fields = OptionParsers.parseGetOptions(query); - return getChange(projectId, zoneName, changeId, fields); - } - - private Response handleChangeList(String projectId, String zoneName, String query) { - Map options = OptionParsers.parseListChangesOptions(query); - return listChanges(projectId, zoneName, options); - } - - private Response handleDnsRecordList(String projectId, String zoneName, String query) { - Map options = OptionParsers.parseListDnsRecordsOptions(query); - return listDnsRecords(projectId, zoneName, options); - } - /** * @throws IOException if the request cannot be parsed. */ @@ -440,7 +344,6 @@ private Response handleZoneCreate(HttpExchange exchange, String projectId, Strin String requestBody = decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); ManagedZone zone; try { - // IllegalArgumentException if the request body is an empty string zone = jsonFactory.fromString(requestBody, ManagedZone.class); } catch (IllegalArgumentException ex) { return Error.REQUIRED.response( @@ -451,7 +354,7 @@ private Response handleZoneCreate(HttpExchange exchange, String projectId, Strin } private LocalDnsHelper(long delay) { - this.delayChange = delay; // 0 makes this synchronous + this.delayChange = delay; try { server = HttpServer.create(new InetSocketAddress(0), 0); port = server.getAddress().getPort(); @@ -540,6 +443,7 @@ private static String decodeContent(Headers headers, InputStream inputStream) th * * @param context managedZones | projects | rrsets | changes */ + @VisibleForTesting static Response toListResponse(List serializedObjects, String context, String pageToken, boolean includePageToken) { // start building response @@ -558,7 +462,7 @@ static Response toListResponse(List serializedObjects, String context, S /** * Prepares DNS records that are created by default for each zone. */ - private static ImmutableList defaultRecords(ManagedZone zone) { + private static ImmutableSortedMap defaultRecords(ManagedZone zone) { ResourceRecordSet soa = new ResourceRecordSet(); soa.setTtl(21600); soa.setName(zone.getDnsName()); @@ -572,17 +476,15 @@ private static ImmutableList defaultRecords(ManagedZone zone) { ns.setName(zone.getDnsName()); ns.setRrdatas(zone.getNameServers()); ns.setType("NS"); - RrsetWrapper nsWrapper = new RrsetWrapper(ns); - RrsetWrapper soaWrapper = new RrsetWrapper(soa); - ImmutableList results = ImmutableList.of(nsWrapper, soaWrapper); - nsWrapper.setId(getUniqueId(results)); - soaWrapper.setId(getUniqueId(results)); - return results; + String nsId = getUniqueId(ImmutableSet.of()); + String soaId = getUniqueId(ImmutableSet.of(nsId)); + return ImmutableSortedMap.of(nsId, ns, soaId, soa); } /** * Returns a list of four nameservers randomly chosen from the predefined set. */ + @VisibleForTesting static List randomNameservers() { ArrayList nameservers = Lists.newArrayList( "dns1.googlecloud.com", "dns2.googlecloud.com", "dns3.googlecloud.com", @@ -598,14 +500,8 @@ static List randomNameservers() { /** * Returns a hex string id (used for a dns record) unique within the set of wrappers. */ - static String getUniqueId(List wrappers) { - TreeSet ids = Sets.newTreeSet(Lists.transform(wrappers, - new Function() { - @Override - public String apply(RrsetWrapper input) { - return input.id() == null ? "null" : input.id(); - } - })); + @VisibleForTesting + static String getUniqueId(Set ids) { String id; do { id = Long.toHexString(System.currentTimeMillis()) @@ -620,6 +516,7 @@ public String apply(RrsetWrapper input) { /** * Tests if a DNS record matches name and type (if provided). Used for filtering. */ + @VisibleForTesting static boolean matchesCriteria(ResourceRecordSet record, String name, String type) { if (type != null && !record.getType().equals(type)) { return false; @@ -631,7 +528,7 @@ static boolean matchesCriteria(ResourceRecordSet record, String name, String typ * Returns a project container. Never returns {@code null} because we assume that all projects * exists. */ - ProjectContainer findProject(String projectId) { + private ProjectContainer findProject(String projectId) { ProjectContainer defaultProject = createProject(projectId); projects.putIfAbsent(projectId, defaultProject); return projects.get(projectId); @@ -640,6 +537,7 @@ ProjectContainer findProject(String projectId) { /** * Returns a zone container. Returns {@code null} if zone does not exist within project. */ + @VisibleForTesting ZoneContainer findZone(String projectId, String zoneName) { ProjectContainer projectContainer = findProject(projectId); // never null return projectContainer.zones().get(zoneName); @@ -648,6 +546,7 @@ ZoneContainer findZone(String projectId, String zoneName) { /** * Returns a change found by its id. Returns {@code null} if such a change does not exist. */ + @VisibleForTesting Change findChange(String projectId, String zoneName, String changeId) { ZoneContainer wrapper = findZone(projectId, zoneName); return wrapper == null ? null : wrapper.findChange(changeId); @@ -656,16 +555,16 @@ Change findChange(String projectId, String zoneName, String changeId) { /** * Returns a response to getChange service call. */ - Response getChange(String projectId, String zoneName, String changeId, String[] fields) { + @VisibleForTesting + Response getChange(String projectId, String zoneName, String changeId, String query) { + String[] fields = OptionParsers.parseGetOptions(query); Change change = findChange(projectId, zoneName, changeId); if (change == null) { ZoneContainer zone = findZone(projectId, zoneName); if (zone == null) { - // zone does not exist return Error.NOT_FOUND.response(String.format( "The 'parameters.managedZone' resource named '%s' does not exist.", zoneName)); } - // zone exists but change does not return Error.NOT_FOUND.response(String.format( "The 'parameters.changeId' resource named '%s' does not exist.", changeId)); } @@ -682,7 +581,9 @@ Response getChange(String projectId, String zoneName, String changeId, String[] /** * Returns a response to getZone service call. */ - Response getZone(String projectId, String zoneName, String[] fields) { + @VisibleForTesting + Response getZone(String projectId, String zoneName, String query) { + String[] fields = OptionParsers.parseGetOptions(query); ZoneContainer container = findZone(projectId, zoneName); if (container == null) { return Error.NOT_FOUND.response(String.format( @@ -701,7 +602,9 @@ Response getZone(String projectId, String zoneName, String[] fields) { * We assume that every project exists. If we do not have it in the collection yet, we just create * a new default project instance with default quota. */ - Response getProject(String projectId, String[] fields) { + @VisibleForTesting + Response getProject(String projectId, String query) { + String[] fields = OptionParsers.parseGetOptions(query); ProjectContainer defaultProject = createProject(projectId); projects.putIfAbsent(projectId, defaultProject); Project project = projects.get(projectId).project(); // project is now guaranteed to exist @@ -715,8 +618,7 @@ Response getProject(String projectId, String[] fields) { } /** - * Creates a project. It generates a project number randomly (we do not have project numbers - * available). + * Creates a project. It generates a project number randomly. */ private ProjectContainer createProject(String projectId) { Quota quota = new Quota(); @@ -734,21 +636,21 @@ private ProjectContainer createProject(String projectId) { return new ProjectContainer(project); } + @VisibleForTesting Response deleteZone(String projectId, String zoneName) { - ProjectContainer projectContainer = projects.get(projectId); - // check if empty ZoneContainer zone = findZone(projectId, zoneName); - ImmutableList wrappers = zone == null - ? ImmutableList.of() : zone.dnsRecords().get(); - for (RrsetWrapper current : wrappers) { - if (!ImmutableList.of("NS", "SOA").contains(current.rrset().getType())) { + ImmutableSortedMap rrsets = zone == null + ? ImmutableSortedMap.of() : zone.dnsRecords().get(); + ImmutableList defaults = ImmutableList.of("NS", "SOA"); + for (ResourceRecordSet current : rrsets.values()) { + if (!defaults.contains(current.getType())) { return Error.CONTAINER_NOT_EMPTY.response(String.format( "The resource named '%s' cannot be deleted because it is not empty", zoneName)); } } + ProjectContainer projectContainer = projects.get(projectId); ZoneContainer previous = projectContainer.zones.remove(zoneName); return previous == null - // map was not in the collection ? Error.NOT_FOUND.response(String.format( "The 'parameters.managedZone' resource named '%s' does not exist.", zoneName)) : new Response(HTTP_NO_CONTENT, "{}"); @@ -757,7 +659,8 @@ Response deleteZone(String projectId, String zoneName) { /** * Creates new managed zone and stores it in the collection. Assumes that project exists. */ - Response createZone(String projectId, ManagedZone zone, String[] fields) { + @VisibleForTesting + Response createZone(String projectId, ManagedZone zone, String... fields) { // check if the provided data is valid Response errorResponse = checkZone(zone); if (errorResponse != null) { @@ -797,7 +700,7 @@ Response createZone(String projectId, ManagedZone zone, String[] fields) { /** * Creates a new change, stores it, and if delayChange > 0, invokes processing in a new thread. */ - Response createChange(String projectId, String zoneName, Change change, String[] fields) { + Response createChange(String projectId, String zoneName, Change change, String... fields) { ZoneContainer zoneContainer = findZone(projectId, zoneName); if (zoneContainer == null) { return Error.NOT_FOUND.response(String.format( @@ -881,36 +784,43 @@ private void applyExistingChange(String projectId, String zoneName, String chang if (wrapper == null) { return; // no such zone exists; it might have been deleted by another thread } - AtomicReference> dnsRecords = wrapper.dnsRecords(); + AtomicReference> dnsRecords = + wrapper.dnsRecords(); while (true) { // managed zone must have a set of records which is not null - ImmutableList original = dnsRecords.get(); - List copy = Lists.newLinkedList(original); + ImmutableSortedMap original = dnsRecords.get(); + // the copy will be populated when handling deletions + SortedMap copy = new TreeMap<>(); // apply deletions first List deletions = change.getDeletions(); if (deletions != null) { - List transformedDeletions = Lists.transform(deletions, - RrsetWrapper.WRAP_FUNCTION); - copy.removeAll(transformedDeletions); + for (Map.Entry entry : original.entrySet()) { + if (!deletions.contains(entry.getValue())) { + copy.put(entry.getKey(), entry.getValue()); + } + } + } else { + copy.putAll(original); } // apply additions List additions = change.getAdditions(); if (additions != null) { for (ResourceRecordSet addition : additions) { - String id = getUniqueId(copy); - RrsetWrapper rrsetWrapper = new RrsetWrapper(addition); - rrsetWrapper.setId(id); - copy.add(rrsetWrapper); + ResourceRecordSet rrset = new ResourceRecordSet(); + rrset.setName(addition.getName()); + rrset.setRrdatas(ImmutableList.copyOf(addition.getRrdatas())); + rrset.setTtl(addition.getTtl()); + rrset.setType(addition.getType()); + String id = getUniqueId(copy.keySet()); + copy.put(id, rrset); } } // make it immutable and replace -// boolean success = dnsRecords.replace(zoneName, original, ImmutableList.copyOf(copy)); - boolean success = dnsRecords.compareAndSet(original, ImmutableList.copyOf(copy)); + boolean success = dnsRecords.compareAndSet(original, ImmutableSortedMap.copyOf(copy)); if (success) { break; // success if no other thread modified the value in the meantime } } - // set status to done change.setStatus("done"); } @@ -918,7 +828,9 @@ private void applyExistingChange(String projectId, String zoneName, String chang * Lists zones. Next page token is the last listed zone name and is returned only of there is more * to list. */ - Response listZones(String projectId, Map options) { + @VisibleForTesting + Response listZones(String projectId, String query) { + Map options = OptionParsers.parseListZonesOptions(query); Response response = checkListOptions(options); if (response != null) { return response; @@ -929,36 +841,34 @@ Response listZones(String projectId, Map options) { String pageToken = (String) options.get("pageToken"); Integer maxResults = options.get("maxResults") == null ? null : Integer.valueOf((String) options.get("maxResults")); - // matches will be included in the result if true - boolean listing = (pageToken == null || !containers.containsKey(pageToken)); boolean sizeReached = false; // maximum result size was reached, we should not return more boolean hasMorePages = false; // should next page token be included in the response? LinkedList serializedZones = new LinkedList<>(); String lastZoneName = null; - for (ZoneContainer zoneContainer : containers.values()) { + ConcurrentNavigableMap fragment = + pageToken != null && containers.containsKey(pageToken) + ? containers.tailMap(pageToken, false) + : containers; + for (ZoneContainer zoneContainer : fragment.values()) { ManagedZone zone = zoneContainer.zone(); - if (listing) { - if (dnsName == null || zone.getDnsName().equals(dnsName)) { - if (sizeReached) { - // we do not add this, just note that there would be more and there should be a token - hasMorePages = true; - break; - } else { - try { - lastZoneName = zone.getName(); - serializedZones.addLast(jsonFactory.toString( - OptionParsers.extractFields(zone, fields))); - } catch (IOException e) { - return Error.INTERNAL_ERROR.response(String.format( - "Error when serializing managed zone %s in project %s", zone.getName(), - projectId)); - } + if (dnsName == null || zone.getDnsName().equals(dnsName)) { + if (sizeReached) { + // we do not add this, just note that there would be more and there should be a token + hasMorePages = true; + break; + } else { + try { + lastZoneName = zone.getName(); + serializedZones.addLast(jsonFactory.toString( + OptionParsers.extractFields(zone, fields))); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response(String.format( + "Error when serializing managed zone %s in project %s", zone.getName(), + projectId)); } } } - // either we are listing already, or we check if we should start in the next iteration - listing = zone.getName().equals(pageToken) || listing; - sizeReached = (maxResults != null) && maxResults.equals(serializedZones.size()); + sizeReached = maxResults != null && maxResults.equals(serializedZones.size()); } boolean includePageToken = hasMorePages && (fields == null || ImmutableList.copyOf(fields).contains("nextPageToken")); @@ -968,7 +878,9 @@ Response listZones(String projectId, Map options) { /** * Lists DNS records for a zone. Next page token is ID of the last record listed. */ - Response listDnsRecords(String projectId, String zoneName, Map options) { + @VisibleForTesting + Response listDnsRecords(String projectId, String zoneName, String query) { + Map options = OptionParsers.parseListDnsRecordsOptions(query); Response response = checkListOptions(options); if (response != null) { return response; @@ -978,42 +890,40 @@ Response listDnsRecords(String projectId, String zoneName, Map o return Error.NOT_FOUND.response(String.format( "The 'parameters.managedZone' resource named '%s' does not exist.", zoneName)); } - List dnsRecords = zoneContainer.dnsRecords().get(); + ImmutableSortedMap dnsRecords = zoneContainer.dnsRecords().get(); String[] fields = (String[]) options.get("fields"); String name = (String) options.get("name"); String type = (String) options.get("type"); String pageToken = (String) options.get("pageToken"); + ImmutableSortedMap fragment = + pageToken != null && dnsRecords.containsKey(pageToken) + ? dnsRecords.tailMap(pageToken, false) : dnsRecords; Integer maxResults = options.get("maxResults") == null ? null : Integer.valueOf((String) options.get("maxResults")); - boolean listing = (pageToken == null); // matches will be included in the result if true boolean sizeReached = false; // maximum result size was reached, we should not return more boolean hasMorePages = false; // should next page token be included in the response? LinkedList serializedRrsets = new LinkedList<>(); String lastRecordId = null; - for (RrsetWrapper recordWrapper : dnsRecords) { - ResourceRecordSet record = recordWrapper.rrset(); - if (listing) { - if (matchesCriteria(record, name, type)) { - if (sizeReached) { - // we do not add this, just note that there would be more and there should be a token - hasMorePages = true; - break; - } else { - lastRecordId = recordWrapper.id(); - try { - serializedRrsets.addLast(jsonFactory.toString( - OptionParsers.extractFields(record, fields))); - } catch (IOException e) { - return Error.INTERNAL_ERROR.response(String.format( - "Error when serializing resource record set in managed zone %s in project %s", - zoneName, projectId)); - } + for (String recordId : fragment.keySet()) { + ResourceRecordSet record = fragment.get(recordId); + if (matchesCriteria(record, name, type)) { + if (sizeReached) { + // we do not add this, just note that there would be more and there should be a token + hasMorePages = true; + break; + } else { + lastRecordId = recordId; + try { + serializedRrsets.addLast(jsonFactory.toString( + OptionParsers.extractFields(record, fields))); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response(String.format( + "Error when serializing resource record set in managed zone %s in project %s", + zoneName, projectId)); } } } - // either we are listing already, or we check if we should start in the next iteration - listing = recordWrapper.id().equals(pageToken) || listing; - sizeReached = (maxResults != null) && maxResults.equals(serializedRrsets.size()); + sizeReached = maxResults != null && maxResults.equals(serializedRrsets.size()); } boolean includePageToken = hasMorePages && (fields == null || ImmutableList.copyOf(fields).contains("nextPageToken")); @@ -1023,7 +933,9 @@ Response listDnsRecords(String projectId, String zoneName, Map o /** * Lists changes. Next page token is ID of the last change listed. */ - Response listChanges(String projectId, String zoneName, Map options) { + @VisibleForTesting + Response listChanges(String projectId, String zoneName, String query) { + Map options = OptionParsers.parseListChangesOptions(query); Response response = checkListOptions(options); if (response != null) { return response; @@ -1034,7 +946,7 @@ Response listChanges(String projectId, String zoneName, Map opti "The 'parameters.managedZone' resource named '%s' does not exist", zoneName)); } // take a sorted snapshot of the current change list - TreeMap changes = new TreeMap<>(); + NavigableMap changes = new TreeMap<>(); for (Change c : zoneContainer.changes()) { if (c.getId() != null) { changes.put(Integer.valueOf(c.getId()), c); @@ -1045,40 +957,43 @@ Response listChanges(String projectId, String zoneName, Map opti String pageToken = (String) options.get("pageToken"); Integer maxResults = options.get("maxResults") == null ? null : Integer.valueOf((String) options.get("maxResults")); - // we are not reading sort by as it the only key is the change sequence + // we are not reading sortBy as the only key is the change sequence NavigableSet keys; if ("descending".equals(sortOrder)) { keys = changes.descendingKeySet(); } else { keys = changes.navigableKeySet(); } - boolean listing = (pageToken == null); // matches will be included in the result if true + Integer from = null; + try { + from = Integer.valueOf(pageToken); + } catch (NumberFormatException ex) { + // ignore page token + } + keys = from != null && keys.contains(from) ? keys.tailSet(from, false) : keys; + NavigableMap fragment = + from != null && changes.containsKey(from) ? changes.tailMap(from, false) : changes; boolean sizeReached = false; // maximum result size was reached, we should not return more boolean hasMorePages = false; // should next page token be included in the response? LinkedList serializedResults = new LinkedList<>(); String lastChangeId = null; for (Integer key : keys) { - Change change = changes.get(key); - if (listing) { - if (sizeReached) { - // we do not add this, just note that there would be more and there should be a token - hasMorePages = true; - break; - } else { - lastChangeId = change.getId(); - try { - serializedResults.addLast(jsonFactory.toString( - OptionParsers.extractFields(change, fields))); - } catch (IOException e) { - return Error.INTERNAL_ERROR.response(String.format( - "Error when serializing change %s in managed zone %s in project %s", - change.getId(), zoneName, projectId)); - } + Change change = fragment.get(key); + if (sizeReached) { + // we do not add this, just note that there would be more and there should be a token + hasMorePages = true; + break; + } else { + lastChangeId = change.getId(); + try { + serializedResults.addLast(jsonFactory.toString( + OptionParsers.extractFields(change, fields))); + } catch (IOException e) { + return Error.INTERNAL_ERROR.response(String.format( + "Error when serializing change %s in managed zone %s in project %s", + change.getId(), zoneName, projectId)); } } - - // either we are listing already, or we check if we should start in the next iteration - listing = change.getId().equals(pageToken) || listing; sizeReached = (maxResults != null) && maxResults.equals(serializedResults.size()); } boolean includePageToken = @@ -1089,7 +1004,7 @@ Response listChanges(String projectId, String zoneName, Map opti /** * Validates a zone to be created. */ - static Response checkZone(ManagedZone zone) { + private static Response checkZone(ManagedZone zone) { if (zone.getName() == null) { return Error.REQUIRED.response( "The 'entity.managedZone.name' parameter is required but was missing."); @@ -1127,6 +1042,7 @@ static Response checkZone(ManagedZone zone) { /** * Validates a change to be created. */ + @VisibleForTesting static Response checkChange(Change change, ZoneContainer zone) { if ((change.getDeletions() == null || change.getDeletions().size() <= 0) && (change.getAdditions() == null || change.getAdditions().size() <= 0)) { @@ -1163,6 +1079,7 @@ static Response checkChange(Change change, ZoneContainer zone) { * @param index the index or addition or deletion in the list * @param zone the zone that this change is applied to */ + @VisibleForTesting static Response checkRrset(ResourceRecordSet rrset, ZoneContainer zone, int index, String type) { if (rrset.getName() == null || !rrset.getName().endsWith(zone.zone().getDnsName())) { return Error.INVALID.response(String.format( @@ -1192,8 +1109,7 @@ static Response checkRrset(ResourceRecordSet rrset, ZoneContainer zone, int inde if ("deletions".equals(type)) { // check that deletion has a match by name and type boolean found = false; - for (RrsetWrapper rrsetWrapper : zone.dnsRecords().get()) { - ResourceRecordSet wrappedRrset = rrsetWrapper.rrset(); + for (ResourceRecordSet wrappedRrset : zone.dnsRecords().get().values()) { if (rrset.getName().equals(wrappedRrset.getName()) && rrset.getType().equals(wrappedRrset.getType())) { found = true; @@ -1207,7 +1123,7 @@ static Response checkRrset(ResourceRecordSet rrset, ZoneContainer zone, int inde } // if found, we still need an exact match if ("deletions".equals(type) - && !zone.dnsRecords().get().contains(new RrsetWrapper(rrset))) { + && !zone.dnsRecords().get().containsValue(rrset)) { // such a record does not exist return Error.CONDITION_NOT_MET.response(String.format( "Precondition not met for 'entity.change.deletions[%s]", index)); @@ -1225,8 +1141,7 @@ static Response additionsMeetDeletions(List additions, if (additions != null) { int index = 0; for (ResourceRecordSet rrset : additions) { - for (RrsetWrapper wrapper : zone.dnsRecords().get()) { - ResourceRecordSet wrappedRrset = wrapper.rrset(); + for (ResourceRecordSet wrappedRrset : zone.dnsRecords().get().values()) { if (rrset.getName().equals(wrappedRrset.getName()) && rrset.getType().equals(wrappedRrset.getType())) { // such a record exist and we must have a deletion @@ -1302,11 +1217,12 @@ static boolean checkRrData(String data, String type) { /** * Check supplied listing options. */ + @VisibleForTesting static Response checkListOptions(Map options) { // for general listing String maxResultsString = (String) options.get("maxResults"); if (maxResultsString != null) { - Integer maxResults = null; + Integer maxResults; try { maxResults = Integer.valueOf(maxResultsString); } catch (NumberFormatException ex) { diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/OptionParsers.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/OptionParsers.java index f77f05123dcc..4f9fe9e14302 100644 --- a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/OptionParsers.java +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/OptionParsers.java @@ -72,11 +72,11 @@ static String[] parseGetOptions(String query) { } } } - return null; + return new String[0]; } static ManagedZone extractFields(ManagedZone fullZone, String[] fields) { - if (fields == null) { + if (fields == null || fields.length == 0) { return fullZone; } ManagedZone managedZone = new ManagedZone(); @@ -111,18 +111,16 @@ static ManagedZone extractFields(ManagedZone fullZone, String[] fields) { } static Change extractFields(Change fullChange, String[] fields) { - if (fields == null) { + if (fields == null || fields.length == 0) { return fullChange; } Change change = new Change(); for (String field : fields) { switch (field) { case "additions": - // todo the fragmentation is ignored here as our api does not support it change.setAdditions(fullChange.getAdditions()); break; case "deletions": - // todo the fragmentation is ignored here as our api does not support it change.setDeletions(fullChange.getDeletions()); break; case "id": @@ -142,7 +140,7 @@ static Change extractFields(Change fullChange, String[] fields) { } static Project extractFields(Project fullProject, String[] fields) { - if (fields == null) { + if (fields == null || fields.length == 0) { return fullProject; } Project project = new Project(); diff --git a/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java index b03f6b56dfb3..7a054efdd2a7 100644 --- a/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java +++ b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java @@ -27,8 +27,10 @@ import com.google.api.services.dns.model.ManagedZone; import com.google.api.services.dns.model.Project; import com.google.api.services.dns.model.ResourceRecordSet; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Lists; import com.google.gcloud.dns.DnsException; import com.google.gcloud.spi.DefaultDnsRpc; @@ -40,6 +42,7 @@ import org.junit.Test; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -321,29 +324,29 @@ public void testDeleteZone() { @Test public void testCreateAndApplyChangeWithThreads() { - LocalDnsHelper localDnsThreaded = LocalDnsHelper.create(1000L); // using threads here - localDnsThreaded.createZone(PROJECT_ID1, ZONE1, null); + LocalDnsHelper localDnsThreaded = LocalDnsHelper.create(50L); // using threads here + localDnsThreaded.createZone(PROJECT_ID1, ZONE1); assertNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); LocalDnsHelper.Response response = - localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add + localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1); // add assertEquals(200, response.code()); assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); assertNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); - localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add - response = localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1, null); // add + localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1); // add + response = localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1); // add assertEquals(200, response.code()); assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); - localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2, null); // delete + localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2); // delete assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("3")); - localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE_KEEP, null); // id is "4" + localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE_KEEP); // check execution Change change = localDnsThreaded.findChange(PROJECT_ID1, ZONE_NAME1, "4"); for (int i = 0; i < 10 && !change.getStatus().equals("done"); i++) { // change has not been finished yet; wait at most 20 seconds - // it takes 5 seconds for the thread to kick in in the first place + // it takes 5O ms for the thread to kick in in the first place try { Thread.sleep(2 * 1000); } catch (InterruptedException e) { @@ -351,9 +354,9 @@ public void testCreateAndApplyChangeWithThreads() { } } assertEquals("done", change.getStatus()); - List list = + ImmutableSortedMap list = localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).dnsRecords().get(); - assertTrue(list.contains(new LocalDnsHelper.RrsetWrapper(RRSET_KEEP))); + assertTrue(list.containsValue(RRSET_KEEP)); localDnsThreaded.stop(); } @@ -424,7 +427,6 @@ public void testGetProject() { assertNotNull(project.getQuota()); } - @Test public void testCreateChange() { // non-existent zone @@ -663,7 +665,6 @@ public void testListZones() { assertEquals(zone.getName(), managedZoneListResult.pageToken()); } - @Test public void testListDnsRecords() { // no zone exists @@ -803,8 +804,6 @@ record = records.get(0); assertNotNull(resourceRecordSetListResult.pageToken()); } - - @Test public void testListChanges() { // no such zone exists @@ -1021,45 +1020,45 @@ public void testCreateZoneValidation() { // no name ManagedZone copy = copyZone(minimalZone); copy.setName(null); - LocalDnsHelper.Response response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy, null); + LocalDnsHelper.Response response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy); assertEquals(400, response.code()); assertTrue(response.body().contains("entity.managedZone.name")); // no description copy = copyZone(minimalZone); copy.setDescription(null); - response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy, null); + response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy); assertEquals(400, response.code()); assertTrue(response.body().contains("entity.managedZone.description")); // no dns name copy = copyZone(minimalZone); copy.setDnsName(null); - response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy, null); + response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy); assertEquals(400, response.code()); assertTrue(response.body().contains("entity.managedZone.dnsName")); // zone name is a number copy = copyZone(minimalZone); copy.setName("123456"); - response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy, null); + response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy); assertEquals(400, response.code()); assertTrue(response.body().contains("entity.managedZone.name")); assertTrue(response.body().contains("Invalid")); // dns name does not end with period copy = copyZone(minimalZone); copy.setDnsName("aaaaaa.com"); - response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy, null); + response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy); assertEquals(400, response.code()); assertTrue(response.body().contains("entity.managedZone.dnsName")); assertTrue(response.body().contains("Invalid")); // dns name is reserved copy = copyZone(minimalZone); copy.setDnsName("com."); - response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy, null); + response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy); assertEquals(400, response.code()); assertTrue(response.body().contains("not available to be created.")); // empty description should pass copy = copyZone(minimalZone); copy.setDescription(""); - response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy, null); + response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy); assertEquals(200, response.code()); } @@ -1143,8 +1142,8 @@ public void testCheckRrset() { valid.setTtl(500); Change validChange = new Change(); validChange.setAdditions(ImmutableList.of(valid)); - LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); - LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1); + LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange); // delete with field mismatch LocalDnsHelper.ZoneContainer zone = LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE_NAME1); valid.setTtl(valid.getTtl() + 20); @@ -1271,8 +1270,8 @@ public void testAdditionsMeetDeletions() { validA.setRrdatas(ImmutableList.of("0.255.1.5")); Change validChange = new Change(); validChange.setAdditions(ImmutableList.of(validA)); - LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); - LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1); + LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange); LocalDnsHelper.ZoneContainer container = LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE_NAME1); LocalDnsHelper.Response response = LocalDnsHelper.additionsMeetDeletions(ImmutableList.of(validA), null, container); @@ -1288,27 +1287,27 @@ public void testCreateChangeContentValidation() { validA.setRrdatas(ImmutableList.of("0.255.1.5")); Change validChange = new Change(); validChange.setAdditions(ImmutableList.of(validA)); - LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1, null); - LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + LOCAL_DNS_HELPER.createZone(PROJECT_ID1, ZONE1); + + LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange); LocalDnsHelper.Response response = - LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange, null); + LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange); assertEquals(409, response.code()); assertTrue(response.body().contains("already exists")); // delete with field mismatch Change delete = new Change(); validA.setTtl(20); // mismatch delete.setDeletions(ImmutableList.of(validA)); - response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, delete, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, delete); assertEquals(412, response.code()); assertTrue(response.body().contains("entity.change.deletions[0]")); // delete and add SOA Change addition = new Change(); - ImmutableList rrsetWrappers - = LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE_NAME1).dnsRecords().get(); + ImmutableCollection dnsRecords = + LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE_NAME1).dnsRecords().get().values(); LinkedList deletions = new LinkedList<>(); LinkedList additions = new LinkedList<>(); - for (LocalDnsHelper.RrsetWrapper wrapper : rrsetWrappers) { - ResourceRecordSet rrset = wrapper.rrset(); + for (ResourceRecordSet rrset : dnsRecords) { if (rrset.getType().equals("SOA")) { deletions.add(rrset); ResourceRecordSet copy = copyRrset(rrset); @@ -1319,12 +1318,12 @@ public void testCreateChangeContentValidation() { } delete.setDeletions(deletions); addition.setAdditions(additions); - response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, delete, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, delete); assertEquals(400, response.code()); assertTrue(response.body().contains( "zone must contain exactly one resource record set of type 'SOA' at the apex")); assertTrue(response.body().contains("deletions[0]")); - response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, addition, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, addition); assertEquals(400, response.code()); assertTrue(response.body().contains( "zone must contain exactly one resource record set of type 'SOA' at the apex")); @@ -1332,8 +1331,7 @@ public void testCreateChangeContentValidation() { // delete NS deletions = new LinkedList<>(); additions = new LinkedList<>(); - for (LocalDnsHelper.RrsetWrapper wrapper : rrsetWrappers) { - ResourceRecordSet rrset = wrapper.rrset(); + for (ResourceRecordSet rrset : dnsRecords) { if (rrset.getType().equals("NS")) { deletions.add(rrset); ResourceRecordSet copy = copyRrset(rrset); @@ -1344,18 +1342,18 @@ public void testCreateChangeContentValidation() { } delete.setDeletions(deletions); addition.setAdditions(additions); - response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, delete, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, delete); assertEquals(400, response.code()); assertTrue(response.body().contains( "zone must contain exactly one resource record set of type 'NS' at the apex")); - response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, addition, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, addition); assertEquals(400, response.code()); assertTrue(response.body().contains( "zone must contain exactly one resource record set of type 'NS' at the apex")); assertTrue(response.body().contains("additions[0]")); // change (delete + add) addition.setDeletions(deletions); - response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, addition, null); + response = LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, addition); assertEquals(200, response.code()); } @@ -1370,7 +1368,7 @@ public void testMatchesCriteria() { @Test public void testGetUniqueId() { - assertNotNull(LocalDnsHelper.getUniqueId(Lists.newLinkedList())); + assertNotNull(LocalDnsHelper.getUniqueId(new HashSet())); } @Test From a9e1852864a2e8150320bf08ed14c5e8dc3576af Mon Sep 17 00:00:00 2001 From: Martin Derka Date: Tue, 1 Mar 2016 10:11:23 -0800 Subject: [PATCH 6/9] Fixed tests and addressed codacy issues. Added fails to expected exceptions. Added error message checks. Fixed some formatting. Refactored some if statements as per codacy suggestions. --- .../gcloud/dns/testing/LocalDnsHelper.java | 64 ++++++--------- .../gcloud/dns/testing/OptionParsers.java | 8 +- .../dns/testing/LocalDnsHelperTest.java | 82 ++++++++++++------- 3 files changed, 85 insertions(+), 69 deletions(-) diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java index 44dd2f5ec19b..3175e15f337c 100644 --- a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java @@ -319,7 +319,7 @@ public void handle(HttpExchange exchange) throws IOException { } /** - * @throws IOException if the request cannot be parsed + * @throws IOException if the request cannot be parsed. */ private Response handleChangeCreate(HttpExchange exchange, String projectId, String zoneName, String query) throws IOException { @@ -340,7 +340,6 @@ private Response handleChangeCreate(HttpExchange exchange, String projectId, Str */ private Response handleZoneCreate(HttpExchange exchange, String projectId, String query) throws IOException { - String[] options = OptionParsers.parseGetOptions(query); String requestBody = decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); ManagedZone zone; try { @@ -349,6 +348,7 @@ private Response handleZoneCreate(HttpExchange exchange, String projectId, Strin return Error.REQUIRED.response( "The 'entity.managedZone' parameter is required but was missing."); } + String[] options = OptionParsers.parseGetOptions(query); return createZone(projectId, zone, options); } } @@ -411,6 +411,7 @@ private static void writeResponse(HttpExchange exchange, Response response) { exchange.getResponseHeaders().add("Connection", "close"); exchange.sendResponseHeaders(response.code(), response.body().length()); if (response.code() != 204) { + // the server automatically sends headers and closes output stream when 204 is returned outputStream.write(response.body().getBytes(StandardCharsets.UTF_8)); } outputStream.close(); @@ -446,7 +447,6 @@ private static String decodeContent(Headers headers, InputStream inputStream) th @VisibleForTesting static Response toListResponse(List serializedObjects, String context, String pageToken, boolean includePageToken) { - // start building response StringBuilder responseBody = new StringBuilder(); responseBody.append("{\"").append(context).append("\": ["); Joiner.on(",").appendTo(responseBody, serializedObjects); @@ -498,7 +498,7 @@ static List randomNameservers() { } /** - * Returns a hex string id (used for a dns record) unique within the set of wrappers. + * Returns a hex string id (used for a dns record) unique within the set of ids. */ @VisibleForTesting static String getUniqueId(Set ids) { @@ -557,7 +557,6 @@ Change findChange(String projectId, String zoneName, String changeId) { */ @VisibleForTesting Response getChange(String projectId, String zoneName, String changeId, String query) { - String[] fields = OptionParsers.parseGetOptions(query); Change change = findChange(projectId, zoneName, changeId); if (change == null) { ZoneContainer zone = findZone(projectId, zoneName); @@ -568,6 +567,7 @@ Response getChange(String projectId, String zoneName, String changeId, String qu return Error.NOT_FOUND.response(String.format( "The 'parameters.changeId' resource named '%s' does not exist.", changeId)); } + String[] fields = OptionParsers.parseGetOptions(query); Change result = OptionParsers.extractFields(change, fields); try { return new Response(HTTP_OK, jsonFactory.toString(result)); @@ -605,9 +605,7 @@ Response getZone(String projectId, String zoneName, String query) { @VisibleForTesting Response getProject(String projectId, String query) { String[] fields = OptionParsers.parseGetOptions(query); - ProjectContainer defaultProject = createProject(projectId); - projects.putIfAbsent(projectId, defaultProject); - Project project = projects.get(projectId).project(); // project is now guaranteed to exist + Project project = findProject(projectId).project(); // creates project if needed Project result = OptionParsers.extractFields(project, fields); try { return new Response(HTTP_OK, jsonFactory.toString(result)); @@ -719,7 +717,7 @@ Response createChange(String projectId, String zoneName, Change change, String.. if (change.getDeletions() != null) { completeChange.setDeletions(ImmutableList.copyOf(change.getDeletions())); } - /* We need set ID for the change. We are working in concurrent environment. We know that the + /* We need to set ID for the change. We are working in concurrent environment. We know that the element fell on an index between 0 and maxId, so we will reset all IDs in this range (all of them are valid for the respective objects). */ ConcurrentLinkedQueue changeSequence = zoneContainer.changes(); @@ -846,9 +844,7 @@ Response listZones(String projectId, String query) { LinkedList serializedZones = new LinkedList<>(); String lastZoneName = null; ConcurrentNavigableMap fragment = - pageToken != null && containers.containsKey(pageToken) - ? containers.tailMap(pageToken, false) - : containers; + pageToken != null ? containers.tailMap(pageToken, false) : containers; for (ZoneContainer zoneContainer : fragment.values()) { ManagedZone zone = zoneContainer.zone(); if (dnsName == null || zone.getDnsName().equals(dnsName)) { @@ -896,8 +892,7 @@ Response listDnsRecords(String projectId, String zoneName, String query) { String type = (String) options.get("type"); String pageToken = (String) options.get("pageToken"); ImmutableSortedMap fragment = - pageToken != null && dnsRecords.containsKey(pageToken) - ? dnsRecords.tailMap(pageToken, false) : dnsRecords; + pageToken != null ? dnsRecords.tailMap(pageToken, false) : dnsRecords; Integer maxResults = options.get("maxResults") == null ? null : Integer.valueOf((String) options.get("maxResults")); boolean sizeReached = false; // maximum result size was reached, we should not return more @@ -970,7 +965,7 @@ Response listChanges(String projectId, String zoneName, String query) { } catch (NumberFormatException ex) { // ignore page token } - keys = from != null && keys.contains(from) ? keys.tailSet(from, false) : keys; + keys = from != null ? keys.tailSet(from, false) : keys; NavigableMap fragment = from != null && changes.containsKey(from) ? changes.tailMap(from, false) : changes; boolean sizeReached = false; // maximum result size was reached, we should not return more @@ -994,7 +989,7 @@ Response listChanges(String projectId, String zoneName, String query) { change.getId(), zoneName, projectId)); } } - sizeReached = (maxResults != null) && maxResults.equals(serializedResults.size()); + sizeReached = maxResults != null && maxResults.equals(serializedResults.size()); } boolean includePageToken = hasMorePages && (fields == null || ImmutableList.copyOf(fields).contains("nextPageToken")); @@ -1068,7 +1063,7 @@ static Response checkChange(Change change, ZoneContainer zone) { counter++; } } - return additionsMeetDeletions(change.getAdditions(), change.getDeletions(), zone); + return checkAdditionsDeletions(change.getAdditions(), change.getDeletions(), zone); // null if everything is ok } @@ -1133,23 +1128,22 @@ static Response checkRrset(ResourceRecordSet rrset, ZoneContainer zone, int inde } /** - * Checks that for each record that already exists, we have a matching deletion. Furthermore, - * check that mandatory SOA and NS records stay. + * Checks against duplicate additions (for each record to be added that already exists, we must + * have a matching deletion. Furthermore, check that mandatory SOA and NS records stay. */ - static Response additionsMeetDeletions(List additions, + static Response checkAdditionsDeletions(List additions, List deletions, ZoneContainer zone) { if (additions != null) { int index = 0; for (ResourceRecordSet rrset : additions) { for (ResourceRecordSet wrappedRrset : zone.dnsRecords().get().values()) { if (rrset.getName().equals(wrappedRrset.getName()) - && rrset.getType().equals(wrappedRrset.getType())) { - // such a record exist and we must have a deletion - if (deletions == null || !deletions.contains(wrappedRrset)) { - return Error.ALREADY_EXISTS.response(String.format( - "The 'entity.change.additions[%s]' resource named '%s (%s)' already exists.", - index, rrset.getName(), rrset.getType())); - } + && rrset.getType().equals(wrappedRrset.getType()) + // such a record exist and we must have a deletion + && (deletions == null || !deletions.contains(wrappedRrset))) { + return Error.ALREADY_EXISTS.response(String.format( + "The 'entity.change.additions[%s]' resource named '%s (%s)' already exists.", + index, rrset.getName(), rrset.getType())); } } if (rrset.getType().equals("SOA") && findByNameAndType(deletions, null, "SOA") == null) { @@ -1235,19 +1229,15 @@ static Response checkListOptions(Map options) { } } String dnsName = (String) options.get("dnsName"); - if (dnsName != null) { - if (!dnsName.endsWith(".")) { - return Error.INVALID.response(String.format( - "Invalid value for 'parameters.dnsName': '%s'", dnsName)); - } + if (dnsName != null && !dnsName.endsWith(".")) { + return Error.INVALID.response(String.format( + "Invalid value for 'parameters.dnsName': '%s'", dnsName)); } // for listing dns records, name must be fully qualified String name = (String) options.get("name"); - if (name != null) { - if (!name.endsWith(".")) { - return Error.INVALID.response(String.format( - "Invalid value for 'parameters.name': '%s'", name)); - } + if (name != null && !name.endsWith(".")) { + return Error.INVALID.response(String.format( + "Invalid value for 'parameters.name': '%s'", name)); } String type = (String) options.get("type"); // must be provided with name if (type != null) { diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/OptionParsers.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/OptionParsers.java index 4f9fe9e14302..dcebf81350ad 100644 --- a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/OptionParsers.java +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/OptionParsers.java @@ -75,7 +75,7 @@ static String[] parseGetOptions(String query) { return new String[0]; } - static ManagedZone extractFields(ManagedZone fullZone, String[] fields) { + static ManagedZone extractFields(ManagedZone fullZone, String... fields) { if (fields == null || fields.length == 0) { return fullZone; } @@ -110,7 +110,7 @@ static ManagedZone extractFields(ManagedZone fullZone, String[] fields) { return managedZone; } - static Change extractFields(Change fullChange, String[] fields) { + static Change extractFields(Change fullChange, String... fields) { if (fields == null || fields.length == 0) { return fullChange; } @@ -139,7 +139,7 @@ static Change extractFields(Change fullChange, String[] fields) { return change; } - static Project extractFields(Project fullProject, String[] fields) { + static Project extractFields(Project fullProject, String... fields) { if (fields == null || fields.length == 0) { return fullProject; } @@ -162,7 +162,7 @@ static Project extractFields(Project fullProject, String[] fields) { return project; } - static ResourceRecordSet extractFields(ResourceRecordSet fullRecord, String[] fields) { + static ResourceRecordSet extractFields(ResourceRecordSet fullRecord, String... fields) { if (fields == null) { return fullRecord; } diff --git a/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java index 7a054efdd2a7..2dfb6c05c27e 100644 --- a/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java +++ b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java @@ -76,11 +76,11 @@ public static void before() { ZONE1.setName(ZONE_NAME1); ZONE1.setDescription(""); ZONE1.setDnsName(DNS_NAME); - ZONE1.setNameServerSet("somenameserveset"); + ZONE1.setNameServerSet("somenameserverset"); ZONE2.setName(ZONE_NAME2); ZONE2.setDescription(""); ZONE2.setDnsName(DNS_NAME); - ZONE2.setNameServerSet("somenameserveset"); + ZONE2.setNameServerSet("somenameserverset"); RRSET1.setName(DNS_NAME); RRSET1.setType(RRSET_TYPE); RRSET1.setRrdatas(ImmutableList.of("1.1.1.1")); @@ -119,9 +119,9 @@ public static void after() { public void testCreateZone() { ManagedZone created = RPC.create(ZONE1, EMPTY_RPC_OPTIONS); // check that default records were created - DnsRpc.ListResult resourceRecordSetListResult + DnsRpc.ListResult listResult = RPC.listDnsRecords(ZONE1.getName(), EMPTY_RPC_OPTIONS); - assertEquals(2, Lists.newLinkedList(resourceRecordSetListResult.results()).size()); + assertEquals(2, Lists.newLinkedList(listResult.results()).size()); assertEquals(created, LOCAL_DNS_HELPER.findZone(REAL_PROJECT_ID, ZONE1.getName()).zone()); ManagedZone zone = RPC.getZone(ZONE_NAME1, EMPTY_RPC_OPTIONS); assertEquals(created, zone); @@ -315,10 +315,12 @@ public void testDeleteZone() { RPC.create(ZONE1, EMPTY_RPC_OPTIONS); RPC.applyChangeRequest(ZONE1.getName(), CHANGE_KEEP, EMPTY_RPC_OPTIONS); try { - assertFalse(RPC.deleteZone(ZONE1.getName())); + RPC.deleteZone(ZONE1.getName()); + fail(); } catch (DnsException ex) { // expected assertEquals(400, ex.code()); + assertTrue(ex.getMessage().contains("not empty")); } } @@ -345,10 +347,10 @@ public void testCreateAndApplyChangeWithThreads() { // check execution Change change = localDnsThreaded.findChange(PROJECT_ID1, ZONE_NAME1, "4"); for (int i = 0; i < 10 && !change.getStatus().equals("done"); i++) { - // change has not been finished yet; wait at most 20 seconds + // change has not been finished yet; wait at most 5 seconds // it takes 5O ms for the thread to kick in in the first place try { - Thread.sleep(2 * 1000); + Thread.sleep(500); } catch (InterruptedException e) { fail("Test was interrupted"); } @@ -365,7 +367,7 @@ public void testCreateAndApplyChange() { // not using threads RPC.create(ZONE1, EMPTY_RPC_OPTIONS); assertNull(RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS)); - //add + // add Change createdChange = RPC.applyChangeRequest(ZONE1.getName(), CHANGE1, EMPTY_RPC_OPTIONS); assertEquals(createdChange.getAdditions(), CHANGE1.getAdditions()); assertEquals(createdChange.getDeletions(), CHANGE1.getDeletions()); @@ -492,9 +494,11 @@ public void testGetChange() { // non-existent zone try { RPC.getChangeRequest(ZONE_NAME2, "1", EMPTY_RPC_OPTIONS); + fail(); } catch (DnsException ex) { // expected assertEquals(404, ex.code()); + assertTrue(ex.getMessage().contains("managedZone")); } // field options RPC.applyChangeRequest(ZONE1.getName(), CHANGE_KEEP, EMPTY_RPC_OPTIONS); @@ -558,17 +562,21 @@ public void testListZones() { options.put(DnsRpc.Option.PAGE_SIZE, 0); try { RPC.listZones(options); + fail(); } catch (DnsException ex) { // expected assertEquals(400, ex.code()); + assertTrue(ex.getMessage().contains("parameters.maxResults")); } options = new HashMap<>(); options.put(DnsRpc.Option.PAGE_SIZE, -1); try { RPC.listZones(options); + fail(); } catch (DnsException ex) { // expected assertEquals(400, ex.code()); + assertTrue(ex.getMessage().contains("parameters.maxResults")); } // ok size options = new HashMap<>(); @@ -581,9 +589,11 @@ public void testListZones() { options.put(DnsRpc.Option.DNS_NAME, "aaa"); try { RPC.listZones(options); + fail(); } catch (DnsException ex) { // expected assertEquals(400, ex.code()); + assertTrue(ex.getMessage().contains("parameters.dnsName")); } // ok name options = new HashMap<>(); @@ -639,9 +649,9 @@ public void testListZones() { assertNull(zone.getNameServerSet()); assertNull(zone.getId()); options.put(DnsRpc.Option.FIELDS, "managedZones(nameServerSet)"); - DnsRpc.ListResult managedZoneListResult = RPC.listZones(options); - zone = managedZoneListResult.results().iterator().next(); - assertNull(managedZoneListResult.pageToken()); + DnsRpc.ListResult listResult = RPC.listZones(options); + zone = listResult.results().iterator().next(); + assertNull(listResult.pageToken()); assertNull(zone.getCreationTime()); assertNull(zone.getName()); assertNull(zone.getDnsName()); @@ -653,8 +663,8 @@ public void testListZones() { options.put(DnsRpc.Option.FIELDS, "managedZones(nameServerSet,description,id,name),nextPageToken"); options.put(DnsRpc.Option.PAGE_SIZE, 1); - managedZoneListResult = RPC.listZones(options); - zone = managedZoneListResult.results().iterator().next(); + listResult = RPC.listZones(options); + zone = listResult.results().iterator().next(); assertNull(zone.getCreationTime()); assertNotNull(zone.getName()); assertNull(zone.getDnsName()); @@ -662,7 +672,7 @@ public void testListZones() { assertNull(zone.getNameServers()); assertNotNull(zone.getNameServerSet()); assertNotNull(zone.getId()); - assertEquals(zone.getName(), managedZoneListResult.pageToken()); + assertEquals(zone.getName(), listResult.pageToken()); } @Test @@ -670,9 +680,11 @@ public void testListDnsRecords() { // no zone exists try { RPC.listDnsRecords(ZONE_NAME1, EMPTY_RPC_OPTIONS); + fail(); } catch (DnsException ex) { // expected assertEquals(404, ex.code()); + assertTrue(ex.getMessage().contains("managedZone")); } // zone exists but has no records RPC.create(ZONE1, EMPTY_RPC_OPTIONS); @@ -690,16 +702,20 @@ public void testListDnsRecords() { options.put(DnsRpc.Option.PAGE_SIZE, 0); try { RPC.listDnsRecords(ZONE1.getName(), options); + fail(); } catch (DnsException ex) { // expected assertEquals(400, ex.code()); + assertTrue(ex.getMessage().contains("parameters.maxResults")); } options.put(DnsRpc.Option.PAGE_SIZE, -1); try { RPC.listDnsRecords(ZONE1.getName(), options); + fail(); } catch (DnsException ex) { // expected assertEquals(400, ex.code()); + assertTrue(ex.getMessage().contains("parameters.maxResults")); } options.put(DnsRpc.Option.PAGE_SIZE, 1); results = RPC.listDnsRecords(ZONE1.getName(), options).results(); @@ -714,9 +730,11 @@ public void testListDnsRecords() { options.put(DnsRpc.Option.NAME, "aaa"); try { RPC.listDnsRecords(ZONE1.getName(), options); + fail(); } catch (DnsException ex) { // expected assertEquals(400, ex.code()); + assertTrue(ex.getMessage().contains("parameters.name")); } options.put(DnsRpc.Option.NAME, "aaa."); results = RPC.listDnsRecords(ZONE1.getName(), options).results(); @@ -726,17 +744,21 @@ public void testListDnsRecords() { options.put(DnsRpc.Option.DNS_TYPE, "A"); try { RPC.listDnsRecords(ZONE1.getName(), options); + fail(); } catch (DnsException ex) { // expected assertEquals(400, ex.code()); + assertTrue(ex.getMessage().contains("parameters.name")); } options.put(DnsRpc.Option.NAME, "aaa."); options.put(DnsRpc.Option.DNS_TYPE, "a"); try { RPC.listDnsRecords(ZONE1.getName(), options); + fail(); } catch (DnsException ex) { // expected assertEquals(400, ex.code()); + assertTrue(ex.getMessage().contains("parameters.type")); } options.put(DnsRpc.Option.NAME, DNS_NAME); options.put(DnsRpc.Option.DNS_TYPE, "SOA"); @@ -809,9 +831,11 @@ public void testListChanges() { // no such zone exists try { RPC.listChangeRequests(ZONE_NAME1, EMPTY_RPC_OPTIONS); + fail(); } catch (DnsException ex) { // expected assertEquals(404, ex.code()); + assertTrue(ex.getMessage().contains("managedZone")); } // zone exists but has no changes RPC.create(ZONE1, EMPTY_RPC_OPTIONS); @@ -830,24 +854,25 @@ public void testListChanges() { options.put(DnsRpc.Option.PAGE_SIZE, 0); try { RPC.listChangeRequests(ZONE1.getName(), options); + fail(); } catch (DnsException ex) { // expected assertEquals(400, ex.code()); + assertTrue(ex.getMessage().contains("parameters.maxResults")); } options.put(DnsRpc.Option.PAGE_SIZE, -1); try { RPC.listChangeRequests(ZONE1.getName(), options); + fail(); } catch (DnsException ex) { // expected assertEquals(400, ex.code()); + assertTrue(ex.getMessage().contains("parameters.maxResults")); } options.put(DnsRpc.Option.PAGE_SIZE, 15); - try { - RPC.listChangeRequests(ZONE1.getName(), options); - } catch (DnsException ex) { - // expected - assertEquals(400, ex.code()); - } + results = RPC.listChangeRequests(ZONE1.getName(), options).results(); + changes = ImmutableList.copyOf(results); + assertEquals(3, changes.size()); options = new HashMap<>(); options.put(DnsRpc.Option.SORTING_ORDER, "descending"); results = RPC.listChangeRequests(ZONE1.getName(), options).results(); @@ -862,9 +887,11 @@ public void testListChanges() { options.put(DnsRpc.Option.SORTING_ORDER, "something else"); try { RPC.listChangeRequests(ZONE1.getName(), options); + fail(); } catch (DnsException ex) { // expected assertEquals(400, ex.code()); + assertTrue(ex.getMessage().contains("parameters.sortOrder")); } // field options RPC.applyChangeRequest(ZONE1.getName(), CHANGE_COMPLEX, EMPTY_RPC_OPTIONS); @@ -941,15 +968,15 @@ public void testDnsRecordPaging() { RPC.listDnsRecords(ZONE1.getName(), EMPTY_RPC_OPTIONS).results()); Map options = new HashMap<>(); options.put(DnsRpc.Option.PAGE_SIZE, 1); - DnsRpc.ListResult resourceRecordSetListResult = + DnsRpc.ListResult listResult = RPC.listDnsRecords(ZONE1.getName(), options); ImmutableList records = - ImmutableList.copyOf(resourceRecordSetListResult.results()); + ImmutableList.copyOf(listResult.results()); assertEquals(1, records.size()); assertEquals(complete.get(0), records.get(0)); - options.put(DnsRpc.Option.PAGE_TOKEN, resourceRecordSetListResult.pageToken()); - resourceRecordSetListResult = RPC.listDnsRecords(ZONE1.getName(), options); - records = ImmutableList.copyOf(resourceRecordSetListResult.results()); + options.put(DnsRpc.Option.PAGE_TOKEN, listResult.pageToken()); + listResult = RPC.listDnsRecords(ZONE1.getName(), options); + records = ImmutableList.copyOf(listResult.results()); assertEquals(1, records.size()); assertEquals(complete.get(1), records.get(0)); } @@ -1132,7 +1159,6 @@ public void testCheckListOptions() { assertTrue(response.body().contains("parameters.sortOrder")); } - // help @Test public void testCheckRrset() { ResourceRecordSet valid = new ResourceRecordSet(); @@ -1274,7 +1300,7 @@ public void testAdditionsMeetDeletions() { LOCAL_DNS_HELPER.createChange(PROJECT_ID1, ZONE_NAME1, validChange); LocalDnsHelper.ZoneContainer container = LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE_NAME1); LocalDnsHelper.Response response = - LocalDnsHelper.additionsMeetDeletions(ImmutableList.of(validA), null, container); + LocalDnsHelper.checkAdditionsDeletions(ImmutableList.of(validA), null, container); assertEquals(409, response.code()); assertTrue(response.body().contains("already exists")); } @@ -1307,7 +1333,7 @@ public void testCreateChangeContentValidation() { LOCAL_DNS_HELPER.findZone(PROJECT_ID1, ZONE_NAME1).dnsRecords().get().values(); LinkedList deletions = new LinkedList<>(); LinkedList additions = new LinkedList<>(); - for (ResourceRecordSet rrset : dnsRecords) { + for (ResourceRecordSet rrset : dnsRecords) { if (rrset.getType().equals("SOA")) { deletions.add(rrset); ResourceRecordSet copy = copyRrset(rrset); From 1be9ccccc0678c913bf117e22682fdb0d9284c69 Mon Sep 17 00:00:00 2001 From: Martin Derka Date: Thu, 3 Mar 2016 10:22:52 -0800 Subject: [PATCH 7/9] Refactored change test. Fixed docs and formatting. Addressed remaining codacy issues. Added missing fails. --- .../gcloud/dns/testing/LocalDnsHelper.java | 77 +++--- .../gcloud/dns/testing/OptionParsers.java | 3 +- .../dns/testing/LocalDnsHelperTest.java | 258 ++++++++++-------- 3 files changed, 173 insertions(+), 165 deletions(-) diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java index 3175e15f337c..4eca683f1a08 100644 --- a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java @@ -54,6 +54,7 @@ import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -71,10 +72,11 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; /** - * A utility to create local Google Cloud DNS mock. + * A local Google Cloud DNS mock. * *

The mock runs in a separate thread, listening for HTTP requests on the local machine at an * ephemeral port. @@ -82,7 +84,7 @@ *

While the mock attempts to simulate the service, there are some differences in the behaviour. * The mock will accept any project ID and never returns a notFound or another error because of * project ID. It assumes that all project IDs exist and that the user has all the necessary - * privileges to manipulate any project. Similarly, the local simulation does not work with any + * privileges to manipulate any project. Similarly, the local simulation does not require * verification of domain name ownership. Any request for creating a managed zone will be approved. * The mock does not track quota and will allow the user to exceed it. The mock provides only basic * validation of the DNS data for records of type A and AAAA. It does not validate any other record @@ -98,12 +100,12 @@ public class LocalDnsHelper { private static final Random ID_GENERATOR = new Random(); private static final String VERSION = "v1"; private static final String CONTEXT = "/dns/" + VERSION + "/projects"; - private static final Set SUPPORTED_COMPRESSION_ENCODINGS = - ImmutableSet.of("gzip", "x-gzip"); + private static final Set SUPPORTED_ENCODINGS = ImmutableSet.of("gzip", "x-gzip"); private static final List TYPES = ImmutableList.of("A", "AAAA", "CNAME", "MX", "NAPTR", "NS", "PTR", "SOA", "SPF", "SRV", "TXT"); private static final TreeSet FORBIDDEN = Sets.newTreeSet( ImmutableList.of("google.com.", "com.", "example.com.", "net.", "org.")); + private static final Pattern ZONE_NAME_RE = Pattern.compile("[a-z][a-z0-9-]*"); static { try { @@ -265,12 +267,13 @@ private String toJson(String message) throws IOException { private class RequestHandler implements HttpHandler { private Response pickHandler(HttpExchange exchange, CallRegex regex) { - String path = BASE_CONTEXT.relativize(exchange.getRequestURI()).getPath(); + URI relative = BASE_CONTEXT.relativize(exchange.getRequestURI()); + String path = relative.getPath(); String[] tokens = path.split("/"); String projectId = tokens.length > 0 ? tokens[0] : null; String zoneName = tokens.length > 2 ? tokens[2] : null; String changeId = tokens.length > 4 ? tokens[4] : null; - String query = BASE_CONTEXT.relativize(exchange.getRequestURI()).getQuery(); + String query = relative.getQuery(); switch (regex) { case CHANGE_GET: return getChange(projectId, zoneName, changeId, query); @@ -315,7 +318,8 @@ public void handle(HttpExchange exchange) throws IOException { } } writeResponse(exchange, Error.NOT_FOUND.response(String.format( - "The url %s does not match any API call.", exchange.getRequestURI()))); + "The url %s for %s method does not match any API call.", + requestMethod, exchange.getRequestURI()))); } /** @@ -323,7 +327,6 @@ public void handle(HttpExchange exchange) throws IOException { */ private Response handleChangeCreate(HttpExchange exchange, String projectId, String zoneName, String query) throws IOException { - String[] fields = OptionParsers.parseGetOptions(query); String requestBody = decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody()); Change change; try { @@ -332,6 +335,7 @@ private Response handleChangeCreate(HttpExchange exchange, String projectId, Str return Error.REQUIRED.response( "The 'entity.change' parameter is required but was missing."); } + String[] fields = OptionParsers.parseGetOptions(query); return createChange(projectId, zoneName, change, fields); } @@ -426,7 +430,7 @@ private static String decodeContent(Headers headers, InputStream inputStream) th try { if (contentEncoding != null && !contentEncoding.isEmpty()) { String encoding = contentEncoding.get(0); - if (SUPPORTED_COMPRESSION_ENCODINGS.contains(encoding)) { + if (SUPPORTED_ENCODINGS.contains(encoding)) { input = new GZIPInputStream(inputStream); } else if (!"identity".equals(encoding)) { throw new IOException( @@ -451,7 +455,7 @@ static Response toListResponse(List serializedObjects, String context, S responseBody.append("{\"").append(context).append("\": ["); Joiner.on(",").appendTo(responseBody, serializedObjects); responseBody.append(']'); - // add page token only if exists and is asked for + // add page token only if it exists and is asked for if (pageToken != null && includePageToken) { responseBody.append(",\"nextPageToken\": \"").append(pageToken).append('"'); } @@ -506,9 +510,6 @@ static String getUniqueId(Set ids) { do { id = Long.toHexString(System.currentTimeMillis()) + Long.toHexString(Math.abs(ID_GENERATOR.nextLong())); - if (!ids.contains(id)) { - return id; - } } while (ids.contains(id)); return id; } @@ -659,12 +660,10 @@ Response deleteZone(String projectId, String zoneName) { */ @VisibleForTesting Response createZone(String projectId, ManagedZone zone, String... fields) { - // check if the provided data is valid Response errorResponse = checkZone(zone); if (errorResponse != null) { return errorResponse; } - // create a copy of the managed zone in order to avoid side effects ManagedZone completeZone = new ManagedZone(); completeZone.setName(zone.getName()); completeZone.setDnsName(zone.getDnsName()); @@ -675,9 +674,7 @@ Response createZone(String projectId, ManagedZone zone, String... fields) { completeZone.setId(BigInteger.valueOf(Math.abs(ID_GENERATOR.nextLong() % Long.MAX_VALUE))); completeZone.setNameServers(randomNameservers()); ZoneContainer zoneContainer = new ZoneContainer(completeZone); - // create the default NS and SOA records zoneContainer.dnsRecords().set(defaultRecords(completeZone)); - // place the zone in the data collection ProjectContainer projectContainer = findProject(projectId); ZoneContainer oldValue = projectContainer.zones().putIfAbsent( completeZone.getName(), zoneContainer); @@ -685,7 +682,6 @@ Response createZone(String projectId, ManagedZone zone, String... fields) { return Error.ALREADY_EXISTS.response(String.format( "The resource 'entity.managedZone' named '%s' already exists", completeZone.getName())); } - // now return the desired attributes ManagedZone result = OptionParsers.extractFields(completeZone, fields); try { return new Response(HTTP_OK, jsonFactory.toString(result)); @@ -704,13 +700,11 @@ Response createChange(String projectId, String zoneName, Change change, String.. return Error.NOT_FOUND.response(String.format( "The 'parameters.managedZone' resource named %s does not exist.", zoneName)); } - // check that the change to be applied is valid Response response = checkChange(change, zoneContainer); if (response != null) { return response; } - // start applying - Change completeChange = new Change(); // copy to avoid side effects + Change completeChange = new Change(); if (change.getAdditions() != null) { completeChange.setAdditions(ImmutableList.copyOf(change.getAdditions())); } @@ -728,11 +722,11 @@ Response createChange(String projectId, String zoneName, Change change, String.. if (index == maxId) { break; } - c.setId(String.valueOf(++index)); // indexing from 1 + c.setId(String.valueOf(++index)); } - completeChange.setStatus("pending"); // not finished yet + completeChange.setStatus("pending"); completeChange.setStartTime(ISODateTimeFormat.dateTime().withZoneUTC() - .print(System.currentTimeMillis())); // accepted + .print(System.currentTimeMillis())); invokeChange(projectId, zoneName, completeChange.getId()); Change result = OptionParsers.extractFields(completeChange, fields); try { @@ -813,7 +807,6 @@ private void applyExistingChange(String projectId, String zoneName, String chang copy.put(id, rrset); } } - // make it immutable and replace boolean success = dnsRecords.compareAndSet(original, ImmutableSortedMap.copyOf(copy)); if (success) { break; // success if no other thread modified the value in the meantime @@ -824,7 +817,7 @@ private void applyExistingChange(String projectId, String zoneName, String chang /** * Lists zones. Next page token is the last listed zone name and is returned only of there is more - * to list. + * to list and if the user does not exclude nextPageToken from field options. */ @VisibleForTesting Response listZones(String projectId, String query) { @@ -839,8 +832,8 @@ Response listZones(String projectId, String query) { String pageToken = (String) options.get("pageToken"); Integer maxResults = options.get("maxResults") == null ? null : Integer.valueOf((String) options.get("maxResults")); - boolean sizeReached = false; // maximum result size was reached, we should not return more - boolean hasMorePages = false; // should next page token be included in the response? + boolean sizeReached = false; + boolean hasMorePages = false; LinkedList serializedZones = new LinkedList<>(); String lastZoneName = null; ConcurrentNavigableMap fragment = @@ -859,20 +852,19 @@ Response listZones(String projectId, String query) { OptionParsers.extractFields(zone, fields))); } catch (IOException e) { return Error.INTERNAL_ERROR.response(String.format( - "Error when serializing managed zone %s in project %s", zone.getName(), - projectId)); + "Error when serializing managed zone %s in project %s", lastZoneName, projectId)); } } } sizeReached = maxResults != null && maxResults.equals(serializedZones.size()); } boolean includePageToken = - hasMorePages && (fields == null || ImmutableList.copyOf(fields).contains("nextPageToken")); + hasMorePages && (fields == null || Arrays.asList(fields).contains("nextPageToken")); return toListResponse(serializedZones, "managedZones", lastZoneName, includePageToken); } /** - * Lists DNS records for a zone. Next page token is ID of the last record listed. + * Lists DNS records for a zone. Next page token is the ID of the last record listed. */ @VisibleForTesting Response listDnsRecords(String projectId, String zoneName, String query) { @@ -895,8 +887,8 @@ Response listDnsRecords(String projectId, String zoneName, String query) { pageToken != null ? dnsRecords.tailMap(pageToken, false) : dnsRecords; Integer maxResults = options.get("maxResults") == null ? null : Integer.valueOf((String) options.get("maxResults")); - boolean sizeReached = false; // maximum result size was reached, we should not return more - boolean hasMorePages = false; // should next page token be included in the response? + boolean sizeReached = false; + boolean hasMorePages = false; LinkedList serializedRrsets = new LinkedList<>(); String lastRecordId = null; for (String recordId : fragment.keySet()) { @@ -921,12 +913,12 @@ Response listDnsRecords(String projectId, String zoneName, String query) { sizeReached = maxResults != null && maxResults.equals(serializedRrsets.size()); } boolean includePageToken = - hasMorePages && (fields == null || ImmutableList.copyOf(fields).contains("nextPageToken")); + hasMorePages && (fields == null || Arrays.asList(fields).contains("nextPageToken")); return toListResponse(serializedRrsets, "rrsets", lastRecordId, includePageToken); } /** - * Lists changes. Next page token is ID of the last change listed. + * Lists changes. Next page token is the ID of the last change listed. */ @VisibleForTesting Response listChanges(String projectId, String zoneName, String query) { @@ -952,7 +944,7 @@ Response listChanges(String projectId, String zoneName, String query) { String pageToken = (String) options.get("pageToken"); Integer maxResults = options.get("maxResults") == null ? null : Integer.valueOf((String) options.get("maxResults")); - // we are not reading sortBy as the only key is the change sequence + // as the only supported field is change sequence, we are not reading sortBy NavigableSet keys; if ("descending".equals(sortOrder)) { keys = changes.descendingKeySet(); @@ -968,8 +960,8 @@ Response listChanges(String projectId, String zoneName, String query) { keys = from != null ? keys.tailSet(from, false) : keys; NavigableMap fragment = from != null && changes.containsKey(from) ? changes.tailMap(from, false) : changes; - boolean sizeReached = false; // maximum result size was reached, we should not return more - boolean hasMorePages = false; // should next page token be included in the response? + boolean sizeReached = false; + boolean hasMorePages = false; LinkedList serializedResults = new LinkedList<>(); String lastChangeId = null; for (Integer key : keys) { @@ -986,13 +978,13 @@ Response listChanges(String projectId, String zoneName, String query) { } catch (IOException e) { return Error.INTERNAL_ERROR.response(String.format( "Error when serializing change %s in managed zone %s in project %s", - change.getId(), zoneName, projectId)); + lastChangeId, zoneName, projectId)); } } sizeReached = maxResults != null && maxResults.equals(serializedResults.size()); } boolean includePageToken = - hasMorePages && (fields == null || ImmutableList.copyOf(fields).contains("nextPageToken")); + hasMorePages && (fields == null || Arrays.asList(fields).contains("nextPageToken")); return toListResponse(serializedResults, "changes", lastChangeId, includePageToken); } @@ -1019,7 +1011,8 @@ private static Response checkZone(ManagedZone zone) { } catch (NumberFormatException ex) { // expected } - if (zone.getName().isEmpty()) { + if (zone.getName().isEmpty() || zone.getName().length() > 32 + || !ZONE_NAME_RE.matcher(zone.getName()).matches()) { return Error.INVALID.response( String.format("Invalid value for 'entity.managedZone.name': '%s'", zone.getName())); } diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/OptionParsers.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/OptionParsers.java index dcebf81350ad..ecd7e8179efe 100644 --- a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/OptionParsers.java +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/OptionParsers.java @@ -163,7 +163,7 @@ static Project extractFields(Project fullProject, String... fields) { } static ResourceRecordSet extractFields(ResourceRecordSet fullRecord, String... fields) { - if (fields == null) { + if (fields == null || fields.length == 0) { return fullRecord; } ResourceRecordSet record = new ResourceRecordSet(); @@ -196,7 +196,6 @@ static Map parseListChangesOptions(String query) { String[] argEntry = arg.split("="); switch (argEntry[0]) { case "fields": - // todo we do not support fragmentation in deletions and additions in the library String replaced = argEntry[1].replace("changes(", ",").replace(")", ","); options.put("fields", replaced.split(",")); // empty strings will be ignored break; diff --git a/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java index 2dfb6c05c27e..489b935d7e50 100644 --- a/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java +++ b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java @@ -30,7 +30,6 @@ import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Lists; import com.google.gcloud.dns.DnsException; import com.google.gcloud.spi.DefaultDnsRpc; @@ -43,6 +42,7 @@ import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -55,8 +55,8 @@ public class LocalDnsHelperTest { private static final ResourceRecordSet RRSET_KEEP = new ResourceRecordSet(); private static final String PROJECT_ID1 = "2135436541254"; private static final String PROJECT_ID2 = "882248761325"; - private static final String ZONE_NAME1 = "my little zone"; - private static final String ZONE_NAME2 = "another zone name"; + private static final String ZONE_NAME1 = "my-little-zone"; + private static final String ZONE_NAME2 = "another-zone-name"; private static final ManagedZone ZONE1 = new ManagedZone(); private static final ManagedZone ZONE2 = new ManagedZone(); private static final String DNS_NAME = "www.example.com."; @@ -66,8 +66,7 @@ public class LocalDnsHelperTest { private static final Change CHANGE_COMPLEX = new Change(); private static final LocalDnsHelper LOCAL_DNS_HELPER = LocalDnsHelper.create(0L); // synchronous private static final Map EMPTY_RPC_OPTIONS = ImmutableMap.of(); - private static final DnsRpc RPC = - new DefaultDnsRpc(LOCAL_DNS_HELPER.options()); + private static final DnsRpc RPC = new DefaultDnsRpc(LOCAL_DNS_HELPER.options()); private static final String REAL_PROJECT_ID = LOCAL_DNS_HELPER.options().projectId(); private Map optionsMap; @@ -104,15 +103,76 @@ public void setUp() { optionsMap = new HashMap<>(); } + @AfterClass + public static void after() { + LOCAL_DNS_HELPER.stop(); + } + private static void resetProjects() { for (String project : LOCAL_DNS_HELPER.projects().keySet()) { LOCAL_DNS_HELPER.projects().remove(project); } } - @AfterClass - public static void after() { - LOCAL_DNS_HELPER.stop(); + private static void assertEqChangesIgnoreStatus(Change expected, Change actual) { + assertEquals(expected.getAdditions(), actual.getAdditions()); + assertEquals(expected.getDeletions(), actual.getDeletions()); + assertEquals(expected.getId(), actual.getId()); + assertEquals(expected.getStartTime(), actual.getStartTime()); + } + + private static void waitUntilComplete(DnsRpc rpc, String zoneName, String changeId) { + while (true) { + Change change = rpc.getChangeRequest(zoneName, changeId, EMPTY_RPC_OPTIONS); + if ("done".equals(change.getStatus())) { + return; + } + try { + Thread.sleep(50L); + } catch (InterruptedException e) { + fail("Thread was interrupted while waiting for change processing."); + } + } + } + + private static void executeCreateAndApplyChangeTest(DnsRpc rpc) { + rpc.create(ZONE1, EMPTY_RPC_OPTIONS); + assertNull(rpc.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS)); + // add + Change createdChange = rpc.applyChangeRequest(ZONE1.getName(), CHANGE1, EMPTY_RPC_OPTIONS); + assertEquals(createdChange.getAdditions(), CHANGE1.getAdditions()); + assertEquals(createdChange.getDeletions(), CHANGE1.getDeletions()); + assertNotNull(createdChange.getStartTime()); + assertEquals("1", createdChange.getId()); + Change retrievedChange = rpc.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS); + assertEqChangesIgnoreStatus(createdChange, retrievedChange); + assertNull(rpc.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); + waitUntilComplete(rpc, ZONE1.getName(), "1"); // necessary for the following to return 409 + try { + rpc.applyChangeRequest(ZONE1.getName(), CHANGE1, EMPTY_RPC_OPTIONS); + fail(); + } catch (DnsException ex) { + assertEquals(409, ex.code()); + } + assertNotNull(rpc.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS)); + assertNull(rpc.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); + // delete + rpc.applyChangeRequest(ZONE1.getName(), CHANGE2, EMPTY_RPC_OPTIONS); + assertNotNull(rpc.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS)); + assertNotNull(rpc.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); + waitUntilComplete(rpc, ZONE1.getName(), "2"); + rpc.applyChangeRequest(ZONE1.getName(), CHANGE_KEEP, EMPTY_RPC_OPTIONS); + waitUntilComplete(rpc, ZONE1.getName(), "3"); + Iterable results = + rpc.listDnsRecords(ZONE1.getName(), EMPTY_RPC_OPTIONS).results(); + boolean ok = false; + for (ResourceRecordSet dnsRecord : results) { + if (dnsRecord.getName().equals(RRSET_KEEP.getName()) + && dnsRecord.getType().equals(RRSET_KEEP.getType())) { + ok = true; + } + } + assertTrue(ok); } @Test @@ -121,7 +181,11 @@ public void testCreateZone() { // check that default records were created DnsRpc.ListResult listResult = RPC.listDnsRecords(ZONE1.getName(), EMPTY_RPC_OPTIONS); - assertEquals(2, Lists.newLinkedList(listResult.results()).size()); + ImmutableList defaultTypes = ImmutableList.of("SOA", "NS"); + Iterator iterator = listResult.results().iterator(); + assertTrue(defaultTypes.contains(iterator.next().getType())); + assertTrue(defaultTypes.contains(iterator.next().getType())); + assertFalse(iterator.hasNext()); assertEquals(created, LOCAL_DNS_HELPER.findZone(REAL_PROJECT_ID, ZONE1.getName()).zone()); ManagedZone zone = RPC.getZone(ZONE_NAME1, EMPTY_RPC_OPTIONS); assertEquals(created, zone); @@ -136,6 +200,7 @@ public void testCreateZone() { // create zone twice try { RPC.create(ZONE1, EMPTY_RPC_OPTIONS); + fail("Zone already exists."); } catch (DnsException ex) { // expected assertEquals(409, ex.code()); @@ -325,81 +390,17 @@ public void testDeleteZone() { } @Test - public void testCreateAndApplyChangeWithThreads() { - LocalDnsHelper localDnsThreaded = LocalDnsHelper.create(50L); // using threads here - localDnsThreaded.createZone(PROJECT_ID1, ZONE1); - assertNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); - LocalDnsHelper.Response response = - localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1); // add - assertEquals(200, response.code()); - assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); - assertNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); - localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1); // add - response = localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE1); // add - assertEquals(200, response.code()); - assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); - assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); - localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE2); // delete - assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("1")); - assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("2")); - assertNotNull(localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).findChange("3")); - localDnsThreaded.createChange(PROJECT_ID1, ZONE_NAME1, CHANGE_KEEP); - // check execution - Change change = localDnsThreaded.findChange(PROJECT_ID1, ZONE_NAME1, "4"); - for (int i = 0; i < 10 && !change.getStatus().equals("done"); i++) { - // change has not been finished yet; wait at most 5 seconds - // it takes 5O ms for the thread to kick in in the first place - try { - Thread.sleep(500); - } catch (InterruptedException e) { - fail("Test was interrupted"); - } - } - assertEquals("done", change.getStatus()); - ImmutableSortedMap list = - localDnsThreaded.findZone(PROJECT_ID1, ZONE_NAME1).dnsRecords().get(); - assertTrue(list.containsValue(RRSET_KEEP)); - localDnsThreaded.stop(); + public void testCreateAndApplyChange() { + executeCreateAndApplyChangeTest(RPC); } @Test - public void testCreateAndApplyChange() { - // not using threads - RPC.create(ZONE1, EMPTY_RPC_OPTIONS); - assertNull(RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS)); - // add - Change createdChange = RPC.applyChangeRequest(ZONE1.getName(), CHANGE1, EMPTY_RPC_OPTIONS); - assertEquals(createdChange.getAdditions(), CHANGE1.getAdditions()); - assertEquals(createdChange.getDeletions(), CHANGE1.getDeletions()); - assertNotNull(createdChange.getStartTime()); - assertEquals("1", createdChange.getId()); - Change retrievedChange = RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS); - assertEquals(createdChange, retrievedChange); - assertNull(RPC.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); - try { - RPC.applyChangeRequest(ZONE1.getName(), CHANGE1, EMPTY_RPC_OPTIONS); - fail(); - } catch (DnsException ex) { - assertEquals(409, ex.code()); - } - assertNotNull(RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS)); - assertNull(RPC.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); - // delete - RPC.applyChangeRequest(ZONE1.getName(), CHANGE2, EMPTY_RPC_OPTIONS); - assertNotNull(RPC.getChangeRequest(ZONE1.getName(), "1", EMPTY_RPC_OPTIONS)); - assertNotNull(RPC.getChangeRequest(ZONE1.getName(), "2", EMPTY_RPC_OPTIONS)); - Change last = RPC.applyChangeRequest(ZONE1.getName(), CHANGE_KEEP, EMPTY_RPC_OPTIONS); - assertEquals("done", last.getStatus()); - Iterable results = - RPC.listDnsRecords(ZONE1.getName(), EMPTY_RPC_OPTIONS).results(); - boolean ok = false; - for (ResourceRecordSet dnsRecord : results) { - if (dnsRecord.getName().equals(RRSET_KEEP.getName()) - && dnsRecord.getType().equals(RRSET_KEEP.getType())) { - ok = true; - } - } - assertTrue(ok); + public void testCreateAndApplyChangeWithThreads() { + LocalDnsHelper localDnsThreaded = LocalDnsHelper.create(50L); + localDnsThreaded.start(); + DnsRpc rpc = new DefaultDnsRpc(localDnsThreaded.options()); + executeCreateAndApplyChangeTest(rpc); + localDnsThreaded.stop(); } @Test @@ -434,6 +435,7 @@ public void testCreateChange() { // non-existent zone try { RPC.applyChangeRequest(ZONE_NAME1, CHANGE1, EMPTY_RPC_OPTIONS); + fail("Zone was not created yet."); } catch (DnsException ex) { assertEquals(404, ex.code()); } @@ -580,10 +582,10 @@ public void testListZones() { } // ok size options = new HashMap<>(); - options.put(DnsRpc.Option.PAGE_SIZE, 1); + options.put(DnsRpc.Option.PAGE_SIZE, 335); results = RPC.listZones(options).results(); zones = ImmutableList.copyOf(results); - assertEquals(1, zones.size()); + assertEquals(2, zones.size()); // dns name problems options = new HashMap<>(); options.put(DnsRpc.Option.DNS_NAME, "aaa"); @@ -717,10 +719,6 @@ public void testListDnsRecords() { assertEquals(400, ex.code()); assertTrue(ex.getMessage().contains("parameters.maxResults")); } - options.put(DnsRpc.Option.PAGE_SIZE, 1); - results = RPC.listDnsRecords(ZONE1.getName(), options).results(); - records = ImmutableList.copyOf(results); - assertEquals(1, records.size()); options.put(DnsRpc.Option.PAGE_SIZE, 15); results = RPC.listDnsRecords(ZONE1.getName(), options).results(); records = ImmutableList.copyOf(results); @@ -768,62 +766,62 @@ public void testListDnsRecords() { // field options options = new HashMap<>(); options.put(DnsRpc.Option.FIELDS, "rrsets(name)"); - DnsRpc.ListResult resourceRecordSetListResult = + DnsRpc.ListResult listResult = RPC.listDnsRecords(ZONE1.getName(), options); - records = ImmutableList.copyOf(resourceRecordSetListResult.results()); + records = ImmutableList.copyOf(listResult.results()); ResourceRecordSet record = records.get(0); assertNotNull(record.getName()); assertNull(record.getRrdatas()); assertNull(record.getType()); assertNull(record.getTtl()); - assertNull(resourceRecordSetListResult.pageToken()); + assertNull(listResult.pageToken()); options.put(DnsRpc.Option.FIELDS, "rrsets(rrdatas)"); - resourceRecordSetListResult = RPC.listDnsRecords(ZONE1.getName(), options); - records = ImmutableList.copyOf(resourceRecordSetListResult.results()); + listResult = RPC.listDnsRecords(ZONE1.getName(), options); + records = ImmutableList.copyOf(listResult.results()); record = records.get(0); assertNull(record.getName()); assertNotNull(record.getRrdatas()); assertNull(record.getType()); assertNull(record.getTtl()); - assertNull(resourceRecordSetListResult.pageToken()); + assertNull(listResult.pageToken()); options.put(DnsRpc.Option.FIELDS, "rrsets(ttl)"); - resourceRecordSetListResult = RPC.listDnsRecords(ZONE1.getName(), options); - records = ImmutableList.copyOf(resourceRecordSetListResult.results()); + listResult = RPC.listDnsRecords(ZONE1.getName(), options); + records = ImmutableList.copyOf(listResult.results()); record = records.get(0); assertNull(record.getName()); assertNull(record.getRrdatas()); assertNull(record.getType()); assertNotNull(record.getTtl()); - assertNull(resourceRecordSetListResult.pageToken()); + assertNull(listResult.pageToken()); options.put(DnsRpc.Option.FIELDS, "rrsets(type)"); - resourceRecordSetListResult = RPC.listDnsRecords(ZONE1.getName(), options); - records = ImmutableList.copyOf(resourceRecordSetListResult.results()); + listResult = RPC.listDnsRecords(ZONE1.getName(), options); + records = ImmutableList.copyOf(listResult.results()); record = records.get(0); assertNull(record.getName()); assertNull(record.getRrdatas()); assertNotNull(record.getType()); assertNull(record.getTtl()); - assertNull(resourceRecordSetListResult.pageToken()); + assertNull(listResult.pageToken()); options.put(DnsRpc.Option.FIELDS, "nextPageToken"); - resourceRecordSetListResult = RPC.listDnsRecords(ZONE1.getName(), options); - records = ImmutableList.copyOf(resourceRecordSetListResult.results()); + listResult = RPC.listDnsRecords(ZONE1.getName(), options); + records = ImmutableList.copyOf(listResult.results()); record = records.get(0); assertNull(record.getName()); assertNull(record.getRrdatas()); assertNull(record.getType()); assertNull(record.getTtl()); - assertNull(resourceRecordSetListResult.pageToken()); + assertNull(listResult.pageToken()); options.put(DnsRpc.Option.FIELDS, "nextPageToken,rrsets(name,rrdatas)"); options.put(DnsRpc.Option.PAGE_SIZE, 1); - resourceRecordSetListResult = RPC.listDnsRecords(ZONE1.getName(), options); - records = ImmutableList.copyOf(resourceRecordSetListResult.results()); + listResult = RPC.listDnsRecords(ZONE1.getName(), options); + records = ImmutableList.copyOf(listResult.results()); assertEquals(1, records.size()); record = records.get(0); assertNotNull(record.getName()); assertNotNull(record.getRrdatas()); assertNull(record.getType()); assertNull(record.getTtl()); - assertNotNull(resourceRecordSetListResult.pageToken()); + assertNotNull(listResult.pageToken()); } @Test @@ -898,8 +896,7 @@ public void testListChanges() { options = new HashMap<>(); options.put(DnsRpc.Option.SORTING_ORDER, "descending"); options.put(DnsRpc.Option.FIELDS, "changes(additions)"); - DnsRpc.ListResult changeListResult = - RPC.listChangeRequests(ZONE1.getName(), options); + DnsRpc.ListResult changeListResult = RPC.listChangeRequests(ZONE1.getName(), options); changes = ImmutableList.copyOf(changeListResult.results()); Change complex = changes.get(0); assertNotNull(complex.getAdditions()); @@ -968,10 +965,8 @@ public void testDnsRecordPaging() { RPC.listDnsRecords(ZONE1.getName(), EMPTY_RPC_OPTIONS).results()); Map options = new HashMap<>(); options.put(DnsRpc.Option.PAGE_SIZE, 1); - DnsRpc.ListResult listResult = - RPC.listDnsRecords(ZONE1.getName(), options); - ImmutableList records = - ImmutableList.copyOf(listResult.results()); + DnsRpc.ListResult listResult = RPC.listDnsRecords(ZONE1.getName(), options); + ImmutableList records = ImmutableList.copyOf(listResult.results()); assertEquals(1, records.size()); assertEquals(complete.get(0), records.get(0)); options.put(DnsRpc.Option.PAGE_TOKEN, listResult.pageToken()); @@ -989,17 +984,17 @@ public void testZonePaging() { RPC.listZones(EMPTY_RPC_OPTIONS).results()); Map options = new HashMap<>(); options.put(DnsRpc.Option.PAGE_SIZE, 1); - DnsRpc.ListResult managedZoneListResult = RPC.listZones(options); - ImmutableList page1 = ImmutableList.copyOf(managedZoneListResult.results()); + DnsRpc.ListResult listResult = RPC.listZones(options); + ImmutableList page1 = ImmutableList.copyOf(listResult.results()); assertEquals(1, page1.size()); assertEquals(complete.get(0), page1.get(0)); - assertEquals(page1.get(0).getName(), managedZoneListResult.pageToken()); - options.put(DnsRpc.Option.PAGE_TOKEN, managedZoneListResult.pageToken()); - managedZoneListResult = RPC.listZones(options); - ImmutableList page2 = ImmutableList.copyOf(managedZoneListResult.results()); + assertEquals(page1.get(0).getName(), listResult.pageToken()); + options.put(DnsRpc.Option.PAGE_TOKEN, listResult.pageToken()); + listResult = RPC.listZones(options); + ImmutableList page2 = ImmutableList.copyOf(listResult.results()); assertEquals(1, page2.size()); assertEquals(complete.get(1), page2.get(0)); - assertNull(managedZoneListResult.pageToken()); + assertNull(listResult.pageToken()); } @Test @@ -1062,9 +1057,30 @@ public void testCreateZoneValidation() { response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy); assertEquals(400, response.code()); assertTrue(response.body().contains("entity.managedZone.dnsName")); - // zone name is a number + // zone name does not start with a letter + copy = copyZone(minimalZone); + copy.setName("1aaaaaa"); + response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy); + assertEquals(400, response.code()); + assertTrue(response.body().contains("entity.managedZone.name")); + assertTrue(response.body().contains("Invalid")); + // zone name is too long + copy = copyZone(minimalZone); + copy.setName("123456aaaa123456aaaa123456aaaa123456aaaa123456aaaa123456aaaa123456aaaa123456aa"); + response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy); + assertEquals(400, response.code()); + assertTrue(response.body().contains("entity.managedZone.name")); + assertTrue(response.body().contains("Invalid")); + // zone name contains invalid characters + copy = copyZone(minimalZone); + copy.setName("x1234AA6aa"); + response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy); + assertEquals(400, response.code()); + assertTrue(response.body().contains("entity.managedZone.name")); + assertTrue(response.body().contains("Invalid")); + // zone name contains invalid characters copy = copyZone(minimalZone); - copy.setName("123456"); + copy.setName("x a"); response = LOCAL_DNS_HELPER.createZone(PROJECT_ID1, copy); assertEquals(400, response.code()); assertTrue(response.body().contains("entity.managedZone.name")); @@ -1289,7 +1305,7 @@ public void testCheckChange() { } @Test - public void testAdditionsMeetDeletions() { + public void testCheckAdditionsDeletions() { ResourceRecordSet validA = new ResourceRecordSet(); validA.setName(ZONE1.getDnsName()); validA.setType("A"); From 47d950031d4768ce1b1a084720605585684d47df Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Thu, 3 Mar 2016 17:15:23 -0800 Subject: [PATCH 8/9] print stack traces --- .../gcloud/dns/testing/LocalDnsHelper.java | 7 +++++- .../com/google/gcloud/spi/DefaultDnsRpc.java | 23 +++++++++++-------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java index 4eca683f1a08..46ba41a5decd 100644 --- a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java @@ -391,7 +391,12 @@ public static LocalDnsHelper create(Long delay) { * Returns a {@link DnsOptions} instance that sets the host to use the mock server. */ public DnsOptions options() { - return DnsOptions.builder().host("http://localhost:" + port).build(); + try { + return DnsOptions.builder().host("http://localhost:" + port).build(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } } /** diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/spi/DefaultDnsRpc.java b/gcloud-java-dns/src/main/java/com/google/gcloud/spi/DefaultDnsRpc.java index 1df0a8a2f831..6c0c65a585cd 100644 --- a/gcloud-java-dns/src/main/java/com/google/gcloud/spi/DefaultDnsRpc.java +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/spi/DefaultDnsRpc.java @@ -33,8 +33,8 @@ public class DefaultDnsRpc implements DnsRpc { private static final String SORT_BY = "changeSequence"; - private final Dns dns; - private final DnsOptions options; + private Dns dns; + private DnsOptions options; private static DnsException translate(IOException exception) { return new DnsException(exception); @@ -44,13 +44,18 @@ private static DnsException translate(IOException exception) { * Constructs an instance of this rpc client with provided {@link DnsOptions}. */ public DefaultDnsRpc(DnsOptions options) { - HttpTransport transport = options.httpTransportFactory().create(); - HttpRequestInitializer initializer = options.httpRequestInitializer(); - this.dns = new Dns.Builder(transport, new JacksonFactory(), initializer) - .setRootUrl(options.host()) - .setApplicationName(options.applicationName()) - .build(); - this.options = options; + try { + HttpTransport transport = options.httpTransportFactory().create(); + HttpRequestInitializer initializer = options.httpRequestInitializer(); + this.dns = + new Dns.Builder(transport, new JacksonFactory(), initializer) + .setRootUrl(options.host()) + .setApplicationName(options.applicationName()) + .build(); + this.options = options; + } catch (Exception e) { + e.printStackTrace(); + } } @Override From d9235688b5382c09d12da71a8cbf9e7ed91fff12 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Thu, 3 Mar 2016 18:06:04 -0800 Subject: [PATCH 9/9] add project ID --- .../com/google/gcloud/dns/testing/LocalDnsHelper.java | 11 +++++++---- .../google/gcloud/dns/testing/LocalDnsHelperTest.java | 5 +++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java index 46ba41a5decd..7079d0e71269 100644 --- a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/testing/LocalDnsHelper.java @@ -90,6 +90,7 @@ * validation of the DNS data for records of type A and AAAA. It does not validate any other record * types. */ +@SuppressWarnings("restriction") public class LocalDnsHelper { private final ConcurrentSkipListMap projects @@ -106,6 +107,7 @@ public class LocalDnsHelper { private static final TreeSet FORBIDDEN = Sets.newTreeSet( ImmutableList.of("google.com.", "com.", "example.com.", "net.", "org.")); private static final Pattern ZONE_NAME_RE = Pattern.compile("[a-z][a-z0-9-]*"); + private final String projectId; static { try { @@ -357,9 +359,10 @@ private Response handleZoneCreate(HttpExchange exchange, String projectId, Strin } } - private LocalDnsHelper(long delay) { + private LocalDnsHelper(String projectId, long delay) { this.delayChange = delay; try { + this.projectId = projectId; server = HttpServer.create(new InetSocketAddress(0), 0); port = server.getAddress().getPort(); server.createContext(CONTEXT, new RequestHandler()); @@ -383,8 +386,8 @@ ConcurrentSkipListMap projects() { * * @param delay delay for processing changes in ms or 0 for synchronous processing */ - public static LocalDnsHelper create(Long delay) { - return new LocalDnsHelper(delay); + public static LocalDnsHelper create(String projectId, Long delay) { + return new LocalDnsHelper(projectId, delay); } /** @@ -392,7 +395,7 @@ public static LocalDnsHelper create(Long delay) { */ public DnsOptions options() { try { - return DnsOptions.builder().host("http://localhost:" + port).build(); + return DnsOptions.builder().projectId(projectId).host("http://localhost:" + port).build(); } catch (Exception e) { e.printStackTrace(); return null; diff --git a/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java index 489b935d7e50..52671c2c6ab6 100644 --- a/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java +++ b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/testing/LocalDnsHelperTest.java @@ -64,7 +64,8 @@ public class LocalDnsHelperTest { private static final Change CHANGE2 = new Change(); private static final Change CHANGE_KEEP = new Change(); private static final Change CHANGE_COMPLEX = new Change(); - private static final LocalDnsHelper LOCAL_DNS_HELPER = LocalDnsHelper.create(0L); // synchronous + private static final LocalDnsHelper LOCAL_DNS_HELPER = + LocalDnsHelper.create("someprojectid", 0L); // synchronous private static final Map EMPTY_RPC_OPTIONS = ImmutableMap.of(); private static final DnsRpc RPC = new DefaultDnsRpc(LOCAL_DNS_HELPER.options()); private static final String REAL_PROJECT_ID = LOCAL_DNS_HELPER.options().projectId(); @@ -396,7 +397,7 @@ public void testCreateAndApplyChange() { @Test public void testCreateAndApplyChangeWithThreads() { - LocalDnsHelper localDnsThreaded = LocalDnsHelper.create(50L); + LocalDnsHelper localDnsThreaded = LocalDnsHelper.create("someprojectid", 50L); localDnsThreaded.start(); DnsRpc rpc = new DefaultDnsRpc(localDnsThreaded.options()); executeCreateAndApplyChangeTest(rpc);