Skip to content

Commit 374b05a

Browse files
lawiknshoes
andauthored
Add preparation step for orchestrator doing firmware deltas (#2262)
Co-authored-by: Nate Shoemaker <[email protected]> Co-authored-by: Nate Shoemaker <[email protected]>
1 parent 48c5b5c commit 374b05a

File tree

28 files changed

+1287
-454
lines changed

28 files changed

+1287
-454
lines changed

.cspell.json

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,22 @@
3838
"comeonin",
3939
"conzole",
4040
"extralight",
41+
"extn",
4142
"Fastlaned",
43+
"filelib",
44+
"filesize",
45+
"firmwares",
46+
"fkey",
4247
"fontawesome",
4348
"fullsweep",
49+
"fwup",
50+
"gencert",
51+
"geocoder",
52+
"geodata",
53+
"geoip",
54+
"getopts",
55+
"getstat",
56+
"haspopup",
4457
"healthcheck",
4558
"healthz",
4659
"hljs",
@@ -65,7 +78,11 @@
6578
"swaggerui",
6679
"tabnav",
6780
"ueberauth",
81+
"undigits",
82+
"unprovisioned",
83+
"validtoken",
6884
"webgl",
69-
"xtermjs"
85+
"xtermjs",
86+
"xdelta"
7087
]
7188
}

lib/nerves_hub/devices.ex

Lines changed: 102 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -613,19 +613,6 @@ defmodule NervesHub.Devices do
613613
@type source_firmware_id() :: firmware_id()
614614
@type target_firmware_id() :: firmware_id()
615615

616-
@spec get_device_firmware_for_delta_generation_by_product(binary()) ::
617-
list({source_firmware_id(), target_firmware_id()})
618-
def get_device_firmware_for_delta_generation_by_product(product_id) do
619-
DeploymentGroup
620-
|> where([dep], dep.product_id == ^product_id)
621-
|> join(:inner, [dep], dev in Device, on: dev.deployment_id == dep.id)
622-
|> join(:inner, [dep, dev], f in Firmware, on: f.uuid == fragment("d1.firmware_metadata->>'uuid'"))
623-
# Exclude the current firmware, we don't need to generate that one
624-
|> where([dep, dev, f], f.id != dep.firmware_id)
625-
|> select([dep, dev, f], {f.id, dep.firmware_id})
626-
|> Repo.all()
627-
end
628-
629616
@spec get_device_firmware_for_delta_generation_by_deployment_group(binary()) ::
630617
list({source_firmware_id(), target_firmware_id()})
631618
def get_device_firmware_for_delta_generation_by_deployment_group(deployment_id) do
@@ -636,6 +623,7 @@ defmodule NervesHub.Devices do
636623
# Exclude the current firmware, we don't need to generate that one
637624
|> where([dep, dev, f], f.id != dep.firmware_id)
638625
|> select([dep, dev, f], {f.id, dep.firmware_id})
626+
|> distinct(true)
639627
|> Repo.all()
640628
end
641629

@@ -777,24 +765,35 @@ defmodule NervesHub.Devices do
777765
defp do_resolve_update(device, deployment_group, opts) do
778766
case verify_update_eligibility(device, deployment_group) do
779767
{:ok, _device} ->
780-
{:ok, url} = get_delta_or_firmware_url(device, deployment_group)
781-
782-
{:ok, meta} = Firmwares.metadata_from_firmware(deployment_group.firmware)
783-
784-
firmware_url =
785-
if opts[:firmware_proxy_url] do
786-
opts[:firmware_proxy_url] <> "?firmware=#{Base.url_encode64(url, padding: false)}"
787-
else
788-
url
789-
end
768+
case get_delta_or_firmware_url(device, deployment_group) do
769+
{:ok, url} ->
770+
{:ok, meta} = Firmwares.metadata_from_firmware(deployment_group.firmware)
771+
772+
firmware_url =
773+
if opts[:firmware_proxy_url] do
774+
opts[:firmware_proxy_url] <> "?firmware=#{Base.url_encode64(url, padding: false)}"
775+
else
776+
url
777+
end
778+
779+
%UpdatePayload{
780+
update_available: true,
781+
firmware_url: firmware_url,
782+
firmware_meta: meta,
783+
deployment_group: deployment_group,
784+
deployment_id: deployment_group.id
785+
}
786+
787+
{:error, reason} ->
788+
Logger.info(
789+
"Firmware URL could not be generated",
790+
reason: reason,
791+
source_firmware: Map.get(device.firmware_metadata, :uuid),
792+
target_firmware: deployment_group.firmware.uuid
793+
)
790794

791-
%UpdatePayload{
792-
update_available: true,
793-
firmware_url: firmware_url,
794-
firmware_meta: meta,
795-
deployment_group: deployment_group,
796-
deployment_id: deployment_group.id
797-
}
795+
%UpdatePayload{update_available: false}
796+
end
798797

799798
{:error, :deployment_group_not_active, _device} ->
800799
%UpdatePayload{update_available: false}
@@ -836,6 +835,23 @@ defmodule NervesHub.Devices do
836835
:delta == update_tool().device_update_type(device, firmware)
837836
end
838837

838+
@spec delta_ready?(Device.t(), Firmware.t()) :: boolean()
839+
def delta_ready?(%Device{firmware_metadata: %{uuid: source_uuid}}, %Firmware{id: target_id, product_id: product_id}) do
840+
source_firmware_id_query =
841+
Firmware
842+
|> where(uuid: ^source_uuid)
843+
|> where(product_id: ^product_id)
844+
|> select([f], f.id)
845+
846+
query =
847+
FirmwareDelta
848+
|> where([fd], fd.source_id == subquery(source_firmware_id_query))
849+
|> where([fd], fd.target_id == ^target_id)
850+
|> where([fd], fd.status == :completed)
851+
852+
Repo.exists?(query)
853+
end
854+
839855
@doc """
840856
Returns true if Version.match? and all deployment tags are in device tags.
841857
"""
@@ -862,6 +878,7 @@ defmodule NervesHub.Devices do
862878

863879
DeviceEvents.deployment_assigned(device)
864880

881+
deployment_group = Repo.preload(deployment_group, :firmware)
865882
Map.put(device, :deployment_group, deployment_group)
866883
end
867884

@@ -1722,116 +1739,74 @@ defmodule NervesHub.Devices do
17221739
@doc """
17231740
Get firmware or delta update URL.
17241741
"""
1725-
@spec get_delta_or_firmware_url(Device.t(), Firmware.t() | DeploymentGroup.t()) ::
1726-
{:ok, String.t()} | {:error, :failure}
1727-
def get_delta_or_firmware_url(%Device{} = device, %DeploymentGroup{delta_updatable: true, firmware: target}) do
1728-
get_delta_or_firmware_url(device, target)
1729-
end
1742+
@spec get_delta_or_firmware_url(Device.t(), DeploymentGroup.t()) ::
1743+
{:ok, String.t()}
1744+
| {:error, :delta_not_completed}
1745+
| {:error, :device_does_not_support_deltas}
1746+
| {:error, :delta_not_found}
1747+
def get_delta_or_firmware_url(%Device{firmware_metadata: %{uuid: source_uuid}} = device, %DeploymentGroup{
1748+
delta_updatable: true,
1749+
firmware: %Firmware{delta_updatable: true} = target_firmware
1750+
}) do
1751+
case Firmwares.get_firmware_by_product_id_and_uuid(device.product_id, source_uuid) do
1752+
{:ok, source_firmware} ->
1753+
case get_delta_if_ready(device, source_firmware, target_firmware) do
1754+
{:ok, delta} ->
1755+
Firmwares.get_firmware_url(delta)
1756+
1757+
{:device_delta_updatable, false} ->
1758+
{:error, :device_does_not_support_deltas}
1759+
1760+
{:delta, {:ok, %FirmwareDelta{}}} ->
1761+
{:error, :delta_not_completed}
1762+
1763+
{:delta, {:error, :not_found}} ->
1764+
{:error, :delta_not_found}
1765+
end
17301766

1731-
def get_delta_or_firmware_url(
1732-
%{firmware_metadata: %{uuid: source_uuid}} = device,
1733-
%Firmware{delta_updatable: true} = target
1734-
) do
1735-
case get_delta_if_ready(device, target) do
1736-
{:ok, delta} ->
1737-
Logger.info(
1738-
"Delivering firmware delta",
1739-
device_id: device.id,
1740-
source_firmware: source_uuid,
1741-
target_firmware: target.uuid,
1742-
delta: delta.id
1743-
)
1744-
1745-
Firmwares.get_firmware_url(delta)
1746-
1747-
{:delta_updatable, false} ->
1748-
Logger.info(
1749-
"Delivering full firmware as delta updates are not enabled",
1750-
device_id: device.id,
1751-
source_firmware: source_uuid,
1752-
target_firmware: target.uuid
1753-
)
1754-
1755-
Firmwares.get_firmware_url(target)
1756-
1757-
{:firmware, _} ->
1758-
Logger.warning(
1759-
"Delivering full firmware as device firmware could not be resolved",
1760-
device_id: device.id,
1761-
source_firmware: source_uuid,
1762-
target_firmware: target.uuid
1763-
)
1764-
1765-
Firmwares.get_firmware_url(target)
1766-
1767-
{:delta, {:ok, %{status: status} = delta}} ->
1768-
Logger.info(
1769-
"Delivering full firmware as delta had status #{status}",
1770-
device_id: device.id,
1771-
source_firmware: source_uuid,
1772-
target_firmware: target.uuid,
1773-
delta: delta.id
1774-
)
1775-
1776-
Firmwares.get_firmware_url(target)
1777-
1778-
{:delta, {:error, :not_found}} ->
1779-
Logger.info(
1780-
"Delivering full firmware as no delta can be found",
1781-
device_id: device.id,
1782-
source_firmware: source_uuid,
1783-
target_firmware: target.uuid
1784-
)
1785-
1786-
Firmwares.get_firmware_url(target)
1767+
{:error, :not_found} ->
1768+
Firmwares.get_firmware_url(target_firmware)
17871769
end
17881770
end
17891771

1790-
def get_delta_or_firmware_url(%Device{firmware_metadata: fw_meta} = device, %DeploymentGroup{firmware: target} = dg) do
1791-
Logger.warning(
1792-
"Delivering full firmware: deltas disabled for deployment group.",
1793-
device_id: device.id,
1794-
deployment_group_id: dg.id,
1795-
source_firmware: Map.get(fw_meta, :uuid),
1796-
target_firmware: target.uuid
1797-
)
1798-
1799-
Firmwares.get_firmware_url(target)
1800-
end
1772+
def get_delta_or_firmware_url(%Device{}, %DeploymentGroup{firmware: target}), do: Firmwares.get_firmware_url(target)
18011773

1802-
def get_delta_or_firmware_url(%Device{firmware_metadata: fw_meta} = device, %Firmware{} = target) do
1803-
Logger.warning(
1804-
"Delivering full firmware: deltas disabled for firmware.",
1805-
device_id: device.id,
1806-
source_firmware: Map.get(fw_meta, :uuid),
1807-
target_firmware: target.uuid
1808-
)
1809-
1810-
Firmwares.get_firmware_url(target)
1811-
end
1812-
1813-
@spec get_delta_if_ready(Device.t(), Firmware.t()) ::
1774+
@spec get_delta_if_ready(Device.t(), Firmware.t(), Firmware.t()) ::
18141775
{:ok, FirmwareDelta.t()}
1815-
| {:delta_updatable, false}
1816-
| {:firmware, {:error, :not_found}}
1817-
| {:firmware, :no_device_firmware_metadata}
1776+
| {:device_delta_updatable, false}
18181777
| {:delta, {:ok, FirmwareDelta.t()}}
18191778
| {:delta, {:error, :not_found}}
1820-
def get_delta_if_ready(
1821-
%Device{firmware_metadata: %{uuid: source_firmware_uuid}, product_id: product_id} = device,
1822-
target_firmware
1823-
) do
1824-
with {:delta_updatable, true} <-
1825-
{:delta_updatable, delta_updatable?(device, target_firmware)},
1826-
{:firmware, {:ok, source_firmware}} <-
1827-
{:firmware, Firmwares.get_firmware_by_product_id_and_uuid(product_id, source_firmware_uuid)},
1779+
defp get_delta_if_ready(device, source_firmware, target_firmware) do
1780+
with {:device_delta_updatable, true} <-
1781+
{:device_delta_updatable, delta_updatable?(device, target_firmware)},
18281782
{:delta, {:ok, %{status: :completed} = delta}} <-
1829-
{:delta, Firmwares.get_firmware_delta_by_source_and_target(source_firmware, target_firmware)} do
1783+
{:delta,
1784+
Firmwares.get_firmware_delta_by_source_and_target(
1785+
source_firmware.id,
1786+
target_firmware.id
1787+
)} do
18301788
{:ok, delta}
18311789
end
18321790
end
18331791

1834-
def get_delta_if_ready(_device, _target_firmware), do: {:firmware, :no_device_firmware_metadata}
1792+
@spec get_delta_url(Device.t(), Firmware.t()) ::
1793+
{:ok, String.t()}
1794+
| {:error, :failure}
1795+
def get_delta_url(%Device{firmware_metadata: %{uuid: source_uuid}}, %Firmware{id: target_id, product_id: product_id}) do
1796+
source_firmware_id_query =
1797+
Firmware
1798+
|> where(uuid: ^source_uuid)
1799+
|> where(product_id: ^product_id)
1800+
|> select([f], f.id)
1801+
1802+
delta =
1803+
FirmwareDelta
1804+
|> where([fd], fd.source_id == subquery(source_firmware_id_query))
1805+
|> where([fd], fd.target_id == ^target_id)
1806+
|> Repo.one()
1807+
1808+
Firmwares.get_firmware_url(delta)
1809+
end
18351810

18361811
@spec soft_deleted_devices_exist_for_product?(non_neg_integer()) :: boolean()
18371812
def soft_deleted_devices_exist_for_product?(product_id) do

lib/nerves_hub/events/device_events.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ defmodule NervesHub.DeviceEvents do
7777
Repo.transact(fn ->
7878
url =
7979
if opts[:delta] do
80-
{:ok, url} = Devices.get_delta_or_firmware_url(device, firmware)
80+
{:ok, url} = Devices.get_delta_url(device, firmware)
8181
url
8282
else
8383
{:ok, url} = Firmwares.get_firmware_url(firmware)

0 commit comments

Comments
 (0)