github.com/vmware/govmomi@v0.51.0/task/wait.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 task
     6  
     7  import (
     8  	"context"
     9  
    10  	"github.com/vmware/govmomi/property"
    11  	"github.com/vmware/govmomi/vim25/progress"
    12  	"github.com/vmware/govmomi/vim25/types"
    13  )
    14  
    15  type taskProgress struct {
    16  	info *types.TaskInfo
    17  }
    18  
    19  func (t taskProgress) Percentage() float32 {
    20  	return float32(t.info.Progress)
    21  }
    22  
    23  func (t taskProgress) Detail() string {
    24  	return ""
    25  }
    26  
    27  func (t taskProgress) Error() error {
    28  	if t.info.Error != nil {
    29  		return Error{t.info.Error, t.info.Description}
    30  	}
    31  
    32  	return nil
    33  }
    34  
    35  type taskCallback struct {
    36  	ch   chan<- progress.Report
    37  	info *types.TaskInfo
    38  	err  error
    39  }
    40  
    41  func (t *taskCallback) fn(pc []types.PropertyChange) bool {
    42  	for _, c := range pc {
    43  		if c.Name != "info" {
    44  			continue
    45  		}
    46  
    47  		if c.Op != types.PropertyChangeOpAssign {
    48  			continue
    49  		}
    50  
    51  		if c.Val == nil {
    52  			continue
    53  		}
    54  
    55  		ti := c.Val.(types.TaskInfo)
    56  		t.info = &ti
    57  	}
    58  
    59  	// t.info could be nil if pc can't satisfy the rules above
    60  	if t.info == nil {
    61  		return false
    62  	}
    63  
    64  	pr := taskProgress{t.info}
    65  
    66  	// Store copy of error, so Wait() can return it as well.
    67  	t.err = pr.Error()
    68  
    69  	switch t.info.State {
    70  	case types.TaskInfoStateQueued, types.TaskInfoStateRunning:
    71  		if t.ch != nil {
    72  			// Don't care if this is dropped
    73  			select {
    74  			case t.ch <- pr:
    75  			default:
    76  			}
    77  		}
    78  		return false
    79  	case types.TaskInfoStateSuccess, types.TaskInfoStateError:
    80  		if t.ch != nil {
    81  			// Last one must always be delivered
    82  			t.ch <- pr
    83  		}
    84  		return true
    85  	default:
    86  		panic("unknown state: " + t.info.State)
    87  	}
    88  }
    89  
    90  // WaitEx waits for a task to finish with either success or failure. It does so
    91  // by waiting for the "info" property of task managed object to change. The
    92  // function returns when it finds the task in the "success" or "error" state.
    93  // In the former case, the return value is nil. In the latter case the return
    94  // value is an instance of this package's Error struct.
    95  //
    96  // Any error returned while waiting for property changes causes the function to
    97  // return immediately and propagate the error.
    98  //
    99  // If the progress.Sinker argument is specified, any progress updates for the
   100  // task are sent here. The completion percentage is passed through directly.
   101  // The detail for the progress update is set to an empty string. If the task
   102  // finishes in the error state, the error instance is passed through as well.
   103  // Note that this error is the same error that is returned by this function.
   104  func WaitEx(
   105  	ctx context.Context,
   106  	ref types.ManagedObjectReference,
   107  	pc *property.Collector,
   108  	s progress.Sinker) (*types.TaskInfo, error) {
   109  
   110  	cb := &taskCallback{}
   111  
   112  	// Include progress sink if specified
   113  	if s != nil {
   114  		cb.ch = s.Sink()
   115  		defer close(cb.ch)
   116  	}
   117  
   118  	filter := &property.WaitFilter{
   119  		WaitOptions: property.WaitOptions{
   120  			PropagateMissing: true,
   121  		},
   122  	}
   123  	filter.Add(ref, ref.Type, []string{"info"})
   124  
   125  	if err := property.WaitForUpdatesEx(
   126  		ctx,
   127  		pc,
   128  		filter,
   129  		func(updates []types.ObjectUpdate) bool {
   130  			for _, update := range updates {
   131  				// Only look at updates for the expected task object.
   132  				if update.Obj.Value == ref.Value && update.Obj.Type == ref.Type {
   133  					if cb.fn(update.ChangeSet) {
   134  						return true
   135  					}
   136  				}
   137  			}
   138  			return false
   139  		}); err != nil {
   140  
   141  		return nil, err
   142  	}
   143  
   144  	return cb.info, cb.err
   145  }