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