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.master; 019 020import java.io.IOException; 021import java.io.InterruptedIOException; 022import java.util.NavigableSet; 023import org.apache.commons.lang3.StringUtils; 024import org.apache.hadoop.conf.Configuration; 025import org.apache.hadoop.hbase.Cell; 026import org.apache.hadoop.hbase.CellBuilderFactory; 027import org.apache.hadoop.hbase.CellBuilderType; 028import org.apache.hadoop.hbase.CellUtil; 029import org.apache.hadoop.hbase.DoNotRetryIOException; 030import org.apache.hadoop.hbase.HTableDescriptor; 031import org.apache.hadoop.hbase.NamespaceDescriptor; 032import org.apache.hadoop.hbase.Stoppable; 033import org.apache.hadoop.hbase.TableName; 034import org.apache.hadoop.hbase.ZKNamespaceManager; 035import org.apache.hadoop.hbase.client.Delete; 036import org.apache.hadoop.hbase.client.Get; 037import org.apache.hadoop.hbase.client.Put; 038import org.apache.hadoop.hbase.client.Result; 039import org.apache.hadoop.hbase.client.ResultScanner; 040import org.apache.hadoop.hbase.client.Table; 041import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 042import org.apache.hadoop.hbase.client.TableState; 043import org.apache.hadoop.hbase.constraint.ConstraintException; 044import org.apache.hadoop.hbase.exceptions.TimeoutIOException; 045import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv; 046import org.apache.hadoop.hbase.master.procedure.ProcedurePrepareLatch; 047import org.apache.hadoop.hbase.procedure2.ProcedureExecutor; 048import org.apache.hadoop.hbase.util.Bytes; 049import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 050import org.apache.hadoop.hbase.util.Threads; 051import org.apache.yetus.audience.InterfaceAudience; 052import org.slf4j.Logger; 053import org.slf4j.LoggerFactory; 054 055import org.apache.hbase.thirdparty.com.google.common.collect.Sets; 056 057import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 058import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; 059 060/** 061 * This is a helper class used internally to manage the namespace metadata that is stored in 062 * TableName.NAMESPACE_TABLE_NAME. It also mirrors updates to the ZK store by forwarding updates to 063 * {@link org.apache.hadoop.hbase.ZKNamespaceManager}. WARNING: Do not use. Go via the higher-level 064 * {@link ClusterSchema} API instead. This manager is likely to go aways anyways. 065 */ 066@InterfaceAudience.Private 067@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "IS2_INCONSISTENT_SYNC", 068 justification = "TODO: synchronize access on nsTable but it is done in tiers above and this " 069 + "class is going away/shrinking") 070public class TableNamespaceManager implements Stoppable { 071 private static final Logger LOG = LoggerFactory.getLogger(TableNamespaceManager.class); 072 private volatile boolean stopped = false; 073 074 private Configuration conf; 075 private MasterServices masterServices; 076 private Table nsTable = null; // FindBugs: IS2_INCONSISTENT_SYNC TODO: Access is not synchronized 077 private ZKNamespaceManager zkNamespaceManager; 078 private boolean initialized; 079 080 public static final String KEY_MAX_REGIONS = "hbase.namespace.quota.maxregions"; 081 public static final String KEY_MAX_TABLES = "hbase.namespace.quota.maxtables"; 082 static final String NS_INIT_TIMEOUT = "hbase.master.namespace.init.timeout"; 083 static final int DEFAULT_NS_INIT_TIMEOUT = 300000; 084 085 TableNamespaceManager(MasterServices masterServices) { 086 this.masterServices = masterServices; 087 this.conf = masterServices.getConfiguration(); 088 } 089 090 public void start() throws IOException { 091 if (!masterServices.getTableDescriptors().exists(TableName.NAMESPACE_TABLE_NAME)) { 092 LOG.info("Namespace table not found. Creating..."); 093 createNamespaceTable(masterServices); 094 } 095 096 try { 097 // Wait for the namespace table to be initialized. 098 long startTime = EnvironmentEdgeManager.currentTime(); 099 int timeout = conf.getInt(NS_INIT_TIMEOUT, DEFAULT_NS_INIT_TIMEOUT); 100 while (!isTableAvailableAndInitialized()) { 101 if (EnvironmentEdgeManager.currentTime() - startTime + 100 > timeout) { 102 // We can't do anything if ns is not online. 103 throw new IOException("Timedout " + timeout + "ms waiting for namespace table to " 104 + "be assigned and enabled: " + getTableState()); 105 } 106 Thread.sleep(100); 107 } 108 } catch (InterruptedException e) { 109 throw (InterruptedIOException) new InterruptedIOException().initCause(e); 110 } 111 } 112 113 private synchronized Table getNamespaceTable() throws IOException { 114 if (!isTableNamespaceManagerInitialized()) { 115 throw new IOException(this.getClass().getName() + " isn't ready to serve"); 116 } 117 return nsTable; 118 } 119 120 /* 121 * check whether a namespace has already existed. 122 */ 123 public boolean doesNamespaceExist(final String namespaceName) throws IOException { 124 if (nsTable == null) { 125 throw new IOException(this.getClass().getName() + " isn't ready to serve"); 126 } 127 return (get(nsTable, namespaceName) != null); 128 } 129 130 public synchronized NamespaceDescriptor get(String name) throws IOException { 131 if (!isTableNamespaceManagerInitialized()) { 132 return null; 133 } 134 return zkNamespaceManager.get(name); 135 } 136 137 private NamespaceDescriptor get(Table table, String name) throws IOException { 138 Result res = table.get(new Get(Bytes.toBytes(name))); 139 if (res.isEmpty()) { 140 return null; 141 } 142 byte[] val = 143 CellUtil.cloneValue(res.getColumnLatestCell(HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES, 144 HTableDescriptor.NAMESPACE_COL_DESC_BYTES)); 145 return ProtobufUtil.toNamespaceDescriptor(HBaseProtos.NamespaceDescriptor.parseFrom(val)); 146 } 147 148 public void insertIntoNSTable(final NamespaceDescriptor ns) throws IOException { 149 if (nsTable == null) { 150 throw new IOException(this.getClass().getName() + " isn't ready to serve"); 151 } 152 byte[] row = Bytes.toBytes(ns.getName()); 153 Put p = new Put(row, true); 154 p.add(CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY).setRow(row) 155 .setFamily(TableDescriptorBuilder.NAMESPACE_FAMILY_INFO_BYTES) 156 .setQualifier(TableDescriptorBuilder.NAMESPACE_COL_DESC_BYTES).setTimestamp(p.getTimestamp()) 157 .setType(Cell.Type.Put).setValue(ProtobufUtil.toProtoNamespaceDescriptor(ns).toByteArray()) 158 .build()); 159 nsTable.put(p); 160 } 161 162 public void updateZKNamespaceManager(final NamespaceDescriptor ns) throws IOException { 163 try { 164 zkNamespaceManager.update(ns); 165 } catch (IOException ex) { 166 String msg = "Failed to update namespace information in ZK."; 167 LOG.error(msg, ex); 168 throw new IOException(msg, ex); 169 } 170 } 171 172 public void removeFromNSTable(final String namespaceName) throws IOException { 173 if (nsTable == null) { 174 throw new IOException(this.getClass().getName() + " isn't ready to serve"); 175 } 176 Delete d = new Delete(Bytes.toBytes(namespaceName)); 177 nsTable.delete(d); 178 } 179 180 public void removeFromZKNamespaceManager(final String namespaceName) throws IOException { 181 zkNamespaceManager.remove(namespaceName); 182 } 183 184 public synchronized NavigableSet<NamespaceDescriptor> list() throws IOException { 185 NavigableSet<NamespaceDescriptor> ret = 186 Sets.newTreeSet(NamespaceDescriptor.NAMESPACE_DESCRIPTOR_COMPARATOR); 187 ResultScanner scanner = 188 getNamespaceTable().getScanner(HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES); 189 try { 190 for (Result r : scanner) { 191 byte[] val = 192 CellUtil.cloneValue(r.getColumnLatestCell(HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES, 193 HTableDescriptor.NAMESPACE_COL_DESC_BYTES)); 194 ret.add(ProtobufUtil.toNamespaceDescriptor(HBaseProtos.NamespaceDescriptor.parseFrom(val))); 195 } 196 } finally { 197 scanner.close(); 198 } 199 return ret; 200 } 201 202 private void createNamespaceTable(MasterServices masterServices) throws IOException { 203 masterServices.createSystemTable(HTableDescriptor.NAMESPACE_TABLEDESC); 204 } 205 206 @SuppressWarnings("deprecation") 207 private boolean isTableNamespaceManagerInitialized() throws IOException { 208 if (initialized) { 209 this.nsTable = this.masterServices.getConnection().getTable(TableName.NAMESPACE_TABLE_NAME); 210 return true; 211 } 212 return false; 213 } 214 215 /** 216 * Create Namespace in a blocking manner. Keeps trying until 217 * {@link ClusterSchema#HBASE_MASTER_CLUSTER_SCHEMA_OPERATION_TIMEOUT_KEY} expires. Note, 218 * by-passes notifying coprocessors and name checks. Use for system namespaces only. 219 */ 220 private void blockingCreateNamespace(final NamespaceDescriptor namespaceDescriptor) 221 throws IOException { 222 ClusterSchema clusterSchema = this.masterServices.getClusterSchema(); 223 long procId = clusterSchema.createNamespace(namespaceDescriptor, null, 224 ProcedurePrepareLatch.getNoopLatch()); 225 block(this.masterServices, procId); 226 } 227 228 /** 229 * An ugly utility to be removed when refactor TableNamespaceManager. n 230 */ 231 private static void block(final MasterServices services, final long procId) 232 throws TimeoutIOException { 233 int timeoutInMillis = services.getConfiguration().getInt( 234 ClusterSchema.HBASE_MASTER_CLUSTER_SCHEMA_OPERATION_TIMEOUT_KEY, 235 ClusterSchema.DEFAULT_HBASE_MASTER_CLUSTER_SCHEMA_OPERATION_TIMEOUT); 236 long deadlineTs = EnvironmentEdgeManager.currentTime() + timeoutInMillis; 237 ProcedureExecutor<MasterProcedureEnv> procedureExecutor = services.getMasterProcedureExecutor(); 238 while (EnvironmentEdgeManager.currentTime() < deadlineTs) { 239 if (procedureExecutor.isFinished(procId)) return; 240 // Sleep some 241 Threads.sleep(10); 242 } 243 throw new TimeoutIOException("Procedure pid=" + procId + " is still running"); 244 } 245 246 /** 247 * This method checks if the namespace table is assigned and then tries to create its Table 248 * reference. If it was already created before, it also makes sure that the connection isn't 249 * closed. 250 * @return true if the namespace table manager is ready to serve, false otherwise 251 */ 252 @SuppressWarnings("deprecation") 253 public synchronized boolean isTableAvailableAndInitialized() throws IOException { 254 // Did we already get a table? If so, still make sure it's available 255 if (isTableNamespaceManagerInitialized()) { 256 return true; 257 } 258 259 // Now check if the table is assigned, if not then fail fast 260 if (isTableAssigned() && isTableEnabled()) { 261 try { 262 boolean initGoodSofar = true; 263 nsTable = this.masterServices.getConnection().getTable(TableName.NAMESPACE_TABLE_NAME); 264 zkNamespaceManager = new ZKNamespaceManager(masterServices.getZooKeeper()); 265 zkNamespaceManager.start(); 266 267 if (get(nsTable, NamespaceDescriptor.DEFAULT_NAMESPACE.getName()) == null) { 268 blockingCreateNamespace(NamespaceDescriptor.DEFAULT_NAMESPACE); 269 } 270 if (get(nsTable, NamespaceDescriptor.SYSTEM_NAMESPACE.getName()) == null) { 271 blockingCreateNamespace(NamespaceDescriptor.SYSTEM_NAMESPACE); 272 } 273 274 if (!initGoodSofar) { 275 // some required namespace is created asynchronized. We should complete init later. 276 return false; 277 } 278 279 ResultScanner scanner = nsTable.getScanner(HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES); 280 try { 281 for (Result result : scanner) { 282 byte[] val = CellUtil 283 .cloneValue(result.getColumnLatestCell(HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES, 284 HTableDescriptor.NAMESPACE_COL_DESC_BYTES)); 285 NamespaceDescriptor ns = 286 ProtobufUtil.toNamespaceDescriptor(HBaseProtos.NamespaceDescriptor.parseFrom(val)); 287 zkNamespaceManager.update(ns); 288 } 289 } finally { 290 scanner.close(); 291 } 292 initialized = true; 293 return true; 294 } catch (IOException ie) { 295 LOG.warn("Caught exception in initializing namespace table manager", ie); 296 if (nsTable != null) { 297 nsTable.close(); 298 } 299 throw ie; 300 } 301 } 302 return false; 303 } 304 305 private TableState getTableState() throws IOException { 306 return masterServices.getTableStateManager().getTableState(TableName.NAMESPACE_TABLE_NAME); 307 } 308 309 private boolean isTableEnabled() throws IOException { 310 return getTableState().isEnabled(); 311 } 312 313 private boolean isTableAssigned() { 314 // TODO: we have a better way now (wait on event) 315 return masterServices.getAssignmentManager().getRegionStates() 316 .hasTableRegionStates(TableName.NAMESPACE_TABLE_NAME); 317 } 318 319 public void validateTableAndRegionCount(NamespaceDescriptor desc) throws IOException { 320 if (getMaxRegions(desc) <= 0) { 321 throw new ConstraintException( 322 "The max region quota for " + desc.getName() + " is less than or equal to zero."); 323 } 324 if (getMaxTables(desc) <= 0) { 325 throw new ConstraintException( 326 "The max tables quota for " + desc.getName() + " is less than or equal to zero."); 327 } 328 } 329 330 public static long getMaxTables(NamespaceDescriptor ns) throws IOException { 331 String value = ns.getConfigurationValue(KEY_MAX_TABLES); 332 long maxTables = 0; 333 if (StringUtils.isNotEmpty(value)) { 334 try { 335 maxTables = Long.parseLong(value); 336 } catch (NumberFormatException exp) { 337 throw new DoNotRetryIOException("NumberFormatException while getting max tables.", exp); 338 } 339 } else { 340 // The property is not set, so assume its the max long value. 341 maxTables = Long.MAX_VALUE; 342 } 343 return maxTables; 344 } 345 346 public static long getMaxRegions(NamespaceDescriptor ns) throws IOException { 347 String value = ns.getConfigurationValue(KEY_MAX_REGIONS); 348 long maxRegions = 0; 349 if (StringUtils.isNotEmpty(value)) { 350 try { 351 maxRegions = Long.parseLong(value); 352 } catch (NumberFormatException exp) { 353 throw new DoNotRetryIOException("NumberFormatException while getting max regions.", exp); 354 } 355 } else { 356 // The property is not set, so assume its the max long value. 357 maxRegions = Long.MAX_VALUE; 358 } 359 return maxRegions; 360 } 361 362 @Override 363 public boolean isStopped() { 364 return this.stopped; 365 } 366 367 @Override 368 public void stop(String why) { 369 if (this.stopped) { 370 return; 371 } 372 try { 373 if (this.zkNamespaceManager != null) { 374 this.zkNamespaceManager.stop(); 375 } 376 } catch (IOException ioe) { 377 LOG.warn("Failed NamespaceManager close", ioe); 378 } 379 try { 380 if (this.nsTable != null) { 381 this.nsTable.close(); 382 } 383 } catch (IOException ioe) { 384 LOG.warn("Failed Namespace Table close", ioe); 385 } 386 this.stopped = true; 387 } 388}