Erlang 20

8.代理实现示例 | 8. Agent Implementation Example

8 代理实现示例

实现示例部分描述了如何使用SNMP开发工具包实现MIB。

在工具包发行版中可以找到所示的示例。

代理是使用配置工具配置的,它使用默认的建议来处理除管理器节点之外的所有内容。

8.1 MIB

本例中使用的MIB称为EX1-MIB。它包含两个对象,一个带有名称的变量和一个带朋友的表。

EX1-MIB DEFINITIONS ::= BEGIN IMPORTS RowStatus FROM STANDARD-MIB DisplayString FROM RFC1213-MIB OBJECT-TYPE FROM RFC-1212 ; example1 OBJECT IDENTIFIER ::= { experimental 7 } myName OBJECT-TYPE SYNTAX DisplayString (SIZE (0..255)) ACCESS read-write STATUS mandatory DESCRIPTION "My own name" ::= { example1 1 } friendsTable OBJECT-TYPE SYNTAX SEQUENCE OF FriendsEntry ACCESS not-accessible STATUS mandatory DESCRIPTION "A list of friends." ::= { example1 4 } friendsEntry OBJECT-TYPE SYNTAX FriendsEntry ACCESS not-accessible STATUS mandatory DESCRIPTION "" INDEX { fIndex } ::= { friendsTable 1 } FriendsEntry ::= SEQUENCE { fIndex INTEGER, fName DisplayString, fAddress DisplayString, fStatus RowStatus } fIndex OBJECT-TYPE SYNTAX INTEGER ACCESS not-accessible STATUS mandatory DESCRIPTION "number of friend" ::= { friendsEntry 1 } fName OBJECT-TYPE SYNTAX DisplayString (SIZE (0..255)) ACCESS read-write STATUS mandatory DESCRIPTION "Name of friend" ::= { friendsEntry 2 } fAddress OBJECT-TYPE SYNTAX DisplayString (SIZE (0..255)) ACCESS read-write STATUS mandatory DESCRIPTION "Address of friend" ::= { friendsEntry 3 } fStatus OBJECT-TYPE SYNTAX RowStatus ACCESS read-write STATUS mandatory DESCRIPTION "The status of this conceptual row." ::= { friendsEntry 4 } fTrap TRAP-TYPE ENTERPRISE example1 VARIABLES { myName, fIndex } DESCRIPTION "This trap is sent when something happens to the friend specified by fIndex." ::= 1 END

8.2默认实现

在不编写任何仪器功能的情况下,我们可以编译MIB并使用它的默认实现。回想一下,在编译时,由“EX1-MIB.mib”导入的MIB必须存在并编译到当前目录(“./STANDARD-MIB.bin","/RFC1213-MIB.bin”)中。

unix> erl -config ./sys 1> application:start(snmp). ok 2> snmpc:compile("EX1-MIB"). No accessfunction for 'friendsTable', using default. No accessfunction for 'myName', using default. {ok, "EX1-MIB.bin"} 3> snmpa:load_mibs(snmp_master_agent, ["EX1-MIB"]). ok

这个MIB现在被加载到代理中,管理者可以提出问题。作为一个例子,我们在工具包中启动另一个Erlang系统和简单的Erlang管理器:

1> snmp_test_mgr:start_link([{agent,"dront.ericsson.se"},{community,"all-rights"}, %% making it understand symbolic names: {mibs,["EX1-MIB","STANDARD-MIB"]}]). {ok, <0.89.0>} %% a get-next request with one OID. 2> snmp_test_mgr:gn([[1,3,6,1,3,7]]). ok * Got PDU: [myName,0] = [] %% A set-request (now using symbolic names for convenience) 3> snmp_test_mgr:s([{[myName,0], "Martin"}]). ok * Got PDU: [myName,0] = "Martin" %% Try the same get-next request again 4> snmp_test_mgr:gn([[1,3,6,1,3,7]]). ok * Got PDU: [myName,0] = "Martin" %% ... and we got the new value. %% you can event do row operations. How to add a row: 5> snmp_test_mgr:s([{[fName,0], "Martin"}, {[fAddress,0],"home"}, {[fStatus,0],4}]). %% createAndGo ok * Got PDU: [fName,0] = "Martin" [fAddress,0] = "home" [fStatus,0] = 4 6> snmp_test_mgr:gn([[myName,0]]). ok * Got PDU: [fName,0] = "Martin" 7> snmp_test_mgr:gn(). ok * Got PDU: [fAddress,0] = "home" 8> snmp_test_mgr:gn(). ok * Got PDU: [fStatus,0] = 1 9>

8.3手册执行

以下示例显示了Erlang中EX1-MIB的“手动”实现。 在这个例子中,对象的值存储在Erlang服务器中。 服务器有一个2元组作为循环数据,其中第一个元素是变量myName的值,第二个元素是表friendsTable中的行的排序列表。 每行是一个4元组。

有更高效的手动创建表的方法,即使用模块snmp_index

代码

-module(ex1). -author('dummy@flop.org'). %% External exports -export([start/0, my_name/1, my_name/2, friends_table/3]). %% Internal exports -export([init/0]). -define(status_col, 4). -define(active, 1). -define(notInService, 2). -define(notReady, 3). -define(createAndGo, 4). % Action; written, not read -define(createAndWait, 5). % Action; written, not read -define(destroy, 6). % Action; written, not read start() -> spawn(ex1, init, []). %%---------------------------------------------------------------- %% Instrumentation function for variable myName. %% Returns: (get) {value, Name} %% (set) noError %%---------------------------------------------------------------- my_name(get) -> ex1_server ! {self(), get_my_name}, Name = wait_answer(), {value, Name}. my_name(set, NewName) -> ex1_server ! {self(), {set_my_name, NewName}}, noError. %%---------------------------------------------------------------- %% Instrumentation function for table friendsTable. %%---------------------------------------------------------------- friends_table(get, RowIndex, Cols) -> case get_row(RowIndex) of {ok, Row} -> get_cols(Cols, Row _ -> {noValue, noSuchInstance} end; friends_table(get_next, RowIndex, Cols) -> case get_next_row(RowIndex) of {ok, Row} -> get_next_cols(Cols, Row _ -> case get_next_row([]) of {ok, Row} -> % Get next cols from first row. NewCols = add_one_to_cols(Cols), get_next_cols(NewCols, Row _ -> end_of_table(Cols) end end; %%---------------------------------------------------------------- %% If RowStatus is set, then: %% *) If set to destroy, check that row does exist %% *) If set to createAndGo, check that row does not exist AND %% that all columns are given values. %% *) Otherwise, error (for simplicity). %% Otherwise, row is modified; check that row exists. %%---------------------------------------------------------------- friends_table(is_set_ok, RowIndex, Cols) -> RowExists = case get_row(RowIndex) of {ok, _Row} -> true; _ -> false end, case is_row_status_col_changed(Cols) of {true, ?destroy} when RowExists == true -> {noError, 0}; {true, ?createAndGo} when RowExists == false, length(Cols) == 3 -> {noError, 0}; {true, _} -> {inconsistentValue, ?status_col}; false when RowExists == true -> {noError, 0}; _ -> [{Col, _NewVal} | _Cols] = Cols, {inconsistentName, Col} end; friends_table(set, RowIndex, Cols) -> case is_row_status_col_changed(Cols) of {true, ?destroy} -> ex1_server ! {self(), {delete_row, RowIndex}}; {true, ?createAndGo} -> NewRow = make_row(RowIndex, Cols), ex1_server ! {self(), {add_row, NewRow}}; false -> {ok, Row} = get_row(RowIndex), NewRow = merge_rows(Row, Cols), ex1_server ! {self(), {delete_row, RowIndex}}, ex1_server ! {self(), {add_row, NewRow}} end, {noError, 0}. %%---------------------------------------------------------------- %% Make a list of {value, Val} of the Row and Cols list. %%---------------------------------------------------------------- get_cols([Col | Cols], Row) -> [{value, element(Col, Row)} | get_cols(Cols, Row)]; get_cols([], _Row) -> []. %%---------------------------------------------------------------- %% As get_cols, but the Cols list may contain invalid column %% numbers. If it does, we must find the next valid column, %% or return endOfTable. %%---------------------------------------------------------------- get_next_cols([Col | Cols], Row) when Col < 2 -> [{[2, element(1, Row)], element(2, Row)} | get_next_cols(Cols, Row)]; get_next_cols([Col | Cols], Row) when Col > 4 -> [endOfTable | get_next_cols(Cols, Row)]; get_next_cols([Col | Cols], Row) -> [{[Col, element(1, Row)], element(Col, Row)} | get_next_cols(Cols, Row)]; get_next_cols([], _Row) -> []. %%---------------------------------------------------------------- %% Make a list of endOfTable with as many elems as Cols list. %%---------------------------------------------------------------- end_of_table([Col | Cols]) -> [endOfTable | end_of_table(Cols)]; end_of_table([]) -> []. add_one_to_cols([Col | Cols]) -> [Col + 1 | add_one_to_cols(Cols)]; add_one_to_cols([]) -> []. is_row_status_col_changed(Cols) -> case lists:keysearch(?status_col, 1, Cols) of {value, {?status_col, StatusVal}} -> {true, StatusVal}; _ -> false end. get_row(RowIndex) -> ex1_server ! {self(), {get_row, RowIndex}}, wait_answer(). get_next_row(RowIndex) -> ex1_server ! {self(), {get_next_row, RowIndex}}, wait_answer(). wait_answer() -> receive {ex1_server, Answer} -> Answer end. %%%--------------------------------------------------------------- %%% Server code follows %%%--------------------------------------------------------------- init() -> register(ex1_server, self()), loop("", []). loop(MyName, Table) -> receive {From, get_my_name} -> From ! {ex1_server, MyName}, loop(MyName, Table {From, {set_my_name, NewName}} -> loop(NewName, Table {From, {get_row, RowIndex}} -> Res = table_get_row(Table, RowIndex), From ! {ex1_server, Res}, loop(MyName, Table {From, {get_next_row, RowIndex}} -> Res = table_get_next_row(Table, RowIndex), From ! {ex1_server, Res}, loop(MyName, Table {From, {delete_row, RowIndex}} -> NewTable = table_delete_row(Table, RowIndex), loop(MyName, NewTable {From, {add_row, NewRow}} -> NewTable = table_add_row(Table, NewRow), loop(MyName, NewTable) end. %%%--------------------------------------------------------------- %%% Functions for table operations. The table is represented as %%% a list of rows. %%%--------------------------------------------------------------- table_get_row([{Index, Name, Address, Status} | _], [Index]) -> {ok, {Index, Name, Address, Status}}; table_get_row([H | T], RowIndex) -> table_get_row(T, RowIndex table_get_row([], _RowIndex) -> no_such_row. table_get_next_row([Row | T], []) -> {ok, Row}; table_get_next_row([Row | T], [Index | _]) when element(1, Row) > Index -> {ok, Row}; table_get_next_row([Row | T], RowIndex) -> table_get_next_row(T, RowIndex table_get_next_row([], RowIndex) -> endOfTable. table_delete_row([{Index, _, _, _} | T], [Index]) -> T; table_delete_row([H | T], RowIndex) -> [H | table_delete_row(T, RowIndex)]; table_delete_row([], _RowIndex) -> []. table_add_row([Row | T], NewRow) when element(1, Row) > element(1, NewRow) -> [NewRow, Row | T]; table_add_row([H | T], NewRow) -> [H | table_add_row(T, NewRow)]; table_add_row([], NewRow) -> [NewRow]. make_row([Index], [{2, Name}, {3, Address} | _]) -> {Index, Name, Address, ?active}. merge_rows(Row, [{Col, NewVal} | T]) -> merge_rows(setelement(Col, Row, NewVal), T merge_rows(Row, []) -> Row.

关联文件

EX1-MIB.funcs真正实现的关联文件如下所示:

{myName, {ex1, my_name, []}}. {friendsTable, {ex1, friends_table, []}}.

抄本

要使用真正的实现,我们必须重新编译MIB并将其加载到代理中。

1> application:start(snmp). ok 2> snmpc:compile("EX1-MIB"). {ok,"EX1-MIB.bin"} 3> snmpa:load_mibs(snmp_master_agent, ["EX1-MIB"]). ok 4> ex1:start(). <0.115.0> %% Now all requests operates on this "real" implementation. %% The output from the manager requests will *look* exactly the %% same as for the default implementation.

陷阱发送

本节介绍如何通过从主代理发送fTrap发送陷阱。 主代理已加载MIB EX1-MIB,其中定义了陷阱。 此陷阱指定应将两个变量与陷阱myName和fIndex一起发送。 fIndex是一个表列,所以我们必须为snmpa:send_trap / 4的调用中的行提供其值和索引。 在下面的例子中,我们假设有问题的行索引2(fIndex 2的行)。

我们使用一个简单的ErlangSNMP管理器,它可以接收陷阱。

[MANAGER] 1> snmp_test_mgr:start_link([{agent,"dront.ericsson.se"},{community,"public"} %% does not have write-access 1>{mibs,["EX1-MIB","STANDARD-MIB"]}]). {ok, <0.100.0>} 2> snmp_test_mgr:s([{[myName,0], "Klas"}]). ok * Got PDU: Received a trap: Generic: 4 %% authenticationFailure Enterprise: [iso,2,3] Specific: 0 Agent addr: [123,12,12,21] TimeStamp: 42993 2> [AGENT] 3> snmpa:send_trap(snmp_master_agent, fTrap,"standard trap", [{fIndex,[2],2}]). [MANAGER] 2> * Got PDU: Received a trap: Generic: 6 Enterprise: [example1] Specific: 1 Agent addr: [123,12,12,21] TimeStamp: 69649 [myName,0] = "Martin" [fIndex,2] = 2 2>