add preseem sync worker oban cron job
This commit is contained in:
parent
78040c0df7
commit
b04d25e607
4 changed files with 192 additions and 2 deletions
|
|
@ -79,7 +79,9 @@ config :towerops, Oban,
|
|||
# Delete expired IP bans every hour
|
||||
{"0 * * * *", Towerops.Workers.ExpiredBanCleanupWorker},
|
||||
# Delete stale violation records daily at 2 AM
|
||||
{"0 2 * * *", Towerops.Workers.StaleViolationCleanupWorker}
|
||||
{"0 2 * * *", Towerops.Workers.StaleViolationCleanupWorker},
|
||||
# Sync Preseem data every 10 minutes
|
||||
{"*/10 * * * *", Towerops.Workers.PreseemSyncWorker}
|
||||
]},
|
||||
# Automatically delete completed jobs after 60 seconds
|
||||
{Oban.Plugins.Pruner, max_age: 60},
|
||||
|
|
|
|||
|
|
@ -192,7 +192,9 @@ if config_env() == :prod do
|
|||
# Delete expired IP bans every hour
|
||||
{"0 * * * *", Towerops.Workers.ExpiredBanCleanupWorker},
|
||||
# Delete stale violation records daily at 2 AM
|
||||
{"0 2 * * *", Towerops.Workers.StaleViolationCleanupWorker}
|
||||
{"0 2 * * *", Towerops.Workers.StaleViolationCleanupWorker},
|
||||
# Sync Preseem data every 10 minutes
|
||||
{"*/10 * * * *", Towerops.Workers.PreseemSyncWorker}
|
||||
]},
|
||||
# Automatically delete completed jobs after 60 seconds
|
||||
{Oban.Plugins.Pruner, max_age: 60},
|
||||
|
|
|
|||
61
lib/towerops/workers/preseem_sync_worker.ex
Normal file
61
lib/towerops/workers/preseem_sync_worker.ex
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
defmodule Towerops.Workers.PreseemSyncWorker do
|
||||
@moduledoc """
|
||||
Oban cron worker that syncs Preseem data for all enabled integrations.
|
||||
|
||||
Runs every 10 minutes. For each org with an enabled Preseem integration,
|
||||
checks if enough time has elapsed since the last sync (based on the
|
||||
integration's sync_interval_minutes), then calls Preseem.Sync.sync_organization/1.
|
||||
"""
|
||||
use Oban.Worker, queue: :maintenance
|
||||
|
||||
alias Towerops.Integrations
|
||||
alias Towerops.Preseem.Sync
|
||||
|
||||
require Logger
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(%Oban.Job{}) do
|
||||
integrations = Integrations.list_enabled_integrations("preseem")
|
||||
|
||||
results =
|
||||
Enum.map(integrations, fn integration ->
|
||||
if should_sync?(integration) do
|
||||
case Sync.sync_organization(integration) do
|
||||
{:ok, result} ->
|
||||
Logger.info("Preseem sync completed for org #{integration.organization_id}: #{inspect(result)}")
|
||||
|
||||
{:ok, result}
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Preseem sync failed for org #{integration.organization_id}: #{inspect(reason)}")
|
||||
|
||||
{:error, reason}
|
||||
end
|
||||
else
|
||||
:skipped
|
||||
end
|
||||
end)
|
||||
|
||||
synced = Enum.count(results, &match?({:ok, _}, &1))
|
||||
failed = Enum.count(results, &match?({:error, _}, &1))
|
||||
skipped = Enum.count(results, &(&1 == :skipped))
|
||||
|
||||
if synced > 0 or failed > 0 do
|
||||
Logger.info("Preseem sync batch: #{synced} synced, #{failed} failed, #{skipped} skipped")
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp should_sync?(integration) do
|
||||
case integration.last_synced_at do
|
||||
nil ->
|
||||
true
|
||||
|
||||
last_synced_at ->
|
||||
interval_seconds = (integration.sync_interval_minutes || 10) * 60
|
||||
elapsed = DateTime.diff(DateTime.utc_now(), last_synced_at, :second)
|
||||
elapsed >= interval_seconds
|
||||
end
|
||||
end
|
||||
end
|
||||
125
test/towerops/workers/preseem_sync_worker_test.exs
Normal file
125
test/towerops/workers/preseem_sync_worker_test.exs
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
defmodule Towerops.Workers.PreseemSyncWorkerTest do
|
||||
use Towerops.DataCase, async: true
|
||||
use Oban.Testing, repo: Towerops.Repo
|
||||
|
||||
import Towerops.AccountsFixtures
|
||||
import Towerops.IntegrationsFixtures
|
||||
import Towerops.OrganizationsFixtures
|
||||
|
||||
alias Towerops.Preseem.Client
|
||||
alias Towerops.Workers.PreseemSyncWorker
|
||||
|
||||
setup do
|
||||
user = user_fixture()
|
||||
org = organization_fixture(user.id)
|
||||
%{org: org}
|
||||
end
|
||||
|
||||
describe "perform/1" do
|
||||
test "syncs enabled preseem integrations", %{org: org} do
|
||||
integration = integration_fixture(org.id)
|
||||
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
Req.Test.json(conn, %{
|
||||
"data" => [
|
||||
%{
|
||||
"id" => "ap-1",
|
||||
"name" => "Test AP",
|
||||
"mac_address" => "AA:BB:CC:DD:EE:FF",
|
||||
"ip_address" => "10.0.0.1"
|
||||
}
|
||||
]
|
||||
})
|
||||
end)
|
||||
|
||||
assert :ok = perform_job(PreseemSyncWorker, %{})
|
||||
|
||||
updated = Repo.reload!(integration)
|
||||
assert updated.last_synced_at
|
||||
assert updated.last_sync_status in ["success", "partial"]
|
||||
end
|
||||
|
||||
test "skips integrations synced recently", %{org: org} do
|
||||
_integration =
|
||||
integration_fixture(org.id, %{
|
||||
last_synced_at: DateTime.truncate(DateTime.utc_now(), :second),
|
||||
last_sync_status: "success"
|
||||
})
|
||||
|
||||
# No Client stub needed - should skip without calling API
|
||||
assert :ok = perform_job(PreseemSyncWorker, %{})
|
||||
end
|
||||
|
||||
test "handles no enabled integrations" do
|
||||
assert :ok = perform_job(PreseemSyncWorker, %{})
|
||||
end
|
||||
|
||||
test "handles API errors without crashing", %{org: org} do
|
||||
_integration = integration_fixture(org.id)
|
||||
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
conn
|
||||
|> Plug.Conn.put_status(401)
|
||||
|> Req.Test.json(%{"error" => "unauthorized"})
|
||||
end)
|
||||
|
||||
assert :ok = perform_job(PreseemSyncWorker, %{})
|
||||
end
|
||||
|
||||
test "respects sync_interval_minutes", %{org: org} do
|
||||
# Last synced 5 minutes ago with a 10-minute interval -> should skip
|
||||
five_minutes_ago =
|
||||
DateTime.utc_now()
|
||||
|> DateTime.add(-5, :minute)
|
||||
|> DateTime.truncate(:second)
|
||||
|
||||
_integration =
|
||||
integration_fixture(org.id, %{
|
||||
sync_interval_minutes: 10,
|
||||
last_synced_at: five_minutes_ago,
|
||||
last_sync_status: "success"
|
||||
})
|
||||
|
||||
# No Client stub needed - should skip without calling API
|
||||
assert :ok = perform_job(PreseemSyncWorker, %{})
|
||||
end
|
||||
|
||||
test "syncs when sync interval has elapsed", %{org: org} do
|
||||
# Last synced 15 minutes ago with a 10-minute interval -> should sync
|
||||
fifteen_minutes_ago =
|
||||
DateTime.utc_now()
|
||||
|> DateTime.add(-15, :minute)
|
||||
|> DateTime.truncate(:second)
|
||||
|
||||
integration =
|
||||
integration_fixture(org.id, %{
|
||||
sync_interval_minutes: 10,
|
||||
last_synced_at: fifteen_minutes_ago,
|
||||
last_sync_status: "success"
|
||||
})
|
||||
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
Req.Test.json(conn, %{"data" => []})
|
||||
end)
|
||||
|
||||
assert :ok = perform_job(PreseemSyncWorker, %{})
|
||||
|
||||
updated = Repo.reload!(integration)
|
||||
assert DateTime.after?(updated.last_synced_at, fifteen_minutes_ago)
|
||||
end
|
||||
|
||||
test "syncs integrations that have never been synced", %{org: org} do
|
||||
integration = integration_fixture(org.id)
|
||||
assert integration.last_synced_at == nil
|
||||
|
||||
Req.Test.stub(Client, fn conn ->
|
||||
Req.Test.json(conn, %{"data" => []})
|
||||
end)
|
||||
|
||||
assert :ok = perform_job(PreseemSyncWorker, %{})
|
||||
|
||||
updated = Repo.reload!(integration)
|
||||
assert updated.last_synced_at
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue