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  }