Administering WebSphere Using JMX

Although administering WebSphere is best done using the Wsadmin API (with scripting languages like Jython or Jacl), there may be situations when this is not a good option. One example is when your application is already using Java and calling out to an external program (wsadmin.sh) would make the application run slower or make your integration cumbersome.

If your application is already using Java, naturally, it’s best that you also use Java to administer WebSphere. And the recommended way of doing this is through the use of the JMX API for Websphere MBeans. Below is an example illustrating how to use Websphere’s JMX API to retrieve memory information of an application server JVM. The code below is composed of two classes (WASAdminClient.java,  JavaVirtualMachine.java) and a property file (soap_client.properties).

The key to the use of Websphere’s JMX API is to obtain a JMX client connection to your WebSphere’s Application Server and then use this connection to access WebSphere’s JMX MBean Interfaces.

The first step in the process is to establish a JMX client connection. This is the goal of the WASAdminClient class. This is achieved by obtaining the AdminClientFactory.createAdminClient(Properties props) object. The Properties object passed to the method contains the credentials and trustore information required to connect to your Websphere Application Server. The credentials are obtained from the soap_client.properties file while the trustore information is obtained from the files generated by Websphere.

WASAdminClient.java would have been simpler if security is not enabled. If security is not enabled in Websphere, credentials and trustore information won’t be needed to obtain the AdminClient object. However, in a real production setup, this is rarely done so I’d like to also show this process. This is probably the most crucial information you would want to hear. Using WebSphere’s JMX MBeans Interface is straightforward, however, getting the WAS AdminClient object with security enabled is what stumped a lot of people. As you can see in the code below, obtaining the adminclient is not as simple as passing the username and password. It also requires the ssl trustStore and keyStore files from a WebSphere installation. The other thing is that you just can’t get these files out of nowhere. They need to be validly generated from a WebSphere installation. Note: The WebSphere installation doesn’t need to be the server where you want to connect.

The WASAdminClient class has two options to obtain the username and password information. These can be obtained from the soap_client.properties file or from Websphere’s CONNECTOR_SOAP_CONFIG file.

Once you have an AdminClient object connection, you can now use this object to access WebSphere’s JMX MBean Interfaces. The JavaVirtualMachine class below demonstrates how to access some memory information of an application server like heap size, maximum memory, etc.

I have also included below a JUnit test case for the JavaVirtualMachine class. Several test cases are not implemented. I have only provided a couple just to show examples on how to use the two Java classes.

Alvin Abad


"WASAdminClient.java"
package admin.service;

import java.util.Properties;
import java.util.ResourceBundle;

import com.ibm.websphere.management.AdminClient;
import com.ibm.websphere.management.AdminClientFactory;
import com.ibm.websphere.management.exception.ConnectorException;

/**
 * Websphere JMX AdminClient Service
 * @author Alvin Abad
 *
 */
public class WASAdminClient {
	private String hostname = "localhost";    // default
	private String port = "8879";             // default

	private String username;
	private String password;
	private String connector_security_enabled;
	private String connector_soap_config;
	private String ssl_trustStore;
	private String ssl_keyStore;
	private String ssl_trustStorePassword;
	private String ssl_keyStorePassword;

	private ResourceBundle soapClient;
	private String soap_client_properties;

	private AdminClient adminClient;

	public WASAdminClient() throws ConnectorException {
		soap_client_properties = "soap_client";
	}

	public WASAdminClient(String soap_client_properties) throws ConnectorException {
		this.soap_client_properties = soap_client_properties;
	}

	public AdminClient getAdminClient() {
		return adminClient;
	}

	public AdminClient create() throws ConnectorException {
		getResourceBundle(soap_client_properties);

		Properties props = new Properties();
		props.setProperty(AdminClient.CONNECTOR_TYPE, AdminClient.CONNECTOR_TYPE_SOAP);
		props.setProperty(AdminClient.CONNECTOR_HOST, hostname);
		props.setProperty(AdminClient.CONNECTOR_PORT, port);
		props.setProperty(AdminClient.CACHE_DISABLED, "false");

		if (connector_security_enabled == "false") {
			adminClient = AdminClientFactory.createAdminClient(props);
			return adminClient;
		}

		props.setProperty(AdminClient.CONNECTOR_SECURITY_ENABLED, "true");
		props.setProperty(AdminClient.CONNECTOR_AUTO_ACCEPT_SIGNER, "true");
		props.setProperty("javax.net.ssl.trustStore", ssl_trustStore);
		props.setProperty("javax.net.ssl.keyStore", ssl_keyStore);
		props.setProperty("javax.net.ssl.trustStorePassword", ssl_trustStorePassword);
		props.setProperty("javax.net.ssl.keyStorePassword", ssl_keyStorePassword);

		// Use username and password or soap.client.props file
		if (username == null || password == null) {
			props.setProperty(AdminClient.CONNECTOR_SOAP_CONFIG, connector_soap_config);
		} else {
			props.setProperty(AdminClient.USERNAME, username);
			props.setProperty(AdminClient.PASSWORD, password);
		}

		adminClient = AdminClientFactory.createAdminClient(props);

		return adminClient;
	}

	public ResourceBundle getResourceBundle(String properties) {
		soapClient = ResourceBundle.getBundle(properties);

		hostname = soapClient.getString("hostname");
		port = soapClient.getString("port");
		connector_security_enabled = soapClient.getString("connector_security_enabled");
		ssl_trustStore = soapClient.getString("ssl_trustStore");
		ssl_keyStore = soapClient.getString("ssl_keyStore");
		ssl_trustStorePassword = soapClient.getString("ssl_trustStorePassword");
		ssl_keyStorePassword = soapClient.getString("ssl_keyStorePassword");

		if (soapClient.containsKey("connector_soap_config")) {
		    connector_soap_config = soapClient.getString("connector_soap_config");
		}

		if (soapClient.containsKey("username")) {
		    username = soapClient.getString("username");
		}

		if (soapClient.containsKey("password")) {
			password = soapClient.getString("password");
		}

		if (soapClient.containsKey("hostname")) {
			hostname = soapClient.getString("hostname");
		}

		if (soapClient.containsKey("port")) {
			port = soapClient.getString("port");
		}

		return soapClient;
	}

}

"JavaVirtualMachine.java"

package admin.service;

import java.util.Set;

import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;

import com.ibm.websphere.management.AdminClient;
import com.ibm.websphere.management.exception.ConnectorException;

/**
 * Websphere JMX JavaVirtualMachine Service
 * @author Alvin Abad
 *
 */
public class JavaVirtualMachine {
	private String heapSize;
	private String maxMemory;
	private Integer maxHeapDumpsOnDisk;
	private String freeMemory;

	private ObjectName mBean;
	private Set mBeans;
	private AdminClient adminClient;

	public JavaVirtualMachine() {
	}

	public JavaVirtualMachine(AdminClient adminClient) {
		this.adminClient = adminClient;
	}

	public AdminClient getAdminClient() {
		return adminClient;
	}

	public void setAdminClient(AdminClient adminClient) {
		this.adminClient = adminClient;
	}

	public Set queryMBeans() throws MalformedObjectNameException,
                                         NullPointerException, ConnectorException {
		String query = "WebSphere:type=JVM,*";
		ObjectName queryName = new ObjectName(query);
		mBeans = adminClient.queryNames(queryName, null);

		return mBeans;
	}

	public Set getMBeans() {
		return mBeans;
	}

	public void setMBeans(Set mBeans) {
		this.mBeans = mBeans;
	}

	public ObjectName getMBean() {
		return mBean;
	}

	public void setMBean(ObjectName mBean) {
		this.mBean = mBean;
	}

	public String getHeapSize(ObjectName mBean) throws AttributeNotFoundException,
	                                   InstanceNotFoundException,
	                                   MBeanException, ReflectionException,
	                                   ConnectorException {
		heapSize = (String) adminClient.getAttribute(mBean, "heapSize");
		return heapSize;
	}

	public String getMaxMemory(ObjectName mBean) throws AttributeNotFoundException,
                                        InstanceNotFoundException, MBeanException,
                                        ReflectionException, ConnectorException {
		maxMemory = (String) adminClient.getAttribute(mBean, "maxMemory");
		return maxMemory;
	}

	public Integer getMaxHeapDumpsOnDisk(ObjectName mBean) throws AttributeNotFoundException,
                                       InstanceNotFoundException, MBeanException,
                                       ReflectionException, ConnectorException {
		maxHeapDumpsOnDisk = (Integer) adminClient.getAttribute(mBean, "maxHeapDumpsOnDisk");
		return maxHeapDumpsOnDisk;
	}

	public String getFreeMemory(ObjectName mBean) throws AttributeNotFoundException,
                                        InstanceNotFoundException, MBeanException,
                                        ReflectionException, ConnectorException {
		freeMemory = (String) adminClient.getAttribute(mBean, "freeMemory");
		return freeMemory;
	}

}

"soap_client.properties"

# Optional parameters
username = wasuser
password = secret
hostname = localhost
port = 8879

connector_security_enabled = true
#connector_soap_config = build/soap.client.props
ssl_trustStore = /usr/IBM/WebSphere/profiles/my_dmgr/etc/DummyClientTrustFile.jks
ssl_keyStore = /usr/IBM/WebSphere/profiles/my_dmgr/etc/DummyClientKeyFile.jks

ssl_trustStorePassword = WebAS
ssl_keyStorePassword = WebAS

"JavaVirtualMachineTest.java"

package admin.service;

import static org.junit.Assert.*;

import java.util.Set;

import javax.management.MalformedObjectNameException;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.ibm.websphere.management.AdminClient;
import com.ibm.websphere.management.exception.ConnectorException;

/**
 * JavaVirtualMachine Test Case
 * @author Alvin Abad
 */
public class JavaVirtualMachineTest {
	AdminClient adminClient;
	JavaVirtualMachine jvm;

	@Before
	public void setUp() throws Exception {
		WASAdminClient wasAdminClient = new WASAdminClient();
		adminClient = wasAdminClient.create();
	}

	@After
	public void tearDown() throws Exception {
	}

	@Test
	public void testJavaVirtualMachine() {
		jvm = new JavaVirtualMachine();
		assertNull(jvm.getAdminClient());
	}

	@Test
	public void testJavaVirtualMachineAdminClient() {
		jvm = new JavaVirtualMachine(adminClient);
		assertNotNull(jvm.getAdminClient());
		assertTrue(jvm.getAdminClient() instanceof AdminClient);
	}

	@Test
	public void testGetAdminClient() {
		jvm = new JavaVirtualMachine(adminClient);
		assertEquals(jvm.getAdminClient(), adminClient);
	}

	@Test
	public void testSetAdminClient() {
		jvm = new JavaVirtualMachine();
		jvm.setAdminClient(adminClient);
		assertEquals(jvm.getAdminClient(), adminClient);
	}

	@Test
	public void testQueryMBeans() throws MalformedObjectNameException,
                                   NullPointerException, ConnectorException {
		jvm = new JavaVirtualMachine();
		Set mBeans = jvm.queryMBeans();
		assertTrue(mBeans.size() > 0);
	}

	@Test
	public void testGetMBeans() {
		fail("Not yet implemented");
	}

	@Test
	public void testSetMBeans() {
		fail("Not yet implemented");
	}

	@Test
	public void testGetMBean() {
		fail("Not yet implemented");
	}

	@Test
	public void testSetMBean() {
		fail("Not yet implemented");
	}

	@Test
	public void testGetHeapSize() {
		fail("Not yet implemented");
	}

	@Test
	public void testGetMaxMemory() {
		fail("Not yet implemented");
	}

	@Test
	public void testGetMaxHeapDumpsOnDisk() {
		fail("Not yet implemented");
	}

	@Test
	public void testGetFreeMemory() {
		fail("Not yet implemented");
	}

}

Advertisements

Unit Testing Wsadmin Jython Scripts

You can use the unittest module from Python to write unit testing for Websphere’s wsadmin Jython scripts. As of WASv61, this module comes shipped with the product so there is no need to refer externally to your CPython library.

To illustrate, let’s say you want to create a unittest for the Cell.py module below:

“Cell.py”

class Cell:
    def __init__(self, AdminControl=None):
        self.AdminControl = AdminControl

    def getName(self):
        return self.AdminControl.getCell()

Here’s a simple class that implements a method to retrieve the name of your Websphere cell.

To write a test case class, you simply create a class that extends unittest.TestCase. Below is an example.

“CellTest.py”

import unittest
from Cell import Cell

class CellTest(unittest.TestCase):
    def setUp(self):
        self.cell = Cell(AdminControl=AdminControl)

    def tearDown(self):
        pass

    def testGetName1(self):
        self.assertNotEqual(None, self.cell.getName())

    def testGetName2(self):
        id = AdminConfig.list('Cell')
        name = AdminConfig.showAttribute(id, 'name')
        self.assertEqual(name, self.cell.getName())

    def testGetName3(self):
        c = Cell()
        self.assertRaises(AttributeError, c.getName)

if __name__ == '__main__' or __name__ == 'main':
    #unittest.main() # not supported by wsadmin jython

    # alternative way of running this test
    suite = unittest.TestLoader().loadTestsFromTestCase(CellTest)
    unittest.TextTestRunner().run(suite)

    # other ways of running this test
    #suite = unittest.TestSuite()
    #suite.addTest(unittest.makeSuite(CellTest))
    #unittest.TextTestRunner().run(suite)

You will notice that I have commented out the line calling unittest.main(). This method call (as of WASv61) does not work with WebSphere’s wsadmin Jython. Fortunately, there are other ways to run a test case, as shown in the code above.

Also, you will notice that there is this odd __name__ == ‘main’ condition. In wsadmin Jython, this is what is being used and not ‘__main__’. For some reason, IBM chose not to use the standard.

Even though Websphere doesn’t support ‘__main__’, I still make it a habit of including it in my test cases for portability reasons. I wanted to have the flexibility of running my test cases using the standard CPython or Jython interpreter, if I need to, like using mock AdminConfig or AdminControl objects. This is also the reason why I chose to inject the AdminControl object into the Cell class instead of importing the module. [1] This easily allows the use of mock Admin* objects.

[1] Importing Websphere Admin* objects is not available by default. You have to set it up. See my other post on how to accomplish this – Websphere administrative objects not accessible from imported Jython modules.

Links to IBM WebSphere Application Server Documentations

In spite of Google’s power, finding information about WebSphere is still a chore. I can’t really blame Google because IBM WebSphere documentation is huge.

Until search results get better or I figure out how to find things faster on the IBM site directly, I am organizing my bookmarks here so that I can immediately refer to documentations I always use.

  1. WebSphere Application Server, Version 6.1 Information Center – Everything should be under here about WebSphere Application Server.
  2. IBM WebSphere Application Server, Release 6 API Specification – Javadoc to all Websphere APIs. This is under the Infocenter documentation but this will save you the trouble of searching where the Javadocs of the API reside.
  3. WebSphere Application Server Release 6.1 Public MBean Interfaces – Javadoc to WebSphere MBeans.
  4. WebSphere Extended Deployment (XD) Release 6.1 Public JMX MBean Interfaces
  5. Server Configuration Interfaces
  6. WebSphere Configuration Documentation – Documentation to WebSphere’s XML files
  7. WSADMIN Scripting

WebSphere Administrative Objects Not Accessible from Imported Jython Modules

In Websphere Jython (as of WAS6.1), the administrative objects are not automatically accessible from imported modules, unlike when you call them from your main script. It is also not possible to import them either since they are not defined in sys.modules.

“myModule.py”

print AdminConfig.types()

For example, the code above will break if this Jython module is imported from the main script or another module. If this is executed as the main script it would work right off the bat even without importing anything.

To fix this problem, you need to add the administrative objects to the “sys.modules” dictionary in the main script so that they will be visible to all imported modules. Once the administrative objects are available in “sys.modules”, your imported modules can then call the import command for those object to access them.

For example, in your main script, you need to construct the lines shown below before you can import any modules that will require access to the WebSphere administrative objects:

“myMain.py”

    import sys
    sys.modules['AdminConfig'] = AdminConfig
    sys.modules['AdminControl'] = AdminControl
    sys.modules['AdminApp'] = AdminApp
    sys.modules['AdminTask'] = AdminTask
    sys.modules['Help'] = Help

    # now you can import your module
    import myModule

Your imported modules can then call the import command to access these objects. The module below shows import commands for each WebSphere Administrative object.

“myModule.py”

    try:
        import AdminConfig
        import AdminControl
        import AdminApp
        import AdminTask
        import Help
    except:
        pass

    # this works now even if this module is imported
    print AdminConfig.types()

The try-except clause above is used to prevent the module from failing if you tried to run this as your main script. If you tried to run this in your main wsadmin script and the sys.modules were not set, this would simply ignore the exceptions and should be able to proceed to run any administrative commands directly.

I hope someday IBM will fix this problem by simply adding the administrative objects to the sys.modules by default so that we won’t need to do this ourselves.

Websphere’s Implementation of Jython (wsadmin) Broke __name__

Jython in IBM Websphere does not implement the global variable “__name__” the same way CPython or the official Jython does. Instead of setting it to “__main__” (if the module is not imported), it sets it to “main”.

For example, if you have this code to check if the script is being imported or not,

if __name__ == '__main__':
    run_method()

you have to write it differently with WebSphere Jython like this:

if __name__ == 'main':
    run_method()

It’s not a big deal, you’d say. Just check for both values if you are concerned about portability:

if __name__ == '__main__' or __name__ == 'main':
    run_method()

Unfortunately, that is not the only problem. There are libraries out there that use __main__. One example is unittest.main(). This method doesn’t work with Websphere Jython (WASv61). IBM ships this unittest module with Websphere v6.1, however, the unittest.main() method doesn’t work. Actually, the primary culprit is that Websphere’s Jython doesn’t implement the __main__ module.

As a workaround to this problem, don’t use unittest.main(). Instead, use the methods TestSuite() and TextTestRunner(). For example, instead of the usual way of writing your test class like this:

if __name__ == '__main__':
    unittest.main()

write it this way with Websphere Jython:

if __name__ == '__main__' or __name__ == 'main':
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(TestMyJythonClass))
    unittest.TextTestRunner().run(suite)

Websphere’s implementation of Jython in wsadmin broke sys.argv

In CPython and JPython, the first index of sys.argv points to the name of the Python script, while the rest of the array elements refer to the arguments. However, in Websphere’s Jython, the first index already points to the first argument of the script. This means that none of the elements of sys.argv has the information about the name of the script it is running.

I think this is a serious drawback. I have some functionalities where I need access to the name of the script. For example, I need to know the directory where that script is located. This will allow me to navigate relatively to other files where the script is located like additional path to libraries (pythonpath ) or configuration files and be able to access them at runtime.

I have yet to figure out a trivial way to access the script name at runtime in wsadmin. One workaround I am planning is to write a wrapper script to grab the script name and then pass that information to wsadmin’s Jython script as the first parameter, so that would become sys.argv[0]. Or perhaps as an option parameter with the value telling it that is the script name.

However, the problem with a wrapper script is that your wsadmin Jython scripts won’t be portable since it would be dependent on the wrapper to run.

If anyone out there has ideas other than a wrapper script, I’d love to hear it.