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 org.apache.hadoop.classification.InterfaceAudience;
021import org.apache.hadoop.hdfs.protocol.Block;
022import org.apache.hadoop.hdfs.server.namenode.NameNode;
023import org.apache.hadoop.ipc.Server;
024
025import java.util.*;
026
027/**
028 * Stores information about all corrupt blocks in the File System.
029 * A Block is considered corrupt only if all of its replicas are
030 * corrupt. While reporting replicas of a Block, we hide any corrupt
031 * copies. These copies are removed once Block is found to have 
032 * expected number of good replicas.
033 * Mapping: Block -> TreeSet<DatanodeDescriptor> 
034 */
035
036@InterfaceAudience.Private
037public class CorruptReplicasMap{
038
039  /** The corruption reason code */
040  public static enum Reason {
041    NONE,                // not specified.
042    ANY,                 // wildcard reason
043    GENSTAMP_MISMATCH,   // mismatch in generation stamps
044    SIZE_MISMATCH,       // mismatch in sizes
045    INVALID_STATE,       // invalid state
046    CORRUPTION_REPORTED  // client or datanode reported the corruption
047  }
048
049  private final SortedMap<Block, Map<DatanodeDescriptor, Reason>> corruptReplicasMap =
050    new TreeMap<Block, Map<DatanodeDescriptor, Reason>>();
051
052  /**
053   * Mark the block belonging to datanode as corrupt.
054   *
055   * @param blk Block to be added to CorruptReplicasMap
056   * @param dn DatanodeDescriptor which holds the corrupt replica
057   * @param reason a textual reason (for logging purposes)
058   * @param reasonCode the enum representation of the reason
059   */
060  void addToCorruptReplicasMap(Block blk, DatanodeDescriptor dn,
061      String reason, Reason reasonCode) {
062    Map <DatanodeDescriptor, Reason> nodes = corruptReplicasMap.get(blk);
063    if (nodes == null) {
064      nodes = new HashMap<DatanodeDescriptor, Reason>();
065      corruptReplicasMap.put(blk, nodes);
066    }
067    
068    String reasonText;
069    if (reason != null) {
070      reasonText = " because " + reason;
071    } else {
072      reasonText = "";
073    }
074    
075    if (!nodes.keySet().contains(dn)) {
076      NameNode.blockStateChangeLog.info(
077          "BLOCK NameSystem.addToCorruptReplicasMap: {} added as corrupt on "
078              + "{} by {} {}", blk.getBlockName(), dn, Server.getRemoteIp(),
079          reasonText);
080    } else {
081      NameNode.blockStateChangeLog.info(
082          "BLOCK NameSystem.addToCorruptReplicasMap: duplicate requested for" +
083              " {} to add as corrupt on {} by {} {}", blk.getBlockName(), dn,
084              Server.getRemoteIp(), reasonText);
085    }
086    // Add the node or update the reason.
087    nodes.put(dn, reasonCode);
088  }
089
090  /**
091   * Remove Block from CorruptBlocksMap
092   *
093   * @param blk Block to be removed
094   */
095  void removeFromCorruptReplicasMap(Block blk) {
096    if (corruptReplicasMap != null) {
097      corruptReplicasMap.remove(blk);
098    }
099  }
100
101  /**
102   * Remove the block at the given datanode from CorruptBlockMap
103   * @param blk block to be removed
104   * @param datanode datanode where the block is located
105   * @return true if the removal is successful; 
106             false if the replica is not in the map
107   */ 
108  boolean removeFromCorruptReplicasMap(Block blk, DatanodeDescriptor datanode) {
109    return removeFromCorruptReplicasMap(blk, datanode, Reason.ANY);
110  }
111
112  boolean removeFromCorruptReplicasMap(Block blk, DatanodeDescriptor datanode,
113      Reason reason) {
114    Map <DatanodeDescriptor, Reason> datanodes = corruptReplicasMap.get(blk);
115    if (datanodes==null)
116      return false;
117
118    // if reasons can be compared but don't match, return false.
119    Reason storedReason = datanodes.get(datanode);
120    if (reason != Reason.ANY && storedReason != null &&
121        reason != storedReason) {
122      return false;
123    }
124
125    if (datanodes.remove(datanode) != null) { // remove the replicas
126      if (datanodes.isEmpty()) {
127        // remove the block if there is no more corrupted replicas
128        corruptReplicasMap.remove(blk);
129      }
130      return true;
131    }
132    return false;
133  }
134    
135
136  /**
137   * Get Nodes which have corrupt replicas of Block
138   * 
139   * @param blk Block for which nodes are requested
140   * @return collection of nodes. Null if does not exists
141   */
142  Collection<DatanodeDescriptor> getNodes(Block blk) {
143    Map <DatanodeDescriptor, Reason> nodes = corruptReplicasMap.get(blk);
144    if (nodes == null)
145      return null;
146    return nodes.keySet();
147  }
148
149  /**
150   * Check if replica belonging to Datanode is corrupt
151   *
152   * @param blk Block to check
153   * @param node DatanodeDescriptor which holds the replica
154   * @return true if replica is corrupt, false if does not exists in this map
155   */
156  boolean isReplicaCorrupt(Block blk, DatanodeDescriptor node) {
157    Collection<DatanodeDescriptor> nodes = getNodes(blk);
158    return ((nodes != null) && (nodes.contains(node)));
159  }
160
161  int numCorruptReplicas(Block blk) {
162    Collection<DatanodeDescriptor> nodes = getNodes(blk);
163    return (nodes == null) ? 0 : nodes.size();
164  }
165  
166  int size() {
167    return corruptReplicasMap.size();
168  }
169
170  /**
171   * Return a range of corrupt replica block ids. Up to numExpectedBlocks 
172   * blocks starting at the next block after startingBlockId are returned
173   * (fewer if numExpectedBlocks blocks are unavailable). If startingBlockId 
174   * is null, up to numExpectedBlocks blocks are returned from the beginning.
175   * If startingBlockId cannot be found, null is returned.
176   *
177   * @param numExpectedBlocks Number of block ids to return.
178   *  0 <= numExpectedBlocks <= 100
179   * @param startingBlockId Block id from which to start. If null, start at
180   *  beginning.
181   * @return Up to numExpectedBlocks blocks from startingBlockId if it exists
182   *
183   */
184  long[] getCorruptReplicaBlockIds(int numExpectedBlocks,
185                                   Long startingBlockId) {
186    if (numExpectedBlocks < 0 || numExpectedBlocks > 100) {
187      return null;
188    }
189    
190    Iterator<Block> blockIt = corruptReplicasMap.keySet().iterator();
191    
192    // if the starting block id was specified, iterate over keys until
193    // we find the matching block. If we find a matching block, break
194    // to leave the iterator on the next block after the specified block. 
195    if (startingBlockId != null) {
196      boolean isBlockFound = false;
197      while (blockIt.hasNext()) {
198        Block b = blockIt.next();
199        if (b.getBlockId() == startingBlockId) {
200          isBlockFound = true;
201          break; 
202        }
203      }
204      
205      if (!isBlockFound) {
206        return null;
207      }
208    }
209
210    ArrayList<Long> corruptReplicaBlockIds = new ArrayList<Long>();
211
212    // append up to numExpectedBlocks blockIds to our list
213    for(int i=0; i<numExpectedBlocks && blockIt.hasNext(); i++) {
214      corruptReplicaBlockIds.add(blockIt.next().getBlockId());
215    }
216    
217    long[] ret = new long[corruptReplicaBlockIds.size()];
218    for(int i=0; i<ret.length; i++) {
219      ret[i] = corruptReplicaBlockIds.get(i);
220    }
221    
222    return ret;
223  }
224
225  /**
226   * return the reason about corrupted replica for a given block
227   * on a given dn
228   * @param block block that has corrupted replica
229   * @param node datanode that contains this corrupted replica
230   * @return reason
231   */
232  String getCorruptReason(Block block, DatanodeDescriptor node) {
233    Reason reason = null;
234    if(corruptReplicasMap.containsKey(block)) {
235      if (corruptReplicasMap.get(block).containsKey(node)) {
236        reason = corruptReplicasMap.get(block).get(node);
237      }
238    }
239    if (reason != null) {
240      return reason.toString();
241    } else {
242      return null;
243    }
244  }
245}