handler.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. /*
  2. Copyright (c) 2020 VMware, Inc. All Rights Reserved.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package keepalive
  14. import (
  15. "context"
  16. "errors"
  17. "net/http"
  18. "sync"
  19. "time"
  20. "github.com/vmware/govmomi/vapi/rest"
  21. "github.com/vmware/govmomi/vim25/methods"
  22. "github.com/vmware/govmomi/vim25/soap"
  23. )
  24. // handler contains the generic keep alive settings and logic
  25. type handler struct {
  26. mu sync.Mutex
  27. notifyStop chan struct{}
  28. notifyWaitGroup sync.WaitGroup
  29. idle time.Duration
  30. send func() error
  31. }
  32. // NewHandlerSOAP returns a soap.RoundTripper for use with a vim25.Client
  33. // The idle time specifies the interval in between send() requests. Defaults to 10 minutes.
  34. // The send func is used to keep a session alive. Defaults to calling vim25 GetCurrentTime().
  35. // The keep alive goroutine starts when a Login method is called and runs until Logout is called or send returns an error.
  36. func NewHandlerSOAP(c soap.RoundTripper, idle time.Duration, send func() error) *HandlerSOAP {
  37. h := &handler{
  38. idle: idle,
  39. send: send,
  40. }
  41. if send == nil {
  42. h.send = func() error {
  43. return h.keepAliveSOAP(c)
  44. }
  45. }
  46. return &HandlerSOAP{h, c}
  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. if send == nil {
  58. h.send = func() error {
  59. return h.keepAliveREST(c)
  60. }
  61. }
  62. return &HandlerREST{h, c.Transport}
  63. }
  64. func (h *handler) keepAliveSOAP(rt soap.RoundTripper) error {
  65. ctx := context.Background()
  66. _, err := methods.GetCurrentTime(ctx, rt)
  67. return err
  68. }
  69. func (h *handler) keepAliveREST(c *rest.Client) error {
  70. ctx := context.Background()
  71. s, err := c.Session(ctx)
  72. if err != nil {
  73. return err
  74. }
  75. if s != nil {
  76. return nil
  77. }
  78. return errors.New(http.StatusText(http.StatusUnauthorized))
  79. }
  80. // Start explicitly starts the keep alive go routine.
  81. // For use with session cache.Client, as cached sessions may not involve Login/Logout via RoundTripper.
  82. func (h *handler) Start() {
  83. h.mu.Lock()
  84. defer h.mu.Unlock()
  85. if h.notifyStop != nil {
  86. return
  87. }
  88. if h.idle == 0 {
  89. h.idle = time.Minute * 10
  90. }
  91. // This channel must be closed to terminate idle timer.
  92. h.notifyStop = make(chan struct{})
  93. h.notifyWaitGroup.Add(1)
  94. go func() {
  95. for t := time.NewTimer(h.idle); ; {
  96. select {
  97. case <-h.notifyStop:
  98. h.notifyWaitGroup.Done()
  99. t.Stop()
  100. return
  101. case <-t.C:
  102. if err := h.send(); err != nil {
  103. h.notifyWaitGroup.Done()
  104. h.Stop()
  105. return
  106. }
  107. t.Reset(h.idle)
  108. }
  109. }
  110. }()
  111. }
  112. // Stop explicitly stops the keep alive go routine.
  113. // For use with session cache.Client, as cached sessions may not involve Login/Logout via RoundTripper.
  114. func (h *handler) Stop() {
  115. h.mu.Lock()
  116. defer h.mu.Unlock()
  117. if h.notifyStop != nil {
  118. close(h.notifyStop)
  119. h.notifyWaitGroup.Wait()
  120. h.notifyStop = nil
  121. }
  122. }
  123. // HandlerSOAP is a keep alive implementation for use with vim25.Client
  124. type HandlerSOAP struct {
  125. *handler
  126. roundTripper soap.RoundTripper
  127. }
  128. // RoundTrip implements soap.RoundTripper
  129. func (h *HandlerSOAP) RoundTrip(ctx context.Context, req, res soap.HasFault) error {
  130. // Stop ticker on logout.
  131. switch req.(type) {
  132. case *methods.LogoutBody:
  133. h.Stop()
  134. }
  135. err := h.roundTripper.RoundTrip(ctx, req, res)
  136. if err != nil {
  137. return err
  138. }
  139. // Start ticker on login.
  140. switch req.(type) {
  141. case *methods.LoginBody, *methods.LoginExtensionByCertificateBody, *methods.LoginByTokenBody:
  142. h.Start()
  143. }
  144. return nil
  145. }
  146. // HandlerREST is a keep alive implementation for use with rest.Client
  147. type HandlerREST struct {
  148. *handler
  149. roundTripper http.RoundTripper
  150. }
  151. // RoundTrip implements http.RoundTripper
  152. func (h *HandlerREST) RoundTrip(req *http.Request) (*http.Response, error) {
  153. if req.URL.Path != "/rest/com/vmware/cis/session" {
  154. return h.roundTripper.RoundTrip(req)
  155. }
  156. if req.Method == http.MethodDelete { // Logout
  157. h.Stop()
  158. }
  159. res, err := h.roundTripper.RoundTrip(req)
  160. if err != nil {
  161. return res, err
  162. }
  163. if req.Method == http.MethodPost { // Login
  164. h.Start()
  165. }
  166. return res, err
  167. }