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 }