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 final 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    *
189    * @param namespace the name of the namespace to delete
190    */
191   void deleteNamespace(String namespace) {
192     this.nsStateCache.remove(namespace);
193   }
194 
195   private void addTable(TableName tableName, int regionCount) throws IOException {
196     NamespaceTableAndRegionInfo info = nsStateCache.get(tableName.getNamespaceAsString());
197     if (info != null) {
198       info.addTable(tableName, regionCount);
199     } else {
200       throw new IOException("Bad state : Namespace quota information not found for namespace : "
201           + tableName.getNamespaceAsString());
202     }
203   }
204 
205   synchronized void removeTable(TableName tableName) {
206     NamespaceTableAndRegionInfo info = nsStateCache.get(tableName.getNamespaceAsString());
207     if (info != null) {
208       info.removeTable(tableName);
209     }
210   }
211 
212   /**
213    * Initialize namespace state cache by scanning meta table.
214    */
215   private void initialize() throws IOException {
216     List<NamespaceDescriptor> namespaces = this.master.listNamespaceDescriptors();
217     for (NamespaceDescriptor namespace : namespaces) {
218       addNamespace(namespace.getName());
219       List<TableName> tables = this.master.listTableNamesByNamespace(namespace.getName());
220       for (TableName table : tables) {
221         if (table.isSystemTable()) {
222           continue;
223         }
224         int regionCount = 0;
225         Map<HRegionInfo, ServerName> regions =
226             MetaScanner.allTableRegions(this.master.getConnection(), table);
227         for (HRegionInfo info : regions.keySet()) {
228           if (!info.isSplit()) {
229             regionCount++;
230           }
231         }
232         addTable(table, regionCount);
233       }
234     }
235     LOG.info("Finished updating state of " + nsStateCache.size() + " namespaces. ");
236     initialized = true;
237   }
238 
239   boolean isInitialized() {
240     return initialized;
241   }
242 
243   public synchronized void removeRegionFromTable(HRegionInfo hri) throws IOException {
244     String namespace = hri.getTable().getNamespaceAsString();
245     NamespaceTableAndRegionInfo nsInfo = nsStateCache.get(namespace);
246     if (nsInfo != null) {
247       nsInfo.decrementRegionCountForTable(hri.getTable(), 1);
248     } else {
249       throw new IOException("Namespace state found null for namespace : " + namespace);
250     }
251   }
252 
253   @Override
254   public void nodeCreated(String path) {
255     checkSplittingOrMergingNode(path);
256   }
257 
258   @Override
259   public void nodeChildrenChanged(String path) {
260     checkSplittingOrMergingNode(path);
261   }
262 
263   private void checkSplittingOrMergingNode(String path) {
264     String msg = "Error reading data from zookeeper, ";
265     try {
266       if (path.startsWith(watcher.assignmentZNode)) {
267         List<String> children =
268             ZKUtil.listChildrenAndWatchForNewChildren(watcher, watcher.assignmentZNode);
269         if (children != null) {
270           for (String child : children) {
271             Stat stat = new Stat();
272             byte[] data =
273                 ZKAssign.getDataAndWatch(watcher, ZKUtil.joinZNode(watcher.assignmentZNode, child),
274                   stat);
275             if (data != null) {
276               RegionTransition rt = RegionTransition.parseFrom(data);
277               if (rt.getEventType().equals(EventType.RS_ZK_REQUEST_REGION_SPLIT)) {
278                 TableName table = HRegionInfo.getTable(rt.getRegionName());
279                 if (!checkAndUpdateNamespaceRegionCount(table, rt.getRegionName(), 1)) {
280                   ZKUtil.deleteNode(watcher, ZKUtil.joinZNode(watcher.assignmentZNode, child));
281                 }
282               } else if (rt.getEventType().equals(EventType.RS_ZK_REQUEST_REGION_MERGE)) {
283                 TableName table = HRegionInfo.getTable(rt.getRegionName());
284                 checkAndUpdateNamespaceRegionCount(table, rt.getRegionName(), -1);
285               }
286             }
287           }
288         }
289       }
290     } catch (KeeperException ke) {
291       LOG.error(msg, ke);
292       watcher.abort(msg, ke);
293     } catch (DeserializationException e) {
294       LOG.error(msg, e);
295       watcher.abort(msg, e);
296     } catch (IOException e) {
297       LOG.error(msg, e);
298       watcher.abort(msg, e);
299     }
300   }
301 }