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 17913d99351 HDDS-10522. Make OmPrefixInfo immutable (#9427)
17913d99351 is described below

commit 17913d99351acd5d04fd6be6f35d9b84641fd1e3
Author: Eric C. Ho <[email protected]>
AuthorDate: Wed Dec 10 19:07:39 2025 +0800

    HDDS-10522. Make OmPrefixInfo immutable (#9427)
---
 hadoop-ozone/interface-storage/pom.xml             |  4 +
 .../hadoop/ozone/om/helpers/OmPrefixInfo.java      | 64 ++++++++-------
 .../hadoop/ozone/om/helpers/TestOmPrefixInfo.java  | 26 ++++--
 .../apache/hadoop/ozone/om/PrefixManagerImpl.java  | 92 ++++++++++++----------
 4 files changed, 112 insertions(+), 74 deletions(-)

diff --git a/hadoop-ozone/interface-storage/pom.xml 
b/hadoop-ozone/interface-storage/pom.xml
index a26ce7f2401..acf48a34e53 100644
--- a/hadoop-ozone/interface-storage/pom.xml
+++ b/hadoop-ozone/interface-storage/pom.xml
@@ -25,6 +25,10 @@
   <name>Apache Ozone Storage Interface</name>
   <description>Apache Ozone Storage Interface</description>
   <dependencies>
+    <dependency>
+      <groupId>com.github.stephenc.jcip</groupId>
+      <artifactId>jcip-annotations</artifactId>
+    </dependency>
     <dependency>
       <groupId>com.google.guava</groupId>
       <artifactId>guava</artifactId>
diff --git 
a/hadoop-ozone/interface-storage/src/main/java/org/apache/hadoop/ozone/om/helpers/OmPrefixInfo.java
 
b/hadoop-ozone/interface-storage/src/main/java/org/apache/hadoop/ozone/om/helpers/OmPrefixInfo.java
index 36d4e035d6a..20a9fbdd4cf 100644
--- 
a/hadoop-ozone/interface-storage/src/main/java/org/apache/hadoop/ozone/om/helpers/OmPrefixInfo.java
+++ 
b/hadoop-ozone/interface-storage/src/main/java/org/apache/hadoop/ozone/om/helpers/OmPrefixInfo.java
@@ -17,12 +17,11 @@
 
 package org.apache.hadoop.ozone.om.helpers;
 
-import java.util.ArrayList;
-import java.util.LinkedList;
+import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.concurrent.CopyOnWriteArrayList;
+import net.jcip.annotations.Immutable;
 import org.apache.hadoop.hdds.utils.db.Codec;
 import org.apache.hadoop.hdds.utils.db.CopyObject;
 import org.apache.hadoop.hdds.utils.db.DelegatedCodec;
@@ -35,6 +34,7 @@
  * can be extended for other OzFS optimizations in future.
  */
 // TODO: support Auditable interface
+@Immutable
 public final class OmPrefixInfo extends WithObjectID implements 
CopyObject<OmPrefixInfo> {
   private static final Codec<OmPrefixInfo> CODEC = new DelegatedCodec<>(
       Proto2Codec.get(PersistedPrefixInfo.getDefaultInstance()),
@@ -43,12 +43,12 @@ public final class OmPrefixInfo extends WithObjectID 
implements CopyObject<OmPre
       OmPrefixInfo.class);
 
   private final String name;
-  private final CopyOnWriteArrayList<OzoneAcl> acls;
+  private final ImmutableList<OzoneAcl> acls;
 
   private OmPrefixInfo(Builder b) {
     super(b);
-    name = b.name;
-    acls = new CopyOnWriteArrayList<>(b.acls);
+    this.name = b.name;
+    this.acls = b.acls.build();
   }
 
   public static Codec<OmPrefixInfo> getCodec() {
@@ -63,18 +63,6 @@ public List<OzoneAcl> getAcls() {
     return acls;
   }
 
-  public boolean addAcl(OzoneAcl acl) {
-    return OzoneAclUtil.addAcl(acls, acl);
-  }
-
-  public boolean removeAcl(OzoneAcl acl) {
-    return OzoneAclUtil.removeAcl(acls, acl);
-  }
-
-  public boolean setAcls(List<OzoneAcl> newAcls) {
-    return OzoneAclUtil.setAcl(acls, newAcls);
-  }
-
   /**
    * Returns the name of the prefix path.
    * @return name of the prefix path.
@@ -88,8 +76,8 @@ public String getName() {
    *
    * @return Builder
    */
-  public static OmPrefixInfo.Builder newBuilder() {
-    return new OmPrefixInfo.Builder();
+  public static Builder newBuilder() {
+    return new Builder();
   }
 
   /**
@@ -97,26 +85,47 @@ public static OmPrefixInfo.Builder newBuilder() {
    */
   public static class Builder extends WithObjectID.Builder<OmPrefixInfo> {
     private String name;
-    private final List<OzoneAcl> acls;
+    private final AclListBuilder acls;
 
     public Builder() {
       //Default values
-      this.acls = new LinkedList<>();
+      this(AclListBuilder.empty());
+    }
+
+    private Builder(AclListBuilder acls) {
+      this.acls = acls;
     }
 
     public Builder(OmPrefixInfo obj) {
       super(obj);
-      setName(obj.name);
-      acls = new ArrayList<>(obj.getAcls());
+      this.acls = AclListBuilder.of(obj.acls);
+      this.name = obj.name;
     }
 
     public Builder setAcls(List<OzoneAcl> listOfAcls) {
-      if (listOfAcls != null) {
-        acls.addAll(listOfAcls);
-      }
+      acls.set(listOfAcls);
+      return this;
+    }
+
+    public Builder addAcls(List<OzoneAcl> listOfAcls) {
+      acls.addAll(listOfAcls);
+      return this;
+    }
+
+    public Builder addAcl(OzoneAcl acl) {
+      acls.add(acl);
+      return this;
+    }
+
+    public Builder removeAcl(OzoneAcl acl) {
+      acls.remove(acl);
       return this;
     }
 
+    public boolean isAclsChanged() {
+      return acls.isChanged();
+    }
+
     public Builder setName(String n) {
       this.name = n;
       return this;
@@ -250,4 +259,3 @@ public Builder toBuilder() {
     return new Builder(this);
   }
 }
-
diff --git 
a/hadoop-ozone/interface-storage/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmPrefixInfo.java
 
b/hadoop-ozone/interface-storage/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmPrefixInfo.java
index 5fcaed544af..687136d16e9 100644
--- 
a/hadoop-ozone/interface-storage/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmPrefixInfo.java
+++ 
b/hadoop-ozone/interface-storage/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmPrefixInfo.java
@@ -20,6 +20,7 @@
 import static org.apache.hadoop.ozone.OzoneAcl.AclScope.ACCESS;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import com.google.protobuf.ByteString;
 import java.util.ArrayList;
@@ -94,14 +95,29 @@ public void testCopyObject() {
 
     assertEquals(omPrefixInfo, clonePrefixInfo);
 
+    OmPrefixInfo modifiedPrefixInfo = omPrefixInfo.toBuilder()
+        .addAcls(Collections.singletonList(OzoneAcl.of(
+            IAccessAuthorizer.ACLIdentityType.USER, username,
+            ACCESS, IAccessAuthorizer.ACLType.READ)))
+        .build();
 
-    // Change acls and check.
-    omPrefixInfo.addAcl(OzoneAcl.of(
-        IAccessAuthorizer.ACLIdentityType.USER, username,
-        ACCESS, IAccessAuthorizer.ACLType.READ));
+    assertNotEquals(modifiedPrefixInfo, clonePrefixInfo);
+  }
 
-    assertNotEquals(omPrefixInfo, clonePrefixInfo);
+  @Test
+  public void testImmutability() {
+    String testPath = "/my/custom/path";
+    String username = "myuser";
+    OmPrefixInfo omPrefixInfo = getOmPrefixInfoForTest(testPath,
+        IAccessAuthorizer.ACLIdentityType.USER,
+        username,
+        IAccessAuthorizer.ACLType.WRITE,
+        ACCESS);
 
+    assertThrows(UnsupportedOperationException.class,
+        () -> omPrefixInfo.getAcls().add(OzoneAcl.of(
+            IAccessAuthorizer.ACLIdentityType.USER, username,
+            ACCESS, IAccessAuthorizer.ACLType.READ)));
   }
 
   @Test
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/PrefixManagerImpl.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/PrefixManagerImpl.java
index f6615b92f2d..2e1e452aaae 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/PrefixManagerImpl.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/PrefixManagerImpl.java
@@ -235,33 +235,35 @@ public OMPrefixAclOpResult addAcl(OzoneObj ozoneObj, 
OzoneAcl ozoneAcl,
     // No explicit prefix create API, both add/set Acl can get new prefix
     // created. When new prefix is created, it should inherit parent prefix
     // or bucket default ACLs.
-    boolean newPrefix = false;
-    if (prefixInfo == null) {
-      OmPrefixInfo.Builder prefixInfoBuilder =
-          new OmPrefixInfo.Builder()
-          .setName(ozoneObj.getPath());
-      if (transactionLogIndex > 0) {
-        prefixInfoBuilder.setObjectID(OmUtils.getObjectIdFromTxId(
-            metadataManager.getOmEpoch(), transactionLogIndex));
-        prefixInfoBuilder.setUpdateID(transactionLogIndex);
-      }
-      prefixInfo = prefixInfoBuilder.build();
-      newPrefix = true;
+    boolean newPrefix = prefixInfo == null;
+    OmPrefixInfo.Builder prefixInfoBuilder = newPrefix
+        ? OmPrefixInfo.newBuilder().setName(ozoneObj.getPath())
+        : prefixInfo.toBuilder();
+
+    if (newPrefix && transactionLogIndex > 0) {
+      prefixInfoBuilder.setObjectID(OmUtils.getObjectIdFromTxId(
+          metadataManager.getOmEpoch(), transactionLogIndex));
+      prefixInfoBuilder.setUpdateID(transactionLogIndex);
     }
 
-    boolean changed = prefixInfo.addAcl(ozoneAcl);
     // Update the in-memory prefix tree regardless whether the ACL is changed.
     // Under OM HA, update ID of the prefix info is updated for every request.
     if (newPrefix) {
-      inheritParentAcl(ozoneObj, prefixInfo);
+      List<OzoneAcl> inheritedAcls = new ArrayList<>();
+      inheritParentAcl(ozoneObj, inheritedAcls);
+      prefixInfoBuilder.addAcls(inheritedAcls);
     }
+    prefixInfoBuilder.addAcl(ozoneAcl);
+    boolean changed = prefixInfoBuilder.isAclsChanged();
+
+    OmPrefixInfo updatedPrefixInfo = prefixInfoBuilder.build();
     // update the in-memory prefix tree
-    prefixTree.insert(ozoneObj.getPath(), prefixInfo);
+    prefixTree.insert(ozoneObj.getPath(), updatedPrefixInfo);
 
     if (!isRatisEnabled) {
-      metadataManager.getPrefixTable().put(ozoneObj.getPath(), prefixInfo);
+      metadataManager.getPrefixTable().put(ozoneObj.getPath(), 
updatedPrefixInfo);
     }
-    return new OMPrefixAclOpResult(prefixInfo, changed);
+    return new OMPrefixAclOpResult(updatedPrefixInfo, changed);
   }
 
   public OMPrefixAclOpResult removeAcl(OzoneObj ozoneObj, OzoneAcl ozoneAcl,
@@ -270,27 +272,32 @@ public OMPrefixAclOpResult removeAcl(OzoneObj ozoneObj, 
OzoneAcl ozoneAcl,
       return new OMPrefixAclOpResult(null, false);
     }
 
-    boolean removed = prefixInfo.removeAcl(ozoneAcl);
+    OmPrefixInfo.Builder prefixInfoBuilder = prefixInfo.toBuilder();
+    prefixInfoBuilder.removeAcl(ozoneAcl);
+    boolean removed = prefixInfoBuilder.isAclsChanged();
+
+    OmPrefixInfo updatedPrefixInfo = removed
+        ? prefixInfoBuilder.build()
+        : prefixInfo;
 
     // Update in-memory prefix tree regardless whether the ACL is changed.
     // Under OM HA, update ID of the prefix info is updated for every request.
-    if (prefixInfo.getAcls().isEmpty()) {
+    if (removed && updatedPrefixInfo.getAcls().isEmpty()) {
       prefixTree.removePrefixPath(ozoneObj.getPath());
       if (!isRatisEnabled) {
         metadataManager.getPrefixTable().delete(ozoneObj.getPath());
       }
     } else {
-      prefixTree.insert(ozoneObj.getPath(), prefixInfo);
+      prefixTree.insert(ozoneObj.getPath(), updatedPrefixInfo);
       if (!isRatisEnabled) {
-        metadataManager.getPrefixTable().put(ozoneObj.getPath(), prefixInfo);
+        metadataManager.getPrefixTable().put(ozoneObj.getPath(), 
updatedPrefixInfo);
       }
     }
-    return new OMPrefixAclOpResult(prefixInfo, removed);
+    return new OMPrefixAclOpResult(updatedPrefixInfo, removed);
   }
 
-  private void inheritParentAcl(OzoneObj ozoneObj, OmPrefixInfo prefixInfo)
+  private void inheritParentAcl(OzoneObj ozoneObj, List<OzoneAcl> aclsToBeSet)
       throws IOException {
-    List<OzoneAcl> aclsToBeSet = prefixInfo.getAcls();
     // Inherit DEFAULT acls from prefix.
     boolean prefixParentFound = false;
     List<OmPrefixInfo> prefixList = getLongestPrefixPathHelper(
@@ -319,29 +326,32 @@ private void inheritParentAcl(OzoneObj ozoneObj, 
OmPrefixInfo prefixInfo)
 
   public OMPrefixAclOpResult setAcl(OzoneObj ozoneObj, List<OzoneAcl> 
ozoneAcls,
       OmPrefixInfo prefixInfo, long transactionLogIndex) throws IOException {
-    boolean newPrefix = false;
-    if (prefixInfo == null) {
-      OmPrefixInfo.Builder prefixInfoBuilder =
-          new OmPrefixInfo.Builder()
-              .setName(ozoneObj.getPath());
-      if (transactionLogIndex > 0) {
-        prefixInfoBuilder.setObjectID(OmUtils.getObjectIdFromTxId(
-            metadataManager.getOmEpoch(), transactionLogIndex));
-        prefixInfoBuilder.setUpdateID(transactionLogIndex);
-      }
-      prefixInfo = prefixInfoBuilder.build();
-      newPrefix = true;
+    boolean newPrefix = prefixInfo == null;
+    OmPrefixInfo.Builder prefixInfoBuilder = newPrefix
+        ? OmPrefixInfo.newBuilder().setName(ozoneObj.getPath())
+        : prefixInfo.toBuilder();
+
+    if (newPrefix && transactionLogIndex > 0) {
+      prefixInfoBuilder.setObjectID(OmUtils.getObjectIdFromTxId(
+          metadataManager.getOmEpoch(), transactionLogIndex));
+      prefixInfoBuilder.setUpdateID(transactionLogIndex);
     }
 
-    boolean changed = prefixInfo.setAcls(ozoneAcls);
+    List<OzoneAcl> aclsToSet = new ArrayList<>();
+    OzoneAclUtil.setAcl(aclsToSet, ozoneAcls);
     if (newPrefix) {
-      inheritParentAcl(ozoneObj, prefixInfo);
+      inheritParentAcl(ozoneObj, aclsToSet);
     }
-    prefixTree.insert(ozoneObj.getPath(), prefixInfo);
+    prefixInfoBuilder.setAcls(aclsToSet);
+    // For new prefix, creation itself is considered a change regardless of 
ACL content
+    boolean changed = newPrefix || prefixInfoBuilder.isAclsChanged();
+
+    OmPrefixInfo updatedPrefixInfo = prefixInfoBuilder.build();
+    prefixTree.insert(ozoneObj.getPath(), updatedPrefixInfo);
     if (!isRatisEnabled) {
-      metadataManager.getPrefixTable().put(ozoneObj.getPath(), prefixInfo);
+      metadataManager.getPrefixTable().put(ozoneObj.getPath(), 
updatedPrefixInfo);
     }
-    return new OMPrefixAclOpResult(prefixInfo, changed);
+    return new OMPrefixAclOpResult(updatedPrefixInfo, changed);
   }
 
   /**


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

Reply via email to