This is an automated email from the ASF dual-hosted git repository.
chungen0126 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new c4b2ae1fff4 HDDS-15086. Wire ozone local command (#10758)
c4b2ae1fff4 is described below
commit c4b2ae1fff4091f22f52e649280898743ff244cc
Author: Chun-Hung Tseng <[email protected]>
AuthorDate: Wed Jul 22 11:45:06 2026 +0200
HDDS-15086. Wire ozone local command (#10758)
---
hadoop-hdds/docs/content/tools/_index.md | 2 +
hadoop-ozone/dist/src/shell/ozone/ozone | 5 +
.../org/apache/hadoop/ozone/local/OzoneLocal.java | 64 +++++++++--
.../apache/hadoop/ozone/local/TestOzoneLocal.java | 120 ++++++++++++++++++---
4 files changed, 168 insertions(+), 23 deletions(-)
diff --git a/hadoop-hdds/docs/content/tools/_index.md
b/hadoop-hdds/docs/content/tools/_index.md
index aa708e263b8..3a8bf08e826 100644
--- a/hadoop-hdds/docs/content/tools/_index.md
+++ b/hadoop-hdds/docs/content/tools/_index.md
@@ -43,6 +43,8 @@ Client commands:
* **sh** - Primary command line interface for ozone to manage
volumes/buckets/keys.
* **fs** - Runs a command on ozone file system (similar to `hdfs dfs`)
+ * **local** - Runs a single-node local Ozone cluster (SCM, OM, and
datanodes)
+ in one process for development.
* **version** - Prints the version of Ozone and HDDS.
diff --git a/hadoop-ozone/dist/src/shell/ozone/ozone
b/hadoop-ozone/dist/src/shell/ozone/ozone
index ecddd4f1ecc..8592ef99e2f 100755
--- a/hadoop-ozone/dist/src/shell/ozone/ozone
+++ b/hadoop-ozone/dist/src/shell/ozone/ozone
@@ -66,6 +66,7 @@ function ozone_usage
ozone_add_subcommand "s3" client "command line interface for s3 related
operations"
ozone_add_subcommand "tenant" client "command line interface for
multi-tenant related operations"
ozone_add_subcommand "insight" client "tool to get runtime operation
information"
+ ozone_add_subcommand "local" client "run a single-node local ozone cluster"
ozone_add_subcommand "version" client "print the version"
ozone_add_subcommand "dtutil" client "operations related to delegation
tokens"
ozone_add_subcommand "interactive" client "interactive shell for ozone
commands"
@@ -208,6 +209,10 @@ function ozonecmd_case
OZONE_CLASSNAME=org.apache.hadoop.ozone.insight.Insight
OZONE_RUN_ARTIFACT_NAME="ozone-insight"
;;
+ local)
+ OZONE_CLASSNAME=org.apache.hadoop.ozone.local.OzoneLocal
+ OZONE_RUN_ARTIFACT_NAME="ozone-tools"
+ ;;
version)
OZONE_CLASSNAME=org.apache.hadoop.ozone.util.OzoneVersionInfo
OZONE_RUN_ARTIFACT_NAME="ozone-tools"
diff --git
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/local/OzoneLocal.java
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/local/OzoneLocal.java
index 6d1d4d1394f..0d5e6ec1734 100644
---
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/local/OzoneLocal.java
+++
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/local/OzoneLocal.java
@@ -17,26 +17,30 @@
package org.apache.hadoop.ozone.local;
+import java.io.PrintWriter;
import java.nio.file.Path;
import java.time.Duration;
import java.time.format.DateTimeParseException;
import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.hadoop.hdds.cli.AbstractSubcommand;
import org.apache.hadoop.hdds.cli.GenericCli;
import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.conf.TimeDurationUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.ITypeConverter;
import picocli.CommandLine.Option;
/**
- * Internal CLI entry point for local Ozone cluster commands.
+ * CLI entry point for local single-node Ozone.
*/
@Command(name = "ozone local",
- hidden = true,
- description = "Internal commands for local Ozone cluster runtime",
+ description = "Run a single-node local Ozone cluster",
versionProvider = HddsVersionProvider.class,
mixinStandardHelpOptions = true,
subcommands = {
@@ -44,6 +48,8 @@
})
public class OzoneLocal extends GenericCli {
+ private static final Logger LOG = LoggerFactory.getLogger(OzoneLocal.class);
+
static final String ENV_DATA_DIR = "OZONE_LOCAL_DATA_DIR";
static final String ENV_FORMAT = "OZONE_LOCAL_FORMAT";
static final String ENV_DATANODES = "OZONE_LOCAL_DATANODES";
@@ -108,8 +114,7 @@ public static void main(String[] args) {
}
@Command(name = "run",
- hidden = true,
- description = "Resolve configuration for local Ozone runtime startup")
+ description = "Start SCM, OM, and datanodes in one local process")
static class RunCommand extends AbstractSubcommand implements Callable<Void>
{
@Option(names = "--data-dir",
@@ -189,11 +194,56 @@ static class RunCommand extends AbstractSubcommand
implements Callable<Void> {
private String s3Region;
@Override
- public Void call() {
- resolveConfig();
+ public Void call() throws Exception {
+ LocalOzoneClusterConfig config = resolveConfig();
+ try (LocalOzoneRuntime runtime = createRuntime(config, getOzoneConf())) {
+ runtime.start();
+ printSummary(runtime, config);
+ awaitShutdown(runtime);
+ }
return null;
}
+ LocalOzoneRuntime createRuntime(LocalOzoneClusterConfig config,
OzoneConfiguration seedConfiguration) {
+ return new LocalOzoneCluster(config, seedConfiguration);
+ }
+
+ private void printSummary(LocalOzoneRuntime runtime,
LocalOzoneClusterConfig config) {
+ PrintWriter writer = out();
+ writer.println("Local Ozone is running from " + config.getDataDir());
+ writer.println("SCM RPC: " + runtime.getDisplayHost() + ":" +
runtime.getScmPort());
+ writer.println("OM RPC: " + runtime.getDisplayHost() + ":" +
runtime.getOmPort());
+ writer.println("Press Ctrl+C to stop.");
+ writer.flush();
+ }
+
+ /**
+ * Blocks until the JVM is asked to shut down (for example by Ctrl+C),
closing the runtime from
+ * the shutdown hook before the JVM exits.
+ */
+ void awaitShutdown(LocalOzoneRuntime runtime) throws InterruptedException {
+ CountDownLatch stopped = new CountDownLatch(1);
+ Thread shutdownHook = new Thread(() -> {
+ try {
+ runtime.close();
+ } catch (Exception ex) {
+ LOG.warn("Failed to close ozone local runtime", ex);
+ } finally {
+ stopped.countDown();
+ }
+ }, "ozone-local-shutdown");
+ Runtime.getRuntime().addShutdownHook(shutdownHook);
+ try {
+ stopped.await();
+ } finally {
+ try {
+ Runtime.getRuntime().removeShutdownHook(shutdownHook);
+ } catch (IllegalStateException ignored) {
+ // JVM shutdown is already in progress.
+ }
+ }
+ }
+
LocalOzoneClusterConfig resolveConfig() {
if (datanodes < 1) {
throw new IllegalArgumentException(
diff --git
a/hadoop-ozone/tools/src/test/java/org/apache/hadoop/ozone/local/TestOzoneLocal.java
b/hadoop-ozone/tools/src/test/java/org/apache/hadoop/ozone/local/TestOzoneLocal.java
index 219b3ae5d54..0f8686e91a2 100644
---
a/hadoop-ozone/tools/src/test/java/org/apache/hadoop/ozone/local/TestOzoneLocal.java
+++
b/hadoop-ozone/tools/src/test/java/org/apache/hadoop/ozone/local/TestOzoneLocal.java
@@ -30,6 +30,7 @@
import java.lang.reflect.Field;
import java.nio.file.Paths;
import java.time.Duration;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.junit.jupiter.api.Test;
import picocli.CommandLine;
import picocli.CommandLine.Command;
@@ -45,21 +46,21 @@
class TestOzoneLocal {
@Test
- void localCommandMetadataIsPresentAndHidden() {
+ void localCommandMetadataIsPresentAndPublic() {
Command command = OzoneLocal.class.getAnnotation(Command.class);
assertNotNull(command);
assertEquals("ozone local", command.name());
- assertTrue(command.hidden());
+ assertFalse(command.hidden());
}
@Test
- void runCommandMetadataIsPresentAndHidden() {
+ void runCommandMetadataIsPresentAndPublic() {
Command command = OzoneLocal.RunCommand.class.getAnnotation(Command.class);
assertNotNull(command);
assertEquals("run", command.name());
- assertTrue(command.hidden());
+ assertFalse(command.hidden());
}
@Test
@@ -70,7 +71,7 @@ void genericCliRegistersRunCommand() {
}
@Test
- void rootHelpHidesRunCommand() throws Exception {
+ void rootHelpListsRunCommand() throws Exception {
OzoneLocal local = new OzoneLocal();
CommandLine commandLine = local.getCmd();
ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -85,27 +86,41 @@ void rootHelpHidesRunCommand() throws Exception {
String help = out.toString(UTF_8.name());
assertEquals(0, exitCode);
assertTrue(help.contains("Usage: ozone local"));
- assertFalse(help.matches("(?s).*\\R\\s+run\\b.*"), help);
+ assertTrue(help.matches("(?s).*\\R\\s+run\\b.*"), help);
assertEquals("", err.toString(UTF_8.name()));
}
@Test
- void runCommandResolvesConfigurationQuietlyUntilRuntimeStartup()
- throws Exception {
- OzoneLocal local = new OzoneLocal();
- CommandLine commandLine = local.getCmd();
+ void runCommandStartsRuntimeAndPrintsStartupSummary() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
- ByteArrayOutputStream err = new ByteArrayOutputStream();
+ StubRuntime runtime = new StubRuntime("localhost", 9860, 9862);
+ TestableRunCommand command = new TestableRunCommand(runtime);
+ CommandLine commandLine = new CommandLine(command);
commandLine.setOut(new PrintWriter(new OutputStreamWriter(out, UTF_8),
true));
- commandLine.setErr(new PrintWriter(new OutputStreamWriter(err, UTF_8),
- true));
- int exitCode = local.execute(new String[] {"run"});
+ int exitCode = commandLine.execute();
assertEquals(0, exitCode);
- assertEquals("", out.toString(UTF_8.name()));
- assertEquals("", err.toString(UTF_8.name()));
+ assertTrue(runtime.started);
+ assertTrue(runtime.closed);
+ String text = out.toString(UTF_8.name());
+ assertTrue(text.contains("Local Ozone is running from"), text);
+ assertTrue(text.contains("SCM RPC: localhost:9860"), text);
+ assertTrue(text.contains("OM RPC: localhost:9862"), text);
+ assertTrue(text.contains("Press Ctrl+C to stop."), text);
+ }
+
+ @Test
+ void runCommandClosesRuntimeWhenStartupFails() {
+ StubRuntime runtime = new StubRuntime("localhost", 9860, 9862);
+ runtime.failStart = true;
+ TestableRunCommand command = new TestableRunCommand(runtime);
+
+ int exitCode = new CommandLine(command).execute();
+
+ assertEquals(1, exitCode);
+ assertTrue(runtime.closed);
}
@Test
@@ -323,6 +338,79 @@ private static void assertEnvDefault(String fieldName,
defaultValue);
}
+ private static final class TestableRunCommand extends OzoneLocal.RunCommand {
+
+ private final LocalOzoneRuntime runtime;
+
+ private TestableRunCommand(LocalOzoneRuntime runtime) {
+ this.runtime = runtime;
+ }
+
+ @Override
+ LocalOzoneRuntime createRuntime(LocalOzoneClusterConfig config,
OzoneConfiguration seedConfiguration) {
+ return runtime;
+ }
+
+ @Override
+ void awaitShutdown(LocalOzoneRuntime localRuntime) {
+ // Return immediately instead of blocking until JVM shutdown.
+ }
+ }
+
+ private static final class StubRuntime implements LocalOzoneRuntime {
+
+ private final String displayHost;
+ private final int scmPort;
+ private final int omPort;
+ private boolean failStart;
+ private boolean started;
+ private boolean closed;
+
+ private StubRuntime(String displayHost, int scmPort, int omPort) {
+ this.displayHost = displayHost;
+ this.scmPort = scmPort;
+ this.omPort = omPort;
+ }
+
+ @Override
+ public void start() {
+ if (failStart) {
+ throw new IllegalStateException("startup failed");
+ }
+ started = true;
+ }
+
+ @Override
+ public String getDisplayHost() {
+ return displayHost;
+ }
+
+ @Override
+ public int getScmPort() {
+ return scmPort;
+ }
+
+ @Override
+ public int getOmPort() {
+ return omPort;
+ }
+
+ @Override
+ public int getS3gPort() {
+ return 0;
+ }
+
+ @Override
+ public String getS3Endpoint() {
+ return "";
+ }
+
+ @Override
+ public void close() {
+ closed = true;
+ }
+ }
+
private static final class RunCommandFallbackDefaults
implements IDefaultValueProvider {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]