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