Class: OsCtld::Monitor::Process
- Inherits:
-
Object
- Object
- OsCtld::Monitor::Process
- Includes:
- OsCtl::Lib::Utils::Log
- Defined in:
- lib/osctld/monitor/process.rb
Class Method Summary collapse
Instance Method Summary collapse
- #cancel_exited_run(ct) ⇒ Object protected
- #check_exiting_runs ⇒ Object protected
-
#initialize(pool, user, group, stdout, containers:) ⇒ Process
constructor
A new instance of Process.
- #monitor ⇒ Object
- #parse(line) ⇒ Object protected
- #update_state(change) ⇒ Object protected
- #wait_for_init_pid(ct) ⇒ Object protected
Constructor Details
#initialize(pool, user, group, stdout, containers:) ⇒ Process
Returns a new instance of Process.
57 58 59 60 61 62 63 64 |
# File 'lib/osctld/monitor/process.rb', line 57 def initialize(pool, user, group, stdout, containers:) @pool = pool @user = user @group = group @stdout = stdout @containers = containers @last_line = nil end |
Class Method Details
.cgroup_path(ct) ⇒ Object
53 54 55 |
# File 'lib/osctld/monitor/process.rb', line 53 def self.cgroup_path(ct) File.join(ct.group.full_cgroup_path(ct.user), 'monitor') end |
.spawn(ct) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/osctld/monitor/process.rb', line 7 def self.spawn(ct) cg_path = cgroup_path(ct) out_r, out_w = IO.pipe CGroup.mkpath_all(cg_path.split('/')) pid = Process.fork do $stdout.reopen(out_w) out_r.close SwitchUser.switch_to( ct.user.sysusername, ct.user.ugid, ct.user.homedir, cg_path ) Process.exec('lxc-monitor', '-P', ct.lxc_home, '-n', '.*') end out_w.close [pid, out_r] end |
.stop_monitord(ct) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/osctld/monitor/process.rb', line 31 def self.stop_monitord(ct) pid = Process.fork do SwitchUser.switch_to( ct.user.sysusername, ct.user.ugid, ct.user.homedir, cgroup_path(ct) ) Process.exec('lxc-monitor', '-P', ct.lxc_home, '--quit') end _, status = Process.wait2(pid) if status.exitstatus == 0 log(:info, :monitor, 'Stopped lxc-monitord') else log(:info, :monitor, 'Failed to stop lxc-monitord') end end |
Instance Method Details
#cancel_exited_run(ct) ⇒ Object (protected)
188 189 190 191 192 193 194 |
# File 'lib/osctld/monitor/process.rb', line 188 def cancel_exited_run(ct) ct.run_conf&.nfs_cancellation&.abort rescue StandardError => e # A failed cancellation must be visible without killing the shared # pool/user/group monitor and losing future state changes. log(:warn, :monitor, "Unable to cancel NFS for exited container #{ct.ident}: #{e.}") end |
#check_exiting_runs ⇒ Object (protected)
99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/osctld/monitor/process.rb', line 99 def check_exiting_runs @containers.call.each do |ct| next unless ct.pool == @pool && ct.user == @user && ct.group == @group run_conf = ct.run_conf next unless run_conf run_conf.nfs_cancellation.capture(run_conf.init_pid) run_conf.nfs_cancellation.abort_if_exiting rescue StandardError => e log(:warn, :monitor, "Unable to check exiting container #{ct.ident}: #{e.}") end end |
#monitor ⇒ Object
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/osctld/monitor/process.rb', line 66 def monitor exit_checks = Queue.new exit_thread = Thread.new do check_exiting_runs until exit_checks.pop(timeout: 1) end until @stdout.eof? line = @stdout.readline next if line == @last_line @last_line = line state = parse(line) next unless state update_state(state) end true rescue IOError log(:info, :monitor, "Monitoring of #{@pool.name}:#{@user.name}:#{@group.name} failed") false ensure begin exit_checks << true if exit_checks exit_thread&.join ensure @stdout.close unless @stdout.closed? end end |
#parse(line) ⇒ Object (protected)
113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/osctld/monitor/process.rb', line 113 def parse(line) if /'([^']+)' changed state to \[([^\]]+)\]/ =~ line log(:info, :monitor, "Container #{@pool.name}:#{::Regexp.last_match(1)} entered state #{::Regexp.last_match(2)}") return { pool: @pool.name, ctid: ::Regexp.last_match(1), state: ::Regexp.last_match(2).downcase.to_sym } elsif /'([^']+)' exited with status \[(\d+)\]/ =~ line log(:info, :monitor, "Container #{@pool.name}:#{::Regexp.last_match(1)} exited with #{::Regexp.last_match(2)}") else log(:warn, :monitor, "Line from lxc-monitor not recognized: '#{line}'") end nil end |
#update_state(change) ⇒ Object (protected)
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/osctld/monitor/process.rb', line 128 def update_state(change) ct = DB::Containers.find(change[:ctid], change[:pool]) unless ct log(:warn, :monitor, "Container #{change[:pool]}:#{change[:ctid]} not found") return end return if ct.state == :error # PID1 has exited by STOPPING, but the LXC monitor can still block while # releasing NFS mounts. Cancel the retained run before publishing a # terminal state that lets a new start discard its namespace handles. if %i[stopping stopped aborted].include?(change[:state]) cancel_exited_run(ct) end # When transitioning to `running`, send the event only after init_pid was set # below, so that when {Commands::Container::Start} finishes waiting and returns, # the init_pid is not nil. if change[:state] != :running Eventd.report(:state, pool: ct.pool.name, id: ct.id, state: change[:state]) end ct.state = change[:state] init_pid = nil case ct.state when :running begin init_pid = wait_for_init_pid(ct) if init_pid init_pid = ct.set_init_pid(init_pid) ct.ensure_run_conf.nfs_cancellation.capture(init_pid) if init_pid end rescue ContainerControl::Error, SystemCallError, IOError => e init_pid = nil log(:warn, :monitor, "Unable to get state of container #{ct.ident}: #{e.}") end Eventd.report(:state, pool: ct.pool.name, id: ct.id, state: change[:state]) if init_pid Eventd.report(:ct_init_pid, pool: ct.pool.name, id: ct.id, init_pid:) end Hook.run(ct, :post_start, init_pid: ct.init_pid) when :aborting # It has happened that ct.run_conf was nil, circumstances unknown ct.ensure_run_conf.aborted = true when :stopping Hook.run(ct, :on_stop) when :stopped, :aborted ct.mounts.prune end end |
#wait_for_init_pid(ct) ⇒ Object (protected)
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/osctld/monitor/process.rb', line 196 def wait_for_init_pid(ct) deadline = Time.now + 10 st = nil loop do st = ContainerControl::Commands::State.run!(ct) return st.init_pid if st.init_pid break if Time.now >= deadline sleep(0.1) end log(:warn, :monitor, "Container #{ct.ident} is running without an init PID, last LXC state was #{st&.state || 'unknown'}") nil end |