Permalink
Browse files

Add support for generating start/stop scripts

This provides the foundation to allow framework/feature plugins to
generate start/stop scripts. The 'startup' script (called by the dea)
calls all generated feature start scripts, then invokes the framework
start script. The framework start script is expected to write the pid
of the appserver into the file 'run.pid' (expected by the dea) in the root
droplet directory. Similarly, the 'stop' script (also called by the dea)
calls all generated feature stop scripts first, then invokes the framework
stop script.

Test plan:
- Unit tests pass
- Able to start/stop sinatra apps created using a rudimentary sinatra plugin
  and the plugin orchestrator.

Change-Id: I2ce9c43af81d06c3700d874417be467454f00fdd
  • Loading branch information...
1 parent 9329bec commit f8e33f2e5fa50158053f89c3674a9066a933c7fc mpage committed Oct 4, 2011
View
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# NB: Everything here is executing _inside_ the container
+
+# Sets globbing behavior such that globs matching nothing evaluate
+# to null rather than themselves.
+shopt -s nullglob
+
+# Execute all the features' start scripts
+for f in "${PWD}/vcap/script/feature_start/*"
+do
+ ${f} $@
+done
+
+# VCAP stop script expects the presence of this file
+echo $PPID > parent.pid
+
+./framework_start $@ > ./logs/stdout.log 2> ./logs/stderr.log &
+wait $!
View
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+# NB: Everything here is executing _inside_ the container
+
+# Sets globbing behavior such that globs matching nothing evaluate
+# to null rather than themselves.
+shopt -s nullglob
+
+# Execute all the features' stop scripts
+for f in "${PWD}/vcap/script/feature_stop/*"
+do
+ ${f} $@
+done
+
+# Execute the framework stop script
+if [ -e "./framework_stop" ]
+then
+ ./framework_stop
+fi
+
+# Just to be safe
+head -1 ./run.pid | xargs kill -9
+head -1 ./parent.pid | xargs kill -9
@@ -2,5 +2,6 @@ module VCAP
module Stager
BIN_DIR = File.expand_path("../../../../bin", __FILE__)
CONFIG_DIR = File.expand_path("../../../../config", __FILE__)
+ ASSET_DIR = File.expand_path("../../../../assets", __FILE__)
end
end
@@ -0,0 +1,77 @@
+require 'fileutils'
+
+require 'vcap/stager/constants'
+
+module VCAP
+ module Stager
+ end
+end
+
+class VCAP::Stager::Droplet
+ attr_reader :base_dir
+ attr_reader :feature_start_dir
+ attr_reader :feature_stop_dir
+ attr_reader :framework_start_path
+ attr_reader :framework_stop_path
+ attr_reader :vcap_start_path
+ attr_reader :vcap_stop_path
+ attr_reader :logs_dir
+ attr_reader :app_source_dir
+ attr_reader :pidfile_path
+
+ def initialize(base_dir, asset_dir=VCAP::Stager::ASSET_DIR)
+ @asset_dir = asset_dir
+
+ @base_dir = base_dir
+ # Dirs contain the start/stop scripts generated by the selected feature plugins
+ @feature_start_dir = File.join(@base_dir, 'vcap', 'script', 'feature_start')
+ @feature_stop_dir = File.join(@base_dir, 'vcap', 'script', 'feature_stop')
+
+ # Framework specific start/stop scripts
+ @framework_start_path = File.join(@base_dir, 'framework_start')
+ @framework_stop_path = File.join(@base_dir, 'framework_stop')
+
+ # Start/stop scripts that the DEA expects
+ @vcap_start_path = File.join(@base_dir, 'startup')
+ @vcap_stop_path = File.join(@base_dir, 'stop')
+
+ # Directory that houses the application source
+ @app_source_dir = File.join(@base_dir, 'app')
+
+ # Directory that will house application logs. VMC/CC expects that logs live here
+ # for the 'logs' command to work.
+ @logs_dir = File.join(@base_dir, 'logs')
+
+ # The DEA looks for the presence of this file when starting the app
+ @pidfile_path = File.join(@base_dir, 'run.pid')
+ end
+
+ # Create the necessary dirs, copies over the app source and expected
+ # vcap start/stop scripts
+ #
+ # @param source_dir String Directory containing the application source
+ def create_skeleton(source_dir)
+ FileUtils.mkdir_p(@feature_start_dir)
+ FileUtils.mkdir_p(@feature_stop_dir)
+ FileUtils.mkdir_p(@app_source_dir)
+ FileUtils.mkdir_p(@logs_dir)
+
+ app_source_glob = File.join(source_dir, '*')
+ system("cp -a #{app_source_glob} #{@app_source_dir} 2> /dev/null")
+
+ # Copy over the vcap start/stop scripts (expected by dea)
+ for script_info in [['startup', @vcap_start_path], ['stop', @vcap_stop_path]]
+ name, dst_path = script_info
+ FileUtils.cp(File.join(@asset_dir, name), dst_path)
+ FileUtils.chmod(0744, dst_path)
+ end
+ end
+
+ def feature_start_path(name)
+ File.join(@feature_start_dir, name)
+ end
+
+ def feature_stop_path(name)
+ File.join(@feature_stop_dir, name)
+ end
+end
@@ -6,6 +6,14 @@ module Stager
# This class exposes actions that modify VCAP resources to staging plugins. An instance
# of it is passed to the plugin as a parameter to the stage() method.
class VCAP::Stager::PluginActionProxy
+ def initialize(start_script_path, stop_script_path, droplet)
+ @start_script_path = start_script_path
+ @start_script = nil
+ @stop_script_path = stop_script_path
+ @stop_script = nil
+ @droplet = droplet
+ end
+
# Creates a service on behalf of the user
#
# @param label String The label that uniquely identifies this service
@@ -29,14 +37,28 @@ def bind_service(name, binding_options)
#
# @return File
def start_script
- raise NotImplementedError
+ @start_script ||= create_script(@start_script_path)
+ @start_script
end
# Returns an open file object that the user can write contents of a stop
# script into.
#
# @return File
def stop_script
- raise NotImplementedError
+ @stop_script ||= create_script(@stop_script_path)
+ @stop_script
+ end
+
+ def droplet_base_dir
+ @droplet.base_dir
+ end
+
+ private
+
+ def create_script(path)
+ ret = File.new(path, 'w+')
+ FileUtils.chmod(0755, path)
+ ret
end
end
@@ -1,7 +1,9 @@
-require 'rubygems'
+require 'fileutils'
require 'vcap/logging'
+require 'vcap/stager/constants'
+require 'vcap/stager/droplet'
require 'vcap/stager/plugin_action_proxy'
require 'vcap/stager/plugin_orchestrator_error'
require 'vcap/stager/plugin_registry'
@@ -14,10 +16,13 @@ module Stager
# Responsible for orchestrating the execution of all staging plugins selected
# by the user.
class VCAP::Stager::PluginOrchestrator
- # @param source_dir String Directory containing application source
+ # @param source_dir String Directory containing application source
+ # @param dest_dir String Directory where the staged droplet should live
# @param app_properties
- def initialize(source_dir, app_properties)
+ def initialize(source_dir, dest_dir, app_properties)
@source_dir = source_dir
+ @dest_dir = dest_dir
+ @droplet = VCAP::Stager::Droplet.new(dest_dir)
@app_properties = app_properties
@logger = VCAP::Logging.logger('vcap.stager.plugin_orchestrator')
end
@@ -27,21 +32,48 @@ def run_plugins
require(name)
end
+ framework_plugin, feature_plugins = collect_plugins
+ unless framework_plugin
+ raise VCAP::Stager::MissingFrameworkPluginError, "No framework plugin found"
+ end
+
+ @logger.info("Setting up base droplet structure")
+ @droplet.create_skeleton(@source_dir)
+
+ actions = VCAP::Stager::PluginActionProxy.new(@droplet.framework_start_path,
+ @droplet.framework_stop_path,
+ @droplet)
+ @logger.info("Running framework plugin: #{framework_plugin.name}")
+ framework_plugin.stage(@droplet.app_source_dir, actions, @app_properties)
+
+ for feature_plugin in feature_plugins
+ pname = feature_plugin.name
+ actions = VCAP::Stager::PluginActionProxy.new(@droplet.feature_start_path(pname),
+ @droplet.feature_stop_path(pname),
+ @droplet)
+ @logger.info("Running feature plugin: #{feature_plugin.name}")
+ feature_plugin.stage(framework_plugin, @droplet.app_source_dir, actions, @app_properties)
+ end
+ end
+
+ protected
+
+ def collect_plugins
framework_plugin = nil
- feature_plugins = []
+ feature_plugins = []
for plugin in VCAP::Stager::PluginRegistry.plugins
ptype = plugin.plugin_type
case ptype
when :framework
- @logger.debug("Found framework plugin: #{name}")
+ @logger.debug("Found framework plugin: #{plugin.name}")
if framework_plugin
raise VCAP::Stager::DuplicateFrameworkPluginError, "Only one framework plugin allowed"
else
framework_plugin = plugin
end
when :feature
- @logger.debug("Found feature plugin: #{name}")
+ @logger.debug("Found feature plugin: #{plugin.name}")
feature_plugins << plugin
else
@@ -50,14 +82,7 @@ def run_plugins
end
end
- raise VCAP::Stager::MissingFrameworkPluginError, "No framework plugin found" unless framework_plugin
-
- actions = VCAP::Stager::PluginActionProxy.new
- @logger.info("Running framework plugin: #{framework_plugin.name}")
- framework_plugin.stage(@source_dir, actions, @app_properties)
- for feature_plugin in feature_plugins
- @logger.info("Running feature plugin: #{feature_plugin.name}")
- feature_plugin.stage(framework_plugin, @source_dir, actions, @app_properties)
- end
+ [framework_plugin, feature_plugins]
end
+
end
@@ -0,0 +1,45 @@
+require File.join(File.dirname(__FILE__), 'spec_helper')
+
+require 'fileutils'
+require 'tmpdir'
+
+describe VCAP::Stager::Droplet do
+ describe '#create_skeleton' do
+ before :each do
+ @src_dir = Dir.mktmpdir
+ @dst_dir = Dir.mktmpdir
+ end
+
+ after :each do
+ FileUtils.rm_rf(@src_dir)
+ FileUtils.rm_rf(@dst_dir)
+ end
+
+ it 'should create directories expected by other vcap components' do
+ droplet = VCAP::Stager::Droplet.new(@dst_dir)
+ droplet.create_skeleton(@src_dir)
+ # Check that dirs exist to house the feature start/stop scripts
+ File.exist?(File.join(@dst_dir, 'vcap', 'script', 'feature_start')).should be_true
+ File.exist?(File.join(@dst_dir, 'vcap', 'script', 'feature_stop')).should be_true
+
+ File.exist?(File.join(@dst_dir, 'app')).should be_true
+ File.exist?(File.join(@dst_dir, 'logs')).should be_true
+ end
+
+ it 'should copy over the vcap start/stop scripts' do
+ droplet = VCAP::Stager::Droplet.new(@dst_dir)
+ droplet.create_skeleton(@src_dir)
+ File.exist?(File.join(@dst_dir, 'startup')).should be_true
+ File.exist?(File.join(@dst_dir, 'stop')).should be_true
+ end
+
+ it 'should copy over the application source' do
+ rel_path = File.join('foo', 'bar', 'baz')
+ FileUtils.mkdir_p(File.join(@src_dir, 'foo', 'bar'))
+ File.open(File.join(@src_dir, rel_path), 'w+') {|f| f.write("Hello!") }
+ droplet = VCAP::Stager::Droplet.new(@dst_dir)
+ droplet.create_skeleton(@src_dir)
+ File.exist?(File.join(@dst_dir, 'app', rel_path)).should be_true
+ end
+ end
+end
@@ -0,0 +1,37 @@
+require File.join(File.dirname(__FILE__), 'spec_helper')
+
+require 'fileutils'
+require 'tmpdir'
+
+describe VCAP::Stager::PluginActionProxy do
+ before :each do
+ @tmpdir = Dir.mktmpdir
+ end
+
+ after :each do
+ FileUtils.rm_rf(@tmpdir)
+ end
+
+ describe '#start_script' do
+ it 'should return an open file object with mode 755' do
+ start_path = File.join(@tmpdir, 'start')
+ proxy = VCAP::Stager::PluginActionProxy.new(start_path, nil, nil)
+ verify_script(proxy.start_script)
+ end
+ end
+
+ describe '#stop_script' do
+ it 'should return an open file object with mode 755' do
+ stop_path = File.join(@tmpdir, 'stop')
+ proxy = VCAP::Stager::PluginActionProxy.new(nil, stop_path, nil)
+ verify_script(proxy.stop_script)
+ end
+ end
+
+ def verify_script(script)
+ script.class.should == File
+ script.closed?.should be_false
+ perms = script.stat.mode & 0xfff
+ perms.should == 0755
+ end
+end
Oops, something went wrong.

0 comments on commit f8e33f2

Please sign in to comment.