VSLDAP Test Programmer's Guide

 

 
 
Overview

The Test programmer's API consists of a set of Java classes that act as an LDAP client API. This can be used to develop tests that extend the scope of VSLDAP.

A test program must be able to do the following:

 
  • Connect to the LDAP server under test
  • Send any number of LDAP requests to the server
  • Receive and interpret any responses from the server
  • Disconnect from the server
  • log the test result and other comments
  • The API provided to acheive this acts like an LDAP client API but it is important to understand that the VSLDAP test programmer's API is not a full-featured LDAP client API, nor is it intended to be. It shares with the typical client API those features that allow it to carry out the required LDAP Certified tests. Having said this, the VSLDAP API is reasonably flexible and can be used to create new tests that test aspects of LDAP server performance that lie outside the LDAP Certified conformance requirements.
    This test programmer's API guide provides an overview for those interested in using VSLDAP as a development tool by writing new tests.
     
     

    First steps

    Being a TETware-based suite, VSLDAP tests should be written using the TETware java API (It is possible to use other non-standard APIs but that outside the scope of this guide). The programmer should be familiar with basics of writing TETware tests  - this involves reading the TETware Programmer's Manual. Essentially, each test is created as a standalone class with a `main'  method that is invoked via a TETware superclass, typically SimpleTestCase.
    The following code fragment can be regarded as a skeleton for typical TETware Java API based tests.
     

    import TET.*;
    import vsldap.api.*;

    public class mytest extends  SimpleTestCase
    {

      public static void main( String[] args )
      {
        main( args, new mytest() );
      }

      public void i1t1( TestSession ts )
      {
        // Test code goes here
      }
     

      public void i1t2( TestSession ts )
      {
        // Second test purpose
      }
     

      public void i2t1( TestSession ts )
      {
        // Second Invocable component
      }

    }


    Note that the TETware java API is imported in the TET package which is a supported component of TETware versions 3.4 and later.
    When called by tcc, the invocable methods are passed an instance of the TestSession class which provides the method with a handle to standard TETware methods and configuration parameters.
    There are three public methods: i1t1, i1t2 and i2t1. These are named according to a convention described in the TETware programmer's guide. The `i' identifies an invocable component, the `t' identifies a test purpose - terms that are explain in the TETware documentation. So in this example there are two invocable components: i1 and i2. I1 has two test purposes: t1 and t2 whereas i2 has a single test purpose.
     

    The Java classes of the VSLDAP test programmer's API are imported from the package vsldap.api which contains a number of clasess that are documented in the VSLDAP API document. Their use is illustrated with several examples below.


    Connecting to an LDAP server

    Before a client can begin to interact with an LDAP server, a connection must be established. This is done by creating a `context' object which represents the LDAP session and then calling one of the object's connect methods.

    The context object is an instance of the vsldap.api.LDAPSession class. The class is a sub-class of a more basic VSLDAP class that implements an interface to the low-level LDAP SDK calls.  The API documentation for all LDAPSession methods is located in the description of vsldap.intf.base.LDAPmethods.
     

    Example 1. Connecting to an LDAP Server
     

    1. public void i1t1( TestSession ts )
    2. {
    3.   LDAPSession ldap = new LDAPSession (ts);
    4.   // attempt to connect to the server
    5.   try{
    6.     ldap.connect();
    7.     // We've connected, now we can do other stuff...
    8.   }
    9.   catch( LDAPexception e ){
    10.     ts.tet_infoline("Failed to connect, got LDAPexception "+e.result.resultCode );
    11.     ts.tet_result( ts.TET_FAIL );
    12.   }
    13.   // send an unbind request to end the session
    14.   try{
    15.     ldap.unbind();
    16.   }
    17.   catch( LDAPException e ){
    18.     ts.tet_infoline("Failed to unbind, got LDAPexception "+e.result.resultCode );
    19.     ts.tet_result( ts.TET_FAIL );
    20.   }
    21. }


    Line 3: A vsldap.api.LDAPSession object `ldap' is created (The test script generation program automatically imports vsldap.api.* for us). The LDAPSession constructor takes a variable called `ts' as it's sole argument. `ts' is an instance of class TET.TestSession that represents the TETware session context. In this fragment, as with real test scripts we can safely assume that ts has been automatically generated for us by the VSLDAP program that converts test scripts into pure Java source files. So now we have an object `ldap' that acts as a handle to our LDAP Session.

    Line 6: We call the connect() method of LDAPSession. This attempts to open a connection to an LDAP server.
    We have put this call in a try-catch block because the connect() method can throw an exception object called LDAPexception. This version of connect() uses default values of hostname and port to connect.  When the TETware controller `tcc' executes a test, it looks for a configuration file, called `tetexec.cfg' by default. This file contains user-defined values which are passed to the test program via the TestSession ts object which we use in the LDAPSession constructor (Line 1). The tetexec.cfg file for VSLDAP contains the global configuration variables VSLDAP_SERVER_HOST and VSLDAP_PORT among others, which declare the hostname and port number respectively of the server under test.  LDAPSession.connect() uses these values. A TETware test session can be used to execute any number of tests (in any required order) and only requires the one tetexec.cfg file to configure this session. All the tests that run in that test session have access to the same value of VSLDAP_PORT and all of the other configuration values.

    Lines 11,12: If the connection request fails then an LDAPexception object is thrown. In this catch block we call two methods of the TestSession class: tet_infoline() and tet_result(). tet_infoline takes a string as it's argument and writes this string to the journal file that TETware has created to log the test session.
    tet_result also logs a value to the test session journal file. In this case it is a record of the result of the test. TETware maps common results onto a set of constants (like an enumeration). In this case, the connection has failed,  and since the purpose of our test was to connect to the server, so we inform  TETware  that the test has failed by calling tet_result() and passing it the pre-defined constant ts.TET_FAIL.

    Lines 15-21: The second try-catch block calls the LDAPSession.unbind() method in an attempt to disconnect gracefully from the server and end the LDAP session. Once unbind() has been (successfully) called, the LDAPSession object `ldap' is no longer an active handle to an LDAP session and can not used be used to send LDAP requests until a new session is created. Again, if the unbind operation fails, we log some pertinent information in the journal file.
     
     


    Example 2. Connecting to a secure port via SSL
     
     

    1.  LDAPSession ldap = new LDAPSession( ts );
    2.   // SSL data is stored as a `feature'. We need to get the SSLFeature object
    3.   // which contains the relevant data
    4.   SSLFeature ssl_feature = (SSLFeature)ldap.get_feature( "sslConnection" );
    5.   if( ! ssl_feature.get_ifActive() )
    6.   {
    7.     // The SSL configuration is incorrect
    8.     // send auto-generated error message to journal
    9.     ssl_feature.statusToJournal( ts );
    10.     ts.tet_result( ts.TET_UNTESTED );
    11.     return;
    12.   }
    13.   // Attempt to connect, passing the SSLFeature object.
    14.   try
    15.   { ldap.connect( ssl_feature ); }
    16.   catch( LDAPexception e )
    17.   {
    18.     // if we can't connect, the test has not failed but is untested
    19.     ts.tet_infoline( "Failed to connect, resultCode: "+e.response.resultCode );
    20.     ts.tet_result(ts.TET_UNRESOLVED);
    21.     return;
    22.   }

    In the example we provide the connect method with the necessary information in the form of an SSLFeature object. This is one of several feature classes that represent optional LDAP functionality that VSLDAP is designed to test. The feature is automatically loaded into the object via the TETware test controller. The relevant data is configured in the tetexec.cfg file that is used as the master configuration file for any test run. (See Using VSLDAP for details on how to configure this). This is the only supported method of requesting an SSL connection.
     
     

    Making LDAP requests

    When a client requires a server to perform an LDAP operation it sends a request to the server using the LDA Protocol. The VSLDAP API models this by defining a set of request classes  (members of the vsldap.api package), one for each of the ten operations specified in RFC2251. The LDAPSession class gives access to a corresponding set of methods. A request is sent by calling the appropriate method with an instance of the relevant request class as it's sole argument. The request object contains all of the data required by RFC2251 to specifiy the request. The test programmer populates the request object with the necessary data. The request object constructors provide default values for any optional data. The LDAPSession request method returns a response object which contains information returned by the server on successfully processing a request ( i.e when the server returns a resultCode of 0 == `success'). If the request fails at any stage, or the server returns a resultCode other than `success' then an exception is thrown. The exception object is an instance of the LDAPexception class, a member of the vsldap.api package. This object contains information about the failure including the LDAP resultCode value as defined in RFC2251.

    The operational request methods available via the LDAPSession class are:

    Method         Argument (request object)   Return type

    abandon        abandonRequest              LDAPResult
    add            AddRequest                  LDAPResult
    bind           BindRequest                 BindResponse
    compare        CompareRequest              LDAPResult
    delete         DeleteRequest               LDAPResult
    extended       ExtendedRequest             ExtendedResponse
    modify         ModifyRequest               LDAPResult
    modifyDN       ModifyDNRequest             LDAPResult
    search         SearchRequest               SearchResponse
    unbind         <no argument required>      <no data returned>
     

    The methods behave synchronously, that is, the methods wait for a result from the server and then return the response.

    The member data of the request objects are public for easy modification and the naming follows that of the structures defined in RFC2251. LDAPString, LDAPOID, LDAPDN and other data represetned as text are treated as java.lang.String objects. Sequences are stored as java.util.Vector lists. Binary OCTET STRINGS are represented as byte[] arrays. Enumerated values are represented as integers with a corresponding vsldap.api class containing static member values  providing a mapping of named values. For example the ENUMERATED resultCode value of the LDAPResult structure in RFC2251 is represented by the vsldap.api.LDAPresultCode class.
     
     

    Examining LDAP responses and exceptions

    The vsldap.api.LDAPResult class contains the resultCode value, matchedDN string, errorMessage string, referral list and returned server control list. Note that these values are public.The control list is the only value not defined in the RFC2251 LDAPResult structure. RFC2251 places the returned control list in the LDAPMessage block. It is moved to vsldap.api.LDAPResult since the API does not provide methods that return an RFC2251 LDAPMessage structure directly.

    The resultCode value can be interrogated by reading the value directly or by comparing to the vsldap.api.LDAPresultCode class. E.g.

    if(  res.resultCode != LDAPresultCode.success )
    { // do something }
    The above example is unrealistic since a request method that returns a non-zero resultCode will throw an exception. The vsldap.api.LDAPexception contains a vsldap.api.LDAPResult object called response. A more realistic code fragment would be:
    try{
       LDAPResult res = ldap.add( my_add_request);
      // success, now do something with res
    }
    catch( LDAPexception e ){
      System.out.println( "Add request failed, resultCode = "+e.response.resultCode );
    }
    Note that most simple requests return an LDAPResult containing the required resultCode. More complex operations that return data, such as bind, search and extended operations return a specialised sub-class of LDAPResult which contains the usual LDAPResult values and also any extra specific data such as a list of matched search entries.
     
     

    Handling Referrals

    Any response may contain a list of referrals. The server is expected to return a resultCode of `referral' (#10) in this case and so any referral handling code must be placed in the catch block of the request method since the API will throw a exception for a non-zero resultCode.

    The referrals list is a java.util.Vector list of vsldap.api.LDAPurl objects. The LDAPurl class contains the original LDAPURL as a string and also contains a parsed break-down of the URL into it's components: hostname, port, dn, etc. Note that beacause these are parsed values, URL-encoded characters have been replaced by plaintext equivalents. The `url' field contains the orginal unparsed URL string.
     
     

    LDAP Operation examples

    The following code fragments demonstrate the way to make LDAP requests and analyse the responses using the vsldap.api package.

    In these examples it is assumed the an LDAPSession object (`ldap')has been created and that a successful connection to the server had been established.
     
     

    Example 3. Simple bind

    The bind operation establishes the client's identity and credentials. In LDAP v3 bind can be called repeatedly during an LDAP session. The level of access available to the client is determined by the permissions allocated by the server to the client following the most recent (successful) bind. The client is not required to unbind before re-binding on second or subsequent occasions during a session. In fact, an unbind will permanently terminate the session.

    This example shows a simple anonymous bind request.
     

    1.   // Construct a simple, anonymous bind request
    2.   BindRequest breq = new BindRequest();
    3.  
    4.   // Make the bind request
    5.   try{
    6.     BindResponse bres = ldap.bind( breq );
    7.   }
    8.   catch( LDAPException e ){
    9.     ts.tet_infoline("Failed to bind, got resultCode "+e.result.resultCode );
    10.     ts.tet_result( ts.TET_FAIL );
    11.   }
    12.  
    13.   // Now perform other LDAP operations...


    To bind as a particular user, a BindRequest should be created by passing the Distinguished Name of the user and a plaintext password. The BindResponse contains the usual LDAPResult values. The serverSaslCreds field of the response may contain server-provided binary data following a request to bind using the SASL method (see example below).
     
     

    Example 4. Binding with SASL

    If the server supports the SASL bind mechanism then the client can attempt to bind using this mechanism. The bind is requested as for a simple bind but in this case a special BindRequest constructor is used that takes a Distinguished Name and a vsldap.api.SaslCredentials object containing any relevant SASL credentials as it's arguments.

    Note that VSLDAP is required to test only SASL binds that use the EXTERNAL mechanism over a secure SSL connection. The API allows any SASL bind mechansim to be requested in principle but the behaviour of the VSLDAP test suite with these  mechanisms is unsupported.
     

    1.   // Construct a SASL bind request object declaring the MY_SASL mechanism
    2.   // DN to identify the user to bind as
    3.  
    4.   String dn = "cn=Joe Schmoe, ou=users, o=myorg, c=UN";
    5.  
    6.   // Mecahnism name
    7.   String mech = "MY_SASL";
    8.  
    9.   // credentials data
    10.   byte[] my_creds = some_data;
    11.  
    12.   SaslCredentials saslcreds = new SaslCredentials( mech, my_creds );
    13.  
    14.   BindRequest breq = new BindRequest( dn, saslcreds );
    15.  
    16.   // Make the bind request
    17.   try{
    18.     BindResponse bres = ldap.bind( breq );
    19.   }
    20.   catch( LDAPException e ){
    21.     ts.tet_infoline("Failed to bind, got resultCode "+e.result.resultCode );
    22.     ts.tet_result( ts.TET_FAIL );
    23.   }

    24.  


     

    Example 5. Add request
     

    This example illustrates the way that attributes are handled by the VSLDAP API. The code is based on the VSLDAP version of the BLITS 3.3.4.2 test (VSLDAP 1.4.7.1).

    An array of vsldap.api.Attribute objects is created and loaded into the AddRequest object.
     
     

    1.   // DN of the entry to be added
    2.   String entry = "cn=Austin Powers, ou=Client1, ou=Vendor1, ou=Add, o=IMC, c=US" );
    3.  
    4.   // New add request object
    5.   AddRequest Request = new AddRequest( entry );
    6.  
    7.   // We want to add 8 attributes for this object
    8.   int n_attributes = 8;
    9.   String[] type = new String[ n_attributes ];
    10.   String[][] vals = new String[ n_attributes ][4];
    11.   Attribute[] attr = new Attribute[ n_attributes ];
    12.  
    13.   // The objectclass attribute has 4 values
    14.   type[0] = "objectclass";
    15.   vals[0][0] = "top";
    16.   vals[0][1] = "person";
    17.   vals[0][2] = "organizationalPerson";
    18.   vals[0][3] = "inetOrgPerson";
    19.  
    20.   // Create Attribute objects to hold the data
    21.   attr[0] = new Attribute( type[0] );
    22.   // Add values to attribute #0
    23.   attr[0].addValue( vals[0][0] );
    24.   attr[0].addValue( vals[0][1] );
    25.   attr[0].addValue( vals[0][2] );
    26.  
    27.   // The next 7 attributes have one value each so we use a
    28.   // convenient Attribute constructor that takes type
    29.   // and single value.
    30.   type[1] = "sn";
    31.   vals[1][0]  = "Powers";
    32.   attr[1] = new Attribute( type[1], vals[1][0] );
    33.  
    34.   // Note the escaped " characters in the name
    35.   type[2] = "cn";
    36.   vals[2][0] =  "Austin \"Danger\" Powers";
    37.   attr[2] = new Attribute( type[2], vals[2][0] );
    38.  
    39.   type[3] = "telephoneNumber";
    40.   vals[3][0] = "+44 582 10101";
    41.   attr[3] = new Attribute( type[3], vals[3][0] );
    42.  
    43.   type[4] = "mail";
    44.   vals[4][0] = "secret_agent_man@imc.org";
    45.   attr[4] = new Attribute( type[4], vals[4][0] );
    46.  
    47.   type[5] = "description";
    48.   vals[5][0] = "Yea Baby!!";
    49.   attr[5] = new Attribute( type[5], vals[5][0] );
    50.  
    51.   type[6] = "uid";
    52.   vals[6][0] = "secret_agent_man";
    53.   attr[6] = new Attribute( type[6], vals[6][0] );
    54.  
    55.   type[7] = "description";
    56.   vals[7][0] = "Behave!!";
    57.   attr[7] = new Attribute( type[7], vals[7][0] );
    58.  
    59.   // Load the attributes into the AddRequest object
    60.   for( int i=0; i<n_attributes; i++)
    61.   {
    62.     Request.addAttribute( attr[i] );
    63.   }
    64.  
    65.   try{
    66.     LDAPResult Response = ldap.add( Request );
    67.    }
    68.     catch( LDAPexception e ){
    69.       // The add request has failed, note the details
    70.       // in the TETware journal
    71.       ts.tet_infoline( "add failed, resultCode = "+e.response.resultCode );
    72.       ts.tet_result( ts.TET_FAIL );
    73.     }

     

     

    Example 6. A search operation

    The search request is the most complex of the LDAP requests and is discussed in some detail in this example.
     

    1. LDAPSession ldap = new LDAPSession (ts);
    2.  
    3. // attempt to connect to the server
    4. try{
    5.   ldap.connect();
    6. }
    7. catch( LDAPexception e ){
    8.   ts.tet_infoline("Failed to connect, got LDAPexception "+e.result.resultCode );
    9.   ts.tet_result( ts.TET_UNRESOLVED );
    10. }
    11.  
    12. // New we will send a search request to the server.
    13. // We start by constructing a SearchRequest object
    14. String baseObject = "ou=Search, o=IMC, c=US";
    15.  
    16. String filter = "sn=Bakers";
    17.  
    18. SearchRequest request = new SearchRequest( baseObject, filter );
    19.  
    20. request.scope = Scope.wholeSubtree;
    21.  
    22. // Now make the request
    23. try{
    24.   SearchResponse response = ldap.search( request );
    25.   // success! lets look at the results
    26.   response.dumpEntries();
    27. }
    28. catch( LDAPexception e){
    29. {
    30.   // The request failed...
    31.   ts.tet_infoline("The search request failed, resultCode = "+e.result.resultCode);
    32. }

    Lines 1-10: These lines contain the standard snippet of code for creating an LDAP session. One small difference from the earlier examples is that the catch block returns a result of ts.TET_UNRESOLVED instead of ts.TET_FAIL. The reason for this is that we can only fail a test if it fails the stipulated purpose of the test (the associated assertion phrases this test purpose). In this example, we are not testing the connection and so if it fails it is wrong to set the test result to TET_FAIL because this failure to connect has nothing to do with our test purpose (which is to test the search operation). In cases like this, we set the test result to TET_UNRESOLVED which means that the test didn't work but the failure is not the subject of the assertion, so we note it in the journal as a problem to be investigated.

    Lines 14-20: We wish the server to perform a search operation and send us the results. This means that we have to compose a SearchRequest and send this to the server. The SearchRequest is a class from vsldap.api and is similar to the SearchRequest structute defined in RFC2251. We know from the LDAP spec. that a search request must specify, among other values a baseObject: the DN of the starting point of the search and a filter: the matching expression for the search.
    From experience it is found that apart from baseObject and filter, the other search request parameters can often be left with default values. This is why, when we create an instance of SearchRequest in Line 15, we pass only two arguments: baseObject and filter. The constructor ensures that all other parameters are set to default values. If we wish to override these defaults we can since the fields of the SearchRequest object are publically accessible.
    Line 16 is an example of overriding the default value. We set the scope of search to Scope.wholeSubtree, where Scope is a simple vsldap.api class that contains constants to define the three possible search scopes (The default value was request.scope = Scope.baseObject).

    Note that there is no data-hiding here, the scope field of SearchRequest is public. It can be modified directly. In many APIs data fields are protected, with acces only via `get' and `set' methods. This level of encapsulation is suitable for some designs, but not here, where objects are mostly short-lived, single-use containers that rarely leave the scope in which they were created.

    Lines 23-32: We send the SearchRequest to the server by passing the object to the ldap.search() method. This method returns an instance of the SearchResponse class, so we roll the call to search() and declaration of SearchResponse response into a single line. As with most LDAPSession methods, search() can throw an LDAPexception and so the method is called inside a try-catch block. If the search request fails, the an exception will be thrown and we deal with it as shown. If the search request succeeds, the server will perform the search operation and return the results, which are delivered to us in the SearchResponse object. We can now examine the contents of that object to see what we have netted in our search.

    Line 26 features SearchResponse.dumpEntries()  - a handy development method which dumps the contents of all the entries returned from the search onto `system.out' (as defined by Java).
    Exactly what we see when we call dumpEntries() depends on the Directory that we are searching. VSLDAP requires the server under test to running a specially designed Directory, based on the BLITS Directory, which contains carefully chosen entries and other features to test all aspects of LDAPv3.
     

    Examining a search response

    A SearchResponse object contains two public Vector lists: entries and references.

    The `entries' field is a list of vsldap.api.SearchResultEntry objects, each of which contains the DN string and a Vector list of Attribute objects. The fields are public - to examine the data use the standard java.util.Vector API to iterate the lists.

    The SearchResultEntry class provides a method `attributeExists()' that test for the presence of a specified attribute type,value pair.  Passing the attribute value "*" will match to any value, thus providing a test for the presence of an attribute in an entry.

    The `findAttribute()' method returns the Attribute object of a named attribute, if present.

    Any continuation references that have been returned by the server will be contained in the `references' list. This is a Vector list of LDAPurl objects, as with referrals (see above).
     


    LDAP Controls

    LDAP controls are sent implicitly with every request. Controls are stored in a Vector list in the request object. When a request object is created the list is empty. To add a control create a vsldap.api.LDAPcontrol object and add it to the `controls' field of the request object using the java.util.Vector API (the controls list is public).
     
     
     

    Other Features

    There are several features of the VSLDAP API which are provided to aid certification testing but are not sufficiently general and flexible to merit discussion in this guide. These features include asynchronous requests (only for the compare operation in this version), optional feature data (although see example 2) and methods for generating certain types of DN used by the BLITS test directory. Examination of the test scripts included in this release and the API documentation will provide some information on the use of these features.
     

    Further Information
     


     
     
    Contents << Prev Next >>
    VSLDAP User Guide.  Release 2.3 GA, February 2007.

    Copyright ©  2007 The Open Group
    All rights reserved.