summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2021-09-14 13:37:50 -0700
committerGitHub <noreply@github.com>2021-09-14 13:37:50 -0700
commit359c5329d2fa999aecec4b4b80c89750f9aac363 (patch)
tree1a05d77a7d3b3b8a1a607fe62d39170f723fef8d
parent892b04fa70c8292948c20e7084c858541cf983eb (diff)
parentc18438be1c22bcb9fc3ee691fe185a418a9aab10 (diff)
downloadchef-359c5329d2fa999aecec4b4b80c89750f9aac363.tar.gz
Merge pull request #11894 from gholtiii/gh/chef_client_scheduled_task_behaves_like_cron
Enable chef-client scheduled task to behave like chef_client_cron, with consistent delay calculated once from splay
-rw-r--r--lib/chef/resource/chef_client_scheduled_task.rb40
-rw-r--r--spec/unit/resource/chef_client_scheduled_task_spec.rb55
2 files changed, 93 insertions, 2 deletions
diff --git a/lib/chef/resource/chef_client_scheduled_task.rb b/lib/chef/resource/chef_client_scheduled_task.rb
index b94ac59fb8..57c3f567f0 100644
--- a/lib/chef/resource/chef_client_scheduled_task.rb
+++ b/lib/chef/resource/chef_client_scheduled_task.rb
@@ -58,6 +58,15 @@ class Chef
daemon_options ['-n audit_only']
end
```
+
+ **Run #{ChefUtils::Dist::Infra::PRODUCT} with a persistent delay on every run calculated once, similar to how chef_client_cron resource works**:
+
+ ```ruby
+ chef_client_scheduled_task 'Run chef-client with persistent splay' do
+ use_consistent_splay true
+ end
+ ```
+
DOC
resource_name :chef_client_scheduled_task
@@ -104,6 +113,9 @@ class Chef
description: "A random number of seconds between 0 and X to add to interval so that all #{ChefUtils::Dist::Infra::CLIENT} commands don't execute at the same time.",
default: 300
+ property :use_consistent_splay, [true, false],
+ default: false
+
property :run_on_battery, [true, false],
description: "Run the #{ChefUtils::Dist::Infra::PRODUCT} task when the system is on batteries.",
default: true
@@ -155,7 +167,7 @@ class Chef
frequency_modifier new_resource.frequency_modifier if frequency_supports_frequency_modifier?
start_time new_resource.start_time
start_day new_resource.start_date unless new_resource.start_date.nil?
- random_delay new_resource.splay if frequency_supports_random_delay?
+ random_delay new_resource.splay if frequency_supports_random_delay? && !new_resource.use_consistent_splay
disallow_start_if_on_batteries new_resource.splay unless new_resource.run_on_battery
priority new_resource.priority
action %i{create enable}
@@ -178,7 +190,31 @@ class Chef
# Fetch path of cmd.exe through environment variable comspec
cmd_path = ENV["COMSPEC"]
- "#{cmd_path} /c \"#{client_cmd}\""
+ "#{cmd_path} /c \"#{consistent_splay_command}#{client_cmd}\""
+ end
+
+ #
+ # Generate a uniformly distributed unique number to sleep from 0 to the splay time
+ #
+ # @param [Integer] splay The number of seconds to splay
+ #
+ # @return [Integer]
+ #
+ def splay_sleep_time(splay)
+ seed = node["shard_seed"] || Digest::MD5.hexdigest(node.name).to_s.hex
+ random = Random.new(seed.to_i)
+ random.rand(splay)
+ end
+
+ #
+ # The consistent splay sleep time when use_consistent_splay is true.
+ #
+ # @return [NilClass,String] The prepended sleep command to run prior to executing the full command.
+ #
+ def consistent_splay_command
+ return unless new_resource.use_consistent_splay
+
+ "C:/windows/system32/windowspowershell/v1.0/powershell.exe Start-Sleep -s #{splay_sleep_time(new_resource.splay)} && "
end
#
diff --git a/spec/unit/resource/chef_client_scheduled_task_spec.rb b/spec/unit/resource/chef_client_scheduled_task_spec.rb
index 0acc268a10..de138ef9b1 100644
--- a/spec/unit/resource/chef_client_scheduled_task_spec.rb
+++ b/spec/unit/resource/chef_client_scheduled_task_spec.rb
@@ -25,6 +25,11 @@ describe Chef::Resource::ChefClientScheduledTask do
let(:resource) { Chef::Resource::ChefClientScheduledTask.new("fakey_fakerton", run_context) }
let(:provider) { resource.provider_for_action(:add) }
+ before do
+ allow(ENV).to receive(:[]).and_call_original
+ allow(ENV).to receive(:[]).with("COMSPEC").and_return("C:\\Windows\\System32\\cmd.exe")
+ end
+
it "sets the default action as :add" do
expect(resource.action).to eql([:add])
end
@@ -92,6 +97,56 @@ describe Chef::Resource::ChefClientScheduledTask do
expect { resource.action :remove }.not_to raise_error
end
+ it "expects use_consistent_splay to be true when set" do
+ resource.use_consistent_splay = true
+ expect(resource.use_consistent_splay).to eql(true)
+ end
+
+ context "when configured to use a consistent splay" do
+ before do
+ node.automatic_attrs[:shard_seed] = nil
+ allow(node).to receive(:name).and_return("test_node")
+ resource.config_directory = "C:/chef" # Allows local unit testing on nix flavors
+ resource.use_consistent_splay = true
+ end
+
+ it "sleeps the same amount each time based on splay before running the task" do
+ expect(provider.full_command).to eql("C:\\Windows\\System32\\cmd.exe /c \"C:/windows/system32/windowspowershell/v1.0/powershell.exe Start-Sleep -s 272 && C:/opscode/chef/bin/chef-client -L C:/chef/log/client.log -c C:/chef/client.rb\"")
+ end
+ end
+
+ describe "#consistent_splay_command" do
+ context "when use_consistent_splay is false" do
+ it "returns nil" do
+ expect(provider.consistent_splay_command).to eql(nil)
+ end
+ end
+
+ context "when use_consistent_splay is true" do
+ before do
+ resource.use_consistent_splay true
+ allow(provider).to receive(:splay_sleep_time).and_return(222)
+ end
+
+ it "returns a powershell sleep command to be appended to the chef client run command" do
+ expect(provider.consistent_splay_command).to eql("C:/windows/system32/windowspowershell/v1.0/powershell.exe Start-Sleep -s 222 && ")
+ end
+ end
+ end
+
+ describe "#splay_sleep_time" do
+ it "uses shard_seed attribute if present" do
+ node.automatic_attrs[:shard_seed] = "73399073"
+ expect(provider.splay_sleep_time(300)).to satisfy { |v| v >= 0 && v <= 300 }
+ end
+
+ it "uses a hex conversion of a md5 hash of the splay if present" do
+ node.automatic_attrs[:shard_seed] = nil
+ allow(node).to receive(:name).and_return("test_node")
+ expect(provider.splay_sleep_time(300)).to satisfy { |v| v >= 0 && v <= 300 }
+ end
+ end
+
describe "#client_cmd" do
it "creates a valid command if using all default properties" do
expect(provider.client_cmd).to eql("C:/opscode/chef/bin/chef-client -L /etc/chef/log/client.log -c /etc/chef/client.rb") | eql("C:/opscode/chef/bin/chef-client -L C:\\chef/log/client.log -c C:\\chef/client.rb")