Example #1
1
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")
	}
}
Example #2
0
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)
		}
	}
}
Example #3
0
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)
	}
}
Example #4
0
func defaultKeepAlive(roundTripper soap.RoundTripper) error {
	_, _ = methods.GetCurrentTime(context.Background(), roundTripper)
	return nil
}
Example #5
0
// 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
}