/** * 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.tools.admin; import com.codahale.metrics.MetricRegistry; import com.github.ambry.clustermap.ClusterAgentsFactory; import com.github.ambry.clustermap.ClusterMap; import com.github.ambry.clustermap.ReplicaId; import com.github.ambry.commons.BlobId; import com.github.ambry.commons.ServerErrorCode; import com.github.ambry.config.ClusterMapConfig; import com.github.ambry.config.ConnectionPoolConfig; import com.github.ambry.config.SSLConfig; import com.github.ambry.config.VerifiableProperties; import com.github.ambry.messageformat.BlobData; import com.github.ambry.messageformat.BlobProperties; import com.github.ambry.messageformat.MessageFormatException; import com.github.ambry.messageformat.MessageFormatFlags; import com.github.ambry.messageformat.MessageFormatRecord; import com.github.ambry.network.BlockingChannelConnectionPool; import com.github.ambry.network.ConnectedChannel; import com.github.ambry.network.ConnectionPool; import com.github.ambry.network.ConnectionPoolTimeoutException; import com.github.ambry.network.Port; 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.tools.util.ToolUtils; import com.github.ambry.utils.Utils; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; import joptsimple.ArgumentAcceptingOptionSpec; import joptsimple.OptionParser; import joptsimple.OptionSet; import joptsimple.OptionSpec; /** * Tool to support admin related operations * Operations supported so far: * List Replicas for a given blobid */ public class AdminTool { private final ConnectionPool connectionPool; public AdminTool(ConnectionPool connectionPool) { this.connectionPool = connectionPool; } public static void main(String args[]) { ConnectionPool connectionPool = null; try { OptionParser parser = new OptionParser(); ArgumentAcceptingOptionSpec<String> hardwareLayoutOpt = parser.accepts("hardwareLayout", "The path of the hardware layout file") .withRequiredArg() .describedAs("hardware_layout") .ofType(String.class); ArgumentAcceptingOptionSpec<String> partitionLayoutOpt = parser.accepts("partitionLayout", "The path of the partition layout file") .withRequiredArg() .describedAs("partition_layout") .ofType(String.class); ArgumentAcceptingOptionSpec<String> typeOfOperationOpt = parser.accepts("typeOfOperation", "The type of operation to execute - LIST_REPLICAS/GET_BLOB/GET_BLOB_PROPERTIES/GET_USERMETADATA") .withRequiredArg() .describedAs("The type of file") .ofType(String.class) .defaultsTo("GET"); ArgumentAcceptingOptionSpec<String> ambryBlobIdOpt = parser.accepts("ambryBlobId", "The blob id to execute get on") .withRequiredArg() .describedAs("The blob id") .ofType(String.class); ArgumentAcceptingOptionSpec<String> includeExpiredBlobsOpt = parser.accepts("includeExpiredBlob", "Included expired blobs too") .withRequiredArg() .describedAs("Whether to include expired blobs while querying or not") .defaultsTo("false") .ofType(String.class); ArgumentAcceptingOptionSpec<String> sslEnabledDatacentersOpt = parser.accepts("sslEnabledDatacenters", "Datacenters to which ssl should be enabled") .withOptionalArg() .describedAs("Comma separated list") .defaultsTo("") .ofType(String.class); ArgumentAcceptingOptionSpec<String> sslKeystorePathOpt = parser.accepts("sslKeystorePath", "SSL key store path") .withOptionalArg() .describedAs("The file path of SSL key store") .defaultsTo("") .ofType(String.class); ArgumentAcceptingOptionSpec<String> sslKeystoreTypeOpt = parser.accepts("sslKeystoreType", "SSL key store type") .withOptionalArg() .describedAs("The type of SSL key store") .defaultsTo("") .ofType(String.class); ArgumentAcceptingOptionSpec<String> sslTruststorePathOpt = parser.accepts("sslTruststorePath", "SSL trust store path") .withOptionalArg() .describedAs("The file path of SSL trust store") .defaultsTo("") .ofType(String.class); ArgumentAcceptingOptionSpec<String> sslKeystorePasswordOpt = parser.accepts("sslKeystorePassword", "SSL key store password") .withOptionalArg() .describedAs("The password of SSL key store") .defaultsTo("") .ofType(String.class); ArgumentAcceptingOptionSpec<String> sslKeyPasswordOpt = parser.accepts("sslKeyPassword", "SSL key password") .withOptionalArg() .describedAs("The password of SSL private key") .defaultsTo("") .ofType(String.class); ArgumentAcceptingOptionSpec<String> sslTruststorePasswordOpt = parser.accepts("sslTruststorePassword", "SSL trust store password") .withOptionalArg() .describedAs("The password of SSL trust store") .defaultsTo("") .ofType(String.class); ArgumentAcceptingOptionSpec<String> sslCipherSuitesOpt = parser.accepts("sslCipherSuites", "SSL enabled cipher suites") .withOptionalArg() .describedAs("Comma separated list") .defaultsTo("TLS_RSA_WITH_AES_128_CBC_SHA") .ofType(String.class); OptionSet options = parser.parse(args); ArrayList<OptionSpec> listOpt = new ArrayList<>(); listOpt.add(hardwareLayoutOpt); listOpt.add(partitionLayoutOpt); listOpt.add(typeOfOperationOpt); listOpt.add(ambryBlobIdOpt); ToolUtils.ensureOrExit(listOpt, options, parser); ToolUtils.validateSSLOptions(options, parser, sslEnabledDatacentersOpt, sslKeystorePathOpt, sslKeystoreTypeOpt, sslTruststorePathOpt, sslKeystorePasswordOpt, sslKeyPasswordOpt, sslTruststorePasswordOpt); String sslEnabledDatacenters = options.valueOf(sslEnabledDatacentersOpt); Properties sslProperties; if (sslEnabledDatacenters.length() != 0) { sslProperties = ToolUtils.createSSLProperties(sslEnabledDatacenters, options.valueOf(sslKeystorePathOpt), options.valueOf(sslKeystoreTypeOpt), options.valueOf(sslKeystorePasswordOpt), options.valueOf(sslKeyPasswordOpt), options.valueOf(sslTruststorePathOpt), options.valueOf(sslTruststorePasswordOpt), options.valueOf(sslCipherSuitesOpt)); } else { sslProperties = new Properties(); } ToolUtils.addClusterMapProperties(sslProperties); Properties connectionPoolProperties = ToolUtils.createConnectionPoolProperties(); ConnectionPoolConfig connectionPoolConfig = new ConnectionPoolConfig(new VerifiableProperties(connectionPoolProperties)); VerifiableProperties vProps = new VerifiableProperties(sslProperties); SSLConfig sslConfig = new SSLConfig(vProps); ClusterMapConfig clusterMapConfig = new ClusterMapConfig(vProps); connectionPool = new BlockingChannelConnectionPool(connectionPoolConfig, sslConfig, clusterMapConfig, new MetricRegistry()); String hardwareLayoutPath = options.valueOf(hardwareLayoutOpt); String partitionLayoutPath = options.valueOf(partitionLayoutOpt); ClusterMap map = ((ClusterAgentsFactory) Utils.getObj(clusterMapConfig.clusterMapClusterAgentsFactory, clusterMapConfig, hardwareLayoutPath, partitionLayoutPath)).getClusterMap(); String blobIdStr = options.valueOf(ambryBlobIdOpt); AdminTool adminTool = new AdminTool(connectionPool); BlobId blobId = new BlobId(blobIdStr, map); String typeOfOperation = options.valueOf(typeOfOperationOpt); boolean includeExpiredBlobs = Boolean.parseBoolean(options.valueOf(includeExpiredBlobsOpt)); if (typeOfOperation.equalsIgnoreCase("LIST_REPLICAS")) { List<? extends ReplicaId> replicaIdList = adminTool.getReplicas(blobId); for (ReplicaId replicaId : replicaIdList) { System.out.println(replicaId); } } else if (typeOfOperation.equalsIgnoreCase("GET_BLOB")) { adminTool.getBlob(blobId, map, includeExpiredBlobs); } else if (typeOfOperation.equalsIgnoreCase("GET_BLOB_PROPERTIES")) { adminTool.getBlobProperties(blobId, map, includeExpiredBlobs); } else if (typeOfOperation.equalsIgnoreCase("GET_USERMETADATA")) { adminTool.getUserMetadata(blobId, map, includeExpiredBlobs); } else { System.out.println("Invalid Type of Operation "); System.exit(1); } } catch (Exception e) { System.out.println("Closed with error " + e); } finally { if (connectionPool != null) { connectionPool.shutdown(); } } } public List<? extends ReplicaId> getReplicas(BlobId blobId) { return blobId.getPartition().getReplicaIds(); } public BlobProperties getBlobProperties(BlobId blobId, ClusterMap map, boolean expiredBlobs) { List<? extends ReplicaId> replicas = blobId.getPartition().getReplicaIds(); BlobProperties blobProperties = null; for (ReplicaId replicaId : replicas) { try { blobProperties = getBlobProperties(blobId, map, replicaId, expiredBlobs); break; } catch (Exception e) { System.out.println("Get blob properties error "); e.printStackTrace(); } } return blobProperties; } public BlobProperties getBlobProperties(BlobId blobId, ClusterMap clusterMap, ReplicaId replicaId, boolean expiredBlobs) throws MessageFormatException, IOException, ConnectionPoolTimeoutException, InterruptedException { ArrayList<BlobId> blobIds = new ArrayList<BlobId>(); blobIds.add(blobId); ConnectedChannel connectedChannel = null; AtomicInteger correlationId = new AtomicInteger(1); PartitionRequestInfo partitionRequestInfo = new PartitionRequestInfo(blobId.getPartition(), blobIds); ArrayList<PartitionRequestInfo> partitionRequestInfos = new ArrayList<PartitionRequestInfo>(); partitionRequestInfos.add(partitionRequestInfo); GetOption getOption = (expiredBlobs) ? GetOption.Include_Expired_Blobs : GetOption.None; try { Port port = replicaId.getDataNodeId().getPortToConnectTo(); connectedChannel = connectionPool.checkOutConnection(replicaId.getDataNodeId().getHostname(), port, 10000); GetRequest getRequest = new GetRequest(correlationId.incrementAndGet(), "readverifier", MessageFormatFlags.BlobProperties, partitionRequestInfos, getOption); System.out.println("Get Request to verify replica blob properties : " + getRequest); GetResponse getResponse = null; getResponse = BlobValidator.getGetResponseFromStream(connectedChannel, getRequest, clusterMap); if (getResponse == null) { System.out.println(" Get Response from Stream to verify replica blob properties is null "); System.out.println(blobId + " STATE FAILED"); throw new IOException(" Get Response from Stream to verify replica blob properties is null "); } ServerErrorCode serverResponseCode = getResponse.getPartitionResponseInfoList().get(0).getErrorCode(); System.out.println("Get Response from Stream to verify replica blob properties : " + getResponse.getError()); if (getResponse.getError() != ServerErrorCode.No_Error || serverResponseCode != ServerErrorCode.No_Error) { System.out.println("getBlobProperties error on response " + getResponse.getError() + " error code on partition " + serverResponseCode + " ambryReplica " + replicaId.getDataNodeId().getHostname() + " port " + port.toString() + " blobId " + blobId); if (serverResponseCode == ServerErrorCode.Blob_Not_Found) { return null; } else if (serverResponseCode == ServerErrorCode.Blob_Deleted) { return null; } else { return null; } } else { BlobProperties properties = MessageFormatRecord.deserializeBlobProperties(getResponse.getInputStream()); System.out.println( "Blob Properties : Content Type : " + properties.getContentType() + ", OwnerId : " + properties.getOwnerId() + ", Size : " + properties.getBlobSize() + ", CreationTimeInMs : " + properties.getCreationTimeInMs() + ", ServiceId : " + properties.getServiceId() + ", TTL : " + properties.getTimeToLiveInSeconds()); return properties; } } catch (MessageFormatException mfe) { System.out.println("MessageFormat Exception Error " + mfe); connectionPool.destroyConnection(connectedChannel); connectedChannel = null; throw mfe; } catch (IOException e) { System.out.println("IOException " + e); connectionPool.destroyConnection(connectedChannel); connectedChannel = null; throw e; } finally { if (connectedChannel != null) { connectionPool.checkInConnection(connectedChannel); } } } public BlobData getBlob(BlobId blobId, ClusterMap map, boolean expiredBlobs) throws MessageFormatException, IOException { List<? extends ReplicaId> replicas = blobId.getPartition().getReplicaIds(); BlobData blobData = null; for (ReplicaId replicaId : replicas) { try { blobData = getBlob(blobId, map, replicaId, expiredBlobs); break; } catch (Exception e) { System.out.println("Get blob error "); e.printStackTrace(); } } return blobData; } public BlobData getBlob(BlobId blobId, ClusterMap clusterMap, ReplicaId replicaId, boolean expiredBlobs) throws MessageFormatException, IOException, ConnectionPoolTimeoutException, InterruptedException { ArrayList<BlobId> blobIds = new ArrayList<BlobId>(); blobIds.add(blobId); ConnectedChannel connectedChannel = null; AtomicInteger correlationId = new AtomicInteger(1); PartitionRequestInfo partitionRequestInfo = new PartitionRequestInfo(blobId.getPartition(), blobIds); ArrayList<PartitionRequestInfo> partitionRequestInfos = new ArrayList<PartitionRequestInfo>(); partitionRequestInfos.add(partitionRequestInfo); GetOption getOption = (expiredBlobs) ? GetOption.Include_Expired_Blobs : GetOption.None; try { Port port = replicaId.getDataNodeId().getPortToConnectTo(); connectedChannel = connectionPool.checkOutConnection(replicaId.getDataNodeId().getHostname(), port, 10000); GetRequest getRequest = new GetRequest(correlationId.incrementAndGet(), "readverifier", MessageFormatFlags.Blob, partitionRequestInfos, getOption); System.out.println("Get Request to get blob : " + getRequest); GetResponse getResponse = null; getResponse = BlobValidator.getGetResponseFromStream(connectedChannel, getRequest, clusterMap); if (getResponse == null) { System.out.println(" Get Response from Stream to verify replica blob is null "); System.out.println(blobId + " STATE FAILED"); throw new IOException(" Get Response from Stream to verify replica blob properties is null "); } System.out.println("Get Response to get blob : " + getResponse.getError()); ServerErrorCode serverResponseCode = getResponse.getPartitionResponseInfoList().get(0).getErrorCode(); if (getResponse.getError() != ServerErrorCode.No_Error || serverResponseCode != ServerErrorCode.No_Error) { System.out.println( "blob get error on response " + getResponse.getError() + " error code on partition " + serverResponseCode + " ambryReplica " + replicaId.getDataNodeId().getHostname() + " port " + port.toString() + " blobId " + blobId); if (serverResponseCode == ServerErrorCode.Blob_Not_Found) { return null; } else if (serverResponseCode == ServerErrorCode.Blob_Deleted) { return null; } else { return null; } } else { return MessageFormatRecord.deserializeBlob(getResponse.getInputStream()); } } catch (MessageFormatException mfe) { System.out.println("MessageFormat Exception Error " + mfe); connectionPool.destroyConnection(connectedChannel); connectedChannel = null; throw mfe; } catch (IOException e) { System.out.println("IOException " + e); connectionPool.destroyConnection(connectedChannel); connectedChannel = null; throw e; } finally { if (connectedChannel != null) { connectionPool.checkInConnection(connectedChannel); } } } public ByteBuffer getUserMetadata(BlobId blobId, ClusterMap map, boolean expiredBlobs) throws MessageFormatException, IOException { List<? extends ReplicaId> replicas = blobId.getPartition().getReplicaIds(); ByteBuffer userMetadata = null; for (ReplicaId replicaId : replicas) { try { userMetadata = getUserMetadata(blobId, map, replicaId, expiredBlobs); break; } catch (Exception e) { System.out.println("Get user metadata error "); e.printStackTrace(); } } return userMetadata; } public ByteBuffer getUserMetadata(BlobId blobId, ClusterMap clusterMap, ReplicaId replicaId, boolean expiredBlobs) throws MessageFormatException, IOException, ConnectionPoolTimeoutException, InterruptedException { ArrayList<BlobId> blobIds = new ArrayList<BlobId>(); blobIds.add(blobId); ConnectedChannel connectedChannel = null; AtomicInteger correlationId = new AtomicInteger(1); PartitionRequestInfo partitionRequestInfo = new PartitionRequestInfo(blobId.getPartition(), blobIds); ArrayList<PartitionRequestInfo> partitionRequestInfos = new ArrayList<PartitionRequestInfo>(); partitionRequestInfos.add(partitionRequestInfo); GetOption getOption = (expiredBlobs) ? GetOption.Include_Expired_Blobs : GetOption.None; try { Port port = replicaId.getDataNodeId().getPortToConnectTo(); connectedChannel = connectionPool.checkOutConnection(replicaId.getDataNodeId().getHostname(), port, 10000); GetRequest getRequest = new GetRequest(correlationId.incrementAndGet(), "readverifier", MessageFormatFlags.BlobUserMetadata, partitionRequestInfos, getOption); System.out.println("Get Request to check blob usermetadata : " + getRequest); GetResponse getResponse = null; getResponse = BlobValidator.getGetResponseFromStream(connectedChannel, getRequest, clusterMap); if (getResponse == null) { System.out.println(" Get Response from Stream to verify replica blob usermetadata is null "); System.out.println(blobId + " STATE FAILED"); throw new IOException(" Get Response from Stream to verify replica blob properties is null "); } System.out.println("Get Response to check blob usermetadata : " + getResponse.getError()); ServerErrorCode serverResponseCode = getResponse.getPartitionResponseInfoList().get(0).getErrorCode(); if (getResponse.getError() != ServerErrorCode.No_Error || serverResponseCode != ServerErrorCode.No_Error) { System.out.println("usermetadata get error on response " + getResponse.getError() + " error code on partition " + serverResponseCode + " ambryReplica " + replicaId.getDataNodeId().getHostname() + " port " + port.toString() + " blobId " + blobId); if (serverResponseCode == ServerErrorCode.Blob_Not_Found) { return null; } else if (serverResponseCode == ServerErrorCode.Blob_Deleted) { return null; } else { return null; } } else { ByteBuffer userMetadata = MessageFormatRecord.deserializeUserMetadata(getResponse.getInputStream()); System.out.println("Usermetadata deserialized. Size " + userMetadata.capacity()); return userMetadata; } } catch (MessageFormatException mfe) { System.out.println("MessageFormat Exception Error " + mfe); connectionPool.destroyConnection(connectedChannel); connectedChannel = null; throw mfe; } catch (IOException e) { System.out.println("IOException " + e); connectionPool.destroyConnection(connectedChannel); connectedChannel = null; throw e; } finally { if (connectedChannel != null) { connectionPool.checkInConnection(connectedChannel); } } } }