Managing GlassFish server using JRuby
If you are a developer writing an application which is inteneded to run on a GlassFish server, you are likely to do a lot of deployment work on your development/test servers. That's just the kind of work I do. And although I don't need to recreate the entire domains very often, I have to do that occasionally, and the process of configuring the application server domain through the web administration console is a pain, especially when you need to go through several wizards to create all those connection pools and JDBC resources which your application needs, set the JVM start parameters, etc., and you need to set up several domains that way (hey, GlassFish developers, is there some standard way I can do that work during the deployment of the application automatically?). Having done that several times, I decided that writing some tool to do all the work for me makes a lot of sense.
I tried to use shell scripts which called a bunch of asadmin commands to configure the domain and deploy the applications, but that didn't work out quite well. And I had some strange problems with remote application deployment using asadmin, so I had to ssh to the remote server and do the work locally. I guess I could create the necessary infrastructure for asadmin approach to work, but I felt that the result would be quite ugly. And I remebered that I had learnt about another interesting possibility last summer, when I had been helping guys to update the JDK samples for the upcoming release of the JDK, that was JMX. I did play with JMX for some time in the fall, but it never had any useful application in my work. This time I decided to use JMX for some real problem which I had been having for such a long time — application server domain configuration.
There are lots of good things in Java, but there are also some things which one may find frustrating to deal with under certain circumstances. One of such things is code verbosity, which is very thwarting when you need to write some small tool or just find out how the API work. One of the solutions to the code verbosity problem is to use some scripting language which can call Java APIs: there are BeanShell, JRuby, Jython, Groovy, Rhino, and many more other nice scripting languages. I'm watching closely the improvements in JRuby since last summer and I find Ruby adorable, hence the choice of a scripting language for the task was easy for me.
Configuration of the JMX connection on the server
Before we start connecting to the application server, we need to know some basic information: server address, JMX port (8686 by default), administrator user name (‘admin’ by default), and whether the server accepts only secure JMX connections, or not. JMX secure connections are disabled by default in GlassFish developer profile and enabled in cluster profile. However, I suggest you enabling the security for the JMX connector even for the developer domains: thus you'd understand better how the secure connections work and you wouldn't have the problems when you decide to deploy your applications on the server which has the secure connections enabled.
Creating connection pools
There is not much information about GlassFish management using JMX on the internet, at least I had a lot of problems finding the important pieces of information, most of them were obtained by reading the GlassFish sources. There is the AMX Javadoc, but it doesn't help to understand, how one should use the API. I discovered that the best way to understand, how the things work, is to use jirb — the JRuby version of the Interactive Ruby — in a typical session I simply instantiated some Java objects, looked at their method names and tried to call them and see what comes out.
To call the Java code from JRuby script, you need to do
require 'java'
in your script. You can use the same code in jirb. See the ‘Calling Java from JRuby’ page for more information on how to interoperate with Java code. There are also some code samples in JRuby distribution which explain the basic things. Unfortunately, you cannot load jar files in your JRuby code (or in a jirb session) and then use the classes from the library, so to use the AMX API you need to set the classpath before calling jirb:
$ CLASSPATH=/opt/glassfish/lib/javaee.jar:/opt/glassfish/lib/appserv-ext.jar jirb
/opt/glassfish is my path to the installed GlassFish. Now you can require 'java' and follow the rest of this post.
Connecting to the server
To connect to the application server you need to instantiate the com.sun.appserv.management.client.AppserverConnectionSource class with the parameters describing your connection:
conn = AppserverConnectionSource.new(AppserverConnectionSource::PROTOCOL_RMI, host, port, user, password, tls_params, nil)
conn.getJMXConnector(false)
Parameter names are self-describing, with the exception of, maybe, tls_params — it is an instance of the com.sun.appserv.management.client.TLSParams class and it contains the security settings for your connection. If you are using insecure JMX connection on the server, you may pass nil (which is treated as null by JRuby, when you pass it as a parameter to some Java method) in place of tls_params.
If you are using secure JMX connections, you have two basic strategies: you may trust any certificate which the server sends you during the TLS handshake, or you may want to check that you have this certificate (or the certificte of the authority which signed it, if the certificate is not self-signed) in your trust-store. It is possible to implement different authentication strategies (e.g., asking the user, whether she trusts the given certificate), but it is out of the scope of this blog post.
To implement the first (trust any certificate) strategy, you can use the following code:
TLSParams = com.sun.appserv.management.client.TLSParams
TrustAnyTrustManager = com.sun.appserv.management.client.TrustAnyTrustManager
tls_params = TLSParams.new(TrustAnyTrustManager.getInstanceArray, nil)
To implement the second (use trust-store to check the certificate) strategy, you should read the trust-store file and create the respective trust-store manager:
TLSParams = com.sun.appserv.management.client.TLSParams
TrustStoreTrustManager = com.sun.appserv.management.client.TrustStoreTrustManager
X509TrustManager = javax.net.ssl.X509TrustManager
store = java.io.File.new(trust_store_file_name)
store_password = java.lang.String.new(trust_store_password).to_char_array
trust_managers = X509TrustManager[1].new(TrustStoreTrustManager.new(store, store_password))
tls_params = TLSParams.new(trust_managers, nil)
Hopefully, you're able to connect to the server now. To control the domain configuration, we need to obtain the DomainConfig object from the established connection:
domain_root = conn.domain_root
domain_config = domain_root.domain_config
Notice how Java integeration code in JRuby automatically creates the Ruby-style accessors for the getters and setters.
Creating a connection pool
Creation of a connection pool is simple:
pool = domain_config.createJDBCConnectionPoolConfig(pool_name, datasource_class, nil)
where pool_name is an identifier of the connection pool and datasource_class is the name of the Java class used as data source (e.g. oracle.jdbc.pool.OracleDataSource for Oracle database). Having that line executed you can even check in the server admin console that the respective conection pool was created.
Then you need to configure the created connection pool. The first thing you might want to configure is the “Resource Type”:
pool.res_type = 'javax.sql.DataSource' # other legal values are ‘javax.sql.XADataSource’
# and ‘javax.sql.ConnectionPoolDataSource’
All parameters which are available from the server admin console can be set using the corresponding methods of the pool variable (which is a proxy of an instance of JDBCConnectionPoolConfig class).
Apart from setting the standard connection pool parameters you might want to pass some additional properties to the database driver (see the ‘Additional Properties’ tab on the connection pool configuration page in admin console). To do this, you need to call the set_property_value(key, value) method of the pool object.
When you are done, you can use web administration console to test the connection (using the “Ping” button on the connection pool configuration page). Probably you can do it from JRuby script directly — but I have not figured it out yet.
Creating a JDBC resource
Once you have a connection pool, you need a corresponding JDBC resource which you can use from your application. The creation of the JDBC resource is a two step process (and it was very hard to figure out the second step — I had to read quite a lot of GlassFish code to understand what's going on): first we create the resource configuration object and after that we associate the created object with all the server instances on which we want to be able to access it.
domain_config.createJDBCResourceConfig resource_name, pool_name,
HashMap.new({com.sun.appserv.management.config.ResourceConfigKeys::ENABLED_KEY => true })
domain_config.server_config_map['server-instance'].create_resource_ref resource_name, true
The first line here creates the JDBC resource configuration object, and the second adds the resource reference to the ‘server-instance’ server configuration.
Wrapping it all up in a script
After I got everything working in jirb, I decided to write a script, which would read the connection pool definitions from the YAML file and create them on the given application server instance, see the link below. When started it would print the usage information, you can also see the YAML configuration files examples in the script comments right after the license text. Note that it is actually a shell script which wraps up a JRuby script: thus it is possible to set up CLASSPATH (and, optionally, various Java VM options) before the jruby execution. Because of this it probably would not work on Windows out of the box — you'd need to strip out the environment setup code and jruby invocation (and don't forget to remove the END_OF_SCRIPT line!). Having that done you'd need to set up the CLASSPATH manually and start the script using the jruby.
You can download the script here or see a nice syntax-highlighted version here.
PS: I'm looking forward to hear your suggestions about using JRuby (or other nice languages) to do the similarly routine tasks!