Copilot commented on code in PR #10030:
URL: https://github.com/apache/ozone/pull/10030#discussion_r3089158895


##########
hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/GrpcOmTransport.java:
##########
@@ -338,6 +388,75 @@ public void close() throws IOException {
     shutdown();
   }
 
+  private OMResponse submitRequest(ManagedChannel channel, OMRequest request,
+      String clientHostname, String clientIpAddress) {
+    Metadata headers = new Metadata();
+    if (clientHostname != null) {
+      headers.put(CLIENT_HOSTNAME_METADATA_KEY, clientHostname);
+    }
+    if (clientIpAddress != null) {
+      headers.put(CLIENT_IP_ADDRESS_METADATA_KEY, clientIpAddress);
+    }
+
+    org.apache.ratis.thirdparty.io.grpc.Channel intercepted =
+        ClientInterceptors.intercept(channel, new 
FixedHeadersInterceptor(headers));
+    return ClientCalls.blockingUnaryCall(intercepted, SUBMIT_REQUEST_METHOD,
+        CallOptions.DEFAULT, request);
+  }
+
+  private static final class FixedHeadersInterceptor implements 
ClientInterceptor {
+    private final Metadata headers;
+
+    private FixedHeadersInterceptor(Metadata headers) {
+      this.headers = headers;
+    }
+
+    @Override
+    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+        MethodDescriptor<ReqT, RespT> methodDescriptor,
+        CallOptions callOptions,
+        org.apache.ratis.thirdparty.io.grpc.Channel channel) {
+      return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
+          channel.newCall(methodDescriptor, callOptions)) {
+        @Override
+        public void start(Listener<RespT> responseListener, Metadata metadata) 
{
+          metadata.merge(headers);
+          super.start(responseListener, metadata);
+        }
+      };
+    }
+  }
+
+  private static final class Proto2Marshaller<T> implements 
MethodDescriptor.Marshaller<T> {
+    private final Proto2Parser<T> parser;
+
+    private Proto2Marshaller(Proto2Parser<T> parser) {
+      this.parser = parser;
+    }
+
+    @Override
+    public InputStream stream(T value) {
+      if (!(value instanceof com.google.protobuf.MessageLite)) {
+        throw new IllegalArgumentException("Expected protobuf 
request/response");
+      }
+      return new ByteArrayInputStream(((com.google.protobuf.MessageLite) 
value).toByteArray());
+    }

Review Comment:
   Proto2Marshaller.stream() serializes via MessageLite.toByteArray(), which 
always copies the message into a new byte[]. This contradicts the PR goal of 
preserving zero-copy marshalling and can add avoidable allocations on every 
RPC. Prefer streaming from the message's ByteString (eg. 
toByteString().newInput()) or another zero-copy InputStream source.



##########
pom.xml:
##########
@@ -2285,6 +2365,15 @@
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-clean-plugin</artifactId>
         <configuration>
+          <!--
+            The Cursor/VSCode Java Language Server recreates class files in 
target/classes/
+            and target/test-classes/ (with com.apple.provenance extended 
attributes) faster
+            than the pre-clean rm -rf can remove them.  failOnError=false lets 
the build
+            continue even if the clean plugin cannot remove those IDE-managed 
files; the
+            subsequent compile phase writes fresh class files over them so the 
build result
+            is still correct.
+          -->
+          <failOnError>false</failOnError>

Review Comment:
   Setting maven-clean-plugin <failOnError>false</failOnError> globally can 
mask legitimate clean failures (eg. permission issues) and allow stale build 
outputs to leak into subsequent phases. Consider scoping this workaround to the 
affected environment (eg. a macOS-only profile/property) so other platforms 
still fail fast on real clean errors.



##########
hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestDelegationToken.java:
##########
@@ -389,14 +390,19 @@ public void testDelegationToken(boolean useIp) throws 
Exception {
 
       // Case 6: Test failure of token cancellation.
       // Get Om client, this time authentication using Token will fail as
-      // token is not in cache anymore.
+      // token is not in cache anymore.  Use minimal retries so the default
+      // failover policy (500 attempts × 2 s) does not exhaust the 5-minute
+      // test timeout before the security rejection propagates.  The
+      // rejection may arrive as OMException (OM-level) or a plain IOException
+      // (transport/auth-level), both are valid security rejections.
+      OzoneConfiguration fastConf = new OzoneConfiguration(conf);
+      fastConf.setInt(OZONE_CLIENT_FAILOVER_MAX_ATTEMPTS_KEY, 3);
+      fastConf.setLong(OZONE_CLIENT_WAIT_BETWEEN_RETRIES_MILLIS_KEY, 100L);
       omClient = new OzoneManagerProtocolClientSideTranslatorPB(
-          OmTransportFactory.create(conf, testUser, null),
+          OmTransportFactory.create(fastConf, testUser, null),
           RandomStringUtils.secure().nextAscii(5));
-      ex = assertThrows(OMException.class,
+      assertThrows(IOException.class,
           () -> omClient.cancelDelegationToken(token));

Review Comment:
   This assertion was broadened to any IOException, which weakens the check 
that token-cancel fails with the expected security-related error. To keep the 
test meaningful while still avoiding long retry timeouts, consider asserting on 
the IOException cause/message (or using assertThrowsExactly/expected subclasses 
like OMException/AccessControlException) rather than any IOException.



##########
hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/GrpcOmTransport.java:
##########
@@ -338,6 +388,75 @@ public void close() throws IOException {
     shutdown();
   }
 
+  private OMResponse submitRequest(ManagedChannel channel, OMRequest request,
+      String clientHostname, String clientIpAddress) {
+    Metadata headers = new Metadata();
+    if (clientHostname != null) {
+      headers.put(CLIENT_HOSTNAME_METADATA_KEY, clientHostname);
+    }
+    if (clientIpAddress != null) {
+      headers.put(CLIENT_IP_ADDRESS_METADATA_KEY, clientIpAddress);
+    }
+
+    org.apache.ratis.thirdparty.io.grpc.Channel intercepted =
+        ClientInterceptors.intercept(channel, new 
FixedHeadersInterceptor(headers));
+    return ClientCalls.blockingUnaryCall(intercepted, SUBMIT_REQUEST_METHOD,
+        CallOptions.DEFAULT, request);

Review Comment:
   submitRequest() wraps the ManagedChannel with 
ClientInterceptors.intercept(...) on every call to attach fixed headers. This 
creates additional wrapper allocations per RPC. Since the hostname/IP values 
are effectively constant for the process, consider attaching a headers 
interceptor once at channel construction time (or caching the intercepted 
Channel) to reduce per-call overhead.



##########
pom.xml:
##########
@@ -2318,6 +2440,174 @@
               </target>
             </configuration>
           </execution>
+          <execution>
+            <!--
+              When running with -Dmaven.test.skip=true, Maven does not compile 
test classes.
+              However the Cursor/VSCode Java Language Server writes class 
files into
+              target/test-classes/ concurrently.  These IDE-generated files 
may be partially
+              written (corrupted) and will cause the maven-dependency-plugin 
analyze-only goal
+              (bound to the verify phase) to fail with "Index out of bounds" 
class-reading
+              errors.  Deleting target/test-classes/ in prepare-package (which 
runs just
+              before verify) removes these LSP-generated stubs before the 
analyzer runs,
+              making the analysis deterministic and correct.
+            -->
+            <id>pre-verify-delete-test-classes</id>
+            <goals>
+              <goal>run</goal>
+            </goals>
+            <phase>pre-integration-test</phase>
+            <configuration>
+              <target>
+                <exec executable="/bin/rm" failonerror="false">
+                  <arg value="-rf" />
+                  <arg value="${project.build.directory}/test-classes" />
+                </exec>
+              </target>
+            </configuration>
+          </execution>
+          <execution>
+            <!--
+              Refresh target/classes/ from the freshly-packaged JAR just 
before the
+              dependency:analyze-only goal (which runs in the verify phase).  
The Cursor/LSP
+              Java Language Server may overwrite class files in 
target/classes/ with corrupt
+              or stale copies between the compile and verify phases.  By 
deleting classes/
+              and re-extracting from the module JAR (produced by 
maven-jar-plugin in the
+              package phase), we guarantee that the dependency analyzer sees 
clean, correct
+              bytecode.  For pom-packaging modules with no JAR, the unzip exec 
fails
+              silently (failonerror=false) and classes/ remains empty - which 
is correct.
+            -->
+            <id>pre-verify-refresh-classes-from-jar</id>
+            <goals>
+              <goal>run</goal>
+            </goals>
+            <phase>pre-integration-test</phase>
+            <configuration>
+              <target>
+                <!--
+                  Only wipe and restore target/classes/ when the module JAR 
was actually produced
+                  (i.e. this is a jar-packaging module whose package phase ran 
successfully).
+                  For pom-packaging modules – and for any edge case where the 
JAR is absent –
+                  we skip the destructive rm -rf so that the freshly compiled 
class files are
+                  left undisturbed for downstream reactor modules to compile 
against.
+                  A POSIX shell one-liner is used because the Ant bundled with
+                  maven-antrun-plugin does not support if/unless on <exec>.
+                -->
+                <exec executable="/bin/sh" failonerror="false">
+                  <arg value="-c" />
+                  <arg 
value="JAR='${project.build.directory}/${project.build.finalName}.jar'; test -f 
&quot;$JAR&quot; &amp;&amp; /bin/rm -rf '${project.build.outputDirectory}' 
&amp;&amp; unzip -qo &quot;$JAR&quot; -d '${project.build.outputDirectory}'" />
+                </exec>

Review Comment:
   The pre-verify-refresh-classes-from-jar execution relies on /bin/sh and 
unzip being available and uses a shell one-liner to conditionally 
delete/restore target/classes. This is fragile and non-portable; consider using 
Ant condition + built-in <delete>/<unzip> tasks (or a platform-scoped profile) 
so the behavior is deterministic across environments.



##########
pom.xml:
##########
@@ -2305,6 +2394,39 @@
         <artifactId>maven-antrun-plugin</artifactId>
         <version>${maven-antrun-plugin.version}</version>
         <executions>
+          <execution>
+            <!--
+              On macOS the Cursor/LSP Java Language Server writes class files 
with the
+              com.apple.provenance extended attribute into target/classes/.  
Java NIO's
+              Files.delete() (used by maven-clean-plugin) cannot remove those 
entries,
+              aborting the clean and leaving a mixed-state target directory 
that confuses
+              incremental compilation and the dependency analyzer.  Deleting 
via the OS
+              rm command in the pre-clean phase resolves this: by the time 
maven-clean-plugin
+              runs, the target directory no longer exists so the plugin has 
nothing to do.
+            -->
+            <id>pre-clean-force-delete-target</id>
+            <goals>
+              <goal>run</goal>
+            </goals>
+            <phase>pre-clean</phase>
+            <configuration>
+              <target>
+                <!--
+                  On macOS, the Cursor/LSP Java Language Server writes class 
files with the
+                  com.apple.provenance extended attribute into 
target/classes/. This attribute
+                  is a protected system attribute that cannot be removed by 
xattr -cr; Java
+                  NIO's Files.delete() (used by maven-clean-plugin) also 
cannot delete such
+                  files. Using /bin/rm -rf in the pre-clean phase deletes the 
entire target
+                  directory via the unlink() syscall, which is not subject to 
this restriction.
+                  By the time maven-clean-plugin runs, the target directory no 
longer exists.
+                -->
+                <exec executable="/bin/rm" failonerror="false">
+                  <arg value="-rf" />
+                  <arg value="${project.build.directory}" />
+                </exec>

Review Comment:
   The build now executes /bin/rm during pre-clean. This is non-portable (eg. 
Windows) and also bypasses Maven's normal clean semantics. Please gate this 
execution behind an OS-activated profile (macOS only) and/or an explicit opt-in 
property so CI and other developer environments are not affected.



##########
hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/GrpcOmTransport.java:
##########
@@ -72,22 +88,36 @@ public class GrpcOmTransport implements OmTransport {
       LoggerFactory.getLogger(GrpcOmTransport.class);
 
   private static final String CLIENT_NAME = "GrpcOmTransport";
+  private static final String SERVICE_NAME = 
"hadoop.ozone.OzoneManagerService";
   private static final int SHUTDOWN_WAIT_INTERVAL = 100;
   private static final int SHUTDOWN_MAX_WAIT_SECONDS = 5;
   private final AtomicBoolean isRunning = new AtomicBoolean(false);
 
   // gRPC specific
   private static List<X509Certificate> caCerts = null;
 
-  private final Map<String,
-      OzoneManagerServiceGrpc.OzoneManagerServiceBlockingStub> clients;
+  private static final Metadata.Key<String> CLIENT_HOSTNAME_METADATA_KEY =
+      Metadata.Key.of("CLIENT_HOSTNAME", Metadata.ASCII_STRING_MARSHALLER);
+  private static final Metadata.Key<String> CLIENT_IP_ADDRESS_METADATA_KEY =
+      Metadata.Key.of("CLIENT_IP_ADDRESS", Metadata.ASCII_STRING_MARSHALLER);

Review Comment:
   GrpcOmTransport duplicates the metadata key definitions for 
CLIENT_HOSTNAME/CLIENT_IP_ADDRESS instead of reusing GrpcClientConstants. This 
risks subtle mismatches if the header names/marshallers change elsewhere. 
Prefer referencing GrpcClientConstants.CLIENT_*_METADATA_KEY directly.
   ```suggestion
         GrpcClientConstants.CLIENT_HOSTNAME_METADATA_KEY;
     private static final Metadata.Key<String> CLIENT_IP_ADDRESS_METADATA_KEY =
         GrpcClientConstants.CLIENT_IP_ADDRESS_METADATA_KEY;
   ```



##########
hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestSecureOzoneRpcClient.java:
##########
@@ -435,18 +434,29 @@ public void testS3Auth() throws Exception {
   }
 
   @Test
-  public void testRemoteException() {
+  public void testRemoteException() throws IOException {
     UserGroupInformation realUser = 
UserGroupInformation.createRemoteUser("realUser");
     UserGroupInformation proxyUser = 
UserGroupInformation.createProxyUser("user", realUser);
 
-    assertThrows(AccessControlException.class, () -> {
-      proxyUser.doAs((PrivilegedExceptionAction<Void>) () -> {
-        try (OzoneClient ozoneClient = 
OzoneClientFactory.getRpcClient(getCluster().getConf())) {
-          ozoneClient.getObjectStore().listVolumes("/");
-        }
-        return null;
-      });
-    });
+    // Use minimal retries so the test fails fast instead of hitting the
+    // 5-minute @Test timeout.  The proxy user has no valid credentials;
+    // the OM rejects it quickly, but the default failover policy
+    // (500 attempts × 2 s) would exhaust the timeout before propagating
+    // the rejection.  The rejection may arrive as AccessControlException
+    // (authorization-layer refusal) or as a plain IOException
+    // (transport/authentication-layer refusal), both of which are valid
+    // security rejections.
+    OzoneConfiguration testConf = new 
OzoneConfiguration(getCluster().getConf());
+    testConf.setInt(OzoneConfigKeys.OZONE_CLIENT_FAILOVER_MAX_ATTEMPTS_KEY, 3);
+    
testConf.setLong(OzoneConfigKeys.OZONE_CLIENT_WAIT_BETWEEN_RETRIES_MILLIS_KEY, 
100L);
+
+    assertThrows(IOException.class, () ->
+        proxyUser.doAs((PrivilegedExceptionAction<Void>) () -> {
+          try (OzoneClient ozoneClient = 
OzoneClientFactory.getRpcClient(testConf)) {
+            ozoneClient.getObjectStore().listVolumes("/");
+          }
+          return null;
+        }));

Review Comment:
   This test now accepts any IOException, which can also include unrelated 
failures (eg. transient network/cluster issues) and reduces the signal that the 
rejection is specifically auth-related. Consider asserting on the exception 
type/cause chain (eg. AccessControlException/OMException) or matching an 
expected message to keep the test deterministic.



##########
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManagerServiceGrpc.java:
##########
@@ -89,4 +119,27 @@ private static byte[] getClientId() {
     return UUIDUtil.randomUUIDBytes();
   }
 
+  private static <T extends MessageLite> MethodDescriptor.Marshaller<T> 
proto2Marshaller(
+      Proto2Parser<T> parser) {
+    return new MethodDescriptor.Marshaller<T>() {
+      @Override
+      public InputStream stream(T value) {
+        return new ByteArrayInputStream(value.toByteArray());
+      }

Review Comment:
   proto2Marshaller().stream() uses value.toByteArray(), which forces a full 
copy of every request/response. If the intent is to keep zero-copy behavior, 
prefer streaming from ByteString (eg. value.toByteString().newInput()) to avoid 
extra allocations and match the PR description.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to