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


##########
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java:
##########
@@ -3268,6 +3270,144 @@ public ListSnapshotResponse listSnapshot(
     }
   }
 
+  @Override
+  public SnapshotCountResponse snapshotCount(String bucketFilter)
+      throws IOException {
+    Map<String, String> auditMap = new LinkedHashMap<>();
+    auditMap.put("bucketFilter", StringUtils.defaultString(bucketFilter));
+
+    try {
+      BucketFilter parsedFilter = BucketFilter.parse(bucketFilter);
+      Map<String, BucketSnapshotCount> bucketCounts = new LinkedHashMap<>();
+      Map<String, Boolean> bucketAclCache = new LinkedHashMap<>();
+
+      try (TableIterator<String, ? extends Table.KeyValue<String, 
SnapshotInfo>> keyIter =
+               metadataManager.getSnapshotInfoTable().iterator()) {
+        while (keyIter.hasNext()) {
+          SnapshotInfo snapshotInfo = keyIter.next().getValue();
+          String volumeName = snapshotInfo.getVolumeName();
+          String bucketName = snapshotInfo.getBucketName();
+          if (!parsedFilter.matches(volumeName, bucketName)) {
+            continue;
+          }
+
+          if (getAclsEnabled() && !hasListAccess(volumeName, bucketName, 
bucketAclCache)) {
+            continue;
+          }
+
+          String bucketKey = volumeName + "/" + bucketName;
+          BucketSnapshotCount bucketCount =
+              bucketCounts.computeIfAbsent(bucketKey, unused -> new 
BucketSnapshotCount(volumeName, bucketName));
+          bucketCount.increment(snapshotInfo.getSnapshotStatus());

Review Comment:
   The added handler test fabricates a zero-count bucket, but this 
implementation only creates a `BucketSnapshotCount` while iterating a 
`SnapshotInfo` row. An existing bucket with no snapshots has no such row, so 
`--bucket vol1/bucket2` returns an empty `buckets` object instead of the zero 
entry asserted by `testCountBucketFilterIncludesMatchingEmptyBucket`. Either 
seed the filtered existing bucket in the server response or align the test and 
documented contract.



##########
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java:
##########
@@ -3268,6 +3270,144 @@ public ListSnapshotResponse listSnapshot(
     }
   }
 
+  @Override
+  public SnapshotCountResponse snapshotCount(String bucketFilter)
+      throws IOException {
+    Map<String, String> auditMap = new LinkedHashMap<>();
+    auditMap.put("bucketFilter", StringUtils.defaultString(bucketFilter));
+
+    try {
+      BucketFilter parsedFilter = BucketFilter.parse(bucketFilter);
+      Map<String, BucketSnapshotCount> bucketCounts = new LinkedHashMap<>();
+      Map<String, Boolean> bucketAclCache = new LinkedHashMap<>();
+
+      try (TableIterator<String, ? extends Table.KeyValue<String, 
SnapshotInfo>> keyIter =
+               metadataManager.getSnapshotInfoTable().iterator()) {
+        while (keyIter.hasNext()) {
+          SnapshotInfo snapshotInfo = keyIter.next().getValue();

Review Comment:
   This raw table iterator reads only persisted RocksDB rows and does not merge 
the table cache. Snapshot create/delete requests place the new or 
status-updated `SnapshotInfo` in that cache before the double buffer flushes 
it, while `OmMetadataManagerImpl.listSnapshot` uses the cache-aware 
`ListIterator.MinHeapIterator`; therefore this command can undercount new 
snapshots and report deleted snapshots as active until a flush. Use the 
cache-aware merge path (or explicitly merge/filter cache entries) here.



##########
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java:
##########
@@ -3268,6 +3270,144 @@ public ListSnapshotResponse listSnapshot(
     }
   }
 
+  @Override
+  public SnapshotCountResponse snapshotCount(String bucketFilter)
+      throws IOException {
+    Map<String, String> auditMap = new LinkedHashMap<>();
+    auditMap.put("bucketFilter", StringUtils.defaultString(bucketFilter));
+
+    try {
+      BucketFilter parsedFilter = BucketFilter.parse(bucketFilter);
+      Map<String, BucketSnapshotCount> bucketCounts = new LinkedHashMap<>();
+      Map<String, Boolean> bucketAclCache = new LinkedHashMap<>();
+
+      try (TableIterator<String, ? extends Table.KeyValue<String, 
SnapshotInfo>> keyIter =
+               metadataManager.getSnapshotInfoTable().iterator()) {
+        while (keyIter.hasNext()) {
+          SnapshotInfo snapshotInfo = keyIter.next().getValue();
+          String volumeName = snapshotInfo.getVolumeName();
+          String bucketName = snapshotInfo.getBucketName();
+          if (!parsedFilter.matches(volumeName, bucketName)) {
+            continue;
+          }
+
+          if (getAclsEnabled() && !hasListAccess(volumeName, bucketName, 
bucketAclCache)) {
+            continue;
+          }
+
+          String bucketKey = volumeName + "/" + bucketName;
+          BucketSnapshotCount bucketCount =
+              bucketCounts.computeIfAbsent(bucketKey, unused -> new 
BucketSnapshotCount(volumeName, bucketName));
+          bucketCount.increment(snapshotInfo.getSnapshotStatus());
+        }
+      }
+
+      long totalActive = 0;
+      long totalDeleted = 0;
+      long total = 0;
+      List<SnapshotBucketCount> bucketCountList = new 
ArrayList<>(bucketCounts.size());
+      for (BucketSnapshotCount bucketCount : bucketCounts.values()) {
+        bucketCountList.add(bucketCount.toResponse());
+        totalActive += bucketCount.active;
+        totalDeleted += bucketCount.deleted;
+        total += bucketCount.total;
+      }
+
+      AUDIT.logReadSuccess(buildAuditMessageForSuccess(OMAction.LIST_SNAPSHOT, 
auditMap));
+      return new SnapshotCountResponse(totalActive, totalDeleted, total, 
bucketCountList);
+    } catch (Exception ex) {
+      AUDIT.logReadFailure(buildAuditMessageForFailure(OMAction.LIST_SNAPSHOT, 
auditMap, ex));
+      throw ex;
+    }
+  }
+
+  private boolean hasListAccess(String volumeName, String bucketName, 
Map<String, Boolean> bucketAclCache)
+      throws IOException {
+    String bucketKey = volumeName + "/" + bucketName;
+    Boolean hasAccess = bucketAclCache.get(bucketKey);
+    if (hasAccess != null) {
+      return hasAccess;
+    }
+
+    try {
+      omMetadataReader.checkAcls(ResourceType.BUCKET, StoreType.OZONE, 
ACLType.LIST, volumeName, bucketName, null);
+      bucketAclCache.put(bucketKey, true);
+      return true;
+    } catch (OMException ex) {
+      if (ex.getResult() == OMException.ResultCodes.ACCESS_DENIED) {

Review Comment:
   `OmMetadataReader.checkAcls` throws 
`OMException.ResultCodes.PERMISSION_DENIED` when access is denied, not 
`ACCESS_DENIED` (see its implementation). With ACLs enabled, encountering the 
first bucket the caller cannot list therefore aborts the global count request 
instead of skipping that bucket. Catch the result code emitted by `checkAcls` 
here.



-- 
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