1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
90
91
92
93
94
95
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
103 private static final int SNAPSHOT_WAKE_MILLIS_DEFAULT = 500;
104
105
106
107
108
109
110
111
112
113
114
115
116 private static final int SNAPSHOT_SENTINELS_CLEANUP_TIMEOUT = 60 * 1000;
117
118
119 public static final String HBASE_SNAPSHOT_ENABLED = "hbase.snapshot.enabled";
120
121
122
123
124
125 private static final String SNAPSHOT_WAKE_MILLIS_KEY = "hbase.snapshot.master.wakeMillis";
126
127
128 public static final String ONLINE_SNAPSHOT_CONTROLLER_DESCRIPTION = "online-snapshot";
129
130
131 private static final String SNAPSHOT_POOL_THREADS_KEY = "hbase.snapshot.master.threads";
132
133
134 private static final int SNAPSHOT_POOL_THREADS_DEFAULT = 1;
135
136 private boolean stopped;
137 private MasterServices master;
138 private ProcedureCoordinator coordinator;
139
140
141 private boolean isSnapshotSupported = false;
142
143
144
145
146
147 private Map<TableName, SnapshotSentinel> snapshotHandlers =
148 new HashMap<TableName, SnapshotSentinel>();
149
150
151
152
153
154 private Map<TableName, SnapshotSentinel> restoreHandlers =
155 new HashMap<TableName, SnapshotSentinel>();
156
157 private Path rootDir;
158 private ExecutorService executorService;
159
160
161
162
163
164
165
166 private ReentrantReadWriteLock takingSnapshotLock = new ReentrantReadWriteLock(true);
167
168 public SnapshotManager() {}
169
170
171
172
173
174
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
191
192
193
194 public List<SnapshotDescription> getCompletedSnapshots() throws IOException {
195 return getCompletedSnapshots(SnapshotDescriptionUtils.getSnapshotsDir(rootDir));
196 }
197
198
199
200
201
202
203
204 private List<SnapshotDescription> getCompletedSnapshots(Path snapshotDir) throws IOException {
205 List<SnapshotDescription> snapshotDescs = new ArrayList<SnapshotDescription>();
206
207 FileSystem fs = master.getMasterFileSystem().getFileSystem();
208 if (snapshotDir == null) snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(rootDir);
209
210
211 if (!fs.exists(snapshotDir)) {
212 return snapshotDescs;
213 }
214
215
216 FileStatus[] snapshots = fs.listStatus(snapshotDir,
217 new SnapshotDescriptionUtils.CompletedSnaphotDirectoriesFilter(fs));
218 MasterCoprocessorHost cpHost = master.getMasterCoprocessorHost();
219
220 for (FileStatus snapshot : snapshots) {
221 Path info = new Path(snapshot.getPath(), SnapshotDescriptionUtils.SNAPSHOTINFO_FILE);
222
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
238 continue;
239 }
240 }
241 snapshotDescs.add(desc);
242
243
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
260
261
262
263
264 void resetTempDir() throws IOException {
265
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
276
277
278
279
280 public void deleteSnapshot(SnapshotDescription snapshot) throws SnapshotDoesNotExistException, IOException {
281
282 if (!isSnapshotCompleted(snapshot)) {
283 throw new SnapshotDoesNotExistException(snapshot);
284 }
285
286 String snapshotName = snapshot.getName();
287
288 FileSystem fs = master.getMasterFileSystem().getFileSystem();
289 Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
290
291
292 snapshot = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
293
294
295 MasterCoprocessorHost cpHost = master.getMasterCoprocessorHost();
296 if (cpHost != null) {
297 cpHost.preDeleteSnapshot(snapshot);
298 }
299
300 LOG.debug("Deleting snapshot: " + snapshotName);
301
302 if (!fs.delete(snapshotDir, true)) {
303 throw new HBaseSnapshotException("Failed to delete snapshot directory: " + snapshotDir);
304 }
305
306
307 if (cpHost != null) {
308 cpHost.postDeleteSnapshot(snapshot);
309 }
310
311 }
312
313
314
315
316
317
318
319
320
321 public boolean isSnapshotDone(SnapshotDescription expected) throws IOException {
322
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
331
332 SnapshotSentinel handler = removeSentinelIfFinished(this.snapshotHandlers, expected);
333
334
335 cleanupSentinels();
336
337 if (handler == null) {
338
339
340
341
342
343
344 if (!isSnapshotCompleted(expected)) {
345 throw new UnknownSnapshotException("Snapshot " + ssString
346 + " is not currently running or one of the known completed snapshots.");
347 }
348
349 return true;
350 }
351
352
353 try {
354 handler.rethrowExceptionIfFailed();
355 } catch (ForeignException e) {
356
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
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
380
381
382
383
384
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
404
405
406
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
415
416
417
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
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
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
449
450 workingDirFS.delete(workingDir, true);
451
452
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
467
468
469
470
471 private synchronized void snapshotDisabledTable(SnapshotDescription snapshot)
472 throws IOException {
473
474 prepareToTakeSnapshot(snapshot);
475
476
477 snapshot = snapshot.toBuilder().setType(Type.DISABLED).build();
478
479
480 DisabledTableSnapshotHandler handler =
481 new DisabledTableSnapshotHandler(snapshot, master, this);
482 snapshotTable(snapshot, handler);
483 }
484
485
486
487
488
489
490
491 private synchronized void snapshotEnabledTable(SnapshotDescription snapshot)
492 throws IOException {
493
494 prepareToTakeSnapshot(snapshot);
495
496
497 EnabledTableSnapshotHandler handler =
498 new EnabledTableSnapshotHandler(snapshot, master, this);
499 snapshotTable(snapshot, handler);
500 }
501
502
503
504
505
506
507
508
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
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
531 throw new SnapshotCreationException("Could not build snapshot handler", e, snapshot);
532 }
533 }
534
535
536
537
538
539
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
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
560 cleanupSentinels();
561
562
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
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
591 MasterCoprocessorHost cpHost = master.getMasterCoprocessorHost();
592 if (cpHost != null) {
593 cpHost.preSnapshot(snapshot, desc);
594 }
595
596
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
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
620 if (cpHost != null) {
621 cpHost.postSnapshot(snapshot, desc);
622 }
623 }
624
625 public ReadWriteLock getTakingSnapshotLock() {
626 return this.takingSnapshotLock;
627 }
628
629
630
631
632
633
634
635
636
637 public synchronized boolean isTakingAnySnapshot() {
638 return this.takingSnapshotLock.getReadHoldCount() > 0 || this.snapshotHandlers.size() > 0;
639 }
640
641
642
643
644
645
646
647
648
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
662
663 ProcedureCoordinator getCoordinator() {
664 return coordinator;
665 }
666
667
668
669
670
671
672
673
674
675
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
682 return fs.exists(snapshotDir);
683 } catch (IllegalArgumentException iae) {
684 throw new UnknownSnapshotException("Unexpected exception thrown", iae);
685 }
686 }
687
688
689
690
691
692
693
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
701 if (isTakingSnapshot(tableName)) {
702 throw new RestoreSnapshotException("Snapshot in progress on the restore table=" + tableName);
703 }
704
705
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
725
726
727
728 @Deprecated
729 public void restoreSnapshot(SnapshotDescription reqSnapshot) throws IOException {
730 restoreSnapshot(reqSnapshot, false);
731 }
732
733
734
735
736
737
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
746 if (!fs.exists(snapshotDir)) {
747 LOG.error("A Snapshot named '" + reqSnapshot.getName() + "' does not exist.");
748 throw new SnapshotDoesNotExistException(reqSnapshot);
749 }
750
751
752
753
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
761 cleanupSentinels();
762
763
764 SnapshotReferenceUtil.verifySnapshot(master.getConfiguration(), fs, manifest);
765
766
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
777 if (cpHost != null) {
778 cpHost.preRestoreSnapshot(snapshot, snapshotTableDesc);
779 }
780
781 int tableRegionCount = -1;
782 try {
783
784
785
786
787 tableRegionCount = getRegionCountOfTable(tableName);
788 int snapshotRegionCount = manifest.getRegionManifestsMap().size();
789
790
791
792
793
794 if (tableRegionCount > 0 && tableRegionCount < snapshotRegionCount) {
795 checkAndUpdateNamespaceRegionQuota(snapshotRegionCount, tableName);
796 }
797 restoreSnapshot(snapshot, snapshotTableDesc, restoreAcl);
798
799
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
807
808
809 throw e;
810 } catch (IOException e) {
811 if (tableRegionCount > 0) {
812
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
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
874
875
876
877
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
885 if (isTakingSnapshot(tableName)) {
886 throw new RestoreSnapshotException("Snapshot in progress on the restore table=" + tableName);
887 }
888
889
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
910
911
912
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
921
922
923
924
925
926
927 public boolean isRestoreDone(final SnapshotDescription snapshot) throws IOException {
928
929
930 SnapshotSentinel sentinel = removeSentinelIfFinished(this.restoreHandlers, snapshot);
931
932
933 cleanupSentinels();
934
935 if (sentinel == null) {
936
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
945 sentinel.rethrowExceptionIfFailed();
946
947
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
963
964
965
966
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
983 return null;
984 }
985
986
987 if (h.isFinished()) {
988 sentinels.remove(snapshotTable);
989 }
990
991 return h;
992 }
993
994
995
996
997
998
999
1000
1001 private void cleanupSentinels() {
1002 cleanupSentinels(this.snapshotHandlers);
1003 cleanupSentinels(this.restoreHandlers);
1004 }
1005
1006
1007
1008
1009
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
1028
1029
1030 @Override
1031 public void stop(String why) {
1032
1033 if (this.stopped) return;
1034
1035 this.stopped = true;
1036
1037 for (SnapshotSentinel snapshotHandler: this.snapshotHandlers.values()) {
1038 snapshotHandler.cancel(why);
1039 }
1040
1041
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
1061
1062
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
1074
1075
1076
1077
1078
1079
1080
1081
1082 private void checkSnapshotSupport(final Configuration conf, final MasterFileSystem mfs)
1083 throws IOException, UnsupportedOperationException {
1084
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
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
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
1108
1109
1110 if (snapshotEnabled) {
1111
1112 hfileCleaners.add(SnapshotHFileCleaner.class.getName());
1113 hfileCleaners.add(HFileLinkCleaner.class.getName());
1114
1115
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
1122 snapshotEnabled =
1123 hfileCleaners.contains(SnapshotHFileCleaner.class.getName()) &&
1124 hfileCleaners.contains(HFileLinkCleaner.class.getName());
1125
1126
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
1135 this.isSnapshotSupported = snapshotEnabled && !userDisabled;
1136
1137
1138
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
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
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 }