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