func TestKeepAliveHandler(t *testing.T) { u := test.URL() if u == nil { t.SkipNow() } m1, u1 := newManager(t) m2, u2 := newManager(t) reauth := make(chan bool) // Keep alive handler that will re-login. // Real-world case: connectivity to ESX/VC is down long enough for the session to expire // Test-world case: we call TerminateSession below k := KeepAliveHandler(m2.client.RoundTripper, 2*time.Second, func(roundTripper soap.RoundTripper) error { _, err := methods.GetCurrentTime(context.Background(), roundTripper) if err != nil { if isNotAuthenticated(err) { err = m2.Login(context.Background(), u2.User) if err != nil { if isInvalidLogin(err) { reauth <- false t.Log("failed to re-authenticate, quitting keep alive handler") return err } } else { reauth <- true } } } return nil }) m2.client.RoundTripper = k // Logging in starts keep alive if err := m1.Login(context.Background(), u1.User); err != nil { t.Error(err) } if err := m2.Login(context.Background(), u2.User); err != nil { t.Error(err) } // Terminate session for m2. Note that self terminate fails, so we need 2 sessions for this test. s, err := m2.UserSession(context.Background()) if err != nil { t.Fatal(err) } err = m1.TerminateSession(context.Background(), []string{s.Key}) if err != nil { t.Fatal(err) } _, err = methods.GetCurrentTime(context.Background(), m2.client) if err == nil { t.Error("expected to fail") } // Wait for keepalive to re-authenticate <-reauth _, err = methods.GetCurrentTime(context.Background(), m2.client) if err != nil { t.Fatal(err) } // Clear credentials to test re-authentication failure u2.User = nil s, err = m2.UserSession(context.Background()) if err != nil { t.Fatal(err) } err = m1.TerminateSession(context.Background(), []string{s.Key}) if err != nil { t.Fatal(err) } // Wait for keepalive re-authenticate attempt result := <-reauth _, err = methods.GetCurrentTime(context.Background(), m2.client) if err == nil { t.Error("expected to fail") } if result { t.Errorf("expected reauth to fail") } }
func TestServeHTTP(t *testing.T) { configs := []struct { content types.ServiceContent folder mo.Folder }{ {esx.ServiceContent, esx.RootFolder}, {vc.ServiceContent, vc.RootFolder}, } for _, config := range configs { s := New(NewServiceInstance(config.content, config.folder)) ts := s.NewServer() defer ts.Close() ctx := context.Background() client, err := govmomi.NewClient(ctx, ts.URL, true) if err != nil { t.Fatal(err) } err = client.Login(ctx, nil) if err == nil { t.Fatal("expected invalid login error") } err = client.Login(ctx, url.UserPassword("user", "pass")) if err != nil { t.Fatal(err) } // Testing http client + reflect client clients := []soap.RoundTripper{client, s.client} for _, c := range clients { now, err := methods.GetCurrentTime(ctx, c) if err != nil { t.Fatal(err) } if now.After(time.Now()) { t.Fail() } // test the fail/Fault path _, err = methods.QueryVMotionCompatibility(ctx, c, &types.QueryVMotionCompatibility{}) if err == nil { t.Errorf("expected error") } } err = client.Logout(ctx) if err != nil { t.Error(err) } } }
func TestServeHTTPErrors(t *testing.T) { s := New(NewServiceInstance(esx.ServiceContent, esx.RootFolder)) ts := s.NewServer() defer ts.Close() ctx := context.Background() client, err := govmomi.NewClient(ctx, ts.URL, true) if err != nil { t.Fatal(err) } // unregister type, covering the ServeHTTP UnmarshalBody error path typeFunc = func(name string) (reflect.Type, bool) { return nil, false } _, err = methods.GetCurrentTime(ctx, client) if err == nil { t.Error("expected error") } typeFunc = types.TypeFunc() // reset // cover the does not implement method error path Map.objects[serviceInstance] = &errorNoSuchMethod{} _, err = methods.GetCurrentTime(ctx, client) if err == nil { t.Error("expected error") } // cover the xml encode error path Map.objects[serviceInstance] = &errorMarshal{} _, err = methods.GetCurrentTime(ctx, client) if err == nil { t.Error("expected error") } // cover the no such object path Map.Remove(serviceInstance) _, err = methods.GetCurrentTime(ctx, client) if err == nil { t.Error("expected error") } // verify we properly marshal the fault fault := soap.ToSoapFault(err).VimFault() f, ok := fault.(types.ManagedObjectNotFound) if !ok { t.Fatalf("fault=%#v", fault) } if f.Obj != serviceInstance.Reference() { t.Errorf("obj=%#v", f.Obj) } // cover the method not supported path res, err := http.Get(ts.URL.String()) if err != nil { log.Fatal(err) } if res.StatusCode != http.StatusMethodNotAllowed { t.Errorf("expected status %d, got %s", http.StatusMethodNotAllowed, res.Status) } // cover the ioutil.ReadAll error path s.readAll = func(io.Reader) ([]byte, error) { return nil, io.ErrShortBuffer } res, err = http.Post(ts.URL.String(), "none", nil) if err != nil { log.Fatal(err) } if res.StatusCode != http.StatusBadRequest { t.Errorf("expected status %d, got %s", http.StatusBadRequest, res.Status) } }
func defaultKeepAlive(roundTripper soap.RoundTripper) error { _, _ = methods.GetCurrentTime(context.Background(), roundTripper) return nil }
// Connect establishes the connection for the session but nothing more func (s *Session) Connect(ctx context.Context) (*Session, error) { soapURL, err := soap.ParseURL(s.Service) if soapURL == nil || err != nil { return nil, errors.Errorf("SDK URL (%s) could not be parsed: %s", s.Service, err) } // LoginExtensionByCertificate proxies connections to a virtual host (sdkTunnel:8089) and // Go's http.Transport.DialTLS isn't called when using a proxy. Even if using a known CA, // "sdkTunnel" does not pass Go's tls.VerifyHostname check. // We are moving away from LoginExtensionByCertificate anyhow, so disable thumbprint checks for now. if s.HasCertificate() { s.Insecure = true } // Update the service URL with expanded defaults s.Service = soapURL.String() soapClient := soap.NewClient(soapURL, s.Insecure) var login func(context.Context) error if s.HasCertificate() { cert, err2 := tls.X509KeyPair([]byte(s.ExtensionCert), []byte(s.ExtensionKey)) if err2 != nil { return nil, errors.Errorf("Unable to load X509 key pair(%s,%s): %s", s.ExtensionCert, s.ExtensionKey, err2) } soapClient.SetCertificate(cert) log.Debugf("Logging in via extension %s certificate", s.ExtensionName) login = func(ctx context.Context) error { return s.LoginExtensionByCertificate(ctx, s.ExtensionName, "") } } else { log.Debugf("Logging in via username/password") login = func(ctx context.Context) error { return s.Client.Login(ctx, soapURL.User) } } soapClient.SetThumbprint(soapURL.Host, s.Thumbprint) // TODO: option to set http.Client.Transport.TLSClientConfig.RootCAs vimClient, err := vim25.NewClient(ctx, soapClient) if err != nil { return nil, errors.Errorf("Failed to connect to %s: %s", soapURL.Host, err) } if s.Keepalive != 0 { vimClient.RoundTripper = session.KeepAliveHandler(soapClient, s.Keepalive, func(roundTripper soap.RoundTripper) error { _, err := methods.GetCurrentTime(context.Background(), roundTripper) if err == nil { return nil } log.Warnf("session keepalive error: %s", err) if isNotAuthenticated(err) { if err = login(ctx); err != nil { log.Errorf("session keepalive failed to re-authenticate: %s", err) } else { log.Info("session keepalive re-authenticated") } } return nil }) } // TODO: get rid of govmomi.Client usage, only provides a few helpers we don't need. s.Client = &govmomi.Client{ Client: vimClient, SessionManager: session.NewManager(vimClient), } err = login(ctx) if err != nil { return nil, errors.Errorf("Failed to log in to %s: %s", soapURL.Host, err) } s.Finder = find.NewFinder(s.Vim25(), false) // log high-level environment information s.logEnvironmentInfo() return s, nil }