When working with containers, the usual trend is to make them as compact as possible by removing any file or executable that is not used by what is deployed in it. Typically, interactive commands are good candidates not to be installed. Sometimes, this trend is so extreme that even simple utilities such as ping or even the less pager are missing from the containers. This is generally fine once a container reaches the production stage but not so much while what’s deployed in it is still in development and has not reached its full maturity yet. Without such essentials utilities, one might as well be blind and deaf.
Recently, I wanted to check if a containerized D2 installation could reach its target repository but could not find any of the usual command-line tools that are included with the content server, such as iapi or dctmbroker. Bummer, I thought, but less politely. Admittedly, those tools belong to the content server binaries and not to DFCs clients such as D2 and DA, so that makes sense. Maybe, but that does not solve my problem.
In article A Small Footprint Docker Container with Documentum command-line Tools, I showed how to have a minimalist installation of the iapi, idql and dmdocbroker command-line tools that could be containerized but for my current need, I don’t need so much power. A bare repository ping, a dctmping if you will, would be enough. Clearly, as D2 is a DFCs application, a simple DFCs-based java utility would be just what the doctor ordered. There are probably hundreds variants of such a basic utility floating on the Web but here is my take on it anyway.

The code

The following listing generates the dctmping.java program.

$ cat - << dctmping.java
// a healthcheck program to check whether all the repositories whose docbroker's hosts are defined in dfc.properties are reachable;
// cec at dbi-services.com, June 201;
// to compile: export CLASSPATH=/path/to/the/dfc.jar javac dctmping.java
// to execute: java -classpath .:/path/to/dfc/config:$CLASSPATH dctmping [target_docbase user_name password]
// if target_docbase is given, the programm will attempt to connect to it using the command-line parameters user_name and password and run a SELECT query;
 
import com.documentum.fc.client.IDfClient;
import com.documentum.fc.client.DfClient;
import com.documentum.fc.client.DfQuery;
import com.documentum.fc.client.IDfCollection;
import com.documentum.fc.client.IDfDocbaseMap;
import com.documentum.fc.client.IDfQuery;
import com.documentum.fc.client.IDfSession;
import com.documentum.fc.client.IDfSessionManager;
import com.documentum.fc.common.DfLoginInfo;
import com.documentum.fc.common.IDfLoginInfo;
import com.documentum.fc.client.IDfTypedObject;
import java.io.RandomAccessFile;
 
public class dctmping {
   IDfSessionManager sessMgr = null;
   IDfSession idfSession;
 
   public void showDFC_properties_file() throws Exception {
      System.out.println("in showDFC_properties_file");
      IDfClient client = DfClient.getLocalClient();
      IDfTypedObject apiConfig = client.getClientConfig();
      String[] values = apiConfig.getString("dfc.config.file").split(":");
      System.out.printf("dfc.properties file found in %snn", values[1]);
      RandomAccessFile f_in = new RandomAccessFile(values[1], "r");
      String s;
      while ((s = f_in.readLine()) != null) {
         System.out.println(s);
      }
      f_in.close();
   }
 
   public void getAllDocbases() throws Exception {
      System.out.printf("%nin getAllDocbases%n");
      IDfClient client = DfClient.getLocalClient();
      IDfDocbaseMap docbaseMap = client.getDocbaseMap();
      for (int i = 0; i < docbaseMap.getDocbaseCount(); i++) {
          System.out.println("Docbase Name : " + docbaseMap.getDocbaseName(i));
          System.out.println("Docbase Desc : " + docbaseMap.getDocbaseDescription(i));
      }
   }
 
   public void getDfSession(String repo, String user, String passwd) throws Exception {
      System.out.printf("%nin getDfSession%n");
      IDfLoginInfo login = new DfLoginInfo();
      login.setUser(user);
      login.setPassword(passwd);
      IDfClient client = DfClient.getLocalClient();
      sessMgr = client.newSessionManager();
      sessMgr.setIdentity(repo, login);
      idfSession = sessMgr.getSession(repo);
      if (idfSession != null)
          System.out.printf("Session created successfully in repository %s as user %sn", repo, user);
      else
         throw new Exception();
   }
 
   public void releaseSession() throws Exception {
      sessMgr.release(idfSession);
   }
 
   public void API_select(String repo, String dql) throws Exception {
      System.out.printf("%nin API_select%s%n", repo);
      System.out.printf("SELECT-ing in repository %sn", repo);
      System.out.printf("Query is: %sn", dql);
      IDfQuery query = new DfQuery();
      query.setDQL(dql);
      IDfCollection collection = null;
      String r_object_id = null;
      String object_name = null;
      final int max_listed = 20;
      int count = 0;
      System.out.printf("%-16s  %sn", "r_object_id", "object_name");
      System.out.printf("%-16s  %sn", "----------------", "-----------");
      try {
         collection = query.execute(idfSession, IDfQuery.DF_READ_QUERY);
         while (collection.next()) {
            count++;
            if (max_listed == count) {
               System.out.printf("... max %d reached, skipping until end of result set ...n", max_listed);
               continue;
            }
            else if (count > max_listed)
               continue;
            r_object_id = collection.getString("r_object_id");
            object_name = collection.getString("object_name");
            System.out.printf("%-16s  %sn", r_object_id, object_name);
         }
         System.out.printf("%d documents foundsn", count);
      } finally {
         if (collection != null) {
            collection.close();
         }
      }
   }
  
   public static void main(String[] args) throws Exception {
      dctmping dmtest = new dctmping();
      dmtest.showDFC_properties_file();
      dmtest.getAllDocbases();
      String docbase;
      String user;
      String passwd;
      if (0 == args.length || args.length > 3) {
         System.out.println("nUsage: dctmping [target_docbase [user_name [password]]]");
         System.exit(1);
      }
      if (1 == args.length) {
         docbase = args[0];
         user    = "dmadmin";
         passwd  = "trusted:no_password_needed";
      } 
      else if (2 == args.length) {
         docbase = args[0];
         user    = args[1];
         passwd  = "trusted:no_password_needed";
      } 
      else {
         docbase = args[0];
         user    = args[1];
         passwd  = args[2];
      } 

      String[] queries = {"SELECT r_object_id, object_name from dm_document where folder('/System/Sysadmin/Reports', descend);",
                          "SELECT r_object_id, object_name from dm_cabinet;"};
      for (String dql_stmt: queries) {
         try {
            dmtest.getDfSession(docbase, user, passwd);
            dmtest.API_select(docbase, dql_stmt);
         }
         catch(Exception exception) {
            System.out.printf("Error while attempting to run DQl query", docbase, user);
            exception.printStackTrace();
         }
         finally {
            try {
               dmtest.releaseSession();
            }
            catch(Exception exception) {}
         }
      }
   }
}
eoj

Compile it as instructed in the header, e.g.:

$ export DOCUMENTUM=/home/dmadmin/documentum
$ export CLASSPATH=$DOCUMENTUM/shared/dfc/dfc.jar
$ javac dctmping.java

Use the command below to execute it:

$ java -classpath .:$DOCUMENTUM/shared/config:$CLASSPATH dctmping [target_docbase [user_name [password]]]

When no parameter is given on the command-line (line 110), dctmping attempts to reach the dfc.properties file and displays its content if it succeeds (function showDFC_properties_file starting on line 25). This proves that the file is accessible through one of the paths in $CLASSPATH.
It then displays the help message (line 16 below):

$ java -classpath .:$DOCUMENTUM/shared/config:$CLASSPATH dctmping
in showDFC_properties_file
dfc.properties file found in /home/dmadmin/documentum/shared/config/dfc.properties

dfc.data.dir=/home/dmadmin/documentum/shared
dfc.tokenstorage.dir=/home/dmadmin/documentum/shared/apptoken
dfc.tokenstorage.enable=false

dfc.docbroker.host[0]=dmtest.cec
dfc.docbroker.port[0]=1489

in getAllDocbases
Docbase Name : dmtest73
Docbase Desc : a v7.3 test repository

Usage: dctmping [target_docbase [user_name [password]]]

Starting on line 12 above (see function getAllDocbases starting on line 39), a list of all the reachable docbases is given, here only one, dmtest73. In some installations, this list be more populated, e.g.:

Docbase Name : global_repository
Docbase Desc : Global Repository
Docbase Name : SERAC
Docbase Desc : SERAC CTLQ Repository
Docbase Name : CARAC
Docbase Desc : CARAC CTLQ Repository

At least one parameter is needed: the repository name. The user default to “dmadmin”. A password must be given when connecting remotely from the content server. If connecting locally, it can be anything as the user is trusted (if trusted authentication is allowed), or even left empty. The session is opened by function getDfSession() starting on line 49.

A Real Case Application

To check a remote repository’s access, all 3 parameters are required, e.g.:

$ java -classpath .:$CLASSPATH dctmping dmtest73 dmadmin my_password
in showDFC_properties_file
dfc.properties file found in /home/dmadmin/documentum/shared/config/dfc.properties

dfc.data.dir=/home/dmadmin/documentum/shared
dfc.tokenstorage.dir=/home/dmadmin/documentum/shared/apptoken
dfc.tokenstorage.enable=false

dfc.docbroker.host[0]=dmtest.cec
dfc.docbroker.port[0]=1489

in getAllDocbases
Docbase Name : dmtest73
Docbase Desc : a v7.3 test repository

in getDfSession
Session created successfully in repository dmtest73 as user dmadmin

in API_selectdmtest73
SELECT-ing in repository dmtest73
Query is: SELECT r_object_id, object_name from dm_document where folder('/System/Sysadmin/Reports', descend);
r_object_id       object_name
----------------  -----------
0900c35080002a1b  StateOfDocbase
0900c350800029d7  UpdateStats
0900c35080002a11  ContentWarning
0900c35080002a18  DBWarning
0900c3508000211e  ConsistencyChecker
5 documents founds

in getDfSession
Session created successfully in repository dmtest73 as user dmadmin

in API_selectdmtest73
SELECT-ing in repository dmtest73
Query is: SELECT r_object_id, object_name from dm_cabinet;
r_object_id       object_name
----------------  -----------
0c00c35080000107  Temp
0c00c3508000012f  Templates
0c00c3508000057b  dm_bof_registry
0c00c350800001ba  Integration
0c00c35080000105  dmadmin
0c00c35080000104  dmtest73
0c00c35080000106  System
0c00c35080000130  Resources
8 documents founds

After displaying the content of the dfc.properties file, dctmping attempts to connect to the given repository with the given credentials and runs both queries below (see function API_select() starting on line 68):

SELECT r_object_id, object_name from dm_document where folder('/System/Sysadmin/Reports', descend);
SELECT r_object_id, object_name from dm_cabinet;

As we don’t need a full listing for testing the connectivity, especially when it may be very large and take several minutes to complete, a maximum of 20 rows by query is returned.

Checking the global_registry

If a global_registry repository has been defined in the dfc.properties file, the credentials to access it are also listed in that file as illustrated below:

...
dfc.globalregistry.repository=my_global_registry
dfc.globalregistry.username=dm_bof_registry
dfc.globalregistry.password=AFAIKL8C2Y/2gRyQUV1R7pmP7hfBDpafeWPST9KKlQRtZVJ4Ya0MhLsEZKmWr1ok9+oThA==
...

Eventhough the password is encrypted, it is available verbatim to connect to the repository as shown below:

$ java -classpath .:$DOCUMENTUM/shared/config:$CLASSPATH dctmping dm_bof_registry 'AFAIKL8C2Y/2gRyQUV1R7pmP7hfBDpafeWPST9KKlQRtZVJ4Ya0MhLsEZKmWr1ok9+oThA=='
...
in getDfSession
Session created successfully in repository my_global_registry as user dm_bof_registry
 
in API_selectmy_global_registry
SELECT-ing in repository my_global_registry
Query is: SELECT r_object_id, object_name from dm_cabinet;
r_object_id       object_name
----------------  -----------
0c0f476f80000106  System
0c0f476f800005b4  dm_bof_registry
2 documents founds
...

Note that the utility does not check if the given docbase is a global_registry, so the connection attempt may raise an authentication error, but at least the given docbase has been proved to be reachable (or not).

Conclusion

This innocent little stand-alone utility can be easily included in any docker image and can assist in ascertaining at least 5 dependencies in the context of a post-deployment’s sanity check or of a problem troubleshooting:

  1. the correct installation of the DFCs
  2. the correct configuration of the dfc.properties
  3. the correct initialization of $DOCUMENTUM and $CLASSPATH environment variables
  4. the accessibility of the remote repository(ies)
  5. and finally, the credentials to connect to said repository.

That is one big chunk of pre-requisites to check at once even before the DFCs clients start.
As a bonus, it also lists all the repositories potentially accessible through the docbrokers listed in the dfc.properties, which is useful in case the local machine hosts a mutualized service serving several repositories. Another freebie is that, if a global_registry is defined in the dfc.properties file, it can be checked too; actually, the utility could be improved to do that automatically (or manually in a subsequent re-run) as the needed credentials are given in that file and require no user interaction but, as they say, let’s leave that as an exercise for the reader.