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 try {
188 final List<ZKUtil.NodeAndData> nodeList =
189 ZKUtil.getChildDataAndWatchForNewChildren(watcher, aclZNode);
190
191 if (childrenChangedFuture != null && !childrenChangedFuture.isDone()) {
192 boolean cancelled = childrenChangedFuture.cancel(true);
193 if (!cancelled) {
194
195 if (! childrenChangedFuture.isDone()) {
196 LOG.warn("Could not cancel processing node children changed event, " +
197 "please file a JIRA and attach logs if possible.");
198 }
199 }
200 }
201 childrenChangedFuture = asyncProcessNodeUpdate(new Runnable() {
202 @Override
203 public void run() {
204 refreshNodes(nodeList);
205 }
206 });
207 } catch (KeeperException ke) {
208 LOG.error("Error reading data from zookeeper for path "+path, ke);
209 watcher.abort("Zookeeper error get node children for path "+path, ke);
210 }
211 }
212 }
213
214 private Future<?> asyncProcessNodeUpdate(Runnable runnable) {
215 if (!executor.isShutdown()) {
216 try {
217 return executor.submit(runnable);
218 } catch (RejectedExecutionException e) {
219 if (executor.isShutdown()) {
220 LOG.warn("aclZNode changed after ZKPermissionWatcher was shutdown");
221 } else {
222 throw e;
223 }
224 }
225 }
226 return null;
227 }
228
229 private void refreshNodes(List<ZKUtil.NodeAndData> nodes) {
230 for (ZKUtil.NodeAndData n : nodes) {
231 if (Thread.interrupted()) {
232
233 break;
234 }
235 if (n.isEmpty()) continue;
236 String path = n.getNode();
237 String entry = (ZKUtil.getNodeName(path));
238 try {
239 refreshAuthManager(entry, n.getData());
240 } catch (IOException ioe) {
241 LOG.error("Failed parsing permissions for table '" + entry +
242 "' from zk", ioe);
243 }
244 }
245 }
246
247 private void refreshAuthManager(String entry, byte[] nodeData) throws IOException {
248 if (LOG.isDebugEnabled()) {
249 LOG.debug("Updating permissions cache from node "+entry+" with data: "+
250 Bytes.toStringBinary(nodeData));
251 }
252 if(AccessControlLists.isNamespaceEntry(entry)) {
253 authManager.refreshNamespaceCacheFromWritable(
254 AccessControlLists.fromNamespaceEntry(entry), nodeData);
255 } else {
256 authManager.refreshTableCacheFromWritable(TableName.valueOf(entry), nodeData);
257 }
258 }
259
260
261
262
263
264
265 public void writeToZookeeper(byte[] entry, byte[] permsData) {
266 String entryName = Bytes.toString(entry);
267 String zkNode = ZKUtil.joinZNode(watcher.baseZNode, ACL_NODE);
268 zkNode = ZKUtil.joinZNode(zkNode, entryName);
269
270 try {
271 ZKUtil.createWithParents(watcher, zkNode);
272 ZKUtil.updateExistingNodeData(watcher, zkNode, permsData, -1);
273 } catch (KeeperException e) {
274 LOG.error("Failed updating permissions for entry '" +
275 entryName + "'", e);
276 watcher.abort("Failed writing node "+zkNode+" to zookeeper", e);
277 }
278 }
279
280
281
282
283
284 public void deleteTableACLNode(final TableName tableName) {
285 String zkNode = ZKUtil.joinZNode(watcher.baseZNode, ACL_NODE);
286 zkNode = ZKUtil.joinZNode(zkNode, tableName.getNameAsString());
287
288 try {
289 ZKUtil.deleteNode(watcher, zkNode);
290 } catch (KeeperException.NoNodeException e) {
291 LOG.warn("No acl notify node of table '" + tableName + "'");
292 } catch (KeeperException e) {
293 LOG.error("Failed deleting acl node of table '" + tableName + "'", e);
294 watcher.abort("Failed deleting node " + zkNode, e);
295 }
296 }
297
298
299
300
301 public void deleteNamespaceACLNode(final String namespace) {
302 String zkNode = ZKUtil.joinZNode(watcher.baseZNode, ACL_NODE);
303 zkNode = ZKUtil.joinZNode(zkNode, AccessControlLists.NAMESPACE_PREFIX + namespace);
304
305 try {
306 ZKUtil.deleteNode(watcher, zkNode);
307 } catch (KeeperException.NoNodeException e) {
308 LOG.warn("No acl notify node of namespace '" + namespace + "'");
309 } catch (KeeperException e) {
310 LOG.error("Failed deleting acl node of namespace '" + namespace + "'", e);
311 watcher.abort("Failed deleting node " + zkNode, e);
312 }
313 }
314 }