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 }