summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Kocoloski <kocolosk@apache.org>2021-03-31 10:20:05 -0400
committerGitHub <noreply@github.com>2021-03-31 10:20:05 -0400
commit776f920021dd15c51355e9a904bdc5a2250b19d9 (patch)
treefb39025ff5415ad491565af23354ae68f1474eee
parent371a763b8b16642996cceb9542b82595b65fd057 (diff)
downloadcouchdb-776f920021dd15c51355e9a904bdc5a2250b19d9.tar.gz
Improve search for FDB cluster files (#3468)
This patch introduces a handful of related improvments to the code that chooses the FoundationDB cluster file. We now check for a cluster file in multiple locations, in priority order: 1. The value of `[erlfdb] cluster_file` in the server config 2. The value of the FDB_CLUSTER_FILE environment variable 3. The default platform-dependent location(s) Unix-y systems: /usr/local/etc/foundationdb/fdb.cluster (MacOS package default) /etc/foundationdb/fdb.cluster (Linux packages default) Windows: C:/ProgramData/foundationdb/fdb.cluster We also check that the file has appropriate access permissions, namely that is RW accessible by the user running CouchDB. If the file is read-only we emit a warning but allow the startup to proceed. Finally, we add some more detailed logging to help users debug any problems in this department on startup. If a location is specified using either one of the first two "custom" approaches and there is a problem with that location, CouchDB will abort rather than try any further option. This avoids a situation where a user intended to override the default cluster file but CouchDB starts up with the default one because e.g. the user failed to actually install the new file with the correct permissions.
-rw-r--r--src/fabric/src/fabric2_server.erl150
1 files changed, 147 insertions, 3 deletions
diff --git a/src/fabric/src/fabric2_server.erl b/src/fabric/src/fabric2_server.erl
index be674b10e..e427f2013 100644
--- a/src/fabric/src/fabric2_server.erl
+++ b/src/fabric/src/fabric2_server.erl
@@ -42,9 +42,11 @@
-include_lib("couch/include/couch_db.hrl").
+-include_lib("kernel/include/file.hrl").
-
--define(CLUSTER_FILE, "/usr/local/etc/foundationdb/fdb.cluster").
+-define(CLUSTER_FILE_MACOS, "/usr/local/etc/foundationdb/fdb.cluster").
+-define(CLUSTER_FILE_LINUX, "/etc/foundationdb/fdb.cluster").
+-define(CLUSTER_FILE_WIN32, "C:/ProgramData/foundationdb/fdb.cluster").
-define(FDB_DIRECTORY, fdb_directory).
-define(FDB_CLUSTER, fdb_cluster).
-define(DEFAULT_FDB_DIRECTORY, <<"couchdb">>).
@@ -212,7 +214,7 @@ get_db_and_cluster(EunitDbOpts) ->
{ok, true} ->
{<<"eunit_test">>, erlfdb_util:get_test_db(EunitDbOpts)};
undefined ->
- ClusterFileStr = config:get("erlfdb", "cluster_file", ?CLUSTER_FILE),
+ ClusterFileStr = get_cluster_file_path(),
{ok, ConnectionStr} = file:read_file(ClusterFileStr),
DbHandle = erlfdb:open(iolist_to_binary(ClusterFileStr)),
{string:trim(ConnectionStr), DbHandle}
@@ -220,6 +222,79 @@ get_db_and_cluster(EunitDbOpts) ->
apply_tx_options(Db, config:get(?TX_OPTIONS_SECTION)),
{Cluster, Db}.
+get_cluster_file_path() ->
+ Locations = [
+ {custom, config:get("erlfdb", "cluster_file")},
+ {custom, os:getenv("FDB_CLUSTER_FILE", undefined)}
+ ] ++ default_locations(os:type()),
+ case find_cluster_file(Locations) of
+ {ok, Location} ->
+ Location;
+ {error, Reason} ->
+ erlang:error(Reason)
+ end.
+
+
+default_locations({unix, _}) ->
+ [
+ {default, ?CLUSTER_FILE_MACOS},
+ {default, ?CLUSTER_FILE_LINUX}
+ ];
+
+default_locations({win32, _}) ->
+ [
+ {default, ?CLUSTER_FILE_WIN32}
+ ].
+
+
+find_cluster_file([]) ->
+ {error, cluster_file_missing};
+
+find_cluster_file([{custom, undefined} | Rest]) ->
+ find_cluster_file(Rest);
+
+find_cluster_file([{Type, Location} | Rest]) ->
+ case file:read_file_info(Location, [posix]) of
+ {ok, #file_info{access = read_write}} ->
+ couch_log:info(
+ "Using ~s FDB cluster file: ~s",
+ [Type, Location]
+ ),
+ {ok, Location};
+ {ok, #file_info{access = read}} ->
+ couch_log:warning(
+ "Using read-only ~s FDB cluster file: ~s -- if coordinators "
+ "are changed without updating this file CouchDB may be unable "
+ "to connect to the FDB cluster!",
+ [Type, Location]
+ ),
+ {ok, Location};
+ {ok, _} ->
+ couch_log:error(
+ "CouchDB needs read/write access to FDB cluster file: ~s",
+ [Location]
+ ),
+ {error, cluster_file_permissions};
+ {error, Reason} when Type =:= custom ->
+ couch_log:error(
+ "Encountered ~p error looking for FDB cluster file: ~s",
+ [Reason, Location]
+ ),
+ {error, Reason};
+ {error, enoent} when Type =:= default ->
+ couch_log:info(
+ "No FDB cluster file found at ~s",
+ [Location]
+ ),
+ find_cluster_file(Rest);
+ {error, Reason} when Type =:= default ->
+ couch_log:warning(
+ "Encountered ~p error looking for FDB cluster file: ~s",
+ [Reason, Location]
+ ),
+ find_cluster_file(Rest)
+ end.
+
apply_tx_options(Db, Cfg) ->
maps:map(fun(Option, {Type, Default}) ->
@@ -274,3 +349,72 @@ sanitize(#{} = Db) ->
security_fun := undefined,
interactive := false
}.
+
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+setup() ->
+ meck:new(file, [unstick, passthrough]),
+ meck:expect(file, read_file_info, fun
+ ("ok.cluster", _) ->
+ {ok, #file_info{access = read_write}};
+ ("readonly.cluster", _) ->
+ {ok, #file_info{access = read}};
+ ("noaccess.cluster", _) ->
+ {ok, #file_info{access = none}};
+ ("missing.cluster", _) ->
+ {error, enoent};
+ (Path, Options) ->
+ meck:passthrough([Path, Options])
+ end).
+
+teardown(_) ->
+ meck:unload().
+
+find_cluster_file_test_() ->
+ {setup,
+ fun setup/0,
+ fun teardown/1,
+ [
+ {"ignore unspecified config", ?_assertEqual(
+ {ok, "ok.cluster"},
+ find_cluster_file([
+ {custom, undefined},
+ {custom, "ok.cluster"}
+ ])
+ )},
+
+ {"allow read-only file", ?_assertEqual(
+ {ok, "readonly.cluster"},
+ find_cluster_file([
+ {custom, "readonly.cluster"}
+ ])
+ )},
+
+ {"fail if no access to configured cluster file", ?_assertEqual(
+ {error, cluster_file_permissions},
+ find_cluster_file([
+ {custom, "noaccess.cluster"}
+ ])
+ )},
+
+ {"fail if configured cluster file is missing", ?_assertEqual(
+ {error, enoent},
+ find_cluster_file([
+ {custom, "missing.cluster"},
+ {default, "ok.cluster"}
+ ])
+ )},
+
+ {"check multiple default locations", ?_assertEqual(
+ {ok, "ok.cluster"},
+ find_cluster_file([
+ {default, "missing.cluster"},
+ {default, "ok.cluster"}
+ ])
+ )}
+ ]
+ }.
+
+-endif.