Who’s On Phirst

Official blog of Phurnace Software.

Tag >> Scripts

Posted by: Wesley Willard on

While Robert Reeves is correct in his assessment that scripting can be evil, especially in cases where scalable and durable automation is needed (like deployments of apps),there are occasions where a script can definitely serve as a useful problem-solving tool. A script can act as another way to view an application's data, allowing the developer to verify the state of the data independently. Creating a script does not generally require the same amount of time to code/compile/edit as a regular programming language, thus can be more quickly developed in an iterative fashion. Also, since scripting languages are not used as often to create commercial applications as are programming languages like Java, a script can be used to prototype some experimental functionality, without danger that the prototype will escape the building.

I have resisted taking the time to learn much about the many scripting languages which have been created in the last few years, but Groovy is one which, as a Java developer, has really caught my eye. It is built on Java, but provides additional features which have been inspired by other languages such as Ruby and Objective-C.

I find Groovy to be my current scripting language of choice for a few simple reasons:

  • Java-like syntax is extremely easy to pick up
  • Ability to leverage the many powerful Java APIs
  • Useful built-in features like Categories, which enable dynamic extension of any class

In the following code, I’ll demonstrate some of these things. I will be making a remote connection to a JBoss Application Server in order to retrieve MBean attributes, as well as invoke operations on the MBean.

First, I will import some classes, and make a connection to the server:


package com.groovy

import javax.management.ObjectName
import javax.management.MBeanServerConnection
import javax.naming.InitialContext
import org.apache.xpath.XPathAPI
import groovy.xml.dom.DOMCategory
import org.w3c.dom.Element

def
password = ""
def initialContextFactory = "org.jboss.security.jndi.
JndiLoginInitialContextFactory"
def JMX_INVOKER_RMI_ADAPTOR = "jmx/invoker/RMIAdaptor"
def port = 1099
def host = "localhost"
def username = ""
def password = ""
def initialContextFactory = "org.jboss.security.jndi.
JndiLoginInitialContextFactory"

//
// Connect to JBoss
//
Properties props = new Properties(System.getProperties())
props.put("java.naming.factory.initial", initialContextFactory)
props.put("java.naming.provider.url", host + ":" + port)
props.put("java.naming.security.principal", username)
props.put("java.naming.security.credentials", password)
def ctx = new InitialContext(props)
Object obj = ctx.lookup(JMX_INVOKER_RMI_ADAPTOR)
def mbsc = (MBeanServerConnection) obj
ctx.close()

Once I have made my connection, I will then use it to invoke an operation on a JBoss MBean.


//
// Create a class to be used as a String Category
// StringCategory extends the String class with
// a new method that splits a String at the newline delimiter
//
class MyStringCategory {
public static List splitOnNewLine(String string) {
return string.tokenize("\n")
}
}
//
// Invoke the operation listDeployedURLs on the Groovy MBean
// Use the category defined above to split up the return string
//
use (MyStringCategory) {
def deploymentScanner =
new GroovyMBean(mbsc, "jboss.deployment:flavor=URL,
type=DeploymentScanner")
def deployedURLs = deploymentScanner.listDeployedURLs()
def deployedList = deployedURLs.splitOnNewLine()
deployedList.each( { println it } )
}

This snippet illustrates a couple of interesting Groovy features. The class MyStringCategory is an example of a Groovy Category. A Category in Groovy is a class which defines methods which can extend any specified class. In the example, the method splitOnNewLine is a category method on the Groovy class String, due to the fact that the method is static, and the first argument (or target) is of type String. This method can be called on any instance of a String, and will return a List containing one entry per new line found in the String. To enable the category method to be called, the call must be enclosed in a use statement, which references the name of the category.

Also notice that I have created an instance of a GroovyMBean, constructing it using the server connection and the name of the JBoss MBean. The GroovyMBean is a built-in Groovy class, which allows an MBean to be used as a regular Groovy object. I use this object to call the listDeployedURLs() method on the JBoss MBean, which returns a String representation of the currently deployed URLs on the server. I then use my splitOnNewLine() category method to create a List, which can then be iterated.

The next snippet of code illustrates how easy it is to make use of an existing Java API within Groovy.

 


//
// Create GroovyMBean to retrieve the JBoss Mail Service
// configuration information, which is returned by JBoss
// as an XML Element object
//
def mailBean = new GroovyMBean(mbsc, "jboss:service=Mail")
def configurationXML = mailBean.Configuration

//
// Use the Apache XPath API to select the nodes,
// and iterate through them with a closure
//
def nodeList = XPathAPI.selectNodeList(configurationXML,
"//property[@name]")
def printNode = { println it.getAttribute("name") + "=" +
it.getAttribute("value")
nodeList.each(printNode)

After the creation of another GroovyMBean, the Apache XPath API is used to select the nodes in the returned XML Element. The resulting NodeList is then iterated. Using an existing Java API in this fashion is just as simple as importing the appropriate class, and then calling its methods.

The final part of this example uses another category, MyDOMCategory, in conjunction with a built-in Groovy category, DOMCategory, to process the same XML Element. The call to list() is a category method which transforms a NodeList object into a List object. The List object is iterated, and its attributes are retrieved using the Element category method attribute(Element node, String attrName) to retrieve the name and value attributes.

 


//
// The class MyDOMCategory extends the Element class
// to return the value for the specified attribute
//
class MyDOMCategory {
static String attribute(Element node, String attrName) {
return node.attributes[attrName]
}
}

//
// Use Groovy Categories to iterate through the NodeList
//
use (DOMCategory, MyDOMCategory) {
configurationXML."property".list().each(
{ println "MyDOMCategory: " + it.attribute("name") + "="
+ it.attribute("value") } )
}

Hopefully, these short snippets of code illustrate the simplicity that I think makes Groovy a very attractive option for scripting use in the Java development world. I would highly recommend it as a useful addition to your software developer's toolkit.

In ScriptsScripting LanguageGroovy
Comment (0) Read More...


Posted by: Robert Reeves on

In the first part of this two part post, I discussed how scripts are terrible at eliminating errors. In fact, scripts simply replace typos with system errors. A rather poor trade-off, I think.

The second problem with scripts is the burden they place on employees to maintain them and the decrease in productivity they can cause. This maintenance burden and productivity sinkhole are the two areas scripts were supposed to fix!

Frankly, I hold the tools architecture responsible. Scripts conflate the Data with the Mechanism because of the enforced procedural nature of the tools (think wsadmin). Please note that I don't mean to pick on WebSphere, but I just have a *ton* of experience with that application server. I could have just easily singled out WebLogic, OAS, JBoss or Glassfish. The architecture problem is common to them all.

To see an example of conflation, navigate to your WAS 6.1 directory and look at samples\bin\PlantsByWebSphere\install.jacl. You will see the following:

  createJ2CResourceAdapter $nodeName $serverName

That's a good start as both resources are provided at the command line when executing the script. However, the script immediately falls apart on the next directive:

  #--------------------------------------------------------------------

  # Setup security cell

  #--------------------------------------------------------------------

  set secAuthAlias "$cellName/samples"

  set secDescript  "JAAS Alias for WebSphere Samples"

  set secUserID    "samples"

  set secPassword  "$samplepwName"

  createJAASAuthenticationAlias $cellName $secAuthAlias $secDescript $secUserID $secPassword

Don't see it the problem? I don't blame you. The problem is a hardcoded secUserId. This will fail if there is another JAASAuthData that uses the same secUserId as "samples". Now, that's a simple mistake; but it's one that will require a very specific use case to find it. So, we have actually created a maintenance headache for ourselves. This script may save me time in my Dev or QA environments. But, once it gets to Production where they use entirely different usernames for the database, I will have an error I simply cannot afford to have.

But, finding the specific JAASAuthData configuration problem is just the first headache. In my Production environment, imagine that this script fails on the duplicate secUserId value. Well, that's easy to fix, right. Let's just add another argument to pass in when the script is run.

So, I'm only into the second call of my script, and already I'm required to make the script more complicated. Furthermore, since my script is not wrapped in a transaction, I'll have to manually delete the J2CResourceAdapter to completely roll back. Or, at least, I *think* that's all I need to roll back my script's changes. After all, the best thing that can happen when I run a wsadmin script is nothing. Because if I see anything on STDOUT, that's normally a bad thing.

Ideally, I would have my Data described in a simple format that defines the System Resource (or MBean) I am trying to create and its associated attributes. Then, my script would read that data structure and create the MBeans necessary. Finally, at the end, it would generate a report that details what was changed in my environment. And, of course, if any failure's occur, all changes up to the failure are automatically rolled back.

Of course, I would love to see you purchase Phurnace Deliver which can do all of that and then some. However, you can create this yourself. The tools to do so are readily available and most Application Servers provide some sort of API to manage those configuration changes. Just remember to separate the Data from the Mechanism, and you should be much better off.

In Scriptsjava
Comment (0) Read More...


Posted by: Robert Reeves on

Up until the release of Phurnace Deliver last year, there were two choices when it came to Java Application Server management: manual data entry or scripts. Typically, a company starts with manually entering configuration data via a web-based admin console. Then, either organically or via a concerted effort, the company will move to using scripts to manage the Application Server configuration and deploying binaries.

Companies begin this migration for two reasons. The first is to automate mistake prone tasks in order to avoid costly errors. Second, the company seeks to boost efficiency and increase employee and system productivity. While both of these motivators are valid, scripts simply fail to deliver on both of these hopes. In this blog entry, I'm going to discuss how scripting fails to deliver on error elimination. In fact, I will contend that scripts will cause the same, if not more, errors than using a manual approach.

When Exceptions Are the Rule...

Imagine that I was tasked with updating the Server heap size on a WebSphere Application Server; specifically, the heap size is currently 512MB and I need to change it to 1024MB. This is a rather simply task using the WAS Admin Console. However, if it turns out that I have 100 Servers to update, the task begins to become a bit more daunting. I'm certain that, around server 60, I'm going to make a mistake and type in "11024" instead of "1024". A Heap Size of 11 GB is not what Server number 60 needs! Thus, a script sounds like a good choice. And, it is.

Unfortunately, you will never run into this specific case. In reality, the Servers' heap size will all be different based on utilization and hardware requirements. In fact, only 78 of the 100 Servers need to be changed. And, of the 78, 42 need to be 1024MB and 24 need to be 768MB, etc. You can see how reality would make this a very complex task, even for scripts.

When exceptions become the rule, you better have good exception handling. Simply put, error handling in the scripting environments provided by AS vendors are terrible. Error handling is almost non-existent and the execution environments provide sparse responses when an error does occur. In fact, when an error occurs, you cannot be sure it is the script or the environment in which the script is run. Moreover, you are unable to distinguish between a network connectivity error or JNDI name conflict error within your script. Thus, you must rely on the user executing your script to determine the root cause of the error and take corrective action.

If that is the case, then there is really no reason to write a script in the first place. That time would have been better spent on educating the user about the AS change process or (shameless plug) purchased Phurnace Deliver.

"Works on my machine."

I have been working on Java Application Servers since Netscape Application Server 4.0. In all those ensuing years, I have yet to come across a company that had an identical environment for dev, test and production. Each stage differed by topology and naming conventions. For example, I would see 2-way clusters in dev and test and 4-way clusters in production. Moreover, you will find different Cell names, JDBC Urls, Usernames and Passwords in each environment.

And, unless all your environments are managed by a single person with OCD and insomnia, you will encounter Configuration Drift across those environments. These drifts will start in Test or Production. From Test, the drift can be started by a Developer making a change to the Test environment without logging a bug or updating the current script. ("Oh, that error? I forgot to add a new Datasource. Let me add that real quick..."). Thus, when the code moves to Production, you will see the same error caused by a missing Datasource. Drifts can start in Production when IT admins make well intentioned changes to their environments without those changes being pushed down to Test and Dev. Thus, Dev and QA are developing and testing against an invalid environment.

The end result is that you begin to have scripts that only work for specific environments and not a specific release. Thus, the results of your script are little better than a crap shoot when it comes time to update Production. And, since most Application Servers do not keep track of configuration changes internally, determining what is different between two environments is near impossible. If you can't determine the delta, then you can't fix the delta.

Thus, hopes for mistake eliminating and increase productivity become pipe dreams. And, the organization is no better off than with manual deployment than with scripts.

Admittedly, errors are a significant cause of productivity loss; especially errors found in production. However, there are other ways that scripts can cause productivity loss . In my next blog entry, I will discuss how scripts simply move tasks from one area to another in order to give the illusion of productivity enhancements.

In Scriptsjava
Comment (1) Read More...