github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/uniter/resolver/opfactory.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package resolver
     5  
     6  import (
     7  	"github.com/juju/charm/v12/hooks"
     8  	"github.com/juju/errors"
     9  
    10  	"github.com/juju/juju/core/model"
    11  	"github.com/juju/juju/worker/uniter/hook"
    12  	"github.com/juju/juju/worker/uniter/operation"
    13  	"github.com/juju/juju/worker/uniter/remotestate"
    14  )
    15  
    16  // resolverOpFactory wraps an operation.Factory such that skips that affect
    17  // local state will, when committed, update the embedded LocalState struct
    18  // to reflect the change made by the operation.
    19  //
    20  // The wrapped operations embed information specific to the remote state
    21  // snapshot that was used to create the operation. Thus, remote state changes
    22  // observed between the time the operation was created and committed do not
    23  // affect the operation; and the local state change will not prevent further
    24  // operations from being enqueued to achieve the new remote state.
    25  type resolverOpFactory struct {
    26  	operation.Factory
    27  
    28  	LocalState  *LocalState
    29  	RemoteState remotestate.Snapshot
    30  }
    31  
    32  func (s *resolverOpFactory) NewRunHook(info hook.Info) (operation.Operation, error) {
    33  	op, err := s.Factory.NewRunHook(info)
    34  	if err != nil {
    35  		return nil, errors.Trace(err)
    36  	}
    37  	return s.wrapHookOp(op, info), nil
    38  }
    39  
    40  func (s *resolverOpFactory) NewSkipHook(info hook.Info) (operation.Operation, error) {
    41  	op, err := s.Factory.NewSkipHook(info)
    42  	if err != nil {
    43  		return nil, errors.Trace(err)
    44  	}
    45  	return s.wrapHookOp(op, info), nil
    46  }
    47  
    48  func (s *resolverOpFactory) NewNoOpFinishUpgradeSeries() (operation.Operation, error) {
    49  	op, err := s.Factory.NewNoOpFinishUpgradeSeries()
    50  	if err != nil {
    51  		return nil, errors.Trace(err)
    52  	}
    53  	f := func(*operation.State) {
    54  		s.LocalState.UpgradeMachineStatus = model.UpgradeSeriesNotStarted
    55  	}
    56  	op = onCommitWrapper{op, f}
    57  	return op, nil
    58  }
    59  
    60  func (s *resolverOpFactory) NewUpgrade(charmURL string) (operation.Operation, error) {
    61  	op, err := s.Factory.NewUpgrade(charmURL)
    62  	if err != nil {
    63  		return nil, errors.Trace(err)
    64  	}
    65  	return s.wrapUpgradeOp(op, charmURL), nil
    66  }
    67  
    68  func (s *resolverOpFactory) NewRemoteInit(runningStatus remotestate.ContainerRunningStatus) (operation.Operation, error) {
    69  	op, err := s.Factory.NewRemoteInit(runningStatus)
    70  	if err != nil {
    71  		return nil, errors.Trace(err)
    72  	}
    73  	return onCommitWrapper{op, func(*operation.State) {
    74  		s.LocalState.ContainerRunningStatus = &runningStatus
    75  		s.LocalState.OutdatedRemoteCharm = false
    76  	}}, nil
    77  }
    78  
    79  func (s *resolverOpFactory) NewSkipRemoteInit(retry bool) (operation.Operation, error) {
    80  	op, err := s.Factory.NewSkipRemoteInit(retry)
    81  	if err != nil {
    82  		return nil, errors.Trace(err)
    83  	}
    84  	return op, nil
    85  }
    86  
    87  func (s *resolverOpFactory) NewRevertUpgrade(charmURL string) (operation.Operation, error) {
    88  	op, err := s.Factory.NewRevertUpgrade(charmURL)
    89  	if err != nil {
    90  		return nil, errors.Trace(err)
    91  	}
    92  	return s.wrapUpgradeOp(op, charmURL), nil
    93  }
    94  
    95  func (s *resolverOpFactory) NewResolvedUpgrade(charmURL string) (operation.Operation, error) {
    96  	op, err := s.Factory.NewResolvedUpgrade(charmURL)
    97  	if err != nil {
    98  		return nil, errors.Trace(err)
    99  	}
   100  	return s.wrapUpgradeOp(op, charmURL), nil
   101  }
   102  
   103  func (s *resolverOpFactory) NewAction(id string) (operation.Operation, error) {
   104  	op, err := s.Factory.NewAction(id)
   105  	if err != nil {
   106  		return nil, errors.Trace(err)
   107  	}
   108  	f := func(*operation.State) {
   109  		if s.LocalState.CompletedActions == nil {
   110  			s.LocalState.CompletedActions = make(map[string]struct{})
   111  		}
   112  		s.LocalState.CompletedActions[id] = struct{}{}
   113  		s.LocalState.CompletedActions = trimCompletedActions(s.RemoteState.ActionsPending, s.LocalState.CompletedActions)
   114  	}
   115  	op = onCommitWrapper{op, f}
   116  	return op, nil
   117  }
   118  
   119  func trimCompletedActions(pendingActions []string, completedActions map[string]struct{}) map[string]struct{} {
   120  	newCompletedActions := map[string]struct{}{}
   121  	for _, pendingAction := range pendingActions {
   122  		if _, ok := completedActions[pendingAction]; ok {
   123  			newCompletedActions[pendingAction] = struct{}{}
   124  		}
   125  	}
   126  	return newCompletedActions
   127  }
   128  
   129  // NewFailAction is part of the factory interface.
   130  func (s *resolverOpFactory) NewFailAction(actionId string) (operation.Operation, error) {
   131  	op, err := s.Factory.NewFailAction(actionId)
   132  	if err != nil {
   133  		return nil, errors.Trace(err)
   134  	}
   135  	f := func(*operation.State) {
   136  		if s.LocalState.CompletedActions == nil {
   137  			s.LocalState.CompletedActions = make(map[string]struct{})
   138  		}
   139  		s.LocalState.CompletedActions[actionId] = struct{}{}
   140  		s.LocalState.CompletedActions = trimCompletedActions(s.RemoteState.ActionsPending, s.LocalState.CompletedActions)
   141  	}
   142  	op = onCommitWrapper{op, f}
   143  	return op, nil
   144  }
   145  
   146  func (s *resolverOpFactory) wrapUpgradeOp(op operation.Operation, charmURL string) operation.Operation {
   147  	charmModifiedVersion := s.RemoteState.CharmModifiedVersion
   148  	return onCommitWrapper{op, func(*operation.State) {
   149  		s.LocalState.CharmURL = charmURL
   150  		s.LocalState.Restart = true
   151  		s.LocalState.Conflicted = false
   152  		s.LocalState.CharmModifiedVersion = charmModifiedVersion
   153  	}}
   154  }
   155  
   156  func (s *resolverOpFactory) wrapHookOp(op operation.Operation, info hook.Info) operation.Operation {
   157  	switch info.Kind {
   158  	case hooks.PreSeriesUpgrade:
   159  		op = onPrepareWrapper{op, func() {
   160  			//on prepare the local status should be made to reflect
   161  			//that the upgrade process for this united has started.
   162  			s.LocalState.UpgradeMachineStatus = s.RemoteState.UpgradeMachineStatus
   163  		}}
   164  		op = onCommitWrapper{op, func(*operation.State) {
   165  			// on commit, the local status should indicate the hook
   166  			// has completed. The remote status should already
   167  			// indicate completion. We sync the states here.
   168  			s.LocalState.UpgradeMachineStatus = model.UpgradeSeriesPrepareCompleted
   169  		}}
   170  	case hooks.PostSeriesUpgrade:
   171  		op = onPrepareWrapper{op, func() {
   172  			s.LocalState.UpgradeMachineStatus = s.RemoteState.UpgradeMachineStatus
   173  		}}
   174  		op = onCommitWrapper{op, func(*operation.State) {
   175  			s.LocalState.UpgradeMachineStatus = model.UpgradeSeriesCompleted
   176  		}}
   177  	case hooks.ConfigChanged:
   178  		configHash := s.RemoteState.ConfigHash
   179  		trustHash := s.RemoteState.TrustHash
   180  		addressesHash := s.RemoteState.AddressesHash
   181  		op = onCommitWrapper{op, func(state *operation.State) {
   182  			if state != nil {
   183  				// Assign these on the operation.State so it gets
   184  				// written into the state file on disk.
   185  				state.ConfigHash = configHash
   186  				state.TrustHash = trustHash
   187  				state.AddressesHash = addressesHash
   188  			}
   189  		}}
   190  	case hooks.LeaderSettingsChanged:
   191  		v := s.RemoteState.LeaderSettingsVersion
   192  		op = onCommitWrapper{op, func(*operation.State) {
   193  			s.LocalState.LeaderSettingsVersion = v
   194  		}}
   195  	}
   196  
   197  	charmModifiedVersion := s.RemoteState.CharmModifiedVersion
   198  	updateStatusVersion := s.RemoteState.UpdateStatusVersion
   199  	op = onCommitWrapper{op, func(*operation.State) {
   200  		// Update UpdateStatusVersion so that the update-status
   201  		// hook only fires after the next timer.
   202  		s.LocalState.UpdateStatusVersion = updateStatusVersion
   203  		s.LocalState.CharmModifiedVersion = charmModifiedVersion
   204  	}}
   205  
   206  	retryHookVersion := s.RemoteState.RetryHookVersion
   207  	op = onPrepareWrapper{op, func() {
   208  		// Update RetryHookVersion so that we don't attempt to
   209  		// retry a hook more than once between timers signals.
   210  		//
   211  		// We need to do this in Prepare, rather than Commit,
   212  		// in case the retried hook fails.
   213  		s.LocalState.RetryHookVersion = retryHookVersion
   214  	}}
   215  	return op
   216  }
   217  
   218  type onCommitWrapper struct {
   219  	operation.Operation
   220  	onCommit func(*operation.State)
   221  }
   222  
   223  func (op onCommitWrapper) Commit(state operation.State) (*operation.State, error) {
   224  	st, err := op.Operation.Commit(state)
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  	op.onCommit(st)
   229  	return st, nil
   230  }
   231  
   232  // WrappedOperation is part of the WrappedOperation interface.
   233  func (op onCommitWrapper) WrappedOperation() operation.Operation {
   234  	return op.Operation
   235  }
   236  
   237  type onPrepareWrapper struct {
   238  	operation.Operation
   239  	onPrepare func()
   240  }
   241  
   242  func (op onPrepareWrapper) Prepare(state operation.State) (*operation.State, error) {
   243  	st, err := op.Operation.Prepare(state)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  	op.onPrepare()
   248  	return st, nil
   249  }
   250  
   251  // WrappedOperation is part of the WrappedOperation interface.
   252  func (op onPrepareWrapper) WrappedOperation() operation.Operation {
   253  	return op.Operation
   254  }