@@ -3,12 +3,22 @@ defmodule DNSCluster do
33 Simple DNS based cluster discovery.
44
55 A DNS query is made every `:interval` milliseconds to discover new ips.
6- Nodes will only be joined if their node basename matches the basename of the
7- current node. For example if `node()` is `myapp-123@fdaa:1:36c9:a7b:198:c4b1:73c6:1`,
8- a `Node.connect/1` attempt will be made against every IP returned by the DNS query,
9- but will only be successful if there is a node running on the remote host with the same
10- basename, for example `myapp-123@fdaa:1:36c9:a7b:198:c4b1:73c6:2`. Nodes running on
11- remote hosts, but with different basenames will fail to connect and will be ignored.
6+
7+ ## Default node discovery
8+ Nodes will only be joined if their node basename matches the basename of the current node.
9+ For example, if `node()` is `myapp-123@fdaa:1:36c9:a7b:198:c4b1:73c6:1`, it will try to connect
10+ to every IP from the DNS query with `Node.connect/1`. But this will only work if the remote node
11+ has the same basename, like `myapp-123@fdaa:1:36c9:a7b:198:c4b1:73c6:2`. If the remote node's
12+ basename is different, the nodes will not connect.
13+
14+ ## Specifying remote basenames
15+ If you want to connect to nodes with different basenames, use a tuple with the basename and query.
16+ For example, to connect to a node named `remote`, use `{"remote", "remote-app.internal"}`.
17+
18+ ## Multiple queries
19+ Sometimes you might want to cluster apps with different domain names. Just pass a list of queries
20+ for this. For instance: `["app-one.internal", "app-two.internal", {"other-basename", "other.internal"}]`.
21+ Remember, all nodes need to share the same secret cookie to connect successfully.
1222
1323 ## Examples
1424
@@ -56,7 +66,9 @@ defmodule DNSCluster do
5666 ## Options
5767
5868 * `:name` - the name of the cluster. Defaults to `DNSCluster`.
59- * `:query` - the required DNS query for node discovery, for example: `"myapp.internal"`.
69+ * `:query` - the required DNS query for node discovery, for example:
70+ `"myapp.internal"` or `["foo.internal", "bar.internal"]`. If the basename
71+ differs between nodes, a tuple of `{basename, query}` can be provided as well.
6072 The value `:ignore` can be used to ignore starting the DNSCluster.
6173 * `:interval` - the millisec interval between DNS queries. Defaults to `5000`.
6274 * `:connect_timeout` - the millisec timeout to allow discovered nodes to connect.
@@ -80,24 +92,26 @@ defmodule DNSCluster do
8092 { :ok , :ignore } ->
8193 :ignore
8294
83- { :ok , query } when is_binary ( query ) ->
84- warn_on_invalid_dist ( )
85- resolver = Keyword . get ( opts , :resolver , Resolver )
86-
87- state = % {
88- interval: Keyword . get ( opts , :interval , 5_000 ) ,
89- basename: resolver . basename ( node ( ) ) ,
90- query: query ,
91- log: Keyword . get ( opts , :log , false ) ,
92- poll_timer: nil ,
93- connect_timeout: Keyword . get ( opts , :connect_timeout , 10_000 ) ,
94- resolver: resolver
95- }
96-
97- { :ok , state , { :continue , :discover_ips } }
98-
99- { :ok , other } ->
100- raise ArgumentError , "expected :query to be a string, got: #{ inspect ( other ) } "
95+ { :ok , query } ->
96+ if valid_query? ( query ) do
97+ warn_on_invalid_dist ( )
98+ resolver = Keyword . get ( opts , :resolver , Resolver )
99+
100+ state = % {
101+ interval: Keyword . get ( opts , :interval , 5_000 ) ,
102+ basename: resolver . basename ( node ( ) ) ,
103+ query: List . wrap ( query ) ,
104+ log: Keyword . get ( opts , :log , false ) ,
105+ poll_timer: nil ,
106+ connect_timeout: Keyword . get ( opts , :connect_timeout , 10_000 ) ,
107+ resolver: resolver
108+ }
109+
110+ { :ok , state , { :continue , :discover_ips } }
111+ else
112+ raise ArgumentError ,
113+ "expected :query to be a string, {basename, query}, or list, got: #{ inspect ( query ) } "
114+ end
101115
102116 :error ->
103117 raise ArgumentError , "missing required :query option in #{ inspect ( opts ) } "
@@ -127,7 +141,7 @@ defmodule DNSCluster do
127141
128142 _results =
129143 ips
130- |> Enum . map ( fn ip -> "#{ state . basename } @#{ ip } " end )
144+ |> Enum . map ( fn { basename , ip } -> "#{ basename } @#{ ip } " end )
131145 |> Enum . filter ( fn node_name -> ! Enum . member? ( node_names , node_name ) end )
132146 |> Task . async_stream (
133147 fn new_name ->
@@ -151,11 +165,38 @@ defmodule DNSCluster do
151165 % { state | poll_timer: Process . send_after ( self ( ) , :discover_ips , state . interval ) }
152166 end
153167
154- defp discover_ips ( % { resolver: resolver , query: query } ) do
168+ defp discover_ips ( % { resolver: resolver , query: queries } = state ) do
155169 [ :a , :aaaa ]
156- |> Enum . flat_map ( & resolver . lookup ( query , & 1 ) )
170+ |> Enum . flat_map ( fn type ->
171+ Enum . flat_map ( queries , fn query ->
172+ { basename , query } =
173+ case query do
174+ { basename , query } ->
175+ # use the user-specified basename
176+ { basename , query }
177+
178+ query when is_binary ( query ) ->
179+ # no basename specified, use host basename
180+ { state . basename , query }
181+ end
182+
183+ for addr <- resolver . lookup ( query , type ) do
184+ { basename , addr }
185+ end
186+ end )
187+ end )
157188 |> Enum . uniq ( )
158- |> Enum . map ( & to_string ( :inet . ntoa ( & 1 ) ) )
189+ |> Enum . map ( fn { basename , addr } -> { basename , to_string ( :inet . ntoa ( addr ) ) } end )
190+ end
191+
192+ defp valid_query? ( list ) do
193+ list
194+ |> List . wrap ( )
195+ |> Enum . all? ( fn
196+ string when is_binary ( string ) -> true
197+ { basename , query } when is_binary ( basename ) and is_binary ( query ) -> true
198+ _ -> false
199+ end )
159200 end
160201
161202 defp warn_on_invalid_dist do
0 commit comments