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
045  private final ConcurrentMap<String, NamespaceTableAndRegionInfo> nsStateCache;
046  private final MasterServices master;
047  private volatile boolean initialized = false;
048
049  public NamespaceStateManager(MasterServices masterServices) {
050    nsStateCache = new ConcurrentHashMap<>();
051    master = masterServices;
052  }
053
054  /**
055   * Starts the NamespaceStateManager. The boot strap of cache is done in the post master start hook
056   * of the NamespaceAuditor class.
057   * @throws IOException Signals that an I/O exception has occurred.
058   */
059  public void start() throws IOException {
060    LOG.info("Namespace State Manager started.");
061    initialize();
062  }
063
064  /**
065   * Gets an instance of NamespaceTableAndRegionInfo associated with namespace.
066   * @param name The name of the namespace
067   * @return An instance of NamespaceTableAndRegionInfo.
068   */
069  public NamespaceTableAndRegionInfo getState(String name) {
070    return nsStateCache.get(name);
071  }
072
073  /**
074   * Check if adding a region violates namespace quota, if not update namespace cache.
075   * @return true, if region can be added to table.
076   * @throws IOException Signals that an I/O exception has occurred.
077   */
078  synchronized boolean checkAndUpdateNamespaceRegionCount(TableName name, byte[] regionName,
079    int incr) throws IOException {
080    if (name.isSystemTable()) {
081      return true;
082    }
083    String namespace = name.getNamespaceAsString();
084    NamespaceDescriptor nspdesc = getNamespaceDescriptor(namespace);
085    if (nspdesc != null) {
086      NamespaceTableAndRegionInfo currentStatus;
087      currentStatus = getState(namespace);
088      int regionCount = currentStatus.getRegionCount();
089      long maxRegionCount = TableNamespaceManager.getMaxRegions(nspdesc);
090      if (incr > 0 && regionCount >= maxRegionCount) {
091        LOG.warn(
092          "The region {} cannot be created. The region count  will exceed quota on the namespace. "
093            + "This may be transient, please retry later if there are any ongoing split"
094            + " operations in the namespace.",
095          Bytes.toStringBinary(regionName));
096        return false;
097      }
098      NamespaceTableAndRegionInfo nsInfo = nsStateCache.get(namespace);
099      if (nsInfo != null) {
100        nsInfo.incRegionCountForTable(name, incr);
101      } else {
102        LOG.warn("Namespace state found null for namespace : {}", namespace);
103      }
104    }
105    return true;
106  }
107
108  /**
109   * Check and update region count for an existing table. To handle scenarios like restore snapshot
110   * @param name name of the table for region count needs to be checked and updated
111   * @param incr count of regions
112   * @throws QuotaExceededException if quota exceeds for the number of regions allowed in a
113   *                                namespace
114   * @throws IOException            Signals that an I/O exception has occurred.
115   */
116  synchronized void checkAndUpdateNamespaceRegionCount(TableName name, int incr)
117    throws IOException {
118    if (name.isSystemTable()) {
119      return;
120    }
121    String namespace = name.getNamespaceAsString();
122    NamespaceDescriptor nspdesc = getNamespaceDescriptor(namespace);
123    if (nspdesc != null) {
124      NamespaceTableAndRegionInfo currentStatus = getState(namespace);
125      int regionCountOfTable = currentStatus.getRegionCountOfTable(name);
126      if (
127        (currentStatus.getRegionCount() - regionCountOfTable + incr)
128            > TableNamespaceManager.getMaxRegions(nspdesc)
129      ) {
130        throw new QuotaExceededException("The table " + name.getNameAsString()
131          + " region count cannot be updated as it would exceed maximum number "
132          + "of regions allowed in the namespace.  The total number of regions permitted is "
133          + TableNamespaceManager.getMaxRegions(nspdesc));
134      }
135      currentStatus.removeTable(name);
136      currentStatus.addTable(name, incr);
137    }
138  }
139
140  private NamespaceDescriptor getNamespaceDescriptor(String namespaceAsString) {
141    try {
142      return this.master.getClusterSchema().getNamespace(namespaceAsString);
143    } catch (IOException e) {
144      LOG.error("Error while fetching namespace descriptor for namespace : {}", namespaceAsString);
145      return null;
146    }
147  }
148
149  synchronized void checkAndUpdateNamespaceTableCount(TableName table, int numRegions)
150    throws IOException {
151    if (table.isSystemTable()) {
152      return;
153    }
154    String namespace = table.getNamespaceAsString();
155    NamespaceDescriptor nspdesc = getNamespaceDescriptor(namespace);
156    if (nspdesc != null) {
157      NamespaceTableAndRegionInfo currentStatus;
158      currentStatus = getState(nspdesc.getName());
159      if ((currentStatus.getTables().size()) >= TableNamespaceManager.getMaxTables(nspdesc)) {
160        throw new QuotaExceededException("The table " + table.getNameAsString()
161          + " cannot be created as it would exceed maximum number of tables allowed "
162          + " in the namespace.  The total number of tables permitted is "
163          + TableNamespaceManager.getMaxTables(nspdesc));
164      }
165      if (
166        (currentStatus.getRegionCount() + numRegions) > TableNamespaceManager.getMaxRegions(nspdesc)
167      ) {
168        throw new QuotaExceededException(
169          "The table " + table.getNameAsString() + " is not allowed to have " + numRegions
170            + " regions. The total number of regions permitted is only "
171            + TableNamespaceManager.getMaxRegions(nspdesc) + ", while current region count is "
172            + currentStatus.getRegionCount()
173            + ". This may be transient, please retry later if there are any"
174            + " ongoing split operations in the namespace.");
175      }
176    } else {
177      throw new IOException(
178        "Namespace Descriptor found null for " + namespace + " This is unexpected.");
179    }
180    addTable(table, numRegions);
181  }
182
183  NamespaceTableAndRegionInfo addNamespace(String namespace) {
184    if (!nsStateCache.containsKey(namespace)) {
185      NamespaceTableAndRegionInfo a1 = new NamespaceTableAndRegionInfo(namespace);
186      nsStateCache.put(namespace, a1);
187    }
188    return nsStateCache.get(namespace);
189  }
190
191  /**
192   * Delete the namespace state.
193   * @param namespace the name of the namespace to delete
194   */
195  void deleteNamespace(String namespace) {
196    this.nsStateCache.remove(namespace);
197  }
198
199  private void addTable(TableName tableName, int regionCount) throws IOException {
200    assert !tableName.isSystemTable() : "Tracking of system tables is not supported";
201    NamespaceTableAndRegionInfo info = nsStateCache.get(tableName.getNamespaceAsString());
202    if (info != null) {
203      info.addTable(tableName, regionCount);
204    } else {
205      throw new IOException("Bad state : Namespace quota information not found for namespace : "
206        + tableName.getNamespaceAsString());
207    }
208  }
209
210  synchronized void removeTable(TableName tableName) {
211    if (tableName.isSystemTable()) {
212      return;
213    }
214    NamespaceTableAndRegionInfo info = nsStateCache.get(tableName.getNamespaceAsString());
215    if (info != null) {
216      info.removeTable(tableName);
217    }
218  }
219
220  /**
221   * Initialize namespace state cache by scanning meta table.
222   */
223  private void initialize() throws IOException {
224    List<NamespaceDescriptor> namespaces = this.master.getClusterSchema().getNamespaces();
225    for (NamespaceDescriptor namespace : namespaces) {
226      addNamespace(namespace.getName());
227      List<TableName> tables = this.master.listTableNamesByNamespace(namespace.getName());
228      for (TableName table : tables) {
229        if (table.isSystemTable()) {
230          continue;
231        }
232        List<RegionInfo> regions =
233          MetaTableAccessor.getTableRegions(this.master.getConnection(), table, true);
234        addTable(table, regions.size());
235      }
236    }
237    LOG.info("Finished updating state of {} namespaces.", nsStateCache.size());
238    initialized = true;
239  }
240
241  boolean isInitialized() {
242    return initialized;
243  }
244
245  public synchronized void removeRegionFromTable(RegionInfo hri) throws IOException {
246    String namespace = hri.getTable().getNamespaceAsString();
247    NamespaceTableAndRegionInfo nsInfo = nsStateCache.get(namespace);
248    if (nsInfo != null) {
249      nsInfo.decrementRegionCountForTable(hri.getTable(), 1);
250    } else {
251      throw new IOException("Namespace state found null for namespace : " + namespace);
252    }
253  }
254}