%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2005-2014. 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%
%%
%%
-module(httpd_block).

-include_lib("common_test/include/ct.hrl").

%% General testcases bodies called from httpd_SUITE
-export([block_disturbing_idle/4, block_non_disturbing_idle/4,
	 block_503/4, block_disturbing_active/4, 
	 block_non_disturbing_active/4, 
	 block_disturbing_active_timeout_not_released/4, 
	 block_disturbing_active_timeout_released/4, 
	 block_non_disturbing_active_timeout_not_released/4,
	 block_non_disturbing_active_timeout_released/4,
	 disturbing_blocker_dies/4,
	 non_disturbing_blocker_dies/4, restart_no_block/4,
	 restart_disturbing_block/4, restart_non_disturbing_block/4
	]).

%% Help functions 
-export([httpd_block/3, httpd_block/4, httpd_unblock/2, httpd_restart/2]).
-export([do_block_server/4, do_block_nd_server/5, do_long_poll/6]).

-define(report(Label, Content), 
	inets:report_event(20, Label, test_case, 
			   [{module, ?MODULE}, {line, ?LINE} | Content])).


%%-------------------------------------------------------------------------
%% Test cases starts here.
%%-------------------------------------------------------------------------
block_disturbing_idle(_Type, Port, Host, Node) ->
    io:format("block_disturbing_idle -> entry~n", []),
    validate_admin_state(Node, Host, Port, unblocked),
    block_server(Node, Host, Port),
    validate_admin_state(Node, Host, Port, blocked),
    unblock_server(Node, Host, Port),
    validate_admin_state(Node, Host, Port, unblocked),
    io:format("block_disturbing_idle -> done~n", []),
    ok.

%%--------------------------------------------------------------------
block_non_disturbing_idle(_Type, Port, Host, Node) ->
    unblocked = get_admin_state(Node, Host, Port),
    block_nd_server(Node, Host, Port),
    blocked = get_admin_state(Node, Host, Port),
    unblock_server(Node, Host, Port),
    unblocked = get_admin_state(Node, Host, Port),
    ok.

%%--------------------------------------------------------------------
block_503(Type, Port, Host, Node) ->
    Req = "GET / HTTP/1.0\r\ndummy-host.ericsson.se:\r\n\r\n",
    unblocked = get_admin_state(Node, Host, Port),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, Req, 
				  [{statuscode, 200},
				   {version, "HTTP/1.0"}]),
    ok = block_server(Node, Host, Port),
    blocked = get_admin_state(Node, Host, Port),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, Req,  
				  [{statuscode, 503},
				   {version, "HTTP/1.0"}]),
    ok = unblock_server(Node, Host, Port),
    unblocked = get_admin_state(Node, Host, Port),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, Req, 
				  [{statuscode, 200},
				   {version, "HTTP/1.0"}]).

%%--------------------------------------------------------------------
block_disturbing_active(Type, Port, Host, Node) ->
    process_flag(trap_exit, true),
    Pid = long_poll(Type, Host, Port, Node, 200, 60000),
    ct:sleep(15000),
    block_server(Node, Host, Port),
    await_suite_failed_process_exit(Pid, "poller", 60000,
				    connection_closed),
    blocked = get_admin_state(Node, Host, Port),
    process_flag(trap_exit, false),
    ok.

%%--------------------------------------------------------------------
block_non_disturbing_active(Type, Port, Host, Node) ->
    process_flag(trap_exit, true),
    Poller = long_poll(Type, Host, Port, Node, 200, 60000),
    ct:sleep(15000),
    ok = block_nd_server(Node, Host, Port),
    await_normal_process_exit(Poller, "poller", 60000),
    blocked = get_admin_state(Node, Host, Port),
    process_flag(trap_exit, false),
    ok.

%%--------------------------------------------------------------------
block_disturbing_active_timeout_not_released(Type, Port, Host, Node) ->
    process_flag(trap_exit, true),
    Poller = long_poll(Type, Host, Port, Node, 200, 60000),
    ct:sleep(15000),
    ok = httpd_block(undefined,  Port, disturbing, 50000),
    await_normal_process_exit(Poller, "poller", 30000),
    blocked = get_admin_state(Node, Host, Port),
    process_flag(trap_exit, false),
    ok.

%%--------------------------------------------------------------------
block_disturbing_active_timeout_released(Type, Port, Host, Node) ->
    process_flag(trap_exit, true),
    Poller = long_poll(Type, Host, Port, Node, 200, 40000),
    ct:sleep(5000),
    ok = httpd_block(undefined,  Port, disturbing, 10000),
    await_suite_failed_process_exit(Poller, "poller", 40000, 
					  connection_closed),
    blocked = get_admin_state(Node, Host, Port),
    process_flag(trap_exit, false),
    ok.
%%--------------------------------------------------------------------
block_non_disturbing_active_timeout_not_released(Type, Port, Host, Node) ->
    process_flag(trap_exit, true),
    Poller = long_poll(Type, Host, Port, Node, 200, 60000),
    test_server:sleep(5000),
    ok = block_nd_server(Node, Host, Port, 40000),
    await_normal_process_exit(Poller, "poller", 60000),
    blocked = get_admin_state(Node, Host, Port),
    process_flag(trap_exit, false),
    ok.

%%--------------------------------------------------------------------
block_non_disturbing_active_timeout_released(Type, Port, Host, Node) ->
    process_flag(trap_exit, true),
    Poller = long_poll(Type, Host, Port, Node, 200, 45000),
    ct:sleep(5000),
    Blocker = blocker_nd(Node, Host, Port ,10000, {error,timeout}),
    await_normal_process_exit(Blocker, "blocker", 15000),
    await_normal_process_exit(Poller, "poller", 50000),
    unblocked = get_admin_state(Node, Host, Port),
    process_flag(trap_exit, false),
    ok.
%%--------------------------------------------------------------------
disturbing_blocker_dies(Type, Port, Host, Node) ->
    process_flag(trap_exit, true),
    Poller = long_poll(Type, Host, Port, Node, 200, 60000),
    ct:sleep(5000),
    Blocker = blocker(Node, Host, Port, 10000),
    ct:sleep(5000),
    exit(Blocker,simulate_blocker_crash),
    await_normal_process_exit(Poller, "poller", 60000),
    unblocked = get_admin_state(Node, Host, Port),
    process_flag(trap_exit, false),
    ok.

%%--------------------------------------------------------------------
non_disturbing_blocker_dies(Type, Port, Host, Node) ->
    process_flag(trap_exit, true),
    Poller = long_poll(Type, Host, Port, Node, 200, 60000),
    ct:sleep(5000),  
    Blocker = blocker_nd(Node, Host, Port, 10000, ok),
    ct:sleep(5000),
    exit(Blocker, simulate_blocker_crash),
    await_normal_process_exit(Poller, "poller", 60000),
    unblocked = get_admin_state(Node, Host, Port),
    process_flag(trap_exit, false),
    ok.
%%--------------------------------------------------------------------
restart_no_block(_, Port, Host, Node) ->
    {error,_Reason} = restart_server(Node, Host, Port).

%%--------------------------------------------------------------------
restart_disturbing_block(_, Port, Host, Node) ->
    ?report("restart_disturbing_block - get_admin_state (unblocked)", []),
    unblocked = get_admin_state(Node, Host, Port),
    ?report("restart_disturbing_block - block_server", []),
    ok = block_server(Node, Host, Port),
    ?report("restart_disturbing_block - restart_server", []),
    ok = restart_server(Node, Host, Port),
    ?report("restart_disturbing_block - unblock_server", []),
    ok = unblock_server(Node, Host, Port),
    ?report("restart_disturbing_block - get_admin_state (unblocked)", []),
    unblocked = get_admin_state(Node, Host, Port).

%%--------------------------------------------------------------------
restart_non_disturbing_block(_, Port, Host, Node) ->
    ?report("restart_non_disturbing_block - get_admin_state (unblocked)", []),
    unblocked = get_admin_state(Node, Host, Port),
    ?report("restart_non_disturbing_block - block_nd_server", []),
    ok = block_nd_server(Node, Host, Port),
    ?report("restart_non_disturbing_block - restart_server", []),
    ok = restart_server(Node, Host, Port),
    ?report("restart_non_disturbing_block - unblock_server", []),
    ok = unblock_server(Node, Host, Port),
    ?report("restart_non_disturbing_block - get_admin_state (unblocked)", []),
    unblocked = get_admin_state(Node, Host, Port).

%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
blocker(Node, Host, Port, Timeout) ->
    spawn_link(?MODULE, do_block_server,[Node, Host, Port,Timeout]).

do_block_server(Node, Host, Port, Timeout) ->
    ok = block_server(Node, Host, Port, Timeout),
    exit(normal).

blocker_nd(Node, Host, Port, Timeout, Reply) ->
    spawn_link(?MODULE, do_block_nd_server,
	       [Node, Host, Port, Timeout, Reply]).

do_block_nd_server(Node, Host, Port, Timeout, Reply) ->
    Reply = block_nd_server(Node, Host, Port, Timeout),
    exit(normal).

restart_server(Node, _Host, Port) ->
    Addr = undefined, 
    rpc:call(Node, ?MODULE, httpd_restart, [Addr, Port]).


block_server(Node, _Host,  Port) ->
    io:format("block_server -> entry~n", []),    
    Addr = undefined, 
    rpc:call(Node, ?MODULE, httpd_block, [Addr, Port, disturbing]).


block_server(Node, _Host, Port, Timeout) ->
    Addr = undefined, 
    rpc:call(Node, ?MODULE, httpd_block, [Addr, Port, disturbing, Timeout]).


block_nd_server(Node, _Host, Port) ->
    Addr = undefined, 
    rpc:call(Node, ?MODULE, httpd_block, [Addr, Port, non_disturbing]).

block_nd_server(Node, _Host, Port, Timeout) ->
    Addr = undefined, 
    rpc:call(Node, ?MODULE, httpd_block, [Addr, Port, non_disturbing, Timeout]).

unblock_server(Node, _Host, Port) ->
    io:format("~p:~p:block_server -> entry~n", [node(),self()]),    
    Addr = undefined, 
    rpc:call(Node, ?MODULE, httpd_unblock, [Addr, Port]).


httpd_block(Addr, Port, Mode) ->
    io:format("~p:~p:httpd_block -> entry~n", [node(),self()]), 
    Name = make_name(Addr, Port),
    case whereis(Name) of
	Pid when is_pid(Pid) ->
	    httpd_manager:block(Pid, Mode);
	_ ->
	    {error, not_started}
    end.
    
httpd_block(Addr, Port, Mode, Timeout) ->
    Name = make_name(Addr, Port),
    case whereis(Name) of
	Pid when is_pid(Pid) ->
	    httpd_manager:block(Pid, Mode, Timeout);
	_ ->
	    {error, not_started}
    end.
    
httpd_unblock(Addr, Port) ->
    io:format("~p:~p:httpd_unblock -> entry~n", [node(),self()]), 
    Name = make_name(Addr, Port),
    case whereis(Name) of
	Pid when is_pid(Pid) ->
	    httpd_manager:unblock(Pid);
	_ ->
	    {error, not_started}
    end.
    
httpd_restart(Addr, Port) ->
    Name = make_name(Addr, Port),
    case whereis(Name) of
	Pid when is_pid(Pid) ->
	    httpd_manager:reload(Pid, undefined);
	_ ->
	    {error, not_started}
    end.
    
make_name(Addr, Port) ->
    httpd_util:make_name("httpd", Addr, Port).

get_admin_state(_, _Host, Port) ->
    Name = make_name(undefined, Port),
    {status, _, _, StatusInfo} = sys:get_status(whereis(Name)),
    [_, _,_, _, Prop] = StatusInfo,
    State = state(Prop),
    element(6, State).

validate_admin_state(Node, Host, Port, Expect) ->
    io:format("try validating server admin state: ~p~n", [Expect]),
    case get_admin_state(Node, Host, Port) of
	Expect ->
	    ok;
	Unexpected ->
	    io:format("failed validating server admin state: ~p~n", 
		      [Unexpected]),
	    exit({unexpected_admin_state, Unexpected, Expect})
    end.


await_normal_process_exit(Pid, Name, Timeout) ->
    receive
	{'EXIT', Pid, normal} ->
	    ok;
	{'EXIT', Pid, Reason} ->
	    Err = 
		lists:flatten(
		  io_lib:format("expected normal exit, "
				"unexpected exit of ~s process: ~p",
				[Name, Reason])),
	    ct:fail(Err)
    after Timeout ->
	    ct:fail("timeout while waiting for " ++ Name)
    end.


await_suite_failed_process_exit(Pid, Name, Timeout, Why) ->
    receive 
	{'EXIT', Pid, {test_failed, Why}} ->
	    ok;
	{'EXIT', Pid, Reason} ->
	    Err = 
		lists:flatten(
		  io_lib:format("expected connection_closed, "
				"unexpected exit of ~s process: ~p",
				[Name, Reason])),
	    ct:fail(Err)
    after Timeout ->
	    ct:fail("timeout while waiting for " ++ Name)
    end.
	  
long_poll(Type, Host, Port, Node, StatusCode, Timeout) ->
    spawn_link(?MODULE, do_long_poll, [Type, Host, Port, Node, 
				       StatusCode, Timeout]).

do_long_poll(Type, Host, Port, Node, StatusCode, Timeout) ->
    Mod  = "httpd_example",
    Func = "delay",
    Req  = lists:flatten(io_lib:format("GET /eval?" ++ Mod ++ ":" ++ Func ++ 
				       "(~p) HTTP/1.0\r\n\r\n",[30000])),
    case httpd_test_lib:verify_request(Type, Host, Port, Node, Req, 
			      [{statuscode, StatusCode},
			       {version, "HTTP/1.0"}], Timeout) of
	ok ->
	    exit(normal);
	Reason ->
	    exit({test_failed, Reason})
    end.


state([{data,[{"State", State}]} | _]) ->
    State;
state([{data,[{"StateData", State}]} | _]) ->
    State;
state([_ | Rest]) ->
    state(Rest).