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.TableName;
35 import org.apache.hadoop.hbase.HRegionInfo;
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 = 3;
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, FSUtils.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,
111 FSUtils.getTableName(tableDir),
112 regionDir.getName());
113
114 FileStatusConverter getAsFile = new FileStatusConverter(fs);
115
116
117
118 Collection<File> toArchive = new ArrayList<File>();
119 final PathFilter dirFilter = new FSUtils.DirFilter(fs);
120 PathFilter nonHidden = new PathFilter() {
121 @Override
122 public boolean accept(Path file) {
123 return dirFilter.accept(file) && !file.getName().toString().startsWith(".");
124 }
125 };
126 FileStatus[] storeDirs = FSUtils.listStatus(fs, regionDir, nonHidden);
127
128 if (storeDirs == null) {
129 LOG.debug("Region directory (" + regionDir + ") was empty, just deleting and returning!");
130 return deleteRegionWithoutArchiving(fs, regionDir);
131 }
132
133
134 toArchive.addAll(Lists.transform(Arrays.asList(storeDirs), getAsFile));
135 LOG.debug("Archiving:" + toArchive);
136 boolean success = false;
137 try {
138 success = resolveAndArchive(fs, regionArchiveDir, toArchive);
139 } catch (IOException e) {
140 LOG.error("Failed to archive: " + toArchive, e);
141 success = false;
142 }
143
144
145 if (success) {
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);
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, HRegionInfo regionInfo,
196 Path tableDir, 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(regionInfo.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 (regionInfo == null || family == null) throw new IOException(
214 "Need to have a region and a family to archive from.");
215
216 Path storeArchiveDir = HFileArchiveUtil.getStoreArchivePath(conf, regionInfo, tableDir, 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 if (LOG.isTraceEnabled()) LOG.trace("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(regionInfo.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(Configuration conf, FileSystem fs, HRegionInfo regionInfo,
250 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 if (LOG.isTraceEnabled()) LOG.trace("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 if (LOG.isTraceEnabled()) LOG.trace("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 if (LOG.isTraceEnabled()) LOG.trace("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 if (LOG.isTraceEnabled()) LOG.trace("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 if (LOG.isTraceEnabled()) LOG.trace(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 if (LOG.isTraceEnabled()) {
402 LOG.trace("No existing file in archive for: " + archiveFile +
403 ", free to archive original file.");
404 }
405
406
407 boolean success = false;
408 for (int i = 0; !success && i < DEFAULT_RETRIES_NUMBER; ++i) {
409 if (i > 0) {
410
411
412
413
414 try {
415 if (!fs.exists(archiveDir)) {
416 if (fs.mkdirs(archiveDir)) {
417 LOG.debug("Created archive directory:" + archiveDir);
418 }
419 }
420 } catch (IOException e) {
421 LOG.warn("Failed to create the archive directory: " + archiveDir, e);
422 }
423 }
424
425 try {
426 success = currentFile.moveAndClose(archiveFile);
427 } catch (IOException e) {
428 LOG.warn("Failed to archive file: " + currentFile + " on try #" + i, e);
429 success = false;
430 }
431 }
432
433 if (!success) {
434 LOG.error("Failed to archive file:" + currentFile);
435 return false;
436 }
437
438 if (LOG.isDebugEnabled()) {
439 LOG.debug("Finished archiving file from: " + currentFile + ", to: " + archiveFile);
440 }
441 return true;
442 }
443
444
445
446
447
448
449
450
451 private static boolean deleteRegionWithoutArchiving(FileSystem fs, Path regionDir)
452 throws IOException {
453 if (fs.delete(regionDir, true)) {
454 LOG.debug("Deleted all region files in: " + regionDir);
455 return true;
456 }
457 LOG.debug("Failed to delete region directory:" + regionDir);
458 return false;
459 }
460
461
462
463
464
465
466
467
468
469
470
471
472 private static void deleteStoreFilesWithoutArchiving(Collection<StoreFile> compactedFiles)
473 throws IOException {
474 LOG.debug("Deleting store files without archiving.");
475 List<IOException> errors = new ArrayList<IOException>(0);
476 for (StoreFile hsf : compactedFiles) {
477 try {
478 hsf.deleteReader();
479 } catch (IOException e) {
480 LOG.error("Failed to delete store file:" + hsf.getPath());
481 errors.add(e);
482 }
483 }
484 if (errors.size() > 0) {
485 throw MultipleIOException.createIOException(errors);
486 }
487 }
488
489
490
491
492
493
494 private static abstract class FileConverter<T> implements Function<T, File> {
495 protected final FileSystem fs;
496
497 public FileConverter(FileSystem fs) {
498 this.fs = fs;
499 }
500 }
501
502
503
504
505 private static class FileStatusConverter extends FileConverter<FileStatus> {
506 public FileStatusConverter(FileSystem fs) {
507 super(fs);
508 }
509
510 @Override
511 public File apply(FileStatus input) {
512 return new FileablePath(fs, input.getPath());
513 }
514 }
515
516
517
518
519
520 private static class StoreToFile extends FileConverter<StoreFile> {
521 public StoreToFile(FileSystem fs) {
522 super(fs);
523 }
524
525 @Override
526 public File apply(StoreFile input) {
527 return new FileableStoreFile(fs, input);
528 }
529 }
530
531
532
533
534 private static abstract class File {
535 protected final FileSystem fs;
536
537 public File(FileSystem fs) {
538 this.fs = fs;
539 }
540
541
542
543
544
545 abstract void delete() throws IOException;
546
547
548
549
550
551
552 abstract boolean isFile() throws IOException;
553
554
555
556
557
558
559 abstract Collection<File> getChildren() throws IOException;
560
561
562
563
564
565 abstract void close() throws IOException;
566
567
568
569
570
571 abstract String getName();
572
573
574
575
576 abstract Path getPath();
577
578
579
580
581
582
583
584 public boolean moveAndClose(Path dest) throws IOException {
585 this.close();
586 Path p = this.getPath();
587 return FSUtils.renameAndSetModifyTime(fs, p, dest);
588 }
589
590
591
592
593 public FileSystem getFileSystem() {
594 return this.fs;
595 }
596
597 @Override
598 public String toString() {
599 return this.getClass() + ", file:" + getPath().toString();
600 }
601 }
602
603
604
605
606 private static class FileablePath extends File {
607 private final Path file;
608 private final FileStatusConverter getAsFile;
609
610 public FileablePath(FileSystem fs, Path file) {
611 super(fs);
612 this.file = file;
613 this.getAsFile = new FileStatusConverter(fs);
614 }
615
616 @Override
617 public void delete() throws IOException {
618 if (!fs.delete(file, true)) throw new IOException("Failed to delete:" + this.file);
619 }
620
621 @Override
622 public String getName() {
623 return file.getName();
624 }
625
626 @Override
627 public Collection<File> getChildren() throws IOException {
628 if (fs.isFile(file)) return Collections.emptyList();
629 return Collections2.transform(Arrays.asList(fs.listStatus(file)), getAsFile);
630 }
631
632 @Override
633 public boolean isFile() throws IOException {
634 return fs.isFile(file);
635 }
636
637 @Override
638 public void close() throws IOException {
639
640 }
641
642 @Override
643 Path getPath() {
644 return file;
645 }
646 }
647
648
649
650
651
652 private static class FileableStoreFile extends File {
653 StoreFile file;
654
655 public FileableStoreFile(FileSystem fs, StoreFile store) {
656 super(fs);
657 this.file = store;
658 }
659
660 @Override
661 public void delete() throws IOException {
662 file.deleteReader();
663 }
664
665 @Override
666 public String getName() {
667 return file.getPath().getName();
668 }
669
670 @Override
671 public boolean isFile() {
672 return true;
673 }
674
675 @Override
676 public Collection<File> getChildren() throws IOException {
677
678 return Collections.emptyList();
679 }
680
681 @Override
682 public void close() throws IOException {
683 file.closeReader(true);
684 }
685
686 @Override
687 Path getPath() {
688 return file.getPath();
689 }
690 }
691 }