aboutsummaryrefslogtreecommitdiffstats
path: root/lib/common_test/test
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common_test/test')
-rw-r--r--lib/common_test/test/Makefile3
-rw-r--r--lib/common_test/test/common_test.spec2
-rw-r--r--lib/common_test/test/ct_misc_1_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE.erl124
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/netconfc1.cfg6
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl1130
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/ns.erl506
-rw-r--r--lib/common_test/test/ct_test_support.erl50
-rw-r--r--lib/common_test/test/ct_testspec_1_SUITE.erl4
9 files changed, 1811 insertions, 16 deletions
diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile
index 560a0b0d5a..4d85b84b5b 100644
--- a/lib/common_test/test/Makefile
+++ b/lib/common_test/test/Makefile
@@ -45,7 +45,8 @@ MODULES= \
ct_config_SUITE \
ct_master_SUITE \
ct_misc_1_SUITE \
- ct_hooks_SUITE
+ ct_hooks_SUITE \
+ ct_netconfc_SUITE
ERL_FILES= $(MODULES:%=%.erl)
diff --git a/lib/common_test/test/common_test.spec b/lib/common_test/test/common_test.spec
index 8755b08117..8bec66d6f2 100644
--- a/lib/common_test/test/common_test.spec
+++ b/lib/common_test/test/common_test.spec
@@ -1 +1 @@
-{suites,"../common_test_test",all}. \ No newline at end of file
+{suites,"../common_test_test",all}.
diff --git a/lib/common_test/test/ct_misc_1_SUITE.erl b/lib/common_test/test/ct_misc_1_SUITE.erl
index cb17af9ab5..d2318de445 100644
--- a/lib/common_test/test/ct_misc_1_SUITE.erl
+++ b/lib/common_test/test/ct_misc_1_SUITE.erl
@@ -106,7 +106,7 @@ beam_me_up(Config) when is_list(Config) ->
{Opts,ERPid} = setup([{suite,Suites},{auto_compile,false}], Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ {_Ok,_Fail,_Skip} = ct_test_support:run(ct, run_test, [Opts], Config),
Events = ct_test_support:get_events(ERPid, Config),
ct_test_support:log_events(beam_me_up,
diff --git a/lib/common_test/test/ct_netconfc_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE.erl
new file mode 100644
index 0000000000..e6e8d5b09c
--- /dev/null
+++ b/lib/common_test/test/ct_netconfc_SUITE.erl
@@ -0,0 +1,124 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2012. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File: ct_netconfc_SUITE
+%%%
+%%% Description:
+%%% Test ct_netconfc module
+%%%
+%%%-------------------------------------------------------------------
+-module(ct_netconfc_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+-define(eh, ct_test_support_eh).
+
+%%--------------------------------------------------------------------
+%% TEST SERVER CALLBACK FUNCTIONS
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% Description: Since Common Test starts another Test Server
+%% instance, the tests need to be performed on a separate node (or
+%% there will be clashes with logging processes etc).
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config1 = ct_test_support:init_per_suite(Config),
+ Config1.
+
+end_per_suite(Config) ->
+ ct_test_support:end_per_suite(Config).
+
+init_per_testcase(TestCase, Config) ->
+ ct_test_support:init_per_testcase(TestCase, Config).
+
+end_per_testcase(TestCase, Config) ->
+ ct_test_support:end_per_testcase(TestCase, Config).
+
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [
+ default
+ ].
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+%%--------------------------------------------------------------------
+
+%%%-----------------------------------------------------------------
+%%%
+default(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ Suite = filename:join(DataDir, "netconfc1_SUITE"),
+ CfgFile = filename:join(DataDir, "netconfc1.cfg"),
+ {Opts,ERPid} = setup([{suite,Suite},{config,CfgFile},
+ {label,default}], Config),
+
+ ok = execute(default, Opts, ERPid, Config).
+
+
+%%%-----------------------------------------------------------------
+%%% HELP FUNCTIONS
+%%%-----------------------------------------------------------------
+
+setup(Test, Config) ->
+ Opts0 = ct_test_support:get_opts(Config),
+ Level = ?config(trace_level, Config),
+ EvHArgs = [{cbm,ct_test_support},{trace_level,Level}],
+ Opts = Opts0 ++ [{event_handler,{?eh,EvHArgs}}|Test],
+ ERPid = ct_test_support:start_event_receiver(Config),
+ {Opts,ERPid}.
+
+execute(Name, Opts, ERPid, Config) ->
+ ok = ct_test_support:run(Opts, Config),
+ Events = ct_test_support:get_events(ERPid, Config),
+
+ ct_test_support:log_events(Name,
+ reformat(Events, ?eh),
+ ?config(priv_dir, Config),
+ Opts),
+
+ TestEvents = events_to_check(Name,Config),
+ ct_test_support:verify_events(TestEvents, Events, Config).
+
+reformat(Events, EH) ->
+ ct_test_support:reformat(Events, EH).
+
+%%%-----------------------------------------------------------------
+%%% TEST EVENTS
+%%%-----------------------------------------------------------------
+events_to_check(Test,Config) ->
+ {module,_} = code:load_abs(filename:join(?config(data_dir,Config),
+ netconfc1_SUITE)),
+ TCs = netconfc1_SUITE:all(),
+ code:purge(netconfc1_SUITE),
+ code:delete(netconfc1_SUITE),
+
+ OneTest =
+ [{?eh,start_logging,{'DEF','RUNDIR'}}] ++
+ [{?eh,tc_done,{netconfc1_SUITE,TC,ok}} || TC <- TCs] ++
+ [{?eh,stop_logging,[]}],
+
+ %% 2 tests (ct:run_test + script_start) is default
+ OneTest ++ OneTest.
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1.cfg b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1.cfg
new file mode 100644
index 0000000000..6466571623
--- /dev/null
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1.cfg
@@ -0,0 +1,6 @@
+%% -*- erlang -*-
+{netconf1,[{ssh,"localhost"},
+ {port,2060},
+ {user,"xxx"},
+ {password,"xxx"}]}.
+{ct_conn_log,[{ct_netconfc,[{log_type,pretty}]}]}. %overrides args to cth_conn_log
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
new file mode 100644
index 0000000000..79768a9a6a
--- /dev/null
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
@@ -0,0 +1,1130 @@
+%%--------------------------------------------------------------------
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2012. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%----------------------------------------------------------------------
+%% File: ct_netconfc_SUITE.erl
+%%
+%% Description:
+%% This file contains the test cases for the ct_netconfc API.
+%%
+%% @author Support
+%% @doc Netconf Client Interface.
+%% @end
+%%----------------------------------------------------------------------
+%%----------------------------------------------------------------------
+-module(netconfc1_SUITE).
+-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/src/ct_netconfc.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+-compile(export_all).
+
+%% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(1)).
+
+-define(NS,ns).
+-define(LOCALHOST, "127.0.0.1").
+-define(SSH_PORT, 2060).
+
+-define(DEFAULT_SSH_OPTS,[{ssh,?LOCALHOST},
+ {port,?SSH_PORT},
+ {user,"xxx"},
+ {password,"xxx"}]).
+-define(DEFAULT_SSH_OPTS(Dir), ?DEFAULT_SSH_OPTS++[{user_dir,Dir}]).
+
+-define(ok,ok).
+
+suite() ->
+ [{ct_hooks, [{cth_conn_log,
+ [{ct_netconfc,[{log_type,html}, %will be overwritten by config
+ {hosts,[my_named_connection,netconf1]}]
+ }]
+ }]
+ }].
+
+all() ->
+ case os:find_executable("ssh") of
+ false ->
+ {skip, "SSH not installed on host"};
+ _ ->
+ [hello,
+ hello_from_server_first,
+ hello_named,
+ hello_configured,
+ hello_configured_extraopts,
+ hello_required,
+ hello_required_exists,
+ hello_global_pwd,
+ hello_no_session_id,
+ hello_incomp_base_vsn,
+ hello_no_base_cap,
+ hello_no_caps,
+ no_server_hello,
+ no_client_hello,
+ get_session_id,
+ get_capabilities,
+ faulty_user,
+ faulty_passwd,
+ faulty_port,
+ no_host,
+ no_port,
+ invalid_opt,
+ get,
+ get_xpath,
+ get_config,
+ get_config_xpath,
+ edit_config,
+ copy_config,
+ delete_config,
+ lock,
+ unlock,
+ kill_session,
+ get_no_such_client,
+ action,
+ send_any_rpc,
+ send_any,
+ hide_password,
+ not_proper_xml,
+ prefixed_namespace,
+ receive_chunked_data,
+ timeout_receive_chunked_data,
+ close_while_waiting_for_chunked_data,
+ connection_crash,
+ get_event_streams,
+ create_subscription,
+ receive_event]
+ end.
+
+
+groups() ->
+ [].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+init_per_testcase(_Case, Config) ->
+ ets:delete_all_objects(ns_tab),
+ Dog = test_server:timetrap(?default_timeout),
+ [{watchdog, Dog}|Config].
+
+end_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+init_per_suite(Config) ->
+ case catch {crypto:start(), ssh:start()} of
+ {ok, ok} ->
+ {ok, _} = get_id_keys(Config),
+ make_dsa_files(Config),
+ Server = ?NS:start(?config(data_dir,Config)),
+ [{server,Server}|Config];
+ _ ->
+ {skip, "Crypto and/or SSH could not be started!"}
+ end.
+
+end_per_suite(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ ?NS:stop(?config(server,Config)),
+ ssh:stop(),
+ crypto:stop(),
+ remove_id_keys(PrivDir),
+ Config.
+
+hello(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+hello_from_server_first(Config) ->
+ DataDir = ?config(data_dir,Config),
+ ?NS:hello(1),
+ {ok,Client} = ct_netconfc:only_open(?DEFAULT_SSH_OPTS(DataDir)),
+ ct:sleep(500),
+ ?NS:expect(hello),
+ ?ok = ct_netconfc:hello(Client),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+hello_named(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(any_name,DataDir),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+hello_configured() ->
+ [{require, netconf1}].
+hello_configured(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_configured_success(netconf1,DataDir),
+ ?NS:expect_do_reply('close-session',close,ok),
+ {error, {no_such_name,netconf1}} = ct_netconfc:close_session(netconf1),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+hello_configured_extraopts() ->
+ [{require, netconf1}].
+hello_configured_extraopts(Config) ->
+ DataDir = ?config(data_dir,Config),
+ %% Test that the cofiguration overwrites the ExtraOpts parameter
+ %% to ct_netconfc:open/2.
+ {ok,Client} = open_configured_success(netconf1,DataDir,[{password,"faulty"}]),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+hello_required() ->
+ [{require, my_named_connection, netconf1}].
+hello_required(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,_Client} = open_configured_success(my_named_connection,DataDir),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(my_named_connection),
+ ok.
+
+hello_required_exists() ->
+ [{require, my_named_connection, netconf1}].
+hello_required_exists(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,_Client1} = open_configured_success(my_named_connection,DataDir),
+
+ %% Check that same name can not be used twice
+ {error,{connection_exists,_Client1}} =
+ ct_netconfc:open(my_named_connection,[{user_dir,DataDir}]),
+
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(my_named_connection),
+
+ %% Then check that it can be used again after the first is closed
+ {ok,_Client2} = open_configured_success(my_named_connection,DataDir),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(my_named_connection),
+ ok.
+
+hello_global_pwd(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir,[{user,"any-user"},
+ {password,"global-xxx"}]),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+hello_no_session_id(Config) ->
+ DataDir = ?config(data_dir,Config),
+ ?NS:hello(no_session_id),
+ ?NS:expect(hello),
+ {error,{incorrect_hello,no_session_id_found}} = open(DataDir),
+ ok.
+
+hello_incomp_base_vsn(Config) ->
+ DataDir = ?config(data_dir,Config),
+ ?NS:hello(1,{base,"1.1"}),
+ ?NS:expect(hello),
+ {error,{incompatible_base_capability_vsn,"1.1"}} = open(DataDir),
+ ok.
+
+hello_no_base_cap(Config) ->
+ DataDir = ?config(data_dir,Config),
+ ?NS:hello(1,no_base),
+ ?NS:expect(hello),
+ {error,{incorrect_hello,no_base_capability_found}} = open(DataDir),
+ ok.
+
+hello_no_caps(Config) ->
+ DataDir = ?config(data_dir,Config),
+ ?NS:hello(1,no_caps),
+ ?NS:expect(hello),
+ {error,{incorrect_hello,capabilities_not_found}} = open(DataDir),
+ ok.
+
+no_server_hello(Config) ->
+ DataDir = ?config(data_dir,Config),
+ ?NS:expect(hello),
+ {error,{hello_session_failed,timeout}} = open(DataDir,[{timeout,2000}]),
+ ok.
+
+no_client_hello(Config) ->
+ DataDir = ?config(data_dir,Config),
+ ?NS:hello(1),
+ {ok,Client} = ct_netconfc:only_open(?DEFAULT_SSH_OPTS(DataDir)),
+
+ %% Allow server hello to arrive
+ ct:sleep(500),
+
+ %% Tell server to receive a get request and then die without
+ %% replying since no hello has been received. (is this correct
+ %% behavoiur??)
+ ?NS:expect_do(get,close),
+ {error,closed} = ct_netconfc:get(Client,whatever),
+ ok.
+
+get_session_id(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+
+ 1 = ct_netconfc:get_session_id(Client),
+
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+get_capabilities(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+
+ Caps = ct_netconfc:get_capabilities(Client),
+ BaseCap = ?NETCONF_BASE_CAP ++ ?NETCONF_BASE_CAP_VSN,
+ [BaseCap,"urn:ietf:params:netconf:capability:writable-running:1.0" |_] = Caps,
+
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+faulty_user(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {error,{ssh,could_not_connect_to_server,
+ "Unable to connect using the available authentication methods"}} =
+ open(DataDir,[{user,"yyy"}]),
+ ok.
+
+faulty_passwd(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {error,{ssh,could_not_connect_to_server,
+ "Unable to connect using the available authentication methods"}} =
+ open(DataDir,[{password,"yyy"}]),
+ ok.
+
+faulty_port(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {error,{ssh,could_not_connect_to_server,econnrefused}} =
+ open(DataDir,[{port,2062}]),
+ ok.
+
+no_host(Config) ->
+ DataDir = ?config(data_dir,Config),
+ Opts = lists:keydelete(ssh,1,?DEFAULT_SSH_OPTS(DataDir)),
+ {error,no_host_address} = ct_netconfc:open(Opts),
+ ok.
+
+no_port(Config) ->
+ DataDir = ?config(data_dir,Config),
+ Opts = lists:keydelete(port,1,?DEFAULT_SSH_OPTS(DataDir)),
+ {error,no_port} = ct_netconfc:open(Opts),
+ ok.
+
+invalid_opt(Config) ->
+ DataDir = ?config(data_dir,Config),
+ Opts1 = ?DEFAULT_SSH_OPTS(DataDir) ++ [{timeout,invalidvalue}],
+ {error,{invalid_option,{timeout,invalidvalue}}} = ct_netconfc:open(Opts1),
+ Opts2 = ?DEFAULT_SSH_OPTS(DataDir) ++ [{some_other_opt,true}],
+ {error,{invalid_option,{some_other_opt,true}}} = ct_netconfc:open(Opts2),
+ ok.
+
+get(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+ Data = [{server,[{xmlns,"myns"}],[{name,[],["myserver"]}]}],
+ ?NS:expect_reply('get',{data,Data}),
+ {ok,Data} = ct_netconfc:get(Client,{server,[{xmlns,"myns"}],[]}),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+get_xpath(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+ Data = [{server,[{xmlns,"myns"}],[{name,[],["myserver"]}]}],
+ ?NS:expect_reply({'get',xpath},{data,Data}),
+ {ok,Data} = ct_netconfc:get(Client,{xpath,"/server"}),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+get_config(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+ Data = [{server,[{xmlns,"myns"}],[{name,[],["myserver"]}]}],
+ ?NS:expect_reply('get-config',{data,Data}),
+ {ok,Data} = ct_netconfc:get_config(Client,running,
+ {server,[{xmlns,"myns"}],[]}),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+get_config_xpath(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+ Data = [{server,[{xmlns,"myns"}],[{name,[],["myserver"]}]}],
+ ?NS:expect_reply({'get-config',xpath},{data,Data}),
+ {ok,Data} = ct_netconfc:get_config(Client,running,{xpath,"/server"}),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+edit_config(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+ ?NS:expect_reply('edit-config',ok),
+ ?ok = ct_netconfc:edit_config(Client,running,
+ {server,[{xmlns,"myns"}],
+ [{name,["myserver"]}]}),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+copy_config(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+ ?NS:expect_reply('copy-config',ok),
+ ?ok = ct_netconfc:copy_config(Client,startup,running),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+delete_config(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+ ?NS:expect_reply('delete-config',ok),
+ ?ok = ct_netconfc:delete_config(Client,startup),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+lock(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+ ?NS:expect_reply('lock',ok),
+ ?ok = ct_netconfc:lock(Client,running),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+unlock(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+ ?NS:expect_reply('unlock',ok),
+ ?ok = ct_netconfc:unlock(Client,running),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+kill_session(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+
+ ?NS:hello(2),
+ ?NS:expect(hello),
+ {ok,_OtherClient} = open(DataDir),
+
+ ?NS:expect_do_reply('kill-session',{kill,2},ok),
+ ?ok = ct_netconfc:kill_session(Client,2),
+
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+
+ ok.
+
+get_no_such_client(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ case ct_netconfc:get(Client,{server,[{xmlns,"myns"}],[]}) of
+ {error,no_such_client} ->
+ ok;
+ {error,closed} ->
+ %% Means that the Client process was not terminated before the call.
+ %% Give it one more go.
+ {error,no_such_client} =
+ ct_netconfc:get(Client,{server,[{xmlns,"myns"}],[]})
+ end,
+ ok.
+
+action(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+ Data = [{myactionreturn,[{xmlns,"myns"}],["value"]}],
+ ?NS:expect_reply(action,{data,Data}),
+ {ok,Data} = ct_netconfc:action(Client,{myaction,[{xmlns,"myns"}],[]}),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+send_any_rpc(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+ Data = [{server,[{xmlns,"myns"}],[{name,[],["myserver"]}]}],
+ GetConf = {'get-config',
+ [{source,["running"]},
+ {filter,[{type,"subtree"}],
+ [{server,[{xmlns,"myns"}],[]}]}]},
+ ?NS:expect_reply('get-config',{data,Data}),
+ [{data,?NETCONF_NAMESPACE_ATTR,Data}] = ct_netconfc:send_rpc(Client,GetConf),
+
+ EditConf = {'edit-config',
+ [{target,["running"]},
+ {config,[{server,[{xmlns,"myns"}],
+ [{name,["myserver"]}]}]}]},
+ ?NS:expect_reply('edit-config',ok),
+ [{ok,?NETCONF_NAMESPACE_ATTR,[]}] = ct_netconfc:send_rpc(Client,EditConf),
+
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+send_any(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+
+ %% Correct get-config rpc
+ Data = [{server,[{xmlns,"myns"}],[{name,[],["myserver"]}]}],
+ RpcAttr1 = ?NETCONF_NAMESPACE_ATTR ++ [{'message-id',"1"}],
+ RpcGetConf = {rpc,RpcAttr1,
+ [{'get-config',
+ [{source,["running"]},
+ {filter,[{type,"subtree"}],
+ [{server,[{xmlns,"myns"}],[]}]}]}]},
+ ?NS:expect_reply('get-config',{data,Data}),
+ {'rpc-reply',RpcAttr1,[{data,_,Data}]} = ct_netconfc:send(Client,RpcGetConf),
+
+ %% Correct edit-config rpc
+ RpcAttr2 = ?NETCONF_NAMESPACE_ATTR ++ [{'message-id',"2"}],
+ RpcEditConf = {rpc,RpcAttr2,
+ [{'edit-config',
+ [{target,["running"]},
+ {config,[{server,[{xmlns,"myns"}],
+ [{name,["myserver"]}]}]}]}]},
+ ?NS:expect_reply('edit-config',ok),
+ {'rpc-reply',RpcAttr2,[{ok,_,[]}]} = ct_netconfc:send(Client,RpcEditConf),
+
+ %% Send any data
+ ?NS:expect_reply(any,{ok,[],[]}),
+ {ok,_,[]} = ct_netconfc:send(Client,{any,[],[]}),
+
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+hide_password(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+ Password = "my_very_secret_password",
+ Data = [{passwords,[{xmlns,"myns"}],
+ [{password,[{xmlns,"pwdns"}],[Password]},
+ {password,[],[Password]}]}],
+ ?NS:expect_reply('get',{data,Data}),
+ ct:capture_start(), % in case of html logging
+ {ok,Data} = ct_netconfc:get(Client,{passwords,[{xmlns,"myns"}],[]}),
+ ct:capture_stop(),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+
+ Log = filename:join(?config(priv_dir,Config),"hide_password-netconf.txt"),
+
+ Text =
+ case file:read_file(Log) of
+ {ok,Bin} ->
+ Bin;
+ _NoLog ->
+ %% Assume html logging
+ list_to_binary(ct:capture_get())
+ end,
+
+ nomatch = binary:match(Text,list_to_binary(Password)),
+
+ ok.
+
+not_proper_xml(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+ NS = list_to_binary(?NETCONF_NAMESPACE),
+ NotProper = <<"<rpc-reply message-id=\"1\" xmlns=\"",
+ NS/binary,"\"><data></rpc-reply>">>,
+ ?NS:expect_reply('get',NotProper),
+ {error,{failed_to_parse_received_data,_}} =
+ ct_netconfc:get(Client,{server,[{xmlns,"myns"}],[]}),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+prefixed_namespace(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+ NS = list_to_binary(?NETCONF_NAMESPACE),
+
+ %% Test that data element can be properly decoded and that
+ %% prefixed namespace attributes (exepct the netconf namespace)
+ %% are forwarded to the content of the data element - i.e. that
+ %% the xmlns:my is forwarded from the rpc-reply element to the
+ %% server element below.
+ Data = <<"<nc:rpc-reply message-id=\"1\" xmlns:nc=\"",
+ NS/binary,"\" xmlns:my=\"myns\"><nc:data><my:server>",
+ "<my:name my:lang=\"en\">myserver</my:name></my:server>"
+ "</nc:data></nc:rpc-reply>">>,
+ ?NS:expect_reply('get',Data),
+ {ok,[{'my:server',[{'xmlns:my',"myns"}],
+ [{'my:name',[{'my:lang',"en"}],["myserver"]}]}]} =
+ ct_netconfc:get(Client,{server,[{xmlns,"myns"}],[]}),
+
+ Ok = <<"<nc:rpc-reply message-id=\"2\" xmlns:nc=\"",
+ NS/binary,"\"><nc:ok/></nc:rpc-reply>">>,
+ ?NS:expect_reply('edit-config',Ok),
+ ?ok = ct_netconfc:edit_config(Client,running,
+ {server,[{xmlns,"myns"}],
+ [{name,["myserver"]}]}),
+
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+%% Test that the client can parse data which is received in chunks,
+%% i.e. when the complete rpc-reply is not contained in one single ssh
+%% data message.
+receive_chunked_data(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+
+ %% Construct the data to return from netconf server
+ Data = [{servers,[{xmlns,"myns"}],
+ [{server,[],[{name,[],["server0"]}]},
+ {server,[],[{name,[],["server1"]}]},
+ {server,[],[{name,[],["server2"]}]},
+ {server,[],[{name,[],["server3"]}]},
+ {server,[],[{name,[],["server4"]}]},
+ {server,[],[{name,[],["server5"]}]},
+ {server,[],[{name,[],["server6"]}]},
+ {server,[],[{name,[],["server7"]}]},
+ {server,[],[{name,[],["server8"]}]},
+ {server,[],[{name,[],["server9"]}]}]
+ }],
+ Rpc = {'rpc-reply',?NETCONF_NAMESPACE_ATTR ++ [{'message-id',"1"}],
+ [{data,Data}]},
+ Xml = list_to_binary(xmerl:export_simple_element(Rpc,xmerl_xml)),
+ Netconf =
+ <<"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
+ Xml/binary,"\n",?END_TAG/binary>>,
+
+ %% Split the data in some chunks
+ PartLength = size(Netconf) div 3,
+ <<Part1:PartLength/binary,Part2:PartLength/binary,Part3:PartLength/binary,
+ Part4/binary>> = Netconf,
+
+ %% Spawn a process which will wait a bit for the client to send
+ %% the request (below), then order the server to the chunks of the
+ %% rpc-reply one by one.
+ spawn(fun() -> timer:sleep(500),?NS:hupp(send,Part1),
+ timer:sleep(100),?NS:hupp(send,Part2),
+ timer:sleep(100),?NS:hupp(send,Part3),
+ timer:sleep(100),?NS:hupp(send,Part4)
+ end),
+
+ %% Order server to expect a get - then the process above will make
+ %% sure the rpc-reply is sent.
+ ?NS:expect('get'),
+ {ok,Data} = ct_netconfc:get(Client,{server,[{xmlns,"myns"}],[]}),
+
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+%% Same as receive_chunked_data, but timeout waiting for last part.
+timeout_receive_chunked_data(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+
+ %% Construct the data to return from netconf server
+ Data = [{servers,[{xmlns,"myns"}],
+ [{server,[],[{name,[],["server0"]}]},
+ {server,[],[{name,[],["server1"]}]},
+ {server,[],[{name,[],["server2"]}]},
+ {server,[],[{name,[],["server3"]}]},
+ {server,[],[{name,[],["server4"]}]},
+ {server,[],[{name,[],["server5"]}]},
+ {server,[],[{name,[],["server6"]}]},
+ {server,[],[{name,[],["server7"]}]},
+ {server,[],[{name,[],["server8"]}]},
+ {server,[],[{name,[],["server9"]}]}]
+ }],
+ Rpc = {'rpc-reply',?NETCONF_NAMESPACE_ATTR ++ [{'message-id',"1"}],
+ [{data,Data}]},
+ Xml = list_to_binary(xmerl:export_simple_element(Rpc,xmerl_xml)),
+ Netconf =
+ <<"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
+ Xml/binary,"\n",?END_TAG/binary>>,
+
+ %% Split the data in some chunks
+ PartLength = size(Netconf) div 3,
+ <<Part1:PartLength/binary,Part2:PartLength/binary,_Part3:PartLength/binary,
+ _Part4/binary>> = Netconf,
+
+ %% Spawn a process which will wait a bit for the client to send
+ %% the request (below), then order the server to the chunks of the
+ %% rpc-reply one by one.
+ spawn(fun() -> timer:sleep(500),?NS:hupp(send,Part1),
+ timer:sleep(100),?NS:hupp(send,Part2)
+ end),
+
+ %% Order server to expect a get - then the process above will make
+ %% sure the rpc-reply is sent - but only a part of it - then timeout.
+ ?NS:expect('get'),
+ {error,timeout} = ct_netconfc:get(Client,{server,[{xmlns,"myns"}],[]},2000),
+
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+%% Same as receive_chunked_data, but timeout waiting for last part.
+close_while_waiting_for_chunked_data(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+
+ %% Construct the data to return from netconf server
+ Data = [{servers,[{xmlns,"myns"}],
+ [{server,[],[{name,[],["server0"]}]},
+ {server,[],[{name,[],["server1"]}]},
+ {server,[],[{name,[],["server2"]}]},
+ {server,[],[{name,[],["server3"]}]},
+ {server,[],[{name,[],["server4"]}]},
+ {server,[],[{name,[],["server5"]}]},
+ {server,[],[{name,[],["server6"]}]},
+ {server,[],[{name,[],["server7"]}]},
+ {server,[],[{name,[],["server8"]}]},
+ {server,[],[{name,[],["server9"]}]}]
+ }],
+ Rpc = {'rpc-reply',?NETCONF_NAMESPACE_ATTR ++ [{'message-id',"1"}],
+ [{data,Data}]},
+ Xml = list_to_binary(xmerl:export_simple_element(Rpc,xmerl_xml)),
+ Netconf =
+ <<"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
+ Xml/binary,"\n",?END_TAG/binary>>,
+
+ %% Split the data in some chunks
+ PartLength = size(Netconf) div 3,
+ <<Part1:PartLength/binary,Part2:PartLength/binary,_Part3:PartLength/binary,
+ _Part4/binary>> = Netconf,
+
+ %% Spawn a process which will wait a bit for the client to send
+ %% the request (below), then order the server to the chunks of the
+ %% rpc-reply one by one.
+ spawn(fun() -> timer:sleep(500),?NS:hupp(send,Part1),
+ timer:sleep(100),?NS:hupp(send,Part2),
+ timer:sleep(100),?NS:hupp(kill)
+ end),
+
+ %% Order server to expect a get - then the process above will make
+ %% sure the rpc-reply is sent - but only a part of it - then close.
+ ?NS:expect('get'),
+ {error,closed} = ct_netconfc:get(Client,{server,[{xmlns,"myns"}],[]},2000),
+ ok.
+
+connection_crash(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+
+ %% Test that if the test survives killing the connection
+ %% process. Earlier this caused ct_util_server to terminate, and
+ %% this aborting the complete test run.
+ spawn(fun() -> timer:sleep(500),exit(Client,kill) end),
+ ?NS:expect(get),
+ {error,{closed,killed}}=ct_netconfc:get(Client,{server,[{xmlns,"myns"}],[]}),
+ ok.
+
+get_event_streams(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+ StreamNames = ["NETCONF","stream1","stream2"],
+ Streams = [{N,[{description,"descr of " ++ N}]} || N <- StreamNames],
+ StreamsXml = [{stream,[{name,[N]}|[{Tag,[Value]} || {Tag,Value} <- Data]]}
+ || {N,Data} <- Streams],
+ ReplyData = [{netconf,?NETMOD_NOTIF_NAMESPACE_ATTR,[{streams,StreamsXml}]}],
+ ?NS:expect_reply('get',{data,ReplyData}),
+ {ok,Streams} = ct_netconfc:get_event_streams(Client,StreamNames),
+
+ ?NS:expect_reply('get',{data,ReplyData}),
+ {ok,Streams} = ct_netconfc:get_event_streams(Client,StreamNames,5000),
+
+ ?NS:expect('get'),
+ {error,timeout} = ct_netconfc:get_event_streams(Client,100),
+
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+
+create_subscription(Config) ->
+ DataDir = ?config(data_dir,Config),
+
+ %% All defaults
+ {ok,Client1} = open_success(DataDir),
+ ?NS:expect_reply({'create-subscription',[stream]},ok),
+ ?ok = ct_netconfc:create_subscription(Client1),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client1),
+
+ %% All defaults with timeout
+ {ok,Client1a} = open_success(DataDir),
+ ?NS:expect_reply({'create-subscription',[stream]},ok),
+ ?ok = ct_netconfc:create_subscription(Client1a,5000),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client1a),
+
+ %% All defaults timing out
+ {ok,Client1b} = open_success(DataDir),
+ ?NS:expect({'create-subscription',[stream]}),
+ {error,timeout} = ct_netconfc:create_subscription(Client1b,100),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client1b),
+
+ %% Stream
+ {ok,Client2} = open_success(DataDir),
+ ?NS:expect_reply({'create-subscription',[stream]},ok),
+ Stream = "some_stream",
+ ?ok = ct_netconfc:create_subscription(Client2,Stream),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client2),
+
+ %% Filter
+ {ok,Client3} = open_success(DataDir),
+ ?NS:expect_reply({'create-subscription',[stream,filter]},ok),
+ Filter = {notification,?NETMOD_NOTIF_NAMESPACE_ATTR,
+ [eventTime]},
+ ?ok = ct_netconfc:create_subscription(Client3,Filter),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client3),
+
+ %% Filter with timeout
+ {ok,Client3a} = open_success(DataDir),
+ ?NS:expect_reply({'create-subscription',[stream,filter]},ok),
+ ?ok = ct_netconfc:create_subscription(Client3a,Filter,5000),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client3a),
+
+ %% Filter timing out
+ {ok,Client3b} = open_success(DataDir),
+ ?NS:expect({'create-subscription',[stream,filter]}),
+ {error,timeout}=ct_netconfc:create_subscription(Client3b,Filter,100),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client3b),
+
+ %% Stream and filter
+ {ok,Client4} = open_success(DataDir),
+ ?NS:expect_reply({'create-subscription',[stream,filter]},ok),
+ ?ok = ct_netconfc:create_subscription(Client4,Stream,Filter),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client4),
+
+ %% Start/stop time
+ {ok,Client5} = open_success(DataDir),
+ ?NS:expect_reply({'create-subscription',[stream,startTime,stopTime]},ok),
+ StartTime = xs_datetime({D,{H,M,S}}= calendar:local_time()),
+ StopTime = xs_datetime({D,{H+2,M,S}}),
+ ?ok = ct_netconfc:create_subscription(Client5,StartTime,StopTime),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client5),
+
+ %% Start/stop time with timeout
+ {ok,Client5a} = open_success(DataDir),
+ ?NS:expect_reply({'create-subscription',[stream,startTime,stopTime]},ok),
+ ?ok = ct_netconfc:create_subscription(Client5a,StartTime,StopTime,5000),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client5a),
+
+ %% Start/stop time timing out
+ {ok,Client5b} = open_success(DataDir),
+ ?NS:expect({'create-subscription',[stream,startTime,stopTime]}),
+ {error,timeout} =
+ ct_netconfc:create_subscription(Client5b,StartTime,StopTime,100),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client5b),
+
+ %% Stream and start/stop time
+ {ok,Client6} = open_success(DataDir),
+ ?NS:expect_reply({'create-subscription',[stream,startTime,stopTime]},ok),
+ ?ok = ct_netconfc:create_subscription(Client6,Stream,StartTime,StopTime),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client6),
+
+ %% Filter and start/stop time
+ {ok,Client7} = open_success(DataDir),
+ ?NS:expect_reply({'create-subscription',[stream,filter,startTime,stopTime]},
+ ok),
+ ?ok = ct_netconfc:create_subscription(Client7,Filter,
+ StartTime,StopTime),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client7),
+
+ %% Stream, filter and start/stop time
+ {ok,Client8} = open_success(DataDir),
+ ?NS:expect_reply({'create-subscription',[stream,filter,startTime,stopTime]},
+ ok),
+ ?ok = ct_netconfc:create_subscription(Client8,Stream,Filter,
+ StartTime,StopTime),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client8),
+
+ ok.
+
+receive_event(Config) ->
+ DataDir = ?config(data_dir,Config),
+ {ok,Client} = open_success(DataDir),
+ ?NS:expect_reply({'create-subscription',[stream]},ok),
+ ?ok = ct_netconfc:create_subscription(Client),
+
+ ?NS:hupp(send_event),
+
+ receive
+ %% Matching ?NS:make_msg(event)
+ {notification,?NETCONF_NOTIF_NAMESPACE_ATTR,
+ [{eventTime,[],[_Time]},
+ {event,[{xmlns,"http://my.namespaces.com/event"}],
+ [{severity,_,_},
+ {description,_,_}]}]} ->
+ ok;
+ Other ->
+ ct:fail({got_unexpected_while_waiting_for_event, Other})
+ after 3000 ->
+ ct:fail(timeout_waiting_for_event)
+ end,
+
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+
+ ok.
+
+%%%-----------------------------------------------------------------
+
+break(_Config) ->
+ test_server:break("break test case").
+
+br() ->
+ test_server:break("").
+
+%%%-----------------------------------------------------------------
+%% Open a netconf session which is not specified in a config file
+open_success(Dir) ->
+ open_success(Dir,[]).
+
+%% Open a netconf session which is not specified in a config file, and
+%% give som extra options in addition to the test defaults.
+open_success(Dir,ExtraOpts) when is_list(Dir), is_list(ExtraOpts) ->
+ ?NS:hello(1), % tell server to send hello with session id 1
+ ?NS:expect(hello), % tell server to expect a hello message from client
+ open(Dir,ExtraOpts);
+
+%% Open a named netconf session which is not specified in a config file
+open_success(KeyOrName,Dir) when is_atom(KeyOrName), is_list(Dir) ->
+ ?NS:hello(1),
+ ?NS:expect(hello),
+ ct_netconfc:open(KeyOrName,?DEFAULT_SSH_OPTS(Dir)).
+
+open(Dir) ->
+ open(Dir,[]).
+open(Dir,ExtraOpts) ->
+ Opts = lists:ukeymerge(1,lists:keysort(1,ExtraOpts),
+ lists:keysort(1,?DEFAULT_SSH_OPTS(Dir))),
+ ct_netconfc:open(Opts).
+
+%%%-----------------------------------------------------------------
+%%% Open a netconf session which is specified in a config file
+%%% KeyOrName is the config key (server_id()) or name given in a
+%%% require statement (target_name()).
+open_configured_success(KeyOrName,Dir) when is_atom(KeyOrName) ->
+ open_configured_success(KeyOrName,Dir,[]).
+open_configured_success(KeyOrName,Dir,ExtraOpts) when is_atom(KeyOrName) ->
+ ?NS:hello(1),
+ ?NS:expect(hello),
+ ct_netconfc:open(KeyOrName,[{user_dir,Dir}|ExtraOpts]).
+
+%%%-----------------------------------------------------------------
+%%% Convert erlang datetime to the simplest variant of XML dateTime
+xs_datetime({{Y,M,D},{H,Mi,S}}) ->
+ lists:flatten(
+ io_lib:format("~p-~s-~sT~s:~s:~s",[Y,pad(M),pad(D),pad(H),pad(Mi),pad(S)])).
+
+pad(I) when I<10 ->
+ "0"++integer_to_list(I);
+pad(I) ->
+ integer_to_list(I).
+
+
+%%%-----------------------------------------------------------------
+%%% BEGIN SSH key management
+%% copy private keys to given dir from ~/.ssh
+get_id_keys(Config) ->
+ DstDir = ?config(priv_dir, Config),
+ SrcDir = filename:join(os:getenv("HOME"), ".ssh"),
+ RsaOk = copyfile(SrcDir, DstDir, "id_rsa"),
+ DsaOk = copyfile(SrcDir, DstDir, "id_dsa"),
+ case {RsaOk, DsaOk} of
+ {{ok, _}, {ok, _}} -> {ok, both};
+ {{ok, _}, _} -> {ok, rsa};
+ {_, {ok, _}} -> {ok, dsa};
+ {Error, _} -> Error
+ end.
+
+%% Remove later on. Use make_dsa_files instead.
+remove_id_keys(Config) ->
+ Dir = ?config(priv_dir, Config),
+ file:delete(filename:join(Dir, "id_rsa")),
+ file:delete(filename:join(Dir, "id_dsa")).
+
+
+make_dsa_files(Config) ->
+ make_dsa_files(Config, rfc4716_public_key).
+make_dsa_files(Config, Type) ->
+ {DSA, EncodedKey} = gen_dsa(128, 20),
+ PKey = DSA#'DSAPrivateKey'.y,
+ P = DSA#'DSAPrivateKey'.p,
+ Q = DSA#'DSAPrivateKey'.q,
+ G = DSA#'DSAPrivateKey'.g,
+ Dss = #'Dss-Parms'{p=P, q=Q, g=G},
+ {ok, Hostname} = inet:gethostname(),
+ {ok, {A, B, C, D}} = inet:getaddr(Hostname, inet),
+ IP = lists:concat([A, ".", B, ".", C, ".", D]),
+ Attributes = [], % Could be [{comment,"user@" ++ Hostname}],
+ HostNames = [{hostnames,[IP, IP]}],
+ PublicKey = [{{PKey, Dss}, Attributes}],
+ KnownHosts = [{{PKey, Dss}, HostNames}],
+
+ KnownHostsEnc = public_key:ssh_encode(KnownHosts, known_hosts),
+ KnownHosts = public_key:ssh_decode(KnownHostsEnc, known_hosts),
+
+ PublicKeyEnc = public_key:ssh_encode(PublicKey, Type),
+
+ SystemTmpDir = ?config(data_dir, Config),
+ filelib:ensure_dir(SystemTmpDir),
+ file:make_dir(SystemTmpDir),
+
+ DSAFile = filename:join(SystemTmpDir, "ssh_host_dsa_key.pub"),
+ file:delete(DSAFile),
+
+ DSAPrivateFile = filename:join(SystemTmpDir, "ssh_host_dsa_key"),
+ file:delete(DSAPrivateFile),
+
+ KHFile = filename:join(SystemTmpDir, "known_hosts"),
+ file:delete(KHFile),
+
+ PemBin = public_key:pem_encode([EncodedKey]),
+
+ file:write_file(DSAFile, PublicKeyEnc),
+ file:write_file(KHFile, KnownHostsEnc),
+ file:write_file(DSAPrivateFile, PemBin),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Creates a dsa key (OBS: for testing only)
+%% the sizes are in bytes
+%% gen_dsa(::integer()) -> {::atom(), ::binary(), ::opaque()}
+%%--------------------------------------------------------------------
+gen_dsa(LSize,NSize) when is_integer(LSize), is_integer(NSize) ->
+ Key = gen_dsa2(LSize, NSize),
+ {Key, encode_key(Key)}.
+
+encode_key(Key = #'RSAPrivateKey'{}) ->
+ {ok, Der} = 'OTP-PUB-KEY':encode('RSAPrivateKey', Key),
+ {'RSAPrivateKey', list_to_binary(Der), not_encrypted};
+encode_key(Key = #'DSAPrivateKey'{}) ->
+ {ok, Der} = 'OTP-PUB-KEY':encode('DSAPrivateKey', Key),
+ {'DSAPrivateKey', list_to_binary(Der), not_encrypted}.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% DSA key generation (OBS: for testing only)
+%% See http://en.wikipedia.org/wiki/Digital_Signature_Algorithm
+%% and the fips_186-3.pdf
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+gen_dsa2(LSize, NSize) ->
+ Q = prime(NSize), %% Choose N-bit prime Q
+ X0 = prime(LSize),
+ P0 = prime((LSize div 2) +1),
+
+ %% Choose L-bit prime modulus P such that p-1 is a multiple of q.
+ case dsa_search(X0 div (2*Q*P0), P0, Q, 1000) of
+ error ->
+ gen_dsa2(LSize, NSize);
+ P ->
+ G = crypto:mod_exp(2, (P-1) div Q, P), % Choose G a number whose multiplicative order modulo p is q.
+ %% such that This may be done by setting g = h^(p-1)/q mod p, commonly h=2 is used.
+
+ X = prime(20), %% Choose x by some random method, where 0 < x < q.
+ Y = crypto:mod_exp(G, X, P), %% Calculate y = g^x mod p.
+
+ #'DSAPrivateKey'{version=0, p=P, q=Q, g=G, y=Y, x=X}
+ end.
+
+%% See fips_186-3.pdf
+dsa_search(T, P0, Q, Iter) when Iter > 0 ->
+ P = 2*T*Q*P0 + 1,
+ case is_prime(crypto:mpint(P), 50) of
+ true -> P;
+ false -> dsa_search(T+1, P0, Q, Iter-1)
+ end;
+dsa_search(_,_,_,_) ->
+ error.
+
+
+%%%%%%% Crypto Math %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+prime(ByteSize) ->
+ Rand = odd_rand(ByteSize),
+ crypto:erlint(prime_odd(Rand, 0)).
+
+prime_odd(Rand, N) ->
+ case is_prime(Rand, 50) of
+ true ->
+ Rand;
+ false ->
+ NotPrime = crypto:erlint(Rand),
+ prime_odd(crypto:mpint(NotPrime+2), N+1)
+ end.
+
+%% see http://en.wikipedia.org/wiki/Fermat_primality_test
+is_prime(_, 0) -> true;
+is_prime(Candidate, Test) ->
+ CoPrime = odd_rand(<<0,0,0,4, 10000:32>>, Candidate),
+ case crypto:mod_exp(CoPrime, Candidate, Candidate) of
+ CoPrime -> is_prime(Candidate, Test-1);
+ _ -> false
+ end.
+
+odd_rand(Size) ->
+ Min = 1 bsl (Size*8-1),
+ Max = (1 bsl (Size*8))-1,
+ odd_rand(crypto:mpint(Min), crypto:mpint(Max)).
+
+odd_rand(Min,Max) ->
+ Rand = <<Sz:32, _/binary>> = crypto:rand_uniform(Min,Max),
+ BitSkip = (Sz+4)*8-1,
+ case Rand of
+ Odd = <<_:BitSkip, 1:1>> -> Odd;
+ Even = <<_:BitSkip, 0:1>> ->
+ crypto:mpint(crypto:erlint(Even)+1)
+ end.
+
+copyfile(SrcDir, DstDir, Fn) ->
+ file:copy(filename:join(SrcDir, Fn),
+ filename:join(DstDir, Fn)).
+
+%%% END SSH key management
+%%%-----------------------------------------------------------------
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl b/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl
new file mode 100644
index 0000000000..665b0e556c
--- /dev/null
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl
@@ -0,0 +1,506 @@
+%%--------------------------------------------------------------------
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2012. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%----------------------------------------------------------------------
+%% A netconf server used for testing of netconfc
+-module(ns).
+
+%-compile(export_all).
+-include_lib("common_test/src/ct_netconfc.hrl").
+
+
+%%%-----------------------------------------------------------------
+%%% API
+-export([start/1,
+ stop/1,
+ hello/1,
+ hello/2,
+ expect/1,
+ expect_reply/2,
+ expect_do/2,
+ expect_do_reply/3,
+ hupp/1,
+ hupp/2]).
+
+%%%-----------------------------------------------------------------
+%%% ssh_channel callbacks
+-export([init/1,
+ terminate/2,
+ handle_ssh_msg/2,
+ handle_msg/2]).
+
+%%%-----------------------------------------------------------------
+%% Server specifications
+-define(SERVER_DATA_NAMESPACE, "ClientTest").
+-define(CAPABILITIES,?CAPABILITIES_VSN("1.0")).
+-define(CAPABILITIES_VSN(Vsn),
+ [
+ ?NETCONF_BASE_CAP ++ Vsn,
+ "urn:ietf:params:netconf:capability:writable-running:1.0",
+ "urn:ietf:params:netconf:capability:candidate:1.0",
+ "urn:ietf:params:netconf:capability:confirmed-commit:1.0",
+ "urn:ietf:params:netconf:capability:rollback-on-error:1.0",
+ "urn:ietf:params:netconf:capability:startup:1.0",
+ "urn:ietf:params:netconf:capability:url:1.0",
+ "urn:ietf:params:netconf:capability:xpath:1.0",
+ "urn:ietf:params:netconf:capability:notification:1.0",
+ "urn:ietf:params:netconf:capability:interleave:1.0",
+ ?ACTION_NAMESPACE,
+ ?SERVER_DATA_NAMESPACE
+ ]).
+-define(SSH_PORT, 2060).
+-define(ssh_config(Dir),[{port, ?SSH_PORT},
+ {interface, {127,0,0,1}},
+ {system_dir, Dir},
+ {user_dir, Dir},
+ {user_passwords, [{"xxx","xxx"}]},
+ {password, "global-xxx"}]).
+
+%% Some help for debugging
+%-define(dbg(F,A),io:format(F,A)).
+-define(dbg(F,A),ok).
+-define(dbg_event(Event,Expect),
+ ?dbg("Event: ~p~nExpected: ~p~n",[Event,Expect])).
+
+%% State
+-record(session, {cb,
+ connection,
+ buffer = <<>>,
+ session_id}).
+
+
+%%%-----------------------------------------------------------------
+%%% API
+
+%% Start the netconf server and use the given directory as system_dir
+%% and user_dir
+start(Dir) ->
+ spawn(fun() -> init_server(Dir) end).
+
+%% Stop the netconf server
+stop(Pid) ->
+ Pid ! {stop,self()},
+ receive stopped -> ok end.
+
+%% Set the session id for the hello message.
+%% If this is not called prior to starting the session, no hello
+%% message will be sent.
+%% 'Stuff' indicates some special handling to e.g. provoke error cases
+hello(SessionId) ->
+ hello(SessionId,undefined).
+hello(SessionId,Stuff) ->
+ insert(hello,{SessionId,Stuff}).
+
+%% Tell server to expect the given message without doing any further
+%% actions. To be called directly before sending a request.
+expect(Expect) ->
+ expect_do_reply(Expect,undefined,undefined).
+
+%% Tell server to expect the given message and reply with the give
+%% reply. To be called directly before sending a request.
+expect_reply(Expect,Reply) ->
+ expect_do_reply(Expect,undefined,Reply).
+
+%% Tell server to expect the given message and perform an action. To
+%% be called directly before sending a request.
+expect_do(Expect,Do) ->
+ expect_do_reply(Expect,Do,undefined).
+
+%% Tell server to expect the given message, perform an action and
+%% reply with the given reply. To be called directly before sending a
+%% request.
+expect_do_reply(Expect,Do,Reply) ->
+ add_expect({Expect,Do,Reply}).
+
+%% Hupp the server - i.e. tell it to do something -
+%% e.g. hupp(send_event) will cause send_event(State) to be called on
+%% the session channel process.
+hupp(send_event) ->
+ hupp(send,[make_msg(event)]);
+hupp(kill) ->
+ hupp(fun hupp_kill/1,[]).
+
+hupp(send,Data) ->
+ hupp(fun hupp_send/2,[Data]);
+hupp(Fun,Args) when is_function(Fun) ->
+ [{_,Pid}] = lookup(channel_process),
+ Pid ! {hupp,Fun,Args}.
+
+%%%-----------------------------------------------------------------
+%%% Main loop of the netconf server
+init_server(Dir) ->
+ ets:new(ns_tab,[set,named_table,public]),
+ Config = ?ssh_config(Dir),
+ {_,Host} = lists:keyfind(interface, 1, Config),
+ {_,Port} = lists:keyfind(port, 1, Config),
+ Opts = lists:filter(fun({Key,_}) ->
+ lists:member(Key,[system_dir,
+ password,
+ user_passwords,
+ pwdfun])
+ end,
+ Config),
+ {ok, Daemon} =
+ ssh:daemon(Host, Port,
+ [{subsystems,[{"netconf",{?MODULE,[]}}]}
+ |Opts]),
+ loop(Daemon).
+
+loop(Daemon) ->
+ receive
+ {stop,From} ->
+ ssh:stop_daemon(Daemon),
+ From ! stopped
+ end.
+
+%%----------------------------------------------------------------------
+%% Behaviour callback functions (ssh_channel)
+%%----------------------------------------------------------------------
+init([]) ->
+ {ok, undefined}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+handle_ssh_msg({ssh_cm,CM,{data, Ch, _Type = 0, Data}}, State) ->
+ %% erlang:display({self(),data,CM,Ch,State}),
+ data_for_channel(CM, Ch, Data, State);
+handle_ssh_msg({ssh_cm,CM,{closed, Ch}}, State) ->
+ %% erlang:display({self(),closed,CM,Ch,State}),
+ stop_channel(CM, Ch, State);
+handle_ssh_msg({ssh_cm,CM,{eof, Ch}}, State) ->
+ %% erlang:display({self(),eof,CM,Ch,State}),
+ data_for_channel(CM,Ch, <<>>, State).
+
+
+handle_msg({'EXIT', _Pid, _Reason}, State) ->
+ {ok, State};
+handle_msg({ssh_channel_up,Ch,CM},undefined) ->
+ %% erlang:display({self(),up,CM,Ch}),
+ ConnRef = {CM,Ch},
+ SessionId = maybe_hello(ConnRef),
+ insert(channel_process,self()), % used to hupp the server
+ {ok, #session{connection = ConnRef,
+ session_id = SessionId}};
+handle_msg({hupp,Fun,Args},State) ->
+ {ok,apply(Fun,Args ++ [State])}.
+
+data_for_channel(CM, Ch, Data, State) ->
+ try data(Data, State) of
+ {ok, NewState} ->
+ case erase(stop) of
+ true ->
+ stop_channel(CM, Ch, NewState);
+ _ ->
+ {ok, NewState}
+ end
+ catch
+ Class:Reason ->
+ Stacktrace = erlang:get_stacktrace(),
+ error_logger:error_report([{?MODULE, data_for_channel},
+ {request, Data},
+ {reason, {Class, Reason}},
+ {stacktrace, Stacktrace}]),
+ stop_channel(CM, Ch, State)
+ end.
+
+data(Data, State = #session{connection = ConnRef,
+ buffer = Buffer}) ->
+ AllData = <<Buffer/binary,Data/binary>>,
+ case find_endtag(AllData) of
+ {ok,Msgs,Rest} ->
+ [check_expected(ConnRef,Msg) || Msg <- Msgs],
+ {ok,State#session{buffer=Rest}};
+ need_more ->
+ {ok,State#session{buffer=AllData}}
+ end.
+
+stop_channel(CM, Ch, State) ->
+ ssh:close(CM),
+ {stop, Ch, State}.
+
+
+%%%-----------------------------------------------------------------
+%%% Functions to trigg via hupp/1:
+
+%% Send data spontaneously - e.g. an event
+hupp_send(Data,State = #session{connection = ConnRef}) ->
+ send(ConnRef,Data),
+ State.
+hupp_kill(State = #session{connection = ConnRef}) ->
+ kill(ConnRef),
+ State.
+
+%%%-----------------------------------------------------------------
+%%% Internal functions
+
+
+%%% Send ssh data to the client
+send({CM,Ch},Data) ->
+ ssh_connection:send(CM, Ch, Data).
+
+%%% Kill ssh connection
+kill({CM,_Ch}) ->
+ ssh:close(CM).
+
+add_expect(Add) ->
+ case lookup(expect) of
+ [] ->
+ insert(expect,[Add]);
+ [{expect,First}] ->
+ insert(expect,First ++ [Add])
+ end,
+ ok.
+
+insert(Key,Value) ->
+ ets:insert(ns_tab,{Key,Value}).
+lookup(Key) ->
+ ets:lookup(ns_tab,Key).
+
+maybe_hello(ConnRef) ->
+ case lookup(hello) of
+ [{hello,{SessionId,Stuff}}] ->
+ %% erlang:display({SessionId,Stuff}),
+ ets:delete(ns_tab,hello),
+ insert({session,SessionId},ConnRef),
+ reply(ConnRef,{hello,SessionId,Stuff}),
+ SessionId;
+ [] ->
+ undefined
+ end.
+
+find_endtag(Data) ->
+ case binary:split(Data,[?END_TAG],[global]) of
+ [Data] ->
+ need_more;
+ Msgs ->
+ {ok,lists:sublist(Msgs,length(Msgs)-1),lists:last(Msgs)}
+ end.
+
+check_expected(ConnRef,Msg) ->
+ case lookup(expect) of
+ [{expect,[{Expect,Do,Reply}|Rest]}] ->
+ insert(expect,Rest),
+ %% erlang:display({got,io_lib:format("~s",[Msg])}),
+ %% erlang:display({expected,Expect}),
+ match(Msg,Expect),
+ do(ConnRef, Do),
+ reply(ConnRef,Reply);
+ Expected ->
+ exit({error,{got_unexpected,Msg,Expected}})
+ end.
+
+match(Msg,Expect) ->
+ ?dbg("Match: ~p~n",[Msg]),
+ {ok,ok,<<>>} = xmerl_sax_parser:stream(Msg,[{event_fun,fun event/3},
+ {event_state,Expect}]).
+
+event(Event,_Loc,Expect) ->
+ ?dbg_event(Event,Expect),
+ event(Event,Expect).
+
+event(startDocument,Expect) -> match(Expect);
+event({startElement,_,Name,_,Attrs},[{se,Name}|Match]) ->
+ msg_id(Name,Attrs),
+ Match;
+event({startElement,_,Name,_,Attrs},[ignore,{se,Name}|Match]) ->
+ msg_id(Name,Attrs),
+ Match;
+event({startElement,_,Name,_,Attrs},[{se,Name,As}|Match]) ->
+ msg_id(Name,Attrs),
+ match_attrs(Name,As,Attrs),
+ Match;
+event({startElement,_,Name,_,Attrs},[ignore,{se,Name,As}|Match]) ->
+ msg_id(Name,Attrs),
+ match_attrs(Name,As,Attrs),
+ Match;
+event({startPrefixMapping,_,Ns},[{ns,Ns}|Match]) -> Match;
+event({startPrefixMapping,_,Ns},[ignore,{ns,Ns}|Match]) -> Match;
+event({endPrefixMapping,_},Match) -> Match;
+event({endElement,_,Name,_},[{ee,Name}|Match]) -> Match;
+event({endElement,_,Name,_},[ignore,{ee,Name}|Match]) -> Match;
+event(endDocument,Match) when Match==[]; Match==[ignore] -> ok;
+event(_,[ignore|_]=Match) -> Match;
+event(Event,Match) -> throw({nomatch,{Event,Match}}).
+
+msg_id("rpc",Attrs) ->
+ case lists:keyfind("message-id",3,Attrs) of
+ {_,_,_,Str} -> put(msg_id,Str);
+ false -> erase(msg_id)
+ end;
+msg_id(_,_) ->
+ ok.
+
+match_attrs(Name,[{Key,Value}|As],Attrs) ->
+ case lists:keyfind(atom_to_list(Key),3,Attrs) of
+ {_,_,_,Value} -> match_attrs(Name,As,Attrs);
+ false -> throw({missing_attr,Key,Name,Attrs});
+ _ -> throw({faulty_attr_value,Key,Name,Attrs})
+ end;
+match_attrs(_,[],_) ->
+ ok.
+
+do(ConnRef, close) ->
+ ets:match_delete(ns_tab,{{session,'_'},ConnRef}),
+ put(stop,true);
+do(_ConnRef, {kill,SessionId}) ->
+ case lookup({session,SessionId}) of
+ [{_,Owner}] ->
+ ets:delete(ns_tab,{session,SessionId}),
+ kill(Owner);
+ _ ->
+ exit({no_session_to_kill,SessionId})
+ end;
+do(_, undefined) ->
+ ok.
+
+reply(_,undefined) ->
+ ?dbg("no reply~n",[]),
+ ok;
+reply(ConnRef,Reply) ->
+ ?dbg("Reply: ~p~n",[Reply]),
+ send(ConnRef, make_msg(Reply)).
+
+from_simple(Simple) ->
+ list_to_binary(xmerl:export_simple_element(Simple,xmerl_xml)).
+
+xml(Content) ->
+ <<"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
+ Content/binary,"\n",?END_TAG/binary>>.
+
+rpc_reply(Content) when is_binary(Content) ->
+ MsgId = case erase(msg_id) of
+ undefined -> <<>>;
+ Id -> list_to_binary([" message-id=\"",Id,"\""])
+ end,
+ <<"<rpc-reply xmlns=\"",?NETCONF_NAMESPACE,"\"",MsgId/binary,">\n",
+ Content/binary,"\n</rpc-reply>">>;
+rpc_reply(Content) ->
+ rpc_reply(list_to_binary(Content)).
+
+session_id(no_session_id) ->
+ <<>>;
+session_id(SessionId0) ->
+ SessionId = list_to_binary(integer_to_list(SessionId0)),
+ <<"<session-id>",SessionId/binary,"</session-id>\n">>.
+
+capabilities(undefined) ->
+ CapsXml = list_to_binary([["<capability>",C,"</capability>\n"]
+ || C <- ?CAPABILITIES]),
+ <<"<capabilities>\n",CapsXml/binary,"</capabilities>\n">>;
+capabilities({base,Vsn}) ->
+ CapsXml = list_to_binary([["<capability>",C,"</capability>\n"]
+ || C <- ?CAPABILITIES_VSN(Vsn)]),
+ <<"<capabilities>\n",CapsXml/binary,"</capabilities>\n">>;
+capabilities(no_base) ->
+ [_|Caps] = ?CAPABILITIES,
+ CapsXml = list_to_binary([["<capability>",C,"</capability>\n"] || C <- Caps]),
+ <<"<capabilities>\n",CapsXml/binary,"</capabilities>\n">>;
+capabilities(no_caps) ->
+ <<>>.
+
+%%%-----------------------------------------------------------------
+%%% Match received netconf message from the client. Add a new clause
+%%% for each new message to recognize. The clause argument shall match
+%%% the Expect argument in expect/1, expect_reply/2 or
+%%% expect_do_reply/3.
+%%%
+%%% match(term()) -> [Match].
+%%% Match = ignore | {se,Name} | {se,Name,Attrs} | {ee,Name} | {ns,Namespace}
+%%% Name = string()
+%%% Attrs = [{atom(),string()}]
+%%% Namespace = string()
+%%%
+%%% 'se' means start element, 'ee' means end element - i.e. to match
+%%% an XML element you need one 'se' entry and one 'ee' entry with the
+%%% same name in the match list.
+match(hello) ->
+ [ignore,{se,"hello"},ignore,{ee,"hello"},ignore];
+match('close-session') ->
+ [ignore,{se,"rpc"},{se,"close-session"},
+ {ee,"close-session"},{ee,"rpc"},ignore];
+match('edit-config') ->
+ [ignore,{se,"rpc"},{se,"edit-config"},{se,"target"},ignore,{ee,"target"},
+ {se,"config"},ignore,{ee,"config"},{ee,"edit-config"},{ee,"rpc"},ignore];
+match('get') ->
+ match({get,subtree});
+match({'get',FilterType}) ->
+ [ignore,{se,"rpc"},{se,"get"},{se,"filter",[{type,atom_to_list(FilterType)}]},
+ ignore,{ee,"filter"},{ee,"get"},{ee,"rpc"},ignore];
+match('get-config') ->
+ match({'get-config',subtree});
+match({'get-config',FilterType}) ->
+ [ignore,{se,"rpc"},{se,"get-config"},{se,"source"},ignore,{ee,"source"},
+ {se,"filter",[{type,atom_to_list(FilterType)}]},ignore,{ee,"filter"},
+ {ee,"get-config"},{ee,"rpc"},ignore];
+match('copy-config') ->
+ [ignore,{se,"rpc"},{se,"copy-config"},{se,"target"},ignore,{ee,"target"},
+ {se,"source"},ignore,{ee,"source"},{ee,"copy-config"},{ee,"rpc"},ignore];
+match('delete-config') ->
+ [ignore,{se,"rpc"},{se,"delete-config"},{se,"target"},ignore,{ee,"target"},
+ {ee,"delete-config"},{ee,"rpc"},ignore];
+match('lock') ->
+ [ignore,{se,"rpc"},{se,"lock"},{se,"target"},ignore,{ee,"target"},
+ {ee,"lock"},{ee,"rpc"},ignore];
+match('unlock') ->
+ [ignore,{se,"rpc"},{se,"unlock"},{se,"target"},ignore,{ee,"target"},
+ {ee,"unlock"},{ee,"rpc"},ignore];
+match('kill-session') ->
+ [ignore,{se,"rpc"},{se,"kill-session"},{se,"session-id"},ignore,
+ {ee,"session-id"},{ee,"kill-session"},{ee,"rpc"},ignore];
+match(action) ->
+ [ignore,{se,"rpc"},{ns,?ACTION_NAMESPACE},{se,"action"},{se,"data"},ignore,
+ {ee,"data"},{ee,"action"},{ee,"rpc"},ignore];
+match({'create-subscription',Content}) ->
+ [ignore,{se,"rpc"},{ns,?NETCONF_NOTIF_NAMESPACE},
+ {se,"create-subscription"}] ++
+ lists:flatmap(fun(X) ->
+ [{se,atom_to_list(X)},ignore,{ee,atom_to_list(X)}]
+ end, Content) ++
+ [{ee,"create-subscription"},{ee,"rpc"},ignore];
+match(any) ->
+ [ignore].
+
+
+
+%%%-----------------------------------------------------------------
+%%% Make message to send to the client.
+%%% Add a new clause for each new message that shall be sent. The
+%%% clause shall match the Reply argument in expect_reply/2 or
+%%% expect_do_reply/3.
+make_msg({hello,SessionId,Stuff}) ->
+ SessionIdXml = session_id(SessionId),
+ CapsXml = capabilities(Stuff),
+ xml(<<"<hello xmlns=\"",?NETCONF_NAMESPACE,"\">\n",CapsXml/binary,
+ SessionIdXml/binary,"</hello>">>);
+make_msg(ok) ->
+ xml(rpc_reply("<ok/>"));
+make_msg({data,Data}) ->
+ xml(rpc_reply(from_simple({data,Data})));
+make_msg(event) ->
+ xml(<<"<notification xmlns=\"",?NETCONF_NOTIF_NAMESPACE,"\">"
+ "<eventTime>2012-06-14T14:50:54+02:00</eventTime>"
+ "<event xmlns=\"http://my.namespaces.com/event\">"
+ "<severity>major</severity>"
+ "<description>Something terrible happened</description>"
+ "</event>"
+ "</notification>">>);
+make_msg(Xml) when is_binary(Xml) ->
+ xml(Xml);
+make_msg(Simple) when is_tuple(Simple) ->
+ xml(from_simple(Simple)).
diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl
index 02246b5763..80cca4a1cc 100644
--- a/lib/common_test/test/ct_test_support.erl
+++ b/lib/common_test/test/ct_test_support.erl
@@ -35,6 +35,8 @@
verify_events/3, reformat/2, log_events/4,
join_abs_dirs/2]).
+-export([ct_test_halt/1]).
+
-include_lib("kernel/include/file.hrl").
%%%-----------------------------------------------------------------
@@ -229,8 +231,9 @@ run(Opts, Config) when is_list(Opts) ->
%% use ct interface
test_server:format(Level, "~n[RUN #1] Calling ct:run_test(~p) on ~p~n",
[Opts, CTNode]),
- Result1 = rpc:call(CTNode, ct, run_test, [Opts]),
-
+ CtRunTestResult = rpc:call(CTNode, ct, run_test, [Opts]),
+ test_server:format(Level, "~n[RUN #1] Got return value ~p~n",
+ [CtRunTestResult]),
case rpc:call(CTNode, erlang, whereis, [ct_util_server]) of
undefined ->
ok;
@@ -242,17 +245,36 @@ run(Opts, Config) when is_list(Opts) ->
undefined = rpc:call(CTNode, erlang, whereis, [ct_util_server])
end,
%% use run_test interface (simulated)
- test_server:format(Level, "Saving start opts on ~p: ~p~n", [CTNode,Opts]),
- rpc:call(CTNode, application, set_env, [common_test, run_test_start_opts, Opts]),
- test_server:format(Level, "[RUN #2] Calling ct_run:script_start() on ~p~n", [CTNode]),
- Result2 = rpc:call(CTNode, ct_run, script_start, []),
- case {Result1,Result2} of
- {ok,ok} ->
+ Opts1 = [{halt_with,{?MODULE,ct_test_halt}} | Opts],
+ test_server:format(Level, "Saving start opts on ~p: ~p~n",
+ [CTNode, Opts1]),
+ rpc:call(CTNode, application, set_env,
+ [common_test, run_test_start_opts, Opts1]),
+ test_server:format(Level, "[RUN #2] Calling ct_run:script_start() on ~p~n",
+ [CTNode]),
+ ExitStatus = rpc:call(CTNode, ct_run, script_start, []),
+ test_server:format(Level, "[RUN #2] Got exit status value ~p~n",
+ [ExitStatus]),
+ case {CtRunTestResult,ExitStatus} of
+ {{_Ok,Failed,{_UserSkipped,_AutoSkipped}},1} when Failed > 0 ->
+ ok;
+ {{_Ok,0,{_UserSkipped,AutoSkipped}},ExitStatus} when AutoSkipped > 0 ->
+ case proplists:get_value(exit_status, Opts1) of
+ ignore_config when ExitStatus == 1 ->
+ {error,{wrong_exit_status,ExitStatus}};
+ _ ->
+ ok
+ end;
+ {{error,_}=Error,ExitStatus} ->
+ if ExitStatus /= 2 ->
+ {error,{wrong_exit_status,ExitStatus}};
+ ExitStatus == 2 ->
+ Error
+ end;
+ {{_Ok,0,{_UserSkipped,_AutoSkipped}},0} ->
ok;
- {E,_} when E =/= ok ->
- E;
- {_,E} when E =/= ok ->
- E
+ Unexpected ->
+ {error,{unexpected_return_value,Unexpected}}
end.
run(M, F, A, Config) ->
@@ -272,6 +294,10 @@ run({M,F,A}, InitCalls, Config) ->
[M, F, A, CTNode]),
rpc:call(CTNode, M, F, A).
+%% this is the last function that ct_run:script_start() calls, so the
+%% return value here is what rpc:call/4 above returns
+ct_test_halt(ExitStatus) ->
+ ExitStatus.
%%%-----------------------------------------------------------------
%%% wait_for_ct_stop/1
diff --git a/lib/common_test/test/ct_testspec_1_SUITE.erl b/lib/common_test/test/ct_testspec_1_SUITE.erl
index b6dcf63fdf..693e8c6567 100644
--- a/lib/common_test/test/ct_testspec_1_SUITE.erl
+++ b/lib/common_test/test/ct_testspec_1_SUITE.erl
@@ -621,7 +621,9 @@ setup_and_execute(TCName, TestSpec, Config) ->
ok = ct_test_support:run(Opts, Config),
TestSpec1 = [{logdir,proplists:get_value(logdir,Opts)},
{label,proplists:get_value(label,TestTerms)} | TestSpec],
- ok = ct_test_support:run(ct, run_testspec, [TestSpec1], Config),
+ {_Ok,_Failed,{_USkipped,_ASkipped}} =
+ ct_test_support:run(ct, run_testspec, [TestSpec1], Config),
+
Events = ct_test_support:get_events(ERPid, Config),
ct_test_support:log_events(TCName,