github.com/vmware/govmomi@v0.43.0/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 }