Files
threshold/internal/tests/api/proxy/proxy_test.go
T
Jeff MitchellandJohan Brandhorst-Satzkorn fff5f9506f Migrate proxy logic to the API package (#4216)
This allows arbitrary programs to have an easy mechanism to make
connections through Boundary using Go. Internal cluster tests and test
helpers have been migrated to use this package, as well as the `connect`
command.

---------

Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
2024-01-21 22:34:33 -05:00

138 lines
4.5 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package proxy
import (
"context"
"net"
"net/netip"
"sync"
"testing"
"time"
"github.com/hashicorp/boundary/api/proxy"
"github.com/hashicorp/boundary/api/targets"
"github.com/hashicorp/boundary/internal/cmd/config"
"github.com/hashicorp/boundary/internal/daemon/controller"
"github.com/hashicorp/boundary/internal/daemon/worker"
"github.com/hashicorp/boundary/internal/tests/helper"
"github.com/hashicorp/go-hclog"
"github.com/stretchr/testify/require"
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/targets/tcp"
)
// TestConnectionsLeft tests general proxy functionality, as well as the
// mechanism to notify the caller of the number of connections left on the
// caller's channel and in the ConnectionsLeft function.
func TestConnectionsLeft(t *testing.T) {
require := require.New(t)
logger := hclog.New(&hclog.LoggerOptions{
Name: t.Name(),
Level: hclog.Trace,
})
// Create controller and worker
conf, err := config.DevController()
require.NoError(err)
c1 := controller.NewTestController(t, &controller.TestControllerOpts{
Config: conf,
InitialResourcesSuffix: "1234567890",
Logger: logger.Named("c1"),
WorkerStatusGracePeriodDuration: helper.DefaultWorkerStatusGracePeriod,
})
defer c1.Shutdown()
helper.ExpectWorkers(t, c1)
w1 := worker.NewTestWorker(t, &worker.TestWorkerOpts{
WorkerAuthKms: c1.Config().WorkerAuthKms,
InitialUpstreams: c1.ClusterAddrs(),
Logger: logger.Named("w1"),
SuccessfulStatusGracePeriodDuration: helper.DefaultWorkerStatusGracePeriod,
})
defer w1.Shutdown()
err = w1.Worker().WaitForNextSuccessfulStatusUpdate()
require.NoError(err)
err = c1.WaitForNextWorkerStatusUpdate(w1.Name())
require.NoError(err)
helper.ExpectWorkers(t, c1, w1)
// Connect target
client := c1.Client()
client.SetToken(c1.Token().Token)
tcl := targets.NewClient(client)
tgt, err := tcl.Read(c1.Context(), "ttcp_1234567890")
require.NoError(err)
require.NotNil(tgt)
// Create test server, update default port on target
ts := helper.NewTestTcpServer(t)
require.NotNil(t, ts)
defer ts.Close()
var sessionConnsLimit int32 = 4
tgt, err = tcl.Update(c1.Context(), tgt.Item.Id, tgt.Item.Version, targets.WithTcpTargetDefaultPort(ts.Port()), targets.WithSessionConnectionLimit(sessionConnsLimit))
require.NoError(err)
require.NotNil(tgt)
// Authorize session to get authorization data
sess, err := tcl.AuthorizeSession(c1.Context(), tgt.Item.Id)
require.NoError(err)
sessAuthz, err := sess.GetSessionAuthorization()
require.NoError(err)
// Create a context we can cancel to stop the proxy, a channel for conns
// left, and a waitgroup to ensure cleanup
pxyCtx, pxyCancel := context.WithCancel(c1.Context())
defer pxyCancel()
connsLeftCh := make(chan int32)
wg := new(sync.WaitGroup)
pxy, err := proxy.New(pxyCtx, sessAuthz.AuthorizationToken, proxy.WithConnectionsLeftCh(connsLeftCh))
require.NoError(err)
wg.Add(1)
go func() {
defer wg.Done()
require.NoError(pxy.Start())
// Ensure that starting it again does not actually result in it starting
// again
require.ErrorContains(pxy.Start(), "already started")
}()
addr := pxy.ListenerAddress(context.Background())
require.NotEmpty(addr)
addrPort, err := netip.ParseAddrPort(addr)
require.NoError(err)
echo := []byte("echo")
readBuf := make([]byte, len(echo))
// While we have sessions left, expect no error and to read and write
// through the proxy, and to read the conns left from the channel. Once we
// have hit the connection limit, we expect an error on dial.
for i := sessionConnsLimit; i >= 0; i-- {
// Give time for the listener to be closed. The information about conns
// left comes from upstream responses and we can circle around too fast
// to have the listener be closed, leading to indeterminate results.
time.Sleep(time.Second)
conn, err := net.DialTCP("tcp", nil, net.TCPAddrFromAddrPort(addrPort))
if i == 0 {
require.Error(err)
break
}
require.NoError(err)
written, err := conn.Write(echo)
require.NoError(err)
require.Equal(written, len(echo))
read, err := conn.Read(readBuf)
require.NoError(err)
require.Equal(read, len(echo))
connsLeft := <-connsLeftCh
require.Equal(i-1, connsLeft)
require.Equal(i-1, pxy.ConnectionsLeft())
}
pxyCancel()
// Wait to ensure cleanup and that the second-start logic works
wg.Wait()
}