Skip to content

redshift: every response omits the envelope, the namespace and the request ID, so no SDK can decode it #1208

Description

@scttfrdmn

What

Redshift is a Query-protocol service and substrate answers it with XML that has no response envelope,
no namespace and no request ID, with every list flattened and one record double-wrapped. No AWS SDK
can decode any of it.

1. There is no envelope, namespace or request ID

redshift_plugin.go:545-557:

func redshiftXMLResponse(result interface{}, _ string) (*AWSResponse, error)

The result is marshalled as the document root and the request-ID parameter is _ string —
accepted and discarded. AWS publishes, for every Redshift operation, the three-level form:

<CreateClusterResponse xmlns="http://redshift.amazonaws.com/doc/2012-12-01/">
  <CreateClusterResult>
    <Cluster>…</Cluster>
  </CreateClusterResult>
  <ResponseMetadata><RequestId>…</RequestId></ResponseMetadata>
</CreateClusterResponse>

Substrate emits <Cluster>…</Cluster> alone. An SDK looks for {Operation}Response and finds
nothing, so every Redshift response fails to decode — the operation might as well not be routed.
This is the same class as #1013's envelope work on other Query services and the same fix shape.

2. Lists flatten to >member

redshift_types.go:94, :99-100 tag slices so they marshal as bare repeated <member> elements.
AWS publishes the wrapped form — <ClusterNodes><member>…</member><member>…</member></ClusterNodes>
— so even inside a correct envelope the members would be in the wrong place.

3. A single cluster is double-wrapped

redshift_types.go:76:

XMLName xml.Name `xml:"member"`

on the Cluster struct forces <member> around it at every site, including
DescribeClusters' single-cluster case and CreateCluster's result, where AWS publishes
<Cluster>. The tag is there to make the list form work, which is the wrong layer: the element name
belongs to the field that holds the value, not to the type. Moving it is part of this issue rather
than the record-shape issue, because the two are the same edit.

4. Two error codes carry an unpublished Fault suffix

redshift_plugin.go:143, :216, :536 answer ClusterAlreadyExistsFault and
ClusterNotFoundFault. Every Redshift page publishes these as ClusterAlreadyExists and
ClusterNotFound, both at 400 — the …Fault spelling is the shape name in the AWS service model,
not the wire code, and the two are different strings. A consumer matching on the published code never
matches.

5. An unrouted operation answers a code Redshift does not publish

10 of Redshift's 141 operations are routed; the other 131 answer InvalidAction/400. Redshift's own
Common Errors page publishes the consolidated fifteen-code list, which does not include
InvalidAction — it publishes ValidationError/400 and InvalidParameterCombination/400 among
others. Note this is the opposite of SQS (#1064), whose Common Errors page is still the legacy
generation and does publish InvalidAction, which is why sqs_errors.go:31 is correct and this is
not. The refusal must take a code from Redshift's own page.

Why this matters

This is the largest single fidelity defect found across the nine services, because it is total: not
one Redshift operation can be consumed by an SDK. A consumer's boto3.client("redshift") call
against substrate raises a parse error on the first response, so the service reads as broken rather
than as partial — and 10 routed operations' worth of work is unreachable behind it.

It also means every other Redshift finding in this batch is currently unobservable. The 11-of-63
Cluster members, the available-at-birth state, the unread Marker/MaxRecords — a consumer
cannot see any of them, because they never get a decoded response to look at. So this issue gates the
value of the rest, which is the argument for doing it first among the Redshift work.

Acceptance criteria

  • Every Redshift response is wrapped in {Operation}Response / {Operation}Result /
    ResponseMetadata.RequestId with the http://redshift.amazonaws.com/doc/2012-12-01/ namespace,
    following the in-tree Query-protocol precedent rather than a new mechanism.
  • The discarded request-ID parameter is used, so ResponseMetadata.RequestId carries the value
    the rest of the emulator already generates and logs.
  • Lists marshal wrapped (<ClusterNodes><member>…), not flattened.
  • The XMLName tag moves off the Cluster type onto the fields that hold it, so a single cluster
    is <Cluster> and a list element is <member>.
  • ClusterAlreadyExists and ClusterNotFound are spelled as published, without the Fault
    suffix, at all three sites.
  • An unrouted Redshift action answers a code Redshift's own Common Errors page publishes, not
    InvalidAction. Record which, and record that SQS's retention of InvalidAction is correct for
    SQS — the two pages are different generations and the divergence between them is real.
  • A test asserts the raw XML of at least one success and one refusal against the published
    shape, following iam_shape_members_test.go:115's raw-assertion approach. A test that decodes
    into substrate's own structs cannot catch a missing envelope, which is why this survived.
  • docs/services.md's Redshift section states the envelope shape, the namespace, and what an
    unrouted action answers.

Provenance

The envelope, namespace and ResponseMetadata shape are from the Response Syntax and Examples
sections of Redshift's own pages — API_CreateCluster, API_DescribeClusters, API_DeleteCluster —
and the wrapped list form is from the same examples. The ClusterAlreadyExists and ClusterNotFound
code spellings and their 400 statuses are from those pages' Errors sections. The consolidated
fifteen-code Common Errors list, and its omission of InvalidAction, are from Redshift's
CommonErrors.html; the SQS contrast is the finding recorded in #1064.

In-tree: emulator/redshift_plugin.go:545-557 (the marshal and the discarded request ID), :143,
:216, :536 (the codes); emulator/redshift_types.go:76 (the XMLName tag), :94, :99-100
(the flattened lists). 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