Module: OsCtld::ContainerControl::Utils::Runscript::Runner
- Included in:
- Commands::Exec::Runner, Commands::Runscript::Runner
- Defined in:
- lib/osctld/container_control/utils/runscript.rb
Constant Summary collapse
- TRANSIENT_READY_TIMEOUT =
30- TRANSIENT_EXIT_TIMEOUT =
5
Instance Method Summary collapse
-
#osctld_wrapper_callback ⇒ Object
Callback to osctld to relocate self-process from container's wrapper cgroup.
-
#runscript_run(opts) ⇒ Object
Execute script in a stopped container.
- #setup_network ⇒ Object
- #stop_transient_runner(pid, graceful: true) ⇒ Object
- #wait_transient_ready(io) ⇒ Object
-
#with_configured_network(opts) ⇒ Object
Start container with lxc-init, configure network and yield.
Instance Method Details
#osctld_wrapper_callback ⇒ Object
Callback to osctld to relocate self-process from container's wrapper cgroup
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/osctld/container_control/utils/runscript.rb', line 241 def osctld_wrapper_callback s = UNIXSocket.new("/run/osctl/user-control/#{Process.uid}.sock") payload = { cmd: :ct_wrapper_start, opts: { id: ctid, pool:, pid: Process.pid } } s.send("#{payload.to_json}\n", 0) ret = JSON.parse(s.readline, symbolize_names: true) s.close return if ret[:status] raise "Error during ct_wrapper_start callback: #{ret[:message]}" end |
#runscript_run(opts) ⇒ Object
Execute script in a stopped container
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/osctld/container_control/utils/runscript.rb', line 80 def runscript_run(opts) pid = Process.fork do Process.setpgrp if opts[:process_group] cur_stdin = opts.fetch(:stdin, stdin) cur_stdout = opts.fetch(:stdout, stdout) cur_stderr = opts.fetch(:stderr, stderr) if cur_stdin $stdin.reopen(cur_stdin) else $stdin.close end $stdout.reopen(cur_stdout) $stderr.reopen(cur_stderr) if cur_stderr opts[:close_fds] && opts[:close_fds].each(&:close) setup_exec_run_env osctld_wrapper_callback cmd = [ 'lxc-execute', '-P', lxc_home, '-n', ctid, '-o', log_file, '-s', "lxc.environment=PATH=#{system_path.join(':')}", '-s', 'lxc.environment=HOME=/root', '-s', 'lxc.environment=USER=root', '--', opts[:script] ] # opts[:cmd] can contain an arbitrary command with multiple arguments # and quotes, so the mapping to process arguments is not clear. We use # the shell to handle this. Process.exec("exec #{cmd.join(' ')}") end if opts[:wait] === false pid else _, status = Process.wait2(pid) wait_for_lxc_stopped ok(status.exitstatus) end end |
#setup_network ⇒ Object
228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/osctld/container_control/utils/runscript.rb', line 228 def setup_network raise 'transient network channel unavailable' unless network_socket network_socket.write("ready\n") ret = JSON.parse(network_socket.readline, symbolize_names: true) ret[:status] ? nil : error(ret[:message]) rescue StandardError => e error("network setup failed: #{e.}") ensure network_socket&.close end |
#stop_transient_runner(pid, graceful: true) ⇒ Object
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/osctld/container_control/utils/runscript.rb', line 186 def stop_transient_runner(pid, graceful: true) grace = graceful ? TRANSIENT_EXIT_TIMEOUT : 0 deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + grace loop do waited_pid, status = Process.wait2(pid, Process::WNOHANG) return status if waited_pid break if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline sleep(0.05) end # Stop the payload while its monitor still owns the LXC control # socket. Killing the monitor first makes LXC report stopped while # lxc-init and its children can survive, orphaned in their namespaces. lxc_ct.stop if lxc_ct.running? wait_for_process(pid, timeout: TRANSIENT_EXIT_TIMEOUT) rescue Errno::ECHILD nil end |
#wait_transient_ready(io) ⇒ Object
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/osctld/container_control/utils/runscript.rb', line 206 def wait_transient_ready(io) expected = "ready\n" received = '' deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + TRANSIENT_READY_TIMEOUT while received.bytesize < expected.bytesize remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC) raise Errno::ETIMEDOUT, 'transient init readiness timed out' if remaining <= 0 chunk = io.read_nonblock(expected.bytesize - received.bytesize, exception: false) case chunk when :wait_readable io.wait_readable(remaining) when nil raise EOFError, 'transient init closed readiness pipe' else received << chunk raise 'invalid transient init readiness' unless expected.start_with?(received) end end end |
#with_configured_network(opts) ⇒ Object
Start container with lxc-init, configure network and yield
opts has to contain path to a script that will be executed
by lxc-init. The purpose of this script is to keep the container running
while the network is being configured and the user command is executed.
The script has to write ready\n to standard output, then block on read
from standard input and exit.
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 |
# File 'lib/osctld/container_control/utils/runscript.rb', line 139 def with_configured_network(opts) ret = nil # Pipes for communicating with opts[:init_script] in_r, in_w = IO.pipe out_r, out_w = IO.pipe # Start the container with lxc-init runner_pid = runscript_run( id: ctid, script: opts[:init_script], stdin: in_r, stdout: out_w, stderr: nil, close_fds: [in_w, out_r], process_group: true, wait: false ) in_r.close out_w.close # Bound startup, not the user's command. Closing these pipes must # also happen on EOF, a malformed token or a failed network handshake. wait_transient_ready(out_r) ct_init_pid = wait_for_lxc_attachable ret = if ct_init_pid setup_network || yield else error('network setup failed: container is not attachable') end ret ensure [in_r, in_w, out_r, out_w].compact.each { |io| io.close unless io.closed? } begin if runner_pid # Keep readiness open until cleanup finishes. Early EOF would # make the daemon reap this runner before it stops the payload. stop_transient_runner(runner_pid, graceful: ret && ret[:status]) wait_for_lxc_stopped end ensure network_socket&.close unless network_socket&.closed? end end |