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.security.AccessDeniedException;
69 import org.apache.hadoop.hbase.security.User;
70 import org.apache.hadoop.hbase.snapshot.ClientSnapshotDescriptionUtils;
71 import org.apache.hadoop.hbase.snapshot.HBaseSnapshotException;
72 import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
73 import org.apache.hadoop.hbase.snapshot.RestoreSnapshotHelper;
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.zookeeper.KeeperException;
85
86
87
88
89
90
91
92
93
94
95 @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
96 @InterfaceStability.Unstable
97 public class SnapshotManager extends MasterProcedureManager implements Stoppable {
98 private static final Log LOG = LogFactory.getLog(SnapshotManager.class);
99
100
101 private static final int SNAPSHOT_WAKE_MILLIS_DEFAULT = 500;
102
103
104
105
106
107
108
109
110
111
112
113
114 private static final int SNAPSHOT_SENTINELS_CLEANUP_TIMEOUT = 60 * 1000;
115
116
117 public static final String HBASE_SNAPSHOT_ENABLED = "hbase.snapshot.enabled";
118
119
120
121
122
123 private static final String SNAPSHOT_WAKE_MILLIS_KEY = "hbase.snapshot.master.wakeMillis";
124
125
126 public static final String ONLINE_SNAPSHOT_CONTROLLER_DESCRIPTION = "online-snapshot";
127
128
129 private static final String SNAPSHOT_POOL_THREADS_KEY = "hbase.snapshot.master.threads";
130
131
132 private static final int SNAPSHOT_POOL_THREADS_DEFAULT = 1;
133
134 private boolean stopped;
135 private MasterServices master;
136 private ProcedureCoordinator coordinator;
137
138
139 private boolean isSnapshotSupported = false;
140
141
142
143
144
145 private Map<TableName, SnapshotSentinel> snapshotHandlers =
146 new HashMap<TableName, SnapshotSentinel>();
147
148
149
150
151
152 private Map<TableName, SnapshotSentinel> restoreHandlers =
153 new HashMap<TableName, SnapshotSentinel>();
154
155 private Path rootDir;
156 private ExecutorService executorService;
157
158 public SnapshotManager() {}
159
160
161
162
163
164
165
166 public SnapshotManager(final MasterServices master, final MetricsMaster metricsMaster,
167 ProcedureCoordinator coordinator, ExecutorService pool)
168 throws IOException, UnsupportedOperationException {
169 this.master = master;
170
171 this.rootDir = master.getMasterFileSystem().getRootDir();
172 checkSnapshotSupport(master.getConfiguration(), master.getMasterFileSystem());
173
174 this.coordinator = coordinator;
175 this.executorService = pool;
176 resetTempDir();
177 }
178
179
180
181
182
183
184 public List<SnapshotDescription> getCompletedSnapshots() throws IOException {
185 return getCompletedSnapshots(SnapshotDescriptionUtils.getSnapshotsDir(rootDir));
186 }
187
188
189
190
191
192
193
194 private List<SnapshotDescription> getCompletedSnapshots(Path snapshotDir) throws IOException {
195 List<SnapshotDescription> snapshotDescs = new ArrayList<SnapshotDescription>();
196
197 FileSystem fs = master.getMasterFileSystem().getFileSystem();
198 if (snapshotDir == null) snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(rootDir);
199
200
201 if (!fs.exists(snapshotDir)) {
202 return snapshotDescs;
203 }
204
205
206 FileStatus[] snapshots = fs.listStatus(snapshotDir,
207 new SnapshotDescriptionUtils.CompletedSnaphotDirectoriesFilter(fs));
208 MasterCoprocessorHost cpHost = master.getMasterCoprocessorHost();
209
210 for (FileStatus snapshot : snapshots) {
211 Path info = new Path(snapshot.getPath(), SnapshotDescriptionUtils.SNAPSHOTINFO_FILE);
212
213 if (!fs.exists(info)) {
214 LOG.error("Snapshot information for " + snapshot.getPath() + " doesn't exist");
215 continue;
216 }
217 FSDataInputStream in = null;
218 try {
219 in = fs.open(info);
220 SnapshotDescription desc = SnapshotDescription.parseFrom(in);
221 if (cpHost != null) {
222 try {
223 cpHost.preListSnapshot(desc);
224 } catch (AccessDeniedException e) {
225 LOG.warn("Current user does not have access to " + desc.getName() + " snapshot. "
226 + "Either you should be owner of this snapshot or admin user.");
227
228 continue;
229 }
230 }
231 snapshotDescs.add(desc);
232
233
234 if (cpHost != null) {
235 cpHost.postListSnapshot(desc);
236 }
237 } catch (IOException e) {
238 LOG.warn("Found a corrupted snapshot " + snapshot.getPath(), e);
239 } finally {
240 if (in != null) {
241 in.close();
242 }
243 }
244 }
245 return snapshotDescs;
246 }
247
248
249
250
251
252
253
254 void resetTempDir() throws IOException {
255
256 Path tmpdir = SnapshotDescriptionUtils.getWorkingSnapshotDir(rootDir);
257 if (master.getMasterFileSystem().getFileSystem().exists(tmpdir)) {
258 if (!master.getMasterFileSystem().getFileSystem().delete(tmpdir, true)) {
259 LOG.warn("Couldn't delete working snapshot directory: " + tmpdir);
260 }
261 }
262 }
263
264
265
266
267
268
269
270 public void deleteSnapshot(SnapshotDescription snapshot) throws SnapshotDoesNotExistException, IOException {
271
272 if (!isSnapshotCompleted(snapshot)) {
273 throw new SnapshotDoesNotExistException(snapshot);
274 }
275
276 String snapshotName = snapshot.getName();
277
278 FileSystem fs = master.getMasterFileSystem().getFileSystem();
279 Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
280
281
282 snapshot = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
283
284
285 MasterCoprocessorHost cpHost = master.getMasterCoprocessorHost();
286 if (cpHost != null) {
287 cpHost.preDeleteSnapshot(snapshot);
288 }
289
290 LOG.debug("Deleting snapshot: " + snapshotName);
291
292 if (!fs.delete(snapshotDir, true)) {
293 throw new HBaseSnapshotException("Failed to delete snapshot directory: " + snapshotDir);
294 }
295
296
297 if (cpHost != null) {
298 cpHost.postDeleteSnapshot(snapshot);
299 }
300
301 }
302
303
304
305
306
307
308
309
310
311 public boolean isSnapshotDone(SnapshotDescription expected) throws IOException {
312
313 if (expected == null) {
314 throw new UnknownSnapshotException(
315 "No snapshot name passed in request, can't figure out which snapshot you want to check.");
316 }
317
318 String ssString = ClientSnapshotDescriptionUtils.toString(expected);
319
320
321
322 SnapshotSentinel handler = removeSentinelIfFinished(this.snapshotHandlers, expected);
323
324
325 cleanupSentinels();
326
327 if (handler == null) {
328
329
330
331
332
333
334 if (!isSnapshotCompleted(expected)) {
335 throw new UnknownSnapshotException("Snapshot " + ssString
336 + " is not currently running or one of the known completed snapshots.");
337 }
338
339 return true;
340 }
341
342
343 try {
344 handler.rethrowExceptionIfFailed();
345 } catch (ForeignException e) {
346
347 String status;
348 Procedure p = coordinator.getProcedure(expected.getName());
349 if (p != null) {
350 status = p.getStatus();
351 } else {
352 status = expected.getName() + " not found in proclist " + coordinator.getProcedureNames();
353 }
354 throw new HBaseSnapshotException("Snapshot " + ssString + " had an error. " + status, e,
355 expected);
356 }
357
358
359 if (handler.isFinished()) {
360 LOG.debug("Snapshot '" + ssString + "' has completed, notifying client.");
361 return true;
362 } else if (LOG.isDebugEnabled()) {
363 LOG.debug("Snapshoting '" + ssString + "' is still in progress!");
364 }
365 return false;
366 }
367
368
369
370
371
372
373
374
375
376 synchronized boolean isTakingSnapshot(final SnapshotDescription snapshot) {
377 TableName snapshotTable = TableName.valueOf(snapshot.getTable());
378 if (isTakingSnapshot(snapshotTable)) {
379 return true;
380 }
381 Iterator<Map.Entry<TableName, SnapshotSentinel>> it = this.snapshotHandlers.entrySet().iterator();
382 while (it.hasNext()) {
383 Map.Entry<TableName, SnapshotSentinel> entry = it.next();
384 SnapshotSentinel sentinel = entry.getValue();
385 if (snapshot.getName().equals(sentinel.getSnapshot().getName()) && !sentinel.isFinished()) {
386 return true;
387 }
388 }
389 return false;
390 }
391
392
393
394
395
396
397
398 synchronized boolean isTakingSnapshot(final TableName tableName) {
399 SnapshotSentinel handler = this.snapshotHandlers.get(tableName);
400 return handler != null && !handler.isFinished();
401 }
402
403
404
405
406
407
408
409 private synchronized void prepareToTakeSnapshot(SnapshotDescription snapshot)
410 throws HBaseSnapshotException {
411 FileSystem fs = master.getMasterFileSystem().getFileSystem();
412 Path workingDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(snapshot, rootDir);
413 TableName snapshotTable =
414 TableName.valueOf(snapshot.getTable());
415
416
417 if (isTakingSnapshot(snapshot)) {
418 SnapshotSentinel handler = this.snapshotHandlers.get(snapshotTable);
419 throw new SnapshotCreationException("Rejected taking "
420 + ClientSnapshotDescriptionUtils.toString(snapshot)
421 + " because we are already running another snapshot "
422 + (handler != null ? ("on the same table " +
423 ClientSnapshotDescriptionUtils.toString(handler.getSnapshot()))
424 : "with the same name"), snapshot);
425 }
426
427
428 if (isRestoringTable(snapshotTable)) {
429 SnapshotSentinel handler = restoreHandlers.get(snapshotTable);
430 throw new SnapshotCreationException("Rejected taking "
431 + ClientSnapshotDescriptionUtils.toString(snapshot)
432 + " because we are already have a restore in progress on the same snapshot "
433 + ClientSnapshotDescriptionUtils.toString(handler.getSnapshot()), snapshot);
434 }
435
436 try {
437
438
439 fs.delete(workingDir, true);
440
441
442 if (!fs.mkdirs(workingDir)) {
443 throw new SnapshotCreationException("Couldn't create working directory (" + workingDir
444 + ") for snapshot" , snapshot);
445 }
446 } catch (HBaseSnapshotException e) {
447 throw e;
448 } catch (IOException e) {
449 throw new SnapshotCreationException(
450 "Exception while checking to see if snapshot could be started.", e, snapshot);
451 }
452 }
453
454
455
456
457
458
459 private synchronized void snapshotDisabledTable(SnapshotDescription snapshot)
460 throws HBaseSnapshotException {
461
462 prepareToTakeSnapshot(snapshot);
463
464
465 snapshot = snapshot.toBuilder().setType(Type.DISABLED).build();
466
467
468 DisabledTableSnapshotHandler handler =
469 new DisabledTableSnapshotHandler(snapshot, master);
470 snapshotTable(snapshot, handler);
471 }
472
473
474
475
476
477
478 private synchronized void snapshotEnabledTable(SnapshotDescription snapshot)
479 throws HBaseSnapshotException {
480
481 prepareToTakeSnapshot(snapshot);
482
483
484 EnabledTableSnapshotHandler handler =
485 new EnabledTableSnapshotHandler(snapshot, master, this);
486 snapshotTable(snapshot, handler);
487 }
488
489
490
491
492
493
494
495
496
497 private synchronized void snapshotTable(SnapshotDescription snapshot,
498 final TakeSnapshotHandler handler) throws HBaseSnapshotException {
499 try {
500 handler.prepare();
501 this.executorService.submit(handler);
502 this.snapshotHandlers.put(TableName.valueOf(snapshot.getTable()), handler);
503 } catch (Exception e) {
504
505 Path workingDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(snapshot, rootDir);
506 try {
507 if (!this.master.getMasterFileSystem().getFileSystem().delete(workingDir, true)) {
508 LOG.error("Couldn't delete working directory (" + workingDir + " for snapshot:" +
509 ClientSnapshotDescriptionUtils.toString(snapshot));
510 }
511 } catch (IOException e1) {
512 LOG.error("Couldn't delete working directory (" + workingDir + " for snapshot:" +
513 ClientSnapshotDescriptionUtils.toString(snapshot));
514 }
515
516 throw new SnapshotCreationException("Could not build snapshot handler", e, snapshot);
517 }
518 }
519
520
521
522
523
524
525
526
527 public void takeSnapshot(SnapshotDescription snapshot) throws IOException {
528
529 if (isSnapshotCompleted(snapshot)) {
530 throw new SnapshotExistsException("Snapshot '" + snapshot.getName()
531 + "' already stored on the filesystem.", snapshot);
532 }
533
534 LOG.debug("No existing snapshot, attempting snapshot...");
535
536
537 cleanupSentinels();
538
539
540 HTableDescriptor desc = null;
541 try {
542 desc = master.getTableDescriptors().get(
543 TableName.valueOf(snapshot.getTable()));
544 } catch (FileNotFoundException e) {
545 String msg = "Table:" + snapshot.getTable() + " info doesn't exist!";
546 LOG.error(msg);
547 throw new SnapshotCreationException(msg, e, snapshot);
548 } catch (IOException e) {
549 throw new SnapshotCreationException("Error while geting table description for table "
550 + snapshot.getTable(), e, snapshot);
551 }
552 if (desc == null) {
553 throw new SnapshotCreationException("Table '" + snapshot.getTable()
554 + "' doesn't exist, can't take snapshot.", snapshot);
555 }
556 SnapshotDescription.Builder builder = snapshot.toBuilder();
557
558 if (!snapshot.hasVersion()) {
559 builder.setVersion(SnapshotDescriptionUtils.SNAPSHOT_LAYOUT_VERSION);
560 }
561 User user = RpcServer.getRequestUser();
562 if (User.isHBaseSecurityEnabled(master.getConfiguration()) && user != null) {
563 builder.setOwner(user.getShortName());
564 }
565 snapshot = builder.build();
566
567
568 MasterCoprocessorHost cpHost = master.getMasterCoprocessorHost();
569 if (cpHost != null) {
570 cpHost.preSnapshot(snapshot, desc);
571 }
572
573
574 TableName snapshotTable = TableName.valueOf(snapshot.getTable());
575 AssignmentManager assignmentMgr = master.getAssignmentManager();
576 if (assignmentMgr.getTableStateManager().isTableState(snapshotTable,
577 ZooKeeperProtos.Table.State.ENABLED)) {
578 LOG.debug("Table enabled, starting distributed snapshot.");
579 snapshotEnabledTable(snapshot);
580 LOG.debug("Started snapshot: " + ClientSnapshotDescriptionUtils.toString(snapshot));
581 }
582
583 else if (assignmentMgr.getTableStateManager().isTableState(snapshotTable,
584 ZooKeeperProtos.Table.State.DISABLED)) {
585 LOG.debug("Table is disabled, running snapshot entirely on master.");
586 snapshotDisabledTable(snapshot);
587 LOG.debug("Started snapshot: " + ClientSnapshotDescriptionUtils.toString(snapshot));
588 } else {
589 LOG.error("Can't snapshot table '" + snapshot.getTable()
590 + "', isn't open or closed, we don't know what to do!");
591 TablePartiallyOpenException tpoe = new TablePartiallyOpenException(snapshot.getTable()
592 + " isn't fully open.");
593 throw new SnapshotCreationException("Table is not entirely open or closed", tpoe, snapshot);
594 }
595
596
597 if (cpHost != null) {
598 cpHost.postSnapshot(snapshot, desc);
599 }
600 }
601
602
603
604
605
606
607
608
609
610
611 public synchronized void setSnapshotHandlerForTesting(
612 final TableName tableName,
613 final SnapshotSentinel handler) {
614 if (handler != null) {
615 this.snapshotHandlers.put(tableName, handler);
616 } else {
617 this.snapshotHandlers.remove(tableName);
618 }
619 }
620
621
622
623
624 ProcedureCoordinator getCoordinator() {
625 return coordinator;
626 }
627
628
629
630
631
632
633
634
635
636
637
638 private boolean isSnapshotCompleted(SnapshotDescription snapshot) throws IOException {
639 try {
640 final Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshot, rootDir);
641 FileSystem fs = master.getMasterFileSystem().getFileSystem();
642
643 return fs.exists(snapshotDir);
644 } catch (IllegalArgumentException iae) {
645 throw new UnknownSnapshotException("Unexpected exception thrown", iae);
646 }
647 }
648
649
650
651
652
653
654
655
656 synchronized void cloneSnapshot(final SnapshotDescription snapshot,
657 final HTableDescriptor hTableDescriptor) throws HBaseSnapshotException {
658 TableName tableName = hTableDescriptor.getTableName();
659
660
661 if (isTakingSnapshot(tableName)) {
662 throw new RestoreSnapshotException("Snapshot in progress on the restore table=" + tableName);
663 }
664
665
666 if (isRestoringTable(tableName)) {
667 throw new RestoreSnapshotException("Restore already in progress on the table=" + tableName);
668 }
669
670 try {
671 CloneSnapshotHandler handler =
672 new CloneSnapshotHandler(master, snapshot, hTableDescriptor).prepare();
673 this.executorService.submit(handler);
674 this.restoreHandlers.put(tableName, handler);
675 } catch (Exception e) {
676 String msg = "Couldn't clone the snapshot=" + ClientSnapshotDescriptionUtils.toString(snapshot) +
677 " on table=" + tableName;
678 LOG.error(msg, e);
679 throw new RestoreSnapshotException(msg, e);
680 }
681 }
682
683
684
685
686
687
688 public void restoreSnapshot(SnapshotDescription reqSnapshot) throws IOException {
689 FileSystem fs = master.getMasterFileSystem().getFileSystem();
690 Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(reqSnapshot, rootDir);
691 MasterCoprocessorHost cpHost = master.getMasterCoprocessorHost();
692
693
694 if (!fs.exists(snapshotDir)) {
695 LOG.error("A Snapshot named '" + reqSnapshot.getName() + "' does not exist.");
696 throw new SnapshotDoesNotExistException(reqSnapshot);
697 }
698
699
700
701
702 SnapshotDescription snapshot = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
703 SnapshotManifest manifest = SnapshotManifest.open(master.getConfiguration(), fs,
704 snapshotDir, snapshot);
705 HTableDescriptor snapshotTableDesc = manifest.getTableDescriptor();
706 TableName tableName = TableName.valueOf(reqSnapshot.getTable());
707
708
709 cleanupSentinels();
710
711
712 SnapshotReferenceUtil.verifySnapshot(master.getConfiguration(), fs, manifest);
713
714
715 if (MetaTableAccessor.tableExists(master.getConnection(), tableName)) {
716 if (master.getAssignmentManager().getTableStateManager().isTableState(
717 TableName.valueOf(snapshot.getTable()), ZooKeeperProtos.Table.State.ENABLED)) {
718 throw new UnsupportedOperationException("Table '" +
719 TableName.valueOf(snapshot.getTable()) + "' must be disabled in order to " +
720 "perform a restore operation" +
721 ".");
722 }
723
724
725 if (cpHost != null) {
726 cpHost.preRestoreSnapshot(reqSnapshot, snapshotTableDesc);
727 }
728 try {
729
730 checkAndUpdateNamespaceRegionQuota(manifest, tableName);
731 restoreSnapshot(snapshot, snapshotTableDesc);
732 } catch (IOException e) {
733 this.master.getMasterQuotaManager().removeTableFromNamespaceQuota(tableName);
734 LOG.error("Exception occurred while restoring the snapshot " + snapshot.getName()
735 + " as table " + tableName.getNameAsString(), e);
736 throw e;
737 }
738 LOG.info("Restore snapshot=" + snapshot.getName() + " as table=" + tableName);
739
740 if (cpHost != null) {
741 cpHost.postRestoreSnapshot(reqSnapshot, snapshotTableDesc);
742 }
743 } else {
744 HTableDescriptor htd = RestoreSnapshotHelper.cloneTableSchema(snapshotTableDesc, tableName);
745 if (cpHost != null) {
746 cpHost.preCloneSnapshot(reqSnapshot, htd);
747 }
748 try {
749 checkAndUpdateNamespaceQuota(manifest, tableName);
750 cloneSnapshot(snapshot, htd);
751 } catch (IOException e) {
752 this.master.getMasterQuotaManager().removeTableFromNamespaceQuota(tableName);
753 LOG.error("Exception occurred while cloning the snapshot " + snapshot.getName()
754 + " as table " + tableName.getNameAsString(), e);
755 throw e;
756 }
757 LOG.info("Clone snapshot=" + snapshot.getName() + " as table=" + tableName);
758
759 if (cpHost != null) {
760 cpHost.postCloneSnapshot(reqSnapshot, htd);
761 }
762 }
763 }
764
765 private void checkAndUpdateNamespaceQuota(SnapshotManifest manifest, TableName tableName)
766 throws IOException {
767 if (this.master.getMasterQuotaManager().isQuotaEnabled()) {
768 this.master.getMasterQuotaManager().checkNamespaceTableAndRegionQuota(tableName,
769 manifest.getRegionManifestsMap().size());
770 }
771 }
772
773 private void checkAndUpdateNamespaceRegionQuota(SnapshotManifest manifest, TableName tableName)
774 throws IOException {
775 if (this.master.getMasterQuotaManager().isQuotaEnabled()) {
776 this.master.getMasterQuotaManager().checkAndUpdateNamespaceRegionQuota(tableName,
777 manifest.getRegionManifestsMap().size());
778 }
779 }
780
781
782
783
784
785
786
787
788 private synchronized void restoreSnapshot(final SnapshotDescription snapshot,
789 final HTableDescriptor hTableDescriptor) throws HBaseSnapshotException {
790 TableName tableName = hTableDescriptor.getTableName();
791
792
793 if (isTakingSnapshot(tableName)) {
794 throw new RestoreSnapshotException("Snapshot in progress on the restore table=" + tableName);
795 }
796
797
798 if (isRestoringTable(tableName)) {
799 throw new RestoreSnapshotException("Restore already in progress on the table=" + tableName);
800 }
801
802 try {
803 RestoreSnapshotHandler handler =
804 new RestoreSnapshotHandler(master, snapshot, hTableDescriptor).prepare();
805 this.executorService.submit(handler);
806 restoreHandlers.put(tableName, handler);
807 } catch (Exception e) {
808 String msg = "Couldn't restore the snapshot=" + ClientSnapshotDescriptionUtils.toString(
809 snapshot) +
810 " on table=" + tableName;
811 LOG.error(msg, e);
812 throw new RestoreSnapshotException(msg, e);
813 }
814 }
815
816
817
818
819
820
821
822 private synchronized boolean isRestoringTable(final TableName tableName) {
823 SnapshotSentinel sentinel = this.restoreHandlers.get(tableName);
824 return(sentinel != null && !sentinel.isFinished());
825 }
826
827
828
829
830
831
832
833
834
835 public boolean isRestoreDone(final SnapshotDescription snapshot) throws IOException {
836
837
838 SnapshotSentinel sentinel = removeSentinelIfFinished(this.restoreHandlers, snapshot);
839
840
841 cleanupSentinels();
842
843 if (sentinel == null) {
844
845 return true;
846 }
847
848 LOG.debug("Verify snapshot=" + snapshot.getName() + " against="
849 + sentinel.getSnapshot().getName() + " table=" +
850 TableName.valueOf(snapshot.getTable()));
851
852
853 sentinel.rethrowExceptionIfFailed();
854
855
856 if (sentinel.isFinished()) {
857 LOG.debug("Restore snapshot=" + ClientSnapshotDescriptionUtils.toString(snapshot) +
858 " has completed. Notifying the client.");
859 return true;
860 }
861
862 if (LOG.isDebugEnabled()) {
863 LOG.debug("Sentinel is not yet finished with restoring snapshot=" +
864 ClientSnapshotDescriptionUtils.toString(snapshot));
865 }
866 return false;
867 }
868
869
870
871
872
873
874
875
876 private synchronized SnapshotSentinel removeSentinelIfFinished(
877 final Map<TableName, SnapshotSentinel> sentinels,
878 final SnapshotDescription snapshot) {
879 if (!snapshot.hasTable()) {
880 return null;
881 }
882
883 TableName snapshotTable = TableName.valueOf(snapshot.getTable());
884 SnapshotSentinel h = sentinels.get(snapshotTable);
885 if (h == null) {
886 return null;
887 }
888
889 if (!h.getSnapshot().getName().equals(snapshot.getName())) {
890
891 return null;
892 }
893
894
895 if (h.isFinished()) {
896 sentinels.remove(snapshotTable);
897 }
898
899 return h;
900 }
901
902
903
904
905
906
907
908
909 private void cleanupSentinels() {
910 cleanupSentinels(this.snapshotHandlers);
911 cleanupSentinels(this.restoreHandlers);
912 }
913
914
915
916
917
918
919 private synchronized void cleanupSentinels(final Map<TableName, SnapshotSentinel> sentinels) {
920 long currentTime = EnvironmentEdgeManager.currentTime();
921 Iterator<Map.Entry<TableName, SnapshotSentinel>> it =
922 sentinels.entrySet().iterator();
923 while (it.hasNext()) {
924 Map.Entry<TableName, SnapshotSentinel> entry = it.next();
925 SnapshotSentinel sentinel = entry.getValue();
926 if (sentinel.isFinished() &&
927 (currentTime - sentinel.getCompletionTimestamp()) > SNAPSHOT_SENTINELS_CLEANUP_TIMEOUT)
928 {
929 it.remove();
930 }
931 }
932 }
933
934
935
936
937
938 @Override
939 public void stop(String why) {
940
941 if (this.stopped) return;
942
943 this.stopped = true;
944
945 for (SnapshotSentinel snapshotHandler: this.snapshotHandlers.values()) {
946 snapshotHandler.cancel(why);
947 }
948
949
950 for (SnapshotSentinel restoreHandler: this.restoreHandlers.values()) {
951 restoreHandler.cancel(why);
952 }
953 try {
954 if (coordinator != null) {
955 coordinator.close();
956 }
957 } catch (IOException e) {
958 LOG.error("stop ProcedureCoordinator error", e);
959 }
960 }
961
962 @Override
963 public boolean isStopped() {
964 return this.stopped;
965 }
966
967
968
969
970
971
972 public void checkSnapshotSupport() throws UnsupportedOperationException {
973 if (!this.isSnapshotSupported) {
974 throw new UnsupportedOperationException(
975 "To use snapshots, You must add to the hbase-site.xml of the HBase Master: '" +
976 HBASE_SNAPSHOT_ENABLED + "' property with value 'true'.");
977 }
978 }
979
980
981
982
983
984
985
986
987
988
989
990 private void checkSnapshotSupport(final Configuration conf, final MasterFileSystem mfs)
991 throws IOException, UnsupportedOperationException {
992
993 String enabled = conf.get(HBASE_SNAPSHOT_ENABLED);
994 boolean snapshotEnabled = conf.getBoolean(HBASE_SNAPSHOT_ENABLED, false);
995 boolean userDisabled = (enabled != null && enabled.trim().length() > 0 && !snapshotEnabled);
996
997
998 Set<String> hfileCleaners = new HashSet<String>();
999 String[] cleaners = conf.getStrings(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS);
1000 if (cleaners != null) Collections.addAll(hfileCleaners, cleaners);
1001
1002 Set<String> logCleaners = new HashSet<String>();
1003 cleaners = conf.getStrings(HConstants.HBASE_MASTER_LOGCLEANER_PLUGINS);
1004 if (cleaners != null) Collections.addAll(logCleaners, cleaners);
1005
1006
1007 Path oldSnapshotDir = new Path(mfs.getRootDir(), HConstants.OLD_SNAPSHOT_DIR_NAME);
1008 FileSystem fs = mfs.getFileSystem();
1009 List<SnapshotDescription> ss = getCompletedSnapshots(new Path(rootDir, oldSnapshotDir));
1010 if (ss != null && !ss.isEmpty()) {
1011 LOG.error("Snapshots from an earlier release were found under: " + oldSnapshotDir);
1012 LOG.error("Please rename the directory as " + HConstants.SNAPSHOT_DIR_NAME);
1013 }
1014
1015
1016
1017
1018 if (snapshotEnabled) {
1019
1020 hfileCleaners.add(SnapshotHFileCleaner.class.getName());
1021 hfileCleaners.add(HFileLinkCleaner.class.getName());
1022 logCleaners.add(SnapshotLogCleaner.class.getName());
1023
1024
1025 conf.setStrings(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS,
1026 hfileCleaners.toArray(new String[hfileCleaners.size()]));
1027 conf.setStrings(HConstants.HBASE_MASTER_LOGCLEANER_PLUGINS,
1028 logCleaners.toArray(new String[logCleaners.size()]));
1029 } else {
1030
1031 snapshotEnabled = logCleaners.contains(SnapshotLogCleaner.class.getName()) &&
1032 hfileCleaners.contains(SnapshotHFileCleaner.class.getName()) &&
1033 hfileCleaners.contains(HFileLinkCleaner.class.getName());
1034
1035
1036 if (snapshotEnabled) {
1037 LOG.warn("Snapshot log and hfile cleaners are present in the configuration, " +
1038 "but the '" + HBASE_SNAPSHOT_ENABLED + "' property " +
1039 (userDisabled ? "is set to 'false'." : "is not set."));
1040 }
1041 }
1042
1043
1044 this.isSnapshotSupported = snapshotEnabled && !userDisabled;
1045
1046
1047
1048 if (!snapshotEnabled) {
1049 LOG.info("Snapshot feature is not enabled, missing log and hfile cleaners.");
1050 Path snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(mfs.getRootDir());
1051 if (fs.exists(snapshotDir)) {
1052 FileStatus[] snapshots = FSUtils.listStatus(fs, snapshotDir,
1053 new SnapshotDescriptionUtils.CompletedSnaphotDirectoriesFilter(fs));
1054 if (snapshots != null) {
1055 LOG.error("Snapshots are present, but cleaners are not enabled.");
1056 checkSnapshotSupport();
1057 }
1058 }
1059 }
1060 }
1061
1062 @Override
1063 public void initialize(MasterServices master, MetricsMaster metricsMaster) throws KeeperException,
1064 IOException, UnsupportedOperationException {
1065 this.master = master;
1066
1067 this.rootDir = master.getMasterFileSystem().getRootDir();
1068 checkSnapshotSupport(master.getConfiguration(), master.getMasterFileSystem());
1069
1070
1071 Configuration conf = master.getConfiguration();
1072 long wakeFrequency = conf.getInt(SNAPSHOT_WAKE_MILLIS_KEY, SNAPSHOT_WAKE_MILLIS_DEFAULT);
1073 long timeoutMillis = Math.max(conf.getLong(SnapshotDescriptionUtils.SNAPSHOT_TIMEOUT_MILLIS_KEY,
1074 SnapshotDescriptionUtils.SNAPSHOT_TIMEOUT_MILLIS_DEFAULT),
1075 conf.getLong(SnapshotDescriptionUtils.MASTER_SNAPSHOT_TIMEOUT_MILLIS,
1076 SnapshotDescriptionUtils.DEFAULT_MAX_WAIT_TIME));
1077 int opThreads = conf.getInt(SNAPSHOT_POOL_THREADS_KEY, SNAPSHOT_POOL_THREADS_DEFAULT);
1078
1079
1080 String name = master.getServerName().toString();
1081 ThreadPoolExecutor tpool = ProcedureCoordinator.defaultPool(name, opThreads);
1082 ProcedureCoordinatorRpcs comms = new ZKProcedureCoordinatorRpcs(
1083 master.getZooKeeper(), SnapshotManager.ONLINE_SNAPSHOT_CONTROLLER_DESCRIPTION, name);
1084
1085 this.coordinator = new ProcedureCoordinator(comms, tpool, timeoutMillis, wakeFrequency);
1086 this.executorService = master.getExecutorService();
1087 resetTempDir();
1088 }
1089
1090 @Override
1091 public String getProcedureSignature() {
1092 return ONLINE_SNAPSHOT_CONTROLLER_DESCRIPTION;
1093 }
1094
1095 @Override
1096 public void execProcedure(ProcedureDescription desc) throws IOException {
1097 takeSnapshot(toSnapshotDescription(desc));
1098 }
1099
1100 @Override
1101 public boolean isProcedureDone(ProcedureDescription desc) throws IOException {
1102 return isSnapshotDone(toSnapshotDescription(desc));
1103 }
1104
1105 private SnapshotDescription toSnapshotDescription(ProcedureDescription desc)
1106 throws IOException {
1107 SnapshotDescription.Builder builder = SnapshotDescription.newBuilder();
1108 if (!desc.hasInstance()) {
1109 throw new IOException("Snapshot name is not defined: " + desc.toString());
1110 }
1111 String snapshotName = desc.getInstance();
1112 List<NameStringPair> props = desc.getConfigurationList();
1113 String table = null;
1114 for (NameStringPair prop : props) {
1115 if ("table".equalsIgnoreCase(prop.getName())) {
1116 table = prop.getValue();
1117 }
1118 }
1119 if (table == null) {
1120 throw new IOException("Snapshot table is not defined: " + desc.toString());
1121 }
1122 TableName tableName = TableName.valueOf(table);
1123 builder.setTable(tableName.getNameAsString());
1124 builder.setName(snapshotName);
1125 builder.setType(SnapshotDescription.Type.FLUSH);
1126 return builder.build();
1127 }
1128 }