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
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
89
90
91
92
93
94
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
102 private static final int SNAPSHOT_WAKE_MILLIS_DEFAULT = 500;
103
104
105
106
107
108
109
110
111
112
113
114
115 private static final int SNAPSHOT_SENTINELS_CLEANUP_TIMEOUT = 60 * 1000;
116
117
118 public static final String HBASE_SNAPSHOT_ENABLED = "hbase.snapshot.enabled";
119
120
121
122
123
124 private static final String SNAPSHOT_WAKE_MILLIS_KEY = "hbase.snapshot.master.wakeMillis";
125
126
127 public static final String ONLINE_SNAPSHOT_CONTROLLER_DESCRIPTION = "online-snapshot";
128
129
130 private static final String SNAPSHOT_POOL_THREADS_KEY = "hbase.snapshot.master.threads";
131
132
133 private static final int SNAPSHOT_POOL_THREADS_DEFAULT = 1;
134
135 private boolean stopped;
136 private MasterServices master;
137 private ProcedureCoordinator coordinator;
138
139
140 private boolean isSnapshotSupported = false;
141
142
143
144
145
146 private Map<TableName, SnapshotSentinel> snapshotHandlers =
147 new HashMap<TableName, SnapshotSentinel>();
148
149
150
151
152
153 private Map<TableName, SnapshotSentinel> restoreHandlers =
154 new HashMap<TableName, SnapshotSentinel>();
155
156 private Path rootDir;
157 private ExecutorService executorService;
158
159
160
161
162
163
164
165 private KeyLocker<String> locks = new KeyLocker<String>();
166
167
168
169 public SnapshotManager() {}
170
171
172
173
174
175
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
192
193
194
195 public List<SnapshotDescription> getCompletedSnapshots() throws IOException {
196 return getCompletedSnapshots(SnapshotDescriptionUtils.getSnapshotsDir(rootDir));
197 }
198
199
200
201
202
203
204
205 private List<SnapshotDescription> getCompletedSnapshots(Path snapshotDir) throws IOException {
206 List<SnapshotDescription> snapshotDescs = new ArrayList<SnapshotDescription>();
207
208 FileSystem fs = master.getMasterFileSystem().getFileSystem();
209 if (snapshotDir == null) snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(rootDir);
210
211
212 if (!fs.exists(snapshotDir)) {
213 return snapshotDescs;
214 }
215
216
217 FileStatus[] snapshots = fs.listStatus(snapshotDir,
218 new SnapshotDescriptionUtils.CompletedSnaphotDirectoriesFilter(fs));
219 MasterCoprocessorHost cpHost = master.getMasterCoprocessorHost();
220
221 for (FileStatus snapshot : snapshots) {
222 Path info = new Path(snapshot.getPath(), SnapshotDescriptionUtils.SNAPSHOTINFO_FILE);
223
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
239 continue;
240 }
241 }
242 snapshotDescs.add(desc);
243
244
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
261
262
263
264
265 void resetTempDir() throws IOException {
266
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
277
278
279
280
281 public void deleteSnapshot(SnapshotDescription snapshot) throws SnapshotDoesNotExistException, IOException {
282
283 if (!isSnapshotCompleted(snapshot)) {
284 throw new SnapshotDoesNotExistException(snapshot);
285 }
286
287 String snapshotName = snapshot.getName();
288
289 FileSystem fs = master.getMasterFileSystem().getFileSystem();
290 Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
291
292
293 snapshot = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
294
295
296 MasterCoprocessorHost cpHost = master.getMasterCoprocessorHost();
297 if (cpHost != null) {
298 cpHost.preDeleteSnapshot(snapshot);
299 }
300
301 LOG.debug("Deleting snapshot: " + snapshotName);
302
303 if (!fs.delete(snapshotDir, true)) {
304 throw new HBaseSnapshotException("Failed to delete snapshot directory: " + snapshotDir);
305 }
306
307
308 if (cpHost != null) {
309 cpHost.postDeleteSnapshot(snapshot);
310 }
311
312 }
313
314
315
316
317
318
319
320
321
322 public boolean isSnapshotDone(SnapshotDescription expected) throws IOException {
323
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
332
333 SnapshotSentinel handler = removeSentinelIfFinished(this.snapshotHandlers, expected);
334
335
336 cleanupSentinels();
337
338 if (handler == null) {
339
340
341
342
343
344
345 if (!isSnapshotCompleted(expected)) {
346 throw new UnknownSnapshotException("Snapshot " + ssString
347 + " is not currently running or one of the known completed snapshots.");
348 }
349
350 return true;
351 }
352
353
354 try {
355 handler.rethrowExceptionIfFailed();
356 } catch (ForeignException e) {
357
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
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
381
382
383
384
385
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
405
406
407
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
416
417
418
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
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
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
449
450 fs.delete(workingDir, true);
451
452
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
467
468
469
470 private synchronized void snapshotDisabledTable(SnapshotDescription snapshot)
471 throws HBaseSnapshotException {
472
473 prepareToTakeSnapshot(snapshot);
474
475
476 snapshot = snapshot.toBuilder().setType(Type.DISABLED).build();
477
478
479 DisabledTableSnapshotHandler handler =
480 new DisabledTableSnapshotHandler(snapshot, master, this);
481 snapshotTable(snapshot, handler);
482 }
483
484
485
486
487
488
489 private synchronized void snapshotEnabledTable(SnapshotDescription snapshot)
490 throws HBaseSnapshotException {
491
492 prepareToTakeSnapshot(snapshot);
493
494
495 EnabledTableSnapshotHandler handler =
496 new EnabledTableSnapshotHandler(snapshot, master, this);
497 snapshotTable(snapshot, handler);
498 }
499
500
501
502
503
504
505
506
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
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
527 throw new SnapshotCreationException("Could not build snapshot handler", e, snapshot);
528 }
529 }
530
531
532
533
534
535
536
537
538 public void takeSnapshot(SnapshotDescription snapshot) throws IOException {
539
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
548 cleanupSentinels();
549
550
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
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
579 MasterCoprocessorHost cpHost = master.getMasterCoprocessorHost();
580 if (cpHost != null) {
581 cpHost.preSnapshot(snapshot, desc);
582 }
583
584
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
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
608 if (cpHost != null) {
609 cpHost.postSnapshot(snapshot, desc);
610 }
611 }
612
613
614
615
616
617
618
619
620
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
634
635 ProcedureCoordinator getCoordinator() {
636 return coordinator;
637 }
638
639
640
641
642
643
644
645
646
647
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
654 return fs.exists(snapshotDir);
655 } catch (IllegalArgumentException iae) {
656 throw new UnknownSnapshotException("Unexpected exception thrown", iae);
657 }
658 }
659
660
661
662
663
664
665
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
673 if (isTakingSnapshot(tableName)) {
674 throw new RestoreSnapshotException("Snapshot in progress on the restore table=" + tableName);
675 }
676
677
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
697
698
699
700 @Deprecated
701 public void restoreSnapshot(SnapshotDescription reqSnapshot) throws IOException {
702 restoreSnapshot(reqSnapshot, false);
703 }
704
705
706
707
708
709
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
718 if (!fs.exists(snapshotDir)) {
719 LOG.error("A Snapshot named '" + reqSnapshot.getName() + "' does not exist.");
720 throw new SnapshotDoesNotExistException(reqSnapshot);
721 }
722
723
724
725
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
733 cleanupSentinels();
734
735
736 SnapshotReferenceUtil.verifySnapshot(master.getConfiguration(), fs, manifest);
737
738
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
749 if (cpHost != null) {
750 cpHost.preRestoreSnapshot(snapshot, snapshotTableDesc);
751 }
752
753 int tableRegionCount = -1;
754 try {
755
756
757
758
759 tableRegionCount = getRegionCountOfTable(tableName);
760 int snapshotRegionCount = manifest.getRegionManifestsMap().size();
761
762
763
764
765
766 if (tableRegionCount > 0 && tableRegionCount < snapshotRegionCount) {
767 checkAndUpdateNamespaceRegionQuota(snapshotRegionCount, tableName);
768 }
769 restoreSnapshot(snapshot, snapshotTableDesc, restoreAcl);
770
771
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
779
780
781 throw e;
782 } catch (IOException e) {
783 if (tableRegionCount > 0) {
784
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
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
846
847
848
849
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
857 if (isTakingSnapshot(tableName)) {
858 throw new RestoreSnapshotException("Snapshot in progress on the restore table=" + tableName);
859 }
860
861
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
882
883
884
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
893
894
895
896
897
898
899 public boolean isRestoreDone(final SnapshotDescription snapshot) throws IOException {
900
901
902 SnapshotSentinel sentinel = removeSentinelIfFinished(this.restoreHandlers, snapshot);
903
904
905 cleanupSentinels();
906
907 if (sentinel == null) {
908
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
917 sentinel.rethrowExceptionIfFailed();
918
919
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
935
936
937
938
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
955 return null;
956 }
957
958
959 if (h.isFinished()) {
960 sentinels.remove(snapshotTable);
961 }
962
963 return h;
964 }
965
966
967
968
969
970
971
972
973 private void cleanupSentinels() {
974 cleanupSentinels(this.snapshotHandlers);
975 cleanupSentinels(this.restoreHandlers);
976 }
977
978
979
980
981
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
1000
1001
1002 @Override
1003 public void stop(String why) {
1004
1005 if (this.stopped) return;
1006
1007 this.stopped = true;
1008
1009 for (SnapshotSentinel snapshotHandler: this.snapshotHandlers.values()) {
1010 snapshotHandler.cancel(why);
1011 }
1012
1013
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
1033
1034
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
1046
1047
1048
1049
1050
1051
1052
1053
1054 private void checkSnapshotSupport(final Configuration conf, final MasterFileSystem mfs)
1055 throws IOException, UnsupportedOperationException {
1056
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
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
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
1080
1081
1082 if (snapshotEnabled) {
1083
1084 hfileCleaners.add(SnapshotHFileCleaner.class.getName());
1085 hfileCleaners.add(HFileLinkCleaner.class.getName());
1086
1087
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
1094 snapshotEnabled =
1095 hfileCleaners.contains(SnapshotHFileCleaner.class.getName()) &&
1096 hfileCleaners.contains(HFileLinkCleaner.class.getName());
1097
1098
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
1107 this.isSnapshotSupported = snapshotEnabled && !userDisabled;
1108
1109
1110
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
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
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 }