Skip to content

Commit

Permalink
[puppetlabs#1058] Serially deploy modules that share a cachedir
Browse files Browse the repository at this point in the history
When we raised the default threadpool for deploying modules above 1, we
began to see issues when caching a remote locally if that remote/cache
was shared by multiple modules within the Puppetfile.

To resolve this, when we create the module we group it with any other
module that may share its cachedir. We then pull modules by cachedir off
of the queue and serially process those.
  • Loading branch information
justinstoller committed May 1, 2020
1 parent b2bce62 commit a58c621
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 9 deletions.
4 changes: 1 addition & 3 deletions lib/r10k/git/cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,8 @@ def reset!

alias cached? exist?

private

# Reformat the remote name into something that can be used as a directory
def sanitized_dirname
@remote.gsub(/[^@\w\.-]/, '-')
@sanitized_dirname ||= @remote.gsub(/[^@\w\.-]/, '-')
end
end
30 changes: 28 additions & 2 deletions lib/r10k/puppetfile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ class Puppetfile
# @return [Boolean] Overwrite any locally made changes
attr_accessor :force

# @!attribute [r] modules_by_vcs_cachedir
# @api private Only exposed for testing purposes
# @return [Hash{:none, String => Array<R10K::Module>}]
attr_reader :modules_by_vcs_cachedir

# @param [String] basedir
# @param [String] moduledir The directory to install the modules, default to #{basedir}/modules
# @param [String] puppetfile_path The path to the Puppetfile, default to #{basedir}/Puppetfile
Expand All @@ -58,6 +63,7 @@ def initialize(basedir, moduledir = nil, puppetfile_path = nil, puppetfile_name

@modules = []
@managed_content = {}
@modules_by_vcs_cachedir = Hash.new { [] }
@forge = 'forgeapi.puppetlabs.com'

@loaded = false
Expand Down Expand Up @@ -137,6 +143,7 @@ def add_module(name, args)
mod.origin = 'Puppetfile'

@managed_content[install_path] << mod.name
@modules_by_vcs_cachedir[module_vcs_cachedir(mod)] << mod
@modules << mod
end

Expand Down Expand Up @@ -180,6 +187,19 @@ def accept(visitor)

private

def module_vcs_cachedir(mod)
if mod.respond_to? :repo
repo = mod.repo
if repo.respond_to? :cache
cache = repo.cache
if cache.respond_to? :sanitized_dirname
return cache.sanitized_dirname
end
end
end
:none
end

def serial_accept(visitor)
visitor.visit(:puppetfile, self) do
modules.each do |mod|
Expand Down Expand Up @@ -212,15 +232,21 @@ def concurrent_accept(visitor, pool_size)
def modules_queue(visitor)
Queue.new.tap do |queue|
visitor.visit(:puppetfile, self) do
modules.each { |mod| queue << mod }
modules_by_cachedir = modules_by_vcs_cachedir.clone
modules_without_vcs_cachedir = modules_by_cachedir.delete(:none) || []

modules_without_vcs_cachedir.each {|mod| queue << Array(mod) }
modules_by_cachedir.values.each {|mods| queue << mods }
end
end
end

def visitor_thread(visitor, mods_queue)
Thread.new do
begin
while mod = mods_queue.pop(true) do mod.accept(visitor) end
while mods = mods_queue.pop(true) do
mods.each {|mod| mod.accept(visitor) }
end
rescue ThreadError => e
logger.debug _("Module thread %{id} exiting: %{message}") % {message: e.message, id: Thread.current.object_id}
Thread.exit
Expand Down
4 changes: 2 additions & 2 deletions spec/unit/action/deploy/module_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@

before do
allow(subject).to receive(:visit_environment).and_wrap_original do |original, environment, &block|
expect(environment.puppetfile).to receive(:modules).and_return(
[R10K::Module::Local.new(environment.name, '/fakedir', [], environment)]
expect(environment.puppetfile).to receive(:modules_by_vcs_cachedir).and_return(
{none: [R10K::Module::Local.new(environment.name, '/fakedir', [], environment)]}
)
original.call(environment, &block)
end
Expand Down
1 change: 1 addition & 0 deletions spec/unit/action/puppetfile/install_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def installer(opts = {}, argv = [], settings = {})
before do
allow(puppetfile).to receive(:purge!)
allow(puppetfile).to receive(:modules).and_return(modules)
allow(puppetfile).to receive(:modules_by_vcs_cachedir).and_return({none: modules})
end

it "syncs each module in the Puppetfile" do
Expand Down
4 changes: 2 additions & 2 deletions spec/unit/puppetfile_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ def expect_wrapped_error(orig, pf_path, wrapped_error)
mod2 = spy('module')
expect(mod2).to receive(:accept).with(visitor)

expect(subject).to receive(:modules).and_return([mod1, mod2])
expect(subject).to receive(:modules_by_vcs_cachedir).and_return({none: [mod1, mod2]})
subject.accept(visitor)
end

Expand All @@ -289,7 +289,7 @@ def expect_wrapped_error(orig, pf_path, wrapped_error)
mod2 = spy('module')
expect(mod2).to receive(:accept).with(visitor)

expect(subject).to receive(:modules).and_return([mod1, mod2])
expect(subject).to receive(:modules_by_vcs_cachedir).and_return({none: [mod1, mod2]})

expect(Thread).to receive(:new).exactly(pool_size).and_call_original
expect(Queue).to receive(:new).and_call_original
Expand Down

0 comments on commit a58c621

Please sign in to comment.