Sunday, July 3, 2011

Setting up cloud manager of Stratos with an external LDAP

Cloud manager is the service in WSO2 Stratos which provides the core functionalities of tenant management like creating new tenants by the super admin and self registration of tenants.

Stratos 1.5 is going to be released with in next couple of weeks. Then you will be able to download the distribution from wso2.org.
For now you need to check out the source from https://svn.wso2.org/repos/wso2/branches/carbon/3.2.0/ and build the stratos manager in products/manager/  location.

You can setup stratos on your machine using the setup script (stratos-setup.pl) which is available here.
In order to run that script, you need to: 
  • have perl installed in your machine. In Linux, you can install "liblist-moreutils-perl".
  • Have my-sql installed in the machine and have username and password root/root.
  • set the following environment variables in the system.
export CARBON_DIR=/home/hasini/WSO2/branch_3.2.0/carbon
export STRATOS_DIR=/home/hasini/WSO2/stratos testing/1.5.0/setup/unpacks
export STRATOS_VERSION=1.5.0
export SSO_ENABLED=false
export CREATE_DB=true
export STARTUP_DELAY=30 
export PACKS_DIR=/home/hasini/WSO2/stratos testing/1.5.0/setup/packs
export PRODUCT_LIST="manager"

Following is an explanation on each of the above environment variable:
CARBON_DIR - root directory of your carbon check out. You need this if you build the stratos service distributions from source.
STRATOS_DIR - this is the directory to where the distributions zip files should be unpacked by the script.
STRATOS_VERSION - current stratos version
SSO_ENABLED - whether single sign on should be enabled across the different stratos services. In this case, we do not need it.
CREATE_DB - whether stratos related databased in my-sql should be created (if exists and this value is true, tables will be dropped and recreated)
STARTUP_DELAY - if several stratos services are going to be started, the delay between two startups
PACKS_DIR - if you are settting up stratos with downloaded packs, this is the folder where those packs are located.
PRODUCT_LIST - the list of stratos services that should be set up from this script.

Now all set, lets follow the following steps to start the cloud manager with an external ApacheDS LDAP server.
  1. Create a new partition in ApacheDS LDAP where the separate user,group bases for each tenant will be created, when creating tenants through cloud manager. You may refer to my previous blog post for creating a new partition in ApacheDS. You may also create separate user base and group base for super tenant space. (i.e ou=Users,dc=wso2,dc=org & ou=groups,dc=wso2,dc=org)
  2. Go to the above mentioned STRATOS_DIR where stratos manager has been unpacked, and change the following configuration files found in [carbon_home]/repository/conf
    • tenant-mgt.xml: There you may notice that default tenant manager is JDBCTenantManager, but we now support tenant management with LDAP as well which we are going to demonstrate here. (if you didn't find this file in the above location, create a file named tenant-mgt.xml and copy the content shown below.)
      • comment out the JDBCTenantManager and uncomment the CommonHybridLDAPTenantManager. 
      • set the "RootPartition" as the partition name that we created in step 1 above.
      • Following is the tenant-mgt.xml in our case:

    dc=wso2,dc=com
    organizationalUnit
    ou
    organizationalUnit
    ou

      • user-mgt.xml:
        • locate the property "MultiTenantRealmConfigBuilder". This is the class which is responsible for cloning the bootstrap user-mgt.xml and creating tenant-specific user-mgt.xml which contains the specific user and group bases of each tenant. Set it as follows: 
        • org.wso2.carbon.user.core.config.multitenancy.CommonLDAPRealmConfigBuilder
          
        • Comment out the JDBCUserStoreManager which comes by default and uncomment the ApacheDSUserStoreManager for an external LDAP in read/write mode.
        • In ApacheDSUserStoreManager configuration element, set ConnectionURL, ConnectionName, ConnectionPassword, UserSearchBase and GroupSearchBase for WSO2 user manager to connect to your external LDAP server.
    
                false
                ldap://localhost:10389
                uid=admin,ou=system
                secret
                SHA
                (objectClass=person)
                inetOrgPerson
                ou=Users,dc=wso2,dc=com
                (&(objectClass=person)(uid=?))
                uid
                [\\S]{5,30}
                true
                true
                false
                ou=Groups,dc=wso2,dc=com
                (objectClass=groupOfNames)
                groupOfNames
                (&(objectClass=groupOfNames)(cn=?))
                cn
                member
            
    
      • embedded-ldap.xml:
        • Disable the embedded-ldap server by setting "enable" property to false.
           3. Start the external ApacheDS server.
           4. Start the WSO2 Stratos Manager by running wso2server.sh/.bat scripts in  [stratos_manager_home] /bin directory.
           5. Access management console through https://localhost:9443/carbon.
           6. Login as super tenant with username: admin, password: secret (note that these are the credentials of admin user that you specified in user-mgt.xml)
           7.  Add a new tenant by providing tenant details as follows:
        8. After the tenant is successfully registered, you need to activate the tenant by checking the "activate" check box.
        9. Then log out from the super admin account.
       10. Login as the tenant admin of newly created tenant, by providing the above given credentials. For an example, in the above case, user name should be hasini@willpower.org.
    You can create new users, new groups and assign users to those groups under your tenant.
    If you look at the multi-tenanted LDAP structure from ApacheDirectory Studio, you will observer that there is a separate organizational unit is created for the tenant wso2.org and inside that, separate user base and a group base is created as shown in the following diagram.

    That's it... You can create more tenants and experience the tenant management with a multi-tenanted LDAP.

    How to create a new partition in ApacheDS

    In LDAP servers, we might need to create new partitions to create the directory structure that we need, to store entries independent of the data inside other partitions and to have a root domain name that we desire.

    I will take ApacheDS LDAP server as an example and demonstrate how to create a new partition.
    1. Edit the server.xml file that resides in the [apacheds_home]/conf to add a new partition entry.
      • locate the aforementioned file.
      • locate the "partitions" element - you can see the default partition dc=example,dc=com.
      • Just copy and paste the element "jdbmpartition" which defines the entry for example.com partition with in the "partitions" element and change the name to the root domain name that you want to give to the new partition.
      • Lets say the new partition name is dc=wso2,dc=com.
    2. Now start the ApacheDS server by running the server starting script inside apacheds_home.
    3. Connect to the ApacheDS LDAP server from ApacheDirectory studio. You can find a guide for this from here.
    4. If you click on "RootDSE" in the LDAP Browser window, you will see an entry as dc=wso2,dc=com on the right hand side panel as shown below. But it is not yet shown as a new partition in the directory in LDAP browser window.

          5. To create a new partition out of that entry, we need to create a new conext entry of the object type "domain." For that;
      • Right click on "RootDSE" -> select "New" -> "New Context Entry"
      • From wizard, select "Create entry from scratch"
      • In the next window, select object class as "domain"
      • Give the Distinguished Name of the context entry as "dc=wso2,dc=com" and click finish.
    Now you can see the created partition in the DIT structure shown in the LDAP Browser window as follows:
    Now you can create your own directory structure there by creating "ou"s and store entries.
    Have a great time...!! :)

    Connection Pooling with LDAP

    When we write client applications to communicate with a LDAP server and query, update entries, one thing we should keep in mind is enabling connection pooling with LDAP when creating the connection to LDAP server from our client application.

    Otherwise we may run into troubles in multi-threaded environments or load testing environments. I learned it in the hard way and sharing with you the details.

    Problem:
    When a load test was run on a LDAP client application, I observed the following errors in Windows Operating System when the load is increased beyond a certain threshold:
    [1]
    javax.naming.CommunicationException: localhost:10389 [Root exception is java.net.SocketException: No buffer space available (maximum connections reached?): connect]
    
    [2]
    javax.naming.CommunicationException: localhost:10389 [Root exception is java.net.BindException: Address already in use: connect]
     at com.sun.jndi.ldap.Connection.(Connection.java:210)
     at com.sun.jndi.ldap.LdapClient.(LdapClient.java:118)
    

    Cause:
    Above errors have been caused due to the running out of dynamic ports in the OS so that client application can not make any more connections to LDAP server.

    Usually LDAP server is running on a specific port (say 10389) and when a connection is created from client, that is assigned a port in the range of dynamic ports which is defined as a property of Windows OS. This issue is not visible on Linux.

    So the above errors can occur when the available number of ports are already being used.

    Solution:
    1. Enable LDAP connection pooling when creating the connection to the LDAP as follows.
    Hashtable environment = new Hashtable();
    //set other environment properties
    .......
    // Enable connection pooling
    environment.put("com.sun.jndi.ldap.connect.pool", "true");
    //create LDAP connection
    DirContext context = context = new InitialDirContext(environment);
    
    This tutorial explains LDAP Connection pooling in detail.

    2. Close any sub Contexts and NamingEnumerations derived by a particular LDAP connection Context, close them explicitly at the end of their usage.

    3. Close the LDAP connection created after using it for the purpose it was created.
    You can read more about LDAP Connection closure from here.