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