/** * 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.admin; 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.ByteBufferReadableStreamChannel; import com.github.ambry.rest.RestResponseChannel; import com.github.ambry.rest.RestServiceErrorCode; import com.github.ambry.rest.RestServiceException; import com.github.ambry.rest.RestUtils; import com.github.ambry.router.ReadableStreamChannel; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Performs the {@link RestUtils.SubResource#Replicas} operation supported by the Admin. */ class GetReplicasHandler { static String REPLICAS_KEY = "replicas"; private final AdminMetrics adminMetrics; private final ClusterMap clusterMap; private final Logger logger = LoggerFactory.getLogger(GetReplicasHandler.class); /** * Instantiate a handler to handle {@link RestUtils.SubResource#Replicas} operations. * @param adminMetrics the {@link AdminMetrics} instance to use for metrics. * @param clusterMap the {@link ClusterMap} to use to find the replicas of a blob ID. */ GetReplicasHandler(AdminMetrics adminMetrics, ClusterMap clusterMap) { this.adminMetrics = adminMetrics; this.clusterMap = clusterMap; } /** * Handles {@link RestUtils.SubResource#Replicas} operations by obtaining the replicas of the blob ID from the cluster * map and returning a serialized JSON object in the response. * @param blobId the blob ID whose replicas are required. * @param restResponseChannel the {@link RestResponseChannel} to set headers in. * @return a {@link ReadableStreamChannel} that contains the getReplicas response. * @throws RestServiceException if there was any problem constructing the response. */ public ReadableStreamChannel getReplicas(String blobId, RestResponseChannel restResponseChannel) throws RestServiceException { logger.trace("Getting replicas of blob ID - {}", blobId); long startTime = System.currentTimeMillis(); ReadableStreamChannel channel = null; try { String replicaStr = getReplicas(blobId).toString(); restResponseChannel.setHeader(RestUtils.Headers.CONTENT_TYPE, "application/json"); restResponseChannel.setHeader(RestUtils.Headers.CONTENT_LENGTH, replicaStr.length()); channel = new ByteBufferReadableStreamChannel(ByteBuffer.wrap(replicaStr.getBytes())); } finally { adminMetrics.getReplicasProcessingTimeInMs.update(System.currentTimeMillis() - startTime); } return channel; } /** * Extracts the blob ID provided by the client and figures out the partition that the blob ID would belong to * based on the cluster map. Using the partition information, returns the list of replicas as a part of a JSONObject. * @param blobId the blob ID whose replicas are required. * @return A {@link JSONObject} that wraps the replica list. * @throws RestServiceException if there were missing or invalid arguments or if there was a {@link JSONException} * or any other while building the response */ private JSONObject getReplicas(String blobId) throws RestServiceException { try { PartitionId partitionId = new BlobId(blobId, clusterMap).getPartition(); if (partitionId == null) { adminMetrics.invalidBlobIdError.inc(); logger.warn("Partition for blob id {} is null. The blob id might be invalid", blobId); throw new RestServiceException("Partition for blob id " + blobId + " is null. The id might be invalid", RestServiceErrorCode.NotFound); } return packageResult(partitionId.getReplicaIds()); } catch (IllegalArgumentException e) { adminMetrics.invalidBlobIdError.inc(); throw new RestServiceException("Invalid blob id received for getReplicasForBlob request - " + blobId, e, RestServiceErrorCode.NotFound); } catch (IOException | JSONException e) { adminMetrics.responseConstructionError.inc(); throw new RestServiceException("Could not create response for GET of replicas for " + blobId, e, RestServiceErrorCode.InternalServerError); } } /** * Packages the list of replicas into a {@link JSONObject}. * @param replicaIds the list of {@link ReplicaId}s that need to packaged into a {@link JSONObject}. * @return A {@link JSONObject} that wraps the replica list. * @throws JSONException if there was an error building the {@link JSONObject}. */ private static JSONObject packageResult(List<? extends ReplicaId> replicaIds) throws JSONException { JSONObject result = new JSONObject(); if (replicaIds != null) { for (ReplicaId replicaId : replicaIds) { result.append(REPLICAS_KEY, replicaId); } } return result; } }