github.com/vmware/govmomi@v0.37.2/simulator/simulator_test.go (about)

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