1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
|
This module implements /_cluster_setup and manages the setting up, duh, of a CouchDB cluster.
### Testing
```
git clone https://git-wip-us.apache.org/repos/asf/couchdb.git
cd couchdb
git checkout setup
./configure
make
# in dev/run comment out the line `connect_nodes("127.0.0.1", 15984)`
dev/run --admin a:b
# in a new terminal
src/setup/test/t.sh
```
The Plan:
N. End User Action
- What happens behind the scenes.
1. Launch CouchDB with `$ couchdb`, or init.d, or any other way, exactly
like it is done in 1.x.x.
- CouchDB launches and listens on 127.0.0.1:5984
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
username, admin password and optionally a bind_address and port to bind
to publicly. Submit the form with the [Enable Cluster] button.
If this is a single node install that already has an admin set up, there
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 a JSON entity to /_cluster_setup, the entity looks like:
```
{
"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 /_cluster_setup as shown above.
Repeat on all nodes.
- keep the same username/password everywhere.
3. Pick any one node, for simplicity use the first one, to be the
“setup coordination node”.
- this is a “master” node that manages the setup and requires all
other nodes to be able to see it and vice versa. Setup won’t work
with unavailable nodes (duh). The notion of “master” will be gone
once the setup is finished. At that point, the system has no
master node. Ignore I ever said “master”.
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 a JSON entity to /_cluster_setup, the entity looks like:
```
{
"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
}
```
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
a UUID and set the erlang cookie to to that UUID.
- 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/_cluster_setup with:
```
{
"action": "receive_cookie",
"cookie": "<secretcookie>",
}
```
- when the request to node B returns, we know the Erlang-level
inter-cluster communication is enabled and we can start adding
the node on the CouchDB level. To do that, the “setup
coordination node” does this to it’s own HTTP endpoint:
PUT /nodes/node_b:5984 or the same thing with internal APIs.
- Repeat for all nodes.
- Fauxton keeps a list of all set up nodes for users to see.
4.a. When all nodes are added, click the [Finish Cluster Setup] button
in Fauxton.
- this does POST /_cluster_setup
```
{
"action": "finish_cluster"
}
```
b. Same as in a.
- this manages the final setup bits, like creating the _users,
_replicator and _metadata, _db_updates endpoints and
whatever else is needed. // TBD: collect what else is needed.
## The Setup Endpoint
This is not a REST-y endpoint, it is a simple state machine operated
by HTTP POST with JSON bodies that have an `action` field.
### State 1: No Cluster Enabled
This is right after starting a node for the first time, and any time
before the cluster is enabled as outlined above.
```
GET /_cluster_setup
{"state": "cluster_disabled"}
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 /_cluster_setup
{"state":"cluster_enabled","nodes":[]}
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 /_cluster_setup
{"state":"cluster_finished","nodes":["node a", "node b", ...]}
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.
|