Skip to content
This repository has been archived by the owner on Jan 26, 2022. It is now read-only.

Commit

Permalink
Minor refactoring
Browse files Browse the repository at this point in the history
Passing in the set of plugins that can be run is more logical,
cleaner, and makes testing easier.

Test plan:
- Unit tests pass

Change-Id: Id52d09f778417b42a56fd30a0a53967555d76f71
  • Loading branch information
mpage committed Nov 15, 2011
1 parent 466d8ad commit e0d2170
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 116 deletions.
36 changes: 27 additions & 9 deletions stager/bin/plugin_runner
Expand Up @@ -4,20 +4,38 @@ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
require 'bundler/setup'
require 'yaml'

if ARGV.length != 2
$stderr.puts "Usage: plugin_runner [serialized plugin runner path] [error path]"
require 'vcap/plugin_registry'
require 'vcap/stager/plugin_runner'

# task_path should be the location of a task file serialized with
# VCAP::Stager::PluginRunner.serialize_task

if ARGV.length != 1
$stderr.puts "Usage: plugin_runner [task_path]"
exit 1
end

plugin_runner_path, error_path = ARGV

task = VCAP::PluginRunner.deserialize_task(ARGV[0])
begin
plugin_runner = VCAP::Stager::PluginRunner.from_file(plugin_runner_path)
plugin_runner.run_plugins
if task['opts']['config_dir']
VCAP::PluginRegistry.configure_plugins(task['opts']['config_dir'])
end

if task['opts']['log_path']
VCAP::Logging.setup_from_config(:log_file => task['opts']['log_path'])
else
# Logs to stdout by default
VCAP::Logging.setup_from_config({})
end

plugin_runner = VCAP::Stager::PluginRunner.new(VCAP::PluginRegistry.plugins)
plugin_runner.run_plugins(task['source_dir'], task['dest_dir'], task['app_props'], task['cc_info'])
exit 0
rescue => e
File.open(error_path, 'w+') do |f|
YAML.dump(e, f)
if task['opts']['error_path']
File.open(task['opts']['error_path'], 'w+') do |f|
YAML.dump(e, f)
end
end
exit 1
end
end
151 changes: 81 additions & 70 deletions stager/lib/vcap/stager/plugin_runner.rb
Expand Up @@ -22,100 +22,111 @@ class VCAP::Stager::PluginRunner
RUNNER_BIN_PATH = File.join(VCAP::Stager::BIN_DIR, 'plugin_runner')

class << self
def from_file(src_path)
YAML.load_file(src_path)
# Writes out necessary information for invoking the plugin runner using
# the standalone script.
#
# @param task_path String Where to serialize the task
# @param opts Hash 'error_path' => Where to store any exceptions
# thrown during the process.
# 'config_dir' => Optional. Directory containing
# config files for staging plugins.
# 'log_path' => Optional. Where to log plugin output.
# If not provided, logs will go to stdout.
def serialize_task(task_path, source_dir, dest_dir, app_props, cc_info, opts={})
task = {
'source_dir' => source_dir,
'dest_dir' => dest_dir,
'app_props' => app_props,
'cc_info' => cc_info,
'opts' => opts,
}
File.open(task_path, 'w+') do |f|
YAML.dump(task, f)
end
end
end

# @param source_dir String Directory containing application source
# @param dest_dir String Directory where the staged droplet should live
# @param app_properties
# @param cc_info Hash Information needed for contacting the CC
# 'host'
# 'port'
# 'task_id'
def initialize(source_dir, dest_dir, app_properties, cc_info)
@source_dir = source_dir
@dest_dir = dest_dir
@droplet = VCAP::Stager::Droplet.new(dest_dir)
@app_properties = app_properties
@services_client = VCAP::CloudController::Ipc::ServiceConsumerV1Client.new(cc_info['host'],
cc_info['port'],
:staging_task_id => cc_info['task_id'])
@environment = {}
def deserialize_task(task_path)
YAML.load_file(task_path)
end
end

def run_plugins
@logger = VCAP::Logging.logger('vcap.stager.plugin_runner')

framework_plugin, feature_plugins = collect_plugins

@logger.info("Setting up base droplet structure")
@droplet.create_skeleton(@source_dir)
# @param plugins Array Available plugins
# name => plugin
def initialize(plugins={})
@plugins = plugins
end

actions = VCAP::Stager::PluginActionProxy.new(@droplet.framework_start_path,
@droplet.framework_stop_path,
@droplet,
@services_client,
@environment)
@logger.info("Running framework plugin: #{framework_plugin.name}")
framework_plugin.stage(@droplet.app_source_dir, actions, @app_properties)
# Runs the appropriate plugins to stage the given application
#
# @param source_dir String Directory containing application source
# @param dest_dir String Directory where the staged droplet should live
# @param app_props Hash Application properties.
# @param cc_info Hash Information needed for contacting the CC
# 'host'
# 'port'
# 'task_id'
def run_plugins(source_dir, dest_dir, app_props, cc_info)
environment = {}
droplet = VCAP::Stager::Droplet.new(dest_dir)
services_client = VCAP::CloudController::Ipc::ServiceConsumerV1Client.new(cc_info['host'],
cc_info['port'],
:staging_task_id => cc_info['task_id'])
logger = VCAP::Logging.logger('vcap.stager.plugin_runner')

framework_plugin, feature_plugins = collect_plugins(app_props['framework'], app_props['plugins'].keys)

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,
services_client,
environment)
logger.info("Running framework plugin: #{framework_plugin.name}")
framework_plugin.stage(droplet.app_source_dir, actions, app_props)

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,
@services_client,
@environment)
@logger.info("Running feature plugin: #{feature_plugin.name}")
feature_plugin.stage(framework_plugin, @droplet.app_source_dir, actions, @app_properties)
actions = VCAP::Stager::PluginActionProxy.new(droplet.feature_start_path(pname),
droplet.feature_stop_path(pname),
droplet,
services_client,
environment)
logger.info("Running feature plugin: #{feature_plugin.name}")
feature_plugin.stage(framework_plugin, droplet.app_source_dir, actions, app_props)
end

@droplet.generate_vcap_start_script(@environment)
droplet.generate_vcap_start_script(environment)
end

# Serializes *self* to the supplied path
# Returns the plugins that will be executed, in order.
#
# @param dest_path String Where to serialize ourselves
def to_file(dest_path)
File.open(dest_path, 'w+') do |f|
YAML.dump(self, f)
end
end

protected

def collect_plugins
framework_plugin = nil
# @param framework String Framework of the application being staged
# @param plugins Array[String] List of plugin names
#
# @return Array [framework_plugin, *feature_plugins]
def collect_plugins(framework, plugins)
feature_plugins = []
framework_plugin = nil

for name in @app_properties['plugins'].keys
plugin = VCAP::PluginRegistry.plugins[name]
unless plugin
for name in plugins
plugin = @plugins[name]
unless plugin || plugin.respond_to?(:stage)
raise VCAP::Stager::UnsupportedPluginError, name
end

ptype = plugin.staging_plugin_type
case ptype
when :framework
@logger.debug("Found framework plugin: #{name}")
if framework_plugin
errstr = [framework_plugin.name, name].join(', ')
raise VCAP::Stager::DuplicateFrameworkPluginError, errstr
if plugin.respond_to?(:framework)
if plugin.framework != framework
raise VCAP::Stager::DuplicateFrameworkPluginError, plugin.name
else
framework_plugin = plugin
end

when :feature
@logger.debug("Found feature plugin: #{name}")
feature_plugins << plugin

else
raise VCAP::Stager::UnknownPluginTypeError, ptype

feature_plugins << plugin
end
end

unless framework_plugin
raise VCAP::Stager::MissingFrameworkPluginError
end
Expand Down
17 changes: 9 additions & 8 deletions stager/lib/vcap/stager/task.rb
Expand Up @@ -251,13 +251,14 @@ def run_staging_plugin(src_dir, dst_dir, work_dir, task_logger)
# @param dst_dir String Where to place the staged app
# @param base_dir String Directory to use to place scratch files (houses {src,dst} dir)
def run_plugins(src_dir, dst_dir, base_dir)
runner_path = File.join(base_dir, 'plugin_runner')
gemfile_path = File.join(base_dir, 'Gemfile')
error_path = File.join(base_dir, 'plugin_runner_error')
task_path = File.join(base_dir, 'task')
task_opts = {
'log_path' => File.join(base_dir, 'staging.log'),
'error_path' => File.join(base_dir, 'plugin_runner_error'),
}

runner = VCAP::Stager::PluginRunner.new(src_dir, dst_dir, @app_props, @cc_info)
runner.to_file(runner_path)
runner_cmd = "#{@ruby_path} %s %s" % [VCAP::Stager::PluginRunner::RUNNER_BIN_PATH, error_path]
VCAP::Stager::PluginRunner.serialize_task(task_path, src_dir, dst_dir, @app_props, @cc_info, task_opts)
runner_cmd = "#{@ruby_path} %s %s" % [VCAP::Stager::PluginRunner::RUNNER_BIN_PATH, task_path]

@vcap_logger.debug("Executing plugin runner with command #{runner_cmd}")
res = run_logged(runner_cmd, 0, @max_staging_duration)
Expand All @@ -270,9 +271,9 @@ def run_plugins(src_dir, dst_dir, base_dir)
@vcap_logger.error("Failed running staging plugins")

begin
err = YAML.load_file(error_path)
err = YAML.load_file(task_opts['error_path'])
rescue => e
@vcap_logger.error("Failed reading error file at #{error_path}")
@vcap_logger.error("Failed reading error file at #{task_opts['error_path']}")
@vcap_logger.error(e)
# We can't communicate the specific reason that staging failed (if any),
# so just raise a generic error.
Expand Down
48 changes: 19 additions & 29 deletions stager/spec/unit/plugin_runner_spec.rb
Expand Up @@ -27,7 +27,6 @@
'fds' => 1024,
}
}
VCAP::PluginRegistry.plugins = {}
end

after :each do
Expand All @@ -37,61 +36,52 @@

it 'should raise an error for unknown plugins' do
@app_props['plugins']['unknown'] = {}
orch = VCAP::Stager::PluginRunner.new(@src_dir, @dst_dir, @app_props, @cc_info)
runner = VCAP::Stager::PluginRunner.new
expect do
orch.run_plugins
runner.run_plugins(@src_dir, @dst_dir, @app_props, @cc_info)
end.to raise_error(VCAP::Stager::UnsupportedPluginError)
end

it 'should raise an error if no framework plugin is supplied' do
orch = VCAP::Stager::PluginRunner.new(@src_dir, @dst_dir, @app_props, @cc_info)
runner = VCAP::Stager::PluginRunner.new
expect do
orch.run_plugins
runner.run_plugins(@src_dir, @dst_dir, @app_props, @cc_info)
end.to raise_error(VCAP::Stager::MissingFrameworkPluginError)
end

it 'should raise an error if > 1 framework plugins are supplied' do
plugins = []
frameworks = ['sinatra', 'unknown']
plugins = {}
2.times do |i|
name = "plugin_#{i}"
@app_props['plugins'][name] = {}
p = create_mock_plugin(name, :framework)
VCAP::PluginRegistry.register_plugins(p)
plugins[name] = create_mock_plugin(name, frameworks[i])
end
orch = VCAP::Stager::PluginRunner.new(@src_dir, @dst_dir, @app_props, @cc_info)
runner = VCAP::Stager::PluginRunner.new(plugins)
expect do
orch.run_plugins
runner.run_plugins(@src_dir, @dst_dir, @app_props, @cc_info)
end.to raise_error(VCAP::Stager::DuplicateFrameworkPluginError)
end

it 'should raise an error if a plugin of unknown type is supplied' do
p = create_mock_plugin('plugin0', :invalid_plugin_type)
VCAP::PluginRegistry.register_plugins(p)
@app_props['plugins']['plugin0'] = {}
orch = VCAP::Stager::PluginRunner.new(@src_dir, @dst_dir, @app_props, @cc_info)
expect do
orch.run_plugins
end.to raise_error(VCAP::Stager::UnknownPluginTypeError)
end

it 'should call stage on each of the registered plugins' do
plugin_types = [:framework, :feature, :feature]
plugin_types.each_with_index do |ptype, ii|
plugins = {}
frameworks = ['sinatra', nil, nil]
frameworks.each_with_index do |framework, ii|
name = "plugin_#{ii}"
@app_props['plugins'][name] = {}
p = create_mock_plugin(name, ptype)
p = create_mock_plugin(name, framework)
p.should_receive(:stage).with(any_args())
VCAP::PluginRegistry.register_plugins(p)
plugins[name] = p
end
orch = VCAP::Stager::PluginRunner.new(@src_dir, @dst_dir, @app_props, @cc_info)
orch.run_plugins
runner = VCAP::Stager::PluginRunner.new(plugins)
runner.run_plugins(@src_dir, @dst_dir, @app_props, @cc_info)
end
end

def create_mock_plugin(name, type)
ret = mock(name)
ret.stub(:staging_plugin_type).and_return(type)
def create_mock_plugin(name, framework=nil)
ret = mock()
ret.stub(:name).and_return(name)
ret.stub(:framework).and_return(framework) if framework
ret
end
end

0 comments on commit e0d2170

Please sign in to comment.