Skip to content

opensearch: ten data-plane responses diverge, including a 200 for deleting an absent document #1213

Description

@scttfrdmn

What

Eight response-fidelity defects in OpenSearch's data plane. Each is small; together they mean no
OpenSearch response can be trusted field-by-field.

# Defect Site Published
1 Every write answers 201 with _version: 1, _seq_no: 0 and no _primary_term :233-240 201 on create and 200 on an update; _version increments; _primary_term is a published member of every write response
2 Bulk handles only index, create, delete and hard-codes "errors": false :298-346 update is a published action; errors is true when any item failed
3 Deleting an absent document answers 200 with result: "deleted", _version: 2 :259-274 404 with result: "not_found"
4 Getting an absent document answers the error envelope :246 404 with {"_index":…,"_id":…,"found": false} — a body, not an error
5 A scroll page reports its own length as hits.total :513 the total matching the query, which is what the initial search correctly reports at :417
6 HEAD on an index answers a JSON body :86 an empty body — HEAD carries none
7 GetIndex hard-codes 1 shard / 0 replicas :183-190 the settings the index was created with, which createIndex already stores at :128-131
8 PUT _mapping is _ = index + {"acknowledged": true} :195-197 the mapping is stored and returned by GET _mapping
9 Every search hit reports _index: "unknown" :832 the real index name, which the scroll path emits correctly at :494
10 The error envelope omits root_cause :1048-1062 {"error": {"root_cause": [...], "type":…, "reason":…}, "status":…}

Two of these are internal inconsistencies rather than mere omissions, which makes them cheap to fix
and embarrassing to leave: #5 (the initial search gets the total right and the scroll page does
not) and #9 (the scroll path emits the real index name and the search path emits "unknown"). In
both cases the correct code is in the same file as the incorrect code.

#3 and #4 are the two that invert a meaning. Deleting a document that was never there reports
success and a version bump, so a consumer's "delete then assert gone" test cannot distinguish a
working delete from a no-op. Getting an absent document returns an error envelope where OpenSearch
returns a body with found: false — a distinction the OpenSearch clients rely on, since
found: false is a normal outcome and an error is not.

#7 and #8 are the two that discard stored input. createIndex stores settings, and GetIndex
answers a constant instead of reading them; PUT _mapping assigns its index to _ and acknowledges.
A consumer configuring shards, replicas or an explicit mapping — the three things one configures on an
index — gets an acknowledgement and no effect.

Why this matters

The OpenSearch data plane is the one surface in this batch that a consumer uses directly rather than
through an IaC tool, because application code talks to it on every request. So these are not
provisioning-time divergences a consumer meets once; they are the shapes their application reads in
its hot path. A client library that switches on found, retries on errors: true, or paginates a
scroll by comparing hits.total to the number seen so far behaves differently against substrate than
against OpenSearch — and #5 in particular makes a scroll loop that compares against hits.total
terminate after the first page or never.

They are one issue because they share one verification technique — assert the raw JSON of each
response against the published shape — and because fixing them individually would mean ten passes over
the same three functions.

Acceptance criteria

  • A write to an existing document answers 200, a new one 201, with _version incrementing
    and _primary_term present.
  • Bulk supports update, and errors is true when any item failed. A test asserts a mixed bulk
    request — one success, one failure — reports errors: true with the per-item status.
  • Deleting an absent document answers 404 with result: "not_found".
  • Getting an absent document answers 404 with found: false in a normal body, not an error
    envelope.
  • A scroll page reports the query's total, matching the initial search's already-correct behaviour.
  • HEAD on an index answers no body.
  • GetIndex reports the settings createIndex stored, and PUT _mapping stores a mapping that
    GET _mapping returns.
  • Every hit reports its real _index, matching the scroll path's already-correct behaviour.
  • The error envelope carries root_cause.
  • Every response is asserted as raw JSON against the published shape, following
    iam_shape_members_test.go:115. This is the load-bearing criterion: substrate's existing
    OpenSearch tests decode into its own structs, which is why ten divergences survived.
  • docs/services.md's OpenSearch section states, per operation, what the response carries.

Provenance

Every published shape above is from the OpenSearch REST API documentation: the index-document response
members including _primary_term and the 200-versus-201 rule; the Bulk API's four actions and its
errors member; the Delete API's result: "not_found" at 404; the Get API's found: false body at
404; the Scroll API's hits.total; the Index Settings and Mapping APIs; and the error-response
envelope with root_cause. Where a shape is published by OpenSearch (the open-source project's REST
reference) rather than by an AWS API Reference page, that is the authority AWS's own OpenSearch Service
documentation points to for the data plane, and the citation is recorded as such.

In-tree, all in emulator/opensearch_plugin.go: :86, :128-131, :183-190, :195-197, :233-240,
:246, :259-274, :298-346, :417, :494, :513, :832, :1048-1062. Line citations are from
the tree at the commit this issue was filed against.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions