github.com/outbrain/consul@v1.4.5/agent/coordinate_endpoint_test.go (about) 1 package agent 2 3 import ( 4 "fmt" 5 "net/http" 6 "net/http/httptest" 7 "strings" 8 "testing" 9 "time" 10 11 "github.com/hashicorp/consul/acl" 12 "github.com/hashicorp/consul/agent/structs" 13 "github.com/hashicorp/consul/testrpc" 14 "github.com/hashicorp/serf/coordinate" 15 ) 16 17 func TestCoordinate_Disabled_Response(t *testing.T) { 18 t.Parallel() 19 a := NewTestAgent(t, t.Name(), ` 20 disable_coordinates = true 21 `) 22 defer a.Shutdown() 23 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 24 25 tests := []func(resp http.ResponseWriter, req *http.Request) (interface{}, error){ 26 a.srv.CoordinateDatacenters, 27 a.srv.CoordinateNodes, 28 a.srv.CoordinateNode, 29 a.srv.CoordinateUpdate, 30 } 31 for i, tt := range tests { 32 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 33 req, _ := http.NewRequest("PUT", "/should/not/care", nil) 34 resp := httptest.NewRecorder() 35 obj, err := tt(resp, req) 36 if err != nil { 37 t.Fatalf("err: %v", err) 38 } 39 if obj != nil { 40 t.Fatalf("bad: %#v", obj) 41 } 42 if got, want := resp.Code, http.StatusUnauthorized; got != want { 43 t.Fatalf("got %d want %d", got, want) 44 } 45 if !strings.Contains(resp.Body.String(), "Coordinate support disabled") { 46 t.Fatalf("bad: %#v", resp) 47 } 48 }) 49 } 50 } 51 52 func TestCoordinate_Datacenters(t *testing.T) { 53 t.Parallel() 54 a := NewTestAgent(t, t.Name(), "") 55 defer a.Shutdown() 56 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 57 58 req, _ := http.NewRequest("GET", "/v1/coordinate/datacenters", nil) 59 resp := httptest.NewRecorder() 60 obj, err := a.srv.CoordinateDatacenters(resp, req) 61 if err != nil { 62 t.Fatalf("err: %v", err) 63 } 64 65 maps := obj.([]structs.DatacenterMap) 66 if len(maps) != 1 || 67 maps[0].Datacenter != "dc1" || 68 len(maps[0].Coordinates) != 1 || 69 maps[0].Coordinates[0].Node != a.Config.NodeName { 70 t.Fatalf("bad: %v", maps) 71 } 72 } 73 74 func TestCoordinate_Nodes(t *testing.T) { 75 t.Parallel() 76 a := NewTestAgent(t, t.Name(), "") 77 defer a.Shutdown() 78 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 79 80 // Make sure an empty list is non-nil. 81 req, _ := http.NewRequest("GET", "/v1/coordinate/nodes?dc=dc1", nil) 82 resp := httptest.NewRecorder() 83 obj, err := a.srv.CoordinateNodes(resp, req) 84 if err != nil { 85 t.Fatalf("err: %v", err) 86 } 87 88 coordinates := obj.(structs.Coordinates) 89 if coordinates == nil || len(coordinates) != 0 { 90 t.Fatalf("bad: %v", coordinates) 91 } 92 93 // Register the nodes. 94 nodes := []string{"foo", "bar"} 95 for _, node := range nodes { 96 req := structs.RegisterRequest{ 97 Datacenter: "dc1", 98 Node: node, 99 Address: "127.0.0.1", 100 } 101 var reply struct{} 102 if err := a.RPC("Catalog.Register", &req, &reply); err != nil { 103 t.Fatalf("err: %s", err) 104 } 105 } 106 107 // Send some coordinates for a few nodes, waiting a little while for the 108 // batch update to run. 109 arg1 := structs.CoordinateUpdateRequest{ 110 Datacenter: "dc1", 111 Node: "foo", 112 Segment: "alpha", 113 Coord: coordinate.NewCoordinate(coordinate.DefaultConfig()), 114 } 115 var out struct{} 116 if err := a.RPC("Coordinate.Update", &arg1, &out); err != nil { 117 t.Fatalf("err: %v", err) 118 } 119 120 arg2 := structs.CoordinateUpdateRequest{ 121 Datacenter: "dc1", 122 Node: "bar", 123 Coord: coordinate.NewCoordinate(coordinate.DefaultConfig()), 124 } 125 if err := a.RPC("Coordinate.Update", &arg2, &out); err != nil { 126 t.Fatalf("err: %v", err) 127 } 128 time.Sleep(300 * time.Millisecond) 129 130 // Query back and check the nodes are present and sorted correctly. 131 req, _ = http.NewRequest("GET", "/v1/coordinate/nodes?dc=dc1", nil) 132 resp = httptest.NewRecorder() 133 obj, err = a.srv.CoordinateNodes(resp, req) 134 if err != nil { 135 t.Fatalf("err: %v", err) 136 } 137 138 coordinates = obj.(structs.Coordinates) 139 if len(coordinates) != 2 || 140 coordinates[0].Node != "bar" || 141 coordinates[1].Node != "foo" { 142 t.Fatalf("bad: %v", coordinates) 143 } 144 145 // Filter on a nonexistent node segment 146 req, _ = http.NewRequest("GET", "/v1/coordinate/nodes?segment=nope", nil) 147 resp = httptest.NewRecorder() 148 obj, err = a.srv.CoordinateNodes(resp, req) 149 if err != nil { 150 t.Fatalf("err: %v", err) 151 } 152 153 coordinates = obj.(structs.Coordinates) 154 if len(coordinates) != 0 { 155 t.Fatalf("bad: %v", coordinates) 156 } 157 158 // Filter on a real node segment 159 req, _ = http.NewRequest("GET", "/v1/coordinate/nodes?segment=alpha", nil) 160 resp = httptest.NewRecorder() 161 obj, err = a.srv.CoordinateNodes(resp, req) 162 if err != nil { 163 t.Fatalf("err: %v", err) 164 } 165 166 coordinates = obj.(structs.Coordinates) 167 if len(coordinates) != 1 || coordinates[0].Node != "foo" { 168 t.Fatalf("bad: %v", coordinates) 169 } 170 171 // Make sure the empty filter works 172 req, _ = http.NewRequest("GET", "/v1/coordinate/nodes?segment=", nil) 173 resp = httptest.NewRecorder() 174 obj, err = a.srv.CoordinateNodes(resp, req) 175 if err != nil { 176 t.Fatalf("err: %v", err) 177 } 178 179 coordinates = obj.(structs.Coordinates) 180 if len(coordinates) != 1 || coordinates[0].Node != "bar" { 181 t.Fatalf("bad: %v", coordinates) 182 } 183 } 184 185 func TestCoordinate_Node(t *testing.T) { 186 t.Parallel() 187 a := NewTestAgent(t, t.Name(), "") 188 defer a.Shutdown() 189 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 190 191 // Make sure we get a 404 with no coordinates. 192 req, _ := http.NewRequest("GET", "/v1/coordinate/node/foo?dc=dc1", nil) 193 resp := httptest.NewRecorder() 194 obj, err := a.srv.CoordinateNode(resp, req) 195 if err != nil { 196 t.Fatalf("err: %v", err) 197 } 198 if resp.Code != http.StatusNotFound { 199 t.Fatalf("bad: %v", resp.Code) 200 } 201 202 // Register the nodes. 203 nodes := []string{"foo", "bar"} 204 for _, node := range nodes { 205 req := structs.RegisterRequest{ 206 Datacenter: "dc1", 207 Node: node, 208 Address: "127.0.0.1", 209 } 210 var reply struct{} 211 if err := a.RPC("Catalog.Register", &req, &reply); err != nil { 212 t.Fatalf("err: %s", err) 213 } 214 } 215 216 // Send some coordinates for a few nodes, waiting a little while for the 217 // batch update to run. 218 arg1 := structs.CoordinateUpdateRequest{ 219 Datacenter: "dc1", 220 Node: "foo", 221 Segment: "alpha", 222 Coord: coordinate.NewCoordinate(coordinate.DefaultConfig()), 223 } 224 var out struct{} 225 if err := a.RPC("Coordinate.Update", &arg1, &out); err != nil { 226 t.Fatalf("err: %v", err) 227 } 228 229 arg2 := structs.CoordinateUpdateRequest{ 230 Datacenter: "dc1", 231 Node: "bar", 232 Coord: coordinate.NewCoordinate(coordinate.DefaultConfig()), 233 } 234 if err := a.RPC("Coordinate.Update", &arg2, &out); err != nil { 235 t.Fatalf("err: %v", err) 236 } 237 time.Sleep(300 * time.Millisecond) 238 239 // Query back and check the nodes are present. 240 req, _ = http.NewRequest("GET", "/v1/coordinate/node/foo?dc=dc1", nil) 241 resp = httptest.NewRecorder() 242 obj, err = a.srv.CoordinateNode(resp, req) 243 if err != nil { 244 t.Fatalf("err: %v", err) 245 } 246 247 coordinates := obj.(structs.Coordinates) 248 if len(coordinates) != 1 || 249 coordinates[0].Node != "foo" { 250 t.Fatalf("bad: %v", coordinates) 251 } 252 253 // Filter on a nonexistent node segment 254 req, _ = http.NewRequest("GET", "/v1/coordinate/node/foo?segment=nope", nil) 255 resp = httptest.NewRecorder() 256 obj, err = a.srv.CoordinateNode(resp, req) 257 if err != nil { 258 t.Fatalf("err: %v", err) 259 } 260 if resp.Code != http.StatusNotFound { 261 t.Fatalf("bad: %v", resp.Code) 262 } 263 264 // Filter on a real node segment 265 req, _ = http.NewRequest("GET", "/v1/coordinate/node/foo?segment=alpha", nil) 266 resp = httptest.NewRecorder() 267 obj, err = a.srv.CoordinateNode(resp, req) 268 if err != nil { 269 t.Fatalf("err: %v", err) 270 } 271 272 coordinates = obj.(structs.Coordinates) 273 if len(coordinates) != 1 || coordinates[0].Node != "foo" { 274 t.Fatalf("bad: %v", coordinates) 275 } 276 277 // Make sure the empty filter works 278 req, _ = http.NewRequest("GET", "/v1/coordinate/node/foo?segment=", nil) 279 resp = httptest.NewRecorder() 280 obj, err = a.srv.CoordinateNode(resp, req) 281 if err != nil { 282 t.Fatalf("err: %v", err) 283 } 284 if resp.Code != http.StatusNotFound { 285 t.Fatalf("bad: %v", resp.Code) 286 } 287 } 288 289 func TestCoordinate_Update(t *testing.T) { 290 t.Parallel() 291 a := NewTestAgent(t, t.Name(), "") 292 defer a.Shutdown() 293 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 294 295 // Register the node. 296 reg := structs.RegisterRequest{ 297 Datacenter: "dc1", 298 Node: "foo", 299 Address: "127.0.0.1", 300 } 301 var reply struct{} 302 if err := a.RPC("Catalog.Register", ®, &reply); err != nil { 303 t.Fatalf("err: %s", err) 304 } 305 306 // Update the coordinates and wait for it to complete. 307 coord := coordinate.NewCoordinate(coordinate.DefaultConfig()) 308 coord.Height = -5.0 309 body := structs.CoordinateUpdateRequest{ 310 Datacenter: "dc1", 311 Node: "foo", 312 Coord: coord, 313 } 314 req, _ := http.NewRequest("PUT", "/v1/coordinate/update", jsonReader(body)) 315 resp := httptest.NewRecorder() 316 _, err := a.srv.CoordinateUpdate(resp, req) 317 if err != nil { 318 t.Fatalf("err: %v", err) 319 } 320 time.Sleep(300 * time.Millisecond) 321 322 // Query back and check the coordinates are present. 323 args := structs.NodeSpecificRequest{Node: "foo", Datacenter: "dc1"} 324 var coords structs.IndexedCoordinates 325 if err := a.RPC("Coordinate.Node", &args, &coords); err != nil { 326 t.Fatalf("err: %s", err) 327 } 328 329 coordinates := coords.Coordinates 330 if len(coordinates) != 1 || 331 coordinates[0].Node != "foo" { 332 t.Fatalf("bad: %v", coordinates) 333 } 334 } 335 336 func TestCoordinate_Update_ACLDeny(t *testing.T) { 337 t.Parallel() 338 a := NewTestAgent(t, t.Name(), TestACLConfig()) 339 defer a.Shutdown() 340 testrpc.WaitForLeader(t, a.RPC, "dc1") 341 342 coord := coordinate.NewCoordinate(coordinate.DefaultConfig()) 343 coord.Height = -5.0 344 body := structs.CoordinateUpdateRequest{ 345 Datacenter: "dc1", 346 Node: "foo", 347 Coord: coord, 348 } 349 350 t.Run("no token", func(t *testing.T) { 351 req, _ := http.NewRequest("PUT", "/v1/coordinate/update", jsonReader(body)) 352 if _, err := a.srv.CoordinateUpdate(nil, req); !acl.IsErrPermissionDenied(err) { 353 t.Fatalf("err: %v", err) 354 } 355 }) 356 357 t.Run("valid token", func(t *testing.T) { 358 req, _ := http.NewRequest("PUT", "/v1/coordinate/update?token=root", jsonReader(body)) 359 if _, err := a.srv.CoordinateUpdate(nil, req); err != nil { 360 t.Fatalf("err: %v", err) 361 } 362 }) 363 }