Skip to content

cluster-autoscaler/kamatera: retries reuse consumed request body for POST requests #9688

@fallintoplace

Description

@fallintoplace

Which component are you using?:

cluster-autoscaler

What version of the component are you using?:

Component version:

Observed on current master at 49f227731.

What k8s version are you using (kubectl version)?:

Not cluster-version dependent. This looks like a client-side request construction bug in the Kamatera provider.

What environment is this in?:

Kamatera cloud provider.

What did you expect to happen?:

When the Kamatera REST client retries a POST request, each retry should send the same JSON request body as the first attempt.

What happened instead?:

request() JSON-encodes the body into a single bytes.Buffer before the retry loop, then passes that same buffer to every http.NewRequestWithContext call:

  • cluster-autoscaler/cloudprovider/kamatera/kamatera_request.go:45-61

After the first http.DefaultClient.Do(req), that buffer has been consumed. Later retry attempts reuse the same exhausted reader, so they send an empty body.

This affects retrying POST call sites such as:

  • DeleteServer() poweroff / terminate: cluster-autoscaler/cloudprovider/kamatera/kamatera_api_client_rest.go:123-156
  • getServerTags(): cluster-autoscaler/cloudprovider/kamatera/kamatera_api_client_rest.go:244-254

There is a second issue in the same loop: res.Body.Close() is deferred inside the retry loop, so response bodies stay open until the function returns instead of being closed before the next attempt.

How to reproduce it (as minimally and precisely as possible):

One minimal way to reproduce is to make a Kamatera API endpoint fail once and then succeed on retry while asserting on the request body for both attempts:

  1. Use a test HTTP server that returns a retriable failure on the first POST.
  2. Retry the same Kamatera POST request (/service/server/terminate, /service/server/poweroff, or /server/tags).
  3. Inspect the request body received by the server for each attempt.
  4. The first attempt contains the expected JSON body, while later attempts receive an empty body.

This behavior appears to have been introduced by 3c187264c (add retry mechanism), which moved request sending into a retry loop without recreating the request body reader for each attempt.

Anything else we need to know?:

A straightforward fix would be to encode once to []byte, then create a fresh reader per attempt, e.g.:

bodyBytes := buf.Bytes()
...
req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewReader(bodyBytes))

and close res.Body before continuing to the next retry instead of deferring it inside the loop.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/cluster-autoscalerIssues or PRs related to the Cluster Autoscaler componentneeds-triageIndicates an issue or PR lacks a `triage/foo` label and requires one.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions