diff --git a/test/fixtures/lxd/profiles/not_found.json b/test/fixtures/lxd/profiles/not_found.json new file mode 100644 index 00000000..a014cd3d --- /dev/null +++ b/test/fixtures/lxd/profiles/not_found.json @@ -0,0 +1,9 @@ +{ + "type": "error", + "status": "", + "status_code": 0, + "operation": "", + "error_code": 404, + "error": "Profile not found", + "metadata": null +} \ No newline at end of file diff --git a/test/scenarios/deployment.ex b/test/scenarios/deployment.ex index 988696db..cf908ec4 100644 --- a/test/scenarios/deployment.ex +++ b/test/scenarios/deployment.ex @@ -62,6 +62,70 @@ defmodule Uplink.Scenarios.Deployment do } } + @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", + "package_size" => %{ + "slug" => "medium", + "allocation" => %{ + "cpu" => 1, + "cpu_allowance" => "100%", + "cpu_priority" => 10, + "memory" => 1, + "memory_unit" => "GiB", + "memory_swap" => false, + "memory_enforce" => "hard" + } + }, + "main_port" => %{ + "slug" => "web", + "source" => 49152, + "target" => 4000, + "routing" => %{ + "router_id" => 1, + "paths" => ["/configure*"] + } + }, + "ports" => [ + %{ + "slug" => "grpc", + "source" => 49153, + "target" => 6000 + } + ], + "hosts" => ["something.com"], + "variables" => [ + %{"key" => "SOMETHING", "value" => "blah"} + ], + "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" + } + } + ] + } + } + def setup_endpoints(_context) do :ok = Ecto.Adapters.SQL.Sandbox.checkout(Uplink.Repo) @@ -157,4 +221,54 @@ defmodule Uplink.Scenarios.Deployment do deployment: deployment, install: executing_install} end + + def setup_base_with_package_size(_context) do + {:ok, actor} = + Members.get_or_create_actor(%{ + "identifier" => "zacksiri", + "provider" => "instellar", + "id" => "1" + }) + + 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 = Secret.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") + + {:ok, %{resource: executing_install}} = + Packages.transition_install_with(validating_install, actor, "execute") + + {:ok, + actor: actor, + metadata: metadata, + app: app, + deployment: deployment, + install: executing_install} + end end diff --git a/test/uplink/packages/instance/bootstrap_test.exs b/test/uplink/packages/instance/bootstrap_test.exs index 3e8cd90e..2febddce 100644 --- a/test/uplink/packages/instance/bootstrap_test.exs +++ b/test/uplink/packages/instance/bootstrap_test.exs @@ -835,4 +835,310 @@ defmodule Uplink.Packages.Instance.BootstrapTest do assert_enqueued(worker: Uplink.Packages.Instance.Cleanup, args: args) end end + + describe "bootstrap instance metadata with package_size" do + setup [:setup_base_with_package_size] + + setup %{metadata: metadata} do + size_profile = + "size.#{metadata.channel.package.organization.slug}.#{metadata.channel.package.slug}.#{metadata.package_size.slug}" + + {:ok, size_profile: size_profile} + end + + test "when project and size does not exist", %{ + bypass: bypass, + install: install, + actor: actor, + public_key_name: public_key_name, + create_instance: create_instance, + start_instance: start_instance, + exec_instance: exec_instance, + wait_for_operation: wait_for_operation, + wait_with_log: wait_with_log, + project: project_name, + size_profile: size_profile + } do + instance_slug = "test-02" + + project_not_found = + File.read!("test/fixtures/lxd/projects/not_found.json") + + Bypass.expect_once( + bypass, + "GET", + "/1.0/projects/#{project_name}", + fn conn -> + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(404, project_not_found) + end + ) + + create_project = File.read!("test/fixtures/lxd/projects/create.json") + + Bypass.expect_once( + bypass, + "POST", + "/1.0/projects", + fn conn -> + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, create_project) + end + ) + + 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_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", + "project" => project + } = conn.query_params + + {:ok, body, conn} = Plug.Conn.read_body(conn) + + assert %{"source" => source, "profiles" => profiles} = + Jason.decode!(body) + + assert size_profile in profiles + + assert %{"server" => server} = source + + assert server == "https://localhost/spaces/test" + + assert project == project_name + + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, create_instance) + end) + + create_instance_params = Jason.decode!(create_instance) + create_instance_operation_id = create_instance_params["metadata"]["id"] + + Bypass.expect_once( + bypass, + "GET", + "/1.0/operations/#{create_instance_operation_id}/wait", + fn conn -> + assert %{"timeout" => "180"} = conn.query_params + + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, wait_for_operation) + end + ) + + Bypass.expect_once( + bypass, + "PUT", + "/1.0/instances/#{instance_slug}/state", + fn conn -> + assert %{"project" => project} = conn.query_params + + assert project == project_name + + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, start_instance) + end + ) + + start_instance_params = Jason.decode!(start_instance) + start_instance_operation_id = start_instance_params["metadata"]["id"] + + Bypass.expect_once( + bypass, + "GET", + "/1.0/operations/#{start_instance_operation_id}/wait", + fn conn -> + assert %{"timeout" => "180"} = conn.query_params + + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, wait_for_operation) + end + ) + + hostname = System.get_env("HOSTNAME") + + distribution_port = + Application.get_env(:uplink, Uplink.Internal) + |> Keyword.get(:port) + + Bypass.expect( + bypass, + "POST", + "/1.0/instances/#{instance_slug}/exec", + fn conn -> + assert %{"project" => project} = conn.query_params + + assert project == project_name + + assert {:ok, body, conn} = Plug.Conn.read_body(conn) + assert {:ok, %{"command" => command}} = Jason.decode(body) + + assert command in [ + [ + "/bin/sh", + "-c", + "echo 'public_key' > /etc/apk/keys/#{public_key_name}.rsa.pub\n" + ], + ["/bin/sh", "-c", "cat /etc/apk/repositories\n"], + [ + "/bin/sh", + "-c", + "echo -e 'http://#{hostname}:#{distribution_port}/distribution/develop/upmaru/something-1640927800' >> /etc/apk/repositories\n" + ] + ] + + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, exec_instance) + end + ) + + setup_public_key_params = Jason.decode!(exec_instance) + setup_public_key_operation_id = setup_public_key_params["metadata"]["id"] + + Bypass.expect( + bypass, + "GET", + "/1.0/operations/#{setup_public_key_operation_id}/wait", + fn conn -> + assert %{"timeout" => _timeout} = conn.query_params + + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, wait_with_log) + end + ) + + Bypass.expect( + bypass, + "GET", + "/1.0/instances/#{instance_slug}/logs/stdout.log", + fn conn -> + assert %{"project" => project} = conn.query_params + + assert project == project_name + + conn + |> Plug.Conn.resp( + 200, + "http://#{hostname}:#{distribution_port}/distribution/develop/upmaru/something-1640927800" + ) + end + ) + + Bypass.expect( + bypass, + "GET", + "/1.0/instances/#{instance_slug}/logs/stderr.log", + fn conn -> + assert %{"project" => project} = conn.query_params + + assert project == project_name + + conn + |> Plug.Conn.resp(200, "") + end + ) + + Bypass.expect_once( + bypass, + "PUT", + "/1.0/instances/#{instance_slug}/state", + fn conn -> + assert %{"project" => project} = conn.query_params + + assert project == project_name + + assert {:ok, body, conn} = Plug.Conn.read_body(conn) + assert {:ok, body} = Jason.decode(body) + + assert body["action"] == "start" + + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, start_instance) + end + ) + + start_instance_key_params = Jason.decode!(start_instance) + start_instance_operation_id = start_instance_key_params["metadata"]["id"] + + Bypass.expect( + bypass, + "GET", + "/1.0/operations/#{start_instance_operation_id}/wait", + fn conn -> + %{"timeout" => "180"} = conn.query_params + + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, wait_for_operation) + end + ) + + Bypass.expect_once( + bypass, + "POST", + "/uplink/installations/#{install.instellar_installation_id}/instances/#{instance_slug}/events", + fn conn -> + assert {:ok, body, conn} = Plug.Conn.read_body(conn) + assert {:ok, body} = Jason.decode(body) + + assert %{"event" => %{"name" => "boot" = event_name}} = body + + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp( + 201, + Jason.encode!(%{ + "data" => %{"attributes" => %{"id" => 1, "name" => event_name}} + }) + ) + end + ) + + assert {:ok, job} = + perform_job(Bootstrap, %{ + instance: %{ + slug: instance_slug, + node: %{slug: "ubuntu-s-1vcpu-1gb-sgp1-01"} + }, + install_id: install.id, + actor_id: actor.id + }) + + assert_enqueued(worker: Uplink.Packages.Instance.Install, args: job.args) + end + end end