1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.backup;
19
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.List;
26
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 import org.apache.hadoop.conf.Configuration;
30 import org.apache.hadoop.fs.FileStatus;
31 import org.apache.hadoop.fs.FileSystem;
32 import org.apache.hadoop.fs.Path;
33 import org.apache.hadoop.fs.PathFilter;
34 import org.apache.hadoop.hbase.HRegionInfo;
35 import org.apache.hadoop.hbase.regionserver.HRegion;
36 import org.apache.hadoop.hbase.regionserver.StoreFile;
37 import org.apache.hadoop.hbase.util.Bytes;
38 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
39 import org.apache.hadoop.hbase.util.FSUtils;
40 import org.apache.hadoop.hbase.util.HFileArchiveUtil;
41 import org.apache.hadoop.io.MultipleIOException;
42
43 import com.google.common.base.Function;
44 import com.google.common.base.Preconditions;
45 import com.google.common.collect.Collections2;
46 import com.google.common.collect.Lists;
47
48
49
50
51
52
53 public class HFileArchiver {
54 private static final Log LOG = LogFactory.getLog(HFileArchiver.class);
55 private static final String SEPARATOR = ".";
56
57
58 private static final int DEFAULT_RETRIES_NUMBER = 3;
59
60 private HFileArchiver() {
61
62 }
63
64
65
66
67
68
69
70
71
72 public static void archiveRegion(Configuration conf, FileSystem fs, HRegionInfo info)
73 throws IOException {
74 Path rootDir = FSUtils.getRootDir(conf);
75 archiveRegion(fs, rootDir, FSUtils.getTableDir(rootDir, info.getTable()),
76 HRegion.getRegionDir(rootDir, info));
77 }
78
79
80
81
82
83
84
85
86
87
88
89
90 public static boolean archiveRegion(FileSystem fs, Path rootdir, Path tableDir, Path regionDir)
91 throws IOException {
92 if (LOG.isDebugEnabled()) {
93 LOG.debug("ARCHIVING " + regionDir.toString());
94 }
95
96
97
98 if (tableDir == null || regionDir == null) {
99 LOG.error("No archive directory could be found because tabledir (" + tableDir
100 + ") or regiondir (" + regionDir + "was null. Deleting files instead.");
101 deleteRegionWithoutArchiving(fs, regionDir);
102
103
104 return false;
105 }
106
107
108 Preconditions.checkArgument(regionDir.toString().startsWith(tableDir.toString()));
109 Path regionArchiveDir = HFileArchiveUtil.getRegionArchiveDir(rootdir,
110 FSUtils.getTableName(tableDir),
111 regionDir.getName());
112
113 FileStatusConverter getAsFile = new FileStatusConverter(fs);
114
115
116
117 Collection<File> toArchive = new ArrayList<File>();
118 final PathFilter dirFilter = new FSUtils.DirFilter(fs);
119 PathFilter nonHidden = new PathFilter() {
120 @Override
121 public boolean accept(Path file) {
122 return dirFilter.accept(file) && !file.getName().toString().startsWith(".");
123 }
124 };
125 FileStatus[] storeDirs = FSUtils.listStatus(fs, regionDir, nonHidden);
126
127 if (storeDirs == null) {
128 LOG.debug("Region directory (" + regionDir + ") was empty, just deleting and returning!");
129 return deleteRegionWithoutArchiving(fs, regionDir);
130 }
131
132
133 toArchive.addAll(Lists.transform(Arrays.asList(storeDirs), getAsFile));
134 LOG.debug("Archiving " + toArchive);
135 boolean success = false;
136 try {
137 success = resolveAndArchive(fs, regionArchiveDir, toArchive);
138 } catch (IOException e) {
139 LOG.error("Failed to archive " + toArchive, e);
140 success = false;
141 }
142
143
144 if (success) {
145 return deleteRegionWithoutArchiving(fs, regionDir);
146 }
147
148 throw new IOException("Received error when attempting to archive files (" + toArchive
149 + "), cannot delete region directory. ");
150 }
151
152
153
154
155
156
157
158
159
160
161
162 public static void archiveFamily(FileSystem fs, Configuration conf,
163 HRegionInfo parent, Path tableDir, byte[] family) throws IOException {
164 Path familyDir = new Path(tableDir, new Path(parent.getEncodedName(), Bytes.toString(family)));
165 archiveFamilyByFamilyDir(fs, conf, parent, familyDir, family);
166 }
167
168
169
170
171
172
173
174
175
176
177
178 public static void archiveFamilyByFamilyDir(FileSystem fs, Configuration conf,
179 HRegionInfo parent, Path familyDir, byte[] family) throws IOException {
180 FileStatus[] storeFiles = FSUtils.listStatus(fs, familyDir);
181 if (storeFiles == null) {
182 LOG.debug("No store files to dispose for region=" + parent.getRegionNameAsString() +
183 ", family=" + Bytes.toString(family));
184 return;
185 }
186
187 FileStatusConverter getAsFile = new FileStatusConverter(fs);
188 Collection<File> toArchive = Lists.transform(Arrays.asList(storeFiles), getAsFile);
189 Path storeArchiveDir = HFileArchiveUtil.getStoreArchivePath(conf, parent, family);
190
191
192 if (!resolveAndArchive(fs, storeArchiveDir, toArchive)) {
193 throw new IOException("Failed to archive/delete all the files for region:"
194 + Bytes.toString(parent.getRegionName()) + ", family:" + Bytes.toString(family)
195 + " into " + storeArchiveDir + ". Something is probably awry on the filesystem.");
196 }
197 }
198
199
200
201
202
203
204
205
206
207
208
209 public static void archiveStoreFiles(Configuration conf, FileSystem fs, HRegionInfo regionInfo,
210 Path tableDir, byte[] family, Collection<StoreFile> compactedFiles) throws IOException {
211
212
213 if (fs == null) {
214 LOG.warn("Passed filesystem is null, so just deleting the files without archiving for region:"
215 + Bytes.toString(regionInfo.getRegionName()) + ", family:" + Bytes.toString(family));
216 deleteStoreFilesWithoutArchiving(compactedFiles);
217 return;
218 }
219
220
221 if (compactedFiles.size() == 0) {
222 LOG.debug("No store files to dispose, done!");
223 return;
224 }
225
226
227 if (regionInfo == null || family == null) throw new IOException(
228 "Need to have a region and a family to archive from.");
229
230 Path storeArchiveDir = HFileArchiveUtil.getStoreArchivePath(conf, regionInfo, tableDir, family);
231
232
233 if (!fs.mkdirs(storeArchiveDir)) {
234 throw new IOException("Could not make archive directory (" + storeArchiveDir + ") for store:"
235 + Bytes.toString(family) + ", deleting compacted files instead.");
236 }
237
238
239 if (LOG.isDebugEnabled()) LOG.debug("Archiving compacted store files.");
240
241
242 StoreToFile getStorePath = new StoreToFile(fs);
243 Collection<File> storeFiles = Collections2.transform(compactedFiles, getStorePath);
244
245
246 if (!resolveAndArchive(fs, storeArchiveDir, storeFiles)) {
247 throw new IOException("Failed to archive/delete all the files for region:"
248 + Bytes.toString(regionInfo.getRegionName()) + ", family:" + Bytes.toString(family)
249 + " into " + storeArchiveDir + ". Something is probably awry on the filesystem.");
250 }
251 }
252
253
254
255
256
257
258
259
260
261
262
263 public static void archiveStoreFile(Configuration conf, FileSystem fs, HRegionInfo regionInfo,
264 Path tableDir, byte[] family, Path storeFile) throws IOException {
265 Path storeArchiveDir = HFileArchiveUtil.getStoreArchivePath(conf, regionInfo, tableDir, family);
266
267 if (!fs.mkdirs(storeArchiveDir)) {
268 throw new IOException("Could not make archive directory (" + storeArchiveDir + ") for store:"
269 + Bytes.toString(family) + ", deleting compacted files instead.");
270 }
271
272
273 long start = EnvironmentEdgeManager.currentTime();
274 File file = new FileablePath(fs, storeFile);
275 if (!resolveAndArchiveFile(storeArchiveDir, file, Long.toString(start))) {
276 throw new IOException("Failed to archive/delete the file for region:"
277 + regionInfo.getRegionNameAsString() + ", family:" + Bytes.toString(family)
278 + " into " + storeArchiveDir + ". Something is probably awry on the filesystem.");
279 }
280 }
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295 private static boolean resolveAndArchive(FileSystem fs, Path baseArchiveDir,
296 Collection<File> toArchive) throws IOException {
297 if (LOG.isTraceEnabled()) LOG.trace("Starting to archive " + toArchive);
298 long start = EnvironmentEdgeManager.currentTime();
299 List<File> failures = resolveAndArchive(fs, baseArchiveDir, toArchive, start);
300
301
302
303
304 if (failures.size() > 0) {
305 LOG.warn("Failed to complete archive of: " + failures +
306 ". Those files are still in the original location, and they may slow down reads.");
307 return false;
308 }
309 return true;
310 }
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326 private static List<File> resolveAndArchive(FileSystem fs, Path baseArchiveDir,
327 Collection<File> toArchive, long start) throws IOException {
328
329 if (toArchive.size() == 0) return Collections.emptyList();
330
331 if (LOG.isTraceEnabled()) LOG.trace("moving files to the archive directory: " + baseArchiveDir);
332
333
334 if (!fs.exists(baseArchiveDir)) {
335 if (!fs.mkdirs(baseArchiveDir)) {
336 throw new IOException("Failed to create the archive directory:" + baseArchiveDir
337 + ", quitting archive attempt.");
338 }
339 if (LOG.isTraceEnabled()) LOG.trace("Created archive directory:" + baseArchiveDir);
340 }
341
342 List<File> failures = new ArrayList<File>();
343 String startTime = Long.toString(start);
344 for (File file : toArchive) {
345
346 try {
347 if (LOG.isTraceEnabled()) LOG.trace("Archiving: " + file);
348 if (file.isFile()) {
349
350 if (!resolveAndArchiveFile(baseArchiveDir, file, startTime)) {
351 LOG.warn("Couldn't archive " + file + " into backup directory: " + baseArchiveDir);
352 failures.add(file);
353 }
354 } else {
355
356 if (LOG.isTraceEnabled()) LOG.trace(file + " is a directory, archiving children files");
357
358 Path parentArchiveDir = new Path(baseArchiveDir, file.getName());
359
360
361 Collection<File> children = file.getChildren();
362 failures.addAll(resolveAndArchive(fs, parentArchiveDir, children, start));
363 }
364 } catch (IOException e) {
365 LOG.warn("Failed to archive " + file, e);
366 failures.add(file);
367 }
368 }
369 return failures;
370 }
371
372
373
374
375
376
377
378
379
380
381
382
383
384 private static boolean resolveAndArchiveFile(Path archiveDir, File currentFile,
385 String archiveStartTime) throws IOException {
386
387 String filename = currentFile.getName();
388 Path archiveFile = new Path(archiveDir, filename);
389 FileSystem fs = currentFile.getFileSystem();
390
391
392
393
394 if (fs.exists(archiveFile)) {
395 if (LOG.isDebugEnabled()) {
396 LOG.debug("File:" + archiveFile + " already exists in archive, moving to "
397 + "timestamped backup and overwriting current.");
398 }
399
400
401 Path backedupArchiveFile = new Path(archiveDir, filename + SEPARATOR + archiveStartTime);
402 if (!fs.rename(archiveFile, backedupArchiveFile)) {
403 LOG.error("Could not rename archive file to backup: " + backedupArchiveFile
404 + ", deleting existing file in favor of newer.");
405
406 if (!fs.delete(archiveFile, false)) {
407 throw new IOException("Couldn't delete existing archive file (" + archiveFile
408 + ") or rename it to the backup file (" + backedupArchiveFile
409 + ") to make room for similarly named file.");
410 }
411 }
412 LOG.debug("Backed up archive file from " + archiveFile);
413 }
414
415 if (LOG.isTraceEnabled()) {
416 LOG.trace("No existing file in archive for: " + archiveFile +
417 ", free to archive original file.");
418 }
419
420
421 boolean success = false;
422 for (int i = 0; !success && i < DEFAULT_RETRIES_NUMBER; ++i) {
423 if (i > 0) {
424
425
426
427
428 try {
429 if (!fs.exists(archiveDir)) {
430 if (fs.mkdirs(archiveDir)) {
431 LOG.debug("Created archive directory:" + archiveDir);
432 }
433 }
434 } catch (IOException e) {
435 LOG.warn("Failed to create directory: " + archiveDir, e);
436 }
437 }
438
439 try {
440 success = currentFile.moveAndClose(archiveFile);
441 } catch (IOException e) {
442 LOG.warn("Failed to archive " + currentFile + " on try #" + i, e);
443 success = false;
444 }
445 }
446
447 if (!success) {
448 LOG.error("Failed to archive " + currentFile);
449 return false;
450 }
451
452 if (LOG.isDebugEnabled()) {
453 LOG.debug("Finished archiving from " + currentFile + ", to " + archiveFile);
454 }
455 return true;
456 }
457
458
459
460
461
462
463
464
465 private static boolean deleteRegionWithoutArchiving(FileSystem fs, Path regionDir)
466 throws IOException {
467 if (fs.delete(regionDir, true)) {
468 LOG.debug("Deleted all region files in: " + regionDir);
469 return true;
470 }
471 LOG.debug("Failed to delete region directory:" + regionDir);
472 return false;
473 }
474
475
476
477
478
479
480
481
482
483
484
485
486 private static void deleteStoreFilesWithoutArchiving(Collection<StoreFile> compactedFiles)
487 throws IOException {
488 LOG.debug("Deleting store files without archiving.");
489 List<IOException> errors = new ArrayList<IOException>(0);
490 for (StoreFile hsf : compactedFiles) {
491 try {
492 hsf.deleteReader();
493 } catch (IOException e) {
494 LOG.error("Failed to delete store file:" + hsf.getPath());
495 errors.add(e);
496 }
497 }
498 if (errors.size() > 0) {
499 throw MultipleIOException.createIOException(errors);
500 }
501 }
502
503
504
505
506
507
508 private static abstract class FileConverter<T> implements Function<T, File> {
509 protected final FileSystem fs;
510
511 public FileConverter(FileSystem fs) {
512 this.fs = fs;
513 }
514 }
515
516
517
518
519 private static class FileStatusConverter extends FileConverter<FileStatus> {
520 public FileStatusConverter(FileSystem fs) {
521 super(fs);
522 }
523
524 @Override
525 public File apply(FileStatus input) {
526 return new FileablePath(fs, input.getPath());
527 }
528 }
529
530
531
532
533
534 private static class StoreToFile extends FileConverter<StoreFile> {
535 public StoreToFile(FileSystem fs) {
536 super(fs);
537 }
538
539 @Override
540 public File apply(StoreFile input) {
541 return new FileableStoreFile(fs, input);
542 }
543 }
544
545
546
547
548 private static abstract class File {
549 protected final FileSystem fs;
550
551 public File(FileSystem fs) {
552 this.fs = fs;
553 }
554
555
556
557
558
559 abstract void delete() throws IOException;
560
561
562
563
564
565
566 abstract boolean isFile() throws IOException;
567
568
569
570
571
572
573 abstract Collection<File> getChildren() throws IOException;
574
575
576
577
578
579 abstract void close() throws IOException;
580
581
582
583
584
585 abstract String getName();
586
587
588
589
590 abstract Path getPath();
591
592
593
594
595
596
597
598 public boolean moveAndClose(Path dest) throws IOException {
599 this.close();
600 Path p = this.getPath();
601 return FSUtils.renameAndSetModifyTime(fs, p, dest);
602 }
603
604
605
606
607 public FileSystem getFileSystem() {
608 return this.fs;
609 }
610
611 @Override
612 public String toString() {
613 return this.getClass() + ", file:" + getPath().toString();
614 }
615 }
616
617
618
619
620 private static class FileablePath extends File {
621 private final Path file;
622 private final FileStatusConverter getAsFile;
623
624 public FileablePath(FileSystem fs, Path file) {
625 super(fs);
626 this.file = file;
627 this.getAsFile = new FileStatusConverter(fs);
628 }
629
630 @Override
631 public void delete() throws IOException {
632 if (!fs.delete(file, true)) throw new IOException("Failed to delete:" + this.file);
633 }
634
635 @Override
636 public String getName() {
637 return file.getName();
638 }
639
640 @Override
641 public Collection<File> getChildren() throws IOException {
642 if (fs.isFile(file)) return Collections.emptyList();
643 return Collections2.transform(Arrays.asList(fs.listStatus(file)), getAsFile);
644 }
645
646 @Override
647 public boolean isFile() throws IOException {
648 return fs.isFile(file);
649 }
650
651 @Override
652 public void close() throws IOException {
653
654 }
655
656 @Override
657 Path getPath() {
658 return file;
659 }
660 }
661
662
663
664
665
666 private static class FileableStoreFile extends File {
667 StoreFile file;
668
669 public FileableStoreFile(FileSystem fs, StoreFile store) {
670 super(fs);
671 this.file = store;
672 }
673
674 @Override
675 public void delete() throws IOException {
676 file.deleteReader();
677 }
678
679 @Override
680 public String getName() {
681 return file.getPath().getName();
682 }
683
684 @Override
685 public boolean isFile() {
686 return true;
687 }
688
689 @Override
690 public Collection<File> getChildren() throws IOException {
691
692 return Collections.emptyList();
693 }
694
695 @Override
696 public void close() throws IOException {
697 file.closeReader(true);
698 }
699
700 @Override
701 Path getPath() {
702 return file.getPath();
703 }
704 }
705 }