defmodule Chaperon.API.HTTP do
@moduledoc """
HTTP API handler used for remote control of chaperon cluster.
Allows scheduling new load tests, aborting currently running or scheduled ones
as well as listing all currently running and scheduled load tests.
"""
require Logger
require Poison
defmodule HealthCheckPlug do
@moduledoc false
import Plug.Conn
@behaviour Plug
@intercepted_routes [
# root page (GET /)
[],
["healthcheck"]
]
def init(opts), do: opts
def call(conn = %{method: "GET", path_info: path_info}, _opts)
when path_info in @intercepted_routes do
conn
|> send_resp(200, "Chaperon @ #{Chaperon.version()}")
|> halt()
end
def call(conn, _opts), do: conn
end
use Plug.Router
import Plug.Conn
import Chaperon.Util, only: [symbolize_keys: 1]
if Mix.env() == :dev do
use Plug.Debugger, otp_app: :cutlass
end
plug(Chaperon.API.HTTP.HealthCheckPlug)
plug(:self_logger)
plug(Plug.RequestId)
plug(BasicAuth, use_config: {:chaperon, Chaperon.API.HTTP})
plug(Plug.Parsers, parsers: [:json], json_decoder: Poison)
plug(:match)
plug(:dispatch)
def start_link() do
port = api_port()
Logger.info("Starting Chaperon.API.HTTP on port #{port}")
case api_ip() do
nil -> Plug.Adapters.Cowboy.http(__MODULE__, [acceptors: 20], port: port)
ip -> Plug.Adapters.Cowboy.http(__MODULE__, [acceptors: 20], port: port, ip: ip)
end
end
def enabled?() do
api_port() != nil
end
def api_ip() do
case System.get_env("CHAPERON_IP") do
nil ->
Application.get_env(:chaperon, __MODULE__)[:ip]
ip ->
String.to_charlist(ip) |> :inet.parse_address() |> elem(1)
end
end
def api_port() do
case System.get_env("CHAPERON_PORT") do
nil -> Application.get_env(:chaperon, __MODULE__)[:port]
port -> port |> String.to_integer()
end
end
get "/load_tests" do
conn
|> send_json_resp(200, %{
running: Chaperon.Master.running_load_tests(),
scheduled: Chaperon.Master.scheduled_load_tests()
})
end
get "/version" do
conn
|> send_resp(200, Chaperon.version())
end
get "/*_" do
conn
|> send_resp(404, "")
end
post "/load_tests" do
load_tests =
for cfg <- conn.params["load_tests"] || [] do
cfg
|> Map.take(["test", "options"])
|> symbolize_keys()
|> parse_options()
end
|> List.flatten()
case Chaperon.Master.schedule_load_tests(load_tests) do
{:error, reason} ->
conn
|> send_json_resp(400, %{error: reason |> inspect})
{:ok, ids} ->
conn
|> send_json_resp(200, %{scheduled: ids})
end
end
delete "/load_tests" do
case Chaperon.Master.cancel_all() do
{:error, reason} ->
conn
|> send_json_resp(400, %{
error: inspect(reason)
})
:ok ->
conn
|> send_resp(202, "")
end
end
delete "/load_tests/scheduled" do
case Chaperon.Master.cancel_scheduled() do
{:error, reason} ->
conn
|> send_json_resp(400, %{
error: inspect(reason)
})
:ok ->
conn
|> send_resp(202, "")
end
end
delete "/load_tests/:id" do
case Chaperon.Master.cancel_running_or_scheduled(conn.params["id"]) do
{:error, reason} ->
conn
|> send_json_resp(400, %{
error: inspect(reason)
})
:ok ->
conn
|> send_resp(202, "")
end
end
post "/*_" do
conn
|> send_resp(404, "")
end
defp self_logger(conn, opts) do
if conn.request_path == "/healthcheck" do
conn
else
Plug.Logger.call(conn, Plug.Logger.init(opts))
end
end
defp send_json_resp(conn, status_code, data) do
conn
|> send_resp(status_code, Poison.encode!(data))
end
def parse_options(args) do
parser = Application.get_env(:chaperon, __MODULE__)[:option_parser]
Logger.info("Chaperon.API.HTTP | Using option parser: #{inspect(parser)}")
case parser.parse_options(args) do
{:ok, lt_configs} ->
for {lt, options} <- lt_configs do
%{test: lt, options: options}
end
{:ok, lt, options} ->
%{test: lt, options: options}
{:error, reason} ->
Logger.error("Error parsing options using parser #{inspect(parser)}: #{inspect(reason)}")
raise ArgumentError, message: inspect(reason)
end
end
end