Skip to content

Commit 4c8f2ce

Browse files
nshoesjjcarstens
andauthored
Use scripts plumbing for connecting code (#2399)
Solves #2395. This uses `scripts/run` to push connecting code to the device rather than broadcasting it to every device node. I added telemetry events for when the connecting code succeeds or fails, as well as logging when it fails. The contract for "failing" a script run is in NHL, which I don't love, but can be fixed at a later time. --------- Co-authored-by: Jon Carstens <[email protected]> Co-authored-by: Jon Carstens <[email protected]>
1 parent 4fc4999 commit 4c8f2ce

File tree

5 files changed

+67
-32
lines changed

5 files changed

+67
-32
lines changed

lib/nerves_hub/logger.ex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ defmodule NervesHub.Logger do
3030
[:phoenix, :endpoint, :stop],
3131
[:nerves_hub, :devices, :invalid_auth],
3232
[:nerves_hub, :devices, :connect],
33+
[:nerves_hub, :devices, :connecting_code_failure],
3334
[:nerves_hub, :devices, :disconnect],
3435
[:nerves_hub, :devices, :duplicate_connection],
3536
[:nerves_hub, :devices, :update, :automatic],
@@ -94,6 +95,14 @@ defmodule NervesHub.Logger do
9495
)
9596
end
9697

98+
def log_event([:nerves_hub, :devices, :connecting_code_failure], _, metadata, _) do
99+
Logger.info("Connecting code failure",
100+
event: "nerves_hub.devices.connecting_code_failure",
101+
output: metadata[:output],
102+
identifier: metadata[:identifier]
103+
)
104+
end
105+
97106
def log_event([:nerves_hub, :devices, :duplicate_connection], _, metadata, _) do
98107
Logger.info("Device duplicate connection detected",
99108
event: "nerves_hub.devices.duplicate_connection",

lib/nerves_hub/scripts/runner.ex

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,7 @@ defmodule NervesHub.Scripts.Runner do
6262
def handle_info({:error, :incompatible_version}, state) do
6363
text = ~s/#{state.text}\n# [NERVESHUB:END]/
6464

65-
text
66-
|> String.graphemes()
67-
|> Enum.each(fn character ->
68-
Endpoint.broadcast_from!(self(), state.send_channel, "dn", %{"data" => character})
69-
end)
65+
Endpoint.broadcast_from!(self(), state.send_channel, "dn", %{"data" => text})
7066

7167
_ = Endpoint.subscribe(state.receive_channel)
7268

lib/nerves_hub_web/channels/console_channel.ex

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
defmodule NervesHubWeb.ConsoleChannel do
33
use Phoenix.Channel
44

5-
alias NervesHub.Repo
65
alias Phoenix.Socket.Broadcast
76

87
def join("console", payload, %{assigns: %{device: device}} = socket) do
@@ -75,30 +74,6 @@ defmodule NervesHubWeb.ConsoleChannel do
7574
%{}
7675
)
7776

78-
# now that the console is connected, push down the device's elixir, line by line
79-
device = socket.assigns.device
80-
device = Repo.preload(device, [:deployment_group])
81-
82-
if device.deployment_group && device.deployment_group.connecting_code do
83-
device.deployment_group.connecting_code
84-
|> String.graphemes()
85-
|> Enum.each(fn character ->
86-
push(socket, "dn", %{"data" => character})
87-
end)
88-
89-
push(socket, "dn", %{"data" => "\r"})
90-
end
91-
92-
if device.connecting_code do
93-
device.connecting_code
94-
|> String.graphemes()
95-
|> Enum.each(fn character ->
96-
push(socket, "dn", %{"data" => character})
97-
end)
98-
99-
push(socket, "dn", %{"data" => "\r"})
100-
end
101-
10277
{:noreply, socket}
10378
end
10479

lib/nerves_hub_web/channels/device_channel.ex

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,36 @@ defmodule NervesHubWeb.DeviceChannel do
6363

6464
def handle_info({:after_join, params}, socket) do
6565
%{device: device, reference_id: reference_id} = socket.assigns
66+
67+
# :deployment_group is manually set to nil in DeviceLink, need to force reload here
68+
device = NervesHub.Repo.preload(device, :deployment_group, force: true)
69+
70+
connecting_codes =
71+
[
72+
get_in(device, [Access.key(:deployment_group), Access.key(:connecting_code)]),
73+
device.connecting_code
74+
]
75+
|> Enum.filter(&(not is_nil(&1) and byte_size(&1) > 0))
76+
77+
case [safe_to_run_scripts?(socket), Enum.empty?(connecting_codes)] do
78+
[true, false] ->
79+
connecting_code = Enum.join(connecting_codes, "\n")
80+
# connecting code first incase it attempts to change things before the other messages
81+
push(socket, "scripts/run", %{"text" => connecting_code, "ref" => "connecting_code"})
82+
83+
[false, false] ->
84+
connecting_code = Enum.join(connecting_codes, "\n")
85+
text = ~s/#{connecting_code}\n\r/
86+
topic = "device:console:#{device.id}"
87+
88+
socket.endpoint.broadcast_from!(self(), topic, "dn", %{"data" => text})
89+
90+
_ ->
91+
:ok
92+
end
93+
6694
:ok = DeviceLink.after_join(device, reference_id, params)
95+
6796
{:noreply, socket}
6897
end
6998

@@ -80,7 +109,7 @@ defmodule NervesHubWeb.DeviceChannel do
80109
end
81110

82111
def handle_info({:run_script, pid, text}, socket) do
83-
if Version.match?(socket.assigns.device_api_version, ">= 2.1.0") do
112+
if safe_to_run_scripts?(socket) do
84113
ref = Base.encode64(:crypto.strong_rand_bytes(4), padding: false)
85114

86115
push(socket, "scripts/run", %{"text" => text, "ref" => ref})
@@ -172,8 +201,28 @@ defmodule NervesHubWeb.DeviceChannel do
172201
{:noreply, socket}
173202
end
174203

204+
def handle_in(
205+
"scripts/run",
206+
%{"ref" => "connecting_code", "result" => result, "return" => return, "output" => output},
207+
socket
208+
)
209+
when result == "error" or return == "nil" do
210+
:telemetry.execute([:nerves_hub, :devices, :connecting_code_failure], %{
211+
output: output,
212+
identifier: socket.assigns.device.identifier
213+
})
214+
215+
{:noreply, socket}
216+
end
217+
218+
def handle_in("scripts/run", %{"ref" => "connecting_code"}, socket) do
219+
:telemetry.execute([:nerves_hub, :devices, :connecting_code_success], %{count: 1})
220+
221+
{:noreply, socket}
222+
end
223+
175224
def handle_in("scripts/run", params, socket) do
176-
if pid = socket.assigns.script_refs[params["ref"]] do
225+
if pid = get_in(socket.assigns, [:script_refs, params["ref"]]) do
177226
output = Enum.join([params["output"], params["return"]], "\n")
178227
output = String.trim(output)
179228
send(pid, {:output, output})
@@ -260,4 +309,6 @@ defmodule NervesHubWeb.DeviceChannel do
260309
"deployment:#{device.deployment_id}"
261310
end
262311
end
312+
313+
defp safe_to_run_scripts?(socket), do: Version.match?(socket.assigns.device_api_version, ">= 2.1.0")
263314
end

test/support/socket_client.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,10 @@ defmodule SocketClient do
188188
{:ok, socket}
189189
end
190190

191+
def handle_message("device", "scripts/run", %{}, socket) do
192+
{:ok, socket}
193+
end
194+
191195
@impl Slipstream
192196
def handle_call(:connected?, _from, socket) do
193197
{:reply, socket.assigns.connected?, socket}

0 commit comments

Comments
 (0)