mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-03 07:40:28 +08:00
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
240 lines
6.8 KiB
Go
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,
|
|
}
|
|
)
|