diff options
author | Lamont Granquist <lamont@chef.io> | 2019-08-15 17:10:06 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-15 17:10:06 -0700 |
commit | f24218b63b44353cdaa2b1bbe1a6eddaf60bbb01 (patch) | |
tree | bb2eadb1f0878f58e6df8bfe1a278ab8311bf283 /lib | |
parent | 1ac98e9fd013496e131ec98201e05f675e444374 (diff) | |
parent | cd32e8e600bd2ee48de9d9032bac5726635033df (diff) | |
download | chef-f24218b63b44353cdaa2b1bbe1a6eddaf60bbb01.tar.gz |
Add unified_mode for resources (#8668)
Add unified_mode for resources
Diffstat (limited to 'lib')
-rw-r--r-- | lib/chef/exceptions.rb | 12 | ||||
-rw-r--r-- | lib/chef/provider.rb | 6 | ||||
-rw-r--r-- | lib/chef/resource.rb | 48 | ||||
-rw-r--r-- | lib/chef/resource/resource_notification.rb | 30 | ||||
-rw-r--r-- | lib/chef/resource_collection.rb | 6 | ||||
-rw-r--r-- | lib/chef/run_context.rb | 78 | ||||
-rw-r--r-- | lib/chef/runner.rb | 62 |
7 files changed, 171 insertions, 71 deletions
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index 7687e7d89c..40d20cc3ac 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -509,5 +509,17 @@ class Chef super "Conflicting requirements for gem '#{gem_name}': Both #{value1.inspect} and #{value2.inspect} given for option #{option.inspect}" end end + + class UnifiedModeImmediateSubscriptionEarlierResource < RuntimeError + def initialize(notification) + super "immediate subscription from #{notification.resource} resource cannot be setup to #{notification.notifying_resource} resource, which has already fired while in unified mode" + end + end + + class UnifiedModeBeforeSubscriptionEarlierResource < RuntimeError + def initialize(notification) + super "before subscription from #{notification.resource} resource cannot be setup to #{notification.notifying_resource} resource, which has already fired while in unified mode" + end + end end end diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb index 6d1985bbbb..fb5697fd0c 100644 --- a/lib/chef/provider.rb +++ b/lib/chef/provider.rb @@ -1,7 +1,7 @@ # # Author:: Adam Jacob (<adam@chef.io>) # Author:: Christopher Walters (<cw@chef.io>) -# Copyright:: Copyright 2008-2016, 2009-2018, Chef Software Inc. +# Copyright:: Copyright 2008-2016, 2009-2019, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -238,8 +238,10 @@ class Chef def compile_and_converge_action(&block) old_run_context = run_context @run_context = run_context.create_child + @run_context.resource_collection.unified_mode = new_resource.class.unified_mode + runner = Chef::Runner.new(@run_context) return_value = instance_eval(&block) - Chef::Runner.new(run_context).converge + runner.converge return_value ensure if run_context.resource_collection.any?(&:updated?) diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 7f0895d6c9..a413e3e8d4 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -182,7 +182,7 @@ class Chef { action: { kind_of: Symbol, equal_to: allowed_actions } } ) # the resource effectively sends a delayed notification to itself - run_context.add_delayed_action(Notification.new(self, action, self)) + run_context.add_delayed_action(Notification.new(self, action, self, run_context.unified_mode)) end end @@ -453,7 +453,6 @@ class Chef # attr_reader :elapsed_time - # # @return [Boolean] If the resource was executed by the runner # attr_accessor :executed_by_runner @@ -985,6 +984,16 @@ class Chef resource_name automatic_name end + # If the resource's action should run in separated compile/converge mode. + # + # @param flag [Boolean] value to set unified_mode to + # @return [Boolean] unified_mode value + def self.unified_mode(flag = nil) + @unified_mode = Chef::Config[:resource_unified_mode_default] if @unified_mode.nil? + @unified_mode = flag unless flag.nil? + !!@unified_mode + end + # # The list of allowed actions for the resource. # @@ -1038,7 +1047,6 @@ class Chef default_action action_name end - # # Define an action on this resource. # # The action is defined as a *recipe* block that will be compiled and then @@ -1076,7 +1084,6 @@ class Chef default_action action if Array(default_action) == [:nothing] end - # # Define a method to load up this resource's properties with the current # actual values. # @@ -1087,7 +1094,6 @@ class Chef define_method(:load_current_value!, &load_block) end - # # Call this in `load_current_value` to indicate that the value does not # exist and that `current_resource` should therefore be `nil`. # @@ -1097,7 +1103,6 @@ class Chef raise Chef::Exceptions::CurrentValueDoesNotExist end - # # Get the current actual value of this resource. # # This does not cache--a new value will be returned each time. @@ -1154,7 +1159,6 @@ class Chef end end - # # Ensure the action class actually gets created. This is called # when the user does `action :x do ... end`. # @@ -1211,22 +1215,27 @@ class Chef # @return [Chef::RunContext] The run context for this Resource. This is # where the context for the current Chef run is stored, including the node # and the resource collection. + # attr_accessor :run_context # @return [Mixlib::Log::Child] The logger for this resources. This is a child # of the run context's logger, if one exists. + # attr_reader :logger # @return [String] The cookbook this resource was declared in. + # attr_accessor :cookbook_name # @return [String] The recipe this resource was declared in. + # attr_accessor :recipe_name # @return [Chef::Provider] The provider this resource was declared in (if # it was declared in an LWRP). When you call methods that do not exist # on this Resource, Chef will try to call the method on the provider # as well before giving up. + # attr_accessor :enclosing_provider # @return [String] The source line where this resource was declared. @@ -1234,6 +1243,7 @@ class Chef # of these formats: # /some/path/to/file.rb:80:in `wombat_tears' # C:/some/path/to/file.rb:80 in 1`wombat_tears' + # attr_accessor :source_line # @return [String] The actual name that was used to create this resource. @@ -1242,37 +1252,40 @@ class Chef # user will expect to see the thing they wrote, not the type that was # returned. May be `nil`, in which case callers should read #resource_name. # See #declared_key. + # attr_accessor :declared_type - # # Iterates over all immediate and delayed notifications, calling # resolve_resource_reference on each in turn, causing them to # resolve lazy/forward references. - def resolve_notification_references + # + def resolve_notification_references(always_raise = false) run_context.before_notifications(self).each do |n| - n.resolve_resource_reference(run_context.resource_collection) + n.resolve_resource_reference(run_context.resource_collection, true) end + run_context.immediate_notifications(self).each do |n| - n.resolve_resource_reference(run_context.resource_collection) + n.resolve_resource_reference(run_context.resource_collection, always_raise) end + run_context.delayed_notifications(self).each do |n| - n.resolve_resource_reference(run_context.resource_collection) + n.resolve_resource_reference(run_context.resource_collection, always_raise) end end # Helper for #notifies def notifies_before(action, resource_spec) - run_context.notifies_before(Notification.new(resource_spec, action, self)) + run_context.notifies_before(Notification.new(resource_spec, action, self, run_context.unified_mode)) end # Helper for #notifies def notifies_immediately(action, resource_spec) - run_context.notifies_immediately(Notification.new(resource_spec, action, self)) + run_context.notifies_immediately(Notification.new(resource_spec, action, self, run_context.unified_mode)) end # Helper for #notifies def notifies_delayed(action, resource_spec) - run_context.notifies_delayed(Notification.new(resource_spec, action, self)) + run_context.notifies_delayed(Notification.new(resource_spec, action, self, run_context.unified_mode)) end class << self @@ -1321,7 +1334,6 @@ class Chef end end - # # This API can be used for backcompat to do: # # chef_version_for_provides "< 14.0" if defined?(:chef_version_for_provides) @@ -1343,7 +1355,6 @@ class Chef @chef_version_for_provides = constraint end - # # Mark this resource as providing particular DSL. # # Resources have an automatic DSL based on their resource_name, equivalent to @@ -1472,7 +1483,6 @@ class Chef @default_description end - # # The cookbook in which this Resource was defined (if any). # # @return Chef::CookbookVersion The cookbook in which this Resource was defined. @@ -1498,7 +1508,6 @@ class Chef provider end - # # Preface an exception message with generic Resource information. # # @param e [StandardError] An exception with `e.message` @@ -1554,7 +1563,6 @@ class Chef klass end - # # Returns the class with the given resource_name. # # NOTE: Chef::Resource.resource_matching_short_name(:package) returns diff --git a/lib/chef/resource/resource_notification.rb b/lib/chef/resource/resource_notification.rb index 7e93fff433..d3b9856332 100644 --- a/lib/chef/resource/resource_notification.rb +++ b/lib/chef/resource/resource_notification.rb @@ -1,6 +1,6 @@ # # Author:: Tyler Ball (<tball@chef.io>) -# Copyright:: Copyright 2014-2016, Chef Software, Inc. +# Copyright:: Copyright 2014-2019, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,12 +26,13 @@ class Chef # @attr [Resource] notifying_resource the Chef resource performing the notification class Notification - attr_accessor :resource, :action, :notifying_resource + attr_accessor :resource, :action, :notifying_resource, :unified_mode - def initialize(resource, action, notifying_resource) + def initialize(resource, action, notifying_resource, unified_mode = false) @resource = resource @action = action&.to_sym @notifying_resource = notifying_resource + @unified_mode = unified_mode end # Is the current notification a duplicate of another notification @@ -52,11 +53,11 @@ class Chef # @param [ResourceCollection] resource_collection # # @return [void] - def resolve_resource_reference(resource_collection) + def resolve_resource_reference(resource_collection, always_raise = false) return resource if resource.is_a?(Chef::Resource) && notifying_resource.is_a?(Chef::Resource) unless resource.is_a?(Chef::Resource) - fix_resource_reference(resource_collection) + fix_resource_reference(resource_collection, always_raise) end unless notifying_resource.is_a?(Chef::Resource) @@ -69,7 +70,7 @@ class Chef # @param [ResourceCollection] resource_collection # # @return [void] - def fix_resource_reference(resource_collection) + def fix_resource_reference(resource_collection, always_raise = false) matching_resource = resource_collection.find(resource) if Array(matching_resource).size > 1 msg = "Notification #{self} from #{notifying_resource} was created with a reference to multiple resources, "\ @@ -79,13 +80,16 @@ class Chef self.resource = matching_resource rescue Chef::Exceptions::ResourceNotFound => e - err = Chef::Exceptions::ResourceNotFound.new(<<~FAIL) - resource #{notifying_resource} is configured to notify resource #{resource} with action #{action}, \ - but #{resource} cannot be found in the resource collection. #{notifying_resource} is defined in \ - #{notifying_resource.source_line} - FAIL - err.set_backtrace(e.backtrace) - raise err + # in unified mode we allow lazy notifications to resources not yet declared + if !unified_mode || always_raise + err = Chef::Exceptions::ResourceNotFound.new(<<~FAIL) + resource #{notifying_resource} is configured to notify resource #{resource} with action #{action}, \ + but #{resource} cannot be found in the resource collection. #{notifying_resource} is defined in \ + #{notifying_resource.source_line} + FAIL + err.set_backtrace(e.backtrace) + raise err + end rescue Chef::Exceptions::InvalidResourceSpecification => e err = Chef::Exceptions::InvalidResourceSpecification.new(<<~F) Resource #{notifying_resource} is configured to notify resource #{resource} with action #{action}, \ diff --git a/lib/chef/resource_collection.rb b/lib/chef/resource_collection.rb index 3b6ff4297e..da675fc4de 100644 --- a/lib/chef/resource_collection.rb +++ b/lib/chef/resource_collection.rb @@ -32,6 +32,8 @@ class Chef include ResourceCollectionSerialization extend Forwardable + attr_accessor :unified_mode + attr_reader :resource_set, :resource_list attr_accessor :run_context @@ -41,6 +43,7 @@ class Chef @run_context = run_context @resource_set = ResourceSet.new @resource_list = ResourceList.new + @unified_mode = false end # @param resource [Chef::Resource] The resource to insert @@ -57,6 +60,9 @@ class Chef else resource_set.insert_as(resource) end + if unified_mode + run_context.runner.run_all_actions(resource) + end end def delete(key) diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb index eb211dc5a5..7a5924a3c7 100644 --- a/lib/chef/run_context.rb +++ b/lib/chef/run_context.rb @@ -26,12 +26,16 @@ require_relative "recipe" require_relative "run_context/cookbook_compiler" require_relative "event_dispatch/events_output_stream" require_relative "train_transport" +require_relative "exceptions" require "forwardable" unless defined?(Forwardable) +require "set" unless defined?(Set) class Chef # Value object that loads and tracks the context of a Chef run class RunContext + extend Forwardable + # # Global state # @@ -72,14 +76,12 @@ class Chef # attr_reader :definitions - # # Event dispatcher for this run. # # @return [Chef::EventDispatch::Dispatcher] # attr_accessor :events - # # Hash of factoids for a reboot request. # # @return [Hash] @@ -90,7 +92,6 @@ class Chef # Scoped state # - # # The parent run context. # # @return [Chef::RunContext] The parent run context, or `nil` if this is the @@ -98,7 +99,6 @@ class Chef # attr_reader :parent_run_context - # # The root run context. # # @return [Chef::RunContext] The root run context. @@ -109,7 +109,6 @@ class Chef rc end - # # The collection of resources intended to be converged (and able to be # notified). # @@ -119,8 +118,12 @@ class Chef # attr_reader :resource_collection - attr_accessor :action_collection + # Handle to the global action_collection of executed actions for reporting / data_collector /etc + # + # @return [Chef::ActionCollection # + attr_accessor :action_collection + # Pointer back to the Chef::Runner that created this # attr_accessor :runner @@ -129,7 +132,6 @@ class Chef # Notification handling # - # # A Hash containing the before notifications triggered by resources # during the converge phase of the chef run. # @@ -138,7 +140,6 @@ class Chef # attr_reader :before_notification_collection - # # A Hash containing the immediate notifications triggered by resources # during the converge phase of the chef run. # @@ -147,7 +148,6 @@ class Chef # attr_reader :immediate_notification_collection - # # A Hash containing the delayed (end of run) notifications triggered by # resources during the converge phase of the chef run. # @@ -156,7 +156,6 @@ class Chef # attr_reader :delayed_notification_collection - # # An Array containing the delayed (end of run) notifications triggered by # resources during the converge phase of the chef run. # @@ -164,7 +163,16 @@ class Chef # attr_reader :delayed_actions + # A Set keyed by the string name, of all the resources that are updated. We do not + # track actions or individual resource objects, since this matches the behavior of + # the notification collections which are keyed by Strings. + # + attr_reader :updated_resources + + # @return [Boolean] If the resource_collection is in unified_mode (no separate converge phase) # + def_delegator :resource_collection, :unified_mode + # A child of the root Chef::Log logging object. # # @return Mixlib::Log::Child A child logger @@ -190,7 +198,6 @@ class Chef @loaded_attributes_hash = {} @reboot_info = {} @cookbook_compiler = nil - @delayed_actions = [] initialize_child_state end @@ -221,6 +228,7 @@ class Chef @immediate_notification_collection = Hash.new { |h, k| h[k] = [] } @delayed_notification_collection = Hash.new { |h, k| h[k] = [] } @delayed_actions = [] + @updated_resources = Set.new end # @@ -232,6 +240,10 @@ class Chef # Note for the future, notification.notifying_resource may be an instance # of Chef::Resource::UnresolvedSubscribes when calling {Resource#subscribes} # with a string value. + if unified_mode && updated_resources.include?(notification.notifying_resource.declared_key) + raise Chef::Exceptions::UnifiedModeBeforeSubscriptionEarlierResource.new(notification) + end + before_notification_collection[notification.notifying_resource.declared_key] << notification end @@ -256,11 +268,13 @@ class Chef # Note for the future, notification.notifying_resource may be an instance # of Chef::Resource::UnresolvedSubscribes when calling {Resource#subscribes} # with a string value. + if unified_mode && updated_resources.include?(notification.notifying_resource.declared_key) + add_delayed_action(notification) + end delayed_notification_collection[notification.notifying_resource.declared_key] << notification end - # - # Adds a delayed action to the +delayed_actions+. + # Adds a delayed action to the delayed_actions collection # def add_delayed_action(notification) if delayed_actions.any? { |existing_notification| existing_notification.duplicates?(notification) } @@ -271,32 +285,45 @@ class Chef end end - # # Get the list of before notifications sent by the given resource. # # @return [Array[Notification]] # def before_notifications(resource) - before_notification_collection[resource.declared_key] + key = resource.is_a?(String) ? resource : resource.declared_key + before_notification_collection[key] end - # # Get the list of immediate notifications sent by the given resource. # # @return [Array[Notification]] # def immediate_notifications(resource) - immediate_notification_collection[resource.declared_key] + key = resource.is_a?(String) ? resource : resource.declared_key + immediate_notification_collection[key] end + # Get the list of immeidate notifications pending to the given resource + # + # @return [Array[Notification]] # + def reverse_immediate_notifications(resource) + immediate_notification_collection.map do |k, v| + v.select do |n| + (n.resource.is_a?(String) && n.resource == resource.declared_key) || + n.resource == resource + end + end.flatten + end + # Get the list of delayed (end of run) notifications sent by the given # resource. # # @return [Array[Notification]] # def delayed_notifications(resource) - delayed_notification_collection[resource.declared_key] + key = resource.is_a?(String) ? resource : resource.declared_key + delayed_notification_collection[key] end # @@ -666,9 +693,9 @@ class Chef rest= rest_clean rest_clean= - unreachable_cookbook? transport transport_connection + unreachable_cookbook? } def initialize(parent_run_context) @@ -681,8 +708,10 @@ class Chef end CHILD_STATE = %w{ - create_child add_delayed_action + before_notification_collection + before_notifications + create_child delayed_actions delayed_notification_collection delayed_notification_collection= @@ -690,21 +719,22 @@ class Chef immediate_notification_collection immediate_notification_collection= immediate_notifications - before_notification_collection - before_notifications include_recipe initialize_child_state load_recipe load_recipe_file notifies_before - notifies_immediately notifies_delayed + notifies_immediately parent_run_context - root_run_context resource_collection resource_collection= + reverse_immediate_notifications + root_run_context runner runner= + unified_mode + updated_resources }.map(&:to_sym) # Verify that we didn't miss any methods diff --git a/lib/chef/runner.rb b/lib/chef/runner.rb index a0ae61fe4c..8c68554af5 100644 --- a/lib/chef/runner.rb +++ b/lib/chef/runner.rb @@ -34,7 +34,7 @@ class Chef def initialize(run_context) @run_context = run_context - run_context.runner = self + @run_context.runner = self end def delayed_actions @@ -45,6 +45,10 @@ class Chef @run_context.events end + def updated_resources + @run_context.updated_resources + end + # Determine the appropriate provider for the given resource, then # execute it. def run_action(resource, action, notification_type = nil, notifying_resource = nil) @@ -73,33 +77,66 @@ class Chef # associated with the resource, but only if it was updated *this time* # we ran an action on it. if resource.updated_by_last_action? + updated_resources.add(resource.declared_key) # track updated resources for unified_mode run_context.immediate_notifications(resource).each do |notification| - Chef::Log.info("#{resource} sending #{notification.action} action to #{notification.resource} (immediate)") - run_action(notification.resource, notification.action, :immediate, resource) + if notification.resource.is_a?(String) && run_context.unified_mode + Chef::Log.debug("immediate notification from #{resource} to #{notification.resource} is delayed until declaration due to unified_mode") + else + Chef::Log.info("#{resource} sending #{notification.action} action to #{notification.resource} (immediate)") + run_action(notification.resource, notification.action, :immediate, resource) + end end run_context.delayed_notifications(resource).each do |notification| - # send the notification to the run_context of the receiving resource - notification.resource.run_context.add_delayed_action(notification) + if notification.resource.is_a?(String) + # for string resources that have not be declared yet in unified mode we only support notifying the current run_context + run_context.add_delayed_action(notification) + else + # send the notification to the run_context of the receiving resource + notification.resource.run_context.add_delayed_action(notification) + end + end + end + end + + # Runs all of the actions on a given resource. This fires notifications and marks + # the resource as having been executed by the runner. + # + # @param resource [Chef::Resource] the resource to run + # + def run_all_actions(resource) + Array(resource.action).each { |action| run_action(resource, action) } + if run_context.unified_mode + run_context.reverse_immediate_notifications(resource).each do |n| + if updated_resources.include?(n.notifying_resource.declared_key) + n.resolve_resource_reference(run_context.resource_collection) + Chef::Log.info("#{resource} sent #{n.action} action to #{n.resource} (immediate at declaration time)") + run_action(n.resource, n.action, :immediate, n.notifying_resource) + end end end + ensure + resource.executed_by_runner = true end - # Iterates over the +resource_collection+ in the +run_context+ calling - # +run_action+ for each resource in turn. + # Iterates over the resource_collection in the run_context calling + # run_action for each resource in turn. + # def converge # Resolve all lazy/forward references in notifications run_context.resource_collection.each(&:resolve_notification_references) # Execute each resource. run_context.resource_collection.execute_each_resource do |resource| - begin - Array(resource.action).each { |action| run_action(resource, action) } - ensure - resource.executed_by_runner = true + unless run_context.resource_collection.unified_mode + run_all_actions(resource) end end + if run_context.resource_collection.unified_mode + run_context.resource_collection.each { |r| r.resolve_notification_references(true) } + end + rescue Exception => e Chef::Log.info "Running queued delayed notifications before re-raising exception" run_delayed_notifications(e) @@ -126,7 +163,8 @@ class Chef def run_delayed_notification(notification) Chef::Log.info( "#{notification.notifying_resource} sending #{notification.action}"\ " action to #{notification.resource} (delayed)") - # Struct of resource/action to call + # notifications may have lazy strings in them to resolve + notification.resolve_resource_reference(run_context.resource_collection) run_action(notification.resource, notification.action, :delayed) true rescue Exception => e |