Skip to content

Commit f95f305

Browse files
authored
feat(opensearchapi,v5preview,osgen): partial-failure errors, operation classifier, default router, and union decode overhaul
Consolidates the gh-816 burn-down: a partial-failure error model with fine-grained masking, an HTTP-layer operation classifier, blocking node discovery with default-router injection, a generator overhaul (idiomatic naming, single-pass union decode, request-body union constructors), and the v5preview regeneration plus integration coverage and user docs that go with it. ## Partial-failure error model OpenSearch returns HTTP 200 for partial successes (bulk item failures, shard failures, single-doc replica failures), forcing callers to double-check responses after `err == nil`. New typed errors expose these through the standard Go `if err != nil` idiom; both `(resp, err)` are non-nil on partial failure and the response is fully populated. - Adds `PartialBulkError`, `PartialSearchError`, `ShardFailureError`, and helpers `IsPartialFailure`, `ToleratePartialFailures`, `RequireSuccessRate` for threshold-based tolerance. - Replaces the initial `Config.ReturnQueryErrors` boolean with `internal/errmask.ErrorMask`, a 15-bit field where each bit masks one wrapper schema in the proposed `x-error-responses` OpenAPI extension (`BulkItems`, `SearchShards`, `WriteShards`, `BroadcastShards`, `NodeFailures`, `BulkByScrollFailures`, `TaskFailures`, `MultiSearchItems`, `MultiDocItems`, `Snapshot{Create,Get}ShardFailures`, `SimulateDocFailures`, `RankEvalFailures`, `IngestionShardFailures`, `PitNodeFailures`). A set bit suppresses that category; the zero value reports every category. - Callers configure policy via `Config.Errors = errmask.BulkItems | errmask.SearchShards` or `OPENSEARCH_GO_ERROR_MASK` with `+/-` tokens (e.g. `+all,-bulk_items`). Lifecycle: - v4 default `errmask.All` preserves existing behavior. `Config.ReturnQueryErrors=true` is honored as a deprecated alias for `errmask.None`. - v5 default flips to `errmask.None` (safe by default). - v6 removes `Config.Errors` / `OPENSEARCH_GO_ERROR_MASK`; behavior is unconditionally `errmask.None`. Spec side: `opensearch-openapi.yaml` is patched with 15 `_common.errors___<Wrapper>` schemas and 115 operations gain an `x-error-responses` annotation, mirroring the upstream proposal in `opensearch-api-specification`. The local patch goes away cleanly once that PR lands and we re-bundle from source. Generator side: `cmd/osgen` reads `x-error-responses` into `ir.Operation.ErrorWrappers`; `cmd/osgen/errwrap` supplies a fallback for plugin operations the spec does not yet annotate. The dispatch fragment carries a data-driven `wrappers` map of `{Template, Applies}`, so generated code stays compilable when annotations land before the underlying response schema models the relevant field. ## OperationClassifier and bit-packed OperationID Adds a zero-allocation HTTP method+path classifier (reusing the existing `routeTrie`) that maps requests to structured `OperationID` values. Enables transparent metrics, tracing, and access-control middleware at the `http.RoundTripper` layer without per-operation wrappers. - `OperationID` is a bit-packed `int64` encoding R/W flag, category, and minor operation. `IsWrite`, `Category`, `Minor` support efficient bitwise filtering. `String()` returns Prometheus-friendly labels. - `OperationClassifier` is built from the canonical route table and is safe for concurrent use. Returns `OpOther` for unrecognized patterns. - `OperationID` field added to `trieLeaf`/`trieMatch`, `OpID()` to `Route`, `.Op()` to `RouteBuilder`. All 124 routes are tagged. ## Blocking DiscoverNodes and default Router injection - `DiscoverNodes` now waits for an in-flight discovery to complete (or for the context to be cancelled) instead of returning `nil` immediately, letting callers block until topology data is available after client construction. - `DiscoverNodesOnStart` becomes `*bool`. Auto-enabled when `OPENSEARCH_GO_ROUTER=true` and the caller did not set it. - `v5preview/opensearchapi.NewClient` and `NewDefaultClient` inject `opensearchtransport.NewDefaultRouter` when `config.Client.Router` is `nil`, opting v5preview clients into role-aware dispatch, RTT-based scoring, AIMD congestion-window, and shard-cost weighting by default. - `OPENSEARCH_GO_ROUTER` is a symmetric override: a falsy value (`false`/`0`) suppresses injection so `Router` stays `nil`, matching v4 behavior. Caller-provided Routers are preserved. - `internal/envvars.Falsy(name)` distinguishes "unset" from "explicitly opted out" so the v5preview rule reads as `!envvars.Falsy(envvars.Router)` without re-implementing parse logic. ## Generator: idiomatic naming, request-body unions, single-pass decode Naming rewrites at PascalCase boundaries: | Spec form | Idiomatic Go | |---------------|-----------------| | `Msearch` | `MSearch` | | `Mget` | `MGet` | | `Mtermvectors`| `MTermVectors` | | `Termvectors` | `TermVectors` | | `Forcemerge` | `ForceMerge` | | `Response` | `Resp` | The `Response -> Resp` rule additionally requires the trailing character to be uppercase, so standalone `SearchResponse` (a spec wrapper) does not collide with the operation-level `<Op>Resp` name. Hand-written types renamed for v4/v5 consistency: `BulkResponseItem -> BulkRespItem`, `ErrorResponseBase -> ErrorRespBase`, `MsearchErrors -> MSearchErrors`, `MsearchTemplateErrors -> MSearchTemplateErrors`. Request-body unions: - `splitUnionsFromSiblings` partitions request-body subtree unions (e.g. `ReindexSourceSort`) so they are routed to `UnionFragment` instead of being emitted as empty structs. - Plumbs `Op + Registry` into `UnionFragment` so plugin-package unions qualify cross-package branches as `opensearchapi.FieldSort`. - Each generated discriminated union gains `New<Union>From<Branch>(v <branch type>) <Union>` constructors per branch and a `SetRaw(json.RawMessage)` typed escape hatch. `SetRaw` clears the typed branch so `MarshalJSON` returns the raw bytes verbatim. Single-pass union decode: - Case A (merged): object unions with one permissive primary branch plus discriminated branch(es) (mget, msearch, indices-open). The primary is embedded and the common case decodes in a single `json.Unmarshal`; each discriminated branch is detected by the presence of its distinguishing key and decoded only when matched. Drops the `build.HasJSONKeys` map probe and per-item raw copy. - Case B (lazy `As<T>()`): aggregation/suggest result unions carry no wire discriminator, so they cannot be auto-selected. `UnmarshalJSON` only retains the raw bytes; generated `As<ConcreteType>()` accessors decode on demand. - Unions fitting neither (reindex bodies, plugin-defined task status) keep the existing try-each decoder; the classifier logs once per union name when it declines. - All union `UnmarshalJSON` aliases the owned response buffer (`u.raw = data`) rather than copying it; `RawJSON()` documents the borrowed-buffer contract. Measured impact on mget (1000 docs): decode allocations down ~2.7x (~43k -> ~16k allocs/op), time down ~1.7x (4.2ms -> 2.5ms). Bulk wrapper template now resolves its walked element type from the IR (via `bulkInnerItemType`) instead of hardcoding `BulkRespItem`, so future spec or naming changes propagate automatically. ## v5preview regeneration Mechanical output of `cmd/osgen` against the patched spec: - `clients_gen.go` declares `errors errmask.ErrorMask` on `Client` and `clientInit` takes the mask as a second arg. - 19 operation dispatch files emit per-wrapper post-`do()` blocks that return typed errors when the corresponding `errmask` bit is unset and the wire data carries a partial failure (`BulkItems`, `SearchShards`, `WriteShards`, `MultiSearchItems`). - Reserved-but-unemitted wrappers (`BroadcastShards`, `NodeFailures`, `BulkByScrollFailures`, `TaskFailures`, `MultiDocItems`, `Snapshot{Create,Get}ShardFailures`, `SimulateDocFailures`, `RankEvalFailures`, `IngestionShardFailures`, `PitNodeFailures`) carry annotations but no emission until detection logic lands. - Operations whose typed response shape lacks the field path a wrapper needs (`CreateResp` lacks `_shards`; msearch union items lack a top-level `Shards`) are silently skipped by the `Applies` guard; emission starts automatically once the response types catch up. - Idiomatic-naming pass touches every generated type containing `Msearch`, `Mget`, `Mtermvectors`, `Termvectors`, `Forcemerge`, or compound `*Response*` substrings. - Single-pass union decode applied to mget/msearch/indices-open success|error items and `As<T>()` accessors generated for aggregation and suggest result unions. ## Integration coverage and CI hardening v5preview integration tests drive real requests and assert decoded shape per server version: - aggregation: lazy `As<T>()` accessors (terms, date_histogram, stats, avg, sum, min, max, value_count, cardinality). - mget: merged success|error decode (`GetResult` found/not-found vs `MGetMultiGetError`). - msearch: merged success|error decode (`MSearchMultiSearchItem` vs `ErrorRespBase`), the first-byte-switch `SearchHitsMetadataTotal` union, and the `MultiSearchItemError` partial-failure surface. CI matrix changes: - Skip multi-node clusters on OpenSearch <=2.17.x to avoid the node-join/node-left coordinator race (`opensearch-project/OpenSearch#15521`, backported via #16118 on the 2.x line). Affected versions run with `OPENSEARCH_NODE_COUNT=1`. - `.github/workflows/test-compatibility.yml` adds a per-entry `node_count` matrix field: `1` for 1.3.20 through 2.17.1, `3` for 2.18.0 and later. Comment cites the upstream PRs so the cutoff is greppable. - `Makefile cluster.docker-up` respects `OPENSEARCH_NODE_COUNT` and passes `--scale opensearch-nodeN=0` to docker compose. Defaults to 3 for local development. - Widen the existing 2.1.0-only shard-routing test skip to all versions below 2.2.0 (security plugin `java.io.OptionalDataException` from non-thread-safe `User` serialization, fixed by `opensearch-project/security#1970`). - Generated-code emit/path test fixtures converted from raw `"GET"`/`"POST"` to `net/http http.Method*` constants for consistency with the rest of the suite. - `testutil.PollUpdate()` decouples jitter calculation from runtime backoff execution to fix a flake. ## Dependency bumps - `github.com/aws/aws-sdk-go-v2/config` 1.32.18 -> 1.32.20 - `github.com/aws/aws-sdk-go-v2/credentials` 1.19.17 -> 1.19.19 - `github.com/getkin/kin-openapi` v0.139.0 -> v0.140.0 Fixes: #852, #850 ## Documentation - `v5preview/opensearchapi/README.md`: Partial Failure Errors (Config.Errors, errmask, `OPENSEARCH_GO_ERROR_MASK`, typed errors, `opensearchapi.Errors` helper, per-Resp helpers, helper functions, operation constants) and Default Router Injection (truth table for `OPENSEARCH_GO_ROUTER`, opt-out semantics). - `v5preview/opensearchapi/MIGRATING.md`: v4 -> v5preview surface delta (import path, `Indices -> Index` on multi-index Req types, `DocumentID -> ID` on `IndexReq`, optional `Params` becomes `*Params`, optional `bool` query params become `*bool`, partial-failure type renames, errmask default flip, default Router injection). - `guides/error_handling.md`: Bulk/Search/Write partial-failure examples as paired v4/v5preview blocks; per-Resp helper subsection (`BulkItemFailures`, `SearchShardFailures`, `WriteShardFailures`, `MultiSearchItemFailures`, `PartialFailures(mask)`); error type reference covering v4 vs v5preview internal-field-type divergence. - `guides/bulk.md`: v4/v5preview field-name and `BulkResp.Items` shape divergences with paired error-iteration examples for v4's `[]map[string]BulkRespItem` and v5preview's `[]BulkItem`. - `UPGRADING.md`: keeps version-history essentials for the >=5.0 partial-failure model and v5preview Router injection; forward-links to the new package docs. Ref: #816 Ref: opensearch-project/opensearch-api-specification/pull/1137
1 parent f591483 commit f95f305

328 files changed

Lines changed: 41870 additions & 8888 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/docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ services:
1616
- cluster.name=opensearch-cluster
1717
- node.name=opensearch-node1
1818
- node.roles=${OPENSEARCH_MANAGER_ROLE:-cluster_manager},data,ingest
19-
- discovery.seed_hosts=opensearch-node1,opensearch-node2,opensearch-node3
20-
- cluster.initial_${OPENSEARCH_MANAGER_SETTING:-cluster_manager}_nodes=opensearch-node1,opensearch-node2,opensearch-node3
19+
- discovery.seed_hosts=${OPENSEARCH_SEED_HOSTS:-opensearch-node1,opensearch-node2,opensearch-node3}
20+
- cluster.initial_${OPENSEARCH_MANAGER_SETTING:-cluster_manager}_nodes=${OPENSEARCH_INITIAL_MANAGER_NODES:-opensearch-node1,opensearch-node2,opensearch-node3}
2121
- bootstrap.memory_lock=false # Disable memory locking for development
2222
- path.repo=/usr/share/opensearch/mnt
2323
- OPENSEARCH_INITIAL_ADMIN_PASSWORD=myStrongPassword123!

.github/workflows/test-compatibility.yml

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ name: Integration for Compatibility
33
on: [push, pull_request]
44

55
env:
6-
OPENSEARCH_NODE_COUNT: 3
76
CONTAINER_RUNTIME: docker
87

98
jobs:
@@ -12,42 +11,49 @@ jobs:
1211
continue-on-error: ${{ matrix.entry.opensearch_version == 'latest' }}
1312
strategy:
1413
fail-fast: false
14+
# OpenSearch <=2.17.x carries a node-join/node-left race condition
15+
# (fixed in 2.18 via opensearch-project/OpenSearch#15521, backported
16+
# via opensearch-project/OpenSearch#16118) that leaves a node in
17+
# cluster state but disconnected at the transport layer, breaking
18+
# NodesStats RPC fan-out indefinitely and hanging /_cat/nodes-based
19+
# readiness gates. Single-node clusters cannot hit the race, so
20+
# affected versions run with OPENSEARCH_NODE_COUNT=1 below.
1521
matrix:
1622
secured: ["true", "false"]
1723
entry:
18-
- { opensearch_version: 1.3.20 }
19-
- { opensearch_version: 2.0.1 }
20-
- { opensearch_version: 2.1.0 }
21-
- { opensearch_version: 2.2.1 }
22-
- { opensearch_version: 2.3.0 }
23-
- { opensearch_version: 2.4.1 }
24-
- { opensearch_version: 2.5.0 }
25-
- { opensearch_version: 2.6.0 }
26-
- { opensearch_version: 2.7.0 }
27-
- { opensearch_version: 2.8.0 }
28-
- { opensearch_version: 2.9.0 }
29-
- { opensearch_version: 2.10.0 }
30-
- { opensearch_version: 2.11.1 }
31-
- { opensearch_version: 2.12.0 }
32-
- { opensearch_version: 2.13.0 }
33-
- { opensearch_version: 2.14.0 }
34-
- { opensearch_version: 2.15.0 }
35-
- { opensearch_version: 2.16.0 }
36-
- { opensearch_version: 2.17.1 }
37-
- { opensearch_version: 2.18.0 }
38-
- { opensearch_version: 2.19.5 }
39-
- { opensearch_version: 3.0.0 }
40-
- { opensearch_version: 3.1.0 }
41-
- { opensearch_version: 3.2.0 }
42-
- { opensearch_version: 3.3.2 }
43-
- { opensearch_version: 3.4.0 }
44-
- { opensearch_version: 3.5.0 }
45-
- { opensearch_version: 3.6.0 }
46-
- { opensearch_version: latest }
24+
- { opensearch_version: 1.3.20, node_count: 1 }
25+
- { opensearch_version: 2.0.1, node_count: 1 }
26+
- { opensearch_version: 2.1.0, node_count: 1 }
27+
- { opensearch_version: 2.2.1, node_count: 1 }
28+
- { opensearch_version: 2.3.0, node_count: 1 }
29+
- { opensearch_version: 2.4.1, node_count: 1 }
30+
- { opensearch_version: 2.5.0, node_count: 1 }
31+
- { opensearch_version: 2.6.0, node_count: 1 }
32+
- { opensearch_version: 2.7.0, node_count: 1 }
33+
- { opensearch_version: 2.8.0, node_count: 1 }
34+
- { opensearch_version: 2.9.0, node_count: 1 }
35+
- { opensearch_version: 2.10.0, node_count: 1 }
36+
- { opensearch_version: 2.11.1, node_count: 1 }
37+
- { opensearch_version: 2.12.0, node_count: 1 }
38+
- { opensearch_version: 2.13.0, node_count: 1 }
39+
- { opensearch_version: 2.14.0, node_count: 1 }
40+
- { opensearch_version: 2.15.0, node_count: 1 }
41+
- { opensearch_version: 2.16.0, node_count: 1 }
42+
- { opensearch_version: 2.17.1, node_count: 1 }
43+
- { opensearch_version: 2.18.0, node_count: 3 }
44+
- { opensearch_version: 2.19.5, node_count: 3 }
45+
- { opensearch_version: 3.0.0, node_count: 3 }
46+
- { opensearch_version: 3.1.0, node_count: 3 }
47+
- { opensearch_version: 3.2.0, node_count: 3 }
48+
- { opensearch_version: 3.3.2, node_count: 3 }
49+
- { opensearch_version: 3.4.0, node_count: 3 }
50+
- { opensearch_version: 3.5.0, node_count: 3 }
51+
- { opensearch_version: 3.6.0, node_count: 3 }
52+
- { opensearch_version: latest, node_count: 3 }
4753
steps:
48-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
54+
- uses: actions/checkout@v6
4955

50-
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
56+
- uses: actions/setup-go@v6
5157
with: { go-version-file: 'go.mod' }
5258

5359
- run: go version
@@ -63,6 +69,7 @@ jobs:
6369
run: |
6470
export OPENSEARCH_VERSION=${{ matrix.entry.opensearch_version }}
6571
export SECURE_INTEGRATION=${{ matrix.secured }}
72+
export OPENSEARCH_NODE_COUNT=${{ matrix.entry.node_count }}
6673
make cluster.clean cluster.build cluster.start
6774
if [ "${SECURE_INTEGRATION}" = "true" ]; then
6875
CURL_URL="https://localhost:9200"
@@ -108,6 +115,7 @@ jobs:
108115
run: |
109116
export OPENSEARCH_VERSION=${{ matrix.entry.opensearch_version }}
110117
export SECURE_INTEGRATION=${{ matrix.secured }}
118+
export OPENSEARCH_NODE_COUNT=${{ matrix.entry.node_count }}
111119
make cluster.get-cert test-integ-core test-integ-plugins race=true
112120
113121
- name: Stop the OpenSearch cluster

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
77
### Added
88

99
- Add `cmd/osgen` code generator for typed path builders and API consumer files from the OpenAPI spec
10+
- v5preview/opensearchapi: `NewClient` and `NewDefaultClient` now inject `opensearchtransport.NewDefaultRouter` when `config.Client.Router` is nil, opting every v5preview client into intelligent request routing by default. The `OPENSEARCH_GO_ROUTER` env var preserves its v4 semantics end-to-end: `=true`/`=1` enables auto-discovery (via `DiscoverNodesOnStart`); `=false`/`=0` suppresses both Router injection and auto-discovery; unset injects the Router without auto-discovery. v4's `opensearchapi.NewClient` is unchanged. ([#816](https://github.com/opensearch-project/opensearch-go/issues/816))
11+
- Add `envvars.Falsy(name)` helper that distinguishes "explicitly opted out" from "unset" (Truthy collapses both into false). Used by v5preview's router injection rule.
1012
- Add `v5preview/opensearchapi/` package: regenerated v5-track API surface produced by `cmd/osgen` from the OpenAPI spec. Fully typed Req/Resp/Params structs, sub-clients matching OpenSearch namespaces (`client.Cat`, `client.Cluster`, `client.Indices`, etc.), and a `plugins/` subtree for ML/k-NN/security/ISM/etc. Coexists with `opensearchapi/` during the v4 -> v5 transition; see `v5preview/opensearchapi/README.md` for usage and `UPGRADING.md` for migration guidance ([#650](https://github.com/opensearch-project/opensearch-go/issues/650))
1113
- Add `primary_terms_map` and `split_shards_metadata` fields to ClusterState index metadata for OpenSearch >=3.6.0 compatibility
1214
- Add generic `opensearch.Do[T]()` function for compile-time pointer enforcement on response types, preventing a class of bugs where non-pointer values are silently passed to `Client.Do()` and fail at runtime during JSON unmarshaling. Includes `opensearch.NoBody` marker type for calls that expect no response body, unifying all internal dispatch through a single generic path ([#809](https://github.com/opensearch-project/opensearch-go/pull/809))
@@ -89,6 +91,27 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
8991
- Document environment variables in `guides/routing.md`
9092
- Document read-after-write visibility guarantees with operation-aware routing in `guides/routing.md`
9193
- Add adaptive `max_concurrent_shard_requests` derived from cluster-wide AIMD congestion window ([#800](https://github.com/opensearch-project/opensearch-go/issues/800))
94+
- Add partial failure error types (`PartialBulkError`, `PartialSearchError`, `ShardFailureError`, `MultiSearchItemError`) that surface HTTP 200 partial failures as typed Go errors, controlled by a per-category `errmask.ErrorMask` bitfield on `Config.Errors` ([#816](https://github.com/opensearch-project/opensearch-go/issues/816))
95+
- `PartialBulkError` returned from `Bulk` when `resp.Errors` is true, carries `FailedItems` and `SucceededCount`
96+
- `PartialSearchError` returned from `Search`, `MSearch`, `MSearchTemplate`, `SearchTemplate`, `Scroll.Get` when `_shards.failed > 0`
97+
- `ShardFailureError` returned from `Index`, `Document.Create`, `Document.Delete`, `Update` when replica shards fail
98+
- `MultiSearchItemError` returned from `MSearch`/`MSearchTemplate` for per-sub-response Error envelopes
99+
- `MSearchErrors` / `MSearchTemplateErrors` per-op containers (Go 1.20+ multi-error contract via `Unwrap() []error`) when 2+ wrapper categories fire on the same response
100+
- `PartialFailureError` marker interface with `IsPartial() bool` for type-switching across all partial-failure types
101+
- Per-Resp helper methods (`BulkItemFailures`, `SearchShardFailures`, `WriteShardFailures`, `MultiSearchItemFailures`) plus `PartialFailures(mask)` aggregator for focused inspection at the call site
102+
- `opensearchapi.Errors(err) []error` package-level helper that flattens single- and multi-wrapper errors into a uniform slice for `switch` dispatch
103+
- Helper functions: `IsPartialFailure`, `ToleratePartialFailures`, `RequireSuccessRate` for threshold-based error tolerance
104+
- Operation constants: `OperationIndex`, `OperationCreate`, `OperationUpdate`, `OperationDelete`
105+
- `Config.Errors *errmask.ErrorMask` replaces a single boolean: each bit suppresses one wrapper category. v4 defaults to `errmask.All` (mask everything, preserves pre-bitfield behavior); v5+ defaults to `errmask.Empty` (report everything)
106+
- `OPENSEARCH_GO_ERROR_MASK` environment variable overrides `Config.Errors` at runtime via comma-separated `+`/`-` tokens (lowercase snake_case wrapper names; unknown tokens silently dropped, debug-logged)
107+
- Both `(resp, error)` are non-nil on partial failure -- response is fully populated
108+
- `v5preview/opensearchapi` ports the same model with spec-driven types (regenerated from the OpenAPI `x-error-responses` extension on every `cmd/osgen` run)
109+
- Add `OperationClassifier` for zero-allocation HTTP method+path to `OperationID` mapping ([#816](https://github.com/opensearch-project/opensearch-go/issues/816))
110+
- Bit-packed `OperationID` (int64) encoding R/W flag, category, and minor operation
111+
- Masking helpers: `IsWrite`, `IsRead`, `Category`, `Minor`
112+
- `String()` returns Prometheus-friendly labels (e.g., `"search"`, `"bulk"`, `"doc_get"`)
113+
- Reuses existing `routeTrie` for O(path-segments) lookup, safe for concurrent use
114+
- Enables transparent metrics/tracing middleware at the `http.RoundTripper` layer
92115
- Transport automatically sets `max_concurrent_shard_requests` query parameter on search requests routed through a coordinator node
93116
- Value derived from a cluster-wide aggregate of all polled nodes' search pool wait-time and completion deltas, clamped to `[floor, cap]` (default: 5–256)
94117
- Cluster-wide signal correctly models data-node fan-out capacity: single hot nodes are diluted by healthy peers, and MCSR only drops when aggregate cluster pressure rises
@@ -198,6 +221,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
198221
- Skip shard routing integration tests on OpenSearch < 2.2.0 with security plugin due to server-side `OptionalDataException` from non-thread-safe User serialization (opensearch-project/security#1970)
199222
- Fix flaky `TestDefaultHealthCheck_RetryAfterMaxRetry`: replace wall-clock `time.Sleep` + `atomic.Int64` synchronization with context cancellation (`ctx.Done()`), and widen `maxRetryClusterHealth` to 5s so the baseline HTTP round-trip cannot race past the retry interval ([#787](https://github.com/opensearch-project/opensearch-go/pull/787))
200223
- Skip opensearchtransport integration tests on OpenSearch < 2.2.0 with security plugin due to server-side `OptionalDataException` from non-thread-safe User serialization (opensearch-project/security#1970)
224+
- Skip shard routing integration tests on OpenSearch < 2.2.0 with security plugin due to server-side `OptionalDataException` from non-thread-safe User serialization (opensearch-project/security#1970)
201225
- Fix connection lifecycle bug in multiServerPool.OnFailure where connections were scheduled for resurrection before being moved from ready to dead list, causing potential race conditions
202226
- Fix flaky connection integration test by replacing arbitrary sleep times with proper server readiness polling
203227
- Fix cluster readiness checks in integration tests to handle HTTPS cold start delays (increase timeout to 15s)

DEVELOPER_GUIDE.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,10 +385,27 @@ make gh.checks.failed # Only failed checks
385385

386386
## Code Generation
387387

388-
The `cmd/osgen` tool generates typed path builder structs (`internal/path/`) and API consumer files (`v5preview/opensearchapi/`, `v5preview/opensearchapi/plugins/`) from the published [OpenSearch OpenAPI specification](https://github.com/opensearch-project/opensearch-api-specification). It reads `x-operation-group`, `x-version-added`, `x-version-deprecated`, and `x-version-removed` extensions from the spec to produce version-aware Go source.
388+
The `cmd/osgen` tool generates typed path builder structs (`internal/path/`) and API consumer files (`v5preview/opensearchapi/`, `v5preview/opensearchapi/plugins/`) from the published [OpenSearch API specification](https://github.com/opensearch-project/opensearch-api-specification). It reads `x-operation-group`, `x-version-added`, `x-version-deprecated`, `x-version-removed`, and `x-error-responses` extensions from the spec to produce version-aware Go source.
389389

390390
The `v5preview/opensearchapi/` package is the v5-track API surface and coexists with the hand-written `opensearchapi/` package during the v4 -> v5 transition. New code should target `v5preview/opensearchapi/`; see `v5preview/opensearchapi/README.md` for usage and `UPGRADING.md` for migration guidance.
391391

392+
> **PRs that edit `*_gen.go` will be rejected.** These files are generated. To change them, send a PR against `cmd/osgen` (the generator) or against the [OpenSearch API specification](https://github.com/opensearch-project/opensearch-api-specification) (the input).
393+
394+
> **Spec-extension status.** Some `x-*` extensions osgen reads (notably `x-error-responses`) are still in flight upstream. Until they merge, the build pulls from the local `opensearch-openapi.yaml` checkout in this repo rather than the published spec. Once upstream catches up, the local file goes away.
395+
396+
### Partial-failure error generation
397+
398+
The `x-error-responses` extension on a spec operation declares the categories of partial failure that operation can produce. For each category named on an operation, osgen emits:
399+
400+
- A typed Go error (e.g. `*PartialBulkError`, `*PartialSearchError`, `*ShardFailureError`, `*MultiSearchItemError`) decoded from the response body when that category fires.
401+
- A bit on `errmask.ErrorMask` (PascalCase, e.g. `errmask.BulkItems`) plus the corresponding env-var token (`bulk_items`) so callers can suppress or surface it via `Config.Errors` or `OPENSEARCH_GO_ERROR_MASK`.
402+
- A per-Resp helper method on the operation's typed response (e.g. `BulkResp.BulkItemFailures()`, `SearchResp.SearchShardFailures()`).
403+
- A `PartialFailures(mask)` aggregator on the same Resp.
404+
405+
Operations that declare two or more categories also get a per-op error container (e.g. `*MSearchErrors`) implementing `Unwrap() []error`, used when more than one category fires on a single response.
406+
407+
The user-facing partial-failure model and best-practices guidance live in [`v5preview/opensearchapi/README.md`](v5preview/opensearchapi/README.md#partial-failure-errors) and [`guides/error_handling.md`](guides/error_handling.md). They deliberately omit `x-error-responses` terminology because callers don't need to read the spec to use the resulting errors. The spec-driven mechanics are documented here and in [`cmd/osgen/README.md`](cmd/osgen/README.md).
408+
392409
To regenerate (downloads the spec automatically if not cached):
393410

394411
```

Makefile

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -501,16 +501,35 @@ cluster.docker-up:
501501
fi \
502502
))
503503
$(eval OPENSEARCH_HEAP_SIZE ?= 1g)
504-
@echo "Starting OpenSearch $(OPENSEARCH_VERSION) with role: $(manager_role), secure: $(SECURE_INTEGRATION), heap: $(OPENSEARCH_HEAP_SIZE)"
504+
$(eval OPENSEARCH_NODE_COUNT ?= 3)
505+
@# OpenSearch <=2.17.x carries a node-join/node-left race condition
506+
@# (fixed in 2.18 via opensearch-project/OpenSearch#15521, backported
507+
@# via opensearch-project/OpenSearch#16118) that leaves a node in
508+
@# cluster state but disconnected at the transport layer, breaking
509+
@# NodesStats RPC fan-out indefinitely. Single-node clusters cannot
510+
@# hit the race; the workflow sets OPENSEARCH_NODE_COUNT=1 for those
511+
@# versions. Scale flags are derived inline below; an earlier form
512+
@# that nested a case statement inside an eval/shell broke CI.
513+
@echo "Starting OpenSearch $(OPENSEARCH_VERSION) with role: $(manager_role), secure: $(SECURE_INTEGRATION), heap: $(OPENSEARCH_HEAP_SIZE), nodes: $(OPENSEARCH_NODE_COUNT)"
505514
@OVERRIDES="$$(ls $(COMPOSE_DIR)/docker-compose.*-override.yml 2>/dev/null | xargs -n1 basename 2>/dev/null)"; \
506515
if [ -n "$$OVERRIDES" ]; then echo "Active overrides: $$OVERRIDES"; fi
507-
export SECURE_INTEGRATION=$(SECURE_INTEGRATION); \
516+
@export SECURE_INTEGRATION=$(SECURE_INTEGRATION); \
508517
export OPENSEARCH_VERSION=$(OPENSEARCH_VERSION); \
509518
export OPENSEARCH_MANAGER_ROLE=$(manager_role); \
510519
export OPENSEARCH_MANAGER_SETTING=$(manager_role); \
511520
export OPENSEARCH_HEAP_SIZE=$(OPENSEARCH_HEAP_SIZE); \
512521
export OPENSEARCH_JAVA_OPTS_EXTRA="$(java_opts_extra)"; \
513-
$(CTR_COMPOSE) up -d
522+
SCALE_ARGS=""; \
523+
if [ "$(OPENSEARCH_NODE_COUNT)" = "1" ]; then \
524+
SCALE_ARGS="--scale opensearch-node2=0 --scale opensearch-node3=0"; \
525+
export OPENSEARCH_SEED_HOSTS="opensearch-node1"; \
526+
export OPENSEARCH_INITIAL_MANAGER_NODES="opensearch-node1"; \
527+
elif [ "$(OPENSEARCH_NODE_COUNT)" = "2" ]; then \
528+
SCALE_ARGS="--scale opensearch-node3=0"; \
529+
export OPENSEARCH_SEED_HOSTS="opensearch-node1,opensearch-node2"; \
530+
export OPENSEARCH_INITIAL_MANAGER_NODES="opensearch-node1,opensearch-node2"; \
531+
fi; \
532+
$(CTR_COMPOSE) up -d $$SCALE_ARGS
514533

515534
##@ Cluster Scaling & Configuration
516535
cluster.scale.1: ## Start single-node cluster

0 commit comments

Comments
 (0)