diff options
author | George Holt <gholtiii@me.com> | 2021-08-02 18:16:34 -0400 |
---|---|---|
committer | George Holt <gholtiii@me.com> | 2021-09-01 14:04:45 -0400 |
commit | c18438be1c22bcb9fc3ee691fe185a418a9aab10 (patch) | |
tree | 4e8eb8e92d524d20f131572fdb913480bd54d4c3 | |
parent | 211463b86f35b68f8388bd10a8667ddc37a6b25d (diff) | |
download | chef-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.rb | 40 | ||||
-rw-r--r-- | spec/unit/resource/chef_client_scheduled_task_spec.rb | 55 |
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") |