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; 027import org.apache.hadoop.conf.Configuration; 028import org.apache.hadoop.fs.FileSystem; 029import org.apache.hadoop.fs.Path; 030import org.apache.hadoop.hbase.MetaTableAccessor; 031import org.apache.hadoop.hbase.TableExistsException; 032import org.apache.hadoop.hbase.TableName; 033import org.apache.hadoop.hbase.client.RegionInfo; 034import org.apache.hadoop.hbase.client.TableDescriptor; 035import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 036import org.apache.hadoop.hbase.errorhandling.ForeignException; 037import org.apache.hadoop.hbase.errorhandling.ForeignExceptionDispatcher; 038import org.apache.hadoop.hbase.master.MasterCoprocessorHost; 039import org.apache.hadoop.hbase.master.MasterFileSystem; 040import org.apache.hadoop.hbase.master.MetricsSnapshot; 041import org.apache.hadoop.hbase.master.RegionState; 042import org.apache.hadoop.hbase.master.assignment.AssignmentManager; 043import org.apache.hadoop.hbase.master.procedure.CreateTableProcedure.CreateHdfsRegions; 044import org.apache.hadoop.hbase.mob.MobUtils; 045import org.apache.hadoop.hbase.monitoring.MonitoredTask; 046import org.apache.hadoop.hbase.monitoring.TaskMonitor; 047import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer; 048import org.apache.hadoop.hbase.snapshot.ClientSnapshotDescriptionUtils; 049import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException; 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.CommonFSUtils; 054import org.apache.hadoop.hbase.util.FSTableDescriptors; 055import org.apache.hadoop.hbase.util.Pair; 056import org.apache.yetus.audience.InterfaceAudience; 057import org.slf4j.Logger; 058import org.slf4j.LoggerFactory; 059 060import org.apache.hbase.thirdparty.com.google.common.base.Preconditions; 061 062import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 063import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; 064import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos; 065import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.CloneSnapshotState; 066import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription; 067 068@InterfaceAudience.Private 069public class CloneSnapshotProcedure 070 extends AbstractStateMachineTableProcedure<CloneSnapshotState> { 071 private static final Logger LOG = LoggerFactory.getLogger(CloneSnapshotProcedure.class); 072 073 private TableDescriptor tableDescriptor; 074 private SnapshotDescription snapshot; 075 private boolean restoreAcl; 076 private List<RegionInfo> newRegions = null; 077 private Map<String, Pair<String, String> > parentsToChildrenPairMap = new HashMap<>(); 078 079 // Monitor 080 private MonitoredTask monitorStatus = null; 081 082 /** 083 * Constructor (for failover) 084 */ 085 public CloneSnapshotProcedure() { 086 } 087 088 public CloneSnapshotProcedure(final MasterProcedureEnv env, 089 final TableDescriptor tableDescriptor, final SnapshotDescription snapshot) { 090 this(env, tableDescriptor, snapshot, false); 091 } 092 093 /** 094 * Constructor 095 * @param env MasterProcedureEnv 096 * @param tableDescriptor the table to operate on 097 * @param snapshot snapshot to clone from 098 */ 099 public CloneSnapshotProcedure(final MasterProcedureEnv env, 100 final TableDescriptor tableDescriptor, final SnapshotDescription snapshot, 101 final boolean restoreAcl) { 102 super(env); 103 this.tableDescriptor = tableDescriptor; 104 this.snapshot = snapshot; 105 this.restoreAcl = restoreAcl; 106 107 getMonitorStatus(); 108 } 109 110 /** 111 * Set up monitor status if it is not created. 112 */ 113 private MonitoredTask getMonitorStatus() { 114 if (monitorStatus == null) { 115 monitorStatus = TaskMonitor.get().createStatus("Cloning snapshot '" + snapshot.getName() + 116 "' to table " + getTableName()); 117 } 118 return monitorStatus; 119 } 120 121 private void restoreSnapshotAcl(MasterProcedureEnv env) throws IOException { 122 Configuration conf = env.getMasterServices().getConfiguration(); 123 if (restoreAcl && snapshot.hasUsersAndPermissions() && snapshot.getUsersAndPermissions() != null 124 && SnapshotDescriptionUtils.isSecurityAvailable(conf)) { 125 RestoreSnapshotHelper.restoreSnapshotAcl(snapshot, tableDescriptor.getTableName(), conf); 126 } 127 } 128 129 @Override 130 protected Flow executeFromState(final MasterProcedureEnv env, final CloneSnapshotState state) 131 throws InterruptedException { 132 LOG.trace("{} execute state={}", this, state); 133 try { 134 switch (state) { 135 case CLONE_SNAPSHOT_PRE_OPERATION: 136 // Verify if we can clone the table 137 prepareClone(env); 138 139 preCloneSnapshot(env); 140 setNextState(CloneSnapshotState.CLONE_SNAPSHOT_WRITE_FS_LAYOUT); 141 break; 142 case CLONE_SNAPSHOT_WRITE_FS_LAYOUT: 143 newRegions = createFilesystemLayout(env, tableDescriptor, newRegions); 144 setNextState(CloneSnapshotState.CLONE_SNAPSHOT_ADD_TO_META); 145 break; 146 case CLONE_SNAPSHOT_ADD_TO_META: 147 addRegionsToMeta(env); 148 setNextState(CloneSnapshotState.CLONE_SNAPSHOT_ASSIGN_REGIONS); 149 break; 150 case CLONE_SNAPSHOT_ASSIGN_REGIONS: 151 CreateTableProcedure.setEnablingState(env, getTableName()); 152 153 // Separate newRegions to split regions and regions to assign 154 List<RegionInfo> splitRegions = new ArrayList<>(); 155 List<RegionInfo> regionsToAssign = new ArrayList<>(); 156 newRegions.forEach(ri -> { 157 if (ri.isOffline() && (ri.isSplit() || ri.isSplitParent())) { 158 splitRegions.add(ri); 159 } else { 160 regionsToAssign.add(ri); 161 } 162 }); 163 164 // For split regions, add them to RegionStates 165 AssignmentManager am = env.getAssignmentManager(); 166 splitRegions.forEach(ri -> 167 am.getRegionStates().updateRegionState(ri, RegionState.State.SPLIT) 168 ); 169 170 addChildProcedure(env.getAssignmentManager() 171 .createRoundRobinAssignProcedures(regionsToAssign)); 172 setNextState(CloneSnapshotState.CLONE_SNAPSHOT_UPDATE_DESC_CACHE); 173 break; 174 case CLONE_SNAPSHOT_UPDATE_DESC_CACHE: 175 CreateTableProcedure.setEnabledState(env, getTableName()); 176 CreateTableProcedure.updateTableDescCache(env, getTableName()); 177 setNextState(CloneSnapshotState.CLONE_SNAPHOST_RESTORE_ACL); 178 break; 179 case CLONE_SNAPHOST_RESTORE_ACL: 180 restoreSnapshotAcl(env); 181 setNextState(CloneSnapshotState.CLONE_SNAPSHOT_POST_OPERATION); 182 break; 183 case CLONE_SNAPSHOT_POST_OPERATION: 184 postCloneSnapshot(env); 185 186 MetricsSnapshot metricsSnapshot = new MetricsSnapshot(); 187 metricsSnapshot.addSnapshotClone( 188 getMonitorStatus().getCompletionTimestamp() - getMonitorStatus().getStartTime()); 189 getMonitorStatus().markComplete("Clone snapshot '"+ snapshot.getName() +"' completed!"); 190 return Flow.NO_MORE_STATE; 191 default: 192 throw new UnsupportedOperationException("unhandled state=" + state); 193 } 194 } catch (IOException e) { 195 if (isRollbackSupported(state)) { 196 setFailure("master-clone-snapshot", e); 197 } else { 198 LOG.warn("Retriable error trying to clone snapshot=" + snapshot.getName() + 199 " to table=" + getTableName() + " state=" + state, e); 200 } 201 } 202 return Flow.HAS_MORE_STATE; 203 } 204 205 @Override 206 protected void rollbackState(final MasterProcedureEnv env, final CloneSnapshotState state) 207 throws IOException { 208 if (state == CloneSnapshotState.CLONE_SNAPSHOT_PRE_OPERATION) { 209 DeleteTableProcedure.deleteTableStates(env, getTableName()); 210 // TODO-MAYBE: call the deleteTable coprocessor event? 211 return; 212 } 213 214 // The procedure doesn't have a rollback. The execution will succeed, at some point. 215 throw new UnsupportedOperationException("unhandled state=" + state); 216 } 217 218 @Override 219 protected boolean isRollbackSupported(final CloneSnapshotState state) { 220 switch (state) { 221 case CLONE_SNAPSHOT_PRE_OPERATION: 222 return true; 223 default: 224 return false; 225 } 226 } 227 228 @Override 229 protected CloneSnapshotState getState(final int stateId) { 230 return CloneSnapshotState.valueOf(stateId); 231 } 232 233 @Override 234 protected int getStateId(final CloneSnapshotState state) { 235 return state.getNumber(); 236 } 237 238 @Override 239 protected CloneSnapshotState getInitialState() { 240 return CloneSnapshotState.CLONE_SNAPSHOT_PRE_OPERATION; 241 } 242 243 @Override 244 public TableName getTableName() { 245 return tableDescriptor.getTableName(); 246 } 247 248 @Override 249 public TableOperationType getTableOperationType() { 250 return TableOperationType.CREATE; // Clone is creating a table 251 } 252 253 @Override 254 public void toStringClassDetails(StringBuilder sb) { 255 sb.append(getClass().getSimpleName()); 256 sb.append(" (table="); 257 sb.append(getTableName()); 258 sb.append(" snapshot="); 259 sb.append(snapshot); 260 sb.append(")"); 261 } 262 263 @Override 264 protected void serializeStateData(ProcedureStateSerializer serializer) 265 throws IOException { 266 super.serializeStateData(serializer); 267 268 MasterProcedureProtos.CloneSnapshotStateData.Builder cloneSnapshotMsg = 269 MasterProcedureProtos.CloneSnapshotStateData.newBuilder() 270 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())) 271 .setSnapshot(this.snapshot) 272 .setTableSchema(ProtobufUtil.toTableSchema(tableDescriptor)); 273 if (newRegions != null) { 274 for (RegionInfo hri: newRegions) { 275 cloneSnapshotMsg.addRegionInfo(ProtobufUtil.toRegionInfo(hri)); 276 } 277 } 278 if (!parentsToChildrenPairMap.isEmpty()) { 279 final Iterator<Map.Entry<String, Pair<String, String>>> it = 280 parentsToChildrenPairMap.entrySet().iterator(); 281 while (it.hasNext()) { 282 final Map.Entry<String, Pair<String, String>> entry = it.next(); 283 284 MasterProcedureProtos.RestoreParentToChildRegionsPair.Builder parentToChildrenPair = 285 MasterProcedureProtos.RestoreParentToChildRegionsPair.newBuilder() 286 .setParentRegionName(entry.getKey()) 287 .setChild1RegionName(entry.getValue().getFirst()) 288 .setChild2RegionName(entry.getValue().getSecond()); 289 cloneSnapshotMsg.addParentToChildRegionsPairList(parentToChildrenPair); 290 } 291 } 292 serializer.serialize(cloneSnapshotMsg.build()); 293 } 294 295 @Override 296 protected void deserializeStateData(ProcedureStateSerializer serializer) 297 throws IOException { 298 super.deserializeStateData(serializer); 299 300 MasterProcedureProtos.CloneSnapshotStateData cloneSnapshotMsg = 301 serializer.deserialize(MasterProcedureProtos.CloneSnapshotStateData.class); 302 setUser(MasterProcedureUtil.toUserInfo(cloneSnapshotMsg.getUserInfo())); 303 snapshot = cloneSnapshotMsg.getSnapshot(); 304 tableDescriptor = ProtobufUtil.toTableDescriptor(cloneSnapshotMsg.getTableSchema()); 305 if (cloneSnapshotMsg.getRegionInfoCount() == 0) { 306 newRegions = null; 307 } else { 308 newRegions = new ArrayList<>(cloneSnapshotMsg.getRegionInfoCount()); 309 for (HBaseProtos.RegionInfo hri: cloneSnapshotMsg.getRegionInfoList()) { 310 newRegions.add(ProtobufUtil.toRegionInfo(hri)); 311 } 312 } 313 if (cloneSnapshotMsg.getParentToChildRegionsPairListCount() > 0) { 314 parentsToChildrenPairMap = new HashMap<>(); 315 for (MasterProcedureProtos.RestoreParentToChildRegionsPair parentToChildrenPair: 316 cloneSnapshotMsg.getParentToChildRegionsPairListList()) { 317 parentsToChildrenPairMap.put( 318 parentToChildrenPair.getParentRegionName(), 319 new Pair<>( 320 parentToChildrenPair.getChild1RegionName(), 321 parentToChildrenPair.getChild2RegionName())); 322 } 323 } 324 // Make sure that the monitor status is set up 325 getMonitorStatus(); 326 } 327 328 /** 329 * Action before any real action of cloning from snapshot. 330 * @param env MasterProcedureEnv 331 * @throws IOException 332 */ 333 private void prepareClone(final MasterProcedureEnv env) throws IOException { 334 final TableName tableName = getTableName(); 335 if (MetaTableAccessor.tableExists(env.getMasterServices().getConnection(), tableName)) { 336 throw new TableExistsException(getTableName()); 337 } 338 } 339 340 /** 341 * Action before cloning from snapshot. 342 * @param env MasterProcedureEnv 343 * @throws IOException 344 * @throws InterruptedException 345 */ 346 private void preCloneSnapshot(final MasterProcedureEnv env) 347 throws IOException, InterruptedException { 348 if (!getTableName().isSystemTable()) { 349 // Check and update namespace quota 350 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 351 352 SnapshotManifest manifest = SnapshotManifest.open( 353 env.getMasterConfiguration(), 354 mfs.getFileSystem(), 355 SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshot, mfs.getRootDir()), 356 snapshot); 357 358 ProcedureSyncWait.getMasterQuotaManager(env) 359 .checkNamespaceTableAndRegionQuota(getTableName(), manifest.getRegionManifestsMap().size()); 360 } 361 362 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 363 if (cpHost != null) { 364 cpHost.preCreateTableAction(tableDescriptor, null, getUser()); 365 } 366 } 367 368 /** 369 * Action after cloning from snapshot. 370 * @param env MasterProcedureEnv 371 * @throws IOException 372 * @throws InterruptedException 373 */ 374 private void postCloneSnapshot(final MasterProcedureEnv env) 375 throws IOException, InterruptedException { 376 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 377 if (cpHost != null) { 378 final RegionInfo[] regions = (newRegions == null) ? null : 379 newRegions.toArray(new RegionInfo[newRegions.size()]); 380 cpHost.postCompletedCreateTableAction(tableDescriptor, regions, getUser()); 381 } 382 } 383 384 /** 385 * Create regions in file system. 386 * @param env MasterProcedureEnv 387 * @throws IOException 388 */ 389 private List<RegionInfo> createFilesystemLayout( 390 final MasterProcedureEnv env, 391 final TableDescriptor tableDescriptor, 392 final List<RegionInfo> newRegions) throws IOException { 393 return createFsLayout(env, tableDescriptor, newRegions, new CreateHdfsRegions() { 394 @Override 395 public List<RegionInfo> createHdfsRegions( 396 final MasterProcedureEnv env, 397 final Path tableRootDir, final TableName tableName, 398 final List<RegionInfo> newRegions) throws IOException { 399 400 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 401 final FileSystem fs = mfs.getFileSystem(); 402 final Path rootDir = mfs.getRootDir(); 403 final Configuration conf = env.getMasterConfiguration(); 404 final ForeignExceptionDispatcher monitorException = new ForeignExceptionDispatcher(); 405 406 getMonitorStatus().setStatus("Clone snapshot - creating regions for table: " + tableName); 407 408 try { 409 // 1. Execute the on-disk Clone 410 Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshot, rootDir); 411 SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, snapshot); 412 RestoreSnapshotHelper restoreHelper = new RestoreSnapshotHelper( 413 conf, fs, manifest, tableDescriptor, tableRootDir, monitorException, monitorStatus); 414 RestoreSnapshotHelper.RestoreMetaChanges metaChanges = restoreHelper.restoreHdfsRegions(); 415 416 // Clone operation should not have stuff to restore or remove 417 Preconditions.checkArgument( 418 !metaChanges.hasRegionsToRestore(), "A clone should not have regions to restore"); 419 Preconditions.checkArgument( 420 !metaChanges.hasRegionsToRemove(), "A clone should not have regions to remove"); 421 422 // At this point the clone is complete. Next step is enabling the table. 423 String msg = 424 "Clone snapshot="+ snapshot.getName() +" on table=" + tableName + " completed!"; 425 LOG.info(msg); 426 monitorStatus.setStatus(msg + " Waiting for table to be enabled..."); 427 428 // 2. Let the next step to add the regions to meta 429 return metaChanges.getRegionsToAdd(); 430 } catch (Exception e) { 431 String msg = "clone snapshot=" + ClientSnapshotDescriptionUtils.toString(snapshot) + 432 " failed because " + e.getMessage(); 433 LOG.error(msg, e); 434 IOException rse = new RestoreSnapshotException(msg, e, 435 ProtobufUtil.createSnapshotDesc(snapshot)); 436 437 // these handlers aren't futures so we need to register the error here. 438 monitorException.receive(new ForeignException("Master CloneSnapshotProcedure", rse)); 439 throw rse; 440 } 441 } 442 }); 443 } 444 445 /** 446 * Create region layout in file system. 447 * @param env MasterProcedureEnv 448 * @throws IOException 449 */ 450 private List<RegionInfo> createFsLayout( 451 final MasterProcedureEnv env, 452 final TableDescriptor tableDescriptor, 453 List<RegionInfo> newRegions, 454 final CreateHdfsRegions hdfsRegionHandler) throws IOException { 455 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 456 final Path tempdir = mfs.getTempDir(); 457 458 // 1. Create Table Descriptor 459 // using a copy of descriptor, table will be created enabling first 460 final Path tempTableDir = CommonFSUtils.getTableDir(tempdir, tableDescriptor.getTableName()); 461 if (CommonFSUtils.isExists(mfs.getFileSystem(), tempTableDir)) { 462 // if the region dirs exist, will cause exception and unlimited retry (see HBASE-24546) 463 LOG.warn("temp table dir already exists on disk: {}, will be deleted.", tempTableDir); 464 CommonFSUtils.deleteDirectory(mfs.getFileSystem(), tempTableDir); 465 } 466 ((FSTableDescriptors) (env.getMasterServices().getTableDescriptors())) 467 .createTableDescriptorForTableDirectory(tempTableDir, 468 TableDescriptorBuilder.newBuilder(tableDescriptor).build(), false); 469 470 // 2. Create Regions 471 newRegions = hdfsRegionHandler.createHdfsRegions( 472 env, tempdir, tableDescriptor.getTableName(), newRegions); 473 474 // 3. Move Table temp directory to the hbase root location 475 CreateTableProcedure.moveTempDirectoryToHBaseRoot(env, tableDescriptor, tempTableDir); 476 // Move Table temp mob directory to the hbase root location 477 Path tempMobTableDir = MobUtils.getMobTableDir(tempdir, tableDescriptor.getTableName()); 478 if (mfs.getFileSystem().exists(tempMobTableDir)) { 479 moveTempMobDirectoryToHBaseRoot(mfs, tableDescriptor, tempMobTableDir); 480 } 481 return newRegions; 482 } 483 484 /** 485 * Move table temp mob directory to the hbase root location 486 * @param mfs The master file system 487 * @param tableDescriptor The table to operate on 488 * @param tempMobTableDir The temp mob directory of table 489 * @throws IOException If failed to move temp mob dir to hbase root dir 490 */ 491 private void moveTempMobDirectoryToHBaseRoot(final MasterFileSystem mfs, 492 final TableDescriptor tableDescriptor, final Path tempMobTableDir) throws IOException { 493 FileSystem fs = mfs.getFileSystem(); 494 final Path tableMobDir = 495 MobUtils.getMobTableDir(mfs.getRootDir(), tableDescriptor.getTableName()); 496 if (!fs.delete(tableMobDir, true) && fs.exists(tableMobDir)) { 497 throw new IOException("Couldn't delete mob table " + tableMobDir); 498 } 499 if (!fs.exists(tableMobDir.getParent())) { 500 fs.mkdirs(tableMobDir.getParent()); 501 } 502 if (!fs.rename(tempMobTableDir, tableMobDir)) { 503 throw new IOException("Unable to move mob table from temp=" + tempMobTableDir 504 + " to hbase root=" + tableMobDir); 505 } 506 } 507 508 /** 509 * Add regions to hbase:meta table. 510 * @param env MasterProcedureEnv 511 * @throws IOException 512 */ 513 private void addRegionsToMeta(final MasterProcedureEnv env) throws IOException { 514 newRegions = CreateTableProcedure.addTableToMeta(env, tableDescriptor, newRegions); 515 516 // TODO: parentsToChildrenPairMap is always empty, which makes updateMetaParentRegions() 517 // a no-op. This part seems unnecessary. Figure out. - Appy 12/21/17 518 RestoreSnapshotHelper.RestoreMetaChanges metaChanges = 519 new RestoreSnapshotHelper.RestoreMetaChanges( 520 tableDescriptor, parentsToChildrenPairMap); 521 metaChanges.updateMetaParentRegions(env.getMasterServices().getConnection(), newRegions); 522 } 523 524}