Skip to content

Commit 96de597

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 4422fd6 commit 96de597

120 files changed

Lines changed: 7950 additions & 3165 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.

.ci/opensearch/Dockerfile.opensearch

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ RUN if [ "$SECURE_INTEGRATION" != "true" ] ; then \
4444
# Ensure the repository mount point has correct permissions for snapshots
4545
RUN mkdir -p /usr/share/opensearch/mnt && chown opensearch:opensearch /usr/share/opensearch/mnt
4646

47-
HEALTHCHECK --start-period=20s --interval=30s \
48-
CMD curl -sf --retry 5 --max-time 5 --retry-delay 5 --retry-max-time 30 --retry-all-errors \
49-
$(if $SECURE_INTEGRATION; then echo "--cert config/kirk.pem --key config/kirk-key.pem -k https://"; fi)"localhost:9200" \
50-
|| bash -c 'kill -s 15 -1 && (sleep 10; kill -s 9 -1)'
47+
HEALTHCHECK --start-period=60s --interval=30s --timeout=10s --retries=3 \
48+
CMD curl -sf --max-time 5 \
49+
$(if [ "$SECURE_INTEGRATION" = "true" ]; then echo "--cert config/kirk.pem --key config/kirk-key.pem -k https://"; else echo "http://"; fi)localhost:9200/_cluster/health

.ci/opensearch/docker-compose.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ services:
2828
- http.publish_host=localhost
2929
- http.publish_port=9200
3030
# Memory settings
31-
- OPENSEARCH_JAVA_OPTS=-Xms${OPENSEARCH_HEAP_SIZE:-1g} -Xmx${OPENSEARCH_HEAP_SIZE:-1g}
31+
- OPENSEARCH_JAVA_OPTS=-Xms${OPENSEARCH_HEAP_SIZE:-1g} -Xmx${OPENSEARCH_HEAP_SIZE:-1g} ${OPENSEARCH_JAVA_OPTS_EXTRA}
3232
ports:
3333
- "9200:9200"
3434
- "9300:9300"
@@ -72,7 +72,7 @@ services:
7272
- http.publish_host=localhost
7373
- http.publish_port=9201
7474
# Memory settings
75-
- OPENSEARCH_JAVA_OPTS=-Xms${OPENSEARCH_HEAP_SIZE:-1g} -Xmx${OPENSEARCH_HEAP_SIZE:-1g}
75+
- OPENSEARCH_JAVA_OPTS=-Xms${OPENSEARCH_HEAP_SIZE:-1g} -Xmx${OPENSEARCH_HEAP_SIZE:-1g} ${OPENSEARCH_JAVA_OPTS_EXTRA}
7676
ports:
7777
- "9201:9200"
7878
- "9301:9300"
@@ -116,7 +116,7 @@ services:
116116
- http.publish_host=localhost
117117
- http.publish_port=9202
118118
# Memory settings
119-
- OPENSEARCH_JAVA_OPTS=-Xms${OPENSEARCH_HEAP_SIZE:-1g} -Xmx${OPENSEARCH_HEAP_SIZE:-1g}
119+
- OPENSEARCH_JAVA_OPTS=-Xms${OPENSEARCH_HEAP_SIZE:-1g} -Xmx${OPENSEARCH_HEAP_SIZE:-1g} ${OPENSEARCH_JAVA_OPTS_EXTRA}
120120
ports:
121121
- "9202:9200"
122122
- "9302:9300"

.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

.github/workflows/test-integration.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,12 @@ jobs:
5757
fi
5858
echo "=====> Attempt $attempt/25 - waiting..."
5959
done
60-
- run: make test-integ-core test-integ-plugins race=true
60+
- run: make test-integ-core test-integ-plugins race=true coverage=true
61+
- uses: codecov/codecov-action@v5
62+
with:
63+
token: ${{ secrets.CODECOV_TOKEN }}
64+
files: tmp/integ.cov
65+
flags: integration
6166

6267
secured:
6368
name: Tests against secure cluster

CHANGELOG.md

Lines changed: 8 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
- Test parallelization support via TEST_PARALLEL environment variable (default: CPU cores - 1, minimum 1)
1111
- opensearchutil/testutil package with PollUntil helper for eventual consistency testing (ISM policies, index readiness, cluster state changes)
1212
- Configuration option `IncludeDedicatedClusterManagers` for controlling cluster manager node routing ([#765](https://github.com/opensearch-project/opensearch-go/issues/765))
@@ -44,6 +44,12 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
4444
- `opensearch.Config` and `opensearchtransport.Config` now accept optional `Context` and `CancelFunc` fields
4545
- `opensearchutil.BulkIndexerConfig` now accepts optional `Context` and `CancelFunc` fields
4646
- Enables proper context propagation for timeouts, cancellation, and graceful shutdown
47+
- Role compatibility validation prevents conflicting role assignments (master+cluster_manager, warm+search)
48+
- OpenSearch 3.0+ searchable snapshots now use `warm` role instead of deprecated `search` role
49+
- **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
50+
- Constructor now takes `aws.Config` instead of `session.Options`
51+
- See USER_GUIDE.md for details required to migrate
52+
- Users who need access to the existing `signer/awsv2` API can still use it, however they are encouraged to migrate to `signer/aws`
4753

4854
### Deprecated
4955

@@ -323,7 +329,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
323329

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

Makefile

Lines changed: 103 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ ifdef race
1616
$(eval testunitargs += "-race")
1717
endif
1818
$(eval testunitargs += "-cover" "./..." "-args" "-test.gocoverdir=$(PWD)/tmp/unit")
19+
@rm -rf $(PWD)/tmp/unit
1920
@mkdir -p $(PWD)/tmp/unit
2021
@echo "go test -v" $(testunitargs); \
2122
go test -v $(testunitargs);
@@ -35,6 +36,7 @@ ifdef race
3536
endif
3637
$(eval TEST_PARALLEL ?= $(shell ncpu=$$(go env GOMAXPROCS 2>/dev/null); [ -z "$$ncpu" ] && ncpu=$$(sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4); parallel=$$((ncpu - 1)); [ $$parallel -lt 1 ] && parallel=1; echo $$parallel))
3738
$(eval testintegargs += "-cover" "-tags=$(testintegtags)" "-timeout=10m" "-parallel=$(TEST_PARALLEL)" "./..." "-args" "-test.gocoverdir=$(PWD)/tmp/integration")
39+
@rm -rf $(PWD)/tmp/integration
3840
@mkdir -p $(PWD)/tmp/integration
3941
@echo "go test -v" $(testintegargs); \
4042
go test -v $(testintegargs);
@@ -230,6 +232,7 @@ cluster.build:
230232

231233
cluster.start:
232234
@$(MAKE) cluster.docker-up
235+
@$(MAKE) cluster.wait-ready
233236
@$(MAKE) cluster.get-cert
234237

235238
cluster.stop:
@@ -238,6 +241,7 @@ cluster.stop:
238241
cluster.docker-build:
239242
@# Determine version-specific settings
240243
$(eval OPENSEARCH_VERSION ?= latest)
244+
$(eval SECURE_INTEGRATION ?= false)
241245
$(eval version_major := $(shell \
242246
if [ "$(OPENSEARCH_VERSION)" = "latest" ]; then \
243247
echo "2"; \
@@ -252,13 +256,14 @@ cluster.docker-build:
252256
echo "cluster_manager"; \
253257
fi \
254258
))
255-
@echo "Building OpenSearch $(OPENSEARCH_VERSION) with role: $(manager_role)"
259+
@echo "Building OpenSearch $(OPENSEARCH_VERSION) with role: $(manager_role), secure: $(SECURE_INTEGRATION)"
256260
OPENSEARCH_MANAGER_ROLE=$(manager_role) OPENSEARCH_MANAGER_SETTING=$(manager_role) \
257261
docker compose --project-directory .ci/opensearch build --pull
258262

259263
cluster.docker-up:
260264
@# Determine version-specific settings
261265
$(eval OPENSEARCH_VERSION ?= latest)
266+
$(eval SECURE_INTEGRATION ?= false)
262267
$(eval version_major := $(shell \
263268
if [ "$(OPENSEARCH_VERSION)" = "latest" ]; then \
264269
echo "2"; \
@@ -273,9 +278,25 @@ cluster.docker-up:
273278
echo "cluster_manager"; \
274279
fi \
275280
))
276-
@echo "Starting OpenSearch $(OPENSEARCH_VERSION) with role: $(manager_role)"
277-
OPENSEARCH_MANAGER_ROLE=$(manager_role) OPENSEARCH_MANAGER_SETTING=$(manager_role) \
278-
docker compose --project-directory .ci/opensearch up -d
281+
@# Apply cgroup workaround for OpenSearch 2.0.1-2.3.0
282+
$(eval java_opts_extra := $(shell \
283+
if [ "$(OPENSEARCH_VERSION)" != "latest" ]; then \
284+
version() { echo "$$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $$1,$$2,$$3,$$4); }'; }; \
285+
v=$$(version $(OPENSEARCH_VERSION)); \
286+
v_min=$$(version 2.0.1); \
287+
v_max=$$(version 2.3.0); \
288+
if [ $$v -ge $$v_min ] && [ $$v -le $$v_max ]; then \
289+
echo " -XX:-UseContainerSupport"; \
290+
fi; \
291+
fi \
292+
))
293+
@echo "Starting OpenSearch $(OPENSEARCH_VERSION) with role: $(manager_role), secure: $(SECURE_INTEGRATION)"
294+
export SECURE_INTEGRATION=$(SECURE_INTEGRATION); \
295+
export OPENSEARCH_VERSION=$(OPENSEARCH_VERSION); \
296+
export OPENSEARCH_MANAGER_ROLE=$(manager_role); \
297+
export OPENSEARCH_MANAGER_SETTING=$(manager_role); \
298+
export OPENSEARCH_JAVA_OPTS_EXTRA="$(java_opts_extra)"; \
299+
docker compose --project-directory .ci/opensearch up -d
279300

280301
cluster.scale.1: ## Start single-node cluster
281302
docker compose --project-directory .ci/opensearch up -d --scale opensearch-node2=0 --scale opensearch-node3=0;
@@ -297,6 +318,84 @@ cluster.get-cert:
297318
docker cp $$CONTAINER:/usr/share/opensearch/config/kirk-key.pem admin.key; \
298319
fi
299320

321+
cluster.wait-ready: ## Poll cluster until health status is green or yellow
322+
@printf "\033[2m-> Waiting for cluster to be ready...\033[0m\n"
323+
@{ \
324+
set -e; \
325+
MAX_ATTEMPTS=60; \
326+
ATTEMPT=1; \
327+
HTTP_URL="http://localhost:9200/_cluster/health"; \
328+
HTTPS_URL="https://localhost:9200/_cluster/health"; \
329+
HEALTH_URL=""; \
330+
CURL_OPTS=""; \
331+
VERSION="$${OPENSEARCH_VERSION:-latest}"; \
332+
if [ "$$VERSION" = "latest" ]; then \
333+
PASSWORD="myStrongPassword123!"; \
334+
else \
335+
MAJOR=$$(echo "$$VERSION" | cut -d. -f1); \
336+
MINOR=$$(echo "$$VERSION" | cut -d. -f2); \
337+
if [ $$MAJOR -gt 2 ] || ([ $$MAJOR -eq 2 ] && [ $$MINOR -ge 12 ]); then \
338+
PASSWORD="myStrongPassword123!"; \
339+
else \
340+
PASSWORD="admin"; \
341+
fi; \
342+
fi; \
343+
while [ $$ATTEMPT -le $$MAX_ATTEMPTS ]; do \
344+
if [ -z "$$HEALTH_URL" ]; then \
345+
if curl -sf "$$HTTP_URL" > /dev/null 2>&1; then \
346+
printf "\033[36m→ Detected insecure cluster (HTTP)\033[0m\n"; \
347+
HEALTH_URL="$$HTTP_URL"; \
348+
CURL_OPTS=""; \
349+
elif curl -sf -k -u "admin:$$PASSWORD" "$$HTTPS_URL" > /dev/null 2>&1; then \
350+
printf "\033[36m→ Detected secure cluster (HTTPS)\033[0m\n"; \
351+
HEALTH_URL="$$HTTPS_URL"; \
352+
CURL_OPTS="-k -u admin:$$PASSWORD"; \
353+
else \
354+
printf "\033[33m⋯ Waiting for cluster to respond (attempt $$ATTEMPT/$$MAX_ATTEMPTS)\033[0m\n"; \
355+
ATTEMPT=$$((ATTEMPT + 1)); \
356+
sleep 2; \
357+
continue; \
358+
fi; \
359+
fi; \
360+
if curl -sf $$CURL_OPTS "$$HEALTH_URL" > /dev/null 2>&1; then \
361+
STATUS=$$(curl -sf $$CURL_OPTS "$$HEALTH_URL" | grep -o '"status":"[^"]*"' | cut -d'"' -f4); \
362+
if [ "$$STATUS" = "green" ] || [ "$$STATUS" = "yellow" ]; then \
363+
printf "\033[32m✓ Cluster is ready (status: $$STATUS) after $$ATTEMPT attempts\033[0m\n"; \
364+
INFO_URL="$${HEALTH_URL%%/_cluster/health}"; \
365+
CLUSTER_INFO=$$(curl -sf $$CURL_OPTS "$$INFO_URL" 2>/dev/null); \
366+
if [ -n "$$CLUSTER_INFO" ]; then \
367+
CLUSTER_NAME=$$(echo "$$CLUSTER_INFO" | grep -o '"cluster_name":"[^"]*"' | cut -d'"' -f4); \
368+
CLUSTER_VERSION=$$(echo "$$CLUSTER_INFO" | grep -o '"number":"[^"]*"' | head -1 | cut -d'"' -f4); \
369+
printf "\033[2m Cluster: $$CLUSTER_NAME\033[0m\n"; \
370+
printf "\033[2m Version: $$CLUSTER_VERSION\033[0m\n"; \
371+
printf "\033[2m URL: $$INFO_URL\033[0m\n"; \
372+
if [ -n "$$CURL_OPTS" ]; then \
373+
printf "\033[2m Auth: admin:****\033[0m\n"; \
374+
fi; \
375+
fi; \
376+
exit 0; \
377+
fi; \
378+
printf "\033[33m⋯ Cluster status: $$STATUS (attempt $$ATTEMPT/$$MAX_ATTEMPTS)\033[0m\n"; \
379+
else \
380+
printf "\033[33m⋯ Waiting for cluster to respond (attempt $$ATTEMPT/$$MAX_ATTEMPTS)\033[0m\n"; \
381+
fi; \
382+
ATTEMPT=$$((ATTEMPT + 1)); \
383+
sleep 2; \
384+
done; \
385+
printf "\033[31m✗ Cluster failed to become ready after $$MAX_ATTEMPTS attempts\033[0m\n"; \
386+
printf "\033[2m\n--- Diagnostic Information ---\033[0m\n"; \
387+
printf "\033[2mDocker containers:\033[0m\n"; \
388+
docker compose --project-directory .ci/opensearch ps || true; \
389+
printf "\033[2m\nFull logs from all containers:\033[0m\n"; \
390+
docker compose --project-directory .ci/opensearch logs || true; \
391+
printf "\033[2m\nAttempted URLs:\033[0m\n"; \
392+
printf " HTTP: $$HTTP_URL\n"; \
393+
printf " HTTPS: $$HTTPS_URL\n"; \
394+
printf "\033[2m\nCurl test results:\033[0m\n"; \
395+
printf " HTTP: "; curl -sf "$$HTTP_URL" && echo "✓ OK" || echo "✗ Failed"; \
396+
printf " HTTPS: "; curl -sf -k -u "admin:$$PASSWORD" "$$HTTPS_URL" && echo "✓ OK" || echo "✗ Failed"; \
397+
exit 1; \
398+
}
300399

301400
cluster.clean: ## Remove unused Docker volumes and networks
302401
@printf "\033[2m-> Cleaning up Docker assets...\033[0m\n"

USER_GUIDE.md

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,19 @@ Before starting, we strongly recommend reading the full AWS documentation regard
221221
>
222222
> See [Managed Domains signing-service requests.](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/ac.html#managedomains-signing-service-requests)
223223
224-
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).
224+
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).
225+
226+
**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.
225227

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

228230
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:
229231

230-
### AWS SDK v1
232+
### AWS SDK v2 (Recommended)
233+
234+
**Migration Note**: If you were previously using `signer/aws` with AWS SDK v1, you need to update your imports and configuration as shown below.
235+
236+
**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.
231237

232238
```go
233239
package main
@@ -237,7 +243,8 @@ import (
237243
"fmt"
238244
"os"
239245

240-
"github.com/aws/aws-sdk-go/aws/session"
246+
"github.com/aws/aws-sdk-go-v2/aws"
247+
"github.com/aws/aws-sdk-go-v2/config"
241248
requestsigner "github.com/opensearch-project/opensearch-go/v4/signer/aws"
242249

243250
"github.com/opensearch-project/opensearch-go/v4"
@@ -256,16 +263,22 @@ func main() {
256263
const endpoint = "" // e.g. https://opensearch-domain.region.com
257264

258265
func example() error {
259-
// Create an AWS request Signer and load AWS configuration using default config folder or env vars.
260-
// See https://docs.aws.amazon.com/opensearch-service/latest/developerguide/request-signing.html#request-signing-go
261-
signer, err := requestsigner.NewSignerWithService(
262-
session.Options{SharedConfigState: session.SharedConfigEnable},
263-
requestsigner.OpenSearchService, // Use requestsigner.OpenSearchServerless for Amazon OpenSearch Serverless.
264-
)
266+
ctx := context.Background()
267+
268+
// Load AWS configuration
269+
awsCfg, err := config.LoadDefaultConfig(ctx)
265270
if err != nil {
266271
return err
267272
}
268-
// Create an opensearch client and use the request-signer.
273+
274+
// Create an AWS request Signer
275+
signer, err := requestsigner.NewSignerWithService(awsCfg, requestsigner.OpenSearchService)
276+
// Use requestsigner.OpenSearchServerless for Amazon OpenSearch Serverless
277+
if err != nil {
278+
return err
279+
}
280+
281+
// Create an opensearch client and use the request-signer
269282
client, err := opensearchapi.NewClient(
270283
opensearchapi.Config{
271284
Client: opensearch.Config{
@@ -278,20 +291,18 @@ func example() error {
278291
return err
279292
}
280293

281-
ctx := context.Background()
282-
283294
ping, err := client.Ping(ctx, nil)
284295
if err != nil {
285296
return err
286297
}
287298

288-
fmt.Println(ping)
299+
fmt.Println(ping)
289300

290301
return nil
291302
}
292303
```
293304

294-
### AWS SDK v2
305+
### Alternative: Using signer/awsv2
295306

296307
Use the AWS SDK v2 for Go to authenticate with Amazon OpenSearch service.
297308

0 commit comments

Comments
 (0)