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