Skip to content

Commit

Permalink
(FACT-2786) Fix fact caching if fact is defined in multiple groups
Browse files Browse the repository at this point in the history
  • Loading branch information
florindragos committed Sep 15, 2020
1 parent 202f8d2 commit 8128105
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 28 deletions.
2 changes: 1 addition & 1 deletion acceptance/tests/custom_facts/cached_custom_fact.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@

step 'should read from the cached file for a custom fact that has been cached' do
on(agent, facter("#{custom_fact_name} --debug", environment: env)) do |facter_result|
assert_match(/Loading cached custom facts from file ".+"|loading cached values for cached-custom-facts facts/, facter_result.stderr,
assert_match(/Loading cached custom facts from file ".+"|loading cached values for random_custom_fact facts/, facter_result.stderr,
'Expected debug message to state that cached custom facts are read from file')
end
end
Expand Down
37 changes: 35 additions & 2 deletions lib/facter/framework/config/fact_groups.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module Facter
class FactGroups
attr_reader :groups, :block_list
attr_reader :groups, :block_list, :facts_ttls

@groups_ttls = []

Expand All @@ -14,6 +14,7 @@ def initialize(group_list_path = nil)
@groups ||= File.readable?(@groups_file_path) ? Hocon.load(@groups_file_path) : {}
load_groups
load_groups_from_options
load_facts_ttls
end

# Breakes down blocked groups in blocked facts
Expand All @@ -31,6 +32,9 @@ def blocked_facts

# Get the group name a fact is part of
def get_fact_group(fact_name)
fact = get_fact(fact_name)
return fact[:group] if fact

@groups.detect { |k, v| break k if Array(v).find { |f| fact_name =~ /^#{f}.*/ } }
end

Expand All @@ -41,6 +45,15 @@ def get_group_ttls(group_name)
ttls_to_seconds(ttls[group_name])
end

def get_fact(fact_name)
return @facts_ttls[fact_name] if @facts_ttls[fact_name]

fact = @facts_ttls.select { |k, v| break v if fact_name =~ /^#{k}\..*/ }
return nil if fact == {}

fact
end

private

def load_groups_from_options
Expand All @@ -55,10 +68,30 @@ def load_groups_from_options
end
end

def load_facts_ttls
@facts_ttls ||= {}
return if @groups_ttls == []

@groups_ttls.reduce(:merge).each do |group, ttls|
ttls = ttls_to_seconds(ttls)
if @groups[group]
@groups[group].each do |fact|
if @facts_ttls[fact]
@facts_ttls[fact] = { ttls: ttls, group: group } if @facts_ttls[fact][:ttls] < ttls
else
@facts_ttls[fact] = { ttls: ttls, group: group }
end
end
else
@facts_ttls[group] = { ttls: ttls, group: group }
end
end
end

def load_groups
config = ConfigReader.init(Options[:config])
@block_list = config.block_list || {}
@groups_ttls = config.ttls || {}
@groups_ttls = config.ttls || []
@groups.merge!(config.fact_groups) if config.fact_groups
end

Expand Down
35 changes: 23 additions & 12 deletions lib/facter/framework/core/cache_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,16 @@ def cache_facts(resolved_facts)
end
end

def group_cached?(group_name)
cached = @fact_groups.get_group_ttls(group_name) ? true : false
delete_cache(group_name) unless cached
def fact_cache_enabled?(fact_name)
fact = @fact_groups.get_fact(fact_name)
cached = if fact
!fact[:ttls].nil?
else
false
end

fact_group = @fact_groups.get_fact_group(fact_name)
delete_cache(fact_group) if fact_group && !cached
cached
end

Expand All @@ -57,14 +64,14 @@ def resolve_fact(searched_fact)

return unless group_name

return unless group_cached?(group_name)
return unless fact_cache_enabled?(searched_fact.name)

return unless check_ttls?(group_name)
return unless check_ttls?(searched_fact.name)

data = read_group_json(group_name)
return unless data

@log.debug("loading cached values for #{group_name} facts")
@log.debug("loading cached values for #{searched_fact.name} facts")

create_facts(searched_fact, data)
end
Expand Down Expand Up @@ -93,7 +100,7 @@ def cache_fact(fact)
end
return if !group_name || fact.value.nil?

return unless group_cached?(group_name)
return unless fact_cache_enabled?(fact.name)

@groups[group_name] ||= {}
@groups[group_name][fact.name] = fact.value
Expand All @@ -106,10 +113,10 @@ def write_cache
end

@groups.each do |group_name, data|
next unless check_ttls?(group_name)
cache_file_name = File.join(@cache_dir, group_name)
next if File.readable?(cache_file_name)

@log.debug("caching values for #{group_name} facts")
cache_file_name = File.join(@cache_dir, group_name)
File.write(cache_file_name, JSON.pretty_generate(data))
end
end
Expand All @@ -128,8 +135,12 @@ def read_group_json(group_name)
@groups[group_name] = data
end

def check_ttls?(group_name)
ttls = @fact_groups.get_group_ttls(group_name)
def check_ttls?(fact_name)
fact = @fact_groups.get_fact(fact_name)
return unless fact

ttls = fact[:ttls]
group_name = fact[:group]
return false unless ttls

cache_file_name = File.join(@cache_dir, group_name)
Expand All @@ -141,7 +152,7 @@ def check_ttls?(group_name)
File.delete(cache_file_name)
end

@log.debug("#{group_name} facts cache file expired/missing")
@log.debug("#{fact_name} facts cache file expired/missing")
true
end

Expand Down
32 changes: 19 additions & 13 deletions spec/facter/cache_manager_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
let(:group_name) { 'operating system' }
let(:cache_file_name) { File.join(cache_dir, group_name) }
let(:fact_groups) { instance_spy(Facter::FactGroups) }
let(:os_fact) { { ttls: 60, group: 'operating system' } }
let(:external_fact) { { ttls: 60, group: 'ext_file.txt' } }

before do
allow(LegacyFacter::Util::Config).to receive(:facts_cache_dir).and_return(cache_dir)
Expand Down Expand Up @@ -72,15 +74,17 @@
allow(File).to receive(:directory?).with(cache_dir).and_return(true)
allow(fact_groups).to receive(:get_fact_group).with('os').and_return(group_name)
allow(fact_groups).to receive(:get_fact_group).with('my_custom_fact').and_return(nil)
allow(fact_groups).to receive(:get_fact_group).with('ext_file.txt').and_return(nil)
allow(fact_groups).to receive(:get_group_ttls).with('ext_file.txt').and_return(nil)
allow(fact_groups).to receive(:get_fact).with('ext_file.txt').and_return(nil)
allow(File).to receive(:readable?).with(cache_file_name).and_return(true)
allow(File).to receive(:mtime).with(cache_file_name).and_return(Time.now)
allow(Facter::Util::FileHelper).to receive(:safe_read).with(cache_file_name).and_return(cached_core_fact)
allow(Facter::Options).to receive(:[]).with(:cache).and_return(true)
end

it 'returns cached fact' do
allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(1000)
allow(fact_groups).to receive(:get_fact).with('os').and_return(os_fact)
allow(File).to receive(:readable?).with(cache_file_name).and_return(true)
allow(File).to receive(:readable?).with(File.join(cache_dir, 'ext_file.txt')).and_return(false)

Expand All @@ -91,7 +95,7 @@
end

it 'returns searched fact' do
allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(1000)
allow(fact_groups).to receive(:get_fact).with('os').and_return(os_fact)
allow(File).to receive(:readable?).with(cache_file_name).and_return(true)
allow(File).to receive(:readable?).with(File.join(cache_dir, 'ext_file.txt')).and_return(false)

Expand All @@ -103,18 +107,19 @@
end

it 'deletes cache file' do
allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(nil)
allow(fact_groups).to receive(:get_fact).with('os').and_return(nil)
allow(File).to receive(:readable?).with(cache_file_name).and_return(true)
allow(File).to receive(:delete).with(cache_file_name)
allow(fact_groups).to receive(:get_fact_group).with('os').and_return(group_name)
allow(File).to receive(:readable?).with(File.join(cache_dir, 'ext_file.txt')).and_return(false)

cache_manager.resolve_facts(searched_facts)
expect(File).to have_received(:delete).with(cache_file_name)
end

it 'returns cached external facts' do
allow(fact_groups).to receive(:get_group_ttls).with('ext_file.txt').and_return(1000)
allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(nil)
allow(fact_groups).to receive(:get_fact).with('ext_file.txt').and_return(external_fact)
allow(fact_groups).to receive(:get_fact).with('os').and_return(nil)
allow(File).to receive(:readable?).with(cache_file_name).and_return(false)
allow(Facter::Util::FileHelper).to receive(:safe_read).with(File.join(cache_dir, 'ext_file.txt'))
.and_return(cached_external_fact)
Expand All @@ -135,7 +140,7 @@
before do
allow(File).to receive(:directory?).with(cache_dir).and_return(true)
allow(File).to receive(:readable?).with(cache_file_name).and_return(false)
allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(nil)
allow(fact_groups).to receive(:get_fact).with('os').and_return(nil)
allow(fact_groups).to receive(:get_fact_group).with('os').and_return(group_name)
allow(File).to receive(:write).with(cache_file_name, cached_core_fact)
allow(Facter::Options).to receive(:[]).with(:cache).and_return(true)
Expand All @@ -150,10 +155,10 @@
context 'with cache group' do
before do
allow(File).to receive(:directory?).with(cache_dir).and_return(true)
allow(fact_groups).to receive(:get_fact).with('os').and_return(os_fact)
allow(fact_groups).to receive(:get_fact_group).with('os').and_return(group_name)
allow(fact_groups).to receive(:get_fact_group).with('my_custom_fact').and_return(nil)
allow(fact_groups).to receive(:get_fact_group).with('my_external_fact').and_return(nil)
allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(1000)
allow(File).to receive(:readable?).with(cache_file_name).and_return(false)
allow(File).to receive(:write).with(cache_file_name, cached_core_fact)
allow(Facter::Options).to receive(:[]).with(:cache).and_return(true)
Expand All @@ -166,35 +171,36 @@
end
end

describe '#group_cached?' do
describe '#fact_cache_enabled?' do
context 'with ttls' do
before do
allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(1000)
allow(fact_groups).to receive(:get_fact).with('os').and_return(os_fact)
allow(File).to receive(:readable?).with(cache_file_name).and_return(false)
end

it 'returns true' do
result = cache_manager.group_cached?(group_name)
result = cache_manager.fact_cache_enabled?('os')
expect(result).to be true
end
end

context 'without ttls' do
before do
allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(nil)
allow(fact_groups).to receive(:get_fact).with('os').and_return(nil)
allow(fact_groups).to receive(:get_fact_group).with('os').and_return(group_name)
allow(Facter::Options).to receive(:[]).with(:cache).and_return(true)
allow(File).to receive(:delete).with(cache_file_name)
end

it 'returns false' do
allow(File).to receive(:readable?).with(cache_file_name).and_return(false)
result = cache_manager.group_cached?(group_name)
result = cache_manager.fact_cache_enabled?('os')
expect(result).to be false
end

it 'deletes invalid cache file' do
allow(File).to receive(:readable?).with(cache_file_name).and_return(true)
cache_manager.group_cached?(group_name)
cache_manager.fact_cache_enabled?('os')
expect(File).to have_received(:delete).with(cache_file_name)
end
end
Expand Down

0 comments on commit 8128105

Please sign in to comment.