github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/uniter/resolver_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package uniter_test
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/testing"
     9  	jc "github.com/juju/testing/checkers"
    10  	gc "gopkg.in/check.v1"
    11  	"gopkg.in/juju/charm.v6"
    12  	"gopkg.in/juju/charm.v6/hooks"
    13  	"gopkg.in/juju/names.v2"
    14  
    15  	"github.com/juju/juju/apiserver/params"
    16  	"github.com/juju/juju/core/lxdprofile"
    17  	"github.com/juju/juju/core/model"
    18  	"github.com/juju/juju/worker/uniter"
    19  	uniteractions "github.com/juju/juju/worker/uniter/actions"
    20  	"github.com/juju/juju/worker/uniter/hook"
    21  	"github.com/juju/juju/worker/uniter/leadership"
    22  	"github.com/juju/juju/worker/uniter/operation"
    23  	"github.com/juju/juju/worker/uniter/relation"
    24  	"github.com/juju/juju/worker/uniter/remotestate"
    25  	"github.com/juju/juju/worker/uniter/resolver"
    26  	"github.com/juju/juju/worker/uniter/storage"
    27  	"github.com/juju/juju/worker/uniter/upgradecharmprofile"
    28  	"github.com/juju/juju/worker/uniter/upgradeseries"
    29  )
    30  
    31  type resolverSuite struct {
    32  	stub                 testing.Stub
    33  	charmModifiedVersion int
    34  	charmURL             *charm.URL
    35  	remoteState          remotestate.Snapshot
    36  	opFactory            operation.Factory
    37  	resolver             resolver.Resolver
    38  	resolverConfig       uniter.ResolverConfig
    39  	modelType            model.ModelType
    40  
    41  	clearResolved   func() error
    42  	reportHookError func(hook.Info) error
    43  }
    44  
    45  type caasResolverSuite struct {
    46  	resolverSuite
    47  }
    48  
    49  type iaasResolverSuite struct {
    50  	resolverSuite
    51  }
    52  
    53  var _ = gc.Suite(&caasResolverSuite{})
    54  var _ = gc.Suite(&iaasResolverSuite{})
    55  
    56  func (s *caasResolverSuite) SetUpTest(c *gc.C) {
    57  	s.modelType = model.CAAS
    58  	s.resolverSuite.SetUpTest(c)
    59  }
    60  
    61  func (s *iaasResolverSuite) SetUpTest(c *gc.C) {
    62  	s.modelType = model.IAAS
    63  	s.resolverSuite.SetUpTest(c)
    64  	s.resolver = uniter.NewUniterResolver(s.resolverConfig)
    65  }
    66  
    67  func (s *resolverSuite) SetUpTest(c *gc.C) {
    68  	s.stub = testing.Stub{}
    69  	s.charmURL = charm.MustParseURL("cs:precise/mysql-2")
    70  	s.remoteState = remotestate.Snapshot{
    71  		CharmModifiedVersion: s.charmModifiedVersion,
    72  		CharmURL:             s.charmURL,
    73  	}
    74  	s.opFactory = operation.NewFactory(operation.FactoryParams{})
    75  
    76  	attachments, err := storage.NewAttachments(&dummyStorageAccessor{}, names.NewUnitTag("u/0"), c.MkDir(), nil)
    77  	c.Assert(err, jc.ErrorIsNil)
    78  
    79  	s.clearResolved = func() error {
    80  		return errors.New("unexpected resolved")
    81  	}
    82  
    83  	s.reportHookError = func(hook.Info) error {
    84  		return errors.New("unexpected report hook error")
    85  	}
    86  
    87  	s.resolverConfig = uniter.ResolverConfig{
    88  		ClearResolved:       func() error { return s.clearResolved() },
    89  		ReportHookError:     func(info hook.Info) error { return s.reportHookError(info) },
    90  		StartRetryHookTimer: func() { s.stub.AddCall("StartRetryHookTimer") },
    91  		StopRetryHookTimer:  func() { s.stub.AddCall("StopRetryHookTimer") },
    92  		ShouldRetryHooks:    true,
    93  		UpgradeSeries:       upgradeseries.NewResolver(),
    94  		UpgradeCharmProfile: upgradecharmprofile.NewResolver(),
    95  		Leadership:          leadership.NewResolver(),
    96  		Actions:             uniteractions.NewResolver(),
    97  		Relations:           relation.NewRelationsResolver(&dummyRelations{}),
    98  		Storage:             storage.NewResolver(attachments, s.modelType),
    99  		Commands:            nopResolver{},
   100  		ModelType:           s.modelType,
   101  	}
   102  
   103  	s.resolver = uniter.NewUniterResolver(s.resolverConfig)
   104  }
   105  
   106  // TestStartedNotInstalled tests whether the Started flag overrides the
   107  // Installed flag being unset, in the event of an unexpected inconsistency in
   108  // local state.
   109  func (s *resolverSuite) TestStartedNotInstalled(c *gc.C) {
   110  	localState := resolver.LocalState{
   111  		CharmModifiedVersion: s.charmModifiedVersion,
   112  		CharmURL:             s.charmURL,
   113  		State: operation.State{
   114  			Kind:      operation.Continue,
   115  			Installed: false,
   116  			Started:   true,
   117  		},
   118  	}
   119  	_, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   120  	c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   121  }
   122  
   123  // TestNotStartedNotInstalled tests whether the next operation for an
   124  // uninstalled local state is an install hook operation.
   125  func (s *resolverSuite) TestNotStartedNotInstalled(c *gc.C) {
   126  	localState := resolver.LocalState{
   127  		CharmModifiedVersion: s.charmModifiedVersion,
   128  		CharmURL:             s.charmURL,
   129  		State: operation.State{
   130  			Kind:      operation.Continue,
   131  			Installed: false,
   132  			Started:   false,
   133  		},
   134  	}
   135  	op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   136  	c.Assert(err, jc.ErrorIsNil)
   137  	c.Assert(op.String(), gc.Equals, "run install hook")
   138  }
   139  
   140  func (s *iaasResolverSuite) TestCharmModifiedTakesPrecedenceOverRelationsChanges(c *gc.C) {
   141  	localState := resolver.LocalState{
   142  		CharmModifiedVersion: s.charmModifiedVersion,
   143  		CharmURL:             s.charmURL,
   144  		State: operation.State{
   145  			Kind:      operation.Continue,
   146  			Installed: true,
   147  			Started:   true,
   148  		},
   149  	}
   150  	s.remoteState.CharmModifiedVersion = s.charmModifiedVersion + 1
   151  	s.remoteState.UpgradeCharmProfileStatus = lxdprofile.NotRequiredStatus
   152  	// Change relation state (to simulate simultaneous change remote state update)
   153  	s.remoteState.Relations = map[int]remotestate.RelationSnapshot{0: {}}
   154  	op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   155  	c.Assert(err, jc.ErrorIsNil)
   156  	c.Assert(op.String(), gc.Equals, "upgrade to cs:precise/mysql-2")
   157  }
   158  
   159  func (s *iaasResolverSuite) TestUpgradeSeriesPrepareStatusChanged(c *gc.C) {
   160  	localState := resolver.LocalState{
   161  		CharmModifiedVersion: s.charmModifiedVersion,
   162  		CharmURL:             s.charmURL,
   163  		UpgradeSeriesStatus:  model.UpgradeSeriesNotStarted,
   164  		State: operation.State{
   165  			Kind:      operation.Continue,
   166  			Installed: true,
   167  			Started:   true,
   168  		},
   169  	}
   170  	s.remoteState.UpgradeSeriesStatus = model.UpgradeSeriesPrepareStarted
   171  	op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   172  	c.Assert(err, jc.ErrorIsNil)
   173  	c.Assert(op.String(), gc.Equals, "run pre-series-upgrade hook")
   174  }
   175  
   176  func (s *iaasResolverSuite) TestPostSeriesUpgradeHookRunsWhenConditionsAreMet(c *gc.C) {
   177  	localState := resolver.LocalState{
   178  		CharmModifiedVersion:  s.charmModifiedVersion,
   179  		CharmURL:              s.charmURL,
   180  		UpgradeSeriesStatus:   model.UpgradeSeriesNotStarted,
   181  		LeaderSettingsVersion: 1,
   182  		State: operation.State{
   183  			Kind:       operation.Continue,
   184  			Installed:  true,
   185  			Started:    true,
   186  			ConfigHash: "version1",
   187  		},
   188  	}
   189  	s.remoteState.UpgradeSeriesStatus = model.UpgradeSeriesCompleteStarted
   190  
   191  	// Bumping the remote state versions verifies that the upgrade-series
   192  	// completion hook takes precedence.
   193  	s.remoteState.ConfigHash = "version2"
   194  	s.remoteState.LeaderSettingsVersion = 2
   195  
   196  	op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   197  	c.Assert(err, jc.ErrorIsNil)
   198  	c.Assert(op.String(), gc.Equals, "run post-series-upgrade hook")
   199  }
   200  
   201  func (s *iaasResolverSuite) TestRunsOperationToResetLocalUpgradeSeriesStateWhenConditionsAreMet(c *gc.C) {
   202  	localState := resolver.LocalState{
   203  		CharmModifiedVersion: s.charmModifiedVersion,
   204  		CharmURL:             s.charmURL,
   205  		UpgradeSeriesStatus:  model.UpgradeSeriesCompleted,
   206  		State: operation.State{
   207  			Kind:      operation.Continue,
   208  			Installed: true,
   209  			Started:   true,
   210  		},
   211  	}
   212  	s.remoteState.UpgradeSeriesStatus = model.UpgradeSeriesNotStarted
   213  	op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   214  	c.Assert(err, jc.ErrorIsNil)
   215  	c.Assert(op.String(), gc.Equals, "complete upgrade series")
   216  }
   217  
   218  func (s *iaasResolverSuite) TestUniterIdlesWhenRemoteStateIsUpgradeSeriesCompleted(c *gc.C) {
   219  	localState := resolver.LocalState{
   220  		UpgradeSeriesStatus: model.UpgradeSeriesNotStarted,
   221  		CharmURL:            s.charmURL,
   222  		State: operation.State{
   223  			Kind:      operation.Continue,
   224  			Installed: true,
   225  		},
   226  	}
   227  	s.remoteState.UpgradeSeriesStatus = model.UpgradeSeriesPrepareCompleted
   228  	_, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   229  	c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   230  }
   231  
   232  func (s *iaasResolverSuite) TestUpgradeCharmProfileWhenNotRequired(c *gc.C) {
   233  	localState := resolver.LocalState{
   234  		CharmModifiedVersion:      s.charmModifiedVersion,
   235  		CharmURL:                  s.charmURL,
   236  		UpgradeCharmProfileStatus: lxdprofile.NotRequiredStatus,
   237  		State: operation.State{
   238  			Kind:      operation.Upgrade,
   239  			Installed: true,
   240  			Started:   true,
   241  		},
   242  	}
   243  	s.remoteState.UpgradeCharmProfileStatus = lxdprofile.NotRequiredStatus
   244  	op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   245  	c.Assert(err, jc.ErrorIsNil)
   246  	c.Assert(op.String(), gc.Equals, "upgrade to cs:precise/mysql-2")
   247  }
   248  
   249  func (s *iaasResolverSuite) TestUpgradeCharmProfileWhenSuccess(c *gc.C) {
   250  	localState := resolver.LocalState{
   251  		CharmModifiedVersion:      s.charmModifiedVersion,
   252  		CharmURL:                  s.charmURL,
   253  		UpgradeCharmProfileStatus: lxdprofile.SuccessStatus,
   254  		State: operation.State{
   255  			Kind:      operation.Upgrade,
   256  			Installed: true,
   257  			Started:   true,
   258  		},
   259  	}
   260  	s.remoteState.UpgradeCharmProfileStatus = lxdprofile.SuccessStatus
   261  	op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   262  	c.Assert(err, jc.ErrorIsNil)
   263  	c.Assert(op.String(), gc.Equals, "upgrade to cs:precise/mysql-2")
   264  }
   265  
   266  func (s *iaasResolverSuite) TestUpgradeCharmProfileWhenErrorState(c *gc.C) {
   267  	localState := resolver.LocalState{
   268  		CharmModifiedVersion:      s.charmModifiedVersion,
   269  		CharmURL:                  s.charmURL,
   270  		UpgradeCharmProfileStatus: lxdprofile.ErrorStatus,
   271  		State: operation.State{
   272  			Kind:      operation.Upgrade,
   273  			Installed: true,
   274  			Started:   true,
   275  		},
   276  	}
   277  	s.remoteState.UpgradeCharmProfileStatus = lxdprofile.ErrorStatus
   278  	op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   279  	c.Assert(err, jc.ErrorIsNil)
   280  	c.Assert(op.String(), gc.Equals, "finish upgrade charm profile")
   281  }
   282  
   283  func (s *iaasResolverSuite) TestUpgradeCharmProfileWhenLocalStateIsNotErroredState(c *gc.C) {
   284  	localState := resolver.LocalState{
   285  		CharmModifiedVersion:      s.charmModifiedVersion,
   286  		CharmURL:                  s.charmURL,
   287  		UpgradeCharmProfileStatus: lxdprofile.NotRequiredStatus,
   288  		State: operation.State{
   289  			Kind:      operation.Upgrade,
   290  			Installed: true,
   291  			Started:   true,
   292  		},
   293  	}
   294  	s.remoteState.UpgradeCharmProfileStatus = lxdprofile.ErrorStatus
   295  	op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   296  	c.Assert(err, jc.ErrorIsNil)
   297  	c.Assert(op.String(), gc.Equals, "finish upgrade charm profile")
   298  }
   299  
   300  func (s *resolverSuite) TestHookErrorDoesNotStartRetryTimerIfShouldRetryFalse(c *gc.C) {
   301  	s.resolverConfig.ShouldRetryHooks = false
   302  	s.resolver = uniter.NewUniterResolver(s.resolverConfig)
   303  	s.reportHookError = func(hook.Info) error { return nil }
   304  	localState := resolver.LocalState{
   305  		CharmURL: s.charmURL,
   306  		State: operation.State{
   307  			Kind:      operation.RunHook,
   308  			Step:      operation.Pending,
   309  			Installed: true,
   310  			Started:   true,
   311  			Hook: &hook.Info{
   312  				Kind: hooks.ConfigChanged,
   313  			},
   314  		},
   315  	}
   316  	// Run the resolver; we should not attempt a hook retry
   317  	_, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   318  	c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   319  	s.stub.CheckNoCalls(c)
   320  }
   321  
   322  func (s *resolverSuite) TestHookErrorStartRetryTimer(c *gc.C) {
   323  	s.reportHookError = func(hook.Info) error { return nil }
   324  	localState := resolver.LocalState{
   325  		CharmModifiedVersion: s.charmModifiedVersion,
   326  		CharmURL:             s.charmURL,
   327  		State: operation.State{
   328  			Kind:      operation.RunHook,
   329  			Step:      operation.Pending,
   330  			Installed: true,
   331  			Started:   true,
   332  			Hook: &hook.Info{
   333  				Kind: hooks.ConfigChanged,
   334  			},
   335  		},
   336  	}
   337  	// Run the resolver twice; we should start the hook retry
   338  	// timer on the first time through, no change on the second.
   339  	_, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   340  	c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   341  	s.stub.CheckCallNames(c, "StartRetryHookTimer")
   342  
   343  	_, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   344  	c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   345  	s.stub.CheckCallNames(c, "StartRetryHookTimer") // no change
   346  }
   347  
   348  func (s *resolverSuite) TestHookErrorStartRetryTimerAgain(c *gc.C) {
   349  	s.reportHookError = func(hook.Info) error { return nil }
   350  	localState := resolver.LocalState{
   351  		CharmModifiedVersion: s.charmModifiedVersion,
   352  		CharmURL:             s.charmURL,
   353  		State: operation.State{
   354  			Kind:      operation.RunHook,
   355  			Step:      operation.Pending,
   356  			Installed: true,
   357  			Started:   true,
   358  			Hook: &hook.Info{
   359  				Kind: hooks.ConfigChanged,
   360  			},
   361  		},
   362  	}
   363  
   364  	_, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   365  	c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   366  	s.stub.CheckCallNames(c, "StartRetryHookTimer")
   367  
   368  	s.remoteState.RetryHookVersion = 1
   369  	op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   370  	c.Assert(err, jc.ErrorIsNil)
   371  	c.Assert(op.String(), gc.Equals, "run config-changed hook")
   372  	s.stub.CheckCallNames(c, "StartRetryHookTimer") // no change
   373  	localState.RetryHookVersion = 1
   374  
   375  	_, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   376  	c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   377  	s.stub.CheckCallNames(c, "StartRetryHookTimer", "StartRetryHookTimer")
   378  }
   379  
   380  func (s *resolverSuite) TestResolvedRetryHooksStopRetryTimer(c *gc.C) {
   381  	// Resolving a failed hook should stop the retry timer.
   382  	s.testResolveHookErrorStopRetryTimer(c, params.ResolvedRetryHooks)
   383  }
   384  
   385  func (s *resolverSuite) TestResolvedNoHooksStopRetryTimer(c *gc.C) {
   386  	// Resolving a failed hook should stop the retry timer.
   387  	s.testResolveHookErrorStopRetryTimer(c, params.ResolvedNoHooks)
   388  }
   389  
   390  func (s *resolverSuite) testResolveHookErrorStopRetryTimer(c *gc.C, mode params.ResolvedMode) {
   391  	s.stub.ResetCalls()
   392  	s.clearResolved = func() error { return nil }
   393  	s.reportHookError = func(hook.Info) error { return nil }
   394  	localState := resolver.LocalState{
   395  		CharmModifiedVersion: s.charmModifiedVersion,
   396  		CharmURL:             s.charmURL,
   397  		State: operation.State{
   398  			Kind:      operation.RunHook,
   399  			Step:      operation.Pending,
   400  			Installed: true,
   401  			Started:   true,
   402  			Hook: &hook.Info{
   403  				Kind: hooks.ConfigChanged,
   404  			},
   405  		},
   406  	}
   407  
   408  	_, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   409  	c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   410  	s.stub.CheckCallNames(c, "StartRetryHookTimer")
   411  
   412  	s.remoteState.ResolvedMode = mode
   413  	_, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   414  	c.Assert(err, jc.ErrorIsNil)
   415  	s.stub.CheckCallNames(c, "StartRetryHookTimer", "StopRetryHookTimer")
   416  }
   417  
   418  func (s *resolverSuite) TestRunHookStopRetryTimer(c *gc.C) {
   419  	s.reportHookError = func(hook.Info) error { return nil }
   420  	localState := resolver.LocalState{
   421  		CharmModifiedVersion: s.charmModifiedVersion,
   422  		CharmURL:             s.charmURL,
   423  		State: operation.State{
   424  			Kind:      operation.RunHook,
   425  			Step:      operation.Pending,
   426  			Installed: true,
   427  			Started:   true,
   428  			Hook: &hook.Info{
   429  				Kind: hooks.ConfigChanged,
   430  			},
   431  		},
   432  	}
   433  
   434  	_, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   435  	c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   436  	s.stub.CheckCallNames(c, "StartRetryHookTimer")
   437  
   438  	localState.Kind = operation.Continue
   439  	_, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   440  	c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   441  	s.stub.CheckCallNames(c, "StartRetryHookTimer", "StopRetryHookTimer")
   442  }
   443  
   444  func (s *resolverSuite) TestRunsConfigChangedIfConfigHashChanges(c *gc.C) {
   445  	localState := resolver.LocalState{
   446  		CharmModifiedVersion: s.charmModifiedVersion,
   447  		CharmURL:             s.charmURL,
   448  		State: operation.State{
   449  			Kind:       operation.Continue,
   450  			Installed:  true,
   451  			Started:    true,
   452  			ConfigHash: "somehash",
   453  		},
   454  	}
   455  	s.remoteState.ConfigHash = "differenthash"
   456  
   457  	op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   458  	c.Assert(err, jc.ErrorIsNil)
   459  	c.Assert(op.String(), gc.Equals, "run config-changed hook")
   460  }
   461  
   462  func (s *resolverSuite) TestRunsConfigChangedIfTrustHashChanges(c *gc.C) {
   463  	localState := resolver.LocalState{
   464  		CharmModifiedVersion: s.charmModifiedVersion,
   465  		CharmURL:             s.charmURL,
   466  		State: operation.State{
   467  			Kind:      operation.Continue,
   468  			Installed: true,
   469  			Started:   true,
   470  			TrustHash: "somehash",
   471  		},
   472  	}
   473  	s.remoteState.TrustHash = "differenthash"
   474  
   475  	op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   476  	c.Assert(err, jc.ErrorIsNil)
   477  	c.Assert(op.String(), gc.Equals, "run config-changed hook")
   478  }
   479  
   480  func (s *resolverSuite) TestRunsConfigChangedIfAddressesHashChanges(c *gc.C) {
   481  	localState := resolver.LocalState{
   482  		CharmModifiedVersion: s.charmModifiedVersion,
   483  		CharmURL:             s.charmURL,
   484  		State: operation.State{
   485  			Kind:          operation.Continue,
   486  			Installed:     true,
   487  			Started:       true,
   488  			AddressesHash: "somehash",
   489  		},
   490  	}
   491  	s.remoteState.AddressesHash = "differenthash"
   492  
   493  	op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   494  	c.Assert(err, jc.ErrorIsNil)
   495  	c.Assert(op.String(), gc.Equals, "run config-changed hook")
   496  }
   497  
   498  func (s *resolverSuite) TestNoOperationIfHashesAllMatch(c *gc.C) {
   499  	localState := resolver.LocalState{
   500  		CharmModifiedVersion: s.charmModifiedVersion,
   501  		CharmURL:             s.charmURL,
   502  		State: operation.State{
   503  			Kind:          operation.Continue,
   504  			Installed:     true,
   505  			Started:       true,
   506  			ConfigHash:    "config",
   507  			TrustHash:     "trust",
   508  			AddressesHash: "addresses",
   509  		},
   510  	}
   511  	s.remoteState.ConfigHash = "config"
   512  	s.remoteState.TrustHash = "trust"
   513  	s.remoteState.AddressesHash = "addresses"
   514  
   515  	_, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
   516  	c.Assert(err, gc.Equals, resolver.ErrNoOperation)
   517  }