Skip to content

Commit abd1b70

Browse files
committed
Fix psuedo heartbeating when HB file missing
With the previous version of this code, the validate_heartbeat code: def validate_heartbeat(w) last_heartbeat = workers_last_heartbeat(w) if w.last_heartbeat.nil? last_heartbeat ||= Time.now.utc w.update_attributes(:last_heartbeat => last_heartbeat) elsif !last_heartbeat.nil? && last_heartbeat > w.last_heartbeat w.update_attributes(:last_heartbeat => last_heartbeat) end end Would update the `last_heartbeat` value for the worker every time `validate_heartbeat` was called, which is not the intent. When working correctly, the validate_heartbeat code should: * If no value is set in the DB, initialize it to either the time pulled from workers_last_heartbeat, or the current time (this should only happen the first time this method is called for this worker) * If workers_last_heartbeat is not nil, and it is a more resent heartbeat time, update the DB with the new value Otherwise, assume we have the most up to date heartbeat value in the DB. This change just returns `nil` if we don't have anything to provide from the heartbeat file. This is fine since the `validate_heartbeat` method will take care of any initialization of the last_heartbeat value in the DB, so workers_last_heartbeat_to_file is just an existence check, plus a value if there is one.
1 parent 62543e2 commit abd1b70

File tree

2 files changed

+71
-1
lines changed

2 files changed

+71
-1
lines changed

app/models/miq_server/worker_management/heartbeat.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,6 @@ def workers_last_heartbeat_to_drb(w)
108108
end
109109

110110
def workers_last_heartbeat_to_file(w)
111-
File.exist?(w.heartbeat_file) ? File.mtime(w.heartbeat_file).utc : Time.now.utc
111+
File.mtime(w.heartbeat_file).utc if File.exist?(w.heartbeat_file)
112112
end
113113
end

spec/models/miq_server/worker_management/heartbeat_spec.rb

+70
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,75 @@
1515
expect(worker.reload.last_heartbeat).to be_within(1.second).of(t)
1616
end
1717
end
18+
19+
# Iterating by 5 each time to allow enough spacing to be more than 1 second
20+
# apart when using be_within(x).of(t)
21+
context "when using file based heartbeating" do
22+
let!(:first_heartbeat) { Time.now.utc }
23+
let!(:heartbeat_file) { "/path/to/worker.hb" }
24+
25+
around do |example|
26+
ENV["WORKER_HEARTBEAT_METHOD"] = "file"
27+
ENV["WORKER_HEARTBEAT_FILE"] = heartbeat_file
28+
example.run
29+
ENV.delete("WORKER_HEARTBEAT_METHOD")
30+
ENV.delete("WORKER_HEARTBEAT_FILE")
31+
end
32+
33+
context "with an existing heartbeat file" do
34+
it "sets initial and subsequent heartbeats" do
35+
expect(File).to receive(:exist?).with(heartbeat_file).and_return(true, true)
36+
expect(File).to receive(:mtime).with(heartbeat_file).and_return(first_heartbeat, first_heartbeat + 5)
37+
38+
[0, 5].each do |i|
39+
Timecop.freeze(first_heartbeat) do
40+
miq_server.worker_heartbeat(pid)
41+
miq_server.validate_heartbeat(worker)
42+
end
43+
44+
expect(worker.reload.last_heartbeat).to be_within(1.second).of(first_heartbeat + i)
45+
end
46+
end
47+
end
48+
49+
context "with a missing heartbeat file" do
50+
it "sets initial heartbeat only" do
51+
expect(File).to receive(:exist?).with(heartbeat_file).and_return(false).exactly(4).times
52+
expect(File).to receive(:mtime).with(heartbeat_file).never
53+
54+
# This has different results first iteration of the loop compared to
55+
# the rest:
56+
# 1. Sets the initial heartbeat
57+
# 2. Doesn't update the worker's last_heartbeat value after that
58+
#
59+
# So the result from the database should not change after the first
60+
# iteration of the loop
61+
[0, 5, 10, 15].each do |i|
62+
Timecop.freeze(first_heartbeat + i) do
63+
miq_server.worker_heartbeat(pid)
64+
miq_server.validate_heartbeat(worker)
65+
end
66+
67+
expect(worker.reload.last_heartbeat).to be_within(1.second).of(first_heartbeat)
68+
end
69+
end
70+
end
71+
72+
context "with a missing heartbeat file on the first validate" do
73+
it "sets initial heartbeat default, and updates the heartbeat from the file second" do
74+
expect(File).to receive(:exist?).with(heartbeat_file).and_return(false, true)
75+
expect(File).to receive(:mtime).with(heartbeat_file).and_return(first_heartbeat + 5)
76+
77+
[0, 5].each do |i|
78+
Timecop.freeze(first_heartbeat) do
79+
miq_server.worker_heartbeat(pid)
80+
miq_server.validate_heartbeat(worker)
81+
end
82+
83+
expect(worker.reload.last_heartbeat).to be_within(1.second).of(first_heartbeat + i)
84+
end
85+
end
86+
end
87+
end
1888
end
1989
end

0 commit comments

Comments
 (0)