-
Notifications
You must be signed in to change notification settings - Fork 626
HDDS-8778. Support recursive volume delete using Ozone sh command. #4842
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
e3c9d07
25fde63
2609247
143f819
5d9b760
6c06e1b
550db40
21098fe
800033b
87a6587
ec50b58
41cd458
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,27 +18,246 @@ | |
|
|
||
| package org.apache.hadoop.ozone.shell.volume; | ||
|
|
||
| import com.google.common.base.Strings; | ||
| import org.apache.hadoop.fs.FileSystem; | ||
| import org.apache.hadoop.fs.Path; | ||
| import org.apache.hadoop.hdds.conf.OzoneConfiguration; | ||
| import org.apache.hadoop.ozone.OmUtils; | ||
| import org.apache.hadoop.ozone.client.OzoneBucket; | ||
| import org.apache.hadoop.ozone.client.OzoneClient; | ||
| import org.apache.hadoop.ozone.client.OzoneKey; | ||
| import org.apache.hadoop.ozone.client.OzoneVolume; | ||
| import org.apache.hadoop.ozone.shell.OzoneAddress; | ||
|
|
||
| import picocli.CommandLine; | ||
| import picocli.CommandLine.Command; | ||
|
|
||
| import java.io.IOException; | ||
| import java.io.InputStreamReader; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.util.ArrayList; | ||
| import java.util.Iterator; | ||
| import java.util.List; | ||
| import java.util.Scanner; | ||
| import java.util.concurrent.ExecutorService; | ||
| import java.util.concurrent.Executors; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
|
|
||
| import static org.apache.hadoop.fs.FileSystem.FS_DEFAULT_NAME_KEY; | ||
| import static org.apache.hadoop.hdds.scm.net.NetConstants.PATH_SEPARATOR_STR; | ||
| import static org.apache.hadoop.ozone.OzoneConsts.OZONE_OFS_URI_SCHEME; | ||
| import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_ADDRESS_KEY; | ||
|
|
||
| /** | ||
| * Executes deleteVolume call for the shell. | ||
| */ | ||
| @Command(name = "delete", | ||
| description = "deletes a volume if it is empty") | ||
| description = "deletes a volume") | ||
| public class DeleteVolumeHandler extends VolumeHandler { | ||
| @CommandLine.Option( | ||
| names = {"-skipTrash"}, | ||
| description = "Delete volume without trash" | ||
| ) | ||
| private boolean bSkipTrash = false; | ||
| @CommandLine.Option( | ||
| names = {"-r"}, | ||
| description = "Delete volume recursively" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add in description similar to Ozonefsdelete description->"Delay is " +
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added in the interactive description when user does recursive delete. About limit number we can have improvement in future for this command. In PR description I have added how the interactive description looks like. |
||
| ) | ||
| private boolean bRecursive = false; | ||
| @CommandLine.Option( | ||
| names = {"-id", "--om-service-id"}, | ||
| description = "Ozone Manager Service ID" | ||
| ) | ||
| private String omServiceId; | ||
|
|
||
| @CommandLine.Option(names = {"-t", "--threads", "--thread"}, | ||
| description = "Number of threads used to execute") | ||
|
ashishkumar50 marked this conversation as resolved.
Outdated
|
||
| private int threadNo = 10; | ||
|
|
||
| @CommandLine.Option(names = "-yes", | ||
| description = "Continue without interactive user confirmation") | ||
| private boolean yes; | ||
|
sadanand48 marked this conversation as resolved.
|
||
|
|
||
| private ExecutorService executor; | ||
| private List<String> bucketIdList = new ArrayList<>(); | ||
| private AtomicInteger cleanedBucketCounter = | ||
| new AtomicInteger(); | ||
| private int totalBucketCount; | ||
| private OzoneVolume vol; | ||
| private AtomicInteger numberOfBucketsCleaned = new AtomicInteger(0); | ||
| private volatile Throwable exception; | ||
| private static final int MAX_KEY_DELETE_BATCH_SIZE = 1000; | ||
|
|
||
| @Override | ||
| protected void execute(OzoneClient client, OzoneAddress address) | ||
| throws IOException { | ||
|
|
||
| String volumeName = address.getVolumeName(); | ||
|
|
||
| try { | ||
| if (bRecursive) { | ||
| if (!bSkipTrash) { | ||
| out().printf("Use -skipTrash for recursive volume delete%n"); | ||
| return; | ||
| } | ||
| if (OmUtils.isServiceIdsDefined(getConf()) && | ||
| Strings.isNullOrEmpty(omServiceId)) { | ||
|
sadanand48 marked this conversation as resolved.
Outdated
|
||
| out().printf("OmServiceID not provided, provide using " + | ||
| "-id <OM_SERVICE_ID>%n"); | ||
| return; | ||
| } | ||
| vol = client.getObjectStore().getVolume(volumeName); | ||
| if (!yes) { | ||
| // Ask for user confirmation | ||
| out().print("Enter 'yes' to confirm recursive volume delete '" + | ||
| volumeName + "': "); | ||
| out().flush(); | ||
| Scanner scanner = new Scanner(new InputStreamReader( | ||
| System.in, StandardCharsets.UTF_8)); | ||
| String confirmation = scanner.next().trim().toLowerCase(); | ||
| if (!confirmation.equals("yes")) { | ||
| out().println("Operation cancelled."); | ||
| return; | ||
| } | ||
| } | ||
| deleteVolumeRecursive(); | ||
| } | ||
| } catch (InterruptedException e) { | ||
| out().printf("Exception while deleting volume recursively%n"); | ||
| return; | ||
| } | ||
| client.getObjectStore().deleteVolume(volumeName); | ||
| out().printf("Volume %s is deleted%n", volumeName); | ||
| } | ||
|
|
||
| private void deleteVolumeRecursive() | ||
| throws InterruptedException { | ||
| // Get all the buckets for given volume | ||
| Iterator<? extends OzoneBucket> bucketIterator = | ||
| vol.listBuckets(null); | ||
|
|
||
| while (bucketIterator.hasNext()) { | ||
| OzoneBucket bucket = bucketIterator.next(); | ||
| bucketIdList.add(bucket.getName()); | ||
| totalBucketCount++; | ||
| } | ||
| doCleanBuckets(); | ||
| } | ||
|
|
||
| /** | ||
| * Clean OBS bucket recursively. | ||
| * | ||
| * @param bucket OzoneBucket | ||
| * @return boolean | ||
| */ | ||
| private boolean cleanOBSBucket(OzoneBucket bucket) { | ||
| ArrayList<String> keys = new ArrayList<>(); | ||
| try { | ||
| if (!bucket.isLink()) { | ||
| Iterator<? extends OzoneKey> iterator = bucket.listKeys(null); | ||
| while (iterator.hasNext()) { | ||
|
sadanand48 marked this conversation as resolved.
|
||
| keys.add(iterator.next().getName()); | ||
| if (MAX_KEY_DELETE_BATCH_SIZE == keys.size()) { | ||
| bucket.deleteKeys(keys); | ||
| keys.clear(); | ||
| } | ||
| } | ||
| // delete if any remaining keys left | ||
| if (keys.size() > 0) { | ||
| bucket.deleteKeys(keys); | ||
| } | ||
| } | ||
| vol.deleteBucket(bucket.getName()); | ||
| numberOfBucketsCleaned.getAndIncrement(); | ||
| return true; | ||
| } catch (Exception e) { | ||
| LOG.error("Could not clean bucket ", e); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Clean Legacy/FSO bucket recursively. | ||
| * | ||
| * @param bucket OzoneBucket | ||
| * @return boolean | ||
| */ | ||
| private boolean cleanFSBucket(OzoneBucket bucket) { | ||
| try { | ||
| String hostPrefix = OZONE_OFS_URI_SCHEME + "://"; | ||
| if (!Strings.isNullOrEmpty(omServiceId)) { | ||
| hostPrefix += omServiceId + PATH_SEPARATOR_STR; | ||
| } else { | ||
| hostPrefix += getConf().get(OZONE_OM_ADDRESS_KEY) + | ||
| PATH_SEPARATOR_STR; | ||
| } | ||
| String ofsPrefix = hostPrefix + vol.getName() + PATH_SEPARATOR_STR + | ||
| bucket.getName(); | ||
| final Path path = new Path(ofsPrefix); | ||
| OzoneConfiguration clientConf = new OzoneConfiguration(getConf()); | ||
| clientConf.set(FS_DEFAULT_NAME_KEY, hostPrefix); | ||
| FileSystem fs = FileSystem.get(clientConf); | ||
| if (!fs.delete(path, true)) { | ||
| throw new IOException("Failed to delete bucket"); | ||
| } | ||
| numberOfBucketsCleaned.getAndIncrement(); | ||
| return true; | ||
| } catch (Exception e) { | ||
| exception = e; | ||
| LOG.error("Could not clean bucket ", e); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| private class BucketCleaner implements Runnable { | ||
| @Override | ||
| public void run() { | ||
| int i; | ||
| while ((i = cleanedBucketCounter.getAndIncrement()) < totalBucketCount) { | ||
| try { | ||
| OzoneBucket bucket = vol.getBucket(bucketIdList.get(i)); | ||
| switch (bucket.getBucketLayout()) { | ||
| case FILE_SYSTEM_OPTIMIZED: | ||
| case LEGACY: | ||
| if (!cleanFSBucket(bucket)) { | ||
| throw new RuntimeException("Failed to clean bucket"); | ||
| } | ||
| break; | ||
| case OBJECT_STORE: | ||
| if (!cleanOBSBucket(bucket)) { | ||
| throw new RuntimeException("Failed to clean bucket"); | ||
| } | ||
| default: | ||
| throw new RuntimeException("Invalid bucket layout"); | ||
| } | ||
| } catch (IOException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private void doCleanBuckets() throws InterruptedException { | ||
| executor = Executors.newFixedThreadPool(threadNo); | ||
| for (int i = 0; i < threadNo; i++) { | ||
| executor.execute(new BucketCleaner()); | ||
| } | ||
|
|
||
| try { | ||
| // wait until all Buckets are cleaned or exception occurred. | ||
| while (numberOfBucketsCleaned.get() != totalBucketCount | ||
|
sadanand48 marked this conversation as resolved.
|
||
| && exception == null) { | ||
| try { | ||
| Thread.sleep(100); | ||
| } catch (InterruptedException e) { | ||
| throw e; | ||
| } | ||
| } | ||
| } catch (InterruptedException e) { | ||
| LOG.error("Failed to wait until all Buckets are cleaned", e); | ||
| Thread.currentThread().interrupt(); | ||
| } | ||
| executor.shutdown(); | ||
| executor.awaitTermination(Integer.MAX_VALUE, TimeUnit.MILLISECONDS); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.