Wednesday, 12 December 2012

WLST: Simplified Server Control

WLST is a necessary evil for administering WebLogic however sometimes often the API that is exposed appears to have been produced without much regard to ease of usability. Take as an example the code required to connect to a running NodeManager in order to start/stop a Managed Server (without an Admin Server being up). WLST provides a function to connect to a NodeManager (in order to start a server) which has the form:

nmConnect(userConfigFile, userKeyFile, host, port, domainName, domainDir, nmType, verbose)

  • userConfigFile - The path to the user config file. Refer to the storeUserConfig() WLST function for further information.
  • userKeyFile - The path to the user key file. Refer to storeUserConfig() WLST function for further information. 
  • host - The listen address for the NodeManager. This is available in the domain configuration. We simply need to know which Machine definition we should reference. If we know which server we are interested in controlling then we can look up the associated Machine definition.
  • port - the listen port for the NodeManager. Again this is available in the domain configuration and again we can use a reference to a Machine definition.
  • domainName - The name for the domain. Again this is available in the domain configuration.
  • domainDir - The directory for the domain. Ok we do need this one.
  • nmType - The "type" of the NodeManager connection. Typically either "plain" or "ssl". This can be derived from the domain configuration. If a value has not been specified then the default of "ssl" should be assumed. Again we can use the reference to a Machine definition.
  • verbose -  Whether or not the NodeManager should use verbose messages. We will ignore this for our purposes and always assume a value of "false".

Therefore, assuming that we have access to the domain configuration in a given domain directory we should be able to derive many of the required parameters and simplify the nmConnect to be something a bit more user-friendly. We introduce a function nmConnectForServer which

nmConnectForServer(domainDir, serverName)

  • domainDir - The directory containing the domain configuration.
  • serverName - The name of the Managed Server whose Machine definition we want reference to determine our NodeManager connection parameters.

Once we have the WLST function we can flesh out a bit of a script that uses this function and takes appropriate arguments to allow us to control the server and view its status with the nmStart(), nmKill() and nmStatus() built-in WLST functions.

The script can then be invoked

wlst.sh nmServerControl.py start | stop | status

The advantages of this approach is that the domain configuration is used to determine the connection parameters rather then coding these parameters into a script or other configuration file. Should they change the start script will not have to be altered. Another advantage is the script becomes environment agnostic.

The following code defines such a WLST script:

 # -----------------------------------------------------------------------------  
 # Control a WebLogic server with NodeManager  
 #  
 # @arg    1    action        One of "start", "stop" or "status"  
 # @arg    2    domainDir    Path to the domain directory  
 # @arg    3    serverName    The name of the server to start  
 #  
 # -----------------------------------------------------------------------------  
 # Author:        weblogically.blogspot.com  
 # Version:        1.0a  
 # Date:            18/07/2012  
 # Tested on:    11g PS5 11.1.1.5  
 # -----------------------------------------------------------------------------  
 # This WLST script controls servers by connecting to and issuing commands  
 # directly to the NodeManager. As such the NodeManager must be running but  
 # there is no requirement for the AdminServer to be running.  
 #  
 # The builtin WLST function 'nmConnect' requires parameters that are readily  
 # available in the domain configuration. This script provides an alternative  
 # function 'nmConnectForServer' which reads the domain to locate the specific  
 # parameters values for the given domain/server and passes these to nmConnect  
 # thereby significantly reducing the number of parameters that must be  
 # manually passed in to start a server.  
 #   
 # This does function requires that the domain directory to readable.  
 #  
 # This script does not try to set the server start properties so the server  
 # should have been started via the AdminServer at least once beforehand. This  
 # should have created a server start properties file that will be then used.  
 #  
 # This script does require previously stored the security credentials in the  
 # appropriate config and key files (see storeUserConfig)  
 # -----------------------------------------------------------------------------  
 import sys  
 # @function: nmConnectForServer  
 #   
 # Connects to the NodeManager associated with the specified NodeManager  
 #   
 # @param domainDir            The path to the domain directory  
 # @param serverName            The name of the server  
 #  
 def nmConnectForServer(domainDir, serverName):  
   readDomain(domainDir)  
   domainName=get("Name")  
   cd("/Server/" + serverName)  
   machineName = get("Machine").getName()  
   cd ("/Machine/" + machineName + "/NodeManager/" + machineName)  
   nmListenAddress = get("ListenAddress")  
   nmListenPort = get("ListenPort")  
   nmType = get("NMType")  
   if nmType == None:  
     nmType = "SSL"  
   closeDomain()  
   nmConnect(  
     userConfigFile=domainDir + "/nm-userconfig.properties",  
     userKeyFile=domainDir + "/nm-userKey.properties",  
     host=nmListenAddress,  
     port=nmListenPort,  
     domainName=domainName,  
     domainDir=domainDir,  
     nmType=nmType,  
     verbose="false")  
 #  
 # @function: nmStartServer  
 #  
 # Convenience function that connects to NM and asks it to start the server  
 #  
 # @param domainDir            The path to the domain directory  
 # @param serverName            The name of the server  
 #  
 def nmStartServer(domainDir, serverName):  
   nmConnectForServer(domainDir, serverName)  
   nmStart(  
     serverName=serverName,  
     domainDir=domainDir)  
   nmDisconnect()  
 #  
 # @function: nmStopServer  
 #  
 # Convenience function that connects to NM and asks it to stop the server  
 #  
 # @param domainDir            The path to the domain directory  
 # @param serverName            The name of the server  
 #  
 def nmStopServer(domainDir, serverName):  
   nmConnectForServer(domainDir, serverName)  
   nmKill(  
     serverName=serverName)  
   nmDisconnect()  
 #  
 # @function: nmStatusServer  
 #  
 # Convenience function that connects to NM and asks it for the status of the  
 # server  
 #  
 # @param domainDir            The path to the domain directory  
 # @param serverName            The name of the server  
 #  
 def nmStatusServer(domainDir, serverName):  
   nmConnectForServer(domainDir, serverName)  
   nmServerStatus(  
     serverName=serverName)  
   nmDisconnect()  
 #  
 # @function: printUsage  
 #  
 # Print the usage pattern for this script  
 #  
 # @param domainDir            The path to the domain directory  
 # @param serverName            The name of the server  
 #  
 def printUsage():  
   print "Usage: weblogic.WLST " + sys.argv[0] + " start|stop|status <domainDir> <serverName>"  
 # hostname = java.net.InetAddress.getLocalHost().getHostName()  
 # -----------------------------------------------------------------------------  
 # MAIN  
 # -----------------------------------------------------------------------------  
 try:  
   if len(sys.argv) < 4:  
     printUsage()  
     exit("y", 1)  
   else:  
     if "start" == sys.argv[1]:  
       nmStartServer(sys.argv[2], sys.argv[3])  
     elif "stop" == sys.argv[1]:  
       nmStopServer(sys.argv[2], sys.argv[3])  
     elif "status" == sys.argv[1]:  
       nmStatusServer(sys.argv[2], sys.argv[3])  
     else:  
       print "Error: Unknown instruction"  
       printUsage()  
       exit("y", 1)  
 except:  
   print "An error occurred."  
   dumpStack()  
   raise  

Wednesday, 1 August 2012

WLST: Password Prompt

It is quite common to need to get a password from a user in a WLST script. Unfortunately WLST does not have the getpass Python package available to it. But WLST is Jython so can make use of the Java JDK classes! Java 6 introduced the Console class that has a readPassword method which would be perfect.

To get a handle on the Console object we get it from the System object:

console = System.console()

We can then invoke the readPassword method. The readPassword method takes as arguments a format string and number of var args. To simplify things we will use a format string that just outputs the first var arg as a string and then pass the prompt in as the first var arg. Jython will interpret the var args as a list so we initialize a list containing the single value which is the prompt :

passwdCharArr = console.readPassword("%s", [prompt])

The final piece of the puzzle is that the readPassword method returns char[]. We want the password as a string value. The Python String object has a join method that will work for this purpose:

passwd = "".join(passwdCharArr)

And that's it. All of this can be truncated into the following:

passwd = "".join(java.lang.System.console().readPassword("%s", [prompt]))

There may be other methods for getting a password, but this works and assuming WLST is invoked from a JDK which is at version 6 (1.6) or greater should be quite portable and not require any other Python packages.

Monday, 23 April 2012

OSB 11g: Patching OSB on Windows 7 with OPatch

There are a few hoops that need to be jumped through to patch Oracle Service Bus on Windows 7 with the OPatch utility. Note that the paths given in this post are accurate for the system on which this post was based but may be different on other systems and are dependant on where the Oracle software has been installed.

1. Start a Command window with Administrator priviledges

Administrator priviledges are required in order for the OPatch utility to get the appropriate locks on the files that are used by the OPatch utility. To initiate the Command window with Administrative priviledges right-click the icon that you use to start the Command window and select "Run as administrator" from the resulting context menu. This should launch the Command window.


2. Set the ORACLE_HOME environment variable

The ORACLE_HOME environment variable needs to be set within the context of the new Command window. This should be set to the OSB_HOME, typically this


SET ORACLE_HOME=c:\oracle\middleware\Oracle_OSB1



3. Navigate to the directory in which you have unzipped the patch to be applied.

cd PATH_TO_PATCH_DIR


4. Run the OPatch utility to apply the patch.

The OPatch utility can be used to apply the patch with the command:

opatch apply -jdk JDK_HOME -jre JRE_HOME

The OPatch utility is available from the OSB_HOME. As we have already set the ORACLE_HOME environment variable to this location we can use this variable to launch the OPatch script. You need to provide the JDK and JRE location. The exact value for JDK and JRE locations will vary depending on the system and installation locations.

%ORACLE_HOME%\OPatch\opatch apply -jdk c:\oracle\middleware\jdk160_24 -jre c:\oracle\middleware\jdk160_24\jre


5. Confirm that the patch has been applied

The OPatch utility can be used to list any patches that have been applied with the following command:

opatch lsinventory

Again, we can use the ORACLE_HOME environment variable to launch the OPatch utility

%ORACLE_HOME%\OPatch\opatch lsinventory

Possible Errors and Solutions


If you have followed the process above then you should not see these errors. They are included here for completeness.

Unable to lock Central Inventory

OiiolLogger.addFileHandler:Error while adding file handler - C:\Program Files (x
86)\Oracle\Inventory/logs\OPatch2012-04-23_11-11-28-AM.log
java.io.FileNotFoundException: C:\Program Files (x86)\Oracle\Inventory\logs\OPat
ch2012-04-23_11-11-28-AM.log (Access is denied)
Unable to lock Central Inventory.  OPatch will attempt to re-lock.
Do you want to proceed? [y|n]
n
User Responded with: N
Unable to lock Central Inventory.  Stop trying per user-request?
OPatchSession cannot load inventory for the given Oracle Home C:\oracle\middlewa
re\Oracle_OSB1. Possible causes are:
   No read or write permission to ORACLE_HOME/.patch_storage
   Central Inventory is locked by another OUI instance
   No read permission to Central Inventory
   The lock file exists in ORACLE_HOME/.patch_storage
   The Oracle Home does not exist in Central Inventory

ApplySession failed: ApplySession failed to prepare the system. Unable to lock C
entral Inventory.  Stop trying per user-request?
System intact, OPatch will not attempt to restore the system

OPatch failed with error code = 73


Solution: Run the command window with Administrator priviledges.


The Oracle Home is not OUI based home


If the ORACLE_HOME environment variable is not set then attempting to run the OPatch utility will result in the error message:

The Oracle Home is not OUI based home. Please give proper Oracle Home.

Solution: Set the ORACLE_HOME to be the OSB_HOME (Oracle_OSB1)