Packages

Erlang/Elixir Web based and pluginable metrics, monitoring, and observer.

Current section

Files

Jump to
emeter src emeter_node_manager.erl
Raw

src/emeter_node_manager.erl

%%% ------------------------------------------------------------------------------------------------
%%% "Emeter" is available for use under the following license, commonly known as the 3-clause (or
%%% "modified") BSD license:
%%%
%%% Copyright (c) 2018-2019, Pouriya Jahanbakhsh
%%% (pouriya.jahanbakhsh@gmail.com)
%%% All rights reserved.
%%%
%%% Redistribution and use in source and binary forms, with or without modification, are permitted
%%% provided that the following conditions are met:
%%%
%%% 1. Redistributions of source code must retain the above copyright notice, this list of
%%% conditions and the following disclaimer.
%%%
%%% 2. Redistributions in binary form must reproduce the above copyright notice, this list of
%%% conditions and the following disclaimer in the documentation and/or other materials provided
%%% with the distribution.
%%%
%%% 3. Neither the name of the copyright holder nor the names of its contributors may be used to
%%% endorse or promote products derived from this software without specific prior written
%%% permission.
%%%
%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
%%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
%%% FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
%%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
%%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
%%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
%%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
%%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
%%% POSSIBILITY OF SUCH DAMAGE.
%%% ------------------------------------------------------------------------------------------------
%% @author Pouriya Jahanbakhsh <pouriya.jahanbakhsh@gmail.com>
%% @version
%% @doc
%%
%% @end
%% -------------------------------------------------------------------------------------------------
-module(emeter_node_manager).
-author("pouriya.jahanbakhsh@gmail.com").
-behavior(gen_server).
%% -------------------------------------------------------------------------------------------------
%% Exports:
%% API:
-export([start_link/1
,connect/1]).
%% 'gen_server' callbacks:
-export([init/1
,handle_call/3
,handle_cast/2
,handle_info/2
,terminate/2
,code_change/3]).
%% -------------------------------------------------------------------------------------------------
%% Records & Macros & Includes:
-define(DEF_RECONNECT_INTERVAL, 3000).
-define(TAB, ?MODULE).
-define(NOTIFY_TAG, notify).
-define(NODE_UP_TAG, nodeup).
-define(S, state).
-record(?S, {node, connection}).
-define(CONNECT_TAG, connect).
-include("emeter_log.hrl").
%% -------------------------------------------------------------------------------------------------
%% API:
start_link(Node) ->
gen_server:start_link(?MODULE, Node, []).
connect(Pid) ->
gen_server:call(Pid, ?CONNECT_TAG, infinity).
%% -------------------------------------------------------------------------------------------------
%% 'gen_server' callbacks:
init(Node) ->
erlang:process_flag(trap_exit, true),
notify(0),
{ok, #?S{node = Node, connection = false}}.
handle_call(?CONNECT_TAG, _, #?S{node = Node}=S) ->
?info(<<"'connect' request received, Try connecting to ~tp">>, [Node]),
case do_connect(Node) of
ok ->
{reply, ok, S};
retry ->
{reply, {error, retry}, S};
{error, Rsn}=Reply ->
{stop, Rsn, Reply, S}
end;
handle_call(Req, _, S) ->
{reply, {error, {unknown, [{request, Req}]}}, S}.
handle_cast(_, S) ->
{noreply, S}.
handle_info(?NODE_UP_TAG, #?S{node = Node}=S) ->
_ = emeter_node:add(Node),
?info(<<"New node ~tp added to nodes">>, [Node]),
{noreply, S#?S{connection = true}};
handle_info({nodedown, _}, #?S{node = Node}=S) ->
_ = emeter_node:delete(Node),
?error(<<"Node ~tp disconnected">>, [Node]),
notify(0),
{noreply, S#?S{connection = false}};
handle_info(notify, #?S{node = Node}=S) ->
?info(<<"Try connecting to ~tp">>, [Node]),
case do_connect(Node) of
Ok when Ok == ok orelse Ok == retry ->
{noreply, S};
{error, Rsn} ->
{stop, Rsn, S}
end;
handle_info(_, S) ->
{noreply, S}.
terminate(_, #?S{node = Node}) ->
_ = emeter_node:delete(Node),
ok.
code_change(_, S, _) ->
{ok, S}.
%% -------------------------------------------------------------------------------------------------
%% Internals:
notify(0) ->
erlang:self() ! ?NOTIFY_TAG,
ok;
notify(Int) ->
_ = erlang:send_after(Int, erlang:self(), ?NOTIFY_TAG),
ok.
do_connect(Node) ->
case net_adm:ping(Node) of
pong ->
try erlang:monitor_node(Node, true) of
_ ->
%% Load 'emeter_agent' on remote node
{Mod, Bin, File} = code:get_object_code(emeter_agent_api),
case rpc:call(Node, code, load_binary, [Mod, File, Bin]) of
{module, _} ->
%% I should start 'emeter_agent' process too
case emeter_agent_api:start(Node) of
{ok, _} ->
erlang:self() ! ?NODE_UP_TAG,
ok;
{error, {already_started, _}} ->
erlang:self() ! ?NODE_UP_TAG,
ok;
{error, Rsn} ->
?error(<<"Could not start 'emeter_agent' process on remote node"
" ~tp for reason ~tp. Stop reconnecting.">>
,[Node, Rsn]),
%% Director will delete this child from its children
{error, load_agent}
end;
{badrpc, Rsn} ->
?error(<<"Could not load EMeter module on node ~tp for reason ~tp. Retr"
"y after ~tp ms.">>
,[Node, Rsn, ?DEF_RECONNECT_INTERVAL]),
notify(?DEF_RECONNECT_INTERVAL),
retry;
{error, Rsn} -> % {error, _}
?error(<<"Could not load EMeter agent on node ~tp for reason ~tp. Stop "
"reconnecting.">>
,[Node, Rsn, ?DEF_RECONNECT_INTERVAL]),
{error, load_agent}
end
catch
_:_ ->
?error(<<"Could not monitor node ~tp. Retry after ~tp ms.">>
,[Node, ?DEF_RECONNECT_INTERVAL]),
notify(?DEF_RECONNECT_INTERVAL),
retry
end;
_ -> % pang
?error(<<"Could not connect to node ~tp. Retry after ~tp ms">>
,[Node, ?DEF_RECONNECT_INTERVAL]),
notify(?DEF_RECONNECT_INTERVAL),
retry
end.