/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 gobblin.util.limiter; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.mockito.Mockito; import com.google.common.collect.Queues; import com.linkedin.common.callback.Callback; import com.linkedin.restli.client.Response; import com.linkedin.restli.client.RestLiResponseException; import com.linkedin.restli.common.ComplexResourceKey; import com.linkedin.restli.common.EmptyRecord; import com.linkedin.restli.server.RestLiServiceException; import gobblin.restli.throttling.LimiterServerResource; import gobblin.restli.throttling.PermitAllocation; import gobblin.restli.throttling.PermitRequest; import lombok.Data; import lombok.extern.slf4j.Slf4j; /** * A mock {@link RestClientRequestSender} that satisfies requests using an embedded * {@link LimiterServerResource}. */ @Slf4j public class MockRequester implements RequestSender { private final BlockingQueue<RequestAndCallback> requestAndCallbackQueue; private final LimiterServerResource limiterServer; private final long latencyMillis; private final int requestHandlerThreads; private boolean started; private ExecutorService handlerExecutorService; public MockRequester(LimiterServerResource limiterServer, long latencyMillis, int requestHandlerThreads) { this.limiterServer = limiterServer; this.latencyMillis = latencyMillis; this.requestHandlerThreads = requestHandlerThreads; this.requestAndCallbackQueue = Queues.newLinkedBlockingQueue(); } public synchronized void start() { if (this.started) { return; } this.started = true; this.handlerExecutorService = Executors.newFixedThreadPool(this.requestHandlerThreads); for (int i = 0; i < this.requestHandlerThreads; i++) { this.handlerExecutorService.submit(new RequestHandler()); } } public synchronized void stop() { if (!this.started) { return; } this.handlerExecutorService.shutdownNow(); this.started = false; } @Override public void sendRequest(PermitRequest request, Callback<Response<PermitAllocation>> callback) { if (!this.started) { throw new IllegalStateException(MockRequester.class.getSimpleName() + " has not been started."); } long nanoTime = System.nanoTime(); long satisfyAt = nanoTime + TimeUnit.MILLISECONDS.toNanos(this.latencyMillis); this.requestAndCallbackQueue.add(new RequestAndCallback(request, callback, satisfyAt)); } @Data public static class RequestAndCallback { private final PermitRequest request; private final Callback<Response<PermitAllocation>> callback; private final long processAfterNanos; } private class RequestHandler implements Runnable { @Override public void run() { try { while (true) { RequestAndCallback requestAndCallback = MockRequester.this.requestAndCallbackQueue.take(); long nanoTime = System.nanoTime(); long delayNanos = requestAndCallback.getProcessAfterNanos() - nanoTime; if (delayNanos > 0) { Thread.sleep(TimeUnit.NANOSECONDS.toMillis(delayNanos)); } try { PermitAllocation allocation = MockRequester.this.limiterServer.getSync(new ComplexResourceKey<>(requestAndCallback.getRequest(), new EmptyRecord())); Response<PermitAllocation> response = Mockito.mock(Response.class); Mockito.when(response.getEntity()).thenReturn(allocation); requestAndCallback.getCallback().onSuccess(response); } catch (RestLiServiceException rexc) { RestLiResponseException returnException = Mockito.mock(RestLiResponseException.class); Mockito.when(returnException.getStatus()).thenReturn(rexc.getStatus().getCode()); requestAndCallback.getCallback().onError(returnException); } } } catch (Throwable t) { log.error("Error", t); throw new RuntimeException(t); } } } }