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('Elixir.RabbitMQ.CLI.Ctl.Commands.ShovelStatusCommand').
9
10-include("rabbit_shovel.hrl").
11
12-behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour').
13
14-ignore_xref({'Elixir.RabbitMQ.CLI.DefaultOutput', output, 1}).
15
16-export([
17         usage/0,
18         usage_doc_guides/0,
19         flags/0,
20         validate/2,
21         merge_defaults/2,
22         banner/2,
23         run/2,
24         switches/0,
25         aliases/0,
26         output/2,
27         scopes/0,
28         formatter/0,
29         help_section/0,
30         description/0
31        ]).
32
33
34%%----------------------------------------------------------------------------
35%% Callbacks
36%%----------------------------------------------------------------------------
37usage() ->
38    <<"shovel_status">>.
39
40usage_doc_guides() ->
41    [?SHOVEL_GUIDE_URL].
42
43description() ->
44    <<"Displays status of Shovel on a node">>.
45
46help_section() ->
47    {plugin, shovel}.
48
49flags() ->
50    [].
51
52formatter() ->
53    'Elixir.RabbitMQ.CLI.Formatters.Table'.
54
55validate(_,_) ->
56    ok.
57
58merge_defaults(A,O) ->
59    {A, O}.
60
61banner(_, #{node := Node}) ->
62    erlang:iolist_to_binary([<<"Shovel status on node ">>,
63                             atom_to_binary(Node, utf8)]).
64
65run(_Args, #{node := Node}) ->
66    case rabbit_misc:rpc_call(Node, rabbit_shovel_status, status, []) of
67        {badrpc, _} = Error ->
68            Error;
69        Status ->
70            {stream, Status}
71    end.
72
73switches() ->
74    [].
75
76aliases() ->
77    [].
78
79output({stream, ShovelStatus}, _Opts) ->
80    Formatted = [fmt_name(Name,
81                          fmt_status(Status,
82                                     #{type => Type,
83                                       last_changed => fmt_ts(Timestamp)}))
84                 || {Name, Type, Status, Timestamp} <- ShovelStatus],
85    {stream, Formatted};
86output(E, _Opts) ->
87    'Elixir.RabbitMQ.CLI.DefaultOutput':output(E).
88
89scopes() ->
90    ['ctl', 'diagnostics'].
91
92%%----------------------------------------------------------------------------
93%% Formatting
94%%----------------------------------------------------------------------------
95fmt_name({Vhost, Name}, Map) ->
96    Map#{name => Name, vhost => Vhost};
97fmt_name(Name, Map) ->
98    %% Static shovel names don't contain the vhost
99    Map#{name => Name}.
100
101fmt_ts({{YY, MM, DD}, {Hour, Min, Sec}}) ->
102    erlang:list_to_binary(
103      io_lib:format("~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w",
104                    [YY, MM, DD, Hour, Min, Sec])).
105
106fmt_status({'running' = St, Proplist}, Map) ->
107    maps:merge(Map#{state => St,
108                    source_protocol => proplists:get_value(src_protocol, Proplist,
109                                                           undefined),
110                    source => proplists:get_value(src_uri, Proplist),
111                    destination_protocol => proplists:get_value(dest_protocol, Proplist, undefined),
112                    destination => proplists:get_value(dest_uri, Proplist),
113                    termination_reason => <<>>}, details_to_map(Proplist));
114fmt_status('starting' = St, Map) ->
115    Map#{state => St,
116         source => <<>>,
117         destination => <<>>,
118         termination_reason => <<>>};
119fmt_status({'terminated' = St, Reason}, Map) ->
120    Map#{state => St,
121         termination_reason => list_to_binary(io_lib:format("~p", [Reason])),
122         source => <<>>,
123         destination => <<>>}.
124
125details_to_map(Proplist) ->
126    Keys = [{src_address, source_address}, {src_queue, source_queue},
127            {src_exchange, source_exchange}, {src_exchange_key, source_exchange_key},
128            {dest_address, destination_address}, {dest_queue, destination_queue},
129            {dest_exchange, destination_exchange}, {dest_exchange_key, destination_exchange_key}],
130    maps:from_list([{New, proplists:get_value(Old, Proplist)}
131                    || {Old, New} <- Keys, proplists:is_defined(Old, Proplist)]).
132