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 }