Using WLST Offline
by Philip Aston
01/17/2005
Abstract
The WebLogic Scripting Tool (WLST) is a BEA tool for scripting the configuration of BEA WebLogic domains. This article contains tips and tricks that are useful in applying WLST to the preparation of production-quality WebLogic domains. It also suggests techniques for writing clearer WLST scripts and describes how to create JMS distributed queues from a concise specification. Example source code is also provided.
Introduction
The WebLogic Scripting Tool is a BEA tool for scripting the configuration of BEA WebLogic domains. WLST is currently available as a developer release for WebLogic 8.1 on the BEA dev2dev web site. BEA plans to include WLST as part of the next major release of WebLogic Server. Please note that neither the dev2dev release of WLST nor the code included with this paper is covered by BEA Support.
I've helped build a number of custom configuration systems over the years. In spite of its developer pre-release status, WLST is more suited to WebLogic configuration than other options available to date. Before WLST, WebLogic users typically would apply a combination of Apache Ant, shell scripts, XSLT, the WebLogic Configuration Wizard, and the tool most akin to WLST, Paco Gomez's
wlshell. I now recommend WLST as the tool of choice to my customers.
Why is WLST so much better than
wlshell? The secret of its power is its scripting language,
Jython, an implementation of the popular Python language in Java.
WLST is available in two flavors:
- WSLT Online: can be used to query and modify a running WebLogic Server domain.
- WLST Offline: can be used to build WebLogic domains without requiring a running WebLogic Server domain.
This article focuses solely on using WLST Offline. When combined with a source control system, WLST Offline scripts are a great way to record and manage configuration. Much could be said about using WLST Online as a multitool for slicing and dicing live WebLogic domains. I'll leave that for others to cover. However, much of my advice is equally applicable to WLST Online, and I'll refer to WLST Offline simply as WLST from now on.
The pre-release of WLST can be forgiven for having a few rough edges and, dare I say it, one or two bugs. This paper contains tips and tricks I've found useful in overcoming these problems and also how to apply the power of Jython and WLST to prepare production-quality WebLogic domains. This article does not attempt to teach the reader Jython or WLST. Jython is easy to learn for Java or shell script programmers (although they might use it in very different styles). Similarly, this is not a tutorial for newcomers to WLST. To get the greatest benefit from this material, you'll need to familiarize yourself with Jython and WLST.
Example source code
This article is accompanied by example source code that illustrates many of the techniques discussed below. The code has been tested with the pre-release of WLST Offline and WebLogic Server 8.1. I expect it will need minor updates to function with the final version of WLST.
If you want to run the example code, be sure you have the latest version of WLST Offline from dev2dev. If you experience errors such as
Error: cd() failed, you're using an older version.
Tip: Accessing WLST from Other Modules
WLST scripts interact with the WLST engine through several functions and variables, for example,
assign(),
cmo, and
readTemplate(). These are implicitly made available to a script invoked though WLST, that is, they are automatically present in the script's global namespace. This is convenient for scripts as they do not need to explicitly import the objects.
Unfortunately, WLST does not allow submodules to import these objects. This hinders our ability to factor scripts appropriately into modules. A simple but ugly solution would be to pass these objects as parameters to every function that required them.
Here's a better solution. Define a method in a
wlstutility package that reads the required objects from a dictionary and stores it in the
wlstutility global namespace. Each of our top-level scripts begins with:
import wlstutility
wlstutility.initialize(globals())
The initialize method is defined in the
wlstutility package's
__init__.py file.
# __all__ defines the public names that the package
# exports.
__all__ = []
def initialize(topLevelNamespace):
for f in ("assign", "cd", "create", "ls",
"readTemplate", "set", "writeDomain"):
globals()[f] = topLevelNamespace[f]
__all__.append(f)
global _topLevelNamespace
_topLevelNamespace = topLevelNamespace
def getCMO():
return _topLevelNamespace["cmo"]
Then, in any other module, import the required objects from
wlstutility:
from wlstutility import assign, cd, ls, set, getCMO
Note that access to the WLST
cmo variable has been replaced with a
getCMO() function, which is better form.
The ability to use WLST from other modules will be added in a future release of WLST. A
writeIniFile() method will be added that creates a Python module containing the WLST defined functions and variables.
Python Power: MBean Constructors
Standard WLST style involves repetitive calls to
MBeans:
cd("/")
jmsTemplate = create("myTemplate", "JMSTemplate")
jmsTemplate.redeliveryDelayOverride = 100
jmsTemplate.redeliveryLimit = 3
# ...
We also have to make sure we use the correct type name in the argument to
create. Wouldn't it be nicer if we had a constructor for each type of MBean? It could be something like:
from wlstutility.constructors import JMSTemplate
cd("/")
jmsTemplate = JMSTemplate("myTemplate",
redeliveryDelayOverride = 100,
redeliveryLimit = 3)
This certainly looks prettier to me. You might need to configure something more complex such as a
JDBCConnectionPool before you appreciate the full benefits. MBean constructors have the additional advantage of allowing sets of parameters to be passed around using dictionaries:
myRedeliveryPolicy = { "redeliveryDelayOverride" : 100,
"redeliveryLimit" : 3 }
cd("/")
jmsTemplate = JMSTemplate("myTemplate",
bytesMaximum = 1000000,
**myRedeliveryPolicy)
Here's the code for creating these MBean constructors:
# wlstutility/constructors.py
import wlstutility
__all__ = []
for mbeanType in (
"Application", "Cluster", "Domain", "DomainLogFilter",
"EJBComponent", "EmbeddedLDAP", "ExecuteQueue",
"JDBCConnectionPool", "JDBCDataSource",
"JDBCTxDataSource", "JMSConnectionFactory",
"JMSDistributedQueue", "JMSDistributedQueueMember",
"JMSDistributedTopic", "JMSDistributedTopicMember",
"JMSFileStore", "JMSQueue", "JMSServer", "JMSTemplate",
"JMSTopic", "JTA", "Log", "Machine", "MailSession",
"SecurityConfiguration", "Server", "StartupClass",
"SSL", "UnixMachine", "User", "WebAppComponent",
"WebServer", ):
def mbeanFactory(name, _type=mbeanType,
existingMBean = None, **kwds):
logMBeanCreation(name, _type, kwds)
if existingMBean:
mbean = existingMBean
existingMBean.name = name
else:
mbean = wlstutility.create(name, _type)
for key, value in kwds.items():
setattr(mbean, key, value)
return mbean
globals()[mbeanType] = mbeanFactory
__all__.append(mbeanType)
The code iterates through a number of known MBean types. For each type it creates a new function
mbeanFactory, which carries out the business of creating an instance of the correct type and applying any keyword arguments by invoking the appropriate set method. The function is then inserted into the module's global namespace and appended to the list of names that the module exports (
__all__). The new functions can be imported from other modules as shown in the examples above.
What does the
existingMBean argument do? It allows you to use the "constructors" with existing MBeans. For example:
from wlstutility import getCMO
from wlstutility.constructors import ExecuteQueue
# ...
cd("/")
cd("Servers/%s/ExecuteQueue/weblogic.kernel.Default" % myServerName)
defaultQueue = ExecuteQueue("weblogic.kernel.Default",
existingMBean = getCMO(),
threadCount = 15)
WLST Idiosyncrasy: The Template Must Contain At Least One Server
When using WLST Offline you start with a Configuration Wizard Domain Template. WLST Offline expects the domain template to contain at least one server entry. It treats the first server as the administration server for the domain. We want the flexibility to define as many or as few servers as we like, and we don't want to end up with an additional server inherited from the template.
To work around this, we define a single server in our domain template. This will become the administration server. We then change its name appropriately using the existingMBean support in the utility constructors.
from wlstutility import getCMO
from wlstutility.constructors import Server
# ...
# Our domain template has a blank server called "admin"
cd("/")
cd("Servers/admin")
# The following statement uses the existingMBean argument to alter the
# existing MBean, rather than defining a new one.
administrationServer = Server(
domainName,
"MyAdministrationServer", # Rename
existingMBean = getCMO(),
administrationPort = 7001,
# ...
)
Tip: You've Got an Object Model—Use It!
WLST works against the JMX object model. You rarely have to keep your own model of the configuration; just pass MBeans around instead. Here's an example of configuring a JDBC
ConnectionPool and
DataSource.
# (server MBean created above).
cd("/")
pool = JDBCConnectionPool("Pool_%s" % server.name,
URL = url,
**nonXAPoolOptions)
dataSource = JDBCDataSource("DataSource_%s" % server.name,
poolName = pool.name,
# ...
)
Note how we refer to the
name attributes of other MBeans in the constructor calls.
WLST Idiosyncrasy: No Script Parameters
It's useful to be able to pass command-line parameters to the script. This isn't possible with WLST as it treats multiple arguments as multiple scripts. It would be better if it just treated the first argument as a script and passed the remaining arguments in
sys.argv. Also, it would be more Pythonic if the
__name__ of the top-level module was
__main__.
Unfortunately there's no clean work around for these problems. To customize the behavior of scripts, I use a properties file with a name that's hard-coded in the script. The sample code provides a
loadProperties method that wraps up the loading of these properties into a Python dictionary.
This will be addressed in a future release of WLST. The first command-line parameter will be treated as a script name, and the remaining arguments will be passed to the script.
WLST Idiosyncrasy:
writeDomain Renames the Domain
When
writeDomain() is called, the names for
Domain,
JTA,
SecurityConfiguration, and
EmbeddedLDAP MBeans are ignored. The name of the output directory is used instead.
I usually want the names of these MBeans to match the domain name, so I pass the domain name as the directory parameter to
writeDomain() and rename the directory afterward if necessary.
This will be addressed in a future dev2dev release of WLST. The domain name will be able to be set separately from the domain directory name.
Python Power: Dealing with JMS Distributed Destinations
Let's consider something a little more complex. Suppose we have a number of application JMS queues that need to be deployed to a cluster as WebLogic distributed destinations. This requires configuring several types of MBean for each application queue:
- A
JMSQueuetargeted to each server in the cluster - A
JMSDistributedQueuetargeted to the cluster - A
JMSDistributedDestinationMemberassociating eachJMSQueuewith theJMSDistributedQueue
We'll also need to be able to create similar MBeans for distributed topics and wire up the references to error destinations.
In the following code, the configuration of an individual queue or topic is wrapped in a
DestinationFactory. A
DestinationFactory knows how to create the appropriate MBeans for the application code. Two additional classes,
QueueFactory and
TopicFactory, are
DestinationFactory subclasses that specialize its behavior appropriately. This is how
DestinationFactories are used:
destinationFactories = (
QueueFactory("AlphaQueue",)
QueueFactory("ErrorQueue", distributed = 0),
QueueFactory("BetaQueue", errorDestination = "ErrorQueue"),
TopicFactory("MyTopic"))
for destinationFactory in destinationFactories:
destinationFactory.createDistributedDestination(cluster)
for server in managedServers:
cd("/")
fileStore = JMSFileStore("JMSFileStore_%s" % server.name,
directory = "jmsfilestore")
jmsServer = JMSServer("JMSServer_%s" % server.name,
store = fileStore)
assign("JMSServer", jmsServer.name, "Target", server.name)
for destinationFactory in destinationFactories:
destinationFactory.createDestinationForServer(
server, jmsServer)
I don't have space to discuss the
DestinationFactory implementation here, but you should be able to understand how they function from the example.
Tip: Post-Processing the Output
WLST only understands a specific set of tokens in template files, for example
@BEAHOME. It replaces these tokens with environment-specific values based on the
stringsubs.xml descriptor file in the template. Your script templates most likely will have additional parameters that do not belong to the set that WLST sets. I post-process the generated files to set these parameters appropriately.
Additionally, there are occasional MBean/WLST bugs that can be worked around by post-processing. Here are a couple I encountered:
Setting the
ApplicationMBean
twoPhase attribute
WLST has a defect such that it considers the default value of
twoPhase for an
ApplicationMBean to be
true. The default value is actually
false. As WLST only generates configuration for non-default values, it is impossible to set
twoPhase to be
true.
To work around this, set
twoPhase to
false so that an attribute is written out, and post-process
config.xml to change all instances of
TwoPhase='false' to
TwoPhase='true'.
Support for the full Oracle JDBC URL syntax
WLST does not accept Oracle URLs of the form:
jdbc:oracle:thin:@//host:1234/SERVICE.myco.com, that is, where an Oracle service name is specified rather than a SID.
To work around this, generate a placeholder URL and post-process
config.xml to insert the real URLs.
Conclusion
This article looks at several aspects of WLST. It shows a number of WLST idiosyncrasies, provides tips for effectively using WLST, and notes how to take advantage of the Python underpinnings. Jython can be used for many other purposes, for example, packaging up the resulting domain into individual tar files for each server that can be more easily copied to the target environment. Fortunately, Jython places both Java and Python libraries at your fingertips, so this is easy to do.
I hope I've encouraged you to increase the level of automated scripting you use to configure your WebLogic domains.
Acknowledgements
Many thanks to Matthew Slingsby and to Larry Du and the WLST team for their input.
References
Download
Philip Aston is a technical manager for BEA Services. He regularly provides consultancy to BEA customers, specializing in WebLogic Server.