github.com/opensearch-project/opensearch-go/v2@v2.3.0/opensearchtransport/connection_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 "net/url" 33 "regexp" 34 "testing" 35 "time" 36 ) 37 38 func TestSingleConnectionPoolNext(t *testing.T) { 39 t.Run("Single URL", func(t *testing.T) { 40 pool := &singleConnectionPool{ 41 connection: &Connection{URL: &url.URL{Scheme: "http", Host: "foo1"}}, 42 } 43 44 for i := 0; i < 7; i++ { 45 c, err := pool.Next() 46 if err != nil { 47 t.Errorf("Unexpected error: %s", err) 48 } 49 50 if c.URL.String() != "http://foo1" { 51 t.Errorf("Unexpected URL, want=http://foo1, got=%s", c.URL) 52 } 53 } 54 }) 55 } 56 57 func TestSingleConnectionPoolOnFailure(t *testing.T) { 58 t.Run("Noop", func(t *testing.T) { 59 pool := &singleConnectionPool{ 60 connection: &Connection{URL: &url.URL{Scheme: "http", Host: "foo1"}}, 61 } 62 63 if err := pool.OnFailure(&Connection{URL: &url.URL{Scheme: "http", Host: "foo1"}}); err != nil { 64 t.Errorf("Unexpected error: %s", err) 65 } 66 }) 67 } 68 69 func TestStatusConnectionPoolNext(t *testing.T) { 70 t.Run("No URL", func(t *testing.T) { 71 pool := &statusConnectionPool{} 72 73 c, err := pool.Next() 74 if err == nil { 75 t.Errorf("Expected error, but got: %s", c.URL) 76 } 77 }) 78 79 t.Run("Two URLs", func(t *testing.T) { 80 var c *Connection 81 82 pool := &statusConnectionPool{ 83 live: []*Connection{ 84 &Connection{URL: &url.URL{Scheme: "http", Host: "foo1"}}, 85 &Connection{URL: &url.URL{Scheme: "http", Host: "foo2"}}, 86 }, 87 selector: &roundRobinSelector{curr: -1}, 88 } 89 90 c, _ = pool.Next() 91 92 if c.URL.String() != "http://foo1" { 93 t.Errorf("Unexpected URL, want=foo1, got=%s", c.URL) 94 } 95 96 c, _ = pool.Next() 97 if c.URL.String() != "http://foo2" { 98 t.Errorf("Unexpected URL, want=http://foo2, got=%s", c.URL) 99 } 100 101 c, _ = pool.Next() 102 if c.URL.String() != "http://foo1" { 103 t.Errorf("Unexpected URL, want=http://foo1, got=%s", c.URL) 104 } 105 }) 106 107 t.Run("Three URLs", func(t *testing.T) { 108 pool := &statusConnectionPool{ 109 live: []*Connection{ 110 &Connection{URL: &url.URL{Scheme: "http", Host: "foo1"}}, 111 &Connection{URL: &url.URL{Scheme: "http", Host: "foo2"}}, 112 &Connection{URL: &url.URL{Scheme: "http", Host: "foo3"}}, 113 }, 114 selector: &roundRobinSelector{curr: -1}, 115 } 116 117 var expected string 118 for i := 0; i < 11; i++ { 119 c, err := pool.Next() 120 121 if err != nil { 122 t.Errorf("Unexpected error: %s", err) 123 } 124 125 switch i % len(pool.live) { 126 case 0: 127 expected = "http://foo1" 128 case 1: 129 expected = "http://foo2" 130 case 2: 131 expected = "http://foo3" 132 default: 133 t.Fatalf("Unexpected i %% 3: %d", i%3) 134 } 135 136 if c.URL.String() != expected { 137 t.Errorf("Unexpected URL, want=%s, got=%s", expected, c.URL) 138 } 139 } 140 }) 141 142 t.Run("Resurrect dead connection when no live is available", func(t *testing.T) { 143 pool := &statusConnectionPool{ 144 live: []*Connection{}, 145 dead: []*Connection{ 146 &Connection{URL: &url.URL{Scheme: "http", Host: "foo1"}, Failures: 3}, 147 &Connection{URL: &url.URL{Scheme: "http", Host: "foo2"}, Failures: 1}, 148 }, 149 selector: &roundRobinSelector{curr: -1}, 150 } 151 152 c, err := pool.Next() 153 if err != nil { 154 t.Errorf("Unexpected error: %s", err) 155 } 156 157 if c == nil { 158 t.Errorf("Expected connection, got nil: %s", c) 159 } 160 161 if c.URL.String() != "http://foo2" { 162 t.Errorf("Expected <http://foo2>, got: %s", c.URL.String()) 163 } 164 165 if c.IsDead { 166 t.Errorf("Expected connection to be live, got: %s", c) 167 } 168 169 if len(pool.live) != 1 { 170 t.Errorf("Expected 1 connection in live list, got: %s", pool.live) 171 } 172 173 if len(pool.dead) != 1 { 174 t.Errorf("Expected 1 connection in dead list, got: %s", pool.dead) 175 } 176 }) 177 } 178 179 func TestStatusConnectionPoolOnSuccess(t *testing.T) { 180 t.Run("Move connection to live list and mark it as healthy", func(t *testing.T) { 181 pool := &statusConnectionPool{ 182 dead: []*Connection{ 183 &Connection{URL: &url.URL{Scheme: "http", Host: "foo1"}, Failures: 3, IsDead: true}, 184 }, 185 selector: &roundRobinSelector{curr: -1}, 186 } 187 188 conn := pool.dead[0] 189 190 if err := pool.OnSuccess(conn); err != nil { 191 t.Fatalf("Unexpected error: %s", err) 192 } 193 194 if conn.IsDead { 195 t.Errorf("Expected the connection to be live; %s", conn) 196 } 197 198 if !conn.DeadSince.IsZero() { 199 t.Errorf("Unexpected value for DeadSince: %s", conn.DeadSince) 200 } 201 202 if len(pool.live) != 1 { 203 t.Errorf("Expected 1 live connection, got: %d", len(pool.live)) 204 } 205 206 if len(pool.dead) != 0 { 207 t.Errorf("Expected 0 dead connections, got: %d", len(pool.dead)) 208 } 209 }) 210 } 211 212 func TestStatusConnectionPoolOnFailure(t *testing.T) { 213 t.Run("Remove connection, mark it, and sort dead connections", func(t *testing.T) { 214 pool := &statusConnectionPool{ 215 live: []*Connection{ 216 &Connection{URL: &url.URL{Scheme: "http", Host: "foo1"}}, 217 &Connection{URL: &url.URL{Scheme: "http", Host: "foo2"}}, 218 }, 219 dead: []*Connection{ 220 &Connection{URL: &url.URL{Scheme: "http", Host: "foo3"}, Failures: 0}, 221 &Connection{URL: &url.URL{Scheme: "http", Host: "foo4"}, Failures: 99}, 222 }, 223 selector: &roundRobinSelector{curr: -1}, 224 } 225 226 conn := pool.live[0] 227 228 if err := pool.OnFailure(conn); err != nil { 229 t.Fatalf("Unexpected error: %s", err) 230 } 231 232 if !conn.IsDead { 233 t.Errorf("Expected the connection to be dead; %s", conn) 234 } 235 236 if conn.DeadSince.IsZero() { 237 t.Errorf("Unexpected value for DeadSince: %s", conn.DeadSince) 238 } 239 240 if len(pool.live) != 1 { 241 t.Errorf("Expected 1 live connection, got: %d", len(pool.live)) 242 } 243 244 if len(pool.dead) != 3 { 245 t.Errorf("Expected 3 dead connections, got: %d", len(pool.dead)) 246 } 247 248 expected := []string{ 249 "http://foo4", 250 "http://foo1", 251 "http://foo3", 252 } 253 254 for i, u := range expected { 255 if pool.dead[i].URL.String() != u { 256 t.Errorf("Unexpected value for item %d in pool.dead: %s", i, pool.dead[i].URL.String()) 257 } 258 } 259 }) 260 261 t.Run("Short circuit when the connection is already dead", func(t *testing.T) { 262 pool := &statusConnectionPool{ 263 live: []*Connection{ 264 &Connection{URL: &url.URL{Scheme: "http", Host: "foo1"}}, 265 &Connection{URL: &url.URL{Scheme: "http", Host: "foo2"}}, 266 &Connection{URL: &url.URL{Scheme: "http", Host: "foo3"}}, 267 }, 268 selector: &roundRobinSelector{curr: -1}, 269 } 270 271 conn := pool.live[0] 272 conn.IsDead = true 273 274 if err := pool.OnFailure(conn); err != nil { 275 t.Fatalf("Unexpected error: %s", err) 276 } 277 278 if len(pool.dead) != 0 { 279 t.Errorf("Expected the dead list to be empty, got: %s", pool.dead) 280 } 281 }) 282 } 283 284 func TestStatusConnectionPoolResurrect(t *testing.T) { 285 t.Run("Mark the connection as dead and add/remove it to the lists", func(t *testing.T) { 286 pool := &statusConnectionPool{ 287 live: []*Connection{}, 288 dead: []*Connection{&Connection{URL: &url.URL{Scheme: "http", Host: "foo1"}, IsDead: true}}, 289 selector: &roundRobinSelector{curr: -1}, 290 } 291 292 conn := pool.dead[0] 293 294 if err := pool.resurrect(conn, true); err != nil { 295 t.Fatalf("Unexpected error: %s", err) 296 } 297 298 if conn.IsDead { 299 t.Errorf("Expected connection to be dead, got: %s", conn) 300 } 301 302 if len(pool.dead) != 0 { 303 t.Errorf("Expected no dead connections, got: %s", pool.dead) 304 } 305 306 if len(pool.live) != 1 { 307 t.Errorf("Expected 1 live connection, got: %s", pool.live) 308 } 309 }) 310 311 t.Run("Short circuit removal when the connection is not in the dead list", func(t *testing.T) { 312 pool := &statusConnectionPool{ 313 dead: []*Connection{&Connection{URL: &url.URL{Scheme: "http", Host: "bar"}, IsDead: true}}, 314 selector: &roundRobinSelector{curr: -1}, 315 } 316 317 conn := &Connection{URL: &url.URL{Scheme: "http", Host: "foo1"}, IsDead: true} 318 319 if err := pool.resurrect(conn, true); err != nil { 320 t.Fatalf("Unexpected error: %s", err) 321 } 322 323 if len(pool.live) != 1 { 324 t.Errorf("Expected 1 live connection, got: %s", pool.live) 325 } 326 327 if len(pool.dead) != 1 { 328 t.Errorf("Expected 1 dead connection, got: %s", pool.dead) 329 } 330 }) 331 332 t.Run("Schedule resurrect", func(t *testing.T) { 333 defaultResurrectTimeoutInitial = 0 334 defer func() { defaultResurrectTimeoutInitial = 60 * time.Second }() 335 336 pool := &statusConnectionPool{ 337 live: []*Connection{}, 338 dead: []*Connection{ 339 &Connection{ 340 URL: &url.URL{Scheme: "http", Host: "foo1"}, 341 Failures: 100, 342 IsDead: true, 343 DeadSince: time.Now().UTC(), 344 }, 345 }, 346 selector: &roundRobinSelector{curr: -1}, 347 } 348 349 conn := pool.dead[0] 350 pool.scheduleResurrect(conn) 351 time.Sleep(50 * time.Millisecond) 352 353 pool.Lock() 354 defer pool.Unlock() 355 356 if len(pool.live) != 1 { 357 t.Errorf("Expected 1 live connection, got: %s", pool.live) 358 } 359 if len(pool.dead) != 0 { 360 t.Errorf("Expected no dead connections, got: %s", pool.dead) 361 } 362 }) 363 } 364 365 func TestConnection(t *testing.T) { 366 t.Run("String", func(t *testing.T) { 367 conn := &Connection{ 368 URL: &url.URL{Scheme: "http", Host: "foo1"}, 369 Failures: 10, 370 IsDead: true, 371 DeadSince: time.Now().UTC(), 372 } 373 374 match, err := regexp.MatchString( 375 `<http://foo1> dead=true failures=10`, 376 conn.String(), 377 ) 378 379 if err != nil { 380 t.Fatalf("Unexpected error: %s", err) 381 } 382 383 if !match { 384 t.Errorf("Unexpected output: %s", conn) 385 } 386 }) 387 }