github.com/vmware/govmomi@v0.51.0/simulator/task_manager.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 simulator
     6  
     7  import (
     8  	"container/list"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/vmware/govmomi/simulator/esx"
    13  	"github.com/vmware/govmomi/simulator/vpx"
    14  	"github.com/vmware/govmomi/vim25/methods"
    15  	"github.com/vmware/govmomi/vim25/mo"
    16  	"github.com/vmware/govmomi/vim25/soap"
    17  	"github.com/vmware/govmomi/vim25/types"
    18  )
    19  
    20  var recentTaskMax = 200 // the VC limit
    21  
    22  type TaskManager struct {
    23  	mo.TaskManager
    24  	sync.Mutex
    25  
    26  	history *history
    27  }
    28  
    29  func (m *TaskManager) init(r *Registry) {
    30  	if len(m.Description.MethodInfo) == 0 {
    31  		if r.IsVPX() {
    32  			m.Description = vpx.Description
    33  		} else {
    34  			m.Description = esx.Description
    35  		}
    36  	}
    37  
    38  	if m.MaxCollector == 0 {
    39  		// In real VC this default can be changed via OptionManager "task.maxCollectors"
    40  		m.MaxCollector = maxCollectors
    41  	}
    42  
    43  	m.history = newHistory()
    44  
    45  	r.AddHandler(m)
    46  }
    47  
    48  func recentTask(recent []types.ManagedObjectReference, ref types.ManagedObjectReference) []types.PropertyChange {
    49  	// TODO: tasks completed > 10m ago should be removed
    50  	recent = append(recent, ref)
    51  	if len(recent) > recentTaskMax {
    52  		recent = recent[1:]
    53  	}
    54  	return []types.PropertyChange{{Name: "recentTask", Val: recent}}
    55  }
    56  
    57  func (m *TaskManager) PutObject(ctx *Context, obj mo.Reference) {
    58  	task, ok := obj.(*Task)
    59  	if !ok {
    60  		return
    61  	}
    62  
    63  	// propagate new Tasks to:
    64  	// - TaskManager.RecentTask
    65  	// - TaskHistoryCollector instances, if Filter matches
    66  	// - $MO.RecentTask
    67  	m.Lock()
    68  	ctx.Update(m, recentTask(m.RecentTask, task.Self))
    69  
    70  	pushHistory(m.history.page, task)
    71  
    72  	for _, hc := range m.history.collectors {
    73  		c := hc.(*TaskHistoryCollector)
    74  		ctx.WithLock(c, func() {
    75  			if c.taskMatches(ctx, &task.Info) {
    76  				pushHistory(c.page, task)
    77  				ctx.Update(c, []types.PropertyChange{{Name: "latestPage", Val: c.GetLatestPage()}})
    78  			}
    79  		})
    80  	}
    81  	m.Unlock()
    82  
    83  	entity := ctx.Map.Get(*task.Info.Entity)
    84  	if e, ok := entity.(mo.Entity); ok {
    85  		ctx.Update(entity, recentTask(e.Entity().RecentTask, task.Self))
    86  	}
    87  }
    88  
    89  func taskStateChanged(pc []types.PropertyChange) bool {
    90  	for i := range pc {
    91  		if pc[i].Name == "info.state" {
    92  			return true
    93  		}
    94  	}
    95  	return false
    96  }
    97  
    98  func (m *TaskManager) UpdateObject(ctx *Context, obj mo.Reference, pc []types.PropertyChange) {
    99  	task, ok := obj.(*mo.Task)
   100  	if !ok {
   101  		return
   102  	}
   103  
   104  	if !taskStateChanged(pc) {
   105  		// real vCenter only updates latestPage when Tasks are created (see PutObject above) and
   106  		// if Task.Info.State changes.
   107  		// Changes to Task.Info.{Description,Progress} does not update lastestPage.
   108  		return
   109  	}
   110  
   111  	m.Lock()
   112  	for _, hc := range m.history.collectors {
   113  		c := hc.(*TaskHistoryCollector)
   114  		ctx.WithLock(c, func() {
   115  			if c.hasTask(ctx, &task.Info) {
   116  				ctx.Update(c, []types.PropertyChange{{Name: "latestPage", Val: c.GetLatestPage()}})
   117  			}
   118  		})
   119  	}
   120  	m.Unlock()
   121  }
   122  
   123  func (*TaskManager) RemoveObject(*Context, types.ManagedObjectReference) {}
   124  
   125  func validTaskID(ctx *Context, taskID string) bool {
   126  	m := ctx.Map.ExtensionManager()
   127  
   128  	for _, x := range m.ExtensionList {
   129  		for _, task := range x.TaskList {
   130  			if task.TaskID == taskID {
   131  				return true
   132  			}
   133  		}
   134  	}
   135  
   136  	return false
   137  }
   138  
   139  func (m *TaskManager) CreateTask(ctx *Context, req *types.CreateTask) soap.HasFault {
   140  	body := &methods.CreateTaskBody{}
   141  
   142  	if !validTaskID(ctx, req.TaskTypeId) {
   143  		body.Fault_ = Fault("", &types.InvalidArgument{
   144  			InvalidProperty: "taskType",
   145  		})
   146  		return body
   147  	}
   148  
   149  	task := &Task{}
   150  
   151  	task.Self = ctx.Map.newReference(task)
   152  	task.Info.Key = task.Self.Value
   153  	task.Info.Task = task.Self
   154  	task.Info.DescriptionId = req.TaskTypeId
   155  	task.Info.Cancelable = req.Cancelable
   156  	task.Info.Entity = &req.Obj
   157  	task.Info.EntityName = req.Obj.Value
   158  	task.Info.Reason = &types.TaskReasonUser{UserName: ctx.Session.UserName}
   159  	task.Info.QueueTime = time.Now()
   160  	task.Info.State = types.TaskInfoStateQueued
   161  
   162  	body.Res = &types.CreateTaskResponse{Returnval: task.Info}
   163  
   164  	go ctx.Map.Put(task)
   165  
   166  	return body
   167  }
   168  
   169  type TaskHistoryCollector struct {
   170  	mo.TaskHistoryCollector
   171  
   172  	*HistoryCollector
   173  }
   174  
   175  func (m *TaskManager) createCollector(ctx *Context, req *types.CreateCollectorForTasks) (*TaskHistoryCollector, *soap.Fault) {
   176  	if len(m.history.collectors) >= int(m.MaxCollector) {
   177  		return nil, Fault("Too many task collectors to create", new(types.InvalidState))
   178  	}
   179  
   180  	collector := &TaskHistoryCollector{
   181  		HistoryCollector: newHistoryCollector(ctx, m.history, defaultPageSize),
   182  	}
   183  	collector.Filter = req.Filter
   184  
   185  	return collector, nil
   186  }
   187  
   188  // taskFilterChildren returns true if a child of self is the task entity.
   189  func taskFilterChildren(ctx *Context, root types.ManagedObjectReference, task *types.TaskInfo) bool {
   190  	seen := false
   191  
   192  	var match func(types.ManagedObjectReference)
   193  
   194  	match = func(child types.ManagedObjectReference) {
   195  		if child == *task.Entity {
   196  			seen = true
   197  			return
   198  		}
   199  
   200  		walk(ctx.Map.Get(child), match)
   201  	}
   202  
   203  	walk(ctx.Map.Get(root), match)
   204  
   205  	return seen
   206  }
   207  
   208  // entityMatches returns true if the spec Entity filter matches the task entity.
   209  func (c *TaskHistoryCollector) entityMatches(ctx *Context, task *types.TaskInfo, spec types.TaskFilterSpec) bool {
   210  	e := spec.Entity
   211  	if e == nil {
   212  		return true
   213  	}
   214  
   215  	isSelf := *task.Entity == e.Entity
   216  
   217  	switch e.Recursion {
   218  	case types.TaskFilterSpecRecursionOptionSelf:
   219  		return isSelf
   220  	case types.TaskFilterSpecRecursionOptionChildren:
   221  		return taskFilterChildren(ctx, e.Entity, task)
   222  	case types.TaskFilterSpecRecursionOptionAll:
   223  		return isSelf || taskFilterChildren(ctx, e.Entity, task)
   224  	}
   225  
   226  	return false
   227  }
   228  
   229  func (c *TaskHistoryCollector) stateMatches(_ *Context, task *types.TaskInfo, spec types.TaskFilterSpec) bool {
   230  	if len(spec.State) == 0 {
   231  		return true
   232  	}
   233  
   234  	for _, state := range spec.State {
   235  		if task.State == state {
   236  			return true
   237  
   238  		}
   239  	}
   240  
   241  	return false
   242  }
   243  
   244  func (c *TaskHistoryCollector) timeMatches(_ *Context, task *types.TaskInfo, spec types.TaskFilterSpec) bool {
   245  	if spec.Time == nil {
   246  		return true
   247  	}
   248  
   249  	created := task.QueueTime
   250  
   251  	if begin := spec.Time.BeginTime; begin != nil {
   252  		if created.Before(*begin) {
   253  			return false
   254  		}
   255  	}
   256  
   257  	if end := spec.Time.EndTime; end != nil {
   258  		if created.After(*end) {
   259  			return false
   260  		}
   261  	}
   262  
   263  	return true
   264  }
   265  
   266  // taskMatches returns true one of the filters matches the task.
   267  func (c *TaskHistoryCollector) taskMatches(ctx *Context, task *types.TaskInfo) bool {
   268  	spec := c.Filter.(types.TaskFilterSpec)
   269  
   270  	matchers := []func(*Context, *types.TaskInfo, types.TaskFilterSpec) bool{
   271  		c.stateMatches,
   272  		c.timeMatches,
   273  		c.entityMatches,
   274  		// TODO: spec.UserName, etc
   275  	}
   276  
   277  	for _, match := range matchers {
   278  		if !match(ctx, task, spec) {
   279  			return false
   280  		}
   281  	}
   282  
   283  	return true
   284  }
   285  
   286  func (c *TaskHistoryCollector) hasTask(_ *Context, task *types.TaskInfo) bool {
   287  	for e := c.page.Front(); e != nil; e = e.Next() {
   288  		if e.Value.(*Task).Info.Key == task.Key {
   289  			return true
   290  		}
   291  	}
   292  	return false
   293  }
   294  
   295  // fillPage copies the manager's latest tasks into the collector's page with Filter applied.
   296  func (m *TaskManager) fillPage(ctx *Context, c *TaskHistoryCollector) {
   297  	m.history.Lock()
   298  	defer m.history.Unlock()
   299  
   300  	for e := m.history.page.Front(); e != nil; e = e.Next() {
   301  		task := e.Value.(*Task)
   302  
   303  		if c.taskMatches(ctx, &task.Info) {
   304  			pushHistory(c.page, task)
   305  		}
   306  	}
   307  }
   308  
   309  func (m *TaskManager) CreateCollectorForTasks(ctx *Context, req *types.CreateCollectorForTasks) soap.HasFault {
   310  	body := new(methods.CreateCollectorForTasksBody)
   311  
   312  	if ctx.Map.IsESX() {
   313  		body.Fault_ = Fault("", new(types.NotSupported))
   314  		return body
   315  	}
   316  
   317  	collector, err := m.createCollector(ctx, req)
   318  	if err != nil {
   319  		body.Fault_ = err
   320  		return body
   321  	}
   322  
   323  	collector.fill = func(x *Context) { m.fillPage(x, collector) }
   324  	collector.fill(ctx)
   325  
   326  	body.Res = &types.CreateCollectorForTasksResponse{
   327  		Returnval: m.history.add(ctx, collector),
   328  	}
   329  
   330  	return body
   331  }
   332  
   333  func (c *TaskHistoryCollector) ReadNextTasks(ctx *Context, req *types.ReadNextTasks) soap.HasFault {
   334  	body := new(methods.ReadNextTasksBody)
   335  	if req.MaxCount <= 0 {
   336  		body.Fault_ = Fault("", errInvalidArgMaxCount)
   337  		return body
   338  	}
   339  	body.Res = new(types.ReadNextTasksResponse)
   340  
   341  	c.next(req.MaxCount, func(e *list.Element) {
   342  		body.Res.Returnval = append(body.Res.Returnval, e.Value.(*Task).Info)
   343  	})
   344  
   345  	return body
   346  }
   347  
   348  func (c *TaskHistoryCollector) ReadPreviousTasks(ctx *Context, req *types.ReadPreviousTasks) soap.HasFault {
   349  	body := new(methods.ReadPreviousTasksBody)
   350  	if req.MaxCount <= 0 {
   351  		body.Fault_ = Fault("", errInvalidArgMaxCount)
   352  		return body
   353  	}
   354  	body.Res = new(types.ReadPreviousTasksResponse)
   355  
   356  	c.prev(req.MaxCount, func(e *list.Element) {
   357  		body.Res.Returnval = append(body.Res.Returnval, e.Value.(*Task).Info)
   358  	})
   359  
   360  	return body
   361  }
   362  
   363  func (c *TaskHistoryCollector) GetLatestPage() []types.TaskInfo {
   364  	var latestPage []types.TaskInfo
   365  
   366  	e := c.page.Back()
   367  	for i := 0; i < c.size; i++ {
   368  		if e == nil {
   369  			break
   370  		}
   371  		latestPage = append(latestPage, e.Value.(*Task).Info)
   372  		e = e.Prev()
   373  	}
   374  
   375  	return latestPage
   376  }
   377  
   378  func (c *TaskHistoryCollector) Get() mo.Reference {
   379  	clone := *c
   380  
   381  	clone.LatestPage = clone.GetLatestPage()
   382  
   383  	return &clone
   384  }