Skip to content

Commit

Permalink
Add REST session keep alive support
Browse files Browse the repository at this point in the history
Refactor the SOAP based session.KeepAlive into its own package that
can be used for both SOAP and REST sessions.

- Expose the Start and Stop methods for use with cache.Client (e.g. govc)

- govc: enable KeepAlive in library.{deploy,import} commands

- vcsim: add idle session expiration support

Fixes vmware#1832
Fixes vmware#1839
  • Loading branch information
dougm committed May 8, 2020
1 parent cb26631 commit 7881f54
Show file tree
Hide file tree
Showing 10 changed files with 667 additions and 360 deletions.
12 changes: 12 additions & 0 deletions govc/flags/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (

"github.com/vmware/govmomi/session"
"github.com/vmware/govmomi/session/cache"
"github.com/vmware/govmomi/session/keepalive"
"github.com/vmware/govmomi/vapi/rest"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/soap"
Expand Down Expand Up @@ -392,6 +393,17 @@ func (flag *ClientFlag) RestClient() (*rest.Client, error) {
return flag.restClient, nil
}

func (flag *ClientFlag) KeepAlive(client cache.Client) {
switch c := client.(type) {
case *vim25.Client:
keepalive.NewHandlerSOAP(c, 0, nil).Start()
case *rest.Client:
keepalive.NewHandlerREST(c, 0, nil).Start()
default:
panic(fmt.Sprintf("unsupported client type=%T", client))
}
}

func (flag *ClientFlag) Logout(ctx context.Context) error {
if flag.client != nil {
_ = flag.Session.Logout(ctx, flag.client)
Expand Down
7 changes: 7 additions & 0 deletions govc/library/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,17 @@ func (cmd *deploy) Run(ctx context.Context, f *flag.FlagSet) error {
name = *cmd.Options.Name
}

vc, err := cmd.DatastoreFlag.Client()
if err != nil {
return err
}
cmd.KeepAlive(vc)

c, err := cmd.DatastoreFlag.RestClient()
if err != nil {
return err
}
cmd.KeepAlive(c)

m := vcenter.NewManager(c)

Expand Down
1 change: 1 addition & 0 deletions govc/library/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ func (cmd *item) Run(ctx context.Context, f *flag.FlagSet) error {
if err != nil {
return err
}
cmd.KeepAlive(c)

m := library.NewManager(c)
res, err := flags.ContentLibraryResult(ctx, c, "", f.Arg(0))
Expand Down
111 changes: 10 additions & 101 deletions session/keep_alive.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (c) 2015,2019 VMware, Inc. All Rights Reserved.
Copyright (c) 2015-2020 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -17,115 +17,24 @@ limitations under the License.
package session

import (
"context"
"sync"
"time"

"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/session/keepalive"
"github.com/vmware/govmomi/vim25/soap"
)

type keepAlive struct {
sync.Mutex

roundTripper soap.RoundTripper
idleTime time.Duration
notifyRequest chan struct{}
notifyStop chan struct{}
notifyWaitGroup sync.WaitGroup

// keepAlive executes a request in the background with the purpose of
// keeping the session active. The response for this request is discarded.
keepAlive func(soap.RoundTripper) error
}

func defaultKeepAlive(roundTripper soap.RoundTripper) error {
_, err := methods.GetCurrentTime(context.Background(), roundTripper)
return err
}

// KeepAlive wraps the specified soap.RoundTripper and executes a meaningless
// API request in the background after the RoundTripper has been idle for the
// specified amount of idle time. The keep alive process only starts once a
// user logs in and runs until the user logs out again.
// KeepAlive is a backward compatible wrapper around KeepAliveHandler.
func KeepAlive(roundTripper soap.RoundTripper, idleTime time.Duration) soap.RoundTripper {
return KeepAliveHandler(roundTripper, idleTime, defaultKeepAlive)
return KeepAliveHandler(roundTripper, idleTime, nil)
}

// KeepAliveHandler works as KeepAlive() does, but the handler param can decide how to handle errors.
// For example, if connectivity to ESX/VC is down long enough for a session to expire, a handler can choose to
// Login() on a types.NotAuthenticated error. If handler returns non-nil, the keep alive go routine will be stopped.
// KeepAliveHandler is a backward compatible wrapper around keepalive.NewHandlerSOAP.
func KeepAliveHandler(roundTripper soap.RoundTripper, idleTime time.Duration, handler func(soap.RoundTripper) error) soap.RoundTripper {
k := &keepAlive{
roundTripper: roundTripper,
idleTime: idleTime,
notifyRequest: make(chan struct{}),
}

k.keepAlive = handler

return k
}

func (k *keepAlive) start() {
k.Lock()
defer k.Unlock()

if k.notifyStop != nil {
return
}

// This channel must be closed to terminate idle timer.
k.notifyStop = make(chan struct{})
k.notifyWaitGroup.Add(1)

go func() {
for t := time.NewTimer(k.idleTime); ; {
select {
case <-k.notifyStop:
k.notifyWaitGroup.Done()
return
case <-k.notifyRequest:
t.Reset(k.idleTime)
case <-t.C:
if err := k.keepAlive(k.roundTripper); err != nil {
k.notifyWaitGroup.Done()
k.stop()
return
}
t = time.NewTimer(k.idleTime)
}
var f func() error
if handler != nil {
f = func() error {
return handler(roundTripper)
}
}()
}

func (k *keepAlive) stop() {
k.Lock()
defer k.Unlock()

if k.notifyStop != nil {
close(k.notifyStop)
k.notifyWaitGroup.Wait()
k.notifyStop = nil
}
}

func (k *keepAlive) RoundTrip(ctx context.Context, req, res soap.HasFault) error {
// Stop ticker on logout.
switch req.(type) {
case *methods.LogoutBody:
k.stop()
}

err := k.roundTripper.RoundTrip(ctx, req, res)
if err != nil {
return err
}
// Start ticker on login.
switch req.(type) {
case *methods.LoginBody, *methods.LoginExtensionByCertificateBody, *methods.LoginByTokenBody:
k.start()
}

return nil
return keepalive.NewHandlerSOAP(roundTripper, idleTime, f)
}
Loading

0 comments on commit 7881f54

Please sign in to comment.