diff options
Diffstat (limited to 'src/setup/src/setup.erl')
-rw-r--r-- | src/setup/src/setup.erl | 395 |
1 files changed, 0 insertions, 395 deletions
diff --git a/src/setup/src/setup.erl b/src/setup/src/setup.erl deleted file mode 100644 index 1757a43e7..000000000 --- a/src/setup/src/setup.erl +++ /dev/null @@ -1,395 +0,0 @@ -% 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(setup). - --export([enable_cluster/1, finish_cluster/1, add_node/1, receive_cookie/1]). --export([is_cluster_enabled/0, has_cluster_system_dbs/1, cluster_system_dbs/0]). --export([enable_single_node/1, is_single_node_enabled/1]). - --include_lib("../couch/include/couch_db.hrl"). - -require_admins(undefined, {undefined, undefined}) -> - % no admin in CouchDB, no admin in request - throw({error, "Cluster setup requires admin account to be configured"}); -require_admins(_, _) -> - ok. - -require_node_count(undefined) -> - throw({error, "Cluster setup requires node_count to be configured"}); -require_node_count(_) -> - ok. - -error_local_bind_address() -> - throw({error, "Cluster setup requires a remote bind_address (not 127.0.0.1 nor ::1)"}). - -error_invalid_bind_address(InvalidBindAddress) -> - throw( - {error, - io:format( - "Setup requires a valid IP bind_address. " ++ - "~p is invalid.", - [InvalidBindAddress] - )} - ). - -require_remote_bind_address(OldBindAddress, NewBindAddress) -> - case {OldBindAddress, NewBindAddress} of - {"127.0.0.1", undefined} -> error_local_bind_address(); - {_, <<"127.0.0.1">>} -> error_local_bind_address(); - {"::1", undefined} -> error_local_bind_address(); - {_, <<"::1">>} -> error_local_bind_address(); - {_, undefined} -> ok; - {_, PresentNewBindAddress} -> require_valid_bind_address(PresentNewBindAddress) - end. - -require_valid_bind_address(BindAddress) -> - ListBindAddress = binary_to_list(BindAddress), - case inet_parse:address(ListBindAddress) of - {ok, _} -> ok; - {error, _} -> error_invalid_bind_address(ListBindAddress) - end. - -is_cluster_enabled() -> - % bind_address != 127.0.0.1 AND admins != empty - BindAddress = config:get("chttpd", "bind_address"), - Admins = config:get("admins"), - case {BindAddress, Admins} of - {"127.0.0.1", _} -> false; - {_, []} -> false; - {_, _} -> true - end. - -is_single_node_enabled(Dbs) -> - % admins != empty AND dbs exist - Admins = config:get("admins"), - HasDbs = has_cluster_system_dbs(Dbs), - case {Admins, HasDbs} of - {[], _} -> false; - {_, false} -> false; - {_, _} -> true - end. - -cluster_system_dbs() -> - ["_users", "_replicator"]. - -has_cluster_system_dbs([]) -> - true; -has_cluster_system_dbs([Db | Dbs]) -> - case catch fabric:get_db_info(Db) of - {ok, _} -> has_cluster_system_dbs(Dbs); - _ -> false - end. - -enable_cluster(Options) -> - case couch_util:get_value(remote_node, Options, undefined) of - undefined -> - enable_cluster_int(Options, is_cluster_enabled()); - _ -> - enable_cluster_http(Options) - end. - -get_remote_request_options(Options) -> - case couch_util:get_value(remote_current_user, Options, undefined) of - undefined -> - []; - _ -> - [ - {basic_auth, { - binary_to_list(couch_util:get_value(remote_current_user, Options)), - binary_to_list(couch_util:get_value(remote_current_password, Options)) - }} - ] - end. - -enable_cluster_http(Options) -> - % POST to nodeB/_setup - RequestOptions = get_remote_request_options(Options), - AdminUsername = couch_util:get_value(username, Options), - AdminPasswordHash = config:get("admins", binary_to_list(AdminUsername)), - - Body = ?JSON_ENCODE( - {[ - {<<"action">>, <<"enable_cluster">>}, - {<<"username">>, AdminUsername}, - {<<"password_hash">>, ?l2b(AdminPasswordHash)}, - {<<"bind_address">>, couch_util:get_value(bind_address, Options)}, - {<<"port">>, couch_util:get_value(port, Options)}, - {<<"node_count">>, couch_util:get_value(node_count, Options)} - ]} - ), - - Headers = [ - {"Content-Type", "application/json"} - ], - - RemoteNode = couch_util:get_value(remote_node, Options), - Port = get_port(couch_util:get_value(port, Options, 5984)), - - Url = binary_to_list(<<"http://", RemoteNode/binary, ":", Port/binary, "/_cluster_setup">>), - - case ibrowse:send_req(Url, Headers, post, Body, RequestOptions) of - {ok, "201", _, _} -> - ok; - Else -> - {error, Else} - end. - -enable_cluster_int(_Options, true) -> - {error, cluster_enabled}; -enable_cluster_int(Options, false) -> - % if no admin in config and no admin in req -> error - CurrentAdmins = config:get("admins"), - NewCredentials = { - proplists:get_value(username, Options), - case proplists:get_value(password_hash, Options) of - undefined -> proplists:get_value(password, Options); - Pw -> Pw - end - }, - ok = require_admins(CurrentAdmins, NewCredentials), - % if bind_address == 127.0.0.1 and no bind_address in req -> error - CurrentBindAddress = config:get("chttpd", "bind_address"), - NewBindAddress = proplists:get_value(bind_address, Options), - ok = require_remote_bind_address(CurrentBindAddress, NewBindAddress), - NodeCount = couch_util:get_value(node_count, Options), - ok = require_node_count(NodeCount), - Port = proplists:get_value(port, Options), - - setup_node(NewCredentials, NewBindAddress, NodeCount, Port), - couch_log:debug("Enable Cluster: ~p~n", [Options]). - -set_admin(Username, Password) -> - config:set("admins", binary_to_list(Username), binary_to_list(Password), #{sensitive => true}). - -setup_node(NewCredentials, NewBindAddress, NodeCount, Port) -> - case NewCredentials of - {undefined, undefined} -> - ok; - {Username, Password} -> - set_admin(Username, Password) - end, - - ok = require_valid_bind_address(NewBindAddress), - case NewBindAddress of - undefined -> - config:set("chttpd", "bind_address", "0.0.0.0"); - NewBindAddress -> - config:set("chttpd", "bind_address", binary_to_list(NewBindAddress)) - end, - - % for single node setups, set n=1, for larger setups, don’t - % exceed n=3 as a default - config:set_integer("cluster", "n", min(NodeCount, 3)), - - case Port of - undefined -> - ok; - Port when is_binary(Port) -> - config:set("chttpd", "port", binary_to_list(Port)); - Port when is_integer(Port) -> - config:set_integer("chttpd", "port", Port) - end. - -finish_cluster(Options) -> - % ensure that uuid is set - couch_server:get_uuid(), - - ok = wait_connected(), - ok = sync_admins(), - ok = sync_uuid(), - ok = sync_auth_secret(), - Dbs = proplists:get_value(ensure_dbs_exist, Options, cluster_system_dbs()), - finish_cluster_int(Dbs, has_cluster_system_dbs(Dbs)). - -wait_connected() -> - Nodes = other_nodes(), - Result = test_util:wait(fun() -> - case disconnected(Nodes) of - [] -> ok; - _ -> wait - end - end), - case Result of - timeout -> - Reason = "Cluster setup timed out waiting for nodes to connect", - throw({setup_error, Reason}); - ok -> - ok - end. - -other_nodes() -> - mem3:nodes() -- [node()]. - -disconnected(Nodes) -> - lists:filter( - fun(Node) -> - case net_adm:ping(Node) of - pong -> false; - pang -> true - end - end, - Nodes - ). - -sync_admins() -> - ok = lists:foreach( - fun({User, Pass}) -> - sync_admin(User, Pass) - end, - config:get("admins") - ). - -sync_admin(User, Pass) -> - sync_config("admins", User, Pass). - -sync_uuid() -> - Uuid = config:get("couchdb", "uuid"), - sync_config("couchdb", "uuid", Uuid). - -sync_auth_secret() -> - Secret = config:get("chttpd_auth", "secret"), - sync_config("chttpd_auth", "secret", Secret). - -sync_config(Section, Key, Value) -> - {Results, Errors} = rpc:multicall( - other_nodes(), - config, - set, - [Section, Key, Value] - ), - case validate_multicall(Results, Errors) of - ok -> - ok; - error -> - couch_log:error( - "~p sync_admin results ~p errors ~p", - [?MODULE, Results, Errors] - ), - Reason = "Cluster setup unable to sync admin passwords", - throw({setup_error, Reason}) - end. - -validate_multicall(Results, Errors) -> - AllOk = lists:all( - fun - (ok) -> true; - (_) -> false - end, - Results - ), - case AllOk andalso Errors == [] of - true -> - ok; - false -> - error - end. - -finish_cluster_int(_Dbs, true) -> - {error, cluster_finished}; -finish_cluster_int(Dbs, false) -> - lists:foreach(fun fabric:create_db/1, Dbs). - -enable_single_node(Options) -> - % if no admin in config and no admin in req -> error - CurrentAdmins = config:get("admins"), - NewCredentials = { - proplists:get_value(username, Options), - case proplists:get_value(password_hash, Options) of - undefined -> proplists:get_value(password, Options); - Pw -> Pw - end - }, - ok = require_admins(CurrentAdmins, NewCredentials), - % skip bind_address validation, anything is fine - NewBindAddress = proplists:get_value(bind_address, Options), - Port = proplists:get_value(port, Options), - - setup_node(NewCredentials, NewBindAddress, 1, Port), - Dbs = proplists:get_value(ensure_dbs_exist, Options, cluster_system_dbs()), - finish_cluster_int(Dbs, has_cluster_system_dbs(Dbs)), - couch_log:debug("Enable Single Node: ~p~n", [Options]). - -add_node(Options) -> - add_node_int(Options, is_cluster_enabled()). - -add_node_int(_Options, false) -> - {error, cluster_not_enabled}; -add_node_int(Options, true) -> - couch_log:debug("add node_int: ~p~n", [Options]), - ErlangCookie = erlang:get_cookie(), - - % POST to nodeB/_setup - RequestOptions = [ - {basic_auth, { - binary_to_list(proplists:get_value(username, Options)), - binary_to_list(proplists:get_value(password, Options)) - }} - ], - - Body = ?JSON_ENCODE( - {[ - {<<"action">>, <<"receive_cookie">>}, - {<<"cookie">>, atom_to_binary(ErlangCookie, utf8)} - ]} - ), - - Headers = [ - {"Content-Type", "application/json"} - ], - - Host = proplists:get_value(host, Options), - Port = get_port(proplists:get_value(port, Options, 5984)), - Name = proplists:get_value(name, Options, get_default_name(Port)), - - Url = binary_to_list(<<"http://", Host/binary, ":", Port/binary, "/_cluster_setup">>), - - case ibrowse:send_req(Url, Headers, post, Body, RequestOptions) of - {ok, "201", _, _} -> - % when done, PUT :5986/nodes/nodeB - create_node_doc(Host, Name); - Else -> - Else - end. - -get_port(Port) when is_integer(Port) -> - list_to_binary(integer_to_list(Port)); -get_port(Port) when is_list(Port) -> - list_to_binary(Port); -get_port(Port) when is_binary(Port) -> - Port. - -create_node_doc(Host, Name) -> - {ok, Db} = couch_db:open_int(<<"_nodes">>, []), - Doc = {[{<<"_id">>, <<Name/binary, "@", Host/binary>>}]}, - Options = [], - CouchDoc = couch_doc:from_json_obj(Doc), - - couch_db:update_doc(Db, CouchDoc, Options). - -get_default_name(Port) -> - case Port of - % shortcut for easier development - <<"15984">> -> - <<"node1">>; - <<"25984">> -> - <<"node2">>; - <<"35984">> -> - <<"node3">>; - % by default, all nodes have the user `couchdb` - _ -> - <<"couchdb">> - end. - -receive_cookie(Options) -> - Cookie = proplists:get_value(cookie, Options), - erlang:set_cookie(node(), binary_to_atom(Cookie, latin1)). |