github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/inttesting/integrationtest/frr.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 integrationtest defines methods to implement integration
    16  // tests in which a server and one or more clients are brought up and
    17  // exercised.
    18  package integrationtest
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"crypto/x509"
    24  	"net"
    25  	"sync/atomic"
    26  	"testing"
    27  	"time"
    28  
    29  	log "github.com/golang/glog"
    30  
    31  	"google.golang.org/grpc"
    32  	"google.golang.org/grpc/credentials/insecure"
    33  
    34  	"github.com/google/fleetspeak/fleetspeak/src/client"
    35  	ccomms "github.com/google/fleetspeak/fleetspeak/src/client/comms"
    36  	"github.com/google/fleetspeak/fleetspeak/src/client/config"
    37  	chttps "github.com/google/fleetspeak/fleetspeak/src/client/https"
    38  	cservice "github.com/google/fleetspeak/fleetspeak/src/client/service"
    39  	"github.com/google/fleetspeak/fleetspeak/src/common/anypbtest"
    40  	"github.com/google/fleetspeak/fleetspeak/src/comtesting"
    41  	"github.com/google/fleetspeak/fleetspeak/src/inttesting/frr"
    42  	"github.com/google/fleetspeak/fleetspeak/src/server"
    43  	"github.com/google/fleetspeak/fleetspeak/src/server/admin"
    44  	"github.com/google/fleetspeak/fleetspeak/src/server/comms"
    45  	"github.com/google/fleetspeak/fleetspeak/src/server/db"
    46  	"github.com/google/fleetspeak/fleetspeak/src/server/https"
    47  	"github.com/google/fleetspeak/fleetspeak/src/server/sertesting"
    48  	"github.com/google/fleetspeak/fleetspeak/src/server/service"
    49  	"github.com/google/fleetspeak/fleetspeak/src/server/stats"
    50  
    51  	clpb "github.com/google/fleetspeak/fleetspeak/src/client/proto/fleetspeak_client"
    52  	fspb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak"
    53  	mpb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak_monitoring"
    54  	fgrpc "github.com/google/fleetspeak/fleetspeak/src/inttesting/frr/proto/fleetspeak_frr"
    55  	fpb "github.com/google/fleetspeak/fleetspeak/src/inttesting/frr/proto/fleetspeak_frr"
    56  	sgrpc "github.com/google/fleetspeak/fleetspeak/src/server/proto/fleetspeak_server"
    57  	spb "github.com/google/fleetspeak/fleetspeak/src/server/proto/fleetspeak_server"
    58  	durationpb "google.golang.org/protobuf/types/known/durationpb"
    59  )
    60  
    61  const numClients = 5
    62  
    63  // statsCounter is a simple server.StatsCollector. It only counts messages for the "FRR" service.
    64  type statsCounter struct {
    65  	messagesIngested, payloadBytesSaved, messagesProcessed, messagesErrored, messagesDropped, clientPolls, datastoreOperations int64
    66  }
    67  
    68  func (c *statsCounter) MessageIngested(backlogged bool, m *fspb.Message, cd *db.ClientData) {
    69  	if m.Destination.ServiceName == "FRR" {
    70  		atomic.AddInt64(&c.messagesIngested, 1)
    71  	}
    72  }
    73  
    74  func (c *statsCounter) MessageSaved(forClient bool, m *fspb.Message, cd *db.ClientData) {
    75  	if m.Destination.ServiceName == "FRR" {
    76  		savedPayloadBytes := 0
    77  		if m.Data != nil {
    78  			savedPayloadBytes = len(m.Data.TypeUrl) + len(m.Data.Value)
    79  		}
    80  		atomic.AddInt64(&c.payloadBytesSaved, int64(savedPayloadBytes))
    81  	}
    82  }
    83  
    84  func (c *statsCounter) MessageProcessed(start, end time.Time, m *fspb.Message, isFirstTry bool, cd *db.ClientData) {
    85  	if m.Destination.ServiceName == "FRR" {
    86  		atomic.AddInt64(&c.messagesProcessed, 1)
    87  	}
    88  }
    89  
    90  func (c *statsCounter) MessageErrored(start, end time.Time, isTemp bool, m *fspb.Message, isFirstTry bool, cd *db.ClientData) {
    91  	if m.Destination.ServiceName == "FRR" {
    92  		atomic.AddInt64(&c.messagesErrored, 1)
    93  	}
    94  }
    95  
    96  func (c *statsCounter) MessageDropped(m *fspb.Message, isFirstTry bool, cd *db.ClientData) {
    97  	if m.Destination.ServiceName == "FRR" {
    98  		atomic.AddInt64(&c.messagesDropped, 1)
    99  	}
   100  }
   101  
   102  func (c *statsCounter) ClientPoll(stats.PollInfo) {
   103  	atomic.AddInt64(&c.clientPolls, 1)
   104  }
   105  
   106  func (c *statsCounter) DatastoreOperation(start, end time.Time, operation string, err error) {
   107  	atomic.AddInt64(&c.datastoreOperations, 1)
   108  }
   109  
   110  func (c *statsCounter) ResourceUsageDataReceived(cd *db.ClientData, rud *mpb.ResourceUsageData, v *fspb.ValidationInfo) {
   111  }
   112  
   113  func (c *statsCounter) KillNotificationReceived(cd *db.ClientData, kn *mpb.KillNotification) {
   114  }
   115  
   116  // FRRIntegrationTest spins up a small FRR installation, backed by the provided datastore
   117  // and exercises it.
   118  func FRRIntegrationTest(t *testing.T, ds db.Store, streaming bool) {
   119  	fin := sertesting.SetServerRetryTime(func(_ uint32) time.Time {
   120  		return db.Now().Add(time.Second)
   121  	})
   122  	defer fin()
   123  
   124  	ctx := context.Background()
   125  
   126  	if err := ds.StoreFile(ctx, "system", "RevokedCertificates", bytes.NewReader([]byte{})); err != nil {
   127  		t.Errorf("Unable to store RevokedCertificate file: %v", err)
   128  	}
   129  
   130  	// Create a FRR master server and start it listening.
   131  	gms := grpc.NewServer()
   132  	masterServer := frr.NewMasterServer(nil)
   133  	fgrpc.RegisterMasterServer(gms, masterServer)
   134  	tl, err := net.Listen("tcp", "localhost:0")
   135  	if err != nil {
   136  		t.Fatalf("net.Listen: %v", err)
   137  	}
   138  	go func() {
   139  		err := gms.Serve(tl)
   140  		log.Infof("Finished with MasterServer[%v]: %v", tl.Addr(), err)
   141  	}()
   142  	defer gms.Stop()
   143  
   144  	// Create FS server certs and server communicator.
   145  	cert, key, err := comtesting.ServerCert()
   146  	if err != nil {
   147  		t.Fatalf("comtesting.ServerCert(): %v", err)
   148  	}
   149  
   150  	listener, err := net.Listen("tcp", "localhost:0")
   151  	if err != nil {
   152  		t.Fatalf("net.Listen: %v", err)
   153  	}
   154  	com, err := https.NewCommunicator(
   155  		https.Params{
   156  			Listener:           listener,
   157  			Cert:               cert,
   158  			Key:                key,
   159  			Streaming:          true,
   160  			StreamingLifespan:  10 * time.Second,
   161  			StreamingCloseTime: 7 * time.Second,
   162  		})
   163  	if err != nil {
   164  		t.Fatalf("https.NewCommunicator: %v", err)
   165  	}
   166  	log.Infof("Communicator listening to: %v", listener.Addr())
   167  
   168  	// Create authorizer and signers.
   169  	auth, signs := makeAuthorizerSigners(t)
   170  
   171  	// Create and start a FS server.
   172  	var stats statsCounter
   173  	fsServer, err := server.MakeServer(
   174  		&spb.ServerConfig{
   175  			Services: []*spb.ServiceConfig{{
   176  				Name:           "FRR",
   177  				Factory:        "FRR",
   178  				MaxParallelism: 5,
   179  				Config: anypbtest.New(t, &fpb.Config{
   180  					MasterServer: tl.Addr().String(),
   181  				}),
   182  			}},
   183  			BroadcastPollTime: durationpb.New(time.Second),
   184  		},
   185  		server.Components{
   186  			Datastore:        ds,
   187  			ServiceFactories: map[string]service.Factory{"FRR": frr.ServerServiceFactory},
   188  			Communicators:    []comms.Communicator{com},
   189  			Stats:            &stats,
   190  			Authorizer:       auth,
   191  		})
   192  	if err != nil {
   193  		t.Fatal(err)
   194  	}
   195  
   196  	// Create a FS Admin interface, and start it listening.
   197  	as := admin.NewServer(ds, nil)
   198  	gas := grpc.NewServer()
   199  	sgrpc.RegisterAdminServer(gas, as)
   200  
   201  	asl, err := net.Listen("tcp", "localhost:0")
   202  	if err != nil {
   203  		t.Fatalf("net.Listen: %v", err)
   204  	}
   205  	go func() {
   206  		err := gas.Serve(asl)
   207  		log.Infof("Finished with AdminServer[%v]: %v", asl.Addr(), err)
   208  	}()
   209  	defer gas.Stop()
   210  
   211  	// Connect the FRR master to the resulting FS AdminInterface.
   212  	conn, err := grpc.Dial(asl.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
   213  	if err != nil {
   214  		t.Fatalf("unable to connect to FS AdminInterface")
   215  	}
   216  	defer conn.Close()
   217  	masterServer.SetAdminClient(sgrpc.NewAdminClient(conn))
   218  
   219  	// Start watching for completed clients, create a broadcast to initiate
   220  	// a hunt.  Because the number of messages coming at once is larger than
   221  	// 3*MaxParallelism, messages tend to end up backlogged.
   222  	completed := masterServer.WatchCompleted()
   223  	trd := &fpb.TrafficRequestData{
   224  		RequestId:      0,
   225  		NumMessages:    20,
   226  		MessageDelayMs: 20,
   227  		Jitter:         1.0,
   228  	}
   229  	if err := masterServer.CreateBroadcastRequest(ctx, trd, numClients); err != nil {
   230  		t.Errorf("unable to create hunt: %v", err)
   231  	}
   232  
   233  	// Prepare a general client config.
   234  	conf := config.Configuration{
   235  		TrustedCerts: x509.NewCertPool(),
   236  		ClientLabels: []*fspb.Label{{
   237  			ServiceName: "client",
   238  			Label:       "integration_test",
   239  		}},
   240  		FixedServices: []*fspb.ClientServiceConfig{{
   241  			Name:    "FRR",
   242  			Factory: "FRR",
   243  		}},
   244  		Servers: []string{listener.Addr().String()},
   245  		CommunicatorConfig: &clpb.CommunicatorConfig{
   246  			MaxPollDelaySeconds:    2,
   247  			MaxBufferDelaySeconds:  1,
   248  			MinFailureDelaySeconds: 1,
   249  		},
   250  	}
   251  	if !conf.TrustedCerts.AppendCertsFromPEM(cert) {
   252  		t.Fatal("unable to add server cert to pool")
   253  	}
   254  
   255  	clients := make([]*client.Client, 0, numClients)
   256  	makeComm := func() ccomms.Communicator {
   257  		return &chttps.Communicator{}
   258  	}
   259  	if streaming {
   260  		makeComm = func() ccomms.Communicator {
   261  			return &chttps.StreamingCommunicator{}
   262  		}
   263  	}
   264  	// Create numClient clients.
   265  	for i := range numClients {
   266  		cl, err := client.New(
   267  			conf,
   268  			client.Components{
   269  				ServiceFactories: map[string]cservice.Factory{"FRR": frr.ClientServiceFactory},
   270  				Communicator:     makeComm(),
   271  				Signers:          signs,
   272  			})
   273  		if err != nil {
   274  			t.Errorf("Unable to start client %v: %v", i, err)
   275  		}
   276  		clients = append(clients, cl)
   277  	}
   278  	log.Infof("%v clients started", numClients)
   279  
   280  	// We expect each client to create one response for the hunt.
   281  	for i := range numClients {
   282  		<-completed
   283  		log.Infof("%v clients finished", i+1)
   284  	}
   285  
   286  	// Now check file handling. Store a file on the server and broadcast a
   287  	// request for all clients to read it.
   288  	if err := ds.StoreFile(ctx, "FRR", "TestFile.txt",
   289  		bytes.NewReader([]byte("This is a test file. It has 42 characters.")),
   290  	); err != nil {
   291  		log.Fatalf("Unable to store test file: %v", err)
   292  	}
   293  	if err := masterServer.CreateFileDownloadHunt(ctx, "TestFile.txt", numClients); err != nil {
   294  		log.Fatalf("Unable to create file download hunt: %v", err)
   295  	}
   296  	// We expect each client to create one response for the hunt.
   297  	for i := range numClients {
   298  		<-completed
   299  		log.Infof("%v clients finished download", i+1)
   300  	}
   301  
   302  	// Shut down everything before reading counters, to avoid even the
   303  	// appearance of a race.
   304  	for _, cl := range clients {
   305  		cl.Stop()
   306  	}
   307  	fsServer.Stop()
   308  
   309  	// Each client should have polled at least once.
   310  	if stats.clientPolls < numClients {
   311  		t.Errorf("Got %v messages processed, expected at least %v.", stats.clientPolls, numClients)
   312  	}
   313  	// Each client should have produced between 20 and 40 messages, each should be processed exactly once.
   314  	if stats.messagesProcessed < 20*numClients || stats.messagesProcessed > 40*numClients {
   315  		t.Errorf("Got %v messages processed, expected %v <= x <= %v", stats.messagesProcessed, 20*numClients+1, 40*numClients+1)
   316  	}
   317  
   318  	// Each client should have produced at least 20 and 40, and each should have been sorted at least once.
   319  	if stats.messagesIngested < 20*numClients {
   320  		t.Errorf("Got %v messages sorted, expected at least %v", stats.messagesIngested, 20*numClients+1)
   321  	}
   322  
   323  	// Sanity check that the authorizer is actually seeing things.
   324  	if auth.authCount < numClients {
   325  		t.Errorf("Got %d messages authorized, expected at least %d", auth.authCount, numClients)
   326  	}
   327  }