diff --git a/changelog.d/reachabililty.change b/changelog.d/reachabililty.change
new file mode 100644
index 000000000..71b9514be
--- /dev/null
+++ b/changelog.d/reachabililty.change
@@ -0,0 +1 @@
+Improved the logic of how we determine if a server is unreachable.
diff --git a/config/config.exs b/config/config.exs
index a231c5ba0..d164f8389 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -194,7 +194,6 @@ config :pleroma, :instance,
account_approval_required: false,
federating: true,
federation_incoming_replies_max_depth: 100,
- federation_reachability_timeout_days: 7,
allow_relay: true,
public: true,
quarantined_instances: [],
@@ -603,7 +602,8 @@ config :pleroma, Oban,
crontab: [
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker},
- {"*/10 * * * *", Pleroma.Workers.Cron.AppCleanupWorker}
+ {"*/10 * * * *", Pleroma.Workers.Cron.AppCleanupWorker},
+ {"0 0 * * *", Pleroma.Workers.Cron.ScheduleReachabilityWorker}
]
config :pleroma, Pleroma.Formatter,
diff --git a/lib/pleroma/instances.ex b/lib/pleroma/instances.ex
index b6d83f591..9237e0944 100644
--- a/lib/pleroma/instances.ex
+++ b/lib/pleroma/instances.ex
@@ -15,25 +15,7 @@ defmodule Pleroma.Instances do
defdelegate set_unreachable(url_or_host, unreachable_since \\ nil), to: Instance
- defdelegate get_consistently_unreachable, to: Instance
-
- def set_consistently_unreachable(url_or_host),
- do: set_unreachable(url_or_host, reachability_datetime_threshold())
-
- def reachability_datetime_threshold do
- federation_reachability_timeout_days =
- Pleroma.Config.get([:instance, :federation_reachability_timeout_days], 0)
-
- if federation_reachability_timeout_days > 0 do
- NaiveDateTime.add(
- NaiveDateTime.utc_now(),
- -federation_reachability_timeout_days * 24 * 3600,
- :second
- )
- else
- ~N[0000-01-01 00:00:00]
- end
- end
+ defdelegate get_unreachable, to: Instance
def host(url_or_host) when is_binary(url_or_host) do
if url_or_host =~ ~r/^http/i do
diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex
index 33f1229d0..baccc314c 100644
--- a/lib/pleroma/instances/instance.ex
+++ b/lib/pleroma/instances/instance.ex
@@ -51,7 +51,7 @@ defmodule Pleroma.Instances.Instance do
|> cast(params, [:software_name, :software_version, :software_repository])
end
- def filter_reachable([]), do: %{}
+ def filter_reachable([]), do: []
def filter_reachable(urls_or_hosts) when is_list(urls_or_hosts) do
hosts =
@@ -68,19 +68,15 @@ defmodule Pleroma.Instances.Instance do
)
|> Map.new(& &1)
- reachability_datetime_threshold = Instances.reachability_datetime_threshold()
-
for entry <- Enum.filter(urls_or_hosts, &is_binary/1) do
host = host(entry)
unreachable_since = unreachable_since_by_host[host]
- if !unreachable_since ||
- NaiveDateTime.compare(unreachable_since, reachability_datetime_threshold) == :gt do
- {entry, unreachable_since}
+ if is_nil(unreachable_since) do
+ entry
end
end
|> Enum.filter(& &1)
- |> Map.new(& &1)
end
def reachable?(url_or_host) when is_binary(url_or_host) do
@@ -88,7 +84,7 @@ defmodule Pleroma.Instances.Instance do
from(i in Instance,
where:
i.host == ^host(url_or_host) and
- i.unreachable_since <= ^Instances.reachability_datetime_threshold(),
+ i.unreachable_since <= ^NaiveDateTime.utc_now(),
select: true
)
)
@@ -132,11 +128,9 @@ defmodule Pleroma.Instances.Instance do
def set_unreachable(_, _), do: {:error, nil}
- def get_consistently_unreachable do
- reachability_datetime_threshold = Instances.reachability_datetime_threshold()
-
+ def get_unreachable do
from(i in Instance,
- where: ^reachability_datetime_threshold > i.unreachable_since,
+ where: not is_nil(i.unreachable_since),
order_by: i.unreachable_since,
select: {i.host, i.unreachable_since}
)
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index b54ef9ce5..ea5480a41 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -4,7 +4,6 @@
defmodule Pleroma.Object.Fetcher do
alias Pleroma.HTTP
- alias Pleroma.Instances
alias Pleroma.Maps
alias Pleroma.Object
alias Pleroma.Object.Containment
@@ -152,10 +151,6 @@ defmodule Pleroma.Object.Fetcher do
{:ok, body} <- get_object(id),
{:ok, data} <- safe_json_decode(body),
:ok <- Containment.contain_origin_from_id(id, data) do
- if not Instances.reachable?(id) do
- Instances.set_reachable(id)
- end
-
{:ok, data}
else
{:scheme, _} ->
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 7ac0bbab4..2ee72c49a 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -53,7 +53,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
)
plug(:log_inbox_metadata when action in [:inbox])
- plug(:set_requester_reachable when action in [:inbox])
plug(:relay_active? when action in [:relay])
defp relay_active?(conn, _) do
@@ -520,15 +519,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|> json(dgettext("errors", "error"))
end
- defp set_requester_reachable(%Plug.Conn{} = conn, _) do
- with actor <- conn.params["actor"],
- true <- is_binary(actor) do
- Pleroma.Instances.set_reachable(actor)
- end
-
- conn
- end
-
defp log_inbox_metadata(%{params: %{"actor" => actor, "type" => type}} = conn, _) do
Logger.metadata(actor: actor, type: type)
conn
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index 0de3a0d43..78312b771 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -148,17 +148,9 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
{"digest", p.digest}
]
) do
- if not is_nil(p.unreachable_since) do
- Instances.set_reachable(p.inbox)
- end
-
result
else
{_post_result, %{status: code} = response} = e ->
- if is_nil(p.unreachable_since) do
- Instances.set_unreachable(p.inbox)
- end
-
Logger.metadata(activity: p.activity_id, inbox: p.inbox, status: code)
Logger.error("Publisher failed to inbox #{p.inbox} with status #{code}")
@@ -179,10 +171,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
connection_pool_snooze()
e ->
- if is_nil(p.unreachable_since) do
- Instances.set_unreachable(p.inbox)
- end
-
Logger.metadata(activity: p.activity_id, inbox: p.inbox)
Logger.error("Publisher failed to inbox #{p.inbox} #{inspect(e)}")
{:error, e}
@@ -308,7 +296,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
Repo.checkout(fn ->
Enum.each(inboxes, fn inboxes ->
- Enum.each(inboxes, fn {inbox, unreachable_since} ->
+ Enum.each(inboxes, fn inbox ->
%User{ap_id: ap_id} = Enum.find(recipients, fn actor -> actor.inbox == inbox end)
# Get all the recipients on the same host and add them to cc. Otherwise, a remote
@@ -318,8 +306,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
__MODULE__.enqueue_one(%{
inbox: inbox,
cc: cc,
- activity_id: activity.id,
- unreachable_since: unreachable_since
+ activity_id: activity.id
})
end)
end)
@@ -352,12 +339,11 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|> Enum.each(fn {inboxes, priority} ->
inboxes
|> Instances.filter_reachable()
- |> Enum.each(fn {inbox, unreachable_since} ->
+ |> Enum.each(fn inbox ->
__MODULE__.enqueue_one(
%{
inbox: inbox,
- activity_id: activity.id,
- unreachable_since: unreachable_since
+ activity_id: activity.id
},
priority: priority
)
diff --git a/lib/pleroma/web/pleroma_api/controllers/instances_controller.ex b/lib/pleroma/web/pleroma_api/controllers/instances_controller.ex
index 6257e3153..85cfe9f00 100644
--- a/lib/pleroma/web/pleroma_api/controllers/instances_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/instances_controller.ex
@@ -13,7 +13,7 @@ defmodule Pleroma.Web.PleromaAPI.InstancesController do
def show(conn, _params) do
unreachable =
- Instances.get_consistently_unreachable()
+ Instances.get_unreachable()
|> Map.new(fn {host, date} -> {host, to_string(date)} end)
json(conn, %{"unreachable" => unreachable})
diff --git a/lib/pleroma/workers/cron/schedule_reachability_worker.ex b/lib/pleroma/workers/cron/schedule_reachability_worker.ex
new file mode 100644
index 000000000..a0b8e261c
--- /dev/null
+++ b/lib/pleroma/workers/cron/schedule_reachability_worker.ex
@@ -0,0 +1,33 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.Cron.ScheduleReachabilityWorker do
+ use Oban.Worker,
+ queue: :background,
+ max_attempts: 2
+
+ alias Pleroma.Instances
+ alias Pleroma.Repo
+
+ @impl true
+ def perform(_job) do
+ unreachable_servers = Instances.get_unreachable()
+
+ jobs =
+ unreachable_servers
+ |> Enum.map(fn {domain, _} ->
+ Pleroma.Workers.ReachabilityWorker.new(%{"domain" => domain})
+ end)
+
+ case Repo.transaction(fn ->
+ Enum.each(jobs, &Oban.insert/1)
+ end) do
+ {:ok, _} ->
+ :ok
+
+ {:error, reason} ->
+ {:error, reason}
+ end
+ end
+end
diff --git a/lib/pleroma/workers/publisher_worker.ex b/lib/pleroma/workers/publisher_worker.ex
index 7d9b022de..10736bef5 100644
--- a/lib/pleroma/workers/publisher_worker.ex
+++ b/lib/pleroma/workers/publisher_worker.ex
@@ -4,6 +4,7 @@
defmodule Pleroma.Workers.PublisherWorker do
alias Pleroma.Activity
+ alias Pleroma.Instances
alias Pleroma.Web.Federator
use Oban.Worker, queue: :federator_outgoing, max_attempts: 5
@@ -14,9 +15,30 @@ defmodule Pleroma.Workers.PublisherWorker do
Federator.perform(:publish, activity)
end
- def perform(%Job{args: %{"op" => "publish_one", "params" => params}}) do
+ def perform(%Job{args: %{"op" => "publish_one", "params" => params}} = job) do
params = Map.new(params, fn {k, v} -> {String.to_atom(k), v} end)
- Federator.perform(:publish_one, params)
+
+ # Cancel / skip the job if this server believed to be unreachable now
+ if not Instances.reachable?(params.inbox) do
+ {:cancel, :unreachable}
+ else
+ case Federator.perform(:publish_one, params) do
+ {:ok, _} ->
+ :ok
+
+ {:error, _} = error ->
+ # Only mark as unreachable on final failure
+ if job.attempt == job.max_attempts do
+ Instances.set_unreachable(params.inbox)
+ end
+
+ error
+
+ error ->
+ # Unexpected error, may have been client side
+ error
+ end
+ end
end
@impl true
diff --git a/lib/pleroma/workers/reachability_worker.ex b/lib/pleroma/workers/reachability_worker.ex
new file mode 100644
index 000000000..3a11dfe2a
--- /dev/null
+++ b/lib/pleroma/workers/reachability_worker.ex
@@ -0,0 +1,31 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.ReachabilityWorker do
+ use Oban.Worker,
+ queue: :background,
+ max_attempts: 3,
+ unique: [period: :infinity, states: [:available, :scheduled]]
+
+ alias Pleroma.HTTP
+ alias Pleroma.Instances
+
+ @impl true
+ def perform(%Oban.Job{args: %{"domain" => domain}}) do
+ case HTTP.get("https://#{domain}/.well-known/nodeinfo") do
+ {:ok, %{status: status}} when status in 200..299 ->
+ Instances.set_reachable("https://#{domain}")
+ :ok
+
+ {:ok, %{status: _status}} ->
+ {:error, :unreachable}
+
+ {:error, _} = error ->
+ error
+ end
+ end
+
+ @impl true
+ def timeout(_job), do: :timer.seconds(5)
+end
diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex
index 11b672bef..e2c950967 100644
--- a/lib/pleroma/workers/receiver_worker.ex
+++ b/lib/pleroma/workers/receiver_worker.ex
@@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.ReceiverWorker do
+ alias Pleroma.Instances
alias Pleroma.Signature
alias Pleroma.User
alias Pleroma.Web.Federator
@@ -37,6 +38,11 @@ defmodule Pleroma.Workers.ReceiverWorker do
{:ok, _public_key} <- Signature.refetch_public_key(conn_data),
{:signature, true} <- {:signature, Signature.validate_signature(conn_data)},
{:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
+ unless Instances.reachable?(params["actor"]) do
+ domain = URI.parse(params["actor"]).host
+ Oban.insert(Pleroma.Workers.ReachabilityWorker.new(%{"domain" => domain}))
+ end
+
{:ok, res}
else
e -> process_errors(e)
@@ -45,6 +51,11 @@ defmodule Pleroma.Workers.ReceiverWorker do
def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
with {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
+ unless Instances.reachable?(params["actor"]) do
+ domain = URI.parse(params["actor"]).host
+ Oban.insert(Pleroma.Workers.ReachabilityWorker.new(%{"domain" => domain}))
+ end
+
{:ok, res}
else
e -> process_errors(e)
diff --git a/lib/pleroma/workers/remote_fetcher_worker.ex b/lib/pleroma/workers/remote_fetcher_worker.ex
index aa09362f5..5f57ec2d7 100644
--- a/lib/pleroma/workers/remote_fetcher_worker.ex
+++ b/lib/pleroma/workers/remote_fetcher_worker.ex
@@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.RemoteFetcherWorker do
+ alias Pleroma.Instances
alias Pleroma.Object.Fetcher
use Oban.Worker, queue: :background, unique: [period: :infinity]
@@ -11,6 +12,15 @@ defmodule Pleroma.Workers.RemoteFetcherWorker do
def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do
case Fetcher.fetch_object_from_id(id, depth: args["depth"]) do
{:ok, _object} ->
+ # Mark the server as reachable since we successfully fetched an object
+ case URI.parse(id) do
+ %URI{host: host} when not is_nil(host) ->
+ Instances.set_reachable("https://#{host}")
+
+ _ ->
+ :ok
+ end
+
:ok
{:allowed_depth, false} ->
diff --git a/test/pleroma/instances/instance_test.exs b/test/pleroma/instances/instance_test.exs
index 6a718be21..f195d9bd6 100644
--- a/test/pleroma/instances/instance_test.exs
+++ b/test/pleroma/instances/instance_test.exs
@@ -3,7 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Instances.InstanceTest do
- alias Pleroma.Instances
alias Pleroma.Instances.Instance
alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
@@ -14,8 +13,6 @@ defmodule Pleroma.Instances.InstanceTest do
import ExUnit.CaptureLog
import Pleroma.Factory
- setup_all do: clear_config([:instance, :federation_reachability_timeout_days], 1)
-
describe "set_reachable/1" do
test "clears `unreachable_since` of existing matching Instance record having non-nil `unreachable_since`" do
unreachable_since = NaiveDateTime.to_iso8601(NaiveDateTime.utc_now())
@@ -145,7 +142,11 @@ defmodule Pleroma.Instances.InstanceTest do
end
test "Doesn't scrapes unreachable instances" do
- instance = insert(:instance, unreachable_since: Instances.reachability_datetime_threshold())
+ instance =
+ insert(:instance,
+ unreachable_since: NaiveDateTime.utc_now() |> NaiveDateTime.add(-:timer.hours(24))
+ )
+
url = "https://" <> instance.host
assert capture_log(fn -> assert nil == Instance.get_or_update_favicon(URI.parse(url)) end) =~
diff --git a/test/pleroma/instances_test.exs b/test/pleroma/instances_test.exs
index 96fa9cffe..cbafbfa44 100644
--- a/test/pleroma/instances_test.exs
+++ b/test/pleroma/instances_test.exs
@@ -7,73 +7,40 @@ defmodule Pleroma.InstancesTest do
use Pleroma.DataCase
- setup_all do: clear_config([:instance, :federation_reachability_timeout_days], 1)
-
describe "reachable?/1" do
test "returns `true` for host / url with unknown reachability status" do
assert Instances.reachable?("unknown.site")
assert Instances.reachable?("http://unknown.site")
end
-
- test "returns `false` for host / url marked unreachable for at least `reachability_datetime_threshold()`" do
- host = "consistently-unreachable.name"
- Instances.set_consistently_unreachable(host)
-
- refute Instances.reachable?(host)
- refute Instances.reachable?("http://#{host}/path")
- end
-
- test "returns `true` for host / url marked unreachable for less than `reachability_datetime_threshold()`" do
- url = "http://eventually-unreachable.name/path"
-
- Instances.set_unreachable(url)
-
- assert Instances.reachable?(url)
- assert Instances.reachable?(URI.parse(url).host)
- end
-
- test "raises FunctionClauseError exception on non-binary input" do
- assert_raise FunctionClauseError, fn -> Instances.reachable?(nil) end
- assert_raise FunctionClauseError, fn -> Instances.reachable?(1) end
- end
end
describe "filter_reachable/1" do
setup do
- host = "consistently-unreachable.name"
- url1 = "http://eventually-unreachable.com/path"
- url2 = "http://domain.com/path"
+ unreachable_host = "consistently-unreachable.name"
+ reachable_host = "http://domain.com/path"
- Instances.set_consistently_unreachable(host)
- Instances.set_unreachable(url1)
+ Instances.set_unreachable(unreachable_host)
- result = Instances.filter_reachable([host, url1, url2, nil])
- %{result: result, url1: url1, url2: url2}
+ result = Instances.filter_reachable([unreachable_host, reachable_host, nil])
+ %{result: result, reachable_host: reachable_host, unreachable_host: unreachable_host}
end
- test "returns a map with keys containing 'not marked consistently unreachable' elements of supplied list",
- %{result: result, url1: url1, url2: url2} do
- assert is_map(result)
- assert Enum.sort([url1, url2]) == result |> Map.keys() |> Enum.sort()
+ test "returns a list of only reachable elements",
+ %{result: result, reachable_host: reachable_host} do
+ assert is_list(result)
+ assert [reachable_host] == result
end
- test "returns a map with `unreachable_since` values for keys",
- %{result: result, url1: url1, url2: url2} do
- assert is_map(result)
- assert %NaiveDateTime{} = result[url1]
- assert is_nil(result[url2])
- end
-
- test "returns an empty map for empty list or list containing no hosts / url" do
- assert %{} == Instances.filter_reachable([])
- assert %{} == Instances.filter_reachable([nil])
+ test "returns an empty list when provided no data" do
+ assert [] == Instances.filter_reachable([])
+ assert [] == Instances.filter_reachable([nil])
end
end
describe "set_reachable/1" do
test "sets unreachable url or host reachable" do
host = "domain.com"
- Instances.set_consistently_unreachable(host)
+ Instances.set_unreachable(host)
refute Instances.reachable?(host)
Instances.set_reachable(host)
@@ -102,23 +69,4 @@ defmodule Pleroma.InstancesTest do
assert {:error, _} = Instances.set_unreachable(1)
end
end
-
- describe "set_consistently_unreachable/1" do
- test "sets reachable url or host unreachable" do
- url = "http://domain.com?q="
- assert Instances.reachable?(url)
-
- Instances.set_consistently_unreachable(url)
- refute Instances.reachable?(url)
- end
-
- test "keeps unreachable url or host unreachable" do
- host = "site.name"
- Instances.set_consistently_unreachable(host)
- refute Instances.reachable?(host)
-
- Instances.set_consistently_unreachable(host)
- refute Instances.reachable?(host)
- end
- end
end
diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs
index 7ba5090e1..9afa34fa2 100644
--- a/test/pleroma/object/fetcher_test.exs
+++ b/test/pleroma/object/fetcher_test.exs
@@ -6,7 +6,6 @@ defmodule Pleroma.Object.FetcherTest do
use Pleroma.DataCase
alias Pleroma.Activity
- alias Pleroma.Instances
alias Pleroma.Object
alias Pleroma.Object.Fetcher
alias Pleroma.Web.ActivityPub.ObjectValidator
@@ -250,17 +249,6 @@ defmodule Pleroma.Object.FetcherTest do
result = Fetcher.fetch_object_from_id("https://example.com/objects/no-content-type")
assert {:fetch, {:error, nil}} = result
end
-
- test "it resets instance reachability on successful fetch" do
- id = "http://mastodon.example.org/@admin/99541947525187367"
- Instances.set_consistently_unreachable(id)
- refute Instances.reachable?(id)
-
- {:ok, _object} =
- Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
-
- assert Instances.reachable?(id)
- end
end
describe "implementation quirks" do
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index 46b3d5f0d..d9be82e64 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
alias Pleroma.Activity
alias Pleroma.Delivery
- alias Pleroma.Instances
alias Pleroma.Object
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
@@ -601,23 +600,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert Activity.get_by_ap_id(data["id"])
end
- test "it clears `unreachable` federation status of the sender", %{conn: conn} do
- data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
-
- sender_url = data["actor"]
- Instances.set_consistently_unreachable(sender_url)
- refute Instances.reachable?(sender_url)
-
- conn =
- conn
- |> assign(:valid_signature, true)
- |> put_req_header("content-type", "application/activity+json")
- |> post("/inbox", data)
-
- assert "ok" == json_response(conn, 200)
- assert Instances.reachable?(sender_url)
- end
-
test "accept follow activity", %{conn: conn} do
clear_config([:instance, :federating], true)
relay = Relay.get_actor()
@@ -1108,24 +1090,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert response(conn, 200) =~ note_object.data["content"]
end
- test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
- user = insert(:user)
- data = Map.put(data, "bcc", [user.ap_id])
-
- sender_host = URI.parse(data["actor"]).host
- Instances.set_consistently_unreachable(sender_host)
- refute Instances.reachable?(sender_host)
-
- conn =
- conn
- |> assign(:valid_signature, true)
- |> put_req_header("content-type", "application/activity+json")
- |> post("/users/#{user.nickname}/inbox", data)
-
- assert "ok" == json_response(conn, 200)
- assert Instances.reachable?(sender_host)
- end
-
test "it removes all follower collections but actor's", %{conn: conn} do
[actor, recipient] = insert_pair(:user)
diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs
index 99ed42877..a6f25c9a7 100644
--- a/test/pleroma/web/activity_pub/publisher_test.exs
+++ b/test/pleroma/web/activity_pub/publisher_test.exs
@@ -6,13 +6,11 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.Web.ConnCase
- import ExUnit.CaptureLog
import Pleroma.Factory
import Tesla.Mock
import Mock
alias Pleroma.Activity
- alias Pleroma.Instances
alias Pleroma.Object
alias Pleroma.Tests.ObanHelpers
alias Pleroma.Web.ActivityPub.Publisher
@@ -167,115 +165,6 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
})
|> Publisher.publish_one()
end
-
- test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
- Instances,
- [:passthrough],
- [] do
- _actor = insert(:user)
- inbox = "http://200.site/users/nick1/inbox"
- activity = insert(:note_activity)
-
- assert {:ok, _} =
- Publisher.prepare_one(%{
- inbox: inbox,
- activity_id: activity.id,
- unreachable_since: NaiveDateTime.utc_now() |> NaiveDateTime.to_string()
- })
- |> Publisher.publish_one()
-
- assert called(Instances.set_reachable(inbox))
- end
-
- test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
- Instances,
- [:passthrough],
- [] do
- _actor = insert(:user)
- inbox = "http://200.site/users/nick1/inbox"
- activity = insert(:note_activity)
-
- assert {:ok, _} =
- Publisher.prepare_one(%{
- inbox: inbox,
- activity_id: activity.id,
- unreachable_since: nil
- })
- |> Publisher.publish_one()
-
- refute called(Instances.set_reachable(inbox))
- end
-
- test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
- Instances,
- [:passthrough],
- [] do
- _actor = insert(:user)
- inbox = "http://404.site/users/nick1/inbox"
- activity = insert(:note_activity)
-
- assert {:cancel, _} =
- Publisher.prepare_one(%{inbox: inbox, activity_id: activity.id})
- |> Publisher.publish_one()
-
- assert called(Instances.set_unreachable(inbox))
- end
-
- test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
- Instances,
- [:passthrough],
- [] do
- _actor = insert(:user)
- inbox = "http://connrefused.site/users/nick1/inbox"
- activity = insert(:note_activity)
-
- assert capture_log(fn ->
- assert {:error, _} =
- Publisher.prepare_one(%{
- inbox: inbox,
- activity_id: activity.id
- })
- |> Publisher.publish_one()
- end) =~ "connrefused"
-
- assert called(Instances.set_unreachable(inbox))
- end
-
- test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable",
- Instances,
- [:passthrough],
- [] do
- _actor = insert(:user)
- inbox = "http://200.site/users/nick1/inbox"
- activity = insert(:note_activity)
-
- assert {:ok, _} =
- Publisher.prepare_one(%{inbox: inbox, activity_id: activity.id})
- |> Publisher.publish_one()
-
- refute called(Instances.set_unreachable(inbox))
- end
-
- test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`",
- Instances,
- [:passthrough],
- [] do
- _actor = insert(:user)
- inbox = "http://connrefused.site/users/nick1/inbox"
- activity = insert(:note_activity)
-
- assert capture_log(fn ->
- assert {:error, _} =
- Publisher.prepare_one(%{
- inbox: inbox,
- activity_id: activity.id,
- unreachable_since: NaiveDateTime.utc_now() |> NaiveDateTime.to_string()
- })
- |> Publisher.publish_one()
- end) =~ "connrefused"
-
- refute called(Instances.set_unreachable(inbox))
- end
end
describe "publish/2" do
diff --git a/test/pleroma/web/federator_test.exs b/test/pleroma/web/federator_test.exs
index 4a398f239..16fe1066a 100644
--- a/test/pleroma/web/federator_test.exs
+++ b/test/pleroma/web/federator_test.exs
@@ -126,22 +126,17 @@ defmodule Pleroma.Web.FederatorTest do
inbox: inbox2
})
- dt = NaiveDateTime.utc_now()
- Instances.set_unreachable(inbox1, dt)
-
- Instances.set_consistently_unreachable(URI.parse(inbox2).host)
+ Instances.set_unreachable(URI.parse(inbox2).host)
{:ok, _activity} =
CommonAPI.post(user, %{status: "HI @nick1@domain.com, @nick2@domain2.com!"})
- expected_dt = NaiveDateTime.to_iso8601(dt)
-
ObanHelpers.perform(all_enqueued(worker: PublisherWorker))
assert ObanHelpers.member?(
%{
"op" => "publish_one",
- "params" => %{"inbox" => inbox1, "unreachable_since" => expected_dt}
+ "params" => %{"inbox" => inbox1}
},
all_enqueued(worker: PublisherWorker)
)
diff --git a/test/pleroma/web/pleroma_api/controllers/instances_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/instances_controller_test.exs
index 0d4951a73..702c05504 100644
--- a/test/pleroma/web/pleroma_api/controllers/instances_controller_test.exs
+++ b/test/pleroma/web/pleroma_api/controllers/instances_controller_test.exs
@@ -7,16 +7,11 @@ defmodule Pleroma.Web.PleromaApi.InstancesControllerTest do
alias Pleroma.Instances
- setup_all do: clear_config([:instance, :federation_reachability_timeout_days], 1)
-
setup do
constant = "http://consistently-unreachable.name/"
- eventual = "http://eventually-unreachable.com/path"
{:ok, %Pleroma.Instances.Instance{unreachable_since: constant_unreachable}} =
- Instances.set_consistently_unreachable(constant)
-
- _eventual_unreachable = Instances.set_unreachable(eventual)
+ Instances.set_unreachable(constant)
%{constant_unreachable: constant_unreachable, constant: constant}
end
diff --git a/test/pleroma/workers/cron/schedule_reachability_worker_test.exs b/test/pleroma/workers/cron/schedule_reachability_worker_test.exs
new file mode 100644
index 000000000..310c2e61a
--- /dev/null
+++ b/test/pleroma/workers/cron/schedule_reachability_worker_test.exs
@@ -0,0 +1,52 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.Cron.ScheduleReachabilityWorkerTest do
+ use Pleroma.DataCase, async: true
+ use Oban.Testing, repo: Pleroma.Repo
+
+ alias Pleroma.Instances
+ alias Pleroma.Workers.Cron.ScheduleReachabilityWorker
+
+ describe "perform/1" do
+ test "schedules reachability checks for unreachable servers" do
+ # Mark some servers as unreachable
+ Instances.set_unreachable("https://example.com")
+ Instances.set_unreachable("https://test.com")
+ Instances.set_unreachable("https://another.com")
+
+ # Verify they are marked as unreachable
+ refute Instances.reachable?("https://example.com")
+ refute Instances.reachable?("https://test.com")
+ refute Instances.reachable?("https://another.com")
+
+ # Run the worker
+ assert :ok = ScheduleReachabilityWorker.perform(%Oban.Job{})
+
+ # Verify ReachabilityWorker jobs were scheduled for each server
+ # Note: domains in get_unreachable/0 are without the https:// prefix
+ assert_enqueued(
+ worker: Pleroma.Workers.ReachabilityWorker,
+ args: %{"domain" => "example.com"}
+ )
+
+ assert_enqueued(
+ worker: Pleroma.Workers.ReachabilityWorker,
+ args: %{"domain" => "test.com"}
+ )
+
+ assert_enqueued(
+ worker: Pleroma.Workers.ReachabilityWorker,
+ args: %{"domain" => "another.com"}
+ )
+ end
+
+ test "handles empty list of unreachable servers" do
+ # Ensure no servers are marked as unreachable
+ assert [] = Instances.get_unreachable()
+ assert :ok = ScheduleReachabilityWorker.perform(%Oban.Job{})
+ refute_enqueued(worker: Pleroma.Workers.ReachabilityWorker)
+ end
+ end
+end
diff --git a/test/pleroma/workers/publisher_worker_test.exs b/test/pleroma/workers/publisher_worker_test.exs
index 13372bf49..ca432d9bf 100644
--- a/test/pleroma/workers/publisher_worker_test.exs
+++ b/test/pleroma/workers/publisher_worker_test.exs
@@ -7,7 +7,9 @@ defmodule Pleroma.Workers.PublisherWorkerTest do
use Oban.Testing, repo: Pleroma.Repo
import Pleroma.Factory
+ import Mock
+ alias Pleroma.Instances
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
@@ -37,4 +39,85 @@ defmodule Pleroma.Workers.PublisherWorkerTest do
assert {:ok, %Oban.Job{priority: 0}} = Federator.publish(post)
end
end
+
+ describe "Server reachability:" do
+ setup do
+ user = insert(:user)
+ remote_user = insert(:user, local: false, inbox: "https://example.com/inbox")
+ {:ok, _, _} = Pleroma.User.follow(remote_user, user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "Test post"})
+
+ %{
+ user: user,
+ remote_user: remote_user,
+ activity: activity
+ }
+ end
+
+ test "marks server as unreachable only on final failure", %{activity: activity} do
+ with_mock Pleroma.Web.Federator,
+ perform: fn :publish_one, _params -> {:error, :connection_error} end do
+ # First attempt
+ job = %Oban.Job{
+ args: %{
+ "op" => "publish_one",
+ "params" => %{
+ "inbox" => "https://example.com/inbox",
+ "activity_id" => activity.id
+ }
+ },
+ attempt: 1,
+ max_attempts: 5
+ }
+
+ assert {:error, :connection_error} = Pleroma.Workers.PublisherWorker.perform(job)
+ assert Instances.reachable?("https://example.com/inbox")
+
+ # Final attempt
+ job = %{job | attempt: 5}
+ assert {:error, :connection_error} = Pleroma.Workers.PublisherWorker.perform(job)
+ refute Instances.reachable?("https://example.com/inbox")
+ end
+ end
+
+ test "does not mark server as unreachable on successful publish", %{activity: activity} do
+ with_mock Pleroma.Web.Federator,
+ perform: fn :publish_one, _params -> {:ok, %{status: 200}} end do
+ job = %Oban.Job{
+ args: %{
+ "op" => "publish_one",
+ "params" => %{
+ "inbox" => "https://example.com/inbox",
+ "activity_id" => activity.id
+ }
+ },
+ attempt: 1,
+ max_attempts: 5
+ }
+
+ assert :ok = Pleroma.Workers.PublisherWorker.perform(job)
+ assert Instances.reachable?("https://example.com/inbox")
+ end
+ end
+
+ test "cancels job if server is unreachable", %{activity: activity} do
+ # First mark the server as unreachable
+ Instances.set_unreachable("https://example.com/inbox")
+ refute Instances.reachable?("https://example.com/inbox")
+
+ job = %Oban.Job{
+ args: %{
+ "op" => "publish_one",
+ "params" => %{
+ "inbox" => "https://example.com/inbox",
+ "activity_id" => activity.id
+ }
+ },
+ attempt: 1,
+ max_attempts: 5
+ }
+
+ assert {:cancel, :unreachable} = Pleroma.Workers.PublisherWorker.perform(job)
+ end
+ end
end
diff --git a/test/pleroma/workers/receiver_worker_test.exs b/test/pleroma/workers/receiver_worker_test.exs
index 4d53c44ed..7f4789f91 100644
--- a/test/pleroma/workers/receiver_worker_test.exs
+++ b/test/pleroma/workers/receiver_worker_test.exs
@@ -3,13 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.ReceiverWorkerTest do
- use Pleroma.DataCase
+ use Pleroma.DataCase, async: true
use Oban.Testing, repo: Pleroma.Repo
import Mock
import Pleroma.Factory
alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Federator
alias Pleroma.Workers.ReceiverWorker
@@ -243,4 +244,62 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
assert {:cancel, _} = ReceiverWorker.perform(oban_job)
end
+
+ describe "Server reachability:" do
+ setup do
+ user = insert(:user)
+ remote_user = insert(:user, local: false, ap_id: "https://example.com/users/remote")
+ {:ok, _, _} = Pleroma.User.follow(user, remote_user)
+ {:ok, activity} = CommonAPI.post(remote_user, %{status: "Test post"})
+
+ %{
+ user: user,
+ remote_user: remote_user,
+ activity: activity
+ }
+ end
+
+ test "schedules ReachabilityWorker if host is unreachable", %{activity: activity} do
+ with_mocks [
+ {Pleroma.Web.ActivityPub.Transmogrifier, [],
+ [handle_incoming: fn _ -> {:ok, activity} end]},
+ {Pleroma.Instances, [], [reachable?: fn _ -> false end]},
+ {Pleroma.Web.Federator, [], [perform: fn :incoming_ap_doc, _params -> {:ok, nil} end]}
+ ] do
+ job = %Oban.Job{
+ args: %{
+ "op" => "incoming_ap_doc",
+ "params" => activity.data
+ }
+ }
+
+ Pleroma.Workers.ReceiverWorker.perform(job)
+
+ assert_enqueued(
+ worker: Pleroma.Workers.ReachabilityWorker,
+ args: %{"domain" => "example.com"}
+ )
+ end
+ end
+
+ test "does not schedule ReachabilityWorker if host is reachable", %{activity: activity} do
+ with_mocks [
+ {Pleroma.Web.ActivityPub.Transmogrifier, [],
+ [handle_incoming: fn _ -> {:ok, activity} end]},
+ {Pleroma.Instances, [], [reachable?: fn _ -> true end]},
+ {Pleroma.Web.Federator, [], [perform: fn :incoming_ap_doc, _params -> {:ok, nil} end]}
+ ] do
+ job = %Oban.Job{
+ args: %{
+ "op" => "incoming_ap_doc",
+ "params" => activity.data
+ }
+ }
+
+ Pleroma.Workers.ReceiverWorker.perform(job)
+
+ refute_enqueued(worker: Pleroma.Workers.ReachabilityWorker)
+ end
+ end
+ end
end
diff --git a/test/pleroma/workers/remote_fetcher_worker_test.exs b/test/pleroma/workers/remote_fetcher_worker_test.exs
index 9caddb600..6eb6932cb 100644
--- a/test/pleroma/workers/remote_fetcher_worker_test.exs
+++ b/test/pleroma/workers/remote_fetcher_worker_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.RemoteFetcherWorkerTest do
- use Pleroma.DataCase
+ use Pleroma.DataCase, async: true
use Oban.Testing, repo: Pleroma.Repo
alias Pleroma.Workers.RemoteFetcherWorker