github.com/vmware/govmomi@v0.51.0/toolbox/service_test.go (about) 1 // © Broadcom. All Rights Reserved. 2 // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. 3 // SPDX-License-Identifier: Apache-2.0 4 5 package toolbox 6 7 import ( 8 "bytes" 9 "context" 10 "errors" 11 "flag" 12 "io" 13 "log" 14 "net" 15 "net/http" 16 "net/http/httptest" 17 "net/url" 18 "os" 19 "sync" 20 "testing" 21 "time" 22 23 "github.com/vmware/govmomi/toolbox/hgfs" 24 "github.com/vmware/govmomi/toolbox/process" 25 "github.com/vmware/govmomi/toolbox/vix" 26 "github.com/vmware/govmomi/vim25/types" 27 ) 28 29 func TestDefaultIP(t *testing.T) { 30 ip := DefaultIP() 31 if ip == "" { 32 t.Error("failed to get a default IP address") 33 } 34 } 35 36 type testRPC struct { 37 cmd string 38 expect string 39 } 40 41 type mockChannelIn struct { 42 t *testing.T 43 service *Service 44 rpc []*testRPC 45 wg sync.WaitGroup 46 start error 47 sendErr int 48 count struct { 49 send int 50 stop int 51 start int 52 } 53 } 54 55 func (c *mockChannelIn) Start() error { 56 c.count.start++ 57 return c.start 58 } 59 60 func (c *mockChannelIn) Stop() error { 61 c.count.stop++ 62 return nil 63 } 64 65 func (c *mockChannelIn) Receive() ([]byte, error) { 66 if len(c.rpc) == 0 { 67 if c.rpc != nil { 68 // All test RPC requests have been consumed 69 c.wg.Done() 70 c.rpc = nil 71 } 72 return nil, io.EOF 73 } 74 75 return []byte(c.rpc[0].cmd), nil 76 } 77 78 func (c *mockChannelIn) Send(buf []byte) error { 79 if c.sendErr > 0 { 80 c.count.send++ 81 if c.count.send%c.sendErr == 0 { 82 c.wg.Done() 83 return errors.New("rpci send error") 84 } 85 } 86 87 if buf == nil { 88 return nil 89 } 90 91 expect := c.rpc[0].expect 92 if string(buf) != expect { 93 c.t.Errorf("expected %q reply for request %q, got: %q", expect, c.rpc[0].cmd, buf) 94 } 95 96 c.rpc = c.rpc[1:] 97 98 return nil 99 } 100 101 // discard rpc out for now 102 type mockChannelOut struct { 103 reply [][]byte 104 start error 105 } 106 107 func (c *mockChannelOut) Start() error { 108 return c.start 109 } 110 111 func (c *mockChannelOut) Stop() error { 112 return nil 113 } 114 115 func (c *mockChannelOut) Receive() ([]byte, error) { 116 if len(c.reply) == 0 { 117 return nil, io.EOF 118 } 119 reply := c.reply[0] 120 c.reply = c.reply[1:] 121 return reply, nil 122 } 123 124 func (c *mockChannelOut) Send(buf []byte) error { 125 if len(buf) == 0 { 126 return io.ErrShortBuffer 127 } 128 return nil 129 } 130 131 func TestServiceRun(t *testing.T) { 132 in := new(mockChannelIn) 133 out := new(mockChannelOut) 134 135 service := NewService(in, out) 136 137 in.rpc = []*testRPC{ 138 {"reset", "OK ATR toolbox"}, 139 {"ping", "OK "}, 140 {"Set_Option synctime 0", "OK "}, 141 {"Capabilities_Register", "OK "}, 142 {"Set_Option broadcastIP 1", "OK "}, 143 } 144 145 in.wg.Add(1) 146 147 // replies to register capabilities 148 for i := 0; i < len(capabilities); i++ { 149 out.reply = append(out.reply, rpciOK) 150 } 151 152 out.reply = append(out.reply, 153 rpciOK, // reply to SendGuestInfo call in Reset() 154 rpciOK, // reply to IP broadcast 155 ) 156 157 in.service = service 158 159 in.t = t 160 161 err := service.Start() 162 if err != nil { 163 t.Fatal(err) 164 } 165 166 in.wg.Wait() 167 168 service.Stop() 169 service.Wait() 170 171 // verify we don't set delay > maxDelay 172 for i := 0; i <= maxDelay+1; i++ { 173 service.backoff() 174 } 175 176 if service.delay != maxDelay { 177 t.Errorf("delay=%d", service.delay) 178 } 179 } 180 181 func TestServiceErrors(t *testing.T) { 182 Trace = true 183 if !testing.Verbose() { 184 // cover TraceChannel but discard output 185 traceLog = io.Discard 186 } 187 188 netInterfaceAddrs = func() ([]net.Addr, error) { 189 return nil, io.EOF 190 } 191 192 in := new(mockChannelIn) 193 out := new(mockChannelOut) 194 195 service := NewService(in, out) 196 197 service.RegisterHandler("Sorry", func([]byte) ([]byte, error) { 198 return nil, errors.New("i am so sorry") 199 }) 200 201 ip := "" 202 service.PrimaryIP = func() string { 203 if ip == "" { 204 ip = "127" 205 } else if ip == "127" { 206 ip = "127.0.0.1" 207 } else if ip == "127.0.0.1" { 208 ip = "" 209 } 210 return ip 211 } 212 213 in.rpc = []*testRPC{ 214 {"Capabilities_Register", "OK "}, 215 {"Set_Option broadcastIP 1", "ERR "}, 216 {"Set_Option broadcastIP 1", "OK "}, 217 {"Set_Option broadcastIP 1", "OK "}, 218 {"NOPE", "Unknown Command"}, 219 {"Sorry", "ERR "}, 220 } 221 222 in.wg.Add(1) 223 224 // replies to register capabilities 225 for i := 0; i < len(capabilities); i++ { 226 out.reply = append(out.reply, rpciERR) 227 } 228 229 foo := []byte("foo") 230 out.reply = append( 231 out.reply, 232 rpciERR, 233 rpciOK, 234 rpciOK, 235 append(rpciOK, foo...), 236 rpciERR, 237 ) 238 239 in.service = service 240 241 in.t = t 242 243 err := service.Start() 244 if err != nil { 245 t.Fatal(err) 246 } 247 248 in.wg.Wait() 249 250 // Done serving RPCs, test ChannelOut errors 251 reply, err := service.out.Request(rpciOK) 252 if err != nil { 253 t.Error(err) 254 } 255 256 if !bytes.Equal(reply, foo) { 257 t.Errorf("reply=%q", foo) 258 } 259 260 _, err = service.out.Request(rpciOK) 261 if err == nil { 262 t.Error("expected error") 263 } 264 265 _, err = service.out.Request(nil) 266 if err == nil { 267 t.Error("expected error") 268 } 269 270 service.Stop() 271 service.Wait() 272 273 // cover service start error paths 274 start := errors.New("fail") 275 276 in.start = start 277 err = service.Start() 278 if err != start { 279 t.Error("expected error") 280 } 281 282 in.start = nil 283 out.start = start 284 err = service.Start() 285 if err != start { 286 t.Error("expected error") 287 } 288 } 289 290 func TestServiceResetChannel(t *testing.T) { 291 in := new(mockChannelIn) 292 out := new(mockChannelOut) 293 294 service := NewService(in, out) 295 296 resetDelay = maxDelay 297 298 fails := 2 299 in.wg.Add(fails) 300 in.sendErr = 10 301 302 err := service.Start() 303 if err != nil { 304 t.Fatal(err) 305 } 306 307 in.wg.Wait() 308 309 service.Stop() 310 service.Wait() 311 312 expect := fails 313 if in.count.start != expect || in.count.stop != expect { 314 t.Errorf("count=%#v", in.count) 315 } 316 } 317 318 var ( 319 testESX = flag.Bool("toolbox.testesx", false, "Test toolbox service against ESX (vmtoolsd must not be running)") 320 testPID = flag.Int64("toolbox.testpid", 0, "PID to return from toolbox start command") 321 testOn = flag.String("toolbox.powerState", "", "Power state of VM prior to starting the test") 322 ) 323 324 // echoHandler for testing hgfs.FileHandler 325 type echoHandler struct{} 326 327 func (e *echoHandler) Stat(u *url.URL) (os.FileInfo, error) { 328 if u.RawQuery == "" { 329 return nil, errors.New("no query") 330 } 331 332 if u.Query().Get("foo") != "bar" { 333 return nil, errors.New("invalid query") 334 } 335 336 return os.Stat(u.Path) 337 } 338 339 func (e *echoHandler) Open(u *url.URL, mode int32) (hgfs.File, error) { 340 _, err := e.Stat(u) 341 if err != nil { 342 return nil, err 343 } 344 345 return os.Open(u.Path) 346 } 347 348 func TestServiceRunESX(t *testing.T) { 349 if *testESX == false { 350 t.SkipNow() 351 } 352 353 Trace = testing.Verbose() 354 355 // A server that echos HTTP requests, for testing toolbox's http.RoundTripper 356 echo := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 357 _ = r.Write(w) 358 })) 359 // Client side can use 'govc guest.getenv' to get the URL w/ random port 360 _ = os.Setenv("TOOLBOX_ECHO_SERVER", echo.URL) 361 362 var wg sync.WaitGroup 363 364 in := NewBackdoorChannelIn() 365 out := NewBackdoorChannelOut() 366 367 service := NewService(in, out) 368 service.Command.FileServer.RegisterFileHandler("echo", new(echoHandler)) 369 370 ping := sync.NewCond(new(sync.Mutex)) 371 372 service.RegisterHandler("ping", func(b []byte) ([]byte, error) { 373 ping.Broadcast() 374 return service.Ping(b) 375 }) 376 377 // assert that reset, ping, Set_Option and Capabilities_Register are called at least once 378 for _, name := range []string{"reset", "ping", "Set_Option", "Capabilities_Register"} { 379 n := name 380 h := service.handlers[name] 381 wg.Add(1) 382 383 service.handlers[name] = func(b []byte) ([]byte, error) { 384 defer wg.Done() 385 386 service.handlers[n] = h // reset 387 388 return h(b) 389 } 390 } 391 392 if *testOn == string(types.VirtualMachinePowerStatePoweredOff) { 393 wg.Add(1) 394 service.Power.PowerOn.Handler = func() error { 395 defer wg.Done() 396 log.Print("power on event") 397 return nil 398 } 399 } else { 400 log.Print("skipping power on test") 401 } 402 403 if *testPID != 0 { 404 service.Command.ProcessStartCommand = func(m *process.Manager, r *vix.StartProgramRequest) (int64, error) { 405 wg.Add(1) 406 defer wg.Done() 407 408 switch r.ProgramPath { 409 case "/bin/date": 410 return *testPID, nil 411 case "sleep": 412 p := process.NewFunc(func(ctx context.Context, arg string) error { 413 d, err := time.ParseDuration(arg) 414 if err != nil { 415 return err 416 } 417 418 select { 419 case <-ctx.Done(): 420 return &process.Error{Err: ctx.Err(), ExitCode: 42} 421 case <-time.After(d): 422 } 423 424 return nil 425 }) 426 return m.Start(r, p) 427 default: 428 return DefaultStartCommand(m, r) 429 } 430 } 431 } 432 433 service.PrimaryIP = func() string { 434 log.Print("broadcasting IP") 435 return DefaultIP() 436 } 437 438 log.Print("starting toolbox service") 439 err := service.Start() 440 if err != nil { 441 log.Fatal(err) 442 } 443 444 wg.Wait() 445 446 // wait for 1 last ping to make sure the final response has reached the client before stopping 447 ping.L.Lock() 448 ping.Wait() 449 ping.L.Unlock() 450 451 service.Stop() 452 service.Wait() 453 }