/**
* 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.zookeeper.server.upgrade;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.jute.BinaryInputArchive;
import org.apache.jute.InputArchive;
import org.apache.jute.Record;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.zookeeper.ZooDefs.OpCode;
import org.apache.zookeeper.data.StatPersisted;
import org.apache.zookeeper.data.StatPersistedV1;
import org.apache.zookeeper.server.DataNode;
import org.apache.zookeeper.server.DataTree;
import org.apache.zookeeper.server.Request;
import org.apache.zookeeper.server.ZooTrace;
import org.apache.zookeeper.server.persistence.FileTxnLog;
import org.apache.zookeeper.server.persistence.Util;
import org.apache.zookeeper.server.util.SerializeUtils;
import org.apache.zookeeper.txn.CreateSessionTxn;
import org.apache.zookeeper.txn.TxnHeader;
/**
* This class gets the old snapshot
* and the old dataDir and creates
* an brand new snapshot that is
* then converted to the new snapshot
* for upgrading.
*/
public class UpgradeSnapShotV1 implements UpgradeSnapShot {
private static final Logger LOG = LoggerFactory.getLogger(UpgradeSnapShotV1.class);
ConcurrentHashMap<Long, Integer> sessionsWithTimeouts =
new ConcurrentHashMap<Long, Integer>();
File dataDir;
File snapShotDir;
DataTreeV1 oldDataTree;
/**
* upgrade from version 1 to version 2
* @param dataDir
* @param snapShotDir
*/
public UpgradeSnapShotV1(File dataDir, File snapShotDir) {
this.dataDir = dataDir;
this.snapShotDir = snapShotDir;
oldDataTree = new DataTreeV1();
}
/**
* deseriluize from an inputarchive
* @param oldTree the tree to be created
* @param ia the input archive to be read from
* @param sessions the sessions to be created
* @throws IOException
*/
private void deserializeSnapshot(DataTreeV1 oldTree, InputArchive ia,
Map<Long, Integer> sessions) throws IOException {
int count = ia.readInt("count");
while (count > 0) {
long id = ia.readLong("id");
int to = ia.readInt("timeout");
sessions.put(id, to);
if (LOG.isTraceEnabled()) {
ZooTrace.logTraceMessage(LOG, ZooTrace.SESSION_TRACE_MASK,
"loadData --- session in archive: " + id
+ " with timeout: " + to);
}
count--;
}
oldTree.deserialize(ia, "tree");
}
/**
* play the log from this logstream into the datatree
* @param logStream
* @return
* @throws IOException
*/
public long playLog(InputArchive logStream) throws IOException {
long highestZxid = 0;
try {
while (true) {
byte[] bytes = logStream.readBuffer("txnEntry");
if (bytes.length == 0) {
// Since we preallocate, we define EOF to be an
// empty transaction
throw new EOFException();
}
TxnHeader hdr = new TxnHeader();
Record txn = SerializeUtils.deserializeTxn(bytes, hdr);
if (logStream.readByte("EOR") != 'B') {
LOG.warn("Last transaction was partial.");
throw new EOFException("Last transaction was partial.");
}
if (hdr.getZxid() <= highestZxid && highestZxid != 0) {
LOG.error(highestZxid + "(higestZxid) >= "
+ hdr.getZxid() + "(next log) for type "
+ hdr.getType());
} else {
highestZxid = hdr.getZxid();
}
switch (hdr.getType()) {
case OpCode.createSession:
sessionsWithTimeouts.put(hdr.getClientId(),
((CreateSessionTxn) txn).getTimeOut());
if (LOG.isTraceEnabled()) {
ZooTrace.logTraceMessage(LOG,
ZooTrace.SESSION_TRACE_MASK,
"playLog --- create session in log: 0x"
+ Long.toHexString(hdr.getClientId())
+ " with timeout: "
+ ((CreateSessionTxn) txn).getTimeOut());
}
// give dataTree a chance to sync its lastProcessedZxid
oldDataTree.processTxn(hdr, txn);
break;
case OpCode.closeSession:
sessionsWithTimeouts.remove(hdr.getClientId());
if (LOG.isTraceEnabled()) {
ZooTrace.logTraceMessage(LOG,
ZooTrace.SESSION_TRACE_MASK,
"playLog --- close session in log: 0x"
+ Long.toHexString(hdr.getClientId()));
}
oldDataTree.processTxn(hdr, txn);
break;
default:
oldDataTree.processTxn(hdr, txn);
}
Request r = new Request(null, 0, hdr.getCxid(), hdr.getType(),
null, null);
r.txn = txn;
r.hdr = hdr;
r.zxid = hdr.getZxid();
}
} catch (EOFException e) {
// expected in some cases - see comments in try block
}
return highestZxid;
}
/**
* apply the log files to the datatree
* @param oldTree the datatreee to apply the logs to
* @param logFiles the logs to be applied
* @throws IOException
*/
private long processLogFiles(DataTreeV1 oldTree,
File[] logFiles) throws IOException {
long zxid = 0;
for (File f: logFiles) {
LOG.info("Processing log file: " + f);
InputStream logIs =
new BufferedInputStream(new FileInputStream(f));
zxid = playLog(BinaryInputArchive.getArchive(logIs));
logIs.close();
}
return zxid;
}
/**
* create the old snapshot database
* apply logs to it and create the final
* database
* @throws IOException
*/
private void loadThisSnapShot() throws IOException {
// pick the most recent snapshot
File snapshot = findMostRecentSnapshot();
if (snapshot == null) {
throw new IOException("Invalid snapshots " +
"or not snapshots in " + snapShotDir);
}
InputStream inputstream = new BufferedInputStream(
new FileInputStream(snapshot));
InputArchive ia = BinaryInputArchive.getArchive(inputstream);
deserializeSnapshot(oldDataTree, ia, sessionsWithTimeouts);
//ok done with the snapshot
// now apply the logs
long snapshotZxid = oldDataTree.lastProcessedZxid;
File[] files = FileTxnLog.getLogFiles(
dataDir.listFiles(), snapshotZxid);
long zxid = processLogFiles(oldDataTree, files);
//check for this zxid to be sane
if (zxid != oldDataTree.lastProcessedZxid) {
LOG.error("Zxids not equal " + " log zxid " +
zxid + " datatree processed " + oldDataTree.lastProcessedZxid);
}
}
/**
* find the most recent snapshot
* in the snapshot directory
* @return
* @throws IOException
*/
private File findMostRecentSnapshot() throws IOException {
List<File> files = Util.sortDataDir(snapShotDir.listFiles(),
"snapshot", false);
for (File f: files) {
try {
if (Util.isValidSnapshot(f))
return f;
} catch(IOException e) {
LOG.info("Invalid snapshot " + f, e);
}
}
return null;
}
/**
* convert the old stat to new stat
* @param oldStat the old stat
* @return the new stat
*/
private StatPersisted convertStat(StatPersistedV1 oldStat) {
StatPersisted stat = new StatPersisted();
stat.setAversion(oldStat.getAversion());
stat.setCtime(oldStat.getCtime());
stat.setCversion(oldStat.getCversion());
stat.setCzxid(oldStat.getCzxid());
stat.setEphemeralOwner(oldStat.getEphemeralOwner());
stat.setMtime(oldStat.getMtime());
stat.setMzxid(oldStat.getMzxid());
stat.setVersion(oldStat.getVersion());
return stat;
}
/**
* convert a given old datanode to new datanode
* @param dt the new datatree
* @param parent the parent of the datanode to be constructed
* @param oldDataNode the old datanode
* @return the new datanode
*/
private DataNode convertDataNode(DataTree dt, DataNode parent,
DataNodeV1 oldDataNode) {
StatPersisted stat = convertStat(oldDataNode.stat);
DataNode dataNode = new DataNode(parent, oldDataNode.data,
dt.convertAcls(oldDataNode.acl), stat);
dataNode.setChildren(oldDataNode.children);
return dataNode;
}
/**
* recurse through the old datatree and construct the
* new data tree
* @param dataTree the new datatree to be constructed
* @param path the path to start with
*/
private void recurseThroughDataTree(DataTree dataTree, String path) {
if (path == null)
return;
DataNodeV1 oldDataNode = oldDataTree.getNode(path);
HashSet<String> children = oldDataNode.children;
DataNode parent = null;
if ("".equals(path)) {
parent = null;
}
else {
int lastSlash = path.lastIndexOf('/');
String parentPath = path.substring(0, lastSlash);
parent = dataTree.getNode(parentPath);
}
DataNode thisDatNode = convertDataNode(dataTree, parent,
oldDataNode);
dataTree.addDataNode(path, thisDatNode);
if (children == null || children.size() == 0) {
return;
}
else {
for (String child: children) {
recurseThroughDataTree(dataTree, path + "/" +child);
}
}
}
private DataTree convertThisSnapShot() throws IOException {
// create a datatree
DataTree dataTree = new DataTree();
DataNodeV1 oldDataNode = oldDataTree.getNode("");
if (oldDataNode == null) {
//should never happen
LOG.error("Upgrading from an empty snapshot.");
}
recurseThroughDataTree(dataTree, "");
dataTree.lastProcessedZxid = oldDataTree.lastProcessedZxid;
return dataTree;
}
public DataTree getNewDataTree() throws IOException {
loadThisSnapShot();
DataTree dt = convertThisSnapShot();
return dt;
}
public ConcurrentHashMap<Long, Integer> getSessionWithTimeOuts() {
return this.sessionsWithTimeouts;
}
}