Skip to content

Commit 21f67b2

Browse files
committed
Refactor test infrastructure and consolidate test utilities
Modernize the test infrastructure by consolidating test helpers into a shared testutil package and enhancing connection reliability: - Move test utilities from internal/test to opensearchutil/testutil for broader reusability across the project and external packages - Remove obsolete internal/test/config.go in favor of improved helper functions with better error handling and connection management - Add dynamic field filtering for JSON comparison tests to handle version-specific and environment-dependent OpenSearch responses - Enhance connection robustness with improved readiness checks and health monitoring in opensearchtransport layer - Update all integration tests across opensearchapi, plugins, and transport packages to use the new unified test infrastructure - Add comprehensive documentation and examples for the new test utilities This refactor provides a more maintainable foundation for testing across different OpenSearch versions and environments while reducing code duplication and improving test reliability. Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
1 parent 30622e9 commit 21f67b2

93 files changed

Lines changed: 2886 additions & 2716 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
with:
1919
version: v2.8.0
2020
only-new-issues: true
21-
args: --fix --build-tags "integration core"
21+
args: --fix --build-tags "integration core plugins plugin_security plugin_index_management multinode"
2222

2323
prettify:
2424
name: Prettify

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
66

77
### Added
88

9-
- Enhanced cluster readiness checking for improved test reliability: `ostest.NewClient()` now includes readiness validation (health + cluster state + nodes info)
9+
- Enhanced cluster readiness checking for improved test reliability: `testutil.NewClient()` now includes readiness validation (health + cluster state + nodes info)
1010
- Configuration option `IncludeDedicatedClusterManagers` for controlling cluster manager node routing ([#765](https://github.com/opensearch-project/opensearch-go/issues/765))
1111
- Request-aware connection routing for improved performance and service availability ([#770](https://github.com/opensearch-project/opensearch-go/pull/770))
1212
- `RequestAwareSelector` interface for operation-based node selection
@@ -25,6 +25,10 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
2525
- Node selection logic now matches Java client `NodeSelector.SKIP_DEDICATED_CLUSTER_MASTERS` behavior
2626
- Role compatibility validation prevents conflicting role assignments (master+cluster_manager, warm+search)
2727
- OpenSearch 3.0+ searchable snapshots now use `warm` role instead of deprecated `search` role
28+
- **BREAKING**: Migrate `signer/aws` package from AWS SDK v1 to AWS SDK v2 due to AWS SDK v1 reaching end-of-support on July 31, 2025
29+
- Constructor now takes `aws.Config` instead of `session.Options`
30+
- See USER_GUIDE.md for details required to migrate
31+
- Users who need access to the existing `signer/awsv2` API can still use it, however they are encouraged to migrate to `signer/aws`
2832

2933
### Deprecated
3034

@@ -299,7 +303,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
299303

300304
- Updates workflow action versions ([#488](https://github.com/opensearch-project/opensearch-go/pull/488))
301305
- Changes integration tests to work with secure and unsecure OpenSearch ([#488](https://github.com/opensearch-project/opensearch-go/pull/488))
302-
- Moves functions from `opensearch/internal/test` to `internal/test` for more general test uses ([#488](https://github.com/opensearch-project/opensearch-go/pull/488))
306+
- Moves functions from `opensearch/internal/test` to `opensearchutil/testutil` for shared test utilities ([#488](https://github.com/opensearch-project/opensearch-go/pull/488))
303307
- Changes `custom_foldername` field to pointer as it can be `null` ([#488](https://github.com/opensearch-project/opensearch-go/pull/488))
304308
- Changs cat indices Primary and Replica field to pointer as it can be `null` ([#488](https://github.com/opensearch-project/opensearch-go/pull/488))
305309
- Replaces `ioutil` with `io` in examples and integration tests [#495](https://github.com/opensearch-project/opensearch-go/pull/495)

Makefile

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ SHELL := /bin/bash
33
# Tool versions
44
GOLANGCI_LINT_VERSION := v2.8.0
55

6+
GOLANGCI_LINT_BUILD_TAGS := "integration core plugins plugin_security plugin_index_management multinode"
7+
68
##@ Format project using goimports tool
79
format:
810
goimports -w .;
@@ -40,32 +42,43 @@ ifdef coverage
4042
endif
4143

4244
test-integ-core: ## Run base integration tests
43-
@make test-integ testintegtags=integration,core
45+
@$(MAKE) test-integ testintegtags=integration,core
4446

4547
test-integ-plugins: ## Run plugin integration tests
46-
@make test-integ testintegtags=integration,plugins
48+
@$(MAKE) test-integ testintegtags=integration,plugins
4749

4850
test-integ-secure: ##Run secure integration tests
49-
@SECURE_INTEGRATION=true make test-integ
51+
@SECURE_INTEGRATION=true $(MAKE) test-integ
5052

5153
test-integ-core-secure: ## Run secure base integration tests
52-
@SECURE_INTEGRATION=true make test-integ testintegtags=integration,core
54+
@SECURE_INTEGRATION=true $(MAKE) test-integ testintegtags=integration,core
5355

5456
test-integ-plugins-secure: ## Run secure plugin integration tests
55-
@SECURE_INTEGRATION=true make test-integ testintegtags=integration,plugins
57+
@SECURE_INTEGRATION=true $(MAKE) test-integ testintegtags=integration,plugins
58+
59+
test-all: ## Run all tests with all build tags (unit + integration)
60+
@printf "\033[2m→ Running all unit tests...\033[0m\n"
61+
@$(MAKE) test-unit
62+
@printf "\033[2m→ Running all integration tests with all tags...\033[0m\n"
63+
@$(MAKE) test-integ testintegtags=integration,core,plugins,plugin_security,plugin_index_management,multinode
5664

65+
test-race: ## Run all tests with race detection enabled
66+
@printf "\033[2m→ Running all unit tests with race detection...\033[0m\n"
67+
@$(MAKE) test-unit race=true
68+
@printf "\033[2m→ Running all integration tests with race detection and all tags...\033[0m\n"
69+
@$(MAKE) test-integ race=true testintegtags=integration,core,plugins,plugin_security,plugin_index_management,multinode
5770

5871
test-bench: ## Run benchmarks
5972
@printf "\033[2m→ Running benchmarks...\033[0m\n"
6073
go test -run=none -bench=. -benchmem ./...
6174

6275
coverage: ## Print test coverage report
63-
@make gen-coverage
76+
@$(MAKE) gen-coverage
6477
@go tool cover -func=$(PWD)/tmp/total.cov
6578
@printf "\033[0m--------------------------------------------------------------------------------\n\033[0m"
6679

6780
coverage-html: ## Open test coverage report in browser
68-
@make gen-coverage
81+
@$(MAKE) gen-coverage
6982
@go tool cover -html $(PWD)/tmp/total.cov
7083

7184
gen-coverage: ## Generate test coverage report
@@ -74,16 +87,20 @@ gen-coverage: ## Generate test coverage report
7487
@mkdir tmp
7588
@mkdir tmp/unit
7689
@mkdir tmp/integration
77-
@make test-unit coverage=true
78-
@make test-integ coverage=true
79-
@make build-coverage
90+
@$(MAKE) test-unit coverage=true
91+
@$(MAKE) test-integ coverage=true
92+
@$(MAKE) build-coverage
8093

8194
build-coverage:
8295
@go tool covdata textfmt -i=$(PWD)/tmp/unit,$(PWD)/tmp/integration -o $(PWD)/tmp/total.cov
8396

8497
##@ Development
8598
lint: ## Run lint on the package
86-
@make linters
99+
@$(MAKE) linters
100+
101+
lint.local: ## Run lint locally (not in Docker) with all build tags
102+
@printf "\033[2m→ Running golangci-lint locally with all build tags...\033[0m\n"
103+
golangci-lint run --fix --build-tags $(GOLANGCI_LINT_BUILD_TAGS) --timeout=5m -v ./...
87104

88105
package := "prettier"
89106
lint.markdown:
@@ -227,8 +244,8 @@ cluster.scale.3: ## Start full 3-node cluster
227244

228245
cluster.get-cert:
229246
@if [[ -v SECURE_INTEGRATION ]] && [[ $$SECURE_INTEGRATION == "true" ]]; then \
230-
docker cp $$(docker compose --project-directory .ci/opensearch ps --format '{{.Name}}'):/usr/share/opensearch/config/kirk.pem admin.pem && \
231-
docker cp $$(docker compose --project-directory .ci/opensearch ps --format '{{.Name}}'):/usr/share/opensearch/config/kirk-key.pem admin.key; \
247+
docker cp $$(docker compose --project-directory .ci/opensearch ps --format '{{.Name}}' | head -1):/usr/share/opensearch/config/kirk.pem admin.pem && \
248+
docker cp $$(docker compose --project-directory .ci/opensearch ps --format '{{.Name}}' | head -1):/usr/share/opensearch/config/kirk-key.pem admin.key; \
232249
fi
233250

234251

@@ -239,23 +256,23 @@ cluster.clean: ## Remove unused Docker volumes and networks
239256
docker system prune --volumes --force
240257

241258
linters:
242-
docker run -t --rm -v $$(pwd):/app -v ~/.cache/golangci-lint/$(GOLANGCI_LINT_VERSION):/root/.cache -w /app golangci/golangci-lint:$(GOLANGCI_LINT_VERSION) golangci-lint run --fix --build-tags "integration core" --timeout=5m -v ./...
259+
docker run -t --rm -v $$(pwd):/app -v ~/.cache/golangci-lint/$(GOLANGCI_LINT_VERSION):/root/.cache -w /app golangci/golangci-lint:$(GOLANGCI_LINT_VERSION) golangci-lint run --fix --build-tags $(GOLANGCI_LINT_BUILD_TAGS) --timeout=5m -v ./...
243260

244261
workflow: ## Run all github workflow commands here sequentially
245262

246263
# Lint
247-
make lint
264+
$(MAKE) lint
248265
# License Checker
249266
.github/check-license-headers.sh
250267
# Unit Test
251-
make test-unit race=true
268+
$(MAKE) test-unit race=true
252269
# Benchmarks Test
253-
make test-bench
270+
$(MAKE) test-bench
254271
# Integration Test
255272
### OpenSearch
256-
make cluster.clean cluster.build cluster.start
257-
make test-integ race=true
258-
make cluster.stop
273+
$(MAKE) cluster.clean cluster.build cluster.start
274+
$(MAKE) test-integ race=true
275+
$(MAKE) cluster.stop
259276

260277
##@ Other
261278
#------------------------------------------------------------------------------
@@ -264,5 +281,5 @@ help: ## Display help
264281
#------------- <https://suva.sh/posts/well-documented-makefiles> --------------
265282

266283
.DEFAULT_GOAL := help
267-
.PHONY: help backport cluster cluster.clean coverage godoc lint release test test-bench test-integ test-unit linters linters.install
284+
.PHONY: help backport cluster cluster.clean coverage godoc lint lint.local release test test-all test-race test-bench test-integ test-unit linters linters.install
268285
.SILENT: lint.markdown

USER_GUIDE.md

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -210,13 +210,19 @@ Before starting, we strongly recommend reading the full AWS documentation regard
210210
>
211211
> See [Managed Domains signing-service requests.](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/ac.html#managedomains-signing-service-requests)
212212
213-
Depending on the version of AWS SDK used, import the v1 or v2 request signer from `signer/aws` or `signer/awsv2` respectively. Both signers are equivalent in their functionality, they provide AWS Signature Version 4 (SigV4).
213+
Depending on the version of AWS SDK used, import the request signer from `signer/aws` (recommended) or `signer/awsv2`. Both signers use AWS SDK v2 and provide AWS Signature Version 4 (SigV4).
214+
215+
**BREAKING CHANGE**: As of this version, the main `signer/aws` package has been migrated from AWS SDK v1 to AWS SDK v2 due to AWS SDK v1 reaching end-of-support on July 31, 2025.
214216

215217
To read more about SigV4 see [Signature Version 4 signing process](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)
216218

217219
Here are some Go samples that show how to sign each OpenSearch request and automatically search for AWS credentials from the ~/.aws folder or environment variables:
218220

219-
### AWS SDK v1
221+
### AWS SDK v2 (Recommended)
222+
223+
**Migration Note**: If you were previously using `signer/aws` with AWS SDK v1, you need to update your imports and configuration as shown below.
224+
225+
**Credential Caching**: The signer automatically enables credential caching for improved performance, especially when using STS credentials (assume role, web identity, etc.). This reduces API calls to AWS STS and improves signing performance.
220226

221227
```go
222228
package main
@@ -226,7 +232,8 @@ import (
226232
"fmt"
227233
"os"
228234

229-
"github.com/aws/aws-sdk-go/aws/session"
235+
"github.com/aws/aws-sdk-go-v2/aws"
236+
"github.com/aws/aws-sdk-go-v2/config"
230237
requestsigner "github.com/opensearch-project/opensearch-go/v4/signer/aws"
231238

232239
"github.com/opensearch-project/opensearch-go/v4"
@@ -245,16 +252,22 @@ func main() {
245252
const endpoint = "" // e.g. https://opensearch-domain.region.com
246253

247254
func example() error {
248-
// Create an AWS request Signer and load AWS configuration using default config folder or env vars.
249-
// See https://docs.aws.amazon.com/opensearch-service/latest/developerguide/request-signing.html#request-signing-go
250-
signer, err := requestsigner.NewSignerWithService(
251-
session.Options{SharedConfigState: session.SharedConfigEnable},
252-
requestsigner.OpenSearchService, // Use requestsigner.OpenSearchServerless for Amazon OpenSearch Serverless.
253-
)
255+
ctx := context.Background()
256+
257+
// Load AWS configuration
258+
awsCfg, err := config.LoadDefaultConfig(ctx)
254259
if err != nil {
255260
return err
256261
}
257-
// Create an opensearch client and use the request-signer.
262+
263+
// Create an AWS request Signer
264+
signer, err := requestsigner.NewSignerWithService(awsCfg, requestsigner.OpenSearchService)
265+
// Use requestsigner.OpenSearchServerless for Amazon OpenSearch Serverless
266+
if err != nil {
267+
return err
268+
}
269+
270+
// Create an opensearch client and use the request-signer
258271
client, err := opensearchapi.NewClient(
259272
opensearchapi.Config{
260273
Client: opensearch.Config{
@@ -267,20 +280,18 @@ func example() error {
267280
return err
268281
}
269282

270-
ctx := context.Background()
271-
272283
ping, err := client.Ping(ctx, nil)
273284
if err != nil {
274285
return err
275286
}
276287

277-
fmt.Println(ping)
288+
fmt.Println(ping)
278289

279290
return nil
280291
}
281292
```
282293

283-
### AWS SDK v2
294+
### Alternative: Using signer/awsv2
284295

285296
Use the AWS SDK v2 for Go to authenticate with Amazon OpenSearch service.
286297

error_test.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func TestError(t *testing.T) {
5050
assert.True(t, resp.IsError())
5151
err := opensearch.ParseError(resp)
5252
var testError *opensearch.StructError
53-
require.True(t, errors.As(err, &testError))
53+
require.ErrorAs(t, err, &testError)
5454
assert.Equal(t, http.StatusBadRequest, testError.Status)
5555
assert.Equal(t, "resource_already_exists_exception", testError.Err.Type)
5656
assert.Equal(t, "index [test/HU2mN_RMRXGcS38j3yV-VQ] already exists", testError.Err.Reason)
@@ -92,7 +92,7 @@ func TestError(t *testing.T) {
9292
assert.True(t, resp.IsError())
9393
err := opensearch.ParseError(resp)
9494
var testError *opensearch.StructError
95-
require.True(t, errors.As(err, &testError))
95+
require.ErrorAs(t, err, &testError)
9696
assert.Equal(t, http.StatusBadRequest, testError.Status)
9797
assert.Equal(t, "illegal_argument_exception", testError.Err.Type)
9898
assert.Equal(t, "composable template [posts] template after composition is invalid", testError.Err.Reason)
@@ -117,14 +117,14 @@ func TestError(t *testing.T) {
117117
}`),
118118
)
119119
body, err := io.ReadAll(reader)
120-
require.Nil(t, err)
120+
require.NoError(t, err)
121121

122122
var errStruct *opensearch.StructError
123123
err = json.Unmarshal(body, &errStruct)
124-
assert.NotNil(t, err)
124+
assert.Error(t, err)
125125

126126
var jsonError *json.UnmarshalTypeError
127-
assert.True(t, errors.As(err, &jsonError))
127+
assert.ErrorAs(t, err, &jsonError)
128128
})
129129
t.Run("string", func(t *testing.T) {
130130
reader := io.NopCloser(
@@ -134,14 +134,14 @@ func TestError(t *testing.T) {
134134
}`),
135135
)
136136
body, err := io.ReadAll(reader)
137-
require.Nil(t, err)
137+
require.NoError(t, err)
138138

139139
var errStruct *opensearch.StructError
140140
err = json.Unmarshal(body, &errStruct)
141-
assert.NotNil(t, err)
141+
assert.Error(t, err)
142142

143143
var errStr *opensearch.StringError
144-
require.True(t, errors.As(err, &errStr))
144+
require.ErrorAs(t, err, &errStr)
145145
})
146146
})
147147
})
@@ -159,7 +159,7 @@ func TestError(t *testing.T) {
159159
assert.True(t, resp.IsError())
160160
err := opensearch.ParseError(resp)
161161
var testError *opensearch.StringError
162-
require.True(t, errors.As(err, &testError))
162+
require.ErrorAs(t, err, &testError)
163163
assert.Equal(t, http.StatusMethodNotAllowed, testError.Status)
164164
assert.Contains(t, testError.Err, "Incorrect HTTP method for uri")
165165
_ = fmt.Sprintf("%s", testError)
@@ -175,7 +175,7 @@ func TestError(t *testing.T) {
175175
assert.True(t, resp.IsError())
176176
err := opensearch.ParseError(resp)
177177
var testError *opensearch.StringError
178-
require.True(t, errors.As(err, &testError))
178+
require.ErrorAs(t, err, &testError)
179179
assert.Equal(t, http.StatusNotFound, testError.Status)
180180
assert.Contains(t, testError.Err, "{\"_index\":\"index\",\"_id\":\"2\",\"matched\":false}")
181181
_ = fmt.Sprintf("%s", testError)
@@ -193,7 +193,7 @@ func TestError(t *testing.T) {
193193
assert.True(t, resp.IsError())
194194
err := opensearch.ParseError(resp)
195195
var testError *opensearch.Error
196-
require.True(t, errors.As(err, &testError))
196+
require.ErrorAs(t, err, &testError)
197197
assert.Contains(t, testError.Err, "no handler found for uri [/_plugins/_security/xxx] and method [GET]")
198198
_ = fmt.Sprintf("%s", testError)
199199
})
@@ -214,7 +214,7 @@ func TestError(t *testing.T) {
214214
assert.True(t, resp.IsError())
215215
err := opensearch.ParseError(resp)
216216
var testError *opensearch.ReasonError
217-
require.True(t, errors.As(err, &testError))
217+
require.ErrorAs(t, err, &testError)
218218
assert.Equal(t, "error", testError.Status)
219219
assert.Contains(t, testError.Reason, "Invalid configuration")
220220
_ = fmt.Sprintf("%s", testError)
@@ -230,7 +230,7 @@ func TestError(t *testing.T) {
230230
assert.True(t, resp.IsError())
231231
err := opensearch.ParseError(resp)
232232
var testError *opensearch.MessageError
233-
require.True(t, errors.As(err, &testError))
233+
require.ErrorAs(t, err, &testError)
234234
assert.Equal(t, "BAD_REQUEST", testError.Status)
235235
assert.Contains(t, testError.Message, "Wrong request body")
236236
_ = fmt.Sprintf("%s", testError)
@@ -277,7 +277,7 @@ func TestError(t *testing.T) {
277277
t.Run(tt.Name, func(t *testing.T) {
278278
err := opensearch.ParseError(tt.Resp)
279279
for _, wantedError := range tt.WantedErrors {
280-
assert.True(t, errors.Is(err, wantedError))
280+
assert.ErrorIs(t, err, wantedError)
281281
}
282282
})
283283
}
@@ -289,7 +289,7 @@ func TestError(t *testing.T) {
289289
}
290290
assert.True(t, resp.IsError())
291291
err := opensearch.ParseError(resp)
292-
assert.True(t, errors.Is(err, opensearch.ErrJSONUnmarshalBody))
292+
assert.ErrorIs(t, err, opensearch.ErrJSONUnmarshalBody)
293293
})
294294
t.Run("too many requests", func(t *testing.T) {
295295
resp := &opensearch.Response{
@@ -298,7 +298,7 @@ func TestError(t *testing.T) {
298298
}
299299
assert.True(t, resp.IsError())
300300
err := opensearch.ParseError(resp)
301-
assert.True(t, errors.Is(err, opensearch.ErrJSONUnmarshalBody))
301+
assert.ErrorIs(t, err, opensearch.ErrJSONUnmarshalBody)
302302
})
303303
})
304304
}

0 commit comments

Comments
 (0)