From d34b3d289475f49d40f302dc732123970f139635 Mon Sep 17 00:00:00 2001 From: Nate Shoemaker Date: Mon, 25 Nov 2024 19:03:11 -0800 Subject: [PATCH] Clear deployment and set reason if firmware doesn't match on connect, clean up websocket_test --- lib/nerves_hub/deployments.ex | 100 ++------ lib/nerves_hub/devices.ex | 26 +-- lib/nerves_hub/devices/device.ex | 4 + lib/nerves_hub_web/channels/device_channel.ex | 3 +- .../live/deployments/edit.html.heex | 21 +- lib/nerves_hub_web/views/api/device_view.ex | 2 - ...914_add_deployment_conflict_to_devices.exs | 9 + test/nerves_hub/deployments_test.exs | 168 -------------- test/nerves_hub/devices_test.exs | 127 ---------- .../channels/device_channel_test.exs | 48 ---- .../channels/websocket_test.exs | 219 ++++++++++++------ 11 files changed, 189 insertions(+), 538 deletions(-) create mode 100644 priv/repo/migrations/20241126000914_add_deployment_conflict_to_devices.exs diff --git a/lib/nerves_hub/deployments.ex b/lib/nerves_hub/deployments.ex index 610daca56..ae909ec9c 100644 --- a/lib/nerves_hub/deployments.ex +++ b/lib/nerves_hub/deployments.ex @@ -6,7 +6,6 @@ defmodule NervesHub.Deployments do alias NervesHub.AuditLogs alias NervesHub.Deployments.Deployment alias NervesHub.Deployments.InflightDeploymentCheck - alias NervesHub.Devices alias NervesHub.Devices.Device alias NervesHub.Products.Product alias NervesHub.Repo @@ -276,38 +275,6 @@ defmodule NervesHub.Deployments do Phoenix.PubSub.broadcast(NervesHub.PubSub, "deployment:#{id}", message) end - @doc """ - Find all potential deployments for a device - - Based on the product, firmware platform, firmware architecture, and device tags - """ - def alternate_deployments(device, active \\ [true, false]) - def alternate_deployments(%Device{firmware_metadata: nil}, _active), do: [] - - def alternate_deployments(device, active) do - Deployment - |> join(:inner, [d], assoc(d, :firmware), as: :firmware) - |> preload([_, firmware: f], firmware: f) - |> where([d], d.product_id == ^device.product_id) - |> where([d], d.is_active in ^active) - |> ignore_same_deployment(device) - |> where([d, firmware: f], f.platform == ^device.firmware_metadata.platform) - |> where([d, firmware: f], f.architecture == ^device.firmware_metadata.architecture) - |> where([d], fragment("?->'tags' <@ to_jsonb(?::text[])", d.conditions, ^device.tags)) - |> Repo.all() - |> Enum.filter(&version_match?(device, &1)) - |> Enum.sort_by( - &{&1.firmware.version, &1.id}, - fn {a_vsn, a_id}, {b_vsn, b_id} -> - case Version.compare(a_vsn, b_vsn) do - :lt -> false - :eq -> a_id <= b_id - :gt -> true - end - end - ) - end - @doc """ Find all potential devices for a deployment @@ -336,61 +303,36 @@ defmodule NervesHub.Deployments do def version_match?(_device, _deployment), do: true - defp ignore_same_deployment(query, %{deployment_id: nil}), do: query - - defp ignore_same_deployment(query, %{deployment_id: deployment_id}) do - where(query, [d], d.id != ^deployment_id) - end - - @doc """ - If the device is missing a deployment, find a matching deployment - - Do nothing if a deployment is already set - """ - @spec set_deployment(Device.t()) :: Device.t() - def set_deployment(device) do - case alternate_deployments(device, [true]) do - [] -> - Logger.debug("No matching deployments for #{device.identifier}") - - device - - [deployment] -> - device - |> Devices.update_deployment(deployment) - |> preload_with_firmware_and_archive(true) - - [deployment | _] -> - Logger.debug( - "More than one deployment matches for #{device.identifier}, setting to the first" - ) - - device - |> Devices.update_deployment(deployment) - |> preload_with_firmware_and_archive(true) - end - end - - @spec maybe_set_deployment(Device.t()) :: Device.t() - def maybe_set_deployment(%Device{deployment_id: nil} = device), do: set_deployment(device) - def maybe_set_deployment(device), do: device - @spec verify_deployment_membership(Device.t()) :: Device.t() def verify_deployment_membership(%Device{deployment_id: deployment_id} = device) when not is_nil(deployment_id) do %{deployment: deployment} = device = Repo.preload(device, deployment: :firmware) + bad_architecture = device.firmware_metadata.architecture != deployment.firmware.architecture + bad_platform = device.firmware_metadata.platform != deployment.firmware.platform + + reason = + cond do + bad_architecture and bad_platform -> + :bad_architecture_and_platform - if device.firmware_metadata.platform == deployment.firmware.platform and - device.firmware_metadata.architecture == deployment.firmware.architecture do + bad_architecture -> + :bad_architecture + + bad_platform -> + :bad_platform + + true -> + nil + end + + if reason do device + |> Ecto.Changeset.change(%{deployment_id: nil, deployment_conflict: reason}) + |> Repo.update!() else - Devices.clear_deployment(device) + device end end def verify_deployment_membership(device), do: device - - def preload_with_firmware_and_archive(device, force \\ false) do - Repo.preload(device, [deployment: [:archive, :firmware]], force: force) - end end diff --git a/lib/nerves_hub/devices.ex b/lib/nerves_hub/devices.ex index 866cd3600..4625a1733 100644 --- a/lib/nerves_hub/devices.ex +++ b/lib/nerves_hub/devices.ex @@ -9,7 +9,6 @@ defmodule NervesHub.Devices do alias NervesHub.Accounts.User alias NervesHub.AuditLogs alias NervesHub.Certificate - alias NervesHub.Deployments alias NervesHub.Deployments.Deployment alias NervesHub.Deployments.Orchestrator alias NervesHub.Devices.CACertificate @@ -1035,7 +1034,7 @@ defmodule NervesHub.Devices do |> Repo.transaction() |> case do {:ok, %{move: updated}} -> - Deployments.set_deployment(updated) + # Deployments.set_deployment(updated) {:ok, updated} err -> @@ -1350,29 +1349,6 @@ defmodule NervesHub.Devices do |> Repo.one() end - @doc """ - Get the firmware status for a device - - "latest", "pending", or "updating" - """ - def firmware_status(device) do - device = Repo.preload(device, deployment: [:firmware]) - - cond do - is_nil(device.deployment_id) -> - "latest" - - get_in(device.firmware_metadata.uuid) == get_in(device.deployment.firmware.uuid) -> - "latest" - - !Enum.empty?(device.update_attempts) -> - "updating" - - true -> - "pending" - end - end - def enable_extension_setting(%Device{} = device, extension_string) do device = get_device(device.id) diff --git a/lib/nerves_hub/devices/device.ex b/lib/nerves_hub/devices/device.ex index b97fa3c19..edc6ce1b5 100644 --- a/lib/nerves_hub/devices/device.ex +++ b/lib/nerves_hub/devices/device.ex @@ -63,6 +63,10 @@ defmodule NervesHub.Devices.Device do field(:connecting_code, :string) field(:connection_metadata, :map, default: %{}) + field(:deployment_conflict, Ecto.Enum, + values: [:bad_architecture, :bad_platform, :bad_architecture_and_platform] + ) + timestamps() # Deprecated fields, replaced with device_connections table. diff --git a/lib/nerves_hub_web/channels/device_channel.ex b/lib/nerves_hub_web/channels/device_channel.ex index d1c71889e..e6fe13a8e 100644 --- a/lib/nerves_hub_web/channels/device_channel.ex +++ b/lib/nerves_hub_web/channels/device_channel.ex @@ -37,7 +37,6 @@ defmodule NervesHubWeb.DeviceChannel do device = device |> Deployments.verify_deployment_membership() - |> Deployments.maybe_set_deployment() maybe_send_public_keys(device, socket, params) @@ -457,7 +456,7 @@ defmodule NervesHubWeb.DeviceChannel do end defp maybe_send_archive(socket) do - device = socket.assigns.device + device = deployment_preload(socket.assigns.device) updates_enabled = device.updates_enabled && !Devices.device_in_penalty_box?(device) version_match = Version.match?(socket.assigns.device_api_version, ">= 2.0.0") diff --git a/lib/nerves_hub_web/live/deployments/edit.html.heex b/lib/nerves_hub_web/live/deployments/edit.html.heex index 1827d8167..8a7857066 100644 --- a/lib/nerves_hub_web/live/deployments/edit.html.heex +++ b/lib/nerves_hub_web/live/deployments/edit.html.heex @@ -67,13 +67,9 @@

Conditions

-
- - - Changing tags will reset all devices on this deployment -
+ <%= text_input(f, :tags, class: "form-control", id: "tags_input", @@ -86,13 +82,10 @@
-
- - - Changing the version requirement will reset all devices on this deployment -
+ + <%= text_input(f, :version, class: "form-control", id: "version_requirement", diff --git a/lib/nerves_hub_web/views/api/device_view.ex b/lib/nerves_hub_web/views/api/device_view.ex index 72e62bd8c..ea483bf5f 100644 --- a/lib/nerves_hub_web/views/api/device_view.ex +++ b/lib/nerves_hub_web/views/api/device_view.ex @@ -1,7 +1,6 @@ defmodule NervesHubWeb.API.DeviceView do use NervesHubWeb, :api_view - alias NervesHub.Devices alias NervesHub.Tracker def render("index.json", %{devices: devices, pagination: pagination}) do @@ -29,7 +28,6 @@ defmodule NervesHubWeb.API.DeviceView do last_communication: connection_last_seen_at(device), description: device.description, firmware_metadata: device.firmware_metadata, - firmware_update_status: Devices.firmware_status(device), deployment: render_one(device.deployment, __MODULE__, "deployment.json", as: :deployment), updates_enabled: device.updates_enabled, updates_blocked_until: device.updates_blocked_until, diff --git a/priv/repo/migrations/20241126000914_add_deployment_conflict_to_devices.exs b/priv/repo/migrations/20241126000914_add_deployment_conflict_to_devices.exs new file mode 100644 index 000000000..3aa9459b9 --- /dev/null +++ b/priv/repo/migrations/20241126000914_add_deployment_conflict_to_devices.exs @@ -0,0 +1,9 @@ +defmodule NervesHub.Repo.Migrations.AddDeploymentConflictToDevices do + use Ecto.Migration + + def change do + alter table(:devices) do + add(:deployment_conflict, :text) + end + end +end diff --git a/test/nerves_hub/deployments_test.exs b/test/nerves_hub/deployments_test.exs index 5747b2c3e..a8695f5b1 100644 --- a/test/nerves_hub/deployments_test.exs +++ b/test/nerves_hub/deployments_test.exs @@ -3,7 +3,6 @@ defmodule NervesHub.DeploymentsTest do import Phoenix.ChannelTest alias NervesHub.Deployments - alias NervesHub.Devices.Device alias NervesHub.Fixtures alias Ecto.Changeset @@ -125,171 +124,4 @@ defmodule NervesHub.DeploymentsTest do assert_broadcast("deployments/update", %{}, 500) end end - - describe "device's matching deployments" do - test "finds all matching deployments", state do - %{org: org, product: product, firmware: firmware} = state - - %{id: beta_deployment_id} = - Fixtures.deployment_fixture(org, firmware, %{ - name: "beta", - conditions: %{"tags" => ["beta"]} - }) - - %{id: rpi_deployment_id} = - Fixtures.deployment_fixture(org, firmware, %{ - name: "rpi", - conditions: %{"tags" => ["rpi"]} - }) - - Fixtures.deployment_fixture(org, firmware, %{ - name: "rpi0", - conditions: %{"tags" => ["rpi0"]} - }) - - device = Fixtures.device_fixture(org, product, firmware, %{tags: ["beta", "rpi"]}) - - assert [ - %{id: ^beta_deployment_id}, - %{id: ^rpi_deployment_id} - ] = Deployments.alternate_deployments(device) - end - - test "finds matching deployments including the platform", state do - %{org: org, org_key: org_key, product: product} = state - - rpi_firmware = Fixtures.firmware_fixture(org_key, product, %{platform: "rpi"}) - rpi0_firmware = Fixtures.firmware_fixture(org_key, product, %{platform: "rpi0"}) - - %{id: rpi_deployment_id} = - Fixtures.deployment_fixture(org, rpi_firmware, %{ - name: "rpi", - conditions: %{"tags" => ["rpi"]} - }) - - Fixtures.deployment_fixture(org, rpi0_firmware, %{ - name: "rpi0", - conditions: %{"tags" => ["rpi"]} - }) - - device = Fixtures.device_fixture(org, product, rpi_firmware, %{tags: ["beta", "rpi"]}) - - assert [%{id: ^rpi_deployment_id}] = Deployments.alternate_deployments(device) - end - - test "finds matching deployments including the architecture", state do - %{org: org, org_key: org_key, product: product} = state - - rpi_firmware = Fixtures.firmware_fixture(org_key, product, %{architecture: "rpi"}) - rpi0_firmware = Fixtures.firmware_fixture(org_key, product, %{architecture: "rpi0"}) - - %{id: rpi_deployment_id} = - Fixtures.deployment_fixture(org, rpi_firmware, %{ - name: "rpi", - conditions: %{"tags" => ["rpi"]} - }) - - Fixtures.deployment_fixture(org, rpi0_firmware, %{ - name: "rpi0", - conditions: %{"tags" => ["rpi"]} - }) - - device = Fixtures.device_fixture(org, product, rpi_firmware, %{tags: ["beta", "rpi"]}) - - assert [%{id: ^rpi_deployment_id}] = Deployments.alternate_deployments(device) - end - - test "finds matching deployments including the version", state do - %{org: org, product: product, firmware: firmware} = state - - %{id: low_deployment_id} = - Fixtures.deployment_fixture(org, firmware, %{ - name: "rpi", - conditions: %{"tags" => ["rpi"], "version" => "~> 1.0"} - }) - - Fixtures.deployment_fixture(org, firmware, %{ - name: "rpi0", - conditions: %{"tags" => ["rpi"], "version" => "~> 2.0"} - }) - - device = Fixtures.device_fixture(org, product, firmware, %{tags: ["beta", "rpi"]}) - - assert [%{id: ^low_deployment_id}] = Deployments.alternate_deployments(device) - end - - test "finds matching deployments including pre versions", state do - %{org: org, org_key: org_key, product: product, firmware: firmware} = state - - %{id: low_deployment_id} = - Fixtures.deployment_fixture(org, firmware, %{ - name: "rpi", - conditions: %{"tags" => ["rpi"], "version" => "~> 1.0"} - }) - - Fixtures.deployment_fixture(org, firmware, %{ - name: "rpi0", - conditions: %{"tags" => ["rpi"], "version" => "~> 2.0"} - }) - - firmware = Fixtures.firmware_fixture(org_key, product, %{version: "1.2.0-pre"}) - - device = Fixtures.device_fixture(org, product, firmware, %{tags: ["beta", "rpi"]}) - - assert [%{id: ^low_deployment_id}] = Deployments.alternate_deployments(device) - end - - test "finds the newest firmware version including pre-releases", state do - %{ - org: org, - org_key: org_key, - product: product, - firmware: %{version: "1.0.0"} = v100_firmware - } = state - - v090_fw = Fixtures.firmware_fixture(org_key, product, %{version: "0.9.0"}) - v100rc1_fw = Fixtures.firmware_fixture(org_key, product, %{version: "1.0.0-rc.1"}) - v100rc2_fw = Fixtures.firmware_fixture(org_key, product, %{version: "1.0.0-rc.2"}) - v101_fw = Fixtures.firmware_fixture(org_key, product, %{version: "1.0.1"}) - - %{id: v100_deployment_id} = - Fixtures.deployment_fixture(org, v100_firmware, %{ - name: v100_firmware.version, - conditions: %{"version" => "", "tags" => ["next"]} - }) - - %{id: v100rc1_deployment_id} = - Fixtures.deployment_fixture(org, v100rc1_fw, %{ - name: v100rc1_fw.version, - conditions: %{"version" => "", "tags" => ["next"]} - }) - - %{id: v100rc2_deployment_id} = - Fixtures.deployment_fixture(org, v100rc2_fw, %{ - name: v100rc2_fw.version, - conditions: %{"version" => "", "tags" => ["next"]} - }) - - %{id: v101_deployment_id} = - Fixtures.deployment_fixture(org, v101_fw, %{ - name: v101_fw.version, - conditions: %{"version" => "", "tags" => ["next"]} - }) - - device = Fixtures.device_fixture(org, product, v090_fw, %{tags: ["next"]}) - - assert [ - %{id: ^v101_deployment_id}, - %{id: ^v100_deployment_id}, - %{id: ^v100rc2_deployment_id}, - %{id: ^v100rc1_deployment_id} - ] = Deployments.alternate_deployments(device) - end - end - - test "alternate_deployments/2 ignores device without firmware metadata" do - assert [] == Deployments.alternate_deployments(%Device{firmware_metadata: nil}) - assert [] == Deployments.alternate_deployments(%Device{firmware_metadata: nil}, [true]) - assert [] == Deployments.alternate_deployments(%Device{firmware_metadata: nil}, [false]) - end end diff --git a/test/nerves_hub/devices_test.exs b/test/nerves_hub/devices_test.exs index b06fb1fb9..9c2761b60 100644 --- a/test/nerves_hub/devices_test.exs +++ b/test/nerves_hub/devices_test.exs @@ -720,133 +720,6 @@ defmodule NervesHub.DevicesTest do end end - describe "firmware status" do - test "latest: no deployment" do - user = Fixtures.user_fixture() - org = Fixtures.org_fixture(user, %{name: "org"}) - product = Fixtures.product_fixture(user, org) - org_key = Fixtures.org_key_fixture(org, user) - firmware = Fixtures.firmware_fixture(org_key, product) - - device = Fixtures.device_fixture(org, product, firmware, %{tags: ["alpha"]}) - - refute device.deployment_id - assert Devices.firmware_status(device) == "latest" - end - - test "latest: firmware matches the deployment" do - user = Fixtures.user_fixture() - org = Fixtures.org_fixture(user, %{name: "org"}) - product = Fixtures.product_fixture(user, org) - org_key = Fixtures.org_key_fixture(org, user) - firmware = Fixtures.firmware_fixture(org_key, product) - - deployment = - Fixtures.deployment_fixture(org, firmware, %{ - name: "alpha", - conditions: %{"tags" => ["alpha"]} - }) - - {:ok, deployment} = Deployments.update_deployment(deployment, %{is_active: true}) - - device = Fixtures.device_fixture(org, product, firmware, %{tags: ["alpha"]}) - device = Deployments.set_deployment(device) - - assert device.deployment_id == deployment.id - assert device.updates_enabled - - assert Devices.firmware_status(device) == "latest" - end - - test "updating: there are update attempts" do - user = Fixtures.user_fixture() - org = Fixtures.org_fixture(user, %{name: "org"}) - product = Fixtures.product_fixture(user, org) - org_key = Fixtures.org_key_fixture(org, user) - firmware_one = Fixtures.firmware_fixture(org_key, product) - firmware_two = Fixtures.firmware_fixture(org_key, product) - - deployment = - Fixtures.deployment_fixture(org, firmware_one, %{ - name: "alpha", - conditions: %{"tags" => ["alpha"]} - }) - - {:ok, deployment} = Deployments.update_deployment(deployment, %{is_active: true}) - - device = Fixtures.device_fixture(org, product, firmware_two, %{tags: ["alpha"]}) - device = Deployments.set_deployment(device) - {:ok, device} = Devices.update_attempted(device) - - assert device.deployment_id == deployment.id - assert device.updates_enabled - - assert Devices.firmware_status(device) == "updating" - end - - test "pending: updates are disabled" do - user = Fixtures.user_fixture() - org = Fixtures.org_fixture(user, %{name: "org"}) - product = Fixtures.product_fixture(user, org) - org_key = Fixtures.org_key_fixture(org, user) - firmware_one = Fixtures.firmware_fixture(org_key, product) - firmware_two = Fixtures.firmware_fixture(org_key, product) - - deployment = - Fixtures.deployment_fixture(org, firmware_one, %{ - name: "alpha", - conditions: %{"tags" => ["alpha"]} - }) - - {:ok, deployment} = Deployments.update_deployment(deployment, %{is_active: true}) - - device = Fixtures.device_fixture(org, product, firmware_two, %{tags: ["alpha"]}) - device = Deployments.set_deployment(device) - {:ok, device} = Devices.disable_updates(device, user) - - assert device.deployment_id == deployment.id - refute device.updates_enabled - - assert Devices.firmware_status(device) == "pending" - end - - test "pending: still waiting on updating" do - user = Fixtures.user_fixture() - org = Fixtures.org_fixture(user, %{name: "org"}) - product = Fixtures.product_fixture(user, org) - org_key = Fixtures.org_key_fixture(org, user) - firmware_one = Fixtures.firmware_fixture(org_key, product) - firmware_two = Fixtures.firmware_fixture(org_key, product) - - deployment = - Fixtures.deployment_fixture(org, firmware_one, %{ - name: "alpha", - conditions: %{"tags" => ["alpha"]} - }) - - {:ok, deployment} = Deployments.update_deployment(deployment, %{is_active: true}) - - device = Fixtures.device_fixture(org, product, firmware_two, %{tags: ["alpha"]}) - device = Deployments.set_deployment(device) - - assert device.deployment_id == deployment.id - assert device.updates_enabled - - assert Devices.firmware_status(device) == "pending" - end - - test "safe against missing metadata", %{device: device} do - # This is mostly for race conditions between finishing the DB write - # of related firmware metadata or deployment update and reading the - # record back in with a firmware_status (i.e via API request) - # In those rare cases we might have missing data and need to be able to - # handle it - missing_meta_device = %{device | deployment: nil, firmware_metadata: nil} - - assert Devices.firmware_status(missing_meta_device) == "latest" - end - end - describe "inflight updates" do test "clears expired inflight updates", %{device: device, deployment: deployment} do deployment = Repo.preload(deployment, :firmware) diff --git a/test/nerves_hub_web/channels/device_channel_test.exs b/test/nerves_hub_web/channels/device_channel_test.exs index 11ba9160c..89b97b359 100644 --- a/test/nerves_hub_web/channels/device_channel_test.exs +++ b/test/nerves_hub_web/channels/device_channel_test.exs @@ -128,54 +128,6 @@ defmodule NervesHubWeb.DeviceChannelTest do assert device.connection_types == [:ethernet, :wifi] end - test "deployment condition changing causes a deployment relookup but it still matches" do - user = Fixtures.user_fixture() - org = Fixtures.org_fixture(user) - product = Fixtures.product_fixture(user, org) - org_key = Fixtures.org_key_fixture(org, user) - - firmware = - Fixtures.firmware_fixture(org_key, product, %{ - version: "0.0.1" - }) - - deployment = - Fixtures.deployment_fixture(org, firmware, %{ - conditions: %{"tags" => ["alpha"], "version" => ""} - }) - - {:ok, deployment} = - NervesHub.Deployments.update_deployment(deployment, %{ - is_active: true - }) - - device_alpha = - Fixtures.device_fixture(org, product, firmware, %{ - tags: ["alpha", "device"], - identifier: "123" - }) - - %{db_cert: alpha_certificate, cert: _cert} = - Fixtures.device_certificate_fixture(device_alpha, X509.PrivateKey.new_ec(:secp256r1)) - - {:ok, socket_alpha} = - connect(DeviceSocket, %{}, connect_info: %{peer_data: %{ssl_cert: alpha_certificate.der}}) - - {:ok, %{}, socket_alpha} = - subscribe_and_join(socket_alpha, DeviceChannel, "device") - - socket_alpha = :sys.get_state(socket_alpha.channel_pid) - refute is_nil(socket_alpha.assigns.device.deployment_id) - - {:ok, _deployment} = - NervesHub.Deployments.update_deployment(deployment, %{ - conditions: %{"tags" => ["alpha", "device"]} - }) - - socket_alpha = :sys.get_state(socket_alpha.channel_pid) - refute is_nil(socket_alpha.assigns.device.deployment_id) - end - describe "unhandled messages are caught" do test "handle_info" do user = Fixtures.user_fixture() diff --git a/test/nerves_hub_web/channels/websocket_test.exs b/test/nerves_hub_web/channels/websocket_test.exs index f43916460..a9b37f1d8 100644 --- a/test/nerves_hub_web/channels/websocket_test.exs +++ b/test/nerves_hub_web/channels/websocket_test.exs @@ -6,7 +6,6 @@ defmodule NervesHubWeb.WebsocketTest do import TrackerHelper alias NervesHub.Fixtures - alias NervesHub.Accounts alias NervesHub.Deployments alias NervesHub.Deployments.Orchestrator alias NervesHub.Devices @@ -643,26 +642,35 @@ defmodule NervesHubWeb.WebsocketTest do user: user, tmp_dir: tmp_dir } do - {device, firmware} = device_fixture(tmp_dir, user, %{identifier: @valid_serial}) - - device = NervesHub.Repo.preload(device, :org) - firmware = NervesHub.Repo.preload(firmware, :product) + org = Fixtures.org_fixture(user) + org_key = Fixtures.org_key_fixture(org, user, tmp_dir) + product = Fixtures.product_fixture(user, org) - Fixtures.device_certificate_fixture(device) - org_key = Fixtures.org_key_fixture(device.org, user, tmp_dir) + firmware = + Fixtures.firmware_fixture(org_key, product, %{ + version: "0.0.1", + dir: tmp_dir + }) + |> Repo.preload([:product]) - deployment = - Fixtures.deployment_fixture(device.org, firmware, %{ + {:ok, deployment} = + Fixtures.deployment_fixture(org, firmware, %{ name: "a different name", conditions: %{ - "tags" => ["beta", "beta-edge"] + "tags" => ["beta"] } }) + |> Deployments.update_deployment(%{is_active: true}) + + device = + Fixtures.device_fixture( + org, + product, + firmware, + %{tags: ["beta", "beta-edge"], identifier: @valid_serial, deployment_id: deployment.id} + ) - {:ok, deployment} = - Deployments.update_deployment(deployment, %{ - is_active: true - }) + Fixtures.device_certificate_fixture(device) {:ok, socket} = SocketClient.start_link(@socket_config) SocketClient.join_and_wait(socket) @@ -726,25 +734,44 @@ defmodule NervesHubWeb.WebsocketTest do SocketClient.clean_close(socket) end - test "removes device from deployment if firmware doesn't match", %{ - user: user, - tmp_dir: tmp_dir - } do - {device, firmware} = - device_fixture(tmp_dir, user, %{identifier: @valid_serial, product: @valid_product}) + test "removes device from deployment and sets reason if firmware doesn't match", + %{ + user: user, + tmp_dir: tmp_dir + } do + org = Fixtures.org_fixture(user) + product = Fixtures.product_fixture(user, org) + org_key = Fixtures.org_key_fixture(org, user, tmp_dir) - org = %Accounts.Org{id: device.org_id} + firmware = + Fixtures.firmware_fixture(org_key, product, %{ + version: "0.0.1", + dir: tmp_dir + }) - Fixtures.deployment_fixture(org, firmware, %{ - name: "a different name", - conditions: %{ - "version" => "~> 0.0.1", - "tags" => ["beta", "beta-edge"] - } - }) - |> Deployments.update_deployment(%{is_active: true}) + {:ok, deployment} = + Fixtures.deployment_fixture(org, firmware, %{ + name: "a different name", + conditions: %{ + "version" => "~> 0.0.1", + "tags" => ["beta", "beta-edge"] + } + }) + |> Deployments.update_deployment(%{is_active: true}) + + device = + Fixtures.device_fixture( + org, + product, + firmware, + %{ + deployment_id: deployment.id, + tags: ["beta", "beta-edge"], + identifier: @valid_serial, + product: @valid_product + } + ) - device = Deployments.set_deployment(device) assert device.deployment_id Fixtures.device_certificate_fixture(device) @@ -762,36 +789,52 @@ defmodule NervesHubWeb.WebsocketTest do "nerves_fw_version" => "0.1.0" }) - assert device.firmware_metadata.architecture != different_architecture - assert device.firmware_metadata.platform != different_platform - Process.sleep(100) device = Repo.reload(device) - refute device.deployment_id + assert device.deployment_conflict == :bad_architecture_and_platform SocketClient.clean_close(socket) end - test "updates device deployment on connect if no device.deployment_id", %{ + test "does nothing when device matches deployment conditions", %{ user: user, tmp_dir: tmp_dir } do - {device, firmware} = - device_fixture(tmp_dir, user, %{identifier: @valid_serial, product: @valid_product}) + org = Fixtures.org_fixture(user) + product = Fixtures.product_fixture(user, org) + org_key = Fixtures.org_key_fixture(org, user, tmp_dir) - org = %Accounts.Org{id: device.org_id} + firmware = + Fixtures.firmware_fixture(org_key, product, %{ + version: "0.0.1", + dir: tmp_dir + }) - Fixtures.deployment_fixture(org, firmware, %{ - name: "a different name", - conditions: %{ - "version" => "~> 0.0.1", - "tags" => ["beta", "beta-edge"] - } - }) - |> Deployments.update_deployment(%{is_active: true}) + {:ok, deployment} = + Fixtures.deployment_fixture(org, firmware, %{ + name: "a different name", + conditions: %{ + "version" => "~> 0.0.1", + "tags" => ["beta", "beta-edge"] + } + }) + |> Deployments.update_deployment(%{is_active: true}) + + device = + Fixtures.device_fixture( + org, + product, + firmware, + %{ + deployment_id: deployment.id, + tags: ["beta", "beta-edge"], + identifier: @valid_serial, + product: @valid_product + } + ) - refute device.deployment_id + assert device.deployment_id Fixtures.device_certificate_fixture(device) @@ -977,24 +1020,34 @@ defmodule NervesHubWeb.WebsocketTest do test "on connect receive an archive", %{user: user, tmp_dir: tmp_dir} do org = Fixtures.org_fixture(user) org_key = Fixtures.org_key_fixture(org, user, tmp_dir) + product = Fixtures.product_fixture(user, org) - {device, firmware} = device_fixture(tmp_dir, user, %{identifier: @valid_serial}, org) - - firmware = Repo.preload(firmware, [:product]) - product = firmware.product + firmware = + Fixtures.firmware_fixture(org_key, product, %{ + version: "0.0.1", + dir: tmp_dir + }) + |> Repo.preload([:product]) archive = Fixtures.archive_fixture(org_key, product, %{dir: tmp_dir}) - deployment = + {:ok, deployment} = Fixtures.deployment_fixture(org, firmware, %{ name: "beta", conditions: %{ "tags" => ["beta"] - } + }, + archive_id: archive.id }) - - {:ok, deployment} = Deployments.update_deployment(deployment, %{archive_id: archive.id}) - {:ok, _deployment} = Deployments.update_deployment(deployment, %{is_active: true}) + |> Deployments.update_deployment(%{is_active: true}) + + device = + Fixtures.device_fixture( + org, + product, + firmware, + %{tags: ["beta", "beta-edge"], identifier: @valid_serial, deployment_id: deployment.id} + ) Fixtures.device_certificate_fixture(device) @@ -1015,24 +1068,34 @@ defmodule NervesHubWeb.WebsocketTest do test "on updates enabled receive an archive", %{user: user, tmp_dir: tmp_dir} do org = Fixtures.org_fixture(user) org_key = Fixtures.org_key_fixture(org, user, tmp_dir) + product = Fixtures.product_fixture(user, org) - {device, firmware} = device_fixture(tmp_dir, user, %{identifier: @valid_serial}, org) - - firmware = Repo.preload(firmware, [:product]) - product = firmware.product + firmware = + Fixtures.firmware_fixture(org_key, product, %{ + version: "0.0.1", + dir: tmp_dir + }) + |> Repo.preload([:product]) archive = Fixtures.archive_fixture(org_key, product, %{dir: tmp_dir}) - deployment = + {:ok, deployment} = Fixtures.deployment_fixture(org, firmware, %{ name: "beta", conditions: %{ "tags" => ["beta"] - } + }, + archive_id: archive.id }) - - {:ok, deployment} = Deployments.update_deployment(deployment, %{archive_id: archive.id}) - {:ok, _deployment} = Deployments.update_deployment(deployment, %{is_active: true}) + |> Deployments.update_deployment(%{is_active: true}) + + device = + Fixtures.device_fixture( + org, + product, + firmware, + %{tags: ["beta", "beta-edge"], identifier: @valid_serial, deployment_id: deployment.id} + ) Fixtures.device_certificate_fixture(device) @@ -1059,23 +1122,33 @@ defmodule NervesHubWeb.WebsocketTest do test "deployment archive updated", %{user: user, tmp_dir: tmp_dir} do org = Fixtures.org_fixture(user) org_key = Fixtures.org_key_fixture(org, user, tmp_dir) + product = Fixtures.product_fixture(user, org) - {device, firmware} = device_fixture(tmp_dir, user, %{identifier: @valid_serial}, org) - - firmware = Repo.preload(firmware, [:product]) - product = firmware.product - - archive = Fixtures.archive_fixture(org_key, product, %{dir: tmp_dir}) + firmware = + Fixtures.firmware_fixture(org_key, product, %{ + version: "0.0.1", + dir: tmp_dir + }) + |> Repo.preload([:product]) - deployment = + {:ok, deployment} = Fixtures.deployment_fixture(org, firmware, %{ name: "beta", conditions: %{ "tags" => ["beta"] } }) + |> Deployments.update_deployment(%{is_active: true}) + + device = + Fixtures.device_fixture( + org, + product, + firmware, + %{tags: ["beta", "beta-edge"], identifier: @valid_serial, deployment_id: deployment.id} + ) - {:ok, deployment} = Deployments.update_deployment(deployment, %{is_active: true}) + archive = Fixtures.archive_fixture(org_key, product, %{dir: tmp_dir}) Fixtures.device_certificate_fixture(device)