github.com/vmware/govmomi@v0.43.0/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 }