github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/server/testserver/testserver.go (about)

     1  // Copyright 2017 Google Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package testserver configures and creates a Fleetspeak server instance
    16  // suitable for unit tests. It also provides utility methods for directly
    17  // adjusting the server state in tests.
    18  package testserver
    19  
    20  import (
    21  	"context"
    22  	"crypto"
    23  	"crypto/rand"
    24  	"crypto/rsa"
    25  	"net"
    26  	"path"
    27  	"testing"
    28  	"time"
    29  
    30  	log "github.com/golang/glog"
    31  	"google.golang.org/protobuf/proto"
    32  
    33  	"github.com/google/fleetspeak/fleetspeak/src/common"
    34  	"github.com/google/fleetspeak/fleetspeak/src/comtesting"
    35  	"github.com/google/fleetspeak/fleetspeak/src/server"
    36  	"github.com/google/fleetspeak/fleetspeak/src/server/comms"
    37  	"github.com/google/fleetspeak/fleetspeak/src/server/service"
    38  	"github.com/google/fleetspeak/fleetspeak/src/server/sqlite"
    39  
    40  	fspb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak"
    41  	spb "github.com/google/fleetspeak/fleetspeak/src/server/proto/fleetspeak_server"
    42  )
    43  
    44  // Server is a test server, with related structures and interfaces to allow
    45  // tests to manipulate it.
    46  type Server struct {
    47  	S  *server.Server
    48  	DS *sqlite.Datastore
    49  	CC comms.Context
    50  }
    51  
    52  // FakeCommunicator implements comms.Communicator to do nothing by
    53  // save the comms.Context to a Server. Most users should simply call
    54  // Make, but this is exposed in order to support more flexible setup
    55  // of test servers.
    56  type FakeCommunicator struct {
    57  	Dest *Server
    58  }
    59  
    60  func (c FakeCommunicator) Setup(cc comms.Context) error {
    61  	c.Dest.CC = cc
    62  	return nil
    63  }
    64  
    65  func (c FakeCommunicator) Start() error { return nil }
    66  
    67  func (c FakeCommunicator) Stop() {}
    68  
    69  // Make creates a server.Server using the provided communicators. It creates and
    70  // attaches it to an sqlite datastore based on the test and test case names.
    71  func Make(t *testing.T, testName, caseName string, comms []comms.Communicator) Server {
    72  	tempDir, tmpDirCleanup := comtesting.GetTempDir(testName)
    73  	defer tmpDirCleanup()
    74  	p := path.Join(tempDir, caseName+".sqlite")
    75  	ds, err := sqlite.MakeDatastore(p)
    76  	if err != nil {
    77  		t.Fatal(err)
    78  	}
    79  	log.Infof("Created database: %s", p)
    80  
    81  	var ret Server
    82  
    83  	s, err := server.MakeServer(
    84  		&spb.ServerConfig{
    85  			Services: []*spb.ServiceConfig{{
    86  				Name:           "TestService",
    87  				Factory:        "NOOP",
    88  				MaxParallelism: 5,
    89  			}},
    90  		},
    91  		server.Components{
    92  			Datastore:        ds,
    93  			ServiceFactories: map[string]service.Factory{"NOOP": service.NOOPFactory},
    94  			Communicators:    append(comms, FakeCommunicator{&ret}),
    95  		})
    96  	if err != nil {
    97  		t.Fatal(err)
    98  	}
    99  	ret.S = s
   100  	ret.DS = ds
   101  	return ret
   102  }
   103  
   104  // MakeWithService creates a server.Server using the provided service. Like in Make(), a sqlite
   105  // datastore is created for the provided test-case.
   106  func MakeWithService(t *testing.T, testName, caseName string, serviceInstance service.Service) Server {
   107  	tempDir, tmpDirCleanup := comtesting.GetTempDir(testName)
   108  	defer tmpDirCleanup()
   109  	p := path.Join(tempDir, caseName+".sqlite")
   110  	ds, err := sqlite.MakeDatastore(p)
   111  	if err != nil {
   112  		t.Fatal(err)
   113  	}
   114  	log.Infof("Created database: %s", p)
   115  
   116  	serviceFactory := func(conf *spb.ServiceConfig) (service.Service, error) {
   117  		return serviceInstance, nil
   118  	}
   119  
   120  	var testServer Server
   121  	s, err := server.MakeServer(
   122  		&spb.ServerConfig{
   123  			Services: []*spb.ServiceConfig{{
   124  				Name:           "TestService",
   125  				Factory:        "CustomFactory",
   126  				MaxParallelism: 5,
   127  			}},
   128  		},
   129  		server.Components{
   130  			Datastore:        ds,
   131  			ServiceFactories: map[string]service.Factory{"CustomFactory": serviceFactory},
   132  			Communicators:    []comms.Communicator{FakeCommunicator{&testServer}},
   133  		})
   134  	if err != nil {
   135  		t.Fatal(err)
   136  	}
   137  
   138  	testServer.S = s
   139  	testServer.DS = ds
   140  	return testServer
   141  }
   142  
   143  // AddClient adds a new client with a random id to a server.
   144  func (s Server) AddClient() (crypto.PublicKey, error) {
   145  	k, err := rsa.GenerateKey(rand.Reader, 2048)
   146  	if err != nil {
   147  		return common.ClientID{}, err
   148  	}
   149  	if _, _, _, err := s.CC.InitializeConnection(
   150  		context.Background(),
   151  		&net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 123},
   152  		k.Public(),
   153  		&fspb.WrappedContactData{},
   154  		false,
   155  	); err != nil {
   156  		return nil, err
   157  	}
   158  	return &k.PublicKey, nil
   159  }
   160  
   161  // ProcessMessageFromClient delivers a message to a server, simulating that it was
   162  // provided by a client. It then waits up to 30 seconds for it to be processed.
   163  func (s Server) ProcessMessageFromClient(k crypto.PublicKey, msg *fspb.Message) error {
   164  	ctx := context.Background()
   165  
   166  	mid, err := common.BytesToMessageID(msg.MessageId)
   167  	if err != nil {
   168  		return err
   169  	}
   170  
   171  	if _, err := s.SimulateContactFromClient(ctx, k, []*fspb.Message{msg}); err != nil {
   172  		return err
   173  	}
   174  
   175  	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
   176  	defer cancel()
   177  	for {
   178  		msg := s.GetMessage(ctx, mid)
   179  		if msg.Result != nil {
   180  			return nil
   181  		}
   182  		if ctx.Err() != nil {
   183  			return ctx.Err()
   184  		}
   185  		time.Sleep(100 * time.Millisecond)
   186  	}
   187  }
   188  
   189  // SimulateContactFromClient accepts zero or more messages as if they came from
   190  // a client, and returns any messages pending for delivery to the client.
   191  func (s Server) SimulateContactFromClient(ctx context.Context, key crypto.PublicKey, msgs []*fspb.Message) ([]*fspb.Message, error) {
   192  	cd := fspb.ContactData{Messages: msgs}
   193  	cdb, err := proto.Marshal(&cd)
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  	_, rcd, _, err := s.CC.InitializeConnection(
   198  		ctx,
   199  		&net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 123},
   200  		key,
   201  		&fspb.WrappedContactData{ContactData: cdb},
   202  		false)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  	return rcd.Messages, nil
   207  }
   208  
   209  // GetMessage retrieves a single message from the datastore, or dies trying.
   210  func (s Server) GetMessage(ctx context.Context, id common.MessageID) *fspb.Message {
   211  	msgs, err := s.DS.GetMessages(ctx, []common.MessageID{id}, true)
   212  	if err != nil {
   213  		log.Fatal(err)
   214  	}
   215  	if len(msgs) != 1 {
   216  		log.Fatalf("Expected 1 message, got: %v", msgs)
   217  	}
   218  	return msgs[0]
   219  }