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.hbase.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 private volatile boolean usecache;
78 private volatile boolean fsvisited;
79
80 @VisibleForTesting long cachehits = 0;
81 @VisibleForTesting long invocations = 0;
82
83
84 static final String TABLEINFO_FILE_PREFIX = ".tableinfo";
85 static final String TABLEINFO_DIR = ".tabledesc";
86 static final String TMP_DIR = ".tmp";
87
88
89
90
91 private final Map<TableName, HTableDescriptor> cache =
92 new ConcurrentHashMap<TableName, HTableDescriptor>();
93
94
95
96
97 private final HTableDescriptor metaTableDescriptor;
98
99
100
101
102
103
104 public FSTableDescriptors(final Configuration conf) throws IOException {
105 this(conf, FSUtils.getCurrentFileSystem(conf), FSUtils.getRootDir(conf));
106 }
107
108 public FSTableDescriptors(final Configuration conf, final FileSystem fs, final Path rootdir)
109 throws IOException {
110 this(conf, fs, rootdir, false, true);
111 }
112
113
114
115
116
117 public FSTableDescriptors(final Configuration conf, final FileSystem fs,
118 final Path rootdir, final boolean fsreadonly, final boolean usecache) throws IOException {
119 super();
120 this.fs = fs;
121 this.rootdir = rootdir;
122 this.fsreadonly = fsreadonly;
123 this.usecache = usecache;
124
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 @VisibleForTesting
141 public boolean isUsecache() {
142 return this.usecache;
143 }
144
145
146
147
148
149
150
151 @Override
152 public HTableDescriptor get(final TableName tablename)
153 throws IOException {
154 invocations++;
155 if (TableName.META_TABLE_NAME.equals(tablename)) {
156 cachehits++;
157 return metaTableDescriptor;
158 }
159
160
161 if (HConstants.HBASE_NON_USER_TABLE_DIRS.contains(tablename.getNameAsString())) {
162 throw new IOException("No descriptor found for non table = " + tablename);
163 }
164
165 if (usecache) {
166
167 HTableDescriptor cachedtdm = this.cache.get(tablename);
168 if (cachedtdm != null) {
169 cachehits++;
170 return cachedtdm;
171 }
172 }
173 HTableDescriptor tdmt = null;
174 try {
175 tdmt = getTableDescriptorFromFs(fs, rootdir, tablename, !fsreadonly);
176 } catch (NullPointerException e) {
177 LOG.debug("Exception during readTableDecriptor. Current table name = "
178 + tablename, e);
179 } catch (TableInfoMissingException e) {
180
181 } catch (IOException ioe) {
182 LOG.debug("Exception during readTableDecriptor. Current table name = "
183 + tablename, ioe);
184 }
185
186 if (usecache && tdmt != null) {
187 this.cache.put(tablename, tdmt);
188 }
189
190 return tdmt;
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
201 if (fsvisited && usecache) {
202 for (Map.Entry<TableName, HTableDescriptor> entry: this.cache.entrySet()) {
203 htds.put(entry.getKey().toString(), entry.getValue());
204 }
205
206 htds.put(this.metaTableDescriptor.getNameAsString(), metaTableDescriptor);
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 != 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 @VisibleForTesting
402 static final Comparator<FileStatus> TABLEINFO_FILESTATUS_COMPARATOR =
403 new Comparator<FileStatus>() {
404 @Override
405 public int compare(FileStatus left, FileStatus right) {
406 return right.compareTo(left);
407 }};
408
409
410
411
412 @VisibleForTesting Path getTableDir(final TableName tableName) {
413 return FSUtils.getTableDir(rootdir, tableName);
414 }
415
416 private static final PathFilter TABLEINFO_PATHFILTER = new PathFilter() {
417 @Override
418 public boolean accept(Path p) {
419
420 return p.getName().startsWith(TABLEINFO_FILE_PREFIX);
421 }};
422
423
424
425
426 @VisibleForTesting static final int WIDTH_OF_SEQUENCE_ID = 10;
427
428
429
430
431
432
433 private static String formatTableInfoSequenceId(final int number) {
434 byte [] b = new byte[WIDTH_OF_SEQUENCE_ID];
435 int d = Math.abs(number);
436 for (int i = b.length - 1; i >= 0; i--) {
437 b[i] = (byte)((d % 10) + '0');
438 d /= 10;
439 }
440 return Bytes.toString(b);
441 }
442
443
444
445
446
447
448 private static final Pattern TABLEINFO_FILE_REGEX =
449 Pattern.compile(TABLEINFO_FILE_PREFIX + "(\\.([0-9]{" + WIDTH_OF_SEQUENCE_ID + "}))?$");
450
451
452
453
454
455 @VisibleForTesting static int getTableInfoSequenceId(final Path p) {
456 if (p == null) return 0;
457 Matcher m = TABLEINFO_FILE_REGEX.matcher(p.getName());
458 if (!m.matches()) throw new IllegalArgumentException(p.toString());
459 String suffix = m.group(2);
460 if (suffix == null || suffix.length() <= 0) return 0;
461 return Integer.parseInt(m.group(2));
462 }
463
464
465
466
467
468 @VisibleForTesting static String getTableInfoFileName(final int sequenceid) {
469 return TABLEINFO_FILE_PREFIX + "." + formatTableInfoSequenceId(sequenceid);
470 }
471
472
473
474
475
476
477 public static HTableDescriptor getTableDescriptorFromFs(FileSystem fs,
478 Path hbaseRootDir, TableName tableName) throws IOException {
479 Path tableDir = FSUtils.getTableDir(hbaseRootDir, tableName);
480 return getTableDescriptorFromFs(fs, tableDir);
481 }
482
483
484
485
486
487
488 public static HTableDescriptor getTableDescriptorFromFs(FileSystem fs,
489 Path hbaseRootDir, TableName tableName, boolean rewritePb) throws IOException {
490 Path tableDir = FSUtils.getTableDir(hbaseRootDir, tableName);
491 return getTableDescriptorFromFs(fs, tableDir, rewritePb);
492 }
493
494
495
496
497
498
499 public static HTableDescriptor getTableDescriptorFromFs(FileSystem fs, Path tableDir)
500 throws IOException {
501 return getTableDescriptorFromFs(fs, tableDir, false);
502 }
503
504
505
506
507
508
509 public static HTableDescriptor getTableDescriptorFromFs(FileSystem fs, Path tableDir,
510 boolean rewritePb)
511 throws IOException {
512 FileStatus status = getTableInfoPath(fs, tableDir, false);
513 if (status == null) {
514 throw new TableInfoMissingException("No table descriptor file under " + tableDir);
515 }
516 return readTableDescriptor(fs, status, rewritePb);
517 }
518
519 private static HTableDescriptor readTableDescriptor(FileSystem fs, FileStatus status,
520 boolean rewritePb)
521 throws IOException {
522 int len = Ints.checkedCast(status.getLen());
523 byte [] content = new byte[len];
524 FSDataInputStream fsDataInputStream = fs.open(status.getPath());
525 try {
526 fsDataInputStream.readFully(content);
527 } finally {
528 fsDataInputStream.close();
529 }
530 HTableDescriptor htd = null;
531 try {
532 htd = HTableDescriptor.parseFrom(content);
533 } catch (DeserializationException e) {
534
535 try {
536 HTableDescriptor ohtd = HTableDescriptor.parseFrom(content);
537 LOG.warn("Found old table descriptor, converting to new format for table " +
538 ohtd.getTableName());
539 htd = new HTableDescriptor(ohtd);
540 if (rewritePb) rewriteTableDescriptor(fs, status, htd);
541 } catch (DeserializationException e1) {
542 throw new IOException("content=" + Bytes.toShort(content), e1);
543 }
544 }
545 if (rewritePb && !ProtobufUtil.isPBMagicPrefix(content)) {
546
547 rewriteTableDescriptor(fs, status, htd);
548 }
549 return htd;
550 }
551
552 private static void rewriteTableDescriptor(final FileSystem fs, final FileStatus status,
553 final HTableDescriptor td)
554 throws IOException {
555 Path tableInfoDir = status.getPath().getParent();
556 Path tableDir = tableInfoDir.getParent();
557 writeTableDescriptor(fs, td, tableDir, status);
558 }
559
560
561
562
563
564
565 @VisibleForTesting Path updateTableDescriptor(HTableDescriptor htd)
566 throws IOException {
567 if (fsreadonly) {
568 throw new NotImplementedException("Cannot update a table descriptor - in read only mode");
569 }
570 Path tableDir = getTableDir(htd.getTableName());
571 Path p = writeTableDescriptor(fs, htd, tableDir, getTableInfoPath(tableDir));
572 if (p == null) throw new IOException("Failed update");
573 LOG.info("Updated tableinfo=" + p);
574 if (usecache) {
575 this.cache.put(htd.getTableName(), htd);
576 }
577 return p;
578 }
579
580
581
582
583
584
585 public void deleteTableDescriptorIfExists(TableName tableName) throws IOException {
586 if (fsreadonly) {
587 throw new NotImplementedException("Cannot delete a table descriptor - in read only mode");
588 }
589
590 Path tableDir = getTableDir(tableName);
591 Path tableInfoDir = new Path(tableDir, TABLEINFO_DIR);
592 deleteTableDescriptorFiles(fs, tableInfoDir, Integer.MAX_VALUE);
593 }
594
595
596
597
598
599 private static void deleteTableDescriptorFiles(FileSystem fs, Path dir, int maxSequenceId)
600 throws IOException {
601 FileStatus [] status = FSUtils.listStatus(fs, dir, TABLEINFO_PATHFILTER);
602 for (FileStatus file : status) {
603 Path path = file.getPath();
604 int sequenceId = getTableInfoSequenceId(path);
605 if (sequenceId <= maxSequenceId) {
606 boolean success = FSUtils.delete(fs, path, false);
607 if (success) {
608 LOG.debug("Deleted table descriptor at " + path);
609 } else {
610 LOG.error("Failed to delete descriptor at " + path);
611 }
612 }
613 }
614 }
615
616
617
618
619
620
621
622
623
624
625 private static Path writeTableDescriptor(final FileSystem fs,
626 final HTableDescriptor htd, final Path tableDir,
627 final FileStatus currentDescriptorFile) throws IOException {
628
629
630 Path tmpTableDir = new Path(tableDir, TMP_DIR);
631 Path tableInfoDir = new Path(tableDir, TABLEINFO_DIR);
632
633
634
635
636
637
638 int currentSequenceId = currentDescriptorFile == null ? 0 :
639 getTableInfoSequenceId(currentDescriptorFile.getPath());
640 int newSequenceId = currentSequenceId;
641
642
643 int retries = 10;
644 int retrymax = currentSequenceId + retries;
645 Path tableInfoDirPath = null;
646 do {
647 newSequenceId += 1;
648 String filename = getTableInfoFileName(newSequenceId);
649 Path tempPath = new Path(tmpTableDir, filename);
650 if (fs.exists(tempPath)) {
651 LOG.debug(tempPath + " exists; retrying up to " + retries + " times");
652 continue;
653 }
654 tableInfoDirPath = new Path(tableInfoDir, filename);
655 try {
656 writeHTD(fs, tempPath, htd);
657 fs.mkdirs(tableInfoDirPath.getParent());
658 if (!fs.rename(tempPath, tableInfoDirPath)) {
659 throw new IOException("Failed rename of " + tempPath + " to " + tableInfoDirPath);
660 }
661 LOG.debug("Wrote descriptor into: " + tableInfoDirPath);
662 } catch (IOException ioe) {
663
664 LOG.debug("Failed write and/or rename; retrying", ioe);
665 if (!FSUtils.deleteDirectory(fs, tempPath)) {
666 LOG.warn("Failed cleanup of " + tempPath);
667 }
668 tableInfoDirPath = null;
669 continue;
670 }
671 break;
672 } while (newSequenceId < retrymax);
673 if (tableInfoDirPath != null) {
674
675 deleteTableDescriptorFiles(fs, tableInfoDir, newSequenceId - 1);
676 }
677 return tableInfoDirPath;
678 }
679
680 private static void writeHTD(final FileSystem fs, final Path p, final HTableDescriptor htd)
681 throws IOException {
682 FSDataOutputStream out = fs.create(p, false);
683 try {
684
685
686 out.write(htd.toByteArray());
687 } finally {
688 out.close();
689 }
690 }
691
692
693
694
695
696
697 public boolean createTableDescriptor(HTableDescriptor htd) throws IOException {
698 return createTableDescriptor(htd, false);
699 }
700
701
702
703
704
705
706
707
708 public boolean createTableDescriptor(HTableDescriptor htd, boolean forceCreation)
709 throws IOException {
710 Path tableDir = getTableDir(htd.getTableName());
711 return createTableDescriptorForTableDirectory(tableDir, htd, forceCreation);
712 }
713
714
715
716
717
718
719
720
721
722
723
724
725 public boolean createTableDescriptorForTableDirectory(Path tableDir,
726 HTableDescriptor htd, boolean forceCreation) throws IOException {
727 if (fsreadonly) {
728 throw new NotImplementedException("Cannot create a table descriptor - in read only mode");
729 }
730 FileStatus status = getTableInfoPath(fs, tableDir);
731 if (status != null) {
732 LOG.debug("Current tableInfoPath = " + status.getPath());
733 if (!forceCreation) {
734 if (fs.exists(status.getPath()) && status.getLen() > 0) {
735 if (readTableDescriptor(fs, status, false).equals(htd)) {
736 LOG.debug("TableInfo already exists.. Skipping creation");
737 return false;
738 }
739 }
740 }
741 }
742 Path p = writeTableDescriptor(fs, htd, tableDir, status);
743 return p != null;
744 }
745
746 }
747