* feat(server): Refactor LookupWorkerByName to be a test method * feat(server): Split worker tags table * refact(worker): Export api and config tags * refact(server): Return worker id from UpsertWorkerStatus * refact(server): Create ListWorkerStorageBucketUpdates domain function * refact(server): Use dedicated HCPb-worker list func * refact(server): Create ConnectionRoute domain function * refact(handlers): Move worker selection logic to internal/server package This commit moves all the logic to select a worker to handle a session to the internal/server package. Most functions were moved to this package with no changes, however, there are a couple of notable exceptions: - Given that we don't use GRPC errors in repos, all errors were adjusted to use the internal/errors package instead. - StorageBucketFilterCredIdFn is now used to prevent the internal/server package from importing internal/storage/plugin, which would cause a dep cycle. Callers of SelectSessionWorkers must implement this func which is responsible for returning a storage bucket's worker filter as well as its associated credential id. - authorizeSessionWithWorkerFilter now receives an internal/server.(*Repository) object when called. This is transparent to SelectSessionWorkers callers as it is handled internally. * feat(server): Add FilteredWithFeatures fn and new server options * refact(server): create new list and lookup worker queries * test(server): Add WorkerList unit tests * feat(server): Implement WorkerList randomness using crypto/rand pkg * chore(db): add index to server_worker table * refact(server): prefix test method with Test * refact(server): rename Downstreamers interface to Graph * chore(server): unexport and rename method * chore: add doc string to WorkerAddress (#5420) --------- Co-authored-by: Irena Rindos <irenarindos@users.noreply.github.com> Co-authored-by: irenarindos <irena.rindos@gmail.com>
163 lines
4.7 KiB
Go
163 lines
4.7 KiB
Go
// 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")
|
|
}
|
|
}
|