1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 package org.apache.hadoop.hdfs.server.namenode;
19 
20 import java.io.File;
21 import java.io.IOException;
22 import java.net.URI;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.Map;
27 
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.apache.hadoop.classification.InterfaceAudience;
31 import org.apache.hadoop.conf.Configuration;
32 import org.apache.hadoop.fs.DF;
33 import org.apache.hadoop.hdfs.DFSConfigKeys;
34 import org.apache.hadoop.hdfs.server.common.Util;
35 
36 import com.google.common.annotations.VisibleForTesting;
37 import com.google.common.collect.Collections2;
38 import com.google.common.base.Predicate;
39 
40 /**
41  *
42  * NameNodeResourceChecker provides a method -
43  * <code>hasAvailableDiskSpace</code> - which will return true if and only if
44  * the NameNode has disk space available on all required volumes, and any volume
45  * which is configured to be redundant. Volumes containing file system edits dirs
46  * are added by default, and arbitrary extra volumes may be configured as well.
47  */
48 @InterfaceAudience.Private
49 public class NameNodeResourceChecker {
50   private static final Log LOG = LogFactory.getLog(NameNodeResourceChecker.class.getName());
51 
52   // Space (in bytes) reserved per volume.
53   private final long duReserved;
54 
55   private final Configuration conf;
56   private Map<String, CheckedVolume> volumes;
57   private int minimumRedundantVolumes;
58 
59   @VisibleForTesting
60   class CheckedVolume implements CheckableNameNodeResource {
61     private DF df;
62     private boolean required;
63     private String volume;
64 
CheckedVolume(File dirToCheck, boolean required)65     public CheckedVolume(File dirToCheck, boolean required)
66         throws IOException {
67       df = new DF(dirToCheck, conf);
68       this.required = required;
69       volume = df.getFilesystem();
70     }
71 
getVolume()72     public String getVolume() {
73       return volume;
74     }
75 
76     @Override
isRequired()77     public boolean isRequired() {
78       return required;
79     }
80 
81     @Override
isResourceAvailable()82     public boolean isResourceAvailable() {
83       long availableSpace = df.getAvailable();
84       if (LOG.isDebugEnabled()) {
85         LOG.debug("Space available on volume '" + volume + "' is "
86             + availableSpace);
87       }
88       if (availableSpace < duReserved) {
89         LOG.warn("Space available on volume '" + volume + "' is "
90             + availableSpace +
91             ", which is below the configured reserved amount " + duReserved);
92         return false;
93       } else {
94         return true;
95       }
96     }
97 
98     @Override
toString()99     public String toString() {
100       return "volume: " + volume + " required: " + required +
101           " resource available: " + isResourceAvailable();
102     }
103   }
104 
105   /**
106    * Create a NameNodeResourceChecker, which will check the edits dirs and any
107    * additional dirs to check set in <code>conf</code>.
108    */
NameNodeResourceChecker(Configuration conf)109   public NameNodeResourceChecker(Configuration conf) throws IOException {
110     this.conf = conf;
111     volumes = new HashMap<String, CheckedVolume>();
112 
113     duReserved = conf.getLong(DFSConfigKeys.DFS_NAMENODE_DU_RESERVED_KEY,
114         DFSConfigKeys.DFS_NAMENODE_DU_RESERVED_DEFAULT);
115 
116     Collection<URI> extraCheckedVolumes = Util.stringCollectionAsURIs(conf
117         .getTrimmedStringCollection(DFSConfigKeys.DFS_NAMENODE_CHECKED_VOLUMES_KEY));
118 
119     Collection<URI> localEditDirs = Collections2.filter(
120         FSNamesystem.getNamespaceEditsDirs(conf),
121         new Predicate<URI>() {
122           @Override
123           public boolean apply(URI input) {
124             if (input.getScheme().equals(NNStorage.LOCAL_URI_SCHEME)) {
125               return true;
126             }
127             return false;
128           }
129         });
130 
131     // Add all the local edits dirs, marking some as required if they are
132     // configured as such.
133     for (URI editsDirToCheck : localEditDirs) {
134       addDirToCheck(editsDirToCheck,
135           FSNamesystem.getRequiredNamespaceEditsDirs(conf).contains(
136               editsDirToCheck));
137     }
138 
139     // All extra checked volumes are marked "required"
140     for (URI extraDirToCheck : extraCheckedVolumes) {
141       addDirToCheck(extraDirToCheck, true);
142     }
143 
144     minimumRedundantVolumes = conf.getInt(
145         DFSConfigKeys.DFS_NAMENODE_CHECKED_VOLUMES_MINIMUM_KEY,
146         DFSConfigKeys.DFS_NAMENODE_CHECKED_VOLUMES_MINIMUM_DEFAULT);
147   }
148 
149   /**
150    * Add the volume of the passed-in directory to the list of volumes to check.
151    * If <code>required</code> is true, and this volume is already present, but
152    * is marked redundant, it will be marked required. If the volume is already
153    * present but marked required then this method is a no-op.
154    *
155    * @param directoryToCheck
156    *          The directory whose volume will be checked for available space.
157    */
addDirToCheck(URI directoryToCheck, boolean required)158   private void addDirToCheck(URI directoryToCheck, boolean required)
159       throws IOException {
160     File dir = new File(directoryToCheck.getPath());
161     if (!dir.exists()) {
162       throw new IOException("Missing directory "+dir.getAbsolutePath());
163     }
164 
165     CheckedVolume newVolume = new CheckedVolume(dir, required);
166     CheckedVolume volume = volumes.get(newVolume.getVolume());
167     if (volume == null || !volume.isRequired()) {
168       volumes.put(newVolume.getVolume(), newVolume);
169     }
170   }
171 
172   /**
173    * Return true if disk space is available on at least one of the configured
174    * redundant volumes, and all of the configured required volumes.
175    *
176    * @return True if the configured amount of disk space is available on at
177    *         least one redundant volume and all of the required volumes, false
178    *         otherwise.
179    */
hasAvailableDiskSpace()180   public boolean hasAvailableDiskSpace() {
181     return NameNodeResourcePolicy.areResourcesAvailable(volumes.values(),
182         minimumRedundantVolumes);
183   }
184 
185   /**
186    * Return the set of directories which are low on space.
187    *
188    * @return the set of directories whose free space is below the threshold.
189    */
190   @VisibleForTesting
getVolumesLowOnSpace()191   Collection<String> getVolumesLowOnSpace() throws IOException {
192     if (LOG.isDebugEnabled()) {
193       LOG.debug("Going to check the following volumes disk space: " + volumes);
194     }
195     Collection<String> lowVolumes = new ArrayList<String>();
196     for (CheckedVolume volume : volumes.values()) {
197       lowVolumes.add(volume.getVolume());
198     }
199     return lowVolumes;
200   }
201 
202   @VisibleForTesting
setVolumes(Map<String, CheckedVolume> volumes)203   void setVolumes(Map<String, CheckedVolume> volumes) {
204     this.volumes = volumes;
205   }
206 
207   @VisibleForTesting
setMinimumReduntdantVolumes(int minimumRedundantVolumes)208   void setMinimumReduntdantVolumes(int minimumRedundantVolumes) {
209     this.minimumRedundantVolumes = minimumRedundantVolumes;
210   }
211 }
212