Friday, February 26, 2010

WebSphere automated deployment on Hudson

Hudson CI server provides plug-in for automated deployment to Tomcat or JBoss servers. But on my current job we use IBM WebSphere as application server in cluster environment. To implement nightly builds with automated testing we had to figure out way to automate deployment.

General idea and code is based on Luciano Resende's posting.



Usual deployment procedure for IBM WebSphere cluster is:



1.Stop cluster

2.Undeploy application

3.Deploy application and change application parameters like classloaders' order

4.Start cluster

5.As a part of automated testing procedure, we had to wait till start completes and then start test

All these steps could take 10-15 minutes depending on environment, was security enabled or not, etc.

To implement automated deployment for WebSphere we can use wsadmin thin client and Apache ant in conjunction with bash scripts.



In order to use wsadmin remotely, several files need to be copied from IBM Websphere node manager.

Wsadmin script, provided on IBM's site, didn't work for me, so I had to change it slightly.

My version of wsadmin



view sourceprint?

01 #!/bin/bash



02 #set -x



03 # example wsadmin launcher



04 binDir=`dirname "$0"`



05 # WAS_HOME should point to the directory for the thin client



06 WAS_HOME="$binDir"



07 USER_INSTALL_ROOT="$WAS_HOME"



08 # JAVA_HOME should point to where java is installed for the thin client



09 WAS_LOGGING="-Djava.util.logging.manager=com.ibm.ws.bootstrap.WsLogManager -Djava.util.logging.configureByServer=true"



10 if [ -f ${JAVA_HOME}/bin/java ]; then



11 JAVA_EXE="${JAVA_HOME}/bin/java"



12 else



13 JAVA_EXE="${JAVA_HOME}/jre/bin/java"



14 fi



15 CLIENTSOAP=-Dcom.ibm.SOAP.ConfigURL=file:"$USER_INSTALL_ROOT"/properties/soap.client.props



16 CLIENTSAS=-Dcom.ibm.CORBA.ConfigURL=file:"$USER_INSTALL_ROOT"/properties/sas.client.props



17 CLIENTSSL=-Dcom.ibm.SSL.ConfigURL=file:"$USER_INSTALL_ROOT"/properties/ssl.client.props



18 wsadminTraceString=-Dcom.ibm.ws.scripting.traceString=com.ibm.*=all=enabled



19 wsadminTraceFile=-Dcom.ibm.ws.scripting.traceFile="$USER_INSTALL_ROOT"/logs/wsadmin.traceout



20 wsadminValOut=-Dcom.ibm.ws.scripting.validationOutput="$USER_INSTALL_ROOT"/logs/wsadmin.valout



21 # For debugging the utility itself



22 WAS_DEBUG="-Djava.compiler=NONE -Xdebug -Xnoagent"



23 #-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=7777"



24 SHELL=com.ibm.ws.scripting.WasxShell



25 # Parse the input arguments



26 isJavaOption=false



27 nonJavaOptionCount=1



28 for option in "$@" ; do



29 if [ "$option" = "-javaoption" ] ; then



30 isJavaOption=true



31 else



32 if [ "$isJavaOption" = "true" ] ; then



33 javaOption="$javaOption $option"



34 isJavaOption=false



35 else



36 nonJavaOption[$nonJavaOptionCount]="$option"



37 nonJavaOptionCount=$((nonJavaOptionCount+1))



38 fi



39 fi



40 done



41 DELIM=" "



42 C_PATH="$WAS_HOME/com.ibm.ws.admin.client_6.1.0.jar:$WAS_HOME/com.ibm.ws.security.crypto_6.1.0.jar"



43 #Platform specific args...



44 PLATFORM='/bin/uname'



45 case $PLATFORM in



46 AIX
Linux
SunOS
HP-UX)



47 CONSOLE_ENCODING=-Dws.output.encoding=console ;;



48 OS/390)



49 EXTRA_D_ARGS="-Dfile.encoding=ISO8859-1 $DELIM-Djava.ext.dirs="$JAVA_EXT_DIRS""



50 EXTRA_X_ARGS="-Xnoargsconversion" ;;



51 esac



52 # Set java options for performance



53 PLATFORM=`/bin/uname`



54 case $PLATFORM in



55 AIX)



56 PERF_JVM_OPTIONS="-Xms256m -Xmx256m -Xquickstart" ;;



57 Linux)



58 PERF_JVM_OPTIONS="-Xms256m -Xmx256m -Xj9 -Xquickstart" ;;



59 SunOS)



60 PERF_JVM_OPTIONS="-Xms256m -Xmx256m -XX:PermSize=40m" ;;



61 HP-UX)



62 PERF_JVM_OPTIONS="-Xms256m -Xmx256m -XX:PermSize=40m" ;;



63 OS/390)



64 PERF_JVM_OPTIONS="-Xms256m -Xmx256m" ;;



65 esac



66 "$JAVA_EXE" \



67 $EXTRA_X_ARGS \



68 $CONSOLE_ENCODING \



69 $javaOption \



70 $WAS_DEBUG \



71 "$CLIENTSAS" \



72 "$CLIENTSSL" \



73 "$CLIENTSOAP" \



74 ${JAASSOAP:+"$JAASSOAP"} \



75 -Dconfig_consistency_check="$CONFIG_CONSISTENCY_CHECK" \



76 -Dwas.install.root="$WAS_HOME" \



77 -Duser.install.root="$USER_INSTALL_ROOT" \



78 $EXTRA_D_ARGS \



79 $PERF_JVM_OPTIONS \



80 $WAS_LOGGING \



81 $wsadminTraceFile \



82 $wsadminTraceString \



83 $wsadminValOut \



84 $wsadminHost \



85 $wsadminConnType \



86 $wsadminPort \



87 $wsadminLang \



88 -classpath "$C_PATH" \



89 $SHELL "${nonJavaOption[@]}"



90 exit $?





Since we want use in on Hudson, probably on other server and locally, we need Ant script.

It's kinda big and a lot of parameters repeats but it gives idea that's happening.



view sourceprint?

001 <?xml version="1.0"?>



002 <project name="was-integration" basedir=".">



003 <property environment="env"/>



004 <property name="was.python.script" value="./wsIntegration.py"/>



005 <property name="application.name" value="APPLICATION"/>



006 <property name="application.cell" value="CELL"/>



007 <property name="application.cluster" value="CLUSTER"/>



008 <property name="host" value="HOST"/>



009 <property name="port" value="PORT"/>



010 <property name="application.ear" value="PATH_TO_EAR"/>



011 <property name="wsadmin" value="${basedir}/wsadmin.sh"/>



012



013 <target name="clusterState" >



014 <exec dir="." executable="${wsadmin}" outputproperty="currentState">



015 <arg value="-conntype"/>



016 <arg value="SOAP"/>



017 <arg value="-lang"/>



018 <arg value="jython"/>



019 <arg value="-host"/>



020 <arg value="${host}"/>



021 <arg value="-port" />



022 <arg value="${port}"/>



023 <arg value="-f"/>



024 <arg value="${was.python.script}"/>



025 <arg value="clusterState"/>



026 <arg value="${application.cell}"/>



027 <arg value="${application.cluster}"/>



028 </exec>



029 </target>



030



031 <target name="clusterStart" >



032 <exec dir="." executable="${wsadmin}">



033 <arg value="-conntype"/>



034 <arg value="SOAP"/>



035 <arg value="-lang"/>



036 <arg value="jython"/>



037 <arg value="-host"/>



038 <arg value="${host}"/>



039 <arg value="-port" />



040 <arg value="${port}"/>



041 <arg value="-f"/>



042 <arg value="${was.python.script}"/>



043 <arg value="clusterStart"/>



044 <arg value="${application.cell}"/>



045 <arg value="${application.cluster}"/>



046 </exec>



047 </target>



048



049 <target name="clusterStopt" >



050 <exec dir="." executable="${wsadmin}">



051 <arg value="-conntype"/>



052 <arg value="SOAP"/>



053 <arg value="-lang"/>



054 <arg value="jython"/>



055 <arg value="-host"/>



056 <arg value="${host}"/>



057 <arg value="-port" />



058 <arg value="${port}"/>



059 <arg value="-f"/>



060 <arg value="${was.python.script}"/>



061 <arg value="clusterStop"/>



062 <arg value="${application.cell}"/>



063 <arg value="${application.cluster}"/>



064 </exec>



065 </target>



066 <target name="undeployApplication" >



067 <exec dir="." executable="${wsadmin}">



068 <arg value="-conntype"/>



069 <arg value="SOAP"/>



070 <arg value="-lang"/>



071 <arg value="jython"/>



072 <arg value="-host"/>



073 <arg value="${host}"/>



074 <arg value="-port" />



075 <arg value="${port}"/>



076 <arg value="-f"/>



077 <arg value="${was.python.script}"/>



078 <arg value="undeployApplication"/>



079 <arg value="${application.name}"/>



080 </exec>



081 </target>



082



083 <target name="redeployApplication" >



084 <exec dir="." executable="${wsadmin}">



085 <arg value="-conntype"/>



086 <arg value="SOAP"/>



087 <arg value="-lang"/>



088 <arg value="jython"/>



089 <arg value="-host"/>



090 <arg value="${host}"/>



091 <arg value="-port" />



092 <arg value="${port}"/>



093 <arg value="-javaoption" />



094 <arg value="-Dwdm.http.host=${host}" />



095 <arg value="-f"/>



096 <arg value="${was.python.script}"/>



097 <arg value="redeployApplication"/>



098 <arg value="${application.ear}"/>



099 <arg value="${application.cell}"/>



100 <arg value="${application.cluster}"/>



101 <arg value="${application.name}"/>



102 </exec>



103 </target>



104 <target name="fullRedeploy">



105 <antcall target="clusterStopt"/>



106 <exec dir="." executable="./waitForState.sh">



107 <arg value="Cluster State: websphere.cluster.stopped"/>



108 <arg value="${host}"/>



109 <arg value="${port}"/>



110 <arg value="${application.cell}"/>



111 <arg value="${application.cluster}"/>



112 </exec>



113 <antcall target="undeployApplication"/>



114 <antcall target="redeployApplication"/>



115 <antcall target="clusterStart"/>



116 <exec dir="." executable="./waitForState.sh">



117 <arg value="Cluster State: websphere.cluster.running"/>



118 <arg value="${host}"/>



119 <arg value="${port}"/>



120 <arg value="${application.cell}"/>



121 <arg value="${application.cluster}"/>



122 </exec>



123 </target>



124 </project>





Most interesting last part then we stop cluster, wait till it shut downs completely, undeploy application, redeploy application, start cluster and wait will it starts successfully.



We use jython as wsadmin programming language.

Source code for wsIntegration.py



view sourceprint?

01 import sys



02 def clusterStop(cell, cluster):



03 cluster = AdminControl.completeObjectName('cell='+cell+',type=Cluster,name='+cluster+',*')



04 print "Stop Cluster : %s" % ( repr(cluster) )



05 AdminControl.invoke(cluster, 'stop')



06 def clusterStart(cell, cluster):



07 cluster = AdminControl.completeObjectName('cell='+cell+',type=Cluster,name='+cluster+',*')



08 print "Start Cluster : %s" % ( repr(cluster) )



09 AdminControl.invoke(cluster, 'start')



10 def clusterState(cell, cluster):



11 cluster = AdminControl.completeObjectName('cell='+cell+',type=Cluster,name='+cluster+',*')



12 state = AdminControl.getAttribute(cluster, 'state')



13 print "Cluster State: %s" %(state)



14 def appState(app):



15 state = AdminControl.completeObjectName('type=Application,name='+app+',*')



16 print "App State: %s" %(state)



17 def undeployApplication(appName):



18 AdminApp.uninstall( appName )



19 AdminConfig.save()



20 def redeployApplication(pathToFile, cell, cluster, appName):



21 print "installApplicationOnServer: fileName=%s appName=%s Cell=%s Cluster=%s" % ( pathToFile, appName, cell, cluster )



22 AdminApp.install(pathToFile,'[-nopreCompileJSPs -distributeApp -nouseMetaDataFromBinary -nodeployejb -appname "'+appName+'" -createMBeansForResources -noreloadEnabled -nodeployws -MapModulesToServers [["WEB_APP_NAME" WEB_APP_NAME.war,WEB-INF/web.xml WebSphere:cell='+cell+',cluster='+cluster+' ]] -MapWebModToVH [["WEB_APP_NAME" WEB_APP_NAME.war,WEB-INF/web.xml shared_host ]] -verbose]')



23 AdminConfig.save()



24 """modify classloader model for application"""



25 deploymentID = AdminConfig.getid('/Deployment:'+appName+'/')



26 deploymentObject = AdminConfig.showAttribute(deploymentID, 'deployedObject')



27 classldr = AdminConfig.showAttribute(deploymentObject, 'classloader')



28 AdminConfig.modify(classldr, [['mode', 'PARENT_LAST']])



29 """Modify WAR class loader model"""



30 AdminConfig.show(deploymentObject, 'warClassLoaderPolicy')



31 AdminConfig.modify(deploymentObject, [['warClassLoaderPolicy', 'SINGLE']])



32 AdminConfig.save()



33 """-----------------------------------------------------------



34 Phyton script to interface with WAS Admin/Management Tools



35 -----------------------------------------------------------"""



36 if len(sys.argv) < 1:



37 print "wasAdminIntegration.py : need parameters : functionName <ARGS>"



38 sys.exit(0)



39 if(sys.argv[0] == 'clusterStop'):



40 clusterStop(sys.argv[1], sys.argv[2])



41 if(sys.argv[0] == 'clusterStart'):



42 clusterStart(sys.argv[1], sys.argv[2])



43 if(sys.argv[0] == 'clusterState'):



44 clusterState(sys.argv[1], sys.argv[2])



45 if(sys.argv[0] == 'undeployApplication'):



46 undeployApplication(sys.argv[1])



47 if(sys.argv[0] == 'appState'):



48 appState(sys.argv[1])



49 if(sys.argv[0] == 'redeployApplication'):



50 redeployApplication(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])





In this code, replace "WEB_APP_NAME" with real application name or change the script so it can be passed with the rest of parameters.



For waiting part of the scripts, we will user same jython script in conjuction with bash.

Source for waitForState.sh:



view sourceprint?

01 #!/bin/bash



02 binDir=`dirname "$0"`



03 REQURED_STATE=$1



04 WAS_HOST=$2



05 WAS_PORT=$3



06 WAS_CELL=$4



07 WAS_CLUSTER=$5



08 if [ ! -n "$REQURED_STATE" ]

[ ! -n "$WAS_HOST" ]

[ ! -n "$WAS_PORT" ]

[ ! -n "$WAS_CELL" ]

[ ! -n "$WAS_CLUSTER" ];



09 then



10 echo "Usage: waitForState.sh <STATE> <HOST> <PORT> <CELL> <CLUSTER>"



11 exit 1



12 fi



13 CURR_STATE=""



14 echo "CURR_STATE: $CURR_STATE"



15 while [[ "$CURR_STATE" != "$REQURED_STATE" ]]



16 do



17 sleep 20



18 CURR_STATE="$($binDir/wsadmin.sh -conntype SOAP -lang jython -host $WAS_HOST -port $WAS_PORT -f ./wsAdminIntegration.py clusterState $WAS_CELL $WAS_CLUSTER
grep State:)"



19 echo "Current State: $CURR_STATE"



20 done


So, now we can use all these code in conjunction just by calling

view sourceprint?

1 ant fullRedeploy

Output log should be monitored for Websphere errors. If it return "Result: 99" for every operation, then everything is fine, otherwise, something went wrong.

1 comment:

  1. Nice post - is there some way we can chat via - e-mail?

    ReplyDelete