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.procedure; 019 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.List; 023import java.util.function.Supplier; 024import org.apache.hadoop.fs.Path; 025import org.apache.hadoop.hbase.DoNotRetryIOException; 026import org.apache.hadoop.hbase.MetaTableAccessor; 027import org.apache.hadoop.hbase.NamespaceDescriptor; 028import org.apache.hadoop.hbase.TableExistsException; 029import org.apache.hadoop.hbase.TableName; 030import org.apache.hadoop.hbase.client.RegionInfo; 031import org.apache.hadoop.hbase.client.RegionReplicaUtil; 032import org.apache.hadoop.hbase.client.TableDescriptor; 033import org.apache.hadoop.hbase.client.TableState; 034import org.apache.hadoop.hbase.fs.ErasureCodingUtils; 035import org.apache.hadoop.hbase.master.MasterCoprocessorHost; 036import org.apache.hadoop.hbase.master.MasterFileSystem; 037import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer; 038import org.apache.hadoop.hbase.procedure2.ProcedureSuspendedException; 039import org.apache.hadoop.hbase.procedure2.ProcedureUtil; 040import org.apache.hadoop.hbase.regionserver.compactions.CustomCellTieredUtils; 041import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory; 042import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerValidationUtils; 043import org.apache.hadoop.hbase.rsgroup.RSGroupInfo; 044import org.apache.hadoop.hbase.util.CommonFSUtils; 045import org.apache.hadoop.hbase.util.FSTableDescriptors; 046import org.apache.hadoop.hbase.util.ModifyRegionUtils; 047import org.apache.hadoop.hbase.util.RetryCounter; 048import org.apache.yetus.audience.InterfaceAudience; 049import org.slf4j.Logger; 050import org.slf4j.LoggerFactory; 051 052import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 053 054import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 055import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; 056import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos; 057import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.CreateTableState; 058import org.apache.hadoop.hbase.shaded.protobuf.generated.ProcedureProtos; 059 060@InterfaceAudience.Private 061public class CreateTableProcedure extends AbstractStateMachineTableProcedure<CreateTableState> { 062 private static final Logger LOG = LoggerFactory.getLogger(CreateTableProcedure.class); 063 064 private static final int MAX_REGION_REPLICATION = 0x10000; 065 066 private TableDescriptor tableDescriptor; 067 private List<RegionInfo> newRegions; 068 private RetryCounter retryCounter; 069 070 public CreateTableProcedure() { 071 // Required by the Procedure framework to create the procedure on replay 072 super(); 073 } 074 075 public CreateTableProcedure(final MasterProcedureEnv env, final TableDescriptor tableDescriptor, 076 final RegionInfo[] newRegions) { 077 this(env, tableDescriptor, newRegions, null); 078 } 079 080 public CreateTableProcedure(final MasterProcedureEnv env, final TableDescriptor tableDescriptor, 081 final RegionInfo[] newRegions, final ProcedurePrepareLatch syncLatch) { 082 super(env, syncLatch); 083 this.tableDescriptor = tableDescriptor; 084 this.newRegions = newRegions != null ? Lists.newArrayList(newRegions) : null; 085 } 086 087 @Override 088 protected Flow executeFromState(final MasterProcedureEnv env, final CreateTableState state) 089 throws InterruptedException, ProcedureSuspendedException { 090 LOG.info("{} execute state={}", this, state); 091 try { 092 switch (state) { 093 case CREATE_TABLE_PRE_OPERATION: 094 // Verify if we can create the table 095 boolean success = prepareCreate(env); 096 releaseSyncLatch(); 097 098 if (!success) { 099 assert isFailed() : "the delete should have an exception here"; 100 return Flow.NO_MORE_STATE; 101 } 102 103 preCreate(env); 104 setNextState(CreateTableState.CREATE_TABLE_WRITE_FS_LAYOUT); 105 break; 106 case CREATE_TABLE_WRITE_FS_LAYOUT: 107 DeleteTableProcedure.deleteFromFs(env, getTableName(), newRegions, true); 108 newRegions = createFsLayout(env, tableDescriptor, newRegions); 109 env.getMasterServices().getTableDescriptors().update(tableDescriptor, true); 110 if (tableDescriptor.getErasureCodingPolicy() != null) { 111 setNextState(CreateTableState.CREATE_TABLE_SET_ERASURE_CODING_POLICY); 112 } else { 113 setNextState(CreateTableState.CREATE_TABLE_ADD_TO_META); 114 } 115 break; 116 case CREATE_TABLE_SET_ERASURE_CODING_POLICY: 117 ErasureCodingUtils.setPolicy(env.getMasterFileSystem().getFileSystem(), 118 env.getMasterFileSystem().getRootDir(), getTableName(), 119 tableDescriptor.getErasureCodingPolicy()); 120 setNextState(CreateTableState.CREATE_TABLE_ADD_TO_META); 121 break; 122 case CREATE_TABLE_ADD_TO_META: 123 newRegions = addTableToMeta(env, tableDescriptor, newRegions); 124 setNextState(CreateTableState.CREATE_TABLE_ASSIGN_REGIONS); 125 break; 126 case CREATE_TABLE_ASSIGN_REGIONS: 127 setEnablingState(env, getTableName()); 128 addChildProcedure( 129 env.getAssignmentManager().createRoundRobinAssignProcedures(newRegions)); 130 setNextState(CreateTableState.CREATE_TABLE_UPDATE_DESC_CACHE); 131 break; 132 case CREATE_TABLE_UPDATE_DESC_CACHE: 133 // XXX: this stage should be named as set table enabled, as now we will cache the 134 // descriptor after writing fs layout. 135 setEnabledState(env, getTableName()); 136 setNextState(CreateTableState.CREATE_TABLE_POST_OPERATION); 137 break; 138 case CREATE_TABLE_POST_OPERATION: 139 postCreate(env); 140 retryCounter = null; 141 return Flow.NO_MORE_STATE; 142 default: 143 throw new UnsupportedOperationException("unhandled state=" + state); 144 } 145 } catch (IOException e) { 146 if (isRollbackSupported(state)) { 147 setFailure("master-create-table", e); 148 } else { 149 if (retryCounter == null) { 150 retryCounter = ProcedureUtil.createRetryCounter(env.getMasterConfiguration()); 151 } 152 long backoff = retryCounter.getBackoffTimeAndIncrementAttempts(); 153 LOG.warn("Retriable error trying to create table={},state={},suspend {}secs.", 154 getTableName(), state, backoff / 1000, e); 155 throw suspend(Math.toIntExact(backoff), true); 156 } 157 } 158 retryCounter = null; 159 return Flow.HAS_MORE_STATE; 160 } 161 162 @Override 163 protected synchronized boolean setTimeoutFailure(MasterProcedureEnv env) { 164 setState(ProcedureProtos.ProcedureState.RUNNABLE); 165 env.getProcedureScheduler().addFront(this); 166 return false; 167 } 168 169 @Override 170 protected void rollbackState(final MasterProcedureEnv env, final CreateTableState state) 171 throws IOException { 172 if (state == CreateTableState.CREATE_TABLE_PRE_OPERATION) { 173 // nothing to rollback, pre-create is just table-state checks. 174 // We can fail if the table does exist or the descriptor is malformed. 175 // TODO: coprocessor rollback semantic is still undefined. 176 if ( 177 hasException() 178 /* avoid NPE */ && getException().getCause().getClass() != TableExistsException.class 179 ) { 180 DeleteTableProcedure.deleteTableStates(env, getTableName()); 181 182 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 183 if (cpHost != null) { 184 cpHost.postDeleteTable(getTableName()); 185 } 186 } 187 188 releaseSyncLatch(); 189 return; 190 } 191 192 // The procedure doesn't have a rollback. The execution will succeed, at some point. 193 throw new UnsupportedOperationException("unhandled state=" + state); 194 } 195 196 @Override 197 protected boolean isRollbackSupported(final CreateTableState state) { 198 switch (state) { 199 case CREATE_TABLE_PRE_OPERATION: 200 return true; 201 default: 202 return false; 203 } 204 } 205 206 @Override 207 protected CreateTableState getState(final int stateId) { 208 return CreateTableState.forNumber(stateId); 209 } 210 211 @Override 212 protected int getStateId(final CreateTableState state) { 213 return state.getNumber(); 214 } 215 216 @Override 217 protected CreateTableState getInitialState() { 218 return CreateTableState.CREATE_TABLE_PRE_OPERATION; 219 } 220 221 @Override 222 public TableName getTableName() { 223 return tableDescriptor.getTableName(); 224 } 225 226 @Override 227 public TableOperationType getTableOperationType() { 228 return TableOperationType.CREATE; 229 } 230 231 @Override 232 protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException { 233 super.serializeStateData(serializer); 234 235 MasterProcedureProtos.CreateTableStateData.Builder state = 236 MasterProcedureProtos.CreateTableStateData.newBuilder() 237 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())) 238 .setTableSchema(ProtobufUtil.toTableSchema(tableDescriptor)); 239 if (newRegions != null) { 240 for (RegionInfo hri : newRegions) { 241 state.addRegionInfo(ProtobufUtil.toRegionInfo(hri)); 242 } 243 } 244 serializer.serialize(state.build()); 245 } 246 247 @Override 248 protected void deserializeStateData(ProcedureStateSerializer serializer) throws IOException { 249 super.deserializeStateData(serializer); 250 251 MasterProcedureProtos.CreateTableStateData state = 252 serializer.deserialize(MasterProcedureProtos.CreateTableStateData.class); 253 setUser(MasterProcedureUtil.toUserInfo(state.getUserInfo())); 254 tableDescriptor = ProtobufUtil.toTableDescriptor(state.getTableSchema()); 255 if (state.getRegionInfoCount() == 0) { 256 newRegions = null; 257 } else { 258 newRegions = new ArrayList<>(state.getRegionInfoCount()); 259 for (HBaseProtos.RegionInfo hri : state.getRegionInfoList()) { 260 newRegions.add(ProtobufUtil.toRegionInfo(hri)); 261 } 262 } 263 } 264 265 @Override 266 protected boolean waitInitialized(MasterProcedureEnv env) { 267 if (isCriticalSystemTable()) { 268 return false; 269 } 270 if (getTableName().isSystemTable()) { 271 // Creating system table is part of the initialization, so only wait for meta loaded instead 272 // of waiting for master fully initialized. 273 return env.getAssignmentManager().waitMetaLoaded(this); 274 } 275 return super.waitInitialized(env); 276 } 277 278 private boolean prepareCreate(final MasterProcedureEnv env) throws IOException { 279 final TableName tableName = getTableName(); 280 if (env.getMasterServices().getTableDescriptors().exists(tableName)) { 281 setFailure("master-create-table", new TableExistsException(getTableName())); 282 return false; 283 } 284 285 // check that we have at least 1 CF 286 if (tableDescriptor.getColumnFamilyCount() == 0) { 287 setFailure("master-create-table", new DoNotRetryIOException( 288 "Table " + getTableName().toString() + " should have at least one column family.")); 289 return false; 290 } 291 292 int regionReplicationCount = tableDescriptor.getRegionReplication(); 293 if (regionReplicationCount > MAX_REGION_REPLICATION) { 294 setFailure("master-create-table", new IllegalArgumentException( 295 "Region Replication cannot exceed " + MAX_REGION_REPLICATION + ".")); 296 return false; 297 } 298 299 if (!tableName.isSystemTable()) { 300 // do not check rs group for system tables as we may block the bootstrap. 301 Supplier<String> forWhom = () -> "table " + tableName; 302 RSGroupInfo rsGroupInfo = MasterProcedureUtil.checkGroupExists( 303 env.getMasterServices().getRSGroupInfoManager()::getRSGroup, 304 tableDescriptor.getRegionServerGroup(), forWhom); 305 if (rsGroupInfo == null) { 306 // we do not set rs group info on table, check if we have one on namespace 307 String namespace = tableName.getNamespaceAsString(); 308 NamespaceDescriptor nd = env.getMasterServices().getClusterSchema().getNamespace(namespace); 309 forWhom = () -> "table " + tableName + "(inherit from namespace)"; 310 rsGroupInfo = MasterProcedureUtil.checkGroupExists( 311 env.getMasterServices().getRSGroupInfoManager()::getRSGroup, 312 MasterProcedureUtil.getNamespaceGroup(nd), forWhom); 313 } 314 MasterProcedureUtil.checkGroupNotEmpty(rsGroupInfo, forWhom); 315 } 316 317 // check for store file tracker configurations 318 StoreFileTrackerValidationUtils.checkForCreateTable(env.getMasterConfiguration(), 319 tableDescriptor); 320 321 CustomCellTieredUtils.checkForModifyTable(tableDescriptor); 322 323 return true; 324 } 325 326 private void preCreate(final MasterProcedureEnv env) throws IOException, InterruptedException { 327 if (!getTableName().isSystemTable()) { 328 ProcedureSyncWait.getMasterQuotaManager(env).checkNamespaceTableAndRegionQuota(getTableName(), 329 (newRegions != null ? newRegions.size() : 0)); 330 } 331 332 tableDescriptor = StoreFileTrackerFactory.updateWithTrackerConfigs(env.getMasterConfiguration(), 333 tableDescriptor); 334 335 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 336 if (cpHost != null) { 337 final RegionInfo[] regions = 338 newRegions == null ? null : newRegions.toArray(new RegionInfo[newRegions.size()]); 339 cpHost.preCreateTableAction(tableDescriptor, regions, getUser()); 340 } 341 } 342 343 private void postCreate(final MasterProcedureEnv env) throws IOException, InterruptedException { 344 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 345 if (cpHost != null) { 346 final RegionInfo[] regions = 347 (newRegions == null) ? null : newRegions.toArray(new RegionInfo[newRegions.size()]); 348 cpHost.postCompletedCreateTableAction(tableDescriptor, regions, getUser()); 349 } 350 } 351 352 protected interface CreateHdfsRegions { 353 List<RegionInfo> createHdfsRegions(final MasterProcedureEnv env, final Path tableRootDir, 354 final TableName tableName, final List<RegionInfo> newRegions) throws IOException; 355 } 356 357 protected static List<RegionInfo> createFsLayout(final MasterProcedureEnv env, 358 final TableDescriptor tableDescriptor, final List<RegionInfo> newRegions) throws IOException { 359 return createFsLayout(env, tableDescriptor, newRegions, new CreateHdfsRegions() { 360 @Override 361 public List<RegionInfo> createHdfsRegions(final MasterProcedureEnv env, 362 final Path tableRootDir, final TableName tableName, final List<RegionInfo> newRegions) 363 throws IOException { 364 RegionInfo[] regions = 365 newRegions != null ? newRegions.toArray(new RegionInfo[newRegions.size()]) : null; 366 return ModifyRegionUtils.createRegions(env, tableRootDir, tableDescriptor, regions, null); 367 } 368 }); 369 } 370 371 protected static List<RegionInfo> createFsLayout(final MasterProcedureEnv env, 372 final TableDescriptor tableDescriptor, List<RegionInfo> newRegions, 373 final CreateHdfsRegions hdfsRegionHandler) throws IOException { 374 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 375 376 // 1. Create Table Descriptor 377 // using a copy of descriptor, table will be created enabling first 378 final Path tableDir = 379 CommonFSUtils.getTableDir(mfs.getRootDir(), tableDescriptor.getTableName()); 380 ((FSTableDescriptors) (env.getMasterServices().getTableDescriptors())) 381 .createTableDescriptorForTableDirectory(tableDir, tableDescriptor, false); 382 383 // 2. Create Regions 384 newRegions = hdfsRegionHandler.createHdfsRegions(env, mfs.getRootDir(), 385 tableDescriptor.getTableName(), newRegions); 386 387 return newRegions; 388 } 389 390 protected static List<RegionInfo> addTableToMeta(final MasterProcedureEnv env, 391 final TableDescriptor tableDescriptor, final List<RegionInfo> regions) throws IOException { 392 assert (regions != null && regions.size() > 0) : "expected at least 1 region, got " + regions; 393 394 ProcedureSyncWait.waitMetaRegions(env); 395 396 // Add replicas if needed 397 // we need to create regions with replicaIds starting from 1 398 List<RegionInfo> newRegions = 399 RegionReplicaUtil.addReplicas(regions, 1, tableDescriptor.getRegionReplication()); 400 401 // Add regions to META 402 addRegionsToMeta(env, tableDescriptor, newRegions); 403 404 return newRegions; 405 } 406 407 protected static void setEnablingState(final MasterProcedureEnv env, final TableName tableName) 408 throws IOException { 409 // Mark the table as Enabling 410 env.getMasterServices().getTableStateManager().setTableState(tableName, 411 TableState.State.ENABLING); 412 } 413 414 protected static void setEnabledState(final MasterProcedureEnv env, final TableName tableName) 415 throws IOException { 416 // Enable table 417 env.getMasterServices().getTableStateManager().setTableState(tableName, 418 TableState.State.ENABLED); 419 } 420 421 /** 422 * Add the specified set of regions to the hbase:meta table. 423 */ 424 private static void addRegionsToMeta(final MasterProcedureEnv env, 425 final TableDescriptor tableDescriptor, final List<RegionInfo> regionInfos) throws IOException { 426 MetaTableAccessor.addRegionsToMeta(env.getMasterServices().getConnection(), regionInfos, 427 tableDescriptor.getRegionReplication()); 428 } 429 430 @Override 431 protected boolean shouldWaitClientAck(MasterProcedureEnv env) { 432 // system tables are created on bootstrap internally by the system 433 // the client does not know about this procedures. 434 return !getTableName().isSystemTable(); 435 } 436 437 RegionInfo getFirstRegionInfo() { 438 if (newRegions == null || newRegions.isEmpty()) { 439 return null; 440 } 441 return newRegions.get(0); 442 } 443}