View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
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   * Implementation of {@link TableDescriptors} that reads descriptors from the
55   * passed filesystem.  It expects descriptors to be in a file in the
56   * {@link #TABLEINFO_DIR} subdir of the table's directory in FS.  Can be read-only
57   *  -- i.e. does not modify the filesystem or can be read and write.
58   *
59   * <p>Also has utility for keeping up the table descriptors tableinfo file.
60   * The table schema file is kept in the {@link #TABLEINFO_DIR} subdir
61   * of the table directory in the filesystem.
62   * It has a {@link #TABLEINFO_FILE_PREFIX} and then a suffix that is the
63   * edit sequenceid: e.g. <code>.tableinfo.0000000003</code>.  This sequenceid
64   * is always increasing.  It starts at zero.  The table schema file with the
65   * highest sequenceid has the most recent schema edit. Usually there is one file
66   * only, the most recent but there may be short periods where there are more
67   * than one file. Old files are eventually cleaned.  Presumption is that there
68   * will not be lots of concurrent clients making table schema edits.  If so,
69   * the below needs a bit of a reworking and perhaps some supporting api in hdfs.
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    /** The file name prefix used to store HTD in HDFS  */
84    static final String TABLEINFO_FILE_PREFIX = ".tableinfo";
85    static final String TABLEINFO_DIR = ".tabledesc";
86    static final String TMP_DIR = ".tmp";
87  
88    // This cache does not age out the old stuff.  Thinking is that the amount
89    // of data we keep up in here is so small, no need to do occasional purge.
90    // TODO.
91    private final Map<TableName, HTableDescriptor> cache =
92      new ConcurrentHashMap<TableName, HTableDescriptor>();
93  
94    /**
95     * Table descriptor for <code>hbase:meta</code> catalog table
96     */
97    private final HTableDescriptor metaTableDescriptor;
98  
99    /**
100    * Construct a FSTableDescriptors instance using the hbase root dir of the given
101    * conf and the filesystem where that root dir lives.
102    * This instance can do write operations (is not read only).
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    * @param fsreadonly True if we are read-only when it comes to filesystem
115    * operations; i.e. on remove, we do not do delete in fs.
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    * Get the current table descriptor for the given table, or null if none exists.
147    *
148    * Uses a local cache of the descriptor but still checks the filesystem on each call
149    * to see if a newer file has been created since the cached one was read.
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     // hbase:meta is already handled. If some one tries to get the descriptor for
160     // .logs, .oldlogs or .corrupt throw an exception.
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       // Look in cache of descriptors.
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       // ignore. This is regular operation
181     } catch (IOException ioe) {
182       LOG.debug("Exception during readTableDecriptor. Current table name = "
183                   + tablename, ioe);
184     }
185     // last HTD written wins
186     if (usecache && tdmt != null) {
187       this.cache.put(tablename, tdmt);
188     }
189 
190     return tdmt;
191   }
192 
193   /**
194    * Returns a map from table name to table descriptor for all tables.
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       // add hbase:meta to the response
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           // inability of retrieving one HTD shouldn't stop getting the remaining
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   /* (non-Javadoc)
231    * @see org.apache.hadoop.hbase.TableDescriptors#getTableDescriptors(org.apache.hadoop.fs.FileSystem, org.apache.hadoop.fs.Path)
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         // inability of retrieving one HTD shouldn't stop getting the remaining
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    * Adds (or updates) the table descriptor to the FileSystem
255    * and updates the local cache with it.
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    * Removes the table descriptor from the local cache and returns it.
274    * If not in read only mode, it also deletes the entire table directory(!)
275    * from the FileSystem.
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    * Checks if a current table info file exists for the given table
299    *
300    * @param tableName name of table
301    * @return true if exists
302    * @throws IOException
303    */
304   public boolean isTableInfoExists(TableName tableName) throws IOException {
305     return getTableInfoPath(tableName) != null;
306   }
307 
308   /**
309    * Find the most current table info file for the given table in the hbase root directory.
310    * @return The file status of the current table info file or null if it does not exist
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    * Find the most current table info file for the table located in the given table directory.
324    *
325    * Looks within the {@link #TABLEINFO_DIR} subdirectory of the given directory for any table info
326    * files and takes the 'current' one - meaning the one with the highest sequence number if present
327    * or no sequence number at all if none exist (for backward compatibility from before there
328    * were sequence numbers).
329    *
330    * @return The file status of the current table info file or null if it does not exist
331    * @throws IOException
332    */
333   public static FileStatus getTableInfoPath(FileSystem fs, Path tableDir)
334   throws IOException {
335     return getTableInfoPath(fs, tableDir, false);
336   }
337 
338   /**
339    * Find the most current table info file for the table in the given table directory.
340    *
341    * Looks within the {@link #TABLEINFO_DIR} subdirectory of the given directory for any table info
342    * files and takes the 'current' one - meaning the one with the highest sequence number if
343    * present or no sequence number at all if none exist (for backward compatibility from before
344    * there were sequence numbers).
345    * If there are multiple table info files found and removeOldFiles is true it also deletes the
346    * older files.
347    *
348    * @return The file status of the current table info file or null if none exist
349    * @throws IOException
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    * Find the most current table info file in the given directory
359    *
360    * Looks within the given directory for any table info files
361    * and takes the 'current' one - meaning the one with the highest sequence number if present
362    * or no sequence number at all if none exist (for backward compatibility from before there
363    * were sequence numbers).
364    * If there are multiple possible files found
365    * and the we're not in read only mode it also deletes the older files.
366    *
367    * @return The file status of the current table info file or null if it does not exist
368    * @throws IOException
369    */
370   // only visible for FSTableDescriptorMigrationToSubdir, can be removed with that
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       // Clean away old versions
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    * Compare {@link FileStatus} instances by {@link Path#getName()}. Returns in
399    * reverse order.
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    * Return the table directory in HDFS
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       // Accept any file that starts with TABLEINFO_NAME
420       return p.getName().startsWith(TABLEINFO_FILE_PREFIX);
421     }};
422 
423   /**
424    * Width of the sequenceid that is a suffix on a tableinfo file.
425    */
426   @VisibleForTesting static final int WIDTH_OF_SEQUENCE_ID = 10;
427 
428   /*
429    * @param number Number to use as suffix.
430    * @return Returns zero-prefixed decimal version of passed
431    * number (Does absolute in case number is negative).
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    * Regex to eat up sequenceid suffix on a .tableinfo file.
445    * Use regex because may encounter oldstyle .tableinfos where there is no
446    * sequenceid on the end.
447    */
448   private static final Pattern TABLEINFO_FILE_REGEX =
449     Pattern.compile(TABLEINFO_FILE_PREFIX + "(\\.([0-9]{" + WIDTH_OF_SEQUENCE_ID + "}))?$");
450 
451   /**
452    * @param p Path to a <code>.tableinfo</code> file.
453    * @return The current editid or 0 if none found.
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    * @param sequenceid
466    * @return Name of tableinfo file.
467    */
468   @VisibleForTesting static String getTableInfoFileName(final int sequenceid) {
469     return TABLEINFO_FILE_PREFIX + "." + formatTableInfoSequenceId(sequenceid);
470   }
471 
472   /**
473    * Returns the latest table descriptor for the given table directly from the file system
474    * if it exists, bypassing the local cache.
475    * Returns null if it's not found.
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    * Returns the latest table descriptor for the table located at the given directory
485    * directly from the file system if it exists.
486    * @throws TableInfoMissingException if there is no descriptor
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    * Returns the latest table descriptor for the table located at the given directory
496    * directly from the file system if it exists.
497    * @throws TableInfoMissingException if there is no descriptor
498    */
499   public static HTableDescriptor getTableDescriptorFromFs(FileSystem fs, Path tableDir)
500   throws IOException {
501     return getTableDescriptorFromFs(fs, tableDir, false);
502   }
503 
504   /**
505    * Returns the latest table descriptor for the table located at the given directory
506    * directly from the file system if it exists.
507    * @throws TableInfoMissingException if there is no descriptor
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       // we have old HTableDescriptor here
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       // Convert the file over to be pb before leaving here.
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    * Update table descriptor on the file system
562    * @throws IOException Thrown if failed update.
563    * @throws NotImplementedException if in read only mode
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    * Deletes all the table descriptor files from the file system.
582    * Used in unit tests only.
583    * @throws NotImplementedException if in read only mode
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    * Deletes files matching the table info file pattern within the given directory
597    * whose sequenceId is at most the given max sequenceId.
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    * Attempts to write a new table descriptor to the given table's directory.
618    * It first writes it to the .tmp dir then uses an atomic rename to move it into place.
619    * It begins at the currentSequenceId + 1 and tries 10 times to find a new sequence number
620    * not already in use.
621    * Removes the current descriptor file if passed in.
622    *
623    * @return Descriptor file or null if we failed write.
624    */
625   private static Path writeTableDescriptor(final FileSystem fs,
626     final HTableDescriptor htd, final Path tableDir,
627     final FileStatus currentDescriptorFile) throws IOException {
628     // Get temporary dir into which we'll first write a file to avoid half-written file phenomenon.
629     // This directory is never removed to avoid removing it out from under a concurrent writer.
630     Path tmpTableDir = new Path(tableDir, TMP_DIR);
631     Path tableInfoDir = new Path(tableDir, TABLEINFO_DIR);
632 
633     // What is current sequenceid?  We read the current sequenceid from
634     // the current file.  After we read it, another thread could come in and
635     // compete with us writing out next version of file.  The below retries
636     // should help in this case some but its hard to do guarantees in face of
637     // concurrent schema edits.
638     int currentSequenceId = currentDescriptorFile == null ? 0 :
639       getTableInfoSequenceId(currentDescriptorFile.getPath());
640     int newSequenceId = currentSequenceId;
641 
642     // Put arbitrary upperbound on how often we retry
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         // Presume clash of names or something; go around again.
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       // if we succeeded, remove old table info files.
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       // We used to write this file out as a serialized HTD Writable followed by two '\n's and then
685       // the toString version of HTD.  Now we just write out the pb serialization.
686       out.write(htd.toByteArray());
687     } finally {
688       out.close();
689     }
690   }
691 
692   /**
693    * Create new HTableDescriptor in HDFS. Happens when we are creating table.
694    * Used by tests.
695    * @return True if we successfully created file.
696    */
697   public boolean createTableDescriptor(HTableDescriptor htd) throws IOException {
698     return createTableDescriptor(htd, false);
699   }
700 
701   /**
702    * Create new HTableDescriptor in HDFS. Happens when we are creating table. If
703    * forceCreation is true then even if previous table descriptor is present it
704    * will be overwritten
705    *
706    * @return True if we successfully created file.
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    * Create a new HTableDescriptor in HDFS in the specified table directory. Happens when we create
716    * a new table or snapshot a table.
717    * @param tableDir table directory under which we should write the file
718    * @param htd description of the table to write
719    * @param forceCreation if <tt>true</tt>,then even if previous table descriptor is present it will
720    *          be overwritten
721    * @return <tt>true</tt> if the we successfully created the file, <tt>false</tt> if the file
722    *         already exists and we weren't forcing the descriptor creation.
723    * @throws IOException if a filesystem error occurs
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