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  }