converge/pkg/server/admin/admin_test.go
Erik Brakkee 28b2545163 test for connecting clients and bidirectional communication to agent.
Required lots of rework since the GOBChannel appeared to be reading
ahead of the data it actually needed. Now using more low-level IO
to send the clientId over to the agent instead.
2024-09-08 11:16:49 +02:00

241 lines
6.7 KiB
Go

package admin
import (
"context"
"crypto/rand"
"fmt"
"git.wamblee.org/converge/pkg/comms"
"git.wamblee.org/converge/pkg/models"
"git.wamblee.org/converge/pkg/support/iowrappers"
"git.wamblee.org/converge/pkg/testsupport"
"github.com/stretchr/testify/suite"
"io"
"net/http"
"strings"
"testing"
"time"
)
// test cases
//
// Agent only: verify state, verify agentregistration message
// - Connect single agent
// - Connect agent, connect second agent with duplicate id
// -> new id taken out
// - Connect more than 100 agents with the same id
// -> 101th agents gets error
//
// Client connected to agent: Verify clientConnection and agentCOnnection, verify state, verify clientInfo message.
// - Connect agent + connect client with mmtching id
// - Connect agent + connect client with wrong id
//
// Overall:
// - Connect agent, connect 2 clients
// - Connect multiple agents and clients
type AdminTestSuite struct {
suite.Suite
ctx context.Context
cancelFunc context.CancelFunc
pprofServer *http.Server
admin *Admin
hostKey []byte
}
func (s *AdminTestSuite) createPipe() (io.ReadWriteCloser, io.ReadWriteCloser) {
bitpipe := testsupport.NewInmemoryConnection(s.ctx, "inmemory", 10)
return bitpipe.Front(), bitpipe.Back()
}
func (s *AdminTestSuite) SetupSuite() {
s.pprofServer = testsupport.StartPprof("")
}
func (s *AdminTestSuite) TearDownSuite() {
testsupport.StopPprof(s.ctx, s.pprofServer)
}
func (s *AdminTestSuite) SetupTest() {
ctx, cancelFunc := testsupport.CreateTestContext(context.Background(), 10*time.Second)
s.ctx = ctx
s.cancelFunc = cancelFunc
s.admin = NewAdmin()
s.hostKey = make([]byte, 100)
s.NotNil(rand.Read(s.hostKey))
}
func (s *AdminTestSuite) TearDownTest() {
}
func TestAdminTestSuite(t *testing.T) {
suite.Run(t, &AdminTestSuite{})
}
type AddAgentResult struct {
agentConn *agentConnection
err error
}
type AgentRegisterResult struct {
registration comms.AgentRegistration
commChannel comms.CommChannel
err error
}
func (s *AdminTestSuite) agentRegisters(requestedPublicId, assignedPublicId string) (AddAgentResult, AgentRegisterResult) {
agentRW, serverRW := s.createPipe()
res := testsupport.RunAndWait(
&s.Suite,
func() any {
agentConn, err := s.addAgent(requestedPublicId, assignedPublicId, serverRW)
return AddAgentResult{
agentConn: agentConn,
err: err,
}
},
func() any {
res := s.agentRegistration(agentRW)
if assignedPublicId != "" {
s.Nil(res.err)
s.True(res.registration.Ok)
s.Equal(s.hostKey, res.registration.HostPrivateKey)
}
return res
})
return res[0].(AddAgentResult), res[1].(AgentRegisterResult)
}
func (s *AdminTestSuite) Test_AgentRegisters() {
res, _ := s.agentRegisters("abc", "abc")
s.Nil(res.err)
agentConn := res.agentConn
state := s.admin.CreateNotifification()
s.Equal(1, len(state.Agents))
s.Equal(0, len(state.Clients))
s.Equal(agentConn.Info, state.Agents[agentConn.Info.Guid])
}
func (s *AdminTestSuite) Test_ManyAgentsRegister() {
N := 10
agentRegistrations := make([]testsupport.TestFunction, N)
for i := range N {
publicId := fmt.Sprintf("abc%d", i)
agentRegistrations[i] = func() any {
res, _ := s.agentRegisters(publicId, publicId)
s.Nil(res.err)
return res.agentConn
}
}
res := testsupport.RunAndWait(
&s.Suite,
agentRegistrations...)
state := s.admin.CreateNotifification()
s.Equal(len(res), len(state.Agents))
s.Equal(0, len(state.Clients))
for _, entry := range res {
agentConn := entry.(*agentConnection)
s.Equal(agentConn.Info, state.Agents[agentConn.Info.Guid])
}
}
func (s *AdminTestSuite) Test_agentDuplicateId() {
res, _ := s.agentRegisters("abc", "abc")
s.Nil(res.err)
for i := range 100 {
res, _ = s.agentRegisters("abc", fmt.Sprintf("abc-%d", i))
s.Nil(res.err)
}
res, agentSideResult := s.agentRegisters("abc", "")
s.NotNil(res.err)
// verify it is the correct error and not an id mismatch.
s.True(strings.Contains(res.err.Error(), "could not allocate a new unique id"))
s.False(agentSideResult.registration.Ok)
}
func (s *AdminTestSuite) Test_connectClient() {
publicId := "abc"
serverRes, agentRes := s.agentRegisters(publicId, "abc")
s.Nil(serverRes.err)
s.Nil(agentRes.err)
serverToClientRW, clientToServerRW := s.createPipe()
data := "connect client test msg"
res := testsupport.RunAndWait(
&s.Suite,
func() any {
// server
clientConn, err := s.admin.AddClient(models.RendezVousId(publicId),
iowrappers.NewSimpleReadWriteAddrCloser(serverToClientRW, testsupport.DummyRemoteAddr("remoteaddr")))
s.Nil(err)
// Connection to agent over yamux
serverToAgentYamux := clientConn.agentConnection
// test by sending a message to the agent.
testsupport.AssertWriteData(&s.Suite, data, serverToAgentYamux)
return clientConn
},
func() any {
// agent
listener := comms.NewAgentListener(agentRes.commChannel.Session)
//.Connection from server over yamux
agentToServerYamux, err := listener.Accept()
s.Nil(err)
// Test by receiving a message from the server
testsupport.AssertReadData(&s.Suite, data, agentToServerYamux)
return agentToServerYamux
})
// Now we need to verify bi-directional communication between client and agent through the wserv
clientConn := res[0].(*clientConnection)
go clientConn.Synchronize()
agentToServerYamux := res[1].(io.ReadWriter)
data1 := "mytestdata"
data2 := "mytestdata-2"
testsupport.RunAndWait(
&s.Suite,
func() any {
testsupport.AssertWriteData(&s.Suite, data1, clientToServerRW)
testsupport.AssertReadData(&s.Suite, data2, agentToServerYamux)
return nil
},
func() any {
testsupport.AssertReadData(&s.Suite, data1, agentToServerYamux)
testsupport.AssertWriteData(&s.Suite, data2, clientToServerRW)
return nil
})
// will close the connections and as a result also th synchronize goroutine.
s.cancelFunc()
}
func (s *AdminTestSuite) agentRegistration(agentRW io.ReadWriteCloser) AgentRegisterResult {
// verify registration message received
agentRegistration, err := comms.ReceiveRegistrationMessage(agentRW)
if err != nil {
return AgentRegisterResult{err: err}
}
commChannel, err := comms.NewCommChannel(comms.Agent, agentRW)
if err != nil {
return AgentRegisterResult{registration: agentRegistration, err: err}
}
s.NotNil(commChannel)
return AgentRegisterResult{registration: agentRegistration, commChannel: commChannel, err: nil}
}
func (s *AdminTestSuite) addAgent(publicId string, assignedPublicId string, serverRW io.ReadWriteCloser) (*agentConnection, error) {
agentConn, err := s.admin.AddAgent(
s.hostKey, models.RendezVousId(publicId), comms.EnvironmentInfo{},
serverRW)
if err != nil {
return nil, err
}
s.Equal(assignedPublicId, string(agentConn.Info.PublicId))
return agentConn, nil
}