1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.util;
19
20 import java.io.FileNotFoundException;
21 import java.io.IOException;
22 import java.util.Comparator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.TreeMap;
26 import java.util.concurrent.ConcurrentHashMap;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29
30 import org.apache.commons.lang.NotImplementedException;
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33 import org.apache.hadoop.classification.InterfaceAudience;
34 import org.apache.hadoop.conf.Configuration;
35 import org.apache.hadoop.fs.FSDataInputStream;
36 import org.apache.hadoop.fs.FSDataOutputStream;
37 import org.apache.hadoop.fs.FileStatus;
38 import org.apache.hadoop.fs.FileSystem;
39 import org.apache.hadoop.fs.Path;
40 import org.apache.hadoop.fs.PathFilter;
41 import org.apache.hadoop.hbase.TableName;
42 import org.apache.hadoop.hbase.exceptions.DeserializationException;
43 import org.apache.hadoop.hbase.HConstants;
44 import org.apache.hadoop.hbase.HTableDescriptor;
45 import org.apache.hadoop.hbase.TableDescriptors;
46 import org.apache.hadoop.hbase.TableInfoMissingException;
47 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
48
49 import com.google.common.annotations.VisibleForTesting;
50 import com.google.common.primitives.Ints;
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71 @InterfaceAudience.Private
72 public class FSTableDescriptors implements TableDescriptors {
73 private static final Log LOG = LogFactory.getLog(FSTableDescriptors.class);
74 private final FileSystem fs;
75 private final Path rootdir;
76 private final boolean fsreadonly;
77 @VisibleForTesting long cachehits = 0;
78 @VisibleForTesting long invocations = 0;
79
80
81 static final String TABLEINFO_FILE_PREFIX = ".tableinfo";
82 static final String TABLEINFO_DIR = ".tabledesc";
83 static final String TMP_DIR = ".tmp";
84
85
86
87
88 private final Map<TableName, TableDescriptorAndModtime> cache =
89 new ConcurrentHashMap<TableName, TableDescriptorAndModtime>();
90
91
92
93
94 private static class TableDescriptorAndModtime {
95 private final HTableDescriptor htd;
96 private final long modtime;
97
98 TableDescriptorAndModtime(final long modtime, final HTableDescriptor htd) {
99 this.htd = htd;
100 this.modtime = modtime;
101 }
102
103 long getModtime() {
104 return this.modtime;
105 }
106
107 HTableDescriptor getTableDescriptor() {
108 return this.htd;
109 }
110 }
111
112
113
114
115
116
117 public FSTableDescriptors(final Configuration conf) throws IOException {
118 this(FSUtils.getCurrentFileSystem(conf), FSUtils.getRootDir(conf));
119 }
120
121 public FSTableDescriptors(final FileSystem fs, final Path rootdir) {
122 this(fs, rootdir, false);
123 }
124
125
126
127
128
129 public FSTableDescriptors(final FileSystem fs,
130 final Path rootdir, final boolean fsreadonly) {
131 super();
132 this.fs = fs;
133 this.rootdir = rootdir;
134 this.fsreadonly = fsreadonly;
135 }
136
137
138
139
140
141
142
143 @Override
144 public HTableDescriptor get(final TableName tablename)
145 throws IOException {
146 invocations++;
147 if (HTableDescriptor.ROOT_TABLEDESC.getTableName().equals(tablename)) {
148 cachehits++;
149 return HTableDescriptor.ROOT_TABLEDESC;
150 }
151 if (HTableDescriptor.META_TABLEDESC.getTableName().equals(tablename)) {
152 cachehits++;
153 return HTableDescriptor.META_TABLEDESC;
154 }
155
156
157 if (HConstants.HBASE_NON_USER_TABLE_DIRS.contains(tablename.getNameAsString())) {
158 throw new IOException("No descriptor found for non table = " + tablename);
159 }
160
161
162 TableDescriptorAndModtime cachedtdm = this.cache.get(tablename);
163
164 if (cachedtdm != null) {
165
166 if (getTableInfoModtime(tablename) <= cachedtdm.getModtime()) {
167 cachehits++;
168 return cachedtdm.getTableDescriptor();
169 }
170 }
171
172 TableDescriptorAndModtime tdmt = null;
173 try {
174 tdmt = getTableDescriptorAndModtime(tablename);
175 } catch (NullPointerException e) {
176 LOG.debug("Exception during readTableDecriptor. Current table name = "
177 + tablename, e);
178 } catch (IOException ioe) {
179 LOG.debug("Exception during readTableDecriptor. Current table name = "
180 + tablename, ioe);
181 }
182
183 if (tdmt == null) {
184 LOG.warn("The following folder is in HBase's root directory and " +
185 "doesn't contain a table descriptor, " +
186 "do consider deleting it: " + tablename);
187 } else {
188 this.cache.put(tablename, tdmt);
189 }
190 return tdmt == null ? null : tdmt.getTableDescriptor();
191 }
192
193
194
195
196 @Override
197 public Map<String, HTableDescriptor> getAll()
198 throws IOException {
199 Map<String, HTableDescriptor> htds = new TreeMap<String, HTableDescriptor>();
200 List<Path> tableDirs = FSUtils.getTableDirs(fs, rootdir);
201 for (Path d: tableDirs) {
202 HTableDescriptor htd = null;
203 try {
204 htd = get(FSUtils.getTableName(d));
205 } catch (FileNotFoundException fnfe) {
206
207 LOG.warn("Trouble retrieving htd", fnfe);
208 }
209 if (htd == null) continue;
210 htds.put(htd.getTableName().getNameAsString(), htd);
211 }
212 return htds;
213 }
214
215
216
217
218 @Override
219 public Map<String, HTableDescriptor> getByNamespace(String name)
220 throws IOException {
221 Map<String, HTableDescriptor> htds = new TreeMap<String, HTableDescriptor>();
222 List<Path> tableDirs =
223 FSUtils.getLocalTableDirs(fs, FSUtils.getNamespaceDir(rootdir, name));
224 for (Path d: tableDirs) {
225 HTableDescriptor htd = null;
226 try {
227 htd = get(FSUtils.getTableName(d));
228 } catch (FileNotFoundException fnfe) {
229
230 LOG.warn("Trouble retrieving htd", fnfe);
231 }
232 if (htd == null) continue;
233 htds.put(FSUtils.getTableName(d).getNameAsString(), htd);
234 }
235 return htds;
236 }
237
238
239
240
241
242 @Override
243 public void add(HTableDescriptor htd) throws IOException {
244 if (fsreadonly) {
245 throw new NotImplementedException("Cannot add a table descriptor - in read only mode");
246 }
247 if (TableName.META_TABLE_NAME.equals(htd.getTableName())) {
248 throw new NotImplementedException();
249 }
250 if (HConstants.HBASE_NON_USER_TABLE_DIRS.contains(htd.getTableName().getNameAsString())) {
251 throw new NotImplementedException(
252 "Cannot add a table descriptor for a reserved subdirectory name: " + htd.getNameAsString());
253 }
254 updateTableDescriptor(htd);
255 long modtime = getTableInfoModtime(htd.getTableName());
256 this.cache.put(htd.getTableName(), new TableDescriptorAndModtime(modtime, htd));
257 }
258
259
260
261
262
263
264 @Override
265 public HTableDescriptor remove(final TableName tablename)
266 throws IOException {
267 if (fsreadonly) {
268 throw new NotImplementedException("Cannot remove a table descriptor - in read only mode");
269 }
270 Path tabledir = getTableDir(tablename);
271 if (this.fs.exists(tabledir)) {
272 if (!this.fs.delete(tabledir, true)) {
273 throw new IOException("Failed delete of " + tabledir.toString());
274 }
275 }
276 TableDescriptorAndModtime tdm = this.cache.remove(tablename);
277 return tdm == null ? null : tdm.getTableDescriptor();
278 }
279
280
281
282
283
284
285
286
287 public boolean isTableInfoExists(TableName tableName) throws IOException {
288 return getTableInfoPath(tableName) != null;
289 }
290
291
292
293
294
295 private FileStatus getTableInfoPath(final TableName tableName) throws IOException {
296 Path tableDir = getTableDir(tableName);
297 return getTableInfoPath(tableDir);
298 }
299
300 private FileStatus getTableInfoPath(Path tableDir)
301 throws IOException {
302 return getTableInfoPath(fs, tableDir, !fsreadonly);
303 }
304
305
306
307
308
309
310
311
312
313
314
315
316 public static FileStatus getTableInfoPath(FileSystem fs, Path tableDir)
317 throws IOException {
318 return getTableInfoPath(fs, tableDir, false);
319 }
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334 private static FileStatus getTableInfoPath(FileSystem fs, Path tableDir, boolean removeOldFiles)
335 throws IOException {
336 Path tableInfoDir = new Path(tableDir, TABLEINFO_DIR);
337 return getCurrentTableInfoStatus(fs, tableInfoDir, removeOldFiles);
338 }
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354 static FileStatus getCurrentTableInfoStatus(FileSystem fs, Path dir, boolean removeOldFiles)
355 throws IOException {
356 FileStatus [] status = FSUtils.listStatus(fs, dir, TABLEINFO_PATHFILTER);
357 if (status == null || status.length < 1) return null;
358 FileStatus mostCurrent = null;
359 for (FileStatus file : status) {
360 if (mostCurrent == null || TABLEINFO_FILESTATUS_COMPARATOR.compare(file, mostCurrent) < 0) {
361 mostCurrent = file;
362 }
363 }
364 if (removeOldFiles && status.length > 1) {
365
366 for (FileStatus file : status) {
367 Path path = file.getPath();
368 if (file != mostCurrent) {
369 if (!fs.delete(file.getPath(), false)) {
370 LOG.warn("Failed cleanup of " + path);
371 } else {
372 LOG.debug("Cleaned up old tableinfo file " + path);
373 }
374 }
375 }
376 }
377 return mostCurrent;
378 }
379
380
381
382
383
384 @VisibleForTesting
385 static final Comparator<FileStatus> TABLEINFO_FILESTATUS_COMPARATOR =
386 new Comparator<FileStatus>() {
387 @Override
388 public int compare(FileStatus left, FileStatus right) {
389 return -left.compareTo(right);
390 }};
391
392
393
394
395 @VisibleForTesting Path getTableDir(final TableName tableName) {
396 return FSUtils.getTableDir(rootdir, tableName);
397 }
398
399 private static final PathFilter TABLEINFO_PATHFILTER = new PathFilter() {
400 @Override
401 public boolean accept(Path p) {
402
403 return p.getName().startsWith(TABLEINFO_FILE_PREFIX);
404 }};
405
406
407
408
409 @VisibleForTesting static final int WIDTH_OF_SEQUENCE_ID = 10;
410
411
412
413
414
415
416 private static String formatTableInfoSequenceId(final int number) {
417 byte [] b = new byte[WIDTH_OF_SEQUENCE_ID];
418 int d = Math.abs(number);
419 for (int i = b.length - 1; i >= 0; i--) {
420 b[i] = (byte)((d % 10) + '0');
421 d /= 10;
422 }
423 return Bytes.toString(b);
424 }
425
426
427
428
429
430
431 private static final Pattern TABLEINFO_FILE_REGEX =
432 Pattern.compile(TABLEINFO_FILE_PREFIX + "(\\.([0-9]{" + WIDTH_OF_SEQUENCE_ID + "}))?$");
433
434
435
436
437
438 @VisibleForTesting static int getTableInfoSequenceId(final Path p) {
439 if (p == null) return 0;
440 Matcher m = TABLEINFO_FILE_REGEX.matcher(p.getName());
441 if (!m.matches()) throw new IllegalArgumentException(p.toString());
442 String suffix = m.group(2);
443 if (suffix == null || suffix.length() <= 0) return 0;
444 return Integer.parseInt(m.group(2));
445 }
446
447
448
449
450
451
452 @VisibleForTesting static String getTableInfoFileName(final int sequenceid) {
453 return TABLEINFO_FILE_PREFIX + "." + formatTableInfoSequenceId(sequenceid);
454 }
455
456
457
458
459
460
461
462
463
464 private long getTableInfoModtime(final TableName tableName) throws IOException {
465 FileStatus status = getTableInfoPath(tableName);
466 return status == null ? 0 : status.getModificationTime();
467 }
468
469
470
471
472
473
474 public static HTableDescriptor getTableDescriptorFromFs(FileSystem fs,
475 Path hbaseRootDir, TableName tableName) throws IOException {
476 Path tableDir = FSUtils.getTableDir(hbaseRootDir, tableName);
477 return getTableDescriptorFromFs(fs, tableDir);
478 }
479
480
481
482
483
484
485 public static HTableDescriptor getTableDescriptorFromFs(FileSystem fs, Path tableDir)
486 throws IOException {
487 FileStatus status = getTableInfoPath(fs, tableDir, false);
488 if (status == null) {
489 throw new TableInfoMissingException("No table descriptor file under " + tableDir);
490 }
491 return readTableDescriptor(fs, status, false);
492 }
493
494 private TableDescriptorAndModtime getTableDescriptorAndModtime(TableName tableName)
495 throws IOException {
496
497 if (tableName.equals(TableName.ROOT_TABLE_NAME)
498 || tableName.equals(TableName.META_TABLE_NAME)) {
499 return null;
500 }
501 return getTableDescriptorAndModtime(getTableDir(tableName));
502 }
503
504 private TableDescriptorAndModtime getTableDescriptorAndModtime(Path tableDir)
505 throws IOException {
506 FileStatus status = getTableInfoPath(tableDir);
507 if (status == null) {
508 throw new TableInfoMissingException("No table descriptor file under " + tableDir);
509 }
510 HTableDescriptor htd = readTableDescriptor(fs, status, !fsreadonly);
511 return new TableDescriptorAndModtime(status.getModificationTime(), htd);
512 }
513
514 private static HTableDescriptor readTableDescriptor(FileSystem fs, FileStatus status,
515 boolean rewritePb) throws IOException {
516 int len = Ints.checkedCast(status.getLen());
517 byte [] content = new byte[len];
518 FSDataInputStream fsDataInputStream = fs.open(status.getPath());
519 try {
520 fsDataInputStream.readFully(content);
521 } finally {
522 fsDataInputStream.close();
523 }
524 HTableDescriptor htd = null;
525 try {
526 htd = HTableDescriptor.parseFrom(content);
527 } catch (DeserializationException e) {
528 throw new IOException("content=" + Bytes.toShort(content), e);
529 }
530 if (rewritePb && !ProtobufUtil.isPBMagicPrefix(content)) {
531
532 Path tableInfoDir = status.getPath().getParent();
533 Path tableDir = tableInfoDir.getParent();
534 writeTableDescriptor(fs, htd, tableDir, status);
535 }
536 return htd;
537 }
538
539
540
541
542
543
544 @VisibleForTesting Path updateTableDescriptor(HTableDescriptor htd)
545 throws IOException {
546 if (fsreadonly) {
547 throw new NotImplementedException("Cannot update a table descriptor - in read only mode");
548 }
549 Path tableDir = getTableDir(htd.getTableName());
550 Path p = writeTableDescriptor(fs, htd, tableDir, getTableInfoPath(tableDir));
551 if (p == null) throw new IOException("Failed update");
552 LOG.info("Updated tableinfo=" + p);
553 return p;
554 }
555
556
557
558
559
560
561 public void deleteTableDescriptorIfExists(TableName tableName) throws IOException {
562 if (fsreadonly) {
563 throw new NotImplementedException("Cannot delete a table descriptor - in read only mode");
564 }
565
566 Path tableDir = getTableDir(tableName);
567 Path tableInfoDir = new Path(tableDir, TABLEINFO_DIR);
568 deleteTableDescriptorFiles(fs, tableInfoDir, Integer.MAX_VALUE);
569 }
570
571
572
573
574
575 private static void deleteTableDescriptorFiles(FileSystem fs, Path dir, int maxSequenceId)
576 throws IOException {
577 FileStatus [] status = FSUtils.listStatus(fs, dir, TABLEINFO_PATHFILTER);
578 for (FileStatus file : status) {
579 Path path = file.getPath();
580 int sequenceId = getTableInfoSequenceId(path);
581 if (sequenceId <= maxSequenceId) {
582 boolean success = FSUtils.delete(fs, path, false);
583 if (success) {
584 LOG.debug("Deleted table descriptor at " + path);
585 } else {
586 LOG.error("Failed to delete descriptor at " + path);
587 }
588 }
589 }
590 }
591
592
593
594
595
596
597
598
599
600
601 private static Path writeTableDescriptor(final FileSystem fs,
602 final HTableDescriptor htd, final Path tableDir,
603 final FileStatus currentDescriptorFile)
604 throws IOException {
605
606
607 Path tmpTableDir = new Path(tableDir, TMP_DIR);
608 Path tableInfoDir = new Path(tableDir, TABLEINFO_DIR);
609
610
611
612
613
614
615 int currentSequenceId = currentDescriptorFile == null ? 0 :
616 getTableInfoSequenceId(currentDescriptorFile.getPath());
617 int newSequenceId = currentSequenceId;
618
619
620 int retries = 10;
621 int retrymax = currentSequenceId + retries;
622 Path tableInfoDirPath = null;
623 do {
624 newSequenceId += 1;
625 String filename = getTableInfoFileName(newSequenceId);
626 Path tempPath = new Path(tmpTableDir, filename);
627 if (fs.exists(tempPath)) {
628 LOG.debug(tempPath + " exists; retrying up to " + retries + " times");
629 continue;
630 }
631 tableInfoDirPath = new Path(tableInfoDir, filename);
632 try {
633 writeHTD(fs, tempPath, htd);
634 fs.mkdirs(tableInfoDirPath.getParent());
635 if (!fs.rename(tempPath, tableInfoDirPath)) {
636 throw new IOException("Failed rename of " + tempPath + " to " + tableInfoDirPath);
637 }
638 LOG.debug("Wrote descriptor into: " + tableInfoDirPath);
639 } catch (IOException ioe) {
640
641 LOG.debug("Failed write and/or rename; retrying", ioe);
642 if (!FSUtils.deleteDirectory(fs, tempPath)) {
643 LOG.warn("Failed cleanup of " + tempPath);
644 }
645 tableInfoDirPath = null;
646 continue;
647 }
648 break;
649 } while (newSequenceId < retrymax);
650 if (tableInfoDirPath != null) {
651
652 deleteTableDescriptorFiles(fs, tableInfoDir, newSequenceId - 1);
653 }
654 return tableInfoDirPath;
655 }
656
657 private static void writeHTD(final FileSystem fs, final Path p, final HTableDescriptor htd)
658 throws IOException {
659 FSDataOutputStream out = fs.create(p, false);
660 try {
661
662
663 out.write(htd.toByteArray());
664 } finally {
665 out.close();
666 }
667 }
668
669
670
671
672
673
674 public boolean createTableDescriptor(HTableDescriptor htd) throws IOException {
675 return createTableDescriptor(htd, false);
676 }
677
678
679
680
681
682
683
684
685 public boolean createTableDescriptor(HTableDescriptor htd, boolean forceCreation)
686 throws IOException {
687 Path tableDir = getTableDir(htd.getTableName());
688 return createTableDescriptorForTableDirectory(tableDir, htd, forceCreation);
689 }
690
691
692
693
694
695
696
697
698
699
700
701
702 public boolean createTableDescriptorForTableDirectory(Path tableDir,
703 HTableDescriptor htd, boolean forceCreation) throws IOException {
704 if (fsreadonly) {
705 throw new NotImplementedException("Cannot create a table descriptor - in read only mode");
706 }
707 FileStatus status = getTableInfoPath(fs, tableDir);
708 if (status != null) {
709 LOG.debug("Current tableInfoPath = " + status.getPath());
710 if (!forceCreation) {
711 if (fs.exists(status.getPath()) && status.getLen() > 0) {
712 if (readTableDescriptor(fs, status, false).equals(htd)) {
713 LOG.debug("TableInfo already exists.. Skipping creation");
714 return false;
715 }
716 }
717 }
718 }
719 Path p = writeTableDescriptor(fs, htd, tableDir, status);
720 return p != null;
721 }
722
723 }
724