| /* |
| Copyright (c) 2015 VMware, Inc. All Rights Reserved. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package session |
| |
| import ( |
| "context" |
| "sync" |
| "time" |
| |
| "github.com/vmware/govmomi/vim25/methods" |
| "github.com/vmware/govmomi/vim25/soap" |
| ) |
| |
| type keepAlive struct { |
| sync.Mutex |
| |
| roundTripper soap.RoundTripper |
| idleTime time.Duration |
| notifyRequest chan struct{} |
| notifyStop chan struct{} |
| notifyWaitGroup sync.WaitGroup |
| |
| // keepAlive executes a request in the background with the purpose of |
| // keeping the session active. The response for this request is discarded. |
| keepAlive func(soap.RoundTripper) error |
| } |
| |
| func defaultKeepAlive(roundTripper soap.RoundTripper) error { |
| _, _ = methods.GetCurrentTime(context.Background(), roundTripper) |
| return nil |
| } |
| |
| // KeepAlive wraps the specified soap.RoundTripper and executes a meaningless |
| // API request in the background after the RoundTripper has been idle for the |
| // specified amount of idle time. The keep alive process only starts once a |
| // user logs in and runs until the user logs out again. |
| func KeepAlive(roundTripper soap.RoundTripper, idleTime time.Duration) soap.RoundTripper { |
| return KeepAliveHandler(roundTripper, idleTime, defaultKeepAlive) |
| } |
| |
| // KeepAliveHandler works as KeepAlive() does, but the handler param can decide how to handle errors. |
| // For example, if connectivity to ESX/VC is down long enough for a session to expire, a handler can choose to |
| // Login() on a types.NotAuthenticated error. If handler returns non-nil, the keep alive go routine will be stopped. |
| func KeepAliveHandler(roundTripper soap.RoundTripper, idleTime time.Duration, handler func(soap.RoundTripper) error) soap.RoundTripper { |
| k := &keepAlive{ |
| roundTripper: roundTripper, |
| idleTime: idleTime, |
| notifyRequest: make(chan struct{}), |
| } |
| |
| k.keepAlive = handler |
| |
| return k |
| } |
| |
| func (k *keepAlive) start() { |
| k.Lock() |
| defer k.Unlock() |
| |
| if k.notifyStop != nil { |
| return |
| } |
| |
| // This channel must be closed to terminate idle timer. |
| k.notifyStop = make(chan struct{}) |
| k.notifyWaitGroup.Add(1) |
| |
| go func() { |
| defer k.notifyWaitGroup.Done() |
| |
| for t := time.NewTimer(k.idleTime); ; { |
| select { |
| case <-k.notifyStop: |
| return |
| case <-k.notifyRequest: |
| t.Reset(k.idleTime) |
| case <-t.C: |
| if err := k.keepAlive(k.roundTripper); err != nil { |
| k.stop() |
| } |
| t = time.NewTimer(k.idleTime) |
| } |
| } |
| }() |
| } |
| |
| func (k *keepAlive) stop() { |
| k.Lock() |
| defer k.Unlock() |
| |
| if k.notifyStop != nil { |
| close(k.notifyStop) |
| k.notifyWaitGroup.Wait() |
| k.notifyStop = nil |
| } |
| } |
| |
| func (k *keepAlive) RoundTrip(ctx context.Context, req, res soap.HasFault) error { |
| err := k.roundTripper.RoundTrip(ctx, req, res) |
| if err != nil { |
| return err |
| } |
| // Start ticker on login, stop ticker on logout. |
| switch req.(type) { |
| case *methods.LoginBody, *methods.LoginExtensionByCertificateBody, *methods.LoginByTokenBody: |
| k.start() |
| case *methods.LogoutBody: |
| k.stop() |
| } |
| |
| return nil |
| } |