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.HashSet; 023import java.util.List; 024import java.util.Set; 025import org.apache.hadoop.hbase.DoNotRetryIOException; 026import org.apache.hadoop.hbase.HBaseIOException; 027import org.apache.hadoop.hbase.HConstants; 028import org.apache.hadoop.hbase.MetaTableAccessor; 029import org.apache.hadoop.hbase.TableName; 030import org.apache.hadoop.hbase.TableNotDisabledException; 031import org.apache.hadoop.hbase.TableNotFoundException; 032import org.apache.hadoop.hbase.client.Connection; 033import org.apache.hadoop.hbase.client.RegionInfo; 034import org.apache.hadoop.hbase.client.Result; 035import org.apache.hadoop.hbase.client.ResultScanner; 036import org.apache.hadoop.hbase.client.Scan; 037import org.apache.hadoop.hbase.client.Table; 038import org.apache.hadoop.hbase.client.TableDescriptor; 039import org.apache.hadoop.hbase.client.TableState; 040import org.apache.hadoop.hbase.master.MasterCoprocessorHost; 041import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer; 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.hadoop.hbase.shaded.protobuf.ProtobufUtil; 048import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos; 049import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.ModifyTableState; 050 051@InterfaceAudience.Private 052public class ModifyTableProcedure 053 extends AbstractStateMachineTableProcedure<ModifyTableState> { 054 private static final Logger LOG = LoggerFactory.getLogger(ModifyTableProcedure.class); 055 056 private TableDescriptor unmodifiedTableDescriptor = null; 057 private TableDescriptor modifiedTableDescriptor; 058 private boolean deleteColumnFamilyInModify; 059 060 public ModifyTableProcedure() { 061 super(); 062 initilize(); 063 } 064 065 public ModifyTableProcedure(final MasterProcedureEnv env, final TableDescriptor htd) 066 throws HBaseIOException { 067 this(env, htd, null); 068 } 069 070 public ModifyTableProcedure(final MasterProcedureEnv env, final TableDescriptor htd, 071 final ProcedurePrepareLatch latch) 072 throws HBaseIOException { 073 super(env, latch); 074 initilize(); 075 this.modifiedTableDescriptor = htd; 076 preflightChecks(env, null/*No table checks; if changing peers, table can be online*/); 077 } 078 079 private void initilize() { 080 this.unmodifiedTableDescriptor = null; 081 this.deleteColumnFamilyInModify = false; 082 } 083 084 @Override 085 protected Flow executeFromState(final MasterProcedureEnv env, final ModifyTableState state) 086 throws InterruptedException { 087 LOG.trace("{} execute state={}", this, state); 088 try { 089 switch (state) { 090 case MODIFY_TABLE_PREPARE: 091 prepareModify(env); 092 setNextState(ModifyTableState.MODIFY_TABLE_PRE_OPERATION); 093 break; 094 case MODIFY_TABLE_PRE_OPERATION: 095 preModify(env, state); 096 setNextState(ModifyTableState.MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR); 097 break; 098 case MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR: 099 updateTableDescriptor(env); 100 setNextState(ModifyTableState.MODIFY_TABLE_REMOVE_REPLICA_COLUMN); 101 break; 102 case MODIFY_TABLE_REMOVE_REPLICA_COLUMN: 103 updateReplicaColumnsIfNeeded(env, unmodifiedTableDescriptor, modifiedTableDescriptor); 104 if (deleteColumnFamilyInModify) { 105 setNextState(ModifyTableState.MODIFY_TABLE_DELETE_FS_LAYOUT); 106 } else { 107 setNextState(ModifyTableState.MODIFY_TABLE_POST_OPERATION); 108 } 109 break; 110 case MODIFY_TABLE_DELETE_FS_LAYOUT: 111 deleteFromFs(env, unmodifiedTableDescriptor, modifiedTableDescriptor); 112 setNextState(ModifyTableState.MODIFY_TABLE_POST_OPERATION); 113 break; 114 case MODIFY_TABLE_POST_OPERATION: 115 postModify(env, state); 116 setNextState(ModifyTableState.MODIFY_TABLE_REOPEN_ALL_REGIONS); 117 break; 118 case MODIFY_TABLE_REOPEN_ALL_REGIONS: 119 if (env.getAssignmentManager().isTableEnabled(getTableName())) { 120 addChildProcedure(new ReopenTableRegionsProcedure(getTableName())); 121 } 122 return Flow.NO_MORE_STATE; 123 default: 124 throw new UnsupportedOperationException("unhandled state=" + state); 125 } 126 } catch (IOException e) { 127 if (isRollbackSupported(state)) { 128 setFailure("master-modify-table", e); 129 } else { 130 LOG.warn("Retriable error trying to modify table={} (in state={})", getTableName(), state, 131 e); 132 } 133 } 134 return Flow.HAS_MORE_STATE; 135 } 136 137 @Override 138 protected void rollbackState(final MasterProcedureEnv env, final ModifyTableState state) 139 throws IOException { 140 if (state == ModifyTableState.MODIFY_TABLE_PREPARE || 141 state == ModifyTableState.MODIFY_TABLE_PRE_OPERATION) { 142 // nothing to rollback, pre-modify is just checks. 143 // TODO: coprocessor rollback semantic is still undefined. 144 return; 145 } 146 147 // The delete 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 ModifyTableState state) { 153 switch (state) { 154 case MODIFY_TABLE_PRE_OPERATION: 155 case MODIFY_TABLE_PREPARE: 156 return true; 157 default: 158 return false; 159 } 160 } 161 162 @Override 163 protected void completionCleanup(final MasterProcedureEnv env) { 164 releaseSyncLatch(); 165 } 166 167 @Override 168 protected ModifyTableState getState(final int stateId) { 169 return ModifyTableState.forNumber(stateId); 170 } 171 172 @Override 173 protected int getStateId(final ModifyTableState state) { 174 return state.getNumber(); 175 } 176 177 @Override 178 protected ModifyTableState getInitialState() { 179 return ModifyTableState.MODIFY_TABLE_PREPARE; 180 } 181 182 @Override 183 protected void serializeStateData(ProcedureStateSerializer serializer) 184 throws IOException { 185 super.serializeStateData(serializer); 186 187 MasterProcedureProtos.ModifyTableStateData.Builder modifyTableMsg = 188 MasterProcedureProtos.ModifyTableStateData.newBuilder() 189 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())) 190 .setModifiedTableSchema(ProtobufUtil.toTableSchema(modifiedTableDescriptor)) 191 .setDeleteColumnFamilyInModify(deleteColumnFamilyInModify); 192 193 if (unmodifiedTableDescriptor != null) { 194 modifyTableMsg 195 .setUnmodifiedTableSchema(ProtobufUtil.toTableSchema(unmodifiedTableDescriptor)); 196 } 197 198 serializer.serialize(modifyTableMsg.build()); 199 } 200 201 @Override 202 protected void deserializeStateData(ProcedureStateSerializer serializer) 203 throws IOException { 204 super.deserializeStateData(serializer); 205 206 MasterProcedureProtos.ModifyTableStateData modifyTableMsg = 207 serializer.deserialize(MasterProcedureProtos.ModifyTableStateData.class); 208 setUser(MasterProcedureUtil.toUserInfo(modifyTableMsg.getUserInfo())); 209 modifiedTableDescriptor = ProtobufUtil.toTableDescriptor(modifyTableMsg.getModifiedTableSchema()); 210 deleteColumnFamilyInModify = modifyTableMsg.getDeleteColumnFamilyInModify(); 211 212 if (modifyTableMsg.hasUnmodifiedTableSchema()) { 213 unmodifiedTableDescriptor = 214 ProtobufUtil.toTableDescriptor(modifyTableMsg.getUnmodifiedTableSchema()); 215 } 216 } 217 218 @Override 219 public TableName getTableName() { 220 return modifiedTableDescriptor.getTableName(); 221 } 222 223 @Override 224 public TableOperationType getTableOperationType() { 225 return TableOperationType.EDIT; 226 } 227 228 /** 229 * Check conditions before any real action of modifying a table. 230 * @param env MasterProcedureEnv 231 * @throws IOException 232 */ 233 private void prepareModify(final MasterProcedureEnv env) throws IOException { 234 // Checks whether the table exists 235 if (!MetaTableAccessor.tableExists(env.getMasterServices().getConnection(), getTableName())) { 236 throw new TableNotFoundException(getTableName()); 237 } 238 239 // check that we have at least 1 CF 240 if (modifiedTableDescriptor.getColumnFamilyCount() == 0) { 241 throw new DoNotRetryIOException("Table " + getTableName().toString() + 242 " should have at least one column family."); 243 } 244 245 // In order to update the descriptor, we need to retrieve the old descriptor for comparison. 246 this.unmodifiedTableDescriptor = 247 env.getMasterServices().getTableDescriptors().get(getTableName()); 248 249 if (env.getMasterServices().getTableStateManager() 250 .isTableState(getTableName(), TableState.State.ENABLED)) { 251 if (modifiedTableDescriptor.getRegionReplication() != unmodifiedTableDescriptor 252 .getRegionReplication()) { 253 throw new TableNotDisabledException( 254 "REGION_REPLICATION change is not supported for enabled tables"); 255 } 256 } 257 258 // Find out whether all column families in unmodifiedTableDescriptor also exists in 259 // the modifiedTableDescriptor. This is to determine whether we are safe to rollback. 260 final Set<byte[]> oldFamilies = unmodifiedTableDescriptor.getColumnFamilyNames(); 261 final Set<byte[]> newFamilies = modifiedTableDescriptor.getColumnFamilyNames(); 262 for (byte[] familyName : oldFamilies) { 263 if (!newFamilies.contains(familyName)) { 264 this.deleteColumnFamilyInModify = true; 265 break; 266 } 267 } 268 } 269 270 /** 271 * Action before modifying table. 272 * @param env MasterProcedureEnv 273 * @param state the procedure state 274 * @throws IOException 275 * @throws InterruptedException 276 */ 277 private void preModify(final MasterProcedureEnv env, final ModifyTableState state) 278 throws IOException, InterruptedException { 279 runCoprocessorAction(env, state); 280 } 281 282 /** 283 * Update descriptor 284 * @param env MasterProcedureEnv 285 * @throws IOException 286 **/ 287 private void updateTableDescriptor(final MasterProcedureEnv env) throws IOException { 288 env.getMasterServices().getTableDescriptors().add(modifiedTableDescriptor); 289 } 290 291 /** 292 * Removes from hdfs the families that are not longer present in the new table descriptor. 293 * @param env MasterProcedureEnv 294 * @throws IOException 295 */ 296 private void deleteFromFs(final MasterProcedureEnv env, 297 final TableDescriptor oldTableDescriptor, final TableDescriptor newTableDescriptor) 298 throws IOException { 299 final Set<byte[]> oldFamilies = oldTableDescriptor.getColumnFamilyNames(); 300 final Set<byte[]> newFamilies = newTableDescriptor.getColumnFamilyNames(); 301 for (byte[] familyName : oldFamilies) { 302 if (!newFamilies.contains(familyName)) { 303 MasterDDLOperationHelper.deleteColumnFamilyFromFileSystem( 304 env, 305 getTableName(), 306 getRegionInfoList(env), 307 familyName, oldTableDescriptor.getColumnFamily(familyName).isMobEnabled()); 308 } 309 } 310 } 311 312 /** 313 * update replica column families if necessary. 314 * @param env MasterProcedureEnv 315 * @throws IOException 316 */ 317 private void updateReplicaColumnsIfNeeded( 318 final MasterProcedureEnv env, 319 final TableDescriptor oldTableDescriptor, 320 final TableDescriptor newTableDescriptor) throws IOException { 321 final int oldReplicaCount = oldTableDescriptor.getRegionReplication(); 322 final int newReplicaCount = newTableDescriptor.getRegionReplication(); 323 324 if (newReplicaCount < oldReplicaCount) { 325 Set<byte[]> tableRows = new HashSet<>(); 326 Connection connection = env.getMasterServices().getConnection(); 327 Scan scan = MetaTableAccessor.getScanForTableName(connection, getTableName()); 328 scan.addColumn(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER); 329 330 try (Table metaTable = connection.getTable(TableName.META_TABLE_NAME)) { 331 ResultScanner resScanner = metaTable.getScanner(scan); 332 for (Result result : resScanner) { 333 tableRows.add(result.getRow()); 334 } 335 MetaTableAccessor.removeRegionReplicasFromMeta( 336 tableRows, 337 newReplicaCount, 338 oldReplicaCount - newReplicaCount, 339 connection); 340 } 341 } 342 if (newReplicaCount > oldReplicaCount) { 343 Connection connection = env.getMasterServices().getConnection(); 344 // Get the existing table regions 345 List<RegionInfo> existingTableRegions = 346 MetaTableAccessor.getTableRegions(connection, getTableName()); 347 // add all the new entries to the meta table 348 addRegionsToMeta(env, newTableDescriptor, existingTableRegions); 349 if (oldReplicaCount <= 1) { 350 // The table has been newly enabled for replica. So check if we need to setup 351 // region replication 352 ServerRegionReplicaUtil.setupRegionReplicaReplication(env.getMasterConfiguration()); 353 } 354 } 355 } 356 357 private static void addRegionsToMeta(final MasterProcedureEnv env, 358 final TableDescriptor tableDescriptor, final List<RegionInfo> regionInfos) 359 throws IOException { 360 MetaTableAccessor.addRegionsToMeta(env.getMasterServices().getConnection(), regionInfos, 361 tableDescriptor.getRegionReplication()); 362 } 363 /** 364 * Action after modifying table. 365 * @param env MasterProcedureEnv 366 * @param state the procedure state 367 * @throws IOException 368 * @throws InterruptedException 369 */ 370 private void postModify(final MasterProcedureEnv env, final ModifyTableState state) 371 throws IOException, InterruptedException { 372 runCoprocessorAction(env, state); 373 } 374 375 /** 376 * Coprocessor Action. 377 * @param env MasterProcedureEnv 378 * @param state the procedure state 379 * @throws IOException 380 * @throws InterruptedException 381 */ 382 private void runCoprocessorAction(final MasterProcedureEnv env, final ModifyTableState state) 383 throws IOException, InterruptedException { 384 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 385 if (cpHost != null) { 386 switch (state) { 387 case MODIFY_TABLE_PRE_OPERATION: 388 cpHost.preModifyTableAction(getTableName(), unmodifiedTableDescriptor, 389 modifiedTableDescriptor, getUser()); 390 break; 391 case MODIFY_TABLE_POST_OPERATION: 392 cpHost.postCompletedModifyTableAction(getTableName(), unmodifiedTableDescriptor, 393 modifiedTableDescriptor,getUser()); 394 break; 395 default: 396 throw new UnsupportedOperationException(this + " unhandled state=" + state); 397 } 398 } 399 } 400 401 /** 402 * Fetches all Regions for a table. Cache the result of this method if you need to use it multiple 403 * times. Be aware that it may change over in between calls to this procedure. 404 */ 405 private List<RegionInfo> getRegionInfoList(final MasterProcedureEnv env) throws IOException { 406 return env.getAssignmentManager().getRegionStates().getRegionsOfTable(getTableName()); 407 } 408}