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