diff --git a/lib/flame/fly_backend.ex b/lib/flame/fly_backend.ex index b99701f..a331c92 100644 --- a/lib/flame/fly_backend.ex +++ b/lib/flame/fly_backend.ex @@ -54,6 +54,7 @@ defmodule FLAME.FlyBackend do :cpu_kind, :cpus, :gpu_kind, + :mounts, :memory_mb, :image, :app, @@ -73,6 +74,7 @@ defmodule FLAME.FlyBackend do cpus: nil, memory_mb: nil, gpu_kind: nil, + mounts: [], image: nil, services: [], app: nil, @@ -86,7 +88,7 @@ defmodule FLAME.FlyBackend do runner_private_ip: nil, runner_node_name: nil - @valid_opts ~w(app image token host cpu_kind cpus memory_mb gpu_kind boot_timeout env terminator_sup log services)a + @valid_opts ~w(app image token host cpu_kind cpus mounts memory_mb gpu_kind boot_timeout env terminator_sup log services)a @impl true def init(opts) do @@ -103,7 +105,8 @@ defmodule FLAME.FlyBackend do memory_mb: 4096, boot_timeout: 30_000, runner_node_basename: node_base, - services: [] + services: [], + mounts: [] } provided_opts = @@ -166,8 +169,57 @@ defmodule FLAME.FlyBackend do {result, div(micro, 1000)} end + defp get_volume_id(%FlyBackend{ mounts: [] }), do: {nil, 0} + defp get_volume_id(%FlyBackend{ mounts: mounts } = state) when is_list(mounts) do + {volumes, time} = get_volumes(state) + + case volumes do + [] -> + {:error, "no volumes to mount"} + all_volumes -> + volume_ids_by_name = + all_volumes + |> Enum.filter(fn vol -> + vol["attached_machine_id"] == nil + and vol["state"] == "created" + end) + |> Enum.group_by(&(&1["name"]), &(&1["id"])) + + new_mounts = Enum.map_reduce( + mounts, + volume_ids_by_name, + fn mount, leftover_vols -> + case List.wrap(leftover_vols[mount.name]) do + [] -> + raise ArgumentError, "not enough fly volumes with the name \"#{mount.name}\" to a FLAME child" + [volume_id | rest] -> + {%{mount | volume: volume_id}, %{leftover_vols | mount.name => rest}} + end + end + ) + + {new_mounts, time} + end + end + defp get_volume_id(_) do + raise ArgumentError, "expected a list of mounts" + end + + defp get_volumes(%FlyBackend{} = state) do + {vols, get_vols_time} = with_elapsed_ms(fn -> + Req.get!("#{state.host}/v1/apps/#{state.app}/volumes", + connect_options: [timeout: state.boot_timeout], + retry: false, + auth: {:bearer, state.token} + ) + end) + + {vols.body, get_vols_time} + end + @impl true def remote_boot(%FlyBackend{parent_ref: parent_ref} = state) do + {mounts, volume_validate_time} = get_volume_id(state) {req, req_connect_time} = with_elapsed_ms(fn -> Req.post!("#{state.host}/v1/apps/#{state.app}/machines", @@ -179,11 +231,12 @@ defmodule FLAME.FlyBackend do name: "#{state.app}-flame-#{rand_id(20)}", config: %{ image: state.image, + mounts: mounts, guest: %{ cpu_kind: state.cpu_kind, cpus: state.cpus, memory_mb: state.memory_mb, - gpu_kind: state.gpu_kind + gpu_kind: state.gpu_kind, }, auto_destroy: true, restart: %{policy: "no"}, @@ -194,7 +247,7 @@ defmodule FLAME.FlyBackend do ) end) - remaining_connect_window = state.boot_timeout - req_connect_time + remaining_connect_window = state.boot_timeout - req_connect_time - volume_validate_time case req.body do %{"id" => id, "instance_id" => instance_id, "private_ip" => ip} -> diff --git a/lib/flame/fly_backend/mounts.ex b/lib/flame/fly_backend/mounts.ex new file mode 100644 index 0000000..4ccd0af --- /dev/null +++ b/lib/flame/fly_backend/mounts.ex @@ -0,0 +1,9 @@ +defmodule FLAME.FlyBackend.Mounts do + @derive Jason.Encoder + defstruct name: nil, + path: nil, + volume: nil, + extend_threshold_percent: 0, + add_size_gb: 0, + size_gb_limit: 0 +end