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