Monday, April 12, 2010

Multi-level replacement of environment specific property values

In our current project we had multiple web applications that has to be deployed and tested across several test environments. Each of these web applications are highly configurable using property file entries. Also, we have load balancing clusters as well. The property file values are on 2 different classes. First class of property file values which needs to change on a per-environment basis(E.g, log.level... needs to be DEBUG on all test environments and INFO on production environment). There is another class of property file values which needs to change on a per-load-balanced-server basis within an environment (E.g, server.host, server.port).

Our development and deployment approach follows the steps:
  1. During development, templatize the property values (i.e., a property file entry would look like server.host=@server.host@) which needs to change, be it across environments or servers.
  2. Maintain environment specific property files - one '.env' file per environment (i.e., for env1 -> server.host=172.24.1.2 and for env2 -> server.host=172.24.1.3 etc.)
  3. During the build, replace each templatized value with an actual value from the specific environment file. Then deploy the generated property files.

This works for templatizing property values across multiple environments, but not for multiple servers within the same environment. What we used initially was we had a .env file per server per environment, e.g., test.server1.env, test.server2.env etc. The problem with this approach is that 98% of the values in both these server specific env files are same. So, when something changes, we had to go and modify all these redundant server-specific files which was painful as well as error-prone.

So, we have now decided to use 2 levels of env files. For an environment, there will be 1 env file which will contain all the templatization keys that is common to all load balancing servers across the test environment. There will be another server specific environment file which will contain JUST the templatization keys that are server specific. Then we use ANT to synthesize the final property file from both these env files on a server by server basis.

To illustrate by an example...

Let's say we have a property file which needs replacement called app.properties.template
log.level=@level@
log.file=@filepath@
throttle=5
host=@host.ip@
port=@host.port@
In the above, I have 5 property key/value pairs. One of which is static (throttle). Another 2 (log.level, log.file) are supposed to be same for all servers in an environment and final two (host, port) which are supposed to be different for each server in an environment.

We have the replace.env which replaces takes care of the properties which are same across all servers.
level=DEBUG
filepath=/usr/home/tomcat/app/logs/

Then, we have the server specific replacement env files:
replace.env.server1
host.ip=172.24.1.1
host.port=61009
replace.env.server2
host.ip=172.24.1.2
host.port=61010

And, finally the ANT build.xml

  
  
  
 
   
    
   

   
    
    
   

   
 
  


If we run ant change -Denv=server1, the app.properties gets generated, which looks like

log.level=DEBUG
log.file=/usr/home/tomcat/app/logs/
throttle=5
host=172.24.1.1
port=61009

And, if we run ant change -Denv=server2, the app.properties gets generated, which looks like

log.level=DEBUG
log.file=/usr/home/tomcat/app/logs/
throttle=5
host=172.24.1.2
port=61010

What is happening here is that the copy task finds all files ending in .template and uses the filterset task to replace the templatization tokens. We are using 2 filtersfile, the first one being the env file which contains environment specific templatization keys and the second one being the server specific templatization keys within the environment.

This approach helps to keep the environment files more modular and clean.