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 DeleteTableProcedure.deleteTableStates(env, getTableName()); 143 releaseSyncLatch(); 144 return; 145 } 146 147 // The procedure doesn't have a rollback. The execution will succeed, at some point. 148 throw new UnsupportedOperationException("unhandled state=" + state); 149 } 150 151 @Override 152 protected boolean isRollbackSupported(final CreateTableState state) { 153 switch (state) { 154 case CREATE_TABLE_PRE_OPERATION: 155 return true; 156 default: 157 return false; 158 } 159 } 160 161 @Override 162 protected CreateTableState getState(final int stateId) { 163 return CreateTableState.valueOf(stateId); 164 } 165 166 @Override 167 protected int getStateId(final CreateTableState state) { 168 return state.getNumber(); 169 } 170 171 @Override 172 protected CreateTableState getInitialState() { 173 return CreateTableState.CREATE_TABLE_PRE_OPERATION; 174 } 175 176 @Override 177 public TableName getTableName() { 178 return tableDescriptor.getTableName(); 179 } 180 181 @Override 182 public TableOperationType getTableOperationType() { 183 return TableOperationType.CREATE; 184 } 185 186 @Override 187 protected void serializeStateData(ProcedureStateSerializer serializer) 188 throws IOException { 189 super.serializeStateData(serializer); 190 191 MasterProcedureProtos.CreateTableStateData.Builder state = 192 MasterProcedureProtos.CreateTableStateData.newBuilder() 193 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())) 194 .setTableSchema(ProtobufUtil.toTableSchema(tableDescriptor)); 195 if (newRegions != null) { 196 for (RegionInfo hri: newRegions) { 197 state.addRegionInfo(ProtobufUtil.toRegionInfo(hri)); 198 } 199 } 200 serializer.serialize(state.build()); 201 } 202 203 @Override 204 protected void deserializeStateData(ProcedureStateSerializer serializer) 205 throws IOException { 206 super.deserializeStateData(serializer); 207 208 MasterProcedureProtos.CreateTableStateData state = 209 serializer.deserialize(MasterProcedureProtos.CreateTableStateData.class); 210 setUser(MasterProcedureUtil.toUserInfo(state.getUserInfo())); 211 tableDescriptor = ProtobufUtil.toTableDescriptor(state.getTableSchema()); 212 if (state.getRegionInfoCount() == 0) { 213 newRegions = null; 214 } else { 215 newRegions = new ArrayList<>(state.getRegionInfoCount()); 216 for (HBaseProtos.RegionInfo hri: state.getRegionInfoList()) { 217 newRegions.add(ProtobufUtil.toRegionInfo(hri)); 218 } 219 } 220 } 221 222 @Override 223 protected boolean waitInitialized(MasterProcedureEnv env) { 224 if (getTableName().isSystemTable()) { 225 // Creating system table is part of the initialization, so do not wait here. 226 return false; 227 } 228 return super.waitInitialized(env); 229 } 230 231 @Override 232 protected LockState acquireLock(final MasterProcedureEnv env) { 233 if (env.getProcedureScheduler().waitTableExclusiveLock(this, getTableName())) { 234 return LockState.LOCK_EVENT_WAIT; 235 } 236 return LockState.LOCK_ACQUIRED; 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 = FSUtils.getTableDir(tempdir, tableDescriptor.getTableName()); 313 ((FSTableDescriptors)(env.getMasterServices().getTableDescriptors())) 314 .createTableDescriptorForTableDirectory( 315 tempTableDir, tableDescriptor, false); 316 317 // 2. Create Regions 318 newRegions = hdfsRegionHandler.createHdfsRegions(env, tempdir, 319 tableDescriptor.getTableName(), newRegions); 320 321 // 3. Move Table temp directory to the hbase root location 322 moveTempDirectoryToHBaseRoot(env, tableDescriptor, tempTableDir); 323 324 return newRegions; 325 } 326 327 protected static void moveTempDirectoryToHBaseRoot( 328 final MasterProcedureEnv env, 329 final TableDescriptor tableDescriptor, 330 final Path tempTableDir) throws IOException { 331 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 332 final Path tableDir = FSUtils.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}