Getting your Rails app running on the SAP HANA Cloud Platform

Getting your Rails app running on the SAP HANA Cloud Platform

SAP HANA Cloud Platform is a PaaS service from SAP which you can use to host web applications running on the JVM and using SAP's in-memory database HANA. Hosting Java applications is obviously the most straightforward route but thanks to the rich ecosystem of JVM languages it's not the only one. In this tutorial I will show you how you can adapt a Ruby on Rails application to achieve a successful HANA deploy. Some of the instructions are based on this tutorial by Krum Baklasky: tutorial

If you don't already have a SAP HANA Cloud Platform account, you can grab a free trial account here: get account

Application setup

  1. Get the SAP HANA Cloud Platform SDK from HANA (neo-java-web-sdk-*.zip) and unpack it anywhere you want (in this tutorial we'll assume ~/neo). Make sure you have write permissions to this directory.
  2. Create an alias for ~/neo/tools/neo.sh or symlink somewhere in your $PATH. You will be using this tool a lot so it's nice to have a convenient command.
  3. If neo complains with this error Unable to set JAVA_HOME environment variable you need to set the JAVA_HOME environment variable yourself. For example, on Ubuntu adding the following line to ~/.bashrc (or ~/.zshrc) fixes the issue:

  4. export JAVA_HOME=/usr/lib/jvm/java-7-openjdk-amd64

  5. Install JRuby. If you are using rbenv, run: "rbenv install jruby" which will list the available jruby versions. As I am writing this post the newest version is jruby-1.7.16 so I ran:

  6. rbenv install jruby-1.7.16

    If you are using rvm the command you are looking for is:

    rvm install jruby --1.7.16

  7. To speed up JRuby start time, set this environment variable: export JRUBY_OPTS=--dev More information about this option can be found in the JRuby wiki.
  8. Set up JRuby as the Ruby version for your project:

  9. mkdir my_app # if you are creating a new app cd my_app rbenv local jruby-1.7.16

    in rvm you have to put this line in your ruby-version file:

    jruby-1.7.16

  10. If you are creating a new app, you need to install Rails with

  11. gem install rails

    and generate the file structure of your application with

    rails new .

    Otherwise, running

    gem install bundler

    should install everything your application needs, including Rails.

  12. To be able to use our database in the SAP HANA Cloud Platform with ActiveRecord (Rails ORM) we need to add some gems. SAP provides some gems for use with the MaxDB database (cloud-active-record-maxdb-adapter, cloud-jdbc-maxdb-gem) but there is no official gem (yet?) for working with the HANA database in the cloud. (Actually there is a activerecord-hana-adapter gem but unfortunately it doesn't work with JRuby.) Because of this, we will be using our forks of SAP's gems which we adapted to work with HANA Add these lines to your Gemfile:

  13. gem 'activerecord-maxdb-adapter', github: 'prograils/cloud-activerecord-maxdb-adapter'
    gem 'jdbc-maxdb', github: 'prograils/cloud-jdbc-maxdb-gem'

  14. To be able to test our application locally, we need to change the sqlite3 gem to:

  15. gem 'activerecord-jdbcsqlite3-adapter', group: [:development, :test]

  16. We will also need the Warbler gem which is used to create a .war file which can be deployed to the SAP HANA Cloud Platform.

  17. gem 'warbler', group: :development, require: false

  18. Run bundle install to install all the gems required by your application
  19. We need to edit the database configuration of our application (config/database.yml). It should look something like this:
  20. default: &default
      adapter: jdbcsqlite3
      timeout: 5000
      encoding: utf8

    development:
      <<: *default
      database: db/development.sqlite3

    test:
      <<: *default
      database: db/test.sqlite3

    production:
      adapter: maxdb
      encoding: utf8
      reconnect: false
      jndi: java:comp/env/jdbc/DefaultDB
      pool: 5
      prepared_statements: true

    local:
      <<: *production
      adapter: derby

  21. Generate warble's config file by running

  22. warble config

    This creates a file config/warble.rb. In this file, we need to uncomment and edit the line containing config.webxml.jndi, like so:

    config.webxml.jndi = 'jdbc/DefaultDB'

  23. There is currently no way to issue a rake db:migrate manually on the remote HANA instance. To overcome this obstacle, create a new initializer (config/initializers/hana_db_migrate.rb for example) with the following code (adapted from github):

# This initializer runs the equivalent of rake db:migrate on application start
# It relies on having all the 'db' folder contents packaged with the application's WAR

if Rails.env.production? && !defined?(::Rake)
  puts "============================= Executing application migrations ...
  ============================= "
  migrations = Rails.root.join('db','migrate')
  ActiveRecord::Migrator.migrate(migrations)
  puts "============================= Application migrations are executed.
  ============================= "
end

The conditional statement makes sure the migration doesn't fire off when you are just testing your application or running RAILS_ENV=production rake assets:precompile (which we will need later on).

Local testing

You should be able to test most of your application's functionality with the standard Rails tools. Running your test suite as well as the local web server should work. However, if your application uses features specific to the SAP HANA Cloud Platform, such as authentication, you have the options of deploying your application to a remote HANA instance or to a local server. Here are the basic steps if you choose the second option:

  1. Run neo install-local to install the local test server into ~/neo/server.
  2. Precompile your assets:

    EXECJS_RUNTIME=Node RAILS_ENV=production rake assets:precompile

    Setting EXECJS_RUNTIME is optional but speeds up the process (and requires Node.js to be installed).
  3. Run warble war to create a .war file for your application.
  4. Clean out old assets by running assets:clean
  5. Run RAILS_ENV=local neo start-local to start the server (this command is needed only once as the server will keep running unless explictly stopped). We set the rails environment to local so we can use a different database configuration for the local server.
  6. Run neo deploy-local -s my_app.war Your application should now be available at `http://localhost:8080/my_app`. You can view the local server's logs in your ~/neo/server/logs directory (ljs_trace.log seems to have the most useful information).

Testing specific features may require editing the server's configuration files which you can find in the ~/neo/server/config_master directory. You can find details on this topic in the SAP HANA Cloud Platform documentation. For example, here's the section on how to test authentication: documentation

One thing to keep in mind is that this local server uses Apache Derby as the database and not HANA so you still need to deploy to a real HANA instance to make sure your database-hitting code works as expected.

Deployment

To deploy your application to a real HANA instance you will need your username and account ID, both of which you can find in your SAP HANA Cloud Platform Cockpit after logging in. The steps for deploying your application are the following:

  1. Precompile your assets:

  2. EXECJS_RUNTIME=Node RAILS_ENV=production rake assets:precompile

  3. Run "warble war" to create a .war file for your application.
  4. Clean out old assets by running assets:clean
  5. Deploy your war file to your cloud account:

  6. neo deploy -s my_app.war -h hanatrial.ondemand.com -u <username> -a <username>trial -b my_app

    You can append a "-p " option to the above command if you don't want to be prompted for your password.

  7. Restart your application:

  8. neo restart -h hanatrial.ondemand.com -u <username> -a <username>trial -b my_app -y

    The -y option makes the command wait for your application to be started.

To avoid typing your account details with every deploy and restart command you can use a properties file. In your ~/neo/tools/samples/deploy_war directory you will find a file named example_war.properties containing a template, which you can copy and fill out. Store your properties file inside your project's directory. Then you can use the above commands like so:

neo deploy my_app.properties
neo restart my_app.properties

You will still need to give your password, either interactively or with a -p option. More information about the properties file here: documentation

For even more convenient deployment, you can throw the above commands into a script. Here's the script I used:

#!/bin/bash
NEO=~/neo/tools/neo.sh
PASSWORD=<your password>
EXECJS_RUNTIME=Node RAILS_ENV=production rake assets:precompile
warble rake assets:clean $NEO deploy hana-booking.properties -p $PASSWORD && \
$NEO restart hana-booking.properties -y -p $PASSWORD && \
notify-send 'Deploy done!'

The last line sends you a desktop notification when your deploy is complete (assuming you are on Linux and have a notification system that supports libnotify installed). You can, of course, create a similar script for local testing.

Bonus: SQL console

It is often useful to be able to check the state of your application's database especially when debugging. Unfortunately there doesn't seem to be a way to run a rails console on your cloud-hosted application. However you can get access to your application's database via an SQL console. Below I present a script which makes this quick and easy (based on this script in the documentation: script).

#!/bin/bash -ex

# Opens an SQL console connected to a SAP HANA Cloud Platform database
# Requires neo (https://tools.hana.ondemand.com/)
# and hdbclient (https://hanadeveditionsapicl.hana.ondemand.com/hanadevedition/)

PATH=$PATH:~/neo/tools:/usr/sap/hdbclient # add console client and HANA client to PATH

user=<username>
account=<username>trial
dbSchema=<name of db schema>
HCP_PASSWORD=<your hana cloud platform password>

json=$(neo.sh open-db-tunnel -h hanatrial.ondemand.com -a "$account" -u "$user" -p "$HCP_PASSWORD" -i "$dbSchema" --background --output json)
regex='.*"host":"([^"]*)".*"port":([^,]*),.*"instanceNumber":"([^"]*)".*"dbUser":"([^"]*)".*"dbUserPassword":"([^"]*)".*"sessionId":"([^"]*)".*'
[[ $json =~ $regex ]]
dbHost=${BASH_REMATCH[1]}
dbPort=${BASH_REMATCH[2]}
dbInstance=${BASH_REMATCH[3]}
dbUser=${BASH_REMATCH[4]}
dbPassword=${BASH_REMATCH[5]}
tunnelSessionId=${BASH_REMATCH[6]}

rlwrap hdbsql -n "$dbHost:$dbPort" -i "$dbInstance" -u "$dbUser" -p "$dbPassword"

neo.sh close-db-tunnel --session-id $tunnelSessionId

As you can see in the comments, to get this working you will need to download the HANA database client.

You will also need the rlwrap package which should be available through your package manager (or through brew if you are on a Mac). This little program improves your console experience by making arrow keys (and others) work as expected.

Once you are connected to your database, type the command \dt to list your tables. You'll see that their names are prefixed with the SQL schema name which looks something like NEO_XXXXXX. To avoid typing out this prefix with every SQL statement, you can set the current schema with set schema NEO_XXXXXX.

credits: photo from unsplash.com