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     */
018    package org.apache.hadoop.hdfs.server.namenode;
019    
020    import java.io.File;
021    import java.io.IOException;
022    import java.net.URI;
023    import java.util.ArrayList;
024    import java.util.Collection;
025    import java.util.HashMap;
026    import java.util.Map;
027    
028    import org.apache.commons.logging.Log;
029    import org.apache.commons.logging.LogFactory;
030    import org.apache.hadoop.conf.Configuration;
031    import org.apache.hadoop.fs.DF;
032    import org.apache.hadoop.hdfs.DFSConfigKeys;
033    import org.apache.hadoop.hdfs.server.common.Util;
034    
035    import com.google.common.annotations.VisibleForTesting;
036    
037    /**
038     * 
039     * NameNodeResourceChecker provides a method -
040     * <code>hasAvailableDiskSpace</code> - which will return true if and only if
041     * the NameNode has disk space available on all volumes which are configured to
042     * be checked. Volumes containing file system name/edits dirs are added by
043     * default, and arbitrary extra volumes may be configured as well.
044     */
045    public class NameNodeResourceChecker {
046      private static final Log LOG = LogFactory.getLog(NameNodeResourceChecker.class.getName());
047    
048      // Space (in bytes) reserved per volume.
049      private long duReserved;
050    
051      private final Configuration conf;
052      private Map<String, DF> volumes;
053    
054      /**
055       * Create a NameNodeResourceChecker, which will check the name dirs and edits
056       * dirs set in <code>conf</code>.
057       * 
058       * @param conf
059       * @throws IOException
060       */
061      public NameNodeResourceChecker(Configuration conf) throws IOException {
062        this.conf = conf;
063        volumes = new HashMap<String, DF>();
064    
065        duReserved = conf.getLong(DFSConfigKeys.DFS_NAMENODE_DU_RESERVED_KEY,
066            DFSConfigKeys.DFS_NAMENODE_DU_RESERVED_DEFAULT);
067    
068        Collection<URI> extraCheckedVolumes = Util.stringCollectionAsURIs(conf
069            .getTrimmedStringCollection(DFSConfigKeys.DFS_NAMENODE_CHECKED_VOLUMES_KEY));
070    
071        addDirsToCheck(FSNamesystem.getNamespaceDirs(conf));
072        addDirsToCheck(FSNamesystem.getNamespaceEditsDirs(conf));
073        addDirsToCheck(extraCheckedVolumes);
074      }
075    
076      /**
077       * Add the passed-in directories to the list of volumes to check.
078       * 
079       * @param directoriesToCheck
080       *          The directories whose volumes will be checked for available space.
081       * @throws IOException
082       */
083      private void addDirsToCheck(Collection<URI> directoriesToCheck)
084          throws IOException {
085        for (URI directoryUri : directoriesToCheck) {
086          File dir = new File(directoryUri.getPath());
087          if (!dir.exists()) {
088            throw new IOException("Missing directory "+dir.getAbsolutePath());
089          }
090          DF df = new DF(dir, conf);
091          volumes.put(df.getFilesystem(), df);
092        }
093      }
094    
095      /**
096       * Return true if disk space is available on at least one of the configured
097       * volumes.
098       * 
099       * @return True if the configured amount of disk space is available on at
100       *         least one volume, false otherwise.
101       * @throws IOException
102       */
103      boolean hasAvailableDiskSpace()
104          throws IOException {
105        return getVolumesLowOnSpace().size() < volumes.size();
106      }
107    
108      /**
109       * Return the set of directories which are low on space.
110       * @return the set of directories whose free space is below the threshold.
111       * @throws IOException 
112       */
113      Collection<String> getVolumesLowOnSpace() throws IOException {
114        if (LOG.isDebugEnabled()) {
115          LOG.debug("Going to check the following volumes disk space: " + volumes);
116        }
117        Collection<String> lowVolumes = new ArrayList<String>();
118        for (DF volume : volumes.values()) {
119          long availableSpace = volume.getAvailable();
120          String fileSystem = volume.getFilesystem();
121          if (LOG.isDebugEnabled()) {
122            LOG.debug("Space available on volume '" + fileSystem + "' is " + availableSpace);
123          }
124          if (availableSpace < duReserved) {
125            LOG.warn("Space available on volume '" + fileSystem + "' is "
126                + availableSpace +
127                ", which is below the configured reserved amount " + duReserved);
128            lowVolumes.add(volume.getFilesystem());
129          }
130        }
131        return lowVolumes;
132      }
133      
134      @VisibleForTesting
135      void setVolumes(Map<String, DF> volumes) {
136        this.volumes = volumes;
137      }
138    }