PageRenderTime 55ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/common_test/src/ct_netconfc.erl

https://github.com/bsmr-erlang/otp
Erlang | 1820 lines | 1391 code | 170 blank | 259 comment | 6 complexity | 53e6661b222f4ce27388f9089d0d6927 MD5 | raw file
Possible License(s): BSD-3-Clause, 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

  1. %%----------------------------------------------------------------------
  2. %% %CopyrightBegin%
  3. %%
  4. %% Copyright Ericsson AB 2012-2017. All Rights Reserved.
  5. %%
  6. %% Licensed under the Apache License, Version 2.0 (the "License");
  7. %% you may not use this file except in compliance with the License.
  8. %% You may obtain a copy of the License at
  9. %%
  10. %% http://www.apache.org/licenses/LICENSE-2.0
  11. %%
  12. %% Unless required by applicable law or agreed to in writing, software
  13. %% distributed under the License is distributed on an "AS IS" BASIS,
  14. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. %% See the License for the specific language governing permissions and
  16. %% limitations under the License.
  17. %%
  18. %% %CopyrightEnd%
  19. %%
  20. %%----------------------------------------------------------------------
  21. %% File: ct_netconfc.erl
  22. %%
  23. %% Description:
  24. %% This file contains the Netconf client interface
  25. %%
  26. %% Netconf servers can be configured by adding the following statement
  27. %% to a configuration file:
  28. %%
  29. %% {server_id(),options()}.
  30. %%
  31. %% The server_id() or an associated ct:target_name() shall then be
  32. %% used in calls to open/2 connect/2.
  33. %%
  34. %% If no configuration exists for a server, use open/1 and connect/1.
  35. %%
  36. %% == Logging ==
  37. %%
  38. %% The netconf server uses the `error_logger' for logging of netconf
  39. %% traffic. A special purpose error handler is implemented in
  40. %% `ct_conn_log_h'. To use this error handler, add the `cth_conn_log'
  41. %% hook in your test suite, e.g.
  42. %%
  43. %% suite() ->
  44. %% [{ct_hooks, [{cth_conn_log, [{ct:conn_log_mod(),ct:conn_log_options()}]}]}].
  45. %%
  46. %% For example:
  47. %%
  48. %% suite() ->
  49. %% [{ct_hooks,
  50. %% [{cth_conn_log,[{ct_netconfc,[{log_type,pretty},
  51. %% {hosts,[my_configured_server]}]}]}
  52. %%
  53. %% == Notifications ==
  54. %%
  55. %% The netconf client is also compliant with RFC5277 NETCONF Event
  56. %% Notifications, which defines a mechanism for an asynchronous
  57. %% message notification delivery service for the netconf protocol.
  58. %%
  59. %% Specific functions to support this are create_subscription/6
  60. %% get_event_streams/3. (The functions also exist with other arities.)
  61. %%
  62. %%----------------------------------------------------------------------
  63. -module(ct_netconfc).
  64. -include("ct_netconfc.hrl").
  65. -include("ct_util.hrl").
  66. -include_lib("xmerl/include/xmerl.hrl").
  67. %%----------------------------------------------------------------------
  68. %% External exports
  69. %%----------------------------------------------------------------------
  70. -export([connect/1,
  71. connect/2,
  72. disconnect/1,
  73. session/1,
  74. session/2,
  75. session/3,
  76. open/1,
  77. open/2,
  78. only_open/1,
  79. only_open/2,
  80. hello/1,
  81. hello/2,
  82. hello/3,
  83. close_session/1,
  84. close_session/2,
  85. kill_session/2,
  86. kill_session/3,
  87. send/2,
  88. send/3,
  89. send_rpc/2,
  90. send_rpc/3,
  91. lock/2,
  92. lock/3,
  93. unlock/2,
  94. unlock/3,
  95. get/2,
  96. get/3,
  97. get_config/3,
  98. get_config/4,
  99. edit_config/3,
  100. edit_config/4,
  101. edit_config/5,
  102. delete_config/2,
  103. delete_config/3,
  104. copy_config/3,
  105. copy_config/4,
  106. action/2,
  107. action/3,
  108. create_subscription/1,
  109. create_subscription/2,
  110. create_subscription/3,
  111. create_subscription/4,
  112. create_subscription/5,
  113. create_subscription/6,
  114. get_event_streams/1,
  115. get_event_streams/2,
  116. get_event_streams/3,
  117. get_capabilities/1,
  118. get_capabilities/2,
  119. get_session_id/1,
  120. get_session_id/2]).
  121. %%----------------------------------------------------------------------
  122. %% Exported types
  123. %%----------------------------------------------------------------------
  124. -export_type([client/0,
  125. handle/0,
  126. notification/0]).
  127. %%----------------------------------------------------------------------
  128. %% Internal exports
  129. %%----------------------------------------------------------------------
  130. %% ct_gen_conn callbacks
  131. -export([init/3,
  132. handle_msg/3,
  133. handle_msg/2,
  134. terminate/2,
  135. close/1]).
  136. %% ct_conn_log callback
  137. -export([format_data/2]).
  138. %%----------------------------------------------------------------------
  139. %% Internal defines
  140. %%----------------------------------------------------------------------
  141. -define(APPLICATION,?MODULE).
  142. -define(DEFAULT_STREAM,"NETCONF").
  143. -define(error(ConnName,Report),
  144. error_logger:error_report([{ct_connection,ConnName},
  145. {client,self()},
  146. {module,?MODULE},
  147. {line,?LINE} |
  148. Report])).
  149. -define(is_timeout(T), (is_integer(T) orelse T==infinity)).
  150. -define(is_filter(F),
  151. (?is_simple_xml(F)
  152. orelse (F==[])
  153. orelse (is_list(F) andalso ?is_simple_xml(hd(F))))).
  154. -define(is_simple_xml(Xml),
  155. (is_atom(Xml) orelse (is_tuple(Xml) andalso is_atom(element(1,Xml))))).
  156. -define(is_string(S), (is_list(S) andalso is_integer(hd(S)))).
  157. %%----------------------------------------------------------------------
  158. %% Records
  159. %%----------------------------------------------------------------------
  160. %% Client state
  161. -record(state, {host,
  162. port,
  163. connection, % #connection
  164. capabilities,
  165. session_id,
  166. msg_id = 1,
  167. hello_status,
  168. no_end_tag_buff = <<>>,
  169. buff = <<>>,
  170. pending = [], % [#pending]
  171. event_receiver}).% pid
  172. %% Run-time client options.
  173. -record(options, {ssh = [], % Options for the ssh application
  174. host,
  175. port = ?DEFAULT_PORT,
  176. timeout = ?DEFAULT_TIMEOUT,
  177. name,
  178. type}).
  179. %% Connection reference
  180. -record(connection, {reference, % {CM,Ch}
  181. host,
  182. port,
  183. name,
  184. type}).
  185. %% Pending replies from server
  186. -record(pending, {tref, % timer ref (returned from timer:xxx)
  187. ref, % pending ref
  188. msg_id,
  189. op,
  190. caller}).% pid which sent the request
  191. %%----------------------------------------------------------------------
  192. %% Type declarations
  193. %%----------------------------------------------------------------------
  194. -type client() :: handle() | server_id() | ct:target_name().
  195. -opaque handle() :: pid().
  196. -type options() :: [option()].
  197. -type option() :: {ssh,host()} | {port,inet:port_number()} | {user,string()} |
  198. {password,string()} | {user_dir,string()} |
  199. {timeout,timeout()}.
  200. -type session_options() :: [session_option()].
  201. -type session_option() :: {timeout,timeout()}.
  202. -type host() :: inet:hostname() | inet:ip_address().
  203. -type notification() :: {notification, xml_attributes(), notification_content()}.
  204. -type notification_content() :: [event_time()|simple_xml()].
  205. -type event_time() :: {eventTime,xml_attributes(),[xs_datetime()]}.
  206. -type stream_name() :: string().
  207. -type streams() :: [{stream_name(),[stream_data()]}].
  208. -type stream_data() :: {description,string()} |
  209. {replaySupport,string()} |
  210. {replayLogCreationTime,string()} |
  211. {replayLogAgedTime,string()}.
  212. %% See XML Schema for Event Notifications found in RFC5277 for further
  213. %% detail about the data format for the string values.
  214. -type error_reason() :: term().
  215. -type server_id() :: atom().
  216. -type simple_xml() :: {xml_tag(), xml_attributes(), xml_content()} |
  217. {xml_tag(), xml_content()} |
  218. xml_tag().
  219. -type xml_tag() :: atom().
  220. -type xml_attributes() :: [{xml_attribute_tag(),xml_attribute_value()}].
  221. -type xml_attribute_tag() :: atom().
  222. -type xml_attribute_value() :: string().
  223. -type xml_content() :: [simple_xml() | iolist()].
  224. -type xpath() :: {xpath,string()}.
  225. -type netconf_db() :: running | startup | candidate.
  226. -type xs_datetime() :: string().
  227. %% This date and time identifyer has the same format as the XML type
  228. %% dateTime and compliant to RFC3339. The format is
  229. %% "[-]CCYY-MM-DDThh:mm:ss[.s][Z|(+|-)hh:mm]"
  230. %%----------------------------------------------------------------------
  231. %% External interface functions
  232. %%----------------------------------------------------------------------
  233. %%----------------------------------------------------------------------
  234. %% Open an SSH connection to a Netconf server
  235. %% If the server options are specified in a configuration file, use
  236. %% open/2.
  237. -spec connect(Options) -> Result when
  238. Options :: options(),
  239. Result :: {ok,handle()} | {error,error_reason()}.
  240. connect(Options) ->
  241. do_connect(Options, #options{type=connection},[]).
  242. -spec connect(KeyOrName,ExtraOptions) -> Result when
  243. KeyOrName :: ct:key_or_name(),
  244. ExtraOptions :: options(),
  245. Result :: {ok,handle()} | {error,error_reason()}.
  246. connect(KeyOrName, ExtraOptions) ->
  247. SortedExtra = lists:keysort(1,ExtraOptions),
  248. SortedConfig = lists:keysort(1,ct:get_config(KeyOrName,[])),
  249. AllOpts = lists:ukeymerge(1,SortedConfig,SortedExtra),
  250. do_connect(AllOpts,#options{name=KeyOrName,type=connection},[{name,KeyOrName}]).
  251. do_connect(OptList,InitOptRec,NameOpt) ->
  252. case check_options(OptList,InitOptRec) of
  253. {Host,Port,Options} ->
  254. ct_gen_conn:start({Host,Port},Options,?MODULE,
  255. NameOpt ++ [{reconnect,false},
  256. {use_existing_connection,false},
  257. {forward_messages,false}]);
  258. Error ->
  259. Error
  260. end.
  261. %%----------------------------------------------------------------------
  262. %% Close the given SSH connection.
  263. -spec disconnect(Conn) -> ok | {error,error_reason()} when
  264. Conn :: handle().
  265. disconnect(Conn) ->
  266. case call(Conn,get_ssh_connection) of
  267. {ok,_} ->
  268. ct_gen_conn:stop(Conn);
  269. Error ->
  270. Error
  271. end.
  272. %%----------------------------------------------------------------------
  273. %% Open a netconf session as a channel on the given SSH connection,
  274. %% and exchange `hello' messages.
  275. -spec session(Conn) -> Result when
  276. Conn :: handle(),
  277. Result :: {ok,handle()} | {error,error_reason()}.
  278. session(Conn) ->
  279. do_session(Conn,[],#options{type=channel},[]).
  280. -spec session(Conn,Options) -> Result when
  281. Conn :: handle(),
  282. Options :: session_options(),
  283. Result :: {ok,handle()} | {error,error_reason()};
  284. (KeyOrName,Conn) -> Result when
  285. KeyOrName :: ct:key_or_name(),
  286. Conn :: handle(),
  287. Result :: {ok,handle()} | {error,error_reason()}.
  288. session(Conn,Options) when is_list(Options) ->
  289. do_session(Conn,Options,#options{type=channel},[]);
  290. session(KeyOrName,Conn) ->
  291. do_session(Conn,[],#options{name=KeyOrName,type=channel},[{name,KeyOrName}]).
  292. -spec session(KeyOrName,Conn,Options) -> Result when
  293. Conn :: handle(),
  294. Options :: session_options(),
  295. KeyOrName :: ct:key_or_name(),
  296. Result :: {ok,handle()} | {error,error_reason()}.
  297. session(KeyOrName,Conn,ExtraOptions) ->
  298. SortedExtra = lists:keysort(1,ExtraOptions),
  299. SortedConfig = lists:keysort(1,ct:get_config(KeyOrName,[])),
  300. AllOpts = lists:ukeymerge(1,SortedConfig,SortedExtra),
  301. do_session(Conn,AllOpts,#options{name=KeyOrName,type=channel},
  302. [{name,KeyOrName}]).
  303. do_session(Conn,OptList,InitOptRec,NameOpt) ->
  304. case call(Conn,get_ssh_connection) of
  305. {ok,SshConn} ->
  306. case check_session_options(OptList,InitOptRec) of
  307. {ok,Options} ->
  308. case ct_gen_conn:start(SshConn,Options,?MODULE,
  309. NameOpt ++
  310. [{reconnect,false},
  311. {use_existing_connection,false},
  312. {forward_messages,true}]) of
  313. {ok,Client} ->
  314. case hello(Client,Options#options.timeout) of
  315. ok ->
  316. {ok,Client};
  317. Error ->
  318. Error
  319. end;
  320. Error ->
  321. Error
  322. end;
  323. Error ->
  324. Error
  325. end;
  326. Error ->
  327. Error
  328. end.
  329. %%----------------------------------------------------------------------
  330. %% Open a netconf session and exchange 'hello' messages.
  331. %% If the server options are specified in a configuration file, use
  332. %% open/2.
  333. -spec open(Options) -> Result when
  334. Options :: options(),
  335. Result :: {ok,handle()} | {error,error_reason()}.
  336. open(Options) ->
  337. open(Options,#options{type=connection_and_channel},[],true).
  338. -spec open(KeyOrName, ExtraOptions) -> Result when
  339. KeyOrName :: ct:key_or_name(),
  340. ExtraOptions :: options(),
  341. Result :: {ok,handle()} | {error,error_reason()}.
  342. open(KeyOrName, ExtraOpts) ->
  343. open(KeyOrName, ExtraOpts, true).
  344. open(KeyOrName, ExtraOpts, Hello) ->
  345. SortedExtra = lists:keysort(1,ExtraOpts),
  346. SortedConfig = lists:keysort(1,ct:get_config(KeyOrName,[])),
  347. AllOpts = lists:ukeymerge(1,SortedConfig,SortedExtra),
  348. open(AllOpts,#options{name=KeyOrName,type=connection_and_channel},
  349. [{name,KeyOrName}],Hello).
  350. open(OptList,InitOptRec,NameOpt,Hello) ->
  351. case check_options(OptList,InitOptRec) of
  352. {Host,Port,Options} ->
  353. case ct_gen_conn:start({Host,Port},Options,?MODULE,
  354. NameOpt ++ [{reconnect,false},
  355. {use_existing_connection,false},
  356. {forward_messages,true}]) of
  357. {ok,Client} when Hello==true ->
  358. case hello(Client,Options#options.timeout) of
  359. ok ->
  360. {ok,Client};
  361. Error ->
  362. Error
  363. end;
  364. Other ->
  365. Other
  366. end;
  367. Error ->
  368. Error
  369. end.
  370. %%----------------------------------------------------------------------
  371. %% As open/1,2, except no 'hello' message is sent.
  372. -spec only_open(Options) -> Result when
  373. Options :: options(),
  374. Result :: {ok,handle()} | {error,error_reason()}.
  375. only_open(Options) ->
  376. open(Options,#options{type=connection_and_channel},[],false).
  377. -spec only_open(KeyOrName,ExtraOptions) -> Result when
  378. KeyOrName :: ct:key_or_name(),
  379. ExtraOptions :: options(),
  380. Result :: {ok,handle()} | {error,error_reason()}.
  381. only_open(KeyOrName, ExtraOpts) ->
  382. open(KeyOrName, ExtraOpts, false).
  383. %%----------------------------------------------------------------------
  384. %% Send a 'hello' message.
  385. -spec hello(Client) -> Result when
  386. Client :: handle(),
  387. Result :: ok | {error,error_reason()}.
  388. hello(Client) ->
  389. hello(Client,[],?DEFAULT_TIMEOUT).
  390. -spec hello(Client,Timeout) -> Result when
  391. Client :: handle(),
  392. Timeout :: timeout(),
  393. Result :: ok | {error,error_reason()}.
  394. hello(Client,Timeout) ->
  395. hello(Client,[],Timeout).
  396. -spec hello(Client,Options,Timeout) -> Result when
  397. Client :: handle(),
  398. Options :: [{capability, [string()]}],
  399. Timeout :: timeout(),
  400. Result :: ok | {error,error_reason()}.
  401. hello(Client,Options,Timeout) ->
  402. call(Client, {hello, Options, Timeout}).
  403. %%----------------------------------------------------------------------
  404. %% Get the session id for the session specified by Client.
  405. -spec get_session_id(Client) -> Result when
  406. Client :: client(),
  407. Result :: pos_integer() | {error,error_reason()}.
  408. get_session_id(Client) ->
  409. get_session_id(Client, ?DEFAULT_TIMEOUT).
  410. -spec get_session_id(Client, Timeout) -> Result when
  411. Client :: client(),
  412. Timeout :: timeout(),
  413. Result :: pos_integer() | {error,error_reason()}.
  414. get_session_id(Client, Timeout) ->
  415. call(Client, get_session_id, Timeout).
  416. %%----------------------------------------------------------------------
  417. %% Get the server side capabilities.
  418. -spec get_capabilities(Client) -> Result when
  419. Client :: client(),
  420. Result :: [string()] | {error,error_reason()}.
  421. get_capabilities(Client) ->
  422. get_capabilities(Client, ?DEFAULT_TIMEOUT).
  423. -spec get_capabilities(Client, Timeout) -> Result when
  424. Client :: client(),
  425. Timeout :: timeout(),
  426. Result :: [string()] | {error,error_reason()}.
  427. get_capabilities(Client, Timeout) ->
  428. call(Client, get_capabilities, Timeout).
  429. %%----------------------------------------------------------------------
  430. %% Send an XML document to the server.
  431. -spec send(Client, SimpleXml) -> Result when
  432. Client :: client(),
  433. SimpleXml :: simple_xml(),
  434. Result :: simple_xml() | {error,error_reason()}.
  435. send(Client, SimpleXml) ->
  436. send(Client, SimpleXml, ?DEFAULT_TIMEOUT).
  437. -spec send(Client, SimpleXml, Timeout) -> Result when
  438. Client :: client(),
  439. SimpleXml :: simple_xml(),
  440. Timeout :: timeout(),
  441. Result :: simple_xml() | {error,error_reason()}.
  442. send(Client, SimpleXml, Timeout) ->
  443. call(Client,{send, Timeout, SimpleXml}).
  444. %%----------------------------------------------------------------------
  445. %% Wrap the given XML document in a valid netconf 'rpc' request and
  446. %% send to the server.
  447. -spec send_rpc(Client, SimpleXml) -> Result when
  448. Client :: client(),
  449. SimpleXml :: simple_xml(),
  450. Result :: [simple_xml()] | {error,error_reason()}.
  451. send_rpc(Client, SimpleXml) ->
  452. send_rpc(Client, SimpleXml, ?DEFAULT_TIMEOUT).
  453. -spec send_rpc(Client, SimpleXml, Timeout) -> Result when
  454. Client :: client(),
  455. SimpleXml :: simple_xml(),
  456. Timeout :: timeout(),
  457. Result :: [simple_xml()] | {error,error_reason()}.
  458. send_rpc(Client, SimpleXml, Timeout) ->
  459. call(Client,{send_rpc, SimpleXml, Timeout}).
  460. %%----------------------------------------------------------------------
  461. %% Send a 'lock' request.
  462. -spec lock(Client, Target) -> Result when
  463. Client :: client(),
  464. Target :: netconf_db(),
  465. Result :: ok | {error,error_reason()}.
  466. lock(Client, Target) ->
  467. lock(Client, Target,?DEFAULT_TIMEOUT).
  468. -spec lock(Client, Target, Timeout) -> Result when
  469. Client :: client(),
  470. Target :: netconf_db(),
  471. Timeout :: timeout(),
  472. Result :: ok | {error,error_reason()}.
  473. lock(Client, Target, Timeout) ->
  474. call(Client,{send_rpc_op,lock,[Target],Timeout}).
  475. %%----------------------------------------------------------------------
  476. %% Send a 'unlock' request.
  477. -spec unlock(Client, Target) -> Result when
  478. Client :: client(),
  479. Target :: netconf_db(),
  480. Result :: ok | {error,error_reason()}.
  481. unlock(Client, Target) ->
  482. unlock(Client, Target,?DEFAULT_TIMEOUT).
  483. -spec unlock(Client, Target, Timeout) -> Result when
  484. Client :: client(),
  485. Target :: netconf_db(),
  486. Timeout :: timeout(),
  487. Result :: ok | {error,error_reason()}.
  488. unlock(Client, Target, Timeout) ->
  489. call(Client, {send_rpc_op, unlock, [Target], Timeout}).
  490. %%----------------------------------------------------------------------
  491. %% Send a 'get' request.
  492. -spec get(Client, Filter) -> Result when
  493. Client :: client(),
  494. Filter :: simple_xml() | xpath(),
  495. Result :: {ok,[simple_xml()]} | {error,error_reason()}.
  496. get(Client, Filter) ->
  497. get(Client, Filter, ?DEFAULT_TIMEOUT).
  498. -spec get(Client, Filter, Timeout) -> Result when
  499. Client :: client(),
  500. Filter :: simple_xml() | xpath(),
  501. Timeout :: timeout(),
  502. Result :: {ok,[simple_xml()]} | {error,error_reason()}.
  503. get(Client, Filter, Timeout) ->
  504. call(Client,{send_rpc_op, get, [Filter], Timeout}).
  505. %%----------------------------------------------------------------------
  506. %% Send a 'get-config' request.
  507. -spec get_config(Client, Source, Filter) -> Result when
  508. Client :: client(),
  509. Source :: netconf_db(),
  510. Filter :: simple_xml() | xpath(),
  511. Result :: {ok,[simple_xml()]} | {error,error_reason()}.
  512. get_config(Client, Source, Filter) ->
  513. get_config(Client, Source, Filter, ?DEFAULT_TIMEOUT).
  514. -spec get_config(Client, Source, Filter, Timeout) -> Result when
  515. Client :: client(),
  516. Source :: netconf_db(),
  517. Filter :: simple_xml() | xpath(),
  518. Timeout :: timeout(),
  519. Result :: {ok,[simple_xml()]} | {error,error_reason()}.
  520. get_config(Client, Source, Filter, Timeout) ->
  521. call(Client, {send_rpc_op, get_config, [Source, Filter], Timeout}).
  522. %%----------------------------------------------------------------------
  523. %% Send a 'edit-config' request.
  524. -spec edit_config(Client, Target, Config) -> Result when
  525. Client :: client(),
  526. Target :: netconf_db(),
  527. Config :: simple_xml() | [simple_xml()],
  528. Result :: ok | {error,error_reason()}.
  529. edit_config(Client, Target, Config) ->
  530. edit_config(Client, Target, Config, ?DEFAULT_TIMEOUT).
  531. -spec edit_config(Client, Target, Config, OptParams) -> Result when
  532. Client :: client(),
  533. Target :: netconf_db(),
  534. Config :: simple_xml() | [simple_xml()],
  535. OptParams :: [simple_xml()],
  536. Result :: ok | {error,error_reason()};
  537. (Client, Target, Config, Timeout) -> Result when
  538. Client :: client(),
  539. Target :: netconf_db(),
  540. Config :: simple_xml(),
  541. Timeout :: timeout(),
  542. Result :: ok | {error,error_reason()}.
  543. edit_config(Client, Target, Config, Timeout) when ?is_timeout(Timeout) ->
  544. edit_config(Client, Target, Config, [], Timeout);
  545. edit_config(Client, Target, Config, OptParams) when is_list(OptParams) ->
  546. edit_config(Client, Target, Config, OptParams, ?DEFAULT_TIMEOUT).
  547. -spec edit_config(Client, Target, Config, OptParams, Timeout) -> Result when
  548. Client :: client(),
  549. Target :: netconf_db(),
  550. Config :: simple_xml() | [simple_xml()],
  551. OptParams :: [simple_xml()],
  552. Timeout :: timeout(),
  553. Result :: ok | {error,error_reason()}.
  554. edit_config(Client, Target, Config, OptParams, Timeout) when not is_list(Config)->
  555. edit_config(Client, Target, [Config], OptParams, Timeout);
  556. edit_config(Client, Target, Config, OptParams, Timeout) ->
  557. call(Client, {send_rpc_op, edit_config, [Target,Config,OptParams], Timeout}).
  558. %%----------------------------------------------------------------------
  559. %% Send a 'delete-config' request.
  560. -spec delete_config(Client, Target) -> Result when
  561. Client :: client(),
  562. Target :: startup | candidate,
  563. Result :: ok | {error,error_reason()}.
  564. delete_config(Client, Target) ->
  565. delete_config(Client, Target, ?DEFAULT_TIMEOUT).
  566. -spec delete_config(Client, Target, Timeout) -> Result when
  567. Client :: client(),
  568. Target :: startup | candidate,
  569. Timeout :: timeout(),
  570. Result :: ok | {error,error_reason()}.
  571. delete_config(Client, Target, Timeout) when Target == startup;
  572. Target == candidate ->
  573. call(Client,{send_rpc_op, delete_config, [Target], Timeout}).
  574. %%----------------------------------------------------------------------
  575. %% Send a 'copy-config' request.
  576. -spec copy_config(Client, Target, Source) -> Result when
  577. Client :: client(),
  578. Target :: netconf_db(),
  579. Source :: netconf_db(),
  580. Result :: ok | {error,error_reason()}.
  581. copy_config(Client, Source, Target) ->
  582. copy_config(Client, Source, Target, ?DEFAULT_TIMEOUT).
  583. -spec copy_config(Client, Target, Source, Timeout) -> Result when
  584. Client :: client(),
  585. Target :: netconf_db(),
  586. Source :: netconf_db(),
  587. Timeout :: timeout(),
  588. Result :: ok | {error,error_reason()}.
  589. copy_config(Client, Target, Source, Timeout) ->
  590. call(Client,{send_rpc_op, copy_config, [Target, Source], Timeout}).
  591. %%----------------------------------------------------------------------
  592. %% Execute an action.
  593. -spec action(Client, Action) -> Result when
  594. Client :: client(),
  595. Action :: simple_xml(),
  596. Result :: ok | {ok,[simple_xml()]} | {error,error_reason()}.
  597. action(Client,Action) ->
  598. action(Client,Action,?DEFAULT_TIMEOUT).
  599. -spec action(Client, Action, Timeout) -> Result when
  600. Client :: client(),
  601. Action :: simple_xml(),
  602. Timeout :: timeout(),
  603. Result :: ok | {ok,[simple_xml()]} | {error,error_reason()}.
  604. action(Client,Action,Timeout) ->
  605. call(Client,{send_rpc_op, action, [Action], Timeout}).
  606. %%----------------------------------------------------------------------
  607. %% Send a 'create-subscription' request
  608. %% See RFC5277, NETCONF Event Notifications
  609. -spec create_subscription(Client) -> Result when
  610. Client :: client(),
  611. Result :: ok | {error,error_reason()}.
  612. create_subscription(Client) ->
  613. create_subscription(Client,?DEFAULT_STREAM,?DEFAULT_TIMEOUT).
  614. -spec create_subscription(Client, Stream | Filter | Timeout) -> Result when
  615. Client :: client(),
  616. Stream :: stream_name(),
  617. Filter :: simple_xml() | [simple_xml()],
  618. Timeout :: timeout(),
  619. Result :: ok | {error,error_reason()}.
  620. create_subscription(Client,Timeout)
  621. when ?is_timeout(Timeout) ->
  622. create_subscription(Client,?DEFAULT_STREAM,Timeout);
  623. create_subscription(Client,Stream)
  624. when ?is_string(Stream) ->
  625. create_subscription(Client,Stream,?DEFAULT_TIMEOUT);
  626. create_subscription(Client,Filter)
  627. when ?is_filter(Filter) ->
  628. create_subscription(Client,?DEFAULT_STREAM,Filter,
  629. ?DEFAULT_TIMEOUT).
  630. create_subscription(Client,Stream,Timeout)
  631. when ?is_string(Stream) andalso
  632. ?is_timeout(Timeout) ->
  633. call(Client,{send_rpc_op,{create_subscription,self()},
  634. [Stream,undefined,undefined,undefined],
  635. Timeout});
  636. create_subscription(Client,StartTime,StopTime)
  637. when ?is_string(StartTime) andalso
  638. ?is_string(StopTime) ->
  639. create_subscription(Client,?DEFAULT_STREAM,StartTime,StopTime,
  640. ?DEFAULT_TIMEOUT);
  641. create_subscription(Client,Filter,Timeout)
  642. when ?is_filter(Filter) andalso
  643. ?is_timeout(Timeout) ->
  644. create_subscription(Client,?DEFAULT_STREAM,Filter,Timeout);
  645. create_subscription(Client,Stream,Filter)
  646. when ?is_string(Stream) andalso
  647. ?is_filter(Filter) ->
  648. create_subscription(Client,Stream,Filter,?DEFAULT_TIMEOUT).
  649. create_subscription(Client,StartTime,StopTime,Timeout)
  650. when ?is_string(StartTime) andalso
  651. ?is_string(StopTime) andalso
  652. ?is_timeout(Timeout) ->
  653. create_subscription(Client,?DEFAULT_STREAM,StartTime,StopTime,Timeout);
  654. create_subscription(Client,Stream,StartTime,StopTime)
  655. when ?is_string(Stream) andalso
  656. ?is_string(StartTime) andalso
  657. ?is_string(StopTime) ->
  658. create_subscription(Client,Stream,StartTime,StopTime,?DEFAULT_TIMEOUT);
  659. create_subscription(Client,Filter,StartTime,StopTime)
  660. when ?is_filter(Filter) andalso
  661. ?is_string(StartTime) andalso
  662. ?is_string(StopTime) ->
  663. create_subscription(Client,?DEFAULT_STREAM,Filter,
  664. StartTime,StopTime,?DEFAULT_TIMEOUT);
  665. create_subscription(Client,Stream,Filter,Timeout)
  666. when ?is_string(Stream) andalso
  667. ?is_filter(Filter) andalso
  668. ?is_timeout(Timeout) ->
  669. call(Client,{send_rpc_op,{create_subscription,self()},
  670. [Stream,Filter,undefined,undefined],
  671. Timeout}).
  672. -spec create_subscription(Client, Stream, StartTime, StopTime, Timeout) ->
  673. Result when
  674. Client :: client(),
  675. Stream :: stream_name(),
  676. StartTime :: xs_datetime(),
  677. StopTime :: xs_datetime(),
  678. Timeout :: timeout(),
  679. Result :: ok | {error,error_reason()};
  680. (Client, Stream, Filter,StartTime, StopTime) ->
  681. Result when
  682. Client :: client(),
  683. Stream :: stream_name(),
  684. Filter :: simple_xml() | [simple_xml()],
  685. StartTime :: xs_datetime(),
  686. StopTime :: xs_datetime(),
  687. Result :: ok | {error,error_reason()}.
  688. create_subscription(Client,Stream,StartTime,StopTime,Timeout)
  689. when ?is_string(Stream) andalso
  690. ?is_string(StartTime) andalso
  691. ?is_string(StopTime) andalso
  692. ?is_timeout(Timeout) ->
  693. call(Client,{send_rpc_op,{create_subscription,self()},
  694. [Stream,undefined,StartTime,StopTime],
  695. Timeout});
  696. create_subscription(Client,Stream,Filter,StartTime,StopTime)
  697. when ?is_string(Stream) andalso
  698. ?is_filter(Filter) andalso
  699. ?is_string(StartTime) andalso
  700. ?is_string(StopTime) ->
  701. create_subscription(Client,Stream,Filter,StartTime,StopTime,?DEFAULT_TIMEOUT).
  702. -spec create_subscription(Client, Stream, Filter,StartTime, StopTime, Timeout) ->
  703. Result when
  704. Client :: client(),
  705. Stream :: stream_name(),
  706. Filter :: simple_xml() | [simple_xml()],
  707. StartTime :: xs_datetime(),
  708. StopTime :: xs_datetime(),
  709. Timeout :: timeout(),
  710. Result :: ok | {error,error_reason()}.
  711. create_subscription(Client,Stream,Filter,StartTime,StopTime,Timeout) ->
  712. call(Client,{send_rpc_op,{create_subscription, self()},
  713. [Stream,Filter,StartTime,StopTime],
  714. Timeout}).
  715. %%----------------------------------------------------------------------
  716. %% Send a request to get the given event streams
  717. %% See RFC5277, NETCONF Event Notifications
  718. -spec get_event_streams(Client)
  719. -> Result when
  720. Client :: client(),
  721. Result :: {ok,streams()} | {error,error_reason()}.
  722. get_event_streams(Client) ->
  723. get_event_streams(Client,[],?DEFAULT_TIMEOUT).
  724. -spec get_event_streams(Client, Timeout)
  725. -> Result when
  726. Client :: client(),
  727. Timeout :: timeout(),
  728. Result :: {ok,streams()} | {error,error_reason()};
  729. (Client, Streams) -> Result when
  730. Client :: client(),
  731. Streams :: [stream_name()],
  732. Result :: {ok,streams()} | {error,error_reason()}.
  733. get_event_streams(Client,Timeout) when is_integer(Timeout); Timeout==infinity ->
  734. get_event_streams(Client,[],Timeout);
  735. get_event_streams(Client,Streams) when is_list(Streams) ->
  736. get_event_streams(Client,Streams,?DEFAULT_TIMEOUT).
  737. -spec get_event_streams(Client, Streams, Timeout)
  738. -> Result when
  739. Client :: client(),
  740. Streams :: [stream_name()],
  741. Timeout :: timeout(),
  742. Result :: {ok,streams()} | {error,error_reason()}.
  743. get_event_streams(Client,Streams,Timeout) ->
  744. call(Client,{get_event_streams,Streams,Timeout}).
  745. %%----------------------------------------------------------------------
  746. %% Send a 'close-session' request
  747. -spec close_session(Client) -> Result when
  748. Client :: client(),
  749. Result :: ok | {error,error_reason()}.
  750. close_session(Client) ->
  751. close_session(Client, ?DEFAULT_TIMEOUT).
  752. -spec close_session(Client, Timeout) -> Result when
  753. Client :: client(),
  754. Timeout :: timeout(),
  755. Result :: ok | {error,error_reason()}.
  756. close_session(Client, Timeout) ->
  757. call(Client,{send_rpc_op, close_session, [], Timeout}, true).
  758. %%----------------------------------------------------------------------
  759. %% Send a 'kill-session' request
  760. -spec kill_session(Client, SessionId) -> Result when
  761. Client :: client(),
  762. SessionId :: pos_integer(),
  763. Result :: ok | {error,error_reason()}.
  764. kill_session(Client, SessionId) ->
  765. kill_session(Client, SessionId, ?DEFAULT_TIMEOUT).
  766. -spec kill_session(Client, SessionId, Timeout) -> Result when
  767. Client :: client(),
  768. SessionId :: pos_integer(),
  769. Timeout :: timeout(),
  770. Result :: ok | {error,error_reason()}.
  771. kill_session(Client, SessionId, Timeout) ->
  772. call(Client,{send_rpc_op, kill_session, [SessionId], Timeout}).
  773. %%----------------------------------------------------------------------
  774. %% Callback functions
  775. %%----------------------------------------------------------------------
  776. init(_KeyOrName,{CM,{Host,Port}},Options) ->
  777. case ssh_channel(#connection{reference=CM,host=Host,port=Port},Options) of
  778. {ok,Connection} ->
  779. {ok, CM, #state{connection = Connection}};
  780. {error,Reason}->
  781. {error,Reason}
  782. end;
  783. init(_KeyOrName,{_Host,_Port},Options) when Options#options.type==connection ->
  784. case ssh_connect(Options) of
  785. {ok, Connection} ->
  786. ConnPid = Connection#connection.reference,
  787. {ok, ConnPid, #state{connection = Connection}};
  788. Error ->
  789. Error
  790. end;
  791. init(_KeyOrName,{_Host,_Port},Options) ->
  792. case ssh_open(Options) of
  793. {ok, Connection} ->
  794. {ConnPid,_} = Connection#connection.reference,
  795. {ok, ConnPid, #state{connection = Connection}};
  796. {error,Reason}->
  797. {error,Reason}
  798. end.
  799. terminate(_, #state{connection=Connection}) ->
  800. ssh_close(Connection),
  801. ok.
  802. handle_msg({hello, Options, Timeout}, From,
  803. #state{connection=Connection,hello_status=HelloStatus} = State) ->
  804. case do_send(Connection, client_hello(Options)) of
  805. ok ->
  806. case HelloStatus of
  807. undefined ->
  808. {Ref,TRef} = set_request_timer(Timeout),
  809. {noreply, State#state{hello_status=#pending{tref=TRef,
  810. ref=Ref,
  811. caller=From}}};
  812. received ->
  813. {reply, ok, State#state{hello_status=done}};
  814. {error,Reason} ->
  815. {stop, {error,Reason}, State}
  816. end;
  817. Error ->
  818. {stop, Error, State}
  819. end;
  820. handle_msg(get_ssh_connection, _From, #state{connection=Connection}=State) ->
  821. Reply =
  822. case Connection#connection.reference of
  823. {_,_} -> {error,not_an_ssh_connection};
  824. CM -> {ok,{CM,{Connection#connection.host,
  825. Connection#connection.port}}}
  826. end,
  827. {reply, Reply, State};
  828. handle_msg(_, _From, #state{session_id=undefined} = State) ->
  829. %% Hello is not yet excanged - this shall never happen
  830. {reply,{error,waiting_for_hello},State};
  831. handle_msg(get_capabilities, _From, #state{capabilities = Caps} = State) ->
  832. {reply, Caps, State};
  833. handle_msg(get_session_id, _From, #state{session_id = Id} = State) ->
  834. {reply, Id, State};
  835. handle_msg({send, Timeout, SimpleXml}, From,
  836. #state{connection=Connection,pending=Pending} = State) ->
  837. case do_send(Connection, SimpleXml) of
  838. ok ->
  839. {Ref,TRef} = set_request_timer(Timeout),
  840. {noreply, State#state{pending=[#pending{tref=TRef,
  841. ref=Ref,
  842. caller=From} | Pending]}};
  843. Error ->
  844. {reply, Error, State}
  845. end;
  846. handle_msg({send_rpc, SimpleXml, Timeout}, From, State) ->
  847. do_send_rpc(undefined, SimpleXml, Timeout, From, State);
  848. handle_msg({send_rpc_op, Op, Data, Timeout}, From, State) ->
  849. SimpleXml = encode_rpc_operation(Op,Data),
  850. do_send_rpc(Op, SimpleXml, Timeout, From, State);
  851. handle_msg({get_event_streams=Op,Streams,Timeout}, From, State) ->
  852. Filter = {netconf,?NETMOD_NOTIF_NAMESPACE_ATTR,
  853. [{streams,[{stream,[{name,[Name]}]} || Name <- Streams]}]},
  854. SimpleXml = encode_rpc_operation(get,[Filter]),
  855. do_send_rpc(Op, SimpleXml, Timeout, From, State).
  856. handle_msg({ssh_cm, CM, {data, Ch, _Type, Data}}, State) ->
  857. ssh_connection:adjust_window(CM,Ch,size(Data)),
  858. handle_data(Data, State);
  859. handle_msg({ssh_cm, _CM, _SshCloseMsg}, State) ->
  860. %% _SshCloseMsg can probably be one of
  861. %% {eof,Ch}
  862. %% {exit_status,Ch,Status}
  863. %% {exit_signal,Ch,ExitSignal,ErrorMsg,LanguageString}
  864. %% {signal,Ch,Signal}
  865. %% This might e.g. happen if the server terminates the connection,
  866. %% as in kill-session (or if ssh:close is called from somewhere
  867. %% unexpected).
  868. %%! Log this??
  869. %%! Currently the log will say that the client closed the
  870. %%! connection - due to terminate/2
  871. {stop, State};
  872. handle_msg({Ref,timeout},
  873. #state{hello_status=#pending{ref=Ref,caller=Caller}} = State) ->
  874. ct_gen_conn:return(Caller,{error,{hello_session_failed,timeout}}),
  875. {stop,State#state{hello_status={error,timeout}}};
  876. handle_msg({Ref,timeout},#state{pending=Pending} = State) ->
  877. {value,#pending{op=Op,caller=Caller},Pending1} =
  878. lists:keytake(Ref,#pending.ref,Pending),
  879. ct_gen_conn:return(Caller,{error,timeout}),
  880. R = case Op of
  881. close_session -> stop;
  882. _ -> noreply
  883. end,
  884. %% Halfhearted try to get in correct state, this matches
  885. %% the implementation before this patch
  886. {R,State#state{pending=Pending1, no_end_tag_buff= <<>>, buff= <<>>}}.
  887. %% Called by ct_util_server to close registered connections before terminate.
  888. close(Client) ->
  889. case get_handle(Client) of
  890. {ok,Pid} ->
  891. case ct_gen_conn:stop(Pid) of
  892. {error,{process_down,Pid,noproc}} ->
  893. {error,already_closed};
  894. Result ->
  895. Result
  896. end;
  897. Error ->
  898. Error
  899. end.
  900. %%----------------------------------------------------------------------
  901. %% Internal functions
  902. %%----------------------------------------------------------------------
  903. call(Client, Msg) ->
  904. call(Client, Msg, infinity, false).
  905. call(Client, Msg, Timeout) when is_integer(Timeout); Timeout==infinity ->
  906. call(Client, Msg, Timeout, false);
  907. call(Client, Msg, WaitStop) when is_boolean(WaitStop) ->
  908. call(Client, Msg, infinity, WaitStop).
  909. call(Client, Msg, Timeout, WaitStop) ->
  910. case get_handle(Client) of
  911. {ok,Pid} ->
  912. case ct_gen_conn:call(Pid,Msg,Timeout) of
  913. {error,{process_down,Pid,noproc}} ->
  914. {error,no_such_client};
  915. {error,{process_down,Pid,normal}} when WaitStop ->
  916. %% This will happen when server closes connection
  917. %% before client received rpc-reply on
  918. %% close-session.
  919. ok;
  920. {error,{process_down,Pid,normal}} ->
  921. {error,closed};
  922. {error,{process_down,Pid,Reason}} ->
  923. {error,{closed,Reason}};
  924. Other when WaitStop ->
  925. MRef = erlang:monitor(process,Pid),
  926. receive
  927. {'DOWN',MRef,process,Pid,Normal} when Normal==normal;
  928. Normal==noproc ->
  929. Other;
  930. {'DOWN',MRef,process,Pid,Reason} ->
  931. {error,{{closed,Reason},Other}}
  932. after Timeout ->
  933. erlang:demonitor(MRef, [flush]),
  934. {error,{timeout,Other}}
  935. end;
  936. Other ->
  937. Other
  938. end;
  939. Error ->
  940. Error
  941. end.
  942. get_handle(Client) when is_pid(Client) ->
  943. {ok,Client};
  944. get_handle(Client) ->
  945. case ct_util:get_connection(Client, ?MODULE) of
  946. {ok,{Pid,_}} ->
  947. {ok,Pid};
  948. {error,no_registered_connection} ->
  949. {error,{no_connection_found,Client}};
  950. Error ->
  951. Error
  952. end.
  953. check_options(OptList,Options) ->
  954. check_options(OptList,undefined,undefined,Options).
  955. check_options([], undefined, _Port, _Options) ->
  956. {error, no_host_address};
  957. check_options([], _Host, undefined, _Options) ->
  958. {error, no_port};
  959. check_options([], Host, Port, Options) ->
  960. {Host,Port,Options};
  961. check_options([{ssh, Host}|T], _, Port, Options) ->
  962. check_options(T, Host, Port, Options#options{host=Host});
  963. check_options([{port,Port}|T], Host, _, Options) ->
  964. check_options(T, Host, Port, Options#options{port=Port});
  965. check_options([{timeout, Timeout}|T], Host, Port, Options)
  966. when is_integer(Timeout); Timeout==infinity ->
  967. check_options(T, Host, Port, Options#options{timeout = Timeout});
  968. check_options([{timeout, _} = Opt|_T], _Host, _Port, _Options) ->
  969. {error, {invalid_option, Opt}};
  970. check_options([Opt|T], Host, Port, #options{ssh=SshOpts}=Options) ->
  971. %% Option verified by ssh
  972. check_options(T, Host, Port, Options#options{ssh=[Opt|SshOpts]}).
  973. check_session_options([],Options) ->
  974. {ok,Options};
  975. check_session_options([{timeout, Timeout}|T], Options)
  976. when is_integer(Timeout); Timeout==infinity ->
  977. check_session_options(T, Options#options{timeout = Timeout});
  978. check_session_options([Opt|_T], _Options) ->
  979. {error, {invalid_option, Opt}}.
  980. %%%-----------------------------------------------------------------
  981. set_request_timer(infinity) ->
  982. {undefined,undefined};
  983. set_request_timer(T) ->
  984. Ref = make_ref(),
  985. {ok,TRef} = timer:send_after(T,{Ref,timeout}),
  986. {Ref,TRef}.
  987. %%%-----------------------------------------------------------------
  988. cancel_request_timer(undefined,undefined) ->
  989. ok;
  990. cancel_request_timer(Ref,TRef) ->
  991. _ = timer:cancel(TRef),
  992. receive {Ref,timeout} -> ok
  993. after 0 -> ok
  994. end.
  995. %%%-----------------------------------------------------------------
  996. client_hello(Options) when is_list(Options) ->
  997. UserCaps = [{capability, UserCap} ||
  998. {capability, UserCap} <- Options,
  999. is_list(hd(UserCap))],
  1000. {hello, ?NETCONF_NAMESPACE_ATTR,
  1001. [{capabilities,
  1002. [{capability,[?NETCONF_BASE_CAP++?NETCONF_BASE_CAP_VSN]}|
  1003. UserCaps]}]}.
  1004. %%%-----------------------------------------------------------------
  1005. encode_rpc_operation(Lock,[Target]) when Lock==lock; Lock==unlock ->
  1006. {Lock,[{target,[Target]}]};
  1007. encode_rpc_operation(get,[Filter]) ->
  1008. {get,filter(Filter)};
  1009. encode_rpc_operation(get_config,[Source,Filter]) ->
  1010. {'get-config',[{source,[Source]}] ++ filter(Filter)};
  1011. encode_rpc_operation(edit_config,[Target,Config,OptParams]) ->
  1012. {'edit-config',[{target,[Target]}] ++ OptParams ++ [{config,Config}]};
  1013. encode_rpc_operation(delete_config,[Target]) ->
  1014. {'delete-config',[{target,[Target]}]};
  1015. encode_rpc_operation(copy_config,[Target,Source]) ->
  1016. {'copy-config',[{target,[Target]},{source,[Source]}]};
  1017. encode_rpc_operation(action,[Action]) ->
  1018. {action,?ACTION_NAMESPACE_ATTR,[{data,[Action]}]};
  1019. encode_rpc_operation(kill_session,[SessionId]) ->
  1020. {'kill-session',[{'session-id',[integer_to_list(SessionId)]}]};
  1021. encode_rpc_operation(close_session,[]) ->
  1022. 'close-session';
  1023. encode_rpc_operation({create_subscription,_},
  1024. [Stream,Filter,StartTime,StopTime]) ->
  1025. {'create-subscription',?NETCONF_NOTIF_NAMESPACE_ATTR,
  1026. [{stream,[Stream]}] ++
  1027. filter(Filter) ++
  1028. maybe_element(startTime,StartTime) ++
  1029. maybe_element(stopTime,StopTime)}.
  1030. filter(undefined) ->
  1031. [];
  1032. filter({xpath,Filter}) when ?is_string(Filter) ->
  1033. [{filter,[{type,"xpath"},{select, Filter}],[]}];
  1034. filter(Filter) when is_list(Filter) ->
  1035. [{filter,[{type,"subtree"}],Filter}];
  1036. filter(Filter) ->
  1037. filter([Filter]).
  1038. maybe_element(_,undefined) ->
  1039. [];
  1040. maybe_element(Tag,Value) ->
  1041. [{Tag,[Value]}].
  1042. %%%-----------------------------------------------------------------
  1043. %%% Send XML data to server
  1044. do_send_rpc(PendingOp,SimpleXml,Timeout,Caller,
  1045. #state{connection=Connection,msg_id=MsgId,pending=Pending} = State) ->
  1046. case do_send_rpc(Connection, MsgId, SimpleXml) of
  1047. ok ->
  1048. {Ref,TRef} = set_request_timer(Timeout),
  1049. {noreply, State#state{msg_id=MsgId+1,
  1050. pending=[#pending{tref=TRef,
  1051. ref=Ref,
  1052. msg_id=MsgId,
  1053. op=PendingOp,
  1054. caller=Caller} | Pending]}};
  1055. Error ->
  1056. {reply, Error, State#state{msg_id=MsgId+1}}
  1057. end.
  1058. do_send_rpc(Connection, MsgId, SimpleXml) ->
  1059. do_send(Connection,
  1060. {rpc,
  1061. [{'message-id',MsgId} | ?NETCONF_NAMESPACE_ATTR],
  1062. [SimpleXml]}).
  1063. do_send(Connection, SimpleXml) ->
  1064. Xml=to_xml_doc(SimpleXml),
  1065. ssh_send(Connection, Xml).
  1066. to_xml_doc(Simple) ->
  1067. Prolog = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
  1068. Xml = unicode:characters_to_binary(
  1069. xmerl:export_simple([Simple],
  1070. xmerl_xml,
  1071. [#xmlAttribute{name=prolog,
  1072. value=Prolog}])),
  1073. <<Xml/binary,?END_TAG/binary>>.
  1074. %%%-----------------------------------------------------------------
  1075. %%% Parse and handle received XML data
  1076. %%% Two buffers are used:
  1077. %%% * 'no_end_tag_buff' contains data that is checked and does not
  1078. %%% contain any (part of an) end tag.
  1079. %%% * 'buff' contains all other saved data - it may or may not
  1080. %%% include (a part of) an end tag.
  1081. %%% The reason for this is to avoid running binary:split/3 multiple
  1082. %%% times on the same data when it does not contain an end tag. This
  1083. %%% can be a considerable optimation in the case when a lot of data is
  1084. %%% received (e.g. when fetching all data from a node) and the data is
  1085. %%% sent in multiple ssh packages.
  1086. handle_data(NewData,#state{connection=Connection} = State0) ->
  1087. log(Connection,recv,NewData),
  1088. NoEndTag0 = State0#state.no_end_tag_buff,
  1089. Buff0 = State0#state.buff,
  1090. Data = <<Buff0/binary, NewData/binary>>,
  1091. case binary:split(Data,?END_TAG,[]) of
  1092. [_NoEndTagFound] ->
  1093. NoEndTagSize = case byte_size(Data) of
  1094. Sz when Sz<5 -> 0;
  1095. Sz -> Sz-5
  1096. end,
  1097. <<NoEndTag1:NoEndTagSize/binary,Buff/binary>> = Data,
  1098. NoEndTag = <<NoEndTag0/binary,NoEndTag1/binary>>,
  1099. {noreply, State0#state{no_end_tag_buff=NoEndTag, buff=Buff}};
  1100. [FirstMsg0,Buff1] ->
  1101. FirstMsg = remove_initial_nl(<<NoEndTag0/binary,FirstMsg0/binary>>),
  1102. SaxArgs = [{event_fun,fun sax_event/3}, {event_state,[]}],
  1103. case xmerl_sax_parser:stream(FirstMsg, SaxArgs) of
  1104. {ok, Simple, _Thrash} ->
  1105. case decode(Simple, State0#state{no_end_tag_buff= <<>>,
  1106. buff=Buff1}) of
  1107. {noreply, #state{buff=Buff} = State} when Buff =/= <<>> ->
  1108. %% Recurse if we have more data in buffer
  1109. handle_data(<<>>, State);
  1110. Other ->
  1111. Other
  1112. end;
  1113. {fatal_error,_Loc,Reason,_EndTags,_EventState} ->
  1114. ?error(Connection#connection.name,
  1115. [{parse_error,Reason},
  1116. {buffer, Buff0},
  1117. {new_data,NewData}]),
  1118. handle_error(Reason, State0#state{no_end_tag_buff= <<>>,
  1119. buff= <<>>})
  1120. end
  1121. end.
  1122. %% xml does not accept a leading nl and some netconf server add a nl after
  1123. %% each ?END_TAG, ignore them
  1124. remove_initial_nl(<<"\n", Data/binary>>) ->
  1125. remove_initial_nl(Data);
  1126. remove_initial_nl(Data) ->
  1127. Data.
  1128. handle_error(Reason, State) ->
  1129. Pending1 = case State#state.pending of
  1130. [] -> [];
  1131. Pending ->
  1132. %% Assuming the first request gets the
  1133. %% first answer
  1134. P=#pending{tref=TRef,ref=Ref,caller=Caller} =
  1135. lists:last(Pending),
  1136. cancel_request_timer(Ref,TRef),
  1137. Reason1 = {failed_to_parse_received_data,Reason},
  1138. ct_gen_conn:return(Caller,{error,Reason1}),
  1139. lists:delete(P,Pending)
  1140. end,
  1141. {noreply, State#state{pending=Pending1}}.
  1142. %% Event function for the sax parser. It builds a simple XML structure.
  1143. %% Care is taken to keep namespace attributes and prefixes as in the original XML.
  1144. sax_event(Event,_Loc,State) ->
  1145. sax_event(Event,State).
  1146. sax_event({startPrefixMapping, Prefix, Uri},Acc) ->
  1147. %% startPrefixMapping will always come immediately before the
  1148. %% startElement where the namespace is defined.
  1149. [{xmlns,{Prefix,Uri}}|Acc];
  1150. sax_event({startElement,_Uri,_Name,QN,Attrs},Acc) ->
  1151. %% Pick out any namespace attributes inserted due to a
  1152. %% startPrefixMapping event.The rest of Acc will then be only
  1153. %% elements.
  1154. {NsAttrs,NewAcc} = split_attrs_and_elements(Acc,[]),
  1155. Tag = qn_to_tag(QN),
  1156. [{Tag,NsAttrs ++ parse_attrs(Attrs),[]}|NewAcc];
  1157. sax_event({endElement,_Uri,_Name,_QN},[{Name,Attrs,Cont},{Parent,PA,PC}|Acc]) ->
  1158. [{Parent,PA,[{Name,Attrs,lists:reverse(Cont)}|PC]}|Acc];
  1159. sax_event(endDocument,[{Tag,Attrs,Cont}]) ->
  1160. {Tag,Attrs,lists:reverse(Cont)};
  1161. sax_event({characters,String},[{Name,Attrs,Cont}|Acc]) ->
  1162. [{Name,Attrs,[String|Cont]}|Acc];
  1163. sax_event(_Event,State) ->
  1164. State.
  1165. split_attrs_and_elements([{xmlns,{Prefix,Uri}}|Rest],Attrs) ->
  1166. split_attrs_and_elements(Rest,[{xmlnstag(Prefix),Uri}|Attrs]);
  1167. split_attrs_and_elements(Elements,Attrs) ->
  1168. {Attrs,Elements}.
  1169. xmlnstag([]) ->
  1170. xmlns;
  1171. xmlnstag(Prefix) ->
  1172. list_to_atom("xmlns:"++Prefix).
  1173. qn_to_tag({[],Name}) ->
  1174. list_to_atom(Name);
  1175. qn_to_tag({Prefix,Name}) ->
  1176. list_to_atom(Prefix ++ ":" ++ Name).
  1177. parse_attrs([{_Uri, [], Name, Value}|Attrs]) ->
  1178. [{list_to_atom(Name),Value}|parse_attrs(Attrs)];
  1179. parse_attrs([{_Uri, Prefix, Name, Value}|Attrs]) ->
  1180. [{list_to_atom(Prefix ++ ":" ++ Name),Value}|parse_attrs(Attrs)];
  1181. parse_attrs([]) ->
  1182. [].
  1183. %%%-----------------------------------------------------------------
  1184. %%% Decoding of parsed XML data
  1185. decode({Tag,Attrs,_}=E, #state{connection=Connection,pending=Pending}=State) ->
  1186. ConnName = Connection#connection.name,
  1187. case get_local_name_atom(Tag) of
  1188. 'rpc-reply' ->
  1189. case get_msg_id(Attrs) of
  1190. undefined ->
  1191. case Pending of
  1192. [#pending{msg_id=MsgId}] ->
  1193. ?error(ConnName,[{warning,rpc_reply_missing_msg_id},
  1194. {assuming,MsgId}]),
  1195. decode_rpc_reply(MsgId,E,State);
  1196. _ ->
  1197. ?error(ConnName,[{error,rpc_reply_missing_msg_id}]),
  1198. {noreply,State}
  1199. end;
  1200. MsgId ->
  1201. decode_rpc_reply(MsgId,E,State)
  1202. end;
  1203. hello ->
  1204. case State#state.hello_status of
  1205. undefined ->
  1206. case decode_hello(E) of
  1207. {ok,SessionId,Capabilities} ->
  1208. {noreply,State#state{session_id = SessionId,
  1209. capabilities = Capabilities,
  1210. hello_status = received}};
  1211. {error,Reason} ->
  1212. {noreply,State#state{hello_status = {error,Reason}}}
  1213. end;
  1214. #pending{tref=TRef,ref=Ref,caller=Caller} ->
  1215. cancel_request_timer(Ref,TRef),
  1216. case decode_hello(E) of
  1217. {ok,SessionId,Capabilities} ->
  1218. ct_gen_conn:return(Caller,ok),
  1219. {noreply,State#state{session_id = SessionId,
  1220. capabilities = Capabilities,
  1221. hello_status = done}};
  1222. {error,Reason} ->
  1223. ct_gen_conn:return(Caller,{error,Reason}),
  1224. {stop,State#state{hello_status={error,Reason}}}
  1225. end;
  1226. Other ->
  1227. ?error(ConnName,[{got_unexpected_hello,E},
  1228. {hello_status,Other}]),
  1229. {noreply,State}
  1230. end;
  1231. notification ->
  1232. EventReceiver = State#state.event_receiver,
  1233. EventReceiver ! E,
  1234. {noreply,State};
  1235. Other ->
  1236. %% Result of send/2, when not sending an rpc request - or
  1237. %% if netconf server sends noise. Can handle this only if
  1238. %% there is just one pending that matches (i.e. has
  1239. %% undefined msg_id and op)
  1240. case [P || P = #pending{msg_id=undefined,op=undefined} <- Pending] of
  1241. [#pending{tref=TRef,ref=Ref,caller=Caller}] ->
  1242. cancel_request_timer(Ref,TRef),
  1243. ct_gen_conn:return(Caller,E),
  1244. {noreply,State#state{pending=[]}};
  1245. _ ->
  1246. ?error(ConnName,[{got_unexpected_msg,Other},
  1247. {expecting,Pending}]),
  1248. {noreply,State}
  1249. end
  1250. end.
  1251. get_msg_id(Attrs) ->
  1252. case lists:keyfind('message-id',1,Attrs) of
  1253. {_,Str} ->
  1254. list_to_integer(Str);
  1255. false ->
  1256. undefined
  1257. end.
  1258. decode_rpc_reply(MsgId,{_,Attrs,Content0}=E,#state{pending=Pending} = State) ->
  1259. case lists:keytake(MsgId,#pending.msg_id,Pending) of
  1260. {value, #pending{tref=TRef,ref=Ref,op=Op,caller=Caller}, Pending1} ->
  1261. cancel_request_timer(Ref,TRef),
  1262. Content = forward_xmlns_attr(Attrs,Content0),
  1263. {CallerReply,{ServerReply,State2}} =
  1264. do_decode_rpc_reply(Op,Content,State#state{pending=Pending1}),
  1265. ct_gen_conn:return(Caller,CallerReply),
  1266. {ServerReply,State2};
  1267. false ->
  1268. %% Result of send/2, when receiving a correct
  1269. %% rpc-reply. Can handle this only if there is just one
  1270. %% pending that matches (i.e. has undefined msg_id and op)
  1271. case [P || P = #pending{msg_id=undefined,op=undefined} <- Pending] of
  1272. [#pending{tref=TRef,
  1273. ref=Ref,
  1274. msg_id=undefined,
  1275. op=undefined,
  1276. caller=Caller}] ->
  1277. cancel_request_timer(Ref,TRef),
  1278. ct_gen_conn:return(Caller,E),
  1279. {noreply,State#state{pending=[]}};
  1280. _ ->
  1281. ConnName = (State#state.connection)#connection.name,
  1282. ?error(ConnName,[{got_unexpected_msg_id,MsgId},
  1283. {expecting,Pending}]),
  1284. {noreply,State}
  1285. end
  1286. end.
  1287. do_decode_rpc_reply(Op,Result,State)
  1288. when Op==lock; Op==unlock; Op==edit_config; Op==delete_config;

Large files files are truncated, but you can click here to view the full file