mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
804 lines
20 KiB
804 lines
20 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: MPL-2.0 |
|
|
|
package agent |
|
|
|
import ( |
|
"bytes" |
|
"context" |
|
"fmt" |
|
"net/http" |
|
"net/http/httptest" |
|
"strings" |
|
"testing" |
|
"time" |
|
|
|
"github.com/hashicorp/consul/testrpc" |
|
"github.com/hashicorp/raft" |
|
autopilot "github.com/hashicorp/raft-autopilot" |
|
"github.com/stretchr/testify/require" |
|
|
|
"github.com/hashicorp/consul/agent/structs" |
|
"github.com/hashicorp/consul/api" |
|
"github.com/hashicorp/consul/sdk/testutil/retry" |
|
) |
|
|
|
func TestOperator_RaftConfiguration(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
t.Parallel() |
|
a := NewTestAgent(t, "") |
|
defer a.Shutdown() |
|
|
|
body := bytes.NewBuffer(nil) |
|
req, _ := http.NewRequest("GET", "/v1/operator/raft/configuration", body) |
|
resp := httptest.NewRecorder() |
|
obj, err := a.srv.OperatorRaftConfiguration(resp, req) |
|
if err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
if resp.Code != 200 { |
|
t.Fatalf("bad code: %d", resp.Code) |
|
} |
|
out, ok := obj.(structs.RaftConfigurationResponse) |
|
if !ok { |
|
t.Fatalf("unexpected: %T", obj) |
|
} |
|
if len(out.Servers) != 1 || |
|
!out.Servers[0].Leader || |
|
!out.Servers[0].Voter { |
|
t.Fatalf("bad: %v", out) |
|
} |
|
} |
|
|
|
func TestOperator_RaftPeer(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
t.Parallel() |
|
t.Run("", func(t *testing.T) { |
|
a := NewTestAgent(t, "") |
|
defer a.Shutdown() |
|
|
|
body := bytes.NewBuffer(nil) |
|
req, _ := http.NewRequest("DELETE", "/v1/operator/raft/peer?address=nope", body) |
|
// If we get this error, it proves we sent the address all the |
|
// way through. |
|
resp := httptest.NewRecorder() |
|
_, err := a.srv.OperatorRaftPeer(resp, req) |
|
if err == nil || !strings.Contains(err.Error(), |
|
"address \"nope\" was not found in the Raft configuration") { |
|
t.Fatalf("err: %v", err) |
|
} |
|
}) |
|
|
|
t.Run("", func(t *testing.T) { |
|
a := NewTestAgent(t, "") |
|
defer a.Shutdown() |
|
|
|
body := bytes.NewBuffer(nil) |
|
req, _ := http.NewRequest("DELETE", "/v1/operator/raft/peer?id=nope", body) |
|
// If we get this error, it proves we sent the ID all the |
|
// way through. |
|
resp := httptest.NewRecorder() |
|
_, err := a.srv.OperatorRaftPeer(resp, req) |
|
if err == nil || !strings.Contains(err.Error(), |
|
"id \"nope\" was not found in the Raft configuration") { |
|
t.Fatalf("err: %v", err) |
|
} |
|
}) |
|
} |
|
|
|
func TestOperator_KeyringInstall(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
t.Parallel() |
|
oldKey := "H3/9gBxcKKRf45CaI2DlRg==" |
|
newKey := "z90lFx3sZZLtTOkutXcwYg==" |
|
a := NewTestAgent(t, ` |
|
encrypt = "`+oldKey+`" |
|
`) |
|
defer a.Shutdown() |
|
|
|
body := bytes.NewBufferString(fmt.Sprintf("{\"Key\":\"%s\"}", newKey)) |
|
req, _ := http.NewRequest("POST", "/v1/operator/keyring", body) |
|
resp := httptest.NewRecorder() |
|
_, err := a.srv.OperatorKeyringEndpoint(resp, req) |
|
if err != nil { |
|
t.Fatalf("err: %s", err) |
|
} |
|
|
|
listResponse, err := a.ListKeys("", false, 0) |
|
if err != nil { |
|
t.Fatalf("err: %s", err) |
|
} |
|
if len(listResponse.Responses) != 2 { |
|
t.Fatalf("bad: %d", len(listResponse.Responses)) |
|
} |
|
|
|
for _, response := range listResponse.Responses { |
|
count, ok := response.Keys[newKey] |
|
if !ok { |
|
t.Fatalf("bad: %v", response.Keys) |
|
} |
|
if count != response.NumNodes { |
|
t.Fatalf("bad: %d, %d", count, response.NumNodes) |
|
} |
|
} |
|
} |
|
|
|
func TestOperator_KeyringList(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
t.Parallel() |
|
key := "H3/9gBxcKKRf45CaI2DlRg==" |
|
a := NewTestAgent(t, ` |
|
encrypt = "`+key+`" |
|
`) |
|
defer a.Shutdown() |
|
|
|
req, _ := http.NewRequest("GET", "/v1/operator/keyring", nil) |
|
resp := httptest.NewRecorder() |
|
r, err := a.srv.OperatorKeyringEndpoint(resp, req) |
|
if err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
responses, ok := r.([]*structs.KeyringResponse) |
|
if !ok { |
|
t.Fatalf("err: %v", !ok) |
|
} |
|
|
|
// Check that we get both a LAN and WAN response, and that they both only |
|
// contain the original key |
|
if len(responses) != 2 { |
|
t.Fatalf("bad: %d", len(responses)) |
|
} |
|
|
|
// WAN |
|
if len(responses[0].Keys) != 1 { |
|
t.Fatalf("bad: %d", len(responses[0].Keys)) |
|
} |
|
if !responses[0].WAN { |
|
t.Fatalf("bad: %v", responses[0].WAN) |
|
} |
|
if _, ok := responses[0].Keys[key]; !ok { |
|
t.Fatalf("bad: %v", ok) |
|
} |
|
|
|
// LAN |
|
if len(responses[1].Keys) != 1 { |
|
t.Fatalf("bad: %d", len(responses[1].Keys)) |
|
} |
|
if responses[1].WAN { |
|
t.Fatalf("bad: %v", responses[1].WAN) |
|
} |
|
if _, ok := responses[1].Keys[key]; !ok { |
|
t.Fatalf("bad: %v", ok) |
|
} |
|
} |
|
func TestOperator_KeyringListLocalOnly(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
t.Parallel() |
|
key := "H3/9gBxcKKRf45CaI2DlRg==" |
|
a := NewTestAgent(t, ` |
|
encrypt = "`+key+`" |
|
`) |
|
defer a.Shutdown() |
|
|
|
req, _ := http.NewRequest("GET", "/v1/operator/keyring?local-only=1", nil) |
|
resp := httptest.NewRecorder() |
|
r, err := a.srv.OperatorKeyringEndpoint(resp, req) |
|
if err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
responses, ok := r.([]*structs.KeyringResponse) |
|
if !ok { |
|
t.Fatalf("err: %v", !ok) |
|
} |
|
|
|
// Check that we only get a LAN response with the original key |
|
if len(responses) != 1 { |
|
for _, r := range responses { |
|
fmt.Println(r) |
|
} |
|
t.Fatalf("bad: %d", len(responses)) |
|
} |
|
|
|
// LAN |
|
if len(responses[0].Keys) != 1 { |
|
t.Fatalf("bad: %d", len(responses[1].Keys)) |
|
} |
|
if responses[0].WAN { |
|
t.Fatalf("bad: %v", responses[1].WAN) |
|
} |
|
if _, ok := responses[0].Keys[key]; !ok { |
|
t.Fatalf("bad: %v", ok) |
|
} |
|
} |
|
|
|
func TestOperator_KeyringRemove(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
t.Parallel() |
|
key := "H3/9gBxcKKRf45CaI2DlRg==" |
|
tempKey := "z90lFx3sZZLtTOkutXcwYg==" |
|
a := NewTestAgent(t, ` |
|
encrypt = "`+key+`" |
|
`) |
|
defer a.Shutdown() |
|
|
|
_, err := a.InstallKey(tempKey, "", 0) |
|
if err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
|
|
// Make sure the temp key is installed |
|
list, err := a.ListKeys("", false, 0) |
|
if err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
responses := list.Responses |
|
if len(responses) != 2 { |
|
t.Fatalf("bad: %d", len(responses)) |
|
} |
|
for _, response := range responses { |
|
if len(response.Keys) != 2 { |
|
t.Fatalf("bad: %d", len(response.Keys)) |
|
} |
|
if _, ok := response.Keys[tempKey]; !ok { |
|
t.Fatalf("bad: %v", ok) |
|
} |
|
} |
|
|
|
body := bytes.NewBufferString(fmt.Sprintf("{\"Key\":\"%s\"}", tempKey)) |
|
req, _ := http.NewRequest("DELETE", "/v1/operator/keyring", body) |
|
resp := httptest.NewRecorder() |
|
if _, err := a.srv.OperatorKeyringEndpoint(resp, req); err != nil { |
|
t.Fatalf("err: %s", err) |
|
} |
|
|
|
// Make sure the temp key has been removed |
|
list, err = a.ListKeys("", false, 0) |
|
if err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
responses = list.Responses |
|
if len(responses) != 2 { |
|
t.Fatalf("bad: %d", len(responses)) |
|
} |
|
for _, response := range responses { |
|
if len(response.Keys) != 1 { |
|
t.Fatalf("bad: %d", len(response.Keys)) |
|
} |
|
if _, ok := response.Keys[tempKey]; ok { |
|
t.Fatalf("bad: %v", ok) |
|
} |
|
} |
|
} |
|
|
|
func TestOperator_KeyringUse(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
t.Parallel() |
|
oldKey := "H3/9gBxcKKRf45CaI2DlRg==" |
|
newKey := "z90lFx3sZZLtTOkutXcwYg==" |
|
a := NewTestAgent(t, ` |
|
encrypt = "`+oldKey+`" |
|
`) |
|
defer a.Shutdown() |
|
|
|
if _, err := a.InstallKey(newKey, "", 0); err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
|
|
body := bytes.NewBufferString(fmt.Sprintf("{\"Key\":\"%s\"}", newKey)) |
|
req, _ := http.NewRequest("PUT", "/v1/operator/keyring", body) |
|
resp := httptest.NewRecorder() |
|
_, err := a.srv.OperatorKeyringEndpoint(resp, req) |
|
if err != nil { |
|
t.Fatalf("err: %s", err) |
|
} |
|
|
|
if _, err := a.RemoveKey(oldKey, "", 0); err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
|
|
// Make sure only the new key remains |
|
list, err := a.ListKeys("", false, 0) |
|
if err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
responses := list.Responses |
|
if len(responses) != 2 { |
|
t.Fatalf("bad: %d", len(responses)) |
|
} |
|
for _, response := range responses { |
|
if len(response.Keys) != 1 { |
|
t.Fatalf("bad: %d", len(response.Keys)) |
|
} |
|
if _, ok := response.Keys[newKey]; !ok { |
|
t.Fatalf("bad: %v", ok) |
|
} |
|
} |
|
} |
|
|
|
func TestOperator_Keyring_InvalidRelayFactor(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
t.Parallel() |
|
key := "H3/9gBxcKKRf45CaI2DlRg==" |
|
a := NewTestAgent(t, ` |
|
encrypt = "`+key+`" |
|
`) |
|
defer a.Shutdown() |
|
|
|
cases := map[string]string{ |
|
"999": "Relay factor must be in range", |
|
"asdf": "Error parsing relay factor", |
|
} |
|
for relayFactor, errString := range cases { |
|
req, err := http.NewRequest("GET", "/v1/operator/keyring?relay-factor="+relayFactor, nil) |
|
require.NoError(t, err) |
|
resp := httptest.NewRecorder() |
|
_, err = a.srv.OperatorKeyringEndpoint(resp, req) |
|
require.Error(t, err, "tc: "+relayFactor) |
|
require.Contains(t, err.Error(), errString, "tc: "+relayFactor) |
|
} |
|
} |
|
|
|
func TestOperator_Keyring_LocalOnly(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
t.Parallel() |
|
key := "H3/9gBxcKKRf45CaI2DlRg==" |
|
a := NewTestAgent(t, ` |
|
encrypt = "`+key+`" |
|
`) |
|
defer a.Shutdown() |
|
|
|
cases := []struct { |
|
description string |
|
method string |
|
local interface{} |
|
ok bool |
|
}{ |
|
{"all ok", "GET", true, true}, |
|
{"garbage local-only value", "GET", "garbage", false}, |
|
{"wrong method (DELETE)", "DELETE", true, false}, |
|
} |
|
|
|
for _, tc := range cases { |
|
url := fmt.Sprintf("/v1/operator/keyring?local-only=%v", tc.local) |
|
req, err := http.NewRequest(tc.method, url, nil) |
|
require.NoError(t, err, "tc: "+tc.description) |
|
|
|
resp := httptest.NewRecorder() |
|
_, err = a.srv.OperatorKeyringEndpoint(resp, req) |
|
if tc.ok { |
|
require.NoError(t, err, "tc: "+tc.description) |
|
} |
|
if !tc.ok { |
|
require.Error(t, err, "tc: "+tc.description) |
|
} |
|
} |
|
} |
|
|
|
func TestOperator_AutopilotGetConfiguration(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
t.Parallel() |
|
a := NewTestAgent(t, "") |
|
defer a.Shutdown() |
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1") |
|
|
|
body := bytes.NewBuffer(nil) |
|
req, _ := http.NewRequest("GET", "/v1/operator/autopilot/configuration", body) |
|
resp := httptest.NewRecorder() |
|
obj, err := a.srv.OperatorAutopilotConfiguration(resp, req) |
|
if err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
if resp.Code != 200 { |
|
t.Fatalf("bad code: %d", resp.Code) |
|
} |
|
out, ok := obj.(api.AutopilotConfiguration) |
|
if !ok { |
|
t.Fatalf("unexpected: %T", obj) |
|
} |
|
if !out.CleanupDeadServers { |
|
t.Fatalf("bad: %#v", out) |
|
} |
|
} |
|
|
|
func TestOperator_AutopilotSetConfiguration(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
t.Parallel() |
|
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 { |
|
t.Fatalf("err: %v", err) |
|
} |
|
if resp.Code != 200 { |
|
t.Fatalf("bad code: %d", resp.Code) |
|
} |
|
|
|
args := structs.DCSpecificRequest{ |
|
Datacenter: "dc1", |
|
} |
|
|
|
var reply structs.AutopilotConfig |
|
if err := a.RPC(context.Background(), "Operator.AutopilotGetConfiguration", &args, &reply); err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
|
|
// 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) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
t.Parallel() |
|
a := NewTestAgent(t, "") |
|
defer a.Shutdown() |
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1") |
|
|
|
body := bytes.NewBuffer([]byte(`{"CleanupDeadServers": false}`)) |
|
req, _ := http.NewRequest("PUT", "/v1/operator/autopilot/configuration", body) |
|
resp := httptest.NewRecorder() |
|
if _, err := a.srv.OperatorAutopilotConfiguration(resp, req); err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
if resp.Code != 200 { |
|
t.Fatalf("bad code: %d", resp.Code) |
|
} |
|
|
|
args := structs.DCSpecificRequest{ |
|
Datacenter: "dc1", |
|
} |
|
|
|
var reply structs.AutopilotConfig |
|
if err := a.RPC(context.Background(), "Operator.AutopilotGetConfiguration", &args, &reply); err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
|
|
if reply.CleanupDeadServers { |
|
t.Fatalf("bad: %#v", reply) |
|
} |
|
|
|
// Create a CAS request, bad index |
|
{ |
|
buf := bytes.NewBuffer([]byte(`{"CleanupDeadServers": true}`)) |
|
req, _ := http.NewRequest("PUT", fmt.Sprintf("/v1/operator/autopilot/configuration?cas=%d", reply.ModifyIndex-1), buf) |
|
resp := httptest.NewRecorder() |
|
obj, err := a.srv.OperatorAutopilotConfiguration(resp, req) |
|
if err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
|
|
if res := obj.(bool); res { |
|
t.Fatalf("should NOT work") |
|
} |
|
} |
|
|
|
// Create a CAS request, good index |
|
{ |
|
buf := bytes.NewBuffer([]byte(`{"CleanupDeadServers": true}`)) |
|
req, _ := http.NewRequest("PUT", fmt.Sprintf("/v1/operator/autopilot/configuration?cas=%d", reply.ModifyIndex), buf) |
|
resp := httptest.NewRecorder() |
|
obj, err := a.srv.OperatorAutopilotConfiguration(resp, req) |
|
if err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
|
|
if res := obj.(bool); !res { |
|
t.Fatalf("should work") |
|
} |
|
} |
|
|
|
// Verify the update |
|
if err := a.RPC(context.Background(), "Operator.AutopilotGetConfiguration", &args, &reply); err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
if !reply.CleanupDeadServers { |
|
t.Fatalf("bad: %#v", reply) |
|
} |
|
} |
|
|
|
func TestOperator_ServerHealth(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
t.Parallel() |
|
a := NewTestAgent(t, ` |
|
raft_protocol = 3 |
|
`) |
|
defer a.Shutdown() |
|
|
|
body := bytes.NewBuffer(nil) |
|
req, _ := http.NewRequest("GET", "/v1/operator/autopilot/health", body) |
|
retry.Run(t, func(r *retry.R) { |
|
resp := httptest.NewRecorder() |
|
obj, err := a.srv.OperatorServerHealth(resp, req) |
|
if err != nil { |
|
r.Fatalf("err: %v", err) |
|
} |
|
if resp.Code != 200 { |
|
r.Fatalf("bad code: %d", resp.Code) |
|
} |
|
out, ok := obj.(*api.OperatorHealthReply) |
|
if !ok { |
|
r.Fatalf("unexpected: %T", obj) |
|
} |
|
if len(out.Servers) != 1 || |
|
!out.Servers[0].Healthy || |
|
out.Servers[0].Name != a.Config.NodeName || |
|
out.Servers[0].SerfStatus != "alive" || |
|
out.FailureTolerance != 0 { |
|
r.Fatalf("bad: %v", out) |
|
} |
|
}) |
|
} |
|
|
|
func TestOperator_ServerHealth_Unhealthy(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
t.Parallel() |
|
a := NewTestAgent(t, ` |
|
raft_protocol = 3 |
|
autopilot { |
|
last_contact_threshold = "-1s" |
|
} |
|
`) |
|
defer a.Shutdown() |
|
|
|
body := bytes.NewBuffer(nil) |
|
req, _ := http.NewRequest("GET", "/v1/operator/autopilot/health", body) |
|
retry.Run(t, func(r *retry.R) { |
|
resp := httptest.NewRecorder() |
|
obj, err := a.srv.OperatorServerHealth(resp, req) |
|
if err != nil { |
|
r.Fatalf("err: %v", err) |
|
} |
|
if resp.Code != 429 { |
|
r.Fatalf("bad code: %d", resp.Code) |
|
} |
|
out, ok := obj.(*api.OperatorHealthReply) |
|
if !ok { |
|
r.Fatalf("unexpected: %T", obj) |
|
} |
|
if len(out.Servers) != 1 || |
|
out.Healthy || |
|
out.Servers[0].Name != a.Config.NodeName { |
|
r.Fatalf("bad: %#v", out.Servers) |
|
} |
|
}) |
|
} |
|
|
|
func TestOperator_AutopilotState(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
a := NewTestAgent(t, "") |
|
defer a.Shutdown() |
|
|
|
req, err := http.NewRequest("GET", "/v1/operator/autopilot/state", nil) |
|
require.NoError(t, err) |
|
retry.Run(t, func(r *retry.R) { |
|
resp := httptest.NewRecorder() |
|
obj, err := a.srv.OperatorAutopilotState(resp, req) |
|
require.NoError(r, err) |
|
require.Equal(r, 200, resp.Code) |
|
state, ok := obj.(*api.AutopilotState) |
|
require.True(r, ok) |
|
|
|
srv, ok := state.Servers[string(a.config.NodeID)] |
|
require.True(r, ok) |
|
require.True(r, srv.Healthy) |
|
require.Equal(r, a.config.NodeName, srv.Name) |
|
|
|
}) |
|
} |
|
|
|
func TestAutopilotStateToAPIConversion(t *testing.T) { |
|
var leaderID raft.ServerID = "79324811-9588-4311-b208-f272e38aaabf" |
|
var follower1ID raft.ServerID = "ef8aee9a-f9d6-4ec4-b383-aac956bdb80f" |
|
var follower2ID raft.ServerID = "ae84aefb-a303-4734-8739-5c102d4ee2d9" |
|
input := autopilot.State{ |
|
Healthy: true, |
|
FailureTolerance: 1, |
|
Leader: leaderID, |
|
Voters: []raft.ServerID{ |
|
leaderID, |
|
follower1ID, |
|
follower2ID, |
|
}, |
|
Servers: map[raft.ServerID]*autopilot.ServerState{ |
|
leaderID: { |
|
Server: autopilot.Server{ |
|
ID: leaderID, |
|
Name: "node1", |
|
Address: "198.18.0.1:8300", |
|
NodeStatus: autopilot.NodeAlive, |
|
Version: "1.9.0", |
|
Meta: map[string]string{ |
|
"foo": "bar", |
|
}, |
|
NodeType: autopilot.NodeVoter, |
|
}, |
|
State: autopilot.RaftLeader, |
|
Stats: autopilot.ServerStats{ |
|
LastContact: 0, |
|
LastTerm: 3, |
|
LastIndex: 42, |
|
}, |
|
Health: autopilot.ServerHealth{ |
|
Healthy: true, |
|
StableSince: time.Date(2020, 11, 6, 14, 51, 0, 0, time.UTC), |
|
}, |
|
}, |
|
follower1ID: { |
|
Server: autopilot.Server{ |
|
ID: follower1ID, |
|
Name: "node2", |
|
Address: "198.18.0.2:8300", |
|
NodeStatus: autopilot.NodeAlive, |
|
Version: "1.9.0", |
|
Meta: map[string]string{ |
|
"bar": "baz", |
|
}, |
|
NodeType: autopilot.NodeVoter, |
|
}, |
|
State: autopilot.RaftVoter, |
|
Stats: autopilot.ServerStats{ |
|
LastContact: time.Millisecond, |
|
LastTerm: 3, |
|
LastIndex: 41, |
|
}, |
|
Health: autopilot.ServerHealth{ |
|
Healthy: true, |
|
StableSince: time.Date(2020, 11, 6, 14, 52, 0, 0, time.UTC), |
|
}, |
|
}, |
|
follower2ID: { |
|
Server: autopilot.Server{ |
|
ID: follower2ID, |
|
Name: "node3", |
|
Address: "198.18.0.3:8300", |
|
NodeStatus: autopilot.NodeAlive, |
|
Version: "1.9.0", |
|
Meta: map[string]string{ |
|
"baz": "foo", |
|
}, |
|
NodeType: autopilot.NodeVoter, |
|
}, |
|
State: autopilot.RaftVoter, |
|
Stats: autopilot.ServerStats{ |
|
LastContact: 2 * time.Millisecond, |
|
LastTerm: 3, |
|
LastIndex: 39, |
|
}, |
|
Health: autopilot.ServerHealth{ |
|
Healthy: true, |
|
StableSince: time.Date(2020, 11, 6, 14, 53, 0, 0, time.UTC), |
|
}, |
|
}, |
|
}, |
|
} |
|
|
|
expected := api.AutopilotState{ |
|
Healthy: true, |
|
FailureTolerance: 1, |
|
OptimisticFailureTolerance: 1, |
|
Leader: string(leaderID), |
|
Voters: []string{ |
|
string(leaderID), |
|
string(follower1ID), |
|
string(follower2ID), |
|
}, |
|
Servers: map[string]api.AutopilotServer{ |
|
string(leaderID): { |
|
ID: string(leaderID), |
|
Name: "node1", |
|
Address: "198.18.0.1:8300", |
|
NodeStatus: "alive", |
|
Version: "1.9.0", |
|
Meta: map[string]string{ |
|
"foo": "bar", |
|
}, |
|
NodeType: api.AutopilotTypeVoter, |
|
Status: api.AutopilotServerLeader, |
|
LastContact: api.NewReadableDuration(0), |
|
LastTerm: 3, |
|
LastIndex: 42, |
|
Healthy: true, |
|
StableSince: time.Date(2020, 11, 6, 14, 51, 0, 0, time.UTC), |
|
}, |
|
string(follower1ID): { |
|
ID: string(follower1ID), |
|
Name: "node2", |
|
Address: "198.18.0.2:8300", |
|
NodeStatus: "alive", |
|
Version: "1.9.0", |
|
Meta: map[string]string{ |
|
"bar": "baz", |
|
}, |
|
NodeType: api.AutopilotTypeVoter, |
|
Status: api.AutopilotServerVoter, |
|
LastContact: api.NewReadableDuration(time.Millisecond), |
|
LastTerm: 3, |
|
LastIndex: 41, |
|
Healthy: true, |
|
StableSince: time.Date(2020, 11, 6, 14, 52, 0, 0, time.UTC), |
|
}, |
|
string(follower2ID): { |
|
ID: string(follower2ID), |
|
Name: "node3", |
|
Address: "198.18.0.3:8300", |
|
NodeStatus: "alive", |
|
Version: "1.9.0", |
|
Meta: map[string]string{ |
|
"baz": "foo", |
|
}, |
|
NodeType: api.AutopilotTypeVoter, |
|
Status: api.AutopilotServerVoter, |
|
LastContact: api.NewReadableDuration(2 * time.Millisecond), |
|
LastTerm: 3, |
|
LastIndex: 39, |
|
Healthy: true, |
|
StableSince: time.Date(2020, 11, 6, 14, 53, 0, 0, time.UTC), |
|
}, |
|
}, |
|
} |
|
|
|
require.Equal(t, &expected, autopilotToAPIState(&input)) |
|
}
|
|
|