Skip to content

Commit 091ba00

Browse files
tallysmartinsPragTob
authored andcommitted
Improvements in Job config, adds support to Wait options (#37)
* Parse new option in config.yml - Docker dependences now require specification of a port where the service respond to TCP connections. This will be used to sync the initialization of benchmark containers that rely in external services e.g. databases. The runner will then try to ping the service in the port specified before initializing the benchmarks run. deps: docker: - image: pg:latest wait: - port: 5454 Signed-off-by: Tallys Martins <[email protected]> * Apply review changes Signed-off-by: Tallys Martins <[email protected]>
1 parent 978fb0a commit 091ba00

File tree

8 files changed

+296
-7
lines changed

8 files changed

+296
-7
lines changed

lib/elixir_bench/benchmarks/config.ex

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ defmodule ElixirBench.Benchmarks.Config do
1515
embeds_many :docker, Docker, primary_key: {:image, :string, []} do
1616
field :container_name, :string
1717
field :environment, {:map, :string}, default: %{}
18+
19+
embeds_one :wait, Wait, primary_key: false do
20+
field :port, :integer
21+
end
1822
end
1923
end
2024
end
@@ -25,8 +29,16 @@ defmodule ElixirBench.Benchmarks.Config do
2529

2630
config
2731
|> cast(attrs, [:elixir, :erlang, :environment])
28-
|> validate_inclusion(:elixir, supported_elixir_version)
29-
|> validate_inclusion(:erlang, supported_erlang_version)
32+
|> validate_inclusion(
33+
:elixir,
34+
supported_elixir_version,
35+
message: "elixir version not supported"
36+
)
37+
|> validate_inclusion(
38+
:erlang,
39+
supported_erlang_version,
40+
message: "erlang version not supported"
41+
)
3042
|> cast_embed(:deps, with: &deps_changeset/2)
3143
end
3244

@@ -40,5 +52,12 @@ defmodule ElixirBench.Benchmarks.Config do
4052
docker
4153
|> cast(attrs, [:image, :container_name, :environment])
4254
|> validate_required([:image])
55+
|> cast_embed(:wait, with: &wait_changeset/2, required: true)
56+
end
57+
58+
defp wait_changeset(wait, attrs) do
59+
wait
60+
|> cast(attrs, [:port])
61+
|> validate_required([:port])
4362
end
4463
end

lib/elixir_bench/github/client_in_memory.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ defmodule ElixirBench.Github.ClientInMemory do
1414
"docker" => [
1515
%{
1616
"container_name" => "postgres",
17+
"wait" => %{port: "123"},
1718
"image" => "postgres:9.6.6-alpine"
1819
},
1920
%{
2021
"container_name" => "mysql",
22+
"wait" => %{port: "321"},
2123
"environment" => %{
2224
"MYSQL_ALLOW_EMPTY_PASSWORD" => "true"
2325
},

lib/elixir_bench_web/router.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ defmodule ElixirBenchWeb.Router do
3838
assign(conn, :runner, runner)
3939

4040
{:error, _reason} ->
41-
send_resp(conn, :unauthorized, Jason.encode!(%{error: "unauthorized"}))
41+
halt(conn)
4242
end
4343
end
4444
end

lib/elixir_bench_web/views/job_view.ex

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,14 @@ defmodule ElixirBenchWeb.JobView do
3434

3535
defp render_docker(nil), do: []
3636

37-
def render_each_docker(docker) do
37+
defp render_each_docker(docker) do
3838
%{
3939
image: docker.image,
4040
environment: docker.environment,
41-
container_name: docker.container_name
41+
container_name: docker.container_name,
42+
wait: %{
43+
port: docker.wait.port
44+
}
4245
}
4346
end
4447
end
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
defmodule ConfigTest do
2+
use ElixirBench.DataCase
3+
alias ElixirBench.Benchmarks.Config
4+
5+
describe "changeset/2" do
6+
test "return config with default values if no input attrs is given" do
7+
elixir_version = Confex.fetch_env!(:elixir_bench, :default_elixir_version)
8+
erlang_version = Confex.fetch_env!(:elixir_bench, :default_erlang_version)
9+
10+
changeset = Config.changeset(%Config{}, %{})
11+
12+
assert %Config{
13+
elixir: ^elixir_version,
14+
erlang: ^erlang_version,
15+
environment: %{},
16+
deps: nil
17+
} = changeset.data
18+
19+
assert changeset.valid?
20+
end
21+
22+
test "return unsupported versions error if elixir and erlang are not supported" do
23+
supported_elixir = Confex.fetch_env!(:elixir_bench, :supported_elixir_versions)
24+
supported_erlang = Confex.fetch_env!(:elixir_bench, :supported_erlang_versions)
25+
26+
refute "99" in supported_elixir
27+
refute "99" in supported_erlang
28+
29+
changeset = Config.changeset(%Config{}, %{elixir: "99", erlang: "99"})
30+
refute changeset.valid?
31+
32+
assert %{elixir: ["elixir version not supported"], erlang: ["erlang version not supported"]} =
33+
errors_on(changeset)
34+
35+
supported_elixir = hd(supported_elixir)
36+
supported_erlang = hd(supported_erlang)
37+
38+
changeset =
39+
Config.changeset(%Config{}, %{elixir: supported_elixir, erlang: supported_erlang})
40+
41+
assert changeset.valid?
42+
end
43+
44+
test "return config given valid docker deps attrs" do
45+
docker_deps = [
46+
%{
47+
image: "postgres:latest",
48+
environment: %{password: "root"},
49+
container_name: "pg",
50+
wait: %{port: 5432}
51+
},
52+
%{
53+
image: "mysql:latest",
54+
container_name: "mysql",
55+
wait: %{port: 3306}
56+
}
57+
]
58+
59+
changeset = Config.changeset(%Config{}, %{deps: %{docker: docker_deps}})
60+
61+
assert changeset.valid?
62+
63+
changeset = Ecto.Changeset.apply_changes(changeset)
64+
65+
assert %{
66+
deps: %{
67+
docker: [
68+
%{
69+
image: "postgres:latest",
70+
environment: %{password: "root"},
71+
container_name: "pg",
72+
wait: %{port: 5432}
73+
},
74+
%{
75+
image: "mysql:latest",
76+
container_name: "mysql",
77+
wait: %{port: 3306}
78+
}
79+
]
80+
}
81+
} = changeset
82+
end
83+
84+
test "allow empty docker deps in config" do
85+
changeset = Config.changeset(%Config{}, %{deps: %{}})
86+
assert changeset.valid?
87+
assert %Config{deps: nil} = changeset.data
88+
89+
changeset = Config.changeset(%Config{}, %{deps: %{docker: []}})
90+
assert changeset.valid?
91+
assert %Config{deps: nil} = changeset.data
92+
93+
changeset = Config.changeset(%Config{}, %{deps: %{docker: %{}}})
94+
assert changeset.valid?
95+
assert %Config{deps: nil} = changeset.data
96+
end
97+
98+
test "return error given invalid docker deps fields" do
99+
docker_deps = [%{some_field: "some value"}]
100+
changeset = Config.changeset(%Config{}, %{deps: %{docker: docker_deps}})
101+
102+
refute changeset.valid?
103+
assert %{deps: %{docker: [%{wait: ["can't be blank"]}]}} = errors_on(changeset)
104+
assert %{deps: %{docker: [%{image: ["can't be blank"]}]}} = errors_on(changeset)
105+
106+
docker_deps = [%{image: 1, wait: 1}]
107+
changeset = Config.changeset(%Config{}, %{deps: %{docker: docker_deps}})
108+
109+
refute changeset.valid?
110+
assert %{deps: %{docker: [%{wait: ["is invalid"]}]}} = errors_on(changeset)
111+
assert %{deps: %{docker: [%{image: ["is invalid"]}]}} = errors_on(changeset)
112+
113+
docker_deps = [%{image: "pg:latest", wait: %{}}]
114+
changeset = Config.changeset(%Config{}, %{deps: %{docker: docker_deps}})
115+
116+
refute changeset.valid?
117+
assert %{deps: %{docker: [%{wait: %{port: ["can't be blank"]}}]}} = errors_on(changeset)
118+
end
119+
120+
test "ignore non docker deps" do
121+
changeset =
122+
Config.changeset(%Config{}, %{
123+
deps: %{some_dep: [%{image: "some_image", wait: %{port: 124}}]}
124+
})
125+
126+
assert changeset.valid?
127+
assert %Config{deps: nil} = changeset.data
128+
end
129+
end
130+
end
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
defmodule ElixirBenchWeb.JobControllerTest do
2+
use ElixirBenchWeb.ConnCase, async: false
3+
4+
import ElixirBenchWeb.TestHelpers
5+
6+
describe "claim/2" do
7+
test "respond 404 when there is no job to be claimed", context do
8+
conn =
9+
context.conn
10+
|> authenticate!
11+
|> post("/runner-api/jobs/claim", %{})
12+
13+
assert response(conn, 404) =~ "Page not found"
14+
end
15+
16+
test "return job data when claimed with success", context do
17+
%{branch_name: branch, commit_sha: commit_sha, uuid: uuid, repo: repo, config: config} =
18+
insert(:job)
19+
20+
%{elixir: elixir, erlang: erlang, environment: env} = config
21+
repo_slug = "#{repo.owner}/#{repo.name}"
22+
23+
{:ok, %{"data" => data}} =
24+
context.conn
25+
|> authenticate!
26+
|> post("/runner-api/jobs/claim", %{})
27+
|> decode_response_body
28+
29+
assert %{
30+
"branch_name" => ^branch,
31+
"commit_sha" => ^commit_sha,
32+
"config" => %{
33+
"deps" => %{"docker" => []},
34+
"elixir_version" => ^elixir,
35+
"environment_variables" => ^env,
36+
"erlang_version" => ^erlang
37+
},
38+
"id" => ^uuid,
39+
"repo_slug" => ^repo_slug
40+
} = data
41+
end
42+
43+
test "return error if runner authentication fails", context do
44+
resp =
45+
context.conn
46+
|> put_req_header("authorization", "Basic " <> Base.encode64("test:test"))
47+
|> post("/runner-api/jobs/claim", %{})
48+
49+
assert "401 Unauthorized" = resp.resp_body
50+
end
51+
end
52+
53+
defp authenticate!(conn) do
54+
%{name: name, api_key: api_key} = insert(:runner)
55+
56+
conn
57+
|> put_req_header("authorization", "Basic " <> Base.encode64("#{name}:#{api_key}"))
58+
end
59+
end
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
defmodule ElixirBenchWeb.JobViewTest do
2+
use ElixirBenchWeb.ConnCase, async: true
3+
4+
# Bring render/3 and render_to_string/3 for testing custom views
5+
import Phoenix.View
6+
7+
test "render index.json given job without docker dependencies and environment variables" do
8+
repo = build(:repo, %{owner: "owner", name: "name"})
9+
job = build(:job, repo: repo)
10+
11+
%{uuid: uuid, branch_name: branch, commit_sha: commit_sha, config: config} = job
12+
%{elixir: elixir, erlang: erlang} = config
13+
14+
%{data: [job_data]} = render(ElixirBenchWeb.JobView, "index.json", %{jobs: [job], repo: repo})
15+
16+
assert %{
17+
branch_name: ^branch,
18+
commit_sha: ^commit_sha,
19+
config: %{
20+
deps: %{docker: []},
21+
elixir_version: ^elixir,
22+
environment_variables: %{},
23+
erlang_version: ^erlang
24+
},
25+
id: ^uuid,
26+
repo_slug: "owner/name"
27+
} = job_data
28+
end
29+
30+
test "render index.json given job with docker dependencies" do
31+
repo = build(:repo)
32+
job = build(:job, repo: repo) |> with_docker_deps
33+
34+
%{config: %{deps: %{docker: [dep]}, environment: environment}} = job
35+
36+
%{container_name: container_name, environment: docker_env, image: image, wait: %{port: port}} =
37+
dep
38+
39+
%{data: [job_data]} = render(ElixirBenchWeb.JobView, "index.json", %{jobs: [job], repo: repo})
40+
41+
assert %{
42+
environment_variables: ^environment
43+
} = job_data.config
44+
45+
assert %{
46+
container_name: ^container_name,
47+
environment: ^docker_env,
48+
image: ^image,
49+
wait: %{port: ^port}
50+
} = hd(job_data.config.deps.docker)
51+
end
52+
end

test/support/factory/elixir_bench_factory.ex

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ defmodule ElixirBench.Factory do
22
use ExMachina.Ecto, repo: ElixirBench.Repo
33

44
alias ElixirBench.Repos
5-
alias ElixirBench.Benchmarks.{Benchmark, Measurement, Job, Runner}
5+
alias ElixirBench.Benchmarks.{Benchmark, Measurement, Job, Runner, Config}
6+
alias Config.{Dep, Dep.Docker, Dep.Docker.Wait}
67

78
def runner_factory do
89
%Runner{
@@ -35,10 +36,33 @@ defmodule ElixirBench.Factory do
3536
elixir_version: "1.5.2",
3637
erlang_version: "20.1",
3738
repo: build(:repo),
38-
uuid: Ecto.UUID.generate()
39+
uuid: Ecto.UUID.generate(),
40+
config: build(:config)
3941
}
4042
end
4143

44+
def config_factory do
45+
%Config{
46+
elixir: "1.5.2",
47+
erlang: "20.1"
48+
}
49+
end
50+
51+
def with_docker_deps(%Job{} = job) do
52+
deps = %Dep{
53+
docker: [
54+
%Docker{
55+
container_name: "postgres",
56+
image: "postgres:9.6.6-alpine",
57+
wait: %Wait{port: 5432},
58+
environment: %{"MYSQL_ALLOW_EMPTY_PASSWORD" => "true"}
59+
}
60+
]
61+
}
62+
63+
%{job | config: %{job.config | deps: deps}}
64+
end
65+
4266
def measurement_factory do
4367
repo = insert(:repo)
4468

0 commit comments

Comments
 (0)