View Javadoc

1   /**
2    * The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.migration;
21  
22  import java.io.IOException;
23  import java.util.Arrays;
24  import java.util.Comparator;
25  import java.util.List;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.conf.Configuration;
30  import org.apache.hadoop.fs.FSDataInputStream;
31  import org.apache.hadoop.fs.FileStatus;
32  import org.apache.hadoop.fs.FileSystem;
33  import org.apache.hadoop.fs.Path;
34  import org.apache.hadoop.fs.PathFilter;
35  import org.apache.hadoop.hbase.HConstants;
36  import org.apache.hadoop.hbase.HRegionInfo;
37  import org.apache.hadoop.hbase.HTableDescriptor;
38  import org.apache.hadoop.hbase.NamespaceDescriptor;
39  import org.apache.hadoop.hbase.ServerName;
40  import org.apache.hadoop.hbase.TableName;
41  import org.apache.hadoop.hbase.catalog.MetaEditor;
42  import org.apache.hadoop.hbase.exceptions.DeserializationException;
43  import org.apache.hadoop.hbase.regionserver.HRegion;
44  import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
45  import org.apache.hadoop.hbase.regionserver.wal.HLog;
46  import org.apache.hadoop.hbase.regionserver.wal.HLogFactory;
47  import org.apache.hadoop.hbase.regionserver.wal.HLogUtil;
48  import org.apache.hadoop.hbase.security.access.AccessControlLists;
49  import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
50  import org.apache.hadoop.hbase.util.Bytes;
51  import org.apache.hadoop.hbase.util.FSTableDescriptors;
52  import org.apache.hadoop.hbase.util.FSUtils;
53  import org.apache.hadoop.util.Tool;
54  
55  import com.google.common.collect.Lists;
56  import com.google.common.primitives.Ints;
57  
58  /**
59   * Upgrades old 0.94 filesystem layout to namespace layout
60   * Does the following:
61   *
62   * - creates system namespace directory and move .META. table there
63   * renaming .META. table to hbase:meta,
64   * this in turn would require to re-encode the region directory name
65   *
66   * <p>The pre-0.96 paths and dir names are hardcoded in here.
67   */
68  public class NamespaceUpgrade implements Tool {
69    private static final Log LOG = LogFactory.getLog(NamespaceUpgrade.class);
70  
71    private Configuration conf;
72  
73    private FileSystem fs;
74  
75    private Path rootDir;
76    private Path sysNsDir;
77    private Path defNsDir;
78    private Path baseDirs[];
79    private Path backupDir;
80    // First move everything to this tmp .data dir in case there is a table named 'data'
81    private static final String TMP_DATA_DIR = ".data";
82    // Old dir names to migrate.
83    private static final String DOT_LOGS = ".logs";
84    private static final String DOT_OLD_LOGS = ".oldlogs";
85    private static final String DOT_CORRUPT = ".corrupt";
86    private static final String DOT_SPLITLOG = "splitlog";
87    private static final String DOT_ARCHIVE = ".archive";
88    private static final String OLD_ACL = "_acl_";
89    /** Directories that are not HBase table directories */
90    static final List<String> NON_USER_TABLE_DIRS = Arrays.asList(new String[] {
91        DOT_LOGS,
92        DOT_OLD_LOGS,
93        DOT_CORRUPT,
94        DOT_SPLITLOG,
95        HConstants.HBCK_SIDELINEDIR_NAME,
96        DOT_ARCHIVE,
97        HConstants.SNAPSHOT_DIR_NAME,
98        HConstants.HBASE_TEMP_DIRECTORY,
99        TMP_DATA_DIR,
100       OLD_ACL});
101 
102   public NamespaceUpgrade() throws IOException {
103     super();
104   }
105 
106   public void init() throws IOException {
107     this.rootDir = FSUtils.getRootDir(conf);
108     this.fs = FileSystem.get(conf);
109     Path tmpDataDir = new Path(rootDir, TMP_DATA_DIR);
110     sysNsDir = new Path(tmpDataDir, NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR);
111     defNsDir = new Path(tmpDataDir, NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR);
112     baseDirs = new Path[]{rootDir,
113         new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY),
114         new Path(rootDir, HConstants.HBASE_TEMP_DIRECTORY)};
115     backupDir = new Path(rootDir, HConstants.MIGRATION_NAME);
116   }
117 
118 
119   public void upgradeTableDirs() throws IOException, DeserializationException {
120     // if new version is written then upgrade is done
121     if (verifyNSUpgrade(fs, rootDir)) {
122       return;
123     }
124 
125     makeNamespaceDirs();
126 
127     migrateTables();
128 
129     migrateSnapshots();
130 
131     migrateDotDirs();
132 
133     migrateMeta();
134 
135     migrateACL();
136 
137     deleteRoot();
138 
139     FSUtils.setVersion(fs, rootDir);
140   }
141 
142   /**
143    * Remove the -ROOT- dir. No longer of use.
144    * @throws IOException
145    */
146   public void deleteRoot() throws IOException {
147     Path rootDir = new Path(this.rootDir, "-ROOT-");
148     if (this.fs.exists(rootDir)) {
149       if (!this.fs.delete(rootDir, true)) LOG.info("Failed remove of " + rootDir);
150       LOG.info("Deleted " + rootDir);
151     }
152   }
153 
154   /**
155    * Rename all the dot dirs -- .data, .archive, etc. -- as data, archive, etc.; i.e. minus the dot.
156    * @throws IOException
157    */
158   public void migrateDotDirs() throws IOException {
159     // Dot dirs to rename.  Leave the tmp dir named '.tmp' and snapshots as .hbase-snapshot.
160     final Path archiveDir = new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY);
161     Path [][] dirs = new Path[][] {
162       new Path [] {new Path(rootDir, DOT_CORRUPT), new Path(rootDir, HConstants.CORRUPT_DIR_NAME)},
163       new Path [] {new Path(rootDir, DOT_LOGS), new Path(rootDir, HConstants.HREGION_LOGDIR_NAME)},
164       new Path [] {new Path(rootDir, DOT_OLD_LOGS),
165         new Path(rootDir, HConstants.HREGION_OLDLOGDIR_NAME)},
166       new Path [] {new Path(rootDir, TMP_DATA_DIR),
167         new Path(rootDir, HConstants.BASE_NAMESPACE_DIR)}};
168     for (Path [] dir: dirs) {
169       Path src = dir[0];
170       Path tgt = dir[1];
171       if (!this.fs.exists(src)) {
172         LOG.info("Does not exist: " + src);
173         continue;
174       }
175       rename(src, tgt);
176     }
177     // Do the .archive dir.  Need to move its subdirs to the default ns dir under data dir... so
178     // from '.archive/foo', to 'archive/data/default/foo'.
179     Path oldArchiveDir = new Path(rootDir, DOT_ARCHIVE);
180     if (this.fs.exists(oldArchiveDir)) {
181       // This is a pain doing two nn calls but portable over h1 and h2.
182       mkdirs(archiveDir);
183       Path archiveDataDir = new Path(archiveDir, HConstants.BASE_NAMESPACE_DIR);
184       mkdirs(archiveDataDir);
185       rename(oldArchiveDir, new Path(archiveDataDir,
186         NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR));
187     }
188     // Update the system and user namespace dirs removing the dot in front of .data.
189     Path dataDir = new Path(rootDir, HConstants.BASE_NAMESPACE_DIR);
190     sysNsDir = new Path(dataDir, NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR);
191     defNsDir = new Path(dataDir, NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR);
192   }
193 
194   private void mkdirs(final Path p) throws IOException {
195     if (!this.fs.mkdirs(p)) throw new IOException("Failed make of " + p);
196   }
197 
198   private void rename(final Path src, final Path tgt) throws IOException {
199     if (!fs.rename(src, tgt)) {
200       throw new IOException("Failed move " + src + " to " + tgt);
201     }
202   }
203 
204   /**
205    * Create the system and default namespaces dirs
206    * @throws IOException
207    */
208   public void makeNamespaceDirs() throws IOException {
209     if (!fs.exists(sysNsDir)) {
210       if (!fs.mkdirs(sysNsDir)) {
211         throw new IOException("Failed to create system namespace dir: " + sysNsDir);
212       }
213     }
214     if (!fs.exists(defNsDir)) {
215       if (!fs.mkdirs(defNsDir)) {
216         throw new IOException("Failed to create default namespace dir: " + defNsDir);
217       }
218     }
219   }
220 
221   /**
222    * Migrate all tables into respective namespaces, either default or system.  We put them into
223    * a temporary location, '.data', in case a user table is name 'data'.  In a later method we will
224    * move stuff from .data to data.
225    * @throws IOException
226    */
227   public void migrateTables() throws IOException {
228     List<String> sysTables = Lists.newArrayList("-ROOT-",".META.");
229 
230     // Migrate tables including archive and tmp
231     for (Path baseDir: baseDirs) {
232       if (!fs.exists(baseDir)) continue;
233       List<Path> oldTableDirs = FSUtils.getLocalTableDirs(fs, baseDir);
234       for (Path oldTableDir: oldTableDirs) {
235         if (NON_USER_TABLE_DIRS.contains(oldTableDir.getName())) continue;
236         if (sysTables.contains(oldTableDir.getName())) continue;
237         // Make the new directory under the ns to which we will move the table.
238         Path nsDir = new Path(this.defNsDir,
239           TableName.valueOf(oldTableDir.getName()).getQualifierAsString());
240         if (!fs.exists(nsDir.getParent())) {
241           if (!fs.mkdirs(nsDir.getParent())) {
242             throw new IOException("Failed to create namespace dir "+nsDir.getParent());
243           }
244         }
245         if (sysTables.indexOf(oldTableDir.getName()) < 0) {
246           LOG.info("Migrating table " + oldTableDir.getName() + " to " + nsDir);
247           if (!fs.rename(oldTableDir, nsDir)) {
248             throw new IOException("Failed to move "+oldTableDir+" to namespace dir "+nsDir);
249           }
250         }
251       }
252     }
253   }
254 
255   public void migrateSnapshots() throws IOException {
256     //migrate snapshot dir
257     Path oldSnapshotDir = new Path(rootDir, HConstants.OLD_SNAPSHOT_DIR_NAME);
258     Path newSnapshotDir = new Path(rootDir, HConstants.SNAPSHOT_DIR_NAME);
259     if (fs.exists(oldSnapshotDir)) {
260       boolean foundOldSnapshotDir = false;
261       // Logic to verify old snapshot dir culled from SnapshotManager
262       // ignore all the snapshots in progress
263       FileStatus[] snapshots = fs.listStatus(oldSnapshotDir,
264         new SnapshotDescriptionUtils.CompletedSnaphotDirectoriesFilter(fs));
265       // loop through all the completed snapshots
266       for (FileStatus snapshot : snapshots) {
267         Path info = new Path(snapshot.getPath(), SnapshotDescriptionUtils.SNAPSHOTINFO_FILE);
268         // if the snapshot is bad
269         if (fs.exists(info)) {
270           foundOldSnapshotDir = true;
271           break;
272         }
273       }
274       if(foundOldSnapshotDir) {
275         LOG.info("Migrating snapshot dir");
276         if (!fs.rename(oldSnapshotDir, newSnapshotDir)) {
277           throw new IOException("Failed to move old snapshot dir "+
278               oldSnapshotDir+" to new "+newSnapshotDir);
279         }
280       }
281     }
282   }
283 
284   public void migrateMeta() throws IOException {
285     Path newMetaDir = new Path(this.sysNsDir, TableName.META_TABLE_NAME.getQualifierAsString());
286     Path newMetaRegionDir =
287       new Path(newMetaDir, HRegionInfo.FIRST_META_REGIONINFO.getEncodedName());
288     Path oldMetaDir = new Path(rootDir, ".META.");
289     if (fs.exists(oldMetaDir)) {
290       LOG.info("Migrating meta table " + oldMetaDir.getName() + " to " + newMetaDir);
291       if (!fs.rename(oldMetaDir, newMetaDir)) {
292         throw new IOException("Failed to migrate meta table "
293             + oldMetaDir.getName() + " to " + newMetaDir);
294       }
295     }
296 
297     // Since meta table name has changed rename meta region dir from it's old encoding to new one
298     Path oldMetaRegionDir = HRegion.getRegionDir(rootDir,
299       new Path(newMetaDir, "1028785192").toString());
300     if (fs.exists(oldMetaRegionDir)) {
301       LOG.info("Migrating meta region " + oldMetaRegionDir + " to " + newMetaRegionDir);
302       if (!fs.rename(oldMetaRegionDir, newMetaRegionDir)) {
303         throw new IOException("Failed to migrate meta region "
304             + oldMetaRegionDir + " to " + newMetaRegionDir);
305       }
306     }
307 
308     Path oldRootDir = new Path(rootDir, "-ROOT-");
309     if(!fs.rename(oldRootDir, backupDir)) {
310       throw new IllegalStateException("Failed to old data: "+oldRootDir+" to "+backupDir);
311     }
312   }
313 
314   public void migrateACL() throws IOException {
315 
316     TableName oldTableName = TableName.valueOf(OLD_ACL);
317     Path oldTablePath = new Path(rootDir, oldTableName.getNameAsString());
318 
319     if(!fs.exists(oldTablePath)) {
320       return;
321     }
322 
323     LOG.info("Migrating ACL table");
324 
325     TableName newTableName = AccessControlLists.ACL_TABLE_NAME;
326     Path newTablePath = FSUtils.getTableDir(rootDir, newTableName);
327     HTableDescriptor oldDesc =
328         readTableDescriptor(fs, getCurrentTableInfoStatus(fs, oldTablePath));
329 
330     if(FSTableDescriptors.getTableInfoPath(fs, newTablePath) == null) {
331       LOG.info("Creating new tableDesc for ACL");
332       HTableDescriptor newDesc = new HTableDescriptor(oldDesc);
333       newDesc.setName(newTableName);
334       new FSTableDescriptors(this.conf).createTableDescriptorForTableDirectory(
335         newTablePath, newDesc, true);
336     }
337 
338 
339     ServerName fakeServer = new ServerName("nsupgrade",96,123);
340     String metaLogName = HLogUtil.getHLogDirectoryName(fakeServer.toString());
341     HLog metaHLog = HLogFactory.createMetaHLog(fs, rootDir,
342         metaLogName, conf, null,
343         fakeServer.toString());
344     HRegion meta = HRegion.openHRegion(rootDir, HRegionInfo.FIRST_META_REGIONINFO,
345         HTableDescriptor.META_TABLEDESC, metaHLog, conf);
346     HRegion region = null;
347     try {
348       for(Path regionDir : FSUtils.getRegionDirs(fs, oldTablePath)) {
349         LOG.info("Migrating ACL region "+regionDir.getName());
350         HRegionInfo oldRegionInfo = HRegionFileSystem.loadRegionInfoFileContent(fs, regionDir);
351         HRegionInfo newRegionInfo =
352             new HRegionInfo(newTableName,
353                 oldRegionInfo.getStartKey(),
354                 oldRegionInfo.getEndKey(),
355                 oldRegionInfo.isSplit(),
356                 oldRegionInfo.getRegionId());
357         newRegionInfo.setOffline(oldRegionInfo.isOffline());
358         region =
359             new HRegion(
360                 HRegionFileSystem.openRegionFromFileSystem(conf, fs, oldTablePath,
361                     oldRegionInfo, false),
362                 metaHLog,
363                 conf,
364                 oldDesc,
365                 null);
366         region.initialize();
367         //Run major compaction to archive old stores
368         //to keep any snapshots to _acl_ unbroken
369         region.compactStores(true);
370         region.waitForFlushesAndCompactions();
371         region.close();
372 
373         //Create new region dir
374         Path newRegionDir = new Path(newTablePath, newRegionInfo.getEncodedName());
375         if(!fs.exists(newRegionDir)) {
376           if(!fs.mkdirs(newRegionDir)) {
377             throw new IllegalStateException("Failed to create new region dir: " + newRegionDir);
378           }
379         }
380 
381         //create new region info file, delete in case one exists
382         HRegionFileSystem.openRegionFromFileSystem(conf, fs, newTablePath, newRegionInfo, false);
383 
384         //migrate region contents
385         for(FileStatus file : fs.listStatus(regionDir, new FSUtils.UserTableDirFilter(fs)))  {
386           if(file.getPath().getName().equals(HRegionFileSystem.REGION_INFO_FILE))
387             continue;
388           if(!fs.rename(file.getPath(), newRegionDir))  {
389             throw new IllegalStateException("Failed to move file "+file.getPath()+" to " +
390                 newRegionDir);
391           }
392         }
393         meta.put(MetaEditor.makePutFromRegionInfo(newRegionInfo));
394         meta.delete(MetaEditor.makeDeleteFromRegionInfo(oldRegionInfo));
395       }
396     } finally {
397       meta.flushcache();
398       meta.waitForFlushesAndCompactions();
399       meta.close();
400       metaHLog.closeAndDelete();
401       if(region != null) {
402         region.close();
403       }
404     }
405     if(!fs.rename(oldTablePath, backupDir)) {
406       throw new IllegalStateException("Failed to old data: "+oldTablePath+" to "+backupDir);
407     }
408   }
409 
410   //Culled from FSTableDescriptors
411   private static HTableDescriptor readTableDescriptor(FileSystem fs,
412                                                       FileStatus status) throws IOException {
413     int len = Ints.checkedCast(status.getLen());
414     byte [] content = new byte[len];
415     FSDataInputStream fsDataInputStream = fs.open(status.getPath());
416     try {
417       fsDataInputStream.readFully(content);
418     } finally {
419       fsDataInputStream.close();
420     }
421     HTableDescriptor htd = null;
422     try {
423       htd = HTableDescriptor.parseFrom(content);
424     } catch (DeserializationException e) {
425       throw new IOException("content=" + Bytes.toShort(content), e);
426     }
427     return htd;
428   }
429 
430   private static final PathFilter TABLEINFO_PATHFILTER = new PathFilter() {
431     @Override
432     public boolean accept(Path p) {
433       // Accept any file that starts with TABLEINFO_NAME
434       return p.getName().startsWith(".tableinfo");
435     }
436   };
437 
438   static final Comparator<FileStatus> TABLEINFO_FILESTATUS_COMPARATOR =
439   new Comparator<FileStatus>() {
440     @Override
441     public int compare(FileStatus left, FileStatus right) {
442       return -left.compareTo(right);
443     }};
444 
445   // logic culled from FSTableDescriptors
446   static FileStatus getCurrentTableInfoStatus(FileSystem fs, Path dir)
447   throws IOException {
448     FileStatus [] status = FSUtils.listStatus(fs, dir, TABLEINFO_PATHFILTER);
449     if (status == null || status.length < 1) return null;
450     FileStatus mostCurrent = null;
451     for (FileStatus file : status) {
452       if (mostCurrent == null || TABLEINFO_FILESTATUS_COMPARATOR.compare(file, mostCurrent) < 0) {
453         mostCurrent = file;
454       }
455     }
456     return mostCurrent;
457   }
458 
459   public static boolean verifyNSUpgrade(FileSystem fs, Path rootDir)
460       throws IOException {
461     try {
462       return FSUtils.getVersion(fs, rootDir).equals(HConstants.FILE_SYSTEM_VERSION);
463     } catch (DeserializationException e) {
464       throw new IOException("Failed to verify namespace upgrade", e);
465     }
466   }
467 
468 
469   @Override
470   public int run(String[] args) throws Exception {
471     if (args.length < 1 || !args[0].equals("--upgrade")) {
472       System.out.println("Usage: <CMD> --upgrade");
473       return 0;
474     }
475     init();
476     upgradeTableDirs();
477     return 0;
478   }
479 
480   @Override
481   public void setConf(Configuration conf) {
482     this.conf = conf;
483   }
484 
485   @Override
486   public Configuration getConf() {
487     return conf;
488   }
489 }