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