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}