github.com/vmware/govmomi@v0.37.2/session/keepalive/handler.go (about)

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