001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hdfs.server.blockmanagement;
019
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.HashMap;
023import java.util.HashSet;
024import java.util.Iterator;
025import java.util.Map;
026import java.util.Set;
027import java.util.TreeMap;
028
029import org.apache.hadoop.classification.InterfaceAudience;
030import org.apache.hadoop.hdfs.protocol.Block;
031import org.apache.hadoop.hdfs.server.namenode.NameNode;
032import org.apache.hadoop.ipc.Server;
033
034import com.google.common.annotations.VisibleForTesting;
035
036/**
037 * Stores information about all corrupt blocks in the File System.
038 * A Block is considered corrupt only if all of its replicas are
039 * corrupt. While reporting replicas of a Block, we hide any corrupt
040 * copies. These copies are removed once Block is found to have 
041 * expected number of good replicas.
042 * Mapping: Block -> TreeSet<DatanodeDescriptor> 
043 */
044
045@InterfaceAudience.Private
046public class CorruptReplicasMap{
047
048  /** The corruption reason code */
049  public static enum Reason {
050    NONE,                // not specified.
051    ANY,                 // wildcard reason
052    GENSTAMP_MISMATCH,   // mismatch in generation stamps
053    SIZE_MISMATCH,       // mismatch in sizes
054    INVALID_STATE,       // invalid state
055    CORRUPTION_REPORTED  // client or datanode reported the corruption
056  }
057
058  private final Map<Block, Map<DatanodeDescriptor, Reason>> corruptReplicasMap =
059    new HashMap<Block, Map<DatanodeDescriptor, Reason>>();
060
061  /**
062   * Mark the block belonging to datanode as corrupt.
063   *
064   * @param blk Block to be added to CorruptReplicasMap
065   * @param dn DatanodeDescriptor which holds the corrupt replica
066   * @param reason a textual reason (for logging purposes)
067   * @param reasonCode the enum representation of the reason
068   */
069  void addToCorruptReplicasMap(Block blk, DatanodeDescriptor dn,
070      String reason, Reason reasonCode) {
071    Map <DatanodeDescriptor, Reason> nodes = corruptReplicasMap.get(blk);
072    if (nodes == null) {
073      nodes = new HashMap<DatanodeDescriptor, Reason>();
074      corruptReplicasMap.put(blk, nodes);
075    }
076    
077    String reasonText;
078    if (reason != null) {
079      reasonText = " because " + reason;
080    } else {
081      reasonText = "";
082    }
083    
084    if (!nodes.keySet().contains(dn)) {
085      NameNode.blockStateChangeLog.debug(
086          "BLOCK NameSystem.addToCorruptReplicasMap: {} added as corrupt on "
087              + "{} by {} {}", blk, dn, Server.getRemoteIp(),
088          reasonText);
089    } else {
090      NameNode.blockStateChangeLog.debug(
091          "BLOCK NameSystem.addToCorruptReplicasMap: duplicate requested for" +
092              " {} to add as corrupt on {} by {} {}", blk, dn,
093              Server.getRemoteIp(), reasonText);
094    }
095    // Add the node or update the reason.
096    nodes.put(dn, reasonCode);
097  }
098
099  /**
100   * Remove Block from CorruptBlocksMap
101   *
102   * @param blk Block to be removed
103   */
104  void removeFromCorruptReplicasMap(Block blk) {
105    if (corruptReplicasMap != null) {
106      corruptReplicasMap.remove(blk);
107    }
108  }
109
110  /**
111   * Remove the block at the given datanode from CorruptBlockMap
112   * @param blk block to be removed
113   * @param datanode datanode where the block is located
114   * @return true if the removal is successful; 
115             false if the replica is not in the map
116   */ 
117  boolean removeFromCorruptReplicasMap(Block blk, DatanodeDescriptor datanode) {
118    return removeFromCorruptReplicasMap(blk, datanode, Reason.ANY);
119  }
120
121  boolean removeFromCorruptReplicasMap(Block blk, DatanodeDescriptor datanode,
122      Reason reason) {
123    Map <DatanodeDescriptor, Reason> datanodes = corruptReplicasMap.get(blk);
124    if (datanodes==null)
125      return false;
126
127    // if reasons can be compared but don't match, return false.
128    Reason storedReason = datanodes.get(datanode);
129    if (reason != Reason.ANY && storedReason != null &&
130        reason != storedReason) {
131      return false;
132    }
133
134    if (datanodes.remove(datanode) != null) { // remove the replicas
135      if (datanodes.isEmpty()) {
136        // remove the block if there is no more corrupted replicas
137        corruptReplicasMap.remove(blk);
138      }
139      return true;
140    }
141    return false;
142  }
143    
144
145  /**
146   * Get Nodes which have corrupt replicas of Block
147   * 
148   * @param blk Block for which nodes are requested
149   * @return collection of nodes. Null if does not exists
150   */
151  Collection<DatanodeDescriptor> getNodes(Block blk) {
152    Map <DatanodeDescriptor, Reason> nodes = corruptReplicasMap.get(blk);
153    if (nodes == null)
154      return null;
155    return nodes.keySet();
156  }
157
158  /**
159   * Check if replica belonging to Datanode is corrupt
160   *
161   * @param blk Block to check
162   * @param node DatanodeDescriptor which holds the replica
163   * @return true if replica is corrupt, false if does not exists in this map
164   */
165  boolean isReplicaCorrupt(Block blk, DatanodeDescriptor node) {
166    Collection<DatanodeDescriptor> nodes = getNodes(blk);
167    return ((nodes != null) && (nodes.contains(node)));
168  }
169
170  int numCorruptReplicas(Block blk) {
171    Collection<DatanodeDescriptor> nodes = getNodes(blk);
172    return (nodes == null) ? 0 : nodes.size();
173  }
174  
175  int size() {
176    return corruptReplicasMap.size();
177  }
178
179  /**
180   * Return a range of corrupt replica block ids. Up to numExpectedBlocks 
181   * blocks starting at the next block after startingBlockId are returned
182   * (fewer if numExpectedBlocks blocks are unavailable). If startingBlockId 
183   * is null, up to numExpectedBlocks blocks are returned from the beginning.
184   * If startingBlockId cannot be found, null is returned.
185   *
186   * @param numExpectedBlocks Number of block ids to return.
187   *  0 <= numExpectedBlocks <= 100
188   * @param startingBlockId Block id from which to start. If null, start at
189   *  beginning.
190   * @return Up to numExpectedBlocks blocks from startingBlockId if it exists
191   *
192   */
193  @VisibleForTesting
194  long[] getCorruptReplicaBlockIdsForTesting(int numExpectedBlocks,
195                                   Long startingBlockId) {
196    if (numExpectedBlocks < 0 || numExpectedBlocks > 100) {
197      return null;
198    }
199    
200    Iterator<Block> blockIt = 
201        new TreeMap<>(corruptReplicasMap).keySet().iterator();
202    
203    // if the starting block id was specified, iterate over keys until
204    // we find the matching block. If we find a matching block, break
205    // to leave the iterator on the next block after the specified block. 
206    if (startingBlockId != null) {
207      boolean isBlockFound = false;
208      while (blockIt.hasNext()) {
209        Block b = blockIt.next();
210        if (b.getBlockId() == startingBlockId) {
211          isBlockFound = true;
212          break; 
213        }
214      }
215      
216      if (!isBlockFound) {
217        return null;
218      }
219    }
220
221    ArrayList<Long> corruptReplicaBlockIds = new ArrayList<Long>();
222
223    // append up to numExpectedBlocks blockIds to our list
224    for(int i=0; i<numExpectedBlocks && blockIt.hasNext(); i++) {
225      corruptReplicaBlockIds.add(blockIt.next().getBlockId());
226    }
227    
228    long[] ret = new long[corruptReplicaBlockIds.size()];
229    for(int i=0; i<ret.length; i++) {
230      ret[i] = corruptReplicaBlockIds.get(i);
231    }
232    
233    return ret;
234  }
235
236  /**
237   * method to get the set of corrupt blocks in corruptReplicasMap.
238   * @return Set of Block objects
239   */
240  Set<Block> getCorruptBlocks() {
241    Set<Block> corruptBlocks = new HashSet<Block>();
242    corruptBlocks.addAll(corruptReplicasMap.keySet());
243    return corruptBlocks;
244  }
245
246  /**
247   * return the reason about corrupted replica for a given block
248   * on a given dn
249   * @param block block that has corrupted replica
250   * @param node datanode that contains this corrupted replica
251   * @return reason
252   */
253  String getCorruptReason(Block block, DatanodeDescriptor node) {
254    Reason reason = null;
255    if(corruptReplicasMap.containsKey(block)) {
256      if (corruptReplicasMap.get(block).containsKey(node)) {
257        reason = corruptReplicasMap.get(block).get(node);
258      }
259    }
260    if (reason != null) {
261      return reason.toString();
262    } else {
263      return null;
264    }
265  }
266}