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.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    @VisibleForTesting long cachehits = 0;
78    @VisibleForTesting long invocations = 0;
79  
80    /** The file name prefix used to store HTD in HDFS  */
81    static final String TABLEINFO_FILE_PREFIX = ".tableinfo";
82    static final String TABLEINFO_DIR = ".tabledesc";
83    static final String TMP_DIR = ".tmp";
84  
85    // This cache does not age out the old stuff.  Thinking is that the amount
86    // of data we keep up in here is so small, no need to do occasional purge.
87    // TODO.
88    private final Map<TableName, TableDescriptorAndModtime> cache =
89      new ConcurrentHashMap<TableName, TableDescriptorAndModtime>();
90  
91    /**
92     * Data structure to hold modification time and table descriptor.
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    * Construct a FSTableDescriptors instance using the hbase root dir of the given
114    * conf and the filesystem where that root dir lives.
115    * This instance can do write operations (is not read only).
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    * @param fsreadonly True if we are read-only when it comes to filesystem
127    * operations; i.e. on remove, we do not do delete in fs.
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    * Get the current table descriptor for the given table, or null if none exists.
139    * 
140    * Uses a local cache of the descriptor but still checks the filesystem on each call
141    * to see if a newer file has been created since the cached one was read.
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     // hbase:meta is already handled. If some one tries to get the descriptor for
152     // .logs, .oldlogs or .corrupt throw an exception.
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     // Look in cache of descriptors.
158     TableDescriptorAndModtime cachedtdm = this.cache.get(tablename);
159 
160     if (cachedtdm != null) {
161       // Check mod time has not changed (this is trip to NN).
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    * Returns a map from table name to table descriptor for all tables.
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         // inability of retrieving one HTD shouldn't stop getting the remaining
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   /* (non-Javadoc)
208    * @see org.apache.hadoop.hbase.TableDescriptors#getTableDescriptors(org.apache.hadoop.fs.FileSystem, org.apache.hadoop.fs.Path)
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         // inability of retrieving one HTD shouldn't stop getting the remaining
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    * Adds (or updates) the table descriptor to the FileSystem
232    * and updates the local cache with it.
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    * Removes the table descriptor from the local cache and returns it.
253    * If not in read only mode, it also deletes the entire table directory(!)
254    * from the FileSystem.
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    * Checks if a current table info file exists for the given table
274    * 
275    * @param tableName name of table
276    * @return true if exists
277    * @throws IOException
278    */
279   public boolean isTableInfoExists(TableName tableName) throws IOException {
280     return getTableInfoPath(tableName) != null;
281   }
282   
283   /**
284    * Find the most current table info file for the given table in the hbase root directory.
285    * @return The file status of the current table info file or null if it does not exist
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    * Find the most current table info file for the table located in the given table directory.
299    * 
300    * Looks within the {@link #TABLEINFO_DIR} subdirectory of the given directory for any table info
301    * files and takes the 'current' one - meaning the one with the highest sequence number if present
302    * or no sequence number at all if none exist (for backward compatibility from before there
303    * were sequence numbers).
304    * 
305    * @return The file status of the current table info file or null if it does not exist
306    * @throws IOException
307    */
308   public static FileStatus getTableInfoPath(FileSystem fs, Path tableDir)
309   throws IOException {
310     return getTableInfoPath(fs, tableDir, false);
311   }
312   
313   /**
314    * Find the most current table info file for the table in the given table directory.
315    * 
316    * Looks within the {@link #TABLEINFO_DIR} subdirectory of the given directory for any table info
317    * files and takes the 'current' one - meaning the one with the highest sequence number if
318    * present or no sequence number at all if none exist (for backward compatibility from before
319    * there were sequence numbers).
320    * If there are multiple table info files found and removeOldFiles is true it also deletes the
321    * older files.
322    * 
323    * @return The file status of the current table info file or null if none exist
324    * @throws IOException
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    * Find the most current table info file in the given directory
334    * 
335    * Looks within the given directory for any table info files
336    * and takes the 'current' one - meaning the one with the highest sequence number if present
337    * or no sequence number at all if none exist (for backward compatibility from before there
338    * were sequence numbers).
339    * If there are multiple possible files found
340    * and the we're not in read only mode it also deletes the older files.
341    * 
342    * @return The file status of the current table info file or null if it does not exist
343    * @throws IOException
344    */
345   // only visible for FSTableDescriptorMigrationToSubdir, can be removed with that
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       // Clean away old versions
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    * Compare {@link FileStatus} instances by {@link Path#getName()}. Returns in
374    * reverse order.
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    * Return the table directory in HDFS
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       // Accept any file that starts with TABLEINFO_NAME
395       return p.getName().startsWith(TABLEINFO_FILE_PREFIX);
396     }}; 
397 
398   /**
399    * Width of the sequenceid that is a suffix on a tableinfo file.
400    */
401   @VisibleForTesting static final int WIDTH_OF_SEQUENCE_ID = 10;
402 
403   /*
404    * @param number Number to use as suffix.
405    * @return Returns zero-prefixed decimal version of passed
406    * number (Does absolute in case number is negative).
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    * Regex to eat up sequenceid suffix on a .tableinfo file.
420    * Use regex because may encounter oldstyle .tableinfos where there is no
421    * sequenceid on the end.
422    */
423   private static final Pattern TABLEINFO_FILE_REGEX =
424     Pattern.compile(TABLEINFO_FILE_PREFIX + "(\\.([0-9]{" + WIDTH_OF_SEQUENCE_ID + "}))?$");
425 
426   /**
427    * @param p Path to a <code>.tableinfo</code> file.
428    * @return The current editid or 0 if none found.
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    * @param tabledir
441    * @param sequenceid
442    * @return Name of tableinfo file.
443    */
444   @VisibleForTesting static String getTableInfoFileName(final int sequenceid) {
445     return TABLEINFO_FILE_PREFIX + "." + formatTableInfoSequenceId(sequenceid);
446   }
447 
448   /**
449    * @param fs
450    * @param rootdir
451    * @param tableName
452    * @return Modification time for the table {@link #TABLEINFO_FILE_PREFIX} file
453    * or <code>0</code> if no tableinfo file found.
454    * @throws IOException
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    * Returns the latest table descriptor for the given table directly from the file system
463    * if it exists, bypassing the local cache.
464    * Returns null if it's not found.
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    * Returns the latest table descriptor for the table located at the given directory
474    * directly from the file system if it exists.
475    * @throws TableInfoMissingException if there is no descriptor
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    * @param tableName table name
488    * @return TableDescriptorAndModtime or null if no table descriptor was found
489    * @throws IOException
490    */
491   private TableDescriptorAndModtime getTableDescriptorAndModtime(TableName tableName)
492   throws IOException {
493     // ignore both -ROOT- and hbase:meta tables
494     if (tableName.equals(TableName.META_TABLE_NAME)) {
495       return null;
496     }
497     return getTableDescriptorAndModtime(getTableDir(tableName));
498   }
499 
500   /**
501    * @param tableDir path to table directory
502    * @return TableDescriptorAndModtime or null if no table descriptor was found
503    * at the specified path
504    * @throws IOException
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       // Convert the file over to be pb before leaving here.
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    * Update table descriptor on the file system
543    * @throws IOException Thrown if failed update.
544    * @throws NotImplementedException if in read only mode
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    * Deletes all the table descriptor files from the file system.
560    * Used in unit tests only.
561    * @throws NotImplementedException if in read only mode
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    * Deletes files matching the table info file pattern within the given directory 
575    * whose sequenceId is at most the given max sequenceId.
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    * Attempts to write a new table descriptor to the given table's directory.
596    * It first writes it to the .tmp dir then uses an atomic rename to move it into place.
597    * It begins at the currentSequenceId + 1 and tries 10 times to find a new sequence number
598    * not already in use.
599    * Removes the current descriptor file if passed in.
600    * 
601    * @return Descriptor file or null if we failed write.
602    */
603   private static Path writeTableDescriptor(final FileSystem fs, 
604     final HTableDescriptor htd, final Path tableDir,
605     final FileStatus currentDescriptorFile)
606   throws IOException {  
607     // Get temporary dir into which we'll first write a file to avoid half-written file phenomenon.
608     // This directory is never removed to avoid removing it out from under a concurrent writer.
609     Path tmpTableDir = new Path(tableDir, TMP_DIR);
610     Path tableInfoDir = new Path(tableDir, TABLEINFO_DIR);
611     
612     // What is current sequenceid?  We read the current sequenceid from
613     // the current file.  After we read it, another thread could come in and
614     // compete with us writing out next version of file.  The below retries
615     // should help in this case some but its hard to do guarantees in face of
616     // concurrent schema edits.
617     int currentSequenceId = currentDescriptorFile == null ? 0 :
618       getTableInfoSequenceId(currentDescriptorFile.getPath());
619     int newSequenceId = currentSequenceId;
620     
621     // Put arbitrary upperbound on how often we retry
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         // Presume clash of names or something; go around again.
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       // if we succeeded, remove old table info files.
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       // We used to write this file out as a serialized HTD Writable followed by two '\n's and then
664       // the toString version of HTD.  Now we just write out the pb serialization.
665       out.write(htd.toByteArray());
666     } finally {
667       out.close();
668     }
669   }
670 
671   /**
672    * Create new HTableDescriptor in HDFS. Happens when we are creating table.
673    * Used by tests.
674    * @return True if we successfully created file.
675    */
676   public boolean createTableDescriptor(HTableDescriptor htd) throws IOException {
677     return createTableDescriptor(htd, false);
678   }
679 
680   /**
681    * Create new HTableDescriptor in HDFS. Happens when we are creating table. If
682    * forceCreation is true then even if previous table descriptor is present it
683    * will be overwritten
684    * 
685    * @return True if we successfully created file.
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    * Create a new HTableDescriptor in HDFS in the specified table directory. Happens when we create
695    * a new table or snapshot a table.
696    * @param tableDir table directory under which we should write the file
697    * @param htd description of the table to write
698    * @param forceCreation if <tt>true</tt>,then even if previous table descriptor is present it will
699    *          be overwritten
700    * @return <tt>true</tt> if the we successfully created the file, <tt>false</tt> if the file
701    *         already exists and we weren't forcing the descriptor creation.
702    * @throws IOException if a filesystem error occurs
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