blob: d108d0f7f9cae9bce3f489e05228d063a0cf080c [file] [log] [blame]
%% -------------------------------------------------------------------
%%
%% derived from riaknostic - automated diagnostic tools for Riak
%%
%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you 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.
%%
%% -------------------------------------------------------------------
%%
%% File renamed from riaknostic_node.erl to weatherreport_node.erl and
%% modified to work with Apache CouchDB
%%
%% Copyright (c) 2014 Cloudant
%%
%% -------------------------------------------------------------------
%% @doc Functions that help diagnostics interact with the local
%% node or other members of the cluster.
-module(weatherreport_node).
-export([
can_connect/0,
can_connect_all/0,
pid/0,
local_command/2,
local_command/3,
local_command/4,
multicall/5,
nodename/0
]).
%% @doc Calls the given 0-arity module and function on the local
%% node and returns the result of that call.
%% @equiv local_command(Module, Function, [])
%% @see can_connect/0.
-spec local_command(Module :: atom(), Function :: atom()) -> term().
local_command(Module, Function) ->
local_command(Module, Function, []).
%% @doc Calls the given module and function with the given arguments
%% on the local node and returns the result of that call.
%% @equiv local_command(Module, Function, Args, 5000)
%% @see can_connect/0
-spec local_command(Module :: atom(), Function :: atom(), Args :: [term()]) -> term().
local_command(Module, Function, Args) ->
local_command(Module, Function, Args, weatherreport_config:timeout()).
%% @doc Calls the given module and function with the given arguments
%% on the local node and returns the result of that call,
%% returning an error if the call doesn't complete within the given
%% timeout.
%% @equiv rpc:call(NodeName, Module, Function, Args, Timeout)
%% @see can_connect/0
-spec local_command(Module :: atom(), Function :: atom(), Args :: [term()], Timeout :: integer()) ->
term().
local_command(Module, Function, Args, Timeout) ->
case is_cluster_node() of
true ->
weatherreport_log:log(
node(),
debug,
"Local function call: ~p:~p(~p)",
[Module, Function, Args]
),
erlang:apply(Module, Function, Args);
_ ->
weatherreport_log:log(
node(),
debug,
"Local RPC: ~p:~p(~p) [~p]",
[Module, Function, Args, Timeout]
),
rpc:call(nodename(), Module, Function, Args, Timeout)
end.
%% @doc Call rpc:multicall/5 from the local cluster node rather than the
%% escript.
-spec multicall(
[node()], Module :: atom(), Function :: atom(), Args :: [term()], Timeout :: integer()
) -> term().
multicall(Nodes, Module, Function, Args, Timeout) ->
case local_command(rpc, multicall, [Nodes, Module, Function, Args, Timeout]) of
{badrpc, Reason} ->
{[{badrpc, Reason}], []};
Resp ->
Resp
end.
%% @doc Retrieves the operating system's process ID of the local
%% node.
%% @equiv local_command(os, getpid)
%% @see can_connect/0
-spec pid() -> string().
pid() ->
local_command(os, getpid).
%% @doc Attempts to connect to the local node if it is not
%% already, and returns whether connection was successful.
-spec can_connect() -> true | false.
can_connect() ->
case is_connected() or is_cluster_node() of
true ->
true;
false ->
weatherreport_log:log(
node(),
debug,
"Not connected to the local cluster node, trying to connect. alive:~p connect_failed:~p",
[is_alive(), connect_failed()]
),
maybe_connect()
end.
-spec can_connect_all() -> true | false.
can_connect_all() ->
case is_connected() of
true ->
case weatherreport_check_nodes_connected:check([]) of
[] -> true;
_ -> false
end;
false ->
false
end.
nodename() ->
Name =
case weatherreport_config:node_name() of
undefined ->
atom_to_list(node());
{_, NodeName} ->
NodeName
end,
case string:tokens(Name, "@") of
[_Node, _Host] ->
list_to_atom(Name);
[Node] ->
[_, Host] = string:tokens(atom_to_list(node()), "@"),
list_to_atom(lists:concat([Node, "@", Host]))
end.
%% Private functions
is_cluster_node() ->
nodename() =:= node().
is_connected() ->
is_alive() andalso connect_failed() =/= true.
maybe_connect() ->
case connect_failed() of
true -> false;
_ -> try_connect()
end.
try_connect() ->
TargetNode = nodename(),
case is_alive() of
true -> ok;
_ -> start_net()
end,
case {net_kernel:hidden_connect_node(TargetNode), net_adm:ping(TargetNode)} of
{true, pong} ->
application:set_env(weatherreport, connect_failed, false),
weatherreport_log:log(
node(),
debug,
"Connected to local cluster node ~p.",
[TargetNode]
),
true;
_ ->
application:set_env(weatherreport, connect_failed, true),
weatherreport_log:log(
node(),
warning,
"Could not connect to the local cluster node ~p, some checks will not run.",
[TargetNode]
),
false
end.
connect_failed() ->
case application:get_env(weatherreport, connect_failed) of
{ok, true} -> true;
undefined -> undefined;
_ -> false
end.
start_net() ->
weatherreport_log:log(node(), debug, "Starting distributed Erlang."),
{Type, NodeName} = weatherreport_config:node_name(),
ThisNode = append_node_suffix(NodeName, "_diag"),
{ok, _} = net_kernel:start([ThisNode, Type]),
case weatherreport_config:cookie() of
undefined ->
% Don't set cookie to undefined so we can pick up the ~/.erlang.cookie
ok;
Cookie when is_atom(Cookie) ->
erlang:set_cookie(node(), Cookie)
end.
append_node_suffix(Name, Suffix) ->
case string:tokens(Name, "@") of
[Node, Host] ->
list_to_atom(lists:concat([Node, Suffix, os:getpid(), "@", Host]));
[Node] ->
list_to_atom(lists:concat([Node, Suffix, os:getpid()]))
end.