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.hbase.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 }