/** * Copyright 2016 LinkedIn Corp. 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. */ package com.github.ambry.router; import com.github.ambry.clustermap.DataNodeId; import com.github.ambry.clustermap.PartitionId; import com.github.ambry.clustermap.ReplicaId; import com.github.ambry.commons.ServerErrorCode; import com.github.ambry.messageformat.BlobProperties; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; /** * Class with helper methods for testing the router. */ class RouterTestHelpers { /** * Test whether two {@link BlobProperties} have the same fields * @return true if the fields are equivalent in the two {@link BlobProperties} */ static boolean haveEquivalentFields(BlobProperties a, BlobProperties b) { return a.getServiceId().equals(b.getServiceId()) && a.getOwnerId().equals(b.getOwnerId()) && a.getContentType() .equals(b.getContentType()) && a.isPrivate() == b.isPrivate() && a.getTimeToLiveInSeconds() == b.getTimeToLiveInSeconds() && a.getCreationTimeInMs() == b.getCreationTimeInMs(); } /** * Test that an operation returns a certain result when servers return error codes corresponding * to {@code serverErrorCodeCounts} * @param serverErrorCodeCounts A map from {@link ServerErrorCode}s to a count of how many servers should be set to * that error code. * @param serverLayout A {@link MockServerLayout} that is used to find the {@link MockServer}s to set error codes on. * @param expectedError The {@link RouterErrorCode} expected, or null if no error is expected. * @param errorCodeChecker Performs the checks that ensure that the expected {@link RouterErrorCode} is returned . * @throws Exception */ static void testWithErrorCodes(Map<ServerErrorCode, Integer> serverErrorCodeCounts, MockServerLayout serverLayout, RouterErrorCode expectedError, ErrorCodeChecker errorCodeChecker) throws Exception { List<ServerErrorCode> serverErrorCodes = new ArrayList<>(); for (Map.Entry<ServerErrorCode, Integer> entry : serverErrorCodeCounts.entrySet()) { for (int j = 0; j < entry.getValue(); j++) { serverErrorCodes.add(entry.getKey()); } } Collections.shuffle(serverErrorCodes); testWithErrorCodes(serverErrorCodes.toArray(new ServerErrorCode[0]), serverLayout, expectedError, errorCodeChecker); } /** * Test that an operation returns a certain result when each server in the layout returns a certain error code. * @param serverErrorCodesInOrder The error codes to set in the order of the servers in {@code serverLayout}. If * there are more values in this array than there are servers, an exception is thrown. * If there are fewer, the rest of the servers are set to * {@link ServerErrorCode#No_Error} * @param serverLayout A {@link MockServerLayout} that is used to find the {@link MockServer}s to set error codes on. * @param expectedError The {@link RouterErrorCode} expected, or null if no error is expected. * @param errorCodeChecker Performs the checks that ensure that the expected {@link RouterErrorCode} is returned . * @throws Exception */ static void testWithErrorCodes(ServerErrorCode[] serverErrorCodesInOrder, MockServerLayout serverLayout, RouterErrorCode expectedError, ErrorCodeChecker errorCodeChecker) throws Exception { setServerErrorCodes(serverErrorCodesInOrder, serverLayout); errorCodeChecker.testAndAssert(expectedError); resetServerErrorCodes(serverLayout); } /** * Test that an operation returns a certain result when each server in a partition returns a certain error code. * @param serverErrorCodesInOrder The error codes to set in the order of the servers returned by * {@link PartitionId#getReplicaIds()}. If there are more values in this array than * there are servers containing the partition, an exception is thrown. If there are * fewer, the rest of the servers are set to {@link ServerErrorCode#No_Error} * @param partition The partition contained by the servers to set error codes on. * @param serverLayout A {@link MockServerLayout} that is used to find the {@link MockServer}s to set error codes on. * @param expectedError The {@link RouterErrorCode} expected, or null if no error is expected. * @param errorCodeChecker Performs the checks that ensure that the expected {@link RouterErrorCode} is returned . * @throws Exception */ static void testWithErrorCodes(ServerErrorCode[] serverErrorCodesInOrder, PartitionId partition, MockServerLayout serverLayout, RouterErrorCode expectedError, ErrorCodeChecker errorCodeChecker) throws Exception { setServerErrorCodes(serverErrorCodesInOrder, partition, serverLayout); errorCodeChecker.testAndAssert(expectedError); resetServerErrorCodes(serverLayout); } /** * Set the servers in the specified layout to respond with the designated error codes. * @param serverErrorCodesInOrder The error codes to set in the order of the servers in {@code serverLayout}. * If there are fewer error codes in this array than there are servers, * the rest of the servers are set to {@link ServerErrorCode#No_Error} * @param serverLayout A {@link MockServerLayout} that is used to find the {@link MockServer}s to set error codes on. * @throws IllegalArgumentException If there are more error codes in the input array then there are servers. */ static void setServerErrorCodes(ServerErrorCode[] serverErrorCodesInOrder, MockServerLayout serverLayout) { Collection<MockServer> servers = serverLayout.getMockServers(); if (serverErrorCodesInOrder.length > servers.size()) { throw new IllegalArgumentException("More server error codes provided than servers in cluster"); } int i = 0; for (MockServer server : servers) { if (i < serverErrorCodesInOrder.length) { server.setServerErrorForAllRequests(serverErrorCodesInOrder[i++]); } else { server.setServerErrorForAllRequests(ServerErrorCode.No_Error); } } } /** * Set the servers containing the specified partition to respond with the designated error codes. * @param serverErrorCodesInOrder The error codes to set in the order of the servers returned by * {@link PartitionId#getReplicaIds()}. If there are fewer error codes in this array * than there are servers containing the partition, the rest of the servers are set to * {@link ServerErrorCode#No_Error} * @param partition The partition contained by the servers to set error codes on. * @param serverLayout A {@link MockServerLayout} that is used to find the {@link MockServer}s to set error codes on. * @throws IllegalArgumentException If there are more error codes in the input array then there are servers. */ static void setServerErrorCodes(ServerErrorCode[] serverErrorCodesInOrder, PartitionId partition, MockServerLayout serverLayout) { List<? extends ReplicaId> replicas = partition.getReplicaIds(); if (serverErrorCodesInOrder.length > replicas.size()) { throw new IllegalArgumentException("More server error codes provided than replicas in partition"); } int i = 0; for (ReplicaId replica : partition.getReplicaIds()) { DataNodeId node = replica.getDataNodeId(); MockServer mockServer = serverLayout.getMockServer(node.getHostname(), node.getPort()); mockServer.setServerErrorForAllRequests(serverErrorCodesInOrder[i++]); } } /** * Reset all preset error codes on the servers in the specified layout. * @param serverLayout A {@link MockServerLayout} that is used to find the {@link MockServer}s to reset error codes * on. */ static void resetServerErrorCodes(MockServerLayout serverLayout) { for (MockServer server : serverLayout.getMockServers()) { server.resetServerErrors(); } } /** * Set the blob format version that the server should respond to get requests with. * @param blobFormatVersion The blob format version to use. * @param serverLayout A {@link MockServerLayout} containing the {@link MockServer}s to change settings on. */ static void setBlobFormatVersionForAllServers(short blobFormatVersion, MockServerLayout serverLayout) { ArrayList<MockServer> mockServers = new ArrayList<>(serverLayout.getMockServers()); for (MockServer mockServer : mockServers) { mockServer.setBlobFormatVersion(blobFormatVersion); } } /** * Set whether all servers should send the preset error code on data blob get requests * only * @param getErrorOnDataBlobOnly {@code true} if the preset error code should only be used for data blobs requests * @param serverLayout A {@link MockServerLayout} containing the {@link MockServer}s to change settings on. */ static void setGetErrorOnDataBlobOnlyForAllServers(boolean getErrorOnDataBlobOnly, MockServerLayout serverLayout) { ArrayList<MockServer> mockServers = new ArrayList<>(serverLayout.getMockServers()); for (MockServer mockServer : mockServers) { mockServer.setGetErrorOnDataBlobOnly(getErrorOnDataBlobOnly); } } /** * Implement this interface to provide {@link #testWithErrorCodes} with custom verification logic. */ interface ErrorCodeChecker { /** * Test and make assertions related to the expected error code. * @param expectedError The {@link RouterErrorCode} that an operation is expected to report, or {@code null} if no * error is expected. * @throws Exception */ void testAndAssert(RouterErrorCode expectedError) throws Exception; } }