github.com/vmware/govmomi@v0.51.0/session/keepalive/handler.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 keepalive
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"net/http"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/vmware/govmomi/vapi/rest"
    15  	"github.com/vmware/govmomi/vim25/methods"
    16  	"github.com/vmware/govmomi/vim25/soap"
    17  )
    18  
    19  // handler contains the generic keep alive settings and logic
    20  type handler struct {
    21  	mu              sync.Mutex
    22  	notifyStop      chan struct{}
    23  	notifyWaitGroup sync.WaitGroup
    24  
    25  	idle time.Duration
    26  	send func() error
    27  }
    28  
    29  // NewHandlerSOAP returns a soap.RoundTripper for use with a vim25.Client
    30  // The idle time specifies the interval in between send() requests. Defaults to 10 minutes.
    31  // The send func is used to keep a session alive. Defaults to calling vim25 GetCurrentTime().
    32  // The keep alive goroutine starts when a Login method is called and runs until Logout is called or send returns an error.
    33  func NewHandlerSOAP(c soap.RoundTripper, idle time.Duration, send func() error) *HandlerSOAP {
    34  	h := &handler{
    35  		idle: idle,
    36  		send: send,
    37  	}
    38  
    39  	if send == nil {
    40  		h.send = func() error {
    41  			return h.keepAliveSOAP(c)
    42  		}
    43  	}
    44  
    45  	return &HandlerSOAP{h, c}
    46  }
    47  
    48  // NewHandlerREST returns an http.RoundTripper for use with a rest.Client
    49  // The idle time specifies the interval in between send() requests. Defaults to 10 minutes.
    50  // The send func is used to keep a session alive. Defaults to calling the rest.Client.Session() method
    51  // The keep alive goroutine starts when a Login method is called and runs until Logout is called or send returns an error.
    52  func NewHandlerREST(c *rest.Client, idle time.Duration, send func() error) *HandlerREST {
    53  	h := &handler{
    54  		idle: idle,
    55  		send: send,
    56  	}
    57  
    58  	if send == nil {
    59  		h.send = func() error {
    60  			return h.keepAliveREST(c)
    61  		}
    62  	}
    63  
    64  	return &HandlerREST{h, c.Transport}
    65  }
    66  
    67  func (h *handler) keepAliveSOAP(rt soap.RoundTripper) error {
    68  	ctx := context.Background()
    69  	_, err := methods.GetCurrentTime(ctx, rt)
    70  	return err
    71  }
    72  
    73  func (h *handler) keepAliveREST(c *rest.Client) error {
    74  	ctx := context.Background()
    75  
    76  	s, err := c.Session(ctx)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	if s != nil {
    81  		return nil
    82  	}
    83  	return errors.New(http.StatusText(http.StatusUnauthorized))
    84  }
    85  
    86  // Start explicitly starts the keep alive go routine.
    87  // For use with session cache.Client, as cached sessions may not involve Login/Logout via RoundTripper.
    88  func (h *handler) Start() {
    89  	h.mu.Lock()
    90  	defer h.mu.Unlock()
    91  
    92  	if h.notifyStop != nil {
    93  		return
    94  	}
    95  
    96  	if h.idle == 0 {
    97  		h.idle = time.Minute * 10
    98  	}
    99  
   100  	// This channel must be closed to terminate idle timer.
   101  	h.notifyStop = make(chan struct{})
   102  	h.notifyWaitGroup.Add(1)
   103  
   104  	go func() {
   105  		for t := time.NewTimer(h.idle); ; {
   106  			select {
   107  			case <-h.notifyStop:
   108  				h.notifyWaitGroup.Done()
   109  				t.Stop()
   110  				return
   111  			case <-t.C:
   112  				if err := h.send(); err != nil {
   113  					h.notifyWaitGroup.Done()
   114  					h.Stop()
   115  					return
   116  				}
   117  				t.Reset(h.idle)
   118  			}
   119  		}
   120  	}()
   121  }
   122  
   123  // Stop explicitly stops the keep alive go routine.
   124  // For use with session cache.Client, as cached sessions may not involve Login/Logout via RoundTripper.
   125  func (h *handler) Stop() {
   126  	h.mu.Lock()
   127  	defer h.mu.Unlock()
   128  
   129  	if h.notifyStop != nil {
   130  		close(h.notifyStop)
   131  		h.notifyWaitGroup.Wait()
   132  		h.notifyStop = nil
   133  	}
   134  }
   135  
   136  // HandlerSOAP is a keep alive implementation for use with vim25.Client
   137  type HandlerSOAP struct {
   138  	*handler
   139  
   140  	roundTripper soap.RoundTripper
   141  }
   142  
   143  // RoundTrip implements soap.RoundTripper
   144  func (h *HandlerSOAP) RoundTrip(ctx context.Context, req, res soap.HasFault) error {
   145  	// Stop ticker on logout.
   146  	switch req.(type) {
   147  	case *methods.LogoutBody:
   148  		h.Stop()
   149  	}
   150  
   151  	err := h.roundTripper.RoundTrip(ctx, req, res)
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	// Start ticker on login.
   157  	switch req.(type) {
   158  	case *methods.LoginBody, *methods.LoginExtensionByCertificateBody, *methods.LoginByTokenBody:
   159  		h.Start()
   160  	}
   161  
   162  	return nil
   163  }
   164  
   165  // HandlerREST is a keep alive implementation for use with rest.Client
   166  type HandlerREST struct {
   167  	*handler
   168  
   169  	roundTripper http.RoundTripper
   170  }
   171  
   172  // RoundTrip implements http.RoundTripper
   173  func (h *HandlerREST) RoundTrip(req *http.Request) (*http.Response, error) {
   174  	if req.URL.Path != "/rest/com/vmware/cis/session" {
   175  		return h.roundTripper.RoundTrip(req)
   176  	}
   177  
   178  	if req.Method == http.MethodDelete { // Logout
   179  		h.Stop()
   180  	}
   181  
   182  	res, err := h.roundTripper.RoundTrip(req)
   183  	if err != nil {
   184  		return res, err
   185  	}
   186  
   187  	if req.Method == http.MethodPost { // Login
   188  		h.Start()
   189  	}
   190  
   191  	return res, err
   192  }