1%% This Source Code Form is subject to the terms of the Mozilla Public
2%% License, v. 2.0. If a copy of the MPL was not distributed with this
3%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
4%%
5%% Copyright (c) 2007-2021 VMware, Inc. or its affiliates.  All rights reserved.
6%%
7
8-module(rabbit_shovel_status).
9-behaviour(gen_server).
10
11-export([start_link/0]).
12
13-export([report/3, remove/1, status/0, lookup/1]).
14
15-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
16         terminate/2, code_change/3]).
17
18-define(SERVER, ?MODULE).
19-define(ETS_NAME, ?MODULE).
20-define(CHECK_FREQUENCY, 60000).
21
22-record(state, {timer}).
23-record(entry, {name, type, info, timestamp}).
24
25start_link() ->
26    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
27
28report(Name, Type, Info) ->
29    gen_server:cast(?SERVER, {report, Name, Type, Info, calendar:local_time()}).
30
31remove(Name) ->
32    gen_server:cast(?SERVER, {remove, Name}).
33
34status() ->
35    gen_server:call(?SERVER, status, infinity).
36
37lookup(Name) ->
38    gen_server:call(?SERVER, {lookup, Name}, infinity).
39
40init([]) ->
41    ?ETS_NAME = ets:new(?ETS_NAME,
42                        [named_table, {keypos, #entry.name}, private]),
43    {ok, ensure_timer(#state{})}.
44
45handle_call(status, _From, State) ->
46    Entries = ets:tab2list(?ETS_NAME),
47    {reply, [{Entry#entry.name, Entry#entry.type, Entry#entry.info,
48              Entry#entry.timestamp}
49             || Entry <- Entries], State};
50
51handle_call({lookup, Name}, _From, State) ->
52    Link = case ets:lookup(?ETS_NAME, Name) of
53               [Entry] -> [{name, Name},
54                           {type, Entry#entry.type},
55                           {info, Entry#entry.info},
56                           {timestamp, Entry#entry.timestamp}];
57               [] -> not_found
58           end,
59    {reply, Link, State}.
60
61handle_cast({report, Name, Type, Info, Timestamp}, State) ->
62    true = ets:insert(?ETS_NAME, #entry{name = Name, type = Type, info = Info,
63                                        timestamp = Timestamp}),
64    rabbit_event:notify(shovel_worker_status,
65                        split_name(Name) ++ split_status(Info)),
66    {noreply, State};
67
68handle_cast({remove, Name}, State) ->
69    true = ets:delete(?ETS_NAME, Name),
70    rabbit_event:notify(shovel_worker_removed, split_name(Name)),
71    {noreply, State}.
72
73handle_info(check, State) ->
74    rabbit_shovel_dyn_worker_sup_sup:cleanup_specs(),
75    {noreply, ensure_timer(State)};
76handle_info(_Info, State) ->
77    {noreply, State}.
78
79terminate(_Reason, State) ->
80    rabbit_misc:stop_timer(State, #state.timer),
81    ok.
82
83code_change(_OldVsn, State, _Extra) ->
84    {ok, State}.
85
86split_status({running, MoreInfo})         -> [{status, running} | MoreInfo];
87split_status({terminated, Reason})        -> [{status, terminated},
88                                              {reason, Reason}];
89split_status(Status) when is_atom(Status) -> [{status, Status}].
90
91split_name({VHost, Name})           -> [{name,  Name},
92                                        {vhost, VHost}];
93split_name(Name) when is_atom(Name) -> [{name, Name}].
94
95ensure_timer(State0) ->
96    State1 = rabbit_misc:stop_timer(State0, #state.timer),
97    rabbit_misc:ensure_timer(State1, #state.timer, ?CHECK_FREQUENCY, check).
98