gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/state/task.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package state 21 22 import ( 23 "encoding/json" 24 "fmt" 25 "time" 26 27 "github.com/snapcore/snapd/logger" 28 ) 29 30 type progress struct { 31 Label string `json:"label"` 32 Done int `json:"done"` 33 Total int `json:"total"` 34 } 35 36 // Task represents an individual operation to be performed 37 // for accomplishing one or more state changes. 38 // 39 // See Change for more details. 40 type Task struct { 41 state *State 42 id string 43 kind string 44 summary string 45 status Status 46 clean bool 47 progress *progress 48 data customData 49 waitTasks []string 50 haltTasks []string 51 lanes []int 52 log []string 53 change string 54 55 spawnTime time.Time 56 readyTime time.Time 57 58 // TODO: add: 59 // {,Un}DoingRetries - number of retries 60 // Retry{,Un}DoingTimes - time spend to figure out a retry is needed 61 doingTime time.Duration 62 undoingTime time.Duration 63 64 atTime time.Time 65 } 66 67 func newTask(state *State, id, kind, summary string) *Task { 68 return &Task{ 69 state: state, 70 id: id, 71 kind: kind, 72 summary: summary, 73 data: make(customData), 74 75 spawnTime: timeNow(), 76 } 77 } 78 79 type marshalledTask struct { 80 ID string `json:"id"` 81 Kind string `json:"kind"` 82 Summary string `json:"summary"` 83 Status Status `json:"status"` 84 Clean bool `json:"clean,omitempty"` 85 Progress *progress `json:"progress,omitempty"` 86 Data map[string]*json.RawMessage `json:"data,omitempty"` 87 WaitTasks []string `json:"wait-tasks,omitempty"` 88 HaltTasks []string `json:"halt-tasks,omitempty"` 89 Lanes []int `json:"lanes,omitempty"` 90 Log []string `json:"log,omitempty"` 91 Change string `json:"change"` 92 93 SpawnTime time.Time `json:"spawn-time"` 94 ReadyTime *time.Time `json:"ready-time,omitempty"` 95 96 DoingTime time.Duration `json:"doing-time,omitempty"` 97 UndoingTime time.Duration `json:"undoing-time,omitempty"` 98 99 AtTime *time.Time `json:"at-time,omitempty"` 100 } 101 102 // MarshalJSON makes Task a json.Marshaller 103 func (t *Task) MarshalJSON() ([]byte, error) { 104 t.state.reading() 105 var readyTime *time.Time 106 if !t.readyTime.IsZero() { 107 readyTime = &t.readyTime 108 } 109 var atTime *time.Time 110 if !t.atTime.IsZero() { 111 atTime = &t.atTime 112 } 113 return json.Marshal(marshalledTask{ 114 ID: t.id, 115 Kind: t.kind, 116 Summary: t.summary, 117 Status: t.status, 118 Clean: t.clean, 119 Progress: t.progress, 120 Data: t.data, 121 WaitTasks: t.waitTasks, 122 HaltTasks: t.haltTasks, 123 Lanes: t.lanes, 124 Log: t.log, 125 Change: t.change, 126 127 SpawnTime: t.spawnTime, 128 ReadyTime: readyTime, 129 130 DoingTime: t.doingTime, 131 UndoingTime: t.undoingTime, 132 133 AtTime: atTime, 134 }) 135 } 136 137 // UnmarshalJSON makes Task a json.Unmarshaller 138 func (t *Task) UnmarshalJSON(data []byte) error { 139 if t.state != nil { 140 t.state.writing() 141 } 142 var unmarshalled marshalledTask 143 err := json.Unmarshal(data, &unmarshalled) 144 if err != nil { 145 return err 146 } 147 t.id = unmarshalled.ID 148 t.kind = unmarshalled.Kind 149 t.summary = unmarshalled.Summary 150 t.status = unmarshalled.Status 151 t.clean = unmarshalled.Clean 152 t.progress = unmarshalled.Progress 153 custData := unmarshalled.Data 154 if custData == nil { 155 custData = make(customData) 156 } 157 t.data = custData 158 t.waitTasks = unmarshalled.WaitTasks 159 t.haltTasks = unmarshalled.HaltTasks 160 t.lanes = unmarshalled.Lanes 161 t.log = unmarshalled.Log 162 t.change = unmarshalled.Change 163 t.spawnTime = unmarshalled.SpawnTime 164 if unmarshalled.ReadyTime != nil { 165 t.readyTime = *unmarshalled.ReadyTime 166 } 167 if unmarshalled.AtTime != nil { 168 t.atTime = *unmarshalled.AtTime 169 } 170 t.doingTime = unmarshalled.DoingTime 171 t.undoingTime = unmarshalled.UndoingTime 172 return nil 173 } 174 175 // ID returns the individual random key for this task. 176 func (t *Task) ID() string { 177 return t.id 178 } 179 180 // Kind returns the nature of this task for managers to know how to handle it. 181 func (t *Task) Kind() string { 182 return t.kind 183 } 184 185 // Summary returns a summary describing what the task is about. 186 func (t *Task) Summary() string { 187 return t.summary 188 } 189 190 // Status returns the current task status. 191 func (t *Task) Status() Status { 192 t.state.reading() 193 if t.status == DefaultStatus { 194 return DoStatus 195 } 196 return t.status 197 } 198 199 // SetStatus sets the task status, overriding the default behavior (see Status method). 200 func (t *Task) SetStatus(new Status) { 201 t.state.writing() 202 old := t.status 203 t.status = new 204 if !old.Ready() && new.Ready() { 205 t.readyTime = timeNow() 206 } 207 chg := t.Change() 208 if chg != nil { 209 chg.taskStatusChanged(t, old, new) 210 } 211 } 212 213 // IsClean returns whether the task has been cleaned. See SetClean. 214 func (t *Task) IsClean() bool { 215 t.state.reading() 216 return t.clean 217 } 218 219 // SetClean flags the task as clean after any left over data was removed. 220 // 221 // Cleaning a task must only be done after the change is ready. 222 func (t *Task) SetClean() { 223 t.state.writing() 224 if t.clean { 225 return 226 } 227 t.clean = true 228 chg := t.Change() 229 if chg != nil { 230 chg.taskCleanChanged() 231 } 232 } 233 234 // State returns the system State 235 func (t *Task) State() *State { 236 return t.state 237 } 238 239 // Change returns the change the task is registered with. 240 func (t *Task) Change() *Change { 241 t.state.reading() 242 return t.state.changes[t.change] 243 } 244 245 // Progress returns the current progress for the task. 246 // If progress is not explicitly set, it returns 247 // (0, 1) if the status is DoStatus and (1, 1) otherwise. 248 func (t *Task) Progress() (label string, done, total int) { 249 t.state.reading() 250 if t.progress == nil { 251 if t.Status() == DoStatus { 252 return "", 0, 1 253 } 254 return "", 1, 1 255 } 256 return t.progress.Label, t.progress.Done, t.progress.Total 257 } 258 259 // SetProgress sets the task progress to cur out of total steps. 260 func (t *Task) SetProgress(label string, done, total int) { 261 // Only mark state for checkpointing if progress is final. 262 if total > 0 && done == total { 263 t.state.writing() 264 } else { 265 t.state.reading() 266 } 267 if total <= 0 || done > total { 268 // Doing math wrong is easy. Be conservative. 269 t.progress = nil 270 } else { 271 t.progress = &progress{Label: label, Done: done, Total: total} 272 } 273 } 274 275 // SpawnTime returns the time when the change was created. 276 func (t *Task) SpawnTime() time.Time { 277 t.state.reading() 278 return t.spawnTime 279 } 280 281 // ReadyTime returns the time when the change became ready. 282 func (t *Task) ReadyTime() time.Time { 283 t.state.reading() 284 return t.readyTime 285 } 286 287 // AtTime returns the time at which the task is scheduled to run. A zero time means no special schedule, i.e. run as soon as prerequisites are met. 288 func (t *Task) AtTime() time.Time { 289 t.state.reading() 290 return t.atTime 291 } 292 293 func (t *Task) accumulateDoingTime(duration time.Duration) { 294 t.state.writing() 295 t.doingTime += duration 296 } 297 298 func (t *Task) accumulateUndoingTime(duration time.Duration) { 299 t.state.writing() 300 t.undoingTime += duration 301 } 302 303 func (t *Task) DoingTime() time.Duration { 304 t.state.reading() 305 return t.doingTime 306 } 307 308 func (t *Task) UndoingTime() time.Duration { 309 t.state.reading() 310 return t.undoingTime 311 } 312 313 const ( 314 // Messages logged in tasks are guaranteed to use the time formatted 315 // per RFC3339 plus the following strings as a prefix, so these may 316 // be handled programmatically and parsed or stripped for presentation. 317 LogInfo = "INFO" 318 LogError = "ERROR" 319 ) 320 321 var timeNow = time.Now 322 323 func MockTime(now time.Time) (restore func()) { 324 timeNow = func() time.Time { return now } 325 return func() { timeNow = time.Now } 326 } 327 328 func (t *Task) addLog(kind, format string, args []interface{}) { 329 if len(t.log) > 9 { 330 copy(t.log, t.log[len(t.log)-9:]) 331 t.log = t.log[:9] 332 } 333 334 tstr := timeNow().Format(time.RFC3339) 335 msg := fmt.Sprintf(tstr+" "+kind+" "+format, args...) 336 t.log = append(t.log, msg) 337 logger.Debugf(msg) 338 } 339 340 // Log returns the most recent messages logged into the task. 341 // 342 // Only the most recent entries logged are returned, potentially with 343 // different behavior for different task statuses. How many entries 344 // are returned is an implementation detail and may change over time. 345 // 346 // Messages are prefixed with one of the known message kinds. 347 // See details about LogInfo and LogError. 348 // 349 // The returned slice should not be read from without the 350 // state lock held, and should not be written to. 351 func (t *Task) Log() []string { 352 t.state.reading() 353 return t.log 354 } 355 356 // Logf logs information about the progress of the task. 357 func (t *Task) Logf(format string, args ...interface{}) { 358 t.state.writing() 359 t.addLog(LogInfo, format, args) 360 } 361 362 // Errorf logs error information about the progress of the task. 363 func (t *Task) Errorf(format string, args ...interface{}) { 364 t.state.writing() 365 t.addLog(LogError, format, args) 366 } 367 368 // Set associates value with key for future consulting by managers. 369 // The provided value must properly marshal and unmarshal with encoding/json. 370 func (t *Task) Set(key string, value interface{}) { 371 t.state.writing() 372 t.data.set(key, value) 373 } 374 375 // Get unmarshals the stored value associated with the provided key 376 // into the value parameter. 377 func (t *Task) Get(key string, value interface{}) error { 378 t.state.reading() 379 return t.data.get(key, value) 380 } 381 382 // Has returns whether the provided key has an associated value. 383 func (t *Task) Has(key string) bool { 384 t.state.reading() 385 return t.data.has(key) 386 } 387 388 // Clear disassociates the value from key. 389 func (t *Task) Clear(key string) { 390 t.state.writing() 391 delete(t.data, key) 392 } 393 394 func addOnce(set []string, s string) []string { 395 for _, cur := range set { 396 if s == cur { 397 return set 398 } 399 } 400 return append(set, s) 401 } 402 403 // WaitFor registers another task as a requirement for t to make progress. 404 func (t *Task) WaitFor(another *Task) { 405 t.state.writing() 406 t.waitTasks = addOnce(t.waitTasks, another.id) 407 another.haltTasks = addOnce(another.haltTasks, t.id) 408 } 409 410 // WaitAll registers all the tasks in the set as a requirement for t 411 // to make progress. 412 func (t *Task) WaitAll(ts *TaskSet) { 413 for _, req := range ts.tasks { 414 t.WaitFor(req) 415 } 416 } 417 418 // WaitTasks returns the list of tasks registered for t to wait for. 419 func (t *Task) WaitTasks() []*Task { 420 t.state.reading() 421 return t.state.tasksIn(t.waitTasks) 422 } 423 424 // HaltTasks returns the list of tasks registered to wait for t. 425 func (t *Task) HaltTasks() []*Task { 426 t.state.reading() 427 return t.state.tasksIn(t.haltTasks) 428 } 429 430 // NumHaltTasks returns the number of tasks registered to wait for t. 431 func (t *Task) NumHaltTasks() int { 432 return len(t.haltTasks) 433 } 434 435 // Lanes returns the lanes the task is in. 436 func (t *Task) Lanes() []int { 437 t.state.reading() 438 if len(t.lanes) == 0 { 439 return []int{0} 440 } 441 return t.lanes 442 } 443 444 // JoinLane registers the task in the provided lane. Tasks in different lanes 445 // abort independently on errors. See Change.AbortLane for details. 446 func (t *Task) JoinLane(lane int) { 447 t.state.writing() 448 t.lanes = append(t.lanes, lane) 449 } 450 451 // At schedules the task, if it's not ready, to happen no earlier than when, if when is the zero time any previous special scheduling is suppressed. 452 func (t *Task) At(when time.Time) { 453 t.state.writing() 454 iszero := when.IsZero() 455 if t.Status().Ready() && !iszero { 456 return 457 } 458 t.atTime = when 459 if !iszero { 460 d := when.Sub(timeNow()) 461 if d < 0 { 462 d = 0 463 } 464 t.state.EnsureBefore(d) 465 } 466 } 467 468 // TaskSetEdge designates tasks inside a TaskSet for outside reference. 469 // 470 // This is useful to give tasks inside TaskSets a special meaning. It 471 // is used to mark e.g. the last task used for downloading a snap. 472 type TaskSetEdge string 473 474 // A TaskSet holds a set of tasks. 475 type TaskSet struct { 476 tasks []*Task 477 478 edges map[TaskSetEdge]*Task 479 } 480 481 // NewTaskSet returns a new TaskSet comprising the given tasks. 482 func NewTaskSet(tasks ...*Task) *TaskSet { 483 // we init all members of TaskSet so that `go vet` will not complain 484 return &TaskSet{tasks, nil} 485 } 486 487 // Edge returns the task marked with the given edge name. 488 func (ts TaskSet) Edge(e TaskSetEdge) (*Task, error) { 489 t, ok := ts.edges[e] 490 if !ok { 491 return nil, fmt.Errorf("internal error: missing %q edge in task set", e) 492 } 493 return t, nil 494 } 495 496 // WaitFor registers a task as a requirement for the tasks in the set 497 // to make progress. 498 func (ts TaskSet) WaitFor(another *Task) { 499 for _, t := range ts.tasks { 500 t.WaitFor(another) 501 } 502 } 503 504 // WaitAll registers all the tasks in the argument set as requirements for ts 505 // the target set to make progress. 506 func (ts *TaskSet) WaitAll(anotherTs *TaskSet) { 507 for _, req := range anotherTs.tasks { 508 ts.WaitFor(req) 509 } 510 } 511 512 // AddTask adds the task to the task set. 513 func (ts *TaskSet) AddTask(task *Task) { 514 for _, t := range ts.tasks { 515 if t == task { 516 return 517 } 518 } 519 ts.tasks = append(ts.tasks, task) 520 } 521 522 // MarkEdge marks the given task as a specific edge. Any pre-existing 523 // edge mark will be overridden. 524 func (ts *TaskSet) MarkEdge(task *Task, edge TaskSetEdge) { 525 if task == nil { 526 panic(fmt.Sprintf("cannot set edge %q with nil task", edge)) 527 } 528 if ts.edges == nil { 529 ts.edges = make(map[TaskSetEdge]*Task) 530 } 531 ts.edges[edge] = task 532 } 533 534 // AddAll adds all the tasks in the argument set to the target set ts. 535 func (ts *TaskSet) AddAll(anotherTs *TaskSet) { 536 for _, t := range anotherTs.tasks { 537 ts.AddTask(t) 538 } 539 } 540 541 // AddAllWithEdges adds all the tasks in the argument set to the target 542 // set ts and also adds all TaskSetEdges. Duplicated TaskSetEdges are 543 // an error. 544 func (ts *TaskSet) AddAllWithEdges(anotherTs *TaskSet) error { 545 ts.AddAll(anotherTs) 546 for edge, t := range anotherTs.edges { 547 if tex, ok := ts.edges[edge]; ok && t != tex { 548 return fmt.Errorf("cannot add taskset: duplicated edge %q", edge) 549 } 550 ts.MarkEdge(t, edge) 551 } 552 return nil 553 } 554 555 // JoinLane adds all the tasks in the current taskset to the given lane. 556 func (ts *TaskSet) JoinLane(lane int) { 557 for _, t := range ts.tasks { 558 t.JoinLane(lane) 559 } 560 } 561 562 // Tasks returns the tasks in the task set. 563 func (ts TaskSet) Tasks() []*Task { 564 // Return something mutable, just like every other Tasks method. 565 return append([]*Task(nil), ts.tasks...) 566 }