github.com/vmware/govmomi@v0.43.0/simulator/task.go (about)

     1  /*
     2  Copyright (c) 2017-2024 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 simulator
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/vmware/govmomi/vim25/methods"
    26  	"github.com/vmware/govmomi/vim25/mo"
    27  	"github.com/vmware/govmomi/vim25/soap"
    28  	"github.com/vmware/govmomi/vim25/types"
    29  )
    30  
    31  const vTaskSuffix = "_Task" // vmomi suffix
    32  const sTaskSuffix = "Task"  // simulator suffix (avoiding golint warning)
    33  
    34  // TaskDelay applies to all tasks.
    35  // Names for DelayConfig.MethodDelay will differ for task and api delays. API
    36  // level names often look like PowerOff_Task, whereas the task name is simply
    37  // PowerOff.
    38  var TaskDelay = DelayConfig{}
    39  
    40  type Task struct {
    41  	mo.Task
    42  
    43  	ctx     *Context
    44  	Execute func(*Task) (types.AnyType, types.BaseMethodFault)
    45  }
    46  
    47  func NewTask(runner TaskRunner) *Task {
    48  	ref := runner.Reference()
    49  	name := reflect.TypeOf(runner).Elem().Name()
    50  	name = strings.Replace(name, "VM", "Vm", 1) // "VM" for the type to make go-lint happy, but "Vm" for the vmodl ID
    51  	return CreateTask(ref, name, runner.Run)
    52  }
    53  
    54  func CreateTask(e mo.Reference, name string, run func(*Task) (types.AnyType, types.BaseMethodFault)) *Task {
    55  	ref := e.Reference()
    56  	id := name
    57  
    58  	if strings.HasSuffix(id, sTaskSuffix) {
    59  		id = id[:len(id)-len(sTaskSuffix)]
    60  		name = id + vTaskSuffix
    61  	}
    62  
    63  	task := &Task{
    64  		Execute: run,
    65  	}
    66  
    67  	task.Self = Map.newReference(task)
    68  	task.Info.Key = task.Self.Value
    69  	task.Info.Task = task.Self
    70  	task.Info.Name = ucFirst(name)
    71  	task.Info.DescriptionId = fmt.Sprintf("%s.%s", ref.Type, id)
    72  	task.Info.Entity = &ref
    73  	task.Info.EntityName = ref.Value
    74  	task.Info.Reason = &types.TaskReasonUser{UserName: "vcsim"} // TODO: Context.Session.User
    75  	task.Info.QueueTime = time.Now()
    76  	task.Info.State = types.TaskInfoStateQueued
    77  
    78  	Map.Put(task)
    79  
    80  	return task
    81  }
    82  
    83  type TaskRunner interface {
    84  	mo.Reference
    85  
    86  	Run(*Task) (types.AnyType, types.BaseMethodFault)
    87  }
    88  
    89  // taskReference is a helper struct so we can call AcquireLock in Run()
    90  type taskReference struct {
    91  	Self types.ManagedObjectReference
    92  }
    93  
    94  func (tr *taskReference) Reference() types.ManagedObjectReference {
    95  	return tr.Self
    96  }
    97  
    98  func (t *Task) Run(ctx *Context) types.ManagedObjectReference {
    99  	t.ctx = ctx
   100  	// alias the global Map to reduce data races in tests that reset the
   101  	// global Map variable.
   102  	vimMap := Map
   103  
   104  	vimMap.AtomicUpdate(t.ctx, t, []types.PropertyChange{
   105  		{Name: "info.startTime", Val: time.Now()},
   106  		{Name: "info.state", Val: types.TaskInfoStateRunning},
   107  	})
   108  
   109  	tr := &taskReference{
   110  		Self: *t.Info.Entity,
   111  	}
   112  
   113  	// in most cases, the caller already holds this lock, and we would like
   114  	// the lock to be held across the "hand off" to the async goroutine.
   115  	// however, with a TaskDelay, PropertyCollector (for example) cannot read
   116  	// any object properties while the lock is held.
   117  	handoff := true
   118  	if v, ok := TaskDelay.MethodDelay["LockHandoff"]; ok {
   119  		handoff = v != 0
   120  	}
   121  	var unlock func()
   122  	if handoff {
   123  		unlock = vimMap.AcquireLock(ctx, tr)
   124  	}
   125  	go func() {
   126  		TaskDelay.delay(t.Info.Name)
   127  		if !handoff {
   128  			unlock = vimMap.AcquireLock(ctx, tr)
   129  		}
   130  		res, err := t.Execute(t)
   131  		unlock()
   132  
   133  		state := types.TaskInfoStateSuccess
   134  		var fault interface{}
   135  		if err != nil {
   136  			state = types.TaskInfoStateError
   137  			fault = types.LocalizedMethodFault{
   138  				Fault:            err,
   139  				LocalizedMessage: fmt.Sprintf("%T", err),
   140  			}
   141  		}
   142  
   143  		vimMap.AtomicUpdate(t.ctx, t, []types.PropertyChange{
   144  			{Name: "info.completeTime", Val: time.Now()},
   145  			{Name: "info.state", Val: state},
   146  			{Name: "info.result", Val: res},
   147  			{Name: "info.error", Val: fault},
   148  		})
   149  	}()
   150  
   151  	return t.Self
   152  }
   153  
   154  // RunBlocking() should only be used when an async simulator task needs to wait
   155  // on another async simulator task.
   156  // It polls for task completion to avoid the need to set up a PropertyCollector.
   157  func (t *Task) RunBlocking(ctx *Context) {
   158  	_ = t.Run(ctx)
   159  	t.Wait()
   160  }
   161  
   162  // Wait blocks until the task is complete.
   163  func (t *Task) Wait() {
   164  	// we do NOT want to share our lock with the tasks's context, because
   165  	// the goroutine that executes the task will use ctx to update the
   166  	// state (among other things).
   167  	isolatedLockingContext := &Context{}
   168  
   169  	isRunning := func() bool {
   170  		var running bool
   171  		Map.WithLock(isolatedLockingContext, t, func() {
   172  			switch t.Info.State {
   173  			case types.TaskInfoStateSuccess, types.TaskInfoStateError:
   174  				running = false
   175  			default:
   176  				running = true
   177  			}
   178  		})
   179  		return running
   180  	}
   181  
   182  	for isRunning() {
   183  		time.Sleep(10 * time.Millisecond)
   184  	}
   185  }
   186  
   187  func (t *Task) isDone() bool {
   188  	return t.Info.State == types.TaskInfoStateError || t.Info.State == types.TaskInfoStateSuccess
   189  }
   190  
   191  func (t *Task) SetTaskState(ctx *Context, req *types.SetTaskState) soap.HasFault {
   192  	body := new(methods.SetTaskStateBody)
   193  
   194  	if t.isDone() {
   195  		body.Fault_ = Fault("", new(types.InvalidState))
   196  		return body
   197  	}
   198  
   199  	changes := []types.PropertyChange{
   200  		{Name: "info.state", Val: req.State},
   201  	}
   202  
   203  	switch req.State {
   204  	case types.TaskInfoStateRunning:
   205  		changes = append(changes, types.PropertyChange{Name: "info.startTime", Val: time.Now()})
   206  	case types.TaskInfoStateError, types.TaskInfoStateSuccess:
   207  		changes = append(changes, types.PropertyChange{Name: "info.completeTime", Val: time.Now()})
   208  
   209  		if req.Fault != nil {
   210  			changes = append(changes, types.PropertyChange{Name: "info.error", Val: req.Fault})
   211  		}
   212  		if req.Result != nil {
   213  			changes = append(changes, types.PropertyChange{Name: "info.result", Val: req.Result})
   214  		}
   215  	}
   216  
   217  	ctx.Map.Update(t, changes)
   218  
   219  	body.Res = new(types.SetTaskStateResponse)
   220  	return body
   221  }
   222  
   223  func (t *Task) SetTaskDescription(ctx *Context, req *types.SetTaskDescription) soap.HasFault {
   224  	body := new(methods.SetTaskDescriptionBody)
   225  
   226  	if t.isDone() {
   227  		body.Fault_ = Fault("", new(types.InvalidState))
   228  		return body
   229  	}
   230  
   231  	ctx.Map.Update(t, []types.PropertyChange{{Name: "info.description", Val: req.Description}})
   232  
   233  	body.Res = new(types.SetTaskDescriptionResponse)
   234  	return body
   235  }
   236  
   237  func (t *Task) UpdateProgress(ctx *Context, req *types.UpdateProgress) soap.HasFault {
   238  	body := new(methods.UpdateProgressBody)
   239  
   240  	if t.Info.State != types.TaskInfoStateRunning {
   241  		body.Fault_ = Fault("", new(types.InvalidState))
   242  		return body
   243  	}
   244  
   245  	ctx.Map.Update(t, []types.PropertyChange{{Name: "info.progress", Val: req.PercentDone}})
   246  
   247  	body.Res = new(types.UpdateProgressResponse)
   248  	return body
   249  }
   250  
   251  func (t *Task) CancelTask(ctx *Context, req *types.CancelTask) soap.HasFault {
   252  	body := new(methods.CancelTaskBody)
   253  
   254  	if t.isDone() {
   255  		body.Fault_ = Fault("", new(types.InvalidState))
   256  		return body
   257  	}
   258  
   259  	changes := []types.PropertyChange{
   260  		{Name: "info.cancelled", Val: true},
   261  		{Name: "info.completeTime", Val: time.Now()},
   262  		{Name: "info.state", Val: types.TaskInfoStateError},
   263  		{Name: "info.error", Val: &types.LocalizedMethodFault{
   264  			Fault:            &types.RequestCanceled{},
   265  			LocalizedMessage: "The task was canceled by a user",
   266  		}},
   267  	}
   268  
   269  	ctx.Map.Update(t, changes)
   270  
   271  	body.Res = new(types.CancelTaskResponse)
   272  	return body
   273  }