summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell Branca <chewbranca@apache.org>2020-03-20 14:34:39 -0700
committerRussell Branca <chewbranca@apache.org>2020-03-25 14:06:17 -0700
commit7c831f68d9049334a2e10a8a3f5d82ba214992e4 (patch)
treee7507659a748864069bd227351506cac4ac9063f
parent2247322f5eeabc5ef7f5bb7719f3d6bf1a1f6ee4 (diff)
downloadcouchdb-7c831f68d9049334a2e10a8a3f5d82ba214992e4.tar.gz
Ensure shards are created with db options
-rw-r--r--src/couch/include/couch_eunit.hrl5
-rw-r--r--src/fabric/src/fabric_rpc.erl2
-rw-r--r--src/fabric/test/eunit/fabric_rpc_tests.erl181
-rw-r--r--src/mem3/src/mem3_rpc.erl2
-rw-r--r--src/mem3/src/mem3_shards.erl10
-rw-r--r--src/mem3/src/mem3_util.erl31
6 files changed, 228 insertions, 3 deletions
diff --git a/src/couch/include/couch_eunit.hrl b/src/couch/include/couch_eunit.hrl
index d3611c88b..188524893 100644
--- a/src/couch/include/couch_eunit.hrl
+++ b/src/couch/include/couch_eunit.hrl
@@ -49,6 +49,11 @@
Suffix = couch_uuids:random(),
iolist_to_binary(["eunit-test-db-", Suffix])
end).
+-define(tempshard,
+ fun() ->
+ Suffix = couch_uuids:random(),
+ iolist_to_binary(["shards/80000000-ffffffff/eunit-test-db-", Suffix])
+ end).
-define(docid,
fun() ->
integer_to_list(couch_util:unique_monotonic_integer())
diff --git a/src/fabric/src/fabric_rpc.erl b/src/fabric/src/fabric_rpc.erl
index a67dcd148..85da3ff12 100644
--- a/src/fabric/src/fabric_rpc.erl
+++ b/src/fabric/src/fabric_rpc.erl
@@ -439,7 +439,7 @@ get_node_seqs(Db, Nodes) ->
get_or_create_db(DbName, Options) ->
- couch_db:open_int(DbName, [{create_if_missing, true} | Options]).
+ mem3_util:get_or_create_db(DbName, Options).
get_view_cb(#mrargs{extra = Options}) ->
diff --git a/src/fabric/test/eunit/fabric_rpc_tests.erl b/src/fabric/test/eunit/fabric_rpc_tests.erl
new file mode 100644
index 000000000..b94caf659
--- /dev/null
+++ b/src/fabric/test/eunit/fabric_rpc_tests.erl
@@ -0,0 +1,181 @@
+% Licensed 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.
+
+-module(fabric_rpc_tests).
+
+
+-include_lib("couch/include/couch_eunit.hrl").
+-include_lib("couch/include/couch_db.hrl").
+
+
+-define(TDEF(A), {A, fun A/1}).
+
+
+main_test_() ->
+ {
+ setup,
+ spawn,
+ fun setup_all/0,
+ fun teardown_all/1,
+ [
+ {
+ foreach,
+ fun setup_no_db_or_config/0,
+ fun teardown_db/1,
+ lists:map(fun wrap/1, [
+ ?TDEF(t_no_config_non_shard_db_create_succeeds)
+ ])
+ },
+ {
+ foreach,
+ fun setup_shard/0,
+ fun teardown_noop/1,
+ lists:map(fun wrap/1, [
+ ?TDEF(t_no_db),
+ ?TDEF(t_no_config_db_create_fails_for_shard),
+ ?TDEF(t_no_config_db_create_fails_for_shard_rpc)
+ ])
+ },
+ {
+ foreach,
+ fun setup_shard/0,
+ fun teardown_db/1,
+ lists:map(fun wrap/1, [
+ ?TDEF(t_db_create_with_config)
+ ])
+ }
+
+ ]
+ }.
+
+
+setup_all() ->
+ test_util:start_couch([rexi, mem3, fabric]).
+
+
+teardown_all(Ctx) ->
+ test_util:stop_couch(Ctx).
+
+
+setup_no_db_or_config() ->
+ ?tempdb().
+
+
+setup_shard() ->
+ ?tempshard().
+
+
+teardown_noop(_DbName) ->
+ ok.
+
+teardown_db(DbName) ->
+ ok = couch_server:delete(DbName, []).
+
+
+wrap({Name, Fun}) ->
+ fun(Arg) ->
+ {timeout, 60, {atom_to_list(Name), fun() ->
+ process_flag(trap_exit, true),
+ Fun(Arg)
+ end}}
+ end.
+
+
+t_no_db(DbName) ->
+ ?assertEqual({not_found, no_db_file}, couch_db:open_int(DbName, [?ADMIN_CTX])).
+
+
+t_no_config_non_shard_db_create_succeeds(DbName) ->
+ ?assertEqual({not_found, no_db_file}, couch_db:open_int(DbName, [?ADMIN_CTX])),
+ ?assertEqual(DbName, mem3:dbname(DbName)),
+ ?assertMatch({ok, _}, mem3_util:get_or_create_db(DbName, [?ADMIN_CTX])).
+
+
+t_no_config_db_create_fails_for_shard(DbName) ->
+ ?assertEqual({not_found, no_db_file}, couch_db:open_int(DbName, [?ADMIN_CTX])),
+ ?assertException(throw, {error, missing_target}, mem3_util:get_or_create_db(DbName, [?ADMIN_CTX])).
+
+
+t_no_config_db_create_fails_for_shard_rpc(DbName) ->
+ ?assertEqual({not_found, no_db_file}, couch_db:open_int(DbName, [?ADMIN_CTX])),
+ ?assertException(throw, {error, missing_target}, mem3_util:get_or_create_db(DbName, [?ADMIN_CTX])),
+ MFA = {fabric_rpc, get_db_info, [DbName]},
+ Ref = rexi:cast(node(), self(), MFA),
+ Resp = receive
+ Resp0 -> Resp0
+ end,
+ ?assertMatch({Ref, {'rexi_EXIT', {{error, missing_target}, _}}}, Resp).
+
+
+t_db_create_with_config(DbName) ->
+ MDbName = mem3:dbname(DbName),
+ DbDoc = #doc{id = MDbName, body = test_db_doc()},
+
+ ?assertEqual({not_found, no_db_file}, couch_db:open_int(DbName, [?ADMIN_CTX])),
+
+ %% Write the dbs db config
+ couch_util:with_db(mem3_sync:shards_db(), fun(Db) ->
+ ?assertEqual({not_found, missing}, couch_db:open_doc(Db, MDbName, [ejson_body])),
+ ?assertMatch({ok, _}, couch_db:update_docs(Db, [DbDoc]))
+ end),
+
+ %% Test get_or_create_db loads the properties as expected
+ couch_util:with_db(mem3_sync:shards_db(), fun(Db) ->
+ ?assertMatch({ok, _}, couch_db:open_doc(Db, MDbName, [ejson_body])),
+ ?assertEqual({not_found, no_db_file}, couch_db:open_int(DbName, [?ADMIN_CTX])),
+ Resp = mem3_util:get_or_create_db(DbName, [?ADMIN_CTX]),
+ ?assertMatch({ok, _}, Resp),
+ {ok, LDb} = Resp,
+
+ {Body} = test_db_doc(),
+ DbProps = mem3_util:get_shard_opts(Body),
+ {Props} = case couch_db_engine:get_props(LDb) of
+ undefined -> {[]};
+ Else -> {Else}
+ end,
+ %% We don't normally store the default engine name
+ EngineProps = case couch_db_engine:get_engine(LDb) of
+ couch_bt_engine ->
+ [];
+ EngineName ->
+ [{engine, EngineName}]
+ end,
+ ?assertEqual([{props, Props} | EngineProps], DbProps)
+ end).
+
+
+test_db_doc() ->
+ {[
+ {<<"shard_suffix">>, ".1584997648"},
+ {<<"changelog">>, [
+ [<<"add">>, <<"00000000-7fffffff">>, <<"node1@127.0.0.1">>],
+ [<<"add">>, <<"00000000-7fffffff">>, <<"node2@127.0.0.1">>],
+ [<<"add">>, <<"00000000-7fffffff">>, <<"node3@127.0.0.1">>],
+ [<<"add">>, <<"80000000-ffffffff">>, <<"node1@127.0.0.1">>],
+ [<<"add">>, <<"80000000-ffffffff">>, <<"node2@127.0.0.1">>],
+ [<<"add">>, <<"80000000-ffffffff">>, <<"node3@127.0.0.1">>]
+ ]},
+ {<<"by_node">>, {[
+ {<<"node1@127.0.0.1">>, [<<"00000000-7fffffff">>, <<"80000000-ffffffff">>]},
+ {<<"node2@127.0.0.1">>, [<<"00000000-7fffffff">>, <<"80000000-ffffffff">>]},
+ {<<"node3@127.0.0.1">>, [<<"00000000-7fffffff">>, <<"80000000-ffffffff">>]}
+ ]}},
+ {<<"by_range">>, {[
+ {<<"00000000-7fffffff">>, [<<"node1@127.0.0.1">>, <<"node2@127.0.0.1">>, <<"node3@127.0.0.1">>]},
+ {<<"80000000-ffffffff">>, [<<"node1@127.0.0.1">>, <<"node2@127.0.0.1">>, <<"node3@127.0.0.1">>]}
+ ]}},
+ {<<"props">>, {[
+ {partitioned, true},
+ {hash, [couch_partition, hash, []]}
+ ]}}
+ ]}.
+
diff --git a/src/mem3/src/mem3_rpc.erl b/src/mem3/src/mem3_rpc.erl
index 0991aa745..5d1c62c06 100644
--- a/src/mem3/src/mem3_rpc.erl
+++ b/src/mem3/src/mem3_rpc.erl
@@ -401,7 +401,7 @@ rexi_call(Node, MFA, Timeout) ->
get_or_create_db(DbName, Options) ->
- couch_db:open_int(DbName, [{create_if_missing, true} | Options]).
+ mem3_util:get_or_create_db(DbName, Options).
-ifdef(TEST).
diff --git a/src/mem3/src/mem3_shards.erl b/src/mem3/src/mem3_shards.erl
index bfee30279..4f3323740 100644
--- a/src/mem3/src/mem3_shards.erl
+++ b/src/mem3/src/mem3_shards.erl
@@ -20,6 +20,7 @@
-export([handle_config_change/5, handle_config_terminate/3]).
-export([start_link/0]).
+-export([opts_for_db/1]).
-export([for_db/1, for_db/2, for_docid/2, for_docid/3, get/3, local/1, fold/2]).
-export([for_shard_range/1]).
-export([set_max_size/1]).
@@ -45,6 +46,15 @@
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+opts_for_db(DbName) ->
+ {ok, Db} = mem3_util:ensure_exists(mem3_sync:shards_db()),
+ case couch_db:open_doc(Db, DbName, [ejson_body]) of
+ {ok, #doc{body = {Props}}} ->
+ mem3_util:get_shard_opts(Props);
+ {not_found, _} ->
+ erlang:error(database_does_not_exist, ?b2l(DbName))
+ end.
+
for_db(DbName) ->
for_db(DbName, []).
diff --git a/src/mem3/src/mem3_util.erl b/src/mem3/src/mem3_util.erl
index 619f7810a..a6ac3a865 100644
--- a/src/mem3/src/mem3_util.erl
+++ b/src/mem3/src/mem3_util.erl
@@ -14,8 +14,9 @@
-export([name_shard/2, create_partition_map/5, build_shards/2,
n_val/2, q_val/1, to_atom/1, to_integer/1, write_db_doc/1, delete_db_doc/1,
- shard_info/1, ensure_exists/1, open_db_doc/1]).
+ shard_info/1, ensure_exists/1, open_db_doc/1, get_or_create_db/2]).
-export([is_deleted/1, rotate_list/2]).
+-export([get_shard_opts/1, get_engine_opt/1, get_props_opt/1]).
-export([
iso8601_timestamp/0,
live_nodes/0,
@@ -506,6 +507,34 @@ sort_ranges_fun({B1, _}, {B2, _}) ->
B1 =< B2.
+get_or_create_db(DbName, Options) ->
+ case couch_db:open_int(DbName, Options) of
+ {ok, _} = OkDb ->
+ OkDb;
+ {not_found, no_db_file} ->
+ try
+ DbOpts = case mem3:dbname(DbName) of
+ DbName -> [];
+ MDbName -> mem3_shards:opts_for_db(MDbName)
+ end,
+ Options1 = [{create_if_missing, true} | Options],
+ Options2 = merge_opts(DbOpts, Options1),
+ couch_db:open_int(DbName, Options2)
+ catch error:database_does_not_exist ->
+ throw({error, missing_target})
+ end;
+ Else ->
+ Else
+ end.
+
+
+%% merge two proplists, atom options only valid in Old
+merge_opts(New, Old) ->
+ lists:foldl(fun({Key, Val}, Acc) ->
+ lists:keystore(Key, 1, Acc, {Key, Val})
+ end, Old, New).
+
+
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").