diff --git a/lib/uplink/packages.ex b/lib/uplink/packages.ex index 2b23205..8fc156e 100644 --- a/lib/uplink/packages.ex +++ b/lib/uplink/packages.ex @@ -76,7 +76,10 @@ defmodule Uplink.Packages do defdelegate get_or_create_project_name(client, metadata), to: Metadata.Manager - defdelegate get_or_upsert_size_profile(cient, metadata), + defdelegate get_size_profile(metadata), + to: Metadata.Manager + + defdelegate upsert_size_profile(metadata), to: Metadata.Manager defdelegate profile_name(metadata), diff --git a/lib/uplink/packages/install/validate.ex b/lib/uplink/packages/install/validate.ex index 776abab..0c15657 100644 --- a/lib/uplink/packages/install/validate.ex +++ b/lib/uplink/packages/install/validate.ex @@ -11,9 +11,8 @@ defmodule Uplink.Packages.Install.Validate do alias Members.Actor - alias Packages.{ - Install - } + alias Packages.Install + alias Packages.Metadata alias Clients.LXD @@ -41,9 +40,28 @@ defmodule Uplink.Packages.Install.Validate do install |> Packages.build_install_state(actor) + |> ensure_size_profile_exists() |> ensure_profile_exists() end + defp ensure_size_profile_exists( + %{metadata: %Metadata{package_size: nil}} = state + ), + do: state + + defp ensure_size_profile_exists( + %{metadata: %Metadata{package_size: %Metadata.Size{}} = metadata} = + state + ) do + case Packages.upsert_size_profile(metadata) do + {:ok, _} -> + state + + {:error, error} -> + raise "Error: #{inspect(error)} occured when attempting to update or create size profile" + end + end + defp ensure_profile_exists(%{ install: install, metadata: metadata, diff --git a/lib/uplink/packages/instance/bootstrap.ex b/lib/uplink/packages/instance/bootstrap.ex index 3e07fc1..b3a86b3 100644 --- a/lib/uplink/packages/instance/bootstrap.ex +++ b/lib/uplink/packages/instance/bootstrap.ex @@ -87,8 +87,7 @@ defmodule Uplink.Packages.Instance.Bootstrap do profile_name = Packages.profile_name(metadata) - size_profile_name = - Packages.get_or_upsert_size_profile(client, metadata) + size_profile_name = Packages.get_size_profile(metadata) lxd_project_name = Packages.get_or_create_project_name(client, metadata) diff --git a/lib/uplink/packages/instance/upgrade.ex b/lib/uplink/packages/instance/upgrade.ex index 2d83364..5aa155e 100644 --- a/lib/uplink/packages/instance/upgrade.ex +++ b/lib/uplink/packages/instance/upgrade.ex @@ -227,7 +227,7 @@ defmodule Uplink.Packages.Instance.Upgrade do defp handle_update_config(client, instance, metadata) do profile_name = Packages.profile_name(metadata) - size_profile_name = Packages.get_or_upsert_size_profile(client, metadata) + size_profile_name = Packages.get_size_profile(metadata) profiles = [profile_name, "default"] diff --git a/lib/uplink/packages/metadata/manager.ex b/lib/uplink/packages/metadata/manager.ex index 915451a..81cf96e 100644 --- a/lib/uplink/packages/metadata/manager.ex +++ b/lib/uplink/packages/metadata/manager.ex @@ -1,5 +1,6 @@ defmodule Uplink.Packages.Metadata.Manager do alias Uplink.Packages.Metadata + alias Uplink.Clients.LXD defdelegate parse(params), to: Metadata @@ -32,14 +33,27 @@ defmodule Uplink.Packages.Metadata.Manager do end end - def get_or_upsert_size_profile(_client, %Metadata{package_size: nil}), do: nil + def get_size_profile(%Metadata{package_size: nil}), do: nil - def get_or_upsert_size_profile( - client, - %Metadata{package_size: %Metadata.Size{}} = metadata - ) do + def get_size_profile(%Metadata{} = metadata) do size_profile = size_profile_name(metadata) + LXD.client() + |> Lexdee.get_profile(size_profile) + |> case do + {:ok, %{body: %{"name" => _name}}} -> + size_profile + + {:error, %{"error_code" => 404}} -> + nil + end + end + + def upsert_size_profile(%Metadata{package_size: %Metadata.Size{}} = metadata) do + size_profile = size_profile_name(metadata) + + client = LXD.client() + client |> Lexdee.get_profile(size_profile) |> case do @@ -79,16 +93,15 @@ defmodule Uplink.Packages.Metadata.Manager do defp create_size_profile(client, %Metadata{} = metadata) do profile_params = build_size_config(metadata) - profile_name = profile_params["name"] client |> Lexdee.create_profile(profile_params) |> case do {:ok, %{body: nil}} -> - profile_name + {:ok, :size_profile_created} - {:error, %{"error" => _message}} -> - nil + {:error, %{"error" => message}} -> + {:error, message} end end @@ -102,10 +115,10 @@ defmodule Uplink.Packages.Metadata.Manager do |> Lexdee.update_profile(profile_name, profile_params) |> case do {:ok, %{body: _body}} -> - profile_name + {:ok, :size_profile_updated} - {:error, %{"error" => _message}} -> - nil + {:error, %{"error" => message}} -> + {:error, message} end end diff --git a/test/uplink/packages/install/validate_test.exs b/test/uplink/packages/install/validate_test.exs index fb9605e..630a56a 100644 --- a/test/uplink/packages/install/validate_test.exs +++ b/test/uplink/packages/install/validate_test.exs @@ -195,4 +195,211 @@ defmodule Uplink.Packages.Install.ValidateTest do assert install.current_state == "executing" end end + + @deployment_params_with_package_size %{ + "hash" => "some-hash-with-package-size", + "archive_url" => "http://localhost/archives/packages.zip", + "stack" => "alpine/3.14", + "channel" => "develop", + "metadata" => %{ + "id" => 1, + "slug" => "uplink-web", + "service_port" => 4000, + "exposed_port" => 49152, + "package_size" => %{ + "slug" => "medium", + "allocation" => %{ + "cpu" => 1, + "cpu_allowance" => "100%", + "cpu_priority" => 10, + "memory" => 1, + "memory_unit" => "GiB", + "memory_swap" => false, + "memory_enforce" => "hard" + } + }, + "channel" => %{ + "slug" => "develop", + "package" => %{ + "slug" => "something-1640927800", + "credential" => %{ + "public_key" => "public_key" + }, + "organization" => %{ + "slug" => "upmaru" + } + } + }, + "instances" => [ + %{ + "id" => 1, + "slug" => "something-1", + "node" => %{ + "slug" => "some-node" + } + } + ] + } + } + + describe "with package size" do + setup %{actor: actor} do + create_profile = File.read!("test/fixtures/lxd/profiles/create.json") + + metadata = Map.get(@deployment_params_with_package_size, "metadata") + + {:ok, metadata} = Packages.parse_metadata(metadata) + + app = + metadata + |> Metadata.app_slug() + |> Packages.get_or_create_app() + + {:ok, deployment} = + Packages.get_or_create_deployment( + app, + @deployment_params_with_package_size + ) + + {:ok, install} = + Packages.create_install(deployment, %{ + "installation_id" => 1, + "deployment" => @deployment_params_with_package_size + }) + + signature = compute_signature(deployment.hash) + + Cache.put( + {:deployment, signature, install.instellar_installation_id}, + metadata + ) + + {:ok, %{resource: validating_install}} = + Packages.transition_install_with(install, actor, "validate") + + size_profile = + "size.#{metadata.channel.package.organization.slug}.#{metadata.channel.package.slug}.#{validating_install.metadata_snapshot.package_size.slug}" + + {:ok, + install: validating_install, + deployment: deployment, + metadata: metadata, + create_profile: create_profile, + size_profile: size_profile} + end + + test "can create app profile and size profile", %{ + bypass: bypass, + install: install, + actor: actor, + list_profiles: list_profiles, + size_profile: size_profile + } do + size_profile_not_found = + File.read!("test/fixtures/lxd/profiles/not_found.json") + + Bypass.expect_once( + bypass, + "GET", + "/1.0/profiles/#{size_profile}", + fn conn -> + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(404, size_profile_not_found) + end + ) + + create_profile = File.read!("test/fixtures/lxd/profiles/create.json") + + Bypass.expect( + bypass, + "POST", + "/1.0/profiles", + fn conn -> + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, create_profile) + end + ) + + Bypass.expect_once(bypass, "GET", "/1.0/profiles", fn conn -> + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, list_profiles) + end) + + assert {:ok, %{resource: install}} = + perform_job(Validate, %{ + install_id: install.id, + actor_id: actor.id + }) + + assert install.current_state == "executing" + end + + test "can update app profile and size profile", %{ + bypass: bypass, + install: install, + metadata: metadata, + actor: actor, + size_profile: size_profile + } do + list_profiles = + File.read!("test/fixtures/lxd/profiles/list_profile_exists.json") + + update_profile = File.read!("test/fixtures/lxd/profiles/update.json") + + profile_name = Packages.profile_name(metadata) + + Bypass.expect_once(bypass, "GET", "/1.0/profiles", fn conn -> + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, list_profiles) + end) + + size_profile_response = + File.read!("test/fixtures/lxd/profiles/show_size_profile.json") + + Bypass.expect_once( + bypass, + "GET", + "/1.0/profiles/#{size_profile}", + fn conn -> + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, size_profile_response) + end + ) + + Bypass.expect_once( + bypass, + "PATCH", + "/1.0/profiles/#{profile_name}", + fn conn -> + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, update_profile) + end + ) + + Bypass.expect_once( + bypass, + "PATCH", + "/1.0/profiles/#{size_profile}", + fn conn -> + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, update_profile) + end + ) + + assert {:ok, %{resource: install}} = + perform_job(Validate, %{ + install_id: install.id, + actor_id: actor.id + }) + + assert install.current_state == "executing" + end + end end diff --git a/test/uplink/packages/instance/bootstrap_test.exs b/test/uplink/packages/instance/bootstrap_test.exs index 55dde10..69ba45b 100644 --- a/test/uplink/packages/instance/bootstrap_test.exs +++ b/test/uplink/packages/instance/bootstrap_test.exs @@ -902,19 +902,6 @@ defmodule Uplink.Packages.Instance.BootstrapTest do end ) - create_size_profile = File.read!("test/fixtures/lxd/profiles/create.json") - - Bypass.expect_once( - bypass, - "POST", - "/1.0/profiles", - fn conn -> - conn - |> Plug.Conn.put_resp_header("content-type", "application/json") - |> Plug.Conn.resp(200, create_size_profile) - end - ) - Bypass.expect_once(bypass, "POST", "/1.0/instances", fn conn -> assert %{ "target" => "ubuntu-s-1vcpu-1gb-sgp1-01", @@ -923,11 +910,9 @@ defmodule Uplink.Packages.Instance.BootstrapTest do {:ok, body, conn} = Plug.Conn.read_body(conn) - assert %{"source" => source, "profiles" => profiles} = + assert %{"source" => source, "profiles" => _profiles} = Jason.decode!(body) - assert size_profile in profiles - assert %{"server" => server} = source assert server == "https://localhost/spaces/test" @@ -1197,25 +1182,6 @@ defmodule Uplink.Packages.Instance.BootstrapTest do end ) - update_size_profile = File.read!("test/fixtures/lxd/profiles/update.json") - - Bypass.expect_once( - bypass, - "PATCH", - "/1.0/profiles/#{size_profile}", - fn conn -> - {:ok, body, _} = Plug.Conn.read_body(conn) - - params = Jason.decode!(body) - - assert is_nil(params["name"]) - - conn - |> Plug.Conn.put_resp_header("content-type", "application/json") - |> Plug.Conn.resp(200, update_size_profile) - end - ) - Bypass.expect_once(bypass, "POST", "/1.0/instances", fn conn -> assert %{ "target" => "ubuntu-s-1vcpu-1gb-sgp1-01", diff --git a/test/uplink/packages/instance/upgrade_test.exs b/test/uplink/packages/instance/upgrade_test.exs index 1fb82e8..41ebae9 100644 --- a/test/uplink/packages/instance/upgrade_test.exs +++ b/test/uplink/packages/instance/upgrade_test.exs @@ -846,8 +846,8 @@ defmodule Uplink.Packages.Instance.UpgradeTest do end ) - size_profile_not_found = - File.read!("test/fixtures/lxd/profiles/not_found.json") + size_profile_response = + File.read!("test/fixtures/lxd/profiles/show_size_profile.json") Bypass.expect_once( bypass, @@ -856,20 +856,7 @@ defmodule Uplink.Packages.Instance.UpgradeTest do fn conn -> conn |> Plug.Conn.put_resp_header("content-type", "application/json") - |> Plug.Conn.resp(404, size_profile_not_found) - end - ) - - create_size_profile = File.read!("test/fixtures/lxd/profiles/create.json") - - Bypass.expect_once( - bypass, - "POST", - "/1.0/profiles", - fn conn -> - conn - |> Plug.Conn.put_resp_header("content-type", "application/json") - |> Plug.Conn.resp(200, create_size_profile) + |> Plug.Conn.resp(200, size_profile_response) end ) @@ -880,6 +867,12 @@ defmodule Uplink.Packages.Instance.UpgradeTest do "PATCH", "/1.0/instances/#{instance_slug}", fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + + assert %{"profiles" => profiles} = Jason.decode!(body) + + assert size_profile in profiles + conn |> Plug.Conn.put_resp_header("content-type", "application/json") |> Plug.Conn.resp(200, update_instance)