This is an automated email from the ASF dual-hosted git repository.

adoroszlai 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 8c6c9fa7229 HDDS-16139. Define and enforce OFS and O3FS IPv6 authority 
contracts (#11108)
8c6c9fa7229 is described below

commit 8c6c9fa7229faa62af1b81e7190934e605e5071c
Author: Eric C. Ho <[email protected]>
AuthorDate: Wed Sep 23 01:11:42 2026 +0800

    HDDS-16139. Define and enforce OFS and O3FS IPv6 authority contracts 
(#11108)
    
    Generated-by: Codex (GPT-5)
---
 .../main/java/org/apache/hadoop/ozone/OmUtils.java |  16 ++--
 .../java/org/apache/hadoop/ozone/TestOmUtils.java  |  52 ++++++++++
 .../ozone/om/ha/TestOMFailoverProxyProvider.java   |  17 ++++
 .../hadoop/fs/ozone/BasicOzoneFileSystem.java      |   8 ++
 .../fs/ozone/BasicRootedOzoneFileSystem.java       |  15 ++-
 .../hadoop/fs/ozone/TestBasicOzoneFileSystems.java | 105 +++++++++++++++++++++
 6 files changed, 205 insertions(+), 8 deletions(-)

diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
index dc676300b42..617e1636440 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
@@ -151,8 +151,8 @@ public static String getOmRpcAddress(ConfigurationSource 
conf) {
     final Optional<String> host = getHostNameFromConfigKeys(conf,
         OZONE_OM_ADDRESS_KEY);
 
-    return host.orElse(OZONE_OM_BIND_HOST_DEFAULT) + ":" +
-        getOmRpcPort(conf);
+    return getHostPortString(host.orElse(OZONE_OM_BIND_HOST_DEFAULT),
+        getOmRpcPort(conf));
   }
 
   /**
@@ -167,8 +167,9 @@ public static String getOmRpcAddress(ConfigurationSource 
conf,
     final Optional<String> host = getHostNameFromConfigKeys(conf, confKey);
 
     if (host.isPresent()) {
-      return host.get() + ":" + getPortNumberFromConfigKeys(conf, confKey)
-              .orElse(OZONE_OM_PORT_DEFAULT);
+      return getHostPortString(host.get(),
+          getPortNumberFromConfigKeys(conf, confKey)
+              .orElse(OZONE_OM_PORT_DEFAULT));
     } else {
       // The specified confKey is not set
       return null;
@@ -626,7 +627,8 @@ public static String 
getHttpAddressForOMPeerNode(ConfigurationSource conf,
 
     String hostName = bindHost.orElse(addressHost.orElse(omNodeHostAddr));
 
-    return hostName + ":" + 
addressPort.orElse(OZONE_OM_HTTP_BIND_PORT_DEFAULT);
+    return getHostPortString(hostName,
+        addressPort.orElse(OZONE_OM_HTTP_BIND_PORT_DEFAULT));
   }
 
   /**
@@ -652,8 +654,8 @@ public static String 
getHttpsAddressForOMPeerNode(ConfigurationSource conf,
 
     String hostName = bindHost.orElse(addressHost.orElse(omNodeHostAddr));
 
-    return hostName + ":" +
-        addressPort.orElse(OZONE_OM_HTTPS_BIND_PORT_DEFAULT);
+    return getHostPortString(hostName,
+        addressPort.orElse(OZONE_OM_HTTPS_BIND_PORT_DEFAULT));
   }
 
   public static File createOMDir(String dirPath) {
diff --git 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java
index 1521cdd24ee..0657d3b4b24 100644
--- a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java
+++ b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java
@@ -38,11 +38,13 @@
 import java.io.IOException;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
 import java.nio.file.Path;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.UUID;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.ha.ConfUtils;
 import org.apache.hadoop.ozone.om.OMConfigKeys;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
@@ -216,6 +218,56 @@ void getOmSocketAddressHostPort() {
     assertEquals(100, addr.getPort());
   }
 
+  @Test
+  void getOmSocketAddressIpv6HostPort() throws UnknownHostException {
+    final OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(OZONE_OM_ADDRESS_KEY, "[2001:db8::10]:100");
+
+    assertEquals("[2001:db8::10]:100", OmUtils.getOmRpcAddress(conf));
+    InetSocketAddress addr = OmUtils.getOmAddress(conf);
+    assertEquals(InetAddress.getByName("2001:db8::10"), addr.getAddress());
+    assertEquals(100, addr.getPort());
+  }
+
+  @Test
+  void getOmSocketAddressIpv6Host() throws UnknownHostException {
+    final OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(OZONE_OM_ADDRESS_KEY, "[2001:db8::10]");
+
+    int defaultPort = OMConfigKeys.OZONE_OM_PORT_DEFAULT;
+    assertEquals("[2001:db8::10]:" + defaultPort,
+        OmUtils.getOmRpcAddress(conf));
+    InetSocketAddress addr = OmUtils.getOmAddress(conf);
+    assertEquals(InetAddress.getByName("2001:db8::10"), addr.getAddress());
+    assertEquals(defaultPort, addr.getPort());
+  }
+
+  @Test
+  void getOmRpcAddressIpv6ForConfigKey() {
+    final OzoneConfiguration conf = new OzoneConfiguration();
+    String addressKey = OZONE_OM_ADDRESS_KEY + ".service.node";
+    conf.set(addressKey, "[2001:db8::10]:100");
+
+    assertEquals("[2001:db8::10]:100",
+        OmUtils.getOmRpcAddress(conf, addressKey));
+  }
+
+  @Test
+  void getOmPeerHttpAddressesIpv6() {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    String serviceId = "service";
+    String nodeId = "node";
+    conf.set(ConfUtils.addKeySuffixes(OMConfigKeys.OZONE_OM_HTTP_ADDRESS_KEY, 
serviceId, nodeId),
+        "[2001:db8::10]:100");
+    conf.set(ConfUtils.addKeySuffixes(OMConfigKeys.OZONE_OM_HTTPS_ADDRESS_KEY, 
serviceId, nodeId),
+        "[2001:db8::10]:200");
+
+    assertEquals("[2001:db8::10]:100",
+        OmUtils.getHttpAddressForOMPeerNode(conf, serviceId, nodeId, 
"fallback"));
+    assertEquals("[2001:db8::10]:200",
+        OmUtils.getHttpsAddressForOMPeerNode(conf, serviceId, nodeId, 
"fallback"));
+  }
+
   @Test
   void getOmSocketAddressEmpty() {
     final OzoneConfiguration conf = new OzoneConfiguration();
diff --git 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestOMFailoverProxyProvider.java
 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestOMFailoverProxyProvider.java
index 4fdb3e1cb1d..4a91b44ee34 100644
--- 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestOMFailoverProxyProvider.java
+++ 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestOMFailoverProxyProvider.java
@@ -177,6 +177,23 @@ public void testExcludesListenerNodes() throws Exception {
     }
   }
 
+  @Test
+  public void testConfiguredIpv6Address() throws Exception {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(OZONE_OM_ADDRESS_KEY, "[2001:db8::10]:9862");
+
+    try (HadoopRpcOMFailoverProxyProvider<OzoneManagerProtocolPB> ipv6Provider 
=
+             new HadoopRpcOMFailoverProxyProvider<>(conf,
+                 UserGroupInformation.getCurrentUser(), null,
+                 OzoneManagerProtocolPB.class)) {
+      List<OMProxyInfo<OzoneManagerProtocolPB>> proxies =
+          ipv6Provider.getOMProxies();
+      assertEquals(1, proxies.size());
+      assertEquals("[2001:db8::10]:9862",
+          proxies.get(0).getAddressString());
+    }
+  }
+
   @Test
   public void testOMProxyMap() {
     final String serviceID = "service0";
diff --git 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneFileSystem.java
 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneFileSystem.java
index 7ade8339232..28cdb10dddb 100644
--- 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneFileSystem.java
+++ 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneFileSystem.java
@@ -33,6 +33,7 @@
 
 import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
+import com.google.common.net.InetAddresses;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -129,6 +130,9 @@ public class BasicOzoneFileSystem extends FileSystem {
       "o3fs://bucket.volume.om-host.example.com:5678/key  OR " +
       "o3fs://bucket.volume.omServiceId/key";
 
+  private static final String IPV6_EXCEPTION_TEXT =
+      "Raw IPv6 literals are not supported in O3FS authorities; use an OM 
service ID or DNS name instead";
+
   private static final int PATH_DEPTH_TO_BUCKET = 0;
 
   @Override
@@ -162,6 +166,10 @@ public void initialize(URI name, Configuration conf) 
throws IOException {
     String volumeStr = matcher.group(2);
     String remaining = matcher.groupCount() == 3 ? matcher.group(3) : null;
 
+    if (!isEmpty(remaining) && remaining.contains(":") && 
InetAddresses.isInetAddress(remaining)) {
+      throw new IllegalArgumentException(IPV6_EXCEPTION_TEXT);
+    }
+
     String omHost = null;
     int omPort = -1;
     if (!isEmpty(remaining)) {
diff --git 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneFileSystem.java
 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneFileSystem.java
index 089f0c453e6..5eb54a21335 100644
--- 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneFileSystem.java
+++ 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneFileSystem.java
@@ -38,6 +38,7 @@
 import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
 import com.google.common.net.HostAndPort;
+import com.google.common.net.InetAddresses;
 import io.opentelemetry.api.trace.Span;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -132,7 +133,12 @@ public class BasicRootedOzoneFileSystem extends FileSystem 
{
       "URL should be one of the following formats: " +
       "ofs://om-service-id/path/to/key  OR " +
       "ofs://om-host.example.com/path/to/key  OR " +
-      "ofs://om-host.example.com:5678/path/to/key";
+      "ofs://om-host.example.com:5678/path/to/key  OR " +
+      "ofs://[2001:db8::10]:9862/path/to/key";
+
+  private static final String UNBRACKETED_IPV6_EXCEPTION_TEXT =
+      "IPv6 literals in OFS authorities must be enclosed in brackets, for 
example " +
+      "ofs://[2001:db8::10]/ or ofs://[2001:db8::10]:9862/";
 
   private static final int PATH_DEPTH_TO_BUCKET = 2;
   private OzoneConfiguration ozoneConfiguration;
@@ -159,6 +165,13 @@ public void initialize(URI name, Configuration conf) 
throws IOException {
       throw new IllegalArgumentException(URI_EXCEPTION_TEXT);
     }
 
+    if (authority.indexOf(':') != authority.lastIndexOf(':') && 
!authority.startsWith("[")) {
+      if (InetAddresses.isInetAddress(authority)) {
+        throw new IllegalArgumentException(UNBRACKETED_IPV6_EXCEPTION_TEXT);
+      }
+      throw new IllegalArgumentException(URI_EXCEPTION_TEXT);
+    }
+
     // Parse hostname and port. HostAndPort is bracket-aware, so IPv6 literal
     // authorities (for example [::1]:9862) are split correctly instead of on
     // every colon.
diff --git 
a/hadoop-ozone/ozonefs-common/src/test/java/org/apache/hadoop/fs/ozone/TestBasicOzoneFileSystems.java
 
b/hadoop-ozone/ozonefs-common/src/test/java/org/apache/hadoop/fs/ozone/TestBasicOzoneFileSystems.java
index 6cab32c9ea0..f9e9ebfc6da 100644
--- 
a/hadoop-ozone/ozonefs-common/src/test/java/org/apache/hadoop/fs/ozone/TestBasicOzoneFileSystems.java
+++ 
b/hadoop-ozone/ozonefs-common/src/test/java/org/apache/hadoop/fs/ozone/TestBasicOzoneFileSystems.java
@@ -29,13 +29,18 @@
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isNull;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.nullable;
+import static org.mockito.Mockito.same;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.io.IOException;
 import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.Arrays;
 import java.util.Collection;
 import org.apache.hadoop.conf.Configuration;
@@ -178,6 +183,106 @@ public void testRootedAuthorityParsing(String uri, String 
expectedHost,
     assertEquals(new URI(uri).getAuthority(), ofs.getUri().getAuthority());
   }
 
+  @Test
+  public void testRootedIpv6UriConstructionAndRoundTrip() throws Exception {
+    URI uri = new URI("ofs", null, "2001:db8::10", 9862, "/", null, null);
+    assertEquals("ofs://[2001:db8::10]:9862/", uri.toString());
+
+    BasicRootedOzoneFileSystem ofs = spy(new BasicRootedOzoneFileSystem());
+    BasicRootedOzoneClientAdapterImpl adapter = 
mock(BasicRootedOzoneClientAdapterImpl.class);
+    doReturn(adapter).when(ofs).createAdapter(any(), anyString(), anyInt());
+    ofs.initialize(uri, new OzoneConfiguration());
+
+    Path qualified = ofs.makeQualified(new Path("/volume/bucket/key"));
+    assertEquals("ofs://[2001:db8::10]:9862/volume/bucket/key", 
qualified.toString());
+    assertEquals(qualified, new Path(qualified.toUri()));
+  }
+
+  @ParameterizedTest
+  @CsvSource({
+      "ofs://2001:db8::10/",
+      "ofs://2001:db8::10:9862/"
+  })
+  public void testRootedAuthorityRejectsUnbracketedIpv6(String uri) throws 
Exception {
+    BasicRootedOzoneFileSystem ofs = new BasicRootedOzoneFileSystem();
+    IllegalArgumentException exception = 
assertThrows(IllegalArgumentException.class,
+        () -> ofs.initialize(new URI(uri), new OzoneConfiguration()));
+    assertTrue(exception.getMessage().contains("must be enclosed in 
brackets"));
+  }
+
+  @Test
+  public void testRootedMalformedAuthorityUsesGeneralError() throws Exception {
+    BasicRootedOzoneFileSystem ofs = new BasicRootedOzoneFileSystem();
+    IllegalArgumentException exception = 
assertThrows(IllegalArgumentException.class,
+        () -> ofs.initialize(new URI("ofs://host:123:456/"), new 
OzoneConfiguration()));
+    assertTrue(exception.getMessage().contains("URL should be one of the 
following formats"));
+  }
+
+  @ParameterizedTest
+  @CsvSource(value = {
+      "o3fs://bucket.volume/, NULL, -1",
+      "o3fs://bucket.volume.omservice1/, omservice1, -1",
+      "o3fs://bucket.volume.om.example.com:9862/, om.example.com, 9862",
+      "o3fs://bucket.volume.192.0.2.1:9862/, 192.0.2.1, 9862"
+  }, nullValues = "NULL")
+  public void testO3fsAuthorityParsing(String uri, String expectedHost, int 
expectedPort) throws Exception {
+    BasicOzoneFileSystem o3fs = spy(new BasicOzoneFileSystem());
+    BasicOzoneClientAdapterImpl adapter = 
mock(BasicOzoneClientAdapterImpl.class);
+    doReturn(adapter).when(o3fs).createAdapter(any(), anyString(), 
anyString(), nullable(String.class), anyInt());
+
+    o3fs.initialize(new URI(uri), new OzoneConfiguration());
+
+    ArgumentCaptor<String> hostCaptor = ArgumentCaptor.forClass(String.class);
+    ArgumentCaptor<Integer> portCaptor = 
ArgumentCaptor.forClass(Integer.class);
+    verify(o3fs).createAdapter(any(), anyString(), anyString(), 
hostCaptor.capture(), portCaptor.capture());
+    assertEquals(expectedHost, hostCaptor.getValue());
+    assertEquals(expectedPort, portCaptor.getValue().intValue());
+    assertEquals(new URI(uri).getAuthority(), o3fs.getUri().getAuthority());
+  }
+
+  @Test
+  public void testO3fsNullEndpoint() throws Exception {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    BasicOzoneFileSystem o3fs = spy(new BasicOzoneFileSystem());
+    BasicOzoneClientAdapterImpl adapter = 
mock(BasicOzoneClientAdapterImpl.class);
+    doReturn(adapter).when(o3fs).createAdapter(any(), anyString(), 
anyString(), nullable(String.class), anyInt());
+
+    o3fs.initialize(new URI("o3fs://bucket.volume/"), conf);
+
+    verify(o3fs).createAdapter(same(conf), eq("bucket"), eq("volume"), 
isNull(), eq(-1));
+  }
+
+  @Test
+  public void testO3fsDottedIpv6UriCannotBeConstructed() {
+    assertThrows(URISyntaxException.class,
+        () -> new URI("o3fs://bucket.volume.[2001:db8::10]:9862/"));
+  }
+
+  @ParameterizedTest
+  @CsvSource({
+      "o3fs://bucket.volume.2001:db8::10/",
+      "o3fs://bucket.volume.2001:db8::10:9862/"
+  })
+  public void testO3fsInitializationRejectsIpv6LiteralForms(String uri) throws 
Exception {
+    BasicOzoneFileSystem o3fs = new BasicOzoneFileSystem();
+    IllegalArgumentException exception = 
assertThrows(IllegalArgumentException.class,
+        () -> o3fs.initialize(new URI(uri), new OzoneConfiguration()));
+    assertTrue(exception.getMessage().contains("use an OM service ID or DNS 
name instead"));
+  }
+
+  @ParameterizedTest
+  @CsvSource({
+      "o3fs://[2001:db8::10]/",
+      "o3fs://[2001:db8::10]:9862/",
+      "o3fs://bucket.volume.host:123:456/"
+  })
+  public void testO3fsMalformedAuthoritiesUseGeneralError(String uri) throws 
Exception {
+    BasicOzoneFileSystem o3fs = new BasicOzoneFileSystem();
+    IllegalArgumentException exception = 
assertThrows(IllegalArgumentException.class,
+        () -> o3fs.initialize(new URI(uri), new OzoneConfiguration()));
+    assertTrue(exception.getMessage().contains("Ozone file system URL should 
be one of the following formats"));
+  }
+
   @Test
   public void testRootedAuthorityRejectsOutOfRangePort() {
     BasicRootedOzoneFileSystem ofs = spy(new BasicRootedOzoneFileSystem());


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to