/** * 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.codahale.metrics.Counter; import com.codahale.metrics.Histogram; import com.github.ambry.clustermap.ClusterMap; import com.github.ambry.clustermap.PartitionId; import com.github.ambry.clustermap.ReplicaId; import com.github.ambry.commons.BlobId; import com.github.ambry.commons.ResponseHandler; import com.github.ambry.commons.ServerErrorCode; import com.github.ambry.config.RouterConfig; import com.github.ambry.messageformat.MessageFormatFlags; import com.github.ambry.network.ResponseInfo; import com.github.ambry.protocol.GetOption; import com.github.ambry.protocol.GetRequest; import com.github.ambry.protocol.GetResponse; import com.github.ambry.protocol.PartitionRequestInfo; import com.github.ambry.utils.Time; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * An abstract class for a get operation. */ abstract class GetOperation { protected final RouterConfig routerConfig; protected final NonBlockingRouterMetrics routerMetrics; protected final ClusterMap clusterMap; protected final ResponseHandler responseHandler; protected final Callback<GetBlobResultInternal> getOperationCallback; protected final BlobId blobId; protected final GetBlobOptionsInternal options; protected final Time time; private final Histogram localColoTracker; private final Histogram crossColoTracker; private final Counter pastDueCounter; protected volatile boolean operationCompleted = false; protected final AtomicReference<Exception> operationException = new AtomicReference<>(); protected GetBlobResultInternal operationResult; protected final long submissionTimeMs; private static final Logger logger = LoggerFactory.getLogger(GetOperation.class); /** * Construct a GetOperation * @param routerConfig the {@link RouterConfig} containing the configs for put operations. * @param routerMetrics The {@link NonBlockingRouterMetrics} to be used for reporting metrics. * @param clusterMap the {@link ClusterMap} of the cluster * @param responseHandler the {@link ResponseHandler} responsible for failure detection. * @param blobIdStr the blobId of the associated blob in string form. * @param options the {@link GetBlobOptionsInternal} associated with this operation. * @param getOperationCallback the callback that is to be called when the operation completes. * @param localColoTracker the {@link Histogram} that tracks intra datacenter latencies for this class of requests. * @param crossColoTracker the {@link Histogram} that tracks inter datacenter latencies for this class of requests. * @param pastDueCounter the {@link Counter} that tracks the number of times a request is past due. * @param time the {@link Time} instance to use. * @throws RouterException if there is an error with any of the parameters, such as an invalid blob id. */ GetOperation(RouterConfig routerConfig, NonBlockingRouterMetrics routerMetrics, ClusterMap clusterMap, ResponseHandler responseHandler, String blobIdStr, GetBlobOptionsInternal options, Callback<GetBlobResultInternal> getOperationCallback, Histogram localColoTracker, Histogram crossColoTracker, Counter pastDueCounter, Time time) throws RouterException { this.routerConfig = routerConfig; this.routerMetrics = routerMetrics; this.clusterMap = clusterMap; this.responseHandler = responseHandler; this.options = options; this.getOperationCallback = getOperationCallback; this.localColoTracker = localColoTracker; this.crossColoTracker = crossColoTracker; this.pastDueCounter = pastDueCounter; this.time = time; submissionTimeMs = time.milliseconds(); blobId = RouterUtils.getBlobIdFromString(blobIdStr, clusterMap); validateTrackerType(); } /** * Return the {@link Callback} associated with this operation. * @return the {@link Callback} associated with this operation. */ Callback<GetBlobResultInternal> getCallback() { return getOperationCallback; } /** * The exception associated with this operation if it failed; null otherwise. * @return exception associated with this operation if it failed; null otherwise. */ Exception getOperationException() { return operationException.get(); } /** * Return the result of the operation. * @return the operation result. */ GetBlobResultInternal getOperationResult() { return operationResult; } /** * Return the blob id string * @return return the blob id string */ String getBlobIdStr() { return blobId.getID(); } /** * @return The {@link GetBlobOptions} associated with this operation. */ GetBlobOptionsInternal getOptions() { return options; } /** * returns whether the operation has completed. * @return whether the operation has completed. */ boolean isOperationComplete() { return operationCompleted; } /** * For this operation, create and populate get requests to send out. * @param requestRegistrationCallback the {@link RequestRegistrationCallback} to call for every request that gets * created as part of this poll operation. */ abstract void poll(RequestRegistrationCallback<GetOperation> requestRegistrationCallback); /** * Handle the given {@link ResponseInfo} received for a request that was sent out. * @param responseInfo the {@link ResponseInfo} to be handled. * @param getResponse the {@link GetResponse} associated with this response. */ abstract void handleResponse(ResponseInfo responseInfo, GetResponse getResponse); /** * Abort operation by invoking any callbacks and updating futures with an exception. * @param abortCause the exception that is the cause for the abort. */ abstract void abort(Exception abortCause); /** * Set the exception associated with this operation. * A {@link ServerErrorCode#Blob_Deleted} or {@link ServerErrorCode#Blob_Expired} error overrides any other * previously received exception. * @param exception the {@link RouterException} to possibly set. */ void setOperationException(Exception exception) { if (exception instanceof RouterException) { RouterErrorCode routerErrorCode = ((RouterException) exception).getErrorCode(); if (operationException.get() == null || routerErrorCode == RouterErrorCode.BlobDeleted || routerErrorCode == RouterErrorCode.BlobExpired) { operationException.set(exception); } } else { if (operationException.get() == null) { operationException.set(exception); } } } /** * Create and return the {@link GetRequest} associated with the given blobId. * @return the created {@link GetRequest}. * @param blobId The {@link BlobId} for which the {@link GetRequest} is being created. * @param flag The {@link MessageFormatFlags} to be set with the GetRequest. * @return the created GetRequest. */ protected GetRequest createGetRequest(BlobId blobId, MessageFormatFlags flag, GetOption getOption) { List<BlobId> blobIds = Collections.singletonList(blobId); List<PartitionRequestInfo> partitionRequestInfoList = Collections.singletonList(new PartitionRequestInfo(blobId.getPartition(), blobIds)); return new GetRequest(NonBlockingRouter.correlationIdGenerator.incrementAndGet(), routerConfig.routerHostname, flag, partitionRequestInfoList, getOption); } /** * Gets an {@link OperationTracker} based on the config and {@code partitionId}. * @param partitionId the {@link PartitionId} for which a tracker is required. * @return an {@link OperationTracker} based on the config and {@code partitionId}. */ protected OperationTracker getOperationTracker(PartitionId partitionId) { OperationTracker operationTracker; String trackerType = routerConfig.routerGetOperationTrackerType; if (trackerType.equals(SimpleOperationTracker.class.getSimpleName())) { operationTracker = new SimpleOperationTracker(routerConfig.routerDatacenterName, partitionId, routerConfig.routerGetCrossDcEnabled, routerConfig.routerGetSuccessTarget, routerConfig.routerGetRequestParallelism); } else if (trackerType.equals(AdaptiveOperationTracker.class.getSimpleName())) { operationTracker = new AdaptiveOperationTracker(routerConfig.routerDatacenterName, partitionId, routerConfig.routerGetCrossDcEnabled, routerConfig.routerGetSuccessTarget, routerConfig.routerGetRequestParallelism, time, localColoTracker, crossColoTracker, pastDueCounter, routerConfig.routerLatencyToleranceQuantile); } else { throw new IllegalArgumentException("Unrecognized tracker type: " + trackerType); } return operationTracker; } /** * Validates the tracker type in config. */ private void validateTrackerType() { String trackerType = routerConfig.routerGetOperationTrackerType; if (!trackerType.equals(SimpleOperationTracker.class.getSimpleName()) && !trackerType.equals( AdaptiveOperationTracker.class.getSimpleName())) { throw new IllegalArgumentException("Unrecognized tracker type: " + trackerType); } } } /** * A class that holds information about the get requests sent out. */ class GetRequestInfo { final ReplicaId replicaId; final long startTimeMs; /** * Construct a GetRequestInfo * @param replicaId the replica to which this request is being sent. * @param startTimeMs the time at which this request was created. */ GetRequestInfo(ReplicaId replicaId, long startTimeMs) { this.replicaId = replicaId; this.startTimeMs = startTimeMs; } }