2023-03-28 18:39:22 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2022-11-14 20:35:12 +00:00
|
|
|
package consul
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-02-17 21:14:46 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2022-11-14 20:35:12 +00:00
|
|
|
"github.com/hashicorp/consul/acl"
|
|
|
|
external "github.com/hashicorp/consul/agent/grpc-external"
|
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
2023-02-17 21:14:46 +00:00
|
|
|
"github.com/hashicorp/consul/proto/private/pboperator"
|
2022-11-14 20:35:12 +00:00
|
|
|
"github.com/hashicorp/consul/sdk/testutil/retry"
|
|
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
gogrpc "google.golang.org/grpc"
|
|
|
|
|
|
|
|
"github.com/hashicorp/consul/sdk/testutil"
|
|
|
|
"github.com/hashicorp/consul/testrpc"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestOperatorBackend_TransferLeader(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
conf := testClusterConfig{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Servers: 3,
|
|
|
|
ServerConf: func(config *Config) {
|
|
|
|
config.RaftConfig.HeartbeatTimeout = 2 * time.Second
|
|
|
|
config.RaftConfig.ElectionTimeout = 2 * time.Second
|
|
|
|
config.RaftConfig.LeaderLeaseTimeout = 1 * time.Second
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
nodes := newTestCluster(t, &conf)
|
|
|
|
s1 := nodes.Servers[0]
|
|
|
|
// Make sure a leader is elected
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
|
|
|
|
// Make a write call to server2 and make sure it gets forwarded to server1
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
t.Cleanup(cancel)
|
|
|
|
|
|
|
|
// Dial server2 directly
|
|
|
|
conn, err := gogrpc.DialContext(ctx, s1.config.RPCAddr.String(),
|
|
|
|
gogrpc.WithContextDialer(newServerDialer(s1.config.RPCAddr.String())),
|
|
|
|
gogrpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
|
|
gogrpc.WithBlock())
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() { conn.Close() })
|
|
|
|
|
|
|
|
operatorClient := pboperator.NewOperatorServiceClient(conn)
|
|
|
|
|
|
|
|
testutil.RunStep(t, "transfer leader", func(t *testing.T) {
|
|
|
|
beforeLeader, _ := s1.raft.LeaderWithID()
|
|
|
|
require.NotEmpty(t, beforeLeader)
|
|
|
|
// Do the grpc Write call to server2
|
|
|
|
req := pboperator.TransferLeaderRequest{
|
|
|
|
ID: "",
|
|
|
|
}
|
|
|
|
reply, err := operatorClient.TransferLeader(ctx, &req)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, reply.Success)
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
2022-12-16 21:31:05 +00:00
|
|
|
time.Sleep(1 * time.Second)
|
2022-11-14 20:35:12 +00:00
|
|
|
afterLeader, _ := s1.raft.LeaderWithID()
|
|
|
|
require.NotEmpty(r, afterLeader)
|
2022-12-16 21:31:05 +00:00
|
|
|
require.NotEqual(r, afterLeader, beforeLeader)
|
2022-11-14 20:35:12 +00:00
|
|
|
})
|
2022-12-16 21:31:05 +00:00
|
|
|
|
2022-11-14 20:35:12 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestOperatorBackend_TransferLeaderWithACL(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
conf := testClusterConfig{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Servers: 3,
|
|
|
|
ServerConf: func(config *Config) {
|
|
|
|
config.RaftConfig.HeartbeatTimeout = 2 * time.Second
|
|
|
|
config.RaftConfig.ElectionTimeout = 2 * time.Second
|
|
|
|
config.RaftConfig.LeaderLeaseTimeout = 1 * time.Second
|
|
|
|
config.ACLsEnabled = true
|
|
|
|
config.ACLInitialManagementToken = "root"
|
|
|
|
config.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
nodes := newTestCluster(t, &conf)
|
|
|
|
s1 := nodes.Servers[0]
|
|
|
|
// Make sure a leader is elected
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
// Make a write call to server2 and make sure it gets forwarded to server1
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
t.Cleanup(cancel)
|
|
|
|
|
|
|
|
// Dial server2 directly
|
|
|
|
conn, err := gogrpc.DialContext(ctx, s1.config.RPCAddr.String(),
|
|
|
|
gogrpc.WithContextDialer(newServerDialer(s1.config.RPCAddr.String())),
|
|
|
|
gogrpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
|
|
gogrpc.WithBlock())
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() { conn.Close() })
|
|
|
|
|
|
|
|
operatorClient := pboperator.NewOperatorServiceClient(conn)
|
|
|
|
|
2022-12-16 21:31:05 +00:00
|
|
|
codec := rpcClient(t, s1)
|
|
|
|
rules := `operator = "write"`
|
|
|
|
tokenWrite := createTokenWithPolicyNameFull(t, codec, "the-policy-write", rules, "root")
|
|
|
|
rules = `operator = "read"`
|
|
|
|
tokenRead := createToken(t, codec, rules)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-11-14 20:35:12 +00:00
|
|
|
testutil.RunStep(t, "transfer leader no token", func(t *testing.T) {
|
|
|
|
beforeLeader, _ := s1.raft.LeaderWithID()
|
|
|
|
require.NotEmpty(t, beforeLeader)
|
|
|
|
// Do the grpc Write call to server2
|
|
|
|
req := pboperator.TransferLeaderRequest{
|
|
|
|
ID: "",
|
|
|
|
}
|
|
|
|
reply, err := operatorClient.TransferLeader(ctx, &req)
|
|
|
|
require.True(t, acl.IsErrPermissionDenied(err))
|
|
|
|
require.Nil(t, reply)
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
2023-02-17 21:14:46 +00:00
|
|
|
retry.RunWith(&retry.Timer{Wait: time.Second, Timeout: 3 * time.Second}, t, func(r *retry.R) {
|
2022-11-14 20:35:12 +00:00
|
|
|
afterLeader, _ := s1.raft.LeaderWithID()
|
|
|
|
require.NotEmpty(r, afterLeader)
|
2022-12-16 21:31:05 +00:00
|
|
|
if afterLeader != beforeLeader {
|
|
|
|
r.Fatalf("leader should have changed %s == %s", afterLeader, beforeLeader)
|
|
|
|
}
|
2022-11-14 20:35:12 +00:00
|
|
|
})
|
2022-12-16 21:31:05 +00:00
|
|
|
|
2022-11-14 20:35:12 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
testutil.RunStep(t, "transfer leader operator read token", func(t *testing.T) {
|
|
|
|
|
|
|
|
beforeLeader, _ := s1.raft.LeaderWithID()
|
|
|
|
require.NotEmpty(t, beforeLeader)
|
|
|
|
// Do the grpc Write call to server2
|
|
|
|
req := pboperator.TransferLeaderRequest{
|
|
|
|
ID: "",
|
|
|
|
}
|
|
|
|
|
|
|
|
ctxToken, err := external.ContextWithQueryOptions(ctx, structs.QueryOptions{Token: tokenRead})
|
|
|
|
require.NoError(t, err)
|
2022-12-16 21:31:05 +00:00
|
|
|
|
2022-11-14 20:35:12 +00:00
|
|
|
reply, err := operatorClient.TransferLeader(ctxToken, &req)
|
|
|
|
require.True(t, acl.IsErrPermissionDenied(err))
|
|
|
|
require.Nil(t, reply)
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
2023-02-17 21:14:46 +00:00
|
|
|
retry.RunWith(&retry.Timer{Wait: time.Second, Timeout: 3 * time.Second}, t, func(r *retry.R) {
|
2022-11-14 20:35:12 +00:00
|
|
|
afterLeader, _ := s1.raft.LeaderWithID()
|
|
|
|
require.NotEmpty(r, afterLeader)
|
2022-12-16 21:31:05 +00:00
|
|
|
if afterLeader != beforeLeader {
|
|
|
|
r.Fatalf("leader should have changed %s == %s", afterLeader, beforeLeader)
|
|
|
|
}
|
2022-11-14 20:35:12 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
testutil.RunStep(t, "transfer leader operator write token", func(t *testing.T) {
|
|
|
|
|
|
|
|
beforeLeader, _ := s1.raft.LeaderWithID()
|
|
|
|
require.NotEmpty(t, beforeLeader)
|
|
|
|
// Do the grpc Write call to server2
|
|
|
|
req := pboperator.TransferLeaderRequest{
|
|
|
|
ID: "",
|
|
|
|
}
|
|
|
|
ctxToken, err := external.ContextWithQueryOptions(ctx, structs.QueryOptions{Token: tokenWrite.SecretID})
|
|
|
|
require.NoError(t, err)
|
|
|
|
reply, err := operatorClient.TransferLeader(ctxToken, &req)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, reply.Success)
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
2023-02-17 21:14:46 +00:00
|
|
|
retry.RunWith(&retry.Timer{Wait: 2 * time.Second, Timeout: 6 * time.Second}, t, func(r *retry.R) {
|
2022-11-14 20:35:12 +00:00
|
|
|
afterLeader, _ := s1.raft.LeaderWithID()
|
|
|
|
require.NotEmpty(r, afterLeader)
|
2022-12-16 21:31:05 +00:00
|
|
|
if afterLeader == beforeLeader {
|
|
|
|
r.Fatalf("leader should have changed %s == %s", afterLeader, beforeLeader)
|
|
|
|
}
|
2022-11-14 20:35:12 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|