Skip to content

Commit d10011b

Browse files
committed
exec_background feature
1 parent a962bed commit d10011b

File tree

2 files changed

+157
-30
lines changed

2 files changed

+157
-30
lines changed

lib/hemdal/host.ex

Lines changed: 92 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,35 @@ defmodule Hemdal.Host do
120120
"""
121121
@spec exec(host_id(), Hemdal.Config.Alert.Command.t(), command_args()) ::
122122
{:ok, map()} | {:error, map()}
123-
def exec(host_id, cmd, args) do
124-
GenServer.call(via(host_id), {:exec, cmd, args}, @timeout_exec)
123+
@spec exec(host_id(), Hemdal.Config.Alert.Command.t(), command_args(), timeout()) ::
124+
{:ok, map()} | {:error, map()}
125+
def exec(host_id, cmd, args \\ [], timeout \\ @timeout_exec)
126+
127+
def exec(host_id, %Command{type: "line"} = cmd, _args, timeout) do
128+
GenServer.call(via(host_id), {:exec, cmd, []}, timeout)
129+
end
130+
131+
def exec(host_id, %Command{type: "script"} = cmd, args, timeout) do
132+
GenServer.call(via(host_id), {:exec, cmd, args}, timeout)
133+
end
134+
135+
def exec(host_id, %Command{type: "interactive"} = cmd, [pid], timeout) do
136+
GenServer.call(via(host_id), {:exec, cmd, [pid]}, timeout)
137+
end
138+
139+
@doc """
140+
Run or execute the command passed as parameter. It's needed to pass the host ID
141+
to find the process where to send the request, and the the command and the
142+
arguments to run the command.
143+
144+
It's returning a tuple for `:ok` or `:error`. The `:ok` tuple have a PID to know
145+
which process is in charge of running the background process and to know if it's
146+
still running or not.
147+
"""
148+
@spec exec_background(host_id(), Hemdal.Config.Alert.Command.t(), command_args()) ::
149+
{:ok, pid()} | {:error, any()}
150+
def exec_background(host_id, cmd, args \\ []) do
151+
GenServer.call(via(host_id), {:exec_background, self(), cmd, args}, @timeout_exec)
125152
end
126153

127154
@doc """
@@ -221,12 +248,49 @@ defmodule Hemdal.Host do
221248

222249
@impl GenServer
223250
@doc false
251+
def handle_call({:exec_background, pid, cmd, args}, _from, %__MODULE__{max_workers: :infinity} = state) do
252+
Logger.debug(
253+
"host => workers: #{state.workers}/infinity ; queue length: #{Queue.len(state.queue)}"
254+
)
255+
256+
send_result = &send(pid, &1)
257+
ref = spawn_monitor(fn -> run_in_background(cmd, args, send_result, state) end)
258+
{:reply, {:ok, ref}, %__MODULE__{state | workers: state.workers + 1}}
259+
end
260+
261+
def handle_call(
262+
{:exec_background, pid, cmd, args},
263+
from,
264+
%__MODULE__{max_workers: max_workers, workers: workers, queue: queue} = state
265+
)
266+
when workers >= max_workers do
267+
Logger.debug(
268+
"host => workers: #{workers}/#{max_workers} ; queue length: #{Queue.len(queue) + 1}"
269+
)
270+
271+
queue = Queue.in({{:exec_background, pid, cmd, args}, from}, queue)
272+
{:noreply, %__MODULE__{state | queue: queue}}
273+
end
274+
275+
def handle_call({:exec_background, pid, cmd, args}, _from, state) do
276+
send_result = &send(pid, &1)
277+
ref = spawn_monitor(fn -> run_in_background(cmd, args, send_result, state) end)
278+
workers = state.workers + 1
279+
280+
Logger.debug(
281+
"host => workers: #{workers}/#{state.max_workers} ; queue length: #{Queue.len(state.queue)}"
282+
)
283+
284+
{:reply, {:ok, ref}, %__MODULE__{state | workers: workers}}
285+
end
286+
224287
def handle_call({:exec, cmd, args}, from, %__MODULE__{max_workers: :infinity} = state) do
225288
Logger.debug(
226289
"host => workers: #{state.workers}/infinity ; queue length: #{Queue.len(state.queue)}"
227290
)
228291

229-
spawn_monitor(fn -> run_in_background(cmd, args, from, state) end)
292+
send_result = &GenServer.reply(from, &1)
293+
spawn_monitor(fn -> run_in_background(cmd, args, send_result, state) end)
230294
{:noreply, %__MODULE__{state | workers: state.workers + 1}}
231295
end
232296

@@ -240,11 +304,13 @@ defmodule Hemdal.Host do
240304
"host => workers: #{workers}/#{max_workers} ; queue length: #{Queue.len(queue) + 1}"
241305
)
242306

243-
{:noreply, %__MODULE__{state | queue: Queue.in({from, cmd, args}, queue)}}
307+
queue = Queue.in({{:exec, cmd, args}, from}, queue)
308+
{:noreply, %__MODULE__{state | queue: queue}}
244309
end
245310

246311
def handle_call({:exec, cmd, args}, from, state) do
247-
spawn_monitor(fn -> run_in_background(cmd, args, from, state) end)
312+
send_result = &GenServer.reply(from, &1)
313+
spawn_monitor(fn -> run_in_background(cmd, args, send_result, state) end)
248314
workers = state.workers + 1
249315

250316
Logger.debug(
@@ -259,13 +325,13 @@ defmodule Hemdal.Host do
259325
def handle_cast({:update, host}, state) do
260326
case {host.max_workers, state.host.max_workers} do
261327
{max_workers, max_workers} ->
262-
{:noreply, %__MODULE__{host: host}}
328+
{:noreply, %__MODULE__{state | host: host}}
263329

264330
{:infinity, _} ->
265-
{:noreply, %__MODULE__{host: host, max_workers: :infinity}}
331+
{:noreply, %__MODULE__{state | host: host, max_workers: :infinity}}
266332

267333
{new_max_workers, old_max_workers} when new_max_workers < old_max_workers ->
268-
{:noreply, %__MODULE__{host: host, max_workers: new_max_workers}}
334+
{:noreply, %__MODULE__{state | host: host, max_workers: new_max_workers}}
269335

270336
{new_max_workers, _old_max_workers} ->
271337
state =
@@ -284,8 +350,8 @@ defmodule Hemdal.Host do
284350
do: state
285351

286352
defp launch_extra(state, false) do
287-
{{:value, {from, cmd, args}}, queue} = Queue.out(state.queue)
288-
{:noreply, state} = handle_call({:exec, cmd, args}, from, %__MODULE__{state | queue: queue})
353+
{{:value, {info, from}}, queue} = Queue.out(state.queue)
354+
{:noreply, state} = handle_call(info, from, %__MODULE__{state | queue: queue})
289355
launch_extra(state, Queue.is_empty(state.queue))
290356
end
291357

@@ -298,9 +364,10 @@ defmodule Hemdal.Host do
298364
Logger.debug("host => workers: #{workers}/#{state.max_workers} ; queue length: 0")
299365
{:noreply, %__MODULE__{state | workers: workers}}
300366

301-
{{:value, {from, cmd, args}}, queue} ->
367+
{{:value, {{:exec, cmd, args}, from}}, queue} ->
302368
state = %__MODULE__{state | queue: queue}
303-
spawn_monitor(fn -> run_in_background(cmd, args, from, state) end)
369+
send_result = &GenServer.reply(from, &1)
370+
spawn_monitor(fn -> run_in_background(cmd, args, send_result, state) end)
304371

305372
Logger.debug(
306373
"host => workers: #{state.workers}/#{state.max_workers} ; queue length: #{Queue.len(queue)}"
@@ -338,19 +405,20 @@ defmodule Hemdal.Host do
338405
{:error, %{"message" => other, "status" => "FAIL"}}
339406
end
340407

341-
defp run_in_background(cmd, args, from, %__MODULE__{host: %_{module: mod} = host}) do
342-
result =
343-
mod.transaction(host, fn handler ->
344-
with {:ok, errorlevel, output} <- exec_cmd(handler, mod, cmd, args),
345-
{:ok, %{"status" => status} = data} <- decode(output) do
346-
Logger.debug("command exit(#{errorlevel}) output: #{inspect(data)}")
347-
run_result(data, errorlevel, status)
348-
else
349-
other -> other
350-
end
351-
end)
408+
defp run_in_background(cmd, args, send_result, %__MODULE__{host: %_{module: mod} = host}) do
409+
mod.transaction(host, fn handler ->
410+
with {:ok, errorlevel, output} <- exec_cmd(handler, mod, cmd, args),
411+
{:ok, %{"status" => status} = data} <- decode(output) do
412+
Logger.debug("command exit(#{errorlevel}) output: #{inspect(data)}")
413+
run_result(data, errorlevel, status)
414+
else
415+
other -> other
416+
end
417+
end)
418+
|> final_run_result()
419+
|> send_result.()
352420

353-
:ok = GenServer.reply(from, final_run_result(result))
421+
:ok
354422
end
355423

356424
defp decode(output) do

test/hemdal/host_test.exs

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ defmodule Hemdal.HostTest do
4040
refute Hemdal.Host.exists?(host.id)
4141
end
4242

43+
test "start all & stop all" do
44+
assert [] == Hemdal.Host.get_all()
45+
assert :ok == Hemdal.Host.start_all()
46+
refute [] == Hemdal.Host.get_all()
47+
end
48+
4349
test "get all hosts" do
4450
host = Hemdal.Config.get_host_by_id!("78eb75f9-2ac7-434c-a1a2-330b23c89982")
4551

@@ -106,7 +112,64 @@ defmodule Hemdal.HostTest do
106112
command: ~s|echo '{"status": "OK", "message": "hello world!"}'|
107113
}
108114

109-
assert {:ok, %{"message" => "hello world!"}} = Hemdal.Host.exec(host_id, echo, [])
115+
assert {:ok, %{"message" => "hello world!"}} = Hemdal.Host.exec(host_id, echo)
116+
end
117+
118+
test "run background shell command" do
119+
assert :ok = Hemdal.Host.reload_all()
120+
assert [host_id, _] = Hemdal.Host.get_all()
121+
122+
echo = %Hemdal.Config.Alert.Command{
123+
name: "hello world!",
124+
type: "line",
125+
command: ~s|echo '{"status": "OK", "message": "hello world!"}'|
126+
}
127+
128+
assert {:ok, {pid, ref}} = Hemdal.Host.exec_background(host_id, echo)
129+
assert is_pid(pid)
130+
assert is_reference(ref)
131+
assert_receive {:ok, %{"message" => "hello world!"}}, 5_000
132+
end
133+
134+
test "run script" do
135+
assert :ok = Hemdal.Host.reload_all()
136+
assert [host_id, _] = Hemdal.Host.get_all()
137+
138+
echo = %Hemdal.Config.Alert.Command{
139+
name: "hello world!",
140+
type: "script",
141+
command: """
142+
#!/bin/bash
143+
144+
MESSAGE="Hello World!"
145+
STATUS="OK"
146+
147+
echo '{"status": "'$STATUS'", "message": "'$MESSAGE'"}'
148+
"""
149+
}
150+
151+
assert {:ok, %{"message" => "Hello World!"}} = Hemdal.Host.exec(host_id, echo)
152+
end
153+
154+
test "run background script" do
155+
assert :ok = Hemdal.Host.reload_all()
156+
assert [host_id, _] = Hemdal.Host.get_all()
157+
158+
echo = %Hemdal.Config.Alert.Command{
159+
name: "hello world!",
160+
type: "script",
161+
command: """
162+
#!/bin/bash
163+
164+
MESSAGE="Hello World!"
165+
STATUS="OK"
166+
167+
echo '{"status": "'$STATUS'", "message": "'$MESSAGE'"}'
168+
"""
169+
}
170+
171+
assert {:ok, {_pid, _ref}} = Hemdal.Host.exec_background(host_id, echo)
172+
assert_receive {:ok, %{"message" => "Hello World!"}}, 5_000
110173
end
111174

112175
test "run interactive shell command" do
@@ -121,11 +184,7 @@ defmodule Hemdal.HostTest do
121184

122185
pid =
123186
spawn_link(fn ->
124-
pid =
125-
receive do
126-
{:start, pid} -> pid
127-
end
128-
187+
assert_receive {:start, pid}
129188
send(pid, {:data, ~s|{"status": "OK",\n|})
130189
assert_receive {:continue, ~s|{"status": "OK",\n|}
131190
send(pid, {:data, ~s| "message": "hello world!"}\n|})

0 commit comments

Comments
 (0)