github.com/opensearch-project/opensearch-go/v2@v2.3.0/opensearchtransport/discovery.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  package opensearchtransport
    28  
    29  import (
    30  	"encoding/json"
    31  	"fmt"
    32  	"io/ioutil"
    33  	"net/http"
    34  	"net/url"
    35  	"sort"
    36  	"strings"
    37  	"sync"
    38  	"time"
    39  )
    40  
    41  // Discoverable defines the interface for transports supporting node discovery.
    42  //
    43  type Discoverable interface {
    44  	DiscoverNodes() error
    45  }
    46  
    47  // nodeInfo represents the information about node in a cluster.
    48  //
    49  type nodeInfo struct {
    50  	ID         string
    51  	Name       string
    52  	URL        *url.URL
    53  	Roles      []string `json:"roles"`
    54  	Attributes map[string]interface{}
    55  	HTTP       struct {
    56  		PublishAddress string `json:"publish_address"`
    57  	}
    58  }
    59  
    60  // DiscoverNodes reloads the client connections by fetching information from the cluster.
    61  //
    62  func (c *Client) DiscoverNodes() error {
    63  	var conns []*Connection
    64  
    65  	nodes, err := c.getNodesInfo()
    66  	if err != nil {
    67  		if debugLogger != nil {
    68  			debugLogger.Logf("Error getting nodes info: %s\n", err)
    69  		}
    70  		return fmt.Errorf("discovery: get nodes: %s", err)
    71  	}
    72  
    73  	for _, node := range nodes {
    74  		var (
    75  			isClusterManagerOnlyNode bool
    76  		)
    77  
    78  		roles := append(node.Roles[:0:0], node.Roles...)
    79  		sort.Strings(roles)
    80  
    81  		if len(roles) == 1 && (roles[0] == "master" || roles[0] == "cluster_manager") {
    82  			isClusterManagerOnlyNode = true
    83  		}
    84  
    85  		if debugLogger != nil {
    86  			var skip string
    87  			if isClusterManagerOnlyNode {
    88  				skip = "; [SKIP]"
    89  			}
    90  			debugLogger.Logf("Discovered node [%s]; %s; roles=%s%s\n", node.Name, node.URL, node.Roles, skip)
    91  		}
    92  
    93  		// Skip cluster_manager only nodes
    94  		// TODO: Move logic to Selector?
    95  		if isClusterManagerOnlyNode {
    96  			continue
    97  		}
    98  
    99  		conns = append(conns, &Connection{
   100  			URL:        node.URL,
   101  			ID:         node.ID,
   102  			Name:       node.Name,
   103  			Roles:      node.Roles,
   104  			Attributes: node.Attributes,
   105  		})
   106  	}
   107  
   108  	c.Lock()
   109  	defer c.Unlock()
   110  
   111  	if lockable, ok := c.pool.(sync.Locker); ok {
   112  		lockable.Lock()
   113  		defer lockable.Unlock()
   114  	}
   115  
   116  	if c.poolFunc != nil {
   117  		c.pool = c.poolFunc(conns, c.selector)
   118  	} else {
   119  		// TODO: Replace only live connections, leave dead scheduled for resurrect?
   120  		c.pool, err = NewConnectionPool(conns, c.selector)
   121  		if err != nil {
   122  			return err
   123  		}
   124  	}
   125  
   126  	return nil
   127  }
   128  
   129  func (c *Client) getNodesInfo() ([]nodeInfo, error) {
   130  	var (
   131  		out    []nodeInfo
   132  		scheme = c.urls[0].Scheme
   133  	)
   134  
   135  	req, err := http.NewRequest("GET", "/_nodes/http", nil)
   136  	if err != nil {
   137  		return out, err
   138  	}
   139  
   140  	c.Lock()
   141  	conn, err := c.pool.Next()
   142  	c.Unlock()
   143  	// TODO: If no connection is returned, fallback to original URLs
   144  	if err != nil {
   145  		return out, err
   146  	}
   147  
   148  	c.setReqURL(conn.URL, req)
   149  	c.setReqAuth(conn.URL, req)
   150  	c.setReqUserAgent(req)
   151  
   152  	res, err := c.transport.RoundTrip(req)
   153  	if err != nil {
   154  		return out, err
   155  	}
   156  	defer res.Body.Close()
   157  
   158  	if res.StatusCode > 200 {
   159  		body, _ := ioutil.ReadAll(res.Body)
   160  		return out, fmt.Errorf("server error: %s: %s", res.Status, body)
   161  	}
   162  
   163  	var env map[string]json.RawMessage
   164  	if err := json.NewDecoder(res.Body).Decode(&env); err != nil {
   165  		return out, err
   166  	}
   167  
   168  	var nodes map[string]nodeInfo
   169  	if err := json.Unmarshal(env["nodes"], &nodes); err != nil {
   170  		return out, err
   171  	}
   172  
   173  	for id, node := range nodes {
   174  		node.ID = id
   175  		u, err := c.getNodeURL(node, scheme)
   176  		if err != nil {
   177  			return out, err
   178  		}
   179  		node.URL = u
   180  		out = append(out, node)
   181  	}
   182  
   183  	return out, nil
   184  }
   185  
   186  func (c *Client) getNodeURL(node nodeInfo, scheme string) (*url.URL, error) {
   187  	var (
   188  		host string
   189  		port string
   190  
   191  		addrs = strings.Split(node.HTTP.PublishAddress, "/")
   192  		ports = strings.Split(node.HTTP.PublishAddress, ":")
   193  	)
   194  
   195  	if len(addrs) > 1 {
   196  		host = addrs[0]
   197  	} else {
   198  		host = strings.Split(addrs[0], ":")[0]
   199  	}
   200  	port = ports[len(ports)-1]
   201  
   202  	u := &url.URL{
   203  		Scheme: scheme,
   204  		Host:   host + ":" + port,
   205  	}
   206  
   207  	return u, nil
   208  }
   209  
   210  func (c *Client) scheduleDiscoverNodes(d time.Duration) {
   211  	go c.DiscoverNodes()
   212  
   213  	c.Lock()
   214  	defer c.Unlock()
   215  	if c.discoverNodesTimer != nil {
   216  		c.discoverNodesTimer.Stop()
   217  	}
   218  	c.discoverNodesTimer = time.AfterFunc(c.discoverNodesInterval, func() {
   219  		c.scheduleDiscoverNodes(c.discoverNodesInterval)
   220  	})
   221  }