Thursday, November 1, 2012

Automated Remote WAS Deployment with Ant and Thin Client

This is a follow up to my previous struggle working with automated WAS deployments. The next goal was to get the deployment running on our Windows 7 build server which did not have WAS installed and I had no intention of installing that 3GB download just for some simple wsadmin tasks.

In my previous post I had examples of the ant task deployments using ws_ant. But I soon found that this is only possible with the full WAS install. I'll include the scripts below for anyone that might have WAS installed anyway and want to go down this route, the script is tested and working:

 <!-- Project properties -->
 <property name="earname" value="myapp-qa-test" />
 <property name="appname" value="myapp-test" />
 <property name="build.dir" location="build" />

 <!-- was properties -->
 <property name="host" value="myhost.ibm.com" />
 <property name="conntype" value="SOAP" />
 <property name="port" value="1107" />
 <property name="user" value="--username--" />
 <property name="password" value="--password--" />
 <property name="node" value="AppSrv01" />
 <property name="server" value="server1" />
 <property name="virtualHost" value="test_host" />
 <property name="webAppDisplayName" value="myapp" />
 
 <!-- These ant tasks require the full WAS install to use with a default profile --> 
 
 <!-- Was Ant task definitions -->
 <taskdef name="wsStartApplication" classname="com.ibm.websphere.ant.tasks.StartApplication" />
 <taskdef name="wsStopApplication" classname="com.ibm.websphere.ant.tasks.StopApplication" />
 <taskdef name="wsStartServer" classname="com.ibm.websphere.ant.tasks.StartServer" />
 <taskdef name="wsStopServer" classname="com.ibm.websphere.ant.tasks.StopServer" />
 <taskdef name="wsInstallApp" classname="com.ibm.websphere.ant.tasks.InstallApplication" />
 <taskdef name="wsUninstallApp" classname="com.ibm.websphere.ant.tasks.UninstallApplication" />
 <taskdef name="wsListApps" classname="com.ibm.websphere.ant.tasks.ListApplications" />

 <target name="listApps">
  <wsListApps host="${host}" port="${port}" conntype="${conntype}"
   user="${user}" password="${password}" failonerror="true"/>
 </target>
 
 <target name="deploy" description="Deoploy the ear to stage">
  <wsInstallApp ear="${build.dir}/${earname}.ear" options="-appname ${appname}" 
   host="${host}" port="${port}" conntype="${conntype}"
   user="${user}" password="${password}" failonerror="true"/>
 </target>

 <target name="undeploy">
  <wsUninstallApp application="${appname}"
   host="${host}" port="${port}" conntype="${conntype}" user="${user}" password="${password}" 
   failonerror="true" />
 </target>

 <target name="startApplication">
  <wsStartApplication application="${appname}" 
   host="${host}" port="${port}" conntype="${conntype}" user="${user}" password="${password}" 
   server="${server}" node="${node}"/>
 </target>

 <target name="stopApplication">
  <wsStopApplication application="${appname}"
   host="${host}" port="${port}" conntype="${conntype}" user="${user}" password="${password}" 
   server="${server}" node="${node}"/>
 </target>

 <target name="update" >
     <wsInstallApp ear="${build.dir}\${earname}.ear" options="-update -appname ${appname} -MapWebModToVH {{${webAppDisplayName} ${appname}.war,WEB-INF/web.xml ${virtualHost}}}" 
      host="${host}" port="${port}" conntype="${conntype}" user="${user}" password="${password}" 
      failonerror="true"/>  
 </target>

These scripts will work assuming ws_ant is working correctly. One interesting thing you may notice I have removed the start/stop server targets from here this is because according to the documentation:

The wsStopServer task enables you to stop a standalone server instance. This is not used to stop a server controlled by DeploymentManager. Therefore, this task is useful for the Base Application Server, and to stop the Node Agent and/or DeploymentManager. If you wish to stop a server managed by the Deployment Manager, use the wsadmin task to execute a scripting command. The structure of the wsStopServer task is shown below

So even if ws_ant could work with the WAS Thin Client I would have no way to bounce the application server anyway. To be thorough I asked IBM if these targets could be used with the WAS Thin Client and this was the response:

The ws_ant implementation depends on having a valid profile in order to correctly set up class loading. Currently, there is no way around this other than to install an instance of WAS on the build machine and create a default profile, or to drive the application install from one of the servers already hosting WAS. Thus, can you temporarily install WAS on that machine, perhaps try WAS Developers Edition, which is free.

If you try using ws_ant with the Thin Client this is the error I received:

WCMD0002E: Command "ws_ant.bat" requires a profile. No default profile exists and a profile name was not specified. Ensure that a default profile exists or use the -profileName parameter to specify the name of an existing profile.

I was not impressed with this response but I knew I could get the Thin Client working and call wsadmin tasks from ant using the exec command. So let look at setting up the thin client.

The latest documentation is here, this will help you get the thin client set up, unfortunately is assumes you have access to a full WAS install and is not available in a small download package.

Try to follow the Procedure section as best you can, It basically involves copying a buch of config and jar files to C:\WASThinClient, you should also copy the Java jre to this folder.

My directory structure looked like this (Note I have not included all the java files but you get the idea):

C:\WASThinClient
|   com.ibm.ws.admin.client_8.5.0.jar
|   com.ibm.ws.security.crypto.jar
|   wsadmin.bat
|   
----etc
|       key.p12
|       trust.p12
|       
----java ...
|   ----bin
|   ----docs
|   ----include
|   ----jre
|   ----lib
|   |---properties
|               
|---properties
        ipc.client.props
        sas.client.props
        soap.client.props
        ssl.client.props
        wsjaas_client.conf
The only interesting part of this is the wsadmin.bat file, which I have included here, if you use the wsadmin from the IBM site you will have problems with new line characters and misc bugs, I would highly recommend starting with the file I provided and modifying the paths as needed. Remember to remove the txt extension as I was unable to upload a bat file.

Once you get to this stage you will want to test that you can call wsadmin tasks directly in the command prompt, so open a prompt at your thin client directory and runs some simple commands:

wsadmin -c "$AdminControl getNode" -host myhost.ibm.com -conntype soap -port 5112 -user <enteruserid> -password <enterpassword>
->AppSrv01
wsadmin -c "$AdminControl getNode" -host myhost.ibm.com -conntype soap -port 1107 -user <enteruserid> -password <enterpassword>
->Dmgr01
wsadmin -c "$AdminControl getHost" -host myhost.ibm.com -conntype soap -port 5112 -user <enteruserid> -password <enterpassword>
->myhost.ibm.com
wsadmin -c "$AdminControl getHost" -host myhost.ibm.com -conntype soap -port 1107 -user <enteruserid> -password <enterpassword>
->myhost.ibm.com

You will notice here the port numbers vary, most commands will be sent to the deployment manager soap port:

Now you have the thin client working and can connect to the remote WAS install and run commands lets tie it all together with ant.

Here is the rest of the ant file which calls the wsadmin.bat file in the thin client. Note the properties can be found above:
 <target name="updateWsAdmin">
   <exec executable="cmd">
     <arg value="/c"/>
     <arg value="C:\WASThinClient\wsadmin.bat"/>
     <arg value="-c"/>
    <arg value="$AdminApp install build/${earname}.ear { -update -appname ${appname} -MapWebModToVH {{${webAppDisplayName} ${appname}.war,WEB-INF/web.xml ${virtualHost}}} }"/>
    <arg value="-host"/>
    <arg value="${host}"/>
    <arg value="-conntype"/>
    <arg value="${conntype}"/>
    <arg value="-port"/>
    <arg value="${port}"/>
    <arg value="-user"/>
    <arg value="${user}"/>
    <arg value="-password"/>
    <arg value="${password}"/>
   </exec>
 </target>
  
 <target name="saveWsAdmin">
   <exec executable="cmd">
     <arg value="/c"/>
     <arg value="C:\WASThinClient\wsadmin.bat"/>
     <arg value="-c"/>
    <arg value="$AdminConfig save"/>
    <arg value="-host"/>
    <arg value="${host}"/>
    <arg value="-conntype"/>
    <arg value="${conntype}"/>
    <arg value="-port"/>
    <arg value="${port}"/>
    <arg value="-user"/>
    <arg value="${user}"/>
    <arg value="-password"/>
    <arg value="${password}"/>
   </exec>
 </target>
 
 <target name="startServerWsAdmin">
   <exec executable="cmd">
     <arg value="/c"/>
     <arg value="C:\WASThinClient\wsadmin.bat"/>
     <arg value="-c"/>
    <arg value="$AdminControl startServer ${server} ${node}"/>
    <arg value="-host"/>
    <arg value="${host}"/>
    <arg value="-conntype"/>
    <arg value="${conntype}"/>
    <arg value="-port"/>
    <arg value="${port}"/>
    <arg value="-user"/>
    <arg value="${user}"/>
    <arg value="-password"/>
    <arg value="${password}"/>
   </exec>
 </target>
 
 <target name="stopServerWsAdmin">
   <exec executable="cmd">
     <arg value="/c"/>
     <arg value="C:\WASThinClient\wsadmin.bat"/>
     <arg value="-c"/>
    <arg value="$AdminControl stopServer ${server} ${node}"/>
    <arg value="-host"/>
    <arg value="${host}"/>
    <arg value="-conntype"/>
    <arg value="${conntype}"/>
    <arg value="-port"/>
    <arg value="${port}"/>
    <arg value="-user"/>
    <arg value="${user}"/>
    <arg value="-password"/>
    <arg value="${password}"/>
   </exec>
 </target>
 
 <target name="deployApp" depends="updateWsAdmin,saveWsAdmin,stopServerWsAdmin,startServerWsAdmin"/>
Here is the full wasdeploy.xml if you need it. There is not too much to discuss about the file the main target which Jenkins should call is deployApp, this will perform an update (assumes the app is already installed), it will then save to the master configuration and bounce the application server. One interesting option here is MapWebModToVH this was needed as our application needs a particular virtual host but you may not need this option. The jacl script can also be written in jython and can be moved into a script as needed.

Here is a list of available options
  [wsadmin] MapModulesToServers
  [wsadmin] MapJaspiProvider
  [wsadmin] MapWebModToVH
  [wsadmin] CtxRootForWebMod
  [wsadmin] MapInitParamForServlet
  [wsadmin] MapSharedLibForMod
  [wsadmin] SharedLibRelationship
  [wsadmin] JSPCompileOptions
  [wsadmin] JSPReloadForWebMod
  [wsadmin] CustomActivationPlan
  [wsadmin] ModuleBuildID
  [wsadmin] GetServerName
  [wsadmin] preCompileJSPs
  [wsadmin] nopreCompileJSPs
  [wsadmin] distributeApp
  [wsadmin] nodistributeApp
  [wsadmin] useMetaDataFromBinary
  [wsadmin] nouseMetaDataFromBinary
  [wsadmin] deployejb
  [wsadmin] nodeployejb
  [wsadmin] createMBeansForResources
  [wsadmin] nocreateMBeansForResources
  [wsadmin] reloadEnabled
  [wsadmin] noreloadEnabled
  [wsadmin] deployws
  [wsadmin] nodeployws
  [wsadmin] processEmbeddedConfig
  [wsadmin] noprocessEmbeddedConfig
  [wsadmin] allowDispatchRemoteInclude
  [wsadmin] noallowDispatchRemoteInclude
  [wsadmin] allowServiceRemoteInclude
  [wsadmin] noallowServiceRemoteInclude
  [wsadmin] useAutoLink
  [wsadmin] nouseAutoLink
  [wsadmin] enableClientModule
  [wsadmin] noenableClientModule
  [wsadmin] validateSchema
  [wsadmin] novalidateSchema
  [wsadmin] usedefaultbindings
  [wsadmin] nousedefaultbindings
  [wsadmin] defaultbinding.force
  [wsadmin] allowPermInFilterPolicy
  [wsadmin] noallowPermInFilterPolicy
  [wsadmin] verbose
  [wsadmin] update
  [wsadmin] update.ignore.old
  [wsadmin] update.ignore.new
  [wsadmin] installed.ear.destination
  [wsadmin] appname
  [wsadmin] reloadInterval
  [wsadmin] validateinstall
  [wsadmin] filepermission
  [wsadmin] buildVersion
  [wsadmin] blaname
  [wsadmin] asyncRequestDispatchType
  [wsadmin] clientMode
  [wsadmin] deployejb.rmic
  [wsadmin] deployejb.dbtype
  [wsadmin] deployejb.dbschema
  [wsadmin] deployejb.classpath
  [wsadmin] deployejb.dbaccesstype
  [wsadmin] deployejb.sqljclasspath
  [wsadmin] deployejb.complianceLevel
  [wsadmin] deployws.classpath
  [wsadmin] deployws.jardirs
  [wsadmin] defaultbinding.datasource.jndi
  [wsadmin] defaultbinding.datasource.username
  [wsadmin] defaultbinding.datasource.password
  [wsadmin] defaultbinding.cf.jndi
  [wsadmin] defaultbinding.cf.resauth
  [wsadmin] defaultbinding.ejbjndi.prefix
  [wsadmin] defaultbinding.virtual.host
  [wsadmin] defaultbinding.strategy.file
  [wsadmin] target
  [wsadmin] server
  [wsadmin] cell
  [wsadmin] cluster
  [wsadmin] contextroot
  [wsadmin] custom

Some additional notes, if the wsadmin task is on the path (which is a good idea) you can then use <exec executable="wsadmin"> rather than using the cmd with the /c param, more info.

Inside the wsadmin.bat there is a few parms worth looking into wsadminConnType, wsadminPort, wsadminHost by using these you should be able to remove some arguments from the ant targets.

You may also be able to remove the hard coded credentials by editing the soap.client.props but I have not had time to test this.

You can simply run the ant task in Eclipse by double clicking the target, no external tool are now needed:



Here is my example output for a full deploy:

  Buildfile: C:\...\wasdeploy.xml
updateWsAdmin:
     [exec] WASX7209I: Connected to process "dmgr" on node Dmgr01 using SOAP connector;  The type of process is: DeploymentManager
     [exec] ADMA5017I: Uninstallation of myapp started.
     [exec] ADMA5104I: The server index entry for WebSphere:cell=Cell01,node=AppSrv01 is updated successfully.
     [exec] ADMA5102I: The configuration data for myapp from the configuration repository is deleted successfully.
     [exec] ADMA5011I: The cleanup of the temp directory for application myapp is complete.
     [exec] ADMA5106I: Application myapp uninstalled successfully.
     [exec] ADMA5016I: Installation of myapp started.
     [exec] ADMA5058I: Application and module versions are validated with versions of deployment targets.
     [exec] ADMA5005I: The application myapp is configured in the WebSphere Application Server repository.
     [exec] ADMA5005I: The application myapp is configured in the WebSphere Application Server repository.
     [exec] ADMA5081I: The bootstrap address for client module is configured in the WebSphere Application Server repository.
     [exec] ADMA5053I: The library references for the installed optional package are created.
     [exec] ADMA5005I: The application myapp is configured in the WebSphere Application Server repository.
     [exec] ADMA5001I: The application binaries are saved in /opt/WebSphere/was80_AppServer/profiles/Dmgr01/wstemp/Script13ab83dd167/workspace/cells/Cell01/applications/myapp.ear/myapp.ear
     [exec] ADMA5005I: The application myapp is configured in the WebSphere Application Server repository.
     [exec] SECJ0400I: Successfully updated the application myapp with the appContextIDForSecurity information.
     [exec] ADMA5005I: The application myapp is configured in the WebSphere Application Server repository.
     [exec] ADMA5005I: The application myapp is configured in the WebSphere Application Server repository.
     [exec] ADMA5113I: Activation plan created successfully.
     [exec] ADMA5011I: The cleanup of the temp directory for application myapp is complete.
     [exec] ADMA5013I: Application myapp installed successfully.
saveWsAdmin:
     [exec] WASX7209I: Connected to process "dmgr" on node Dmgr01 using SOAP connector;  The type of process is: DeploymentManager
stopServerWsAdmin:
     [exec] WASX7209I: Connected to process "dmgr" on node Dmgr01 using SOAP connector;  The type of process is: DeploymentManager
     [exec] WASX7337I: Invoked stop for server "server1" on node "AppSrv01"; Waiting for stop completion.
     [exec] WASX7264I: Stop completed for server "server1" on node "AppSrv01"
startServerWsAdmin:
     [exec] WASX7209I: Connected to process "dmgr" on node Dmgr01 using SOAP connector;  The type of process is: DeploymentManager
     [exec] WASX7262I: Start completed for server "server1" on node "AppSrv01"
deployApp:
BUILD SUCCESSFUL
Total time: 3 minutes 6 seconds

References: