View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional information regarding
4    * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
7    * law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
8    * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
9    * for the specific language governing permissions and limitations under the License.
10   */
11  package org.apache.hadoop.hbase.namespace;
12  
13  import java.io.IOException;
14  import java.util.List;
15  import java.util.Map;
16  import java.util.concurrent.ConcurrentHashMap;
17  import java.util.concurrent.ConcurrentMap;
18  
19  import org.apache.commons.logging.Log;
20  import org.apache.commons.logging.LogFactory;
21  import org.apache.hadoop.hbase.HRegionInfo;
22  import org.apache.hadoop.hbase.NamespaceDescriptor;
23  import org.apache.hadoop.hbase.RegionTransition;
24  import org.apache.hadoop.hbase.ServerName;
25  import org.apache.hadoop.hbase.TableName;
26  import org.apache.hadoop.hbase.classification.InterfaceAudience;
27  import org.apache.hadoop.hbase.client.MetaScanner;
28  import org.apache.hadoop.hbase.exceptions.DeserializationException;
29  import org.apache.hadoop.hbase.executor.EventType;
30  import org.apache.hadoop.hbase.master.MasterServices;
31  import org.apache.hadoop.hbase.master.TableNamespaceManager;
32  import org.apache.hadoop.hbase.quotas.QuotaExceededException;
33  import org.apache.hadoop.hbase.util.Bytes;
34  import org.apache.hadoop.hbase.zookeeper.ZKAssign;
35  import org.apache.hadoop.hbase.zookeeper.ZKUtil;
36  import org.apache.hadoop.hbase.zookeeper.ZooKeeperListener;
37  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
38  import org.apache.zookeeper.KeeperException;
39  import org.apache.zookeeper.data.Stat;
40  
41  /**
42   * NamespaceStateManager manages state (in terms of quota) of all the namespaces. It contains a
43   * cache which is updated based on the hooks in the NamespaceAuditor class.
44   */
45  @InterfaceAudience.Private
46  class NamespaceStateManager extends ZooKeeperListener {
47  
48    private static Log LOG = LogFactory.getLog(NamespaceStateManager.class);
49    private ConcurrentMap<String, NamespaceTableAndRegionInfo> nsStateCache;
50    private MasterServices master;
51    private volatile boolean initialized = false;
52  
53    public NamespaceStateManager(MasterServices masterServices, ZooKeeperWatcher zkw) {
54      super(zkw);
55      nsStateCache = new ConcurrentHashMap<String, NamespaceTableAndRegionInfo>();
56      master = masterServices;
57    }
58  
59    /**
60     * Starts the NamespaceStateManager. The boot strap of cache is done in the post master start hook
61     * of the NamespaceAuditor class.
62     * @throws IOException Signals that an I/O exception has occurred.
63     */
64    public void start() throws IOException {
65      LOG.info("Namespace State Manager started.");
66      initialize();
67      watcher.registerListenerFirst(this);
68    }
69  
70    /**
71     * Gets an instance of NamespaceTableAndRegionInfo associated with namespace.
72     * @param The name of the namespace
73     * @return An instance of NamespaceTableAndRegionInfo.
74     */
75    public NamespaceTableAndRegionInfo getState(String name) {
76      return nsStateCache.get(name);
77    }
78  
79    /**
80     * Check if adding a region violates namespace quota, if not update namespace cache.
81     * @param TableName
82     * @param regionName
83     * @param incr
84     * @return true, if region can be added to table.
85     * @throws IOException Signals that an I/O exception has occurred.
86     */
87    synchronized boolean checkAndUpdateNamespaceRegionCount(TableName name, byte[] regionName,
88        int incr) throws IOException {
89      String namespace = name.getNamespaceAsString();
90      NamespaceDescriptor nspdesc = getNamespaceDescriptor(namespace);
91      if (nspdesc != null) {
92        NamespaceTableAndRegionInfo currentStatus;
93        currentStatus = getState(namespace);
94        if (incr > 0
95            && currentStatus.getRegionCount() >= TableNamespaceManager.getMaxRegions(nspdesc)) {
96          LOG.warn("The region " + Bytes.toStringBinary(regionName)
97              + " cannot be created. The region count  will exceed quota on the namespace. "
98              + "This may be transient, please retry later if there are any ongoing split"
99              + " operations in the namespace.");
100         return false;
101       }
102       NamespaceTableAndRegionInfo nsInfo = nsStateCache.get(namespace);
103       if (nsInfo != null) {
104         nsInfo.incRegionCountForTable(name, incr);
105       } else {
106         LOG.warn("Namespace state found null for namespace : " + namespace);
107       }
108     }
109     return true;
110   }
111   
112   /**
113    * Check and update region count for an existing table. To handle scenarios like restore snapshot
114    * @param TableName name of the table for region count needs to be checked and updated
115    * @param incr count of regions
116    * @throws QuotaExceededException if quota exceeds for the number of regions allowed in a
117    *           namespace
118    * @throws IOException Signals that an I/O exception has occurred.
119    */
120   synchronized void checkAndUpdateNamespaceRegionCount(TableName name, int incr) 
121       throws IOException {
122     String namespace = name.getNamespaceAsString();
123     NamespaceDescriptor nspdesc = getNamespaceDescriptor(namespace);
124     if (nspdesc != null) {
125       NamespaceTableAndRegionInfo currentStatus = getState(namespace);
126       int regionCountOfTable = currentStatus.getRegionCountOfTable(name);
127       if ((currentStatus.getRegionCount() - regionCountOfTable + incr) > TableNamespaceManager
128           .getMaxRegions(nspdesc)) {
129         throw new QuotaExceededException("The table " + name.getNameAsString()
130             + " region count cannot be updated as it would exceed maximum number "
131             + "of regions allowed in the namespace.  The total number of regions permitted is "
132             + TableNamespaceManager.getMaxRegions(nspdesc));
133       }
134       currentStatus.removeTable(name);
135       currentStatus.addTable(name, incr);
136     }
137   }
138 
139   private NamespaceDescriptor getNamespaceDescriptor(String namespaceAsString) {
140     try {
141       return this.master.getNamespaceDescriptor(namespaceAsString);
142     } catch (IOException e) {
143       LOG.error("Error while fetching namespace descriptor for namespace : " + namespaceAsString);
144       return null;
145     }
146   }
147 
148   synchronized void checkAndUpdateNamespaceTableCount(TableName table, int numRegions)
149       throws IOException {
150     String namespace = table.getNamespaceAsString();
151     NamespaceDescriptor nspdesc = getNamespaceDescriptor(namespace);
152     if (nspdesc != null) {
153       NamespaceTableAndRegionInfo currentStatus;
154       currentStatus = getState(nspdesc.getName());
155       if ((currentStatus.getTables().size()) >= TableNamespaceManager.getMaxTables(nspdesc)) {
156         throw new QuotaExceededException("The table " + table.getNameAsString()
157             + " cannot be created as it would exceed maximum number of tables allowed "
158             + " in the namespace.  The total number of tables permitted is "
159             + TableNamespaceManager.getMaxTables(nspdesc));
160       }
161       if ((currentStatus.getRegionCount() + numRegions) > TableNamespaceManager
162           .getMaxRegions(nspdesc)) {
163         throw new QuotaExceededException("The table " + table.getNameAsString()
164             + " is not allowed to have " + numRegions
165             + " regions. The total number of regions permitted is only "
166             + TableNamespaceManager.getMaxRegions(nspdesc) + ", while current region count is "
167             + currentStatus.getRegionCount()
168             + ". This may be transient, please retry later if there are any"
169             + " ongoing split operations in the namespace.");
170       }
171     } else {
172       throw new IOException("Namespace Descriptor found null for " + namespace
173           + " This is unexpected.");
174     }
175     addTable(table, numRegions);
176   }
177 
178   NamespaceTableAndRegionInfo addNamespace(String namespace) {
179     if (!nsStateCache.containsKey(namespace)) {
180       NamespaceTableAndRegionInfo a1 = new NamespaceTableAndRegionInfo(namespace);
181       nsStateCache.put(namespace, a1);
182     }
183     return nsStateCache.get(namespace);
184   }
185 
186   /**
187    * Delete the namespace state.
188    * @param An instance of NamespaceTableAndRegionInfo
189    */
190   void deleteNamespace(String namespace) {
191     this.nsStateCache.remove(namespace);
192   }
193 
194   private void addTable(TableName tableName, int regionCount) throws IOException {
195     NamespaceTableAndRegionInfo info = nsStateCache.get(tableName.getNamespaceAsString());
196     if (info != null) {
197       info.addTable(tableName, regionCount);
198     } else {
199       throw new IOException("Bad state : Namespace quota information not found for namespace : "
200           + tableName.getNamespaceAsString());
201     }
202   }
203 
204   synchronized void removeTable(TableName tableName) {
205     NamespaceTableAndRegionInfo info = nsStateCache.get(tableName.getNamespaceAsString());
206     if (info != null) {
207       info.removeTable(tableName);
208     }
209   }
210 
211   /**
212    * Initialize namespace state cache by scanning meta table.
213    */
214   private void initialize() throws IOException {
215     List<NamespaceDescriptor> namespaces = this.master.listNamespaceDescriptors();
216     for (NamespaceDescriptor namespace : namespaces) {
217       addNamespace(namespace.getName());
218       List<TableName> tables = this.master.listTableNamesByNamespace(namespace.getName());
219       for (TableName table : tables) {
220         if (table.isSystemTable()) {
221           continue;
222         }
223         int regionCount = 0;
224         Map<HRegionInfo, ServerName> regions =
225             MetaScanner.allTableRegions(this.master.getConnection(), table);
226         for (HRegionInfo info : regions.keySet()) {
227           if (!info.isSplit()) {
228             regionCount++;
229           }
230         }
231         addTable(table, regionCount);
232       }
233     }
234     LOG.info("Finished updating state of " + nsStateCache.size() + " namespaces. ");
235     initialized = true;
236   }
237 
238   boolean isInitialized() {
239     return initialized;
240   }
241 
242   public synchronized void removeRegionFromTable(HRegionInfo hri) throws IOException {
243     String namespace = hri.getTable().getNamespaceAsString();
244     NamespaceTableAndRegionInfo nsInfo = nsStateCache.get(namespace);
245     if (nsInfo != null) {
246       nsInfo.decrementRegionCountForTable(hri.getTable(), 1);
247     } else {
248       throw new IOException("Namespace state found null for namespace : " + namespace);
249     }
250   }
251 
252   @Override
253   public void nodeCreated(String path) {
254     checkSplittingOrMergingNode(path);
255   }
256 
257   @Override
258   public void nodeChildrenChanged(String path) {
259     checkSplittingOrMergingNode(path);
260   }
261 
262   private void checkSplittingOrMergingNode(String path) {
263     String msg = "Error reading data from zookeeper, ";
264     try {
265       if (path.startsWith(watcher.assignmentZNode)) {
266         List<String> children =
267             ZKUtil.listChildrenAndWatchForNewChildren(watcher, watcher.assignmentZNode);
268         if (children != null) {
269           for (String child : children) {
270             Stat stat = new Stat();
271             byte[] data =
272                 ZKAssign.getDataAndWatch(watcher, ZKUtil.joinZNode(watcher.assignmentZNode, child),
273                   stat);
274             if (data != null) {
275               RegionTransition rt = RegionTransition.parseFrom(data);
276               if (rt.getEventType().equals(EventType.RS_ZK_REQUEST_REGION_SPLIT)) {
277                 TableName table = HRegionInfo.getTable(rt.getRegionName());
278                 if (!checkAndUpdateNamespaceRegionCount(table, rt.getRegionName(), 1)) {
279                   ZKUtil.deleteNode(watcher, ZKUtil.joinZNode(watcher.assignmentZNode, child));
280                 }
281               } else if (rt.getEventType().equals(EventType.RS_ZK_REQUEST_REGION_MERGE)) {
282                 TableName table = HRegionInfo.getTable(rt.getRegionName());
283                 checkAndUpdateNamespaceRegionCount(table, rt.getRegionName(), -1);
284               }
285             }
286           }
287         }
288       }
289     } catch (KeeperException ke) {
290       LOG.error(msg, ke);
291       watcher.abort(msg, ke);
292     } catch (DeserializationException e) {
293       LOG.error(msg, e);
294       watcher.abort(msg, e);
295     } catch (IOException e) {
296       LOG.error(msg, e);
297       watcher.abort(msg, e);
298     }
299   }
300 }