Permalink
Browse files

add experimental 'shell' plugin (to run shell cmd on client nodes)

  • Loading branch information...
1 parent b6fac57 commit e471914d05ede07dfba33d9904cdfc5ce750930c @nniclausse nniclausse committed Aug 18, 2010
Showing with 284 additions and 6 deletions.
  1. +47 −0 include/ts_shell.hrl
  2. +2 −2 src/tsung/ts_client.erl
  3. +6 −0 src/tsung/ts_search.erl
  4. +152 −0 src/tsung/ts_shell.erl
  5. +67 −0 src/tsung_controller/ts_config_shell.erl
  6. +10 −4 tsung-1.0.dtd
View
@@ -0,0 +1,47 @@
+%%%
+%%% Copyright 2010 © INRIA
+%%%
+%%% Author : Nicolas Niclausse <nniclaus@sophia.inria.fr>
+%%% Created: 13 january 2010 by Nicolas Niclausse <nniclaus@sophia.inria.fr>
+%%%
+%%% This program is free software; you can redistribute it and/or modify
+%%% it under the terms of the GNU General Public License as published by
+%%% the Free Software Foundation; either version 2 of the License, or
+%%% (at your option) any later version.
+%%%
+%%% This program is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+%%% GNU General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License
+%%% along with this program; if not, write to the Free Software
+%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+%%%
+%%% In addition, as a special exception, you have the permission to
+%%% link the code of this program with any library released under
+%%% the EPL license and distribute linked combinations including
+%%% the two.
+
+
+-vc('$Id$ ').
+-author('nicolas.niclausse@niclux.org').
+
+%% use by the client to create the request
+
+-record(shell_dyndata,
+ {
+ fixme
+ }
+ ).
+
+-record(shell, {
+ command,
+ args
+ }).
+
+-record(shell_sess, {
+ fixme
+ }).
+
+
View
@@ -779,7 +779,7 @@ reconnect(Socket, _Server, _Port, _Protocol, _IP) ->
%%----------------------------------------------------------------------
%% Func: send/5
%% Purpose: wrapper function for send
-%% Return: ok | {error, Reason}
+%% Return: ok | {error, Reason}
%%----------------------------------------------------------------------
send(gen_tcp,Socket,Message,_,_) -> gen_tcp:send(Socket,Message);
send(ssl,Socket,Message,_,_) -> ssl:send(Socket,Message);
@@ -953,7 +953,7 @@ set_new_buffer(#ts_request{match=[], dynvar_specs=[]},_,_) ->
<< >>;
set_new_buffer(_, Buffer,closed) ->
Buffer;
-set_new_buffer(_, OldBuffer, Data) when is_binary(OldBuffer)->
+set_new_buffer(_, OldBuffer, Data) when is_binary(OldBuffer) andalso is_binary(Data)->
?Debug("Bufferize response~n"),
<< OldBuffer/binary, Data/binary >>;
set_new_buffer(_, _, Data) -> % don't need buffer for non binary responses (erlang fun case)
View
@@ -249,6 +249,12 @@ parse_dynvar([], _Data) -> ts_dynvars:new();
parse_dynvar(DynVarSpecs, Data) when is_binary(Data) ->
?DebugF("Parsing Dyn Variable (specs=~p); data is ~p~n",[DynVarSpecs,Data]),
parse_dynvar(DynVarSpecs,Data, undefined,undefined,[]);
+parse_dynvar(DynVarSpecs, {_,_,_,Data}) when is_binary(Data) ->
+ ?DebugF("Parsing Dyn Variable (specs=~p); data is ~p~n",[DynVarSpecs,Data]),
+ parse_dynvar(DynVarSpecs,Data, undefined,undefined,[]);
+parse_dynvar(DynVarSpecs, {_,_,_,Data}) when is_list(Data) ->
+ ?DebugF("Parsing Dyn Variable (specs=~p); data is ~p~n",[DynVarSpecs,Data]),
+ parse_dynvar(DynVarSpecs,list_to_binary(Data), undefined,undefined,[]);
parse_dynvar(DynVarSpecs, _Data) ->
?LOGF("Error while Parsing dyn Variable(~p)~n",[DynVarSpecs],?WARN),
ts_dynvars:new().
View
@@ -0,0 +1,152 @@
+%%%
+%%% Copyright 2009 © INRIA
+%%%
+%%% Author : Nicolas Niclausse <nniclaus@sophia.inria.fr>
+%%% Created: 20 août 2009 by Nicolas Niclausse <nniclaus@sophia.inria.fr>
+%%%
+%%% This program is free software; you can redistribute it and/or modify
+%%% it under the terms of the GNU General Public License as published by
+%%% the Free Software Foundation; either version 2 of the License, or
+%%% (at your option) any later version.
+%%%
+%%% This program is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+%%% GNU General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License
+%%% along with this program; if not, write to the Free Software
+%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+%%%
+
+-module(ts_shell).
+-vc('$Id: ts_erlang.erl,v 0.0 2009/08/20 16:31:58 nniclaus Exp $ ').
+-author('nniclaus@sophia.inria.fr').
+
+-include("ts_profile.hrl").
+-include("ts_shell.hrl").
+-include_lib("kernel/include/file.hrl").
+
+-export([init_dynparams/0,
+ add_dynparams/4,
+ get_message/1,
+ session_defaults/0,
+ parse/2,
+ parse_bidi/2,
+ parse_config/2,
+ new_session/0]).
+
+
+%%====================================================================
+%% Data Types
+%%====================================================================
+
+%% @type dyndata() = #dyndata{proto=ProtoData::term(),dynvars=list()}.
+%% Dynamic data structure
+%% @end
+
+%% @type server() = {Host::tuple(),Port::integer(),Protocol::atom()}.
+%% Host/Port/Protocol tuple
+%% @end
+
+%% @type param() = {dyndata(), server()}.
+%% Dynamic data structure
+%% @end
+
+%% @type hostdata() = {Host::tuple(),Port::integer()}.
+%% Host/Port pair
+%% @end
+
+%% @type client_data() = binary() | closed.
+%% Data passed to a protocol implementation is either a binary or the
+%% atom closed indicating that the server closed the tcp connection.
+%% @end
+
+%%====================================================================
+%% API
+%%====================================================================
+
+parse_config(El,Config) ->
+ ts_config_shell:parse_config(El, Config).
+
+
+%% @spec session_defaults() -> {ok, Persistent} | {ok, Persistent, Bidi}
+%% Persistent = bool()
+%% Bidi = bool()
+%% @doc Default parameters for sessions of this protocol. Persistent
+%% is true if connections are preserved after the underlying tcp
+%% connection closes. Bidi should be true for bidirectional protocols
+%% where the protocol module needs to reply to data sent from the
+%% server. @end
+session_defaults() ->
+ {ok, true}. % not relevant for erlang type (?).
+
+%% @spec new_session() -> State::term()
+%% @doc Initialises the state for a new protocol session.
+%% @end
+new_session() ->
+ #shell{}.
+
+%% @spec init_dynparams() -> dyndata()
+%% @doc Creates a new record/term for storing dynamic request data.
+%% @end
+init_dynparams() ->
+ #dyndata{proto=#shell_dyndata{}}.
+
+%% @spec add_dynparams(Subst, dyndata(), param(), hostdata()) -> {dyndata(), server()} | dyndata()
+%% Subst = term()
+%% @doc Updates the dynamic request data structure created by
+%% {@link ts_protocol:init_dynparams/0. init_dynparams/0}.
+%% @end
+add_dynparams(false, DynData, Param, HostData) ->
+ add_dynparams(DynData#dyndata.proto, Param, HostData);
+add_dynparams(true, DynData, Param, HostData) ->
+ NewParam = subst(Param, DynData#dyndata.dynvars),
+ add_dynparams(DynData#dyndata.proto,NewParam, HostData).
+
+add_dynparams(#shell_dyndata{}, Param, _HostData) ->
+ Param.
+
+%%----------------------------------------------------------------------
+%% @spec subst(Req, term())
+%% Purpose: Replace on the fly dynamic element of the request.
+%% Returns: record()
+%%----------------------------------------------------------------------
+subst(Req=#shell{command=Cmd,args=Args}, DynVars) ->
+ Req#shell{command=ts_search:subst(Cmd,DynVars),args=ts_search:subst(Args,DynVars)}.
+
+%% @spec parse(Data::client_data(), State) -> {NewState, Opts, Close}
+%% State = #state_rcv{}
+%% Opts = proplist()
+%% Close = bool()
+%% @doc
+%% Opts is a list of inet:setopts socket options. Don't change the
+%% active/passive mode here as tsung will set {active,once} before
+%% your options.
+%% Setting Close to true will cause tsung to close the connection to
+%% the server.
+%% @end
+parse({os, cmd, _Args, Res},State) when is_list(Res)->
+ {State#state_rcv{ack_done=true,datasize=length(Res)}, [], false};
+parse({os, cmd, _Args, Res},State) ->
+ {State#state_rcv{ack_done=true,datasize=size(term_to_binary(Res))}, [], false}.
+
+%% @spec parse_bidi(Data, State) -> {nodata, NewState} | {Data, NewState}
+%% Data = client_data()
+%% NewState = term()
+%% State = term()
+%% @doc Parse a block of data from the server. No reply will be sent
+%% if the return value is nodata, otherwise the Data binary will be
+%% sent back to the server immediately.
+%% @end
+parse_bidi(_Data, _State) ->
+ erlang:error(dummy_implementation).
+
+%% @spec get_message(param()) -> Message::binary()|tuple()
+%% @doc Creates a new message to send to the connected server.
+%% @end
+get_message(#shell{command=Cmd, args=Args}) ->
+ Msg=Cmd++" "++Args ,
+ {os, cmd, [Msg], length(Msg) }.
+
+
@@ -0,0 +1,67 @@
+%%%
+%%% Copyright 2010 © INRIA
+%%%
+%%% Author : Nicolas Niclausse <nniclaus@sophia.inria.fr>
+%%% Created: 18 août 2010 by Nicolas Niclausse <nniclaus@sophia.inria.fr>
+%%%
+%%% This program is free software; you can redistribute it and/or modify
+%%% it under the terms of the GNU General Public License as published by
+%%% the Free Software Foundation; either version 2 of the License, or
+%%% (at your option) any later version.
+%%%
+%%% This program is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+%%% GNU General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License
+%%% along with this program; if not, write to the Free Software
+%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+%%%
+
+-module(ts_config_shell).
+-vc('$Id$ ').
+-author('nicolas.niclausse@sophia.inria.fr').
+
+-export([parse_config/2]).
+
+-include("ts_profile.hrl").
+-include("ts_http.hrl").
+-include("ts_config.hrl").
+
+-include("xmerl.hrl").
+
+-include("ts_shell.hrl").
+
+%% @spec parse_config(#xmlElement{}, Config::term()) -> NewConfig::term()
+%% @doc Parses a tsung.xml configuration file xml element for this
+%% protocol and updates the Config term.
+%% @end
+parse_config(Element = #xmlElement{name=dyn_variable}, Conf = #config{}) ->
+ ts_config:parse(Element,Conf);
+parse_config(Element = #xmlElement{name=shell},
+ Config=#config{curid = Id, session_tab = Tab,
+ sessions = [CurS | _], dynvar=DynVar,
+ subst = SubstFlag, match=MatchRegExp}) ->
+
+ Cmd = ts_config:getAttr(string,Element#xmlElement.attributes, cmd),
+ Args = ts_config:getAttr(string,Element#xmlElement.attributes, args, ""),
+
+ Request = #shell{command=Cmd,args=Args},
+ Msg= #ts_request{ack = parse,
+ endpage = true,
+ dynvar_specs = DynVar,
+ subst = SubstFlag,
+ match = MatchRegExp,
+ param = Request},
+
+ ts_config:mark_prev_req(Id-1, Tab, CurS),
+ ets:insert(Tab,{{CurS#session.id, Id},Msg}),
+ lists:foldl( fun(A,B)->ts_config:parse(A,B) end, Config#config{dynvar=[]},
+ Element#xmlElement.content);
+%% Parsing other elements
+parse_config(Element = #xmlElement{}, Conf = #config{}) ->
+ ts_config:parse(Element,Conf);
+%% Parsing non #xmlElement elements
+parse_config(_, Conf = #config{}) ->
+ Conf.
View
@@ -103,19 +103,19 @@ repeat | if | change_type )*>
bidi CDATA #IMPLIED
persistent (true | false) #IMPLIED
probability NMTOKEN #REQUIRED
- type (ts_jabber | ts_http | ts_raw | ts_pgsql | ts_ldap | ts_webdav |ts_mysql| ts_fs ) #REQUIRED>
+ type (ts_jabber | ts_http | ts_raw | ts_pgsql | ts_ldap | ts_webdav |ts_mysql| ts_fs |ts_shell) #REQUIRED>
<!ELEMENT change_type EMPTY>
<!ATTLIST change_type
- new_type (ts_jabber | ts_http | ts_raw | ts_pgsql | ts_ldap | ts_webdav |ts_mysql| ts_fs ) #REQUIRED
+ new_type (ts_jabber | ts_http | ts_raw | ts_pgsql | ts_ldap | ts_webdav |ts_mysql| ts_fs | ts_shell) #REQUIRED
host NMTOKEN #REQUIRED
port NMTOKEN #REQUIRED
server_type NMTOKEN #REQUIRED
store ( true | false ) "false"
restore ( true | false ) "false"
>
-<!ELEMENT request ( match*, dyn_variable*, ( http | jabber | raw | pgsql | ldap | mysql |fs ) )>
+<!ELEMENT request ( match*, dyn_variable*, ( http | jabber | raw | pgsql | ldap | mysql |fs | shell ) )>
<!ATTLIST request
subst (true|false) "false"
>
@@ -205,7 +205,7 @@ repeat | if | change_type )*>
node CDATA #IMPLIED
node_type CDATA #IMPLIED >
-<!ELEMENT fs (#PCDATA) >
+<!ELEMENT fs EMPTY >
<!ATTLIST fs
cmd (read|write|open|delete|stats|copy|read_chunk|write_chunk|close) "write"
path CDATA #IMPLIED
@@ -215,6 +215,12 @@ repeat | if | change_type )*>
dest CDATA #IMPLIED
>
+<!ELEMENT shell EMPTY >
+<!ATTLIST shell
+ cmd CDATA #REQUIRED
+ args CDATA ""
+>
+
<!ELEMENT pgsql (#PCDATA) >
<!ATTLIST pgsql
password CDATA #IMPLIED

0 comments on commit e471914

Please sign in to comment.