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; 024 025import org.apache.hadoop.fs.FileSystem; 026import org.apache.hadoop.fs.Path; 027import org.apache.hadoop.hbase.DoNotRetryIOException; 028import org.apache.hadoop.hbase.MetaTableAccessor; 029import org.apache.hadoop.hbase.TableExistsException; 030import org.apache.hadoop.hbase.TableName; 031import org.apache.hadoop.hbase.client.RegionInfo; 032import org.apache.hadoop.hbase.client.RegionReplicaUtil; 033import org.apache.hadoop.hbase.client.TableDescriptor; 034import org.apache.hadoop.hbase.client.TableState; 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.util.FSTableDescriptors; 039import org.apache.hadoop.hbase.util.FSUtils; 040import org.apache.hadoop.hbase.util.ModifyRegionUtils; 041import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil; 042import org.apache.yetus.audience.InterfaceAudience; 043import org.slf4j.Logger; 044import org.slf4j.LoggerFactory; 045import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; 046import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 047import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 048import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; 049import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos; 050import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.CreateTableState; 051 052@InterfaceAudience.Private 053public class CreateTableProcedure 054 extends AbstractStateMachineTableProcedure<CreateTableState> { 055 private static final Logger LOG = LoggerFactory.getLogger(CreateTableProcedure.class); 056 057 private TableDescriptor tableDescriptor; 058 private List<RegionInfo> newRegions; 059 060 public CreateTableProcedure() { 061 // Required by the Procedure framework to create the procedure on replay 062 super(); 063 } 064 065 public CreateTableProcedure(final MasterProcedureEnv env, 066 final TableDescriptor tableDescriptor, final RegionInfo[] newRegions) { 067 this(env, tableDescriptor, newRegions, null); 068 } 069 070 public CreateTableProcedure(final MasterProcedureEnv env, 071 final TableDescriptor tableDescriptor, final RegionInfo[] newRegions, 072 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 if (LOG.isTraceEnabled()) { 082 LOG.trace(this + " execute state=" + state); 083 } 084 try { 085 switch (state) { 086 case CREATE_TABLE_PRE_OPERATION: 087 // Verify if we can create the table 088 boolean exists = !prepareCreate(env); 089 releaseSyncLatch(); 090 091 if (exists) { 092 assert isFailed() : "the delete should have an exception here"; 093 return Flow.NO_MORE_STATE; 094 } 095 096 preCreate(env); 097 setNextState(CreateTableState.CREATE_TABLE_WRITE_FS_LAYOUT); 098 break; 099 case CREATE_TABLE_WRITE_FS_LAYOUT: 100 DeleteTableProcedure.deleteFromFs(env, getTableName(), newRegions, true); 101 newRegions = createFsLayout(env, tableDescriptor, newRegions); 102 setNextState(CreateTableState.CREATE_TABLE_ADD_TO_META); 103 break; 104 case CREATE_TABLE_ADD_TO_META: 105 newRegions = addTableToMeta(env, tableDescriptor, newRegions); 106 setNextState(CreateTableState.CREATE_TABLE_ASSIGN_REGIONS); 107 break; 108 case CREATE_TABLE_ASSIGN_REGIONS: 109 setEnablingState(env, getTableName()); 110 addChildProcedure(env.getAssignmentManager() 111 .createRoundRobinAssignProcedures(newRegions)); 112 setNextState(CreateTableState.CREATE_TABLE_UPDATE_DESC_CACHE); 113 break; 114 case CREATE_TABLE_UPDATE_DESC_CACHE: 115 setEnabledState(env, getTableName()); 116 updateTableDescCache(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 (hasException() /* avoid NPE */ && 143 getException().getCause().getClass() != TableExistsException.class) { 144 DeleteTableProcedure.deleteTableStates(env, getTableName()); 145 146 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 147 if (cpHost != null) { 148 cpHost.postDeleteTable(getTableName()); 149 } 150 } 151 152 releaseSyncLatch(); 153 return; 154 } 155 156 // The procedure doesn't have a rollback. The execution will succeed, at some point. 157 throw new UnsupportedOperationException("unhandled state=" + state); 158 } 159 160 @Override 161 protected boolean isRollbackSupported(final CreateTableState state) { 162 switch (state) { 163 case CREATE_TABLE_PRE_OPERATION: 164 return true; 165 default: 166 return false; 167 } 168 } 169 170 @Override 171 protected CreateTableState getState(final int stateId) { 172 return CreateTableState.forNumber(stateId); 173 } 174 175 @Override 176 protected int getStateId(final CreateTableState state) { 177 return state.getNumber(); 178 } 179 180 @Override 181 protected CreateTableState getInitialState() { 182 return CreateTableState.CREATE_TABLE_PRE_OPERATION; 183 } 184 185 @Override 186 public TableName getTableName() { 187 return tableDescriptor.getTableName(); 188 } 189 190 @Override 191 public TableOperationType getTableOperationType() { 192 return TableOperationType.CREATE; 193 } 194 195 @Override 196 protected void serializeStateData(ProcedureStateSerializer serializer) 197 throws IOException { 198 super.serializeStateData(serializer); 199 200 MasterProcedureProtos.CreateTableStateData.Builder state = 201 MasterProcedureProtos.CreateTableStateData.newBuilder() 202 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())) 203 .setTableSchema(ProtobufUtil.toTableSchema(tableDescriptor)); 204 if (newRegions != null) { 205 for (RegionInfo hri: newRegions) { 206 state.addRegionInfo(ProtobufUtil.toRegionInfo(hri)); 207 } 208 } 209 serializer.serialize(state.build()); 210 } 211 212 @Override 213 protected void deserializeStateData(ProcedureStateSerializer serializer) 214 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 do not wait here. 235 return false; 236 } 237 return super.waitInitialized(env); 238 } 239 240 @Override 241 protected LockState acquireLock(final MasterProcedureEnv env) { 242 if (env.getProcedureScheduler().waitTableExclusiveLock(this, getTableName())) { 243 return LockState.LOCK_EVENT_WAIT; 244 } 245 return LockState.LOCK_ACQUIRED; 246 } 247 248 private boolean prepareCreate(final MasterProcedureEnv env) throws IOException { 249 final TableName tableName = getTableName(); 250 if (MetaTableAccessor.tableExists(env.getMasterServices().getConnection(), tableName)) { 251 setFailure("master-create-table", new TableExistsException(getTableName())); 252 return false; 253 } 254 255 // check that we have at least 1 CF 256 if (tableDescriptor.getColumnFamilyCount() == 0) { 257 setFailure("master-create-table", new DoNotRetryIOException("Table " + 258 getTableName().toString() + " should have at least one column family.")); 259 return false; 260 } 261 262 return true; 263 } 264 265 private void preCreate(final MasterProcedureEnv env) 266 throws IOException, InterruptedException { 267 if (!getTableName().isSystemTable()) { 268 ProcedureSyncWait.getMasterQuotaManager(env) 269 .checkNamespaceTableAndRegionQuota( 270 getTableName(), (newRegions != null ? newRegions.size() : 0)); 271 } 272 273 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 274 if (cpHost != null) { 275 final RegionInfo[] regions = newRegions == null ? null : 276 newRegions.toArray(new RegionInfo[newRegions.size()]); 277 cpHost.preCreateTableAction(tableDescriptor, regions, getUser()); 278 } 279 } 280 281 private void postCreate(final MasterProcedureEnv env) 282 throws IOException, InterruptedException { 283 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 284 if (cpHost != null) { 285 final RegionInfo[] regions = (newRegions == null) ? null : 286 newRegions.toArray(new RegionInfo[newRegions.size()]); 287 cpHost.postCompletedCreateTableAction(tableDescriptor, regions, getUser()); 288 } 289 } 290 291 protected interface CreateHdfsRegions { 292 List<RegionInfo> createHdfsRegions(final MasterProcedureEnv env, 293 final Path tableRootDir, final TableName tableName, 294 final List<RegionInfo> newRegions) throws IOException; 295 } 296 297 protected static List<RegionInfo> createFsLayout(final MasterProcedureEnv env, 298 final TableDescriptor tableDescriptor, final List<RegionInfo> newRegions) 299 throws IOException { 300 return createFsLayout(env, tableDescriptor, newRegions, new CreateHdfsRegions() { 301 @Override 302 public List<RegionInfo> createHdfsRegions(final MasterProcedureEnv env, 303 final Path tableRootDir, final TableName tableName, 304 final List<RegionInfo> newRegions) throws IOException { 305 RegionInfo[] regions = newRegions != null ? 306 newRegions.toArray(new RegionInfo[newRegions.size()]) : null; 307 return ModifyRegionUtils.createRegions(env.getMasterConfiguration(), 308 tableRootDir, tableDescriptor, regions, null); 309 } 310 }); 311 } 312 313 protected static List<RegionInfo> createFsLayout(final MasterProcedureEnv env, 314 final TableDescriptor tableDescriptor, List<RegionInfo> newRegions, 315 final CreateHdfsRegions hdfsRegionHandler) throws IOException { 316 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 317 final Path tempdir = mfs.getTempDir(); 318 319 // 1. Create Table Descriptor 320 // using a copy of descriptor, table will be created enabling first 321 final Path tempTableDir = FSUtils.getTableDir(tempdir, tableDescriptor.getTableName()); 322 ((FSTableDescriptors)(env.getMasterServices().getTableDescriptors())) 323 .createTableDescriptorForTableDirectory( 324 tempTableDir, tableDescriptor, false); 325 326 // 2. Create Regions 327 newRegions = hdfsRegionHandler.createHdfsRegions(env, tempdir, 328 tableDescriptor.getTableName(), newRegions); 329 330 // 3. Move Table temp directory to the hbase root location 331 moveTempDirectoryToHBaseRoot(env, tableDescriptor, tempTableDir); 332 333 return newRegions; 334 } 335 336 protected static void moveTempDirectoryToHBaseRoot( 337 final MasterProcedureEnv env, 338 final TableDescriptor tableDescriptor, 339 final Path tempTableDir) throws IOException { 340 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 341 final Path tableDir = FSUtils.getTableDir(mfs.getRootDir(), tableDescriptor.getTableName()); 342 FileSystem fs = mfs.getFileSystem(); 343 if (!fs.delete(tableDir, true) && fs.exists(tableDir)) { 344 throw new IOException("Couldn't delete " + tableDir); 345 } 346 if (!fs.rename(tempTableDir, tableDir)) { 347 throw new IOException("Unable to move table from temp=" + tempTableDir + 348 " to hbase root=" + tableDir); 349 } 350 } 351 352 protected static List<RegionInfo> addTableToMeta(final MasterProcedureEnv env, 353 final TableDescriptor tableDescriptor, 354 final List<RegionInfo> regions) throws IOException { 355 assert (regions != null && regions.size() > 0) : "expected at least 1 region, got " + regions; 356 357 ProcedureSyncWait.waitMetaRegions(env); 358 359 // Add replicas if needed 360 // we need to create regions with replicaIds starting from 1 361 List<RegionInfo> newRegions = RegionReplicaUtil.addReplicas(tableDescriptor, regions, 1, 362 tableDescriptor.getRegionReplication()); 363 364 // Add regions to META 365 addRegionsToMeta(env, tableDescriptor, newRegions); 366 367 // Setup replication for region replicas if needed 368 if (tableDescriptor.getRegionReplication() > 1) { 369 ServerRegionReplicaUtil.setupRegionReplicaReplication(env.getMasterConfiguration()); 370 } 371 return newRegions; 372 } 373 374 protected static void setEnablingState(final MasterProcedureEnv env, final TableName tableName) 375 throws IOException { 376 // Mark the table as Enabling 377 env.getMasterServices().getTableStateManager() 378 .setTableState(tableName, TableState.State.ENABLING); 379 } 380 381 protected static void setEnabledState(final MasterProcedureEnv env, final TableName tableName) 382 throws IOException { 383 // Enable table 384 env.getMasterServices().getTableStateManager() 385 .setTableState(tableName, TableState.State.ENABLED); 386 } 387 388 /** 389 * Add the specified set of regions to the hbase:meta table. 390 */ 391 private static void addRegionsToMeta(final MasterProcedureEnv env, 392 final TableDescriptor tableDescriptor, 393 final List<RegionInfo> regionInfos) throws IOException { 394 MetaTableAccessor.addRegionsToMeta(env.getMasterServices().getConnection(), 395 regionInfos, tableDescriptor.getRegionReplication()); 396 } 397 398 protected static void updateTableDescCache(final MasterProcedureEnv env, 399 final TableName tableName) throws IOException { 400 env.getMasterServices().getTableDescriptors().get(tableName); 401 } 402 403 @Override 404 protected boolean shouldWaitClientAck(MasterProcedureEnv env) { 405 // system tables are created on bootstrap internally by the system 406 // the client does not know about this procedures. 407 return !getTableName().isSystemTable(); 408 } 409 410 @VisibleForTesting 411 RegionInfo getFirstRegionInfo() { 412 if (newRegions == null || newRegions.isEmpty()) { 413 return null; 414 } 415 return newRegions.get(0); 416 } 417}