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 }