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