summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordanielsdeleo <dan@opscode.com>2012-12-27 09:38:02 -0700
committerdanielsdeleo <dan@opscode.com>2012-12-27 09:38:02 -0700
commitd7fb0ef322b26a843056d95a6241614ad43cc064 (patch)
treeb9f0c608334605c3479c1de53de9939c329ab18c
parentf132b98c4faddcaee5002105a6aa647bbb549c19 (diff)
parent1fbf3f48454ae47e13cdb502ad5d040865a9989a (diff)
downloadchef-d7fb0ef322b26a843056d95a6241614ad43cc064.tar.gz
Merge branch 'CHEF-3681'
-rw-r--r--lib/chef/provider/lwrp_base.rb74
-rw-r--r--lib/chef/resource_collection.rb46
-rw-r--r--spec/data/lwrp/providers/inline_compiler.rb26
-rw-r--r--spec/unit/lwrp_spec.rb42
4 files changed, 167 insertions, 21 deletions
diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb
index 1ae17e288c..90ce70ae61 100644
--- a/lib/chef/provider/lwrp_base.rb
+++ b/lib/chef/provider/lwrp_base.rb
@@ -27,6 +27,48 @@ class Chef
# Base class from which LWRP providers inherit.
class LWRPBase < Provider
+ # Chef::Provider::LWRPBase::InlineResources
+ # Implementation of inline resource convergence for LWRP providers. See
+ # Provider::LWRPBase.use_inline_resources for a longer explanation.
+ #
+ # This code is restricted to a module so that it can be selectively
+ # applied to providers on an opt-in basis.
+ module InlineResources
+
+ # Class methods for InlineResources. Overrides the `action` DSL method
+ # with one that enables inline resource convergence.
+ module ClassMethods
+ # Defines an action method on the provider, using
+ # recipe_eval_with_update_check to execute the given block.
+ def action(name, &block)
+ define_method("action_#{name}") do
+ recipe_eval_with_update_check(&block)
+ end
+ end
+ end
+
+ # Executes the given block in a temporary run_context with its own
+ # resource collection. After the block is executed, any resources
+ # declared inside are converged, and if any are updated, the
+ # new_resource will be marked updated.
+ def recipe_eval_with_update_check(&block)
+ saved_run_context = @run_context
+ temp_run_context = @run_context.dup
+ @run_context = temp_run_context
+ @run_context.resource_collection = Chef::ResourceCollection.new
+
+ return_value = instance_eval(&block)
+ Chef::Runner.new(@run_context).converge
+ return_value
+ ensure
+ @run_context = saved_run_context
+ if temp_run_context.resource_collection.any? {|r| r.updated? }
+ new_resource.updated_by_last_action(true)
+ end
+ end
+
+ end
+
extend Chef::Mixin::ConvertToClassName
extend Chef::Mixin::FromFile
@@ -58,6 +100,38 @@ class Chef
provider_class
end
+ # Enables inline evaluation of resources in provider actions.
+ #
+ # Without this option, any resources declared inside the LWRP are added
+ # to the resource collection after the current position at the time the
+ # action is executed. Because they are added to the primary resource
+ # collection for the chef run, they can notify other resources outside
+ # the LWRP, and potentially be notified by resources outside the LWRP
+ # (but this is complicated by the fact that they don't exist until the
+ # provider executes). In this mode, it is impossible to correctly set the
+ # updated_by_last_action flag on the parent LWRP resource, since it
+ # executes and returns before its component resources are run.
+ #
+ # With this option enabled, each action creates a temporary run_context
+ # with its own resource collection, evaluates the action's code in that
+ # context, and then converges the resources created. If any resources
+ # were updated, then this provider's new_resource will be marked updated.
+ #
+ # In this mode, resources created within the LWRP cannot interact with
+ # external resources via notifies, though notifications to other
+ # resources within the LWRP will work. Delayed notifications are executed
+ # at the conclusion of the provider's action, *not* at the end of the
+ # main chef run.
+ #
+ # This mode of evaluation is experimental, but is believed to be a better
+ # set of tradeoffs than the append-after mode, so it will likely become
+ # the default in a future major release of Chef.
+ #
+ def self.use_inline_resources
+ extend InlineResources::ClassMethods
+ include InlineResources
+ end
+
# DSL for defining a provider's actions.
def self.action(name, &block)
define_method("action_#{name}") do
diff --git a/lib/chef/resource_collection.rb b/lib/chef/resource_collection.rb
index 51858c53d8..baf959c33b 100644
--- a/lib/chef/resource_collection.rb
+++ b/lib/chef/resource_collection.rb
@@ -7,9 +7,9 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -23,7 +23,7 @@ require 'chef/resource_collection/stepable_iterator'
class Chef
class ResourceCollection
include Enumerable
-
+
attr_reader :iterator
def initialize
@@ -31,18 +31,18 @@ class Chef
@resources_by_name = Hash.new
@insert_after_idx = nil
end
-
+
def all_resources
@resources
end
-
+
def [](index)
@resources[index]
end
-
+
def []=(index, arg)
is_chef_resource(arg)
- @resources[index] = arg
+ @resources[index] = arg
@resources_by_name[arg.to_s] = index
end
@@ -50,7 +50,7 @@ class Chef
args.flatten.each do |a|
is_chef_resource(a)
@resources << a
- @resources_by_name[a.to_s] = @resources.length - 1
+ @resources_by_name[a.to_s] = @resources.length - 1
end
end
@@ -67,12 +67,12 @@ class Chef
end
@resources_by_name[resource.to_s] = @insert_after_idx + 1
@insert_after_idx += 1
- else
+ else
@resources << resource
@resources_by_name[resource.to_s] = @resources.length - 1
end
end
-
+
def push(*args)
args.flatten.each do |arg|
is_chef_resource(arg)
@@ -80,7 +80,7 @@ class Chef
@resources_by_name[arg.to_s] = @resources.length - 1
end
end
-
+
def each
@resources.each do |resource|
yield resource
@@ -94,13 +94,17 @@ class Chef
yield resource
end
end
-
+
def each_index
@resources.each_index do |i|
yield i
end
end
-
+
+ def empty?
+ @resources.empty?
+ end
+
def lookup(resource)
lookup_by = nil
if resource.kind_of?(Chef::Resource)
@@ -125,7 +129,7 @@ class Chef
# find("file[foobar]", "file[baz]")
# find("file[foobar,baz]")
#
- # Returns the matching resource, or an Array of matching resources.
+ # Returns the matching resource, or an Array of matching resources.
#
# Raises an ArgumentError if you feed it bad lookup information
# Raises a Runtime Error if it can't find the resources you are looking for.
@@ -145,12 +149,12 @@ class Chef
flat_results = results.flatten
flat_results.length == 1 ? flat_results[0] : flat_results
end
-
+
# resources is a poorly named, but we have to maintain it for back
# compat.
alias_method :resources, :find
-
- # Serialize this object as a hash
+
+ # Serialize this object as a hash
def to_json(*a)
instance_vars = Hash.new
self.instance_variables.each do |iv|
@@ -162,7 +166,7 @@ class Chef
}
results.to_json(*a)
end
-
+
def self.json_create(o)
collection = self.new()
o["instance_vars"].each do |k,v|
@@ -172,7 +176,7 @@ class Chef
end
private
-
+
def find_resource_by_hash(arg)
results = Array.new
arg.each do |resource_name, name_list|
@@ -193,7 +197,7 @@ class Chef
arg =~ /^.+\[(.+)\]$/
resource_list = $1
resource_list.split(",").each do |name|
- resource_name = "#{resource_type}[#{name}]"
+ resource_name = "#{resource_type}[#{name}]"
results << lookup(resource_name)
end
when /^(.+)\[(.+)\]$/
@@ -209,7 +213,7 @@ class Chef
def is_chef_resource(arg)
unless arg.kind_of?(Chef::Resource)
- raise ArgumentError, "Members must be Chef::Resource's"
+ raise ArgumentError, "Members must be Chef::Resource's"
end
true
end
diff --git a/spec/data/lwrp/providers/inline_compiler.rb b/spec/data/lwrp/providers/inline_compiler.rb
new file mode 100644
index 0000000000..2535276b24
--- /dev/null
+++ b/spec/data/lwrp/providers/inline_compiler.rb
@@ -0,0 +1,26 @@
+
+use_inline_resources
+
+action :test do
+
+ ruby_block "interior-ruby-block-1" do
+ block do
+ # doesn't need to do anything
+ end
+ notifies :run, "ruby_block[interior-ruby-block-2]", :immediately
+ end
+
+ ruby_block "interior-ruby-block-2" do
+ block do
+ $interior_ruby_block_2 = "executed"
+ end
+ action :nothing
+ end
+end
+
+action :no_updates do
+ ruby_block "no-action" do
+ block {}
+ action :nothing
+ end
+end
diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb
index b28fd812a2..a158bce440 100644
--- a/spec/unit/lwrp_spec.rb
+++ b/spec/unit/lwrp_spec.rb
@@ -226,6 +226,48 @@ describe "LWRP" do
provider.enclosed_resource.monkey.should == 'bob, the monkey'
end
+ describe "when using inline compilation" do
+ before do
+ # Behavior in these examples depends on implementation of fixture provider.
+ # See spec/data/lwrp/providers/inline_compiler
+
+ # Side effect of lwrp_inline_compiler provider for testing notifications.
+ $interior_ruby_block_2 = nil
+ # resource type doesn't matter, so make an existing resource type work with provider.
+ @resource = Chef::Resource::LwrpFoo.new("morpheus", @run_context)
+ @resource.allowed_actions << :test
+ @resource.action(:test)
+ @resource.provider(:lwrp_inline_compiler)
+ end
+
+ it "does not add interior resources to the exterior resource collection" do
+ @resource.run_action(:test)
+ @run_context.resource_collection.should be_empty
+ end
+
+ context "when interior resources are updated" do
+ it "processes notifications within the LWRP provider's action" do
+ @resource.run_action(:test)
+ $interior_ruby_block_2.should == "executed"
+ end
+
+ it "marks the parent resource updated" do
+ @resource.run_action(:test)
+ @resource.should be_updated
+ @resource.should be_updated_by_last_action
+ end
+ end
+
+ context "when interior resources are not updated" do
+ it "does not mark the parent resource updated" do
+ @resource.run_action(:no_updates)
+ @resource.should_not be_updated
+ @resource.should_not be_updated_by_last_action
+ end
+ end
+
+ end
+
end
end