From 83b7f6338802372ed3893ad73db0d3645d4a67f3 Mon Sep 17 00:00:00 2001 From: Dmitriy Pertsev Date: Fri, 29 Mar 2024 23:12:45 +0300 Subject: [PATCH 1/2] Elixir 1.12+ compatibility OTP 24 compatibility :inet_res.getbyname instead of :inet_res.lookup in resolver --- .credo.exs | 2 +- .formatter.exs | 25 +++++++++++--- .github/workflows/ci.yml | 59 +++++++++++++++++++++++++++------ .gitignore | 3 ++ README.md | 23 ++++++++++--- lib/dns_srv_cluster/app.ex | 9 +++-- lib/dns_srv_cluster/resolver.ex | 17 +++++++--- lib/dns_srv_cluster/worker.ex | 9 +++-- mix.exs | 19 ++++++++--- 9 files changed, 130 insertions(+), 36 deletions(-) diff --git a/.credo.exs b/.credo.exs index 00cafc2..baa1ea2 100644 --- a/.credo.exs +++ b/.credo.exs @@ -121,7 +121,7 @@ # {Credo.Check.Refactor.Apply, []}, {Credo.Check.Refactor.CondStatements, []}, - {Credo.Check.Refactor.CyclomaticComplexity, [max_complexity: 10]}, + {Credo.Check.Refactor.CyclomaticComplexity, []}, {Credo.Check.Refactor.FilterCount, []}, {Credo.Check.Refactor.FilterFilter, []}, {Credo.Check.Refactor.FunctionArity, []}, diff --git a/.formatter.exs b/.formatter.exs index 015debb..853a49d 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,5 +1,22 @@ # Used by "mix format" -[ - plugins: [Styler], - inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] -] + +defmodule LocalFormat do + @moduledoc false + def format(styler_compat) when styler_compat in [:gt, :eq] do + [ + plugins: [Styler], + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] + ] + end + + def format(_) do + [ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] + ] + end +end + +elixir_version = System.version() +styler_compat = Version.compare(elixir_version, "1.14.0") + +LocalFormat.format(styler_compat) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1bf052f..f0b9cae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,14 +11,51 @@ permissions: jobs: test_linux_elixir_latest: - name: ${{ matrix.os }}, Erlang/OTP ${{ matrix.otp_version }} Elixir ${{ matrix.elixir_version }} + name: ${{ matrix.os }}, Erlang/OTP ${{ matrix.otp-version }} Elixir ${{ matrix.elixir-version }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: [ubuntu-latest] - elixir_version: ['1'] - otp_version: ['26'] + include: + - os: ubuntu-20.04 + elixir-version: 1.12 + otp-version: 24 + - os: ubuntu-20.04 + elixir-version: 1.13 + otp-version: 24 + - os: ubuntu-20.04 + elixir-version: 1.13 + otp-version: 25 + - os: ubuntu-20.04 + elixir-version: 1.14 + otp-version: 24 + - os: ubuntu-20.04 + elixir-version: 1.14 + otp-version: 25 + - os: ubuntu-22.04 + elixir-version: 1.14 + otp-version: 26 + - os: ubuntu-20.04 + elixir-version: 1.15 + otp-version: 24 + - os: ubuntu-20.04 + elixir-version: 1.15 + otp-version: 25 + - os: ubuntu-22.04 + elixir-version: 1.15 + otp-version: 26 + - os: ubuntu-20.04 + elixir-version: 1.16 + otp-version: 24 + - os: ubuntu-20.04 + elixir-version: 1.16 + otp-version: 25 + - os: ubuntu-20.04 + elixir-version: 1.16 + otp-version: 26 + - os: ubuntu-22.04 + elixir-version: 1.16 + otp-version: 26 env: MIX_ENV: test GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -27,19 +64,21 @@ jobs: - name: Set up Elixir uses: erlef/setup-beam@v1 with: - elixir-version: ${{ matrix.elixir_version }} - otp-version: ${{ matrix.otp_version }} + elixir-version: ${{ matrix.elixir-version }} + otp-version: ${{ matrix.otp-version }} - name: Restore dependencies cache uses: actions/cache@v4 with: path: deps - key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} - restore-keys: ${{ runner.os }}-mix- + key: ${{ runner.os }}-${{ matrix.elixir-version }}-${{ matrix.otp-version}}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-${{ matrix.elixir-version}}-${{ matrix.otp-version }}-mix- - name: Install dependencies run: mix deps.get - name: Run mix format run: mix format --check-formatted - - name: Run coverage export - run: mix coveralls.github + if: startsWith(matrix.elixir-version, '1.14') || startsWith(matrix.elixir-version, '1.15') || startsWith(matrix.elixir-version, '1.16') - name: Run tests run: mix test + - name: Run coverage export + run: mix coveralls.github + if: startsWith(matrix.elixir-version, '1.16') && startsWith(matrix.otp-version, '26') diff --git a/.gitignore b/.gitignore index 8e999bc..a2b3697 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ dns_srv_cluster-*.tar # VSCode /.vscode/ + +# ASDF +.tool-versions diff --git a/README.md b/README.md index 5aa5e0d..9f0f4f8 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ Elixir clustering with DNS SRV records. --- +[![CI](https://github.com/pertsevds/dns_srv_cluster/actions/workflows/ci.yml/badge.svg)](https://github.com/pertsevds/dns_srv_cluster/actions/workflows/ci.yml) [![Coverage Status](https://coveralls.io/repos/github/pertsevds/dns_srv_cluster/badge.svg?branch=main)](https://coveralls.io/github/pertsevds/dns_srv_cluster?branch=main) ## Installation @@ -21,10 +22,10 @@ end Add to your DNS zone your SRV record (https://en.wikipedia.org/wiki/SRV_record): ```sh -_app._tcp.yourdomain.com. 86400 IN SRV 0 10 1234 node1.yourdomain.com. -_app._tcp.yourdomain.com. 86400 IN SRV 0 10 1234 node2.yourdomain.com. -_app._tcp.yourdomain.com. 86400 IN SRV 0 10 1234 node3.yourdomain.com. -_app._tcp.yourdomain.com. 86400 IN SRV 0 10 1234 node4.yourdomain.com. +_app._tcp.yourdomain.com. 60 IN SRV 0 10 1234 node1.yourdomain.com. +_app._tcp.yourdomain.com. 60 IN SRV 0 10 1234 node2.yourdomain.com. +_app._tcp.yourdomain.com. 60 IN SRV 0 10 1234 node3.yourdomain.com. +_app._tcp.yourdomain.com. 60 IN SRV 0 10 1234 node4.yourdomain.com. ``` Add to your config files (`config/prod.exs`, `config/dev.exs`): @@ -49,7 +50,7 @@ either in your deployment platform or inside `rel/env.sh.eex`: export RELEASE_COOKIE="my-app-cookie" ``` -## All configuration options +## Configuration options * `query` - your DNS SRV record, for example: "_app._tcp.yourdomain.com". * `interval` - the millisec interval between DNS queries. Defaults to `5_000`. @@ -80,4 +81,16 @@ children = [ {:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one) ``` +## Support Matrix + +Tests automatically run against a matrix of OTP and Elixir Versions, see the [ci.yml](https://github.com/pertsevds/dns_srv_cluster/tree/main/.github/workflows/ci.yml) for details. + +| OTP \ Elixir | 1.12 | 1.13 | 1.14 | 1.15 | 1.16 | +|:------------:|:----:|:----:|:----:|:----:|:----:| +| 24 | ✅ | ✅ | ✅ | ✅ | ✅ | +| 25 | N/A | ✅ | ✅ | ✅ | ✅ | +| 26 | N/A | N/A | ✅ | ✅ | ✅ | + +## Documentation + Documentation can be found at . diff --git a/lib/dns_srv_cluster/app.ex b/lib/dns_srv_cluster/app.ex index 7e1eb62..9649858 100644 --- a/lib/dns_srv_cluster/app.ex +++ b/lib/dns_srv_cluster/app.ex @@ -56,11 +56,10 @@ defmodule DNSSRVCluster.App do case query do {:ok, query} -> - child_spec = - [ - {DNSSRVCluster.Worker, - query: query, interval: interval(), connect_timeout: connect_timeout(), resolver: resolver()} - ] + child_spec = [ + {DNSSRVCluster.Worker, + query: query, interval: interval(), connect_timeout: connect_timeout(), resolver: resolver()} + ] opts = [strategy: :one_for_one, name: DNSSRVCluster.Supervisor] Supervisor.start_link(child_spec, opts) diff --git a/lib/dns_srv_cluster/resolver.ex b/lib/dns_srv_cluster/resolver.ex index cea63cc..78ab9e5 100644 --- a/lib/dns_srv_cluster/resolver.ex +++ b/lib/dns_srv_cluster/resolver.ex @@ -1,5 +1,9 @@ defmodule DNSSRVCluster.Resolver do @moduledoc false + require Logger + require Record + + Record.defrecord(:hostent, Record.extract(:hostent, from_lib: "kernel/include/inet.hrl")) # Public @@ -20,11 +24,16 @@ defmodule DNSSRVCluster.Resolver do def list_connected_nodes, do: Node.list(:visible) def lookup(query, type) when is_binary(query) and type in [:srv] do - :inet_res.lookup(~c"#{query}", :in, type) + case :inet_res.getbyname(~c"#{query}", type) do + {:ok, hostent(h_addr_list: addr_list)} -> + addr_list + + {:error, _} -> + Logger.warning("inet_res.getbyname for query \"#{query}\" with type \"#{type}\"failed.") + [] + end end @spec my_node() :: atom() - def my_node do - node() - end + def my_node, do: node() end diff --git a/lib/dns_srv_cluster/worker.ex b/lib/dns_srv_cluster/worker.ex index ee37d6d..76723ce 100644 --- a/lib/dns_srv_cluster/worker.ex +++ b/lib/dns_srv_cluster/worker.ex @@ -98,10 +98,13 @@ defmodule DNSSRVCluster.Worker do defp warn_on_invalid_dist do release? = is_binary(System.get_env("RELEASE_NAME")) - %{started: started} = net_state = :net_kernel.get_state() + net_state = if function_exported?(:net_kernel, :get_state, 0), do: :net_kernel.get_state() cond do - started == :no and release? -> + !net_state -> + :ok + + net_state.started == :no and release? -> Logger.warning(""" Node not running in distributed mode. Ensure the following exports are set in your rel/env.sh.eex file: @@ -109,7 +112,7 @@ defmodule DNSSRVCluster.Worker do export RELEASE_NODE="${RELEASE_NODE:-"<%= @release.name %>"}" """) - started == :no or (!release? and started != :no and net_state[:name_domain] != :longnames) -> + net_state.started == :no or (!release? and net_state.started != :no and net_state[:name_domain] != :longnames) -> Logger.warning(""" Node not running in distributed mode. When running outside of a release, you must start net_kernel manually with longnames. diff --git a/mix.exs b/mix.exs index 41a0f2d..54efde6 100644 --- a/mix.exs +++ b/mix.exs @@ -6,10 +6,13 @@ defmodule DNSSRVCluster.MixProject do @maintainer "Dmitriy Pertsev" def project do + elixir_version = System.version() + styler_compat = Version.compare(elixir_version, "1.14.0") + [ app: :dns_srv_cluster, version: @version, - elixir: "~> 1.15", + elixir: "~> 1.12", start_permanent: Mix.env() == :prod, package: package(), aliases: aliases(), @@ -21,7 +24,7 @@ defmodule DNSSRVCluster.MixProject do "coveralls.html": :test ], docs: docs(), - deps: deps(), + deps: deps(styler_compat), source_url: @scm_url, homepage_url: @scm_url, description: "Elixir clustering with DNS SRV records" @@ -46,12 +49,20 @@ defmodule DNSSRVCluster.MixProject do end # Run "mix help deps" to learn about dependencies. - defp deps do + defp deps(styler_compat) when styler_compat in [:gt, :eq] do [ + {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:excoveralls, "~> 0.10", only: :test, runtime: false}, {:ex_doc, "~> 0.30", only: :dev, runtime: false}, + {:styler, "~> 0.11", only: [:dev, :test], runtime: false} + ] + end + + defp deps(_) do + [ {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, - {:styler, "~> 0.9", only: [:dev, :test], runtime: false} + {:excoveralls, "~> 0.10", only: :test, runtime: false}, + {:ex_doc, "~> 0.30", only: :dev, runtime: false} ] end From 319516bd14100f5d81d7bb10d910c5f0eb254c2f Mon Sep 17 00:00:00 2001 From: Dmitriy Pertsev Date: Fri, 29 Mar 2024 23:29:13 +0300 Subject: [PATCH 2/2] fix --- lib/dns_srv_cluster/resolver.ex | 2 +- lib/dns_srv_cluster/worker.ex | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/dns_srv_cluster/resolver.ex b/lib/dns_srv_cluster/resolver.ex index 78ab9e5..6173f93 100644 --- a/lib/dns_srv_cluster/resolver.ex +++ b/lib/dns_srv_cluster/resolver.ex @@ -29,7 +29,7 @@ defmodule DNSSRVCluster.Resolver do addr_list {:error, _} -> - Logger.warning("inet_res.getbyname for query \"#{query}\" with type \"#{type}\"failed.") + Logger.warning(~s(inet_res.getbyname for query "#{query}" with type "#{type}"failed.)) [] end end diff --git a/lib/dns_srv_cluster/worker.ex b/lib/dns_srv_cluster/worker.ex index 76723ce..258bfc3 100644 --- a/lib/dns_srv_cluster/worker.ex +++ b/lib/dns_srv_cluster/worker.ex @@ -96,6 +96,7 @@ defmodule DNSSRVCluster.Worker do schedule_next_poll(state) end + # credo:disable-for-next-line defp warn_on_invalid_dist do release? = is_binary(System.get_env("RELEASE_NAME")) net_state = if function_exported?(:net_kernel, :get_state, 0), do: :net_kernel.get_state()