Skip to content

Commit

Permalink
Merge pull request #462 from elyscape/fix/fqdn_rotate_seeds_with_argu…
Browse files Browse the repository at this point in the history
…ment

fqdn_rotate: Don't use the value itself as part of the random seed
  • Loading branch information
DavidS committed Jun 2, 2015
2 parents a383705 + b436216 commit 07e8b39
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 17 deletions.
15 changes: 14 additions & 1 deletion README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,20 @@ fqdn_rand_string(10, '', 'custom seed')

#### `fqdn_rotate`

Rotates an array a random number of times, based on a node's fqdn. *Type*: rvalue.
Rotates an array or string a random number of times, combining the `$fqdn` fact and an optional seed for repeatable randomness.

*Usage:*
~~~
fqdn_rotate(VALUE, [SEED])
~~~
*Examples:*
~~~
fqdn_rotate(['a', 'b', 'c', 'd'])
fqdn_rotate('abcd')
fqdn_rotate([1, 2, 3], 'custom seed')
~~~

*Type*: rvalue.

#### `get_module_path`

Expand Down
24 changes: 15 additions & 9 deletions lib/puppet/parser/functions/fqdn_rotate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@
# fqdn_rotate.rb
#

module Puppet::Parser::Functions
newfunction(:fqdn_rotate, :type => :rvalue, :doc => <<-EOS
Rotates an array a random number of times based on a nodes fqdn.
EOS
) do |arguments|
Puppet::Parser::Functions.newfunction(
:fqdn_rotate,
:type => :rvalue,
:doc => "Usage: `fqdn_rotate(VALUE, [SEED])`. VALUE is required and
must be an array or a string. SEED is optional and may be any number
or string.
Rotates VALUE a random number of times, combining the `$fqdn` fact and
the value of SEED for repeatable randomness. (That is, each node will
get a different random rotation from this function, but a given node's
result will be the same every time unless its hostname changes.) Adding
a SEED can be useful if you need more than one unrelated rotation.") do |args|

raise(Puppet::ParseError, "fqdn_rotate(): Wrong number of arguments " +
"given (#{arguments.size} for 1)") if arguments.size < 1
"given (#{args.size} for 1)") if args.size < 1

value = arguments[0]
value = args.shift
require 'digest/md5'

unless value.is_a?(Array) || value.is_a?(String)
Expand All @@ -31,7 +38,7 @@ module Puppet::Parser::Functions

elements = result.size

seed = Digest::MD5.hexdigest([lookupvar('::fqdn'),arguments].join(':')).hex
seed = Digest::MD5.hexdigest([lookupvar('::fqdn'),args].join(':')).hex
# deterministic_rand() was added in Puppet 3.2.0; reimplement if necessary
if Puppet::Util.respond_to?(:deterministic_rand)
offset = Puppet::Util.deterministic_rand(seed, elements).to_i
Expand All @@ -51,7 +58,6 @@ module Puppet::Parser::Functions
result = string ? result.join : result

return result
end
end

# vim: set ts=2 sw=2 et :
2 changes: 1 addition & 1 deletion spec/acceptance/fqdn_rotate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
EOS

apply_manifest(pp, :catch_failures => true) do |r|
expect(r.stdout).to match(/fqdn_rotate is \["c", "d", "a", "b"\]/)
expect(r.stdout).to match(/fqdn_rotate is \["d", "a", "b", "c"\]/)
end
end
end
Expand Down
22 changes: 16 additions & 6 deletions spec/functions/fqdn_rotate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) }
it { is_expected.to run.with_params(0).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) }
it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) }
it {
pending("Current implementation ignores parameters after the first.")
is_expected.to run.with_params("one", "two").and_raise_error(Puppet::ParseError)
}
it { is_expected.to run.with_params('').and_return('') }
it { is_expected.to run.with_params('a').and_return('a') }

Expand All @@ -25,6 +21,18 @@
expect(val1).to eq(val2)
end

it "allows extra arguments to control the random rotation on a single host" do
val1 = fqdn_rotate("abcdefg", :extra_identifier => [1, "different", "host"])
val2 = fqdn_rotate("abcdefg", :extra_identifier => [2, "different", "host"])
expect(val1).not_to eq(val2)
end

it "considers the same host and same extra arguments to have the same random rotation" do
val1 = fqdn_rotate("abcdefg", :extra_identifier => [1, "same", "host"])
val2 = fqdn_rotate("abcdefg", :extra_identifier => [1, "same", "host"])
expect(val1).to eq(val2)
end

it "should rotate a string to give different values on different hosts" do
val1 = fqdn_rotate("abcdefg", :host => 'one')
val2 = fqdn_rotate("abcdefg", :host => 'two')
Expand All @@ -38,7 +46,7 @@

it "should use the Puppet::Util.deterministic_rand function" do
if Puppet::Util.respond_to?(:deterministic_rand)
Puppet::Util.expects(:deterministic_rand).with(113646079810780526294648115052177588845,4)
Puppet::Util.expects(:deterministic_rand).with(44489829212339698569024999901561968770,4)
fqdn_rotate("asdf")
else
skip 'Puppet::Util#deterministic_rand not available'
Expand All @@ -55,11 +63,13 @@

def fqdn_rotate(value, args = {})
host = args[:host] || '127.0.0.1'
extra = args[:extra_identifier] || []

# workaround not being able to use let(:facts) because some tests need
# multiple different hostnames in one context
scope.stubs(:lookupvar).with("::fqdn").returns(host)

scope.function_fqdn_rotate([value])
function_args = [value] + extra
scope.function_fqdn_rotate(function_args)
end
end

0 comments on commit 07e8b39

Please sign in to comment.