github.com/opensearch-project/opensearch-go/v2@v2.3.0/opensearchutil/bulk_indexer_integration_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 //go:build integration 28 // +build integration 29 30 package opensearchutil_test 31 32 import ( 33 "context" 34 "fmt" 35 "os" 36 "strconv" 37 "strings" 38 "testing" 39 "time" 40 41 "github.com/opensearch-project/opensearch-go/v2" 42 "github.com/opensearch-project/opensearch-go/v2/opensearchtransport" 43 "github.com/opensearch-project/opensearch-go/v2/opensearchutil" 44 ) 45 46 func TestBulkIndexerIntegration(t *testing.T) { 47 testRecordCount := uint64(10000) 48 49 testCases := []struct { 50 name string 51 compressRequestBodyEnabled bool 52 tests []struct { 53 name string 54 action string 55 body string 56 numItems uint64 57 numIndexed uint64 58 numCreated uint64 59 numUpdated uint64 60 numFailed uint64 61 } 62 }{ 63 { 64 name: "With body compression", 65 compressRequestBodyEnabled: true, 66 tests: []struct { 67 name string 68 action string 69 body string 70 numItems uint64 71 numIndexed uint64 72 numCreated uint64 73 numUpdated uint64 74 numFailed uint64 75 }{ 76 { 77 name: "Index", 78 action: "index", 79 body: `{"title":"bar"}`, 80 numItems: testRecordCount, 81 numIndexed: testRecordCount, 82 numCreated: 0, 83 numUpdated: 0, 84 numFailed: 0, 85 }, 86 { 87 name: "Upsert", 88 action: "update", 89 body: `{"doc":{"title":"qwe"}, "doc_as_upsert": true}`, 90 numItems: testRecordCount, 91 numIndexed: 0, 92 numCreated: 0, 93 numUpdated: testRecordCount, 94 numFailed: 0, 95 }, 96 { 97 name: "Create", 98 action: "create", 99 body: `{"title":"bar"}`, 100 numItems: testRecordCount, 101 numIndexed: 0, 102 numCreated: 0, 103 numUpdated: 0, 104 numFailed: testRecordCount, 105 }, 106 }, 107 }, 108 { 109 name: "Without body compression", 110 compressRequestBodyEnabled: false, 111 tests: []struct { 112 name string 113 action string 114 body string 115 numItems uint64 116 numIndexed uint64 117 numCreated uint64 118 numUpdated uint64 119 numFailed uint64 120 }{ 121 { 122 name: "Index", 123 action: "index", 124 body: `{"title":"bar"}`, 125 numItems: testRecordCount, 126 numIndexed: testRecordCount, 127 numCreated: 0, 128 numUpdated: 0, 129 numFailed: 0, 130 }, 131 { 132 name: "Upsert", 133 action: "update", 134 body: `{"doc":{"title":"qwe"}, "doc_as_upsert": true}`, 135 numItems: testRecordCount, 136 numIndexed: 0, 137 numCreated: 0, 138 numUpdated: testRecordCount, 139 numFailed: 0, 140 }, 141 { 142 name: "Create", 143 action: "create", 144 body: `{"title":"bar"}`, 145 numItems: testRecordCount, 146 numIndexed: 0, 147 numCreated: 0, 148 numUpdated: 0, 149 numFailed: testRecordCount, 150 }, 151 }, 152 }, 153 } 154 155 for _, c := range testCases { 156 indexName := "test-bulk-integration" 157 158 client, _ := opensearch.NewClient(opensearch.Config{ 159 CompressRequestBody: c.compressRequestBodyEnabled, 160 Logger: &opensearchtransport.ColorLogger{Output: os.Stdout}, 161 }) 162 163 client.Indices.Delete([]string{indexName}, client.Indices.Delete.WithIgnoreUnavailable(true)) 164 client.Indices.Create( 165 indexName, 166 client.Indices.Create.WithBody(strings.NewReader(`{"settings": {"number_of_shards": 1, "number_of_replicas": 0, "refresh_interval":"5s"}}`)), 167 client.Indices.Create.WithWaitForActiveShards("1")) 168 169 for _, tt := range c.tests { 170 t.Run(tt.name, func(t *testing.T) { 171 t.Run(c.name, func(t *testing.T) { 172 bi, _ := opensearchutil.NewBulkIndexer(opensearchutil.BulkIndexerConfig{ 173 Index: indexName, 174 Client: client, 175 ErrorTrace: true, 176 Human: true, 177 Pretty: true, 178 // FlushBytes: 3e+6, 179 }) 180 181 start := time.Now().UTC() 182 183 for i := 1; i <= int(tt.numItems); i++ { 184 err := bi.Add(context.Background(), opensearchutil.BulkIndexerItem{ 185 Index: indexName, 186 Action: tt.action, 187 DocumentID: strconv.Itoa(i), 188 Body: strings.NewReader(tt.body), 189 }) 190 if err != nil { 191 t.Fatalf("Unexpected error: %s", err) 192 } 193 } 194 195 if err := bi.Close(context.Background()); err != nil { 196 t.Errorf("Unexpected error: %s", err) 197 } 198 199 stats := bi.Stats() 200 201 if stats.NumAdded != tt.numItems { 202 t.Errorf("Unexpected NumAdded: want=%d, got=%d", tt.numItems, stats.NumAdded) 203 } 204 205 if stats.NumIndexed != tt.numIndexed { 206 t.Errorf("Unexpected NumIndexed: want=%d, got=%d", tt.numItems, stats.NumIndexed) 207 } 208 209 if stats.NumUpdated != tt.numUpdated { 210 t.Errorf("Unexpected NumUpdated: want=%d, got=%d", tt.numUpdated, stats.NumUpdated) 211 } 212 213 if stats.NumCreated != tt.numCreated { 214 t.Errorf("Unexpected NumCreated: want=%d, got=%d", tt.numCreated, stats.NumCreated) 215 } 216 217 if stats.NumFailed != tt.numFailed { 218 t.Errorf("Unexpected NumFailed: want=0, got=%d", stats.NumFailed) 219 } 220 221 fmt.Printf(" Added %d documents to indexer. Succeeded: %d. Failed: %d. Requests: %d. Duration: %s (%.0f docs/sec)\n", 222 stats.NumAdded, 223 stats.NumFlushed, 224 stats.NumFailed, 225 stats.NumRequests, 226 time.Since(start).Truncate(time.Millisecond), 227 1000.0/float64(time.Since(start)/time.Millisecond)*float64(stats.NumFlushed)) 228 }) 229 230 t.Run("Multiple indices", func(t *testing.T) { 231 bi, _ := opensearchutil.NewBulkIndexer(opensearchutil.BulkIndexerConfig{ 232 Index: "test-index-a", 233 Client: client, 234 }) 235 236 // Default index 237 for i := 1; i <= 10; i++ { 238 err := bi.Add(context.Background(), opensearchutil.BulkIndexerItem{ 239 Action: "index", 240 DocumentID: strconv.Itoa(i), 241 Body: strings.NewReader(tt.body), 242 }) 243 if err != nil { 244 t.Fatalf("Unexpected error: %s", err) 245 } 246 } 247 248 // Index 1 249 for i := 1; i <= 10; i++ { 250 err := bi.Add(context.Background(), opensearchutil.BulkIndexerItem{ 251 Action: "index", 252 Index: "test-index-b", 253 Body: strings.NewReader(tt.body), 254 }) 255 if err != nil { 256 t.Fatalf("Unexpected error: %s", err) 257 } 258 } 259 260 // Index 2 261 for i := 1; i <= 10; i++ { 262 err := bi.Add(context.Background(), opensearchutil.BulkIndexerItem{ 263 Action: "index", 264 Index: "test-index-c", 265 Body: strings.NewReader(tt.body), 266 }) 267 if err != nil { 268 t.Fatalf("Unexpected error: %s", err) 269 } 270 } 271 272 if err := bi.Close(context.Background()); err != nil { 273 t.Errorf("Unexpected error: %s", err) 274 } 275 stats := bi.Stats() 276 277 expectedIndexed := 10 + 10 + 10 278 if stats.NumIndexed != uint64(expectedIndexed) { 279 t.Errorf("Unexpected NumIndexed: want=%d, got=%d", expectedIndexed, stats.NumIndexed) 280 } 281 282 res, err := client.Indices.Exists([]string{"test-index-a", "test-index-b", "test-index-c"}) 283 if err != nil { 284 t.Fatalf("Unexpected error: %s", err) 285 } 286 if res.StatusCode != 200 { 287 t.Errorf("Expected indices to exist, but got a [%s] response", res.Status()) 288 } 289 }) 290 }) 291 } 292 } 293 }