All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.accumulo.fate.AdminUtil Maven / Gradle / Ivy

There is a newer version: 1.10.4
Show newest version
/*
 * 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.accumulo.fate;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.accumulo.fate.ReadOnlyTStore.TStatus;
import org.apache.accumulo.fate.zookeeper.IZooReader;
import org.apache.accumulo.fate.zookeeper.IZooReaderWriter;
import org.apache.accumulo.fate.zookeeper.ZooLock;
import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeMissingPolicy;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A utility to administer FATE operations
 */
public class AdminUtil {
  private static final Logger log = LoggerFactory.getLogger(AdminUtil.class);

  private final boolean exitOnError;

  /**
   * Default constructor
   */
  public AdminUtil() {
    this(true);
  }

  /**
   * Constructor
   *
   * @param exitOnError
   *          System.exit(1) on error if true
   */
  public AdminUtil(boolean exitOnError) {
    super();
    this.exitOnError = exitOnError;
  }

  /**
   * FATE transaction status, including lock information.
   */
  public static class TransactionStatus {

    private final long txid;
    private final TStatus status;
    private final String debug;
    private final List hlocks;
    private final List wlocks;
    private final String top;

    private TransactionStatus(Long tid, TStatus status, String debug, List hlocks,
        List wlocks, String top) {

      this.txid = tid;
      this.status = status;
      this.debug = debug;
      this.hlocks = Collections.unmodifiableList(hlocks);
      this.wlocks = Collections.unmodifiableList(wlocks);
      this.top = top;

    }

    /**
     * @return This fate operations transaction id, formatted in the same way as FATE transactions
     *         are in the Accumulo logs.
     */
    public String getTxid() {
      return String.format("%016x", txid);
    }

    public TStatus getStatus() {
      return status;
    }

    /**
     * @return The debug info for the operation on the top of the stack for this Fate operation.
     */
    public String getDebug() {
      return debug;
    }

    /**
     * @return list of namespace and table ids locked
     */
    public List getHeldLocks() {
      return hlocks;
    }

    /**
     * @return list of namespace and table ids locked
     */
    public List getWaitingLocks() {
      return wlocks;
    }

    /**
     * @return The operation on the top of the stack for this Fate operation.
     */
    public String getTop() {
      return top;
    }
  }

  public static class FateStatus {

    private final List transactions;
    private final Map> danglingHeldLocks;
    private final Map> danglingWaitingLocks;

    /**
     * Convert FATE transactions IDs in keys of map to format that used in printing and logging FATE
     * transactions ids. This is done so that if the map is printed, the output can be used to
     * search Accumulo's logs.
     */
    private static Map> convert(Map> danglocks) {
      if (danglocks.isEmpty()) {
        return Collections.emptyMap();
      }

      Map> ret = new HashMap<>();
      for (Entry> entry : danglocks.entrySet()) {
        ret.put(String.format("%016x", entry.getKey()),
            Collections.unmodifiableList(entry.getValue()));
      }
      return Collections.unmodifiableMap(ret);
    }

    private FateStatus(List transactions,
        Map> danglingHeldLocks, Map> danglingWaitingLocks) {
      this.transactions = Collections.unmodifiableList(transactions);
      this.danglingHeldLocks = convert(danglingHeldLocks);
      this.danglingWaitingLocks = convert(danglingWaitingLocks);
    }

    public List getTransactions() {
      return transactions;
    }

    /**
     * Get locks that are held by non existent FATE transactions. These are table or namespace
     * locks.
     *
     * @return map where keys are transaction ids and values are a list of table IDs and/or
     *         namespace IDs. The transaction IDs are in the same format as transaction IDs in the
     *         Accumulo logs.
     */
    public Map> getDanglingHeldLocks() {
      return danglingHeldLocks;
    }

    /**
     * Get locks that are waiting to be acquired by non existent FATE transactions. These are table
     * or namespace locks.
     *
     * @return map where keys are transaction ids and values are a list of table IDs and/or
     *         namespace IDs. The transaction IDs are in the same format as transaction IDs in the
     *         Accumulo logs.
     */
    public Map> getDanglingWaitingLocks() {
      return danglingWaitingLocks;
    }
  }

  /**
   * Returns a list of the FATE transactions, optionally filtered by transaction id and status. This
   * method does not process lock information, if lock information is desired, use
   * {@link #getStatus(ReadOnlyTStore, IZooReader, String, Set, EnumSet)}
   *
   * @param zs
   *          read-only zoostore
   * @param filterTxid
   *          filter results to include for provided transaction ids.
   * @param filterStatus
   *          filter results to include only provided status types
   * @return list of FATE transactions that match filter criteria
   */
  public List getTransactionStatus(ReadOnlyTStore zs, Set filterTxid,
      EnumSet filterStatus) {

    FateStatus status = getTransactionStatus(zs, filterTxid, filterStatus,
        Collections.>emptyMap(), Collections.>emptyMap());

    return status.getTransactions();
  }

  /**
   * Get the FATE transaction status and lock information stored in zookeeper, optionally filtered
   * by transaction id and filter status.
   *
   * @param zs
   *          read-only zoostore
   * @param zk
   *          zookeeper reader.
   * @param lockPath
   *          the zookeeper path for locks
   * @param filterTxid
   *          filter results to include for provided transaction ids.
   * @param filterStatus
   *          filter results to include only provided status types
   * @return a summary container of the fate transactions.
   * @throws KeeperException
   *           if zookeeper exception occurs
   * @throws InterruptedException
   *           if process is interrupted.
   */
  public FateStatus getStatus(ReadOnlyTStore zs, IZooReader zk, String lockPath,
      Set filterTxid, EnumSet filterStatus)
      throws KeeperException, InterruptedException {

    Map> heldLocks = new HashMap<>();
    Map> waitingLocks = new HashMap<>();

    findLocks(zk, lockPath, heldLocks, waitingLocks);

    return getTransactionStatus(zs, filterTxid, filterStatus, heldLocks, waitingLocks);

  }

  /**
   * Walk through the lock nodes in zookeeper to find and populate held locks and waiting locks.
   *
   * @param zk
   *          zookeeper reader
   * @param lockPath
   *          the zookeeper path for locks
   * @param heldLocks
   *          map for returning transactions with held locks
   * @param waitingLocks
   *          map for returning transactions with waiting locks
   * @throws KeeperException
   *           if initial lock list cannot be read.
   * @throws InterruptedException
   *           if thread interrupt detected while processing.
   */
  private void findLocks(IZooReader zk, final String lockPath,
      final Map> heldLocks, final Map> waitingLocks)
      throws KeeperException, InterruptedException {

    // stop with exception if lock ids cannot be retrieved from zookeeper
    List lockedIds = zk.getChildren(lockPath);

    for (String id : lockedIds) {

      try {

        List lockNodes = zk.getChildren(lockPath + "/" + id);
        lockNodes = new ArrayList<>(lockNodes);
        Collections.sort(lockNodes);

        int pos = 0;
        boolean sawWriteLock = false;

        for (String node : lockNodes) {
          try {
            byte[] data = zk.getData(lockPath + "/" + id + "/" + node, null);
            String[] lda = new String(data, UTF_8).split(":");

            if (lda[0].charAt(0) == 'W')
              sawWriteLock = true;

            Map> locks;

            if (pos == 0) {
              locks = heldLocks;
            } else {
              if (lda[0].charAt(0) == 'R' && !sawWriteLock) {
                locks = heldLocks;
              } else {
                locks = waitingLocks;
              }
            }

            List tables = locks.get(Long.parseLong(lda[1], 16));
            if (tables == null) {
              tables = new ArrayList<>();
              locks.put(Long.parseLong(lda[1], 16), tables);
            }

            tables.add(lda[0].charAt(0) + ":" + id);

          } catch (Exception e) {
            log.error("{}", e.getMessage(), e);
          }
          pos++;
        }

      } catch (KeeperException ex) {
        /*
         * could be transient zk error. Log, but try to process rest of list rather than throwing
         * exception here
         */
        log.error("Failed to read locks for " + id + " continuing.", ex);

      } catch (InterruptedException ex) {
        Thread.currentThread().interrupt();
        throw ex;
      }
    }
  }

  /**
   * Returns fate status, possibly filtered
   *
   * @param zs
   *          read-only access to a populated transaction store.
   * @param filterTxid
   *          Optional. List of transactions to filter results - if null, all transactions are
   *          returned
   * @param filterStatus
   *          Optional. List of status types to filter results - if null, all transactions are
   *          returned.
   * @param heldLocks
   *          populated list of locks held by transaction - or an empty map if none.
   * @param waitingLocks
   *          populated list of locks held by transaction - or an empty map if none.
   * @return current fate and lock status
   */
  private FateStatus getTransactionStatus(ReadOnlyTStore zs, Set filterTxid,
      EnumSet filterStatus, Map> heldLocks,
      Map> waitingLocks) {

    List transactions = zs.list();
    List statuses = new ArrayList<>(transactions.size());

    for (Long tid : transactions) {

      zs.reserve(tid);

      String debug = (String) zs.getProperty(tid, "debug");

      List hlocks = heldLocks.remove(tid);

      if (hlocks == null) {
        hlocks = Collections.emptyList();
      }

      List wlocks = waitingLocks.remove(tid);

      if (wlocks == null) {
        wlocks = Collections.emptyList();
      }

      String top = null;
      ReadOnlyRepo repo = zs.top(tid);
      if (repo != null)
        top = repo.getDescription();

      TStatus status = zs.getStatus(tid);

      zs.unreserve(tid, 0);

      if ((filterTxid != null && !filterTxid.contains(tid))
          || (filterStatus != null && !filterStatus.contains(status)))
        continue;

      statuses.add(new TransactionStatus(tid, status, debug, hlocks, wlocks, top));
    }

    return new FateStatus(statuses, heldLocks, waitingLocks);

  }

  public void print(ReadOnlyTStore zs, IZooReader zk, String lockPath)
      throws KeeperException, InterruptedException {
    print(zs, zk, lockPath, new Formatter(System.out), null, null);
  }

  public void print(ReadOnlyTStore zs, IZooReader zk, String lockPath, Formatter fmt,
      Set filterTxid, EnumSet filterStatus)
      throws KeeperException, InterruptedException {

    FateStatus fateStatus = getStatus(zs, zk, lockPath, filterTxid, filterStatus);

    for (TransactionStatus txStatus : fateStatus.getTransactions()) {
      fmt.format("txid: %s  status: %-18s  op: %-15s  locked: %-15s locking: %-15s top: %s%n",
          txStatus.getTxid(), txStatus.getStatus(), txStatus.getDebug(), txStatus.getHeldLocks(),
          txStatus.getWaitingLocks(), txStatus.getTop());
    }
    fmt.format(" %s transactions", fateStatus.getTransactions().size());

    if (fateStatus.getDanglingHeldLocks().size() != 0
        || fateStatus.getDanglingWaitingLocks().size() != 0) {
      fmt.format("%nThe following locks did not have an associated FATE operation%n");
      for (Entry> entry : fateStatus.getDanglingHeldLocks().entrySet())
        fmt.format("txid: %s  locked: %s%n", entry.getKey(), entry.getValue());

      for (Entry> entry : fateStatus.getDanglingWaitingLocks().entrySet())
        fmt.format("txid: %s  locking: %s%n", entry.getKey(), entry.getValue());
    }
  }

  public boolean prepDelete(TStore zs, IZooReaderWriter zk, String path, String txidStr) {
    if (!checkGlobalLock(zk, path)) {
      return false;
    }

    long txid;
    try {
      txid = Long.parseLong(txidStr, 16);
    } catch (NumberFormatException nfe) {
      System.out.printf("Invalid transaction ID format: %s%n", txidStr);
      return false;
    }
    boolean state = false;
    zs.reserve(txid);
    TStatus ts = zs.getStatus(txid);
    switch (ts) {
      case UNKNOWN:
        System.out.printf("Invalid transaction ID: %016x%n", txid);
        break;

      case IN_PROGRESS:
      case NEW:
      case FAILED:
      case FAILED_IN_PROGRESS:
      case SUCCESSFUL:
        System.out.printf("Deleting transaction: %016x (%s)%n", txid, ts);
        zs.delete(txid);
        state = true;
        break;
    }

    zs.unreserve(txid, 0);
    return state;
  }

  public boolean prepFail(TStore zs, IZooReaderWriter zk, String path, String txidStr) {
    if (!checkGlobalLock(zk, path)) {
      return false;
    }

    long txid;
    try {
      txid = Long.parseLong(txidStr, 16);
    } catch (NumberFormatException nfe) {
      System.out.printf("Invalid transaction ID format: %s%n", txidStr);
      return false;
    }
    boolean state = false;
    zs.reserve(txid);
    TStatus ts = zs.getStatus(txid);
    switch (ts) {
      case UNKNOWN:
        System.out.printf("Invalid transaction ID: %016x%n", txid);
        break;

      case IN_PROGRESS:
      case NEW:
        System.out.printf("Failing transaction: %016x (%s)%n", txid, ts);
        zs.setStatus(txid, TStatus.FAILED_IN_PROGRESS);
        state = true;
        break;

      case SUCCESSFUL:
        System.out.printf("Transaction already completed: %016x (%s)%n", txid, ts);
        break;

      case FAILED:
      case FAILED_IN_PROGRESS:
        System.out.printf("Transaction already failed: %016x (%s)%n", txid, ts);
        state = true;
        break;
    }

    zs.unreserve(txid, 0);
    return state;
  }

  public void deleteLocks(TStore zs, IZooReaderWriter zk, String path, String txidStr)
      throws KeeperException, InterruptedException {
    // delete any locks assoc w/ fate operation
    List lockedIds = zk.getChildren(path);

    for (String id : lockedIds) {
      List lockNodes = zk.getChildren(path + "/" + id);
      for (String node : lockNodes) {
        String lockPath = path + "/" + id + "/" + node;
        byte[] data = zk.getData(path + "/" + id + "/" + node, null);
        String lda[] = new String(data, UTF_8).split(":");
        if (lda[1].equals(txidStr))
          zk.recursiveDelete(lockPath, NodeMissingPolicy.SKIP);
      }
    }
  }

  public boolean checkGlobalLock(IZooReaderWriter zk, String path) {
    try {
      if (ZooLock.getLockData(zk.getZooKeeper(), path) != null) {
        System.err.println("ERROR: Master lock is held, not running");
        if (this.exitOnError)
          System.exit(1);
        else
          return false;
      }
    } catch (KeeperException e) {
      System.err.println("ERROR: Could not read master lock, not running " + e.getMessage());
      if (this.exitOnError)
        System.exit(1);
      else
        return false;
    } catch (InterruptedException e) {
      System.err.println("ERROR: Could not read master lock, not running" + e.getMessage());
      if (this.exitOnError)
        System.exit(1);
      else
        return false;
    }
    return true;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy