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 }