2016-06-15 20:43:07 +00:00
|
|
|
%% -------------------------------------------------------------------
|
|
|
|
%%
|
|
|
|
%% Copyright (c) 2016 Basho Technologies, Inc.
|
|
|
|
%%
|
|
|
|
%% This file is provided to you under the Apache License,
|
|
|
|
%% Version 2.0 (the "License"); you may not use this file
|
|
|
|
%% except in compliance with the License. You may obtain
|
|
|
|
%% a copy of the License at
|
|
|
|
%%
|
|
|
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
%%
|
|
|
|
%% Unless required by applicable law or agreed to in writing,
|
|
|
|
%% software distributed under the License is distributed on an
|
|
|
|
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
|
|
%% KIND, either express or implied. See the License for the
|
|
|
|
%% specific language governing permissions and limitations
|
|
|
|
%% under the License.
|
|
|
|
%%
|
|
|
|
%% -------------------------------------------------------------------
|
2016-07-05 14:45:50 +00:00
|
|
|
%%
|
2016-07-15 01:11:34 +00:00
|
|
|
%% @doc Facilities to use in CT-enabled upgrade/downgrade tests.
|
2016-11-06 04:20:25 +00:00
|
|
|
%%
|
|
|
|
%% The purpose if this module is to enable CT test module writers to
|
|
|
|
%% run a cluster through a succession of upgrade/downgrades
|
|
|
|
%% (scenarios), each consisting of these three stages:
|
|
|
|
%%
|
|
|
|
%% (1) table creation, via a CREATE TABLE query,
|
|
|
|
%% (2) inserting data, via riakc_ts:put,
|
|
|
|
%% (3) querying data back, via one or more SELECT queries.
|
|
|
|
%%
|
|
|
|
%% Scenarios (see #scenario{} in ts_updown_util.hrl) define which
|
|
|
|
%% version (previous or current, whichever they are in your
|
|
|
|
%% ~/.riak_test.config), which nodes, if any, to transitions before
|
|
|
|
%% CREATE and, separately, before SELECT stages, and which SELECTs to
|
|
|
|
%% run. Each of the stages or queries can be expected to fail.
|
|
|
|
%%
|
|
|
|
%% Queries to nodes can be made using a client of matching version or
|
|
|
|
%% the current client (controlled by 'use_previous_client' property in
|
|
|
|
%% Config).
|
|
|
|
%%
|
|
|
|
%% The contents of riak.conf can be manipulated after a node
|
|
|
|
%% transition using hooks (#scenario.convert_config_to_previous,
|
|
|
|
%% .convert_config_to_current}).
|
|
|
|
%%
|
|
|
|
%% If an upgrade/downgrade involves a change in capabilities, these
|
|
|
|
%% would need to be listed in #scenario.ensure_full_caps and
|
|
|
|
%% .ensure_degraded_caps.
|
|
|
|
|
2016-07-05 14:45:50 +00:00
|
|
|
|
2016-06-15 20:43:07 +00:00
|
|
|
-module(ts_updown_util).
|
|
|
|
|
|
|
|
-export([
|
2016-10-29 16:13:47 +00:00
|
|
|
convert_riak_conf_to_previous/1,
|
2016-07-05 14:45:50 +00:00
|
|
|
maybe_shutdown_client_node/1,
|
2016-10-29 16:13:47 +00:00
|
|
|
setup/1,
|
2016-07-15 13:37:35 +00:00
|
|
|
run_scenarios/2,
|
|
|
|
run_scenario/2
|
2016-06-15 20:43:07 +00:00
|
|
|
]).
|
|
|
|
|
2016-07-15 01:11:34 +00:00
|
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
|
|
-include("ts_updown_util.hrl").
|
2016-07-05 14:45:50 +00:00
|
|
|
|
2016-07-15 01:11:34 +00:00
|
|
|
-type versioned_cluster() :: [{node(), version()}].
|
2016-11-06 04:20:25 +00:00
|
|
|
|
2016-07-15 01:11:34 +00:00
|
|
|
%% preparations
|
2016-11-06 04:20:25 +00:00
|
|
|
%% ------------
|
2016-07-02 20:52:27 +00:00
|
|
|
|
2016-07-21 14:02:48 +00:00
|
|
|
setup(Config) ->
|
2016-07-05 14:45:50 +00:00
|
|
|
lists:foldl(
|
|
|
|
fun(Fun, Cfg) -> Fun(Cfg) end,
|
|
|
|
Config,
|
2016-07-21 14:02:48 +00:00
|
|
|
[fun setup_cluster/1,
|
|
|
|
fun setup_client/1]).
|
2016-07-05 14:45:50 +00:00
|
|
|
|
2016-07-21 14:02:48 +00:00
|
|
|
setup_cluster(Config) ->
|
2016-07-05 14:45:50 +00:00
|
|
|
%% build the starting (old = upgraded, current) cluster
|
|
|
|
Nodes = rt:build_cluster(
|
|
|
|
lists:duplicate(3, current)),
|
|
|
|
Config ++
|
|
|
|
[
|
2016-07-15 01:11:34 +00:00
|
|
|
{nodes, Nodes}
|
2016-07-05 14:45:50 +00:00
|
|
|
].
|
|
|
|
|
2016-07-21 14:02:48 +00:00
|
|
|
setup_client(Config) ->
|
|
|
|
%% By default, we use the client in the 'current' version for all
|
|
|
|
%% queries. Add `{use_previous_client, true}` to the Config arg
|
|
|
|
%% when calling it from your_module:init_per_suite to change that
|
|
|
|
%% to selectively use old code to connect to downgraded nodes.
|
2016-07-05 14:45:50 +00:00
|
|
|
UsePreviousClient = proplists:get_value(use_previous_client, Config, false),
|
|
|
|
PrevClientNode = maybe_setup_slave_for_previous_client(UsePreviousClient),
|
|
|
|
Config ++
|
|
|
|
[
|
|
|
|
{previous_client_node, PrevClientNode}
|
|
|
|
].
|
|
|
|
|
|
|
|
maybe_setup_slave_for_previous_client(true) ->
|
|
|
|
%% set up a separate, slave node for the 'previous' version
|
|
|
|
%% client, to talk to downgraded nodes
|
|
|
|
_ = application:start(crypto),
|
|
|
|
Suffix = [crypto:rand_uniform($a, $z) || _ <- [x,x,x,i,x,x,x,i]],
|
|
|
|
PrevRiakcNode = list_to_atom("alsoran_"++Suffix++"@127.0.0.1"),
|
2016-10-29 16:17:25 +00:00
|
|
|
ct:pal("Setting up ~p for old client", [PrevRiakcNode]),
|
2016-07-05 14:45:50 +00:00
|
|
|
rt_client:set_up_slave_for_previous_client(PrevRiakcNode);
|
|
|
|
maybe_setup_slave_for_previous_client(_) ->
|
|
|
|
node().
|
|
|
|
|
|
|
|
maybe_shutdown_client_node(Config) ->
|
|
|
|
case ?CFG(previous_client_node, Config) of
|
|
|
|
ThisNode when ThisNode == node() ->
|
|
|
|
ok;
|
|
|
|
SlaveNode ->
|
|
|
|
rt_slave:stop(SlaveNode)
|
|
|
|
end.
|
2016-06-15 20:43:07 +00:00
|
|
|
|
|
|
|
|
2016-07-15 01:11:34 +00:00
|
|
|
%% scenarios
|
2016-11-06 04:20:25 +00:00
|
|
|
%% ---------
|
2016-07-15 01:11:34 +00:00
|
|
|
|
|
|
|
-spec run_scenarios(config(), [#scenario{}]) -> [#failure_report{}].
|
|
|
|
run_scenarios(Config, Scenarios) ->
|
2016-07-15 13:37:35 +00:00
|
|
|
Failures =
|
2016-07-15 01:11:34 +00:00
|
|
|
lists:foldl(
|
2016-07-15 13:37:35 +00:00
|
|
|
fun(Scenario, FF) ->
|
|
|
|
run_scenario(Config, Scenario) ++ FF
|
2016-07-15 01:11:34 +00:00
|
|
|
end,
|
2016-07-15 13:37:35 +00:00
|
|
|
[], Scenarios),
|
2016-07-15 01:11:34 +00:00
|
|
|
|
|
|
|
Failures.
|
|
|
|
|
2016-07-15 13:37:35 +00:00
|
|
|
-spec run_scenario(config(), #scenario{})
|
2016-07-18 21:54:59 +00:00
|
|
|
-> [#failure_report{}].
|
2016-07-15 13:37:35 +00:00
|
|
|
run_scenario(Config,
|
2016-11-06 04:20:25 +00:00
|
|
|
#scenario{tests = Tests} = Scenario) ->
|
2016-11-07 14:11:27 +00:00
|
|
|
ct:pal("Scenario: table/query node vsn: ~p/~p\n"
|
|
|
|
" Need table/query node transition: ~p/~p",
|
|
|
|
[Scenario#scenario.table_node_vsn,
|
|
|
|
Scenario#scenario.query_node_vsn,
|
|
|
|
Scenario#scenario.need_table_node_transition,
|
|
|
|
Scenario#scenario.need_query_node_transition]),
|
2016-11-06 04:20:25 +00:00
|
|
|
PrevRiakcNode =
|
|
|
|
?CFG(previous_client_node, Config),
|
|
|
|
NodesAtVersions0 =
|
|
|
|
[{N, rtdev:node_version(rtdev:node_id(N))} || N <- ?CFG(nodes, Config)],
|
|
|
|
|
|
|
|
%% 1. retrieve scenario-invariant data from Config
|
|
|
|
AllTests =
|
|
|
|
add_timestamps(
|
|
|
|
lists:append(
|
|
|
|
[proplists:get_value(default_tests, Config, []), Tests])),
|
|
|
|
|
|
|
|
{_NodesAtVersions9, _TableNode9, _QueryNode9, Failures} =
|
|
|
|
lists:foldl(
|
|
|
|
fun(Stage, {NodesAtVersionsI, TableNodeI, QueryNodeI, []}) ->
|
|
|
|
{NodesAtVersionsJ, TableNodeJ, QueryNodeJ, Failures} =
|
|
|
|
Stage(NodesAtVersionsI, TableNodeI, QueryNodeI, PrevRiakcNode,
|
|
|
|
Scenario#scenario{tests = AllTests}),
|
|
|
|
{NodesAtVersionsJ, TableNodeJ, QueryNodeJ, Failures};
|
|
|
|
(_Stage, PrevState) ->
|
|
|
|
%% not doing anything because a previous stage has failed
|
|
|
|
PrevState
|
|
|
|
end,
|
|
|
|
{NodesAtVersions0, node_tbd, node_tbd, []},
|
|
|
|
[fun create_stage/5,
|
|
|
|
fun insert_stage/5,
|
|
|
|
fun select_stage/5]),
|
|
|
|
|
|
|
|
case Failures of
|
|
|
|
[] -> ok;
|
|
|
|
_ -> ct:pal("Failing queries in this scenario:\n"
|
|
|
|
"----------------\n"
|
|
|
|
"~s\n", [layout_fails_for_printing(Failures, Scenario)])
|
|
|
|
end,
|
|
|
|
|
|
|
|
Failures.
|
|
|
|
|
|
|
|
|
|
|
|
%% running tests: stages
|
|
|
|
%% ---------------------
|
|
|
|
|
|
|
|
create_stage(NodesAtVersions0, _TableNode, _QueryNode,
|
|
|
|
PrevRiakcNode,
|
2016-07-15 01:11:34 +00:00
|
|
|
#scenario{table_node_vsn = TableNodeVsn,
|
|
|
|
query_node_vsn = QueryNodeVsn,
|
2016-11-01 00:29:16 +00:00
|
|
|
need_pre_cluster_mixed = NeedPreClusterMixed,
|
2016-10-29 16:13:47 +00:00
|
|
|
convert_config_to_previous = ConvertConfigToPreviousFun,
|
|
|
|
convert_config_to_current = ConvertConfigToCurrentFun,
|
2016-11-01 00:29:16 +00:00
|
|
|
ensure_full_caps = EnsureFullCaps,
|
|
|
|
ensure_degraded_caps = EnsureDegradedCaps,
|
2016-07-29 08:19:15 +00:00
|
|
|
tests = Tests}) ->
|
2016-11-08 20:58:23 +00:00
|
|
|
ct:pal("Creating tables in ~p", [NodesAtVersions0]),
|
2016-11-01 00:29:16 +00:00
|
|
|
ConvConfFuns = {ConvertConfigToPreviousFun, ConvertConfigToCurrentFun},
|
|
|
|
CapsReqs = {EnsureFullCaps, EnsureDegradedCaps},
|
|
|
|
|
2016-07-25 23:30:30 +00:00
|
|
|
%% 2. Pick two nodes for create table and subsequent selects
|
2016-07-15 01:11:34 +00:00
|
|
|
{TableNode, NodesAtVersions1} =
|
2016-10-29 16:13:47 +00:00
|
|
|
find_or_make_node_at_vsn(NodesAtVersions0, TableNodeVsn, [], ConvConfFuns),
|
2016-07-15 01:11:34 +00:00
|
|
|
{QueryNode, NodesAtVersions2} =
|
2016-10-29 16:13:47 +00:00
|
|
|
find_or_make_node_at_vsn(NodesAtVersions1, QueryNodeVsn, [TableNode], ConvConfFuns),
|
2016-07-15 01:11:34 +00:00
|
|
|
|
2016-07-25 23:30:30 +00:00
|
|
|
%% 3. Try to ensure cluster is (not) mixed as hinted but keep the
|
2016-07-15 01:11:34 +00:00
|
|
|
%% interesting nodes at their versions as set in step 1.
|
|
|
|
NodesAtVersions3 =
|
2016-10-29 16:13:47 +00:00
|
|
|
ensure_cluster(
|
2016-11-01 00:29:16 +00:00
|
|
|
NodesAtVersions2, NeedPreClusterMixed, [TableNode, QueryNode],
|
|
|
|
ConvConfFuns, CapsReqs),
|
2016-07-15 01:11:34 +00:00
|
|
|
|
2016-11-08 20:58:23 +00:00
|
|
|
ct:pal("Selected Table/Query nodes ~p/~p", [TableNode, QueryNode]),
|
2016-11-01 00:29:16 +00:00
|
|
|
%% 4. For each table in the test, issue CREATE TABLE queries
|
2016-11-06 04:20:25 +00:00
|
|
|
Fails =
|
|
|
|
lists:append(
|
|
|
|
[make_tables(X, NodesAtVersions3, TableNode, QueryNode, PrevRiakcNode)
|
|
|
|
|| X <- Tests]),
|
|
|
|
|
|
|
|
{NodesAtVersions3, TableNode, QueryNode, Fails}.
|
2016-07-29 08:19:15 +00:00
|
|
|
|
2016-11-06 04:20:25 +00:00
|
|
|
|
|
|
|
insert_stage(NodesAtVersions3, TableNode, QueryNode,
|
|
|
|
_PrevRiakcNode, %% no need to fall back to PrevRiakcNode as we use riakc_ts:put
|
|
|
|
#scenario{tests = Tests}) ->
|
2016-11-08 20:58:23 +00:00
|
|
|
ct:pal("Inserting data in ~p", [NodesAtVersions3]),
|
2016-11-01 00:29:16 +00:00
|
|
|
%% 5. For each table in the test, issue INSERT queries
|
2016-11-06 04:20:25 +00:00
|
|
|
Fails =
|
|
|
|
lists:append(
|
|
|
|
[insert_data(X, NodesAtVersions3, TableNode, QueryNode)
|
|
|
|
|| X <- Tests]),
|
|
|
|
|
|
|
|
{NodesAtVersions3, TableNode, QueryNode, Fails}.
|
|
|
|
|
|
|
|
|
|
|
|
select_stage(NodesAtVersions3, TableNode, QueryNode,
|
|
|
|
PrevRiakcNode,
|
|
|
|
#scenario{table_node_vsn = TableNodeVsn,
|
|
|
|
query_node_vsn = QueryNodeVsn,
|
|
|
|
need_table_node_transition = NeedTableNodeTransition,
|
|
|
|
need_query_node_transition = NeedQueryNodeTransition,
|
|
|
|
need_post_cluster_mixed = NeedPostClusterMixed,
|
|
|
|
convert_config_to_previous = ConvertConfigToPreviousFun,
|
|
|
|
convert_config_to_current = ConvertConfigToCurrentFun,
|
|
|
|
ensure_full_caps = EnsureFullCaps,
|
|
|
|
ensure_degraded_caps = EnsureDegradedCaps,
|
|
|
|
tests = Tests}) ->
|
2016-11-08 20:58:23 +00:00
|
|
|
ct:pal("Running selects ~p", [NodesAtVersions3]),
|
2016-11-06 04:20:25 +00:00
|
|
|
ConvConfFuns = {ConvertConfigToPreviousFun, ConvertConfigToCurrentFun},
|
|
|
|
CapsReqs = {EnsureFullCaps, EnsureDegradedCaps},
|
2016-07-15 01:11:34 +00:00
|
|
|
|
2016-07-29 08:19:15 +00:00
|
|
|
%% 6. possibly do a transition, on none, one of, or both create
|
2016-07-15 01:11:34 +00:00
|
|
|
%% table node and query node
|
|
|
|
NodesAtVersions4 =
|
|
|
|
if NeedTableNodeTransition ->
|
2016-10-29 16:13:47 +00:00
|
|
|
possibly_transition_node(
|
|
|
|
NodesAtVersions3, TableNode, other_version(TableNodeVsn), ConvConfFuns);
|
2016-07-15 01:11:34 +00:00
|
|
|
el/=se ->
|
|
|
|
NodesAtVersions3
|
|
|
|
end,
|
|
|
|
NodesAtVersions5 =
|
|
|
|
if NeedQueryNodeTransition ->
|
2016-10-29 16:13:47 +00:00
|
|
|
possibly_transition_node(
|
|
|
|
NodesAtVersions4, QueryNode, other_version(QueryNodeVsn), ConvConfFuns);
|
2016-07-15 01:11:34 +00:00
|
|
|
el/=se ->
|
|
|
|
NodesAtVersions4
|
|
|
|
end,
|
|
|
|
|
2016-07-29 08:19:15 +00:00
|
|
|
%% 7. after transitioning the two relevant nodes, try to bring the
|
2016-07-15 01:11:34 +00:00
|
|
|
%% other nodes to satisfy the mixed/non-mixed hint
|
2016-10-31 16:20:54 +00:00
|
|
|
NodesAtVersions6 =
|
2016-11-01 00:29:16 +00:00
|
|
|
ensure_cluster(NodesAtVersions5, NeedPostClusterMixed, [TableNode, QueryNode],
|
|
|
|
ConvConfFuns, CapsReqs),
|
2016-07-15 01:11:34 +00:00
|
|
|
|
2016-07-29 08:19:15 +00:00
|
|
|
%% 8. issue the queries and collect results
|
2016-11-06 04:20:25 +00:00
|
|
|
Fails =
|
|
|
|
lists:append(
|
|
|
|
[run_selects(X, NodesAtVersions6, TableNode, QueryNode, PrevRiakcNode)
|
|
|
|
|| X <- Tests]),
|
2016-07-18 15:23:06 +00:00
|
|
|
|
2016-11-06 04:20:25 +00:00
|
|
|
{NodesAtVersions6, TableNode, QueryNode, Fails}.
|
2016-07-29 08:19:15 +00:00
|
|
|
|
|
|
|
|
2016-11-06 04:20:25 +00:00
|
|
|
add_timestamps(TestSets) ->
|
|
|
|
[X#test_set{timestamp = make_timestamp()} || X <- TestSets].
|
2016-07-29 08:19:15 +00:00
|
|
|
|
2016-11-06 04:20:25 +00:00
|
|
|
make_timestamp() ->
|
|
|
|
{_Mega, Sec, Milli} = os:timestamp(),
|
|
|
|
fmt("~b~b", [Sec, Milli]).
|
2016-07-18 21:52:19 +00:00
|
|
|
|
2016-07-15 01:11:34 +00:00
|
|
|
|
2016-11-06 04:20:25 +00:00
|
|
|
%% running test stages: create, insert, select
|
|
|
|
%% -------------------------------------------
|
|
|
|
|
|
|
|
make_tables(#test_set{create = #create{should_skip = true}}, _, _, _, _) ->
|
|
|
|
[];
|
|
|
|
make_tables(#test_set{testname = Testname,
|
|
|
|
timestamp = Timestamp,
|
|
|
|
create = #create{ddl = DDLFmt,
|
|
|
|
expected = Exp}},
|
|
|
|
NodesAtVersions, TableNode, QueryNode,
|
|
|
|
PrevClientNode) ->
|
|
|
|
%% fast machines:
|
|
|
|
timer:sleep(1),
|
|
|
|
Table = get_table_name(Testname, Timestamp),
|
|
|
|
DDL = fmt(DDLFmt, [Table]),
|
|
|
|
case query_with_client(DDL, TableNode, PrevClientNode) of
|
|
|
|
Exp ->
|
|
|
|
ok = wait_until_active_table(TableNode, PrevClientNode, Table, 5),
|
|
|
|
ct:log("Table ~p created on ~p", [Table, TableNode]),
|
|
|
|
[];
|
|
|
|
{error, {_No, Error}} ->
|
|
|
|
ct:log("Failed to create table ~p: (~s) with ~s", [Table, Error, DDL]),
|
|
|
|
[#failure_report{failing_test = make_msg("Creation of ~s failed", [Table]),
|
|
|
|
cluster = NodesAtVersions,
|
|
|
|
table_node = TableNode,
|
|
|
|
query_node = QueryNode,
|
|
|
|
expected = Exp,
|
|
|
|
got = Error}]
|
|
|
|
end.
|
|
|
|
|
|
|
|
|
|
|
|
insert_data(#test_set{insert = #insert{should_skip = true}}, _, _, _) ->
|
|
|
|
[];
|
|
|
|
insert_data(#test_set{testname = Testname,
|
|
|
|
timestamp = Timestamp,
|
|
|
|
insert = #insert{data = Data,
|
|
|
|
expected = Exp}},
|
|
|
|
NodesAtVersions, TableNode, QueryNode) ->
|
|
|
|
Client1 = rt:pbc(TableNode),
|
|
|
|
Table = get_table_name(Testname, Timestamp),
|
|
|
|
case riakc_ts:put(Client1, Table, Data) of
|
|
|
|
Exp ->
|
|
|
|
ct:log("Table ~p on ~p had ~b records successfully inserted",
|
|
|
|
[Table, TableNode, length(Data)]),
|
|
|
|
[];
|
|
|
|
Error ->
|
|
|
|
ct:log("Failed to insert data into ~p (~p)", [Table, Error]),
|
|
|
|
[#failure_report{message = make_msg("Insert of data into ~s failed", [Table]),
|
|
|
|
cluster = NodesAtVersions,
|
|
|
|
table_node = TableNode,
|
|
|
|
query_node = QueryNode,
|
|
|
|
expected = Exp,
|
|
|
|
got = Error}]
|
|
|
|
end.
|
|
|
|
|
|
|
|
|
|
|
|
run_selects(#test_set{testname = Testname,
|
|
|
|
timestamp = Timestamp,
|
|
|
|
selects = Selects},
|
|
|
|
NodesAtVersions, TableNode, QueryNode,
|
|
|
|
PrevClientNode) ->
|
|
|
|
QryNos = lists:seq(1, length(Selects)),
|
|
|
|
Zip = lists:zip(Selects, QryNos),
|
|
|
|
Tablename = get_table_name(Testname, Timestamp),
|
|
|
|
|
|
|
|
RunSelect =
|
|
|
|
fun(#select{should_skip = true}, _QryNo) ->
|
|
|
|
[];
|
|
|
|
(#select{qry = Q,
|
|
|
|
expected = Exp,
|
|
|
|
assert_mod = Mod,
|
|
|
|
assert_fun = Fun}, QryNo) ->
|
|
|
|
SelectQuery = fmt(Q, [Tablename]),
|
|
|
|
Got = query_with_client(SelectQuery, QueryNode, PrevClientNode),
|
|
|
|
case Mod:Fun(fmt("Query #~p", [QryNo]), Exp, Got) of
|
|
|
|
pass -> [];
|
|
|
|
fail -> [#failure_report{message = SelectQuery,
|
|
|
|
cluster = NodesAtVersions,
|
|
|
|
table_node = TableNode,
|
|
|
|
query_node = QueryNode,
|
|
|
|
expected = Exp,
|
|
|
|
got = Got}]
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
lists:append(
|
|
|
|
[RunSelect(S, QN) || {S, QN} <- Zip]).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
%% query wrapper, possibly using a previous version client
|
|
|
|
%% -------------------------------------------------------
|
|
|
|
|
|
|
|
query_with_client(Query, Node, PrevClientNode) ->
|
|
|
|
Version = rtdev:node_version(rtdev:node_id(Node)),
|
|
|
|
case Version of
|
|
|
|
current ->
|
|
|
|
Client = rt:pbc(Node),
|
|
|
|
riakc_ts:query(Client, Query);
|
|
|
|
previous ->
|
|
|
|
ConnInfo = proplists:get_value(Node, rt:connection_info([Node])),
|
|
|
|
{IP, Port} = proplists:get_value(pb, ConnInfo),
|
|
|
|
ct:log("using ~p client to contact node ~p at ~p:~p", [Version, Node, IP, Port]),
|
|
|
|
{ok, Client} =
|
|
|
|
rpc:call(
|
|
|
|
PrevClientNode,
|
|
|
|
riakc_pb_socket, start_link, [IP, Port, [{auto_reconnect, true}]]),
|
|
|
|
rpc:call(
|
|
|
|
PrevClientNode,
|
|
|
|
riakc_ts, query, [Client, Query])
|
|
|
|
end.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
layout_fails_for_printing(Failures,
|
|
|
|
#scenario{table_node_vsn = TableNodeVsn,
|
|
|
|
query_node_vsn = QueryNodeVsn}) ->
|
|
|
|
[begin
|
|
|
|
DidTableNodeTransition =
|
|
|
|
TableNodeVsn /= proplists:get_value(TableNode, NodesAtVersions),
|
|
|
|
DidQueryNodeTransition =
|
|
|
|
QueryNodeVsn /= proplists:get_value(QueryNode, NodesAtVersions),
|
|
|
|
lists:flatten(
|
|
|
|
[io_lib:format(
|
|
|
|
" Cluster: ~p\n"
|
|
|
|
"TableNode: ~p, transitioned? ~p\n"
|
|
|
|
"QueryNode: ~p, transitioned? ~p\n"
|
|
|
|
" Test: ~p\n"
|
|
|
|
" Expected: ~p\n"
|
|
|
|
" Got: ~p\n\n",
|
|
|
|
[NodesAtVersions,
|
|
|
|
TableNode, DidTableNodeTransition,
|
|
|
|
QueryNode, DidQueryNodeTransition,
|
|
|
|
FailingTest,
|
|
|
|
Expected,
|
|
|
|
Got])])
|
|
|
|
end || #failure_report{cluster = NodesAtVersions,
|
|
|
|
table_node = TableNode,
|
|
|
|
query_node = QueryNode,
|
|
|
|
failing_test = FailingTest,
|
|
|
|
expected = Expected,
|
|
|
|
got = Got} <- Failures].
|
|
|
|
|
|
|
|
|
|
|
|
%% cluster preparation & node transitions
|
|
|
|
%% --------------------------------------
|
2016-07-15 01:11:34 +00:00
|
|
|
|
|
|
|
-spec is_cluster_mixed(versioned_cluster()) -> boolean().
|
|
|
|
is_cluster_mixed(NodesAtVersions) ->
|
|
|
|
{_N0, V0} = hd(NodesAtVersions),
|
2016-07-18 15:22:08 +00:00
|
|
|
not lists:all(fun({_N, V}) -> V == V0 end, NodesAtVersions).
|
2016-07-15 01:11:34 +00:00
|
|
|
|
|
|
|
|
2016-11-01 00:29:16 +00:00
|
|
|
-spec ensure_cluster(versioned_cluster(), boolean(), [node()],
|
|
|
|
{function(), function()}, {[cap_with_ver()], [cap_with_ver()]}) ->
|
2016-10-29 16:13:47 +00:00
|
|
|
versioned_cluster().
|
2016-07-15 01:11:34 +00:00
|
|
|
%% @doc Massage the cluster if necessary (and if possible) to pick two
|
|
|
|
%% nodes of specific versions, optionally ensuring the resulting
|
|
|
|
%% cluster is mixed or not.
|
2016-11-01 00:29:16 +00:00
|
|
|
ensure_cluster(NodesAtVersions0, NeedClusterMixed, ImmutableNodes,
|
|
|
|
ConvConfFuns, {FullCaps, DegradedCaps}) ->
|
2016-07-15 01:11:34 +00:00
|
|
|
ImmutableVersions =
|
|
|
|
[rtdev:node_version(rtdev:node_id(Node)) || Node <- ImmutableNodes],
|
|
|
|
IsInherentlyMixed =
|
|
|
|
(length(lists:usort(ImmutableVersions)) > 1),
|
|
|
|
%% possibly transition some other node to fulfil cluster
|
|
|
|
%% homogeneity condition
|
|
|
|
NodesAtVersions1 =
|
|
|
|
case {is_cluster_mixed(NodesAtVersions0), NeedClusterMixed} of
|
|
|
|
{true, true} ->
|
|
|
|
NodesAtVersions0;
|
|
|
|
{false, false} ->
|
|
|
|
NodesAtVersions0;
|
|
|
|
{false, true} ->
|
|
|
|
%% just flip an uninvolved node
|
|
|
|
{_ThirdNode, NodesAtDiffVersions} =
|
|
|
|
find_or_make_node_at_vsn(
|
2016-10-29 16:13:47 +00:00
|
|
|
NodesAtVersions0, other_version(hd(ImmutableVersions)),
|
|
|
|
ImmutableNodes, ConvConfFuns),
|
2016-07-15 01:11:34 +00:00
|
|
|
NodesAtDiffVersions;
|
|
|
|
|
|
|
|
%% non-mixed/mixed hints can be honoured only when
|
|
|
|
%% TableNodeVsn and QueryNodeVsn are same/not the same:
|
|
|
|
{true, false} when not IsInherentlyMixed ->
|
|
|
|
%% cluster is mixed even though both relevant nodes
|
|
|
|
%% are at same version: align the rest
|
|
|
|
ensure_single_version_cluster(
|
2016-10-29 16:13:47 +00:00
|
|
|
NodesAtVersions0, hd(ImmutableVersions), ImmutableNodes, ConvConfFuns);
|
2016-07-15 01:11:34 +00:00
|
|
|
{true, false} when IsInherentlyMixed ->
|
|
|
|
%% cluster is mixed because both relevant nodes are
|
|
|
|
%% not at same version: don't honour the hint
|
|
|
|
ct:log("ignoring NeedClusterMixed == false hint because TableNodeVsn /= QueryNodeVsn", []),
|
|
|
|
NodesAtVersions0
|
|
|
|
end,
|
2016-11-01 00:29:16 +00:00
|
|
|
|
|
|
|
{SomeNode, _Ver} = hd(NodesAtVersions1),
|
|
|
|
CapsToCheck =
|
|
|
|
case ((current == rtdev:node_version(rtdev:node_id(SomeNode)))
|
|
|
|
and not is_cluster_mixed(NodesAtVersions1)) of
|
|
|
|
true ->
|
|
|
|
FullCaps;
|
|
|
|
false ->
|
|
|
|
DegradedCaps
|
|
|
|
end,
|
|
|
|
lists:foreach(
|
|
|
|
fun({Node, {Cap, Ver}}) ->
|
|
|
|
ok = rt:wait_until_capability(Node, Cap, Ver)
|
|
|
|
end,
|
|
|
|
[{Node, CapVer} || {Node, _} <- NodesAtVersions1, CapVer <- CapsToCheck]),
|
|
|
|
|
2016-07-15 01:11:34 +00:00
|
|
|
NodesAtVersions1.
|
|
|
|
|
|
|
|
|
2016-10-29 16:13:47 +00:00
|
|
|
-spec find_or_make_node_at_vsn(versioned_cluster(), version(),
|
|
|
|
[node()], {function(), function()}) ->
|
|
|
|
{node(), versioned_cluster()}.
|
|
|
|
find_or_make_node_at_vsn(NodesAtVersions0, ReqVersion, ImmutableNodes, ConvConfFuns) ->
|
2016-07-15 01:11:34 +00:00
|
|
|
MutableNodes =
|
|
|
|
[{N, V} || {N, V} <- NodesAtVersions0, not lists:member(N, ImmutableNodes)],
|
2016-11-08 20:57:58 +00:00
|
|
|
case lists:keyfind(ReqVersion, 2, MutableNodes) of
|
|
|
|
{SuitableNode, ReqVersion} ->
|
|
|
|
{SuitableNode, NodesAtVersions0};
|
|
|
|
false ->
|
|
|
|
case hd(MutableNodes) of
|
|
|
|
{Node, Vsn} when Vsn == ReqVersion ->
|
|
|
|
{Node, NodesAtVersions0};
|
|
|
|
{Node, TransitionMe} ->
|
|
|
|
OtherVersion = other_version(TransitionMe),
|
|
|
|
ok = transition_node(Node, OtherVersion, ConvConfFuns),
|
|
|
|
{Node, lists:keyreplace(Node, 1, NodesAtVersions0, {Node, OtherVersion})}
|
|
|
|
end
|
2016-07-15 01:11:34 +00:00
|
|
|
end.
|
2016-07-05 14:45:50 +00:00
|
|
|
|
|
|
|
|
2016-10-29 16:13:47 +00:00
|
|
|
-spec ensure_single_version_cluster(versioned_cluster(), version(),
|
|
|
|
[node()], {function(), function()}) ->
|
|
|
|
versioned_cluster().
|
|
|
|
ensure_single_version_cluster(NodesAtVersions, ReqVersion, ImmutableNodes, ConvConfFuns) ->
|
2016-07-15 01:11:34 +00:00
|
|
|
Transitionable =
|
|
|
|
[N || {N, V} <- NodesAtVersions,
|
|
|
|
V /= ReqVersion, not lists:member(N, ImmutableNodes)],
|
2016-10-29 16:13:47 +00:00
|
|
|
rt:pmap(fun(N) -> transition_node(N, ReqVersion, ConvConfFuns) end,
|
|
|
|
Transitionable),
|
2016-07-15 01:11:34 +00:00
|
|
|
%% recreate NodesAtVersions
|
|
|
|
[{N, ReqVersion} || {N, _V} <- NodesAtVersions].
|
2016-07-05 14:45:50 +00:00
|
|
|
|
|
|
|
|
2016-10-29 16:13:47 +00:00
|
|
|
-spec transition_node(node(), version(), {function(), function()}) -> ok.
|
|
|
|
transition_node(Node, Version,
|
|
|
|
{ConvertConfigToPreviousFun, ConvertConfigToCurrentFun}) ->
|
2016-07-18 15:23:06 +00:00
|
|
|
ct:log("transitioning node ~p to version '~p'", [Node, Version]),
|
2016-08-16 21:23:44 +00:00
|
|
|
case Version of
|
|
|
|
previous ->
|
2016-10-29 16:13:47 +00:00
|
|
|
ok = rt:upgrade(Node, Version, ConvertConfigToPreviousFun);
|
2016-08-16 21:23:44 +00:00
|
|
|
current ->
|
2016-10-29 16:13:47 +00:00
|
|
|
ok = rt:upgrade(Node, Version, ConvertConfigToCurrentFun)
|
2016-08-16 21:23:44 +00:00
|
|
|
end,
|
2016-07-15 01:11:34 +00:00
|
|
|
ok = rt:wait_for_service(Node, riak_kv).
|
2016-07-05 14:45:50 +00:00
|
|
|
|
2016-10-29 16:13:47 +00:00
|
|
|
|
|
|
|
-spec possibly_transition_node(versioned_cluster(), node(), version(),
|
|
|
|
{function(), function()}) ->
|
|
|
|
versioned_cluster().
|
|
|
|
possibly_transition_node(NodesAtVersions, Node, ReqVsn, ConvConfFuns) ->
|
2016-07-15 01:11:34 +00:00
|
|
|
case lists:keyfind(Node, 1, NodesAtVersions) of
|
|
|
|
ReqVsn ->
|
|
|
|
NodesAtVersions;
|
|
|
|
_TransitionMe ->
|
2016-10-29 16:13:47 +00:00
|
|
|
ok = transition_node(Node, ReqVsn, ConvConfFuns),
|
2016-07-15 01:11:34 +00:00
|
|
|
lists:keyreplace(Node, 1, NodesAtVersions,
|
|
|
|
{Node, ReqVsn})
|
2016-07-05 14:45:50 +00:00
|
|
|
end.
|
|
|
|
|
2016-07-15 01:11:34 +00:00
|
|
|
other_version(current) -> previous;
|
|
|
|
other_version(previous) -> current.
|
|
|
|
|
2016-07-02 20:52:27 +00:00
|
|
|
|
2016-11-06 04:20:25 +00:00
|
|
|
%% misc functions
|
|
|
|
%% --------------
|
|
|
|
|
|
|
|
get_table_name(Testname, Timestamp) when is_list(Testname) andalso
|
|
|
|
is_list(Timestamp) ->
|
|
|
|
Testname ++ Timestamp.
|
|
|
|
|
2016-10-29 16:17:25 +00:00
|
|
|
wait_until_active_table(_TargetNode, _PrevClientNode, Table, 0) ->
|
2016-07-02 20:52:27 +00:00
|
|
|
ct:fail("Table ~s took too long to activate", [Table]),
|
|
|
|
not_ok;
|
2016-10-29 16:17:25 +00:00
|
|
|
wait_until_active_table(TargetNode, PrevClientNode, Table, N) ->
|
|
|
|
case query_with_client("DESCRIBE "++Table, TargetNode, PrevClientNode) of
|
2016-07-02 20:52:27 +00:00
|
|
|
{ok, _} ->
|
|
|
|
ok;
|
|
|
|
_ ->
|
|
|
|
timer:sleep(1000),
|
2016-10-29 16:17:25 +00:00
|
|
|
wait_until_active_table(TargetNode, PrevClientNode, Table, N-1)
|
2016-07-02 20:52:27 +00:00
|
|
|
end.
|
|
|
|
|
2016-07-29 08:19:15 +00:00
|
|
|
make_msg(Format, Payload) ->
|
|
|
|
list_to_binary(fmt(Format, Payload)).
|
2016-10-29 16:13:47 +00:00
|
|
|
|
|
|
|
%% riak.conf created under 1.5 cannot be read by 1.4 because of new keys:
|
|
|
|
%% riak_kv.query.timeseries.max_returned_data_size,
|
|
|
|
%% riak_kv.query.timeseries.qbuf_soft_watermark,
|
|
|
|
%% riak_kv.query.timeseries.qbuf_hard_watermark,
|
|
|
|
%% riak_kv.query.timeseries.qbuf_expire_ms,
|
|
|
|
%% riak_kv.query.timeseries.qbuf_incomplete_release_ms
|
|
|
|
%% let's comment them out?
|
2016-11-06 04:20:25 +00:00
|
|
|
convert_riak_conf_to_previous(Config) ->
|
|
|
|
DatafPath = ?CFG(new_data_dir, Config),
|
|
|
|
RiakConfPath = filename:join(DatafPath, "../etc/riak.conf"),
|
2016-10-29 16:13:47 +00:00
|
|
|
{ok, Content0} = file:read_file(RiakConfPath),
|
|
|
|
Content9 =
|
|
|
|
re:replace(
|
|
|
|
Content0,
|
|
|
|
<<"(^riak_kv.query.timeseries.max_returned_data_size"
|
|
|
|
%% this key exists in 1.4, but has a cap of 256, whereas
|
|
|
|
%% 1.5 has no upper limit
|
2016-11-29 18:08:49 +00:00
|
|
|
"|^riak_kv.query.timeseries.max_running_fsms"
|
2016-10-29 16:13:47 +00:00
|
|
|
"|^riak_kv.query.timeseries.max_quanta_span"
|
|
|
|
"|^riak_kv.query.timeseries.qbuf_soft_watermark"
|
|
|
|
"|^riak_kv.query.timeseries.qbuf_hard_watermark"
|
|
|
|
"|^riak_kv.query.timeseries.qbuf_expire_ms"
|
|
|
|
"|^riak_kv.query.timeseries.qbuf_incomplete_release_ms)">>,
|
|
|
|
<<"#\\1">>, [global, multiline, {return, binary}]),
|
|
|
|
ok = file:write_file(RiakConfPath, Content9).
|
|
|
|
|
|
|
|
%% Keep the old convert_riak_conf_to_previous functions around, for
|
|
|
|
%% reference and occasional test rerun
|
|
|
|
|
|
|
|
%% %% riak.conf created under 1.4 cannot be read by 1.3 because the properties
|
|
|
|
%% %% have been renamed so they need to be replaced with the 1.3 versions.
|
|
|
|
%% convert_riak_conf_to_1_3(RiakConfPath) ->
|
|
|
|
%% {ok, Content1} = file:read_file(RiakConfPath),
|
|
|
|
%% Content2 = binary:replace(
|
|
|
|
%% Content1,
|
|
|
|
%% <<"riak_kv.query.timeseries.max_quanta_span">>,
|
|
|
|
%% <<"timeseries_query_max_quanta_span">>, [global]),
|
|
|
|
%% Content3 = binary:replace(
|
|
|
|
%% Content2,
|
|
|
|
%% <<"riak_kv.query.timeseries.max_concurrent_queries">>,
|
|
|
|
%% <<"riak_kv.query.concurrent_queries">>, [global]),
|
|
|
|
%% Content4 = convert_timeout_config_to_1_3(Content3),
|
|
|
|
%% ok = file:write_file(RiakConfPath, Content4).
|
|
|
|
%%
|
|
|
|
%% %% The timeout property needs some special care because 1.3 uses 10000 for
|
|
|
|
%% %% milliseconds and 1.4 uses 10s for a number and unit.
|
|
|
|
%% convert_timeout_config_to_1_3(Content) ->
|
|
|
|
%% Re = "(riak_kv.query.timeseries.timeout)\s*=\s*([0-9]*)([smh])",
|
|
|
|
%% re:replace(Content, Re, "timeseries_query_timeout_ms = 10000").
|
|
|
|
|
|
|
|
fmt(F, A) ->
|
|
|
|
lists:flatten(io_lib:format(F, A)).
|