diff options
Diffstat (limited to 'lib/snmp/src/agent/snmpa_symbolic_store.erl')
-rw-r--r-- | lib/snmp/src/agent/snmpa_symbolic_store.erl | 711 |
1 files changed, 711 insertions, 0 deletions
diff --git a/lib/snmp/src/agent/snmpa_symbolic_store.erl b/lib/snmp/src/agent/snmpa_symbolic_store.erl new file mode 100644 index 0000000000..6c58ffde41 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_symbolic_store.erl @@ -0,0 +1,711 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(snmpa_symbolic_store). + +%%---------------------------------------------------------------------- +%% This module implements a multipurpose symbolic store. +%% 1) For internal and use from application: aliasname_to_value/1. +%% If this was stored in the mib, deadlock would occur. +%% 2 table_info/1. Getting information about a table. Used by default +%% implementation of tables. +%% 3) variable_info/1. Used by default implementation of variables. +%% 4) notification storage. Used by snmpa_trap. +%% There is one symbolic store per node and it uses the ets table +%% snmp_agent_table, owned by the snmpa_supervisor. +%% +%%---------------------------------------------------------------------- + +-include_lib("kernel/include/file.hrl"). +-include("snmp_types.hrl"). +-include("snmp_debug.hrl"). +-include("snmp_verbosity.hrl"). +-include("SNMPv2-MIB.hrl"). + + +%% API +-export([start_link/2, + stop/0, + info/0, + backup/1, + aliasname_to_oid/1, oid_to_aliasname/1, + add_aliasnames/2, delete_aliasnames/1, + which_aliasnames/0, + which_tables/0, + which_variables/0, + enum_to_int/2, int_to_enum/2, + table_info/1, add_table_infos/2, delete_table_infos/1, + variable_info/1, add_variable_infos/2, delete_variable_infos/1, + get_notification/1, set_notification/2, + delete_notifications/1, which_notifications/0, + add_types/2, delete_types/1]). + +%% API (for quick access to the db, note that this is only reads). +-export([get_db/0, + aliasname_to_oid/2, oid_to_aliasname/2, + enum_to_int/3, int_to_enum/3]). + + +%% Internal exports +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). + +-export([verbosity/1]). + +-define(SERVER, ?MODULE). + +-ifdef(snmp_debug). +-define(GS_START_LINK(Prio, Opts), + gen_server:start_link({local, ?SERVER}, ?MODULE, [Prio, Opts], + [{debug,[trace]}])). +-else. +-define(GS_START_LINK(Prio, Opts), + gen_server:start_link({local, ?SERVER}, ?MODULE, [Prio, Opts], [])). +-endif. + +-record(state, {db, backup}). +-record(symbol, {key, mib_name, info}). + + +%%----------------------------------------------------------------- +%% Func: start_link/1 +%% Args: Prio is priority of mib-server +%% Opts is a list of options +%% Purpose: starts the mib server synchronized +%% Returns: {ok, Pid} | {error, Reason} +%%----------------------------------------------------------------- +start_link(Prio, Opts) -> + ?d("start_link -> entry with" + "~n Prio: ~p" + "~n Opts: ~p", [Prio,Opts]), + ?GS_START_LINK(Prio,Opts). + +stop() -> + call(stop). + +info() -> + call(info). + +backup(BackupDir) -> + call({backup, BackupDir}). + + +%%---------------------------------------------------------------------- +%% Returns: Db +%%---------------------------------------------------------------------- + +get_db() -> + call(get_db). + + +%%---------------------------------------------------------------------- +%% Returns: {value, Oid} | false +%%---------------------------------------------------------------------- +aliasname_to_oid(Aliasname) -> + call({aliasname_to_oid, Aliasname}). + +oid_to_aliasname(OID) -> + call({oid_to_aliasname, OID}). + +int_to_enum(TypeOrObjName, Int) -> + call({int_to_enum,TypeOrObjName,Int}). + +enum_to_int(TypeOrObjName, Enum) -> + call({enum_to_int,TypeOrObjName,Enum}). + +add_types(MibName, Types) -> + cast({add_types, MibName, Types}). + +delete_types(MibName) -> + cast({delete_types, MibName}). + +add_aliasnames(MibName, MEs) -> + cast({add_aliasnames, MibName, MEs}). + +delete_aliasnames(MibName) -> + cast({delete_aliasname, MibName}). + +which_aliasnames() -> + call(which_aliasnames). + +which_tables() -> + call(which_tables). + +which_variables() -> + call(which_variables). + + +%%---------------------------------------------------------------------- +%% Returns: false|{value, Info} +%%---------------------------------------------------------------------- +table_info(TableName) -> + call({table_info, TableName}). + + +%%---------------------------------------------------------------------- +%% Returns: false|{value, Info} +%%---------------------------------------------------------------------- +variable_info(VariableName) -> + call({variable_info, VariableName}). + +add_table_infos(MibName, TableInfos) -> + cast({add_table_infos, MibName, TableInfos}). + +delete_table_infos(MibName) -> + cast({delete_table_infos, MibName}). + +add_variable_infos(MibName, VariableInfos) -> + cast({add_variable_infos, MibName, VariableInfos}). + +delete_variable_infos(MibName) -> + cast({delete_variable_infos, MibName}). + + +%%----------------------------------------------------------------- +%% Store traps +%%----------------------------------------------------------------- +%% A notification is stored as {Key, Value}, where +%% Key is the symbolic trap name, and Value is +%% a #trap record. +%%----------------------------------------------------------------- +%% Returns: {value, Val} | undefined +%%----------------------------------------------------------------- +get_notification(Key) -> + call({get_notification, Key}). +set_notification(Trap, MibName) -> + call({set_notification, MibName, Trap}). +delete_notifications(MibName) -> + call({delete_notifications, MibName}). +which_notifications() -> + call(which_notifications). + + +verbosity(Verbosity) -> + cast({verbosity,Verbosity}). + + +%%---------------------------------------------------------------------- +%% DB access (read) functions: Returns: {value, Oid} | false +%%---------------------------------------------------------------------- +aliasname_to_oid(Db, Aliasname) -> + case snmpa_general_db:read(Db, {alias, Aliasname}) of + {value,#symbol{info = {Oid, _Enums}}} -> {value, Oid}; + false -> false + end. + +oid_to_aliasname(Db,Oid) -> + case snmpa_general_db:read(Db, {oid, Oid}) of + {value,#symbol{info = Aliasname}} -> {value, Aliasname}; + _ -> false + end. + +which_notifications(Db) -> + Pattern = #symbol{key = {trap, '_'}, _ = '_'}, + Symbols = snmpa_general_db:match_object(Db, Pattern), + [{Name, Mib, Rec} || #symbol{key = {trap, Name}, + mib_name = Mib, + info = Rec} <- Symbols]. + +which_aliasnames(Db) -> + Pattern = #symbol{key = {alias, '_'}, _ = '_'}, + Symbols = snmpa_general_db:match_object(Db, Pattern), + [Alias || #symbol{key = {alias, Alias}} <- Symbols]. + +which_tables(Db) -> + Pattern = #symbol{key = {table_info, '_'}, _ = '_'}, + Symbols = snmpa_general_db:match_object(Db, Pattern), + [Name || #symbol{key = {table_info, Name}} <- Symbols]. + +which_variables(Db) -> + Pattern = #symbol{key = {variable_info, '_'}, _ = '_'}, + Symbols = snmpa_general_db:match_object(Db, Pattern), + [Name || #symbol{key = {variable_info, Name}} <- Symbols]. + +int_to_enum(Db,TypeOrObjName,Int) -> + case snmpa_general_db:read(Db, {alias, TypeOrObjName}) of + {value,#symbol{info = {_Oid, Enums}}} -> + case lists:keysearch(Int, 2, Enums) of + {value, {Enum, _Int}} -> {value, Enum}; + false -> false + end; + false -> % Not an Aliasname -> + case snmpa_general_db:read(Db, {type, TypeOrObjName}) of + {value,#symbol{info = Enums}} -> + case lists:keysearch(Int, 2, Enums) of + {value, {Enum, _Int}} -> {value, Enum}; + false -> false + end; + false -> + false + end + end. + +enum_to_int(Db, TypeOrObjName, Enum) -> + case snmpa_general_db:read(Db, {alias, TypeOrObjName}) of + {value,#symbol{info = {_Oid, Enums}}} -> + case lists:keysearch(Enum, 1, Enums) of + {value, {_Enum, Int}} -> {value, Int}; + false -> false + end; + false -> % Not an Aliasname + case snmpa_general_db:read(Db, {type, TypeOrObjName}) of + {value,#symbol{info = Enums}} -> + case lists:keysearch(Enum, 1, Enums) of + {value, {_Enum, Int}} -> {value, Int}; + false -> false + end; + false -> + false + end + end. + + +%%---------------------------------------------------------------------- +%% DB access (read) functions: Returns: false|{value, Info} +%%---------------------------------------------------------------------- +table_info(Db,TableName) -> + case snmpa_general_db:read(Db, {table_info, TableName}) of + {value,#symbol{info = Info}} -> {value, Info}; + false -> false + end. + + +%%---------------------------------------------------------------------- +%% DB access (read) functions: Returns: false|{value, Info} +%%---------------------------------------------------------------------- +variable_info(Db,VariableName) -> + case snmpa_general_db:read(Db, {variable_info, VariableName}) of + {value,#symbol{info = Info}} -> {value, Info}; + false -> false + end. + + +%%---------------------------------------------------------------------- +%% Implementation +%%---------------------------------------------------------------------- + +init([Prio,Opts]) -> + ?d("init -> entry with" + "~n Prio: ~p" + "~n Opts: ~p", [Prio,Opts]), + case (catch do_init(Prio, Opts)) of + {ok, State} -> + {ok, State}; + Error -> + config_err("failed starting symbolic-store: ~n~p", [Error]), + {stop, {error, Error}} + end. + +do_init(Prio, Opts) -> + process_flag(priority, Prio), + process_flag(trap_exit, true), + put(sname,ss), + put(verbosity,get_verbosity(Opts)), + ?vlog("starting",[]), + Storage = get_mib_storage(Opts), + %% type = bag solves the problem with import and multiple + %% object/type definitions. + Db = snmpa_general_db:open(Storage, snmpa_symbolic_store, + symbol, record_info(fields,symbol), bag), + S = #state{db = Db}, + ?vdebug("started",[]), + {ok, S}. + + +handle_call(get_db, _From, #state{db = DB} = S) -> + ?vlog("get db",[]), + {reply, DB, S}; + +handle_call({table_info, TableName}, _From, #state{db = DB} = S) -> + ?vlog("table info: ~p",[TableName]), + Res = table_info(DB, TableName), + ?vdebug("table info result: ~p",[Res]), + {reply, Res, S}; + +handle_call({variable_info, VariableName}, _From, #state{db = DB} = S) -> + ?vlog("variable info: ~p",[VariableName]), + Res = variable_info(DB, VariableName), + ?vdebug("variable info result: ~p",[Res]), + {reply, Res, S}; + +handle_call({aliasname_to_oid, Aliasname}, _From, #state{db = DB} = S) -> + ?vlog("aliasname to oid: ~p",[Aliasname]), + Res = aliasname_to_oid(DB,Aliasname), + ?vdebug("aliasname to oid result: ~p",[Res]), + {reply, Res, S}; + +handle_call({oid_to_aliasname, Oid}, _From, #state{db = DB} = S) -> + ?vlog("oid to aliasname: ~p",[Oid]), + Res = oid_to_aliasname(DB, Oid), + ?vdebug("oid to aliasname result: ~p",[Res]), + {reply, Res, S}; + +handle_call(which_aliasnames, _From, #state{db = DB} = S) -> + ?vlog("which aliasnames",[]), + Res = which_aliasnames(DB), + ?vdebug("which aliasnames: ~p",[Res]), + {reply, Res, S}; + +handle_call(which_tables, _From, #state{db = DB} = S) -> + ?vlog("which tables",[]), + Res = which_tables(DB), + ?vdebug("which tables: ~p",[Res]), + {reply, Res, S}; + +handle_call(which_variables, _From, #state{db = DB} = S) -> + ?vlog("which variables",[]), + Res = which_variables(DB), + ?vdebug("which variables: ~p",[Res]), + {reply, Res, S}; + +handle_call({enum_to_int, TypeOrObjName, Enum}, _From, #state{db = DB} = S) -> + ?vlog("enum to int: ~p, ~p",[TypeOrObjName,Enum]), + Res = enum_to_int(DB, TypeOrObjName, Enum), + ?vdebug("enum to int result: ~p",[Res]), + {reply, Res, S}; + +handle_call({int_to_enum, TypeOrObjName, Int}, _From, #state{db = DB} = S) -> + ?vlog("int to enum: ~p, ~p",[TypeOrObjName,Int]), + Res = int_to_enum(DB, TypeOrObjName, Int), + ?vdebug("int to enum result: ~p",[Res]), + {reply, Res, S}; + +handle_call({set_notification, MibName, Trap}, _From, #state{db = DB} = S) -> + ?vlog("set notification:" + "~n ~p~n ~p", [MibName,Trap]), + set_notif(DB, MibName, Trap), + {reply, true, S}; + +handle_call({delete_notifications, MibName}, _From, #state{db = DB} = S) -> + ?vlog("delete notification: ~p",[MibName]), + delete_notif(DB, MibName), + {reply, true, S}; + +handle_call(which_notifications, _From, #state{db = DB} = S) -> + ?vlog("which notifications", []), + Reply = which_notifications(DB), + {reply, Reply, S}; + +handle_call({get_notification, Key}, _From, #state{db = DB} = S) -> + ?vlog("get notification: ~p",[Key]), + Res = get_notif(DB, Key), + ?vdebug("get notification result: ~p",[Res]), + {reply, Res, S}; + +handle_call(info, _From, #state{db = DB} = S) -> + ?vlog("info",[]), + Info = get_info(DB), + {reply, Info, S}; + +handle_call({backup, BackupDir}, From, #state{db = DB} = S) -> + ?vlog("info to ~p",[BackupDir]), + Pid = self(), + V = get(verbosity), + case file:read_file_info(BackupDir) of + {ok, #file_info{type = directory}} -> + BackupServer = + erlang:spawn_link( + fun() -> + put(sname, albs), + put(verbosity, V), + Dir = filename:join([BackupDir]), + Reply = snmpa_general_db:backup(DB, Dir), + Pid ! {backup_done, Reply}, + unlink(Pid) + end), + ?vtrace("backup server: ~p", [BackupServer]), + {noreply, S#state{backup = {BackupServer, From}}}; + {ok, _} -> + {reply, {error, not_a_directory}, S}; + Error -> + {reply, Error, S} + end; + +handle_call(stop, _From, S) -> + ?vlog("stop",[]), + {stop, normal, ok, S}; + +handle_call(Req, _From, S) -> + info_msg("received unknown request: ~n~p", [Req]), + Reply = {error, {unknown, Req}}, + {reply, Reply, S}. + + +handle_cast({add_types, MibName, Types}, #state{db = DB} = S) -> + ?vlog("add types for ~p:",[MibName]), + F = fun(#asn1_type{assocList = Alist, aliasname = Name}) -> + case snmp_misc:assq(enums, Alist) of + {value, Es} -> + ?vlog("add type~n ~p -> ~p",[Name,Es]), + Rec = #symbol{key = {type, Name}, + mib_name = MibName, + info = Es}, + snmpa_general_db:write(DB, Rec); + false -> done + end + end, + lists:foreach(F, Types), + {noreply, S}; + +handle_cast({delete_types, MibName}, #state{db = DB} = S) -> + ?vlog("delete types: ~p",[MibName]), + Pattern = #symbol{key = {type, '_'}, mib_name = MibName, info = '_'}, + snmpa_general_db:match_delete(DB, Pattern), + {noreply, S}; + +handle_cast({add_aliasnames, MibName, MEs}, #state{db = DB} = S) -> + ?vlog("add aliasnames for ~p:",[MibName]), + F = fun(#me{aliasname = AN, oid = Oid, asn1_type = AT}) -> + Enums = + case AT of + #asn1_type{assocList = Alist} -> + case lists:keysearch(enums, 1, Alist) of + {value, {enums, Es}} -> Es; + _ -> [] + end; + _ -> [] + end, + write_alias(AN, DB, Enums, MibName, Oid) + end, + lists:foreach(F, MEs), + {noreply, S}; + +handle_cast({delete_aliasname, MibName}, #state{db = DB} = S) -> + ?vlog("delete aliasname: ~p",[MibName]), + Pattern1 = #symbol{key = {alias, '_'}, mib_name = MibName, info = '_'}, + snmpa_general_db:match_delete(DB, Pattern1), + Pattern2 = #symbol{key = {oid, '_'}, mib_name = MibName, info = '_'}, + snmpa_general_db:match_delete(DB, Pattern2), + {noreply, S}; + +handle_cast({add_table_infos, MibName, TableInfos}, #state{db = DB} = S) -> + ?vlog("add table infos for ~p:",[MibName]), + F = fun({Name, TableInfo}) -> + ?vlog("add table info~n ~p -> ~p", + [Name, TableInfo]), + Rec = #symbol{key = {table_info, Name}, + mib_name = MibName, + info = TableInfo}, + snmpa_general_db:write(DB, Rec) + end, + lists:foreach(F, TableInfos), + {noreply, S}; + +handle_cast({delete_table_infos, MibName}, #state{db = DB} = S) -> + ?vlog("delete table infos: ~p",[MibName]), + Pattern = #symbol{key = {table_info, '_'}, mib_name = MibName, info = '_'}, + snmpa_general_db:match_delete(DB, Pattern), + {noreply, S}; + +handle_cast({add_variable_infos, MibName, VariableInfos}, + #state{db = DB} = S) -> + ?vlog("add variable infos for ~p:",[MibName]), + F = fun({Name, VariableInfo}) -> + ?vlog("add variable info~n ~p -> ~p", + [Name,VariableInfo]), + Rec = #symbol{key = {variable_info, Name}, + mib_name = MibName, + info = VariableInfo}, + snmpa_general_db:write(DB, Rec) + end, + lists:foreach(F, VariableInfos), + {noreply, S}; + +handle_cast({delete_variable_infos, MibName}, #state{db = DB} = S) -> + ?vlog("delete variable infos: ~p",[MibName]), + Pattern = #symbol{key = {variable_info,'_'}, + mib_name = MibName, + info = '_'}, + snmpa_general_db:match_delete(DB, Pattern), + {noreply, S}; + +handle_cast({verbosity,Verbosity}, State) -> + ?vlog("verbosity: ~p -> ~p",[get(verbosity),Verbosity]), + put(verbosity,snmp_verbosity:validate(Verbosity)), + {noreply, State}; + +handle_cast(Msg, S) -> + info_msg("received unknown message: ~n~p", [Msg]), + {noreply, S}. + + +handle_info({'EXIT', Pid, Reason}, #state{backup = {Pid, From}} = S) -> + ?vlog("backup server (~p) exited for reason ~n~p", [Pid, Reason]), + gen_server:reply(From, {error, Reason}), + {noreply, S#state{backup = undefined}}; + +handle_info({'EXIT', Pid, Reason}, S) -> + %% The only other processes we should be linked to are + %% either the master agent or our supervisor, so die... + {stop, {received_exit, Pid, Reason}, S}; + +handle_info({backup_done, Reply}, #state{backup = {_, From}} = S) -> + ?vlog("backup done:" + "~n Reply: ~p", [Reply]), + gen_server:reply(From, Reply), + {noreply, S#state{backup = undefined}}; + +handle_info(Info, S) -> + info_msg("received unknown info: ~n~p", [Info]), + {noreply, S}. + + +terminate(Reason, S) -> + ?vlog("terminate: ~p",[Reason]), + snmpa_general_db:close(S#state.db). + + +%%---------------------------------------------------------- +%% Code change +%%---------------------------------------------------------- + +% downgrade +code_change({down, _Vsn}, #state{db = DB, backup = B}, downgrade_to_pre_4_7) -> + ?d("code_change(down) -> entry", []), + stop_backup_server(B), + S = {state, DB}, + {ok, S}; + +% upgrade +code_change(_Vsn, S, upgrade_from_pre_4_7) -> + ?d("code_change(up) -> entry", []), + {state, DB} = S, + S1 = #state{db = DB}, + {ok, S1}; + +code_change(_Vsn, S, _Extra) -> + ?d("code_change -> entry [do nothing]", []), + {ok, S}. + + +stop_backup_server(undefined) -> + ok; +stop_backup_server({Pid, _}) when is_pid(Pid) -> + exit(Pid, kill). + + + +%%----------------------------------------------------------------- +%% Trap operations (write, read, delete) +%%----------------------------------------------------------------- +%% A notification is stored as {Key, Value}, where +%% Key is the symbolic trap name, and Value is +%% a #trap or a #notification record. +%%----------------------------------------------------------------- +%% Returns: {value, Value} | undefined +%%----------------------------------------------------------------- +get_notif(Db, Key) -> + case snmpa_general_db:read(Db, {trap, Key}) of + {value,#symbol{info = Value}} -> {value, Value}; + false -> undefined + end. + +set_notif(Db, MibName, Trap) when is_record(Trap, trap) -> + #trap{trapname = Name} = Trap, + Rec = #symbol{key = {trap, Name}, mib_name = MibName, info = Trap}, + %% convert old v1 trap to oid + Oid = case Trap#trap.enterpriseoid of + ?snmp -> + ?snmpTraps ++ [Trap#trap.specificcode + 1]; + Oid0 -> + Oid0 ++ [0, Trap#trap.specificcode] + end, + write_alias(Name, Db, MibName, Oid), + snmpa_general_db:write(Db, Rec); +set_notif(Db, MibName, Trap) -> + #notification{trapname = Name, oid = Oid} = Trap, + Rec = #symbol{key = {trap, Name}, mib_name = MibName, info = Trap}, + write_alias(Name, Db, MibName, Oid), + snmpa_general_db:write(Db, Rec). + +delete_notif(Db, MibName) -> + Pattern = #symbol{key = {trap, '_'}, mib_name = MibName, info = '_'}, + snmpa_general_db:match_delete(Db, Pattern). + + +write_alias(AN, DB, MibName, Oid) -> + write_alias(AN, DB, [], MibName, Oid). + +write_alias(AN, DB, Enums, MibName, Oid) -> + ?vlog("add alias~n ~p -> {~p,~p}",[AN, Oid, Enums]), + Rec1 = #symbol{key = {alias, AN}, + mib_name = MibName, + info = {Oid,Enums}}, + snmpa_general_db:write(DB, Rec1), + ?vlog("add oid~n ~p -> ~p",[Oid, AN]), + Rec2 = #symbol{key = {oid, Oid}, + mib_name = MibName, + info = AN}, + snmpa_general_db:write(DB, Rec2). + +%% ------------------------------------- + +get_info(DB) -> + ProcSize = proc_mem(self()), + DbSz = tab_size(DB), + [{process_memory, ProcSize}, {db_memory, DbSz}]. + +proc_mem(P) when is_pid(P) -> + case (catch erlang:process_info(P, memory)) of + {memory, Sz} when is_integer(Sz) -> + Sz; + _ -> + undefined + end. +%% proc_mem(_) -> +%% undefined. + +tab_size(DB) -> + case (catch snmpa_general_db:info(DB, memory)) of + Sz when is_integer(Sz) -> + Sz; + _ -> + undefined + end. + + + +%% ------------------------------------- + +get_verbosity(L) -> + snmp_misc:get_option(verbosity,L,?default_verbosity). + +get_mib_storage(L) -> + snmp_misc:get_option(mib_storage,L,ets). + + +%% ------------------------------------- + +call(Req) -> + call(Req, infinity). + +call(Req, Timeout) -> + gen_server:call(?SERVER, Req, Timeout). + +cast(Msg) -> + gen_server:cast(?SERVER, Msg). + + +%% ---------------------------------------------------------------- + +info_msg(F, A) -> + error_logger:info_msg("~w: " ++ F ++ "~n", [?MODULE|A]). + +config_err(F, A) -> + snmpa_error:config_err(F, A). + |