/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase.snapshot; import java.io.Closeable; import java.io.IOException; import java.util.Map; import java.util.TreeMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.io.HLogLink; import org.apache.hadoop.hbase.regionserver.HRegion; import org.apache.hadoop.hbase.regionserver.wal.HLog; import org.apache.hadoop.hbase.regionserver.wal.HLogKey; import org.apache.hadoop.hbase.util.Bytes; /** * If the snapshot has references to one or more log files, * those must be split (each log contains multiple tables and regions) * and must be placed in the region/recovered.edits folder. * (recovered.edits files will be played on region startup) * * In case of Restore: the log can just be split in the recovered.edits folder. * In case of Clone: each entry in the log must be modified to use the new region name. * (region names are encoded with: tableName, startKey, regionIdTimeStamp) * * We can't use the normal split code, because the HLogKey contains the * table name and the region name, and in case of "clone from snapshot" * region name and table name will be different and must be replaced in * the recovered.edits. */ @InterfaceAudience.Private class SnapshotLogSplitter implements Closeable { static final Log LOG = LogFactory.getLog(SnapshotLogSplitter.class); private final class LogWriter implements Closeable { private HLog.Writer writer; private Path logFile; private long seqId; public LogWriter(final Configuration conf, final FileSystem fs, final Path logDir, long seqId) throws IOException { logFile = new Path(logDir, logFileName(seqId, true)); this.writer = HLog.createWriter(fs, logFile, conf); this.seqId = seqId; } public void close() throws IOException { writer.close(); Path finalFile = new Path(logFile.getParent(), logFileName(seqId, false)); LOG.debug("LogWriter tmpLogFile=" + logFile + " -> logFile=" + finalFile); fs.rename(logFile, finalFile); } public void append(final HLog.Entry entry) throws IOException { writer.append(entry); if (seqId < entry.getKey().getLogSeqNum()) { seqId = entry.getKey().getLogSeqNum(); } } private String logFileName(long seqId, boolean temp) { String fileName = String.format("%019d", seqId); if (temp) fileName += HLog.RECOVERED_LOG_TMPFILE_SUFFIX; return fileName; } } private final Map<byte[], LogWriter> regionLogWriters = new TreeMap<byte[], LogWriter>(Bytes.BYTES_COMPARATOR); private final Map<byte[], byte[]> regionsMap; private final Configuration conf; private final byte[] snapshotTableName; private final byte[] tableName; private final Path tableDir; private final FileSystem fs; /** * @params tableName snapshot table name * @params regionsMap maps original region names to the new ones. */ public SnapshotLogSplitter(final Configuration conf, final FileSystem fs, final Path tableDir, final byte[] snapshotTableName, final Map<byte[], byte[]> regionsMap) { this.regionsMap = regionsMap; this.snapshotTableName = snapshotTableName; this.tableName = Bytes.toBytes(tableDir.getName()); this.tableDir = tableDir; this.conf = conf; this.fs = fs; } public void close() throws IOException { for (LogWriter writer: regionLogWriters.values()) { writer.close(); } } public void splitLog(final String serverName, final String logfile) throws IOException { LOG.debug("Restore log=" + logfile + " server=" + serverName + " for snapshotTable=" + Bytes.toString(snapshotTableName) + " to table=" + Bytes.toString(tableName)); splitLog(new HLogLink(conf, serverName, logfile).getAvailablePath(fs)); } public void splitRecoveredEdit(final Path editPath) throws IOException { LOG.debug("Restore recover.edits=" + editPath + " for snapshotTable=" + Bytes.toString(snapshotTableName) + " to table=" + Bytes.toString(tableName)); splitLog(editPath); } /** * Split the snapshot HLog reference into regions recovered.edits. * * The HLogKey contains the table name and the region name, * and they must be changed to the restored table names. * * @param logPath Snapshot HLog reference path */ public void splitLog(final Path logPath) throws IOException { HLog.Reader log = HLog.getReader(fs, logPath, conf); try { HLog.Entry entry; LogWriter writer = null; byte[] regionName = null; byte[] newRegionName = null; while ((entry = log.next()) != null) { HLogKey key = entry.getKey(); // We're interested only in the snapshot table that we're restoring if (!Bytes.equals(key.getTablename(), snapshotTableName)) continue; // Writer for region. if (!Bytes.equals(regionName, key.getEncodedRegionName())) { regionName = key.getEncodedRegionName().clone(); // Get the new region name in case of clone, or use the original one newRegionName = regionsMap.get(regionName); if (newRegionName == null) newRegionName = regionName; writer = getOrCreateWriter(newRegionName, key.getLogSeqNum()); LOG.debug("+ regionName=" + Bytes.toString(regionName)); } // Append Entry key = new HLogKey(newRegionName, tableName, key.getLogSeqNum(), key.getWriteTime(), key.getClusterId()); writer.append(new HLog.Entry(key, entry.getEdit())); } } catch (IOException e) { LOG.warn("Something wrong during the log split", e); } finally { log.close(); } } /** * Create a LogWriter for specified region if not already created. */ private LogWriter getOrCreateWriter(final byte[] regionName, long seqId) throws IOException { LogWriter writer = regionLogWriters.get(regionName); if (writer == null) { Path regionDir = HRegion.getRegionDir(tableDir, Bytes.toString(regionName)); Path dir = HLog.getRegionDirRecoveredEditsDir(regionDir); fs.mkdirs(dir); writer = new LogWriter(conf, fs, dir, seqId); regionLogWriters.put(regionName, writer); } return(writer); } }