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 (getTableName().isSystemTable()) { 268 // Creating system table is part of the initialization, so only wait for meta loaded instead 269 // of waiting for master fully initialized. 270 return env.getAssignmentManager().waitMetaLoaded(this); 271 } 272 return super.waitInitialized(env); 273 } 274 275 private boolean prepareCreate(final MasterProcedureEnv env) throws IOException { 276 final TableName tableName = getTableName(); 277 if (env.getMasterServices().getTableDescriptors().exists(tableName)) { 278 setFailure("master-create-table", new TableExistsException(getTableName())); 279 return false; 280 } 281 282 // check that we have at least 1 CF 283 if (tableDescriptor.getColumnFamilyCount() == 0) { 284 setFailure("master-create-table", new DoNotRetryIOException( 285 "Table " + getTableName().toString() + " should have at least one column family.")); 286 return false; 287 } 288 289 int regionReplicationCount = tableDescriptor.getRegionReplication(); 290 if (regionReplicationCount > MAX_REGION_REPLICATION) { 291 setFailure("master-create-table", new IllegalArgumentException( 292 "Region Replication cannot exceed " + MAX_REGION_REPLICATION + ".")); 293 return false; 294 } 295 296 if (!tableName.isSystemTable()) { 297 // do not check rs group for system tables as we may block the bootstrap. 298 Supplier<String> forWhom = () -> "table " + tableName; 299 RSGroupInfo rsGroupInfo = MasterProcedureUtil.checkGroupExists( 300 env.getMasterServices().getRSGroupInfoManager()::getRSGroup, 301 tableDescriptor.getRegionServerGroup(), forWhom); 302 if (rsGroupInfo == null) { 303 // we do not set rs group info on table, check if we have one on namespace 304 String namespace = tableName.getNamespaceAsString(); 305 NamespaceDescriptor nd = env.getMasterServices().getClusterSchema().getNamespace(namespace); 306 forWhom = () -> "table " + tableName + "(inherit from namespace)"; 307 rsGroupInfo = MasterProcedureUtil.checkGroupExists( 308 env.getMasterServices().getRSGroupInfoManager()::getRSGroup, 309 MasterProcedureUtil.getNamespaceGroup(nd), forWhom); 310 } 311 MasterProcedureUtil.checkGroupNotEmpty(rsGroupInfo, forWhom); 312 } 313 314 // check for store file tracker configurations 315 StoreFileTrackerValidationUtils.checkForCreateTable(env.getMasterConfiguration(), 316 tableDescriptor); 317 318 CustomCellTieredUtils.checkForModifyTable(tableDescriptor); 319 320 return true; 321 } 322 323 private void preCreate(final MasterProcedureEnv env) throws IOException, InterruptedException { 324 if (!getTableName().isSystemTable()) { 325 ProcedureSyncWait.getMasterQuotaManager(env).checkNamespaceTableAndRegionQuota(getTableName(), 326 (newRegions != null ? newRegions.size() : 0)); 327 } 328 329 tableDescriptor = StoreFileTrackerFactory.updateWithTrackerConfigs(env.getMasterConfiguration(), 330 tableDescriptor); 331 332 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 333 if (cpHost != null) { 334 final RegionInfo[] regions = 335 newRegions == null ? null : newRegions.toArray(new RegionInfo[newRegions.size()]); 336 cpHost.preCreateTableAction(tableDescriptor, regions, getUser()); 337 } 338 } 339 340 private void postCreate(final MasterProcedureEnv env) throws IOException, InterruptedException { 341 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 342 if (cpHost != null) { 343 final RegionInfo[] regions = 344 (newRegions == null) ? null : newRegions.toArray(new RegionInfo[newRegions.size()]); 345 cpHost.postCompletedCreateTableAction(tableDescriptor, regions, getUser()); 346 } 347 } 348 349 protected interface CreateHdfsRegions { 350 List<RegionInfo> createHdfsRegions(final MasterProcedureEnv env, final Path tableRootDir, 351 final TableName tableName, final List<RegionInfo> newRegions) throws IOException; 352 } 353 354 protected static List<RegionInfo> createFsLayout(final MasterProcedureEnv env, 355 final TableDescriptor tableDescriptor, final List<RegionInfo> newRegions) throws IOException { 356 return createFsLayout(env, tableDescriptor, newRegions, new CreateHdfsRegions() { 357 @Override 358 public List<RegionInfo> createHdfsRegions(final MasterProcedureEnv env, 359 final Path tableRootDir, final TableName tableName, final List<RegionInfo> newRegions) 360 throws IOException { 361 RegionInfo[] regions = 362 newRegions != null ? newRegions.toArray(new RegionInfo[newRegions.size()]) : null; 363 return ModifyRegionUtils.createRegions(env.getMasterConfiguration(), tableRootDir, 364 tableDescriptor, regions, null); 365 } 366 }); 367 } 368 369 protected static List<RegionInfo> createFsLayout(final MasterProcedureEnv env, 370 final TableDescriptor tableDescriptor, List<RegionInfo> newRegions, 371 final CreateHdfsRegions hdfsRegionHandler) throws IOException { 372 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 373 374 // 1. Create Table Descriptor 375 // using a copy of descriptor, table will be created enabling first 376 final Path tableDir = 377 CommonFSUtils.getTableDir(mfs.getRootDir(), tableDescriptor.getTableName()); 378 ((FSTableDescriptors) (env.getMasterServices().getTableDescriptors())) 379 .createTableDescriptorForTableDirectory(tableDir, tableDescriptor, false); 380 381 // 2. Create Regions 382 newRegions = hdfsRegionHandler.createHdfsRegions(env, mfs.getRootDir(), 383 tableDescriptor.getTableName(), newRegions); 384 385 return newRegions; 386 } 387 388 protected static List<RegionInfo> addTableToMeta(final MasterProcedureEnv env, 389 final TableDescriptor tableDescriptor, final List<RegionInfo> regions) throws IOException { 390 assert (regions != null && regions.size() > 0) : "expected at least 1 region, got " + regions; 391 392 ProcedureSyncWait.waitMetaRegions(env); 393 394 // Add replicas if needed 395 // we need to create regions with replicaIds starting from 1 396 List<RegionInfo> newRegions = 397 RegionReplicaUtil.addReplicas(regions, 1, tableDescriptor.getRegionReplication()); 398 399 // Add regions to META 400 addRegionsToMeta(env, tableDescriptor, newRegions); 401 402 return newRegions; 403 } 404 405 protected static void setEnablingState(final MasterProcedureEnv env, final TableName tableName) 406 throws IOException { 407 // Mark the table as Enabling 408 env.getMasterServices().getTableStateManager().setTableState(tableName, 409 TableState.State.ENABLING); 410 } 411 412 protected static void setEnabledState(final MasterProcedureEnv env, final TableName tableName) 413 throws IOException { 414 // Enable table 415 env.getMasterServices().getTableStateManager().setTableState(tableName, 416 TableState.State.ENABLED); 417 } 418 419 /** 420 * Add the specified set of regions to the hbase:meta table. 421 */ 422 private static void addRegionsToMeta(final MasterProcedureEnv env, 423 final TableDescriptor tableDescriptor, final List<RegionInfo> regionInfos) throws IOException { 424 MetaTableAccessor.addRegionsToMeta(env.getMasterServices().getConnection(), regionInfos, 425 tableDescriptor.getRegionReplication()); 426 } 427 428 @Override 429 protected boolean shouldWaitClientAck(MasterProcedureEnv env) { 430 // system tables are created on bootstrap internally by the system 431 // the client does not know about this procedures. 432 return !getTableName().isSystemTable(); 433 } 434 435 RegionInfo getFirstRegionInfo() { 436 if (newRegions == null || newRegions.isEmpty()) { 437 return null; 438 } 439 return newRegions.get(0); 440 } 441}