github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/client/socketservice/socketservice_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 socketservice 16 17 import ( 18 "context" 19 "fmt" 20 "os" 21 "os/exec" 22 "path/filepath" 23 "runtime" 24 "strings" 25 "sync/atomic" 26 "testing" 27 "time" 28 29 log "github.com/golang/glog" 30 "google.golang.org/protobuf/proto" 31 32 "github.com/google/fleetspeak/fleetspeak/src/client/clitesting" 33 "github.com/google/fleetspeak/fleetspeak/src/client/stats" 34 "github.com/google/fleetspeak/fleetspeak/src/common/anypbtest" 35 "github.com/google/fleetspeak/fleetspeak/src/comtesting" 36 37 sspb "github.com/google/fleetspeak/fleetspeak/src/client/socketservice/proto/fleetspeak_socketservice" 38 fspb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak" 39 mpb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak_monitoring" 40 ) 41 42 type testStatsCollector struct { 43 stats.NoopCollector 44 socketsClosed atomic.Int64 45 } 46 47 func (sc *testStatsCollector) SocketServiceSocketClosed(_ string, _ error) { 48 sc.socketsClosed.Add(1) 49 } 50 51 func SetUpSocketPath(t *testing.T, nameHint string) string { 52 t.Helper() 53 54 return filepath.Join(SetUpTempDir(t), nameHint) 55 } 56 57 func SetUpTempDir(t *testing.T) string { 58 t.Helper() 59 60 if runtime.GOOS == "windows" { 61 tmpDir, cleanUpFn := comtesting.GetTempDir("socketservice") 62 t.Cleanup(cleanUpFn) 63 return tmpDir 64 } 65 66 // We need unix socket paths to be short (see socketservice_unix.go), so we ignore the $TMPDIR env variable, 67 // which could point to arbitrary directories, and use /tmp. 68 tmpDir, err := os.MkdirTemp("/tmp", "socketservice_") 69 if err != nil { 70 t.Fatalf("os.MkdirTemp: %v", err) 71 } 72 t.Cleanup(func() { os.RemoveAll(tmpDir) }) 73 74 // Explicitly set the group owner for tempDir in case it was inherited from /tmp. 75 if err := os.Chown(tmpDir, os.Getuid(), os.Getgid()); err != nil { 76 t.Fatalf("os.Chown(%q): %v", tmpDir, err) 77 } 78 return tmpDir 79 } 80 81 func isErrKilled(err error) bool { 82 if runtime.GOOS == "windows" { 83 return strings.HasSuffix(err.Error(), "exit status 1") 84 } 85 return strings.HasSuffix(err.Error(), "signal: killed") 86 } 87 88 // exerciseLoopback attempts to send messages through a testclient in loopback mode using 89 // socketPath. 90 func exerciseLoopback(t *testing.T, socketPath string) { 91 t.Helper() 92 93 s, err := Factory(&fspb.ClientServiceConfig{ 94 Name: "TestSocketService", 95 Config: anypbtest.New(t, &sspb.Config{ 96 ApiProxyPath: socketPath, 97 }), 98 }) 99 if err != nil { 100 t.Fatalf("Factory(...): %v", err) 101 } 102 103 stats := &testStatsCollector{} 104 sc := clitesting.MockServiceContext{ 105 OutChan: make(chan *fspb.Message, 5), 106 StatsCollector: stats, 107 } 108 if err := s.Start(&sc); err != nil { 109 t.Fatalf("socketservice.Start(...): %v", err) 110 } 111 defer func() { 112 // We are shutting down this service. Give the service a bit of time to ack 113 // the messages to the testclient, so that they won't be repeated. 114 time.Sleep(time.Second) 115 if err := s.Stop(); err != nil { 116 t.Errorf("Error stopping service: %v", err) 117 } 118 }() 119 120 msgs := []*fspb.Message{ 121 { 122 MessageId: []byte("\000\000\000"), 123 MessageType: "RequestTypeA", 124 }, 125 { 126 MessageId: []byte("\000\000\001"), 127 MessageType: "RequestTypeB", 128 }, 129 } 130 ctx := context.Background() 131 go func() { 132 for _, m := range msgs { 133 log.Infof("Processing message [%x]", m.MessageId) 134 err := s.ProcessMessage(ctx, m) 135 if err != nil { 136 t.Errorf("Error processing message: %v", err) 137 } 138 log.Info("Message Processed.") 139 } 140 log.Info("Done processing messages.") 141 }() 142 for _, m := range msgs { 143 g := <-sc.OutChan 144 got := proto.Clone(g).(*fspb.Message) 145 got.SourceMessageId = nil 146 147 want := proto.Clone(m).(*fspb.Message) 148 want.MessageType = want.MessageType + "Response" 149 150 if !proto.Equal(got, want) { 151 t.Errorf("Unexpected message from loopback: got [%v], want [%v]", got, want) 152 } 153 } 154 155 socketsClosed := stats.socketsClosed.Load() 156 if want := int64(0); socketsClosed != want { 157 t.Errorf("Got %d sockets closed, want %d", socketsClosed, want) 158 } 159 log.Infof("looped %d messages", len(msgs)) 160 } 161 162 func TestLoopback(t *testing.T) { 163 socketPath := SetUpSocketPath(t, "Loopback") 164 cmd := exec.Command(testClient(t), "--mode=loopback", "--socket_path="+socketPath) 165 if err := cmd.Start(); err != nil { 166 t.Fatalf("cmd.Start() returned error: %v", err) 167 } 168 defer func() { 169 if err := cmd.Process.Kill(); err != nil { 170 t.Errorf("failed to kill testclient[%d]: %v", cmd.Process.Pid, err) 171 } 172 if err := cmd.Wait(); err != nil { 173 if !isErrKilled(err) { 174 t.Errorf("error waiting for testclient: %v", err) 175 } 176 } 177 }() 178 // Each call to exerciseLoopback closes and recreates the socket, 179 // simulating a fs client restart. The testclient in loopback mode should 180 // be able to handle this. 181 exerciseLoopback(t, socketPath) 182 exerciseLoopback(t, socketPath) 183 exerciseLoopback(t, socketPath) 184 } 185 186 func TestAckLoopback(t *testing.T) { 187 socketPath := SetUpSocketPath(t, "AckLoopback") 188 cmd := exec.Command(testClient(t), "--mode=ackLoopback", "--socket_path="+socketPath) 189 if err := cmd.Start(); err != nil { 190 t.Fatalf("cmd.Start() returned error: %v", err) 191 } 192 defer func() { 193 if err := cmd.Process.Kill(); err != nil { 194 t.Errorf("failed to kill testclient[%d]: %v", cmd.Process.Pid, err) 195 } 196 if err := cmd.Wait(); err != nil { 197 if !isErrKilled(err) { 198 t.Errorf("error waiting for testclient: %v", err) 199 } 200 } 201 }() 202 exerciseLoopback(t, socketPath) 203 } 204 205 func TestStutteringLoopback(t *testing.T) { 206 socketPath := SetUpSocketPath(t, "StutteringLoopback") 207 cmd := exec.Command(testClient(t), "--mode=stutteringLoopback", "--socket_path="+socketPath) 208 if err := cmd.Start(); err != nil { 209 t.Fatalf("cmd.Start() returned error: %v", err) 210 } 211 defer func() { 212 if err := cmd.Process.Kill(); err != nil { 213 t.Errorf("failed to kill testclient[%d]: %v", cmd.Process.Pid, err) 214 } 215 if err := cmd.Wait(); err != nil { 216 if !isErrKilled(err) { 217 t.Errorf("error waiting for testclient: %v", err) 218 } 219 } 220 }() 221 222 s, err := Factory(&fspb.ClientServiceConfig{ 223 Name: "TestSocketService", 224 Config: anypbtest.New(t, &sspb.Config{ 225 ApiProxyPath: socketPath, 226 }), 227 }) 228 if err != nil { 229 t.Fatalf("Factory(...): %v", err) 230 } 231 232 starts := make(chan struct{}, 1) 233 s.(*Service).newChan = func() { 234 log.Infof("starting new channel") 235 starts <- struct{}{} 236 } 237 238 stats := &testStatsCollector{} 239 sc := clitesting.MockServiceContext{ 240 OutChan: make(chan *fspb.Message), 241 StatsCollector: stats, 242 } 243 if err := s.Start(&sc); err != nil { 244 t.Fatalf("socketservice.Start(...): %v", err) 245 } 246 ctx := context.Background() 247 248 // Stuttering loopback will close the connection and reconnect after 249 // every message. 250 for i := range 5 { 251 // Wait for our end of the connection to start/restart. 252 <-starts 253 err := s.ProcessMessage(ctx, &fspb.Message{ 254 MessageId: []byte{0, 0, byte(i)}, 255 MessageType: "RequestType", 256 }) 257 if err != nil { 258 t.Errorf("Error processing message: %v", err) 259 } 260 // Wait for the looped message. After sending the message, the other end 261 // will close and restart. 262 m := <-sc.OutChan 263 log.Infof("received looped back message: %x", m.MessageId) 264 } 265 if err := s.Stop(); err != nil { 266 t.Errorf("Error stopping service: %v", err) 267 } 268 socketsClosed := stats.socketsClosed.Load() 269 if want := int64(5); socketsClosed != want { 270 t.Errorf("Got %d sockets closed, want %d", socketsClosed, want) 271 } 272 } 273 274 func TestMaxSockLenUnix(t *testing.T) { 275 var maxSockLen int 276 if runtime.GOOS == "windows" { 277 t.Skip("Not applicable to windows.") 278 } else if runtime.GOOS == "darwin" { 279 maxSockLen = 103 280 } else { 281 maxSockLen = 107 282 } 283 sockPath := "/tmp/This is a really really long path that definitely will not fit in the sockaddr_un struct on most unix platforms" 284 want := fmt.Errorf("socket path is too long (120 chars) - max allowed length is %d: %s.tmp", maxSockLen, sockPath) 285 _, got := listen(sockPath) 286 if got.Error() != want.Error() { 287 t.Errorf("Wrong error received. Want '%s'; got '%s'", want, got) 288 } 289 } 290 291 func TestResourceMonitoring(t *testing.T) { 292 socketPath := SetUpSocketPath(t, "Loopback") 293 cmd := exec.Command(testClient(t), "--mode=loopback", "--socket_path="+socketPath) 294 if err := cmd.Start(); err != nil { 295 t.Fatalf("cmd.Start() returned error: %v", err) 296 } 297 defer func() { 298 if err := cmd.Process.Kill(); err != nil { 299 t.Errorf("failed to kill testclient[%d]: %v", cmd.Process.Pid, err) 300 } 301 if err := cmd.Wait(); err != nil { 302 if !isErrKilled(err) { 303 t.Errorf("error waiting for testclient: %v", err) 304 } 305 } 306 }() 307 308 s, err := Factory(&fspb.ClientServiceConfig{ 309 Name: "TestSocketService", 310 Config: anypbtest.New(t, &sspb.Config{ 311 ApiProxyPath: socketPath, 312 ResourceMonitoringSampleSize: 2, 313 ResourceMonitoringSamplePeriodSeconds: 1, 314 }), 315 }) 316 if err != nil { 317 t.Fatalf("Factory(...): %v", err) 318 } 319 320 sc := clitesting.MockServiceContext{ 321 OutChan: make(chan *fspb.Message, 5), 322 } 323 if err := s.Start(&sc); err != nil { 324 t.Fatalf("socketservice.Start(...): %v", err) 325 } 326 defer func() { 327 if err := s.Stop(); err != nil { 328 t.Errorf("Error stopping service: %v", err) 329 } 330 }() 331 332 to := time.After(10 * time.Second) 333 select { 334 case <-to: 335 t.Errorf("resource usage report not received") 336 return 337 case m := <-sc.OutChan: 338 if m.MessageType != "ResourceUsage" { 339 t.Errorf("expected ResourceUsage, got %+v", m) 340 } 341 rud := &mpb.ResourceUsageData{} 342 if err := m.Data.UnmarshalTo(rud); err != nil { 343 t.Fatalf("Unable to unmarshal ResourceUsageData: %v", err) 344 } 345 if rud.Pid != int64(cmd.Process.Pid) { 346 t.Errorf("ResourceUsageData.Pid=%d, but test client has pid %d", rud.Pid, cmd.Process.Pid) 347 } 348 if rud.Version != "0.5" { 349 t.Errorf("ResourceUsageData.Version=\"%s\" but expected \"0.5\"", rud.Version) 350 } 351 } 352 } 353 354 func TestStopDoesNotBlock(t *testing.T) { 355 socketPath := SetUpSocketPath(t, "Loopback") 356 stopTimeout := 10 * time.Second 357 s, err := Factory(&fspb.ClientServiceConfig{ 358 Name: "TestSocketService", 359 Config: anypbtest.New(t, &sspb.Config{ 360 ApiProxyPath: socketPath, 361 }), 362 }) 363 if err != nil { 364 t.Fatalf("Factory(...): %v", err) 365 } 366 367 sc := clitesting.MockServiceContext{ 368 OutChan: make(chan *fspb.Message, 5), 369 } 370 if err := s.Start(&sc); err != nil { 371 t.Fatalf("socketservice.Start(...): %v", err) 372 } 373 374 timer := time.AfterFunc(stopTimeout, func() { 375 panic(fmt.Sprintf("Socket service failed to stop after %v.", stopTimeout)) 376 }) 377 defer timer.Stop() 378 379 // Make sure Fleetspeak does not block waiting for the (non-existent) 380 // process on the other side of the socket to connect. 381 err = s.Stop() 382 383 if err != nil { 384 t.Errorf("socketservice.Stop(): %v", err) 385 } 386 } 387 388 func init() { 389 // Hack so that socketservice logs get written to the right place. 390 if d := os.Getenv("TEST_UNDECLARED_OUTPUTS_DIR"); d != "" { 391 os.Setenv("GOOGLE_LOG_DIR", d) 392 } 393 }