github.com/vmware/govmomi@v0.51.0/simulator/simulator_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 simulator
     6  
     7  import (
     8  	"context"
     9  	"crypto/tls"
    10  	"errors"
    11  	"io"
    12  	"log"
    13  	"net/http"
    14  	"net/url"
    15  	"reflect"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/vmware/govmomi"
    20  	"github.com/vmware/govmomi/object"
    21  	"github.com/vmware/govmomi/simulator/esx"
    22  	"github.com/vmware/govmomi/simulator/vpx"
    23  	"github.com/vmware/govmomi/vim25"
    24  	"github.com/vmware/govmomi/vim25/methods"
    25  	"github.com/vmware/govmomi/vim25/mo"
    26  	"github.com/vmware/govmomi/vim25/soap"
    27  	"github.com/vmware/govmomi/vim25/types"
    28  )
    29  
    30  func TestUnmarshal(t *testing.T) {
    31  	requests := []struct {
    32  		body any
    33  		data string
    34  	}{
    35  		{
    36  			&types.RetrieveServiceContent{
    37  				This: types.ManagedObjectReference{
    38  					Type: "ServiceInstance", Value: "ServiceInstance",
    39  				},
    40  			},
    41  			`<?xml version="1.0" encoding="UTF-8"?>
    42                           <Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
    43                             <Body>
    44                               <RetrieveServiceContent xmlns="urn:vim25">
    45                                 <_this type="ServiceInstance">ServiceInstance</_this>
    46                               </RetrieveServiceContent>
    47                             </Body>
    48                           </Envelope>`,
    49  		},
    50  		{
    51  			&types.Login{
    52  				This: types.ManagedObjectReference{
    53  					Type:  "SessionManager",
    54  					Value: "SessionManager",
    55  				},
    56  				UserName: "root",
    57  				Password: "secret",
    58  			},
    59  			`<?xml version="1.0" encoding="UTF-8"?>
    60                           <Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
    61                             <Body>
    62                               <Login xmlns="urn:vim25">
    63                                 <_this type="SessionManager">SessionManager</_this>
    64                                 <userName>root</userName>
    65                                 <password>secret</password>
    66                               </Login>
    67                             </Body>
    68                           </Envelope>`,
    69  		},
    70  		{
    71  			&types.RetrieveProperties{
    72  				This: types.ManagedObjectReference{Type: "PropertyCollector", Value: "ha-property-collector"},
    73  				SpecSet: []types.PropertyFilterSpec{
    74  					{
    75  						DynamicData: types.DynamicData{},
    76  						PropSet: []types.PropertySpec{
    77  							{
    78  								DynamicData: types.DynamicData{},
    79  								Type:        "ManagedEntity",
    80  								All:         (*bool)(nil),
    81  								PathSet:     []string{"name", "parent"},
    82  							},
    83  						},
    84  						ObjectSet: []types.ObjectSpec{
    85  							{
    86  								DynamicData: types.DynamicData{},
    87  								Obj:         types.ManagedObjectReference{Type: "Folder", Value: "ha-folder-root"},
    88  								Skip:        types.NewBool(false),
    89  								SelectSet: []types.BaseSelectionSpec{ // test decode of interface
    90  									&types.TraversalSpec{
    91  										SelectionSpec: types.SelectionSpec{
    92  											DynamicData: types.DynamicData{},
    93  											Name:        "traverseParent",
    94  										},
    95  										Type: "ManagedEntity",
    96  										Path: "parent",
    97  										Skip: types.NewBool(false),
    98  										SelectSet: []types.BaseSelectionSpec{
    99  											&types.SelectionSpec{
   100  												DynamicData: types.DynamicData{},
   101  												Name:        "traverseParent",
   102  											},
   103  										},
   104  									},
   105  								},
   106  							},
   107  						},
   108  						ReportMissingObjectsInResults: (*bool)(nil),
   109  					},
   110  				}},
   111  			`<?xml version="1.0" encoding="UTF-8"?>
   112                           <Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
   113                            <Body>
   114                             <RetrieveProperties xmlns="urn:vim25">
   115                              <_this type="PropertyCollector">ha-property-collector</_this>
   116                              <specSet>
   117                               <propSet>
   118                                <type>ManagedEntity</type>
   119                                <pathSet>name</pathSet>
   120                                <pathSet>parent</pathSet>
   121                               </propSet>
   122                               <objectSet>
   123                                <obj type="Folder">ha-folder-root</obj>
   124                                <skip>false</skip>
   125                                <selectSet xmlns:XMLSchema-instance="http://www.w3.org/2001/XMLSchema-instance" XMLSchema-instance:type="TraversalSpec">
   126                                 <name>traverseParent</name>
   127                                 <type>ManagedEntity</type>
   128                                 <path>parent</path>
   129                                 <skip>false</skip>
   130                                 <selectSet XMLSchema-instance:type="SelectionSpec">
   131                                  <name>traverseParent</name>
   132                                 </selectSet>
   133                                </selectSet>
   134                               </objectSet>
   135                              </specSet>
   136                             </RetrieveProperties>
   137                            </Body>
   138                           </Envelope>`,
   139  		},
   140  	}
   141  
   142  	for i, req := range requests {
   143  		method, err := UnmarshalBody(vim25MapType, []byte(req.data))
   144  		if err != nil {
   145  			t.Errorf("failed to decode %d (%s): %s", i, req, err)
   146  		}
   147  		if !reflect.DeepEqual(method.Body, req.body) {
   148  			t.Errorf("malformed body %d (%#v):", i, method.Body)
   149  		}
   150  	}
   151  }
   152  
   153  func TestUnmarshalError(t *testing.T) {
   154  	requests := []string{
   155  		"", // io.EOF
   156  		`<?xml version="1.0" encoding="UTF-8"?>
   157                   <Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
   158                     <Body>
   159                     </MissingEndTag
   160                   </Envelope>`,
   161  		`<?xml version="1.0" encoding="UTF-8"?>
   162                   <Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
   163                     <Body>
   164                       <UnknownType xmlns="urn:vim25">
   165                         <_this type="ServiceInstance">ServiceInstance</_this>
   166                       </UnknownType>
   167                     </Body>
   168                   </Envelope>`,
   169  		`<?xml version="1.0" encoding="UTF-8"?>
   170                   <Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
   171                     <Body>
   172                     <!-- no start tag -->
   173                     </Body>
   174                   </Envelope>`,
   175  		`<?xml version="1.0" encoding="UTF-8"?>
   176                   <Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
   177                     <Body>
   178                       <NoSuchMethod xmlns="urn:vim25">
   179                         <_this type="ServiceInstance">ServiceInstance</_this>
   180                       </NoSuchMethod>
   181                     </Body>
   182                   </Envelope>`,
   183  	}
   184  
   185  	for i, data := range requests {
   186  		if _, err := UnmarshalBody(vim25MapType, []byte(data)); err != nil {
   187  			continue
   188  		}
   189  		t.Errorf("expected %d (%s) to return an error", i, data)
   190  	}
   191  }
   192  
   193  func TestServeHTTP(t *testing.T) {
   194  	configs := []struct {
   195  		content types.ServiceContent
   196  		folder  mo.Folder
   197  	}{
   198  		{esx.ServiceContent, esx.RootFolder},
   199  		{vpx.ServiceContent, vpx.RootFolder},
   200  	}
   201  
   202  	for _, config := range configs {
   203  		ctx := NewContext()
   204  		s := New(NewServiceInstance(ctx, config.content, config.folder))
   205  
   206  		ts := s.NewServer()
   207  		defer ts.Close()
   208  
   209  		u := ts.URL.User
   210  		ts.URL.User = nil
   211  
   212  		client, err := govmomi.NewClient(ctx, ts.URL, true)
   213  		if err != nil {
   214  			t.Fatal(err)
   215  		}
   216  
   217  		err = client.Login(ctx, nil)
   218  		if err == nil {
   219  			t.Fatal("expected invalid login error")
   220  		}
   221  
   222  		err = client.Login(ctx, u)
   223  		if err != nil {
   224  			t.Fatal(err)
   225  		}
   226  
   227  		// Testing http client + reflect client
   228  		clients := []soap.RoundTripper{client, s.client()}
   229  		for _, c := range clients {
   230  			now, err := methods.GetCurrentTime(ctx, c)
   231  			if err != nil {
   232  				t.Fatal(err)
   233  			}
   234  
   235  			if now.After(time.Now()) {
   236  				t.Fail()
   237  			}
   238  
   239  			// test the fail/Fault path
   240  			_, err = methods.QueryVMotionCompatibility(ctx, c, &types.QueryVMotionCompatibility{})
   241  			if err == nil {
   242  				t.Errorf("expected error")
   243  			}
   244  		}
   245  
   246  		err = client.Logout(ctx)
   247  		if err != nil {
   248  			t.Error(err)
   249  		}
   250  	}
   251  }
   252  
   253  func TestServeAbout(t *testing.T) {
   254  	ctx := context.Background()
   255  
   256  	m := VPX()
   257  	m.App = 1
   258  	m.Pod = 1
   259  
   260  	defer m.Remove()
   261  
   262  	err := m.Create()
   263  	if err != nil {
   264  		t.Fatal(err)
   265  	}
   266  
   267  	s := m.Service.NewServer()
   268  	defer s.Close()
   269  
   270  	c, err := govmomi.NewClient(ctx, s.URL, true)
   271  	if err != nil {
   272  		t.Fatal(err)
   273  	}
   274  
   275  	u := *s.URL
   276  	u.Path += "/vimServiceVersions.xml"
   277  	r, err := c.Get(u.String())
   278  	if err != nil {
   279  		t.Fatal(err)
   280  	}
   281  	_ = r.Body.Close()
   282  
   283  	u.Path = "/about"
   284  	r, err = c.Get(u.String())
   285  	if err != nil {
   286  		t.Fatal(err)
   287  	}
   288  	_ = r.Body.Close()
   289  }
   290  
   291  func TestServeHTTPS(t *testing.T) {
   292  	s := New(NewServiceInstance(NewContext(), esx.ServiceContent, esx.RootFolder))
   293  	s.TLS = new(tls.Config)
   294  	ts := s.NewServer()
   295  	defer ts.Close()
   296  
   297  	ts.Config.ErrorLog = log.New(io.Discard, "", 0) // silence benign "TLS handshake error" log messages
   298  
   299  	ctx := context.Background()
   300  
   301  	// insecure=true OK
   302  	_, err := govmomi.NewClient(ctx, ts.URL, true)
   303  	if err != nil {
   304  		t.Fatal(err)
   305  	}
   306  
   307  	// insecure=false should FAIL
   308  	_, err = govmomi.NewClient(ctx, ts.URL, false)
   309  	if err == nil {
   310  		t.Fatal("expected error")
   311  	}
   312  
   313  	uerr, ok := err.(*url.Error)
   314  	if !ok {
   315  		t.Fatalf("err type=%T", err)
   316  	}
   317  
   318  	ok = soap.IsCertificateUntrusted(uerr.Err)
   319  	if !ok {
   320  		t.Fatalf("err type=%T (%s)", uerr.Err, uerr.Err)
   321  	}
   322  
   323  	sinfo := ts.CertificateInfo()
   324  
   325  	// Test thumbprint validation
   326  	sc := soap.NewClient(ts.URL, false)
   327  	// Add host with thumbprint mismatch should fail
   328  	sc.SetThumbprint(ts.URL.Host, "nope")
   329  	_, err = vim25.NewClient(ctx, sc)
   330  	if err == nil {
   331  		t.Error("expected error")
   332  	}
   333  	// Add host with thumbprint match should pass
   334  	sc.SetThumbprint(ts.URL.Host, sinfo.ThumbprintSHA1)
   335  	_, err = vim25.NewClient(ctx, sc)
   336  	if err != nil {
   337  		t.Fatal(err)
   338  	}
   339  
   340  	var pinfo object.HostCertificateInfo
   341  	err = pinfo.FromURL(ts.URL, nil)
   342  	if err != nil {
   343  		t.Fatal(err)
   344  	}
   345  	if pinfo.ThumbprintSHA1 != sinfo.ThumbprintSHA1 {
   346  		t.Error("thumbprint mismatch")
   347  	}
   348  
   349  	// Test custom RootCAs list
   350  	sc = soap.NewClient(ts.URL, false)
   351  	caFile, err := ts.CertificateFile()
   352  	if err != nil {
   353  		t.Fatal(err)
   354  	}
   355  	if err = sc.SetRootCAs(caFile); err != nil {
   356  		t.Fatal(err)
   357  	}
   358  
   359  	_, err = vim25.NewClient(ctx, sc)
   360  	if err != nil {
   361  		t.Fatal(err)
   362  	}
   363  }
   364  
   365  type errorMarshal struct {
   366  	mo.ServiceInstance
   367  }
   368  
   369  func (*errorMarshal) Fault() *soap.Fault {
   370  	return nil
   371  }
   372  
   373  func (*errorMarshal) MarshalText() ([]byte, error) {
   374  	return nil, errors.New("time has stopped")
   375  }
   376  
   377  func (h *errorMarshal) CurrentTime(types.AnyType) soap.HasFault {
   378  	return h
   379  }
   380  
   381  func (s *errorMarshal) ServiceContent() types.ServiceContent {
   382  	return s.Content
   383  }
   384  
   385  type errorNoSuchMethod struct {
   386  	mo.ServiceInstance
   387  }
   388  
   389  func (s *errorNoSuchMethod) ServiceContent() types.ServiceContent {
   390  	return s.Content
   391  }
   392  
   393  func TestServeHTTPErrors(t *testing.T) {
   394  	ctx := NewContext()
   395  	s := New(NewServiceInstance(ctx, esx.ServiceContent, esx.RootFolder))
   396  
   397  	ts := s.NewServer()
   398  	defer ts.Close()
   399  
   400  	client, err := govmomi.NewClient(ctx, ts.URL, true)
   401  	if err != nil {
   402  		t.Fatal(err)
   403  	}
   404  
   405  	// test response to unimplemented method
   406  	req := &types.QueryMemoryOverhead{This: esx.HostSystem.Reference()}
   407  	_, err = methods.QueryMemoryOverhead(ctx, client.Client, req)
   408  	if _, ok := soap.ToSoapFault(err).VimFault().(types.MethodNotFound); !ok {
   409  		t.Error("expected MethodNotFound fault")
   410  	}
   411  
   412  	si := mo.ServiceInstance{Content: ctx.Map.content()}
   413  
   414  	// cover the does not implement method error path
   415  	ctx.Map.objects[vim25.ServiceInstance] = &errorNoSuchMethod{
   416  		ServiceInstance: si,
   417  	}
   418  	_, err = methods.GetCurrentTime(ctx, client)
   419  	if err == nil {
   420  		t.Error("expected error")
   421  	}
   422  
   423  	// cover the xml encode error path
   424  	ctx.Map.objects[vim25.ServiceInstance] = &errorMarshal{
   425  		ServiceInstance: si,
   426  	}
   427  	_, err = methods.GetCurrentTime(ctx, client)
   428  	if err == nil {
   429  		t.Error("expected error")
   430  	}
   431  
   432  	// cover the no such object path
   433  	treq := types.CurrentTime{
   434  		This: types.ManagedObjectReference{
   435  			Type:  "ServiceInstance",
   436  			Value: "invalid",
   437  		},
   438  	}
   439  	_, err = methods.CurrentTime(ctx, client.Client, &treq)
   440  	if err == nil {
   441  		t.Error("expected error")
   442  	}
   443  
   444  	// verify we properly marshal the fault
   445  	fault := soap.ToSoapFault(err).VimFault()
   446  	f, ok := fault.(types.ManagedObjectNotFound)
   447  	if !ok {
   448  		t.Fatalf("fault=%#v", fault)
   449  	}
   450  	if f.Obj != treq.This {
   451  		t.Errorf("obj=%#v", f.Obj)
   452  	}
   453  
   454  	// cover the method not supported path
   455  	res, err := http.Get(ts.URL.String())
   456  	if err != nil {
   457  		log.Fatal(err)
   458  	}
   459  
   460  	if res.StatusCode != http.StatusMethodNotAllowed {
   461  		t.Errorf("expected status %d, got %s", http.StatusMethodNotAllowed, res.Status)
   462  	}
   463  
   464  	// cover the ioutil.ReadAll error path
   465  	s.readAll = func(io.Reader) ([]byte, error) {
   466  		return nil, io.ErrShortBuffer
   467  	}
   468  	res, err = http.Post(ts.URL.String(), "none", nil)
   469  	if err != nil {
   470  		log.Fatal(err)
   471  	}
   472  
   473  	if res.StatusCode != http.StatusBadRequest {
   474  		t.Errorf("expected status %d, got %s", http.StatusBadRequest, res.Status)
   475  	}
   476  }
   477  
   478  func TestDelay(t *testing.T) {
   479  	m := ESX()
   480  	defer m.Remove()
   481  
   482  	err := m.Create()
   483  	if err != nil {
   484  		t.Fatal(err)
   485  	}
   486  
   487  	s := m.Service.NewServer()
   488  	defer s.Close()
   489  
   490  	client, err := govmomi.NewClient(context.Background(), s.URL, true)
   491  	if err != nil {
   492  		t.Fatal(err)
   493  	}
   494  
   495  	simvm := m.Map().Any("VirtualMachine").(*VirtualMachine)
   496  	vm := object.NewVirtualMachine(client.Client, simvm.Reference())
   497  
   498  	m.Service.delay.Delay = 1000
   499  
   500  	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
   501  	defer cancel()
   502  
   503  	_, err = vm.PowerOff(ctx)
   504  	if err == nil {
   505  		t.Fatalf("expected timeout initiating task")
   506  	}
   507  	// give time for task to finish
   508  	time.Sleep(1000 * time.Millisecond)
   509  }
   510  
   511  func TestDelayTask(t *testing.T) {
   512  	m := ESX()
   513  	defer m.Remove()
   514  
   515  	err := m.Create()
   516  	if err != nil {
   517  		t.Fatal(err)
   518  	}
   519  
   520  	s := m.Service.NewServer()
   521  	defer s.Close()
   522  
   523  	client, err := govmomi.NewClient(context.Background(), s.URL, true)
   524  	if err != nil {
   525  		t.Fatal(err)
   526  	}
   527  
   528  	simvm := m.Map().Any("VirtualMachine").(*VirtualMachine)
   529  	vm := object.NewVirtualMachine(client.Client, simvm.Reference())
   530  
   531  	TaskDelay.Delay = 1000
   532  	defer func() { TaskDelay.Delay = 0 }()
   533  
   534  	task, err := vm.PowerOff(context.Background())
   535  	if err != nil {
   536  		t.Fatal(err)
   537  	}
   538  
   539  	timeoutCtx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
   540  	defer cancel()
   541  	err = task.Wait(timeoutCtx)
   542  	if err == nil {
   543  		t.Fatal("expected timeout waiting for task")
   544  	}
   545  	// make sure to wait for task, or else it can run while other tests run!
   546  	task.Wait(context.Background())
   547  }