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  
19  package org.apache.hadoop.hbase.backup.impl;
20  
21  import java.io.IOException;
22  import java.nio.ByteBuffer;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Map.Entry;
31  import java.util.Set;
32  import java.util.TreeSet;
33  
34  import org.apache.commons.lang.StringUtils;
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.apache.hadoop.conf.Configuration;
38  import org.apache.hadoop.fs.Path;
39  import org.apache.hadoop.hbase.TableName;
40  import org.apache.hadoop.hbase.backup.BackupType;
41  import org.apache.hadoop.hbase.backup.HBackupFileSystem;
42  import org.apache.hadoop.hbase.backup.RestoreClient;
43  import org.apache.hadoop.hbase.backup.impl.BackupManifest.BackupImage;
44  import org.apache.hadoop.hbase.backup.mapreduce.MapReduceRestoreService;
45  import org.apache.hadoop.hbase.backup.util.BackupClientUtil;
46  import org.apache.hadoop.hbase.backup.util.RestoreServerUtil;
47  import org.apache.hadoop.hbase.classification.InterfaceAudience;
48  import org.apache.hadoop.hbase.classification.InterfaceStability;
49  import org.apache.hadoop.hbase.client.Admin;
50  import org.apache.hadoop.hbase.client.Connection;
51  import org.apache.hadoop.hbase.client.ConnectionFactory;
52  import org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles;
53  import org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles.LoadQueueItem;
54  
55  /**
56   * The main class which interprets the given arguments and trigger restore operation.
57   */
58  @InterfaceAudience.Public
59  @InterfaceStability.Evolving
60  public final class RestoreClientImpl implements RestoreClient {
61  
62    private static final Log LOG = LogFactory.getLog(RestoreClientImpl.class);
63    private Configuration conf;
64    private String fullBackupId;
65  
66    public RestoreClientImpl() {
67    }
68  
69    @Override
70    public void setConf(Configuration conf) {
71      this.conf = conf;
72    }
73  
74  
75  
76    /**
77     * Restore operation. Stage 1: validate backupManifest, and check target tables
78     * @param backupRootDir The root dir for backup image
79     * @param backupId The backup id for image to be restored
80     * @param check True if only do dependency check
81     * @param sTableArray The array of tables to be restored
82     * @param tTableArray The array of mapping tables to restore to
83     * @param isOverwrite True then do restore overwrite if target table exists, otherwise fail the
84     *          request if target table exists
85     * @throws IOException if any failure during restore
86     */
87    @Override
88    public void restore(String backupRootDir,
89        String backupId, boolean check, TableName[] sTableArray,
90        TableName[] tTableArray, boolean isOverwrite) throws IOException {
91  
92      HashMap<TableName, BackupManifest> backupManifestMap = new HashMap<>();
93      // check and load backup image manifest for the tables
94      Path rootPath = new Path(backupRootDir);
95      HBackupFileSystem.checkImageManifestExist(backupManifestMap, sTableArray, conf, rootPath,
96        backupId);
97      try {
98        // Check and validate the backup image and its dependencies
99        if (check) {
100         if (validate(backupManifestMap)) {
101           LOG.info("Checking backup images: ok");
102         } else {
103           String errMsg = "Some dependencies are missing for restore";
104           LOG.error(errMsg);
105           throw new IOException(errMsg);
106         }
107       }
108 
109       if (tTableArray == null) {
110         tTableArray = sTableArray;
111       }
112       // check the target tables
113       checkTargetTables(tTableArray, isOverwrite);
114       // start restore process
115       restoreStage(backupId, backupManifestMap, sTableArray, tTableArray, isOverwrite);
116       LOG.info("Restore for " + Arrays.asList(sTableArray) + " are successful!");
117     } catch (IOException e) {
118       LOG.error("ERROR: restore failed with error: " + e.getMessage());
119       throw e;
120     }
121 
122   }
123 
124   private  boolean validate(HashMap<TableName, BackupManifest> backupManifestMap)
125       throws IOException {
126     boolean isValid = true;
127 
128     for (Entry<TableName, BackupManifest> manifestEntry : backupManifestMap.entrySet()) {
129       TableName table = manifestEntry.getKey();
130       TreeSet<BackupImage> imageSet = new TreeSet<BackupImage>();
131 
132       ArrayList<BackupImage> depList = manifestEntry.getValue().getDependentListByTable(table);
133       if (depList != null && !depList.isEmpty()) {
134         imageSet.addAll(depList);
135       }
136 
137       LOG.info("Dependent image(s) from old to new:");
138       for (BackupImage image : imageSet) {
139         String imageDir =
140             HBackupFileSystem.getTableBackupDir(image.getRootDir(), image.getBackupId(), table);
141         if (!BackupClientUtil.checkPathExist(imageDir, conf)) {
142           LOG.error("ERROR: backup image does not exist: " + imageDir);
143           isValid = false;
144           break;
145         }
146         // TODO More validation?
147         LOG.info("Backup image: " + image.getBackupId() + " for '" + table + "' is available");
148       }
149     }
150 
151     return isValid;
152   }
153 
154   /**
155    * Validate target Tables
156    * @param tTableArray: target tables
157    * @param isOverwrite overwrite existing table
158    * @throws IOException exception
159    */
160   private  void checkTargetTables(TableName[] tTableArray, boolean isOverwrite)
161       throws IOException {
162     ArrayList<TableName> existTableList = new ArrayList<>();
163     ArrayList<TableName> disabledTableList = new ArrayList<>();
164 
165     // check if the tables already exist
166     try(Connection conn = ConnectionFactory.createConnection(conf);
167         Admin admin = conn.getAdmin()) {
168       for (TableName tableName : tTableArray) {
169         if (admin.tableExists(tableName)) {
170           existTableList.add(tableName);
171           if (admin.isTableDisabled(tableName)) {
172             disabledTableList.add(tableName);
173           }
174         } else {
175           LOG.info("HBase table " + tableName
176               + " does not exist. It will be created during restore process");
177         }
178       }
179     }
180 
181     if (existTableList.size() > 0) {
182       if (!isOverwrite) {
183         LOG.error("Existing table found in the restore target, please add \"-overwrite\" "
184             + "option in the command if you mean to restore to these existing tables");
185         LOG.info("Existing table list in restore target: " + existTableList);
186         throw new IOException("Existing table found in target while no \"-overwrite\" "
187             + "option found");
188       } else {
189         if (disabledTableList.size() > 0) {
190           LOG.error("Found offline table in the restore target, "
191               + "please enable them before restore with \"-overwrite\" option");
192           LOG.info("Offline table list in restore target: " + disabledTableList);
193           throw new IOException(
194               "Found offline table in the target when restore with \"-overwrite\" option");
195         }
196       }
197     }
198   }
199 
200   /**
201    * Restore operation. Stage 2: resolved Backup Image dependency
202    * @param backupManifestMap : tableName,  Manifest
203    * @param sTableArray The array of tables to be restored
204    * @param tTableArray The array of mapping tables to restore to
205    * @return set of BackupImages restored
206    * @throws IOException exception
207    */
208   private void restoreStage(String backupId, HashMap<TableName, BackupManifest> backupManifestMap,
209       TableName[] sTableArray, TableName[] tTableArray, boolean isOverwrite) throws IOException {
210     TreeSet<BackupImage> restoreImageSet = new TreeSet<BackupImage>();
211     boolean truncateIfExists = isOverwrite;
212     try {
213       Set<String> backupIdSet = new HashSet<>();
214       for (int i = 0; i < sTableArray.length; i++) {
215         TableName table = sTableArray[i];
216         BackupManifest manifest = backupManifestMap.get(table);
217         // Get the image list of this backup for restore in time order from old
218         // to new.
219         List<BackupImage> list = new ArrayList<BackupImage>();
220         list.add(manifest.getBackupImage());
221         List<BackupImage> depList = manifest.getDependentListByTable(table);
222         list.addAll(depList);
223         TreeSet<BackupImage> restoreList = new TreeSet<BackupImage>(list);
224         LOG.debug("need to clear merged Image. to be implemented in future jira");
225         restoreImages(restoreList.iterator(), table, tTableArray[i], truncateIfExists);
226         restoreImageSet.addAll(restoreList);
227 
228         if (restoreImageSet != null && !restoreImageSet.isEmpty()) {
229           LOG.info("Restore includes the following image(s):");
230           for (BackupImage image : restoreImageSet) {
231             LOG.info("Backup: "
232                 + image.getBackupId()
233                 + " "
234                 + HBackupFileSystem.getTableBackupDir(image.getRootDir(), image.getBackupId(),
235                   table));
236             if (image.getType() == BackupType.INCREMENTAL) {
237               backupIdSet.add(image.getBackupId());
238               LOG.debug("adding " + image.getBackupId() + " for bulk load");
239             }
240           }
241         }
242       }
243       try (Connection conn = ConnectionFactory.createConnection(conf);
244           BackupSystemTable table = new BackupSystemTable(conn)) {
245         List<TableName> sTableList = Arrays.asList(sTableArray);
246         for (String id : backupIdSet) {
247           LOG.debug("restoring bulk load for " + id);
248           Map<byte[], List<Path>>[] mapForSrc = table.readBulkLoadedFiles(id, sTableList);
249           Map<LoadQueueItem, ByteBuffer> loaderResult;
250           conf.setBoolean(LoadIncrementalHFiles.ALWAYS_COPY_FILES, true);
251           LoadIncrementalHFiles loader = MapReduceRestoreService.createLoader(conf);
252           for (int i = 0; i < sTableList.size(); i++) {
253             if (mapForSrc[i] != null && !mapForSrc[i].isEmpty()) {
254               loaderResult = loader.run(null, mapForSrc[i], tTableArray[i]);
255               LOG.debug("bulk loading " + sTableList.get(i) + " to " + tTableArray[i]);
256               if (loaderResult.isEmpty()) {
257                 String msg = "Couldn't bulk load for " + sTableList.get(i) + " to " +tTableArray[i];
258                 LOG.error(msg);
259                 throw new IOException(msg);
260               }
261             }
262           }
263         }
264       }
265     } catch (Exception e) {
266       LOG.error("Failed", e);
267       if (e instanceof IOException) {
268         throw (IOException)e;
269       }
270       throw new IOException(e);
271     }
272     LOG.debug("restoreStage finished");
273   }
274 
275   static long getTsFromBackupId(String backupId) {
276     if (backupId == null) {
277       return 0;
278     }
279     return Long.valueOf(backupId.substring(backupId.lastIndexOf("_")+1));
280   }
281 
282   static boolean withinRange(long a, long lower, long upper) {
283     if (a < lower || a > upper) {
284       return false;
285     }
286     return true;
287   }
288 
289   int getIndex(TableName tbl, List<TableName> sTableList) {
290     for (int i = 0; i < sTableList.size(); i++) {
291       if (tbl.equals(sTableList.get(i))) {
292         return i;
293       }
294     }
295     return -1;
296   }
297 
298   /**
299    * Restore operation handle each backupImage in iterator
300    * @param it: backupImage iterator - ascending
301    * @param sTable: table to be restored
302    * @param tTable: table to be restored to
303    * @throws IOException exception
304    */
305   private void restoreImages(Iterator<BackupImage> it, TableName sTable,
306       TableName tTable, boolean truncateIfExists)
307       throws IOException {
308 
309     // First image MUST be image of a FULL backup
310     BackupImage image = it.next();
311 
312     String rootDir = image.getRootDir();
313     String backupId = image.getBackupId();
314     Path backupRoot = new Path(rootDir);
315 
316     // We need hFS only for full restore (see the code)
317     RestoreServerUtil restoreTool = new RestoreServerUtil(conf, backupRoot, backupId);
318     BackupManifest manifest = HBackupFileSystem.getManifest(sTable, conf, backupRoot, backupId);
319 
320     Path tableBackupPath = HBackupFileSystem.getTableBackupPath(sTable, backupRoot, backupId);
321 
322     // TODO: convert feature will be provided in a future JIRA
323     boolean converted = false;
324 
325     if (manifest.getType() == BackupType.FULL || converted) {
326       if (manifest.getType() == BackupType.FULL) {
327         fullBackupId = manifest.getBackupImage().getBackupId();
328       }
329       LOG.info("Restoring '" + sTable + "' to '" + tTable + "' from "
330           + (converted ? "converted" : "full") + " backup image " + tableBackupPath.toString());
331       restoreTool.fullRestoreTable(tableBackupPath, sTable, tTable,
332         converted, truncateIfExists);
333 
334     } else { // incremental Backup
335       throw new IOException("Unexpected backup type " + image.getType());
336     }
337 
338     // The rest one are incremental
339     if (it.hasNext()) {
340       List<String> fileDirList = new ArrayList<String>();
341       while (it.hasNext()) {
342         BackupImage im = it.next();
343         String fileBackupDir = HBackupFileSystem.getTableBackupDir(im.getRootDir(),
344           im.getBackupId(), sTable)+ Path.SEPARATOR+"data";
345         fileDirList.add(fileBackupDir);
346       }
347       String logDirs = StringUtils.join(fileDirList, ",");
348       LOG.info("Restoring '" + sTable + "' to '" + tTable
349           + "' from file dirs: " + logDirs);
350       String[] sarr = new String[fileDirList.size()];
351       fileDirList.toArray(sarr);
352       Path[] paths = org.apache.hadoop.util.StringUtils.stringToPath(sarr);
353       restoreTool.incrementalRestoreTable(paths, new TableName[] { sTable },
354         new TableName[] { tTable });
355     }
356     LOG.info(sTable + " has been successfully restored to " + tTable);
357   }
358 
359 }