Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Systemd export: provide $PS to processes, no longer use systemd templates #747

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion data/export/systemd/master.target.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[Unit]
Wants=<%= process_master_names.join(' ') %>
Wants=<%= service_names.join(' ') %>

[Install]
WantedBy=multi-user.target
9 changes: 6 additions & 3 deletions data/export/systemd/process.service.erb
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
[Unit]
PartOf=<%= app %>-<%= name %>.target
PartOf=<%= app %>.target
StopWhenUnneeded=yes

[Service]
User=<%= user %>
WorkingDirectory=<%= engine.root %>
Environment=PORT=%i
Environment=PORT=<%= port %>
Environment=PS=<%= process_name %>
<% engine.env.each_pair do |var,env| -%>
Environment="<%= var %>=<%= env %>"
<% end -%>
ExecStart=/bin/bash -lc 'exec <%= process.command %>'
ExecStart=/bin/bash -lc 'exec -a "<%= app %>-<%= process_name %>" <%= process.command %>'
Restart=always
RestartSec=14s
StandardInput=null
StandardOutput=syslog
StandardError=syslog
Expand Down
2 changes: 0 additions & 2 deletions data/export/systemd/process_master.target.erb

This file was deleted.

20 changes: 7 additions & 13 deletions lib/foreman/export/systemd.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,16 @@ def export
clean_dir file
end

process_master_names = []
service_names = []

engine.each_process do |name, process|
service_fn = "#{app}-#{name}@.service"
write_template "systemd/process.service.erb", service_fn, binding

create_directory("#{app}-#{name}.target.wants")
1.upto(engine.formation[name])
.collect { |num| engine.port_for(process, num) }
.collect { |port| "#{app}-#{name}@#{port}.service" }
.each do |process_name|
create_symlink("#{app}-#{name}.target.wants/#{process_name}", "../#{service_fn}") rescue Errno::EEXIST # This is needed because rr-mocks do not call the origial cleanup
1.upto(engine.formation[name]) do |num|
port = engine.port_for(process, num)
process_name = "#{name}.#{num}"
service_filename = "#{app}-#{process_name}.service"
write_template "systemd/process.service.erb", service_filename, binding
service_names << service_filename
end

write_template "systemd/process_master.target.erb", "#{app}-#{name}.target", binding
process_master_names << "#{app}-#{name}.target"
end

write_template "systemd/master.target.erb", "#{app}.target", binding
Expand Down
134 changes: 88 additions & 46 deletions spec/foreman/export/systemd_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
require "foreman/export/systemd"
require "tmpdir"

describe Foreman::Export::Systemd, :fakefs do
describe Foreman::Export::Systemd, :fakefs, :aggregate_failures do
let(:procfile) { write_procfile("/tmp/app/Procfile") }
let(:formation) { nil }
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
Expand All @@ -16,71 +16,101 @@
it "exports to the filesystem" do
systemd.export

expect(File.read("/tmp/init/app.target")).to eq(example_export_file("systemd/app.target"))
expect(File.read("/tmp/init/app-alpha.target")).to eq(example_export_file("systemd/app-alpha.target"))
expect(File.read("/tmp/init/app-alpha@.service")).to eq(example_export_file("systemd/app-alpha@.service"))
expect(File.read("/tmp/init/app-bravo.target")).to eq(example_export_file("systemd/app-bravo.target"))
expect(File.read("/tmp/init/app-bravo@.service")).to eq(example_export_file("systemd/app-bravo@.service"))

expect(File.directory?("/tmp/init/app-alpha.target.wants")).to be_truthy
expect(File.symlink?("/tmp/init/app-alpha.target.wants/app-alpha@5000.service")).to be_truthy
expect(File.read("/tmp/init/app.target")).to eq(example_export_file("systemd/app.target"))
expect(File.read("/tmp/init/app-alpha.1.service")).to eq(example_export_file("systemd/app-alpha.1.service"))
expect(File.read("/tmp/init/app-bravo.1.service")).to eq(example_export_file("systemd/app-bravo.1.service"))
end

it "cleans up if exporting into an existing dir" do
expect(FileUtils).to receive(:rm).with("/tmp/init/app.target")
context "when systemd export was run using the previous version of systemd export" do
before do
write_file("/tmp/init/app.target")

expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha@.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha.target")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha.target.wants/app-alpha@5000.service")
expect(FileUtils).to receive(:rm_r).with("/tmp/init/app-alpha.target.wants")
write_file("/tmp/init/app-alpha@.service")
write_file("/tmp/init/app-alpha.target")
write_file("/tmp/init/app-alpha.target.wants/app-alpha@5000.service")

expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo.target")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo@.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo.target.wants/app-bravo@5100.service")
expect(FileUtils).to receive(:rm_r).with("/tmp/init/app-bravo.target.wants")
write_file("/tmp/init/app-bravo.target")
write_file("/tmp/init/app-bravo@.service")
write_file("/tmp/init/app-bravo.target.wants/app-bravo@5100.service")

expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar.target")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar@.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar.target.wants/app-foo_bar@5200.service")
expect(FileUtils).to receive(:rm_r).with("/tmp/init/app-foo_bar.target.wants")
write_file("/tmp/init/app-foo_bar.target")
write_file("/tmp/init/app-foo_bar@.service")
write_file("/tmp/init/app-foo_bar.target.wants/app-foo_bar@5200.service")

expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar.target")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar@.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar.target.wants/app-foo-bar@5300.service")
expect(FileUtils).to receive(:rm_r).with("/tmp/init/app-foo-bar.target.wants")
write_file("/tmp/init/app-foo-bar.target")
write_file("/tmp/init/app-foo-bar@.service")
write_file("/tmp/init/app-foo-bar.target.wants/app-foo-bar@5300.service")
end

it "cleans up service files created by systemd export" do
expect(FileUtils).to receive(:rm).with("/tmp/init/app.target")

systemd.export
systemd.export
expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha@.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha.target")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha.target.wants/app-alpha@5000.service")
expect(FileUtils).to receive(:rm_r).with("/tmp/init/app-alpha.target.wants")

expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo.target")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo@.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo.target.wants/app-bravo@5100.service")
expect(FileUtils).to receive(:rm_r).with("/tmp/init/app-bravo.target.wants")

expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar.target")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar@.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar.target.wants/app-foo_bar@5200.service")
expect(FileUtils).to receive(:rm_r).with("/tmp/init/app-foo_bar.target.wants")

expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar.target")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar@.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar.target.wants/app-foo-bar@5300.service")
expect(FileUtils).to receive(:rm_r).with("/tmp/init/app-foo-bar.target.wants")

systemd.export
end
end

context "when systemd export was run using the current version of systemd export" do
before do
systemd.export
end

it "cleans up service files created by systemd export" do
expect(FileUtils).to receive(:rm).with("/tmp/init/app.target")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha.1.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo.1.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar.1.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar.1.service")

systemd.export
end
end

it "includes environment variables" do
engine.env['KEY'] = 'some "value"'
systemd.export
expect(File.read("/tmp/init/app-alpha@.service")).to match(/KEY=some "value"/)
expect(File.read("/tmp/init/app-alpha.1.service")).to match(/KEY=some "value"/)
end

it "includes ExecStart line" do
engine.env['KEY'] = 'some "value"'
systemd.export
expect(File.read("/tmp/init/app-alpha@.service")).to match(/^ExecStart=/)
expect(File.read("/tmp/init/app-alpha.1.service")).to match(/^ExecStart=/)
end

context "with a formation" do
context "with a custom formation specified" do
let(:formation) { "alpha=2" }

it "exports to the filesystem with concurrency" do
it "exports only those services that are specified in the formation" do
systemd.export

expect(File.read("/tmp/init/app.target")).to eq(example_export_file("systemd/app.target"))
expect(File.read("/tmp/init/app-alpha.target")).to eq(example_export_file("systemd/app-alpha.target"))
expect(File.read("/tmp/init/app-alpha@.service")).to eq(example_export_file("systemd/app-alpha@.service"))
expect(File.read("/tmp/init/app-bravo.target")).to eq(example_export_file("systemd/app-bravo.target"))
expect(File.read("/tmp/init/app-bravo@.service")).to eq(example_export_file("systemd/app-bravo@.service"))
expect(File.read("/tmp/init/app.target")).to include("Wants=app-alpha.1.service app-alpha.2.service\n")
expect(File.read("/tmp/init/app-alpha.1.service")).to eq(example_export_file("systemd/app-alpha.1.service"))
expect(File.read("/tmp/init/app-alpha.2.service")).to eq(example_export_file("systemd/app-alpha.2.service"))
expect(File.exist?("/tmp/init/app-bravo.1.service")).to be_falsey
end
end

context "with alternate templates" do
context "with alternate template directory specified" do
let(:template) { "/tmp/alternate" }
let(:options) { { :app => "app", :template => template } }

Expand All @@ -89,25 +119,37 @@
File.open("#{template}/master.target.erb", "w") { |f| f.puts "alternate_template" }
end

it "can export with alternate template files" do
it "uses template files found in the alternate directory" do
systemd.export
expect(File.read("/tmp/init/app.target")).to eq("alternate_template\n")
end
end

context "with alternate templates from home dir" do
context "with alternate templates in the user home directory" do
before do
FileUtils.mkdir_p File.expand_path("~/.foreman/templates/systemd")
File.open(File.expand_path("~/.foreman/templates/systemd/master.target.erb"), "w") do |file|
file.puts "home_dir_template"
end
end

it "uses template files found in the alternate directory" do
systemd.export
expect(File.read("/tmp/init/app.target")).to eq("alternate_template\n")
end
end
end

context "with alternate templates in the user home directory" do
before do
FileUtils.mkdir_p File.expand_path("~/.foreman/templates/systemd")
File.open(File.expand_path("~/.foreman/templates/systemd/master.target.erb"), "w") do |file|
file.puts "default_alternate_template"
file.puts "home_dir_template"
end
end

it "can export with alternate template files" do
it "uses template files found in the user home directory" do
systemd.export
expect(File.read("/tmp/init/app.target")).to eq("default_alternate_template\n")
expect(File.read("/tmp/init/app.target")).to eq("home_dir_template\n")
end
end

end
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
[Unit]
PartOf=app-alpha.target
PartOf=app.target
StopWhenUnneeded=yes

[Service]
User=app
WorkingDirectory=/tmp/app
Environment=PORT=%i
ExecStart=/bin/bash -lc 'exec ./alpha'
Environment=PORT=5000
Environment=PS=alpha.1
ExecStart=/bin/bash -lc 'exec -a "app-alpha.1" ./alpha'
Restart=always
RestartSec=14s
StandardInput=null
StandardOutput=syslog
StandardError=syslog
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
[Unit]
PartOf=app-bravo.target
PartOf=app.target
StopWhenUnneeded=yes

[Service]
User=app
WorkingDirectory=/tmp/app
Environment=PORT=%i
ExecStart=/bin/bash -lc 'exec ./bravo'
Environment=PORT=5001
Environment=PS=alpha.2
ExecStart=/bin/bash -lc 'exec -a "app-alpha.2" ./alpha'
Restart=always
RestartSec=14s
StandardInput=null
StandardOutput=syslog
StandardError=syslog
Expand Down
18 changes: 18 additions & 0 deletions spec/resources/export/systemd/app-bravo.1.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[Unit]
PartOf=app.target
StopWhenUnneeded=yes

[Service]
User=app
WorkingDirectory=/tmp/app
Environment=PORT=5100
Environment=PS=bravo.1
ExecStart=/bin/bash -lc 'exec -a "app-bravo.1" ./bravo'
Restart=always
RestartSec=14s
StandardInput=null
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=%n
KillMode=mixed
TimeoutStopSec=5
2 changes: 1 addition & 1 deletion spec/resources/export/systemd/app.target
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[Unit]
Wants=app-alpha.target app-bravo.target app-foo_bar.target app-foo-bar.target
Wants=app-alpha.1.service app-bravo.1.service app-foo_bar.1.service app-foo-bar.1.service

[Install]
WantedBy=multi-user.target
2 changes: 1 addition & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def write_procfile(procfile="Procfile", alpha_env="")
def write_file(file)
FileUtils.mkdir_p(File.dirname(file))
File.open(file, 'w') do |f|
yield(f)
yield(f) if block_given?
end
end

Expand Down