github.com/opensearch-project/opensearch-go/v2@v2.3.0/opensearchtransport/discovery_internal_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // 3 // The OpenSearch Contributors require contributions made to 4 // this file be licensed under the Apache-2.0 license or a 5 // compatible open source license. 6 // 7 // Modifications Copyright OpenSearch Contributors. See 8 // GitHub history for details. 9 10 // Licensed to Elasticsearch B.V. under one or more contributor 11 // license agreements. See the NOTICE file distributed with 12 // this work for additional information regarding copyright 13 // ownership. Elasticsearch B.V. licenses this file to you under 14 // the Apache License, Version 2.0 (the "License"); you may 15 // not use this file except in compliance with the License. 16 // You may obtain a copy of the License at 17 // 18 // http://www.apache.org/licenses/LICENSE-2.0 19 // 20 // Unless required by applicable law or agreed to in writing, 21 // software distributed under the License is distributed on an 22 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 23 // KIND, either express or implied. See the License for the 24 // specific language governing permissions and limitations 25 // under the License. 26 27 // +build !integration 28 29 package opensearchtransport 30 31 import ( 32 "bytes" 33 "crypto/tls" 34 "encoding/json" 35 "fmt" 36 "io" 37 "io/ioutil" 38 "net/http" 39 "net/url" 40 "os" 41 "reflect" 42 "testing" 43 "time" 44 ) 45 46 func TestDiscovery(t *testing.T) { 47 defaultHandler := func(w http.ResponseWriter, r *http.Request) { 48 f, err := os.Open("testdata/nodes.info.json") 49 if err != nil { 50 http.Error(w, fmt.Sprintf("Fixture error: %s", err), 500) 51 return 52 } 53 io.Copy(w, f) 54 } 55 56 srv := &http.Server{Addr: "localhost:10001", Handler: http.HandlerFunc(defaultHandler)} 57 srvTLS := &http.Server{Addr: "localhost:12001", Handler: http.HandlerFunc(defaultHandler)} 58 59 go func() { 60 if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { 61 t.Errorf("Unable to start server: %s", err) 62 return 63 } 64 }() 65 go func() { 66 if err := srvTLS.ListenAndServeTLS("testdata/cert.pem", "testdata/key.pem"); err != nil && err != http.ErrServerClosed { 67 t.Errorf("Unable to start server: %s", err) 68 return 69 } 70 }() 71 defer func() { srv.Close() }() 72 defer func() { srvTLS.Close() }() 73 74 time.Sleep(50 * time.Millisecond) 75 76 t.Run("getNodesInfo()", func(t *testing.T) { 77 u, _ := url.Parse("http://" + srv.Addr) 78 tp, _ := New(Config{URLs: []*url.URL{u}}) 79 80 nodes, err := tp.getNodesInfo() 81 if err != nil { 82 t.Fatalf("ERROR: %s", err) 83 } 84 fmt.Printf("NodesInfo: %+v\n", nodes) 85 86 if len(nodes) != 3 { 87 t.Errorf("Unexpected number of nodes, want=3, got=%d", len(nodes)) 88 } 89 90 for _, node := range nodes { 91 switch node.Name { 92 case "es1": 93 if node.URL.String() != "http://127.0.0.1:10001" { 94 t.Errorf("Unexpected URL: %s", node.URL.String()) 95 } 96 case "es2": 97 if node.URL.String() != "http://localhost:10002" { 98 t.Errorf("Unexpected URL: %s", node.URL.String()) 99 } 100 case "es3": 101 if node.URL.String() != "http://127.0.0.1:10003" { 102 t.Errorf("Unexpected URL: %s", node.URL.String()) 103 } 104 } 105 } 106 }) 107 108 t.Run("DiscoverNodes()", func(t *testing.T) { 109 u, _ := url.Parse("http://" + srv.Addr) 110 tp, _ := New(Config{URLs: []*url.URL{u}}) 111 112 tp.DiscoverNodes() 113 114 pool, ok := tp.pool.(*statusConnectionPool) 115 if !ok { 116 t.Fatalf("Unexpected pool, want=statusConnectionPool, got=%T", tp.pool) 117 } 118 119 if len(pool.live) != 2 { 120 t.Errorf("Unexpected number of nodes, want=2, got=%d", len(pool.live)) 121 } 122 123 for _, conn := range pool.live { 124 switch conn.Name { 125 case "es1": 126 if conn.URL.String() != "http://127.0.0.1:10001" { 127 t.Errorf("Unexpected URL: %s", conn.URL.String()) 128 } 129 case "es2": 130 if conn.URL.String() != "http://localhost:10002" { 131 t.Errorf("Unexpected URL: %s", conn.URL.String()) 132 } 133 default: 134 t.Errorf("Unexpected node: %s", conn.Name) 135 } 136 } 137 }) 138 139 t.Run("DiscoverNodes() with SSL and authorization", func(t *testing.T) { 140 u, _ := url.Parse("https://" + srvTLS.Addr) 141 tp, _ := New(Config{ 142 URLs: []*url.URL{u}, 143 Username: "foo", 144 Password: "bar", 145 Transport: &http.Transport{ 146 TLSClientConfig: &tls.Config{ 147 InsecureSkipVerify: true, 148 }, 149 }, 150 }) 151 152 tp.DiscoverNodes() 153 154 pool, ok := tp.pool.(*statusConnectionPool) 155 if !ok { 156 t.Fatalf("Unexpected pool, want=statusConnectionPool, got=%T", tp.pool) 157 } 158 159 if len(pool.live) != 2 { 160 t.Errorf("Unexpected number of nodes, want=2, got=%d", len(pool.live)) 161 } 162 163 for _, conn := range pool.live { 164 switch conn.Name { 165 case "es1": 166 if conn.URL.String() != "https://127.0.0.1:10001" { 167 t.Errorf("Unexpected URL: %s", conn.URL.String()) 168 } 169 case "es2": 170 if conn.URL.String() != "https://localhost:10002" { 171 t.Errorf("Unexpected URL: %s", conn.URL.String()) 172 } 173 default: 174 t.Errorf("Unexpected node: %s", conn.Name) 175 } 176 } 177 }) 178 179 t.Run("scheduleDiscoverNodes()", func(t *testing.T) { 180 t.Skip("Skip") // TODO(karmi): Investigate the intermittent failures of this test 181 182 var numURLs int 183 u, _ := url.Parse("http://" + srv.Addr) 184 185 tp, _ := New(Config{URLs: []*url.URL{u}, DiscoverNodesInterval: 10 * time.Millisecond}) 186 187 tp.Lock() 188 numURLs = len(tp.pool.URLs()) 189 tp.Unlock() 190 if numURLs != 1 { 191 t.Errorf("Unexpected number of nodes, want=1, got=%d", numURLs) 192 } 193 194 time.Sleep(18 * time.Millisecond) // Wait until (*Client).scheduleDiscoverNodes() 195 tp.Lock() 196 numURLs = len(tp.pool.URLs()) 197 tp.Unlock() 198 if numURLs != 2 { 199 t.Errorf("Unexpected number of nodes, want=2, got=%d", numURLs) 200 } 201 }) 202 203 t.Run("Role based nodes discovery", func(t *testing.T) { 204 type Node struct { 205 URL string 206 Roles []string 207 } 208 209 type fields struct { 210 Nodes map[string]Node 211 } 212 type wants struct { 213 wantErr bool 214 wantsNConn int 215 } 216 tests := []struct { 217 name string 218 args fields 219 want wants 220 }{ 221 { 222 "Default roles should allow every node to be selected", 223 fields{ 224 Nodes: map[string]Node{ 225 "es1": { 226 URL: "http://es1:9200", 227 Roles: []string{ 228 "data", 229 "data_cold", 230 "data_content", 231 "data_frozen", 232 "data_hot", 233 "data_warm", 234 "ingest", 235 "cluster_manager", 236 "ml", 237 "remote_cluster_client", 238 "transform", 239 }, 240 }, 241 "es2": { 242 URL: "http://es2:9200", 243 Roles: []string{ 244 "data", 245 "data_cold", 246 "data_content", 247 "data_frozen", 248 "data_hot", 249 "data_warm", 250 "ingest", 251 "cluster_manager", 252 "ml", 253 "remote_cluster_client", 254 "transform", 255 }, 256 }, 257 "es3": { 258 URL: "http://es3:9200", 259 Roles: []string{ 260 "data", 261 "data_cold", 262 "data_content", 263 "data_frozen", 264 "data_hot", 265 "data_warm", 266 "ingest", 267 "cluster_manager", 268 "ml", 269 "remote_cluster_client", 270 "transform", 271 }, 272 }, 273 }, 274 }, 275 wants{ 276 false, 3, 277 }, 278 }, 279 { 280 "Cluster manager only node should not be selected", 281 fields{ 282 Nodes: map[string]Node{ 283 "es1": { 284 URL: "http://es1:9200", 285 Roles: []string{ 286 "cluster_manager", 287 }, 288 }, 289 "es2": { 290 URL: "http://es2:9200", 291 Roles: []string{ 292 "data", 293 "data_cold", 294 "data_content", 295 "data_frozen", 296 "data_hot", 297 "data_warm", 298 "ingest", 299 "cluster_manager", 300 "ml", 301 "remote_cluster_client", 302 "transform", 303 }, 304 }, 305 "es3": { 306 URL: "http://es3:9200", 307 Roles: []string{ 308 "data", 309 "data_cold", 310 "data_content", 311 "data_frozen", 312 "data_hot", 313 "data_warm", 314 "ingest", 315 "cluster_manager", 316 "ml", 317 "remote_cluster_client", 318 "transform", 319 }, 320 }, 321 }, 322 }, 323 324 wants{ 325 false, 2, 326 }, 327 }, 328 { 329 "Cluster manager and data only nodes should be selected", 330 fields{ 331 Nodes: map[string]Node{ 332 "es1": { 333 URL: "http://es1:9200", 334 Roles: []string{ 335 "data", 336 "cluster_manager", 337 }, 338 }, 339 "es2": { 340 URL: "http://es2:9200", 341 Roles: []string{ 342 "data", 343 "cluster_manager", 344 }, 345 }, 346 }, 347 }, 348 349 wants{ 350 false, 2, 351 }, 352 }, 353 { 354 "Default roles should allow every node to be selected", 355 fields{ 356 Nodes: map[string]Node{ 357 "es1": { 358 URL: "http://es1:9200", 359 Roles: []string{ 360 "data", 361 "data_cold", 362 "data_content", 363 "data_frozen", 364 "data_hot", 365 "data_warm", 366 "ingest", 367 "master", 368 "ml", 369 "remote_cluster_client", 370 "transform", 371 }, 372 }, 373 "es2": { 374 URL: "http://es2:9200", 375 Roles: []string{ 376 "data", 377 "data_cold", 378 "data_content", 379 "data_frozen", 380 "data_hot", 381 "data_warm", 382 "ingest", 383 "master", 384 "ml", 385 "remote_cluster_client", 386 "transform", 387 }, 388 }, 389 "es3": { 390 URL: "http://es3:9200", 391 Roles: []string{ 392 "data", 393 "data_cold", 394 "data_content", 395 "data_frozen", 396 "data_hot", 397 "data_warm", 398 "ingest", 399 "master", 400 "ml", 401 "remote_cluster_client", 402 "transform", 403 }, 404 }, 405 }, 406 }, 407 wants{ 408 false, 3, 409 }, 410 }, 411 { 412 "Master only node should not be selected", 413 fields{ 414 Nodes: map[string]Node{ 415 "es1": { 416 URL: "http://es1:9200", 417 Roles: []string{ 418 "master", 419 }, 420 }, 421 "es2": { 422 URL: "http://es2:9200", 423 Roles: []string{ 424 "data", 425 "data_cold", 426 "data_content", 427 "data_frozen", 428 "data_hot", 429 "data_warm", 430 "ingest", 431 "master", 432 "ml", 433 "remote_cluster_client", 434 "transform", 435 }, 436 }, 437 "es3": { 438 URL: "http://es3:9200", 439 Roles: []string{ 440 "data", 441 "data_cold", 442 "data_content", 443 "data_frozen", 444 "data_hot", 445 "data_warm", 446 "ingest", 447 "master", 448 "ml", 449 "remote_cluster_client", 450 "transform", 451 }, 452 }, 453 }, 454 }, 455 456 wants{ 457 false, 2, 458 }, 459 }, 460 { 461 "Master and data only nodes should be selected", 462 fields{ 463 Nodes: map[string]Node{ 464 "es1": { 465 URL: "http://es1:9200", 466 Roles: []string{ 467 "data", 468 "master", 469 }, 470 }, 471 "es2": { 472 URL: "http://es2:9200", 473 Roles: []string{ 474 "data", 475 "master", 476 }, 477 }, 478 }, 479 }, 480 481 wants{ 482 false, 2, 483 }, 484 }, 485 } 486 for _, tt := range tests { 487 t.Run(tt.name, func(t *testing.T) { 488 var names []string 489 var urls []*url.URL 490 for name, node := range tt.args.Nodes { 491 u, _ := url.Parse(node.URL) 492 urls = append(urls, u) 493 names = append(names, name) 494 } 495 496 newRoundTripper := func() http.RoundTripper { 497 return &mockTransp{ 498 RoundTripFunc: func(req *http.Request) (*http.Response, error) { 499 nodes := make(map[string]map[string]nodeInfo) 500 nodes["nodes"] = make(map[string]nodeInfo) 501 for name, node := range tt.args.Nodes { 502 nodes["nodes"][name] = nodeInfo{Roles: node.Roles} 503 } 504 505 b, _ := json.Marshal(nodes) 506 507 return &http.Response{ 508 Status: "200 OK", 509 StatusCode: 200, 510 ContentLength: int64(len(b)), 511 Header: http.Header(map[string][]string{"Content-Type": {"application/json"}}), 512 Body: ioutil.NopCloser(bytes.NewReader(b)), 513 }, nil 514 }, 515 } 516 } 517 518 c, _ := New(Config{ 519 URLs: urls, 520 Transport: newRoundTripper(), 521 }) 522 c.DiscoverNodes() 523 524 pool, ok := c.pool.(*statusConnectionPool) 525 if !ok { 526 t.Fatalf("Unexpected pool, want=statusConnectionPool, got=%T", c.pool) 527 } 528 529 if len(pool.live) != tt.want.wantsNConn { 530 t.Errorf("Unexpected number of nodes, want=%d, got=%d", tt.want.wantsNConn, len(pool.live)) 531 } 532 533 for _, conn := range pool.live { 534 if !reflect.DeepEqual(tt.args.Nodes[conn.ID].Roles, conn.Roles) { 535 t.Errorf("Unexpected roles for node %s, want=%s, got=%s", conn.Name, tt.args.Nodes[conn.ID], conn.Roles) 536 } 537 } 538 539 if err := c.DiscoverNodes(); (err != nil) != tt.want.wantErr { 540 t.Errorf("DiscoverNodes() error = %v, wantErr %v", err, tt.want.wantErr) 541 } 542 }) 543 } 544 }) 545 }