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 java.io.Closeable;
22  import java.io.IOException;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.concurrent.ConcurrentSkipListMap;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.hadoop.hbase.AuthUtil;
31  import org.apache.hadoop.hbase.classification.InterfaceAudience;
32  import org.apache.hadoop.conf.Configuration;
33  import org.apache.hadoop.hbase.Cell;
34  import org.apache.hadoop.hbase.TableName;
35  import org.apache.hadoop.hbase.exceptions.DeserializationException;
36  import org.apache.hadoop.hbase.security.Superusers;
37  import org.apache.hadoop.hbase.security.User;
38  import org.apache.hadoop.hbase.security.UserProvider;
39  import org.apache.hadoop.hbase.util.Bytes;
40  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
41  import org.apache.zookeeper.KeeperException;
42  
43  import com.google.common.annotations.VisibleForTesting;
44  import com.google.common.collect.ArrayListMultimap;
45  import com.google.common.collect.ListMultimap;
46  import com.google.common.collect.Lists;
47  
48  /**
49   * Performs authorization checks for a given user's assigned permissions
50   */
51  @InterfaceAudience.Private
52  public class TableAuthManager implements Closeable {
53    private static class PermissionCache<T extends Permission> {
54      /** Cache of user permissions */
55      private ListMultimap<String,T> userCache = ArrayListMultimap.create();
56      /** Cache of group permissions */
57      private ListMultimap<String,T> groupCache = ArrayListMultimap.create();
58  
59      public List<T> getUser(String user) {
60        return userCache.get(user);
61      }
62  
63      public void putUser(String user, T perm) {
64        userCache.put(user, perm);
65      }
66  
67      public List<T> replaceUser(String user, Iterable<? extends T> perms) {
68        return userCache.replaceValues(user, perms);
69      }
70  
71      public List<T> getGroup(String group) {
72        return groupCache.get(group);
73      }
74  
75      public void putGroup(String group, T perm) {
76        groupCache.put(group, perm);
77      }
78  
79      public List<T> replaceGroup(String group, Iterable<? extends T> perms) {
80        return groupCache.replaceValues(group, perms);
81      }
82  
83      /**
84       * Returns a combined map of user and group permissions, with group names prefixed by
85       * {@link AuthUtil#GROUP_PREFIX}.
86       */
87      public ListMultimap<String,T> getAllPermissions() {
88        ListMultimap<String,T> tmp = ArrayListMultimap.create();
89        tmp.putAll(userCache);
90        for (String group : groupCache.keySet()) {
91          tmp.putAll(AuthUtil.toGroupEntry(group), groupCache.get(group));
92        }
93        return tmp;
94      }
95    }
96  
97    private static Log LOG = LogFactory.getLog(TableAuthManager.class);
98  
99    /** Cache of global permissions */
100   private volatile PermissionCache<Permission> globalCache;
101 
102   private ConcurrentSkipListMap<TableName, PermissionCache<TablePermission>> tableCache =
103       new ConcurrentSkipListMap<TableName, PermissionCache<TablePermission>>();
104 
105   private ConcurrentSkipListMap<String, PermissionCache<TablePermission>> nsCache =
106     new ConcurrentSkipListMap<String, PermissionCache<TablePermission>>();
107 
108   private Configuration conf;
109   private ZKPermissionWatcher zkperms;
110   private volatile long mtime;
111 
112   private TableAuthManager(ZooKeeperWatcher watcher, Configuration conf)
113       throws IOException {
114     this.conf = conf;
115 
116     // initialize global permissions based on configuration
117     globalCache = initGlobal(conf);
118 
119     this.zkperms = new ZKPermissionWatcher(watcher, this, conf);
120     try {
121       this.zkperms.start();
122     } catch (KeeperException ke) {
123       LOG.error("ZooKeeper initialization failed", ke);
124     }
125   }
126 
127   @Override
128   public void close() {
129     this.zkperms.close();
130   }
131 
132   /**
133    * Returns a new {@code PermissionCache} initialized with permission assignments
134    * from the {@code hbase.superuser} configuration key.
135    */
136   private PermissionCache<Permission> initGlobal(Configuration conf) throws IOException {
137     UserProvider userProvider = UserProvider.instantiate(conf);
138     User user = userProvider.getCurrent();
139     if (user == null) {
140       throw new IOException("Unable to obtain the current user, " +
141           "authorization checks for internal operations will not work correctly!");
142     }
143     PermissionCache<Permission> newCache = new PermissionCache<Permission>();
144     String currentUser = user.getShortName();
145 
146     // the system user is always included
147     List<String> superusers = Lists.asList(currentUser, conf.getStrings(
148         Superusers.SUPERUSER_CONF_KEY, new String[0]));
149     if (superusers != null) {
150       for (String name : superusers) {
151         if (AuthUtil.isGroupPrincipal(name)) {
152           newCache.putGroup(AuthUtil.getGroupName(name),
153               new Permission(Permission.Action.values()));
154         } else {
155           newCache.putUser(name, new Permission(Permission.Action.values()));
156         }
157       }
158     }
159     return newCache;
160   }
161 
162   public ZKPermissionWatcher getZKPermissionWatcher() {
163     return this.zkperms;
164   }
165 
166   public void refreshTableCacheFromWritable(TableName table,
167                                        byte[] data) throws IOException {
168     if (data != null && data.length > 0) {
169       ListMultimap<String,TablePermission> perms;
170       try {
171         perms = AccessControlLists.readPermissions(data, conf);
172       } catch (DeserializationException e) {
173         throw new IOException(e);
174       }
175 
176       if (perms != null) {
177         if (Bytes.equals(table.getName(), AccessControlLists.ACL_GLOBAL_NAME)) {
178           updateGlobalCache(perms);
179         } else {
180           updateTableCache(table, perms);
181         }
182       }
183     } else {
184       LOG.debug("Skipping permission cache refresh because writable data is empty");
185     }
186   }
187 
188   public void refreshNamespaceCacheFromWritable(String namespace, byte[] data) throws IOException {
189     if (data != null && data.length > 0) {
190       ListMultimap<String,TablePermission> perms;
191       try {
192         perms = AccessControlLists.readPermissions(data, conf);
193       } catch (DeserializationException e) {
194         throw new IOException(e);
195       }
196       if (perms != null) {
197         updateNsCache(namespace, perms);
198       }
199     } else {
200       LOG.debug("Skipping permission cache refresh because writable data is empty");
201     }
202   }
203 
204   /**
205    * Updates the internal global permissions cache
206    *
207    * @param userPerms
208    */
209   private void updateGlobalCache(ListMultimap<String,TablePermission> userPerms) {
210     PermissionCache<Permission> newCache = null;
211     try {
212       newCache = initGlobal(conf);
213       for (Map.Entry<String,TablePermission> entry : userPerms.entries()) {
214         if (AuthUtil.isGroupPrincipal(entry.getKey())) {
215           newCache.putGroup(AuthUtil.getGroupName(entry.getKey()),
216               new Permission(entry.getValue().getActions()));
217         } else {
218           newCache.putUser(entry.getKey(), new Permission(entry.getValue().getActions()));
219         }
220       }
221       globalCache = newCache;
222       mtime++;
223     } catch (IOException e) {
224       // Never happens
225       LOG.error("Error occured while updating the global cache", e);
226     }
227   }
228 
229   /**
230    * Updates the internal permissions cache for a single table, splitting
231    * the permissions listed into separate caches for users and groups to optimize
232    * group lookups.
233    *
234    * @param table
235    * @param tablePerms
236    */
237   private void updateTableCache(TableName table,
238                                 ListMultimap<String,TablePermission> tablePerms) {
239     PermissionCache<TablePermission> newTablePerms = new PermissionCache<TablePermission>();
240 
241     for (Map.Entry<String,TablePermission> entry : tablePerms.entries()) {
242       if (AuthUtil.isGroupPrincipal(entry.getKey())) {
243         newTablePerms.putGroup(AuthUtil.getGroupName(entry.getKey()), entry.getValue());
244       } else {
245         newTablePerms.putUser(entry.getKey(), entry.getValue());
246       }
247     }
248 
249     tableCache.put(table, newTablePerms);
250     mtime++;
251   }
252 
253   /**
254    * Updates the internal permissions cache for a single table, splitting
255    * the permissions listed into separate caches for users and groups to optimize
256    * group lookups.
257    *
258    * @param namespace
259    * @param tablePerms
260    */
261   private void updateNsCache(String namespace,
262                              ListMultimap<String, TablePermission> tablePerms) {
263     PermissionCache<TablePermission> newTablePerms = new PermissionCache<TablePermission>();
264 
265     for (Map.Entry<String, TablePermission> entry : tablePerms.entries()) {
266       if (AuthUtil.isGroupPrincipal(entry.getKey())) {
267         newTablePerms.putGroup(AuthUtil.getGroupName(entry.getKey()), entry.getValue());
268       } else {
269         newTablePerms.putUser(entry.getKey(), entry.getValue());
270       }
271     }
272 
273     nsCache.put(namespace, newTablePerms);
274     mtime++;
275   }
276 
277   private PermissionCache<TablePermission> getTablePermissions(TableName table) {
278     if (!tableCache.containsKey(table)) {
279       tableCache.putIfAbsent(table, new PermissionCache<TablePermission>());
280     }
281     return tableCache.get(table);
282   }
283 
284   private PermissionCache<TablePermission> getNamespacePermissions(String namespace) {
285     if (!nsCache.containsKey(namespace)) {
286       nsCache.putIfAbsent(namespace, new PermissionCache<TablePermission>());
287     }
288     return nsCache.get(namespace);
289   }
290 
291   /**
292    * Authorizes a global permission
293    * @param perms
294    * @param action
295    * @return true if authorized, false otherwise
296    */
297   private boolean authorize(List<Permission> perms, Permission.Action action) {
298     if (perms != null) {
299       for (Permission p : perms) {
300         if (p.implies(action)) {
301           return true;
302         }
303       }
304     } else if (LOG.isDebugEnabled()) {
305       LOG.debug("No permissions found for " + action);
306     }
307 
308     return false;
309   }
310 
311   /**
312    * Authorize a global permission based on ACLs for the given user and the
313    * user's groups.
314    * @param user
315    * @param action
316    * @return true if known and authorized, false otherwise
317    */
318   public boolean authorize(User user, Permission.Action action) {
319     if (user == null) {
320       return false;
321     }
322 
323     if (authorize(globalCache.getUser(user.getShortName()), action)) {
324       return true;
325     }
326 
327     String[] groups = user.getGroupNames();
328     if (groups != null) {
329       for (String group : groups) {
330         if (authorize(globalCache.getGroup(group), action)) {
331           return true;
332         }
333       }
334     }
335     return false;
336   }
337 
338   private boolean authorize(List<TablePermission> perms,
339                             TableName table, byte[] family,
340                             Permission.Action action) {
341     return authorize(perms, table, family, null, action);
342   }
343 
344   private boolean authorize(List<TablePermission> perms,
345                             TableName table, byte[] family,
346                             byte[] qualifier, Permission.Action action) {
347     if (perms != null) {
348       for (TablePermission p : perms) {
349         if (p.implies(table, family, qualifier, action)) {
350           return true;
351         }
352       }
353     } else if (LOG.isDebugEnabled()) {
354       LOG.debug("No permissions found for table="+table);
355     }
356     return false;
357   }
358 
359   private boolean hasAccess(List<TablePermission> perms,
360                             TableName table, Permission.Action action) {
361     if (perms != null) {
362       for (TablePermission p : perms) {
363         if (p.implies(action)) {
364           return true;
365         }
366       }
367     } else if (LOG.isDebugEnabled()) {
368       LOG.debug("No permissions found for table="+table);
369     }
370     return false;
371   }
372 
373   /**
374    * Authorize a user for a given KV. This is called from AccessControlFilter.
375    */
376   public boolean authorize(User user, TableName table, Cell cell, Permission.Action action) {
377     try {
378       List<Permission> perms = AccessControlLists.getCellPermissionsForUser(user, cell);
379       if (LOG.isTraceEnabled()) {
380         LOG.trace("Perms for user " + user.getShortName() + " in cell " + cell + ": " +
381           (perms != null ? perms : ""));
382       }
383       if (perms != null) {
384         for (Permission p: perms) {
385           if (p.implies(action)) {
386             return true;
387           }
388         }
389       }
390     } catch (IOException e) {
391       // We failed to parse the KV tag
392       LOG.error("Failed parse of ACL tag in cell " + cell);
393       // Fall through to check with the table and CF perms we were able
394       // to collect regardless
395     }
396     return false;
397   }
398 
399   public boolean authorize(User user, String namespace, Permission.Action action) {
400     // Global authorizations supercede namespace level
401     if (authorize(user, action)) {
402       return true;
403     }
404     // Check namespace permissions
405     PermissionCache<TablePermission> tablePerms = nsCache.get(namespace);
406     if (tablePerms != null) {
407       List<TablePermission> userPerms = tablePerms.getUser(user.getShortName());
408       if (authorize(userPerms, namespace, action)) {
409         return true;
410       }
411       String[] groupNames = user.getGroupNames();
412       if (groupNames != null) {
413         for (String group : groupNames) {
414           List<TablePermission> groupPerms = tablePerms.getGroup(group);
415           if (authorize(groupPerms, namespace, action)) {
416             return true;
417           }
418         }
419       }
420     }
421     return false;
422   }
423 
424   private boolean authorize(List<TablePermission> perms, String namespace,
425                             Permission.Action action) {
426     if (perms != null) {
427       for (TablePermission p : perms) {
428         if (p.implies(namespace, action)) {
429           return true;
430         }
431       }
432     } else if (LOG.isDebugEnabled()) {
433       LOG.debug("No permissions for authorize() check, table=" + namespace);
434     }
435 
436     return false;
437   }
438 
439   /**
440    * Checks authorization to a given table and column family for a user, based on the
441    * stored user permissions.
442    *
443    * @param user
444    * @param table
445    * @param family
446    * @param action
447    * @return true if known and authorized, false otherwise
448    */
449   public boolean authorizeUser(User user, TableName table, byte[] family,
450       Permission.Action action) {
451     return authorizeUser(user, table, family, null, action);
452   }
453 
454   public boolean authorizeUser(User user, TableName table, byte[] family,
455       byte[] qualifier, Permission.Action action) {
456     if (table == null) table = AccessControlLists.ACL_TABLE_NAME;
457     // Global and namespace authorizations supercede table level
458     if (authorize(user, table.getNamespaceAsString(), action)) {
459       return true;
460     }
461     // Check table permissions
462     return authorize(getTablePermissions(table).getUser(user.getShortName()), table, family,
463         qualifier, action);
464   }
465 
466   /**
467    * Checks if the user has access to the full table or at least a family/qualifier
468    * for the specified action.
469    *
470    * @param user
471    * @param table
472    * @param action
473    * @return true if the user has access to the table, false otherwise
474    */
475   public boolean userHasAccess(User user, TableName table, Permission.Action action) {
476     if (table == null) table = AccessControlLists.ACL_TABLE_NAME;
477     // Global and namespace authorizations supercede table level
478     if (authorize(user, table.getNamespaceAsString(), action)) {
479       return true;
480     }
481     // Check table permissions
482     return hasAccess(getTablePermissions(table).getUser(user.getShortName()), table, action);
483   }
484 
485   /**
486    * Checks global authorization for a given action for a group, based on the stored
487    * permissions.
488    */
489   public boolean authorizeGroup(String groupName, Permission.Action action) {
490     List<Permission> perms = globalCache.getGroup(groupName);
491     if (LOG.isDebugEnabled()) {
492       LOG.debug("authorizing " + (perms != null && !perms.isEmpty() ? perms.get(0) : "") +
493         " for " + action);
494     }
495     return authorize(perms, action);
496   }
497 
498   /**
499    * Checks authorization to a given table, column family and column for a group, based
500    * on the stored permissions.
501    * @param groupName
502    * @param table
503    * @param family
504    * @param qualifier
505    * @param action
506    * @return true if known and authorized, false otherwise
507    */
508   public boolean authorizeGroup(String groupName, TableName table, byte[] family,
509       byte[] qualifier, Permission.Action action) {
510     // Global authorization supercedes table level
511     if (authorizeGroup(groupName, action)) {
512       return true;
513     }
514     if (table == null) table = AccessControlLists.ACL_TABLE_NAME;
515     // Namespace authorization supercedes table level
516     String namespace = table.getNamespaceAsString();
517     if (authorize(getNamespacePermissions(namespace).getGroup(groupName), namespace, action)) {
518       return true;
519     }
520     // Check table level
521     List<TablePermission> tblPerms = getTablePermissions(table).getGroup(groupName);
522     if (LOG.isDebugEnabled()) {
523       LOG.debug("authorizing " + (tblPerms != null && !tblPerms.isEmpty() ? tblPerms.get(0) : "") +
524         " for " +groupName + " on " + table + "." + Bytes.toString(family) + "." +
525         Bytes.toString(qualifier) + " with " + action);
526     }
527     return authorize(tblPerms, table, family, qualifier, action);
528   }
529 
530   /**
531    * Checks if the user has access to the full table or at least a family/qualifier
532    * for the specified action.
533    * @param groupName
534    * @param table
535    * @param action
536    * @return true if the group has access to the table, false otherwise
537    */
538   public boolean groupHasAccess(String groupName, TableName table, Permission.Action action) {
539     // Global authorization supercedes table level
540     if (authorizeGroup(groupName, action)) {
541       return true;
542     }
543     if (table == null) table = AccessControlLists.ACL_TABLE_NAME;
544     // Namespace authorization supercedes table level
545     if (hasAccess(getNamespacePermissions(table.getNamespaceAsString()).getGroup(groupName),
546         table, action)) {
547       return true;
548     }
549     // Check table level
550     return hasAccess(getTablePermissions(table).getGroup(groupName), table, action);
551   }
552 
553   public boolean authorize(User user, TableName table, byte[] family,
554       byte[] qualifier, Permission.Action action) {
555     if (authorizeUser(user, table, family, qualifier, action)) {
556       return true;
557     }
558 
559     String[] groups = user.getGroupNames();
560     if (groups != null) {
561       for (String group : groups) {
562         if (authorizeGroup(group, table, family, qualifier, action)) {
563           return true;
564         }
565       }
566     }
567     return false;
568   }
569 
570   public boolean hasAccess(User user, TableName table, Permission.Action action) {
571     if (userHasAccess(user, table, action)) {
572       return true;
573     }
574 
575     String[] groups = user.getGroupNames();
576     if (groups != null) {
577       for (String group : groups) {
578         if (groupHasAccess(group, table, action)) {
579           return true;
580         }
581       }
582     }
583     return false;
584   }
585 
586   public boolean authorize(User user, TableName table, byte[] family,
587       Permission.Action action) {
588     return authorize(user, table, family, null, action);
589   }
590 
591   /**
592    * Returns true if the given user has a {@link TablePermission} matching up
593    * to the column family portion of a permission.  Note that this permission
594    * may be scoped to a given column qualifier and does not guarantee that
595    * authorize() on the same column family would return true.
596    */
597   public boolean matchPermission(User user,
598       TableName table, byte[] family, Permission.Action action) {
599     PermissionCache<TablePermission> tablePerms = tableCache.get(table);
600     if (tablePerms != null) {
601       List<TablePermission> userPerms = tablePerms.getUser(user.getShortName());
602       if (userPerms != null) {
603         for (TablePermission p : userPerms) {
604           if (p.matchesFamily(table, family, action)) {
605             return true;
606           }
607         }
608       }
609 
610       String[] groups = user.getGroupNames();
611       if (groups != null) {
612         for (String group : groups) {
613           List<TablePermission> groupPerms = tablePerms.getGroup(group);
614           if (groupPerms != null) {
615             for (TablePermission p : groupPerms) {
616               if (p.matchesFamily(table, family, action)) {
617                 return true;
618               }
619             }
620           }
621         }
622       }
623     }
624 
625     return false;
626   }
627 
628   public boolean matchPermission(User user,
629       TableName table, byte[] family, byte[] qualifier,
630       Permission.Action action) {
631     PermissionCache<TablePermission> tablePerms = tableCache.get(table);
632     if (tablePerms != null) {
633       List<TablePermission> userPerms = tablePerms.getUser(user.getShortName());
634       if (userPerms != null) {
635         for (TablePermission p : userPerms) {
636           if (p.matchesFamilyQualifier(table, family, qualifier, action)) {
637             return true;
638           }
639         }
640       }
641 
642       String[] groups = user.getGroupNames();
643       if (groups != null) {
644         for (String group : groups) {
645           List<TablePermission> groupPerms = tablePerms.getGroup(group);
646           if (groupPerms != null) {
647             for (TablePermission p : groupPerms) {
648               if (p.matchesFamilyQualifier(table, family, qualifier, action)) {
649                 return true;
650               }
651             }
652           }
653         }
654       }
655     }
656     return false;
657   }
658 
659   public void removeNamespace(byte[] ns) {
660     nsCache.remove(Bytes.toString(ns));
661   }
662 
663   public void removeTable(TableName table) {
664     tableCache.remove(table);
665   }
666 
667   /**
668    * Overwrites the existing permission set for a given user for a table, and
669    * triggers an update for zookeeper synchronization.
670    * @param username
671    * @param table
672    * @param perms
673    */
674   public void setTableUserPermissions(String username, TableName table,
675       List<TablePermission> perms) {
676     PermissionCache<TablePermission> tablePerms = getTablePermissions(table);
677     tablePerms.replaceUser(username, perms);
678     writeTableToZooKeeper(table, tablePerms);
679   }
680 
681   /**
682    * Overwrites the existing permission set for a group and triggers an update
683    * for zookeeper synchronization.
684    * @param group
685    * @param table
686    * @param perms
687    */
688   public void setTableGroupPermissions(String group, TableName table,
689       List<TablePermission> perms) {
690     PermissionCache<TablePermission> tablePerms = getTablePermissions(table);
691     tablePerms.replaceGroup(group, perms);
692     writeTableToZooKeeper(table, tablePerms);
693   }
694 
695   /**
696    * Overwrites the existing permission set for a given user for a table, and
697    * triggers an update for zookeeper synchronization.
698    * @param username
699    * @param namespace
700    * @param perms
701    */
702   public void setNamespaceUserPermissions(String username, String namespace,
703       List<TablePermission> perms) {
704     PermissionCache<TablePermission> tablePerms = getNamespacePermissions(namespace);
705     tablePerms.replaceUser(username, perms);
706     writeNamespaceToZooKeeper(namespace, tablePerms);
707   }
708 
709   /**
710    * Overwrites the existing permission set for a group and triggers an update
711    * for zookeeper synchronization.
712    * @param group
713    * @param namespace
714    * @param perms
715    */
716   public void setNamespaceGroupPermissions(String group, String namespace,
717       List<TablePermission> perms) {
718     PermissionCache<TablePermission> tablePerms = getNamespacePermissions(namespace);
719     tablePerms.replaceGroup(group, perms);
720     writeNamespaceToZooKeeper(namespace, tablePerms);
721   }
722 
723   public void writeTableToZooKeeper(TableName table,
724       PermissionCache<TablePermission> tablePerms) {
725     byte[] serialized = new byte[0];
726     if (tablePerms != null) {
727       serialized = AccessControlLists.writePermissionsAsBytes(tablePerms.getAllPermissions(), conf);
728     }
729     zkperms.writeToZookeeper(table.getName(), serialized);
730   }
731 
732   public void writeNamespaceToZooKeeper(String namespace,
733       PermissionCache<TablePermission> tablePerms) {
734     byte[] serialized = new byte[0];
735     if (tablePerms != null) {
736       serialized = AccessControlLists.writePermissionsAsBytes(tablePerms.getAllPermissions(), conf);
737     }
738     zkperms.writeToZookeeper(Bytes.toBytes(AccessControlLists.toNamespaceEntry(namespace)),
739         serialized);
740   }
741 
742   public long getMTime() {
743     return mtime;
744   }
745 
746   private static Map<ZooKeeperWatcher,TableAuthManager> managerMap =
747     new HashMap<ZooKeeperWatcher,TableAuthManager>();
748 
749   private static Map<TableAuthManager, Integer> refCount = new HashMap<TableAuthManager, Integer>();
750 
751   /** Returns a TableAuthManager from the cache. If not cached, constructs a new one. Returned
752    * instance should be released back by calling {@link #release(TableAuthManager)}. */
753   public synchronized static TableAuthManager getOrCreate(
754       ZooKeeperWatcher watcher, Configuration conf) throws IOException {
755     TableAuthManager instance = managerMap.get(watcher);
756     if (instance == null) {
757       instance = new TableAuthManager(watcher, conf);
758       managerMap.put(watcher, instance);
759     }
760     int ref = refCount.get(instance) == null ? 0 : refCount.get(instance).intValue();
761     refCount.put(instance, ref + 1);
762     return instance;
763   }
764 
765   @VisibleForTesting
766   static int getTotalRefCount() {
767     int total = 0;
768     for (int count : refCount.values()) {
769       total += count;
770     }
771     return total;
772   }
773 
774   /**
775    * Releases the resources for the given TableAuthManager if the reference count is down to 0.
776    * @param instance TableAuthManager to be released
777    */
778   public synchronized static void release(TableAuthManager instance) {
779     if (refCount.get(instance) == null || refCount.get(instance) < 1) {
780       String msg = "Something wrong with the TableAuthManager reference counting: " + instance
781           + " whose count is " + refCount.get(instance);
782       LOG.fatal(msg);
783       instance.close();
784       managerMap.remove(instance.getZKPermissionWatcher().getWatcher());
785       instance.getZKPermissionWatcher().getWatcher().abort(msg, null);
786     } else {
787       int ref = refCount.get(instance);
788       refCount.put(instance, ref-1);
789       if (ref-1 == 0) {
790         instance.close();
791         managerMap.remove(instance.getZKPermissionWatcher().getWatcher());
792         refCount.remove(instance);
793       }
794     }
795   }
796 }