try {
Assert.assertEquals(0, f.self.getAcceptedEpoch());
Assert.assertEquals(0, f.self.getCurrentEpoch());
// Setup a database with a single /foo node
ZKDatabase zkDb = new ZKDatabase(new FileTxnSnapLog(tmpDir, tmpDir));
final long firstZxid = ZxidUtils.makeZxid(1, 1);
zkDb.processTxn(new TxnHeader(13, 1313, firstZxid, 33, ZooDefs.OpCode.create), new CreateTxn("/foo", "data1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, false, 1));
Stat stat = new Stat();
Assert.assertEquals("data1", new String(zkDb.getData("/foo", stat, null)));
QuorumPacket qp = new QuorumPacket();
readPacketSkippingPing(ia, qp);
Assert.assertEquals(Leader.FOLLOWERINFO, qp.getType());
Assert.assertEquals(qp.getZxid(), 0);
LearnerInfo learnInfo = new LearnerInfo();
ByteBufferInputStream.byteBuffer2Record(ByteBuffer.wrap(qp.getData()), learnInfo);
Assert.assertEquals(learnInfo.getProtocolVersion(), 0x10000);
Assert.assertEquals(learnInfo.getServerid(), 0);
// We are simulating an established leader, so the epoch is 1
qp.setType(Leader.LEADERINFO);
qp.setZxid(ZxidUtils.makeZxid(1, 0));
byte protoBytes[] = new byte[4];
ByteBuffer.wrap(protoBytes).putInt(0x10000);
qp.setData(protoBytes);
oa.writeRecord(qp, null);
readPacketSkippingPing(ia, qp);
Assert.assertEquals(Leader.ACKEPOCH, qp.getType());
Assert.assertEquals(0, qp.getZxid());
Assert.assertEquals(ZxidUtils.makeZxid(0, 0), ByteBuffer.wrap(qp.getData()).getInt());
Assert.assertEquals(1, f.self.getAcceptedEpoch());
Assert.assertEquals(0, f.self.getCurrentEpoch());
// Send the snapshot we created earlier
qp.setType(Leader.SNAP);
qp.setData(new byte[0]);
qp.setZxid(zkDb.getDataTreeLastProcessedZxid());
oa.writeRecord(qp, null);
zkDb.serializeSnapshot(oa);
oa.writeString("BenWasHere", null);
qp.setType(Leader.NEWLEADER);
qp.setZxid(ZxidUtils.makeZxid(1, 0));
oa.writeRecord(qp, null);
// Get the ack of the new leader
readPacketSkippingPing(ia, qp);
Assert.assertEquals(Leader.ACK, qp.getType());
Assert.assertEquals(ZxidUtils.makeZxid(1, 0), qp.getZxid());
Assert.assertEquals(1, f.self.getAcceptedEpoch());
Assert.assertEquals(1, f.self.getCurrentEpoch());
Assert.assertEquals(firstZxid, f.fzk.getLastProcessedZxid());
// Make sure the data was recorded in the filesystem ok
ZKDatabase zkDb2 = new ZKDatabase(new FileTxnSnapLog(logDir, snapDir));
long lastZxid = zkDb2.loadDataBase();
Assert.assertEquals("data1", new String(zkDb2.getData("/foo", stat, null)));
Assert.assertEquals(firstZxid, lastZxid);
// Propose an update
long proposalZxid = ZxidUtils.makeZxid(1, 1000);
proposeSetData(qp, proposalZxid, "data2", 2);
oa.writeRecord(qp, null);
// We want to track the change with a callback rather than depending on timing
class TrackerWatcher implements Watcher {
boolean changed;
synchronized void waitForChange() throws InterruptedException {
while(!changed) {
wait();
}
}
@Override
public void process(WatchedEvent event) {
if (event.getType() == EventType.NodeDataChanged) {
synchronized(this) {
changed = true;
notifyAll();
}
}
}
synchronized public boolean changed() {
return changed;
}
};
TrackerWatcher watcher = new TrackerWatcher();
// The change should not have happened yet, since we haven't committed
Assert.assertEquals("data1", new String(f.fzk.getZKDatabase().getData("/foo", stat, watcher)));
// The change should happen now
qp.setType(Leader.COMMIT);
qp.setZxid(proposalZxid);
oa.writeRecord(qp, null);
qp.setType(Leader.UPTODATE);
qp.setZxid(0);
oa.writeRecord(qp, null);
// Read the uptodate ack
readPacketSkippingPing(ia, qp);
Assert.assertEquals(Leader.ACK, qp.getType());
Assert.assertEquals(ZxidUtils.makeZxid(1, 0), qp.getZxid());
readPacketSkippingPing(ia, qp);
Assert.assertEquals(Leader.ACK, qp.getType());
Assert.assertEquals(proposalZxid, qp.getZxid());
watcher.waitForChange();
Assert.assertEquals("data2", new String(f.fzk.getZKDatabase().getData("/foo", stat, null)));
// check and make sure the change is persisted
zkDb2 = new ZKDatabase(new FileTxnSnapLog(logDir, snapDir));
lastZxid = zkDb2.loadDataBase();
Assert.assertEquals("data2", new String(zkDb2.getData("/foo", stat, null)));
Assert.assertEquals(proposalZxid, lastZxid);
} finally {
recursiveDelete(tmpDir);
}