/* * Copyright 2014 Alen Caljkusic. * * Licensed 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 com.zklogtool.cli; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.zklogtool.data.DataDirTransactionLogFileList; import com.zklogtool.data.DataState; import com.zklogtool.data.Transaction; import com.zklogtool.data.TransactionIterator; import com.zklogtool.data.TransactionLog; import com.zklogtool.data.TransactionLogFileList; import com.zklogtool.data.TransactionState; import com.zklogtool.printer.DataDecoder; import com.zklogtool.printer.DataNodePrinter; import com.zklogtool.printer.UnicodeDecoder; import com.zklogtool.reader.SnapshotFileReader; import com.zklogtool.reader.TransactionLogReaderFactory; import com.zklogtool.util.DataDirHelper; import com.zklogtool.util.PropertiesReader; import static com.zklogtool.util.Util.getZxidFromName; import java.io.File; import java.io.IOException; import static java.lang.Long.parseLong; import static java.lang.System.exit; import static java.text.Collator.getInstance; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeSet; import org.apache.zookeeper.server.DataNode; /** * Represents <b>snapshot</b> command and holds logic for <b>snapshot</b> * command execution. It is also used by jCommander for command parameters. * <br> * Snapshot command can printout contents of single Zookeeper snapshot file. * Snapshot file is taken lazily so it may not represent data tree at any point * of time. Snapshot command can reconstruct Zookeeper data tree after a certain * zxid by taking one of the snapshots and applying transactions from * transaction log files to it. Reconstructed data tree is not fuzzy and it * corresponds to Zookeeper data tree at some point in time. To reconstruct data * tree one must provide <code>Arguments.DATA_DIR</code> argument. * */ @Parameters(commandDescription = "Display zookeeper data tree at certain time") public class CommandSnapshot { /** * Zxid up to which data tree should be restored. Strings first and last can * also be used. In that case, first or last data tree state that it can be * reconstructed from snapshots and transaction logs will get displayed. * Default value is last. * */ @Parameter(names = Arguments.ZXID, description = "Hex value of last commited zxid. Strings first and last can also be used") public String zxid = "last"; /** * Directory where snapshot and transaction log files are stored. If * snapshot and transaction log files are not stored in same directory use * this option to point to directory where snapshot files are stored. * */ @Parameter(names = Arguments.DATA_DIR, description = "Zookeeper data direcory path") public String dataDir; /** * Directory where transaction log files are stored. Zookeeper can be * configured to store transaction log files and snapshots in different * directories. In that case use this option to point to directory where * transaction log files are stored. * */ @Parameter(names = Arguments.DATA_LOG_DIR, description = "Zookeeper log direcory path. If not provided dataDir is used") public String dataLogDir; /** * Zookeeper snapshot file. Snapshot file is taken lazily so it may not * represent data tree at any point of time. Therefor it is called fuzzy * snapshot. * */ @Parameter(names = Arguments.SNAP_FILE, description = "Zookeeper snapshot file. Data is fuzzy.") public String snapFile; /** * Zookeeper configuration file. Zookeeper configuration file tells * zookeeper where to store transaction log files and snapshot files. * zklogtool can read locations from configuration file. * */ @Parameter(names = Arguments.PROPERTIES_FILE, description = "Zookeeper configuration file path") public String propertiesFile; /** * Decoder that converts znodes byte array to <code>String</code> that can * be printed to output. Znode holds data in form of a byte array. That byte * array must be decoded in something that can be printed to output in order * to display it. * */ @Parameter(names = Arguments.DATA_DECODER, description = "Decoder used to display znode's data byte array") public String dataDecoder = "UnicodeDecoder"; /** * Holds logic for <b>snapshot</b> command execution. * */ public void execute() { final StringBuilder print = new StringBuilder(); DataDecoder decoder = null; DataState dataState = null; if (dataDecoder.contentEquals("UnicodeDecoder")) { decoder = new UnicodeDecoder(); } else { System.err.println("Decoder not recognized"); exit(1); } final DataNodePrinter printer = new DataNodePrinter(print, decoder); if (snapFile != null) { File snapshotFile = new File(snapFile); if (!checkFileValid(snapshotFile)) { exit(1); } SnapshotFileReader reader = new SnapshotFileReader(snapshotFile, 0); try { dataState = reader.readFuzzySnapshot(); } catch (IOException ex) { System.err.println("Problem while reading file or corruption: " + snapshotFile.getAbsolutePath()); exit(1); } } else { TransactionLog transactionLog = null; TransactionLogReaderFactory factory = new TransactionLogReaderFactory(); File transactionLogDir = null; File snapshotDir = null; long zxidLong = 0; if (zxid != null && !(zxid.contentEquals("last") || zxid.contentEquals("first"))) { if(zxid.startsWith("0x")){ zxidLong = parseLong(zxid.substring(2),16); }else{ zxidLong = parseLong(zxid); } } if (dataDir != null) { transactionLogDir = new File(dataDir); snapshotDir = new File(dataDir); } if (dataLogDir != null) { transactionLogDir = new File(dataLogDir); } if (propertiesFile != null) { File properties = new File(propertiesFile); if (!checkFileValid(properties)) { exit(1); } PropertiesReader propertiesReader = null; try { propertiesReader = new PropertiesReader(properties); } catch (IOException e) { System.err.println("Problem with reading properties file: " + properties.getAbsolutePath()); exit(1); } String transactionLogDirPath = propertiesReader.getTransactionLogDir(); String snapshotDirPath = propertiesReader.getSnapshotDir(); if (transactionLogDirPath == null || snapshotDirPath == null) { System.err.println("Problem in properties file: " + properties.getAbsolutePath()); exit(1); } transactionLogDir = new File(transactionLogDirPath); snapshotDir = new File(snapshotDirPath); } if (!checkDirectoryValid(transactionLogDir)) { exit(1); } if (!checkDirectoryValid(snapshotDir)) { exit(1); } DataDirHelper dataDirHelper = new DataDirHelper(transactionLogDir, snapshotDir); List<File> snapshots = dataDirHelper.getSortedSnapshotList(); TransactionLogFileList l = new DataDirTransactionLogFileList(transactionLogDir); transactionLog = new TransactionLog(l, factory); if (snapshots.isEmpty()) { System.err.println("No snapshot files found"); exit(1); } //determine what snapshot file to read File snapFile = null; if (zxid.contentEquals("first")) { snapFile = snapshots.get(0); } else if (zxid.contentEquals("last")) { if (snapshots.size() >= 2) { snapFile = snapshots.get(snapshots.size() - 2); } else { snapFile = snapshots.get(0); } } else { int i = snapshots.size() - 1; while (i >= 0) { long snapZxid = getZxidFromName(snapshots.get(i).getName()); if (snapZxid <= zxidLong) { if (i == 0) { snapFile = snapshots.get(0); } else { snapFile = snapshots.get(i - 1); } break; } i--; } } if(snapFile==null){ System.err.println("Not enough data to reconstruct data tree."); exit(1); } long TS = getZxidFromName(snapFile.getName()); SnapshotFileReader snapReader = new SnapshotFileReader(snapFile, TS); try { dataState = snapReader.restoreDataState(transactionLog.iterator()); } catch (Exception ex) { System.err.println("Problem while reading transaction log: " + ex.getMessage()); exit(1); } //set iterator to last zxid TransactionIterator iterator = transactionLog.iterator(); Transaction t; do { t = iterator.next(); //treba i provjera next transaction state } while (t.getTxnHeader().getZxid() < TS); //rewind if (zxid.contentEquals("last")) { while (iterator.nextTransactionState() == TransactionState.OK) { dataState.processTransaction(iterator.next()); } } else if (!zxid.contentEquals("first")) { while (iterator.nextTransactionState() == TransactionState.OK && dataState.getLastZxid() < zxidLong) { dataState.processTransaction(iterator.next()); } } //check if null } //print dataState lexicograph ordering Map<String, DataNode> nodes = dataState.getNodes(); //create sorted collection Collection<String> paths = new TreeSet<String>(getInstance()); Iterator<Map.Entry<String, DataNode>> it = dataState.getNodes().entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, DataNode> entry = it.next(); paths.add(entry.getKey()); } System.out.println("Last processed zxid: 0x" + Long.toString(dataState.getLastZxid(),16)+System.lineSeparator()); //print sessions System.out.println("Sessions:"+System.lineSeparator()); Iterator<Map.Entry<Long, Integer>> se = dataState.getSessions().entrySet().iterator(); while (se.hasNext()) { Map.Entry<Long, Integer> entry = se.next(); System.out.println("id 0x"+Long.toString(entry.getKey(),16)+" timeout "+entry.getValue()); } System.out.println(System.lineSeparator()); //print data nodes System.out.println("Data :"+System.lineSeparator()); for (String path : paths) { System.out.println("Path:\t\t" + path); printer.printDataNode(nodes.get(path), dataState); System.out.println(print); print.setLength(0); } } private boolean checkFileValid(File file) { if (file.isDirectory()) { System.err.println(file + " is directory"); return false; } else if (!file.isFile()) { System.err.println("File " + file + " not found"); return false; } else if (!file.canRead()) { System.err.println("File " + file + " not readable"); return false; } return true; } private boolean checkDirectoryValid(File directory) { if (directory.isFile()) { System.err.println(directory + " is file"); return false; } else if (!directory.isDirectory()) { System.err.println("Directory " + directory + " not found"); return false; } else if (!directory.canRead()) { System.err.println("Directory " + directory + " not readable"); return false; } return true; } }