PageRenderTime 27ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/common_test/src/ct_netconfc.erl

https://github.com/cobusc/otp
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
  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(),
  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(),
  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(),
  551. OptParams :: [simple_xml()],
  552. Timeout :: timeout(),
  553. Result :: ok | {error,error_reason()}.
  554. edit_config(Client, Target, Config, OptParams, Timeout) ->
  555. call(Client, {send_rpc_op, edit_config, [Target,Config,OptParams], Timeout}).
  556. %%----------------------------------------------------------------------
  557. %% Send a 'delete-config' request.
  558. -spec delete_config(Client, Target) -> Result when
  559. Client :: client(),
  560. Target :: startup | candidate,
  561. Result :: ok | {error,error_reason()}.
  562. delete_config(Client, Target) ->
  563. delete_config(Client, Target, ?DEFAULT_TIMEOUT).
  564. -spec delete_config(Client, Target, Timeout) -> Result when
  565. Client :: client(),
  566. Target :: startup | candidate,
  567. Timeout :: timeout(),
  568. Result :: ok | {error,error_reason()}.
  569. delete_config(Client, Target, Timeout) when Target == startup;
  570. Target == candidate ->
  571. call(Client,{send_rpc_op, delete_config, [Target], Timeout}).
  572. %%----------------------------------------------------------------------
  573. %% Send a 'copy-config' request.
  574. -spec copy_config(Client, Target, Source) -> Result when
  575. Client :: client(),
  576. Target :: netconf_db(),
  577. Source :: netconf_db(),
  578. Result :: ok | {error,error_reason()}.
  579. copy_config(Client, Source, Target) ->
  580. copy_config(Client, Source, Target, ?DEFAULT_TIMEOUT).
  581. -spec copy_config(Client, Target, Source, Timeout) -> Result when
  582. Client :: client(),
  583. Target :: netconf_db(),
  584. Source :: netconf_db(),
  585. Timeout :: timeout(),
  586. Result :: ok | {error,error_reason()}.
  587. copy_config(Client, Target, Source, Timeout) ->
  588. call(Client,{send_rpc_op, copy_config, [Target, Source], Timeout}).
  589. %%----------------------------------------------------------------------
  590. %% Execute an action.
  591. -spec action(Client, Action) -> Result when
  592. Client :: client(),
  593. Action :: simple_xml(),
  594. Result :: ok | {ok,[simple_xml()]} | {error,error_reason()}.
  595. action(Client,Action) ->
  596. action(Client,Action,?DEFAULT_TIMEOUT).
  597. -spec action(Client, Action, Timeout) -> Result when
  598. Client :: client(),
  599. Action :: simple_xml(),
  600. Timeout :: timeout(),
  601. Result :: ok | {ok,[simple_xml()]} | {error,error_reason()}.
  602. action(Client,Action,Timeout) ->
  603. call(Client,{send_rpc_op, action, [Action], Timeout}).
  604. %%----------------------------------------------------------------------
  605. %% Send a 'create-subscription' request
  606. %% See RFC5277, NETCONF Event Notifications
  607. -spec create_subscription(Client) -> Result when
  608. Client :: client(),
  609. Result :: ok | {error,error_reason()}.
  610. create_subscription(Client) ->
  611. create_subscription(Client,?DEFAULT_STREAM,?DEFAULT_TIMEOUT).
  612. -spec create_subscription(Client, Stream | Filter | Timeout) -> Result when
  613. Client :: client(),
  614. Stream :: stream_name(),
  615. Filter :: simple_xml() | [simple_xml()],
  616. Timeout :: timeout(),
  617. Result :: ok | {error,error_reason()}.
  618. create_subscription(Client,Timeout)
  619. when ?is_timeout(Timeout) ->
  620. create_subscription(Client,?DEFAULT_STREAM,Timeout);
  621. create_subscription(Client,Stream)
  622. when ?is_string(Stream) ->
  623. create_subscription(Client,Stream,?DEFAULT_TIMEOUT);
  624. create_subscription(Client,Filter)
  625. when ?is_filter(Filter) ->
  626. create_subscription(Client,?DEFAULT_STREAM,Filter,
  627. ?DEFAULT_TIMEOUT).
  628. create_subscription(Client,Stream,Timeout)
  629. when ?is_string(Stream) andalso
  630. ?is_timeout(Timeout) ->
  631. call(Client,{send_rpc_op,{create_subscription,self()},
  632. [Stream,undefined,undefined,undefined],
  633. Timeout});
  634. create_subscription(Client,StartTime,StopTime)
  635. when ?is_string(StartTime) andalso
  636. ?is_string(StopTime) ->
  637. create_subscription(Client,?DEFAULT_STREAM,StartTime,StopTime,
  638. ?DEFAULT_TIMEOUT);
  639. create_subscription(Client,Filter,Timeout)
  640. when ?is_filter(Filter) andalso
  641. ?is_timeout(Timeout) ->
  642. create_subscription(Client,?DEFAULT_STREAM,Filter,Timeout);
  643. create_subscription(Client,Stream,Filter)
  644. when ?is_string(Stream) andalso
  645. ?is_filter(Filter) ->
  646. create_subscription(Client,Stream,Filter,?DEFAULT_TIMEOUT).
  647. create_subscription(Client,StartTime,StopTime,Timeout)
  648. when ?is_string(StartTime) andalso
  649. ?is_string(StopTime) andalso
  650. ?is_timeout(Timeout) ->
  651. create_subscription(Client,?DEFAULT_STREAM,StartTime,StopTime,Timeout);
  652. create_subscription(Client,Stream,StartTime,StopTime)
  653. when ?is_string(Stream) andalso
  654. ?is_string(StartTime) andalso
  655. ?is_string(StopTime) ->
  656. create_subscription(Client,Stream,StartTime,StopTime,?DEFAULT_TIMEOUT);
  657. create_subscription(Client,Filter,StartTime,StopTime)
  658. when ?is_filter(Filter) andalso
  659. ?is_string(StartTime) andalso
  660. ?is_string(StopTime) ->
  661. create_subscription(Client,?DEFAULT_STREAM,Filter,
  662. StartTime,StopTime,?DEFAULT_TIMEOUT);
  663. create_subscription(Client,Stream,Filter,Timeout)
  664. when ?is_string(Stream) andalso
  665. ?is_filter(Filter) andalso
  666. ?is_timeout(Timeout) ->
  667. call(Client,{send_rpc_op,{create_subscription,self()},
  668. [Stream,Filter,undefined,undefined],
  669. Timeout}).
  670. -spec create_subscription(Client, Stream, StartTime, StopTime, Timeout) ->
  671. Result when
  672. Client :: client(),
  673. Stream :: stream_name(),
  674. StartTime :: xs_datetime(),
  675. StopTime :: xs_datetime(),
  676. Timeout :: timeout(),
  677. Result :: ok | {error,error_reason()};
  678. (Client, Stream, Filter,StartTime, StopTime) ->
  679. Result when
  680. Client :: client(),
  681. Stream :: stream_name(),
  682. Filter :: simple_xml() | [simple_xml()],
  683. StartTime :: xs_datetime(),
  684. StopTime :: xs_datetime(),
  685. Result :: ok | {error,error_reason()}.
  686. create_subscription(Client,Stream,StartTime,StopTime,Timeout)
  687. when ?is_string(Stream) andalso
  688. ?is_string(StartTime) andalso
  689. ?is_string(StopTime) andalso
  690. ?is_timeout(Timeout) ->
  691. call(Client,{send_rpc_op,{create_subscription,self()},
  692. [Stream,undefined,StartTime,StopTime],
  693. Timeout});
  694. create_subscription(Client,Stream,Filter,StartTime,StopTime)
  695. when ?is_string(Stream) andalso
  696. ?is_filter(Filter) andalso
  697. ?is_string(StartTime) andalso
  698. ?is_string(StopTime) ->
  699. create_subscription(Client,Stream,Filter,StartTime,StopTime,?DEFAULT_TIMEOUT).
  700. -spec create_subscription(Client, Stream, Filter,StartTime, StopTime, Timeout) ->
  701. Result when
  702. Client :: client(),
  703. Stream :: stream_name(),
  704. Filter :: simple_xml() | [simple_xml()],
  705. StartTime :: xs_datetime(),
  706. StopTime :: xs_datetime(),
  707. Timeout :: timeout(),
  708. Result :: ok | {error,error_reason()}.
  709. create_subscription(Client,Stream,Filter,StartTime,StopTime,Timeout) ->
  710. call(Client,{send_rpc_op,{create_subscription, self()},
  711. [Stream,Filter,StartTime,StopTime],
  712. Timeout}).
  713. %%----------------------------------------------------------------------
  714. %% Send a request to get the given event streams
  715. %% See RFC5277, NETCONF Event Notifications
  716. -spec get_event_streams(Client)
  717. -> Result when
  718. Client :: client(),
  719. Result :: {ok,streams()} | {error,error_reason()}.
  720. get_event_streams(Client) ->
  721. get_event_streams(Client,[],?DEFAULT_TIMEOUT).
  722. -spec get_event_streams(Client, Timeout)
  723. -> Result when
  724. Client :: client(),
  725. Timeout :: timeout(),
  726. Result :: {ok,streams()} | {error,error_reason()};
  727. (Client, Streams) -> Result when
  728. Client :: client(),
  729. Streams :: [stream_name()],
  730. Result :: {ok,streams()} | {error,error_reason()}.
  731. get_event_streams(Client,Timeout) when is_integer(Timeout); Timeout==infinity ->
  732. get_event_streams(Client,[],Timeout);
  733. get_event_streams(Client,Streams) when is_list(Streams) ->
  734. get_event_streams(Client,Streams,?DEFAULT_TIMEOUT).
  735. -spec get_event_streams(Client, Streams, Timeout)
  736. -> Result when
  737. Client :: client(),
  738. Streams :: [stream_name()],
  739. Timeout :: timeout(),
  740. Result :: {ok,streams()} | {error,error_reason()}.
  741. get_event_streams(Client,Streams,Timeout) ->
  742. call(Client,{get_event_streams,Streams,Timeout}).
  743. %%----------------------------------------------------------------------
  744. %% Send a 'close-session' request
  745. -spec close_session(Client) -> Result when
  746. Client :: client(),
  747. Result :: ok | {error,error_reason()}.
  748. close_session(Client) ->
  749. close_session(Client, ?DEFAULT_TIMEOUT).
  750. -spec close_session(Client, Timeout) -> Result when
  751. Client :: client(),
  752. Timeout :: timeout(),
  753. Result :: ok | {error,error_reason()}.
  754. close_session(Client, Timeout) ->
  755. call(Client,{send_rpc_op, close_session, [], Timeout}, true).
  756. %%----------------------------------------------------------------------
  757. %% Send a 'kill-session' request
  758. -spec kill_session(Client, SessionId) -> Result when
  759. Client :: client(),
  760. SessionId :: pos_integer(),
  761. Result :: ok | {error,error_reason()}.
  762. kill_session(Client, SessionId) ->
  763. kill_session(Client, SessionId, ?DEFAULT_TIMEOUT).
  764. -spec kill_session(Client, SessionId, Timeout) -> Result when
  765. Client :: client(),
  766. SessionId :: pos_integer(),
  767. Timeout :: timeout(),
  768. Result :: ok | {error,error_reason()}.
  769. kill_session(Client, SessionId, Timeout) ->
  770. call(Client,{send_rpc_op, kill_session, [SessionId], Timeout}).
  771. %%----------------------------------------------------------------------
  772. %% Callback functions
  773. %%----------------------------------------------------------------------
  774. init(_KeyOrName,{CM,{Host,Port}},Options) ->
  775. case ssh_channel(#connection{reference=CM,host=Host,port=Port},Options) of
  776. {ok,Connection} ->
  777. {ok, CM, #state{connection = Connection}};
  778. {error,Reason}->
  779. {error,Reason}
  780. end;
  781. init(_KeyOrName,{_Host,_Port},Options) when Options#options.type==connection ->
  782. case ssh_connect(Options) of
  783. {ok, Connection} ->
  784. ConnPid = Connection#connection.reference,
  785. {ok, ConnPid, #state{connection = Connection}};
  786. Error ->
  787. Error
  788. end;
  789. init(_KeyOrName,{_Host,_Port},Options) ->
  790. case ssh_open(Options) of
  791. {ok, Connection} ->
  792. {ConnPid,_} = Connection#connection.reference,
  793. {ok, ConnPid, #state{connection = Connection}};
  794. {error,Reason}->
  795. {error,Reason}
  796. end.
  797. terminate(_, #state{connection=Connection}) ->
  798. ssh_close(Connection),
  799. ok.
  800. handle_msg({hello, Options, Timeout}, From,
  801. #state{connection=Connection,hello_status=HelloStatus} = State) ->
  802. case do_send(Connection, client_hello(Options)) of
  803. ok ->
  804. case HelloStatus of
  805. undefined ->
  806. {Ref,TRef} = set_request_timer(Timeout),
  807. {noreply, State#state{hello_status=#pending{tref=TRef,
  808. ref=Ref,
  809. caller=From}}};
  810. received ->
  811. {reply, ok, State#state{hello_status=done}};
  812. {error,Reason} ->
  813. {stop, {error,Reason}, State}
  814. end;
  815. Error ->
  816. {stop, Error, State}
  817. end;
  818. handle_msg(get_ssh_connection, _From, #state{connection=Connection}=State) ->
  819. Reply =
  820. case Connection#connection.reference of
  821. {_,_} -> {error,not_an_ssh_connection};
  822. CM -> {ok,{CM,{Connection#connection.host,
  823. Connection#connection.port}}}
  824. end,
  825. {reply, Reply, State};
  826. handle_msg(_, _From, #state{session_id=undefined} = State) ->
  827. %% Hello is not yet excanged - this shall never happen
  828. {reply,{error,waiting_for_hello},State};
  829. handle_msg(get_capabilities, _From, #state{capabilities = Caps} = State) ->
  830. {reply, Caps, State};
  831. handle_msg(get_session_id, _From, #state{session_id = Id} = State) ->
  832. {reply, Id, State};
  833. handle_msg({send, Timeout, SimpleXml}, From,
  834. #state{connection=Connection,pending=Pending} = State) ->
  835. case do_send(Connection, SimpleXml) of
  836. ok ->
  837. {Ref,TRef} = set_request_timer(Timeout),
  838. {noreply, State#state{pending=[#pending{tref=TRef,
  839. ref=Ref,
  840. caller=From} | Pending]}};
  841. Error ->
  842. {reply, Error, State}
  843. end;
  844. handle_msg({send_rpc, SimpleXml, Timeout}, From, State) ->
  845. do_send_rpc(undefined, SimpleXml, Timeout, From, State);
  846. handle_msg({send_rpc_op, Op, Data, Timeout}, From, State) ->
  847. SimpleXml = encode_rpc_operation(Op,Data),
  848. do_send_rpc(Op, SimpleXml, Timeout, From, State);
  849. handle_msg({get_event_streams=Op,Streams,Timeout}, From, State) ->
  850. Filter = {netconf,?NETMOD_NOTIF_NAMESPACE_ATTR,
  851. [{streams,[{stream,[{name,[Name]}]} || Name <- Streams]}]},
  852. SimpleXml = encode_rpc_operation(get,[Filter]),
  853. do_send_rpc(Op, SimpleXml, Timeout, From, State).
  854. handle_msg({ssh_cm, CM, {data, Ch, _Type, Data}}, State) ->
  855. ssh_connection:adjust_window(CM,Ch,size(Data)),
  856. handle_data(Data, State);
  857. handle_msg({ssh_cm, _CM, _SshCloseMsg}, State) ->
  858. %% _SshCloseMsg can probably be one of
  859. %% {eof,Ch}
  860. %% {exit_status,Ch,Status}
  861. %% {exit_signal,Ch,ExitSignal,ErrorMsg,LanguageString}
  862. %% {signal,Ch,Signal}
  863. %% This might e.g. happen if the server terminates the connection,
  864. %% as in kill-session (or if ssh:close is called from somewhere
  865. %% unexpected).
  866. %%! Log this??
  867. %%! Currently the log will say that the client closed the
  868. %%! connection - due to terminate/2
  869. {stop, State};
  870. handle_msg({Ref,timeout},
  871. #state{hello_status=#pending{ref=Ref,caller=Caller}} = State) ->
  872. ct_gen_conn:return(Caller,{error,{hello_session_failed,timeout}}),
  873. {stop,State#state{hello_status={error,timeout}}};
  874. handle_msg({Ref,timeout},#state{pending=Pending} = State) ->
  875. {value,#pending{op=Op,caller=Caller},Pending1} =
  876. lists:keytake(Ref,#pending.ref,Pending),
  877. ct_gen_conn:return(Caller,{error,timeout}),
  878. R = case Op of
  879. close_session -> stop;
  880. _ -> noreply
  881. end,
  882. %% Halfhearted try to get in correct state, this matches
  883. %% the implementation before this patch
  884. {R,State#state{pending=Pending1, no_end_tag_buff= <<>>, buff= <<>>}}.
  885. %% Called by ct_util_server to close registered connections before terminate.
  886. close(Client) ->
  887. case get_handle(Client) of
  888. {ok,Pid} ->
  889. case ct_gen_conn:stop(Pid) of
  890. {error,{process_down,Pid,noproc}} ->
  891. {error,already_closed};
  892. Result ->
  893. Result
  894. end;
  895. Error ->
  896. Error
  897. end.
  898. %%----------------------------------------------------------------------
  899. %% Internal functions
  900. %%----------------------------------------------------------------------
  901. call(Client, Msg) ->
  902. call(Client, Msg, infinity, false).
  903. call(Client, Msg, Timeout) when is_integer(Timeout); Timeout==infinity ->
  904. call(Client, Msg, Timeout, false);
  905. call(Client, Msg, WaitStop) when is_boolean(WaitStop) ->
  906. call(Client, Msg, infinity, WaitStop).
  907. call(Client, Msg, Timeout, WaitStop) ->
  908. case get_handle(Client) of
  909. {ok,Pid} ->
  910. case ct_gen_conn:call(Pid,Msg,Timeout) of
  911. {error,{process_down,Pid,noproc}} ->
  912. {error,no_such_client};
  913. {error,{process_down,Pid,normal}} when WaitStop ->
  914. %% This will happen when server closes connection
  915. %% before client received rpc-reply on
  916. %% close-session.
  917. ok;
  918. {error,{process_down,Pid,normal}} ->
  919. {error,closed};
  920. {error,{process_down,Pid,Reason}} ->
  921. {error,{closed,Reason}};
  922. Other when WaitStop ->
  923. MRef = erlang:monitor(process,Pid),
  924. receive
  925. {'DOWN',MRef,process,Pid,Normal} when Normal==normal;
  926. Normal==noproc ->
  927. Other;
  928. {'DOWN',MRef,process,Pid,Reason} ->
  929. {error,{{closed,Reason},Other}}
  930. after Timeout ->
  931. erlang:demonitor(MRef, [flush]),
  932. {error,{timeout,Other}}
  933. end;
  934. Other ->
  935. Other
  936. end;
  937. Error ->
  938. Error
  939. end.
  940. get_handle(Client) when is_pid(Client) ->
  941. {ok,Client};
  942. get_handle(Client) ->
  943. case ct_util:get_connection(Client, ?MODULE) of
  944. {ok,{Pid,_}} ->
  945. {ok,Pid};
  946. {error,no_registered_connection} ->
  947. {error,{no_connection_found,Client}};
  948. Error ->
  949. Error
  950. end.
  951. check_options(OptList,Options) ->
  952. check_options(OptList,undefined,undefined,Options).
  953. check_options([], undefined, _Port, _Options) ->
  954. {error, no_host_address};
  955. check_options([], _Host, undefined, _Options) ->
  956. {error, no_port};
  957. check_options([], Host, Port, Options) ->
  958. {Host,Port,Options};
  959. check_options([{ssh, Host}|T], _, Port, Options) ->
  960. check_options(T, Host, Port, Options#options{host=Host});
  961. check_options([{port,Port}|T], Host, _, Options) ->
  962. check_options(T, Host, Port, Options#options{port=Port});
  963. check_options([{timeout, Timeout}|T], Host, Port, Options)
  964. when is_integer(Timeout); Timeout==infinity ->
  965. check_options(T, Host, Port, Options#options{timeout = Timeout});
  966. check_options([{timeout, _} = Opt|_T], _Host, _Port, _Options) ->
  967. {error, {invalid_option, Opt}};
  968. check_options([Opt|T], Host, Port, #options{ssh=SshOpts}=Options) ->
  969. %% Option verified by ssh
  970. check_options(T, Host, Port, Options#options{ssh=[Opt|SshOpts]}).
  971. check_session_options([],Options) ->
  972. {ok,Options};
  973. check_session_options([{timeout, Timeout}|T], Options)
  974. when is_integer(Timeout); Timeout==infinity ->
  975. check_session_options(T, Options#options{timeout = Timeout});
  976. check_session_options([Opt|_T], _Options) ->
  977. {error, {invalid_option, Opt}}.
  978. %%%-----------------------------------------------------------------
  979. set_request_timer(infinity) ->
  980. {undefined,undefined};
  981. set_request_timer(T) ->
  982. Ref = make_ref(),
  983. {ok,TRef} = timer:send_after(T,{Ref,timeout}),
  984. {Ref,TRef}.
  985. %%%-----------------------------------------------------------------
  986. cancel_request_timer(undefined,undefined) ->
  987. ok;
  988. cancel_request_timer(Ref,TRef) ->
  989. _ = timer:cancel(TRef),
  990. receive {Ref,timeout} -> ok
  991. after 0 -> ok
  992. end.
  993. %%%-----------------------------------------------------------------
  994. client_hello(Options) when is_list(Options) ->
  995. UserCaps = [{capability, UserCap} ||
  996. {capability, UserCap} <- Options,
  997. is_list(hd(UserCap))],
  998. {hello, ?NETCONF_NAMESPACE_ATTR,
  999. [{capabilities,
  1000. [{capability,[?NETCONF_BASE_CAP++?NETCONF_BASE_CAP_VSN]}|
  1001. UserCaps]}]}.
  1002. %%%-----------------------------------------------------------------
  1003. encode_rpc_operation(Lock,[Target]) when Lock==lock; Lock==unlock ->
  1004. {Lock,[{target,[Target]}]};
  1005. encode_rpc_operation(get,[Filter]) ->
  1006. {get,filter(Filter)};
  1007. encode_rpc_operation(get_config,[Source,Filter]) ->
  1008. {'get-config',[{source,[Source]}] ++ filter(Filter)};
  1009. encode_rpc_operation(edit_config,[Target,Config,OptParams]) ->
  1010. {'edit-config',[{target,[Target]}] ++ OptParams ++ [{config,[Config]}]};
  1011. encode_rpc_operation(delete_config,[Target]) ->
  1012. {'delete-config',[{target,[Target]}]};
  1013. encode_rpc_operation(copy_config,[Target,Source]) ->
  1014. {'copy-config',[{target,[Target]},{source,[Source]}]};
  1015. encode_rpc_operation(action,[Action]) ->
  1016. {action,?ACTION_NAMESPACE_ATTR,[{data,[Action]}]};
  1017. encode_rpc_operation(kill_session,[SessionId]) ->
  1018. {'kill-session',[{'session-id',[integer_to_list(SessionId)]}]};
  1019. encode_rpc_operation(close_session,[]) ->
  1020. 'close-session';
  1021. encode_rpc_operation({create_subscription,_},
  1022. [Stream,Filter,StartTime,StopTime]) ->
  1023. {'create-subscription',?NETCONF_NOTIF_NAMESPACE_ATTR,
  1024. [{stream,[Stream]}] ++
  1025. filter(Filter) ++
  1026. maybe_element(startTime,StartTime) ++
  1027. maybe_element(stopTime,StopTime)}.
  1028. filter(undefined) ->
  1029. [];
  1030. filter({xpath,Filter}) when ?is_string(Filter) ->
  1031. [{filter,[{type,"xpath"},{select, Filter}],[]}];
  1032. filter(Filter) when is_list(Filter) ->
  1033. [{filter,[{type,"subtree"}],Filter}];
  1034. filter(Filter) ->
  1035. filter([Filter]).
  1036. maybe_element(_,undefined) ->
  1037. [];
  1038. maybe_element(Tag,Value) ->
  1039. [{Tag,[Value]}].
  1040. %%%-----------------------------------------------------------------
  1041. %%% Send XML data to server
  1042. do_send_rpc(PendingOp,SimpleXml,Timeout,Caller,
  1043. #state{connection=Connection,msg_id=MsgId,pending=Pending} = State) ->
  1044. case do_send_rpc(Connection, MsgId, SimpleXml) of
  1045. ok ->
  1046. {Ref,TRef} = set_request_timer(Timeout),
  1047. {noreply, State#state{msg_id=MsgId+1,
  1048. pending=[#pending{tref=TRef,
  1049. ref=Ref,
  1050. msg_id=MsgId,
  1051. op=PendingOp,
  1052. caller=Caller} | Pending]}};
  1053. Error ->
  1054. {reply, Error, State#state{msg_id=MsgId+1}}
  1055. end.
  1056. do_send_rpc(Connection, MsgId, SimpleXml) ->
  1057. do_send(Connection,
  1058. {rpc,
  1059. [{'message-id',MsgId} | ?NETCONF_NAMESPACE_ATTR],
  1060. [SimpleXml]}).
  1061. do_send(Connection, SimpleXml) ->
  1062. Xml=to_xml_doc(SimpleXml),
  1063. ssh_send(Connection, Xml).
  1064. to_xml_doc(Simple) ->
  1065. Prolog = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
  1066. Xml = unicode:characters_to_binary(
  1067. xmerl:export_simple([Simple],
  1068. xmerl_xml,
  1069. [#xmlAttribute{name=prolog,
  1070. value=Prolog}])),
  1071. <<Xml/binary,?END_TAG/binary>>.
  1072. %%%-----------------------------------------------------------------
  1073. %%% Parse and handle received XML data
  1074. %%% Two buffers are used:
  1075. %%% * 'no_end_tag_buff' contains data that is checked and does not
  1076. %%% contain any (part of an) end tag.
  1077. %%% * 'buff' contains all other saved data - it may or may not
  1078. %%% include (a part of) an end tag.
  1079. %%% The reason for this is to avoid running binary:split/3 multiple
  1080. %%% times on the same data when it does not contain an end tag. This
  1081. %%% can be a considerable optimation in the case when a lot of data is
  1082. %%% received (e.g. when fetching all data from a node) and the data is
  1083. %%% sent in multiple ssh packages.
  1084. handle_data(NewData,#state{connection=Connection} = State0) ->
  1085. log(Connection,recv,NewData),
  1086. NoEndTag0 = State0#state.no_end_tag_buff,
  1087. Buff0 = State0#state.buff,
  1088. Data = <<Buff0/binary, NewData/binary>>,
  1089. case binary:split(Data,?END_TAG,[]) of
  1090. [_NoEndTagFound] ->
  1091. NoEndTagSize = case byte_size(Data) of
  1092. Sz when Sz<5 -> 0;
  1093. Sz -> Sz-5
  1094. end,
  1095. <<NoEndTag1:NoEndTagSize/binary,Buff/binary>> = Data,
  1096. NoEndTag = <<NoEndTag0/binary,NoEndTag1/binary>>,
  1097. {noreply, State0#state{no_end_tag_buff=NoEndTag, buff=Buff}};
  1098. [FirstMsg0,Buff1] ->
  1099. FirstMsg = remove_initial_nl(<<NoEndTag0/binary,FirstMsg0/binary>>),
  1100. SaxArgs = [{event_fun,fun sax_event/3}, {event_state,[]}],
  1101. case xmerl_sax_parser:stream(FirstMsg, SaxArgs) of
  1102. {ok, Simple, _Thrash} ->
  1103. case decode(Simple, State0#state{no_end_tag_buff= <<>>,
  1104. buff=Buff1}) of
  1105. {noreply, #state{buff=Buff} = State} when Buff =/= <<>> ->
  1106. %% Recurse if we have more data in buffer
  1107. handle_data(<<>>, State);
  1108. Other ->
  1109. Other
  1110. end;
  1111. {fatal_error,_Loc,Reason,_EndTags,_EventState} ->
  1112. ?error(Connection#connection.name,
  1113. [{parse_error,Reason},
  1114. {buffer, Buff0},
  1115. {new_data,NewData}]),
  1116. handle_error(Reason, State0#state{no_end_tag_buff= <<>>,
  1117. buff= <<>>})
  1118. end
  1119. end.
  1120. %% xml does not accept a leading nl and some netconf server add a nl after
  1121. %% each ?END_TAG, ignore them
  1122. remove_initial_nl(<<"\n", Data/binary>>) ->
  1123. remove_initial_nl(Data);
  1124. remove_initial_nl(Data) ->
  1125. Data.
  1126. handle_error(Reason, State) ->
  1127. Pending1 = case State#state.pending of
  1128. [] -> [];
  1129. Pending ->
  1130. %% Assuming the first request gets the
  1131. %% first answer
  1132. P=#pending{tref=TRef,ref=Ref,caller=Caller} =
  1133. lists:last(Pending),
  1134. cancel_request_timer(Ref,TRef),
  1135. Reason1 = {failed_to_parse_received_data,Reason},
  1136. ct_gen_conn:return(Caller,{error,Reason1}),
  1137. lists:delete(P,Pending)
  1138. end,
  1139. {noreply, State#state{pending=Pending1}}.
  1140. %% Event function for the sax parser. It builds a simple XML structure.
  1141. %% Care is taken to keep namespace attributes and prefixes as in the original XML.
  1142. sax_event(Event,_Loc,State) ->
  1143. sax_event(Event,State).
  1144. sax_event({startPrefixMapping, Prefix, Uri},Acc) ->
  1145. %% startPrefixMapping will always come immediately before the
  1146. %% startElement where the namespace is defined.
  1147. [{xmlns,{Prefix,Uri}}|Acc];
  1148. sax_event({startElement,_Uri,_Name,QN,Attrs},Acc) ->
  1149. %% Pick out any namespace attributes inserted due to a
  1150. %% startPrefixMapping event.The rest of Acc will then be only
  1151. %% elements.
  1152. {NsAttrs,NewAcc} = split_attrs_and_elements(Acc,[]),
  1153. Tag = qn_to_tag(QN),
  1154. [{Tag,NsAttrs ++ parse_attrs(Attrs),[]}|NewAcc];
  1155. sax_event({endElement,_Uri,_Name,_QN},[{Name,Attrs,Cont},{Parent,PA,PC}|Acc]) ->
  1156. [{Parent,PA,[{Name,Attrs,lists:reverse(Cont)}|PC]}|Acc];
  1157. sax_event(endDocument,[{Tag,Attrs,Cont}]) ->
  1158. {Tag,Attrs,lists:reverse(Cont)};
  1159. sax_event({characters,String},[{Name,Attrs,Cont}|Acc]) ->
  1160. [{Name,Attrs,[String|Cont]}|Acc];
  1161. sax_event(_Event,State) ->
  1162. State.
  1163. split_attrs_and_elements([{xmlns,{Prefix,Uri}}|Rest],Attrs) ->
  1164. split_attrs_and_elements(Rest,[{xmlnstag(Prefix),Uri}|Attrs]);
  1165. split_attrs_and_elements(Elements,Attrs) ->
  1166. {Attrs,Elements}.
  1167. xmlnstag([]) ->
  1168. xmlns;
  1169. xmlnstag(Prefix) ->
  1170. list_to_atom("xmlns:"++Prefix).
  1171. qn_to_tag({[],Name}) ->
  1172. list_to_atom(Name);
  1173. qn_to_tag({Prefix,Name}) ->
  1174. list_to_atom(Prefix ++ ":" ++ Name).
  1175. parse_attrs([{_Uri, [], Name, Value}|Attrs]) ->
  1176. [{list_to_atom(Name),Value}|parse_attrs(Attrs)];
  1177. parse_attrs([{_Uri, Prefix, Name, Value}|Attrs]) ->
  1178. [{list_to_atom(Prefix ++ ":" ++ Name),Value}|parse_attrs(Attrs)];
  1179. parse_attrs([]) ->
  1180. [].
  1181. %%%-----------------------------------------------------------------
  1182. %%% Decoding of parsed XML data
  1183. decode({Tag,Attrs,_}=E, #state{connection=Connection,pending=Pending}=State) ->
  1184. ConnName = Connection#connection.name,
  1185. case get_local_name_atom(Tag) of
  1186. 'rpc-reply' ->
  1187. case get_msg_id(Attrs) of
  1188. undefined ->
  1189. case Pending of
  1190. [#pending{msg_id=MsgId}] ->
  1191. ?error(ConnName,[{warning,rpc_reply_missing_msg_id},
  1192. {assuming,MsgId}]),
  1193. decode_rpc_reply(MsgId,E,State);
  1194. _ ->
  1195. ?error(ConnName,[{error,rpc_reply_missing_msg_id}]),
  1196. {noreply,State}
  1197. end;
  1198. MsgId ->
  1199. decode_rpc_reply(MsgId,E,State)
  1200. end;
  1201. hello ->
  1202. case State#state.hello_status of
  1203. undefined ->
  1204. case decode_hello(E) of
  1205. {ok,SessionId,Capabilities} ->
  1206. {noreply,State#state{session_id = SessionId,
  1207. capabilities = Capabilities,
  1208. hello_status = received}};
  1209. {error,Reason} ->
  1210. {noreply,State#state{hello_status = {error,Reason}}}
  1211. end;
  1212. #pending{tref=TRef,ref=Ref,caller=Caller} ->
  1213. cancel_request_timer(Ref,TRef),
  1214. case decode_hello(E) of
  1215. {ok,SessionId,Capabilities} ->
  1216. ct_gen_conn:return(Caller,ok),
  1217. {noreply,State#state{session_id = SessionId,
  1218. capabilities = Capabilities,
  1219. hello_status = done}};
  1220. {error,Reason} ->
  1221. ct_gen_conn:return(Caller,{error,Reason}),
  1222. {stop,State#state{hello_status={error,Reason}}}
  1223. end;
  1224. Other ->
  1225. ?error(ConnName,[{got_unexpected_hello,E},
  1226. {hello_status,Other}]),
  1227. {noreply,State}
  1228. end;
  1229. notification ->
  1230. EventReceiver = State#state.event_receiver,
  1231. EventReceiver ! E,
  1232. {noreply,State};
  1233. Other ->
  1234. %% Result of send/2, when not sending an rpc request - or
  1235. %% if netconf server sends noise. Can handle this only if
  1236. %% there is just one pending that matches (i.e. has
  1237. %% undefined msg_id and op)
  1238. case [P || P = #pending{msg_id=undefined,op=undefined} <- Pending] of
  1239. [#pending{tref=TRef,ref=Ref,caller=Caller}] ->
  1240. cancel_request_timer(Ref,TRef),
  1241. ct_gen_conn:return(Caller,E),
  1242. {noreply,State#state{pending=[]}};
  1243. _ ->
  1244. ?error(ConnName,[{got_unexpected_msg,Other},
  1245. {expecting,Pending}]),
  1246. {noreply,State}
  1247. end
  1248. end.
  1249. get_msg_id(Attrs) ->
  1250. case lists:keyfind('message-id',1,Attrs) of
  1251. {_,Str} ->
  1252. list_to_integer(Str);
  1253. false ->
  1254. undefined
  1255. end.
  1256. decode_rpc_reply(MsgId,{_,Attrs,Content0}=E,#state{pending=Pending} = State) ->
  1257. case lists:keytake(MsgId,#pending.msg_id,Pending) of
  1258. {value, #pending{tref=TRef,ref=Ref,op=Op,caller=Caller}, Pending1} ->
  1259. cancel_request_timer(Ref,TRef),
  1260. Content = forward_xmlns_attr(Attrs,Content0),
  1261. {CallerReply,{ServerReply,State2}} =
  1262. do_decode_rpc_reply(Op,Content,State#state{pending=Pending1}),
  1263. ct_gen_conn:return(Caller,CallerReply),
  1264. {ServerReply,State2};
  1265. false ->
  1266. %% Result of send/2, when receiving a correct
  1267. %% rpc-reply. Can handle this only if there is just one
  1268. %% pending that matches (i.e. has undefined msg_id and op)
  1269. case [P || P = #pending{msg_id=undefined,op=undefined} <- Pending] of
  1270. [#pending{tref=TRef,
  1271. ref=Ref,
  1272. msg_id=undefined,
  1273. op=undefined,
  1274. caller=Caller}] ->
  1275. cancel_request_timer(Ref,TRef),
  1276. ct_gen_conn:return(Caller,E),
  1277. {noreply,State#state{pending=[]}};
  1278. _ ->
  1279. ConnName = (State#state.connection)#connection.name,
  1280. ?error(ConnName,[{got_unexpected_msg_id,MsgId},
  1281. {expecting,Pending}]),
  1282. {noreply,State}
  1283. end
  1284. end.
  1285. do_decode_rpc_reply(Op,Result,State)
  1286. when Op==lock; Op==unlock; Op==edit_config; Op==delete_config;
  1287. Op==copy_config; Op==kill_session ->
  1288. {decode_ok(Result),{noreply,State}};
  1289. do_decode_rpc_reply(Op,Result,State)
  1290. when Op==get; Op==get_config; Op==action ->
  1291. {decode_data(Result),{noreply,State}};
  1292. do_decode_rpc_reply(close_session,Result,State) ->
  1293. case decode_ok(Result) of
  1294. ok -> {ok,{stop,State}};
  1295. Other -> {Other,{noreply,State}}
  1296. end;
  1297. do_decode_rpc_reply({create_subscription,Caller},Result,State) ->
  1298. case decode_ok(Result) of
  1299. ok ->
  1300. {ok,{noreply,State#state{event_receiver=Caller}}};
  1301. Other ->
  1302. {Other,{noreply,State}}
  1303. end;
  1304. do_decode_rpc_reply(get_event_streams,Result,State) ->
  1305. {decode_streams(decode_data(Result)),{noreply,State}};
  1306. do_decode_rpc_reply(undefined,Result,State) ->
  1307. {Result,{noreply,State}}.
  1308. decode_ok([{Tag,Attrs,Content}]) ->
  1309. case get_local_name_atom(Tag) of
  1310. ok ->
  1311. ok;
  1312. 'rpc-error' ->
  1313. {error,forward_xmlns_attr(Attrs,Content)};
  1314. _Other ->
  1315. {error,{unexpected_rpc_reply,[{Tag,Attrs,Content}]}}
  1316. end;
  1317. decode_ok(Other) ->
  1318. {error,{unexpected_rpc_reply,Other}}.
  1319. decode_data([{Tag,Attrs,Content}]) ->
  1320. case get_local_name_atom(Tag) of
  1321. ok ->
  1322. %% when action has return type void
  1323. ok;
  1324. data ->
  1325. %% Since content of data has nothing from the netconf
  1326. %% namespace, we remove the parent's xmlns attribute here
  1327. %% - just to make the result cleaner
  1328. {ok,forward_xmlns_attr(remove_xmlnsattr_for_tag(Tag,Attrs),Content)};
  1329. 'rpc-error' ->
  1330. {error,forward_xmlns_attr(Attrs,Content)};
  1331. _Other ->
  1332. {error,{unexpected_rpc_reply,[{Tag,Attrs,Content}]}}
  1333. end;
  1334. decode_data(Other) ->
  1335. {error,{unexpected_rpc_reply,Other}}.
  1336. get_qualified_name(Tag) ->
  1337. case string:lexemes(atom_to_list(Tag),":") of
  1338. [TagStr] -> {[],TagStr};
  1339. [PrefixStr,TagStr] -> {PrefixStr,TagStr}
  1340. end.
  1341. get_local_name_atom(Tag) ->
  1342. {_,TagStr} = get_qualified_name(Tag),
  1343. list_to_atom(TagStr).
  1344. %% Remove the xmlns attr that points to the tag. I.e. if the tag has a
  1345. %% prefix, remove {'xmlns:prefix',_}, else remove default {xmlns,_}.
  1346. remove_xmlnsattr_for_tag(Tag,Attrs) ->
  1347. {Prefix,_TagStr} = get_qualified_name(Tag),
  1348. XmlnsTag = xmlnstag(Prefix),
  1349. case lists:keytake(XmlnsTag,1,Attrs) of
  1350. {value,_,NoNsAttrs} ->
  1351. NoNsAttrs;
  1352. false ->
  1353. Attrs
  1354. end.
  1355. %% Take all xmlns attributes from the parent's attribute list and
  1356. %% forward into all childrens' attribute lists. But do not overwrite
  1357. %% any.
  1358. forward_xmlns_attr(ParentAttrs,Children) ->
  1359. do_forward_xmlns_attr(get_all_xmlns_attrs(ParentAttrs,[]),Children).
  1360. do_forward_xmlns_attr(XmlnsAttrs,[{ChT,ChA,ChC}|Children]) ->
  1361. ChA1 = add_xmlns_attrs(XmlnsAttrs,ChA),
  1362. [{ChT,ChA1,ChC} | do_forward_xmlns_attr(XmlnsAttrs,Children)];
  1363. do_forward_xmlns_attr(_XmlnsAttrs,[]) ->
  1364. [].
  1365. add_xmlns_attrs([{Key,_}=A|XmlnsAttrs],ChA) ->
  1366. case lists:keymember(Key,1,ChA) of
  1367. true ->
  1368. add_xmlns_attrs(XmlnsAttrs,ChA);
  1369. false ->
  1370. add_xmlns_attrs(XmlnsAttrs,[A|ChA])
  1371. end;
  1372. add_xmlns_attrs([],ChA) ->
  1373. ChA.
  1374. get_all_xmlns_attrs([{xmlns,_}=Default|Attrs],XmlnsAttrs) ->
  1375. get_all_xmlns_attrs(Attrs,[Default|XmlnsAttrs]);
  1376. get_all_xmlns_attrs([{Key,_}=Attr|Attrs],XmlnsAttrs) ->
  1377. case atom_to_list(Key) of
  1378. "xmlns:"++_Prefix ->
  1379. get_all_xmlns_attrs(Attrs,[Attr|XmlnsAttrs]);
  1380. _ ->
  1381. get_all_xmlns_attrs(Attrs,XmlnsAttrs)
  1382. end;
  1383. get_all_xmlns_attrs([],XmlnsAttrs) ->
  1384. XmlnsAttrs.
  1385. %% Decode server hello to pick out session id and capabilities
  1386. decode_hello({hello,_Attrs,Hello}) ->
  1387. case lists:keyfind('session-id',1,Hello) of
  1388. {'session-id',_,[SessionId]} ->
  1389. case lists:keyfind(capabilities,1,Hello) of
  1390. {capabilities,_,Capabilities} ->
  1391. case decode_caps(Capabilities,[],false) of
  1392. {ok,Caps} ->
  1393. {ok,list_to_integer(SessionId),Caps};
  1394. Error ->
  1395. Error
  1396. end;
  1397. false ->
  1398. {error,{incorrect_hello,capabilities_not_found}}
  1399. end;
  1400. false ->
  1401. {error,{incorrect_hello,no_session_id_found}}
  1402. end.
  1403. decode_caps([{capability,[],[?NETCONF_BASE_CAP++Vsn=Cap]} |Caps], Acc, _) ->
  1404. case Vsn of
  1405. ?NETCONF_BASE_CAP_VSN ->
  1406. decode_caps(Caps, [Cap|Acc], true);
  1407. _ ->
  1408. {error,{incompatible_base_capability_vsn,Vsn}}
  1409. end;
  1410. decode_caps([{capability,[],[Cap]}|Caps],Acc,Base) ->
  1411. decode_caps(Caps,[Cap|Acc],Base);
  1412. decode_caps([H|_T],_,_) ->
  1413. {error,{unexpected_capability_element,H}};
  1414. decode_caps([],_,false) ->
  1415. {error,{incorrect_hello,no_base_capability_found}};
  1416. decode_caps([],Acc,true) ->
  1417. {ok,lists:reverse(Acc)}.
  1418. %% Return a list of {Name,Data}, where data is a {Tag,Value} list for each stream
  1419. decode_streams({error,Reason}) ->
  1420. {error,Reason};
  1421. decode_streams({ok,[{netconf,_,Streams}]}) ->
  1422. {ok,decode_streams(Streams)};
  1423. decode_streams([{streams,_,Streams}]) ->
  1424. decode_streams(Streams);
  1425. decode_streams([{stream,_,Stream} | Streams]) ->
  1426. {name,_,[Name]} = lists:keyfind(name,1,Stream),
  1427. [{Name,[{Tag,Value} || {Tag,_,[Value]} <- Stream, Tag /= name]}
  1428. | decode_streams(Streams)];
  1429. decode_streams([]) ->
  1430. [].
  1431. %%%-----------------------------------------------------------------
  1432. %%% Logging
  1433. log(Connection,Action) ->
  1434. log(Connection,Action,<<>>).
  1435. log(#connection{reference=Ref,host=Host,port=Port,name=Name},Action,Data) ->
  1436. Address =
  1437. case Ref of
  1438. {_,Ch} -> {Host,Port,Ch};
  1439. _ -> {Host,Port}
  1440. end,
  1441. error_logger:info_report(#conn_log{client=self(),
  1442. address=Address,
  1443. name=Name,
  1444. action=Action,
  1445. module=?MODULE},
  1446. Data).
  1447. %% Log callback - called from the error handler process
  1448. format_data(How,Data) ->
  1449. %% Assuming that the data is encoded as UTF-8. If it is not, then
  1450. %% the printout might be wrong, but the format function will not
  1451. %% crash!
  1452. %% FIXME: should probably read encoding from the data and do
  1453. %% unicode:characters_to_binary(Data,InEncoding,utf8) when calling
  1454. %% log/3 instead of assuming utf8 in as done here!
  1455. do_format_data(How,unicode:characters_to_binary(Data)).
  1456. do_format_data(raw,Data) ->
  1457. io_lib:format("~n~ts~n",[hide_password(Data)]);
  1458. do_format_data(pretty,Data) ->
  1459. maybe_io_lib_format(indent(Data));
  1460. do_format_data(html,Data) ->
  1461. maybe_io_lib_format(html_format(Data)).
  1462. maybe_io_lib_format(<<>>) ->
  1463. [];
  1464. maybe_io_lib_format(String) ->
  1465. io_lib:format("~n~ts~n",[String]).
  1466. %%%-----------------------------------------------------------------
  1467. %%% Hide password elements from XML data
  1468. hide_password(Bin) ->
  1469. re:replace(Bin,<<"(<password[^>]*>)[^<]*(</password>)">>,<<"\\1*****\\2">>,
  1470. [global,{return,binary},unicode]).
  1471. %%%-----------------------------------------------------------------
  1472. %%% HTML formatting
  1473. html_format(Bin) ->
  1474. binary:replace(indent(Bin),<<"<">>,<<"&lt;">>,[global]).
  1475. %%%-----------------------------------------------------------------
  1476. %%% Indentation of XML code
  1477. indent(Bin) ->
  1478. String = normalize(hide_password(Bin)),
  1479. IndentedString =
  1480. case erase(part_of_line) of
  1481. undefined ->
  1482. indent1(String,[]);
  1483. Part ->
  1484. indent1(lists:reverse(Part)++String,erase(indent))
  1485. end,
  1486. unicode:characters_to_binary(IndentedString).
  1487. %% Normalizes the XML document by removing all space and newline
  1488. %% between two XML tags.
  1489. %% Returns a list, no matter if the input was a list or a binary.
  1490. normalize(Bin) ->
  1491. re:replace(Bin,<<">[ \r\n\t]+<">>,<<"><">>,[global,{return,list},unicode]).
  1492. indent1("<?"++Rest1,Indent1) ->
  1493. %% Prolog
  1494. {Line,Rest2,Indent2} = indent_line(Rest1,Indent1,[$?,$<]),
  1495. Line++indent1(Rest2,Indent2);
  1496. indent1("</"++Rest1,Indent1) ->
  1497. %% Stop tag
  1498. case indent_line1(Rest1,Indent1,[$/,$<]) of
  1499. {[],[],_} ->
  1500. [];
  1501. {Line,Rest2,Indent2} ->
  1502. "\n"++Line++indent1(Rest2,Indent2)
  1503. end;
  1504. indent1("<"++Rest1,Indent1) ->
  1505. %% Start- or empty tag
  1506. put(tag,get_tag(Rest1)),
  1507. case indent_line(Rest1,Indent1,[$<]) of
  1508. {[],[],_} ->
  1509. [];
  1510. {Line,Rest2,Indent2} ->
  1511. "\n"++Line++indent1(Rest2,Indent2)
  1512. end;
  1513. indent1([H|T],Indent) ->
  1514. [H|indent1(T,Indent)];
  1515. indent1([],_Indent) ->
  1516. [].
  1517. indent_line("?>"++Rest,Indent,Line) ->
  1518. %% Prolog
  1519. {lists:reverse(Line)++"?>",Rest,Indent};
  1520. indent_line("/></"++Rest,Indent,Line) ->
  1521. %% Empty tag, and stop of parent tag -> one step out in indentation
  1522. {Indent++lists:reverse(Line)++"/>","</"++Rest,Indent--" "};
  1523. indent_line("/>"++Rest,Indent,Line) ->
  1524. %% Empty tag, then probably next tag -> keep indentation
  1525. {Indent++lists:reverse(Line)++"/>",Rest,Indent};
  1526. indent_line("></"++Rest,Indent,Line) ->
  1527. LastTag = erase(tag),
  1528. case get_tag(Rest) of
  1529. LastTag ->
  1530. %% Start and stop tag, but no content
  1531. indent_line1(Rest,Indent,[$/,$<,$>|Line]);
  1532. _ ->
  1533. %% Stop tag completed, and then stop tag of parent -> one step out
  1534. {Indent++lists:reverse(Line)++">","</"++Rest,Indent--" "}
  1535. end;
  1536. indent_line("><"++Rest,Indent,Line) ->
  1537. %% Stop tag completed, and new tag comming -> keep indentation
  1538. {Indent++lists:reverse(Line)++">","<"++Rest," "++Indent};
  1539. indent_line("</"++Rest,Indent,Line) ->
  1540. %% Stop tag starting -> search for end of this tag
  1541. indent_line1(Rest,Indent,[$/,$<|Line]);
  1542. indent_line([H|T],Indent,Line) ->
  1543. indent_line(T,Indent,[H|Line]);
  1544. indent_line([],Indent,Line) ->
  1545. %% The line is not complete - will be continued later
  1546. put(part_of_line,Line),
  1547. put(indent,Indent),
  1548. {[],[],Indent}.
  1549. indent_line1("></"++Rest,Indent,Line) ->
  1550. %% Stop tag completed, and then stop tag of parent -> one step out
  1551. {Indent++lists:reverse(Line)++">","</"++Rest,Indent--" "};
  1552. indent_line1(">"++Rest,Indent,Line) ->
  1553. %% Stop tag completed -> keep indentation
  1554. {Indent++lists:reverse(Line)++">",Rest,Indent};
  1555. indent_line1([H|T],Indent,Line) ->
  1556. indent_line1(T,Indent,[H|Line]);
  1557. indent_line1([],Indent,Line) ->
  1558. %% The line is not complete - will be continued later
  1559. put(part_of_line,Line),
  1560. put(indent,Indent),
  1561. {[],[],Indent}.
  1562. get_tag("/>"++_) ->
  1563. [];
  1564. get_tag(">"++_) ->
  1565. [];
  1566. get_tag([H|T]) ->
  1567. [H|get_tag(T)];
  1568. get_tag([]) ->
  1569. %% The line is not complete - will be continued later.
  1570. [].
  1571. %%%-----------------------------------------------------------------
  1572. %%% SSH stuff
  1573. ssh_connect(#options{host=Host,timeout=Timeout,port=Port,
  1574. ssh=SshOpts,name=Name,type=Type}) ->
  1575. case ssh:connect(Host, Port,
  1576. [{user_interaction,false},
  1577. {silently_accept_hosts, true}|SshOpts],
  1578. Timeout) of
  1579. {ok,CM} ->
  1580. Connection = #connection{reference = CM,
  1581. host = Host,
  1582. port = Port,
  1583. name = Name,
  1584. type = Type},
  1585. log(Connection,connect),
  1586. {ok,Connection};
  1587. {error,Reason} ->
  1588. {error,{ssh,could_not_connect_to_server,Reason}}
  1589. end.
  1590. ssh_channel(#connection{reference=CM}=Connection0,
  1591. #options{timeout=Timeout,name=Name,type=Type}) ->
  1592. case ssh_connection:session_channel(CM, Timeout) of
  1593. {ok,Ch} ->
  1594. case ssh_connection:subsystem(CM, Ch, "netconf", Timeout) of
  1595. success ->
  1596. Connection = Connection0#connection{reference = {CM,Ch},
  1597. name = Name,
  1598. type = Type},
  1599. log(Connection,open),
  1600. {ok, Connection};
  1601. failure ->
  1602. ssh_connection:close(CM,Ch),
  1603. {error,{ssh,could_not_execute_netconf_subsystem}};
  1604. {error,timeout} ->
  1605. ssh_connection:close(CM,Ch),
  1606. {error,{ssh,could_not_execute_netconf_subsystem,timeout}}
  1607. end;
  1608. {error, Reason} ->
  1609. {error,{ssh,could_not_open_channel,Reason}}
  1610. end.
  1611. ssh_open(Options) ->
  1612. case ssh_connect(Options) of
  1613. {ok,Connection} ->
  1614. case ssh_channel(Connection,Options) of
  1615. {ok,_} = Ok ->
  1616. Ok;
  1617. Error ->
  1618. ssh_close(Connection),
  1619. Error
  1620. end;
  1621. Error ->
  1622. Error
  1623. end.
  1624. ssh_send(#connection{reference = {CM,Ch}}=Connection, Data) ->
  1625. case ssh_connection:send(CM, Ch, Data) of
  1626. ok ->
  1627. log(Connection,send,Data),
  1628. ok;
  1629. {error,Reason} ->
  1630. {error,{ssh,failed_to_send_data,Reason}}
  1631. end.
  1632. ssh_close(Connection=#connection{reference = {CM,Ch}, type = Type}) ->
  1633. _ = ssh_connection:close(CM,Ch),
  1634. log(Connection,close),
  1635. case Type of
  1636. connection_and_channel ->
  1637. ssh_close(Connection#connection{reference = CM});
  1638. _ ->
  1639. ok
  1640. end,
  1641. ok;
  1642. ssh_close(Connection=#connection{reference = CM}) ->
  1643. _ = ssh:close(CM),
  1644. log(Connection,disconnect),
  1645. ok.
  1646. %%----------------------------------------------------------------------
  1647. %% END OF MODULE
  1648. %%----------------------------------------------------------------------