001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.backup.example;
019
020import java.io.IOException;
021import java.util.List;
022
023import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
024import org.apache.yetus.audience.InterfaceAudience;
025import org.apache.hadoop.conf.Configuration;
026import org.apache.hadoop.hbase.ZooKeeperConnectionException;
027import org.apache.hadoop.hbase.zookeeper.ZKUtil;
028import org.apache.hadoop.hbase.zookeeper.ZKListener;
029import org.apache.zookeeper.KeeperException;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033/**
034 * Track HFile archiving state changes in ZooKeeper. Keeps track of the tables whose HFiles should
035 * be kept in the archive.
036 * <p>
037 * {@link TableHFileArchiveTracker#start()} needs to be called to start monitoring for tables to
038 * archive.
039 */
040@InterfaceAudience.Private
041public class TableHFileArchiveTracker extends ZKListener {
042  private static final Logger LOG = LoggerFactory.getLogger(TableHFileArchiveTracker.class);
043  public static final String HFILE_ARCHIVE_ZNODE_PARENT = "hfilearchive";
044  private HFileArchiveTableMonitor monitor;
045  private String archiveHFileZNode;
046  private boolean stopped = false;
047
048  private TableHFileArchiveTracker(ZKWatcher watcher, HFileArchiveTableMonitor monitor) {
049    super(watcher);
050    watcher.registerListener(this);
051    this.monitor = monitor;
052    this.archiveHFileZNode = ZKTableArchiveClient.getArchiveZNode(watcher.getConfiguration(),
053      watcher);
054  }
055
056  /**
057   * Start monitoring for archive updates
058   * @throws KeeperException on failure to find/create nodes
059   */
060  public void start() throws KeeperException {
061    // if archiving is enabled, then read in the list of tables to archive
062    LOG.debug("Starting hfile archive tracker...");
063    this.checkEnabledAndUpdate();
064    LOG.debug("Finished starting hfile archive tracker!");
065  }
066
067  @Override
068  public void nodeCreated(String path) {
069    // if it is the archive path
070    if (!path.startsWith(archiveHFileZNode)) return;
071
072    LOG.debug("Archive node: " + path + " created");
073    // since we are already enabled, just update a single table
074    String table = path.substring(archiveHFileZNode.length());
075
076    // the top level node has come up, so read in all the tables
077    if (table.length() == 0) {
078
079      checkEnabledAndUpdate();
080      return;
081    }
082    // find the table that needs to be archived
083    try {
084      addAndReWatchTable(path);
085    } catch (KeeperException e) {
086      LOG.warn("Couldn't read zookeeper data for table for path:" + path
087          + ", not preserving a table.", e);
088    }
089  }
090
091  @Override
092  public void nodeChildrenChanged(String path) {
093    if (!path.startsWith(archiveHFileZNode)) return;
094
095    LOG.debug("Archive node: " + path + " children changed.");
096    // a table was added to the archive
097    try {
098      updateWatchedTables();
099    } catch (KeeperException e) {
100      LOG.error("Failed to update tables to archive", e);
101    }
102  }
103
104  /**
105   * Add this table to the tracker and then read a watch on that node.
106   * <p>
107   * Handles situation where table is deleted in the time between the update and resetting the watch
108   * by deleting the table via {@link #safeStopTrackingTable(String)}
109   * @param tableZnode full zookeeper path to the table to be added
110   * @throws KeeperException if an unexpected zk exception occurs
111   */
112  private void addAndReWatchTable(String tableZnode) throws KeeperException {
113    getMonitor().addTable(ZKUtil.getNodeName(tableZnode));
114    // re-add a watch to the table created
115    // and check to make sure it wasn't deleted
116    if (!ZKUtil.watchAndCheckExists(watcher, tableZnode)) {
117      safeStopTrackingTable(tableZnode);
118    }
119  }
120
121  /**
122   * Stop tracking a table. Ensures that the table doesn't exist, but if it does, it attempts to add
123   * the table back via {@link #addAndReWatchTable(String)} - its a 'safe' removal.
124   * @param tableZnode full zookeeper path to the table to be added
125   * @throws KeeperException if an unexpected zk exception occurs
126   */
127  private void safeStopTrackingTable(String tableZnode) throws KeeperException {
128    getMonitor().removeTable(ZKUtil.getNodeName(tableZnode));
129    // if the table exists, then add and rewatch it
130    if (ZKUtil.checkExists(watcher, tableZnode) >= 0) {
131      addAndReWatchTable(tableZnode);
132    }
133  }
134
135  @Override
136  public void nodeDeleted(String path) {
137    if (!path.startsWith(archiveHFileZNode)) return;
138
139    LOG.debug("Archive node: " + path + " deleted");
140    String table = path.substring(archiveHFileZNode.length());
141    // if we stop archiving all tables
142    if (table.length() == 0) {
143      // make sure we have the tracker before deleting the archive
144      // but if we don't, we don't care about delete
145      clearTables();
146      // watches are one-time events, so we need to renew our subscription to
147      // the archive node and might as well check to make sure archiving
148      // didn't come back on at the same time
149      checkEnabledAndUpdate();
150      return;
151    }
152    // just stop archiving one table
153    // note that we don't attempt to add another watch for that table into zk.
154    // We have no assurances that the table will be archived again (or even
155    // exists for that matter), so its better not to add unnecessary load to
156    // zk for watches. If the table is created again, then we will get the
157    // notification in childrenChanaged.
158    getMonitor().removeTable(ZKUtil.getNodeName(path));
159  }
160
161  /**
162   * Sets the watch on the top-level archive znode, and then updates the monitor with the current
163   * tables that should be archived (and ensures that those nodes are watched as well).
164   */
165  private void checkEnabledAndUpdate() {
166    try {
167      if (ZKUtil.watchAndCheckExists(watcher, archiveHFileZNode)) {
168        LOG.debug(archiveHFileZNode + " znode does exist, checking for tables to archive");
169
170        // update the tables we should backup, to get the most recent state.
171        // This is safer than also watching for children and then hoping we get
172        // all the updates as it makes sure we get and watch all the children
173        updateWatchedTables();
174      } else {
175        LOG.debug("Archiving not currently enabled, waiting");
176      }
177    } catch (KeeperException e) {
178      LOG.warn("Failed to watch for archiving znode", e);
179    }
180  }
181
182  /**
183   * Read the list of children under the archive znode as table names and then sets those tables to
184   * the list of tables that we should archive
185   * @throws KeeperException if there is an unexpected zk exception
186   */
187  private void updateWatchedTables() throws KeeperException {
188    // get the children and watch for new children
189    LOG.debug("Updating watches on tables to archive.");
190    // get the children and add watches for each of the children
191    List<String> tables = ZKUtil.listChildrenAndWatchThem(watcher, archiveHFileZNode);
192    LOG.debug("Starting archive for tables:" + tables);
193    // if archiving is still enabled
194    if (tables != null && tables.size() > 0) {
195      getMonitor().setArchiveTables(tables);
196    } else {
197      LOG.debug("No tables to archive.");
198      // only if we currently have a tracker, then clear the archive
199      clearTables();
200    }
201  }
202
203  /**
204   * Remove the currently archived tables.
205   * <p>
206   * Does some intelligent checking to make sure we don't prematurely create an archive tracker.
207   */
208  private void clearTables() {
209    getMonitor().clearArchive();
210  }
211
212  /**
213   * Determine if the given table should or should not allow its hfiles to be deleted
214   * @param tableName name of the table to check
215   * @return <tt>true</tt> if its store files should be retained, <tt>false</tt> otherwise
216   */
217  public boolean keepHFiles(String tableName) {
218    return getMonitor().shouldArchiveTable(tableName);
219  }
220
221  /**
222   * @return the tracker for which tables should be archived.
223   */
224  public final HFileArchiveTableMonitor getMonitor() {
225    return this.monitor;
226  }
227
228  /**
229   * Create an archive tracker for the passed in server
230   * @param conf to read for zookeeper connection information
231   * @return ZooKeeper tracker to monitor for this server if this server should archive hfiles for a
232   *         given table
233   * @throws IOException If a unexpected exception occurs
234   * @throws ZooKeeperConnectionException if we can't reach zookeeper
235   */
236  public static TableHFileArchiveTracker create(Configuration conf)
237      throws ZooKeeperConnectionException, IOException {
238    ZKWatcher zkw = new ZKWatcher(conf, "hfileArchiveCleaner", null);
239    return create(zkw, new HFileArchiveTableMonitor());
240  }
241
242  /**
243   * Create an archive tracker with the special passed in table monitor. Should only be used in
244   * special cases (e.g. testing)
245   * @param zkw Watcher for the ZooKeeper cluster that we should track
246   * @param monitor Monitor for which tables need hfile archiving
247   * @return ZooKeeper tracker to monitor for this server if this server should archive hfiles for a
248   *         given table
249   */
250  private static TableHFileArchiveTracker create(ZKWatcher zkw,
251      HFileArchiveTableMonitor monitor) {
252    return new TableHFileArchiveTracker(zkw, monitor);
253  }
254
255  public ZKWatcher getZooKeeperWatcher() {
256    return this.watcher;
257  }
258
259  /**
260   * Stop this tracker and the passed zookeeper
261   */
262  public void stop() {
263    if (this.stopped) return;
264    this.stopped = true;
265    this.watcher.close();
266  }
267}