github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/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  }