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.
boundary/internal/cmd/commands/server/worker_shutdown_reload_test.go

163 lines
4.7 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
//go:build !hsm
// +build !hsm
package server
import (
"context"
"encoding/json"
"fmt"
"testing"
"time"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/authmethods"
"github.com/hashicorp/boundary/api/authtokens"
"github.com/hashicorp/boundary/api/targets"
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/targets/tcp"
"github.com/hashicorp/boundary/internal/server"
"github.com/hashicorp/boundary/internal/session"
"github.com/hashicorp/boundary/internal/tests/helper"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/hashicorp/boundary/testing/controller"
"github.com/mitchellh/cli"
"github.com/stretchr/testify/require"
"go.uber.org/atomic"
)
const shutdownReloadWorkerProvidedConfiguration = `
disable_mlock = true
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
initial_upstreams = ["%s"]
}
kms "aead" {
purpose = "worker-auth"
aead_type = "aes-gcm"
key = "%s"
key_id = "global_worker-auth"
}
listener "tcp" {
purpose = "proxy"
address = "127.0.0.1:9205"
}
`
func TestServer_ShutdownWorker(t *testing.T) {
require := require.New(t)
rootWrapper, _ := wrapperWithKey(t)
recoveryWrapper, _ := wrapperWithKey(t)
workerAuthWrapper, key := wrapperWithKey(t)
testController := controller.NewTestController(t, controller.WithWorkerAuthKms(workerAuthWrapper), controller.WithRootKms(rootWrapper), controller.WithRecoveryKms(recoveryWrapper))
t.Cleanup(testController.Shutdown)
// Start the worker
workerCmd := testServerCommand(t, testServerCommandOpts{})
workerCmd.presetConfig = atomic.NewString(fmt.Sprintf(shutdownReloadWorkerProvidedConfiguration, testController.ClusterAddrs()[0], key))
workerCodeChan := make(chan int)
go func() {
workerCodeChan <- workerCmd.Run(nil)
}()
waitCh(t, workerCmd.startedCh)
// Give the worker time for initial status or we can fail because no worker
// is ready for the connection yet
time.Sleep(5 * time.Second)
// Set up the target
ctx := context.Background()
client := buildClient(t, testController.ApiAddrs()[0])
setAuthToken(ctx, t, client)
tcl := targets.NewClient(client)
tgtL, err := tcl.List(ctx, scope.Global.String(), targets.WithRecursive(true))
require.NoError(err)
require.LessOrEqual(2, len(tgtL.Items))
tgt := tgtL.Items[0]
require.NotNil(tgt)
require.NotNil(tgtL.GetItems()[1])
// Create test server, update default port on target
ts := helper.NewTestTcpServer(t)
require.NotNil(ts)
t.Cleanup(ts.Close)
tgtR, err := tcl.Update(ctx, tgt.Id, tgt.Version, targets.WithTcpTargetDefaultPort(ts.Port()))
require.NoError(err)
require.NotNil(tgtR)
// Authorize and connect
// This prevents us from running tests in parallel.
server.TestUseCommunityFilterWorkersFn(t)
sess := helper.NewTestSession(ctx, t, tcl, tgt.Id, helper.WithSkipSessionTeardown(true))
sConn := sess.Connect(ctx, t)
// Run initial send/receive test, make sure things are working
t.Log("running initial send/recv test")
sConn.TestSendRecvAll(t)
// Shutdown the worker and close the connection, as the worker will otherwise wait for it to close.
err = sConn.Close()
require.NoError(err)
workerCmd.ShutdownCh <- struct{}{}
if <-workerCodeChan != 0 {
output := workerCmd.UI.(*cli.MockUi).ErrorWriter.String() + workerCmd.UI.(*cli.MockUi).OutputWriter.String()
require.FailNow(output, "command exited with non-zero error code")
}
// Connection should fail, and the session should be closed on the DB.
sConn.TestSendRecvFail(t)
sess.ExpectConnectionStateOnController(ctx, t, testController.Controller().ConnectionRepoFn, session.StatusClosed)
}
// largely copied from controller/testing.go
func buildClient(t *testing.T, addr string) *api.Client {
require := require.New(t)
client, err := api.NewClient(nil)
require.NoError(err)
err = client.SetAddr(addr)
require.NoError(err)
// Because this is using the real lib it can pick up from stored locations
// like the system keychain. Explicitly clear the token to ensure we
// understand the client state at the start of each test.
client.SetToken("")
return client
}
func setAuthToken(ctx context.Context, t *testing.T, client *api.Client) {
require := require.New(t)
result, err := authmethods.NewClient(client).Authenticate(
ctx,
"ampw_1234567890",
"login",
map[string]any{
"login_name": "admin",
"password": "passpass",
},
)
require.NoError(err)
token := new(authtokens.AuthToken)
err = json.Unmarshal(result.GetRawAttributes(), token)
require.NoError(err)
client.SetToken(token.Token)
}
func waitCh(t *testing.T, c chan struct{}) {
require := require.New(t)
select {
case <-c:
case <-time.After(15 * time.Second):
require.FailNow("timeout")
}
}