/* * Copyright 2017 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.server; import com.codahale.metrics.MetricRegistry; import com.github.ambry.clustermap.MockClusterMap; import com.github.ambry.clustermap.PartitionId; import com.github.ambry.commons.ServerErrorCode; import com.github.ambry.config.StoreConfig; import com.github.ambry.config.VerifiableProperties; import com.github.ambry.network.Request; import com.github.ambry.network.Send; import com.github.ambry.network.ServerNetworkResponseMetrics; import com.github.ambry.network.SocketRequestResponseChannel; import com.github.ambry.protocol.AdminRequest; import com.github.ambry.protocol.AdminRequestOrResponseType; import com.github.ambry.protocol.AdminResponse; import com.github.ambry.store.FindInfo; import com.github.ambry.store.FindToken; import com.github.ambry.store.MessageWriteSet; import com.github.ambry.store.StorageManager; import com.github.ambry.store.Store; import com.github.ambry.store.StoreException; import com.github.ambry.store.StoreGetOptions; import com.github.ambry.store.StoreInfo; import com.github.ambry.store.StoreKey; import com.github.ambry.store.StoreStats; import com.github.ambry.utils.ByteBufferChannel; import com.github.ambry.utils.ByteBufferInputStream; import com.github.ambry.utils.MockTime; import com.github.ambry.utils.TestUtils; import com.github.ambry.utils.Utils; import com.github.ambry.utils.UtilsTest; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Properties; import java.util.Set; import org.junit.Test; import static org.junit.Assert.*; /** * Tests for {@link AmbryRequests}. */ public class AmbryRequestsTest { private final MockClusterMap clusterMap; private final MockStorageManager storageManager; private final MockRequestResponseChannel requestResponseChannel = new MockRequestResponseChannel(); private final AmbryRequests ambryRequests; public AmbryRequestsTest() throws IOException, StoreException { clusterMap = new MockClusterMap(); storageManager = new MockStorageManager(); ambryRequests = new AmbryRequests(storageManager, requestResponseChannel, clusterMap, clusterMap.getDataNodeIds().get(0), clusterMap.getMetricRegistry(), null, null, null, null); } /** * Tests that compactions are scheduled correctly. * @throws InterruptedException * @throws IOException */ @Test public void scheduleCompactionSuccessTest() throws InterruptedException, IOException { List<? extends PartitionId> partitionIds = clusterMap.getWritablePartitionIds(); for (PartitionId id : partitionIds) { doScheduleCompactionTest(id, ServerErrorCode.No_Error); assertEquals("Partition scheduled for compaction not as expected", id, storageManager.compactionScheduledPartitionId); } } /** * Tests failure scenarios for compaction - disk down, store not scheduled for compaction, exception while scheduling. * @throws InterruptedException * @throws IOException */ @Test public void scheduleCompactionFailureTest() throws InterruptedException, IOException { PartitionId id = clusterMap.getWritablePartitionIds().get(0); // store is not started - Disk_Unavailable storageManager.returnNullStore = true; doScheduleCompactionTest(id, ServerErrorCode.Disk_Unavailable); storageManager.returnNullStore = false; // PartitionUnknown is hard to simulate without betraying knowledge of the internals of MockClusterMap. // store cannot be scheduled for compaction - Unknown_Error storageManager.returnValueOfSchedulingCompaction = false; doScheduleCompactionTest(id, ServerErrorCode.Unknown_Error); storageManager.returnValueOfSchedulingCompaction = true; // exception while attempting to schedule - InternalServerError storageManager.exceptionToThrowOnSchedulingCompaction = new IllegalStateException(); doScheduleCompactionTest(id, ServerErrorCode.Unknown_Error); storageManager.exceptionToThrowOnSchedulingCompaction = null; } // helpers // scheduleCompactionSuccessTest() and scheduleCompactionFailuresTest() helpers /** * Schedules a compaction for {@code id} and checks that the {@link ServerErrorCode} returned matches * {@code expectedServerErrorCode}. * @param id the {@link PartitionId} to schedule compaction for. * @param expectedServerErrorCode the {@link ServerErrorCode} expected when the request is processed. * @throws InterruptedException * @throws IOException */ private void doScheduleCompactionTest(PartitionId id, ServerErrorCode expectedServerErrorCode) throws InterruptedException, IOException { int correlationId = TestUtils.RANDOM.nextInt(); String clientId = UtilsTest.getRandomString(10); AdminRequest adminRequest = new AdminRequest(AdminRequestOrResponseType.TriggerCompaction, id, correlationId, clientId); Request request = MockRequest.fromAdminRequest(adminRequest, true); ambryRequests.handleRequests(request); assertEquals("Request accompanying response does not match original request", request, requestResponseChannel.lastOriginalRequest); assertNotNull("Response not sent", requestResponseChannel.lastResponse); assertTrue("Response is not of type AdminResponse", requestResponseChannel.lastResponse instanceof AdminResponse); AdminResponse response = (AdminResponse) requestResponseChannel.lastResponse; assertEquals("Correlation id in response does match the one in the request", correlationId, response.getCorrelationId()); assertEquals("Client id in response does match the one in the request", clientId, response.getClientId()); assertEquals("Error code does not match expected", expectedServerErrorCode, response.getError()); } /** * Implementation of {@link Request} to help with tests. */ private static class MockRequest implements Request { private final InputStream stream; /** * Constructs a {@link MockRequest} from {@code adminRequest}. * @param adminRequest the {@link AdminRequest} to construct the {@link MockRequest} for. * @param prepareForHandling prepares for handling by {@link AmbryRequests#handleRequests(Request)} by reading * some initial field(s). * @return an instance of {@link MockRequest} that represents {@code adminRequest}. * @throws IOException */ static MockRequest fromAdminRequest(AdminRequest adminRequest, boolean prepareForHandling) throws IOException { ByteBuffer buffer = ByteBuffer.allocate((int) adminRequest.sizeInBytes()); adminRequest.writeTo(new ByteBufferChannel(buffer)); buffer.flip(); if (prepareForHandling) { // read length buffer.getLong(); } return new MockRequest(new ByteBufferInputStream(buffer)); } /** * Constructs a {@link MockRequest}. * @param stream the {@link InputStream} that will be returned on a call to {@link #getInputStream()}. */ private MockRequest(InputStream stream) { this.stream = stream; } @Override public InputStream getInputStream() { return stream; } @Override public long getStartTimeInMs() { return 0; } } /** * An extension of {@link SocketRequestResponseChannel} to help with tests. */ private static class MockRequestResponseChannel extends SocketRequestResponseChannel { /** * {@link Request} provided in the last call to {@link #sendResponse(Send, Request, ServerNetworkResponseMetrics). */ Request lastOriginalRequest = null; /** * The {@link Send} provided in the last call to {@link #sendResponse(Send, Request, ServerNetworkResponseMetrics). */ Send lastResponse = null; MockRequestResponseChannel() { super(1, 1); } @Override public void sendResponse(Send payloadToSend, Request originalRequest, ServerNetworkResponseMetrics metrics) { lastResponse = payloadToSend; lastOriginalRequest = originalRequest; } } /** * An extension of {@link StorageManager} to help with tests. */ private static class MockStorageManager extends StorageManager { /** * An empty {@link Store} implementation. */ private static Store store = new Store() { @Override public void start() throws StoreException { } @Override public StoreInfo get(List<? extends StoreKey> ids, EnumSet<StoreGetOptions> storeGetOptions) throws StoreException { return null; } @Override public void put(MessageWriteSet messageSetToWrite) throws StoreException { } @Override public void delete(MessageWriteSet messageSetToDelete) throws StoreException { } @Override public FindInfo findEntriesSince(FindToken token, long maxTotalSizeOfEntries) throws StoreException { return null; } @Override public Set<StoreKey> findMissingKeys(List<StoreKey> keys) throws StoreException { return null; } @Override public StoreStats getStoreStats() { return null; } @Override public boolean isKeyDeleted(StoreKey key) throws StoreException { return false; } @Override public long getSizeInBytes() { return 0; } @Override public void shutdown() throws StoreException { } }; /** * if {@code true}, a {@code null} {@link Store} is returned on a call to {@link #getStore(PartitionId)}. Otherwise * {@link #store} is returned. */ boolean returnNullStore = false; /** * If non-null, the given exception is thrown when {@link #scheduleNextForCompaction(PartitionId)} is called. */ RuntimeException exceptionToThrowOnSchedulingCompaction = null; /** * The return value for a call to {@link #scheduleNextForCompaction(PartitionId)}. */ boolean returnValueOfSchedulingCompaction = true; /** * The {@link PartitionId} that was provided in the call to {@link #scheduleNextForCompaction(PartitionId)} */ PartitionId compactionScheduledPartitionId = null; MockStorageManager() throws StoreException { super(new StoreConfig(new VerifiableProperties(new Properties())), Utils.newScheduler(1, true), new MetricRegistry(), Collections.EMPTY_LIST, null, null, null, new MockTime()); } @Override public Store getStore(PartitionId id) { return returnNullStore ? null : store; } @Override public boolean scheduleNextForCompaction(PartitionId id) { if (exceptionToThrowOnSchedulingCompaction != null) { throw exceptionToThrowOnSchedulingCompaction; } compactionScheduledPartitionId = id; return returnValueOfSchedulingCompaction; } } }