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 }