blob: 14b322008938a3af3cfbd704d43b3ab13b84e99e [file] [log] [blame]
% 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(smoosh_utils).
-include_lib("couch/include/couch_db.hrl").
-export([get/2, get/3, split/1, stringify/1, ignore_db/1]).
-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).
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) ->
ignore_db(?b2l(DbName));
ignore_db(DbName) when is_list(DbName) ->
case config:get("smoosh.ignore", DbName, false) of
"true" ->
true;
_ ->
false
end.
in_allowed_window(Channel) ->
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) ->
{_, {HH, MM, _}} = calendar:universal_time(),
case From < To of
true ->
({HH, MM} >= From) andalso ({HH, MM} < To);
false ->
({HH, MM} >= From) orelse ({HH, MM} < To)
end.
parse_time(undefined, Default) ->
Default;
parse_time(String, Default) ->
case string:tokens(String, ":") of
[HH, MM] ->
try
{list_to_integer(HH), list_to_integer(MM)}
catch
error:badarg ->
couch_log:error("Malformed compaction schedule configuration: ~s", [String]),
Default
end;
_Else ->
couch_log:error("Malformed compaction schedule configuration: ~s", [String]),
Default
end.
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]),
test_util:mock(config)
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.