View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.backup.example;
19  
20  import java.io.IOException;
21  import java.util.List;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.apache.hadoop.classification.InterfaceAudience;
26  import org.apache.hadoop.conf.Configuration;
27  import org.apache.hadoop.hbase.ZooKeeperConnectionException;
28  import org.apache.hadoop.hbase.zookeeper.ZKUtil;
29  import org.apache.hadoop.hbase.zookeeper.ZooKeeperListener;
30  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
31  import org.apache.zookeeper.KeeperException;
32  
33  /**
34   * Track HFile archiving state changes in ZooKeeper. Keeps track of the tables whose HFiles should
35   * be kept in the archive.
36   * <p>
37   * {@link TableHFileArchiveTracker#start()} needs to be called to start monitoring for tables to
38   * archive.
39   */
40  @InterfaceAudience.Private
41  public class TableHFileArchiveTracker extends ZooKeeperListener {
42    private static final Log LOG = LogFactory.getLog(TableHFileArchiveTracker.class);
43    public static final String HFILE_ARCHIVE_ZNODE_PARENT = "hfilearchive";
44    private HFileArchiveTableMonitor monitor;
45    private String archiveHFileZNode;
46    private boolean stopped = false;
47  
48    private TableHFileArchiveTracker(ZooKeeperWatcher watcher, HFileArchiveTableMonitor monitor) {
49      super(watcher);
50      watcher.registerListener(this);
51      this.monitor = monitor;
52      this.archiveHFileZNode = ZKTableArchiveClient.getArchiveZNode(watcher.getConfiguration(),
53        watcher);
54    }
55  
56    /**
57     * Start monitoring for archive updates
58     * @throws KeeperException on failure to find/create nodes
59     */
60    public void start() throws KeeperException {
61      // if archiving is enabled, then read in the list of tables to archive
62      LOG.debug("Starting hfile archive tracker...");
63      this.checkEnabledAndUpdate();
64      LOG.debug("Finished starting hfile archive tracker!");
65    }
66  
67    @Override
68    public void nodeCreated(String path) {
69      // if it is the archive path
70      if (!path.startsWith(archiveHFileZNode)) return;
71  
72      LOG.debug("Archive node: " + path + " created");
73      // since we are already enabled, just update a single table
74      String table = path.substring(archiveHFileZNode.length());
75  
76      // the top level node has come up, so read in all the tables
77      if (table.length() == 0) {
78  
79        checkEnabledAndUpdate();
80        return;
81      }
82      // find the table that needs to be archived
83      try {
84        addAndReWatchTable(path);
85      } catch (KeeperException e) {
86        LOG.warn("Couldn't read zookeeper data for table for path:" + path
87            + ", not preserving a table.", e);
88      }
89    }
90  
91    @Override
92    public void nodeChildrenChanged(String path) {
93      if (!path.startsWith(archiveHFileZNode)) return;
94  
95      LOG.debug("Archive node: " + path + " children changed.");
96      // a table was added to the archive
97      try {
98        updateWatchedTables();
99      } 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     ZooKeeperWatcher zkw = new ZooKeeperWatcher(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(ZooKeeperWatcher zkw,
251       HFileArchiveTableMonitor monitor) {
252     return new TableHFileArchiveTracker(zkw, monitor);
253   }
254 
255   public ZooKeeperWatcher 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 }