001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.namespace;
019
020import java.io.IOException;
021import java.util.List;
022import java.util.concurrent.ConcurrentHashMap;
023import java.util.concurrent.ConcurrentMap;
024import org.apache.hadoop.hbase.MetaTableAccessor;
025import org.apache.hadoop.hbase.NamespaceDescriptor;
026import org.apache.hadoop.hbase.TableName;
027import org.apache.hadoop.hbase.client.RegionInfo;
028import org.apache.hadoop.hbase.master.MasterServices;
029import org.apache.hadoop.hbase.master.TableNamespaceManager;
030import org.apache.hadoop.hbase.quotas.QuotaExceededException;
031import org.apache.hadoop.hbase.util.Bytes;
032import org.apache.yetus.audience.InterfaceAudience;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036/**
037 * NamespaceStateManager manages state (in terms of quota) of all the namespaces. It contains a
038 * cache which is updated based on the hooks in the NamespaceAuditor class.
039 */
040@InterfaceAudience.Private
041class NamespaceStateManager {
042
043  private static final Logger LOG = LoggerFactory.getLogger(NamespaceStateManager.class);
044  private ConcurrentMap<String, NamespaceTableAndRegionInfo> nsStateCache;
045  private MasterServices master;
046  private volatile boolean initialized = false;
047
048  public NamespaceStateManager(MasterServices masterServices) {
049    nsStateCache = new ConcurrentHashMap<>();
050    master = masterServices;
051  }
052
053  /**
054   * Starts the NamespaceStateManager. The boot strap of cache is done in the post master start hook
055   * of the NamespaceAuditor class.
056   * @throws IOException Signals that an I/O exception has occurred.
057   */
058  public void start() throws IOException {
059    LOG.info("Namespace State Manager started.");
060    initialize();
061  }
062
063  /**
064   * Gets an instance of NamespaceTableAndRegionInfo associated with namespace.
065   * @param name The name of the namespace
066   * @return An instance of NamespaceTableAndRegionInfo.
067   */
068  public NamespaceTableAndRegionInfo getState(String name) {
069    return nsStateCache.get(name);
070  }
071
072  /**
073   * Check if adding a region violates namespace quota, if not update namespace cache.
074   * @return true, if region can be added to table.
075   * @throws IOException Signals that an I/O exception has occurred.
076   */
077  synchronized boolean checkAndUpdateNamespaceRegionCount(TableName name, byte[] regionName,
078    int incr) throws IOException {
079    String namespace = name.getNamespaceAsString();
080    NamespaceDescriptor nspdesc = getNamespaceDescriptor(namespace);
081    if (nspdesc != null) {
082      NamespaceTableAndRegionInfo currentStatus;
083      currentStatus = getState(namespace);
084      int regionCount = currentStatus.getRegionCount();
085      long maxRegionCount = TableNamespaceManager.getMaxRegions(nspdesc);
086      if (incr > 0 && regionCount >= maxRegionCount) {
087        LOG.warn("The region " + Bytes.toStringBinary(regionName)
088          + " cannot be created. The region count  will exceed quota on the namespace. "
089          + "This may be transient, please retry later if there are any ongoing split"
090          + " operations in the namespace.");
091        return false;
092      }
093      NamespaceTableAndRegionInfo nsInfo = nsStateCache.get(namespace);
094      if (nsInfo != null) {
095        nsInfo.incRegionCountForTable(name, incr);
096      } else {
097        LOG.warn("Namespace state found null for namespace : " + namespace);
098      }
099    }
100    return true;
101  }
102
103  /**
104   * Check and update region count for an existing table. To handle scenarios like restore snapshot
105   * @param name name of the table for region count needs to be checked and updated
106   * @param incr count of regions
107   * @throws QuotaExceededException if quota exceeds for the number of regions allowed in a
108   *                                namespace
109   * @throws IOException            Signals that an I/O exception has occurred.
110   */
111  synchronized void checkAndUpdateNamespaceRegionCount(TableName name, int incr)
112    throws IOException {
113    String namespace = name.getNamespaceAsString();
114    NamespaceDescriptor nspdesc = getNamespaceDescriptor(namespace);
115    if (nspdesc != null) {
116      NamespaceTableAndRegionInfo currentStatus = getState(namespace);
117      int regionCountOfTable = currentStatus.getRegionCountOfTable(name);
118      if (
119        (currentStatus.getRegionCount() - regionCountOfTable + incr)
120            > TableNamespaceManager.getMaxRegions(nspdesc)
121      ) {
122        throw new QuotaExceededException("The table " + name.getNameAsString()
123          + " region count cannot be updated as it would exceed maximum number "
124          + "of regions allowed in the namespace.  The total number of regions permitted is "
125          + TableNamespaceManager.getMaxRegions(nspdesc));
126      }
127      currentStatus.removeTable(name);
128      currentStatus.addTable(name, incr);
129    }
130  }
131
132  private NamespaceDescriptor getNamespaceDescriptor(String namespaceAsString) {
133    try {
134      return this.master.getClusterSchema().getNamespace(namespaceAsString);
135    } catch (IOException e) {
136      LOG.error("Error while fetching namespace descriptor for namespace : " + namespaceAsString);
137      return null;
138    }
139  }
140
141  synchronized void checkAndUpdateNamespaceTableCount(TableName table, int numRegions)
142    throws IOException {
143    String namespace = table.getNamespaceAsString();
144    NamespaceDescriptor nspdesc = getNamespaceDescriptor(namespace);
145    if (nspdesc != null) {
146      NamespaceTableAndRegionInfo currentStatus;
147      currentStatus = getState(nspdesc.getName());
148      if ((currentStatus.getTables().size()) >= TableNamespaceManager.getMaxTables(nspdesc)) {
149        throw new QuotaExceededException("The table " + table.getNameAsString()
150          + " cannot be created as it would exceed maximum number of tables allowed "
151          + " in the namespace.  The total number of tables permitted is "
152          + TableNamespaceManager.getMaxTables(nspdesc));
153      }
154      if (
155        (currentStatus.getRegionCount() + numRegions) > TableNamespaceManager.getMaxRegions(nspdesc)
156      ) {
157        throw new QuotaExceededException(
158          "The table " + table.getNameAsString() + " is not allowed to have " + numRegions
159            + " regions. The total number of regions permitted is only "
160            + TableNamespaceManager.getMaxRegions(nspdesc) + ", while current region count is "
161            + currentStatus.getRegionCount()
162            + ". This may be transient, please retry later if there are any"
163            + " ongoing split operations in the namespace.");
164      }
165    } else {
166      throw new IOException(
167        "Namespace Descriptor found null for " + namespace + " This is unexpected.");
168    }
169    addTable(table, numRegions);
170  }
171
172  NamespaceTableAndRegionInfo addNamespace(String namespace) {
173    if (!nsStateCache.containsKey(namespace)) {
174      NamespaceTableAndRegionInfo a1 = new NamespaceTableAndRegionInfo(namespace);
175      nsStateCache.put(namespace, a1);
176    }
177    return nsStateCache.get(namespace);
178  }
179
180  /**
181   * Delete the namespace state.
182   * @param namespace the name of the namespace to delete
183   */
184  void deleteNamespace(String namespace) {
185    this.nsStateCache.remove(namespace);
186  }
187
188  private void addTable(TableName tableName, int regionCount) throws IOException {
189    NamespaceTableAndRegionInfo info = nsStateCache.get(tableName.getNamespaceAsString());
190    if (info != null) {
191      info.addTable(tableName, regionCount);
192    } else {
193      throw new IOException("Bad state : Namespace quota information not found for namespace : "
194        + tableName.getNamespaceAsString());
195    }
196  }
197
198  synchronized void removeTable(TableName tableName) {
199    NamespaceTableAndRegionInfo info = nsStateCache.get(tableName.getNamespaceAsString());
200    if (info != null) {
201      info.removeTable(tableName);
202    }
203  }
204
205  /**
206   * Initialize namespace state cache by scanning meta table.
207   */
208  private void initialize() throws IOException {
209    List<NamespaceDescriptor> namespaces = this.master.getClusterSchema().getNamespaces();
210    for (NamespaceDescriptor namespace : namespaces) {
211      addNamespace(namespace.getName());
212      List<TableName> tables = this.master.listTableNamesByNamespace(namespace.getName());
213      for (TableName table : tables) {
214        if (table.isSystemTable()) {
215          continue;
216        }
217        List<RegionInfo> regions =
218          MetaTableAccessor.getTableRegions(this.master.getConnection(), table, true);
219        addTable(table, regions.size());
220      }
221    }
222    LOG.info("Finished updating state of " + nsStateCache.size() + " namespaces. ");
223    initialized = true;
224  }
225
226  boolean isInitialized() {
227    return initialized;
228  }
229
230  public synchronized void removeRegionFromTable(RegionInfo hri) throws IOException {
231    String namespace = hri.getTable().getNamespaceAsString();
232    NamespaceTableAndRegionInfo nsInfo = nsStateCache.get(namespace);
233    if (nsInfo != null) {
234      nsInfo.decrementRegionCountForTable(hri.getTable(), 1);
235    } else {
236      throw new IOException("Namespace state found null for namespace : " + namespace);
237    }
238  }
239}