View Javadoc

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.hbase.master.snapshot;
19  
20  import java.io.FileNotFoundException;
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.concurrent.ThreadPoolExecutor;
31  import java.util.concurrent.locks.ReadWriteLock;
32  import java.util.concurrent.locks.ReentrantReadWriteLock;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.hadoop.hbase.classification.InterfaceAudience;
37  import org.apache.hadoop.hbase.classification.InterfaceStability;
38  import org.apache.hadoop.conf.Configuration;
39  import org.apache.hadoop.fs.FSDataInputStream;
40  import org.apache.hadoop.fs.FileStatus;
41  import org.apache.hadoop.fs.FileSystem;
42  import org.apache.hadoop.fs.Path;
43  import org.apache.hadoop.hbase.TableName;
44  import org.apache.hadoop.hbase.HBaseInterfaceAudience;
45  import org.apache.hadoop.hbase.HConstants;
46  import org.apache.hadoop.hbase.HTableDescriptor;
47  import org.apache.hadoop.hbase.Stoppable;
48  import org.apache.hadoop.hbase.MetaTableAccessor;
49  import org.apache.hadoop.hbase.errorhandling.ForeignException;
50  import org.apache.hadoop.hbase.executor.ExecutorService;
51  import org.apache.hadoop.hbase.ipc.RpcServer;
52  import org.apache.hadoop.hbase.master.AssignmentManager;
53  import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
54  import org.apache.hadoop.hbase.master.MasterFileSystem;
55  import org.apache.hadoop.hbase.master.MasterServices;
56  import org.apache.hadoop.hbase.master.MetricsMaster;
57  import org.apache.hadoop.hbase.master.SnapshotSentinel;
58  import org.apache.hadoop.hbase.master.cleaner.HFileCleaner;
59  import org.apache.hadoop.hbase.master.cleaner.HFileLinkCleaner;
60  import org.apache.hadoop.hbase.procedure.MasterProcedureManager;
61  import org.apache.hadoop.hbase.procedure.Procedure;
62  import org.apache.hadoop.hbase.procedure.ProcedureCoordinator;
63  import org.apache.hadoop.hbase.procedure.ProcedureCoordinatorRpcs;
64  import org.apache.hadoop.hbase.procedure.ZKProcedureCoordinatorRpcs;
65  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.NameStringPair;
66  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.ProcedureDescription;
67  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
68  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription.Type;
69  import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
70  import org.apache.hadoop.hbase.quotas.QuotaExceededException;
71  import org.apache.hadoop.hbase.security.AccessDeniedException;
72  import org.apache.hadoop.hbase.security.User;
73  import org.apache.hadoop.hbase.snapshot.ClientSnapshotDescriptionUtils;
74  import org.apache.hadoop.hbase.snapshot.HBaseSnapshotException;
75  import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
76  import org.apache.hadoop.hbase.snapshot.SnapshotCreationException;
77  import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
78  import org.apache.hadoop.hbase.snapshot.SnapshotDoesNotExistException;
79  import org.apache.hadoop.hbase.snapshot.SnapshotExistsException;
80  import org.apache.hadoop.hbase.snapshot.SnapshotManifest;
81  import org.apache.hadoop.hbase.snapshot.SnapshotReferenceUtil;
82  import org.apache.hadoop.hbase.snapshot.TablePartiallyOpenException;
83  import org.apache.hadoop.hbase.snapshot.UnknownSnapshotException;
84  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
85  import org.apache.hadoop.hbase.util.FSUtils;
86  import org.apache.zookeeper.KeeperException;
87  
88  /**
89   * This class manages the procedure of taking and restoring snapshots. There is only one
90   * SnapshotManager for the master.
91   * <p>
92   * The class provides methods for monitoring in-progress snapshot actions.
93   * <p>
94   * Note: Currently there can only be one snapshot being taken at a time over the cluster. This is a
95   * simplification in the current implementation.
96   */
97  @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
98  @InterfaceStability.Unstable
99  public class SnapshotManager extends MasterProcedureManager implements Stoppable {
100   private static final Log LOG = LogFactory.getLog(SnapshotManager.class);
101 
102   /** By default, check to see if the snapshot is complete every WAKE MILLIS (ms) */
103   private static final int SNAPSHOT_WAKE_MILLIS_DEFAULT = 500;
104 
105   /**
106    * Wait time before removing a finished sentinel from the in-progress map
107    *
108    * NOTE: This is used as a safety auto cleanup.
109    * The snapshot and restore handlers map entries are removed when a user asks if a snapshot or
110    * restore is completed. This operation is part of the HBaseAdmin snapshot/restore API flow.
111    * In case something fails on the client side and the snapshot/restore state is not reclaimed
112    * after a default timeout, the entry is removed from the in-progress map.
113    * At this point, if the user asks for the snapshot/restore status, the result will be
114    * snapshot done if exists or failed if it doesn't exists.
115    */
116   private static final int SNAPSHOT_SENTINELS_CLEANUP_TIMEOUT = 60 * 1000;
117 
118   /** Enable or disable snapshot support */
119   public static final String HBASE_SNAPSHOT_ENABLED = "hbase.snapshot.enabled";
120 
121   /**
122    * Conf key for # of ms elapsed between checks for snapshot errors while waiting for
123    * completion.
124    */
125   private static final String SNAPSHOT_WAKE_MILLIS_KEY = "hbase.snapshot.master.wakeMillis";
126 
127   /** Name of the operation to use in the controller */
128   public static final String ONLINE_SNAPSHOT_CONTROLLER_DESCRIPTION = "online-snapshot";
129 
130   /** Conf key for # of threads used by the SnapshotManager thread pool */
131   private static final String SNAPSHOT_POOL_THREADS_KEY = "hbase.snapshot.master.threads";
132 
133   /** number of current operations running on the master */
134   private static final int SNAPSHOT_POOL_THREADS_DEFAULT = 1;
135 
136   private boolean stopped;
137   private MasterServices master;  // Needed by TableEventHandlers
138   private ProcedureCoordinator coordinator;
139 
140   // Is snapshot feature enabled?
141   private boolean isSnapshotSupported = false;
142 
143   // Snapshot handlers map, with table name as key.
144   // The map is always accessed and modified under the object lock using synchronized.
145   // snapshotTable() will insert an Handler in the table.
146   // isSnapshotDone() will remove the handler requested if the operation is finished.
147   private Map<TableName, SnapshotSentinel> snapshotHandlers =
148       new HashMap<TableName, SnapshotSentinel>();
149 
150   // Restore Sentinels map, with table name as key.
151   // The map is always accessed and modified under the object lock using synchronized.
152   // restoreSnapshot()/cloneSnapshot() will insert an Handler in the table.
153   // isRestoreDone() will remove the handler requested if the operation is finished.
154   private Map<TableName, SnapshotSentinel> restoreHandlers =
155       new HashMap<TableName, SnapshotSentinel>();
156 
157   private Path rootDir;
158   private ExecutorService executorService;
159 
160   /**
161    * Read write lock between taking snapshot and snapshot HFile cleaner. The cleaner should skip to
162    * check the HFiles if any snapshot is in progress, otherwise it may clean a HFile which would
163    * belongs to the newly creating snapshot. So we should grab the write lock first when cleaner
164    * start to work. (See HBASE-21387)
165    */
166   private ReentrantReadWriteLock takingSnapshotLock = new ReentrantReadWriteLock(true);
167 
168   public SnapshotManager() {}
169 
170   /**
171    * Fully specify all necessary components of a snapshot manager. Exposed for testing.
172    * @param master services for the master where the manager is running
173    * @param coordinator procedure coordinator instance.  exposed for testing.
174    * @param pool HBase ExecutorServcie instance, exposed for testing.
175    */
176   public SnapshotManager(final MasterServices master, final MetricsMaster metricsMaster,
177       ProcedureCoordinator coordinator, ExecutorService pool)
178       throws IOException, UnsupportedOperationException {
179     this.master = master;
180 
181     this.rootDir = master.getMasterFileSystem().getRootDir();
182     checkSnapshotSupport(master.getConfiguration(), master.getMasterFileSystem());
183 
184     this.coordinator = coordinator;
185     this.executorService = pool;
186     resetTempDir();
187   }
188 
189   /**
190    * Gets the list of all completed snapshots.
191    * @return list of SnapshotDescriptions
192    * @throws IOException File system exception
193    */
194   public List<SnapshotDescription> getCompletedSnapshots() throws IOException {
195     return getCompletedSnapshots(SnapshotDescriptionUtils.getSnapshotsDir(rootDir));
196   }
197 
198   /**
199    * Gets the list of all completed snapshots.
200    * @param snapshotDir snapshot directory
201    * @return list of SnapshotDescriptions
202    * @throws IOException File system exception
203    */
204   private List<SnapshotDescription> getCompletedSnapshots(Path snapshotDir) throws IOException {
205     List<SnapshotDescription> snapshotDescs = new ArrayList<SnapshotDescription>();
206     // first create the snapshot root path and check to see if it exists
207     FileSystem fs = master.getMasterFileSystem().getFileSystem();
208     if (snapshotDir == null) snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(rootDir);
209 
210     // if there are no snapshots, return an empty list
211     if (!fs.exists(snapshotDir)) {
212       return snapshotDescs;
213     }
214 
215     // ignore all the snapshots in progress
216     FileStatus[] snapshots = fs.listStatus(snapshotDir,
217       new SnapshotDescriptionUtils.CompletedSnaphotDirectoriesFilter(fs));
218     MasterCoprocessorHost cpHost = master.getMasterCoprocessorHost();
219     // loop through all the completed snapshots
220     for (FileStatus snapshot : snapshots) {
221       Path info = new Path(snapshot.getPath(), SnapshotDescriptionUtils.SNAPSHOTINFO_FILE);
222       // if the snapshot is bad
223       if (!fs.exists(info)) {
224         LOG.error("Snapshot information for " + snapshot.getPath() + " doesn't exist");
225         continue;
226       }
227       FSDataInputStream in = null;
228       try {
229         in = fs.open(info);
230         SnapshotDescription desc = SnapshotDescription.parseFrom(in);
231         if (cpHost != null) {
232           try {
233             cpHost.preListSnapshot(desc);
234           } catch (AccessDeniedException e) {
235             LOG.warn("Current user does not have access to " + desc.getName() + " snapshot. "
236                 + "Either you should be owner of this snapshot or admin user.");
237             // Skip this and try for next snapshot
238             continue;
239           }
240         }
241         snapshotDescs.add(desc);
242 
243         // call coproc post hook
244         if (cpHost != null) {
245           cpHost.postListSnapshot(desc);
246         }
247       } catch (IOException e) {
248         LOG.warn("Found a corrupted snapshot " + snapshot.getPath(), e);
249       } finally {
250         if (in != null) {
251           in.close();
252         }
253       }
254     }
255     return snapshotDescs;
256   }
257 
258   /**
259    * Cleans up any snapshots in the snapshot/.tmp directory that were left from failed
260    * snapshot attempts.
261    *
262    * @throws IOException if we can't reach the filesystem
263    */
264   void resetTempDir() throws IOException {
265     // cleanup any existing snapshots.
266     Path tmpdir = SnapshotDescriptionUtils.getWorkingSnapshotDir(rootDir,
267         master.getConfiguration());
268     FileSystem tmpFs = tmpdir.getFileSystem(master.getConfiguration());
269     if (!tmpFs.delete(tmpdir, true)) {
270       LOG.warn("Couldn't delete working snapshot directory: " + tmpdir);
271     }
272   }
273 
274   /**
275    * Delete the specified snapshot
276    * @param snapshot
277    * @throws SnapshotDoesNotExistException If the specified snapshot does not exist.
278    * @throws IOException For filesystem IOExceptions
279    */
280   public void deleteSnapshot(SnapshotDescription snapshot) throws SnapshotDoesNotExistException, IOException {
281     // check to see if it is completed
282     if (!isSnapshotCompleted(snapshot)) {
283       throw new SnapshotDoesNotExistException(snapshot);
284     }
285 
286     String snapshotName = snapshot.getName();
287     // first create the snapshot description and check to see if it exists
288     FileSystem fs = master.getMasterFileSystem().getFileSystem();
289     Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
290     // Get snapshot info from file system. The one passed as parameter is a "fake" snapshotInfo with
291     // just the "name" and it does not contains the "real" snapshot information
292     snapshot = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
293 
294     // call coproc pre hook
295     MasterCoprocessorHost cpHost = master.getMasterCoprocessorHost();
296     if (cpHost != null) {
297       cpHost.preDeleteSnapshot(snapshot);
298     }
299 
300     LOG.debug("Deleting snapshot: " + snapshotName);
301     // delete the existing snapshot
302     if (!fs.delete(snapshotDir, true)) {
303       throw new HBaseSnapshotException("Failed to delete snapshot directory: " + snapshotDir);
304     }
305 
306     // call coproc post hook
307     if (cpHost != null) {
308       cpHost.postDeleteSnapshot(snapshot);
309     }
310 
311   }
312 
313   /**
314    * Check if the specified snapshot is done
315    *
316    * @param expected
317    * @return true if snapshot is ready to be restored, false if it is still being taken.
318    * @throws IOException IOException if error from HDFS or RPC
319    * @throws UnknownSnapshotException if snapshot is invalid or does not exist.
320    */
321   public boolean isSnapshotDone(SnapshotDescription expected) throws IOException {
322     // check the request to make sure it has a snapshot
323     if (expected == null) {
324       throw new UnknownSnapshotException(
325          "No snapshot name passed in request, can't figure out which snapshot you want to check.");
326     }
327 
328     String ssString = ClientSnapshotDescriptionUtils.toString(expected);
329 
330     // check to see if the sentinel exists,
331     // and if the task is complete removes it from the in-progress snapshots map.
332     SnapshotSentinel handler = removeSentinelIfFinished(this.snapshotHandlers, expected);
333 
334     // stop tracking "abandoned" handlers
335     cleanupSentinels();
336 
337     if (handler == null) {
338       // If there's no handler in the in-progress map, it means one of the following:
339       //   - someone has already requested the snapshot state
340       //   - the requested snapshot was completed long time ago (cleanupSentinels() timeout)
341       //   - the snapshot was never requested
342       // In those cases returns to the user the "done state" if the snapshots exists on disk,
343       // otherwise raise an exception saying that the snapshot is not running and doesn't exist.
344       if (!isSnapshotCompleted(expected)) {
345         throw new UnknownSnapshotException("Snapshot " + ssString
346             + " is not currently running or one of the known completed snapshots.");
347       }
348       // was done, return true;
349       return true;
350     }
351 
352     // pass on any failure we find in the sentinel
353     try {
354       handler.rethrowExceptionIfFailed();
355     } catch (ForeignException e) {
356       // Give some procedure info on an exception.
357       String status;
358       Procedure p = coordinator.getProcedure(expected.getName());
359       if (p != null) {
360         status = p.getStatus();
361       } else {
362         status = expected.getName() + " not found in proclist " + coordinator.getProcedureNames();
363       }
364       throw new HBaseSnapshotException("Snapshot " + ssString +  " had an error.  " + status, e,
365           expected);
366     }
367 
368     // check to see if we are done
369     if (handler.isFinished()) {
370       LOG.debug("Snapshot '" + ssString + "' has completed, notifying client.");
371       return true;
372     } else if (LOG.isDebugEnabled()) {
373       LOG.debug("Snapshoting '" + ssString + "' is still in progress!");
374     }
375     return false;
376   }
377 
378   /**
379    * Check to see if there is a snapshot in progress with the same name or on the same table.
380    * Currently we have a limitation only allowing a single snapshot per table at a time. Also we
381    * don't allow snapshot with the same name.
382    * @param snapshot description of the snapshot being checked.
383    * @return <tt>true</tt> if there is a snapshot in progress with the same name or on the same
384    *         table.
385    */
386   synchronized boolean isTakingSnapshot(final SnapshotDescription snapshot) {
387     TableName snapshotTable = TableName.valueOf(snapshot.getTable());
388     if (isTakingSnapshot(snapshotTable)) {
389       return true;
390     }
391     Iterator<Map.Entry<TableName, SnapshotSentinel>> it = this.snapshotHandlers.entrySet().iterator();
392     while (it.hasNext()) {
393       Map.Entry<TableName, SnapshotSentinel> entry = it.next();
394       SnapshotSentinel sentinel = entry.getValue();
395       if (snapshot.getName().equals(sentinel.getSnapshot().getName()) && !sentinel.isFinished()) {
396         return true;
397       }
398     }
399     return false;
400   }
401 
402   /**
403    * Check to see if the specified table has a snapshot in progress.  Currently we have a
404    * limitation only allowing a single snapshot per table at a time.
405    * @param tableName name of the table being snapshotted.
406    * @return <tt>true</tt> if there is a snapshot in progress on the specified table.
407    */
408   synchronized boolean isTakingSnapshot(final TableName tableName) {
409     SnapshotSentinel handler = this.snapshotHandlers.get(tableName);
410     return handler != null && !handler.isFinished();
411   }
412 
413   /**
414    * Check to make sure that we are OK to run the passed snapshot. Checks to make sure that we
415    * aren't already running a snapshot or restore on the requested table.
416    * @param snapshot description of the snapshot we want to start
417    * @throws HBaseSnapshotException if the filesystem could not be prepared to start the snapshot
418    */
419   private synchronized void prepareToTakeSnapshot(SnapshotDescription snapshot)
420       throws HBaseSnapshotException {
421     Path workingDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(snapshot, rootDir,
422         master.getConfiguration());
423     TableName snapshotTable =
424         TableName.valueOf(snapshot.getTable());
425 
426     // make sure we aren't already running a snapshot
427     if (isTakingSnapshot(snapshot)) {
428       SnapshotSentinel handler = this.snapshotHandlers.get(snapshotTable);
429       throw new SnapshotCreationException("Rejected taking "
430           + ClientSnapshotDescriptionUtils.toString(snapshot)
431           + " because we are already running another snapshot "
432           + (handler != null ? ("on the same table " +
433               ClientSnapshotDescriptionUtils.toString(handler.getSnapshot()))
434               : "with the same name"), snapshot);
435     }
436 
437     // make sure we aren't running a restore on the same table
438     if (isRestoringTable(snapshotTable)) {
439       SnapshotSentinel handler = restoreHandlers.get(snapshotTable);
440       throw new SnapshotCreationException("Rejected taking "
441           + ClientSnapshotDescriptionUtils.toString(snapshot)
442           + " because we are already have a restore in progress on the same snapshot "
443           + ClientSnapshotDescriptionUtils.toString(handler.getSnapshot()), snapshot);
444     }
445 
446     try {
447       FileSystem workingDirFS = workingDir.getFileSystem(master.getConfiguration());
448       // delete the working directory, since we aren't running the snapshot. Likely leftovers
449       // from a failed attempt.
450       workingDirFS.delete(workingDir, true);
451 
452       // recreate the working directory for the snapshot
453       if (!workingDirFS.mkdirs(workingDir)) {
454         throw new SnapshotCreationException("Couldn't create working directory (" + workingDir
455             + ") for snapshot" , snapshot);
456       }
457     } catch (HBaseSnapshotException e) {
458       throw e;
459     } catch (IOException e) {
460       throw new SnapshotCreationException(
461           "Exception while checking to see if snapshot could be started.", e, snapshot);
462     }
463   }
464 
465   /**
466    * Take a snapshot of a disabled table.
467    * @param snapshot description of the snapshot to take. Modified to be {@link Type#DISABLED}.
468    * @throws IOException if the snapshot could not be started or filesystem for snapshot
469    *         temporary directory could not be determined
470    */
471   private synchronized void snapshotDisabledTable(SnapshotDescription snapshot)
472       throws IOException {
473     // setup the snapshot
474     prepareToTakeSnapshot(snapshot);
475 
476     // set the snapshot to be a disabled snapshot, since the client doesn't know about that
477     snapshot = snapshot.toBuilder().setType(Type.DISABLED).build();
478 
479     // Take the snapshot of the disabled table
480     DisabledTableSnapshotHandler handler =
481         new DisabledTableSnapshotHandler(snapshot, master, this);
482     snapshotTable(snapshot, handler);
483   }
484 
485   /**
486    * Take a snapshot of an enabled table.
487    * @param snapshot description of the snapshot to take.
488    * @throws IOException if the snapshot could not be started or filesystem for snapshot
489    *         temporary directory could not be determined
490    */
491   private synchronized void snapshotEnabledTable(SnapshotDescription snapshot)
492           throws IOException {
493     // setup the snapshot
494     prepareToTakeSnapshot(snapshot);
495 
496     // Take the snapshot of the enabled table
497     EnabledTableSnapshotHandler handler =
498         new EnabledTableSnapshotHandler(snapshot, master, this);
499     snapshotTable(snapshot, handler);
500   }
501 
502   /**
503    * Take a snapshot using the specified handler.
504    * On failure the snapshot temporary working directory is removed.
505    * NOTE: prepareToTakeSnapshot() called before this one takes care of the rejecting the
506    *       snapshot request if the table is busy with another snapshot/restore operation.
507    * @param snapshot the snapshot description
508    * @param handler the snapshot handler
509    */
510   private synchronized void snapshotTable(SnapshotDescription snapshot,
511       final TakeSnapshotHandler handler) throws IOException {
512     try {
513       handler.prepare();
514       this.executorService.submit(handler);
515       this.snapshotHandlers.put(TableName.valueOf(snapshot.getTable()), handler);
516     } catch (Exception e) {
517       // cleanup the working directory by trying to delete it from the fs.
518       Path workingDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(snapshot, rootDir,
519           master.getConfiguration());
520       FileSystem workingDirFs = workingDir.getFileSystem(master.getConfiguration());
521       try {
522         if (!workingDirFs.delete(workingDir, true)) {
523           LOG.error("Couldn't delete working directory (" + workingDir + " for snapshot:" +
524               ClientSnapshotDescriptionUtils.toString(snapshot));
525         }
526       } catch (IOException e1) {
527         LOG.error("Couldn't delete working directory (" + workingDir + " for snapshot:" +
528             ClientSnapshotDescriptionUtils.toString(snapshot));
529       }
530       // fail the snapshot
531       throw new SnapshotCreationException("Could not build snapshot handler", e, snapshot);
532     }
533   }
534 
535   /**
536    * Take a snapshot based on the enabled/disabled state of the table.
537    * @param snapshot
538    * @throws HBaseSnapshotException when a snapshot specific exception occurs.
539    * @throws IOException when some sort of generic IO exception occurs.
540    */
541   public void takeSnapshot(SnapshotDescription snapshot) throws IOException {
542     this.takingSnapshotLock.readLock().lock();
543     try {
544       takeSnapshotInternal(snapshot);
545     } finally {
546       this.takingSnapshotLock.readLock().unlock();
547     }
548   }
549 
550   private void takeSnapshotInternal(SnapshotDescription snapshot) throws IOException {
551     // check to see if we already completed the snapshot
552     if (isSnapshotCompleted(snapshot)) {
553       throw new SnapshotExistsException("Snapshot '" + snapshot.getName()
554           + "' already stored on the filesystem.", snapshot);
555     }
556 
557     LOG.debug("No existing snapshot, attempting snapshot...");
558 
559     // stop tracking "abandoned" handlers
560     cleanupSentinels();
561 
562     // check to see if the table exists
563     HTableDescriptor desc = null;
564     try {
565       desc = master.getTableDescriptors().get(
566           TableName.valueOf(snapshot.getTable()));
567     } catch (FileNotFoundException e) {
568       String msg = "Table:" + snapshot.getTable() + " info doesn't exist!";
569       LOG.error(msg);
570       throw new SnapshotCreationException(msg, e, snapshot);
571     } catch (IOException e) {
572       throw new SnapshotCreationException("Error while geting table description for table "
573           + snapshot.getTable(), e, snapshot);
574     }
575     if (desc == null) {
576       throw new SnapshotCreationException("Table '" + snapshot.getTable()
577           + "' doesn't exist, can't take snapshot.", snapshot);
578     }
579     SnapshotDescription.Builder builder = snapshot.toBuilder();
580     // if not specified, set the snapshot format
581     if (!snapshot.hasVersion()) {
582       builder.setVersion(SnapshotDescriptionUtils.SNAPSHOT_LAYOUT_VERSION);
583     }
584     User user = RpcServer.getRequestUser();
585     if (User.isHBaseSecurityEnabled(master.getConfiguration()) && user != null) {
586       builder.setOwner(user.getShortName());
587     }
588     snapshot = builder.build();
589 
590     // call pre coproc hook
591     MasterCoprocessorHost cpHost = master.getMasterCoprocessorHost();
592     if (cpHost != null) {
593       cpHost.preSnapshot(snapshot, desc);
594     }
595 
596     // if the table is enabled, then have the RS run actually the snapshot work
597     TableName snapshotTable = TableName.valueOf(snapshot.getTable());
598     AssignmentManager assignmentMgr = master.getAssignmentManager();
599     if (assignmentMgr.getTableStateManager().isTableState(snapshotTable,
600         ZooKeeperProtos.Table.State.ENABLED)) {
601       LOG.debug("Table enabled, starting distributed snapshot.");
602       snapshotEnabledTable(snapshot);
603       LOG.debug("Started snapshot: " + ClientSnapshotDescriptionUtils.toString(snapshot));
604     }
605     // For disabled table, snapshot is created by the master
606     else if (assignmentMgr.getTableStateManager().isTableState(snapshotTable,
607         ZooKeeperProtos.Table.State.DISABLED)) {
608       LOG.debug("Table is disabled, running snapshot entirely on master.");
609       snapshotDisabledTable(snapshot);
610       LOG.debug("Started snapshot: " + ClientSnapshotDescriptionUtils.toString(snapshot));
611     } else {
612       LOG.error("Can't snapshot table '" + snapshot.getTable()
613           + "', isn't open or closed, we don't know what to do!");
614       TablePartiallyOpenException tpoe = new TablePartiallyOpenException(snapshot.getTable()
615           + " isn't fully open.");
616       throw new SnapshotCreationException("Table is not entirely open or closed", tpoe, snapshot);
617     }
618 
619     // call post coproc hook
620     if (cpHost != null) {
621       cpHost.postSnapshot(snapshot, desc);
622     }
623   }
624 
625   public ReadWriteLock getTakingSnapshotLock() {
626     return this.takingSnapshotLock;
627   }
628 
629   /**
630    * The snapshot operation processing as following: <br>
631    * 1. Create a Snapshot Handler, and do some initialization; <br>
632    * 2. Put the handler into snapshotHandlers <br>
633    * So when we consider if any snapshot is taking, we should consider both the takingSnapshotLock
634    * and snapshotHandlers;
635    * @return true to indicate that there're some running snapshots.
636    */
637   public synchronized boolean isTakingAnySnapshot() {
638     return this.takingSnapshotLock.getReadHoldCount() > 0 || this.snapshotHandlers.size() > 0;
639   }
640 
641   /**
642    * Set the handler for the current snapshot
643    * <p>
644    * Exposed for TESTING
645    * @param tableName
646    * @param handler handler the master should use
647    *
648    * TODO get rid of this if possible, repackaging, modify tests.
649    */
650   public synchronized void setSnapshotHandlerForTesting(
651       final TableName tableName,
652       final SnapshotSentinel handler) {
653     if (handler != null) {
654       this.snapshotHandlers.put(tableName, handler);
655     } else {
656       this.snapshotHandlers.remove(tableName);
657     }
658   }
659 
660   /**
661    * @return distributed commit coordinator for all running snapshots
662    */
663   ProcedureCoordinator getCoordinator() {
664     return coordinator;
665   }
666 
667   /**
668    * Check to see if the snapshot is one of the currently completed snapshots
669    * Returns true if the snapshot exists in the "completed snapshots folder".
670    *
671    * @param snapshot expected snapshot to check
672    * @return <tt>true</tt> if the snapshot is stored on the {@link FileSystem}, <tt>false</tt> if is
673    *         not stored
674    * @throws IOException if the filesystem throws an unexpected exception,
675    * @throws IllegalArgumentException if snapshot name is invalid.
676    */
677   private boolean isSnapshotCompleted(SnapshotDescription snapshot) throws IOException {
678     try {
679       final Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshot, rootDir);
680       FileSystem fs = master.getMasterFileSystem().getFileSystem();
681       // check to see if the snapshot already exists
682       return fs.exists(snapshotDir);
683     } catch (IllegalArgumentException iae) {
684       throw new UnknownSnapshotException("Unexpected exception thrown", iae);
685     }
686   }
687 
688   /**
689    * Clone the specified snapshot into a new table.
690    * The operation will fail if the destination table has a snapshot or restore in progress.
691    *
692    * @param snapshot Snapshot Descriptor
693    * @param hTableDescriptor Table Descriptor of the table to create
694    */
695   synchronized void cloneSnapshot(final SnapshotDescription snapshot,
696       final HTableDescriptor hTableDescriptor, final boolean restoreAcl)
697       throws HBaseSnapshotException {
698     TableName tableName = hTableDescriptor.getTableName();
699 
700     // make sure we aren't running a snapshot on the same table
701     if (isTakingSnapshot(tableName)) {
702       throw new RestoreSnapshotException("Snapshot in progress on the restore table=" + tableName);
703     }
704 
705     // make sure we aren't running a restore on the same table
706     if (isRestoringTable(tableName)) {
707       throw new RestoreSnapshotException("Restore already in progress on the table=" + tableName);
708     }
709 
710     try {
711       CloneSnapshotHandler handler =
712           new CloneSnapshotHandler(master, snapshot, hTableDescriptor, restoreAcl).prepare();
713       this.executorService.submit(handler);
714       this.restoreHandlers.put(tableName, handler);
715     } catch (Exception e) {
716       String msg = "Couldn't clone the snapshot=" + ClientSnapshotDescriptionUtils.toString(snapshot) +
717         " on table=" + tableName;
718       LOG.error(msg, e);
719       throw new RestoreSnapshotException(msg, e);
720     }
721   }
722 
723   /**
724    * Restore the specified snapshot
725    * @param reqSnapshot the snapshot to restore
726    * @throws IOException
727    */
728   @Deprecated
729   public void restoreSnapshot(SnapshotDescription reqSnapshot) throws IOException {
730     restoreSnapshot(reqSnapshot, false);
731   }
732 
733   /**
734    * Restore the specified snapshot
735    * @param reqSnapshot the snapshot to restore
736    * @param restoreAcl whether or not to restore ACLs on the snapshot
737    * @throws IOException
738    */
739   public void restoreSnapshot(SnapshotDescription reqSnapshot, boolean restoreAcl)
740       throws IOException {
741     FileSystem fs = master.getMasterFileSystem().getFileSystem();
742     Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(reqSnapshot, rootDir);
743     MasterCoprocessorHost cpHost = master.getMasterCoprocessorHost();
744 
745     // check if the snapshot exists
746     if (!fs.exists(snapshotDir)) {
747       LOG.error("A Snapshot named '" + reqSnapshot.getName() + "' does not exist.");
748       throw new SnapshotDoesNotExistException(reqSnapshot);
749     }
750 
751     // Get snapshot info from file system. The reqSnapshot is a "fake" snapshotInfo with
752     // just the snapshot "name" and table name to restore. It does not contains the "real" snapshot
753     // information.
754     SnapshotDescription snapshot = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
755     SnapshotManifest manifest = SnapshotManifest.open(master.getConfiguration(), fs,
756         snapshotDir, snapshot);
757     HTableDescriptor snapshotTableDesc = manifest.getTableDescriptor();
758     TableName tableName = TableName.valueOf(reqSnapshot.getTable());
759 
760     // stop tracking "abandoned" handlers
761     cleanupSentinels();
762 
763     // Verify snapshot validity
764     SnapshotReferenceUtil.verifySnapshot(master.getConfiguration(), fs, manifest);
765 
766     // Execute the restore/clone operation
767     if (MetaTableAccessor.tableExists(master.getConnection(), tableName)) {
768       if (master.getAssignmentManager().getTableStateManager().isTableState(
769           TableName.valueOf(snapshot.getTable()), ZooKeeperProtos.Table.State.ENABLED)) {
770         throw new UnsupportedOperationException("Table '" +
771             TableName.valueOf(snapshot.getTable()) + "' must be disabled in order to " +
772             "perform a restore operation" +
773             ".");
774       }
775 
776       // call coproc pre hook
777       if (cpHost != null) {
778         cpHost.preRestoreSnapshot(snapshot, snapshotTableDesc);
779       }
780 
781       int tableRegionCount = -1;
782       try {
783         // Table already exist. Check and update the region quota for this table namespace.
784         // The region quota may not be updated correctly if there are concurrent restore snapshot
785         // requests for the same table
786 
787         tableRegionCount = getRegionCountOfTable(tableName);
788         int snapshotRegionCount = manifest.getRegionManifestsMap().size();
789 
790         // Update region quota when snapshotRegionCount is larger. If we updated the region count
791         // to a smaller value before retoreSnapshot and the retoreSnapshot fails, we may fail to
792         // reset the region count to its original value if the region quota is consumed by other
793         // tables in the namespace
794         if (tableRegionCount > 0 && tableRegionCount < snapshotRegionCount) {
795           checkAndUpdateNamespaceRegionQuota(snapshotRegionCount, tableName);
796         }
797         restoreSnapshot(snapshot, snapshotTableDesc, restoreAcl);
798         // Update the region quota if snapshotRegionCount is smaller. This step should not fail
799         // because we have reserved enough region quota before hand
800         if (tableRegionCount > 0 && tableRegionCount > snapshotRegionCount) {
801           checkAndUpdateNamespaceRegionQuota(snapshotRegionCount, tableName);
802         }
803       } catch (QuotaExceededException e) {
804         LOG.error("Region quota exceeded while restoring the snapshot " + snapshot.getName()
805           + " as table " + tableName.getNameAsString(), e);
806         // If QEE is thrown before restoreSnapshot, quota information is not updated, so we
807         // should throw the exception directly. If QEE is thrown after restoreSnapshot, there
808         // must be unexpected reasons, we also throw the exception directly
809         throw e;
810       } catch (IOException e) {
811         if (tableRegionCount > 0) {
812           // reset the region count for table
813           checkAndUpdateNamespaceRegionQuota(tableRegionCount, tableName);
814         }
815         LOG.error("Exception occurred while restoring the snapshot " + snapshot.getName()
816             + " as table " + tableName.getNameAsString(), e);
817         throw e;
818       }
819       LOG.info("Restore snapshot=" + snapshot.getName() + " as table=" + tableName);
820 
821       if (cpHost != null) {
822         cpHost.postRestoreSnapshot(snapshot, snapshotTableDesc);
823       }
824     } else {
825       HTableDescriptor htd = new HTableDescriptor(tableName, snapshotTableDesc);
826       if (cpHost != null) {
827         cpHost.preCloneSnapshot(snapshot, htd);
828       }
829       try {
830         checkAndUpdateNamespaceQuota(manifest, tableName);
831         cloneSnapshot(snapshot, htd, restoreAcl);
832       } catch (IOException e) {
833         this.master.getMasterQuotaManager().removeTableFromNamespaceQuota(tableName);
834         LOG.error("Exception occurred while cloning the snapshot " + snapshot.getName()
835             + " as table " + tableName.getNameAsString(), e);
836         throw e;
837       }
838       LOG.info("Clone snapshot=" + snapshot.getName() + " as table=" + tableName);
839 
840       if (cpHost != null) {
841         cpHost.postCloneSnapshot(snapshot, htd);
842       }
843     }
844   }
845 
846   private void checkAndUpdateNamespaceQuota(SnapshotManifest manifest, TableName tableName)
847       throws IOException {
848     if (this.master.getMasterQuotaManager().isQuotaInitialized()) {
849       this.master.getMasterQuotaManager().checkNamespaceTableAndRegionQuota(tableName,
850         manifest.getRegionManifestsMap().size());
851     }
852   }
853 
854   private void checkAndUpdateNamespaceRegionQuota(int updatedRegionCount, TableName tableName)
855       throws IOException {
856     if (this.master.getMasterQuotaManager().isQuotaInitialized()) {
857       this.master.getMasterQuotaManager().checkAndUpdateNamespaceRegionQuota(tableName,
858         updatedRegionCount);
859     }
860   }
861 
862   /**
863    * @return cached region count, or -1 if quota manager is disabled or table status not found
864   */
865   private int getRegionCountOfTable(TableName tableName) throws IOException {
866     if (this.master.getMasterQuotaManager().isQuotaInitialized()) {
867       return this.master.getMasterQuotaManager().getRegionCountOfTable(tableName);
868     }
869     return -1;
870   }
871 
872   /**
873    * Restore the specified snapshot.
874    * The restore will fail if the destination table has a snapshot or restore in progress.
875    *
876    * @param snapshot Snapshot Descriptor
877    * @param hTableDescriptor Table Descriptor
878    */
879   private synchronized void restoreSnapshot(final SnapshotDescription snapshot,
880       final HTableDescriptor hTableDescriptor, final boolean restoreAcl)
881       throws HBaseSnapshotException {
882     TableName tableName = hTableDescriptor.getTableName();
883 
884     // make sure we aren't running a snapshot on the same table
885     if (isTakingSnapshot(tableName)) {
886       throw new RestoreSnapshotException("Snapshot in progress on the restore table=" + tableName);
887     }
888 
889     // make sure we aren't running a restore on the same table
890     if (isRestoringTable(tableName)) {
891       throw new RestoreSnapshotException("Restore already in progress on the table=" + tableName);
892     }
893 
894     try {
895       RestoreSnapshotHandler handler =
896         new RestoreSnapshotHandler(master, snapshot, hTableDescriptor, restoreAcl).prepare();
897       this.executorService.submit(handler);
898       restoreHandlers.put(tableName, handler);
899     } catch (Exception e) {
900       String msg = "Couldn't restore the snapshot=" + ClientSnapshotDescriptionUtils.toString(
901           snapshot)  +
902           " on table=" + tableName;
903       LOG.error(msg, e);
904       throw new RestoreSnapshotException(msg, e);
905     }
906   }
907 
908   /**
909    * Verify if the restore of the specified table is in progress.
910    *
911    * @param tableName table under restore
912    * @return <tt>true</tt> if there is a restore in progress of the specified table.
913    */
914   private synchronized boolean isRestoringTable(final TableName tableName) {
915     SnapshotSentinel sentinel = this.restoreHandlers.get(tableName);
916     return(sentinel != null && !sentinel.isFinished());
917   }
918 
919   /**
920    * Returns the status of a restore operation.
921    * If the in-progress restore is failed throws the exception that caused the failure.
922    *
923    * @param snapshot
924    * @return false if in progress, true if restore is completed or not requested.
925    * @throws IOException if there was a failure during the restore
926    */
927   public boolean isRestoreDone(final SnapshotDescription snapshot) throws IOException {
928     // check to see if the sentinel exists,
929     // and if the task is complete removes it from the in-progress restore map.
930     SnapshotSentinel sentinel = removeSentinelIfFinished(this.restoreHandlers, snapshot);
931 
932     // stop tracking "abandoned" handlers
933     cleanupSentinels();
934 
935     if (sentinel == null) {
936       // there is no sentinel so restore is not in progress.
937       return true;
938     }
939 
940     LOG.debug("Verify snapshot=" + snapshot.getName() + " against="
941         + sentinel.getSnapshot().getName() + " table=" +
942         TableName.valueOf(snapshot.getTable()));
943 
944     // If the restore is failed, rethrow the exception
945     sentinel.rethrowExceptionIfFailed();
946 
947     // check to see if we are done
948     if (sentinel.isFinished()) {
949       LOG.debug("Restore snapshot=" + ClientSnapshotDescriptionUtils.toString(snapshot) +
950           " has completed. Notifying the client.");
951       return true;
952     }
953 
954     if (LOG.isDebugEnabled()) {
955       LOG.debug("Sentinel is not yet finished with restoring snapshot=" +
956           ClientSnapshotDescriptionUtils.toString(snapshot));
957     }
958     return false;
959   }
960 
961   /**
962    * Return the handler if it is currently live and has the same snapshot target name.
963    * The handler is removed from the sentinels map if completed.
964    * @param sentinels live handlers
965    * @param snapshot snapshot description
966    * @return null if doesn't match, else a live handler.
967    */
968   private synchronized SnapshotSentinel removeSentinelIfFinished(
969       final Map<TableName, SnapshotSentinel> sentinels,
970       final SnapshotDescription snapshot) {
971     if (!snapshot.hasTable()) {
972       return null;
973     }
974 
975     TableName snapshotTable = TableName.valueOf(snapshot.getTable());
976     SnapshotSentinel h = sentinels.get(snapshotTable);
977     if (h == null) {
978       return null;
979     }
980 
981     if (!h.getSnapshot().getName().equals(snapshot.getName())) {
982       // specified snapshot is to the one currently running
983       return null;
984     }
985 
986     // Remove from the "in-progress" list once completed
987     if (h.isFinished()) {
988       sentinels.remove(snapshotTable);
989     }
990 
991     return h;
992   }
993 
994   /**
995    * Removes "abandoned" snapshot/restore requests.
996    * As part of the HBaseAdmin snapshot/restore API the operation status is checked until completed,
997    * and the in-progress maps are cleaned up when the status of a completed task is requested.
998    * To avoid having sentinels staying around for long time if something client side is failed,
999    * each operation tries to clean up the in-progress maps sentinels finished from a long time.
1000    */
1001   private void cleanupSentinels() {
1002     cleanupSentinels(this.snapshotHandlers);
1003     cleanupSentinels(this.restoreHandlers);
1004   }
1005 
1006   /**
1007    * Remove the sentinels that are marked as finished and the completion time
1008    * has exceeded the removal timeout.
1009    * @param sentinels map of sentinels to clean
1010    */
1011   private synchronized void cleanupSentinels(final Map<TableName, SnapshotSentinel> sentinels) {
1012     long currentTime = EnvironmentEdgeManager.currentTime();
1013     Iterator<Map.Entry<TableName, SnapshotSentinel>> it =
1014         sentinels.entrySet().iterator();
1015     while (it.hasNext()) {
1016       Map.Entry<TableName, SnapshotSentinel> entry = it.next();
1017       SnapshotSentinel sentinel = entry.getValue();
1018       if (sentinel.isFinished() &&
1019           (currentTime - sentinel.getCompletionTimestamp()) > SNAPSHOT_SENTINELS_CLEANUP_TIMEOUT)
1020       {
1021         it.remove();
1022       }
1023     }
1024   }
1025 
1026   //
1027   // Implementing Stoppable interface
1028   //
1029 
1030   @Override
1031   public void stop(String why) {
1032     // short circuit
1033     if (this.stopped) return;
1034     // make sure we get stop
1035     this.stopped = true;
1036     // pass the stop onto take snapshot handlers
1037     for (SnapshotSentinel snapshotHandler: this.snapshotHandlers.values()) {
1038       snapshotHandler.cancel(why);
1039     }
1040 
1041     // pass the stop onto all the restore handlers
1042     for (SnapshotSentinel restoreHandler: this.restoreHandlers.values()) {
1043       restoreHandler.cancel(why);
1044     }
1045     try {
1046       if (coordinator != null) {
1047         coordinator.close();
1048       }
1049     } catch (IOException e) {
1050       LOG.error("stop ProcedureCoordinator error", e);
1051     }
1052   }
1053 
1054   @Override
1055   public boolean isStopped() {
1056     return this.stopped;
1057   }
1058 
1059   /**
1060    * Throws an exception if snapshot operations (take a snapshot, restore, clone) are not supported.
1061    * Called at the beginning of snapshot() and restoreSnapshot() methods.
1062    * @throws UnsupportedOperationException if snapshot are not supported
1063    */
1064   public void checkSnapshotSupport() throws UnsupportedOperationException {
1065     if (!this.isSnapshotSupported) {
1066       throw new UnsupportedOperationException(
1067         "To use snapshots, You must add to the hbase-site.xml of the HBase Master: '" +
1068           HBASE_SNAPSHOT_ENABLED + "' property with value 'true'.");
1069     }
1070   }
1071 
1072   /**
1073    * Called at startup, to verify if snapshot operation is supported, and to avoid
1074    * starting the master if there're snapshots present but the cleaners needed are missing.
1075    * Otherwise we can end up with snapshot data loss.
1076    * @param conf The {@link Configuration} object to use
1077    * @param mfs The MasterFileSystem to use
1078    * @throws IOException in case of file-system operation failure
1079    * @throws UnsupportedOperationException in case cleaners are missing and
1080    *         there're snapshot in the system
1081    */
1082   private void checkSnapshotSupport(final Configuration conf, final MasterFileSystem mfs)
1083       throws IOException, UnsupportedOperationException {
1084     // Verify if snapshot is disabled by the user
1085     String enabled = conf.get(HBASE_SNAPSHOT_ENABLED);
1086     boolean snapshotEnabled = conf.getBoolean(HBASE_SNAPSHOT_ENABLED, false);
1087     boolean userDisabled = (enabled != null && enabled.trim().length() > 0 && !snapshotEnabled);
1088 
1089     // Extract cleaners from conf
1090     Set<String> hfileCleaners = new HashSet<String>();
1091     String[] cleaners = conf.getStrings(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS);
1092     if (cleaners != null) Collections.addAll(hfileCleaners, cleaners);
1093 
1094     Set<String> logCleaners = new HashSet<String>();
1095     cleaners = conf.getStrings(HConstants.HBASE_MASTER_LOGCLEANER_PLUGINS);
1096     if (cleaners != null) Collections.addAll(logCleaners, cleaners);
1097 
1098     // check if an older version of snapshot directory was present
1099     Path oldSnapshotDir = new Path(mfs.getRootDir(), HConstants.OLD_SNAPSHOT_DIR_NAME);
1100     FileSystem fs = mfs.getFileSystem();
1101     List<SnapshotDescription> ss = getCompletedSnapshots(new Path(rootDir, oldSnapshotDir));
1102     if (ss != null && !ss.isEmpty()) {
1103       LOG.error("Snapshots from an earlier release were found under: " + oldSnapshotDir);
1104       LOG.error("Please rename the directory as " + HConstants.SNAPSHOT_DIR_NAME);
1105     }
1106 
1107     // If the user has enabled the snapshot, we force the cleaners to be present
1108     // otherwise we still need to check if cleaners are enabled or not and verify
1109     // that there're no snapshot in the .snapshot folder.
1110     if (snapshotEnabled) {
1111       // Inject snapshot cleaners, if snapshot.enable is true
1112       hfileCleaners.add(SnapshotHFileCleaner.class.getName());
1113       hfileCleaners.add(HFileLinkCleaner.class.getName());
1114 
1115       // Set cleaners conf
1116       conf.setStrings(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS,
1117         hfileCleaners.toArray(new String[hfileCleaners.size()]));
1118       conf.setStrings(HConstants.HBASE_MASTER_LOGCLEANER_PLUGINS,
1119         logCleaners.toArray(new String[logCleaners.size()]));
1120     } else {
1121       // Verify if cleaners are present
1122       snapshotEnabled =
1123         hfileCleaners.contains(SnapshotHFileCleaner.class.getName()) &&
1124         hfileCleaners.contains(HFileLinkCleaner.class.getName());
1125 
1126       // Warn if the cleaners are enabled but the snapshot.enabled property is false/not set.
1127       if (snapshotEnabled) {
1128         LOG.warn("Snapshot log and hfile cleaners are present in the configuration, " +
1129           "but the '" + HBASE_SNAPSHOT_ENABLED + "' property " +
1130           (userDisabled ? "is set to 'false'." : "is not set."));
1131       }
1132     }
1133 
1134     // Mark snapshot feature as enabled if cleaners are present and user has not disabled it.
1135     this.isSnapshotSupported = snapshotEnabled && !userDisabled;
1136 
1137     // If cleaners are not enabled, verify that there're no snapshot in the .snapshot folder
1138     // otherwise we end up with snapshot data loss.
1139     if (!snapshotEnabled) {
1140       LOG.info("Snapshot feature is not enabled, missing log and hfile cleaners.");
1141       Path snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(mfs.getRootDir());
1142       if (fs.exists(snapshotDir)) {
1143         FileStatus[] snapshots = FSUtils.listStatus(fs, snapshotDir,
1144           new SnapshotDescriptionUtils.CompletedSnaphotDirectoriesFilter(fs));
1145         if (snapshots != null) {
1146           LOG.error("Snapshots are present, but cleaners are not enabled.");
1147           checkSnapshotSupport();
1148         }
1149       }
1150     }
1151   }
1152 
1153   @Override
1154   public void initialize(MasterServices master, MetricsMaster metricsMaster) throws KeeperException,
1155       IOException, UnsupportedOperationException {
1156     this.master = master;
1157 
1158     this.rootDir = master.getMasterFileSystem().getRootDir();
1159     checkSnapshotSupport(master.getConfiguration(), master.getMasterFileSystem());
1160 
1161     // get the configuration for the coordinator
1162     Configuration conf = master.getConfiguration();
1163     long wakeFrequency = conf.getInt(SNAPSHOT_WAKE_MILLIS_KEY, SNAPSHOT_WAKE_MILLIS_DEFAULT);
1164     long timeoutMillis = Math.max(conf.getLong(SnapshotDescriptionUtils.SNAPSHOT_TIMEOUT_MILLIS_KEY,
1165                     SnapshotDescriptionUtils.SNAPSHOT_TIMEOUT_MILLIS_DEFAULT),
1166             conf.getLong(SnapshotDescriptionUtils.MASTER_SNAPSHOT_TIMEOUT_MILLIS,
1167                     SnapshotDescriptionUtils.DEFAULT_MAX_WAIT_TIME));
1168     int opThreads = conf.getInt(SNAPSHOT_POOL_THREADS_KEY, SNAPSHOT_POOL_THREADS_DEFAULT);
1169 
1170     // setup the default procedure coordinator
1171     String name = master.getServerName().toString();
1172     ThreadPoolExecutor tpool = ProcedureCoordinator.defaultPool(name, opThreads);
1173     ProcedureCoordinatorRpcs comms = new ZKProcedureCoordinatorRpcs(
1174         master.getZooKeeper(), SnapshotManager.ONLINE_SNAPSHOT_CONTROLLER_DESCRIPTION, name);
1175 
1176     this.coordinator = new ProcedureCoordinator(comms, tpool, timeoutMillis, wakeFrequency);
1177     this.executorService = master.getExecutorService();
1178     resetTempDir();
1179   }
1180 
1181   @Override
1182   public String getProcedureSignature() {
1183     return ONLINE_SNAPSHOT_CONTROLLER_DESCRIPTION;
1184   }
1185 
1186   @Override
1187   public void execProcedure(ProcedureDescription desc) throws IOException {
1188     takeSnapshot(toSnapshotDescription(desc));
1189   }
1190 
1191   @Override
1192   public boolean isProcedureDone(ProcedureDescription desc) throws IOException {
1193     return isSnapshotDone(toSnapshotDescription(desc));
1194   }
1195 
1196   private SnapshotDescription toSnapshotDescription(ProcedureDescription desc)
1197       throws IOException {
1198     SnapshotDescription.Builder builder = SnapshotDescription.newBuilder();
1199     if (!desc.hasInstance()) {
1200       throw new IOException("Snapshot name is not defined: " + desc.toString());
1201     }
1202     String snapshotName = desc.getInstance();
1203     List<NameStringPair> props = desc.getConfigurationList();
1204     String table = null;
1205     for (NameStringPair prop : props) {
1206       if ("table".equalsIgnoreCase(prop.getName())) {
1207         table = prop.getValue();
1208       }
1209     }
1210     if (table == null) {
1211       throw new IOException("Snapshot table is not defined: " + desc.toString());
1212     }
1213     TableName tableName = TableName.valueOf(table);
1214     builder.setTable(tableName.getNameAsString());
1215     builder.setName(snapshotName);
1216     builder.setType(SnapshotDescription.Type.FLUSH);
1217     return builder.build();
1218   }
1219 }