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