github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/uniter/container/workload.go (about)

     1  // Copyright 2021 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package container
     5  
     6  import (
     7  	"strconv"
     8  	"sync"
     9  
    10  	"github.com/juju/charm/v12/hooks"
    11  	"github.com/juju/errors"
    12  
    13  	"github.com/juju/juju/worker/uniter/hook"
    14  	"github.com/juju/juju/worker/uniter/operation"
    15  	"github.com/juju/juju/worker/uniter/remotestate"
    16  	"github.com/juju/juju/worker/uniter/resolver"
    17  )
    18  
    19  // Logger defines the logging methods that the package uses.
    20  type Logger interface {
    21  	Tracef(string, ...interface{})
    22  	Debugf(string, ...interface{})
    23  	Errorf(string, ...interface{})
    24  	Warningf(string, ...interface{})
    25  }
    26  
    27  // WorkloadEventType is used to distinguish between each event type triggered
    28  // by the workload.
    29  type WorkloadEventType int
    30  
    31  const (
    32  	// ReadyEvent is triggered when the container/pebble starts up.
    33  	ReadyEvent WorkloadEventType = iota
    34  	CustomNoticeEvent
    35  )
    36  
    37  // WorkloadEvent contains information about the event type and data associated with
    38  // the event.
    39  type WorkloadEvent struct {
    40  	Type         WorkloadEventType
    41  	WorkloadName string
    42  	NoticeID     string
    43  	NoticeType   string
    44  	NoticeKey    string
    45  }
    46  
    47  // WorkloadEventCallback is the type used to callback when an event has been processed.
    48  type WorkloadEventCallback func(err error)
    49  
    50  // WorkloadEvents is an interface providing a means of storing and retrieving
    51  // arguments for running workload hooks.
    52  type WorkloadEvents interface {
    53  	// AddWorkloadEvent adds the given command arguments and response function
    54  	// and returns a unique identifier.
    55  	AddWorkloadEvent(evt WorkloadEvent, cb WorkloadEventCallback) string
    56  
    57  	// GetWorkloadEvent returns the command arguments and response function
    58  	// with the specified ID, as registered in AddWorkloadEvent.
    59  	GetWorkloadEvent(id string) (WorkloadEvent, WorkloadEventCallback, error)
    60  
    61  	// RemoveWorkloadEvent removes the command arguments and response function
    62  	// associated with the specified ID.
    63  	RemoveWorkloadEvent(id string)
    64  
    65  	// Events returns all the currently queued events pending processing.
    66  	// Useful for debugging/testing.
    67  	Events() []WorkloadEvent
    68  
    69  	// EventIDs returns all the ids for the events currently queued.
    70  	EventIDs() []string
    71  }
    72  
    73  type workloadEvents struct {
    74  	mu      sync.Mutex
    75  	nextID  int
    76  	pending map[string]workloadEventItem
    77  }
    78  
    79  type workloadEventItem struct {
    80  	WorkloadEvent
    81  	cb WorkloadEventCallback
    82  }
    83  
    84  // NewWorkloadEvents returns a new workload event queue.
    85  func NewWorkloadEvents() WorkloadEvents {
    86  	return &workloadEvents{pending: make(map[string]workloadEventItem)}
    87  }
    88  
    89  func (c *workloadEvents) AddWorkloadEvent(evt WorkloadEvent, cb WorkloadEventCallback) string {
    90  	c.mu.Lock()
    91  	defer c.mu.Unlock()
    92  	id := strconv.Itoa(c.nextID)
    93  	c.nextID++
    94  	c.pending[id] = workloadEventItem{evt, cb}
    95  	return id
    96  }
    97  
    98  func (c *workloadEvents) RemoveWorkloadEvent(id string) {
    99  	c.mu.Lock()
   100  	delete(c.pending, id)
   101  	c.mu.Unlock()
   102  }
   103  
   104  func (c *workloadEvents) GetWorkloadEvent(id string) (WorkloadEvent, WorkloadEventCallback, error) {
   105  	c.mu.Lock()
   106  	defer c.mu.Unlock()
   107  	item, ok := c.pending[id]
   108  	if !ok {
   109  		return WorkloadEvent{}, nil, errors.NotFoundf("workload event %s", id)
   110  	}
   111  	return item.WorkloadEvent, item.cb, nil
   112  }
   113  
   114  func (c *workloadEvents) Events() []WorkloadEvent {
   115  	c.mu.Lock()
   116  	defer c.mu.Unlock()
   117  	events := make([]WorkloadEvent, 0, len(c.pending))
   118  	for _, v := range c.pending {
   119  		events = append(events, v.WorkloadEvent)
   120  	}
   121  	return events
   122  }
   123  
   124  func (c *workloadEvents) EventIDs() []string {
   125  	c.mu.Lock()
   126  	defer c.mu.Unlock()
   127  	ids := make([]string, 0, len(c.pending))
   128  	for id := range c.pending {
   129  		ids = append(ids, id)
   130  	}
   131  	return ids
   132  }
   133  
   134  type workloadHookResolver struct {
   135  	logger         Logger
   136  	events         WorkloadEvents
   137  	eventCompleted func(id string)
   138  }
   139  
   140  // NewWorkloadHookResolver returns a new resolver with determines which workload related operation
   141  // should be run based on local and remote uniter states.
   142  func NewWorkloadHookResolver(logger Logger, events WorkloadEvents, eventCompleted func(string)) resolver.Resolver {
   143  	return &workloadHookResolver{
   144  		logger:         logger,
   145  		events:         events,
   146  		eventCompleted: eventCompleted,
   147  	}
   148  }
   149  
   150  // NextOp implements the resolver.Resolver interface.
   151  func (r *workloadHookResolver) NextOp(
   152  	localState resolver.LocalState,
   153  	remoteState remotestate.Snapshot,
   154  	opFactory operation.Factory,
   155  ) (operation.Operation, error) {
   156  	noOp := func() (operation.Operation, error) {
   157  		if localState.Kind == operation.RunHook &&
   158  			localState.Hook != nil && localState.Hook.Kind.IsWorkload() {
   159  			// If we are resuming from an unexpected state, skip hook.
   160  			return opFactory.NewSkipHook(*localState.Hook)
   161  		}
   162  		return nil, resolver.ErrNoOperation
   163  	}
   164  
   165  	switch localState.Kind {
   166  	case operation.RunHook:
   167  		if localState.Step != operation.Pending ||
   168  			localState.Hook == nil || !localState.Hook.Kind.IsWorkload() {
   169  			break
   170  		}
   171  		fallthrough
   172  
   173  	case operation.Continue:
   174  		for _, id := range remoteState.WorkloadEvents {
   175  			evt, cb, err := r.events.GetWorkloadEvent(id)
   176  			if errors.IsNotFound(err) {
   177  				continue
   178  			} else if err != nil {
   179  				return nil, errors.Trace(err)
   180  			}
   181  
   182  			done := func(err error) {
   183  				cb(err)
   184  				r.events.RemoveWorkloadEvent(id)
   185  				if r.eventCompleted != nil {
   186  					r.eventCompleted(id)
   187  				}
   188  			}
   189  
   190  			var op operation.Operation
   191  			switch evt.Type {
   192  			case CustomNoticeEvent:
   193  				op, err = opFactory.NewRunHook(hook.Info{
   194  					Kind:         hooks.PebbleCustomNotice,
   195  					WorkloadName: evt.WorkloadName,
   196  					NoticeID:     evt.NoticeID,
   197  					NoticeType:   evt.NoticeType,
   198  					NoticeKey:    evt.NoticeKey,
   199  				})
   200  			case ReadyEvent:
   201  				op, err = opFactory.NewRunHook(hook.Info{
   202  					Kind:         hooks.PebbleReady,
   203  					WorkloadName: evt.WorkloadName,
   204  				})
   205  			default:
   206  				return nil, errors.NotValidf("workload event type %v", evt.Type)
   207  			}
   208  			if err != nil {
   209  				done(err)
   210  				return nil, errors.Trace(err)
   211  			}
   212  			return &errorWrappedOp{
   213  				op,
   214  				done,
   215  			}, nil
   216  		}
   217  		return noOp()
   218  	}
   219  
   220  	return noOp()
   221  }
   222  
   223  // errorWrappedOp calls the handler function when any Prepare, Execute or Commit fail.
   224  // On success handler will be called once with a nil error.
   225  type errorWrappedOp struct {
   226  	operation.Operation
   227  	handler func(error)
   228  }
   229  
   230  // Prepare is part of the Operation interface.
   231  func (op *errorWrappedOp) Prepare(state operation.State) (*operation.State, error) {
   232  	newState, err := op.Operation.Prepare(state)
   233  	if err != nil && err != operation.ErrSkipExecute {
   234  		op.handler(err)
   235  	}
   236  	return newState, err
   237  }
   238  
   239  // Execute is part of the Operation interface.
   240  func (op *errorWrappedOp) Execute(state operation.State) (*operation.State, error) {
   241  	newState, err := op.Operation.Execute(state)
   242  	if err != nil {
   243  		op.handler(err)
   244  	}
   245  	return newState, err
   246  }
   247  
   248  // Commit preserves the recorded hook, and returns a neutral state.
   249  // Commit is part of the Operation interface.
   250  func (op *errorWrappedOp) Commit(state operation.State) (*operation.State, error) {
   251  	newState, err := op.Operation.Commit(state)
   252  	op.handler(err)
   253  	return newState, err
   254  }
   255  
   256  // WrappedOperation is part of the WrappedOperation interface.
   257  func (op *errorWrappedOp) WrappedOperation() operation.Operation {
   258  	return op.Operation
   259  }