Monday Apr 30, 2007

Ruby on Rails meets GlassFish High Availability

One of the key features of GlassFish v2 release is its cluster support. Various GlassFish server instances can be grouped together to form a single virtual entity - a cluster. One of the salient services offered via clustering is high availability, which is implemented as in-memory replication of state across participating server instances.

Since, I've been looking at getting Ruby on Rails applications running as web applications in a Java EE container, I was asked a couple of weeks ago if RoR applications could run in a GlassFish cluster. Well, with Rails-Integration 1.1.1, you can ! Its relatively straightforward to enable failover support for a web application in GlassFish and is no different for RoR (as a web application). The hard part, or so it seems at first, is setting up the GlassFish cluster.

For the easy part first, the only changes needed to enable failover session support for a web application are :

  • The deployment descriptor, web.xml, needs a marking distributable element - <distributable/>
  • Enable the high availability flag during deployment. For command line, this would be of the form
    ${GLASSFISH_HOME}/bin/asadmin deploy --target ${CLUSTER_NAME} --availabilityenabled=true ${path-to-war-file}

One important fact that I learned during this process, was that a load balancer facade is not necessarily needed for development/testing scenario. In other words if server instances, that share application state, are listening on the same host (different port), then failover can be simulated by simply sending requests to the other node (port).
Now, the part about setting up a GlassFish cluster. The references below give detailed information about the clustering architecture and setup steps but the takeaway is a lot simpler.

  • Download GlassFish v2 from https://glassfish.dev.java.net/public/downloadsindex.html
  • java -Xmx256m -jar ${GLASSFISH-v2.jar downloaded above}
  • cd glassfish; ant -f setup-cluster.xml

  • {GLASSFISH_HOME}/bin/asadmin start-domain ${domain-name} // ${domain-name} typically is 'domain1'

  • cd ${GLASSFISH_HOME}/samples/quickstart/clusterjsp/
  • ${GLASSFISH_HOME}/bin/asant setup-one-machine-cluster
  • ${GLASSFISH_HOME}/bin/asant start_cluster // stop_cluster to stop
  • (The last 2 steps will create a cluster named 'cluster1' with two instances, instance-ONE and instance-TWO listening for HTTP requests on port 1110 and 1111 respectively. Of course, you could do the same(creating/starting/stopping) via the asadmin command options like '(create|start|stop)-(cluster|instance|node-agent)', but it's a lot easier to get started via the clusterjsp sample)
That's all there is to it !

References
http://wiki.glassfish.java.net/gfwiki/Wiki.jsp?page=GlassFishV2Architecture
http://developers.sun.com/appserver/reference/techart/load-balancing.html
https://glassfish.dev.java.net/javaee5/build/GlassFish_LB_Cluster.html
http://wiki.glassfish.java.net/gfwiki/attach/OnePagersOrFunctionalSpecs/memory-replication-one-pager.html

Wednesday Feb 21, 2007

RoR with Embedded Derby

I got a request on trying to figure out how to get a RoR application working with Embedded Derby. And even though it's pretty straightforward it took me a couple of hours, so maybe the details below will save you some time.

The starting point would be to modify the RoR configuration in database.yml to look something like

 ...
    driver: org.apache.derby.jdbc.EmbeddedDriver
    url:    jdbc:derby:${database-name}

The Apache Derby database data files persists in the directory defined by 'derby.system.home'. And if this system property is not explicitly defined, the current working directory defaults as the property value. Surprisingly, in my case I could not locate the database files in the working directory used to start the database process. Well, it so happens that the derby isn't very user friendly in it's spewing error messages(at least not version 10.1.2.1). Even though on startup the server would emit 'Server is ready to accept connections on port 1527', it was not the process serving client requests(though I suspect it has got something to do with network interface bindings). There was another long running Derby process which was actually responsible for servicing the requests and its 'derby.system.home' will forever remain a mystery. After killing the long running process I was able to give a meaningful value to derby.system.home

For the RoR application running as a WAR in GlassFish, I made the following modification to domain configuration

   <java-config ...
     ...
     <jvm-options&rt; -Dderby.system.home=/my/value/of/derby/system/home <jvm-options>
     ...

Last but not the least, the Derby database process probably has some kind of an locking mechanism so that multiple processes do not step over each other. So, it means something like this - one process per database. You can use either the embedded or network mode Derby to serve you data from 'the' database.

Thursday Feb 15, 2007

RoRaWAR - A War Roar ?

No, it's not a war roar. This is not a current world affairs blog ;)

RoRaWAR stands for Ruby on Rails as Web ARchive.

The rails integration (under the jruby-extras umbrella) project's purpose is to WAR'up a RoR application. And it's great to see how far the project has come along since its inception a few months ago !

The creation of a web archive from a RoR application is as simple as

  • svn checkout svn://rubyforge.org/var/svn/jruby-extras/trunk/rails-integration rails-integration
  • cd rails-integration; mvn install;

  • the couple of steps above are one time only

  • cp -R <rails-integrations>/plugins/war <rails-app-dir>/vendor/plugins/
    cp -R <rails-integrations>/plugins/war-snapshot/\* <rails-app-dir>/vendor/plugins/war
  • cd <rails-app-dir>; rake war:standalone:create or rake war:shared:create ;
    #standalone mode will archive the rails framework libraries whereas the shared mode will not
It's as simple as that !

Note: There are a few implied dependencies here - JRuby, maven2, rails framework libraries. Also, since the rails-integration project is trying to utilize the latest and greatest JRuby releases, you might have to install the jruby.jar into maven2 repository via the maven install:install-file command. Eventually, the first couple of steps will not be needed once the rails-integration libraries are downloadable as a binary release, but until then ...

Wednesday Jan 24, 2007

Grizzlicious ... Part II

Part I described how to get a RoR application working on GlassFish. However, if you want something lightweight for development only or do not want anything to do with configuration changes(which btw, will only get simpler) or any other reason, read on ...

The elegant design of Grizzly allows you to write just a few lines of code and have a custom HTTP server suited to your specific requirements. The rails module does just that for primarily running a RoR application. This is how:

  1. Download rails-module-0.1-SNAPSHOT.jar
  2. The latest Grizzly binaries
  3. And of course, the JRuby binaries, Rails framework libraries and database specific JDBC drivers. For JRuby, I recommend working with the current trunk version as there are issues with the 0.9.2 release and the rails-module. Copy plaincharset.jar(in ${JRUBY_HOME}/lib) to jre/lib/ext of your Java runtime. For installing the Rails framework including activerecord-jdbc see steps #3 and #4. Use the flags(--no-rdoc --no-ri) for faster gem installation.

    Steps 1,2 and 3 are one time only.

  4. Create the Rails applications. Perform the following edits
    • Database related configuration edits - db config and step #8 here
    • In config/development.rb set config.breakpoint_server to false. See JRuby issue for more details.
  5. That's it ! Run it as something like
    # set the CLASSPATH variable to include
    # rails-module, grizzly, JDBC driver, 
    # JRuby libs(jruby.jar, jvyaml, asm, jline and jruby-openssl)
    java -classpath ${CLASSPATH} -Djruby.base=${JRUBY_HOME}
        com.sun.grizzly.rails.standalone.Main ${RAILS_ROOT}
    
    Or download the script and modify it to run in your environment.

Friday Jan 19, 2007

Grizzlicious ... RoR on GlassFish

In some of the previous episodes, I described how to go about deploying a Ruby on Rails(RoR) application on GlassFish. The first post described the method of deployment in which the RoR application is packaged as a WAR file, whereas the later posts(here and here) utilized the native Ruby interpreter at runtime(RoR-as-WAR runs on JRuby).

However, both deployment models are not quite ideal for a production deployment scenario. Enter Grizzly ! The NIO based HTTP connector for GlassFish. The beauty of Grizzly is not only that it's fast and scalable but that it is also highly extensible. TAKAI Naoto was quick to realize this fact and quickly dished out the first version of the adapter code needed to deploy RoR applications directly on top of Grizzly.

Without further ado, the steps to get a RoR application working as a first-class citizen within GlassFish:

  1. Download and install GlassFish in ${GLASSFISH_HOME}
  2. Download rails-module-0.1-SNAPSHOT.jar to ${GLASSFISH_HOME}/lib
  3. Checkout the latest source of JRuby and build it.
    Copy jruby.jar jvyaml.jar and asm-2.2.2.jar and to ${GLASSFISH_HOME}/lib
  4. Copy plaincharset.jar(in ${JRUBY_HOME}/lib) to jre/lib/ext of your Java runtime installation.
  5. Install the rails libraries including activerecord-jdbc. See step #3, #4 .
  6. The rails applications should installed under ${GLASSFISH_HOME}/domains/${domain-name}/applications/rails and you still need to do edits to get jdbc adapter working with JRuby. See steps #7 and #8 from the previous post
  7. Modify GlassFish configuration (which persists in ${GLASSFISH_HOME}/domains/${domain-name}/config/domain.xml - you could use the great administration UI - typically available at port 4848 via the browser or the command line interface to perform the changes) as below.
    • Add RoR specific http listener
          <http-listener acceptor-threads="1" address="0.0.0.0" 
                blocking-enabled="false" default-virtual-server="server" enabled="true" 
                family="inet" id="http-listener-rails" port="8081"
                security-enabled="false" server-name="" xpowered-by="true">
      
              <property name="selectorThreadImpl" value="com.sun.grizzly.rails.RailsSelectorThread"/>
          </http-listener>
      
    • To the java-config add the system property 'jruby.base'
          <jvm-options>-Djruby.base=jruby-home-value-here</jvm-options>
      
  8. Modify $GLASSFISH_HOME}/domains/${domain-name}/applications/rails/config/environments/development.rb by setting the property config.breakpoint_server to false. Once issue #427 in JRuby is fixed this step will not be needed.
Give it a shot !

Monday Jan 08, 2007

Derby's Identity column trivia

In a previous post, I described the SQL command to create a table with an auto increment column in the Derby database. It turns out that the 'GENERATED' keyword can be used in a couple of way.
  • GENERATED ALWAYS AS IDENTITY
  • GENERATED BY DEFAULT AS IDENTITY
For details see the relevant section of the Derby manual here. Essentially the distinguishing fact being - by using 'BY DEFAULT' the user has the ability to specify a value for the identity column as part of a (insert) SQL statement, whereas with 'ALWAYS', the column can never be explicitly edited by the user. Also 'ALWAYS' guarantees uniqueness, but 'BY DEFAULT' does not (use the UNIQUE/PRIMARY KEY attribute to achieve the same effect though)

So, the better suited SQL statement for creating a table in Derby in a RoR application would be

CREATE TABLE ${table-name} ( 
    ID INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 
    ${other-columns-and-constraints} )
Running a simple RoR application with the underlying identifier created via either option works without any issues. However, if you want to run the tests that comes in built with Rails framework the difference is detrimental (since the test framework seems to explicitly want to insert a value for the identifier column).

Tuesday Dec 19, 2006

GlassFish and Ruby - Part II

In part I, a GlassFish server was dedicated to hosting a single RoR application running on a native Ruby interpreter. Starting with changes in build 29, GlassFish v2 is capable of hosting multiple RoR applications along with your other web/ejb applications. All you need to do is make a couple of minor tweaks
  • Specify the context root associated with the RoR application in the descriptor file and the 'stripRequestURI' initialization paramater along with others specified in part I as illustrated below.
         <servlet>
            <servlet-name>cgi</servlet-name>
            <servlet-class>org.apache.catalina.servlets.CGIServlet</servlet-class>
            ...
            <init-param>
              <-- stripRequestURI parameter value maps to context root -->
              <-- in this case - http://.../ror/${ror-request-URI} -->
              <param-name>stripRequestURI</param-name>
              <param-value>/ror/</param-value>
            <init-param>
            ...
        </servlet>
    
    
        <servlet-mapping>
              <servlet-name>cgi</servlet-name>
              <url-pattern>/ror/\*</url-pattern>
        </servlet-mapping>
    
  • Since Rails auto-generates links, you'll have to make the following change in config/routes.rb
      # Specify the context root as the relative URL for all requests
      ActionController::AbstractRequest.relative_url_root = "/ror"
    
      # Install the default route as the lowest priority.
      map.connect ':controller/:action/:id'
    
Rails application will be accessible @ http(s)://.../ror/...
Simply replicate the configuration with the customized value of context root and the 'cgiPathPrefix' initialization parameter pointing to each application's specific 'dispatch.cgi' to host multiple RoR applications !

Friday Dec 15, 2006

GlassFish and Ruby

GlassFish and JRuby are obviously a great match, since both run on the Java virtual machine which allows Ruby code running on JRuby to access Java framework and libraries and the possibilities are endless.

However, GlassFish is also capable of running Ruby code via a native Ruby interpreter. This is possible using CGI support in GlassFish. For this first post, I shall keep things simple and demonstrate how you could go about dedicating an entire instance of the GlassFish server for a single RoR application

All you'll need is to install GlassFish v2 build 29 or greater, the native Ruby interpreter with the Rails framework libraries and make some minor modifications to GlassFish configuration. Of course, you'll need to create your rails application. The minor modifications amount to modifying ${glassfish.home}/domains/${domain-name}/config/default-web.xml with the following CGI configuration enabled.

   <servlet>
        <servlet-name>cgi</servlet-name>
        <servlet-class>org.apache.catalina.servlets.CGIServlet</servlet-class>
        <init-param>
          <!-- in this case the path will be appended to
               ${glassfish.home}/domains/${domain-name}/docroot/ -->
          <param-name>cgiPathPrefix</param-name>
          <param-value>${rails-root-dir}/public/dispatch.cgi</param-value>
        </init-param>
        <init-param>
          <param-name>executable</param-name>
          <param-value>${path-to-ruby}</param-value>
        </init-param>
        <init-param>
          <param-name>passShellEnvironment</param-name>
          <param-value>true</param-value>
        </init-param>
        ...
    </servlet>

    <servlet-mapping>
        <servlet-name>cgi</servlet-name>
        <url-pattern>/\*</url-pattern>
    </servlet-mapping>

On UNIX make sure that the native Ruby interpreter is in the PATH and LD_LIBRARY_PATH is set to satisfy the native Ruby interpreter's/Rails dependencies in the GlassFish process's environment. This is the reason for configuring the 'passShellEnvironment' parameter to true.

That's pretty much all there is to it. Accessing http://${host}:${port}/ should bring up the default Rails page !

Wednesday Dec 06, 2006

RoR in GlassFish with Derby as the database

In my previous post I wrote about getting a RoR application to work on GlassFish as a web application. Since the Derby database is embedded in GlassFish why not try to use it as the backend datastore for the RoR-web-application.

Getting a Ruby on Rails application to work with a Derby database should be relatively straightforward. Well it is, but only after you've jumped through a few hoops trying to figure things out for the first time.

1. Modify config/database.yml to something like:

development:
 adapter: jdbc
 driver: org.apache.derby.jdbc.ClientDriver
 url: jdbc:derby://${database host}:${database port}/${database name}
 username: ${username}
 password: ${password}
Derby has the concept of 'database name' and 'schema'. If 'schema' is unspecified it defaults to 'username'

2. Create table(s) in Derby. Points to note here are:
   2.1 Use upper case for all table/column names
   2.2 To create the auto increment identifier column use the following SQL statement

CREATE TABLE ${table-name} ( ID INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, ${other-columns-and-constraints} )


I'm not sure what the SQL standard is on auto increment column types but this was bit of a pain to figure out. Also remember that 'DESC' is a reserved work in SQL terminology so do not use that as a column name.

The Derby jdbc driver should by default be in the classpath of GlassFish application server so no tweaking needed on that account. However, if you are running the RoR application on JRuby/WEBrick combo you'll need to add the derby-jdbc-driver library to the classpath.

That's pretty much all there is to it. Give it a shot !

Friday Dec 01, 2006

Deploying a Ruby on Rails application in GlassFish

There are a few ways a Ruby on Rails(RoR) application could be deployed on a Java EE based application server platform. One of them is to create a WAR file containing the RoR application with the appropriate adapter code between the application server and the ruby application. On how to go about developing and deploying a RoR application on GlassFish - an open source Java EE 5 application server - follow the steps below:

1. Install GlassFish application server
2. Install JRuby
3. Install Rails Framework 'gems install rails -y' (if behind a http proxy, set HTTP_PROXY=http://${http-proxy-host}:${http-proxy-port}/)
4. Install activerecord-jdbc 'gems install activerecord-jdbc'
5. Install database/jdbc driver (for mysql http://mysql.com/)
6. Generate your Ruby on Rails application (http://rubyonrails.org/)
7. Modify database.yaml
       development:
           adapter: jdbc
           driver: com.mysql.jdbc.Driver (for mysql)
           url: jdbc:mysql://${database-hostname}/${db-schema}
           username: ${username}
           password: ${password}
8. Modify environment.rb by adding
       require 'active_record/connection_adapters/jdbc_adapter'
9. Download rails-integration-${version}-SNAPSHOT.jar into the WEB-INF/lib by checking out and building the rails-integration project
svn://rubyforge.org/var/svn/jruby-extras/trunk/rails-integration
10. Modify the template web.xml(see references below) with the right value of jruby.home
11. Create a WEB-INF directory in the RoR application directory
12. Copy web.xml to WEB-INF and rails-integration-${version}-SNAPSHOT.jar under WEB-INF/lib
13. Jar up the RoR application directory contents as a WAR file

Requests to appropriate context-root of the deployed web application should invoke your Ruby code !

If you'd like to automate the building of a war file:
Edit rails-integration/build.xml and add the following XML snippet
  <property environment="env"/>
  <target name="build-rails-war" depends="jar">
    <delete file="${rails-app-dir}/${rails-app-name}.war"/>
    <mkdir dir="${rails-app-dir}/WEB-INF/lib"/>
    <copy todir="${rails-app-dir}/WEB-INF/lib">
      <fileset file="${maven.build.directory}/${maven.build.final.name}.jar"/>
      <fileset file="${maven.repo.local}/org/jruby/jruby/0.9.1/jruby-0.9.1.jar"/>
      <fileset file="${maven.repo.local}/asm/asm/2.2.2/asm-2.2.2.jar"/>
      <fileset file="${maven.repo.local}/javax/activation/activation/1.1/activation-1.1.jar"/>
    </copy>
    <copy todir="${rails-app-dir}/WEB-INF/">
      <fileset file="samples/scaffold/WEB-INF/web.xml"/>
    </copy>
    <replace file="${rails-app-dir}/WEB-INF/web.xml" token="/usr/local/jruby" value="${env.JRUBY_HOME}"/>
    <jar jarfile="${rails-app-dir}/${rails-app-name}.war" basedir="${rails-app-dir}"/>
  </target>

Make sure JRUBY_HOME is set and run
'ant -Drails-app-dir=${ror-app-dir} -Drails-app-name=${war-file-name} build-rails-war'
${ror-app-dir}/${ror-app-name}.war should be ready for deployment !

All of this is work in progress some of this might change very soon. Thanks to rail-integration developers and the jruby folks !
About

whacko

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today