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