summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Vatamaniuc <vatamane@gmail.com>2022-11-04 00:59:47 -0400
committerNick Vatamaniuc <nickva@users.noreply.github.com>2022-11-18 17:04:30 -0500
commit57b2dc340efc5f267f3200d17e30f977eedf491b (patch)
tree63d7002fa943090cd5fd407d51c43ee97f9f252d
parent4e13953211c6e1900fbfd021344255766348885f (diff)
downloadcouchdb-57b2dc340efc5f267f3200d17e30f977eedf491b.tar.gz
Improve smoosh_utils
Add functions related to dealing with new index cleanup channel. Add functions which fetch the channels list from config. Fix an unfortunate corner case where operators when creating custom smoosh channel may omit by mistake some of the built-in channel like view upgrades. To improve the behavior, make built-in channel always available. Channels configured by the user are always appended to the list of built-in ones. The new validate_arg/1 function is in charge of validation smoosh's input args. It centralizes ?b2l, ?l2b and other transforms and checks in one place so they are not sprinkled throughout the internal functions. After the validation steps all the db and view names are guaranteed to be binaries. The most significant changes is probably addition of extra tests to get to 100% test coverage. Since we cover all of the cases in time window checking, remove the more heavyweight elixir test, since we now get a nice coverage report in Erlang and the test is overall smaller as well.
-rw-r--r--src/smoosh/src/smoosh_utils.erl280
-rw-r--r--src/smoosh/test/exunit/scheduling_window_test.exs79
-rw-r--r--src/smoosh/test/exunit/test_helper.exs2
3 files changed, 217 insertions, 144 deletions
diff --git a/src/smoosh/src/smoosh_utils.erl b/src/smoosh/src/smoosh_utils.erl
index 1942aebf4..9a2f59086 100644
--- a/src/smoosh/src/smoosh_utils.erl
+++ b/src/smoosh/src/smoosh_utils.erl
@@ -14,8 +14,20 @@
-include_lib("couch/include/couch_db.hrl").
-export([get/2, get/3, split/1, stringify/1, ignore_db/1]).
--export([in_allowed_window/1, is_view_channel/1, write_to_file/3]).
+-export([db_channels/0, view_channels/0, cleanup_channels/0]).
+-export([in_allowed_window/1]).
+-export([concurrency/1, capacity/1]).
-export([log_level/2]).
+-export([validate_arg/1]).
+
+-define(BUILT_IN_DB_CHANNELS, "upgrade_dbs,ratio_dbs,slack_dbs").
+-define(BUILT_IN_VIEW_CHANNELS, "upgrade_views,ratio_views,slack_views").
+-define(BUILT_IN_CLEANUP_CHANNELS, "index_cleanup").
+
+-define(INDEX_CLEANUP, index_cleanup).
+
+-define(DEFAULT_CONCURRENCY, "1").
+-define(DEFAULT_CAPACITY, "9999").
get(Channel, Key) ->
?MODULE:get(Channel, Key, undefined).
@@ -23,14 +35,22 @@ get(Channel, Key) ->
get(Channel, Key, Default) ->
config:get("smoosh." ++ Channel, Key, Default).
+split(undefined) ->
+ [];
+split("") ->
+ [];
split(CSV) ->
re:split(CSV, "\\s*,\\s*", [{return, list}, trim]).
+stringify({?INDEX_CLEANUP, DbName}) ->
+ io_lib:format("~s index_cleanup", [DbName]);
stringify({DbName, GroupId}) ->
io_lib:format("~s ~s", [DbName, GroupId]);
stringify(DbName) ->
io_lib:format("~s", [DbName]).
+ignore_db({?INDEX_CLEANUP, DbName}) ->
+ ignore_db(DbName);
ignore_db({DbName, _GroupName}) ->
ignore_db(DbName);
ignore_db(DbName) when is_binary(DbName) ->
@@ -41,13 +61,11 @@ ignore_db(DbName) when is_list(DbName) ->
true;
_ ->
false
- end;
-ignore_db(Db) ->
- ignore_db(couch_db:name(Db)).
+ end.
in_allowed_window(Channel) ->
- From = parse_time(get(Channel, "from"), {00, 00}),
- To = parse_time(get(Channel, "to"), {24, 00}),
+ From = parse_time(?MODULE:get(Channel, "from"), {00, 00}),
+ To = parse_time(?MODULE:get(Channel, "to"), {24, 00}),
in_allowed_window(From, To).
in_allowed_window(From, To) ->
@@ -59,63 +77,6 @@ in_allowed_window(From, To) ->
({HH, MM} >= From) orelse ({HH, MM} < To)
end.
-is_view_channel(ChannelName) ->
- ViewChannels = smoosh_utils:split(
- config:get("smoosh", "view_channels", "upgrade_views,ratio_views,slack_views")
- ),
- lists:member(ChannelName, ViewChannels).
-
-file_delete(Path) ->
- case file:delete(Path) of
- % When deleting a state file, we do not care if it does not exist.
- Ret when Ret =:= ok; Ret =:= {error, enoent} ->
- ok;
- {error, Reason} ->
- {error, Reason}
- end.
-
-write_to_file(Content, FileName, VSN) ->
- Level = log_level("compaction_log_level", "debug"),
- couch_log:Level("~p Writing state ~s", [?MODULE, FileName]),
- OnDisk = <<VSN, (erlang:term_to_binary(Content, [compressed, {minor_version, 1}]))/binary>>,
- TmpFileName = FileName ++ ".tmp",
- case file_delete(TmpFileName) of
- ok ->
- case file:write_file(TmpFileName, OnDisk, [sync]) of
- ok ->
- case file_delete(FileName) of
- ok ->
- case file:rename(TmpFileName, FileName) of
- ok ->
- ok;
- {error, Reason} ->
- couch_log:error(
- "~p (~p) Error renaming temporary state file ~s to ~s", [
- ?MODULE, Reason, TmpFileName, FileName
- ]
- ),
- ok
- end;
- {error, Reason} ->
- couch_log:error("~p (~p) Error deleting state file ~s", [
- ?MODULE, Reason, FileName
- ]),
- ok
- end;
- {error, Reason} ->
- couch_log:error("~p (~p) Error writing state to temporary file ~s", [
- ?MODULE, Reason, TmpFileName
- ]),
- ok
- end;
- {error, Reason} ->
- % Failing to persist the queue should not crash smoosh. Return ok.
- couch_log:error("~p (~p) Error deleting temporary state file ~s", [
- ?MODULE, Reason, TmpFileName
- ]),
- ok
- end.
-
parse_time(undefined, Default) ->
Default;
parse_time(String, Default) ->
@@ -135,3 +96,196 @@ parse_time(String, Default) ->
log_level(Key, Default) when is_list(Key), is_list(Default) ->
list_to_existing_atom(config:get("smoosh", Key, Default)).
+
+db_channels() ->
+ ConfStr = config:get("smoosh", "db_channels"),
+ channel_list(ConfStr, ?BUILT_IN_DB_CHANNELS).
+
+view_channels() ->
+ Conf = config:get("smoosh", "view_channels"),
+ channel_list(Conf, ?BUILT_IN_VIEW_CHANNELS).
+
+cleanup_channels() ->
+ Conf = config:get("smoosh", "cleanup_channels"),
+ channel_list(Conf, ?BUILT_IN_CLEANUP_CHANNELS).
+
+channel_list(ConfStr, Default) ->
+ DefaultList = split(Default),
+ ConfList = split(ConfStr),
+ lists:usort(DefaultList ++ ConfList).
+
+concurrency(ChannelName) ->
+ list_to_integer(?MODULE:get(ChannelName, "concurrency", ?DEFAULT_CONCURRENCY)).
+
+capacity(ChannelName) ->
+ list_to_integer(?MODULE:get(ChannelName, "capacity", ?DEFAULT_CAPACITY)).
+
+% Validate enqueue arg at the front of the API instead of adding ?l2b and ?b2l
+% everywhere internally
+%
+validate_arg({?INDEX_CLEANUP, DbName}) when is_list(DbName) ->
+ validate_arg({?INDEX_CLEANUP, ?l2b(DbName)});
+validate_arg(DbName) when is_list(DbName) ->
+ validate_arg(?l2b(DbName));
+validate_arg({DbName, GroupId}) when is_list(DbName) ->
+ validate_arg({?l2b(DbName), GroupId});
+validate_arg({DbName, GroupId}) when is_list(GroupId) ->
+ validate_arg({DbName, ?l2b(GroupId)});
+validate_arg(DbName) when is_binary(DbName) ->
+ DbName;
+validate_arg({DbName, GroupId}) when is_binary(DbName), is_binary(GroupId) ->
+ {DbName, GroupId};
+validate_arg({?INDEX_CLEANUP, DbName}) when is_binary(DbName) ->
+ {?INDEX_CLEANUP, DbName};
+validate_arg(_) ->
+ error(invalid_smoosh_arg).
+
+-ifdef(TEST).
+
+-include_lib("couch/include/couch_eunit.hrl").
+
+smoosh_util_validate_test() ->
+ ?assertEqual(<<"x">>, validate_arg(<<"x">>)),
+ ?assertEqual(<<"x">>, validate_arg("x")),
+ ?assertEqual({<<"x">>, <<"y">>}, validate_arg({"x", "y"})),
+ ?assertEqual({<<"x">>, <<"y">>}, validate_arg({<<"x">>, "y"})),
+ ?assertEqual({<<"x">>, <<"y">>}, validate_arg({"x", <<"y">>})),
+ ?assertEqual({<<"x">>, <<"y">>}, validate_arg({<<"x">>, <<"y">>})),
+ ?assertEqual({?INDEX_CLEANUP, <<"x">>}, validate_arg({?INDEX_CLEANUP, "x"})),
+ ?assertEqual({?INDEX_CLEANUP, <<"x">>}, validate_arg({?INDEX_CLEANUP, <<"x">>})),
+ ?assertError(invalid_smoosh_arg, validate_arg(foo)),
+ ?assertError(invalid_smoosh_arg, validate_arg({foo, bar})),
+ ?assertError(invalid_smoosh_arg, validate_arg({?INDEX_CLEANUP, foo})).
+
+smoosh_utils_test_() ->
+ {
+ foreach,
+ fun() ->
+ meck:new(calendar, [passthrough, unstick]),
+ meck:expect(config, get, fun(_, _, Default) -> Default end)
+ end,
+ fun(_) ->
+ meck:unload()
+ end,
+ [
+ ?TDEF_FE(t_channel_list_default),
+ ?TDEF_FE(t_channel_list_configured),
+ ?TDEF_FE(t_capacity),
+ ?TDEF_FE(t_concurrency),
+ ?TDEF_FE(t_stringify),
+ ?TDEF_FE(t_ignore_db),
+ ?TDEF_FE(t_log_level),
+ ?TDEF_FE(t_allowed_window)
+ ]
+ }.
+
+t_channel_list_default(_) ->
+ ?assertEqual(
+ ["ratio_dbs", "slack_dbs", "upgrade_dbs"],
+ db_channels()
+ ),
+ ?assertEqual(
+ ["ratio_views", "slack_views", "upgrade_views"],
+ view_channels()
+ ),
+ ?assertEqual(
+ ["index_cleanup"],
+ cleanup_channels()
+ ).
+
+t_channel_list_configured(_) ->
+ meck_chans("db_channels", "foo_dbs"),
+ ?assertEqual(
+ ["foo_dbs", "ratio_dbs", "slack_dbs", "upgrade_dbs"],
+ db_channels()
+ ),
+ meck_chans("view_channels", "foo_views"),
+ ?assertEqual(
+ ["foo_views", "ratio_views", "slack_views", "upgrade_views"],
+ view_channels()
+ ),
+ meck_chans("cleanup_channels", "foo_cleanup"),
+ ?assertEqual(
+ ["foo_cleanup", "index_cleanup"],
+ cleanup_channels()
+ ),
+ meck_chans("db_channels", undefined),
+ ?assertEqual(
+ ["ratio_dbs", "slack_dbs", "upgrade_dbs"],
+ db_channels()
+ ),
+ meck_chans("db_channels", ""),
+ ?assertEqual(
+ ["ratio_dbs", "slack_dbs", "upgrade_dbs"],
+ db_channels()
+ ).
+
+t_capacity(_) ->
+ ?assertEqual(9999, capacity("foo")),
+ meck:expect(config, get, fun("smoosh.foo", "capacity", _) -> "2" end),
+ ?assertEqual(2, capacity("foo")).
+
+t_concurrency(_) ->
+ ?assertEqual(1, concurrency("foo")),
+ meck:expect(config, get, fun("smoosh.foo", "concurrency", _) -> "2" end),
+ ?assertEqual(2, concurrency("foo")).
+
+t_stringify(_) ->
+ Flat = fun(L) -> lists:flatten(L) end,
+ ?assertEqual("x index_cleanup", Flat(stringify({?INDEX_CLEANUP, <<"x">>}))),
+ ?assertEqual("x y", Flat(stringify({<<"x">>, <<"y">>}))),
+ ?assertEqual("x", Flat(stringify(<<"x">>))).
+
+t_ignore_db(_) ->
+ ?assertNot(ignore_db(<<"x">>)),
+ ?assertNot(ignore_db({<<"x">>, <<"z">>})),
+ ?assertNot(ignore_db({?INDEX_CLEANUP, <<"x">>})),
+ meck:expect(config, get, fun("smoosh.ignore", "x", _) -> "true" end),
+ ?assert(ignore_db(<<"x">>)),
+ ?assert(ignore_db({<<"x">>, <<"z">>})),
+ ?assert(ignore_db({?INDEX_CLEANUP, <<"x">>})).
+
+t_log_level(_) ->
+ ?assertEqual(notice, log_level("compaction_log", "notice")),
+ meck:expect(config, get, fun("smoosh", "compaction_log", _) -> "error" end),
+ ?assertEqual(error, log_level("compaction_log", "notice")).
+
+t_allowed_window(_) ->
+ ?assert(in_allowed_window("baz")),
+
+ meck_from_to("blah", undefined),
+ ?assert(in_allowed_window("foo")),
+
+ meck_from_to("X:Y", "9.5:Z"),
+ ?assert(in_allowed_window("foo")),
+
+ meck_time(3, 30),
+
+ meck_from_to("02:30", "04:30"),
+ ?assert(in_allowed_window("foo")),
+
+ meck_from_to("3:35", "3:50"),
+ ?assertNot(in_allowed_window("foo")),
+
+ meck_from_to("1:20", "3:29"),
+ ?assertNot(in_allowed_window("foo")),
+
+ meck_from_to("23:30", "3:52"),
+ ?assert(in_allowed_window("foo")),
+
+ meck_from_to("23:30", "3:29"),
+ ?assertNot(in_allowed_window("foo")).
+
+meck_time(H, M) ->
+ meck:expect(calendar, universal_time, 0, {{2000, 10, 10}, {H, M, 42}}).
+
+meck_from_to(From, To) ->
+ meck:expect(config, get, fun
+ ("smoosh.foo", "from", _) -> From;
+ ("smoosh.foo", "to", _) -> To
+ end).
+
+meck_chans(Type, Channels) ->
+ meck:expect(config, get, fun("smoosh", T, _) when T =:= Type -> Channels end).
+
+-endif.
diff --git a/src/smoosh/test/exunit/scheduling_window_test.exs b/src/smoosh/test/exunit/scheduling_window_test.exs
deleted file mode 100644
index 9da4a3150..000000000
--- a/src/smoosh/test/exunit/scheduling_window_test.exs
+++ /dev/null
@@ -1,79 +0,0 @@
-defmodule SmooshSchedulingWindowTest do
- use Couch.Test.ExUnit.Case
-
- setup_all(context) do
- test_ctx = :test_util.start_couch([])
-
- on_exit(fn ->
- :config.delete('smoosh.test_channel', 'from')
- :config.delete('smoosh.test_channel', 'to')
- :test_util.stop_couch(test_ctx)
- end)
-
- context
- end
-
- test "in_allowed_window returns true by default", _context do
- assert :smoosh_utils.in_allowed_window('nonexistent_channel') == true
- end
-
- test "in_allowed_window ignores bad input", _context do
- :config.set('smoosh.test_channel', 'from', 'midnight', false)
- :config.set('smoosh.test_channel', 'to', 'infinity', false)
- assert :smoosh_utils.in_allowed_window('test_channel') == true
- end
-
- test "in_allowed_window returns false when now < from < to", _context do
- now = DateTime.utc_now()
- from = DateTime.add(now, 18_000)
- to = DateTime.add(now, 36_000)
- :config.set('smoosh.test_channel', 'from', '#{from.hour}:#{from.minute}', false)
- :config.set('smoosh.test_channel', 'to', '#{to.hour}:#{to.minute}', false)
- assert :smoosh_utils.in_allowed_window('test_channel') == false
- end
-
- test "in_allowed_window returns true when from < now < to", _context do
- now = DateTime.utc_now()
- from = DateTime.add(now, -18_000)
- to = DateTime.add(now, 18_000)
- :config.set('smoosh.test_channel', 'from', '#{from.hour}:#{from.minute}', false)
- :config.set('smoosh.test_channel', 'to', '#{to.hour}:#{to.minute}', false)
- assert :smoosh_utils.in_allowed_window('test_channel') == true
- end
-
- test "in_allowed_window returns false when from < to < now", _context do
- now = DateTime.utc_now()
- from = DateTime.add(now, -36_000)
- to = DateTime.add(now, -18_000)
- :config.set('smoosh.test_channel', 'from', '#{from.hour}:#{from.minute}', false)
- :config.set('smoosh.test_channel', 'to', '#{to.hour}:#{to.minute}', false)
- assert :smoosh_utils.in_allowed_window('test_channel') == false
- end
-
- test "in_allowed_window returns true when to < from < now", _context do
- now = DateTime.utc_now()
- from = DateTime.add(now, -18_000)
- to = DateTime.add(now, -36_000)
- :config.set('smoosh.test_channel', 'from', '#{from.hour}:#{from.minute}', false)
- :config.set('smoosh.test_channel', 'to', '#{to.hour}:#{to.minute}', false)
- assert :smoosh_utils.in_allowed_window('test_channel') == true
- end
-
- test "in_allowed_window returns false when to < now < from", _context do
- now = DateTime.utc_now()
- from = DateTime.add(now, 18_000)
- to = DateTime.add(now, -18_000)
- :config.set('smoosh.test_channel', 'from', '#{from.hour}:#{from.minute}', false)
- :config.set('smoosh.test_channel', 'to', '#{to.hour}:#{to.minute}', false)
- assert :smoosh_utils.in_allowed_window('test_channel') == false
- end
-
- test "in_allowed_window returns true when now < to < from", _context do
- now = DateTime.utc_now()
- from = DateTime.add(now, 36_000)
- to = DateTime.add(now, 18_000)
- :config.set('smoosh.test_channel', 'from', '#{from.hour}:#{from.minute}', false)
- :config.set('smoosh.test_channel', 'to', '#{to.hour}:#{to.minute}', false)
- assert :smoosh_utils.in_allowed_window('test_channel') == true
- end
-end
diff --git a/src/smoosh/test/exunit/test_helper.exs b/src/smoosh/test/exunit/test_helper.exs
deleted file mode 100644
index 314050085..000000000
--- a/src/smoosh/test/exunit/test_helper.exs
+++ /dev/null
@@ -1,2 +0,0 @@
-ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter])
-ExUnit.start()