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

sammichen 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 d12b34df43 HDDS-9046. [ozone-cert-rotation] cert clean is unable to 
cleanup certificates LOCK error (#5157)
d12b34df43 is described below

commit d12b34df43a085a1f3f9b7b03b3f70e0b86ac075
Author: Galsza <[email protected]>
AuthorDate: Thu Aug 10 16:27:41 2023 +0200

    HDDS-9046. [ozone-cert-rotation] cert clean is unable to cleanup 
certificates LOCK error (#5157)
---
 .../hadoop/hdds/protocol/SCMSecurityProtocol.java  |   7 ++
 .../SCMSecurityProtocolClientSideTranslatorPB.java |  14 ++-
 .../certificate/authority/CertificateStore.java    |   3 +-
 .../x509/certificate/authority/MockCAStore.java    |   4 +-
 .../src/main/proto/ScmServerSecurityProtocol.proto |  12 +++
 .../SCMSecurityProtocolServerSideTranslatorPB.java |  12 +++
 .../hadoop/hdds/scm/server/SCMCertStore.java       |  27 +++--
 .../hdds/scm/server/SCMSecurityProtocolServer.java |  11 ++
 .../hadoop/hdds/scm/cli/cert/CertCommands.java     |   2 +-
 .../hadoop/hdds/scm/cli/cert/CleanExpired.java     | 117 ---------------------
 ...mmand.java => CleanExpiredCertsSubcommand.java} |  30 +++---
 .../hadoop/hdds/scm/cli/cert/ListSubcommand.java   |  21 +---
 .../hdds/scm/cli/cert/ScmCertSubcommand.java       |  30 ++++++
 .../hadoop/hdds/scm/cli/cert/TestCleanExpired.java | 100 ------------------
 .../cli/cert/TestCleanExpiredCertsSubcommand.java  |  86 +++++++++++++++
 15 files changed, 212 insertions(+), 264 deletions(-)

diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocol/SCMSecurityProtocol.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocol/SCMSecurityProtocol.java
index 1b88cee107..703deec76a 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocol/SCMSecurityProtocol.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocol/SCMSecurityProtocol.java
@@ -190,4 +190,11 @@ public interface SCMSecurityProtocol {
    */
   List<String> getAllRootCaCertificates() throws IOException;
 
+  /**
+   * Remove all expired certificates from the SCM metadata store.
+   *
+   * @return list of the removed certificates
+   * @throws IOException
+   */
+  List<String> removeExpiredCertificates() throws IOException;
 }
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocolPB/SCMSecurityProtocolClientSideTranslatorPB.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocolPB/SCMSecurityProtocolClientSideTranslatorPB.java
index 4aa32f04cf..2ab5cc1c46 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocolPB/SCMSecurityProtocolClientSideTranslatorPB.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocolPB/SCMSecurityProtocolClientSideTranslatorPB.java
@@ -49,6 +49,7 @@ import 
org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMRevoke
 import 
org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMSecurityRequest;
 import 
org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMSecurityRequest.Builder;
 import 
org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMSecurityResponse;
+import 
org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMRemoveExpiredCertificatesRequestProto;
 import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.Type;
 import 
org.apache.hadoop.hdds.scm.proxy.SCMSecurityProtocolFailoverProxyProvider;
 import org.apache.hadoop.hdds.security.exception.SCMSecurityException;
@@ -87,8 +88,7 @@ public class SCMSecurityProtocolClientSideTranslatorPB 
implements
   /**
    * Helper method to wrap the request and send the message.
    */
-  private SCMSecurityResponse submitRequest(
-      SCMSecurityProtocolProtos.Type type,
+  private SCMSecurityResponse submitRequest(Type type,
       Consumer<Builder> builderConsumer) throws IOException {
     final SCMSecurityResponse response;
     try {
@@ -437,4 +437,14 @@ public class SCMSecurityProtocolClientSideTranslatorPB 
implements
         .getAllRootCaCertificatesResponseProto()
         .getAllX509RootCaCertificatesList();
   }
+
+  @Override
+  public List<String> removeExpiredCertificates() throws IOException {
+    SCMRemoveExpiredCertificatesRequestProto protoIns =
+        SCMRemoveExpiredCertificatesRequestProto.getDefaultInstance();
+    return submitRequest(Type.RemoveExpiredCertificates,
+        builder -> builder.setRemoveExpiredCertificatesRequestProto(protoIns))
+        .getRemoveExpiredCertificatesResponseProto()
+        .getRemovedExpiredCertificatesList();
+  }
 }
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/CertificateStore.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/CertificateStore.java
index e78206ca58..b5bfc54f33 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/CertificateStore.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/CertificateStore.java
@@ -109,10 +109,11 @@ public interface CertificateStore {
   /**
    * Deletes all non-revoked expired certificates from the store.
    *
+   * @return The list of removed expired certificates
    * @throws IOException - on failure
    */
   @Replicate
-  void removeAllExpiredCertificates() throws IOException;
+  List<X509Certificate> removeAllExpiredCertificates() throws IOException;
 
   /**
    * Retrieves a Certificate based on the Serial number of that certificate.
diff --git 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/authority/MockCAStore.java
 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/authority/MockCAStore.java
index 650af970ca..9b3125c11b 100644
--- 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/authority/MockCAStore.java
+++ 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/authority/MockCAStore.java
@@ -29,6 +29,7 @@ import org.apache.hadoop.hdds.scm.metadata.SCMMetadataStore;
 import java.io.IOException;
 import java.math.BigInteger;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
@@ -72,7 +73,8 @@ public class MockCAStore implements CertificateStore {
   }
 
   @Override
-  public void removeAllExpiredCertificates() {
+  public List<X509Certificate> removeAllExpiredCertificates() {
+    return new ArrayList<>();
   }
 
   @Override
diff --git 
a/hadoop-hdds/interface-server/src/main/proto/ScmServerSecurityProtocol.proto 
b/hadoop-hdds/interface-server/src/main/proto/ScmServerSecurityProtocol.proto
index dd3ef42308..adc2eee385 100644
--- 
a/hadoop-hdds/interface-server/src/main/proto/ScmServerSecurityProtocol.proto
+++ 
b/hadoop-hdds/interface-server/src/main/proto/ScmServerSecurityProtocol.proto
@@ -58,6 +58,8 @@ message SCMSecurityRequest {
     optional SCMGetCertRequestProto getCertRequest = 13;
     optional SCMGetAllRootCaCertificatesRequestProto
         getAllRootCaCertificatesRequestProto = 14;
+    optional SCMRemoveExpiredCertificatesRequestProto
+        removeExpiredCertificatesRequestProto = 15;
 }
 
 message SCMSecurityResponse {
@@ -84,6 +86,8 @@ message SCMSecurityResponse {
     optional SCMRevokeCertificatesResponseProto 
revokeCertificatesResponseProto = 10;
 
     optional SCMGetAllRootCaCertificatesResponseProto 
allRootCaCertificatesResponseProto = 11;
+
+    optional SCMRemoveExpiredCertificatesResponseProto 
removeExpiredCertificatesResponseProto = 12;
 }
 
 enum Type {
@@ -100,6 +104,7 @@ enum Type {
     RevokeCertificates = 11;
     GetCert = 12;
     GetAllRootCaCertificates = 13;
+    RemoveExpiredCertificates = 14;
 }
 
 enum Status {
@@ -205,6 +210,10 @@ message SCMGetAllRootCaCertificatesResponseProto {
     repeated string allX509RootCaCertificates = 1;
 }
 
+message SCMRemoveExpiredCertificatesResponseProto {
+    repeated string removedExpiredCertificates = 1;
+}
+
 message SCMGetRootCACertificateRequestProto {
 }
 
@@ -258,6 +267,9 @@ message SCMRevokeCertificatesResponseProto {
     optional int64 crlId = 1;
 }
 
+message SCMRemoveExpiredCertificatesRequestProto {
+}
+
 service SCMSecurityProtocolService {
     rpc submitRequest (SCMSecurityRequest) returns (SCMSecurityResponse);
 }
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/SCMSecurityProtocolServerSideTranslatorPB.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/SCMSecurityProtocolServerSideTranslatorPB.java
index aeb1fb6ea6..3fb32a2e26 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/SCMSecurityProtocolServerSideTranslatorPB.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/SCMSecurityProtocolServerSideTranslatorPB.java
@@ -35,6 +35,7 @@ import 
org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetOMC
 import 
org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetSCMCertRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMListCertificateRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMListCertificateResponseProto;
+import 
org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMRemoveExpiredCertificatesResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMRevokeCertificatesRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMRevokeCertificatesResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMSecurityRequest;
@@ -153,6 +154,11 @@ public class SCMSecurityProtocolServerSideTranslatorPB
         return scmSecurityResponse
             .setAllRootCaCertificatesResponseProto(getAllRootCa())
             .build();
+      case RemoveExpiredCertificates:
+        return scmSecurityResponse
+            .setRemoveExpiredCertificatesResponseProto(
+                removeExpiredCertificates())
+            .build();
 
       default:
         throw new IllegalArgumentException(
@@ -414,4 +420,10 @@ public class SCMSecurityProtocolServerSideTranslatorPB
     }
   }
 
+  public SCMRemoveExpiredCertificatesResponseProto removeExpiredCertificates()
+      throws IOException {
+    return SCMRemoveExpiredCertificatesResponseProto.newBuilder()
+        .addAllRemovedExpiredCertificates(impl.removeExpiredCertificates())
+        .build();
+  }
 }
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMCertStore.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMCertStore.java
index 539caf6d17..f2a753f9da 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMCertStore.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMCertStore.java
@@ -25,7 +25,6 @@ import java.math.BigInteger;
 import java.security.cert.CRLException;
 import java.security.cert.X509CRL;
 import java.security.cert.X509Certificate;
-import java.time.Instant;
 import java.util.ArrayList;
 
 import java.util.List;
@@ -223,33 +222,41 @@ public final class SCMCertStore implements 
CertificateStore {
   }
 
   @Override
-  public void removeAllExpiredCertificates() throws IOException {
+  public List<X509Certificate> removeAllExpiredCertificates()
+      throws IOException {
+    List<X509Certificate> removedCerts = new ArrayList<>();
     lock.lock();
     try (BatchOperation batchOperation =
              scmMetadataStore.getBatchHandler().initBatchOperation()) {
-      addExpiredCertsToBeRemoved(batchOperation,
-          scmMetadataStore.getValidCertsTable());
-      addExpiredCertsToBeRemoved(batchOperation,
-          scmMetadataStore.getValidSCMCertsTable());
+      removedCerts.addAll(addExpiredCertsToBeRemoved(batchOperation,
+          scmMetadataStore.getValidCertsTable()));
+      removedCerts.addAll(addExpiredCertsToBeRemoved(batchOperation,
+          scmMetadataStore.getValidSCMCertsTable()));
       scmMetadataStore.getStore().commitBatchOperation(batchOperation);
     } finally {
       lock.unlock();
     }
+    return removedCerts;
   }
 
-  private void addExpiredCertsToBeRemoved(BatchOperation batchOperation,
-      Table<BigInteger, X509Certificate> certTable) throws IOException {
+  private List<X509Certificate> addExpiredCertsToBeRemoved(
+      BatchOperation batchOperation, Table<BigInteger,
+      X509Certificate> certTable) throws IOException {
+    List<X509Certificate> removedCerts = new ArrayList<>();
     try (TableIterator<BigInteger, ? extends Table.KeyValue<BigInteger,
         X509Certificate>> certsIterator = certTable.iterator()) {
-      Instant now = Instant.now();
+      Date now = new Date();
       while (certsIterator.hasNext()) {
         Table.KeyValue<BigInteger, X509Certificate> certEntry =
             certsIterator.next();
-        if (certEntry.getValue().getNotAfter().toInstant().isBefore(now)) {
+        X509Certificate cert = certEntry.getValue();
+        if (cert.getNotAfter().before(now)) {
+          removedCerts.add(cert);
           certTable.deleteWithBatch(batchOperation, certEntry.getKey());
         }
       }
     }
+    return removedCerts;
   }
 
   @Override
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMSecurityProtocolServer.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMSecurityProtocolServer.java
index 180c4574a3..bec5a8dcff 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMSecurityProtocolServer.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMSecurityProtocolServer.java
@@ -508,6 +508,17 @@ public class SCMSecurityProtocolServer implements 
SCMSecurityProtocol,
     }
   }
 
+  @Override
+  public List<String> removeExpiredCertificates() throws IOException {
+    storageContainerManager.checkAdminAccess(getRpcRemoteUser(), false);
+    List<String> pemEncodedCerts = new ArrayList<>();
+    for (X509Certificate cert : storageContainerManager.getCertificateStore()
+        .removeAllExpiredCertificates()) {
+      pemEncodedCerts.add(CertificateCodec.getPEMEncodedString(cert));
+    }
+    return pemEncodedCerts;
+  }
+
   public SCMUpdateServiceGrpcServer getGrpcUpdateServer() {
     return grpcUpdateServer;
   }
diff --git 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/CertCommands.java
 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/CertCommands.java
index 6b50cb451b..d466c9554a 100644
--- 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/CertCommands.java
+++ 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/CertCommands.java
@@ -40,7 +40,7 @@ import picocli.CommandLine.Spec;
     subcommands = {
         InfoSubcommand.class,
         ListSubcommand.class,
-        CleanExpired.class,
+        CleanExpiredCertsSubcommand.class,
     })
 
 @MetaInfServices(SubcommandWithParent.class)
diff --git 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/CleanExpired.java
 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/CleanExpired.java
deleted file mode 100644
index b5a2ec523f..0000000000
--- 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/CleanExpired.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- * <p>
- * http://www.apache.org/licenses/LICENSE-2.0
- * <p>
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.hadoop.hdds.scm.cli.cert;
-
-import com.google.common.annotations.VisibleForTesting;
-import org.apache.hadoop.hdds.cli.GenericParentCommand;
-import org.apache.hadoop.hdds.cli.HddsVersionProvider;
-import org.apache.hadoop.hdds.cli.SubcommandWithParent;
-import org.apache.hadoop.hdds.conf.OzoneConfiguration;
-import org.apache.hadoop.hdds.scm.metadata.SCMDBDefinition;
-import org.apache.hadoop.hdds.utils.HAUtils;
-import org.apache.hadoop.hdds.utils.db.DBStore;
-import org.apache.hadoop.hdds.utils.db.Table;
-import org.apache.hadoop.hdds.utils.db.TableIterator;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import picocli.CommandLine;
-
-import java.io.File;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.cert.X509Certificate;
-import java.time.Instant;
-import java.util.concurrent.Callable;
-
-/**
- * This is the handler to clean SCM database from expired certificates.
- */
[email protected](
-    name = "clean",
-    description = "Clean expired certificates from the SCM metadata. " +
-        "This command is only supported when the SCM is shutdown.",
-    mixinStandardHelpOptions = true,
-    versionProvider = HddsVersionProvider.class)
-public class CleanExpired implements Callable<Void>, SubcommandWithParent {
-
-  private static final Logger LOG = 
LoggerFactory.getLogger(CleanExpired.class);
-
-  @CommandLine.Option(names = {"--db"},
-      required = true,
-      description = "Database file path")
-  private String dbFilePath;
-
-  @CommandLine.Spec
-  private CommandLine.Model.CommandSpec spec;
-
-  @Override
-  public Void call() {
-    GenericParentCommand parent =
-        (GenericParentCommand) spec.root().userObject();
-
-    OzoneConfiguration configuration = parent.createOzoneConfiguration();
-
-    File db = new File(dbFilePath);
-    if (!db.exists()) {
-      LOG.error("DB path does not exist: " + dbFilePath);
-      return null;
-    }
-    if (!db.isDirectory()) {
-      LOG.error("DB path does not point to a directory: " + dbFilePath);
-      return null;
-    }
-
-    try {
-      DBStore dbStore = HAUtils.loadDB(
-          configuration, db.getParentFile(),
-          db.getName(), new SCMDBDefinition());
-      removeExpiredCertificates(dbStore);
-    } catch (Exception e) {
-      LOG.error("Error trying to open file: " + dbFilePath +
-          " failed with exception: " + e);
-    }
-    return null;
-  }
-
-  @VisibleForTesting
-  void removeExpiredCertificates(DBStore dbStore) {
-    try {
-      Table<BigInteger, X509Certificate> certsTable =
-          SCMDBDefinition.VALID_CERTS.getTable(dbStore);
-      TableIterator<BigInteger, ? extends Table.KeyValue<BigInteger,
-          X509Certificate>> tableIterator = certsTable.iterator();
-      while (tableIterator.hasNext()) {
-        Table.KeyValue<?, ?> certPair = tableIterator.next();
-        X509Certificate certificate = (X509Certificate) certPair.getValue();
-        if (Instant.now().isAfter(certificate.getNotAfter().toInstant())) {
-          LOG.info("Certificate with id " + certPair.getKey() +
-              " and value: " + certificate + "will be deleted");
-          tableIterator.removeFromDB();
-        }
-      }
-    } catch (IOException e) {
-      LOG.error("Error when trying to open " +
-          "certificate table from db: " + e);
-    }
-  }
-
-  @Override
-  public Class<?> getParentType() {
-    return CertCommands.class;
-  }
-}
diff --git 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/ScmCertSubcommand.java
 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/CleanExpiredCertsSubcommand.java
similarity index 55%
copy from 
hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/ScmCertSubcommand.java
copy to 
hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/CleanExpiredCertsSubcommand.java
index 98bd76a284..cab7a29a4e 100644
--- 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/ScmCertSubcommand.java
+++ 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/CleanExpiredCertsSubcommand.java
@@ -17,28 +17,32 @@
  */
 package org.apache.hadoop.hdds.scm.cli.cert;
 
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
 import org.apache.hadoop.hdds.protocol.SCMSecurityProtocol;
-import org.apache.hadoop.hdds.scm.cli.ScmOption;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import picocli.CommandLine;
 
 import java.io.IOException;
-import java.util.concurrent.Callable;
+import java.util.List;
 
 /**
- * Base class for admin commands that connect via SCM security client.
+ * This is the handler to clean SCM database from expired certificates.
  */
-public abstract class ScmCertSubcommand implements Callable<Void> {
[email protected](
+    name = "clean",
+    description = "Clean expired certificates from the SCM metadata.",
+    mixinStandardHelpOptions = true,
+    versionProvider = HddsVersionProvider.class)
+public class CleanExpiredCertsSubcommand extends ScmCertSubcommand {
 
-  @CommandLine.Mixin
-  private ScmOption scmOption;
-
-  protected abstract void execute(SCMSecurityProtocol client)
-      throws IOException;
+  private static final Logger LOG =
+      LoggerFactory.getLogger(CleanExpiredCertsSubcommand.class);
 
   @Override
-  public final Void call() throws Exception {
-    SCMSecurityProtocol client = scmOption.createScmSecurityClient();
-    execute(client);
-    return null;
+  protected void execute(SCMSecurityProtocol client) throws IOException {
+    List<String> pemEncodedCerts = client.removeExpiredCertificates();
+    LOG.info("List of removed expired certificates:");
+    printCertList(LOG, pemEncodedCerts);
   }
 }
diff --git 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/ListSubcommand.java
 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/ListSubcommand.java
index 857bf17cc8..c2e0bd7fad 100644
--- 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/ListSubcommand.java
+++ 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/ListSubcommand.java
@@ -81,9 +81,7 @@ public class ListSubcommand extends ScmCertSubcommand {
       defaultValue = "false",
       description = "Format output as JSON")
   private boolean json;
-
-  private static final String OUTPUT_FORMAT = "%-17s %-30s %-30s %-110s 
%-110s";
-
+  
   private HddsProtos.NodeType parseCertRole(String r) {
     if (r.equalsIgnoreCase("om")) {
       return HddsProtos.NodeType.OM;
@@ -94,12 +92,6 @@ public class ListSubcommand extends ScmCertSubcommand {
     }
   }
 
-  private void printCert(X509Certificate cert) {
-    LOG.info(String.format(OUTPUT_FORMAT, cert.getSerialNumber(),
-        cert.getNotBefore(), cert.getNotAfter(), cert.getSubjectDN(),
-        cert.getIssuerDN()));
-  }
-
   @Override
   protected void execute(SCMSecurityProtocol client) throws IOException {
     boolean isRevoked = type.equalsIgnoreCase("revoked");
@@ -132,16 +124,7 @@ public class ListSubcommand extends ScmCertSubcommand {
 
     LOG.info("Certificate list:(Type={}, BatchSize={}, CertCount={})",
         type.toUpperCase(), count, certPemList.size());
-    LOG.info(String.format(OUTPUT_FORMAT, "SerialNumber", "Valid From",
-        "Expiry", "Subject", "Issuer"));
-    for (String certPemStr : certPemList) {
-      try {
-        X509Certificate cert = CertificateCodec.getX509Certificate(certPemStr);
-        printCert(cert);
-      } catch (CertificateException ex) {
-        LOG.error("Failed to parse certificate.");
-      }
-    }
+    printCertList(LOG, certPemList);
   }
 
   private static class BigIntJsonSerializer extends JsonSerializer<BigInteger> 
{
diff --git 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/ScmCertSubcommand.java
 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/ScmCertSubcommand.java
index 98bd76a284..d7ebb44e0f 100644
--- 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/ScmCertSubcommand.java
+++ 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/ScmCertSubcommand.java
@@ -19,9 +19,14 @@ package org.apache.hadoop.hdds.scm.cli.cert;
 
 import org.apache.hadoop.hdds.protocol.SCMSecurityProtocol;
 import org.apache.hadoop.hdds.scm.cli.ScmOption;
+import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec;
+import org.slf4j.Logger;
 import picocli.CommandLine;
 
 import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.List;
 import java.util.concurrent.Callable;
 
 /**
@@ -32,6 +37,31 @@ public abstract class ScmCertSubcommand implements 
Callable<Void> {
   @CommandLine.Mixin
   private ScmOption scmOption;
 
+  private static final String OUTPUT_FORMAT = "%-17s %-30s %-30s %-110s 
%-110s";
+
+  protected void printCertList(Logger log, List<String> pemEncodedCerts) {
+    if (pemEncodedCerts.isEmpty()) {
+      log.info("No certificates to list");
+      return;
+    }
+    log.info(String.format(OUTPUT_FORMAT, "SerialNumber", "Valid From",
+        "Expiry", "Subject", "Issuer"));
+    for (String certPemStr : pemEncodedCerts) {
+      try {
+        X509Certificate cert = CertificateCodec.getX509Certificate(certPemStr);
+        printCert(cert, log);
+      } catch (CertificateException e) {
+        log.error("Failed to parse certificate.", e);
+      }
+    }
+  }
+
+  protected void printCert(X509Certificate cert, Logger log) {
+    log.info(String.format(OUTPUT_FORMAT, cert.getSerialNumber(),
+        cert.getNotBefore(), cert.getNotAfter(), cert.getSubjectDN(),
+        cert.getIssuerDN()));
+  }
+
   protected abstract void execute(SCMSecurityProtocol client)
       throws IOException;
 
diff --git 
a/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/cert/TestCleanExpired.java
 
b/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/cert/TestCleanExpired.java
deleted file mode 100644
index dc5705f670..0000000000
--- 
a/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/cert/TestCleanExpired.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- * <p>
- * http://www.apache.org/licenses/LICENSE-2.0
- * <p>
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.hadoop.hdds.scm.cli.cert;
-
-import org.apache.hadoop.hdds.scm.metadata.SCMDBDefinition;
-import org.apache.hadoop.hdds.utils.db.DBStore;
-import org.apache.hadoop.hdds.utils.db.Table;
-import org.apache.hadoop.hdds.utils.db.TableIterator;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
-import java.security.cert.X509Certificate;
-import java.sql.Date;
-import java.time.Duration;
-import java.time.Instant;
-
-/**
- * Test the cleaning tool for expired certificates.
- */
-public class TestCleanExpired {
-
-  private CleanExpired cmd;
-  private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
-  private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();
-  private final PrintStream originalOut = System.out;
-  private final PrintStream originalErr = System.err;
-  private static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name();
-
-  @Mock
-  private DBStore dbStore;
-  @Mock
-  private Table<BigInteger, X509Certificate> mockTable;
-  @Mock
-  private TableIterator iterator;
-  @Mock
-  private Table.KeyValue<BigInteger, X509Certificate> kv;
-  @Mock
-  private Table.KeyValue<BigInteger, X509Certificate> kv2;
-  @Mock
-  private X509Certificate nonExpiredCert;
-  @Mock
-  private X509Certificate expiredCert;
-
-  @BeforeEach
-  public void setup() throws IOException {
-    MockitoAnnotations.initMocks(this);
-    cmd = new CleanExpired();
-    System.setOut(new PrintStream(outContent, false, DEFAULT_ENCODING));
-    System.setErr(new PrintStream(errContent, false, DEFAULT_ENCODING));
-  }
-
-  @AfterEach
-  public void tearDown() {
-    System.setOut(originalOut);
-    System.setErr(originalErr);
-  }
-
-  @Test
-  public void testOnlyExpiredCertsRemoved()
-      throws Exception {
-    Mockito.when(SCMDBDefinition.VALID_CERTS.getTable(dbStore))
-        .thenReturn(mockTable);
-    Mockito.when(mockTable.iterator()).thenReturn(iterator);
-    Mockito.when(nonExpiredCert.getNotAfter())
-        .thenReturn(Date.from(Instant.now().plus(Duration.ofDays(365))));
-    Mockito.when(expiredCert.getNotAfter())
-        .thenReturn(Date.from(Instant.now().minus(Duration.ofDays(365))));
-    Mockito.when(iterator.hasNext()).thenReturn(true, true, false);
-    Mockito.when(iterator.next()).thenReturn(kv, kv2);
-    Mockito.when(kv.getValue()).thenReturn(expiredCert);
-    Mockito.when(kv2.getValue()).thenReturn(nonExpiredCert);
-
-    cmd.removeExpiredCertificates(dbStore);
-    Mockito.verify(iterator, Mockito.times(1)).removeFromDB();
-  }
-}
diff --git 
a/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/cert/TestCleanExpiredCertsSubcommand.java
 
b/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/cert/TestCleanExpiredCertsSubcommand.java
new file mode 100644
index 0000000000..231d44542a
--- /dev/null
+++ 
b/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/cert/TestCleanExpiredCertsSubcommand.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.hadoop.hdds.scm.cli.cert;
+
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.protocol.SCMSecurityProtocol;
+
+import static 
org.apache.hadoop.hdds.security.x509.CertificateTestUtils.createSelfSignedCert;
+
+import org.apache.hadoop.hdds.security.x509.CertificateTestUtils;
+import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec;
+import org.hamcrest.core.StringContains;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class TestCleanExpiredCertsSubcommand {
+
+  private SCMSecurityProtocol scmSecurityProtocolMock;
+  private CleanExpiredCertsSubcommand cmd;
+  private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+  private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();
+  private final PrintStream originalOut = System.out;
+  private final PrintStream originalErr = System.err;
+  private static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name();
+  private static final String OUTPUT_FORMAT = "%-17s %-30s %-30s %-110s 
%-110s";
+
+  @BeforeEach
+  public void setup() throws IOException {
+    scmSecurityProtocolMock = mock(SCMSecurityProtocol.class);
+    System.setOut(new PrintStream(outContent, false, DEFAULT_ENCODING));
+    System.setErr(new PrintStream(errContent, false, DEFAULT_ENCODING));
+  }
+
+  @AfterEach
+  public void tearDown() {
+    System.setOut(originalOut);
+    System.setErr(originalErr);
+  }
+
+  @Test
+  public void testCleaningOneCertificate() throws Exception {
+    cmd = new CleanExpiredCertsSubcommand();
+    KeyPair keyPair = CertificateTestUtils.aKeyPair(new OzoneConfiguration());
+    X509Certificate cert = createSelfSignedCert(keyPair, "aCert");
+    ArrayList<String> certPemList = new ArrayList<>();
+    certPemList.add(CertificateCodec.getPEMEncodedString(cert));
+    when(scmSecurityProtocolMock.removeExpiredCertificates())
+        .thenReturn(certPemList);
+    cmd.execute(scmSecurityProtocolMock);
+    String cliOutPut = outContent.toString(DEFAULT_ENCODING);
+    String certInfo = String.format(OUTPUT_FORMAT, cert.getSerialNumber(),
+        cert.getNotBefore(), cert.getNotAfter(), cert.getSubjectDN(),
+        cert.getIssuerDN());
+    assertThat(cliOutPut, new StringContains(true, certInfo));
+  }
+}


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

Reply via email to