Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP (do not review!) Combine hub stats #673

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/ret/node_stat.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ defmodule Ret.NodeStat do
])
end

@spec max_ccu_for_time_range(DateTime.t(), DateTime.t()) :: number()
def max_ccu_for_time_range(start_time, end_time) do
start_time_truncated = start_time |> NaiveDateTime.truncate(:second)
end_time_truncated = end_time |> NaiveDateTime.truncate(:second)
Expand All @@ -36,6 +37,8 @@ defmodule Ret.NodeStat do
where: stat.measured_at < ^end_time_truncated
)

# Can I check that the db actually has the time range? So we could technically fill in previous data.

if max_ccu === nil, do: 0, else: max_ccu
end
end
24 changes: 24 additions & 0 deletions lib/ret_web/controllers/api-internal/v1/hub_stats_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule RetWeb.ApiInternal.V1.HubStatsController do
use RetWeb, :controller
alias Ret.NodeStat

# Params start_time and end_time should be in iso format such as "2000-02-28 23:00:13"
# or what is returned from NaiveDateTime.to_string()
def hub_stats(conn, %{"start_time" => start_time_str, "end_time" => end_time_str}) do
conn = put_resp_header(conn, "content-type", "application/json")

case Ret.Storage.storage_used() do
{:ok, storage_used_kb} when is_number(storage_used_kb) ->
max_ccu =
NodeStat.max_ccu_for_time_range(
start_time_str |> NaiveDateTime.from_iso8601!(),
end_time_str |> NaiveDateTime.from_iso8601!()
)

conn |> send_resp(200, %{max_ccu: max_ccu, storage_mb: storage_used_kb / 1024} |> Poison.encode!())

_ ->
send_resp(conn, 503, %{error: :storage_usage_unavailable} |> Poison.encode!())
end
end
end
9 changes: 5 additions & 4 deletions lib/ret_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ defmodule RetWeb.Router do
end

pipeline :graphql do
plug RetWeb.ApiTokenAuthPipeline
plug RetWeb.AddAbsintheContext
plug(RetWeb.ApiTokenAuthPipeline)
plug(RetWeb.AddAbsintheContext)
end

scope "/health", RetWeb do
Expand Down Expand Up @@ -193,8 +193,8 @@ defmodule RetWeb.Router do
pipe_through [:parsed_body, :api, :public_api_access, :graphql] ++
if(Mix.env() == :prod, do: [:ssl_only], else: [])

forward "/graphiql", Absinthe.Plug.GraphiQL, json_codec: Jason, schema: RetWeb.Schema
forward "/", Absinthe.Plug, json_codec: Jason, schema: RetWeb.Schema
forward("/graphiql", Absinthe.Plug.GraphiQL, json_codec: Jason, schema: RetWeb.Schema)
forward("/", Absinthe.Plug, json_codec: Jason, schema: RetWeb.Schema)
end

scope "/api-internal", RetWeb do
Expand All @@ -208,6 +208,7 @@ defmodule RetWeb.Router do
post "/rewrite_assets", ApiInternal.V1.RewriteAssetsController, :post
put "/change_email_for_login", ApiInternal.V1.LoginEmailController, :update
post "/make_auth_token_for_email", ApiInternal.V1.AuthTokenController, :post
get("/hub_stats", ApiInternal.V1.HubStatsController, :show)
end
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
defmodule RetWeb.ApiInternal.V1.StorageControllerTest do
use RetWeb.ConnCase
import Ret.TestHelpers

@dashboard_access_header "x-ret-dashboard-access-key"
@dashboard_access_key "test-key"

setup_all do
merge_module_config(:ret, RetWeb.Plugs.DashboardHeaderAuthorization, %{
dashboard_access_key: @dashboard_access_key
})

on_exit(fn ->
Ret.TestHelpers.merge_module_config(:ret, RetWeb.Plugs.DashboardHeaderAuthorization, %{
dashboard_access_key: nil
})
end)
end

test "hub stats endpoint responds with cached storage value", %{conn: conn} do
mock_storage_used(0)
resp = request_hub_stats(conn)
assert resp["storage_mb"] === 0.0 and resp["max_ccu"] === 0.0

mock_storage_used(10)
resp = request_hub_stats(conn)
assert resp["storage_mb"] === 10.0 and resp["max_ccu"] === 0.0
end


test "hub stats endpoint returns 401 without access key header", %{conn: conn} do
resp = get(conn, "/api-internal/v1/hub_stats")
assert resp.status === 401
end

test "hub stats endpoint returns 401 with incorrect access key", %{conn: conn} do
resp =
conn
|> put_req_header(@dashboard_access_header, "incorrect-access-key")
|> get("/api-internal/v1/hub_stats")

assert resp.status === 401
end

test "hub stats endpoint errors with 503 status when storage usage is not available", %{conn: conn} do
mock_storage_used(nil)
resp = request_hub_stats(conn, expected_status: 503)
assert resp["error"] === "storage_usage_unavailable"
end

# The Ret.Storage module relies on a cached value to retrieve storage usage via Ret.StorageUsed.
# Since we mainly care about testing the endpoint here, we use the cache to mock the usage value
# and ensure that the endpoint returns it as expected.
defp mock_storage_used(nil), do: Cachex.put(:storage_used, :storage_used, nil)

defp mock_storage_used(storage_used_mb),
do: Cachex.put(:storage_used, :storage_used, storage_used_mb * 1024)

defp request_hub_stats(conn, opts \\ [expected_status: 200]) do
{:ok, start_time} = NaiveDateTime.utc_now() |> NaiveDateTime.to_date() |> NaiveDateTime.new(Time.new(0,0,0,0))
{:ok, end_time} = NaiveDateTime.utc_now() |> NaiveDateTime.to_date() |> Date.add(1) |> NaiveDateTime.new(Time.new(0,0,0,0))

conn
|> put_req_header(@dashboard_access_header, @dashboard_access_key)
|> get("/api-internal/v1/hub_stats", %{start_time: start_time, end_time: end_time})
|> json_response(opts[:expected_status])
end
end