diff options
-rw-r--r-- | README.md | 98 | ||||
-rw-r--r-- | src/setup.erl | 21 | ||||
-rw-r--r-- | src/setup_httpd.erl | 6 | ||||
-rwxr-xr-x | test/t-frontend-setup.sh | 9 | ||||
-rwxr-xr-x | test/t.sh | 9 |
5 files changed, 82 insertions, 61 deletions
@@ -32,6 +32,14 @@ From here on, there are two paths, one is via Fauxton (a) the other is using a HTTP endpoint (b). Fauxton just uses the HTTP endpoint in (b). (b) can be used to set up a cluster programmatically. +When using (b) you POST HTTP requests with a JSON request body (the request content type has to be set to application/json). + +If you have already setup a server admin account, you might need to pass the credentials to the HTTP calls using HTTP basic authentication. +Alternativaly, if you use the cURL command you can can add username and password inline, like so: + +``` +curl -X PUT "http://admin:password@127.0.0.1:5984/mydb" +``` 2.a. Go to Fauxton. There is a “Cluster Setup” tab in the sidebar. Go to the tab and get presented with a form that asks you to enter an admin @@ -43,23 +51,21 @@ is no need to ask for admin credentials here. If the bind_address is != 127.0.0.1, we can skip this entirely and Fauxton can show the add_node UI right away. -- POST to /_setup with +- POST a JSON entity to /_cluster_setup, the entity looks like: ``` - { - "action": "enable_cluster", - "admin": { - "user": "username", - "pass": "password" - }, - ["bind_address": "xxxx",] - ["port": yyyy] - } +{ + "action":"enable_cluster", + "username":"username", + "password":"password", + "bind_address":"0.0.0.0", + "port": 5984 +} ``` This sets up the admin user on the current node and binds to 0.0.0.0:5984 or the specified ip:port. Logs admin user into Fauxton automatically. -2.b. POST to /_setup as shown above. +2.b. POST to /_cluster_setup as shown above. Repeat on all nodes. - keep the same username/password everywhere. @@ -75,23 +81,21 @@ Repeat on all nodes. a. Go to Fauxton / Cluster Setup, once we have enabled the cluster, the UI shows an “Add Node” interface with the fields admin, and node: -- POST to /_setup with +- POST a JSON entity to /_cluster_setup, the entity looks like: ``` - { - "action": "add_node", - "admin": { // should be auto-filled from Fauxton, store plaintext PW in - // localStorage until we finish_cluster or timeout. - "user": "username", - "pass": "password" - }, - "node": { - "host": "hostname", - ["port": 5984] - } - } +{ + "action":"add_node", + "username":"username", + "password":"password", + "host":"192.168.1.100", + ["port": 5984], + "name": "node1" // as in “node1@hostname”, same as in vm.args +} ``` -b. as in a, but without the Fauxton bits, just POST to /_setup +In the example above, this adds the node with IP address 192.168.1.100 to the cluster. + +b. as in a, but without the Fauxton bits, just POST to /_cluster_setup - this request will do this: - on the “setup coordination node”: - check if we have an Erlang Cookie Secret. If not, generate @@ -99,7 +103,7 @@ b. as in a, but without the Fauxton bits, just POST to /_setup - store the cookie in config.ini, re-set_cookie() on startup. - make a POST request to the node specified in the body above using the admin credentials in the body above: - POST to http://username:password@node_b:5984/_setup with: + POST to http://username:password@node_b:5984/_cluster_setup with: ``` { "action": "receive_cookie", @@ -119,10 +123,10 @@ b. as in a, but without the Fauxton bits, just POST to /_setup 4.a. When all nodes are added, click the [Finish Cluster Setup] button in Fauxton. -- this does POST /_setup +- this does POST /_cluster_setup ``` { - "action": "finish_setup" + "action": "finish_cluster" } ``` @@ -144,41 +148,41 @@ This is right after starting a node for the first time, and any time before the cluster is enabled as outlined above. ``` -GET /_setup +GET /_cluster_setup {"state": "cluster_disabled"} -POST /_setup {"action":"enable_cluster"...} -> Transition to State 2 -POST /_setup {"action":"enable_cluster"...} with empty admin user/pass or invalid host/post or host/port not available -> Error -POST /_setup {"action":"anything_but_enable_cluster"...} -> Error +POST /_cluster_setup {"action":"enable_cluster"...} -> Transition to State 2 +POST /_cluster_setup {"action":"enable_cluster"...} with empty admin user/pass or invalid host/post or host/port not available -> Error +POST /_cluster_setup {"action":"anything_but_enable_cluster"...} -> Error ``` ### State 2: Cluster enabled, admin user set, waiting for nodes to be added. ``` -GET /_setup +GET /_cluster_setup {"state":"cluster_enabled","nodes":[]} -POST /_setup {"action":"enable_cluster"...} -> Error -POST /_setup {"action":"add_node"...} -> Stay in State 2, but return "nodes":["node B"}] on GET -POST /_setup {"action":"add_node"...} -> if target node not available, Error -POST /_setup {"action":"finish_cluster"} with no nodes set up -> Error -POST /_setup {"action":"finish_cluster"} -> Transition to State 3 -POST /_setup {"action":"delete_node"...} -> Stay in State 2, but delete node from /nodes, reflect the change in GET /_setup -POST /_setup {"action":"delete_node","node":"unknown"} -> Error Unknown Node +POST /_cluster_setup {"action":"enable_cluster"...} -> Error +POST /_cluster_setup {"action":"add_node"...} -> Stay in State 2, but return "nodes":["node B"}] on GET +POST /_cluster_setup {"action":"add_node"...} -> if target node not available, Error +POST /_cluster_setup {"action":"finish_cluster"} with no nodes set up -> Error +POST /_cluster_setup {"action":"finish_cluster"} -> Transition to State 3 +POST /_cluster_setup {"action":"delete_node"...} -> Stay in State 2, but delete node from /nodes, reflect the change in GET /_cluster_setup +POST /_cluster_setup {"action":"delete_node","node":"unknown"} -> Error Unknown Node ``` ### State 3: Cluster set up, all nodes operational ``` -GET /_setup +GET /_cluster_setup {"state":"cluster_finished","nodes":["node a", "node b", ...]} -POST /_setup {"action":"enable_cluster"...} -> Error -POST /_setup {"action":"finish_cluster"...} -> Stay in State 3, do nothing -POST /_setup {"action":"add_node"...} -> Error -POST /_setup?i_know_what_i_am_doing=true {"action":"add_node"...} -> Add node, stay in State 3. -POST /_setup {"action":"delete_node"...} -> Stay in State 3, but delete node from /nodes, reflect the change in GET /_setup -POST /_setup {"action":"delete_node","node":"unknown"} -> Error Unknown Node +POST /_cluster_setup {"action":"enable_cluster"...} -> Error +POST /_cluster_setup {"action":"finish_cluster"...} -> Stay in State 3, do nothing +POST /_cluster_setup {"action":"add_node"...} -> Error +POST /_cluster_setup?i_know_what_i_am_doing=true {"action":"add_node"...} -> Add node, stay in State 3. +POST /_cluster_setup {"action":"delete_node"...} -> Stay in State 3, but delete node from /nodes, reflect the change in GET /_cluster_setup +POST /_cluster_setup {"action":"delete_node","node":"unknown"} -> Error Unknown Node ``` // TBD: we need to persist the setup state somewhere. diff --git a/src/setup.erl b/src/setup.erl index 6657073ce..5a7100491 100644 --- a/src/setup.erl +++ b/src/setup.erl @@ -24,6 +24,11 @@ require_admins(undefined, {undefined, undefined}) -> require_admins(_,_) -> ok. +require_node_count(undefined) -> + throw({error, "Cluster setup requires node_count to be configured"}); +require_node_count(_) -> + ok. + error_bind_address() -> throw({error, "Cluster setup requires bind_addres != 127.0.0.1"}). @@ -93,7 +98,8 @@ enable_cluster_http(Options) -> {<<"username">>, AdminUsername}, {<<"password_hash">>, ?l2b(AdminPasswordHash)}, {<<"bind_address">>, couch_util:get_value(bind_address, Options)}, - {<<"port">>, couch_util:get_value(port, Options)} + {<<"port">>, couch_util:get_value(port, Options)}, + {<<"node_count">>, couch_util:get_value(node_count, Options)} ]}), Headers = [ @@ -147,6 +153,10 @@ enable_cluster_int(Options, no) -> config:set("chttpd", "bind_address", binary_to_list(NewBindAddress)) end, + NodeCount = couch_util:get_value(node_count, Options), + ok = require_node_count(NodeCount), + config:set_integer("cluster", "n", NodeCount), + Port = proplists:get_value(port, Options), case Port of undefined -> @@ -157,7 +167,6 @@ enable_cluster_int(Options, no) -> config:set_integer("chttpd", "port", Port) end, couch_log:notice("Enable Cluster: ~p~n", [Options]). - %cluster_state:set(enabled). set_admin(Username, Password) -> config:set("admins", binary_to_list(Username), binary_to_list(Password)). @@ -199,13 +208,14 @@ add_node_int(Options, ok) -> 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, Port); + create_node_doc(Host, Name); Else -> couch_log:notice("send_req: ~p~n", [Else]), Else @@ -218,16 +228,15 @@ get_port(Port) when is_list(Port) -> get_port(Port) when is_binary(Port) -> Port. -create_node_doc(Host, Port) -> +create_node_doc(Host, Name) -> {ok, Db} = couch_db:open_int(<<"_nodes">>, []), - Name = get_name(Port), Doc = {[{<<"_id">>, <<Name/binary, "@", Host/binary>>}]}, Options = [], CouchDoc = couch_doc:from_json_obj(Doc), couch_db:update_doc(Db, CouchDoc, Options). -get_name(Port) -> +get_default_name(Port) -> case Port of % shortcut for easier development <<"15984">> -> diff --git a/src/setup_httpd.erl b/src/setup_httpd.erl index 910fcb78d..a23a3e21d 100644 --- a/src/setup_httpd.erl +++ b/src/setup_httpd.erl @@ -62,7 +62,8 @@ handle_action("enable_cluster", Setup) -> {port, <<"port">>}, {remote_node, <<"remote_node">>}, {remote_current_user, <<"remote_current_user">>}, - {remote_current_password, <<"remote_current_password">>} + {remote_current_password, <<"remote_current_password">>}, + {node_count, <<"node_count">>} ], Setup), case setup:enable_cluster(Options) of {error, cluster_enabled} -> @@ -88,7 +89,8 @@ handle_action("add_node", Setup) -> {username, <<"username">>}, {password, <<"password">>}, {host, <<"host">>}, - {port, <<"port">>} + {port, <<"port">>}, + {name, <<"name">>} ], Setup), case setup:add_node(Options) of {error, cluster_not_enabled} -> diff --git a/test/t-frontend-setup.sh b/test/t-frontend-setup.sh index 1c610b6cc..52056a374 100755 --- a/test/t-frontend-setup.sh +++ b/test/t-frontend-setup.sh @@ -16,13 +16,13 @@ HEADERS="-HContent-Type:application/json" curl a:b@127.0.0.1:15986/_nodes/_all_docs # Enable Cluster on node A -curl a:b@127.0.0.1:15984/_cluster_setup -d '{"action":"enable_cluster","username":"foo","password":"baz","bind_address":"0.0.0.0"}' $HEADERS +curl a:b@127.0.0.1:15984/_cluster_setup -d '{"action":"enable_cluster","username":"foo","password":"baz","bind_address":"0.0.0.0","node_count":2}' $HEADERS # Enable Cluster on node B -curl a:b@127.0.0.1:15984/_cluster_setup -d '{"action":"enable_cluster","remote_node":"127.0.0.1","port":"25984","remote_current_user":"a","remote_current_password":"b","username":"foo","password":"baz","bind_address":"0.0.0.0"}' $HEADERS +curl a:b@127.0.0.1:15984/_cluster_setup -d '{"action":"enable_cluster","remote_node":"127.0.0.1","port":"25984","remote_current_user":"a","remote_current_password":"b","username":"foo","password":"baz","bind_address":"0.0.0.0","node_count":2}' $HEADERS # Add node B on node A -curl a:b@127.0.0.1:15984/_cluster_setup -d '{"action":"add_node","username":"foo","password":"baz","host":"127.0.0.1","port":25984}' $HEADERS +curl a:b@127.0.0.1:15984/_cluster_setup -d '{"action":"add_node","username":"foo","password":"baz","host":"127.0.0.1","port":25984,"name":"node2"}' $HEADERS # Show cluster state: curl a:b@127.0.0.1:15986/_nodes/_all_docs @@ -57,4 +57,7 @@ curl a:b@127.0.0.1:25984/_replicator curl a:b@127.0.0.1:25984/_metadata curl a:b@127.0.0.1:25984/_global_changes +# Number of nodes is set to 2 +curl a:b@127.0.0.1:25984/_node/node2@127.0.0.1/_config/cluster/n + echo "YAY ALL GOOD" @@ -16,13 +16,13 @@ HEADERS="-HContent-Type:application/json" curl a:b@127.0.0.1:15986/_nodes/_all_docs # Enable Cluster on node A -curl a:b@127.0.0.1:15984/_cluster_setup -d '{"action":"enable_cluster","username":"foo","password":"baz","bind_address":"0.0.0.0"}' $HEADERS +curl a:b@127.0.0.1:15984/_cluster_setup -d '{"action":"enable_cluster","username":"foo","password":"baz","bind_address":"0.0.0.0","node_count":2}' $HEADERS # Enable Cluster on node B -curl a:b@127.0.0.1:25984/_cluster_setup -d '{"action":"enable_cluster","username":"foo","password":"baz","bind_address":"0.0.0.0"}' $HEADERS +curl a:b@127.0.0.1:25984/_cluster_setup -d '{"action":"enable_cluster","username":"foo","password":"baz","bind_address":"0.0.0.0","node_count":2}' $HEADERS # Add node B on node A -curl a:b@127.0.0.1:15984/_cluster_setup -d '{"action":"add_node","username":"foo","password":"baz","host":"127.0.0.1","port":25984}' $HEADERS +curl a:b@127.0.0.1:15984/_cluster_setup -d '{"action":"add_node","username":"foo","password":"baz","host":"127.0.0.1","port":25984,"name":"node2"}' $HEADERS # Show cluster state: curl a:b@127.0.0.1:15986/_nodes/_all_docs @@ -57,4 +57,7 @@ curl a:b@127.0.0.1:25984/_replicator curl a:b@127.0.0.1:25984/_metadata curl a:b@127.0.0.1:25984/_global_changes +# Number of nodes is set to 2 +curl a:b@127.0.0.1:25984/_node/node2@127.0.0.1/_config/cluster/n + echo "YAY ALL GOOD" |