001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.master;
020
021import java.io.IOException;
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Set;
027import java.util.concurrent.locks.Lock;
028import java.util.concurrent.locks.ReentrantLock;
029import java.util.stream.Collectors;
030import java.util.stream.Stream;
031import org.apache.hadoop.conf.Configuration;
032import org.apache.hadoop.fs.FileStatus;
033import org.apache.hadoop.fs.FileSystem;
034import org.apache.hadoop.fs.Path;
035import org.apache.hadoop.fs.PathFilter;
036import org.apache.hadoop.hbase.HConstants;
037import org.apache.hadoop.hbase.ServerName;
038import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
039import org.apache.hadoop.hbase.util.FSUtils;
040import org.apache.hadoop.hbase.wal.AbstractFSWALProvider;
041import org.apache.hadoop.hbase.wal.WALSplitter;
042import org.apache.yetus.audience.InterfaceAudience;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
046
047/**
048 * This class abstracts a bunch of operations the HMaster needs
049 * when splitting log files e.g. finding log files, dirs etc.
050 */
051@InterfaceAudience.Private
052public class MasterWalManager {
053  private static final Logger LOG = LoggerFactory.getLogger(MasterWalManager.class);
054
055  final static PathFilter META_FILTER = new PathFilter() {
056    @Override
057    public boolean accept(Path p) {
058      return AbstractFSWALProvider.isMetaFile(p);
059    }
060  };
061
062  final static PathFilter NON_META_FILTER = new PathFilter() {
063    @Override
064    public boolean accept(Path p) {
065      return !AbstractFSWALProvider.isMetaFile(p);
066    }
067  };
068
069  // metrics for master
070  // TODO: Rename it, since those metrics are split-manager related
071  private final MetricsMasterFileSystem metricsMasterFilesystem = new MetricsMasterFileSystem();
072
073  // Keep around for convenience.
074  private final MasterServices services;
075  private final Configuration conf;
076  private final FileSystem fs;
077
078  // The Path to the old logs dir
079  private final Path oldLogDir;
080  private final Path rootDir;
081
082  // create the split log lock
083  private final Lock splitLogLock = new ReentrantLock();
084  private final SplitLogManager splitLogManager;
085
086  // Is the fileystem ok?
087  private volatile boolean fsOk = true;
088
089  public MasterWalManager(MasterServices services) throws IOException {
090    this(services.getConfiguration(), services.getMasterFileSystem().getWALFileSystem(),
091      services.getMasterFileSystem().getWALRootDir(), services);
092  }
093
094  public MasterWalManager(Configuration conf, FileSystem fs, Path rootDir, MasterServices services)
095      throws IOException {
096    this.fs = fs;
097    this.conf = conf;
098    this.rootDir = rootDir;
099    this.services = services;
100    this.splitLogManager = new SplitLogManager(services, conf);
101
102    this.oldLogDir = new Path(rootDir, HConstants.HREGION_OLDLOGDIR_NAME);
103  }
104
105  public void stop() {
106    if (splitLogManager != null) {
107      splitLogManager.stop();
108    }
109  }
110
111  @VisibleForTesting
112  SplitLogManager getSplitLogManager() {
113    return this.splitLogManager;
114  }
115
116  /**
117   * Get the directory where old logs go
118   * @return the dir
119   */
120  Path getOldLogDir() {
121    return this.oldLogDir;
122  }
123
124  public FileSystem getFileSystem() {
125    return this.fs;
126  }
127
128  /**
129   * Checks to see if the file system is still accessible.
130   * If not, sets closed
131   * @return false if file system is not available
132   */
133  private boolean checkFileSystem() {
134    if (this.fsOk) {
135      try {
136        FSUtils.checkFileSystemAvailable(this.fs);
137        FSUtils.checkDfsSafeMode(this.conf);
138      } catch (IOException e) {
139        services.abort("Shutting down HBase cluster: file system not available", e);
140        this.fsOk = false;
141      }
142    }
143    return this.fsOk;
144  }
145
146  public Set<ServerName> getLiveServersFromWALDir() throws IOException {
147    Path walDirPath = new Path(rootDir, HConstants.HREGION_LOGDIR_NAME);
148    FileStatus[] walDirForLiveServers = FSUtils.listStatus(fs, walDirPath,
149      p -> !p.getName().endsWith(AbstractFSWALProvider.SPLITTING_EXT));
150    if (walDirForLiveServers == null) {
151      return Collections.emptySet();
152    }
153    return Stream.of(walDirForLiveServers).map(s -> {
154      ServerName serverName = AbstractFSWALProvider.getServerNameFromWALDirectoryName(s.getPath());
155      if (serverName == null) {
156        LOG.warn("Log folder {} doesn't look like its name includes a " +
157          "region server name; leaving in place. If you see later errors about missing " +
158          "write ahead logs they may be saved in this location.", s.getPath());
159        return null;
160      }
161      return serverName;
162    }).filter(s -> s != null).collect(Collectors.toSet());
163  }
164
165  /**
166   * Inspect the log directory to find dead servers which need recovery work
167   * @return A set of ServerNames which aren't running but still have WAL files left in file system
168   * @deprecated With proc-v2, we can record the crash server with procedure store, so do not need
169   *             to scan the wal directory to find out the splitting wal directory any more. Leave
170   *             it here only because {@code RecoverMetaProcedure}(which is also deprecated) uses
171   *             it.
172   */
173  @Deprecated
174  public Set<ServerName> getFailedServersFromLogFolders() {
175    boolean retrySplitting = !conf.getBoolean("hbase.hlog.split.skip.errors",
176        WALSplitter.SPLIT_SKIP_ERRORS_DEFAULT);
177
178    Set<ServerName> serverNames = new HashSet<>();
179    Path logsDirPath = new Path(this.rootDir, HConstants.HREGION_LOGDIR_NAME);
180
181    do {
182      if (services.isStopped()) {
183        LOG.warn("Master stopped while trying to get failed servers.");
184        break;
185      }
186      try {
187        if (!this.fs.exists(logsDirPath)) return serverNames;
188        FileStatus[] logFolders = FSUtils.listStatus(this.fs, logsDirPath, null);
189        // Get online servers after getting log folders to avoid log folder deletion of newly
190        // checked in region servers . see HBASE-5916
191        Set<ServerName> onlineServers = services.getServerManager().getOnlineServers().keySet();
192
193        if (logFolders == null || logFolders.length == 0) {
194          LOG.debug("No log files to split, proceeding...");
195          return serverNames;
196        }
197        for (FileStatus status : logFolders) {
198          FileStatus[] curLogFiles = FSUtils.listStatus(this.fs, status.getPath(), null);
199          if (curLogFiles == null || curLogFiles.length == 0) {
200            // Empty log folder. No recovery needed
201            continue;
202          }
203          final ServerName serverName = AbstractFSWALProvider.getServerNameFromWALDirectoryName(
204              status.getPath());
205          if (null == serverName) {
206            LOG.warn("Log folder " + status.getPath() + " doesn't look like its name includes a " +
207                "region server name; leaving in place. If you see later errors about missing " +
208                "write ahead logs they may be saved in this location.");
209          } else if (!onlineServers.contains(serverName)) {
210            LOG.info("Log folder " + status.getPath() + " doesn't belong "
211                + "to a known region server, splitting");
212            serverNames.add(serverName);
213          } else {
214            LOG.info("Log folder " + status.getPath() + " belongs to an existing region server");
215          }
216        }
217        retrySplitting = false;
218      } catch (IOException ioe) {
219        LOG.warn("Failed getting failed servers to be recovered.", ioe);
220        if (!checkFileSystem()) {
221          LOG.warn("Bad Filesystem, exiting");
222          Runtime.getRuntime().halt(1);
223        }
224        try {
225          if (retrySplitting) {
226            Thread.sleep(conf.getInt("hbase.hlog.split.failure.retry.interval", 30 * 1000));
227          }
228        } catch (InterruptedException e) {
229          LOG.warn("Interrupted, aborting since cannot return w/o splitting");
230          Thread.currentThread().interrupt();
231          retrySplitting = false;
232          Runtime.getRuntime().halt(1);
233        }
234      }
235    } while (retrySplitting);
236
237    return serverNames;
238  }
239
240  public void splitLog(final ServerName serverName) throws IOException {
241    splitLog(Collections.<ServerName>singleton(serverName));
242  }
243
244  /**
245   * Specialized method to handle the splitting for meta WAL
246   * @param serverName logs belonging to this server will be split
247   */
248  public void splitMetaLog(final ServerName serverName) throws IOException {
249    splitMetaLog(Collections.<ServerName>singleton(serverName));
250  }
251
252  /**
253   * Specialized method to handle the splitting for meta WAL
254   * @param serverNames logs belonging to these servers will be split
255   */
256  public void splitMetaLog(final Set<ServerName> serverNames) throws IOException {
257    splitLog(serverNames, META_FILTER);
258  }
259
260  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UL_UNRELEASED_LOCK", justification=
261      "We only release this lock when we set it. Updates to code that uses it should verify use " +
262      "of the guard boolean.")
263  private List<Path> getLogDirs(final Set<ServerName> serverNames) throws IOException {
264    List<Path> logDirs = new ArrayList<>();
265    boolean needReleaseLock = false;
266    if (!this.services.isInitialized()) {
267      // during master initialization, we could have multiple places splitting a same wal
268      // XXX: Does this still exist after we move to proc-v2?
269      this.splitLogLock.lock();
270      needReleaseLock = true;
271    }
272    try {
273      for (ServerName serverName : serverNames) {
274        Path logDir = new Path(this.rootDir,
275          AbstractFSWALProvider.getWALDirectoryName(serverName.toString()));
276        Path splitDir = logDir.suffix(AbstractFSWALProvider.SPLITTING_EXT);
277        // Rename the directory so a rogue RS doesn't create more WALs
278        if (fs.exists(logDir)) {
279          if (!this.fs.rename(logDir, splitDir)) {
280            throw new IOException("Failed fs.rename for log split: " + logDir);
281          }
282          logDir = splitDir;
283          LOG.debug("Renamed region directory: " + splitDir);
284        } else if (!fs.exists(splitDir)) {
285          LOG.info("Log dir for server " + serverName + " does not exist");
286          continue;
287        }
288        logDirs.add(splitDir);
289      }
290    } catch (IOException ioe) {
291      if (!checkFileSystem()) {
292        this.services.abort("Aborting due to filesystem unavailable", ioe);
293        throw ioe;
294      }
295    } finally {
296      if (needReleaseLock) {
297        this.splitLogLock.unlock();
298      }
299    }
300    return logDirs;
301  }
302
303  public void splitLog(final Set<ServerName> serverNames) throws IOException {
304    splitLog(serverNames, NON_META_FILTER);
305  }
306
307  /**
308   * This method is the base split method that splits WAL files matching a filter. Callers should
309   * pass the appropriate filter for meta and non-meta WALs.
310   * @param serverNames logs belonging to these servers will be split; this will rename the log
311   *                    directory out from under a soft-failed server
312   */
313  public void splitLog(final Set<ServerName> serverNames, PathFilter filter) throws IOException {
314    long splitTime = 0, splitLogSize = 0;
315    List<Path> logDirs = getLogDirs(serverNames);
316
317    splitLogManager.handleDeadWorkers(serverNames);
318    splitTime = EnvironmentEdgeManager.currentTime();
319    splitLogSize = splitLogManager.splitLogDistributed(serverNames, logDirs, filter);
320    splitTime = EnvironmentEdgeManager.currentTime() - splitTime;
321
322    if (this.metricsMasterFilesystem != null) {
323      if (filter == META_FILTER) {
324        this.metricsMasterFilesystem.addMetaWALSplit(splitTime, splitLogSize);
325      } else {
326        this.metricsMasterFilesystem.addSplit(splitTime, splitLogSize);
327      }
328    }
329  }
330}