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.hdfs.server.namenode;
19 
20 import java.io.File;
21 import java.io.FilenameFilter;
22 import java.io.IOException;
23 import java.nio.file.Files;
24 import java.util.List;
25 
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.apache.hadoop.conf.Configuration;
29 import org.apache.hadoop.hdfs.server.common.Storage;
30 import org.apache.hadoop.hdfs.server.common.Storage.StorageDirectory;
31 import org.apache.hadoop.hdfs.server.common.StorageInfo;
32 
33 import com.google.common.base.Preconditions;
34 import org.apache.hadoop.io.IOUtils;
35 
36 public abstract class NNUpgradeUtil {
37 
38   private static final Log LOG = LogFactory.getLog(NNUpgradeUtil.class);
39 
40   /**
41    * Return true if this storage dir can roll back to the previous storage
42    * state, false otherwise. The NN will refuse to run the rollback operation
43    * unless at least one JM or fsimage storage directory can roll back.
44    *
45    * @param storage the storage info for the current state
46    * @param prevStorage the storage info for the previous (unupgraded) state
47    * @param targetLayoutVersion the layout version we intend to roll back to
48    * @return true if this JM can roll back, false otherwise.
49    * @throws IOException in the event of error
50    */
canRollBack(StorageDirectory sd, StorageInfo storage, StorageInfo prevStorage, int targetLayoutVersion)51   static boolean canRollBack(StorageDirectory sd, StorageInfo storage,
52       StorageInfo prevStorage, int targetLayoutVersion) throws IOException {
53     File prevDir = sd.getPreviousDir();
54     if (!prevDir.exists()) {  // use current directory then
55       LOG.info("Storage directory " + sd.getRoot()
56                + " does not contain previous fs state.");
57       // read and verify consistency with other directories
58       storage.readProperties(sd);
59       return false;
60     }
61 
62     // read and verify consistency of the prev dir
63     prevStorage.readPreviousVersionProperties(sd);
64 
65     if (prevStorage.getLayoutVersion() != targetLayoutVersion) {
66       throw new IOException(
67         "Cannot rollback to storage version " +
68         prevStorage.getLayoutVersion() +
69         " using this version of the NameNode, which uses storage version " +
70         targetLayoutVersion + ". " +
71         "Please use the previous version of HDFS to perform the rollback.");
72     }
73 
74     return true;
75   }
76 
77   /**
78    * Finalize the upgrade. The previous dir, if any, will be renamed and
79    * removed. After this is completed, rollback is no longer allowed.
80    *
81    * @param sd the storage directory to finalize
82    * @throws IOException in the event of error
83    */
doFinalize(StorageDirectory sd)84   static void doFinalize(StorageDirectory sd) throws IOException {
85     File prevDir = sd.getPreviousDir();
86     if (!prevDir.exists()) { // already discarded
87       LOG.info("Directory " + prevDir + " does not exist.");
88       LOG.info("Finalize upgrade for " + sd.getRoot()+ " is not required.");
89       return;
90     }
91     LOG.info("Finalizing upgrade of storage directory " + sd.getRoot());
92     Preconditions.checkState(sd.getCurrentDir().exists(),
93         "Current directory must exist.");
94     final File tmpDir = sd.getFinalizedTmp();
95     // rename previous to tmp and remove
96     NNStorage.rename(prevDir, tmpDir);
97     NNStorage.deleteDir(tmpDir);
98     LOG.info("Finalize upgrade for " + sd.getRoot()+ " is complete.");
99   }
100 
101   /**
102    * Perform any steps that must succeed across all storage dirs/JournalManagers
103    * involved in an upgrade before proceeding onto the actual upgrade stage. If
104    * a call to any JM's or local storage dir's doPreUpgrade method fails, then
105    * doUpgrade will not be called for any JM. The existing current dir is
106    * renamed to previous.tmp, and then a new, empty current dir is created.
107    *
108    * @param conf configuration for creating {@link EditLogFileOutputStream}
109    * @param sd the storage directory to perform the pre-upgrade procedure.
110    * @throws IOException in the event of error
111    */
doPreUpgrade(Configuration conf, StorageDirectory sd)112   static void doPreUpgrade(Configuration conf, StorageDirectory sd)
113       throws IOException {
114     LOG.info("Starting upgrade of storage directory " + sd.getRoot());
115 
116     // rename current to tmp
117     renameCurToTmp(sd);
118 
119     final File curDir = sd.getCurrentDir();
120     final File tmpDir = sd.getPreviousTmp();
121     List<String> fileNameList = IOUtils.listDirectory(tmpDir, new FilenameFilter() {
122       @Override
123       public boolean accept(File dir, String name) {
124         return dir.equals(tmpDir)
125             && name.startsWith(NNStorage.NameNodeFile.EDITS.getName());
126       }
127     });
128 
129     for (String s : fileNameList) {
130       File prevFile = new File(tmpDir, s);
131       File newFile = new File(curDir, prevFile.getName());
132       Files.createLink(newFile.toPath(), prevFile.toPath());
133     }
134   }
135 
136   /**
137    * Rename the existing current dir to previous.tmp, and create a new empty
138    * current dir.
139    */
renameCurToTmp(StorageDirectory sd)140   public static void renameCurToTmp(StorageDirectory sd) throws IOException {
141     File curDir = sd.getCurrentDir();
142     File prevDir = sd.getPreviousDir();
143     final File tmpDir = sd.getPreviousTmp();
144 
145     Preconditions.checkState(curDir.exists(),
146         "Current directory must exist for preupgrade.");
147     Preconditions.checkState(!prevDir.exists(),
148         "Previous directory must not exist for preupgrade.");
149     Preconditions.checkState(!tmpDir.exists(),
150         "Previous.tmp directory must not exist for preupgrade."
151             + "Consider restarting for recovery.");
152 
153     // rename current to tmp
154     NNStorage.rename(curDir, tmpDir);
155 
156     if (!curDir.mkdir()) {
157       throw new IOException("Cannot create directory " + curDir);
158     }
159   }
160 
161   /**
162    * Perform the upgrade of the storage dir to the given storage info. The new
163    * storage info is written into the current directory, and the previous.tmp
164    * directory is renamed to previous.
165    *
166    * @param sd the storage directory to upgrade
167    * @param storage info about the new upgraded versions.
168    * @throws IOException in the event of error
169    */
doUpgrade(StorageDirectory sd, Storage storage)170   public static void doUpgrade(StorageDirectory sd, Storage storage)
171       throws IOException {
172     LOG.info("Performing upgrade of storage directory " + sd.getRoot());
173     try {
174       // Write the version file, since saveFsImage only makes the
175       // fsimage_<txid>, and the directory is otherwise empty.
176       storage.writeProperties(sd);
177 
178       File prevDir = sd.getPreviousDir();
179       File tmpDir = sd.getPreviousTmp();
180       Preconditions.checkState(!prevDir.exists(),
181           "previous directory must not exist for upgrade.");
182       Preconditions.checkState(tmpDir.exists(),
183           "previous.tmp directory must exist for upgrade.");
184 
185       // rename tmp to previous
186       NNStorage.rename(tmpDir, prevDir);
187     } catch (IOException ioe) {
188       LOG.error("Unable to rename temp to previous for " + sd.getRoot(), ioe);
189       throw ioe;
190     }
191   }
192 
193   /**
194    * Perform rollback of the storage dir to the previous state. The existing
195    * current dir is removed, and the previous dir is renamed to current.
196    *
197    * @param sd the storage directory to roll back.
198    * @throws IOException in the event of error
199    */
doRollBack(StorageDirectory sd)200   static void doRollBack(StorageDirectory sd)
201       throws IOException {
202     File prevDir = sd.getPreviousDir();
203     if (!prevDir.exists()) {
204       return;
205     }
206 
207     File tmpDir = sd.getRemovedTmp();
208     Preconditions.checkState(!tmpDir.exists(),
209         "removed.tmp directory must not exist for rollback."
210             + "Consider restarting for recovery.");
211     // rename current to tmp
212     File curDir = sd.getCurrentDir();
213     Preconditions.checkState(curDir.exists(),
214         "Current directory must exist for rollback.");
215 
216     NNStorage.rename(curDir, tmpDir);
217     // rename previous to current
218     NNStorage.rename(prevDir, curDir);
219 
220     // delete tmp dir
221     NNStorage.deleteDir(tmpDir);
222     LOG.info("Rollback of " + sd.getRoot() + " is complete.");
223   }
224 
225 }
226