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 JMSQueue targeted to each server in the cluster
  • A JMSDistributedQueue targeted to the cluster
  • A JMSDistributedDestinationMember associating each JMSQueue with the JMSDistributedQueue

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.