diff --git a/.changelog/10559.txt b/.changelog/10559.txt new file mode 100644 index 000000000..9e755940f --- /dev/null +++ b/.changelog/10559.txt @@ -0,0 +1,3 @@ +```release-note:bug +api: Fix default values used for optional fields in autopilot configuration update (POST to `/v1/operator/autopilot/configuration`) [[GH-10558](https://github.com/hashicorp/consul/issues/10558)] +``` diff --git a/agent/operator_endpoint.go b/agent/operator_endpoint.go index f8f2f4fcc..644fe7fba 100644 --- a/agent/operator_endpoint.go +++ b/agent/operator_endpoint.go @@ -217,7 +217,7 @@ func (s *HTTPHandlers) OperatorAutopilotConfiguration(resp http.ResponseWriter, s.parseDC(req, &args.Datacenter) s.parseToken(req, &args.Token) - var conf api.AutopilotConfiguration + conf := api.NewAutopilotConfiguration() if err := decodeBody(req.Body, &conf); err != nil { return nil, BadRequestError{Reason: fmt.Sprintf("Error parsing autopilot config: %v", err)} } diff --git a/agent/operator_endpoint_test.go b/agent/operator_endpoint_test.go index ae22e9705..37f893b44 100644 --- a/agent/operator_endpoint_test.go +++ b/agent/operator_endpoint_test.go @@ -435,7 +435,21 @@ func TestOperator_AutopilotSetConfiguration(t *testing.T) { a := NewTestAgent(t, "") defer a.Shutdown() + // Provide a non-default value only for CleanupDeadServers. + // Expect all other fields to be updated with default values + // (except CreateIndex and ModifyIndex). body := bytes.NewBuffer([]byte(`{"CleanupDeadServers": false}`)) + expected := structs.AutopilotConfig{ + CleanupDeadServers: false, // only non-default value + LastContactThreshold: 200 * time.Millisecond, + MaxTrailingLogs: 250, + MinQuorum: 0, + ServerStabilizationTime: 10 * time.Second, + RedundancyZoneTag: "", + DisableUpgradeMigration: false, + UpgradeVersionTag: "", + } + req, _ := http.NewRequest("PUT", "/v1/operator/autopilot/configuration", body) resp := httptest.NewRecorder() if _, err := a.srv.OperatorAutopilotConfiguration(resp, req); err != nil { @@ -453,9 +467,11 @@ func TestOperator_AutopilotSetConfiguration(t *testing.T) { if err := a.RPC("Operator.AutopilotGetConfiguration", &args, &reply); err != nil { t.Fatalf("err: %v", err) } - if reply.CleanupDeadServers { - t.Fatalf("bad: %#v", reply) - } + + // For equality comparison check, ignore CreateIndex and ModifyIndex + expected.CreateIndex = reply.CreateIndex + expected.ModifyIndex = reply.ModifyIndex + require.Equal(t, expected, reply) } func TestOperator_AutopilotCASConfiguration(t *testing.T) { diff --git a/api/operator_autopilot.go b/api/operator_autopilot.go index 8175f5133..d713ee1cc 100644 --- a/api/operator_autopilot.go +++ b/api/operator_autopilot.go @@ -58,6 +58,23 @@ type AutopilotConfiguration struct { ModifyIndex uint64 } +// Defines default values for the AutopilotConfiguration type, consistent with +// https://www.consul.io/api-docs/operator/autopilot#parameters-1 +func NewAutopilotConfiguration() AutopilotConfiguration { + cfg := AutopilotConfiguration{ + CleanupDeadServers: true, + LastContactThreshold: NewReadableDuration(200 * time.Millisecond), + MaxTrailingLogs: 250, + MinQuorum: 0, + ServerStabilizationTime: NewReadableDuration(10 * time.Second), + RedundancyZoneTag: "", + DisableUpgradeMigration: false, + UpgradeVersionTag: "", + } + + return cfg +} + // ServerHealth is the health (from the leader's point of view) of a server. type ServerHealth struct { // ID is the raft ID of the server.