Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Scaffolding for staging plugins

This diff includes the skeleton for the new staging plugins.
Future commits will flesh out the functionality of the ActionProxy.

Test plan:
- New unit tests pass.

Change-Id: Icfe1ef6fc4652f105999a1e1573984fdef20f756
  • Loading branch information...
commit 9329becf331e9a6e3bad25da6405dcef009ef684 1 parent ae924b8
mpage authored
View
7 stager/lib/vcap/stager.rb
@@ -1,11 +1,16 @@
+require 'vcap/stager/app_properties'
require 'vcap/stager/constants'
require 'vcap/stager/config'
-require 'vcap/stager/server'
+require 'vcap/stager/plugin_action_proxy'
+require 'vcap/stager/plugin_orchestrator'
+require 'vcap/stager/plugin_orchestrator_error'
+require 'vcap/stager/plugin_registry'
require 'vcap/stager/task'
require 'vcap/stager/task_error'
require 'vcap/stager/task_logger'
require 'vcap/stager/task_manager'
require 'vcap/stager/task_result'
+require 'vcap/stager/server'
require 'vcap/stager/util'
require 'vcap/stager/version'
View
71 stager/lib/vcap/stager/app_properties.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
View
42 stager/lib/vcap/stager/plugin_action_proxy.rb
@@ -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
View
63 stager/lib/vcap/stager/plugin_orchestrator.rb
@@ -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
View
8 stager/lib/vcap/stager/plugin_orchestrator_error.rb
@@ -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
View
35 stager/lib/vcap/stager/plugin_registry.rb
@@ -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
View
80 stager/spec/unit/plugin_orchestrator_spec.rb
@@ -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
Please sign in to comment.
Something went wrong with that request. Please try again.