Files
teleport/lib/srv/db/sqlserver/test.go
T
Trent Clarke 9514a313c3 Break DB integration tests out into their own package (#16133)
Making all of our integration tests run in entirely parallel requires
a large engineering effort to enforce test isolation and remove all race
conditions between tests.

A lower-effort alternative may be to split apart the various test suites
into their own Go packages, and test those packages in parallel, even if
the tests inside are still executed serially. Auditing the test suites
for races on system-level resources (e.g. files, ports) is much easier
than chasing down every p[ossible race in the testing system.

This patch acts as a trial run, breaking a fairly well-defined and
self-contained test suite out into its own package. Note that the goal of
this change is not necessarily to shave minutes off the build (although
that would be nice), but to act as an illustration of how other, less
well-formed test suites might be broken apart.

See-Also: #12421
See-Also: #14408
2022-09-07 11:04:35 +10:00

240 lines
6.8 KiB
Go

/*
Copyright 2022 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sqlserver
import (
"context"
"io"
"net"
"strconv"
"github.com/sirupsen/logrus"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/srv/db/common"
"github.com/gravitational/teleport/lib/srv/db/sqlserver/protocol"
"github.com/gravitational/teleport/lib/utils"
mssql "github.com/denisenkom/go-mssqldb"
"github.com/denisenkom/go-mssqldb/msdsn"
"github.com/gravitational/trace"
)
// MakeTestClient returns SQL Server client used in tests.
func MakeTestClient(ctx context.Context, config common.TestClientConfig) (*mssql.Conn, error) {
host, port, err := net.SplitHostPort(config.Address)
if err != nil {
return nil, trace.Wrap(err)
}
portI, err := strconv.ParseUint(port, 10, 64)
if err != nil {
return nil, trace.Wrap(err)
}
connector := mssql.NewConnectorConfig(msdsn.Config{
Host: host,
Port: portI,
User: config.RouteToDatabase.Username,
Database: config.RouteToDatabase.Database,
Encryption: msdsn.EncryptionDisabled,
}, nil)
conn, err := connector.Connect(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
mssqlConn, ok := conn.(*mssql.Conn)
if !ok {
return nil, trace.BadParameter("expected *mssql.Conn, got: %T", conn)
}
return mssqlConn, nil
}
// TestConnector is used in tests to mock connections to SQL Server.
type TestConnector struct{}
// Connect simulates successful connection to a SQL Server.
func (c *TestConnector) Connect(ctx context.Context, sessionCtx *common.Session, loginPacket *protocol.Login7Packet) (io.ReadWriteCloser, []mssql.Token, error) {
host, port, err := net.SplitHostPort(sessionCtx.Database.GetURI())
if err != nil {
return nil, nil, trace.Wrap(err)
}
portI, err := strconv.ParseUint(port, 10, 64)
if err != nil {
return nil, nil, trace.Wrap(err)
}
// Pass all login options from the client to the server.
options := msdsn.LoginOptions{
OptionFlags1: loginPacket.OptionFlags1(),
OptionFlags2: loginPacket.OptionFlags2(),
TypeFlags: loginPacket.TypeFlags(),
}
connector := mssql.NewConnectorConfig(msdsn.Config{
Host: host,
Port: portI,
LoginOptions: options,
}, nil)
conn, err := connector.Connect(ctx)
if err != nil {
return nil, nil, trace.Wrap(err)
}
mssqlConn, ok := conn.(*mssql.Conn)
if !ok {
return nil, nil, trace.BadParameter("expected *mssql.Conn, got: %T", conn)
}
return mssqlConn.GetUnderlyingConn(), mssqlConn.GetLoginFlags(), nil
}
// TestServer is a test MSServer server used in functional database
// access tests.
type TestServer struct {
cfg common.TestServerConfig
listener net.Listener
port string
log logrus.FieldLogger
}
// NewTestServer returns a new instance of a test MSServer.
func NewTestServer(config common.TestServerConfig) (svr *TestServer, err error) {
err = config.CheckAndSetDefaults()
if err != nil {
return nil, trace.Wrap(err)
}
defer config.CloseOnError(&err)
port, err := config.Port()
if err != nil {
return nil, trace.Wrap(err)
}
log := logrus.WithFields(logrus.Fields{
trace.Component: defaults.ProtocolSQLServer,
"name": config.Name,
})
server := &TestServer{
cfg: config,
listener: config.Listener,
port: port,
log: log,
}
return server, nil
}
// Port returns SQL Server port.
func (s *TestServer) Port() string {
return s.port
}
// Serve starts serving client connections.
func (s *TestServer) Serve() error {
s.log.Debugf("Starting test MSServer server on %v.", s.listener.Addr())
defer s.log.Debug("Test MSServer server stopped.")
for {
conn, err := s.listener.Accept()
if err != nil {
if utils.IsOKNetworkError(err) {
return nil
}
s.log.WithError(err).Error("Failed to accept connection.")
continue
}
s.log.Debug("Accepted connection.")
go func() {
defer s.log.Debug("Connection done.")
defer conn.Close()
err = s.handleConnection(conn)
if err != nil {
s.log.Errorf("Failed to handle connection: %v.",
trace.DebugReport(err))
}
}()
}
}
func (s *TestServer) handleConnection(conn net.Conn) error {
if err := handleLogin(conn); err != nil {
return trace.Wrap(err)
}
for {
basicPacket, err := protocol.ReadPacket(conn)
if err != nil {
return trace.Wrap(err)
}
packet, err := protocol.ToSQLPacket(basicPacket)
if err != nil {
return trace.Wrap(err)
}
switch packet.(type) {
case *protocol.SQLBatch:
if _, err = conn.Write(mockSQLBatchServerResp); err != nil {
return trace.Wrap(err)
}
}
}
}
func handleLogin(conn net.Conn) error {
if _, err := protocol.ReadPreLoginPacket(conn); err != nil {
return trace.Wrap(err)
}
if err := protocol.WritePreLoginResponse(conn); err != nil {
return trace.Wrap(err)
}
if _, err := protocol.ReadLogin7Packet(conn); err != nil {
return trace.Wrap(err)
}
if _, err := conn.Write(mockLoginServerResp); err != nil {
return trace.Wrap(err)
}
return nil
}
// Close closes the server listener.
func (s *TestServer) Close() error {
return s.listener.Close()
}
var (
mockLoginServerResp = []byte{
0x04, 0x01, 0x00, 0xa7, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x1b, 0x00, 0x01, 0x06, 0x6d, 0x00, 0x61,
0x00, 0x73, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x06, 0x6d, 0x00, 0x61, 0x00, 0x73, 0x00,
0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0xe3, 0x08, 0x00, 0x07, 0x05, 0x09, 0x04, 0xd0, 0x00, 0x34,
0x00, 0xe3, 0x17, 0x00, 0x02, 0x0a, 0x75, 0x00, 0x73, 0x00, 0x5f, 0x00, 0x65, 0x00, 0x6e, 0x00,
0x67, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x73, 0x00, 0x68, 0x00, 0x00, 0xad, 0x36, 0x00, 0x01, 0x74,
0x00, 0x00, 0x04, 0x16, 0x4d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00,
0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x20, 0x00, 0x53, 0x00, 0x51, 0x00, 0x4c, 0x00, 0x20, 0x00,
0x53, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0f, 0x00, 0x0f, 0xe9, 0xe3, 0x13, 0x00, 0x04, 0x04, 0x34, 0x00, 0x30, 0x00, 0x39, 0x00, 0x36,
0x00, 0x04, 0x34, 0x00, 0x30, 0x00, 0x39, 0x00, 0x36, 0x00, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}
mockSQLBatchServerResp = []byte{
0x04, 0x01, 0x00, 0x2b, 0x00, 0x4a, 0x01, 0x00, 0xe3, 0x03, 0x00, 0x12, 0x00, 0x00, 0x81, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x38, 0x00, 0xd1, 0x01, 0x00, 0x00, 0x00, 0xfd, 0x10,
0x00, 0xc1, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}
)