/* * 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.clustermap; import com.codahale.metrics.MetricRegistry; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashSet; import java.util.List; import java.util.Set; /** * A cluster manager that is a wrapper over a {@link StaticClusterManager} instance and a {@link HelixClusterManager} * instance and uses the {@link StaticClusterManager} as the source-of-truth. It relays events to both and checks for * and reports inconsistencies in the views from the two underlying cluster managers. */ class CompositeClusterManager implements ClusterMap { final StaticClusterManager staticClusterManager; final HelixClusterManager helixClusterManager; final HelixClusterManagerMetrics helixClusterManagerMetrics; /** * Construct a CompositeClusterManager instance. * @param staticClusterManager the {@link StaticClusterManager} instance to use as the source-of-truth. * @param helixClusterManager the {@link HelixClusterManager} instance to use for comparison of views. */ CompositeClusterManager(StaticClusterManager staticClusterManager, HelixClusterManager helixClusterManager) { this.staticClusterManager = staticClusterManager; this.helixClusterManager = helixClusterManager; this.helixClusterManagerMetrics = helixClusterManager != null ? helixClusterManager.helixClusterManagerMetrics : null; } @Override public PartitionId getPartitionIdFromStream(InputStream stream) throws IOException { DuplicatingInputStream duplicatingInputStream = new DuplicatingInputStream(stream); duplicatingInputStream.mark(0); PartitionId partitionIdStatic = staticClusterManager.getPartitionIdFromStream(duplicatingInputStream); if (helixClusterManager != null) { duplicatingInputStream.reset(); PartitionId partitionIdDynamic = helixClusterManager.getPartitionIdFromStream(duplicatingInputStream); if (!partitionIdStatic.toString().equals(partitionIdDynamic.toString())) { helixClusterManagerMetrics.getPartitionIdFromStreamMismatchCount.inc(); } } return partitionIdStatic; } /** * Get writable partition ids from both the underlying {@link StaticClusterManager} and the underlying * {@link HelixClusterManager}. Compare the two and if there is a mismatch, update a metric. * @return a list of writable partition ids from the underlying {@link StaticClusterManager}. */ @Override public List<PartitionId> getWritablePartitionIds() { List<PartitionId> staticWritablePartitionIds = staticClusterManager.getWritablePartitionIds(); if (helixClusterManager != null) { if (!areEqual(staticWritablePartitionIds, helixClusterManager.getWritablePartitionIds())) { helixClusterManagerMetrics.getAllPartitionIdsMismatchCount.inc(); } } return staticWritablePartitionIds; } /** * Get all partition ids from both the underlying {@link StaticClusterManager} and the underlying * {@link HelixClusterManager}. Compare the two and if there is a mismatch, update a metric. * @return a list of partition ids from the underlying {@link StaticClusterManager}. */ @Override public List<PartitionId> getAllPartitionIds() { List<PartitionId> staticPartitionIds = staticClusterManager.getAllPartitionIds(); if (helixClusterManager != null) { if (!areEqual(staticPartitionIds, helixClusterManager.getAllPartitionIds())) { helixClusterManagerMetrics.getAllPartitionIdsMismatchCount.inc(); } } return staticPartitionIds; } /** * Check for existence of the given datacenter from both the static and the helix based cluster managers and update * a metric if there is a mismatch. * @param datacenterName name of datacenter * @return true if the datacenter exists in the underlying {@link StaticClusterManager}; false otherwise. */ @Override public boolean hasDatacenter(String datacenterName) { boolean staticHas = staticClusterManager.hasDatacenter(datacenterName); if (helixClusterManager != null) { boolean helixHas = helixClusterManager.hasDatacenter(datacenterName); if (staticHas != helixHas) { helixClusterManagerMetrics.hasDatacenterMismatchCount.inc(); } } return staticHas; } /** * Return the {@link DataNodeId} associated with the given hostname and port in the underlying * {@link StaticClusterManager}. * Also get the associated data node from the underlying {@link HelixClusterManager} and if the two returned * DataNodeId * objects do not refer to the same actual instance, update a metric. * @param hostname of the DataNodeId * @param port of the DataNodeId * @return the {@link DataNodeId} associated with the given hostname and port in the underlying * {@link StaticClusterManager}. */ @Override public DataNodeId getDataNodeId(String hostname, int port) { DataNodeId staticDataNode = staticClusterManager.getDataNodeId(hostname, port); if (helixClusterManager != null) { DataNodeId helixDataNode = helixClusterManager.getDataNodeId(hostname, port); if (!staticDataNode.toString().equals(helixDataNode.toString())) { helixClusterManagerMetrics.getDataNodeIdMismatchCount.inc(); } } return staticDataNode; } /** * Get the list of {@link ReplicaId}s associated with the given {@link DataNodeId} in the underlying * {@link StaticClusterManager}. * Also get the associated list of {@link ReplicaId}s from the underlying {@link HelixClusterManager} and verify * equality, updating a metric if there is a mismatch. * @param dataNodeId the {@link DataNodeId} for which the replicas are to be listed. * @return the list of {@link ReplicaId}s as present in the underlying {@link StaticClusterManager} for the given * node. */ @Override public List<ReplicaId> getReplicaIds(DataNodeId dataNodeId) { List<ReplicaId> staticReplicaIds = staticClusterManager.getReplicaIds(dataNodeId); if (helixClusterManager != null) { Set<String> staticReplicaIdStrings = new HashSet<>(); for (ReplicaId replicaId : staticReplicaIds) { staticReplicaIdStrings.add(replicaId.toString()); } Set<String> helixReplicaIdStrings = new HashSet<>(); DataNodeId ambryDataNode = helixClusterManager.getDataNodeId(dataNodeId.getHostname(), dataNodeId.getPort()); for (ReplicaId replicaId : helixClusterManager.getReplicaIds(ambryDataNode)) { helixReplicaIdStrings.add(replicaId.toString()); } if (!staticReplicaIdStrings.equals(helixReplicaIdStrings)) { helixClusterManagerMetrics.getReplicaIdsMismatchCount.inc(); } } return staticReplicaIds; } /** * The list of {@link DataNodeId}s present in the underlying {@link StaticClusterManager} * Also verify that the underlying {@link HelixClusterManager} has the same set of nodes, if not, update a metric. * @return the list of {@link DataNodeId}s present in the underlying {@link StaticClusterManager} */ @Override public List<DataNodeId> getDataNodeIds() { List<DataNodeId> staticDataNodeIds = staticClusterManager.getDataNodeIds(); if (helixClusterManager != null) { Set<String> staticDataNodeIdStrings = new HashSet<>(); for (DataNodeId dataNodeId : staticDataNodeIds) { staticDataNodeIdStrings.add(dataNodeId.toString()); } Set<String> helixDataNodeIdStrings = new HashSet<>(); for (DataNodeId dataNodeId : helixClusterManager.getDataNodeIds()) { helixDataNodeIdStrings.add(dataNodeId.toString()); } if (!staticDataNodeIdStrings.equals(helixDataNodeIdStrings)) { helixClusterManagerMetrics.getDataNodeIdsMismatchCount.inc(); } } return staticDataNodeIds; } @Override public MetricRegistry getMetricRegistry() { return staticClusterManager.getMetricRegistry(); } /** * Relay the event to both the underlying {@link StaticClusterManager} and the underlying {@link HelixClusterManager}. * @param replicaId the {@link ReplicaId} for which this event has occurred. * @param event the {@link ReplicaEventType}. */ @Override public void onReplicaEvent(ReplicaId replicaId, ReplicaEventType event) { staticClusterManager.onReplicaEvent(replicaId, event); if (helixClusterManager != null) { AmbryReplica ambryReplica = helixClusterManager.getReplicaForPartitionOnNode(replicaId.getDataNodeId().getHostname(), replicaId.getDataNodeId().getPort(), replicaId.getPartitionId().toString()); helixClusterManager.onReplicaEvent(ambryReplica, event); } } @Override public void close() { staticClusterManager.close(); if (helixClusterManager != null) { helixClusterManager.close(); } } /** * Check if two lists of partitions are equivalent * @param partitionListOne {@link List} of {@link PartitionId}s to compare * @param partitionListTwo {@link List} of {@link AmbryPartition}s to compare * @return {@code true} if both list are equal, {@code false} otherwise */ private boolean areEqual(List<PartitionId> partitionListOne, List<AmbryPartition> partitionListTwo) { Set<String> partitionStringsOne = new HashSet<>(); for (PartitionId partitionId : partitionListOne) { partitionStringsOne.add(partitionId.toString()); } Set<String> partitionStringsTwo = new HashSet<>(); for (PartitionId partitionId : partitionListTwo) { partitionStringsTwo.add(partitionId.toString()); } return partitionStringsOne.equals(partitionStringsTwo); } } /** * A helper implementation of {@link FilterInputStream} via which the same data can be read from an underlying stream * a second time using the mark/reset flow. It works similar to {@link java.io.BufferedInputStream}, except * that it does not do any extra buffering - it only reads as many bytes from the underlying stream as is * required to return in the immediate call as part of which the underlying reads are done. */ class DuplicatingInputStream extends FilterInputStream { private enum Mode { SAVE_READS, SERVE_DUPLICATES } private Mode mode = Mode.SAVE_READS; private final ByteArrayOutputStream writeStream = new ByteArrayOutputStream(); private ByteArrayInputStream readStream; /** * Construct an instance. The initial mode is "save-reads". * @param in the underlying {@link InputStream} to read from. */ DuplicatingInputStream(InputStream in) { super(in); } /** * Bytes read until the next reset() call are buffered and served after the reset. * @param readLimit parameter is ignored. */ @Override public void mark(int readLimit) { mode = Mode.SAVE_READS; writeStream.reset(); } @Override public void reset() { mode = Mode.SERVE_DUPLICATES; readStream = new ByteArrayInputStream(writeStream.toByteArray()); } @Override public int read() throws IOException { byte[] data = new byte[1]; if (read(data, 0, 1) == -1) { return -1; } else { return data[0] & 0xFF; } } @Override public int read(byte[] data, int offset, int len) throws IOException { int actuallyRead; if (mode == Mode.SERVE_DUPLICATES) { actuallyRead = readStream.read(data, offset, len); } else { actuallyRead = in.read(data, offset, len); if (actuallyRead != -1) { writeStream.write(data, offset, actuallyRead); } } return actuallyRead; } }