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 }