Permalink
Please sign in to comment.
Browse files
Merge "Scaffolding for staging plugins" into staging_plugin
- Loading branch information...
Showing
with
305 additions
and 1 deletion.
- +6 −1 stager/lib/vcap/stager.rb
- +71 −0 stager/lib/vcap/stager/app_properties.rb
- +42 −0 stager/lib/vcap/stager/plugin_action_proxy.rb
- +63 −0 stager/lib/vcap/stager/plugin_orchestrator.rb
- +8 −0 stager/lib/vcap/stager/plugin_orchestrator_error.rb
- +35 −0 stager/lib/vcap/stager/plugin_registry.rb
- +80 −0 stager/spec/unit/plugin_orchestrator_spec.rb
@@ -0,0 +1,71 @@ | ||
+require 'yajl' | ||
+ | ||
+require 'vcap/json_schema' | ||
+ | ||
+module VCAP | ||
+ module Stager | ||
+ end | ||
+end | ||
+ | ||
+class VCAP::Stager::AppProperties | ||
+ SCHEMA = VCAP::JsonSchema.build do | ||
+ { :name => String, | ||
+ :framework => String, | ||
+ :runtime => String, | ||
+ :plugins => Hash, | ||
+ :environment => Hash, | ||
+ :resource_limits => { | ||
+ :memory => Integer, | ||
+ :disk => Integer, | ||
+ :fds => Integer, | ||
+ }, | ||
+ :service_bindings => Array, | ||
+ } | ||
+ end | ||
+ | ||
+ class << self | ||
+ def decode(enc) | ||
+ dec = Yajl::Parser.parse(enc, :symbolize_keys => true) | ||
+ SCHEMA.validate(dec) | ||
+ VCAP::Stager::AppProperties.new(dec[:name], | ||
+ dec[:framework], | ||
+ dec[:runtime], | ||
+ dec[:plugins], | ||
+ dec[:environment], | ||
+ dec[:resource_limits], | ||
+ dec[:service_bindings]) | ||
+ end | ||
+ end | ||
+ | ||
+ attr_accessor :name | ||
+ attr_accessor :framework | ||
+ attr_accessor :runtime | ||
+ attr_accessor :plugins | ||
+ attr_accessor :environment | ||
+ attr_accessor :resource_limits | ||
+ attr_accessor :service_bindings | ||
+ | ||
+ def initialize(name, framework, runtime, plugins, environment, resource_limits, service_bindings) | ||
+ @name = name | ||
+ @framework = framework | ||
+ @runtime = runtime | ||
+ @plugins = plugins | ||
+ @environment = environment | ||
+ @resource_limits = resource_limits | ||
+ @service_bindings = service_bindings | ||
+ end | ||
+ | ||
+ def encode | ||
+ h = { | ||
+ :name => @name, | ||
+ :framework => @framework, | ||
+ :runtime => @runtime, | ||
+ :plugins => @plugins, | ||
+ :environment => @environment, | ||
+ :resource_limits => @resource_limits, | ||
+ :service_bindings => @service_bindings, | ||
+ } | ||
+ Yajl::Encoder.encode(h) | ||
+ end | ||
+ | ||
+end |
@@ -0,0 +1,42 @@ | ||
+module VCAP | ||
+ module Stager | ||
+ end | ||
+end | ||
+ | ||
+# 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 | ||
+ # Creates a service on behalf of the user | ||
+ # | ||
+ # @param label String The label that uniquely identifies this service | ||
+ # @param name String What to call the provisioned service | ||
+ # @param plan String Which plan should be provisioned | ||
+ # @param plan_option String Optional plan option to select. | ||
+ def create_service(label, name, plan, plan_option=nil) | ||
+ raise NotImplementedError | ||
+ end | ||
+ | ||
+ # Binds a service to the application being staged | ||
+ # | ||
+ # @param name String Name of service to bind | ||
+ # @param binding_options Hash Service specific binding options | ||
+ def bind_service(name, binding_options) | ||
+ raise NotImplementedError | ||
+ end | ||
+ | ||
+ # Returns an open file object that the user can write contents of a | ||
+ # start script into. | ||
+ # | ||
+ # @return File | ||
+ def start_script | ||
+ raise NotImplementedError | ||
+ end | ||
+ | ||
+ # Returns an open file object that the user can write contents of a stop | ||
+ # script into. | ||
+ # | ||
+ # @return File | ||
+ def stop_script | ||
+ raise NotImplementedError | ||
+ end | ||
+end |
@@ -0,0 +1,63 @@ | ||
+require 'rubygems' | ||
+ | ||
+require 'vcap/logging' | ||
+ | ||
+require 'vcap/stager/plugin_action_proxy' | ||
+require 'vcap/stager/plugin_orchestrator_error' | ||
+require 'vcap/stager/plugin_registry' | ||
+ | ||
+module VCAP | ||
+ module Stager | ||
+ end | ||
+end | ||
+ | ||
+# 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 app_properties | ||
+ def initialize(source_dir, app_properties) | ||
+ @source_dir = source_dir | ||
+ @app_properties = app_properties | ||
+ @logger = VCAP::Logging.logger('vcap.stager.plugin_orchestrator') | ||
+ end | ||
+ | ||
+ def run_plugins | ||
+ for name, props in @app_properties.plugins | ||
+ require(name) | ||
+ end | ||
+ | ||
+ framework_plugin = nil | ||
+ feature_plugins = [] | ||
+ for plugin in VCAP::Stager::PluginRegistry.plugins | ||
+ ptype = plugin.plugin_type | ||
+ case ptype | ||
+ when :framework | ||
+ @logger.debug("Found framework 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}") | ||
+ feature_plugins << plugin | ||
+ | ||
+ else | ||
+ raise VCAP::Stager::UnknownPluginTypeError, "Unknown plugin type: #{ptype}" | ||
+ | ||
+ 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 | ||
+ end | ||
+end |
@@ -0,0 +1,8 @@ | ||
+module VCAP | ||
+ module Stager | ||
+ class PluginOrchestratorError < StandardError; end | ||
+ class MissingFrameworkPluginError < PluginOrchestratorError; end | ||
+ class DuplicateFrameworkPluginError < PluginOrchestratorError; end | ||
+ class UnknownPluginTypeError < PluginOrchestratorError; end | ||
+ end | ||
+end |
@@ -0,0 +1,35 @@ | ||
+module VCAP | ||
+ module Stager | ||
+ end | ||
+end | ||
+ | ||
+# This is a simple container class for all plugins that should be invoked during | ||
+# staging. The contract between the stager and plugins is simple: | ||
+# | ||
+# A staging plugin MUST call VCAP::Stager::PluginRegistry.register_plugin() with | ||
+# an instance of itself when it is required. | ||
+class VCAP::Stager::PluginRegistry | ||
+ class << self | ||
+ def method_missing(method, *args, &block) | ||
+ @registry ||= VCAP::Stager::PluginRegistry.new | ||
+ @registry.send(method, *args, &block) | ||
+ end | ||
+ end | ||
+ | ||
+ def initialize | ||
+ @plugins = [] | ||
+ end | ||
+ | ||
+ attr_reader :plugins | ||
+ | ||
+ # Registers a plugin to be invoked during staging. | ||
+ # | ||
+ # @param Object Instance of a class implementing the staging plugin interface | ||
+ def register_plugin(plugin) | ||
+ @plugins << plugin | ||
+ end | ||
+ | ||
+ def reset | ||
+ @plugins = [] | ||
+ end | ||
+end |
@@ -0,0 +1,80 @@ | ||
+require File.join(File.dirname(__FILE__), 'spec_helper') | ||
+ | ||
+require 'fileutils' | ||
+require 'tmpdir' | ||
+ | ||
+describe VCAP::Stager::PluginOrchestrator do | ||
+ describe '#run_plugins' do | ||
+ before :each do | ||
+ @tmpdir = Dir.mktmpdir | ||
+ @app_props = VCAP::Stager::AppProperties.new('test', | ||
+ 'sinatra', | ||
+ 'ruby18', | ||
+ {}, | ||
+ {}, | ||
+ { :memory => 128, | ||
+ :disk => 2048, | ||
+ :fds => 1024}, | ||
+ :service_bindings => []) | ||
+ VCAP::Stager::PluginRegistry.reset() | ||
+ end | ||
+ | ||
+ after :each do | ||
+ FileUtils.rm_rf(@tmpdir) | ||
+ end | ||
+ | ||
+ it 'should raise an error for unknown plugins' do | ||
+ @app_props.plugins = {'unknown_plugin' => {}} | ||
+ orch = VCAP::Stager::PluginOrchestrator.new(@tmpdir, @app_props) | ||
+ expect do | ||
+ orch.run_plugins | ||
+ end.to raise_error(LoadError) | ||
+ end | ||
+ | ||
+ it 'should raise an error if no framework plugin is supplied' do | ||
+ orch = VCAP::Stager::PluginOrchestrator.new(@tmpdir, @app_props) | ||
+ expect do | ||
+ orch.run_plugins | ||
+ end.to raise_error(VCAP::Stager::MissingFrameworkPluginError) | ||
+ end | ||
+ | ||
+ it 'should raise an error if > 1 framework plugins are supplied' do | ||
+ plugins = [] | ||
+ 2.times do |i| | ||
+ p = create_mock_plugin("plugin_#{i}", :framework) | ||
+ VCAP::Stager::PluginRegistry.register_plugin(p) | ||
+ end | ||
+ orch = VCAP::Stager::PluginOrchestrator.new(@tmpdir, @app_props) | ||
+ expect do | ||
+ orch.run_plugins | ||
+ 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::Stager::PluginRegistry.register_plugin(p) | ||
+ orch = VCAP::Stager::PluginOrchestrator.new(@tmpdir, @app_props) | ||
+ 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| | ||
+ p = create_mock_plugin("plugin_#{ii}", ptype) | ||
+ p.should_receive(:stage).with(any_args()) | ||
+ VCAP::Stager::PluginRegistry.register_plugin(p) | ||
+ end | ||
+ orch = VCAP::Stager::PluginOrchestrator.new(@tmpdir, @app_props) | ||
+ orch.run_plugins | ||
+ end | ||
+ end | ||
+ | ||
+ def create_mock_plugin(name, type) | ||
+ ret = mock(name) | ||
+ ret.stub(:plugin_type).and_return(type) | ||
+ ret.stub(:name).and_return(name) | ||
+ ret | ||
+ end | ||
+end |
0 comments on commit
22ef9dc