Skip to content

Commit

Permalink
fix: create containers per tenant (#1301)
Browse files Browse the repository at this point in the history
Using docker we create containers per tenant_fixture to properly parallize testing and isolate DB changes from impacting other tests
  • Loading branch information
filipecabaco authored Feb 25, 2025
1 parent b803079 commit b5eba2f
Show file tree
Hide file tree
Showing 46 changed files with 838 additions and 909 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,6 @@ jobs:
- name: Start epmd
run: epmd -daemon
- name: Run tests
run: MIX_ENV=test mix coveralls.github --trace
run: MIX_ENV=test MAX_CASES=2 mix coveralls.github --trace
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2 changes: 1 addition & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ config :realtime,

# Configures the endpoint
config :realtime, RealtimeWeb.Endpoint,
url: [host: "localhost"],
url: [host: "127.0.0.1"],
secret_key_base: "ktyW57usZxrivYdvLo9os7UGcUUZYKchOMHT3tzndmnHuxD09k+fQnPUmxlPMUI3",
render_errors: [view: RealtimeWeb.ErrorView, accepts: ~w(html json), layout: false],
pubsub_server: Realtime.PubSub,
Expand Down
2 changes: 1 addition & 1 deletion config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ config :logflare_logger_backend,
url: System.get_env("LOGFLARE_LOGGER_BACKEND_URL", "https://api.logflare.app")

app_name = System.get_env("APP_NAME", "")
default_db_host = System.get_env("DB_HOST", "localhost")
default_db_host = System.get_env("DB_HOST", "127.0.0.1")
username = System.get_env("DB_USER", "postgres")
password = System.get_env("DB_PASSWORD", "postgres")
database = System.get_env("DB_NAME", "postgres")
Expand Down
6 changes: 4 additions & 2 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ for repo <- [
username: "postgres",
password: "postgres",
database: "realtime_test",
hostname: "localhost",
hostname: "127.0.0.1",
pool: Ecto.Adapters.SQL.Sandbox
end

Expand All @@ -43,7 +43,9 @@ config :joken,
current_time_adapter: RealtimeWeb.Joken.CurrentTime.Mock

# Print only errors during test
config :logger, level: :warning
config :logger,
compile_time_purge_matching: [[module: Postgrex], [module: DBConnection]],
level: :warning

# Configures Elixir's Logger
config :logger, :console,
Expand Down
11 changes: 3 additions & 8 deletions lib/realtime/context_cache.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,9 @@ defmodule Realtime.ContextCache do
cache = cache_name(context)
cache_key = {{fun, arity}, args}

case Cachex.fetch(cache, cache_key, fn {{_fun, _arity}, args} ->
{:commit, {:cached, apply(context, fun, args)}}
end) do
{:commit, {:cached, value}} ->
value

{:ok, {:cached, value}} ->
value
case Cachex.fetch(cache, cache_key, fn {{_fun, _arity}, args} -> {:commit, {:cached, apply(context, fun, args)}} end) do
{:commit, {:cached, value}} -> value
{:ok, {:cached, value}} -> value
end
end

Expand Down
27 changes: 14 additions & 13 deletions lib/realtime/tenants/connect.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@ defmodule Realtime.Tenants.Connect do
alias Realtime.Tenants.Migrations
alias Realtime.UsersCounter

@pipes [
GetTenant,
CheckConnection,
StartCounters,
RegisterProcess
]
@rpc_timeout_default 30_000
@check_connected_user_interval_default 50_000
@connected_users_bucket_shutdown [0, 0, 0, 0, 0, 0]
Expand All @@ -52,7 +46,7 @@ defmodule Realtime.Tenants.Connect do
| {:error, :initializing}
| {:error, :tenant_database_connection_initializing}
| {:error, :rpc_error, term()}
def lookup_or_start_connection(tenant_id, opts \\ []) do
def lookup_or_start_connection(tenant_id, opts \\ []) when is_binary(tenant_id) do
case get_status(tenant_id) do
{:ok, conn} ->
{:ok, conn}
Expand Down Expand Up @@ -174,7 +168,14 @@ defmodule Realtime.Tenants.Connect do
def init(%{tenant_id: tenant_id} = state) do
Logger.metadata(external_id: tenant_id, project: tenant_id)

case Piper.run(@pipes, state) do
pipes = [
GetTenant,
CheckConnection,
StartCounters,
RegisterProcess
]

case Piper.run(pipes, state) do
{:ok, acc} ->
{:ok, acc, {:continue, :run_migrations}}

Expand Down Expand Up @@ -298,11 +299,6 @@ defmodule Realtime.Tenants.Connect do
{:stop, :normal, state}
end

# Ignore unsuspend messages to avoid handle_info unmatched functions
def handle_info(:unsuspend_tenant, state) do
{:noreply, state}
end

def handle_info(
{:DOWN, db_conn_reference, _, _, _},
%{db_conn_reference: db_conn_reference} = state
Expand All @@ -311,6 +307,11 @@ defmodule Realtime.Tenants.Connect do
{:stop, :normal, state}
end

# Ignore messages to avoid handle_info unmatched functions
def handle_info(_, state) do
{:noreply, state}
end

@impl true
def terminate(reason, %{tenant_id: tenant_id}) do
Logger.info("Tenant #{tenant_id} has been terminated: #{inspect(reason)}")
Expand Down
7 changes: 2 additions & 5 deletions lib/realtime/tenants/connect/check_connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,8 @@ defmodule Realtime.Tenants.Connect.CheckConnection do
%{tenant: tenant} = acc

case Database.check_tenant_connection(tenant) do
{:ok, conn} ->
{:ok, %{acc | db_conn_pid: conn, db_conn_reference: Process.monitor(conn)}}

{:error, error} ->
{:error, error}
{:ok, conn} -> {:ok, %{acc | db_conn_pid: conn, db_conn_reference: Process.monitor(conn)}}
{:error, error} -> {:error, error}
end
end
end
3 changes: 1 addition & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Realtime.MixProject do
def project do
[
app: :realtime,
version: "2.34.33",
version: "2.34.34",
elixir: "~> 1.17.3",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
Expand Down Expand Up @@ -101,7 +101,6 @@ defmodule Realtime.MixProject do
"ecto.create --quiet",
"run priv/repo/seeds_before_migration.exs",
"ecto.migrate --migrations-path=priv/repo/migrations",
"run priv/repo/seeds_after_migration.exs",
"test"
],
"assets.deploy": ["esbuild default --minify", "tailwind default --minify", "phx.digest"]
Expand Down
2 changes: 1 addition & 1 deletion priv/repo/seeds.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Ecto.Adapters.SQL, only: [query: 3]

tenant_name = System.get_env("SELF_HOST_TENANT_NAME", "realtime-dev")
env = if :ets.whereis(Mix.State) != :undefined, do: Mix.env(), else: :prod
default_db_host = if env in [:dev, :test], do: "localhost", else: "host.docker.internal"
default_db_host = if env in [:dev, :test], do: "127.0.0.1", else: "host.docker.internal"

Repo.transaction(fn ->
case Repo.get_by(Tenant, external_id: tenant_name) do
Expand Down
58 changes: 0 additions & 58 deletions priv/repo/seeds_after_migration.exs

This file was deleted.

2 changes: 1 addition & 1 deletion rel/overlays/config.example.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
endpoint_port: 4000
db_repo:
- hostname: "localhost"
- hostname: "127.0.0.1"
username: "postgres"
password: "postgres"
database: "postgres"
Expand Down
3 changes: 2 additions & 1 deletion test/api_jwt_secret_test.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
defmodule RealtimeWeb.ApiJwtSecretTest do
use RealtimeWeb.ConnCase
# async: false due to usage of mock
use RealtimeWeb.ConnCase, async: false
import Mock
alias RealtimeWeb.JwtVerification

Expand Down
16 changes: 7 additions & 9 deletions test/integration/rt_channel_test.exs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
Code.require_file("../support/websocket_client.exs", __DIR__)

defmodule Realtime.Integration.RtChannelTest do
# async: false due to the fact that multiple operations against the database will use the same connection

# async: false due to the fact that multiple operations against the same tenant and usage of mocks
use RealtimeWeb.ConnCase, async: false
import ExUnit.CaptureLog
import Generators
Expand All @@ -14,16 +13,14 @@ defmodule Realtime.Integration.RtChannelTest do
alias Phoenix.Socket.Message
alias Phoenix.Socket.V1
alias Postgrex
alias Realtime.Api.Tenant
alias Realtime.Database
alias Realtime.Integration.RtChannelTest.Endpoint
alias Realtime.Integration.WebsocketClient
alias Realtime.RateCounter
alias Realtime.Repo
alias Realtime.Tenants
alias Realtime.Tenants.Authorization
alias Realtime.Tenants.Cache
alias Realtime.Tenants.Migrations

@moduletag :capture_log
@port 4002
@serializer V1.JSONSerializer
Expand Down Expand Up @@ -78,8 +75,9 @@ defmodule Realtime.Integration.RtChannelTest do
RateCounter.stop(@external_id)
Cache.invalidate_tenant_cache(@external_id)
Process.sleep(500)
[tenant] = Tenant |> Repo.all() |> Repo.preload(:extensions)
:ok = Migrations.run_migrations(tenant)

tenant = Tenants.get_tenant_by_external_id(@external_id)

%{tenant: tenant}
end

Expand Down Expand Up @@ -1265,7 +1263,7 @@ defmodule Realtime.Integration.RtChannelTest do
get_connection("authenticated", %{:exp => System.system_time(:second) - 1000})
end)

assert log =~ "InvalidJWTToken: Token as expired 1000 seconds ago"
assert log =~ "InvalidJWTToken: Token as expired"
end
end

Expand Down Expand Up @@ -1466,7 +1464,7 @@ defmodule Realtime.Integration.RtChannelTest do
claims =
Map.merge(
%{
ref: "localhost",
ref: "127.0.0.1",
iat: System.system_time(:second),
exp: System.system_time(:second) + 604_800
},
Expand Down
Loading

0 comments on commit b5eba2f

Please sign in to comment.