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.HBaseIOException; 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.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 056 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, 068 final TableDescriptor tableDescriptor, final RegionInfo[] newRegions) { 069 this(env, tableDescriptor, newRegions, null); 070 } 071 072 public CreateTableProcedure(final MasterProcedureEnv env, 073 final TableDescriptor tableDescriptor, final RegionInfo[] newRegions, 074 final ProcedurePrepareLatch syncLatch) { 075 super(env, syncLatch); 076 this.tableDescriptor = tableDescriptor; 077 this.newRegions = newRegions != null ? Lists.newArrayList(newRegions) : null; 078 } 079 080 @Override 081 protected Flow executeFromState(final MasterProcedureEnv env, final CreateTableState state) 082 throws InterruptedException { 083 LOG.info("{} execute state={}", this, state); 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 env.getMasterServices().getTableDescriptors().update(tableDescriptor, true); 103 setNextState(CreateTableState.CREATE_TABLE_ADD_TO_META); 104 break; 105 case CREATE_TABLE_ADD_TO_META: 106 newRegions = addTableToMeta(env, tableDescriptor, newRegions); 107 setNextState(CreateTableState.CREATE_TABLE_ASSIGN_REGIONS); 108 break; 109 case CREATE_TABLE_ASSIGN_REGIONS: 110 setEnablingState(env, getTableName()); 111 addChildProcedure(env.getAssignmentManager() 112 .createRoundRobinAssignProcedures(newRegions)); 113 setNextState(CreateTableState.CREATE_TABLE_UPDATE_DESC_CACHE); 114 break; 115 case CREATE_TABLE_UPDATE_DESC_CACHE: 116 // XXX: this stage should be named as set table enabled, as now we will cache the 117 // descriptor after writing fs layout. 118 setEnabledState(env, getTableName()); 119 setNextState(CreateTableState.CREATE_TABLE_POST_OPERATION); 120 break; 121 case CREATE_TABLE_POST_OPERATION: 122 postCreate(env); 123 return Flow.NO_MORE_STATE; 124 default: 125 throw new UnsupportedOperationException("unhandled state=" + state); 126 } 127 } catch (IOException e) { 128 if (isRollbackSupported(state)) { 129 setFailure("master-create-table", e); 130 } else { 131 LOG.warn("Retriable error trying to create table=" + getTableName() + " state=" + state, e); 132 } 133 } 134 return Flow.HAS_MORE_STATE; 135 } 136 137 @Override 138 protected void rollbackState(final MasterProcedureEnv env, final CreateTableState state) 139 throws IOException { 140 if (state == CreateTableState.CREATE_TABLE_PRE_OPERATION) { 141 // nothing to rollback, pre-create is just table-state checks. 142 // We can fail if the table does exist or the descriptor is malformed. 143 // TODO: coprocessor rollback semantic is still undefined. 144 if (hasException() /* avoid NPE */ && 145 getException().getCause().getClass() != TableExistsException.class) { 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) 199 throws IOException { 200 super.serializeStateData(serializer); 201 202 MasterProcedureProtos.CreateTableStateData.Builder state = 203 MasterProcedureProtos.CreateTableStateData.newBuilder() 204 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())) 205 .setTableSchema(ProtobufUtil.toTableSchema(tableDescriptor)); 206 if (newRegions != null) { 207 for (RegionInfo hri: newRegions) { 208 state.addRegionInfo(ProtobufUtil.toRegionInfo(hri)); 209 } 210 } 211 serializer.serialize(state.build()); 212 } 213 214 @Override 215 protected void deserializeStateData(ProcedureStateSerializer serializer) 216 throws IOException { 217 super.deserializeStateData(serializer); 218 219 MasterProcedureProtos.CreateTableStateData state = 220 serializer.deserialize(MasterProcedureProtos.CreateTableStateData.class); 221 setUser(MasterProcedureUtil.toUserInfo(state.getUserInfo())); 222 tableDescriptor = ProtobufUtil.toTableDescriptor(state.getTableSchema()); 223 if (state.getRegionInfoCount() == 0) { 224 newRegions = null; 225 } else { 226 newRegions = new ArrayList<>(state.getRegionInfoCount()); 227 for (HBaseProtos.RegionInfo hri: state.getRegionInfoList()) { 228 newRegions.add(ProtobufUtil.toRegionInfo(hri)); 229 } 230 } 231 } 232 233 @Override 234 protected boolean waitInitialized(MasterProcedureEnv env) { 235 if (getTableName().isSystemTable()) { 236 // Creating system table is part of the initialization, so only wait for meta loaded instead 237 // of waiting for master fully initialized. 238 return env.getAssignmentManager().waitMetaLoaded(this); 239 } 240 return super.waitInitialized(env); 241 } 242 243 private boolean prepareCreate(final MasterProcedureEnv env) throws IOException { 244 final TableName tableName = getTableName(); 245 if (env.getMasterServices().getTableDescriptors().exists(tableName)) { 246 setFailure("master-create-table", new TableExistsException(getTableName())); 247 return false; 248 } 249 250 // check that we have at least 1 CF 251 if (tableDescriptor.getColumnFamilyCount() == 0) { 252 setFailure("master-create-table", new DoNotRetryIOException("Table " + 253 getTableName().toString() + " should have at least one column family.")); 254 return false; 255 } 256 257 return true; 258 } 259 260 private void preCreate(final MasterProcedureEnv env) 261 throws IOException, InterruptedException { 262 if (!getTableName().isSystemTable()) { 263 ProcedureSyncWait.getMasterQuotaManager(env) 264 .checkNamespaceTableAndRegionQuota( 265 getTableName(), (newRegions != null ? newRegions.size() : 0)); 266 } 267 268 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 269 if (cpHost != null) { 270 final RegionInfo[] regions = newRegions == null ? null : 271 newRegions.toArray(new RegionInfo[newRegions.size()]); 272 cpHost.preCreateTableAction(tableDescriptor, regions, getUser()); 273 } 274 } 275 276 private void postCreate(final MasterProcedureEnv env) 277 throws IOException, InterruptedException { 278 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 279 if (cpHost != null) { 280 final RegionInfo[] regions = (newRegions == null) ? null : 281 newRegions.toArray(new RegionInfo[newRegions.size()]); 282 cpHost.postCompletedCreateTableAction(tableDescriptor, regions, getUser()); 283 } 284 } 285 286 protected interface CreateHdfsRegions { 287 List<RegionInfo> createHdfsRegions(final MasterProcedureEnv env, 288 final Path tableRootDir, final TableName tableName, 289 final List<RegionInfo> newRegions) throws IOException; 290 } 291 292 protected static List<RegionInfo> createFsLayout(final MasterProcedureEnv env, 293 final TableDescriptor tableDescriptor, final List<RegionInfo> newRegions) 294 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, 299 final List<RegionInfo> newRegions) throws IOException { 300 RegionInfo[] regions = newRegions != null ? 301 newRegions.toArray(new RegionInfo[newRegions.size()]) : null; 302 return ModifyRegionUtils.createRegions(env.getMasterConfiguration(), 303 tableRootDir, 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 final Path tempdir = mfs.getTempDir(); 313 314 // 1. Create Table Descriptor 315 // using a copy of descriptor, table will be created enabling first 316 final Path tempTableDir = CommonFSUtils.getTableDir(tempdir, tableDescriptor.getTableName()); 317 ((FSTableDescriptors)(env.getMasterServices().getTableDescriptors())) 318 .createTableDescriptorForTableDirectory(tempTableDir, tableDescriptor, false); 319 320 // 2. Create Regions 321 newRegions = hdfsRegionHandler.createHdfsRegions(env, tempdir, 322 tableDescriptor.getTableName(), newRegions); 323 324 // 3. Move Table temp directory to the hbase root location 325 moveTempDirectoryToHBaseRoot(env, tableDescriptor, tempTableDir); 326 327 return newRegions; 328 } 329 330 protected static void moveTempDirectoryToHBaseRoot( 331 final MasterProcedureEnv env, 332 final TableDescriptor tableDescriptor, 333 final Path tempTableDir) throws IOException { 334 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 335 final Path tableDir = 336 CommonFSUtils.getTableDir(mfs.getRootDir(), tableDescriptor.getTableName()); 337 FileSystem fs = mfs.getFileSystem(); 338 if (!fs.delete(tableDir, true) && fs.exists(tableDir)) { 339 throw new IOException("Couldn't delete " + tableDir); 340 } 341 if (!fs.rename(tempTableDir, tableDir)) { 342 throw new IOException("Unable to move table from temp=" + tempTableDir + 343 " to hbase root=" + tableDir); 344 } 345 } 346 347 protected static List<RegionInfo> addTableToMeta(final MasterProcedureEnv env, 348 final TableDescriptor tableDescriptor, final List<RegionInfo> regions) throws IOException { 349 assert (regions != null && regions.size() > 0) : "expected at least 1 region, got " + regions; 350 351 ProcedureSyncWait.waitMetaRegions(env); 352 353 // Add replicas if needed 354 // we need to create regions with replicaIds starting from 1 355 List<RegionInfo> newRegions = 356 RegionReplicaUtil.addReplicas(regions, 1, tableDescriptor.getRegionReplication()); 357 358 // Add regions to META 359 addRegionsToMeta(env, tableDescriptor, newRegions); 360 361 // Setup replication for region replicas if needed 362 if (tableDescriptor.getRegionReplication() > 1) { 363 try { 364 ServerRegionReplicaUtil.setupRegionReplicaReplication(env.getMasterServices()); 365 } catch (ReplicationException e) { 366 throw new HBaseIOException(e); 367 } 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() 376 .setTableState(tableName, 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() 383 .setTableState(tableName, 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, 391 final List<RegionInfo> regionInfos) throws IOException { 392 MetaTableAccessor.addRegionsToMeta(env.getMasterServices().getConnection(), 393 regionInfos, tableDescriptor.getRegionReplication()); 394 } 395 396 @Override 397 protected boolean shouldWaitClientAck(MasterProcedureEnv env) { 398 // system tables are created on bootstrap internally by the system 399 // the client does not know about this procedures. 400 return !getTableName().isSystemTable(); 401 } 402 403 RegionInfo getFirstRegionInfo() { 404 if (newRegions == null || newRegions.isEmpty()) { 405 return null; 406 } 407 return newRegions.get(0); 408 } 409}