%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(net_adm).
-export([host_file/0,
localhost/0,
names/0, names/1,
ping_list/1,
world/0,world/1,
world_list/1, world_list/2,
dns_hostname/1,
ping/1]).
%%------------------------------------------------------------------------
-type verbosity() :: 'silent' | 'verbose'.
%%------------------------------------------------------------------------
%% Try to read .hosts.erlang file in
%% 1. cwd , 2. $HOME 3. init:root_dir()
-spec host_file() -> Hosts | {error, Reason} when
Hosts :: [Host :: atom()],
%% Copied from file:path_consult/2:
Reason :: file:posix() | badarg | terminated | system_limit
| {Line :: integer(), Mod :: module(), Term :: term()}.
host_file() ->
Home = case init:get_argument(home) of
{ok, [[H]]} -> [H];
_ -> []
end,
case file:path_consult(["."] ++ Home ++ [code:root_dir()], ".hosts.erlang") of
{ok, Hosts, _} -> Hosts;
Error -> Error
end.
%% Check whether a node is up or down
%% side effect: set up a connection to Node if there not yet is one.
-spec ping(Node) -> pong | pang when
Node :: atom().
ping(Node) when is_atom(Node) ->
case catch gen:call({net_kernel, Node},
'$gen_call',
{is_auth, node()},
infinity) of
{ok, yes} -> pong;
_ ->
erlang:disconnect_node(Node),
pang
end.
-spec localhost() -> Name when
Name :: string().
localhost() ->
{ok, Host} = inet:gethostname(),
case inet_db:res_option(domain) of
"" -> Host;
Domain -> Host ++ "." ++ Domain
end.
-spec names() -> {ok, [{Name, Port}]} | {error, Reason} when
Name :: string(),
Port :: non_neg_integer(),
Reason :: address | file:posix().
names() ->
names(localhost()).
-spec names(Host) -> {ok, [{Name, Port}]} | {error, Reason} when
Host :: atom() | string() | inet:ip_address(),
Name :: string(),
Port :: non_neg_integer(),
Reason :: address | file:posix().
names(Hostname) ->
erl_epmd:names(Hostname).
-spec dns_hostname(Host) -> {ok, Name} | {error, Host} when
Host :: atom() | string(),
Name :: string().
dns_hostname(Hostname) ->
case inet:gethostbyname(Hostname) of
{ok,{hostent, Name, _ , _Af, _Size, _Addr}} ->
{ok, Name};
_ ->
{error, Hostname}
end.
%% A common situation in "life" is to have a configuration file with a list
%% of nodes, and then at startup, all nodes in the list are ping'ed
%% this can lead to no end of troubles if two disconnected nodes
%% simultaneously ping each other.
%% Use this function in order to do it safely.
%% It assumes a working global.erl which ensures a fully
%% connected network.
%% Had the erlang runtime system been able to fully cope with
%% the possibility of two simultaneous (unix) connects, this function would
%% merley be lists:map({net_adm, ping}, [], Nodelist).
%% It is also assumed, that the same (identical) Nodelist is given to all
%% nodes which are to perform this call (possibly simultaneously).
%% Even this code has a flaw, and that is the case where two
%% nodes simultaneously and without *any* other already
%% running nodes execute this code. :-(
-spec ping_list([atom()]) -> [atom()].
ping_list(Nodelist) ->
ok = net_kernel:monitor_nodes(true),
Sofar = ping_first(Nodelist, nodes()),
collect_new(Sofar, Nodelist).
ping_first([], _S) ->
[];
ping_first([Node|Nodes], S) ->
case lists:member(Node, S) of
true -> [Node | ping_first(Nodes, S)];
false ->
case ping(Node) of
pong -> [Node];
pang -> ping_first(Nodes, S)
end
end.
collect_new(Sofar, Nodelist) ->
receive
{nodeup, Node} ->
case lists:member(Node, Nodelist) of
true ->
collect_new(Sofar, Nodelist);
false ->
collect_new([Node | Sofar], Nodelist)
end
after 3000 ->
ok = net_kernel:monitor_nodes(false),
Sofar
end.
%% This function polls a set of hosts according to a file called
%% .hosts.erlang that need to reside either in the current directory
%% or in your home directory. (The current directory is tried first.)
%% world() returns a list of all nodes on the network that can be
%% found (including ourselves). Note: the $HOME variable is inspected.
%%
%% Added possibility to supply a list of hosts instead of reading
%% the .hosts.erlang file. 971016 [email protected]
%% e.g.
%% net_adm:world_list(['elrond.du.etx.ericsson.se', 'thorin.du.etx.ericsson.se']).
-spec world() -> [node()].
world() ->
world(silent).
-spec world(Arg) -> [node()] when
Arg :: verbosity().
world(Verbose) ->
case net_adm:host_file() of
{error,R} -> exit({error, R});
Hosts -> expand_hosts(Hosts, Verbose)
end.
-spec world_list(Hosts) -> [node()] when
Hosts :: [atom()].
world_list(Hosts) when is_list(Hosts) ->
expand_hosts(Hosts, silent).
-spec world_list(Hosts, Arg) -> [node()] when
Hosts :: [atom()],
Arg :: verbosity().
world_list(Hosts, Verbose) when is_list(Hosts) ->
expand_hosts(Hosts, Verbose).
expand_hosts(Hosts, Verbose) ->
lists:flatten(collect_nodes(Hosts, Verbose)).
collect_nodes([], _) -> [];
collect_nodes([Host|Tail], Verbose) ->
case collect_host_nodes(Host, Verbose) of
nil ->
collect_nodes(Tail, Verbose);
L ->
[L|collect_nodes(Tail, Verbose)]
end.
collect_host_nodes(Host, Verbose) ->
case names(Host) of
{ok, Namelist} ->
do_ping(Namelist, atom_to_list(Host), Verbose);
_ ->
nil
end.
do_ping(Names, Host0, Verbose) ->
case longshort(Host0) of
ignored -> [];
Host -> do_ping_1(Names, Host, Verbose)
end.
do_ping_1([], _Host, _Verbose) ->
[];
do_ping_1([{Name, _} | Rest], Host, Verbose) ->
Node = list_to_atom(Name ++ "@" ++ longshort(Host)),
verbose(Verbose, "Pinging ~w -> ", [Node]),
Result = ping(Node),
verbose(Verbose, "~p\n", [Result]),
case Result of
pong ->
[Node | do_ping_1(Rest, Host, Verbose)];
pang ->
do_ping_1(Rest, Host, Verbose)
end.
verbose(verbose, Format, Args) ->
io:format(Format, Args);
verbose(_, _, _) ->
ok.
longshort(Host) ->
case net_kernel:longnames() of
false -> uptodot(Host);
true -> Host;
ignored -> ignored
end.
uptodot([$.|_]) -> [];
uptodot([])-> [];
uptodot([H|T]) -> [H|uptodot(T)].