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.HashMap; 024import java.util.Iterator; 025import java.util.List; 026import java.util.Map; 027 028import org.apache.hadoop.fs.FileSystem; 029import org.apache.hadoop.fs.Path; 030import org.apache.hadoop.hbase.DoNotRetryIOException; 031import org.apache.hadoop.hbase.HBaseIOException; 032import org.apache.hadoop.hbase.MetaTableAccessor; 033import org.apache.hadoop.hbase.TableName; 034import org.apache.hadoop.hbase.TableNotFoundException; 035import org.apache.hadoop.hbase.client.Connection; 036import org.apache.hadoop.hbase.client.RegionInfo; 037import org.apache.hadoop.hbase.client.RegionReplicaUtil; 038import org.apache.hadoop.hbase.client.TableDescriptor; 039import org.apache.hadoop.hbase.errorhandling.ForeignException; 040import org.apache.hadoop.hbase.errorhandling.ForeignExceptionDispatcher; 041import org.apache.hadoop.hbase.favored.FavoredNodesManager; 042import org.apache.hadoop.hbase.master.MasterFileSystem; 043import org.apache.hadoop.hbase.master.MetricsSnapshot; 044import org.apache.hadoop.hbase.master.RegionState; 045import org.apache.hadoop.hbase.master.assignment.AssignmentManager; 046import org.apache.hadoop.hbase.monitoring.MonitoredTask; 047import org.apache.hadoop.hbase.monitoring.TaskMonitor; 048import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer; 049import org.apache.hadoop.hbase.snapshot.ClientSnapshotDescriptionUtils; 050import org.apache.hadoop.hbase.snapshot.RestoreSnapshotHelper; 051import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils; 052import org.apache.hadoop.hbase.snapshot.SnapshotManifest; 053import org.apache.hadoop.hbase.util.Pair; 054import org.apache.yetus.audience.InterfaceAudience; 055import org.slf4j.Logger; 056import org.slf4j.LoggerFactory; 057import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 058import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; 059import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos; 060import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.RestoreSnapshotState; 061import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription; 062 063@InterfaceAudience.Private 064public class RestoreSnapshotProcedure 065 extends AbstractStateMachineTableProcedure<RestoreSnapshotState> { 066 private static final Logger LOG = LoggerFactory.getLogger(RestoreSnapshotProcedure.class); 067 068 private TableDescriptor modifiedTableDescriptor; 069 private List<RegionInfo> regionsToRestore = null; 070 private List<RegionInfo> regionsToRemove = null; 071 private List<RegionInfo> regionsToAdd = null; 072 private Map<String, Pair<String, String>> parentsToChildrenPairMap = new HashMap<>(); 073 074 private SnapshotDescription snapshot; 075 private boolean restoreAcl; 076 077 // Monitor 078 private MonitoredTask monitorStatus = null; 079 080 private Boolean traceEnabled = null; 081 082 /** 083 * Constructor (for failover) 084 */ 085 public RestoreSnapshotProcedure() { 086 } 087 088 public RestoreSnapshotProcedure(final MasterProcedureEnv env, 089 final TableDescriptor tableDescriptor, final SnapshotDescription snapshot) 090 throws HBaseIOException { 091 this(env, tableDescriptor, snapshot, false); 092 } 093 /** 094 * Constructor 095 * @param env MasterProcedureEnv 096 * @param tableDescriptor the table to operate on 097 * @param snapshot snapshot to restore from 098 * @throws IOException 099 */ 100 public RestoreSnapshotProcedure( 101 final MasterProcedureEnv env, 102 final TableDescriptor tableDescriptor, 103 final SnapshotDescription snapshot, 104 final boolean restoreAcl) 105 throws HBaseIOException { 106 super(env); 107 // This is the new schema we are going to write out as this modification. 108 this.modifiedTableDescriptor = tableDescriptor; 109 preflightChecks(env, null/*Table can be online when restore is called?*/); 110 // Snapshot information 111 this.snapshot = snapshot; 112 this.restoreAcl = restoreAcl; 113 114 // Monitor 115 getMonitorStatus(); 116 } 117 118 /** 119 * Set up monitor status if it is not created. 120 */ 121 private MonitoredTask getMonitorStatus() { 122 if (monitorStatus == null) { 123 monitorStatus = TaskMonitor.get().createStatus("Restoring snapshot '" + snapshot.getName() 124 + "' to table " + getTableName()); 125 } 126 return monitorStatus; 127 } 128 129 @Override 130 protected Flow executeFromState(final MasterProcedureEnv env, final RestoreSnapshotState state) 131 throws InterruptedException { 132 if (isTraceEnabled()) { 133 LOG.trace(this + " execute state=" + state); 134 } 135 136 // Make sure that the monitor status is set up 137 getMonitorStatus(); 138 139 try { 140 switch (state) { 141 case RESTORE_SNAPSHOT_PRE_OPERATION: 142 // Verify if we can restore the table 143 prepareRestore(env); 144 setNextState(RestoreSnapshotState.RESTORE_SNAPSHOT_UPDATE_TABLE_DESCRIPTOR); 145 break; 146 case RESTORE_SNAPSHOT_UPDATE_TABLE_DESCRIPTOR: 147 updateTableDescriptor(env); 148 setNextState(RestoreSnapshotState.RESTORE_SNAPSHOT_WRITE_FS_LAYOUT); 149 break; 150 case RESTORE_SNAPSHOT_WRITE_FS_LAYOUT: 151 restoreSnapshot(env); 152 setNextState(RestoreSnapshotState.RESTORE_SNAPSHOT_UPDATE_META); 153 break; 154 case RESTORE_SNAPSHOT_UPDATE_META: 155 updateMETA(env); 156 setNextState(RestoreSnapshotState.RESTORE_SNAPSHOT_RESTORE_ACL); 157 break; 158 case RESTORE_SNAPSHOT_RESTORE_ACL: 159 restoreSnapshotAcl(env); 160 return Flow.NO_MORE_STATE; 161 default: 162 throw new UnsupportedOperationException("unhandled state=" + state); 163 } 164 } catch (IOException e) { 165 if (isRollbackSupported(state)) { 166 setFailure("master-restore-snapshot", e); 167 } else { 168 LOG.warn("Retriable error trying to restore snapshot=" + snapshot.getName() + 169 " to table=" + getTableName() + " (in state=" + state + ")", e); 170 } 171 } 172 return Flow.HAS_MORE_STATE; 173 } 174 175 @Override 176 protected void rollbackState(final MasterProcedureEnv env, final RestoreSnapshotState state) 177 throws IOException { 178 if (state == RestoreSnapshotState.RESTORE_SNAPSHOT_PRE_OPERATION) { 179 // nothing to rollback 180 return; 181 } 182 183 // The restore snapshot doesn't have a rollback. The execution will succeed, at some point. 184 throw new UnsupportedOperationException("unhandled state=" + state); 185 } 186 187 @Override 188 protected boolean isRollbackSupported(final RestoreSnapshotState state) { 189 switch (state) { 190 case RESTORE_SNAPSHOT_PRE_OPERATION: 191 return true; 192 default: 193 return false; 194 } 195 } 196 197 @Override 198 protected RestoreSnapshotState getState(final int stateId) { 199 return RestoreSnapshotState.valueOf(stateId); 200 } 201 202 @Override 203 protected int getStateId(final RestoreSnapshotState state) { 204 return state.getNumber(); 205 } 206 207 @Override 208 protected RestoreSnapshotState getInitialState() { 209 return RestoreSnapshotState.RESTORE_SNAPSHOT_PRE_OPERATION; 210 } 211 212 @Override 213 public TableName getTableName() { 214 return modifiedTableDescriptor.getTableName(); 215 } 216 217 @Override 218 public TableOperationType getTableOperationType() { 219 return TableOperationType.EDIT; // Restore is modifying a table 220 } 221 222 @Override 223 public boolean abort(final MasterProcedureEnv env) { 224 // TODO: We may be able to abort if the procedure is not started yet. 225 return false; 226 } 227 228 @Override 229 public void toStringClassDetails(StringBuilder sb) { 230 sb.append(getClass().getSimpleName()); 231 sb.append(" (table="); 232 sb.append(getTableName()); 233 sb.append(" snapshot="); 234 sb.append(snapshot); 235 sb.append(")"); 236 } 237 238 @Override 239 protected void serializeStateData(ProcedureStateSerializer serializer) 240 throws IOException { 241 super.serializeStateData(serializer); 242 243 MasterProcedureProtos.RestoreSnapshotStateData.Builder restoreSnapshotMsg = 244 MasterProcedureProtos.RestoreSnapshotStateData.newBuilder() 245 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())) 246 .setSnapshot(this.snapshot) 247 .setModifiedTableSchema(ProtobufUtil.toTableSchema(modifiedTableDescriptor)); 248 249 if (regionsToRestore != null) { 250 for (RegionInfo hri: regionsToRestore) { 251 restoreSnapshotMsg.addRegionInfoForRestore(ProtobufUtil.toRegionInfo(hri)); 252 } 253 } 254 if (regionsToRemove != null) { 255 for (RegionInfo hri: regionsToRemove) { 256 restoreSnapshotMsg.addRegionInfoForRemove(ProtobufUtil.toRegionInfo(hri)); 257 } 258 } 259 if (regionsToAdd != null) { 260 for (RegionInfo hri: regionsToAdd) { 261 restoreSnapshotMsg.addRegionInfoForAdd(ProtobufUtil.toRegionInfo(hri)); 262 } 263 } 264 if (!parentsToChildrenPairMap.isEmpty()) { 265 final Iterator<Map.Entry<String, Pair<String, String>>> it = 266 parentsToChildrenPairMap.entrySet().iterator(); 267 while (it.hasNext()) { 268 final Map.Entry<String, Pair<String, String>> entry = it.next(); 269 270 MasterProcedureProtos.RestoreParentToChildRegionsPair.Builder parentToChildrenPair = 271 MasterProcedureProtos.RestoreParentToChildRegionsPair.newBuilder() 272 .setParentRegionName(entry.getKey()) 273 .setChild1RegionName(entry.getValue().getFirst()) 274 .setChild2RegionName(entry.getValue().getSecond()); 275 restoreSnapshotMsg.addParentToChildRegionsPairList (parentToChildrenPair); 276 } 277 } 278 serializer.serialize(restoreSnapshotMsg.build()); 279 } 280 281 @Override 282 protected void deserializeStateData(ProcedureStateSerializer serializer) 283 throws IOException { 284 super.deserializeStateData(serializer); 285 286 MasterProcedureProtos.RestoreSnapshotStateData restoreSnapshotMsg = 287 serializer.deserialize(MasterProcedureProtos.RestoreSnapshotStateData.class); 288 setUser(MasterProcedureUtil.toUserInfo(restoreSnapshotMsg.getUserInfo())); 289 snapshot = restoreSnapshotMsg.getSnapshot(); 290 modifiedTableDescriptor = 291 ProtobufUtil.toTableDescriptor(restoreSnapshotMsg.getModifiedTableSchema()); 292 293 if (restoreSnapshotMsg.getRegionInfoForRestoreCount() == 0) { 294 regionsToRestore = null; 295 } else { 296 regionsToRestore = new ArrayList<>(restoreSnapshotMsg.getRegionInfoForRestoreCount()); 297 for (HBaseProtos.RegionInfo hri: restoreSnapshotMsg.getRegionInfoForRestoreList()) { 298 regionsToRestore.add(ProtobufUtil.toRegionInfo(hri)); 299 } 300 } 301 if (restoreSnapshotMsg.getRegionInfoForRemoveCount() == 0) { 302 regionsToRemove = null; 303 } else { 304 regionsToRemove = new ArrayList<>(restoreSnapshotMsg.getRegionInfoForRemoveCount()); 305 for (HBaseProtos.RegionInfo hri: restoreSnapshotMsg.getRegionInfoForRemoveList()) { 306 regionsToRemove.add(ProtobufUtil.toRegionInfo(hri)); 307 } 308 } 309 if (restoreSnapshotMsg.getRegionInfoForAddCount() == 0) { 310 regionsToAdd = null; 311 } else { 312 regionsToAdd = new ArrayList<>(restoreSnapshotMsg.getRegionInfoForAddCount()); 313 for (HBaseProtos.RegionInfo hri: restoreSnapshotMsg.getRegionInfoForAddList()) { 314 regionsToAdd.add(ProtobufUtil.toRegionInfo(hri)); 315 } 316 } 317 if (restoreSnapshotMsg.getParentToChildRegionsPairListCount() > 0) { 318 for (MasterProcedureProtos.RestoreParentToChildRegionsPair parentToChildrenPair: 319 restoreSnapshotMsg.getParentToChildRegionsPairListList()) { 320 parentsToChildrenPairMap.put( 321 parentToChildrenPair.getParentRegionName(), 322 new Pair<>( 323 parentToChildrenPair.getChild1RegionName(), 324 parentToChildrenPair.getChild2RegionName())); 325 } 326 } 327 } 328 329 /** 330 * Action before any real action of restoring from snapshot. 331 * @param env MasterProcedureEnv 332 * @throws IOException 333 */ 334 private void prepareRestore(final MasterProcedureEnv env) throws IOException { 335 final TableName tableName = getTableName(); 336 // Checks whether the table exists 337 if (!MetaTableAccessor.tableExists(env.getMasterServices().getConnection(), tableName)) { 338 throw new TableNotFoundException(tableName); 339 } 340 341 // Check whether table is disabled. 342 env.getMasterServices().checkTableModifiable(tableName); 343 344 // Check that we have at least 1 CF 345 if (modifiedTableDescriptor.getColumnFamilyCount() == 0) { 346 throw new DoNotRetryIOException("Table " + getTableName().toString() + 347 " should have at least one column family."); 348 } 349 350 if (!getTableName().isSystemTable()) { 351 // Table already exist. Check and update the region quota for this table namespace. 352 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 353 SnapshotManifest manifest = SnapshotManifest.open( 354 env.getMasterConfiguration(), 355 mfs.getFileSystem(), 356 SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshot, mfs.getRootDir()), 357 snapshot); 358 int snapshotRegionCount = manifest.getRegionManifestsMap().size(); 359 int tableRegionCount = 360 ProcedureSyncWait.getMasterQuotaManager(env).getRegionCountOfTable(tableName); 361 362 if (snapshotRegionCount > 0 && tableRegionCount != snapshotRegionCount) { 363 ProcedureSyncWait.getMasterQuotaManager(env).checkAndUpdateNamespaceRegionQuota( 364 tableName, snapshotRegionCount); 365 } 366 } 367 } 368 369 /** 370 * Update descriptor 371 * @param env MasterProcedureEnv 372 * @throws IOException 373 **/ 374 private void updateTableDescriptor(final MasterProcedureEnv env) throws IOException { 375 env.getMasterServices().getTableDescriptors().add(modifiedTableDescriptor); 376 } 377 378 /** 379 * Execute the on-disk Restore 380 * @param env MasterProcedureEnv 381 * @throws IOException 382 **/ 383 private void restoreSnapshot(final MasterProcedureEnv env) throws IOException { 384 MasterFileSystem fileSystemManager = env.getMasterServices().getMasterFileSystem(); 385 FileSystem fs = fileSystemManager.getFileSystem(); 386 Path rootDir = fileSystemManager.getRootDir(); 387 final ForeignExceptionDispatcher monitorException = new ForeignExceptionDispatcher(); 388 389 LOG.info("Starting restore snapshot=" + ClientSnapshotDescriptionUtils.toString(snapshot)); 390 try { 391 Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshot, rootDir); 392 SnapshotManifest manifest = SnapshotManifest.open( 393 env.getMasterServices().getConfiguration(), fs, snapshotDir, snapshot); 394 RestoreSnapshotHelper restoreHelper = new RestoreSnapshotHelper( 395 env.getMasterServices().getConfiguration(), 396 fs, 397 manifest, 398 modifiedTableDescriptor, 399 rootDir, 400 monitorException, 401 getMonitorStatus()); 402 403 RestoreSnapshotHelper.RestoreMetaChanges metaChanges = restoreHelper.restoreHdfsRegions(); 404 regionsToRestore = metaChanges.getRegionsToRestore(); 405 regionsToRemove = metaChanges.getRegionsToRemove(); 406 regionsToAdd = metaChanges.getRegionsToAdd(); 407 parentsToChildrenPairMap = metaChanges.getParentToChildrenPairMap(); 408 } catch (IOException e) { 409 String msg = "restore snapshot=" + ClientSnapshotDescriptionUtils.toString(snapshot) 410 + " failed in on-disk restore. Try re-running the restore command."; 411 LOG.error(msg, e); 412 monitorException.receive( 413 new ForeignException(env.getMasterServices().getServerName().toString(), e)); 414 throw new IOException(msg, e); 415 } 416 } 417 418 /** 419 * Apply changes to hbase:meta 420 * @param env MasterProcedureEnv 421 * @throws IOException 422 **/ 423 private void updateMETA(final MasterProcedureEnv env) throws IOException { 424 try { 425 Connection conn = env.getMasterServices().getConnection(); 426 int regionReplication = modifiedTableDescriptor.getRegionReplication(); 427 428 // 1. Prepare to restore 429 getMonitorStatus().setStatus("Preparing to restore each region"); 430 431 // 2. Applies changes to hbase:meta and in-memory states 432 // (2.1). Removes the current set of regions from META and in-memory states 433 // 434 // By removing also the regions to restore (the ones present both in the snapshot 435 // and in the current state) we ensure that no extra fields are present in META 436 // e.g. with a simple add addRegionToMeta() the splitA and splitB attributes 437 // not overwritten/removed, so you end up with old informations 438 // that are not correct after the restore. 439 if (regionsToRemove != null) { 440 MetaTableAccessor.deleteRegionInfos(conn, regionsToRemove); 441 deleteRegionsFromInMemoryStates(regionsToRemove, env, regionReplication); 442 } 443 444 // (2.2). Add the new set of regions to META and in-memory states 445 // 446 // At this point the old regions are no longer present in META. 447 // and the set of regions present in the snapshot will be written to META. 448 // All the information in hbase:meta are coming from the .regioninfo of each region present 449 // in the snapshot folder. 450 if (regionsToAdd != null) { 451 MetaTableAccessor.addRegionsToMeta(conn, regionsToAdd, regionReplication); 452 addRegionsToInMemoryStates(regionsToAdd, env, regionReplication); 453 } 454 455 if (regionsToRestore != null) { 456 MetaTableAccessor.overwriteRegions(conn, regionsToRestore, regionReplication); 457 458 deleteRegionsFromInMemoryStates(regionsToRestore, env, regionReplication); 459 addRegionsToInMemoryStates(regionsToRestore, env, regionReplication); 460 } 461 462 RestoreSnapshotHelper.RestoreMetaChanges metaChanges = 463 new RestoreSnapshotHelper.RestoreMetaChanges( 464 modifiedTableDescriptor, parentsToChildrenPairMap); 465 metaChanges.updateMetaParentRegions(conn, regionsToAdd); 466 467 // At this point the restore is complete. 468 LOG.info("Restore snapshot=" + ClientSnapshotDescriptionUtils.toString(snapshot) + 469 " on table=" + getTableName() + " completed!"); 470 } catch (IOException e) { 471 final ForeignExceptionDispatcher monitorException = new ForeignExceptionDispatcher(); 472 String msg = "restore snapshot=" + ClientSnapshotDescriptionUtils.toString(snapshot) 473 + " failed in meta update. Try re-running the restore command."; 474 LOG.error(msg, e); 475 monitorException.receive( 476 new ForeignException(env.getMasterServices().getServerName().toString(), e)); 477 throw new IOException(msg, e); 478 } 479 480 monitorStatus.markComplete("Restore snapshot '"+ snapshot.getName() +"'!"); 481 MetricsSnapshot metricsSnapshot = new MetricsSnapshot(); 482 metricsSnapshot.addSnapshotRestore( 483 monitorStatus.getCompletionTimestamp() - monitorStatus.getStartTime()); 484 } 485 486 /** 487 * Delete regions from in-memory states 488 * @param regionInfos regions to delete 489 * @param env MasterProcedureEnv 490 * @param regionReplication the number of region replications 491 */ 492 private void deleteRegionsFromInMemoryStates(List<RegionInfo> regionInfos, 493 MasterProcedureEnv env, int regionReplication) { 494 FavoredNodesManager fnm = env.getMasterServices().getFavoredNodesManager(); 495 496 env.getAssignmentManager().getRegionStates().deleteRegions(regionInfos); 497 env.getMasterServices().getServerManager().removeRegions(regionInfos); 498 if (fnm != null) { 499 fnm.deleteFavoredNodesForRegions(regionInfos); 500 } 501 502 // For region replicas 503 if (regionReplication > 1) { 504 for (RegionInfo regionInfo : regionInfos) { 505 for (int i = 1; i < regionReplication; i++) { 506 RegionInfo regionInfoForReplica = 507 RegionReplicaUtil.getRegionInfoForReplica(regionInfo, i); 508 env.getAssignmentManager().getRegionStates().deleteRegion(regionInfoForReplica); 509 env.getMasterServices().getServerManager().removeRegion(regionInfoForReplica); 510 if (fnm != null) { 511 fnm.deleteFavoredNodesForRegion(regionInfoForReplica); 512 } 513 } 514 } 515 } 516 } 517 518 /** 519 * Add regions to in-memory states 520 * @param regionInfos regions to add 521 * @param env MasterProcedureEnv 522 * @param regionReplication the number of region replications 523 */ 524 private void addRegionsToInMemoryStates(List<RegionInfo> regionInfos, MasterProcedureEnv env, 525 int regionReplication) { 526 AssignmentManager am = env.getAssignmentManager(); 527 for (RegionInfo regionInfo : regionInfos) { 528 if (regionInfo.isSplit()) { 529 am.getRegionStates().updateRegionState(regionInfo, RegionState.State.SPLIT); 530 } else { 531 am.getRegionStates().updateRegionState(regionInfo, RegionState.State.CLOSED); 532 533 // For region replicas 534 for (int i = 1; i < regionReplication; i++) { 535 RegionInfo regionInfoForReplica = 536 RegionReplicaUtil.getRegionInfoForReplica(regionInfo, i); 537 am.getRegionStates().updateRegionState(regionInfoForReplica, RegionState.State.CLOSED); 538 } 539 } 540 } 541 } 542 543 private void restoreSnapshotAcl(final MasterProcedureEnv env) throws IOException { 544 if (restoreAcl && snapshot.hasUsersAndPermissions() && snapshot.getUsersAndPermissions() != null 545 && SnapshotDescriptionUtils 546 .isSecurityAvailable(env.getMasterServices().getConfiguration())) { 547 // restore acl of snapshot to table. 548 RestoreSnapshotHelper.restoreSnapshotAcl(snapshot, TableName.valueOf(snapshot.getTable()), 549 env.getMasterServices().getConfiguration()); 550 } 551 } 552 553 /** 554 * The procedure could be restarted from a different machine. If the variable is null, we need to 555 * retrieve it. 556 * @return traceEnabled 557 */ 558 private Boolean isTraceEnabled() { 559 if (traceEnabled == null) { 560 traceEnabled = LOG.isTraceEnabled(); 561 } 562 return traceEnabled; 563 } 564}