summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Holt <gholtiii@me.com>2021-08-02 18:16:34 -0400
committerGeorge Holt <gholtiii@me.com>2021-09-01 14:04:45 -0400
commitc18438be1c22bcb9fc3ee691fe185a418a9aab10 (patch)
tree4e8eb8e92d524d20f131572fdb913480bd54d4c3
parent211463b86f35b68f8388bd10a8667ddc37a6b25d (diff)
downloadchef-c18438be1c22bcb9fc3ee691fe185a418a9aab10.tar.gz
Enable chef-client scheduled task to behave like chef_client_cron, with consistent delay calculated once
Signed-off-by: George Holt <gholtiii@me.com>
-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 6f88460d73..88e754f7db 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
@@ -151,7 +163,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
action %i{create enable}
end
@@ -173,7 +185,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 b3c663cdae..1aa0ff2432 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
@@ -78,6 +83,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")