From bc8f2b83132c30f88699b555cc7ffb79d8018ac2 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Wed, 11 Jul 2012 16:22:09 +0200 Subject: [common_test] Move ct_netconfc_SUITE into datadir and run with ct_test_support --- lib/common_test/test/Makefile | 2 - lib/common_test/test/common_test.spec | 1 - lib/common_test/test/ct_netconfc.cfg | 6 - lib/common_test/test/ct_netconfc_SUITE.erl | 1164 ++------------------ .../test/ct_netconfc_SUITE_data/netconfc1.cfg | 6 + .../ct_netconfc_SUITE_data/netconfc1_SUITE.erl | 1130 +++++++++++++++++++ lib/common_test/test/ct_netconfc_SUITE_data/ns.erl | 506 +++++++++ lib/common_test/test/ct_test_netconf_server.erl | 506 --------- 8 files changed, 1721 insertions(+), 1600 deletions(-) delete mode 100644 lib/common_test/test/ct_netconfc.cfg create mode 100644 lib/common_test/test/ct_netconfc_SUITE_data/netconfc1.cfg create mode 100644 lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl create mode 100644 lib/common_test/test/ct_netconfc_SUITE_data/ns.erl delete mode 100644 lib/common_test/test/ct_test_netconf_server.erl diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile index c970593053..4d85b84b5b 100644 --- a/lib/common_test/test/Makefile +++ b/lib/common_test/test/Makefile @@ -28,7 +28,6 @@ MODULES= \ ct_test_support \ ct_test_support_eh \ ct_userconfig_callback \ - ct_test_netconf_server \ ct_smoke_test_SUITE \ ct_priv_dir_SUITE \ ct_event_handler_SUITE \ @@ -102,7 +101,6 @@ release_tests_spec: $(INSTALL_DIR) "$(RELSYSDIR)" $(INSTALL_DATA) $(ERL_FILES) $(COVERFILE) "$(RELSYSDIR)" $(INSTALL_DATA) common_test.spec "$(RELSYSDIR)" - $(INSTALL_DATA) ct_netconfc.cfg "$(RELSYSDIR)" chmod -R u+w "$(RELSYSDIR)" @tar cf - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -) diff --git a/lib/common_test/test/common_test.spec b/lib/common_test/test/common_test.spec index 04f3c64442..8bec66d6f2 100644 --- a/lib/common_test/test/common_test.spec +++ b/lib/common_test/test/common_test.spec @@ -1,2 +1 @@ {suites,"../common_test_test",all}. -{config,"../common_test_test/ct_netconfc.cfg"}. \ No newline at end of file diff --git a/lib/common_test/test/ct_netconfc.cfg b/lib/common_test/test/ct_netconfc.cfg deleted file mode 100644 index 6466571623..0000000000 --- a/lib/common_test/test/ct_netconfc.cfg +++ /dev/null @@ -1,6 +0,0 @@ -%% -*- 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.erl b/lib/common_test/test/ct_netconfc_SUITE.erl index 317ed3a3c5..e6e8d5b09c 100644 --- a/lib/common_test/test/ct_netconfc_SUITE.erl +++ b/lib/common_test/test/ct_netconfc_SUITE.erl @@ -1,7 +1,7 @@ -%%-------------------------------------------------------------------- +%% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2012. All Rights Reserved. +%% 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 @@ -16,1115 +16,109 @@ %% %% %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 -%%---------------------------------------------------------------------- -%%---------------------------------------------------------------------- + +%%%------------------------------------------------------------------- +%%% File: ct_netconfc_SUITE +%%% +%%% Description: +%%% Test ct_netconfc module +%%% +%%%------------------------------------------------------------------- -module(ct_netconfc_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,ct_test_netconf_server). --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. +-include_lib("common_test/include/ct.hrl"). +-include_lib("common_test/include/ct_event.hrl"). -init_per_testcase(_Case, Config) -> - ets:delete_all_objects(ns_tab), - Dog = test_server:timetrap(?default_timeout), - [{watchdog, Dog}|Config]. +-define(eh, ct_test_support_eh). -end_per_testcase(_Case, Config) -> - Dog=?config(watchdog, Config), - test_server:timetrap_cancel(Dog), - ok. +%%-------------------------------------------------------------------- +%% 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) -> - 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. + Config1 = ct_test_support:init_per_suite(Config), + Config1. 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. + ct_test_support:end_per_suite(Config). -not_proper_xml(Config) -> - DataDir = ?config(data_dir,Config), - {ok,Client} = open_success(DataDir), - NS = list_to_binary(?NETCONF_NAMESPACE), - NotProper = <<"">>, - ?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. +init_per_testcase(TestCase, Config) -> + ct_test_support:init_per_testcase(TestCase, Config). -prefixed_namespace(Config) -> - DataDir = ?config(data_dir,Config), - {ok,Client} = open_success(DataDir), - NS = list_to_binary(?NETCONF_NAMESPACE), +end_per_testcase(TestCase, Config) -> + ct_test_support:end_per_testcase(TestCase, Config). - %% 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 = <<"", - "myserver" - "">>, - ?NS:expect_reply('get',Data), - {ok,[{'my:server',[{'xmlns:my',"myns"}], - [{'my:name',[{'my:lang',"en"}],["myserver"]}]}]} = - ct_netconfc:get(Client,{server,[{xmlns,"myns"}],[]}), +suite() -> [{ct_hooks,[ts_install_cth]}]. - Ok = <<"">>, - ?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 = - <<"\n", - Xml/binary,"\n",?END_TAG/binary>>, - - %% Split the data in some chunks - PartLength = size(Netconf) div 3, - <> = 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 = - <<"\n", - Xml/binary,"\n",?END_TAG/binary>>, - - %% Split the data in some chunks - PartLength = size(Netconf) div 3, - <> = 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 = - <<"\n", - Xml/binary,"\n",?END_TAG/binary>>, - - %% Split the data in some chunks - PartLength = size(Netconf) div 3, - <> = 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"). +all() -> + [ + default + ]. -br() -> - test_server:break(""). +%%-------------------------------------------------------------------- +%% TEST CASES +%%-------------------------------------------------------------------- %%%----------------------------------------------------------------- -%% Open a netconf session which is not specified in a config file -open_success(Dir) -> - open_success(Dir,[]). +%%% +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), -%% 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); + ok = execute(default, Opts, ERPid, Config). -%% 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). - - +%%% HELP FUNCTIONS %%%----------------------------------------------------------------- -%%% 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), +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}. - SystemTmpDir = ?config(data_dir, Config), - filelib:ensure_dir(SystemTmpDir), - file:make_dir(SystemTmpDir), +execute(Name, Opts, ERPid, Config) -> + ok = ct_test_support:run(Opts, Config), + Events = ct_test_support:get_events(ERPid, Config), - DSAFile = filename:join(SystemTmpDir, "ssh_host_dsa_key.pub"), - file:delete(DSAFile), + ct_test_support:log_events(Name, + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), - DSAPrivateFile = filename:join(SystemTmpDir, "ssh_host_dsa_key"), - file:delete(DSAPrivateFile), + TestEvents = events_to_check(Name,Config), + ct_test_support:verify_events(TestEvents, Events, Config). - KHFile = filename:join(SystemTmpDir, "known_hosts"), - file:delete(KHFile), +reformat(Events, EH) -> + ct_test_support:reformat(Events, EH). - 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 = <> = 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 %%%----------------------------------------------------------------- +%%% 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 = <<"">>, + ?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 = <<"", + "myserver" + "">>, + ?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 = <<"">>, + ?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 = + <<"\n", + Xml/binary,"\n",?END_TAG/binary>>, + + %% Split the data in some chunks + PartLength = size(Netconf) div 3, + <> = 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 = + <<"\n", + Xml/binary,"\n",?END_TAG/binary>>, + + %% Split the data in some chunks + PartLength = size(Netconf) div 3, + <> = 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 = + <<"\n", + Xml/binary,"\n",?END_TAG/binary>>, + + %% Split the data in some chunks + PartLength = size(Netconf) div 3, + <> = 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 = <> = 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 = <>, + 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) -> + <<"\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, + <<"\n", + Content/binary,"\n">>; +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)), + <<"",SessionId/binary,"\n">>. + +capabilities(undefined) -> + CapsXml = list_to_binary([["",C,"\n"] + || C <- ?CAPABILITIES]), + <<"\n",CapsXml/binary,"\n">>; +capabilities({base,Vsn}) -> + CapsXml = list_to_binary([["",C,"\n"] + || C <- ?CAPABILITIES_VSN(Vsn)]), + <<"\n",CapsXml/binary,"\n">>; +capabilities(no_base) -> + [_|Caps] = ?CAPABILITIES, + CapsXml = list_to_binary([["",C,"\n"] || C <- Caps]), + <<"\n",CapsXml/binary,"\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(<<"\n",CapsXml/binary, + SessionIdXml/binary,"">>); +make_msg(ok) -> + xml(rpc_reply("")); +make_msg({data,Data}) -> + xml(rpc_reply(from_simple({data,Data}))); +make_msg(event) -> + xml(<<"" + "2012-06-14T14:50:54+02:00" + "" + "major" + "Something terrible happened" + "" + "">>); +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_netconf_server.erl b/lib/common_test/test/ct_test_netconf_server.erl deleted file mode 100644 index 11855ce959..0000000000 --- a/lib/common_test/test/ct_test_netconf_server.erl +++ /dev/null @@ -1,506 +0,0 @@ -%%-------------------------------------------------------------------- -%% %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(ct_test_netconf_server). - -%-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 = <>, - 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) -> - <<"\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, - <<"\n", - Content/binary,"\n">>; -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)), - <<"",SessionId/binary,"\n">>. - -capabilities(undefined) -> - CapsXml = list_to_binary([["",C,"\n"] - || C <- ?CAPABILITIES]), - <<"\n",CapsXml/binary,"\n">>; -capabilities({base,Vsn}) -> - CapsXml = list_to_binary([["",C,"\n"] - || C <- ?CAPABILITIES_VSN(Vsn)]), - <<"\n",CapsXml/binary,"\n">>; -capabilities(no_base) -> - [_|Caps] = ?CAPABILITIES, - CapsXml = list_to_binary([["",C,"\n"] || C <- Caps]), - <<"\n",CapsXml/binary,"\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(<<"\n",CapsXml/binary, - SessionIdXml/binary,"">>); -make_msg(ok) -> - xml(rpc_reply("")); -make_msg({data,Data}) -> - xml(rpc_reply(from_simple({data,Data}))); -make_msg(event) -> - xml(<<"" - "2012-06-14T14:50:54+02:00" - "" - "major" - "Something terrible happened" - "" - "">>); -make_msg(Xml) when is_binary(Xml) -> - xml(Xml); -make_msg(Simple) when is_tuple(Simple) -> - xml(from_simple(Simple)). -- cgit v1.2.3