1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2003-2015. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21%%----------------------------------------------------------------------
22%% Purpose: Simple (snmp) manager used when performing appup tests.
23%%----------------------------------------------------------------------
24-module(snmp_appup_mgr).
25
26-behaviour(snmpm_user).
27
28-include_lib("snmp/include/STANDARD-MIB.hrl").
29-include_lib("snmp/include/snmp_types.hrl").
30
31-export([start/0, start/1, start/2]).
32-export([handle_error/3,
33	 handle_agent/4,
34	 handle_pdu/5,
35	 handle_trap/4,
36	 handle_inform/4,
37	 handle_report/4]).
38-export([main/2]).
39
40-record(agent, {host, port, conf}).
41-record(state, {timer, reqs, ids, agent}).
42
43-define(USER_ID,      ?MODULE).
44-define(REQ_TIMEOUT,  10000).
45-define(POLL_TIMEOUT, 5000).
46-define(DEFAULT_PORT, 4000).
47%% -define(DEFAULT_PORT, 161).
48
49-define(v1_2(V1,V2),
50	case get(vsn) of
51	    v1 -> V1;
52	    _  -> V2
53	end).
54
55
56start() ->
57    {ok, AgentHost} = inet:gethostname(),
58    AgentPort = ?DEFAULT_PORT,
59    start(AgentHost, AgentPort).
60
61start(AgentPort) when is_integer(AgentPort) ->
62    {ok, AgentHost} = inet:gethostname(),
63    start(AgentHost, AgentPort);
64start(AgentHost) when is_list(AgentHost) ->
65    AgentPort = 161,
66    start(AgentHost, AgentPort).
67
68start(AgentHost, AgentPort)
69  when is_list(AgentHost) and is_integer(AgentPort) ->
70    ensure_started(snmp),
71    Pid = erlang:spawn_link(?MODULE, main, [AgentHost, AgentPort]),
72    receive
73	{'EXIT', Pid, normal} ->
74	    ok;
75	{'EXIT', Pid, Reason} ->
76	    {error, {unexpected_exit, Reason}}
77    end.
78
79ensure_started(App) ->
80    case application:start(App) of
81	ok ->
82	    ok;
83	{error, {already_started, _}} ->
84	    ok;
85	{error, Reason} ->
86	    exit(Reason)
87    end.
88
89poll_timer() ->
90    poll_timer(first).
91
92poll_timer(How) ->
93    erlang:send_after(?POLL_TIMEOUT, self(), {poll_timeout, How}).
94
95next_poll_type(first) ->
96    all;
97next_poll_type(all) ->
98    first.
99
100main(AgentHost, AgentPort) ->
101    ok = snmpm:register_user_monitor(?USER_ID, ?MODULE, self()),
102    AgentConf = [{community, "all-rights"},
103		 {engine_id, "agentEngine"},
104		 {sec_level, noAuthNoPriv},
105		 {version,   v1}],
106    ok = snmpm:register_agent(?USER_ID, AgentHost, AgentPort, AgentConf),
107    Reqs = [{"sysDescr",    get, ?sysDescr_instance},
108	    {"sysObjectID", get, ?sysObjectID_instance},
109	    {"sysUpTime",   get, ?sysUpTime_instance}],
110    Agent = #agent{host = AgentHost, port = AgentPort, conf = AgentConf},
111    State = #state{timer = poll_timer(), reqs = Reqs, agent = Agent},
112    loop(State).
113
114loop(State) ->
115    receive
116	{poll_timeout, How} ->
117	    NewState = handle_poll_timeout(State, How),
118	    loop(NewState#state{timer = poll_timer(next_poll_type(How))});
119
120	{req_timeout, ReqId} ->
121	    NewState = handle_req_timeout(State, ReqId),
122	    loop(NewState);
123
124	{snmp_callback, Info} ->
125	    NewState = handle_snmp(State, Info),
126	    loop(NewState)
127    end.
128
129
130handle_poll_timeout(#state{agent = Agent, reqs = [Req|Reqs], ids = IDs} = S,
131	       first) ->
132    ReqId = handle_req(Agent, [Req]),
133    S#state{reqs = Reqs ++ [Req], ids = [ReqId|IDs]};
134handle_poll_timeout(#state{agent = Agent, reqs = Reqs, ids = IDs} = S, all) ->
135    ReqId = handle_req(Agent, Reqs),
136    S#state{ids = [ReqId|IDs]}.
137
138handle_req(#agent{host = Host, port = Port}, Reqs) ->
139    Oids  = [Oid  || {_Desc, Op, Oid} <- Reqs, Op == get],
140    Descs = [Desc || {Desc, Op, _Oid} <- Reqs, Op == get],
141    {ok, ReqId} = snmpm:ag(?USER_ID, Host, Port, Oids),
142    p("issued get-request (~w) for: ~s", [ReqId, oid_descs(Descs)]),
143    ReqTimer = erlang:send_after(?REQ_TIMEOUT, self(), {req_timeout, ReqId}),
144    {ReqId, erlang:monotonic_time(micro_seconds), ReqTimer}.
145
146oid_descs([]) ->
147    [];
148oid_descs([Desc]) ->
149    lists:flatten(io_lib:format("~s", [Desc]));
150oid_descs([Desc|Descs]) ->
151    lists:flatten(io_lib:format("~s, ", [Desc])) ++ oid_descs(Descs).
152
153handle_req_timeout(#state{ids = IDs0} = State, ReqId) ->
154    case lists:keysearch(ReqId, 1, IDs0) of
155	{value, {ReqId, _T, _Ref}} ->
156	    e("Request timeout for request ~w", [ReqId]),
157	    IDs = lists:keydelete(ReqId, 1, IDs0),
158	    State#state{ids = IDs};
159	false ->
160	    w("Did not find request corresponding to id ~w", [ReqId]),
161	    State
162    end.
163
164handle_snmp(#state{ids = IDs0} = S, {error, ReqId, Reason}) ->
165    case lists:keysearch(ReqId, 1, IDs0) of
166	{value, {ReqId, T, Ref}} ->
167	    Diff = erlang:monotonic_time(micro_seconds) - T,
168	    p("SNMP error regarding outstanding request after ~w microsec:"
169	      "~n   ReqId:  ~w"
170	      "~n   Reason: ~w", [Diff, ReqId, Reason]),
171	    IDs = lists:keydelete(ReqId, 1, IDs0),
172	    erlang:cancel_timer(Ref),
173	    S#state{ids = IDs};
174	false ->
175	    w("SNMP error regarding unknown request:"
176	      "~n   ReqId:  ~w"
177	      "~n   Reason: ~w", [ReqId, Reason]),
178	    S
179    end;
180
181handle_snmp(State, {agent, Addr, Port, SnmpInfo}) ->
182    p("Received agent info:"
183      "~n   Addr:     ~w"
184      "~n   Port:     ~w"
185      "~n   SnmpInfo: ~w", [Addr, Port, SnmpInfo]),
186    State;
187
188handle_snmp(#state{ids = IDs0} = S, {pdu, Addr, Port, ReqId, SnmpResponse}) ->
189    case lists:keysearch(ReqId, 1, IDs0) of
190	{value, {ReqId, T, Ref}} ->
191	    Diff = erlang:monotonic_time(micro_seconds) - T,
192	    p("SNMP pdu regarding outstanding request after ~w microsec:"
193	      "~n   ReqId:        ~w"
194	      "~n   Addr:         ~w"
195	      "~n   Port:         ~w"
196	      "~n   SnmpResponse: ~w",
197	      [Diff, ReqId, Addr, Port, SnmpResponse]),
198	    IDs = lists:keydelete(ReqId, 1, IDs0),
199	    erlang:cancel_timer(Ref),
200	    S#state{ids = IDs};
201	false ->
202	    w("SNMP pdu regarding unknown request:"
203	      "~n   ReqId:        ~w"
204	      "~n   Addr:         ~w"
205	      "~n   Port:         ~w"
206	      "~n   SnmpResponse: ~w", [ReqId, Addr, Port, SnmpResponse]),
207	    S
208    end;
209
210handle_snmp(State, {trap, Addr, Port, SnmpTrapInfo}) ->
211    p("Received trap:"
212      "~n   Addr:         ~w"
213      "~n   Port:         ~w"
214      "~n   SnmpTrapInfo: ~w", [Addr, Port, SnmpTrapInfo]),
215    State;
216
217handle_snmp(State, {inform, Addr, Port, SnmpInform}) ->
218    p("Received inform:"
219      "~n   Addr:       ~w"
220      "~n   Port:       ~w"
221      "~n   SnmpInform: ~w", [Addr, Port, SnmpInform]),
222    State;
223
224handle_snmp(State, {report, Addr, Port, SnmpReport}) ->
225    p("Received report:"
226      "~n   Addr:       ~w"
227      "~n   Port:       ~w"
228      "~n   SnmpReport: ~w", [Addr, Port, SnmpReport]),
229    State;
230
231handle_snmp(State, Unknown) ->
232    p("Received unknown snmp info:"
233      "~n   Unknown: ~w", [Unknown]),
234    State.
235
236
237%% -----------------------------------------------------------------------
238%%
239%% Manager user callback API
240%%
241%% -----------------------------------------------------------------------
242
243
244handle_error(ReqId, Reason, Pid) ->
245    Pid ! {snmp_callback, {error, ReqId, Reason}},
246    ignore.
247
248handle_agent(Addr, Port, SnmpInfo, Pid) ->
249    Pid ! {snmp_callback, {agent, Addr, Port, SnmpInfo}},
250    ignore.
251
252handle_pdu(Addr, Port, ReqId, SnmpResponse, Pid) ->
253    Pid ! {snmp_callback, {pdu, Addr, Port, ReqId, SnmpResponse}},
254    ignore.
255
256handle_trap(Addr, Port, SnmpTrapInfo, Pid) ->
257    Pid ! {snmp_callback, {trap, Addr, Port, SnmpTrapInfo}},
258    ignore.
259
260handle_inform(Addr, Port, SnmpInform, Pid) ->
261    Pid ! {snmp_callback, {inform, Addr, Port, SnmpInform}},
262    ignore.
263
264handle_report(Addr, Port, SnmpReport, Pid) ->
265    Pid ! {snmp_callback, {report, Addr, Port, SnmpReport}},
266    ignore.
267
268
269%% -----------------------------------------------------------------------
270
271e(F, A) ->
272    p("*** ERROR ***", F, A).
273
274w(F, A) ->
275    p("*** WARNING ***", F, A).
276
277p(F, A) ->
278    p("*** INFO ***", F, A).
279
280p(P, F, A) ->
281    io:format("~s~nMGR: " ++ F ++ "~n~n", [P|A]).
282