github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/client/client_test.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 client 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "os" 22 "os/exec" 23 "path/filepath" 24 "reflect" 25 "strconv" 26 "strings" 27 "sync" 28 "sync/atomic" 29 "syscall" 30 "testing" 31 "time" 32 33 "github.com/google/fleetspeak/fleetspeak/src/client/clienttestutils" 34 "github.com/google/fleetspeak/fleetspeak/src/client/config" 35 "github.com/google/fleetspeak/fleetspeak/src/client/service" 36 "github.com/google/fleetspeak/fleetspeak/src/client/stats" 37 "github.com/google/fleetspeak/fleetspeak/src/common" 38 "github.com/google/fleetspeak/fleetspeak/src/common/anypbtest" 39 "github.com/google/fleetspeak/fleetspeak/src/comtesting" 40 "google.golang.org/protobuf/proto" 41 42 fspb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak" 43 ) 44 45 var ( 46 fakeServiceStopCount = 0 47 fakeServiceStartCount = 0 48 fakeServiceCountersMutex = &sync.Mutex{} 49 ) 50 51 // A fakeService implements Service and passes all received messages into a channel. 52 type fakeService struct { 53 c chan *fspb.Message 54 sc service.Context 55 } 56 57 func (s *fakeService) Start(sc service.Context) error { 58 s.sc = sc 59 fakeServiceCountersMutex.Lock() 60 fakeServiceStartCount++ 61 fakeServiceCountersMutex.Unlock() 62 return nil 63 } 64 65 func (s *fakeService) ProcessMessage(ctx context.Context, m *fspb.Message) error { 66 s.c <- m 67 return nil 68 } 69 70 func (s *fakeService) Stop() error { 71 fakeServiceCountersMutex.Lock() 72 fakeServiceStopCount++ 73 fakeServiceCountersMutex.Unlock() 74 return nil 75 } 76 77 func TestMessageDelivery(t *testing.T) { 78 msgs := make(chan *fspb.Message) 79 fs := fakeService{c: msgs} 80 81 fakeServiceFactory := func(*fspb.ClientServiceConfig) (service.Service, error) { 82 return &fs, nil 83 } 84 85 cl, err := New( 86 config.Configuration{ 87 FixedServices: []*fspb.ClientServiceConfig{{Name: "FakeService", Factory: "FakeService"}}, 88 }, 89 Components{ 90 ServiceFactories: map[string]service.Factory{ 91 "NOOP": service.NOOPFactory, 92 "FakeService": fakeServiceFactory, 93 }, 94 }) 95 if err != nil { 96 t.Fatalf("Unable to create client: %v", err) 97 } 98 defer cl.Stop() 99 100 mid, err := common.RandomMessageID() 101 if err != nil { 102 t.Fatalf("Unable to create message id: %v", err) 103 } 104 105 if err := cl.ProcessMessage(context.Background(), 106 service.AckMessage{ 107 M: &fspb.Message{ 108 MessageId: mid.Bytes(), 109 Source: &fspb.Address{ServiceName: "FakeService"}, 110 Destination: &fspb.Address{ 111 ClientId: cl.config.ClientID().Bytes(), 112 ServiceName: "FakeService", 113 }, 114 }}); err != nil { 115 t.Fatalf("Unable to process message: %v", err) 116 } 117 118 m := <-msgs 119 120 if !bytes.Equal(m.MessageId, mid.Bytes()) { 121 t.Errorf("Got message with id: %v, want: %v", m, mid) 122 } 123 124 // Check that the service.Context provided to fs has a working LocalInfo. 125 // TODO: move this to a separate test. 126 li := fs.sc.GetLocalInfo() 127 if li.ClientID != cl.config.ClientID() { 128 t.Errorf("Got LocalInfo.ClientID: %v, want %v", li.ClientID, cl.config.ClientID()) 129 } 130 if !reflect.DeepEqual(li.Services, []string{"FakeService"}) { 131 t.Errorf("Got LocalInfo.Services: %v, want %v", li.Services, []string{"FakeService"}) 132 } 133 } 134 135 func TestRekey(t *testing.T) { 136 cl, err := New( 137 config.Configuration{ 138 FixedServices: []*fspb.ClientServiceConfig{{Name: "FakeService", Factory: "FakeService"}}, 139 }, 140 Components{ 141 ServiceFactories: map[string]service.Factory{ 142 "NOOP": service.NOOPFactory, 143 }, 144 }) 145 if err != nil { 146 t.Fatalf("Unable to create client: %v", err) 147 } 148 defer cl.Stop() 149 150 oid := cl.config.ClientID() 151 152 mid, err := common.RandomMessageID() 153 if err != nil { 154 t.Fatalf("Unable to create message id: %v", err) 155 } 156 157 if err := cl.ProcessMessage(context.Background(), 158 service.AckMessage{ 159 M: &fspb.Message{ 160 MessageId: mid.Bytes(), 161 Source: &fspb.Address{ServiceName: "system"}, 162 Destination: &fspb.Address{ 163 ClientId: oid.Bytes(), 164 ServiceName: "system", 165 }, 166 MessageType: "RekeyRequest", 167 }, 168 }); err != nil { 169 t.Fatalf("Unable to process message: %v", err) 170 } 171 172 tk := time.NewTicker(200 * time.Millisecond) 173 start := time.Now() 174 var nid common.ClientID 175 defer tk.Stop() 176 for range tk.C { 177 nid = cl.config.ClientID() 178 if nid != oid { 179 break 180 } 181 if time.Since(start) > 20*time.Second { 182 t.Errorf("Timed out waiting for id to change.") 183 break 184 } 185 } 186 } 187 188 func triggerDeath(force bool, t *testing.T) { 189 cl, err := New( 190 config.Configuration{}, 191 Components{}) 192 if err != nil { 193 t.Fatalf("Unable to create client: %v", err) 194 } 195 defer cl.Stop() 196 197 oid := cl.config.ClientID() 198 199 mid, err := common.RandomMessageID() 200 if err != nil { 201 t.Fatalf("Unable to create message id: %v", err) 202 } 203 204 if err := cl.ProcessMessage(context.Background(), 205 service.AckMessage{ 206 M: &fspb.Message{ 207 MessageId: mid.Bytes(), 208 Source: &fspb.Address{ServiceName: "system"}, 209 Destination: &fspb.Address{ 210 ClientId: oid.Bytes(), 211 ServiceName: "system", 212 }, 213 MessageType: "Die", 214 Data: anypbtest.New(t, &fspb.DieRequest{ 215 Force: force, 216 }), 217 }, 218 }); err != nil { 219 t.Fatalf("Unable to process message: %v", err) 220 } 221 222 tk := time.NewTicker(200 * time.Millisecond) 223 start := time.Now() 224 defer tk.Stop() 225 for range tk.C { 226 if time.Since(start) > 20*time.Second { 227 t.Errorf("Timed out waiting for the client to die.") 228 break 229 } 230 } 231 } 232 233 func TestDie(t *testing.T) { 234 // Using a workaround from https://talks.golang.org/2014/testing.slide#23 to test os.Exit behavior. 235 if os.Getenv("TRIGGER_DEATH") == "1" { 236 force, err := strconv.ParseBool(os.Getenv("TRIGGER_DEATH_FORCE")) 237 if err != nil { 238 t.Fatalf("Can't parse TRIGGER_DEATH_FORCE env variable: %v", err) 239 } 240 triggerDeath(force, t) 241 return 242 } 243 244 for _, force := range []bool{true, false} { 245 t.Run(fmt.Sprintf("TestDie[force=%v]", force), func(t *testing.T) { 246 247 cmd := exec.Command(os.Args[0], "-test.run=TestDie") 248 cmd.Env = append(os.Environ(), "TRIGGER_DEATH=1", fmt.Sprintf("TRIGGER_DEATH_FORCE=%v", force)) 249 err := cmd.Run() 250 if e, ok := err.(*exec.ExitError); ok { 251 if status, ok := e.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == SuicideExitCode { 252 return 253 } 254 } 255 t.Fatalf("Process ran with err %v, want exit status %d", err, SuicideExitCode) 256 }) 257 } 258 } 259 260 func TestDieDoesNotAck(t *testing.T) { 261 cl, err := New( 262 config.Configuration{}, 263 Components{}, 264 ) 265 if err != nil { 266 t.Fatalf("Unable to create client: %v", err) 267 } 268 269 clientStopped := false 270 stopClientOnce := func() { 271 if !clientStopped { 272 cl.Stop() 273 clientStopped = true 274 } 275 } 276 277 defer stopClientOnce() 278 279 oid := cl.config.ClientID() 280 281 midDie, err := common.RandomMessageID() 282 if err != nil { 283 t.Fatalf("Unable to create message id: %v", err) 284 } 285 midFoo, err := common.RandomMessageID() 286 if err != nil { 287 t.Fatalf("Unable to create message id: %v", err) 288 } 289 290 // Replace system service with a fake service 291 292 cl.sc.services["system"].service.Stop() 293 cl.sc.services["system"].service = &fakeService{c: make(chan *fspb.Message, 5)} 294 295 // Send a Die message 296 297 am := service.AckMessage{ 298 M: &fspb.Message{ 299 MessageId: midDie.Bytes(), 300 Source: &fspb.Address{ServiceName: "system"}, 301 Destination: &fspb.Address{ 302 ClientId: oid.Bytes(), 303 ServiceName: "system"}, 304 MessageType: "Die", 305 }, 306 } 307 308 if err := cl.ProcessMessage(context.Background(), am); err != nil { 309 t.Fatalf("Unable to process message: %v", err) 310 } 311 312 // Send a Foo message 313 314 am = service.AckMessage{ 315 M: &fspb.Message{ 316 MessageId: midFoo.Bytes(), 317 Source: &fspb.Address{ServiceName: "system"}, 318 Destination: &fspb.Address{ 319 ClientId: oid.Bytes(), 320 ServiceName: "system"}, 321 MessageType: "Foo", 322 }, 323 } 324 325 if err := cl.ProcessMessage(context.Background(), am); err != nil { 326 t.Fatalf("Unable to process message: %v", err) 327 } 328 329 // Stop the client to make sure processing has finished. 330 331 stopClientOnce() 332 333 if len(cl.acks) != 1 { 334 t.Fatalf("Got %v acks, want 1", len(cl.acks)) 335 } 336 } 337 338 func TestRestartService(t *testing.T) { 339 fakeServiceCountersMutex.Lock() 340 prevStartCount := fakeServiceStartCount 341 prevStopCount := fakeServiceStopCount 342 fakeServiceCountersMutex.Unlock() 343 344 msgs := make(chan *fspb.Message) 345 fs := fakeService{c: msgs} 346 347 fakeServiceFactory := func(*fspb.ClientServiceConfig) (service.Service, error) { 348 return &fs, nil 349 } 350 351 cl, err := New( 352 config.Configuration{ 353 FixedServices: []*fspb.ClientServiceConfig{{Name: "FakeService", Factory: "FakeService"}}, 354 }, 355 Components{ 356 ServiceFactories: map[string]service.Factory{ 357 "NOOP": service.NOOPFactory, 358 "FakeService": fakeServiceFactory, 359 }, 360 }) 361 if err != nil { 362 t.Fatalf("Unable to create client: %v", err) 363 } 364 defer cl.Stop() 365 366 oid := cl.config.ClientID() 367 368 mid, err := common.RandomMessageID() 369 if err != nil { 370 t.Fatalf("Unable to create message id: %v", err) 371 } 372 373 if err := cl.ProcessMessage(context.Background(), 374 service.AckMessage{ 375 M: &fspb.Message{ 376 MessageId: mid.Bytes(), 377 Source: &fspb.Address{ServiceName: "system"}, 378 Destination: &fspb.Address{ 379 ClientId: oid.Bytes(), 380 ServiceName: "system"}, 381 MessageType: "RestartService", 382 Data: anypbtest.New(t, &fspb.RestartServiceRequest{ 383 Name: "FakeService", 384 }), 385 }, 386 }); err != nil { 387 t.Fatalf("Unable to process message: %v", err) 388 } 389 390 tk := time.NewTicker(200 * time.Millisecond) 391 start := time.Now() 392 defer tk.Stop() 393 for range tk.C { 394 fakeServiceCountersMutex.Lock() 395 startDiff := fakeServiceStartCount - prevStartCount 396 stopDiff := fakeServiceStopCount - prevStopCount 397 fakeServiceCountersMutex.Unlock() 398 399 // fakeService is started once and then restarted, a restart is a Stop() 400 // followed by Start(), meaning that overall 2 starts and 1 stop are expected. 401 if startDiff == 2 && stopDiff == 1 { 402 break 403 } 404 405 if time.Since(start) > 20*time.Second { 406 t.Errorf("Timed out waiting for the server restart.") 407 break 408 } 409 } 410 } 411 412 func TestMessageValidation(t *testing.T) { 413 msgs := make(chan *fspb.Message) 414 fs := fakeService{c: msgs} 415 416 fakeServiceFactory := func(*fspb.ClientServiceConfig) (service.Service, error) { 417 return &fs, nil 418 } 419 420 cl, err := New( 421 config.Configuration{ 422 FixedServices: []*fspb.ClientServiceConfig{{Name: "FakeService", Factory: "FakeService"}}, 423 }, 424 Components{ 425 ServiceFactories: map[string]service.Factory{ 426 "NOOP": service.NOOPFactory, 427 "FakeService": fakeServiceFactory, 428 }, 429 }) 430 if err != nil { 431 t.Fatalf("Unable to create client: %v", err) 432 } 433 434 for _, tc := range []struct { 435 m *fspb.Message 436 want string 437 }{ 438 {m: &fspb.Message{}, 439 want: "destination must have ServiceName", 440 }, 441 {m: &fspb.Message{Destination: &fspb.Address{ServiceName: ""}}, 442 want: "destination must have ServiceName", 443 }, 444 {m: &fspb.Message{Destination: &fspb.Address{ServiceName: "FakeService", ClientId: []byte("abcdef")}}, 445 want: "cannot send directly to client", 446 }, 447 } { 448 err := cl.ProcessMessage(context.Background(), service.AckMessage{M: tc.m}) 449 if err == nil || !strings.HasPrefix(err.Error(), tc.want) { 450 t.Errorf("ProcessMessage(%v) got [%v] but should give error starting with [%v]", tc.m.String(), err, tc.want) 451 } 452 } 453 454 cl.Stop() 455 } 456 457 func TestServiceValidation(t *testing.T) { 458 tmpPath, fin := comtesting.GetTempDir("client_service_validation") 459 defer fin() 460 461 sp := filepath.Join(tmpPath, "services") 462 if err := os.Mkdir(sp, 0777); err != nil { 463 t.Fatalf("Unable to create services path [%s]: %v", sp, err) 464 } 465 466 msgs := make(chan *fspb.Message, 1) 467 fakeServiceFactory := func(*fspb.ClientServiceConfig) (service.Service, error) { 468 return &fakeService{c: msgs}, nil 469 } 470 471 // This factory is used for service configs that shouldn't validate - if it does, it 472 // causes the test to fail. 473 failingServiceFactory := func(cfg *fspb.ClientServiceConfig) (service.Service, error) { 474 t.Fatalf("failingServiceFactory called on %s", cfg.Name) 475 return nil, fmt.Errorf("failingServiceFactory called") 476 } 477 478 // A service which should work. 479 cfg := signServiceConfig(t, &fspb.ClientServiceConfig{ 480 Name: "FakeService", 481 Factory: "FakeService", 482 RequiredLabels: []*fspb.Label{{ServiceName: "client", Label: "linux"}}, 483 }) 484 if err := clienttestutils.WriteSignedServiceConfig(sp, "FakeService.signed", cfg); err != nil { 485 t.Fatal(err) 486 } 487 488 // A service requiring the wrong label. 489 cfg = signServiceConfig(t, &fspb.ClientServiceConfig{ 490 Name: "FailingServiceBadLabel", 491 Factory: "FailingService", 492 RequiredLabels: []*fspb.Label{{ServiceName: "client", Label: "windows"}}, 493 }) 494 if err := clienttestutils.WriteSignedServiceConfig(sp, "FailingServiceBadLabel.signed", cfg); err != nil { 495 t.Fatal(err) 496 } 497 498 ph, err := config.NewFilesystemPersistenceHandler(tmpPath, "") 499 if err != nil { 500 t.Fatal(err) 501 } 502 503 cl, err := New( 504 config.Configuration{ 505 PersistenceHandler: ph, 506 ClientLabels: []*fspb.Label{ 507 {ServiceName: "client", Label: "TestClient"}, 508 {ServiceName: "client", Label: "linux"}}, 509 }, 510 Components{ 511 ServiceFactories: map[string]service.Factory{ 512 "NOOP": service.NOOPFactory, 513 "FakeService": fakeServiceFactory, 514 "FailingService": failingServiceFactory, 515 }, 516 }) 517 if err != nil { 518 t.Fatalf("Unable to create client: %v", err) 519 } 520 defer cl.Stop() 521 522 // Check that the good service started by passing a message through it. 523 mid, err := common.RandomMessageID() 524 if err != nil { 525 t.Fatalf("Unable to create message id: %v", err) 526 } 527 if err := cl.ProcessMessage(context.Background(), 528 service.AckMessage{ 529 M: &fspb.Message{ 530 MessageId: mid.Bytes(), 531 Source: &fspb.Address{ServiceName: "FakeService"}, 532 Destination: &fspb.Address{ 533 ClientId: cl.config.ClientID().Bytes(), 534 ServiceName: "FakeService", 535 }, 536 }}); err != nil { 537 t.Fatalf("Unable to process message: %v", err) 538 } 539 540 m := <-msgs 541 542 if !bytes.Equal(m.MessageId, mid.Bytes()) { 543 t.Errorf("Got message with id: %v, want: %v", m, mid) 544 } 545 } 546 547 func TestTextServiceConfig(t *testing.T) { 548 tmpPath, fin := comtesting.GetTempDir("TestTextServiceConfig") 549 defer fin() 550 551 tsp := filepath.Join(tmpPath, "textservices") 552 if err := os.Mkdir(tsp, 0777); err != nil { 553 t.Fatalf("Unable to create services path [%s]: %v", tsp, err) 554 } 555 556 msgs := make(chan *fspb.Message, 1) 557 fakeServiceFactory := func(*fspb.ClientServiceConfig) (service.Service, error) { 558 return &fakeService{c: msgs}, nil 559 } 560 561 // A text service. 562 cfg := &fspb.ClientServiceConfig{ 563 Name: "FakeService", 564 Factory: "FakeService", 565 RequiredLabels: []*fspb.Label{{ServiceName: "client", Label: "linux"}}, 566 } 567 if err := clienttestutils.WriteServiceConfig(tsp, "FakeService.txt", cfg); err != nil { 568 t.Fatal(err) 569 } 570 571 ph, err := config.NewFilesystemPersistenceHandler(tmpPath, "") 572 if err != nil { 573 t.Fatal(err) 574 } 575 576 cl, err := New( 577 config.Configuration{ 578 PersistenceHandler: ph, 579 ClientLabels: []*fspb.Label{ 580 {ServiceName: "client", Label: "TestClient"}, 581 {ServiceName: "client", Label: "linux"}}, 582 }, 583 Components{ 584 ServiceFactories: map[string]service.Factory{ 585 "NOOP": service.NOOPFactory, 586 "FakeService": fakeServiceFactory, 587 }, 588 }) 589 if err != nil { 590 t.Fatalf("Unable to create client: %v", err) 591 } 592 defer cl.Stop() 593 594 // Check that the service started by passing a message through it. 595 mid, err := common.RandomMessageID() 596 if err != nil { 597 t.Fatalf("Unable to create message id: %v", err) 598 } 599 if err := cl.ProcessMessage(context.Background(), 600 service.AckMessage{ 601 M: &fspb.Message{ 602 MessageId: mid.Bytes(), 603 Source: &fspb.Address{ServiceName: "FakeService"}, 604 Destination: &fspb.Address{ 605 ClientId: cl.config.ClientID().Bytes(), 606 ServiceName: "FakeService", 607 }, 608 }}); err != nil { 609 t.Fatalf("Unable to process message: %v", err) 610 } 611 612 m := <-msgs 613 614 if !bytes.Equal(m.MessageId, mid.Bytes()) { 615 t.Errorf("Got message with id: %v, want: %v", m, mid) 616 } 617 } 618 619 type clientStats struct { 620 stats.NoopCollector 621 messages atomic.Int32 622 } 623 624 func (cs *clientStats) AfterMessageProcessed(msg *fspb.Message, isLocal bool, err error) { 625 if msg.GetSource().GetServiceName() == "NOOPService" { 626 cs.messages.Add(1) 627 } 628 } 629 630 func TestClientStats(t *testing.T) { 631 cs := &clientStats{} 632 633 cl, err := New( 634 config.Configuration{ 635 FixedServices: []*fspb.ClientServiceConfig{{Name: "NOOPService", Factory: "NOOP"}}, 636 }, 637 Components{ 638 ServiceFactories: map[string]service.Factory{ 639 "NOOP": service.NOOPFactory, 640 }, 641 Stats: cs, 642 }) 643 if err != nil { 644 t.Fatalf("Unable to create client: %v", err) 645 } 646 defer cl.Stop() 647 648 mid, err := common.RandomMessageID() 649 if err != nil { 650 t.Fatalf("Unable to create message id: %v", err) 651 } 652 653 if err := cl.ProcessMessage(context.Background(), 654 service.AckMessage{ 655 M: &fspb.Message{ 656 MessageId: mid.Bytes(), 657 Source: &fspb.Address{ServiceName: "NOOPService"}, 658 Destination: &fspb.Address{ 659 ClientId: cl.config.ClientID().Bytes(), 660 ServiceName: "NOOPService", 661 }, 662 }, 663 }); err != nil { 664 t.Fatalf("Unable to process message: %v", err) 665 } 666 667 messageCount := cs.messages.Load() 668 if messageCount != 1 { 669 t.Errorf("Unexpected number of messages reported, got: %d, want: 1", messageCount) 670 } 671 } 672 673 func signServiceConfig(t *testing.T, cfg *fspb.ClientServiceConfig) *fspb.SignedClientServiceConfig { 674 b, err := proto.Marshal(cfg) 675 if err != nil { 676 t.Fatalf("Unable to serialize service config: %v", err) 677 } 678 return &fspb.SignedClientServiceConfig{ServiceConfig: b} 679 }