farside/update.exs
Ben Busby 8ee4f308a4
Prevent same instance from being selected twice in a row
Introduces a new db key "<service>-previous" to track which instance was
last selected for a particular service. This allows for filtering the
list of available instances to exclude the instance that was last
picked, to ensure a (slightly) more even distribution of traffic.
There's still the possiblity of the following scenario, however:

:service instances > 2

/:service request #1 -> instance #1
/:service request #2 -> instance #2
/:service request #3 -> instance #1
/:service request #4 -> instance #2

where there are many ignored instances for a particular service. One
possible solution would be to implement the "<service>-previous" value
to be a list, rather than a single value, and push to that list until
only one element is left in the original "instance" array after
filtering, and then delete the "<service>-previous" key.
2021-11-10 12:19:37 -07:00

88 lines
2.3 KiB
Elixir

defmodule Instances do
@fallback_suffix Application.fetch_env!(:farside, :fallback_suffix)
@update_file Application.fetch_env!(:farside, :update_file)
@services_json Application.fetch_env!(:farside, :services_json)
@service_prefix Application.fetch_env!(:farside, :service_prefix)
def init() do
File.rename(@update_file, "#{@update_file}-prev")
update
end
def request(url) do
cond do
System.get_env("FARSIDE_TEST") ->
:good
true ->
case HTTPoison.get(url) do
{:ok, %HTTPoison.Response{status_code: 200}} ->
# TODO: Add validation of results, not just status code
:good
_ ->
:bad
end
end
end
def update do
{:ok, file} = File.read(@services_json)
{:ok, json} = Poison.decode(file, as: [%Service{}])
# Loop through all instances and check each for availability
for service <- json do
IO.puts "======== " <> service.type
result = Enum.filter(service.instances, fn(instance_url) ->
IO.puts " " <> instance_url
request(instance_url <> service.test_url) == :good
end)
add_to_redis(service, result)
log_results(service.type, result)
end
end
def add_to_redis(service, instances) do
# Remove previous list of instances
Redix.command(:redix, [
"DEL",
"#{@service_prefix}#{service.type}"
])
# Update with new list of available instances
Redix.command(:redix, [
"LPUSH",
"#{@service_prefix}#{service.type}"
] ++ instances)
# Set fallback to one of the available instances,
# or the default instance if all are "down"
if Enum.count(instances) > 0 do
Redix.command(:redix, [
"SET",
"#{service.type}#{@fallback_suffix}",
Enum.random(instances)
])
else
Redix.command(:redix, [
"SET",
"#{service.type}#{@fallback_suffix}",
service.fallback
])
end
end
def log_results(service_name, results) do
{:ok, file} = File.open(@update_file, [:append, {:delayed_write, 100, 20}])
IO.write(file, "#{service_name}: #{inspect(results)}\n")
File.close(file)
end
end
Instances.init()
# Add UTC time of last update
Redix.command(:redix, [
"SET",
"last_updated",
Calendar.strftime(DateTime.utc_now(), "%c")
])