%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2012. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(snmp_generic_mnesia).
-export([variable_get/1, variable_set/2, variable_inc/2]).
-export([table_func/2, table_func/4,
table_set_cols/4, table_set_element/4, table_set_elements/3,
table_get_elements/4, table_get_row/2, table_get_row/3,
table_next/2,table_set_status/7,
table_try_make_consistent/2,
table_delete_row/2]).
-include("STANDARD-MIB.hrl").
-include("snmp_types.hrl").
%% -include("snmp_generic.hrl").
%%%-----------------------------------------------------------------
%%% Generic functions for implementing software tables
%%% and variables. Mnesia is used.
%%%-----------------------------------------------------------------
%%------------------------------------------------------------------
%% Theses functions could be in the MIB for simple
%% variables or tables, i.e. vars without complex
%% set-operations. If there are complex set op, an
%% extra layer-function should be added, and that
%% function should be in the MIB, and it can call these
%% functions.
%%------------------------------------------------------------------
%%------------------------------------------------------------------
%% Variables
%%------------------------------------------------------------------
%%------------------------------------------------------------------
%% This is the default function for variables.
%%------------------------------------------------------------------
variable_get(Name) ->
case mnesia:dirty_read({snmp_variables, Name}) of
[{_Db, _Name, Val}] -> {value, Val};
_ -> undefined
end.
variable_set(Name, Val) ->
mnesia:dirty_write({snmp_variables, Name, Val}),
true.
variable_inc(Name, N) ->
case mnesia:dirty_update_counter({snmp_variables, Name}, N) of
NewVal when NewVal < 4294967296 ->
ok;
NewVal ->
mnesia:dirty_write({snmp_variables, Name, NewVal rem 4294967296})
end.
%%------------------------------------------------------------------
%% Tables
%% Assumes the RowStatus is the last column in the
%% table.
%%------------------------------------------------------------------
%%------------------------------------------------------------------
%% This is the default function for tables.
%%
%% Name is the name of the table (atom)
%% RowIndex is a flat list of the indexes for the row.
%% Cols is a list of column numbers.
%%------------------------------------------------------------------
table_func(new, _Name) ->
ok;
table_func(delete, _Name) ->
ok.
table_func(get, RowIndex, Cols, Name) ->
TableInfo = snmp_generic:table_info(Name),
snmp_generic:handle_table_get({Name, mnesia}, RowIndex, Cols,
TableInfo#table_info.first_own_index);
%%------------------------------------------------------------------
%% Returns: List of endOfTable | {NextOid, Value}.
%% Implements the next operation, with the function
%% handle_table_next. Next should return the next accessible
%% instance, which cannot be a key (well, it could, but it
%% shouldn't).
%%------------------------------------------------------------------
table_func(get_next, RowIndex, Cols, Name) ->
#table_info{first_accessible = FirstCol, first_own_index = FOI,
nbr_of_cols = LastCol} = snmp_generic:table_info(Name),
snmp_generic:handle_table_next({Name,mnesia},RowIndex,Cols,
FirstCol, FOI, LastCol);
table_func(is_set_ok, RowIndex, Cols, Name) ->
snmp_generic:table_try_row({Name, mnesia}, nofunc, RowIndex, Cols);
%%------------------------------------------------------------------
%% Cols is here a list of {ColumnNumber, NewValue}
%% This function must only be used by tables with a RowStatus col!
%% Other tables should use table_set_cols/4.
%% All set functionality is handled within a transaction.
%%
%% GenericMnesia uses its own table_set_status and own table_try_make_consistent
%% for performance reasons.
%%------------------------------------------------------------------
table_func(set, RowIndex, Cols, Name) ->
case mnesia:transaction(
fun() ->
snmp_generic:table_set_row(
{Name, mnesia}, nofunc,
fun table_try_make_consistent/2,
RowIndex, Cols)
end) of
{atomic, Value} ->
Value;
{aborted, Reason} ->
user_err("set transaction aborted. Tab ~w, RowIndex"
" ~w, Cols ~w. Reason ~w",
[Name, RowIndex, Cols, Reason]),
{Col, _Val} = hd(Cols),
{commitFailed, Col}
end;
table_func(undo, _RowIndex, _Cols, _Name) ->
{noError, 0}.
table_get_row(Name, RowIndex) ->
case mnesia:snmp_get_row(Name, RowIndex) of
{ok, DbRow} ->
TableInfo = snmp_generic:table_info(Name),
make_row(DbRow, TableInfo#table_info.first_own_index);
undefined ->
undefined
end.
table_get_row(Name, RowIndex, FOI) ->
case mnesia:snmp_get_row(Name, RowIndex) of
{ok, DbRow} ->
make_row(DbRow, FOI);
undefined ->
undefined
end.
%%-----------------------------------------------------------------
%% Returns: [Val | noacc | noinit] | undefined
%%-----------------------------------------------------------------
table_get_elements(Name, RowIndex, Cols, FirstOwnIndex) ->
case mnesia:snmp_get_row(Name, RowIndex) of
{ok, DbRow} ->
Row = make_row(DbRow, FirstOwnIndex),
get_elements(Cols, Row);
undefined ->
undefined
end.
get_elements([Col | Cols], Row) ->
[element(Col, Row) | get_elements(Cols, Row)];
get_elements([], _Row) -> [].
%%-----------------------------------------------------------------
%% Args: DbRow is a mnesia row ({name, Keys, Cols, ...}).
%% Returns: A tuple with a SNMP-table row. Each SNMP-col is one
%% element, list or int.
%%-----------------------------------------------------------------
make_row(DbRow, 0) ->
[_Name, _Keys | Cols] = tuple_to_list(DbRow),
list_to_tuple(Cols);
make_row(DbRow, FirstOwnIndex) ->
list_to_tuple(make_row2(make_row_list(DbRow), FirstOwnIndex)).
make_row2(RowList, 1) -> RowList;
make_row2([_OtherIndex | RowList], N) ->
make_row2(RowList, N-1).
make_row_list(Row) ->
make_row_list(size(Row), Row, []).
make_row_list(N, Row, Acc) when N > 2 ->
make_row_list(N-1, Row, [element(N, Row) | Acc]);
make_row_list(2, Row, Acc) ->
case element(2, Row) of
Keys when is_tuple(Keys) ->
lists:append(tuple_to_list(Keys), Acc);
Key ->
[Key | Acc]
end.
%% createAndGo
table_set_status(Name, RowIndex, ?'RowStatus_createAndGo', _StatusCol, Cols,
ChangedStatusFunc, _ConsFunc) ->
Row = table_construct_row(Name, RowIndex, ?'RowStatus_active', Cols),
mnesia:write(Row),
snmp_generic:try_apply(ChangedStatusFunc, [Name, ?'RowStatus_createAndGo',
RowIndex, Cols]);
%%------------------------------------------------------------------
%% createAndWait - set status to notReady, and try to
%% make row consistent.
%%------------------------------------------------------------------
table_set_status(Name, RowIndex, ?'RowStatus_createAndWait', _StatusCol,
Cols, ChangedStatusFunc, ConsFunc) ->
Row = table_construct_row(Name, RowIndex, ?'RowStatus_notReady', Cols),
mnesia:write(Row),
case snmp_generic:try_apply(ConsFunc, [RowIndex, Row]) of
{noError, 0} -> snmp_generic:try_apply(ChangedStatusFunc,
[Name, ?'RowStatus_createAndWait',
RowIndex, Cols]);
Error -> Error
end;
%% destroy
table_set_status(Name, RowIndex, ?'RowStatus_destroy', _StatusCol, Cols,
ChangedStatusFunc, _ConsFunc) ->
case snmp_generic:try_apply(ChangedStatusFunc,
[Name, ?'RowStatus_destroy', RowIndex, Cols]) of
{noError, 0} ->
#table_info{index_types = Indexes} = snmp_generic:table_info(Name),
Key =
case snmp_generic:split_index_to_keys(Indexes, RowIndex) of
[Key1] -> Key1;
KeyList -> list_to_tuple(KeyList)
end,
mnesia:delete({Name, Key}),
{noError, 0};
Error -> Error
end;
%% Otherwise, active or notInService
table_set_status(Name, RowIndex, Val, _StatusCol, Cols,
ChangedStatusFunc, ConsFunc) ->
table_set_cols(Name, RowIndex, Cols, ConsFunc),
snmp_generic:try_apply(ChangedStatusFunc, [Name, Val, RowIndex, Cols]).
table_delete_row(Name, RowIndex) ->
case mnesia:snmp_get_mnesia_key(Name, RowIndex) of
{ok, Key} ->
mnesia:delete({Name, Key});
undefined ->
ok
end.
%%------------------------------------------------------------------
%% This function is a simple consistency check
%% function which could be used by the user-defined
%% table functions.
%% Check if the row has all information needed to
%% make row notInService (from notReady). This is
%% a simple check, which just checks if some col
%% in the row has the value 'noinit'.
%% If it has the information, the status is changed
%% to notInService.
%%------------------------------------------------------------------
table_try_make_consistent(RowIndex, NewDbRow) ->
Name = element(1, NewDbRow),
#table_info{first_own_index = FirstOwnIndex,
status_col = StatusCol, index_types = IT} =
snmp_generic:table_info(Name),
if
is_integer(StatusCol) ->
NewRow = make_row(NewDbRow, FirstOwnIndex),
StatusVal = element(StatusCol, NewRow),
AddCol = if
FirstOwnIndex == 0 -> 2;
true -> 1 + FirstOwnIndex - length(IT)
end,
table_try_make_consistent(Name, RowIndex, NewRow, NewDbRow,
AddCol, StatusCol, StatusVal);
true ->
{noError, 0}
end.
table_try_make_consistent(Name, RowIndex, NewRow, NewDbRow,
AddCol, StatusCol, ?'RowStatus_notReady') ->
case lists:member(noinit, tuple_to_list(NewRow)) of
true -> {noError, 0};
false ->
table_set_element(Name, RowIndex, StatusCol,
?'RowStatus_notInService'),
NewDbRow2 = set_new_row([{StatusCol, ?'RowStatus_notInService'}],
AddCol, NewDbRow),
mnesia:write(NewDbRow2),
{noError, 0}
end;
table_try_make_consistent(_Name, _RowIndex, _NewRow, _NewDBRow,
_AddCol, _StatusCol, _StatusVal) ->
{noError, 0}.
%%------------------------------------------------------------------
%% Constructs a row that is to be stored in Mnesia, i.e.
%% {Name, Key, Col1, ...} |
%% {Name, {Key1, Key2, ..}, ColN, ColN+1...}
%% dynamic key values are stored without length first.
%% RowIndex is a list of the first elements. RowStatus is needed,
%% because the provided value may not be stored, e.g. createAndGo
%% should be active. If a value isn't specified in the Col list,
%% then the corresponding value will be noinit.
%%------------------------------------------------------------------
table_construct_row(Name, RowIndex, Status, Cols) ->
#table_info{nbr_of_cols = LastCol, index_types = Indexes,
defvals = Defs, status_col = StatusCol,
first_own_index = FirstOwnIndex, not_accessible = NoAccs} =
snmp_generic:table_info(Name),
KeyList = snmp_generic:split_index_to_keys(Indexes, RowIndex),
OwnKeyList = snmp_generic:get_own_indexes(FirstOwnIndex, KeyList),
StartCol = length(OwnKeyList) + 1,
RowList = snmp_generic:table_create_rest(StartCol, LastCol,
StatusCol, Status, Cols, NoAccs),
L = snmp_generic:init_defaults(Defs, RowList, StartCol),
Keys = case KeyList of
[H] -> H;
_ -> list_to_tuple(KeyList)
end,
list_to_tuple([Name, Keys | L]).
%%------------------------------------------------------------------
%% table_set_cols/4
%% can be used by the set procedure of all tables
%% to set all columns in Cols, one at a time.
%% ConsFunc is a check-consistency function, which will
%% be called with the RowIndex of this row, when
%% all columns are set. This is useful when the RowStatus
%% could change, e.g. if the manager has provided all
%% mandatory columns in this set operation.
%% If ConsFunc is nofunc, no function will be called after all
%% sets.
%% Returns: {noError, 0} | {Error, Col}
%%------------------------------------------------------------------
table_set_cols(Name, RowIndex, Cols, ConsFunc) ->
table_set_elements(Name, RowIndex, Cols, ConsFunc).
%%-----------------------------------------------------------------
%% Col is _not_ a key column. A row in the db is stored as
%% {Name, {Key1, Key2,...}, Col1, Col2, ...}
%%-----------------------------------------------------------------
table_set_element(Name, RowIndex, Col, NewVal) ->
#table_info{index_types = Indexes, first_own_index = FirstOwnIndex} =
snmp_generic:table_info(Name),
DbCol = if
FirstOwnIndex == 0 -> Col + 2;
true -> 1 + FirstOwnIndex - length(Indexes) + Col
end,
case mnesia:snmp_get_row(Name, RowIndex) of
{ok, DbRow} ->
NewDbRow = setelement(DbCol, DbRow, NewVal),
mnesia:write(NewDbRow),
true;
undefined ->
false
end.
table_set_elements(Name, RowIndex, Cols) ->
case table_set_elements(Name, RowIndex, Cols, nofunc) of
{noError, 0} -> true;
_ -> false
end.
table_set_elements(Name, RowIndex, Cols, ConsFunc) ->
#table_info{index_types = Indexes,
first_own_index = FirstOwnIndex} =
snmp_generic:table_info(Name),
AddCol = if
FirstOwnIndex == 0 -> 2;
true -> 1 + FirstOwnIndex - length(Indexes)
end,
case mnesia:snmp_get_row(Name, RowIndex) of
{ok, DbRow} ->
NewDbRow = set_new_row(Cols, AddCol, DbRow),
mnesia:write(NewDbRow),
snmp_generic:try_apply(ConsFunc, [RowIndex, NewDbRow]);
undefined ->
{Col, _Val} = hd(Cols),
{commitFailed, Col}
end.
set_new_row([{Col, Val} | Cols], AddCol, Row) ->
set_new_row(Cols, AddCol, setelement(Col+AddCol, Row, Val));
set_new_row([], _AddCol, Row) ->
Row.
table_next(Name, RestOid) ->
case mnesia:snmp_get_next_index(Name, RestOid) of
{ok, NextIndex} -> NextIndex;
endOfTable -> endOfTable
end.
user_err(F, A) ->
snmpa_error:user_err(F, A).