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  
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   * Handles synchronization of access control list entries and updates
47   * throughout all nodes in the cluster.  The {@link AccessController} instance
48   * on the {@code _acl_} table regions, creates a znode for each table as
49   * {@code /hbase/acl/tablename}, with the znode data containing a serialized
50   * list of the permissions granted for the table.  The {@code AccessController}
51   * instances on all other cluster hosts watch the znodes for updates, which
52   * trigger updates in the {@link TableAuthManager} permission cache.
53   */
54  @InterfaceAudience.Private
55  public class ZKPermissionWatcher extends ZooKeeperListener implements Closeable {
56    private static final Log LOG = LogFactory.getLog(ZKPermissionWatcher.class);
57    // parent node for permissions lists
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             // only option is to abort
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           // update cache on an existing table node
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             // only option is to abort
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         // preempt any existing nodeChildrenChanged event processing
191         if (childrenChangedFuture != null && !childrenChangedFuture.isDone()) {
192           boolean cancelled = childrenChangedFuture.cancel(true);
193           if (!cancelled) {
194             // task may have finished between our check and attempted cancel, this is fine.
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; // No task launched so there will be nothing to cancel later
227   }
228 
229   private void refreshNodes(List<ZKUtil.NodeAndData> nodes) {
230     for (ZKUtil.NodeAndData n : nodes) {
231       if (Thread.interrupted()) {
232         // Use Thread.interrupted so that we clear interrupt status
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    * Write a table's access controls to the permissions mirror in zookeeper
262    * @param entry
263    * @param permsData
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    * Delete the acl notify node of table
282    * @param tableName
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    * Delete the acl notify node of namespace
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 }