diff options
author | danielsdeleo <dan@opscode.com> | 2012-12-27 09:38:02 -0700 |
---|---|---|
committer | danielsdeleo <dan@opscode.com> | 2012-12-27 09:38:02 -0700 |
commit | d7fb0ef322b26a843056d95a6241614ad43cc064 (patch) | |
tree | b9f0c608334605c3479c1de53de9939c329ab18c | |
parent | f132b98c4faddcaee5002105a6aa647bbb549c19 (diff) | |
parent | 1fbf3f48454ae47e13cdb502ad5d040865a9989a (diff) | |
download | chef-d7fb0ef322b26a843056d95a6241614ad43cc064.tar.gz |
Merge branch 'CHEF-3681'
-rw-r--r-- | lib/chef/provider/lwrp_base.rb | 74 | ||||
-rw-r--r-- | lib/chef/resource_collection.rb | 46 | ||||
-rw-r--r-- | spec/data/lwrp/providers/inline_compiler.rb | 26 | ||||
-rw-r--r-- | spec/unit/lwrp_spec.rb | 42 |
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 |