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