In our last post we walked through adding GemStone/S as a service in Cloud Foundry. With this, we could access GemStone/S from an application written in a runtime/framework recognized by Cloud Foundry. Now, we look at what it takes to have GemStone/S recognized as a runtime/framework (named ‘topaz’) so we can work directly in server Smalltalk.

Changes to VMC on the Client

First, we need to make changes on the client so that VMC recognizes topaz as a framework. This process is discussed in detail here and involves a couple small edits to lib/cli/frameworks.rb. Insert the following at line 9 (to the list of FRAMEWORKS):

'Topaz' => ['topaz', { :mem => '256M', :description => 'Topaz for GemStone/S'}],

Then we insert the following detection code staring at line 42 (after the Rails detection):

# Topaz on GemStone
elsif File.exist?('main.tpz')
  return Framework.lookup('Topaz')

Changes to VCAP on the Server

First we navigate to the VCAP location and create some directories:

cd ~/cloudfoundry/vcap/
mkdir ./dev_setup/cookbooks/topaz
mkdir ./dev_setup/cookbooks/topaz/recipes
mkdir ./dev_setup/cookbooks/topaz/attributes
mkdir ./staging/lib/vcap/staging/plugin/topaz

The template for the topaz script at dev_setup/cookbooks/gemstone/templates/default/topaz.erb that we created earlier needs a few changes and should be replaced with the following:

#!/bin/bash
if [[ "$1" == "-v" ]]; then
  echo "<%= node[:gemstone][:version] %>"
  exit 0
fi
export GEMSTONE=<%= node[:gemstone][:path] %>/product
export PATH=$GEMSTONE/bin:$PATH
export GEMSTONE_GLOBAL_DIR=<%= node[:gemstone][:service_dir] %>
topaz -l \
  -I <%= node[:gemstone][:service_dir] %>/bin/.topazini \
  -e <%= node[:gemstone][:service_dir] %>/etc/topaz.conf \
  -z <%= node[:gemstone][:service_dir] %>/etc/system.conf $*

Next we need to modify the provisioning script at services/gemstone/resources/provision.tpz.erb to remove the NoGsFile* privileges so that we can write to stdout from Smalltalk. File-level security can be enforced by Cloud Foundry by setting ‘secure: true‘ in dea/config/dea.yml and starting VCAP as root (e.g., with sudo).

Next we modify cloud_controller/app/models/app.rb lines 26, 27 to add topaz as a runtime and framework:

Runtimes = %w[topaz ruby18 ruby19 java node php erlangR14B02 python26]
Frameworks = %w[topaz sinatra rails3 java_web spring grails node php otp_rebar lift wsgi django unknown]

Next we modify dev_setup/cookbooks/cloud_controller/attributes/default.rb to insert the following at line 10:

default[:cloud_controller][:staging][:topaz] = "topaz.yml"

Next we modify
dev_setup/cookbooks/cloud_controller/templates/default/cloud_controller.yml.erb to insert the following at line 110:

topaz:
 version: 3.0.1

Next we create dev_setup/cookbooks/cloud_controller/templates/default/topaz.yml.erb with the following:

---
name: "topaz"
runtimes:
  - "topaz":
      version: "3.0.1"
      description: "Topaz"
      executable: "/var/vcap/services/gemstone/bin/topaz"
      default: true
      environment:
detection:
  - "main.tpz"

Next we modify dev_setup/cookbooks/dea/attributes/default.rb to add “topaz” to line 3:

default[:dea][:runtimes] = ["topaz", "ruby18", "ruby19", "nodejs", "java", "erlang", "php"]

Next we modify dev_setup/cookbooks/dea/templates/default/dea.yml.erb to insert the following at line 46:

<% if node[:dea][:runtimes].include?("topaz") %>
  topaz:
    executable: <%= File.join(node[:topaz][:path]) %>
    version: 3.0.1
    environment:
<% end %>

Next we create dev_setup/cookbooks/topaz/attributes/default.rb with the following:

include_attribute "deployment"
default[:topaz][:version] = "3.0.1"
default[:topaz][:path] = "/var/vcap/services/gemstone/bin/topaz"

Next we create dev_setup/cookbooks/topaz/recipes/default.rb with the following:

# 
# Cookbook Name:: topaz
# Recipe:: default 
# 
# Copyright 2012, VMware 
# 
Chef::Log.debug("Topaz should have been installed with GemStone/S 64 Bit")

Next we create staging/lib/vcap/staging/plugin/manifests/topaz.yml with the following:

---
name: "topaz"
runtimes:
  - topaz:
      version: '3.0.1'
      description: 'Topaz for GemStone/S'
      executable: /var/vcap/services/gemstone/bin/topaz
      default: true
app_servers:
detection:
  - "main.tpz": '.'
staged_services:

Next we create staging/lib/vcap/staging/plugin/topaz/plugin.rb with the following:

class TopazPlugin < StagingPlugin
  include GemfileSupport
  def framework
    'topaz'
  end
#
  def stage_application
    Dir.chdir(destination_directory) do
      create_app_directories
      copy_source_files
      do_staging
      create_startup_script
      create_stop_script
    end
  end
#
  def do_staging
    Dir.chdir("app") {|dir|
      # nothing really to do here!
    }
  end
#
  def start_command
    "/var/vcap/services/gemstone/bin/topaz -I .topazini < main.tpz"
  end
#
  private
  def startup_script
    vars = environment_hash
    # PWD here is the parent of the 'app' directory.
    generate_startup_script(vars) do
      cmds = []
      cmds << 'USER=$(echo $VCAP_SERVICES | ' + \
        'grep -Po \'"user":.*?[^\\\\]",\' | cut -d\'"\' -f4)'
      cmds << 'PASS=$(echo $VCAP_SERVICES | ' + \
        'grep -Po \'"pass":.*?[^\\\\]",\' | cut -d\'"\' -f4)'
      cmds << 'HOST=$(echo $VCAP_SERVICES | ' + \
        'grep -Po \'"host":.*?[^\\\\]",\' | cut -d\'"\' -f4)'
      cmds << 'echo "set user $USER pass $PASS" > app/.topazini'
      cmds << 'echo "set gems !tcp@$HOST#server!gs64stone" >> app/.topazini'
      cmds << 'echo "login" >> app/.topazini'
      cmds.join("\n")
    end
  end
#
  def stop_script
    vars = environment_hash
    generate_stop_script(vars)
  end
#
end

Next we create staging/lib/vcap/staging/plugin/topaz/staging with the following:

#!/usr/bin/env ruby
require File.expand_path('../../common', __FILE__)
plugin_class = StagingPlugin.load_plugin_for('topaz')
plugin_class.validate_arguments!
plugin_class.new(*ARGV).stage_application

Our demo application will be of WebTools, a “goodie” provided with GemStone/S, but it needs a couple fixes to work with Cloud Foundry. Our earlier post shows how to install GemStone/S and the Chef script installs the product tree at ~/cloudfoundry/.deployments/devbox/deploy/gemstone/product/. The changes need to be made to examples/www/src/Server.gs (I would give the full path but it doesn’t display well with the blog formatting). In the #’doAnswer’ method we need to replace the #’lf’ message sends with #’crlf’ so that we get a CR/LF as required by the HTTP spec. In the #’handleRequest’ method we need to remove the check for HTTP/1.1 since Cloud Foundry sends the requests using a 1.0 format.

At this point we can deploy Cloud Foundry:

~/cloudfoundry/vcap/dev_setup/bin/vcap_dev_setup -d ~/cloudfoundry/

As we found earlier, this doesn’t properly install the staging code, so we copy it across manually:

fromDir=`find ~/cloudfoundry/vcap/staging/ | grep pip_support.rb`
fromDir=`dirname $fromDir`
toDir=`find ~/cloudfoundry/.deployments/devbox/ | grep pip_support.rb`
toDir=`dirname $toDir`
cp -r $fromDir/topaz $toDir
cp $fromDir/manifests/topaz.yml $toDir/manifests/topaz.yml

We can now start Cloud Foundry, vcap start, and return to the client to create a new GemStone/S application.

Creating an Application

We need to have an application to push to the cloud, so we will start by creating a directory to hold the application on the client:

mkdir ~/cloud/topaz
cd ~/cloud/topaz
vim ~/cloud/topaz/main.tpz

Our application, main.tpz, will consist of the following Topaz script:

input $GEMSTONE/examples/www/install.tpz
run
| class port |
class := GsSession currentSession userProfile symbolList last at: #'Server'.
port := System gemEnvironmentVariable: 'VCAP_APP_PORT'.
class new startForegroundServerAtPort: port asNumber.
%
logout
exit

At this point we can push our application, give it a name, and bind it to a gemstone service:

Would you like to deploy from the current directory? [Yn]: 
Application Name: topaz-app
Application Deployed URL [topaz-app.vcap.me]: 
Detected a Topaz for GemStone/S, is this correct? [Yn]: 
Memory reservation (128M, 256M, 512M, 1G, 2G) [256M]: 
How many instances? [1]: 
Would you like to bind any services to 'topaz-app'? [yN]: y
The following system services are available
1: gemstone
2: mongodb
3: mysql
4: neo4j
5: redis
Please select the one you wish to provision: 1
Specify the name of the service [gemstone-1b271]: 
Would you like to bind another service? [yN]: 
Would you like to save this configuration? [yN]: 
Creating Application: OK
Creating Service [gemstone-1b271]: OK
Binding Service [gemstone-1b271]: OK
Uploading Application:
 Checking for available resources: OK
 Packing application: OK
 Uploading (0K): OK 
Push Status: OK
Staging Application 'topaz-app': OK 
Starting Application 'topaz-app': OK

When the application has been started, we can navigate to http://topaz-app.vcap.me/ and see that we are interacting with a GemStone/S system over the web.