/lib/common_test/src/ct_netconfc.erl
Erlang | 1818 lines | 1389 code | 170 blank | 259 comment | 6 complexity | 04ec3c3e2e5a7b084dd59196da8a63cc MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception, Apache-2.0
Large files files are truncated, but you can click here to view the full file
- %%----------------------------------------------------------------------
- %% %CopyrightBegin%
- %%
- %% Copyright Ericsson AB 2012-2017. 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%
- %%
- %%----------------------------------------------------------------------
- %% File: ct_netconfc.erl
- %%
- %% Description:
- %% This file contains the Netconf client interface
- %%
- %% Netconf servers can be configured by adding the following statement
- %% to a configuration file:
- %%
- %% {server_id(),options()}.
- %%
- %% The server_id() or an associated ct:target_name() shall then be
- %% used in calls to open/2 connect/2.
- %%
- %% If no configuration exists for a server, use open/1 and connect/1.
- %%
- %% == Logging ==
- %%
- %% The netconf server uses the `error_logger' for logging of netconf
- %% traffic. A special purpose error handler is implemented in
- %% `ct_conn_log_h'. To use this error handler, add the `cth_conn_log'
- %% hook in your test suite, e.g.
- %%
- %% suite() ->
- %% [{ct_hooks, [{cth_conn_log, [{ct:conn_log_mod(),ct:conn_log_options()}]}]}].
- %%
- %% For example:
- %%
- %% suite() ->
- %% [{ct_hooks,
- %% [{cth_conn_log,[{ct_netconfc,[{log_type,pretty},
- %% {hosts,[my_configured_server]}]}]}
- %%
- %% == Notifications ==
- %%
- %% The netconf client is also compliant with RFC5277 NETCONF Event
- %% Notifications, which defines a mechanism for an asynchronous
- %% message notification delivery service for the netconf protocol.
- %%
- %% Specific functions to support this are create_subscription/6
- %% get_event_streams/3. (The functions also exist with other arities.)
- %%
- %%----------------------------------------------------------------------
- -module(ct_netconfc).
- -include("ct_netconfc.hrl").
- -include("ct_util.hrl").
- -include_lib("xmerl/include/xmerl.hrl").
- %%----------------------------------------------------------------------
- %% External exports
- %%----------------------------------------------------------------------
- -export([connect/1,
- connect/2,
- disconnect/1,
- session/1,
- session/2,
- session/3,
- open/1,
- open/2,
- only_open/1,
- only_open/2,
- hello/1,
- hello/2,
- hello/3,
- close_session/1,
- close_session/2,
- kill_session/2,
- kill_session/3,
- send/2,
- send/3,
- send_rpc/2,
- send_rpc/3,
- lock/2,
- lock/3,
- unlock/2,
- unlock/3,
- get/2,
- get/3,
- get_config/3,
- get_config/4,
- edit_config/3,
- edit_config/4,
- edit_config/5,
- delete_config/2,
- delete_config/3,
- copy_config/3,
- copy_config/4,
- action/2,
- action/3,
- create_subscription/1,
- create_subscription/2,
- create_subscription/3,
- create_subscription/4,
- create_subscription/5,
- create_subscription/6,
- get_event_streams/1,
- get_event_streams/2,
- get_event_streams/3,
- get_capabilities/1,
- get_capabilities/2,
- get_session_id/1,
- get_session_id/2]).
- %%----------------------------------------------------------------------
- %% Exported types
- %%----------------------------------------------------------------------
- -export_type([client/0,
- handle/0,
- notification/0]).
- %%----------------------------------------------------------------------
- %% Internal exports
- %%----------------------------------------------------------------------
- %% ct_gen_conn callbacks
- -export([init/3,
- handle_msg/3,
- handle_msg/2,
- terminate/2,
- close/1]).
- %% ct_conn_log callback
- -export([format_data/2]).
- %%----------------------------------------------------------------------
- %% Internal defines
- %%----------------------------------------------------------------------
- -define(APPLICATION,?MODULE).
- -define(DEFAULT_STREAM,"NETCONF").
- -define(error(ConnName,Report),
- error_logger:error_report([{ct_connection,ConnName},
- {client,self()},
- {module,?MODULE},
- {line,?LINE} |
- Report])).
- -define(is_timeout(T), (is_integer(T) orelse T==infinity)).
- -define(is_filter(F),
- (?is_simple_xml(F)
- orelse (F==[])
- orelse (is_list(F) andalso ?is_simple_xml(hd(F))))).
- -define(is_simple_xml(Xml),
- (is_atom(Xml) orelse (is_tuple(Xml) andalso is_atom(element(1,Xml))))).
- -define(is_string(S), (is_list(S) andalso is_integer(hd(S)))).
- %%----------------------------------------------------------------------
- %% Records
- %%----------------------------------------------------------------------
- %% Client state
- -record(state, {host,
- port,
- connection, % #connection
- capabilities,
- session_id,
- msg_id = 1,
- hello_status,
- no_end_tag_buff = <<>>,
- buff = <<>>,
- pending = [], % [#pending]
- event_receiver}).% pid
- %% Run-time client options.
- -record(options, {ssh = [], % Options for the ssh application
- host,
- port = ?DEFAULT_PORT,
- timeout = ?DEFAULT_TIMEOUT,
- name,
- type}).
- %% Connection reference
- -record(connection, {reference, % {CM,Ch}
- host,
- port,
- name,
- type}).
- %% Pending replies from server
- -record(pending, {tref, % timer ref (returned from timer:xxx)
- ref, % pending ref
- msg_id,
- op,
- caller}).% pid which sent the request
- %%----------------------------------------------------------------------
- %% Type declarations
- %%----------------------------------------------------------------------
- -type client() :: handle() | server_id() | ct:target_name().
- -opaque handle() :: pid().
- -type options() :: [option()].
- -type option() :: {ssh,host()} | {port,inet:port_number()} | {user,string()} |
- {password,string()} | {user_dir,string()} |
- {timeout,timeout()}.
- -type session_options() :: [session_option()].
- -type session_option() :: {timeout,timeout()}.
- -type host() :: inet:hostname() | inet:ip_address().
- -type notification() :: {notification, xml_attributes(), notification_content()}.
- -type notification_content() :: [event_time()|simple_xml()].
- -type event_time() :: {eventTime,xml_attributes(),[xs_datetime()]}.
- -type stream_name() :: string().
- -type streams() :: [{stream_name(),[stream_data()]}].
- -type stream_data() :: {description,string()} |
- {replaySupport,string()} |
- {replayLogCreationTime,string()} |
- {replayLogAgedTime,string()}.
- %% See XML Schema for Event Notifications found in RFC5277 for further
- %% detail about the data format for the string values.
- -type error_reason() :: term().
- -type server_id() :: atom().
- -type simple_xml() :: {xml_tag(), xml_attributes(), xml_content()} |
- {xml_tag(), xml_content()} |
- xml_tag().
- -type xml_tag() :: atom().
- -type xml_attributes() :: [{xml_attribute_tag(),xml_attribute_value()}].
- -type xml_attribute_tag() :: atom().
- -type xml_attribute_value() :: string().
- -type xml_content() :: [simple_xml() | iolist()].
- -type xpath() :: {xpath,string()}.
- -type netconf_db() :: running | startup | candidate.
- -type xs_datetime() :: string().
- %% This date and time identifyer has the same format as the XML type
- %% dateTime and compliant to RFC3339. The format is
- %% "[-]CCYY-MM-DDThh:mm:ss[.s][Z|(+|-)hh:mm]"
- %%----------------------------------------------------------------------
- %% External interface functions
- %%----------------------------------------------------------------------
- %%----------------------------------------------------------------------
- %% Open an SSH connection to a Netconf server
- %% If the server options are specified in a configuration file, use
- %% open/2.
- -spec connect(Options) -> Result when
- Options :: options(),
- Result :: {ok,handle()} | {error,error_reason()}.
- connect(Options) ->
- do_connect(Options, #options{type=connection},[]).
- -spec connect(KeyOrName,ExtraOptions) -> Result when
- KeyOrName :: ct:key_or_name(),
- ExtraOptions :: options(),
- Result :: {ok,handle()} | {error,error_reason()}.
- connect(KeyOrName, ExtraOptions) ->
- SortedExtra = lists:keysort(1,ExtraOptions),
- SortedConfig = lists:keysort(1,ct:get_config(KeyOrName,[])),
- AllOpts = lists:ukeymerge(1,SortedConfig,SortedExtra),
- do_connect(AllOpts,#options{name=KeyOrName,type=connection},[{name,KeyOrName}]).
- do_connect(OptList,InitOptRec,NameOpt) ->
- case check_options(OptList,InitOptRec) of
- {Host,Port,Options} ->
- ct_gen_conn:start({Host,Port},Options,?MODULE,
- NameOpt ++ [{reconnect,false},
- {use_existing_connection,false},
- {forward_messages,false}]);
- Error ->
- Error
- end.
- %%----------------------------------------------------------------------
- %% Close the given SSH connection.
- -spec disconnect(Conn) -> ok | {error,error_reason()} when
- Conn :: handle().
- disconnect(Conn) ->
- case call(Conn,get_ssh_connection) of
- {ok,_} ->
- ct_gen_conn:stop(Conn);
- Error ->
- Error
- end.
- %%----------------------------------------------------------------------
- %% Open a netconf session as a channel on the given SSH connection,
- %% and exchange `hello' messages.
- -spec session(Conn) -> Result when
- Conn :: handle(),
- Result :: {ok,handle()} | {error,error_reason()}.
- session(Conn) ->
- do_session(Conn,[],#options{type=channel},[]).
- -spec session(Conn,Options) -> Result when
- Conn :: handle(),
- Options :: session_options(),
- Result :: {ok,handle()} | {error,error_reason()};
- (KeyOrName,Conn) -> Result when
- KeyOrName :: ct:key_or_name(),
- Conn :: handle(),
- Result :: {ok,handle()} | {error,error_reason()}.
- session(Conn,Options) when is_list(Options) ->
- do_session(Conn,Options,#options{type=channel},[]);
- session(KeyOrName,Conn) ->
- do_session(Conn,[],#options{name=KeyOrName,type=channel},[{name,KeyOrName}]).
- -spec session(KeyOrName,Conn,Options) -> Result when
- Conn :: handle(),
- Options :: session_options(),
- KeyOrName :: ct:key_or_name(),
- Result :: {ok,handle()} | {error,error_reason()}.
- session(KeyOrName,Conn,ExtraOptions) ->
- SortedExtra = lists:keysort(1,ExtraOptions),
- SortedConfig = lists:keysort(1,ct:get_config(KeyOrName,[])),
- AllOpts = lists:ukeymerge(1,SortedConfig,SortedExtra),
- do_session(Conn,AllOpts,#options{name=KeyOrName,type=channel},
- [{name,KeyOrName}]).
- do_session(Conn,OptList,InitOptRec,NameOpt) ->
- case call(Conn,get_ssh_connection) of
- {ok,SshConn} ->
- case check_session_options(OptList,InitOptRec) of
- {ok,Options} ->
- case ct_gen_conn:start(SshConn,Options,?MODULE,
- NameOpt ++
- [{reconnect,false},
- {use_existing_connection,false},
- {forward_messages,true}]) of
- {ok,Client} ->
- case hello(Client,Options#options.timeout) of
- ok ->
- {ok,Client};
- Error ->
- Error
- end;
- Error ->
- Error
- end;
- Error ->
- Error
- end;
- Error ->
- Error
- end.
- %%----------------------------------------------------------------------
- %% Open a netconf session and exchange 'hello' messages.
- %% If the server options are specified in a configuration file, use
- %% open/2.
- -spec open(Options) -> Result when
- Options :: options(),
- Result :: {ok,handle()} | {error,error_reason()}.
- open(Options) ->
- open(Options,#options{type=connection_and_channel},[],true).
- -spec open(KeyOrName, ExtraOptions) -> Result when
- KeyOrName :: ct:key_or_name(),
- ExtraOptions :: options(),
- Result :: {ok,handle()} | {error,error_reason()}.
- open(KeyOrName, ExtraOpts) ->
- open(KeyOrName, ExtraOpts, true).
- open(KeyOrName, ExtraOpts, Hello) ->
- SortedExtra = lists:keysort(1,ExtraOpts),
- SortedConfig = lists:keysort(1,ct:get_config(KeyOrName,[])),
- AllOpts = lists:ukeymerge(1,SortedConfig,SortedExtra),
- open(AllOpts,#options{name=KeyOrName,type=connection_and_channel},
- [{name,KeyOrName}],Hello).
- open(OptList,InitOptRec,NameOpt,Hello) ->
- case check_options(OptList,InitOptRec) of
- {Host,Port,Options} ->
- case ct_gen_conn:start({Host,Port},Options,?MODULE,
- NameOpt ++ [{reconnect,false},
- {use_existing_connection,false},
- {forward_messages,true}]) of
- {ok,Client} when Hello==true ->
- case hello(Client,Options#options.timeout) of
- ok ->
- {ok,Client};
- Error ->
- Error
- end;
- Other ->
- Other
- end;
- Error ->
- Error
- end.
- %%----------------------------------------------------------------------
- %% As open/1,2, except no 'hello' message is sent.
- -spec only_open(Options) -> Result when
- Options :: options(),
- Result :: {ok,handle()} | {error,error_reason()}.
- only_open(Options) ->
- open(Options,#options{type=connection_and_channel},[],false).
- -spec only_open(KeyOrName,ExtraOptions) -> Result when
- KeyOrName :: ct:key_or_name(),
- ExtraOptions :: options(),
- Result :: {ok,handle()} | {error,error_reason()}.
- only_open(KeyOrName, ExtraOpts) ->
- open(KeyOrName, ExtraOpts, false).
- %%----------------------------------------------------------------------
- %% Send a 'hello' message.
- -spec hello(Client) -> Result when
- Client :: handle(),
- Result :: ok | {error,error_reason()}.
- hello(Client) ->
- hello(Client,[],?DEFAULT_TIMEOUT).
- -spec hello(Client,Timeout) -> Result when
- Client :: handle(),
- Timeout :: timeout(),
- Result :: ok | {error,error_reason()}.
- hello(Client,Timeout) ->
- hello(Client,[],Timeout).
- -spec hello(Client,Options,Timeout) -> Result when
- Client :: handle(),
- Options :: [{capability, [string()]}],
- Timeout :: timeout(),
- Result :: ok | {error,error_reason()}.
- hello(Client,Options,Timeout) ->
- call(Client, {hello, Options, Timeout}).
- %%----------------------------------------------------------------------
- %% Get the session id for the session specified by Client.
- -spec get_session_id(Client) -> Result when
- Client :: client(),
- Result :: pos_integer() | {error,error_reason()}.
- get_session_id(Client) ->
- get_session_id(Client, ?DEFAULT_TIMEOUT).
- -spec get_session_id(Client, Timeout) -> Result when
- Client :: client(),
- Timeout :: timeout(),
- Result :: pos_integer() | {error,error_reason()}.
- get_session_id(Client, Timeout) ->
- call(Client, get_session_id, Timeout).
- %%----------------------------------------------------------------------
- %% Get the server side capabilities.
- -spec get_capabilities(Client) -> Result when
- Client :: client(),
- Result :: [string()] | {error,error_reason()}.
- get_capabilities(Client) ->
- get_capabilities(Client, ?DEFAULT_TIMEOUT).
- -spec get_capabilities(Client, Timeout) -> Result when
- Client :: client(),
- Timeout :: timeout(),
- Result :: [string()] | {error,error_reason()}.
- get_capabilities(Client, Timeout) ->
- call(Client, get_capabilities, Timeout).
- %%----------------------------------------------------------------------
- %% Send an XML document to the server.
- -spec send(Client, SimpleXml) -> Result when
- Client :: client(),
- SimpleXml :: simple_xml(),
- Result :: simple_xml() | {error,error_reason()}.
- send(Client, SimpleXml) ->
- send(Client, SimpleXml, ?DEFAULT_TIMEOUT).
- -spec send(Client, SimpleXml, Timeout) -> Result when
- Client :: client(),
- SimpleXml :: simple_xml(),
- Timeout :: timeout(),
- Result :: simple_xml() | {error,error_reason()}.
- send(Client, SimpleXml, Timeout) ->
- call(Client,{send, Timeout, SimpleXml}).
- %%----------------------------------------------------------------------
- %% Wrap the given XML document in a valid netconf 'rpc' request and
- %% send to the server.
- -spec send_rpc(Client, SimpleXml) -> Result when
- Client :: client(),
- SimpleXml :: simple_xml(),
- Result :: [simple_xml()] | {error,error_reason()}.
- send_rpc(Client, SimpleXml) ->
- send_rpc(Client, SimpleXml, ?DEFAULT_TIMEOUT).
- -spec send_rpc(Client, SimpleXml, Timeout) -> Result when
- Client :: client(),
- SimpleXml :: simple_xml(),
- Timeout :: timeout(),
- Result :: [simple_xml()] | {error,error_reason()}.
- send_rpc(Client, SimpleXml, Timeout) ->
- call(Client,{send_rpc, SimpleXml, Timeout}).
- %%----------------------------------------------------------------------
- %% Send a 'lock' request.
- -spec lock(Client, Target) -> Result when
- Client :: client(),
- Target :: netconf_db(),
- Result :: ok | {error,error_reason()}.
- lock(Client, Target) ->
- lock(Client, Target,?DEFAULT_TIMEOUT).
- -spec lock(Client, Target, Timeout) -> Result when
- Client :: client(),
- Target :: netconf_db(),
- Timeout :: timeout(),
- Result :: ok | {error,error_reason()}.
- lock(Client, Target, Timeout) ->
- call(Client,{send_rpc_op,lock,[Target],Timeout}).
- %%----------------------------------------------------------------------
- %% Send a 'unlock' request.
- -spec unlock(Client, Target) -> Result when
- Client :: client(),
- Target :: netconf_db(),
- Result :: ok | {error,error_reason()}.
- unlock(Client, Target) ->
- unlock(Client, Target,?DEFAULT_TIMEOUT).
- -spec unlock(Client, Target, Timeout) -> Result when
- Client :: client(),
- Target :: netconf_db(),
- Timeout :: timeout(),
- Result :: ok | {error,error_reason()}.
- unlock(Client, Target, Timeout) ->
- call(Client, {send_rpc_op, unlock, [Target], Timeout}).
- %%----------------------------------------------------------------------
- %% Send a 'get' request.
- -spec get(Client, Filter) -> Result when
- Client :: client(),
- Filter :: simple_xml() | xpath(),
- Result :: {ok,[simple_xml()]} | {error,error_reason()}.
- get(Client, Filter) ->
- get(Client, Filter, ?DEFAULT_TIMEOUT).
- -spec get(Client, Filter, Timeout) -> Result when
- Client :: client(),
- Filter :: simple_xml() | xpath(),
- Timeout :: timeout(),
- Result :: {ok,[simple_xml()]} | {error,error_reason()}.
- get(Client, Filter, Timeout) ->
- call(Client,{send_rpc_op, get, [Filter], Timeout}).
- %%----------------------------------------------------------------------
- %% Send a 'get-config' request.
- -spec get_config(Client, Source, Filter) -> Result when
- Client :: client(),
- Source :: netconf_db(),
- Filter :: simple_xml() | xpath(),
- Result :: {ok,[simple_xml()]} | {error,error_reason()}.
- get_config(Client, Source, Filter) ->
- get_config(Client, Source, Filter, ?DEFAULT_TIMEOUT).
- -spec get_config(Client, Source, Filter, Timeout) -> Result when
- Client :: client(),
- Source :: netconf_db(),
- Filter :: simple_xml() | xpath(),
- Timeout :: timeout(),
- Result :: {ok,[simple_xml()]} | {error,error_reason()}.
- get_config(Client, Source, Filter, Timeout) ->
- call(Client, {send_rpc_op, get_config, [Source, Filter], Timeout}).
- %%----------------------------------------------------------------------
- %% Send a 'edit-config' request.
- -spec edit_config(Client, Target, Config) -> Result when
- Client :: client(),
- Target :: netconf_db(),
- Config :: simple_xml(),
- Result :: ok | {error,error_reason()}.
- edit_config(Client, Target, Config) ->
- edit_config(Client, Target, Config, ?DEFAULT_TIMEOUT).
- -spec edit_config(Client, Target, Config, OptParams) -> Result when
- Client :: client(),
- Target :: netconf_db(),
- Config :: simple_xml(),
- OptParams :: [simple_xml()],
- Result :: ok | {error,error_reason()};
- (Client, Target, Config, Timeout) -> Result when
- Client :: client(),
- Target :: netconf_db(),
- Config :: simple_xml(),
- Timeout :: timeout(),
- Result :: ok | {error,error_reason()}.
- edit_config(Client, Target, Config, Timeout) when ?is_timeout(Timeout) ->
- edit_config(Client, Target, Config, [], Timeout);
- edit_config(Client, Target, Config, OptParams) when is_list(OptParams) ->
- edit_config(Client, Target, Config, OptParams, ?DEFAULT_TIMEOUT).
- -spec edit_config(Client, Target, Config, OptParams, Timeout) -> Result when
- Client :: client(),
- Target :: netconf_db(),
- Config :: simple_xml(),
- OptParams :: [simple_xml()],
- Timeout :: timeout(),
- Result :: ok | {error,error_reason()}.
- edit_config(Client, Target, Config, OptParams, Timeout) ->
- call(Client, {send_rpc_op, edit_config, [Target,Config,OptParams], Timeout}).
- %%----------------------------------------------------------------------
- %% Send a 'delete-config' request.
- -spec delete_config(Client, Target) -> Result when
- Client :: client(),
- Target :: startup | candidate,
- Result :: ok | {error,error_reason()}.
- delete_config(Client, Target) ->
- delete_config(Client, Target, ?DEFAULT_TIMEOUT).
- -spec delete_config(Client, Target, Timeout) -> Result when
- Client :: client(),
- Target :: startup | candidate,
- Timeout :: timeout(),
- Result :: ok | {error,error_reason()}.
- delete_config(Client, Target, Timeout) when Target == startup;
- Target == candidate ->
- call(Client,{send_rpc_op, delete_config, [Target], Timeout}).
- %%----------------------------------------------------------------------
- %% Send a 'copy-config' request.
- -spec copy_config(Client, Target, Source) -> Result when
- Client :: client(),
- Target :: netconf_db(),
- Source :: netconf_db(),
- Result :: ok | {error,error_reason()}.
- copy_config(Client, Source, Target) ->
- copy_config(Client, Source, Target, ?DEFAULT_TIMEOUT).
- -spec copy_config(Client, Target, Source, Timeout) -> Result when
- Client :: client(),
- Target :: netconf_db(),
- Source :: netconf_db(),
- Timeout :: timeout(),
- Result :: ok | {error,error_reason()}.
- copy_config(Client, Target, Source, Timeout) ->
- call(Client,{send_rpc_op, copy_config, [Target, Source], Timeout}).
- %%----------------------------------------------------------------------
- %% Execute an action.
- -spec action(Client, Action) -> Result when
- Client :: client(),
- Action :: simple_xml(),
- Result :: ok | {ok,[simple_xml()]} | {error,error_reason()}.
- action(Client,Action) ->
- action(Client,Action,?DEFAULT_TIMEOUT).
- -spec action(Client, Action, Timeout) -> Result when
- Client :: client(),
- Action :: simple_xml(),
- Timeout :: timeout(),
- Result :: ok | {ok,[simple_xml()]} | {error,error_reason()}.
- action(Client,Action,Timeout) ->
- call(Client,{send_rpc_op, action, [Action], Timeout}).
- %%----------------------------------------------------------------------
- %% Send a 'create-subscription' request
- %% See RFC5277, NETCONF Event Notifications
- -spec create_subscription(Client) -> Result when
- Client :: client(),
- Result :: ok | {error,error_reason()}.
- create_subscription(Client) ->
- create_subscription(Client,?DEFAULT_STREAM,?DEFAULT_TIMEOUT).
- -spec create_subscription(Client, Stream | Filter | Timeout) -> Result when
- Client :: client(),
- Stream :: stream_name(),
- Filter :: simple_xml() | [simple_xml()],
- Timeout :: timeout(),
- Result :: ok | {error,error_reason()}.
- create_subscription(Client,Timeout)
- when ?is_timeout(Timeout) ->
- create_subscription(Client,?DEFAULT_STREAM,Timeout);
- create_subscription(Client,Stream)
- when ?is_string(Stream) ->
- create_subscription(Client,Stream,?DEFAULT_TIMEOUT);
- create_subscription(Client,Filter)
- when ?is_filter(Filter) ->
- create_subscription(Client,?DEFAULT_STREAM,Filter,
- ?DEFAULT_TIMEOUT).
- create_subscription(Client,Stream,Timeout)
- when ?is_string(Stream) andalso
- ?is_timeout(Timeout) ->
- call(Client,{send_rpc_op,{create_subscription,self()},
- [Stream,undefined,undefined,undefined],
- Timeout});
- create_subscription(Client,StartTime,StopTime)
- when ?is_string(StartTime) andalso
- ?is_string(StopTime) ->
- create_subscription(Client,?DEFAULT_STREAM,StartTime,StopTime,
- ?DEFAULT_TIMEOUT);
- create_subscription(Client,Filter,Timeout)
- when ?is_filter(Filter) andalso
- ?is_timeout(Timeout) ->
- create_subscription(Client,?DEFAULT_STREAM,Filter,Timeout);
- create_subscription(Client,Stream,Filter)
- when ?is_string(Stream) andalso
- ?is_filter(Filter) ->
- create_subscription(Client,Stream,Filter,?DEFAULT_TIMEOUT).
- create_subscription(Client,StartTime,StopTime,Timeout)
- when ?is_string(StartTime) andalso
- ?is_string(StopTime) andalso
- ?is_timeout(Timeout) ->
- create_subscription(Client,?DEFAULT_STREAM,StartTime,StopTime,Timeout);
- create_subscription(Client,Stream,StartTime,StopTime)
- when ?is_string(Stream) andalso
- ?is_string(StartTime) andalso
- ?is_string(StopTime) ->
- create_subscription(Client,Stream,StartTime,StopTime,?DEFAULT_TIMEOUT);
- create_subscription(Client,Filter,StartTime,StopTime)
- when ?is_filter(Filter) andalso
- ?is_string(StartTime) andalso
- ?is_string(StopTime) ->
- create_subscription(Client,?DEFAULT_STREAM,Filter,
- StartTime,StopTime,?DEFAULT_TIMEOUT);
- create_subscription(Client,Stream,Filter,Timeout)
- when ?is_string(Stream) andalso
- ?is_filter(Filter) andalso
- ?is_timeout(Timeout) ->
- call(Client,{send_rpc_op,{create_subscription,self()},
- [Stream,Filter,undefined,undefined],
- Timeout}).
- -spec create_subscription(Client, Stream, StartTime, StopTime, Timeout) ->
- Result when
- Client :: client(),
- Stream :: stream_name(),
- StartTime :: xs_datetime(),
- StopTime :: xs_datetime(),
- Timeout :: timeout(),
- Result :: ok | {error,error_reason()};
- (Client, Stream, Filter,StartTime, StopTime) ->
- Result when
- Client :: client(),
- Stream :: stream_name(),
- Filter :: simple_xml() | [simple_xml()],
- StartTime :: xs_datetime(),
- StopTime :: xs_datetime(),
- Result :: ok | {error,error_reason()}.
- create_subscription(Client,Stream,StartTime,StopTime,Timeout)
- when ?is_string(Stream) andalso
- ?is_string(StartTime) andalso
- ?is_string(StopTime) andalso
- ?is_timeout(Timeout) ->
- call(Client,{send_rpc_op,{create_subscription,self()},
- [Stream,undefined,StartTime,StopTime],
- Timeout});
- create_subscription(Client,Stream,Filter,StartTime,StopTime)
- when ?is_string(Stream) andalso
- ?is_filter(Filter) andalso
- ?is_string(StartTime) andalso
- ?is_string(StopTime) ->
- create_subscription(Client,Stream,Filter,StartTime,StopTime,?DEFAULT_TIMEOUT).
- -spec create_subscription(Client, Stream, Filter,StartTime, StopTime, Timeout) ->
- Result when
- Client :: client(),
- Stream :: stream_name(),
- Filter :: simple_xml() | [simple_xml()],
- StartTime :: xs_datetime(),
- StopTime :: xs_datetime(),
- Timeout :: timeout(),
- Result :: ok | {error,error_reason()}.
- create_subscription(Client,Stream,Filter,StartTime,StopTime,Timeout) ->
- call(Client,{send_rpc_op,{create_subscription, self()},
- [Stream,Filter,StartTime,StopTime],
- Timeout}).
- %%----------------------------------------------------------------------
- %% Send a request to get the given event streams
- %% See RFC5277, NETCONF Event Notifications
- -spec get_event_streams(Client)
- -> Result when
- Client :: client(),
- Result :: {ok,streams()} | {error,error_reason()}.
- get_event_streams(Client) ->
- get_event_streams(Client,[],?DEFAULT_TIMEOUT).
- -spec get_event_streams(Client, Timeout)
- -> Result when
- Client :: client(),
- Timeout :: timeout(),
- Result :: {ok,streams()} | {error,error_reason()};
- (Client, Streams) -> Result when
- Client :: client(),
- Streams :: [stream_name()],
- Result :: {ok,streams()} | {error,error_reason()}.
- get_event_streams(Client,Timeout) when is_integer(Timeout); Timeout==infinity ->
- get_event_streams(Client,[],Timeout);
- get_event_streams(Client,Streams) when is_list(Streams) ->
- get_event_streams(Client,Streams,?DEFAULT_TIMEOUT).
- -spec get_event_streams(Client, Streams, Timeout)
- -> Result when
- Client :: client(),
- Streams :: [stream_name()],
- Timeout :: timeout(),
- Result :: {ok,streams()} | {error,error_reason()}.
- get_event_streams(Client,Streams,Timeout) ->
- call(Client,{get_event_streams,Streams,Timeout}).
- %%----------------------------------------------------------------------
- %% Send a 'close-session' request
- -spec close_session(Client) -> Result when
- Client :: client(),
- Result :: ok | {error,error_reason()}.
- close_session(Client) ->
- close_session(Client, ?DEFAULT_TIMEOUT).
- -spec close_session(Client, Timeout) -> Result when
- Client :: client(),
- Timeout :: timeout(),
- Result :: ok | {error,error_reason()}.
- close_session(Client, Timeout) ->
- call(Client,{send_rpc_op, close_session, [], Timeout}, true).
- %%----------------------------------------------------------------------
- %% Send a 'kill-session' request
- -spec kill_session(Client, SessionId) -> Result when
- Client :: client(),
- SessionId :: pos_integer(),
- Result :: ok | {error,error_reason()}.
- kill_session(Client, SessionId) ->
- kill_session(Client, SessionId, ?DEFAULT_TIMEOUT).
- -spec kill_session(Client, SessionId, Timeout) -> Result when
- Client :: client(),
- SessionId :: pos_integer(),
- Timeout :: timeout(),
- Result :: ok | {error,error_reason()}.
- kill_session(Client, SessionId, Timeout) ->
- call(Client,{send_rpc_op, kill_session, [SessionId], Timeout}).
- %%----------------------------------------------------------------------
- %% Callback functions
- %%----------------------------------------------------------------------
- init(_KeyOrName,{CM,{Host,Port}},Options) ->
- case ssh_channel(#connection{reference=CM,host=Host,port=Port},Options) of
- {ok,Connection} ->
- {ok, CM, #state{connection = Connection}};
- {error,Reason}->
- {error,Reason}
- end;
- init(_KeyOrName,{_Host,_Port},Options) when Options#options.type==connection ->
- case ssh_connect(Options) of
- {ok, Connection} ->
- ConnPid = Connection#connection.reference,
- {ok, ConnPid, #state{connection = Connection}};
- Error ->
- Error
- end;
- init(_KeyOrName,{_Host,_Port},Options) ->
- case ssh_open(Options) of
- {ok, Connection} ->
- {ConnPid,_} = Connection#connection.reference,
- {ok, ConnPid, #state{connection = Connection}};
- {error,Reason}->
- {error,Reason}
- end.
- terminate(_, #state{connection=Connection}) ->
- ssh_close(Connection),
- ok.
- handle_msg({hello, Options, Timeout}, From,
- #state{connection=Connection,hello_status=HelloStatus} = State) ->
- case do_send(Connection, client_hello(Options)) of
- ok ->
- case HelloStatus of
- undefined ->
- {Ref,TRef} = set_request_timer(Timeout),
- {noreply, State#state{hello_status=#pending{tref=TRef,
- ref=Ref,
- caller=From}}};
- received ->
- {reply, ok, State#state{hello_status=done}};
- {error,Reason} ->
- {stop, {error,Reason}, State}
- end;
- Error ->
- {stop, Error, State}
- end;
- handle_msg(get_ssh_connection, _From, #state{connection=Connection}=State) ->
- Reply =
- case Connection#connection.reference of
- {_,_} -> {error,not_an_ssh_connection};
- CM -> {ok,{CM,{Connection#connection.host,
- Connection#connection.port}}}
- end,
- {reply, Reply, State};
- handle_msg(_, _From, #state{session_id=undefined} = State) ->
- %% Hello is not yet excanged - this shall never happen
- {reply,{error,waiting_for_hello},State};
- handle_msg(get_capabilities, _From, #state{capabilities = Caps} = State) ->
- {reply, Caps, State};
- handle_msg(get_session_id, _From, #state{session_id = Id} = State) ->
- {reply, Id, State};
- handle_msg({send, Timeout, SimpleXml}, From,
- #state{connection=Connection,pending=Pending} = State) ->
- case do_send(Connection, SimpleXml) of
- ok ->
- {Ref,TRef} = set_request_timer(Timeout),
- {noreply, State#state{pending=[#pending{tref=TRef,
- ref=Ref,
- caller=From} | Pending]}};
- Error ->
- {reply, Error, State}
- end;
- handle_msg({send_rpc, SimpleXml, Timeout}, From, State) ->
- do_send_rpc(undefined, SimpleXml, Timeout, From, State);
- handle_msg({send_rpc_op, Op, Data, Timeout}, From, State) ->
- SimpleXml = encode_rpc_operation(Op,Data),
- do_send_rpc(Op, SimpleXml, Timeout, From, State);
- handle_msg({get_event_streams=Op,Streams,Timeout}, From, State) ->
- Filter = {netconf,?NETMOD_NOTIF_NAMESPACE_ATTR,
- [{streams,[{stream,[{name,[Name]}]} || Name <- Streams]}]},
- SimpleXml = encode_rpc_operation(get,[Filter]),
- do_send_rpc(Op, SimpleXml, Timeout, From, State).
- handle_msg({ssh_cm, CM, {data, Ch, _Type, Data}}, State) ->
- ssh_connection:adjust_window(CM,Ch,size(Data)),
- handle_data(Data, State);
- handle_msg({ssh_cm, _CM, _SshCloseMsg}, State) ->
- %% _SshCloseMsg can probably be one of
- %% {eof,Ch}
- %% {exit_status,Ch,Status}
- %% {exit_signal,Ch,ExitSignal,ErrorMsg,LanguageString}
- %% {signal,Ch,Signal}
- %% This might e.g. happen if the server terminates the connection,
- %% as in kill-session (or if ssh:close is called from somewhere
- %% unexpected).
- %%! Log this??
- %%! Currently the log will say that the client closed the
- %%! connection - due to terminate/2
- {stop, State};
- handle_msg({Ref,timeout},
- #state{hello_status=#pending{ref=Ref,caller=Caller}} = State) ->
- ct_gen_conn:return(Caller,{error,{hello_session_failed,timeout}}),
- {stop,State#state{hello_status={error,timeout}}};
- handle_msg({Ref,timeout},#state{pending=Pending} = State) ->
- {value,#pending{op=Op,caller=Caller},Pending1} =
- lists:keytake(Ref,#pending.ref,Pending),
- ct_gen_conn:return(Caller,{error,timeout}),
- R = case Op of
- close_session -> stop;
- _ -> noreply
- end,
- %% Halfhearted try to get in correct state, this matches
- %% the implementation before this patch
- {R,State#state{pending=Pending1, no_end_tag_buff= <<>>, buff= <<>>}}.
- %% Called by ct_util_server to close registered connections before terminate.
- close(Client) ->
- case get_handle(Client) of
- {ok,Pid} ->
- case ct_gen_conn:stop(Pid) of
- {error,{process_down,Pid,noproc}} ->
- {error,already_closed};
- Result ->
- Result
- end;
- Error ->
- Error
- end.
- %%----------------------------------------------------------------------
- %% Internal functions
- %%----------------------------------------------------------------------
- call(Client, Msg) ->
- call(Client, Msg, infinity, false).
- call(Client, Msg, Timeout) when is_integer(Timeout); Timeout==infinity ->
- call(Client, Msg, Timeout, false);
- call(Client, Msg, WaitStop) when is_boolean(WaitStop) ->
- call(Client, Msg, infinity, WaitStop).
- call(Client, Msg, Timeout, WaitStop) ->
- case get_handle(Client) of
- {ok,Pid} ->
- case ct_gen_conn:call(Pid,Msg,Timeout) of
- {error,{process_down,Pid,noproc}} ->
- {error,no_such_client};
- {error,{process_down,Pid,normal}} when WaitStop ->
- %% This will happen when server closes connection
- %% before client received rpc-reply on
- %% close-session.
- ok;
- {error,{process_down,Pid,normal}} ->
- {error,closed};
- {error,{process_down,Pid,Reason}} ->
- {error,{closed,Reason}};
- Other when WaitStop ->
- MRef = erlang:monitor(process,Pid),
- receive
- {'DOWN',MRef,process,Pid,Normal} when Normal==normal;
- Normal==noproc ->
- Other;
- {'DOWN',MRef,process,Pid,Reason} ->
- {error,{{closed,Reason},Other}}
- after Timeout ->
- erlang:demonitor(MRef, [flush]),
- {error,{timeout,Other}}
- end;
- Other ->
- Other
- end;
- Error ->
- Error
- end.
- get_handle(Client) when is_pid(Client) ->
- {ok,Client};
- get_handle(Client) ->
- case ct_util:get_connection(Client, ?MODULE) of
- {ok,{Pid,_}} ->
- {ok,Pid};
- {error,no_registered_connection} ->
- {error,{no_connection_found,Client}};
- Error ->
- Error
- end.
- check_options(OptList,Options) ->
- check_options(OptList,undefined,undefined,Options).
- check_options([], undefined, _Port, _Options) ->
- {error, no_host_address};
- check_options([], _Host, undefined, _Options) ->
- {error, no_port};
- check_options([], Host, Port, Options) ->
- {Host,Port,Options};
- check_options([{ssh, Host}|T], _, Port, Options) ->
- check_options(T, Host, Port, Options#options{host=Host});
- check_options([{port,Port}|T], Host, _, Options) ->
- check_options(T, Host, Port, Options#options{port=Port});
- check_options([{timeout, Timeout}|T], Host, Port, Options)
- when is_integer(Timeout); Timeout==infinity ->
- check_options(T, Host, Port, Options#options{timeout = Timeout});
- check_options([{timeout, _} = Opt|_T], _Host, _Port, _Options) ->
- {error, {invalid_option, Opt}};
- check_options([Opt|T], Host, Port, #options{ssh=SshOpts}=Options) ->
- %% Option verified by ssh
- check_options(T, Host, Port, Options#options{ssh=[Opt|SshOpts]}).
- check_session_options([],Options) ->
- {ok,Options};
- check_session_options([{timeout, Timeout}|T], Options)
- when is_integer(Timeout); Timeout==infinity ->
- check_session_options(T, Options#options{timeout = Timeout});
- check_session_options([Opt|_T], _Options) ->
- {error, {invalid_option, Opt}}.
- %%%-----------------------------------------------------------------
- set_request_timer(infinity) ->
- {undefined,undefined};
- set_request_timer(T) ->
- Ref = make_ref(),
- {ok,TRef} = timer:send_after(T,{Ref,timeout}),
- {Ref,TRef}.
- %%%-----------------------------------------------------------------
- cancel_request_timer(undefined,undefined) ->
- ok;
- cancel_request_timer(Ref,TRef) ->
- _ = timer:cancel(TRef),
- receive {Ref,timeout} -> ok
- after 0 -> ok
- end.
- %%%-----------------------------------------------------------------
- client_hello(Options) when is_list(Options) ->
- UserCaps = [{capability, UserCap} ||
- {capability, UserCap} <- Options,
- is_list(hd(UserCap))],
- {hello, ?NETCONF_NAMESPACE_ATTR,
- [{capabilities,
- [{capability,[?NETCONF_BASE_CAP++?NETCONF_BASE_CAP_VSN]}|
- UserCaps]}]}.
- %%%-----------------------------------------------------------------
- encode_rpc_operation(Lock,[Target]) when Lock==lock; Lock==unlock ->
- {Lock,[{target,[Target]}]};
- encode_rpc_operation(get,[Filter]) ->
- {get,filter(Filter)};
- encode_rpc_operation(get_config,[Source,Filter]) ->
- {'get-config',[{source,[Source]}] ++ filter(Filter)};
- encode_rpc_operation(edit_config,[Target,Config,OptParams]) ->
- {'edit-config',[{target,[Target]}] ++ OptParams ++ [{config,[Config]}]};
- encode_rpc_operation(delete_config,[Target]) ->
- {'delete-config',[{target,[Target]}]};
- encode_rpc_operation(copy_config,[Target,Source]) ->
- {'copy-config',[{target,[Target]},{source,[Source]}]};
- encode_rpc_operation(action,[Action]) ->
- {action,?ACTION_NAMESPACE_ATTR,[{data,[Action]}]};
- encode_rpc_operation(kill_session,[SessionId]) ->
- {'kill-session',[{'session-id',[integer_to_list(SessionId)]}]};
- encode_rpc_operation(close_session,[]) ->
- 'close-session';
- encode_rpc_operation({create_subscription,_},
- [Stream,Filter,StartTime,StopTime]) ->
- {'create-subscription',?NETCONF_NOTIF_NAMESPACE_ATTR,
- [{stream,[Stream]}] ++
- filter(Filter) ++
- maybe_element(startTime,StartTime) ++
- maybe_element(stopTime,StopTime)}.
- filter(undefined) ->
- [];
- filter({xpath,Filter}) when ?is_string(Filter) ->
- [{filter,[{type,"xpath"},{select, Filter}],[]}];
- filter(Filter) when is_list(Filter) ->
- [{filter,[{type,"subtree"}],Filter}];
- filter(Filter) ->
- filter([Filter]).
- maybe_element(_,undefined) ->
- [];
- maybe_element(Tag,Value) ->
- [{Tag,[Value]}].
- %%%-----------------------------------------------------------------
- %%% Send XML data to server
- do_send_rpc(PendingOp,SimpleXml,Timeout,Caller,
- #state{connection=Connection,msg_id=MsgId,pending=Pending} = State) ->
- case do_send_rpc(Connection, MsgId, SimpleXml) of
- ok ->
- {Ref,TRef} = set_request_timer(Timeout),
- {noreply, State#state{msg_id=MsgId+1,
- pending=[#pending{tref=TRef,
- ref=Ref,
- msg_id=MsgId,
- op=PendingOp,
- caller=Caller} | Pending]}};
- Error ->
- {reply, Error, State#state{msg_id=MsgId+1}}
- end.
- do_send_rpc(Connection, MsgId, SimpleXml) ->
- do_send(Connection,
- {rpc,
- [{'message-id',MsgId} | ?NETCONF_NAMESPACE_ATTR],
- [SimpleXml]}).
- do_send(Connection, SimpleXml) ->
- Xml=to_xml_doc(SimpleXml),
- ssh_send(Connection, Xml).
- to_xml_doc(Simple) ->
- Prolog = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
- Xml = unicode:characters_to_binary(
- xmerl:export_simple([Simple],
- xmerl_xml,
- [#xmlAttribute{name=prolog,
- value=Prolog}])),
- <<Xml/binary,?END_TAG/binary>>.
- %%%-----------------------------------------------------------------
- %%% Parse and handle received XML data
- %%% Two buffers are used:
- %%% * 'no_end_tag_buff' contains data that is checked and does not
- %%% contain any (part of an) end tag.
- %%% * 'buff' contains all other saved data - it may or may not
- %%% include (a part of) an end tag.
- %%% The reason for this is to avoid running binary:split/3 multiple
- %%% times on the same data when it does not contain an end tag. This
- %%% can be a considerable optimation in the case when a lot of data is
- %%% received (e.g. when fetching all data from a node) and the data is
- %%% sent in multiple ssh packages.
- handle_data(NewData,#state{connection=Connection} = State0) ->
- log(Connection,recv,NewData),
- NoEndTag0 = State0#state.no_end_tag_buff,
- Buff0 = State0#state.buff,
- Data = <<Buff0/binary, NewData/binary>>,
- case binary:split(Data,?END_TAG,[]) of
- [_NoEndTagFound] ->
- NoEndTagSize = case byte_size(Data) of
- Sz when Sz<5 -> 0;
- Sz -> Sz-5
- end,
- <<NoEndTag1:NoEndTagSize/binary,Buff/binary>> = Data,
- NoEndTag = <<NoEndTag0/binary,NoEndTag1/binary>>,
- {noreply, State0#state{no_end_tag_buff=NoEndTag, buff=Buff}};
- [FirstMsg0,Buff1] ->
- FirstMsg = remove_initial_nl(<<NoEndTag0/binary,FirstMsg0/binary>>),
- SaxArgs = [{event_fun,fun sax_event/3}, {event_state,[]}],
- case xmerl_sax_parser:stream(FirstMsg, SaxArgs) of
- {ok, Simple, _Thrash} ->
- case decode(Simple, State0#state{no_end_tag_buff= <<>>,
- buff=Buff1}) of
- {noreply, #state{buff=Buff} = State} when Buff =/= <<>> ->
- %% Recurse if we have more data in buffer
- handle_data(<<>>, State);
- Other ->
- Other
- end;
- {fatal_error,_Loc,Reason,_EndTags,_EventState} ->
- ?error(Connection#connection.name,
- [{parse_error,Reason},
- {buffer, Buff0},
- {new_data,NewData}]),
- handle_error(Reason, State0#state{no_end_tag_buff= <<>>,
- buff= <<>>})
- end
- end.
- %% xml does not accept a leading nl and some netconf server add a nl after
- %% each ?END_TAG, ignore them
- remove_initial_nl(<<"\n", Data/binary>>) ->
- remove_initial_nl(Data);
- remove_initial_nl(Data) ->
- Data.
- handle_error(Reason, State) ->
- Pending1 = case State#state.pending of
- [] -> [];
- Pending ->
- %% Assuming the first request gets the
- %% first answer
- P=#pending{tref=TRef,ref=Ref,caller=Caller} =
- lists:last(Pending),
- cancel_request_timer(Ref,TRef),
- Reason1 = {failed_to_parse_received_data,Reason},
- ct_gen_conn:return(Caller,{error,Reason1}),
- lists:delete(P,Pending)
- end,
- {noreply, State#state{pending=Pending1}}.
- %% Event function for the sax parser. It builds a simple XML structure.
- %% Care is taken to keep namespace attributes and prefixes as in the original XML.
- sax_event(Event,_Loc,State) ->
- sax_event(Event,State).
- sax_event({startPrefixMapping, Prefix, Uri},Acc) ->
- %% startPrefixMapping will always come immediately before the
- %% startElement where the namespace is defined.
- [{xmlns,{Prefix,Uri}}|Acc];
- sax_event({startElement,_Uri,_Name,QN,Attrs},Acc) ->
- %% Pick out any namespace attributes inserted due to a
- %% startPrefixMapping event.The rest of Acc will then be only
- %% elements.
- {NsAttrs,NewAcc} = split_attrs_and_elements(Acc,[]),
- Tag = qn_to_tag(QN),
- [{Tag,NsAttrs ++ parse_attrs(Attrs),[]}|NewAcc];
- sax_event({endElement,_Uri,_Name,_QN},[{Name,Attrs,Cont},{Parent,PA,PC}|Acc]) ->
- [{Parent,PA,[{Name,Attrs,lists:reverse(Cont)}|PC]}|Acc];
- sax_event(endDocument,[{Tag,Attrs,Cont}]) ->
- {Tag,Attrs,lists:reverse(Cont)};
- sax_event({characters,String},[{Name,Attrs,Cont}|Acc]) ->
- [{Name,Attrs,[String|Cont]}|Acc];
- sax_event(_Event,State) ->
- State.
- split_attrs_and_elements([{xmlns,{Prefix,Uri}}|Rest],Attrs) ->
- split_attrs_and_elements(Rest,[{xmlnstag(Prefix),Uri}|Attrs]);
- split_attrs_and_elements(Elements,Attrs) ->
- {Attrs,Elements}.
- xmlnstag([]) ->
- xmlns;
- xmlnstag(Prefix) ->
- list_to_atom("xmlns:"++Prefix).
- qn_to_tag({[],Name}) ->
- list_to_atom(Name);
- qn_to_tag({Prefix,Name}) ->
- list_to_atom(Prefix ++ ":" ++ Name).
- parse_attrs([{_Uri, [], Name, Value}|Attrs]) ->
- [{list_to_atom(Name),Value}|parse_attrs(Attrs)];
- parse_attrs([{_Uri, Prefix, Name, Value}|Attrs]) ->
- [{list_to_atom(Prefix ++ ":" ++ Name),Value}|parse_attrs(Attrs)];
- parse_attrs([]) ->
- [].
- %%%-----------------------------------------------------------------
- %%% Decoding of parsed XML data
- decode({Tag,Attrs,_}=E, #state{connection=Connection,pending=Pending}=State) ->
- ConnName = Connection#connection.name,
- case get_local_name_atom(Tag) of
- 'rpc-reply' ->
- case get_msg_id(Attrs) of
- undefined ->
- case Pending of
- [#pending{msg_id=MsgId}] ->
- ?error(ConnName,[{warning,rpc_reply_missing_msg_id},
- {assuming,MsgId}]),
- decode_rpc_reply(MsgId,E,State);
- _ ->
- ?error(ConnName,[{error,rpc_reply_missing_msg_id}]),
- {noreply,State}
- end;
- MsgId ->
- decode_rpc_reply(MsgId,E,State)
- end;
- hello ->
- case State#state.hello_status of
- undefined ->
- case decode_hello(E) of
- {ok,SessionId,Capabilities} ->
- {noreply,State#state{session_id = SessionId,
- capabilities = Capabilities,
- hello_status = received}};
- {error,Reason} ->
- {noreply,State#state{hello_status = {error,Reason}}}
- end;
- #pending{tref=TRef,ref=Ref,caller=Caller} ->
- cancel_request_timer(Ref,TRef),
- case decode_hello(E) of
- {ok,SessionId,Capabilities} ->
- ct_gen_conn:return(Caller,ok),
- {noreply,State#state{session_id = SessionId,
- capabilities = Capabilities,
- hello_status = done}};
- {error,Reason} ->
- ct_gen_conn:return(Caller,{error,Reason}),
- {stop,State#state{hello_status={error,Reason}}}
- end;
- Other ->
- ?error(ConnName,[{got_unexpected_hello,E},
- {hello_status,Other}]),
- {noreply,State}
- end;
- notification ->
- EventReceiver = State#state.event_receiver,
- EventReceiver ! E,
- {noreply,State};
- Other ->
- %% Result of send/2, when not sending an rpc request - or
- %% if netconf server sends noise. Can handle this only if
- %% there is just one pending that matches (i.e. has
- %% undefined msg_id and op)
- case [P || P = #pending{msg_id=undefined,op=undefined} <- Pending] of
- [#pending{tref=TRef,ref=Ref,caller=Caller}] ->
- cancel_request_timer(Ref,TRef),
- ct_gen_conn:return(Caller,E),
- {noreply,State#state{pending=[]}};
- _ ->
- ?error(ConnName,[{got_unexpected_msg,Other},
- {expecting,Pending}]),
- {noreply,State}
- end
- end.
- get_msg_id(Attrs) ->
- case lists:keyfind('message-id',1,Attrs) of
- {_,Str} ->
- list_to_integer(Str);
- false ->
- undefined
- end.
- decode_rpc_reply(MsgId,{_,Attrs,Content0}=E,#state{pending=Pending} = State) ->
- case lists:keytake(MsgId,#pending.msg_id,Pending) of
- {value, #pending{tref=TRef,ref=Ref,op=Op,caller=Caller}, Pending1} ->
- cancel_request_timer(Ref,TRef),
- Content = forward_xmlns_attr(Attrs,Content0),
- {CallerReply,{ServerReply,State2}} =
- do_decode_rpc_reply(Op,Content,State#state{pending=Pending1}),
- ct_gen_conn:return(Caller,CallerReply),
- {ServerReply,State2};
- false ->
- %% Result of send/2, when receiving a correct
- %% rpc-reply. Can handle this only if there is just one
- %% pending that matches (i.e. has undefined msg_id and op)
- case [P || P = #pending{msg_id=undefined,op=undefined} <- Pending] of
- [#pending{tref=TRef,
- ref=Ref,
- msg_id=undefined,
- op=undefined,
- caller=Caller}] ->
- cancel_request_timer(Ref,TRef),
- ct_gen_conn:return(Caller,E),
- {noreply,State#state{pending=[]}};
- _ ->
- ConnName = (State#state.connection)#connection.name,
- ?error(ConnName,[{got_unexpected_msg_id,MsgId},
- {expecting,Pending}]),
- {noreply,State}
- end
- end.
- do_decode_rpc_reply(Op,Result,State)
- when Op==lock; Op==unlock; Op==edit_config; Op==delete_config;
- Op==copy_config; Op==kill_session ->
- {decode_ok(Result),{noreply,State}};
- do_decode_rpc_reply(Op,Result,State)
- when Op==get; Op==get_config; Op==action ->
- {decode_data(Result),{noreā¦
Large files files are truncated, but you can click here to view the full file