1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.security.access;
20
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23 import org.apache.hadoop.hbase.classification.InterfaceAudience;
24 import org.apache.hadoop.conf.Configuration;
25 import org.apache.hadoop.hbase.DaemonThreadFactory;
26 import org.apache.hadoop.hbase.TableName;
27 import org.apache.hadoop.hbase.util.Bytes;
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 import java.io.Closeable;
34 import java.io.IOException;
35 import java.util.List;
36 import java.util.concurrent.Callable;
37 import java.util.concurrent.CountDownLatch;
38 import java.util.concurrent.ExecutionException;
39 import java.util.concurrent.ExecutorService;
40 import java.util.concurrent.Executors;
41 import java.util.concurrent.Future;
42 import java.util.concurrent.RejectedExecutionException;
43 import java.util.concurrent.atomic.AtomicReference;
44
45
46
47
48
49
50
51
52
53
54 @InterfaceAudience.Private
55 public class ZKPermissionWatcher extends ZooKeeperListener implements Closeable {
56 private static final Log LOG = LogFactory.getLog(ZKPermissionWatcher.class);
57
58 static final String ACL_NODE = "acl";
59 private final TableAuthManager authManager;
60 private final String aclZNode;
61 private final CountDownLatch initialized = new CountDownLatch(1);
62 private final ExecutorService executor;
63 private Future<?> childrenChangedFuture;
64
65 public ZKPermissionWatcher(ZooKeeperWatcher watcher,
66 TableAuthManager authManager, Configuration conf) {
67 super(watcher);
68 this.authManager = authManager;
69 String aclZnodeParent = conf.get("zookeeper.znode.acl.parent", ACL_NODE);
70 this.aclZNode = ZKUtil.joinZNode(watcher.baseZNode, aclZnodeParent);
71 executor = Executors.newSingleThreadExecutor(
72 new DaemonThreadFactory("zk-permission-watcher"));
73 }
74
75 public void start() throws KeeperException {
76 try {
77 watcher.registerListener(this);
78 if (ZKUtil.watchAndCheckExists(watcher, aclZNode)) {
79 try {
80 executor.submit(new Callable<Void>() {
81 @Override
82 public Void call() throws KeeperException {
83 List<ZKUtil.NodeAndData> existing =
84 ZKUtil.getChildDataAndWatchForNewChildren(watcher, aclZNode);
85 if (existing != null) {
86 refreshNodes(existing);
87 }
88 return null;
89 }
90 }).get();
91 } catch (ExecutionException ex) {
92 if (ex.getCause() instanceof KeeperException) {
93 throw (KeeperException)ex.getCause();
94 } else {
95 throw new RuntimeException(ex.getCause());
96 }
97 } catch (InterruptedException ex) {
98 Thread.currentThread().interrupt();
99 }
100 }
101 } finally {
102 initialized.countDown();
103 }
104 }
105
106 @Override
107 public void close() {
108 executor.shutdown();
109 }
110
111 private void waitUntilStarted() {
112 try {
113 initialized.await();
114 } catch (InterruptedException e) {
115 LOG.warn("Interrupted while waiting for start", e);
116 Thread.currentThread().interrupt();
117 }
118 }
119
120 @Override
121 public void nodeCreated(String path) {
122 waitUntilStarted();
123 if (path.equals(aclZNode)) {
124 asyncProcessNodeUpdate(new Runnable() {
125 @Override
126 public void run() {
127 try {
128 List<ZKUtil.NodeAndData> nodes =
129 ZKUtil.getChildDataAndWatchForNewChildren(watcher, aclZNode);
130 refreshNodes(nodes);
131 } catch (KeeperException ke) {
132 LOG.error("Error reading data from zookeeper", ke);
133
134 watcher.abort("Zookeeper error obtaining acl node children", ke);
135 }
136 }
137 });
138 }
139 }
140
141 @Override
142 public void nodeDeleted(final String path) {
143 waitUntilStarted();
144 if (aclZNode.equals(ZKUtil.getParent(path))) {
145 asyncProcessNodeUpdate(new Runnable() {
146 @Override
147 public void run() {
148 String table = ZKUtil.getNodeName(path);
149 if(AccessControlLists.isNamespaceEntry(table)) {
150 authManager.removeNamespace(Bytes.toBytes(table));
151 } else {
152 authManager.removeTable(TableName.valueOf(table));
153 }
154 }
155 });
156 }
157 }
158
159 @Override
160 public void nodeDataChanged(final String path) {
161 waitUntilStarted();
162 if (aclZNode.equals(ZKUtil.getParent(path))) {
163 asyncProcessNodeUpdate(new Runnable() {
164 @Override
165 public void run() {
166
167 String entry = ZKUtil.getNodeName(path);
168 try {
169 byte[] data = ZKUtil.getDataAndWatch(watcher, path);
170 refreshAuthManager(entry, data);
171 } catch (KeeperException ke) {
172 LOG.error("Error reading data from zookeeper for node " + entry, ke);
173
174 watcher.abort("Zookeeper error getting data for node " + entry, ke);
175 } catch (IOException ioe) {
176 LOG.error("Error reading permissions writables", ioe);
177 }
178 }
179 });
180 }
181 }
182
183 @Override
184 public void nodeChildrenChanged(final String path) {
185 waitUntilStarted();
186 if (path.equals(aclZNode)) {
187
188 if (childrenChangedFuture != null && !childrenChangedFuture.isDone()) {
189 boolean cancelled = childrenChangedFuture.cancel(true);
190 if (!cancelled) {
191
192 if (!childrenChangedFuture.isDone()) {
193 LOG.warn("Could not cancel processing node children changed event, "
194 + "please file a JIRA and attach logs if possible.");
195 }
196 }
197 }
198
199 childrenChangedFuture = asyncProcessNodeUpdate(new Runnable() {
200 @Override public void run() {
201 try {
202 final List<ZKUtil.NodeAndData> nodeList =
203 ZKUtil.getChildDataAndWatchForNewChildren(watcher, aclZNode, false);
204 refreshNodes(nodeList);
205
206 } catch (KeeperException ke) {
207 String msg = "ZooKeeper error while reading node children data for path " + path;
208 LOG.error(msg, ke);
209 watcher.abort(msg, ke);
210 }
211 }
212 });
213 }
214 }
215
216 private Future<?> asyncProcessNodeUpdate(Runnable runnable) {
217 if (!executor.isShutdown()) {
218 try {
219 return executor.submit(runnable);
220 } catch (RejectedExecutionException e) {
221 if (executor.isShutdown()) {
222 LOG.warn("aclZNode changed after ZKPermissionWatcher was shutdown");
223 } else {
224 throw e;
225 }
226 }
227 }
228 return null;
229 }
230
231 private void refreshNodes(List<ZKUtil.NodeAndData> nodes) {
232 for (ZKUtil.NodeAndData n : nodes) {
233 if (Thread.interrupted()) {
234
235 break;
236 }
237 if (n.isEmpty()) continue;
238 String path = n.getNode();
239 String entry = (ZKUtil.getNodeName(path));
240 try {
241 refreshAuthManager(entry, n.getData());
242 } catch (IOException ioe) {
243 LOG.error("Failed parsing permissions for table '" + entry +
244 "' from zk", ioe);
245 }
246 }
247 }
248
249 private void refreshAuthManager(String entry, byte[] nodeData) throws IOException {
250 if (LOG.isDebugEnabled()) {
251 LOG.debug("Updating permissions cache from node "+entry+" with data: "+
252 Bytes.toStringBinary(nodeData));
253 }
254 if(AccessControlLists.isNamespaceEntry(entry)) {
255 authManager.refreshNamespaceCacheFromWritable(
256 AccessControlLists.fromNamespaceEntry(entry), nodeData);
257 } else {
258 authManager.refreshTableCacheFromWritable(TableName.valueOf(entry), nodeData);
259 }
260 }
261
262
263
264
265
266
267 public void writeToZookeeper(byte[] entry, byte[] permsData) {
268 String entryName = Bytes.toString(entry);
269 String zkNode = ZKUtil.joinZNode(watcher.baseZNode, ACL_NODE);
270 zkNode = ZKUtil.joinZNode(zkNode, entryName);
271
272 try {
273 ZKUtil.createWithParents(watcher, zkNode);
274 ZKUtil.updateExistingNodeData(watcher, zkNode, permsData, -1);
275 } catch (KeeperException e) {
276 LOG.error("Failed updating permissions for entry '" +
277 entryName + "'", e);
278 watcher.abort("Failed writing node "+zkNode+" to zookeeper", e);
279 }
280 }
281
282
283
284
285
286 public void deleteTableACLNode(final TableName tableName) {
287 String zkNode = ZKUtil.joinZNode(watcher.baseZNode, ACL_NODE);
288 zkNode = ZKUtil.joinZNode(zkNode, tableName.getNameAsString());
289
290 try {
291 ZKUtil.deleteNode(watcher, zkNode);
292 } catch (KeeperException.NoNodeException e) {
293 LOG.warn("No acl notify node of table '" + tableName + "'");
294 } catch (KeeperException e) {
295 LOG.error("Failed deleting acl node of table '" + tableName + "'", e);
296 watcher.abort("Failed deleting node " + zkNode, e);
297 }
298 }
299
300
301
302
303 public void deleteNamespaceACLNode(final String namespace) {
304 String zkNode = ZKUtil.joinZNode(watcher.baseZNode, ACL_NODE);
305 zkNode = ZKUtil.joinZNode(zkNode, AccessControlLists.NAMESPACE_PREFIX + namespace);
306
307 try {
308 ZKUtil.deleteNode(watcher, zkNode);
309 } catch (KeeperException.NoNodeException e) {
310 LOG.warn("No acl notify node of namespace '" + namespace + "'");
311 } catch (KeeperException e) {
312 LOG.error("Failed deleting acl node of namespace '" + namespace + "'", e);
313 watcher.abort("Failed deleting node " + zkNode, e);
314 }
315 }
316 }