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