github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/action_test.go (about)

     1  // Copyright 2014-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"math/rand"
    10  	"strings"
    11  	"sync"
    12  	"sync/atomic"
    13  	"time"
    14  
    15  	"github.com/juju/clock/testclock"
    16  	"github.com/juju/names/v5"
    17  	jc "github.com/juju/testing/checkers"
    18  	jujutxn "github.com/juju/txn/v3"
    19  	gc "gopkg.in/check.v1"
    20  
    21  	"github.com/juju/juju/core/actions"
    22  	"github.com/juju/juju/state"
    23  	stateerrors "github.com/juju/juju/state/errors"
    24  	statetesting "github.com/juju/juju/state/testing"
    25  	coretesting "github.com/juju/juju/testing"
    26  	"github.com/juju/juju/testing/factory"
    27  )
    28  
    29  type ActionSuite struct {
    30  	ConnSuite
    31  	charm                 *state.Charm
    32  	actionlessCharm       *state.Charm
    33  	application           *state.Application
    34  	actionlessApplication *state.Application
    35  	unit                  *state.Unit
    36  	unit2                 *state.Unit
    37  	charmlessUnit         *state.Unit
    38  	actionlessUnit        *state.Unit
    39  	model                 *state.Model
    40  }
    41  
    42  var _ = gc.Suite(&ActionSuite{})
    43  
    44  func (s *ActionSuite) SetUpTest(c *gc.C) {
    45  	var err error
    46  
    47  	s.ConnSuite.SetUpTest(c)
    48  
    49  	s.charm = s.AddTestingCharm(c, "dummy")
    50  	s.actionlessCharm = s.AddTestingCharm(c, "actionless")
    51  
    52  	s.application = s.AddTestingApplication(c, "dummy", s.charm)
    53  	c.Assert(err, jc.ErrorIsNil)
    54  	s.actionlessApplication = s.AddTestingApplication(c, "actionless", s.actionlessCharm)
    55  	c.Assert(err, jc.ErrorIsNil)
    56  
    57  	sURL, _ := s.application.CharmURL()
    58  	c.Assert(sURL, gc.NotNil)
    59  	actionlessSURL, _ := s.actionlessApplication.CharmURL()
    60  	c.Assert(actionlessSURL, gc.NotNil)
    61  
    62  	s.unit, err = s.application.AddUnit(state.AddUnitParams{})
    63  	c.Assert(err, jc.ErrorIsNil)
    64  	c.Assert(s.unit.Base(), jc.DeepEquals, state.Base{OS: "ubuntu", Channel: "12.10/stable"})
    65  
    66  	err = s.unit.SetCharmURL(*sURL)
    67  	c.Assert(err, jc.ErrorIsNil)
    68  
    69  	s.unit2, err = s.application.AddUnit(state.AddUnitParams{})
    70  	c.Assert(err, jc.ErrorIsNil)
    71  	c.Assert(s.unit2.Base(), jc.DeepEquals, state.Base{OS: "ubuntu", Channel: "12.10/stable"})
    72  
    73  	err = s.unit2.SetCharmURL(*sURL)
    74  	c.Assert(err, jc.ErrorIsNil)
    75  
    76  	s.charmlessUnit, err = s.application.AddUnit(state.AddUnitParams{})
    77  	c.Assert(err, jc.ErrorIsNil)
    78  	c.Assert(s.charmlessUnit.Base(), jc.DeepEquals, state.Base{OS: "ubuntu", Channel: "12.10/stable"})
    79  
    80  	s.actionlessUnit, err = s.actionlessApplication.AddUnit(state.AddUnitParams{})
    81  	c.Assert(err, jc.ErrorIsNil)
    82  	c.Assert(s.actionlessUnit.Base(), jc.DeepEquals, state.Base{OS: "ubuntu", Channel: "12.10/stable"})
    83  
    84  	err = s.actionlessUnit.SetCharmURL(*actionlessSURL)
    85  	c.Assert(err, jc.ErrorIsNil)
    86  
    87  	s.model, err = s.State.Model()
    88  	c.Assert(err, jc.ErrorIsNil)
    89  }
    90  
    91  func (s *ActionSuite) TestActionTag(c *gc.C) {
    92  	operationID, err := s.Model.EnqueueOperation("a test", 1)
    93  	c.Assert(err, jc.ErrorIsNil)
    94  	action, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil)
    95  	c.Assert(err, jc.ErrorIsNil)
    96  
    97  	tag := action.Tag()
    98  	c.Assert(tag.String(), gc.Equals, "action-"+action.Id())
    99  
   100  	result, err := action.Finish(state.ActionResults{Status: state.ActionCompleted})
   101  	c.Assert(err, jc.ErrorIsNil)
   102  
   103  	actions, err := s.unit.CompletedActions()
   104  	c.Assert(err, jc.ErrorIsNil)
   105  	c.Assert(len(actions), gc.Equals, 1)
   106  
   107  	actionResult := actions[0]
   108  	c.Assert(actionResult, gc.DeepEquals, result)
   109  
   110  	tag = actionResult.Tag()
   111  	c.Assert(tag.String(), gc.Equals, "action-"+actionResult.Id())
   112  }
   113  
   114  func (s *ActionSuite) TestAddAction(c *gc.C) {
   115  	for i, t := range []struct {
   116  		should         string
   117  		name           string
   118  		params         map[string]interface{}
   119  		parallel       bool
   120  		executionGroup string
   121  		whichUnit      *state.Unit
   122  		expectedErr    string
   123  	}{{
   124  		should:         "enqueue normally",
   125  		name:           "snapshot",
   126  		whichUnit:      s.unit,
   127  		params:         map[string]interface{}{"outfile": "outfile.tar.bz2"},
   128  		parallel:       true,
   129  		executionGroup: "group",
   130  	}, {
   131  		should:      "fail on actionless charms",
   132  		name:        "something",
   133  		whichUnit:   s.actionlessUnit,
   134  		expectedErr: "no actions defined on charm \"local:quantal/quantal-actionless-1\"",
   135  	}, {
   136  		should:      "fail on action not defined in schema",
   137  		whichUnit:   s.unit,
   138  		name:        "something-nonexistent",
   139  		expectedErr: "action \"something-nonexistent\" not defined on unit \"dummy/0\"",
   140  	}, {
   141  		should:    "invalidate with bad params",
   142  		whichUnit: s.unit,
   143  		name:      "snapshot",
   144  		params: map[string]interface{}{
   145  			"outfile": 5.0,
   146  		},
   147  		expectedErr: "validation failed: \\(root\\)\\.outfile : must be of type string, given 5",
   148  	}} {
   149  		c.Logf("Test %d: should %s", i, t.should)
   150  		before := state.NowToTheSecond(s.State)
   151  		later := before.Add(coretesting.LongWait)
   152  
   153  		// Copy params over into empty premade map for comparison later
   154  		params := make(map[string]interface{})
   155  		for k, v := range t.params {
   156  			params[k] = v
   157  		}
   158  
   159  		// Verify we can add an Action
   160  		operationID, err := s.Model.EnqueueOperation("a test", 1)
   161  		c.Assert(err, jc.ErrorIsNil)
   162  		a, err := s.Model.AddAction(t.whichUnit, operationID, t.name, params, &t.parallel, &t.executionGroup)
   163  
   164  		if t.expectedErr == "" {
   165  			c.Assert(err, jc.ErrorIsNil)
   166  			curl := t.whichUnit.CharmURL()
   167  			c.Assert(curl, gc.NotNil)
   168  			ch, _ := s.State.Charm(*curl)
   169  			schema := ch.Actions()
   170  			c.Logf("Schema for unit %q:\n%#v", t.whichUnit.Name(), schema)
   171  
   172  			// verify we can get it back out by Id
   173  			model, err := s.State.Model()
   174  			c.Assert(err, jc.ErrorIsNil)
   175  
   176  			action, err := model.Action(a.Id())
   177  			c.Assert(err, jc.ErrorIsNil)
   178  			c.Assert(action, gc.NotNil)
   179  			c.Check(action.Id(), gc.Equals, a.Id())
   180  			c.Check(state.ActionOperationId(action), gc.Equals, operationID)
   181  
   182  			// verify we get out what we put in
   183  			c.Check(action.Name(), gc.Equals, t.name)
   184  			c.Check(action.Parameters(), jc.DeepEquals, params)
   185  			c.Check(action.Parallel(), gc.Equals, t.parallel)
   186  			c.Check(action.ExecutionGroup(), gc.Equals, t.executionGroup)
   187  
   188  			// Enqueued time should be within a reasonable time of the beginning
   189  			// of the test
   190  			now := state.NowToTheSecond(s.State)
   191  			c.Check(action.Enqueued(), jc.TimeBetween(before, now))
   192  			c.Check(action.Enqueued(), jc.TimeBetween(before, later))
   193  			continue
   194  		}
   195  
   196  		c.Check(err, gc.ErrorMatches, t.expectedErr)
   197  	}
   198  }
   199  
   200  func (s *ActionSuite) TestAddActionInsertsDefaults(c *gc.C) {
   201  	units := make(map[string]*state.Unit)
   202  	schemas := map[string]string{
   203  		"simple": `
   204  act:
   205    params:
   206      val:
   207        type: string
   208        default: somestr
   209  `[1:],
   210  		"complicated": `
   211  act:
   212    params:
   213      val:
   214        type: object
   215        properties:
   216          foo:
   217            type: string
   218          bar:
   219            type: object
   220            properties:
   221              baz:
   222                type: string
   223                default: woz
   224  `[1:],
   225  		"none": `
   226  act:
   227    params:
   228      val:
   229        type: string
   230  `[1:]}
   231  
   232  	// Prepare the units for this test
   233  	makeUnits(c, s, units, schemas)
   234  
   235  	for i, t := range []struct {
   236  		should         string
   237  		params         map[string]interface{}
   238  		schema         string
   239  		expectedParams map[string]interface{}
   240  	}{{
   241  		should:         "do nothing with no defaults",
   242  		params:         map[string]interface{}{},
   243  		schema:         "none",
   244  		expectedParams: map[string]interface{}{},
   245  	}, {
   246  		should: "insert a simple default value",
   247  		params: map[string]interface{}{"foo": "bar"},
   248  		schema: "simple",
   249  		expectedParams: map[string]interface{}{
   250  			"foo": "bar",
   251  			"val": "somestr",
   252  		},
   253  	}, {
   254  		should: "insert a default value when an empty map is passed",
   255  		params: map[string]interface{}{},
   256  		schema: "simple",
   257  		expectedParams: map[string]interface{}{
   258  			"val": "somestr",
   259  		},
   260  	}, {
   261  		should: "insert a default value when a nil map is passed",
   262  		params: nil,
   263  		schema: "simple",
   264  		expectedParams: map[string]interface{}{
   265  			"val": "somestr",
   266  		},
   267  	}, {
   268  		should: "insert a nested default value",
   269  		params: map[string]interface{}{"foo": "bar"},
   270  		schema: "complicated",
   271  		expectedParams: map[string]interface{}{
   272  			"foo": "bar",
   273  			"val": map[string]interface{}{
   274  				"bar": map[string]interface{}{
   275  					"baz": "woz",
   276  				}}},
   277  	}} {
   278  		c.Logf("test %d: should %s", i, t.should)
   279  		u := units[t.schema]
   280  		// Note that AddAction will only result in errors in the case
   281  		// of malformed schemas, and schema objects can only be
   282  		// created from valid schemas.  The error handling for this
   283  		// is tested in the gojsonschema package.
   284  		operationID, err := s.Model.EnqueueOperation("a test", 1)
   285  		c.Assert(err, jc.ErrorIsNil)
   286  		action, err := s.Model.AddAction(u, operationID, "act", t.params, nil, nil)
   287  		c.Assert(err, jc.ErrorIsNil)
   288  		c.Check(action.Parameters(), jc.DeepEquals, t.expectedParams)
   289  		c.Check(action.Parallel(), jc.IsFalse)
   290  		c.Check(action.ExecutionGroup(), gc.Equals, "")
   291  	}
   292  }
   293  
   294  func (s *ActionSuite) TestActionBeginStartsOperation(c *gc.C) {
   295  	clock := testclock.NewClock(coretesting.NonZeroTime().Round(time.Second))
   296  	err := s.State.SetClockForTesting(clock)
   297  	c.Assert(err, jc.ErrorIsNil)
   298  
   299  	operationID, err := s.Model.EnqueueOperation("a test", 2)
   300  	c.Assert(err, jc.ErrorIsNil)
   301  	anAction, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil)
   302  	c.Assert(err, jc.ErrorIsNil)
   303  	anAction2, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil)
   304  	c.Assert(err, jc.ErrorIsNil)
   305  
   306  	anAction, err = anAction.Begin()
   307  	c.Assert(err, jc.ErrorIsNil)
   308  	operation, err := s.model.Operation(operationID)
   309  	c.Assert(err, jc.ErrorIsNil)
   310  	c.Assert(operation.Status(), gc.Equals, state.ActionRunning)
   311  	c.Assert(operation.Started(), gc.Equals, anAction.Started())
   312  
   313  	// Starting a second action does not affect the original start time.
   314  	clock.Advance(5 * time.Second)
   315  	anAction2, err = anAction2.Begin()
   316  	c.Assert(err, jc.ErrorIsNil)
   317  	err = operation.Refresh()
   318  	c.Assert(err, jc.ErrorIsNil)
   319  	c.Assert(operation.Status(), gc.Equals, state.ActionRunning)
   320  	c.Assert(operation.Started(), gc.Equals, anAction.Started())
   321  	c.Assert(operation.Started(), gc.Not(gc.Equals), anAction2.Started())
   322  }
   323  
   324  func (s *ActionSuite) TestActionBeginStartsOperationRace(c *gc.C) {
   325  	clock := testclock.NewClock(coretesting.NonZeroTime().Round(time.Second))
   326  	err := s.State.SetClockForTesting(clock)
   327  	c.Assert(err, jc.ErrorIsNil)
   328  
   329  	operationID, err := s.Model.EnqueueOperation("a test", 2)
   330  	c.Assert(err, jc.ErrorIsNil)
   331  	anAction, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil)
   332  	c.Assert(err, jc.ErrorIsNil)
   333  	anAction2, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil)
   334  	c.Assert(err, jc.ErrorIsNil)
   335  
   336  	defer state.SetBeforeHooks(c, s.State, func() {
   337  		clock.Advance(5 * time.Second)
   338  		anAction2, err = anAction2.Begin()
   339  		c.Assert(err, jc.ErrorIsNil)
   340  	})()
   341  
   342  	anAction, err = anAction.Begin()
   343  	c.Assert(err, jc.ErrorIsNil)
   344  	operation, err := s.model.Operation(operationID)
   345  	c.Assert(err, jc.ErrorIsNil)
   346  
   347  	c.Assert(operation.Status(), gc.Equals, state.ActionRunning)
   348  	c.Assert(operation.Started(), gc.Equals, anAction2.Started())
   349  	c.Assert(operation.Started(), gc.Not(gc.Equals), anAction.Started())
   350  }
   351  
   352  func (s *ActionSuite) TestLastActionFinishCompletesOperation(c *gc.C) {
   353  	clock := testclock.NewClock(coretesting.NonZeroTime().Round(time.Second))
   354  	err := s.State.SetClockForTesting(clock)
   355  	c.Assert(err, jc.ErrorIsNil)
   356  
   357  	operationID, err := s.Model.EnqueueOperation("a test", 2)
   358  	c.Assert(err, jc.ErrorIsNil)
   359  	anAction, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil)
   360  	c.Assert(err, jc.ErrorIsNil)
   361  	anAction2, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil)
   362  	c.Assert(err, jc.ErrorIsNil)
   363  
   364  	anAction, err = anAction.Begin()
   365  	c.Assert(err, jc.ErrorIsNil)
   366  	clock.Advance(5 * time.Second)
   367  	anAction2, err = anAction2.Begin()
   368  	c.Assert(err, jc.ErrorIsNil)
   369  
   370  	// Finishing only one action does not complete the operation.
   371  	_, err = anAction.Finish(state.ActionResults{
   372  		Status: state.ActionFailed,
   373  	})
   374  	c.Assert(err, jc.ErrorIsNil)
   375  	operation, err := s.model.Operation(operationID)
   376  	c.Assert(err, jc.ErrorIsNil)
   377  	c.Assert(operation.Status(), gc.Equals, state.ActionRunning)
   378  	c.Assert(operation.Completed(), gc.Equals, time.Time{})
   379  
   380  	clock.Advance(5 * time.Second)
   381  	anAction2, err = anAction2.Finish(state.ActionResults{
   382  		Status: state.ActionCompleted,
   383  	})
   384  	c.Assert(err, jc.ErrorIsNil)
   385  	err = operation.Refresh()
   386  	c.Assert(err, jc.ErrorIsNil)
   387  	// Failed task precedence over completed.
   388  	c.Assert(operation.Status(), gc.Equals, state.ActionFailed)
   389  	c.Assert(operation.Completed(), gc.Equals, anAction2.Completed())
   390  }
   391  
   392  func (s *ActionSuite) TestLastActionFinishCompletesOperationMany(c *gc.C) {
   393  	numActions := 50
   394  
   395  	operationID, err := s.Model.EnqueueOperation("a test", numActions)
   396  	c.Assert(err, jc.ErrorIsNil)
   397  
   398  	wg := sync.WaitGroup{}
   399  	var actions []state.Action
   400  	for i := 0; i < numActions; i++ {
   401  		anAction, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil)
   402  		c.Assert(err, jc.ErrorIsNil)
   403  
   404  		anAction, err = anAction.Begin()
   405  		c.Assert(err, jc.ErrorIsNil)
   406  		actions = append(actions, anAction)
   407  		wg.Add(1)
   408  	}
   409  
   410  	completeCount := int32(0)
   411  	for i := 0; i < numActions; i++ {
   412  		go func(a int) {
   413  			defer func() {
   414  				atomic.AddInt32(&completeCount, 1)
   415  				wg.Done()
   416  			}()
   417  			time.Sleep(time.Millisecond * time.Duration(rand.Intn(5)))
   418  			if atomic.LoadInt32(&completeCount) < int32(numActions) {
   419  				operation, err := s.model.Operation(operationID)
   420  				c.Assert(err, jc.ErrorIsNil)
   421  				c.Assert(operation.Status(), gc.Not(gc.Equals), state.ActionCompleted)
   422  			}
   423  
   424  			_, err := actions[a].Finish(state.ActionResults{
   425  				Status: state.ActionCompleted,
   426  			})
   427  			c.Assert(err, jc.ErrorIsNil)
   428  		}(i)
   429  	}
   430  	wg.Wait()
   431  
   432  	operation, err := s.model.Operation(operationID)
   433  	c.Assert(err, jc.ErrorIsNil)
   434  	c.Assert(operation.Status(), gc.Equals, state.ActionCompleted)
   435  }
   436  
   437  func (s *ActionSuite) TestLastActionFinishCompletesOperationRace(c *gc.C) {
   438  	clock := testclock.NewClock(coretesting.NonZeroTime().Round(time.Second))
   439  	err := s.State.SetClockForTesting(clock)
   440  	c.Assert(err, jc.ErrorIsNil)
   441  
   442  	operationID, err := s.Model.EnqueueOperation("a test", 2)
   443  	c.Assert(err, jc.ErrorIsNil)
   444  	anAction, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil)
   445  	c.Assert(err, jc.ErrorIsNil)
   446  	anAction2, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil)
   447  	c.Assert(err, jc.ErrorIsNil)
   448  
   449  	anAction, err = anAction.Begin()
   450  	c.Assert(err, jc.ErrorIsNil)
   451  	clock.Advance(5 * time.Second)
   452  	anAction2, err = anAction2.Begin()
   453  	c.Assert(err, jc.ErrorIsNil)
   454  
   455  	operation, err := s.model.Operation(operationID)
   456  	defer state.SetBeforeHooks(c, s.State, func() {
   457  		//clock.Advance(5 * time.Second)
   458  		anAction2, err = anAction2.Finish(state.ActionResults{
   459  			Status: state.ActionCancelled,
   460  		})
   461  		c.Assert(err, jc.ErrorIsNil)
   462  		err = operation.Refresh()
   463  		c.Assert(err, jc.ErrorIsNil)
   464  		c.Assert(operation.Status(), gc.Equals, state.ActionRunning)
   465  		c.Assert(operation.Completed(), gc.Equals, time.Time{})
   466  	})()
   467  
   468  	// Finishing does complete the operation due to the other action
   469  	// finishing during the update of this one.
   470  	anAction, err = anAction.Finish(state.ActionResults{
   471  		Status: state.ActionCompleted,
   472  	})
   473  	c.Assert(err, jc.ErrorIsNil)
   474  	err = operation.Refresh()
   475  	c.Assert(err, jc.ErrorIsNil)
   476  	c.Assert(operation.Status(), gc.Equals, state.ActionCancelled)
   477  	c.Assert(operation.Completed(), gc.Equals, anAction.Completed())
   478  }
   479  
   480  func (s *ActionSuite) TestActionMessages(c *gc.C) {
   481  	clock := testclock.NewClock(coretesting.NonZeroTime().Round(time.Second))
   482  	err := s.State.SetClockForTesting(clock)
   483  	c.Assert(err, jc.ErrorIsNil)
   484  
   485  	operationID, err := s.Model.EnqueueOperation("a test", 1)
   486  	c.Assert(err, jc.ErrorIsNil)
   487  	anAction, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil)
   488  	c.Assert(err, jc.ErrorIsNil)
   489  	c.Assert(anAction.Messages(), gc.HasLen, 0)
   490  
   491  	// Cannot log messages until action is running.
   492  	err = anAction.Log("hello")
   493  	c.Assert(err, gc.ErrorMatches, `cannot log message to task "2" with status pending`)
   494  
   495  	anAction, err = anAction.Begin()
   496  	c.Assert(err, jc.ErrorIsNil)
   497  	messages := []string{"one", "two", "three"}
   498  	for i, msg := range messages {
   499  		err = anAction.Log(msg)
   500  		c.Assert(err, jc.ErrorIsNil)
   501  
   502  		a, err := s.Model.Action(anAction.Id())
   503  		c.Assert(err, jc.ErrorIsNil)
   504  		obtained := a.Messages()
   505  		c.Assert(obtained, gc.HasLen, i+1)
   506  		for j, am := range obtained {
   507  			c.Assert(am.Timestamp(), gc.Equals, clock.Now().UTC())
   508  			c.Assert(am.Message(), gc.Equals, messages[j])
   509  		}
   510  	}
   511  
   512  	// Cannot log messages after action finishes.
   513  	_, err = anAction.Finish(state.ActionResults{Status: state.ActionCompleted})
   514  	c.Assert(err, jc.ErrorIsNil)
   515  	err = anAction.Log("hello")
   516  	c.Assert(err, gc.ErrorMatches, `cannot log message to task "2" with status completed`)
   517  }
   518  
   519  func (s *ActionSuite) TestActionLogMessageRace(c *gc.C) {
   520  	clock := testclock.NewClock(coretesting.NonZeroTime().Round(time.Second))
   521  	err := s.State.SetClockForTesting(clock)
   522  	c.Assert(err, jc.ErrorIsNil)
   523  
   524  	operationID, err := s.Model.EnqueueOperation("a test", 1)
   525  	c.Assert(err, jc.ErrorIsNil)
   526  	anAction, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil)
   527  	c.Assert(err, jc.ErrorIsNil)
   528  	c.Assert(anAction.Messages(), gc.HasLen, 0)
   529  
   530  	anAction, err = anAction.Begin()
   531  	c.Assert(err, jc.ErrorIsNil)
   532  
   533  	defer state.SetBeforeHooks(c, s.State, func() {
   534  		_, err = anAction.Finish(state.ActionResults{Status: state.ActionCompleted})
   535  		c.Assert(err, jc.ErrorIsNil)
   536  	})()
   537  
   538  	err = anAction.Log("hello")
   539  	c.Assert(err, gc.ErrorMatches, `cannot log message to task "2" with status completed`)
   540  }
   541  
   542  // makeUnits prepares units with given Action schemas
   543  func makeUnits(c *gc.C, s *ActionSuite, units map[string]*state.Unit, schemas map[string]string) {
   544  	// A few dummy charms that haven't been used yet
   545  	freeCharms := map[string]string{
   546  		"simple":      "mysql",
   547  		"complicated": "mysql-alternative",
   548  		"none":        "wordpress",
   549  	}
   550  
   551  	for name, schema := range schemas {
   552  		appName := name + "-defaults-application"
   553  
   554  		// Add a testing application
   555  		ch := s.AddActionsCharm(c, freeCharms[name], schema, 1)
   556  		app := s.AddTestingApplication(c, appName, ch)
   557  
   558  		// Get its charm URL
   559  		sURL, _ := app.CharmURL()
   560  		c.Assert(sURL, gc.NotNil)
   561  
   562  		// Add a unit
   563  		var err error
   564  		u, err := app.AddUnit(state.AddUnitParams{})
   565  		c.Assert(err, jc.ErrorIsNil)
   566  		c.Assert(u.Base(), jc.DeepEquals, state.Base{OS: "ubuntu", Channel: "12.10/stable"})
   567  		err = u.SetCharmURL(*sURL)
   568  		c.Assert(err, jc.ErrorIsNil)
   569  
   570  		units[name] = u
   571  	}
   572  }
   573  
   574  func (s *ActionSuite) TestEnqueueAction(c *gc.C) {
   575  	// verify can not enqueue an Action without a name
   576  	operationID, err := s.Model.EnqueueOperation("a test", 1)
   577  	c.Assert(err, jc.ErrorIsNil)
   578  	params := map[string]interface{}{"foo": "bar"}
   579  	a, err := s.model.EnqueueAction(operationID, s.unit.Tag(), "test", params, true, "group", nil)
   580  	c.Assert(err, jc.ErrorIsNil)
   581  	c.Assert(a.Name(), gc.Equals, "test")
   582  	c.Assert(a.Parameters(), jc.DeepEquals, params)
   583  	c.Assert(a.Receiver(), gc.Equals, s.unit.Name())
   584  	c.Assert(a.Parallel(), jc.IsTrue)
   585  	c.Assert(a.ExecutionGroup(), gc.Equals, "group")
   586  }
   587  
   588  func (s *ActionSuite) TestEnqueueActionRequiresName(c *gc.C) {
   589  	name := ""
   590  
   591  	// verify can not enqueue an Action without a name
   592  	operationID, err := s.Model.EnqueueOperation("a test", 1)
   593  	c.Assert(err, jc.ErrorIsNil)
   594  	_, err = s.model.EnqueueAction(operationID, s.unit.Tag(), name, nil, false, "", nil)
   595  	c.Assert(err, gc.ErrorMatches, "action name required")
   596  }
   597  
   598  func (s *ActionSuite) TestEnqueueActionRequiresValidOperation(c *gc.C) {
   599  	_, err := s.model.EnqueueAction("666", s.unit.Tag(), "test", nil, false, "", nil)
   600  	c.Assert(err, gc.ErrorMatches, `operation "666" not found`)
   601  }
   602  
   603  func (s *ActionSuite) TestAddActionAcceptsDuplicateNames(c *gc.C) {
   604  	name := "snapshot"
   605  	params1 := map[string]interface{}{"outfile": "outfile.tar.bz2"}
   606  	params2 := map[string]interface{}{"infile": "infile.zip"}
   607  
   608  	// verify can add two actions with same name
   609  	operationID, err := s.Model.EnqueueOperation("a test", 2)
   610  	c.Assert(err, jc.ErrorIsNil)
   611  	a1, err := s.Model.AddAction(s.unit, operationID, name, params1, nil, nil)
   612  	c.Assert(err, jc.ErrorIsNil)
   613  
   614  	a2, err := s.Model.AddAction(s.unit, operationID, name, params2, nil, nil)
   615  	c.Assert(err, jc.ErrorIsNil)
   616  
   617  	c.Assert(a1.Id(), gc.Not(gc.Equals), a2.Id())
   618  
   619  	// verify both actually got added
   620  	actions, err := s.unit.PendingActions()
   621  	c.Assert(err, jc.ErrorIsNil)
   622  	c.Assert(len(actions), gc.Equals, 2)
   623  
   624  	// verify we can Fail one, retrieve the other, and they're not mixed up
   625  	model, err := s.State.Model()
   626  	c.Assert(err, jc.ErrorIsNil)
   627  
   628  	action1, err := model.Action(a1.Id())
   629  	c.Assert(err, jc.ErrorIsNil)
   630  	_, err = action1.Finish(state.ActionResults{Status: state.ActionFailed})
   631  	c.Assert(err, jc.ErrorIsNil)
   632  
   633  	action2, err := model.Action(a2.Id())
   634  	c.Assert(err, jc.ErrorIsNil)
   635  	c.Assert(action2.Parameters(), jc.DeepEquals, params2)
   636  
   637  	// verify only one left, and it's the expected one
   638  	actions, err = s.unit.PendingActions()
   639  	c.Assert(err, jc.ErrorIsNil)
   640  	c.Assert(len(actions), gc.Equals, 1)
   641  	c.Assert(actions[0].Id(), gc.Equals, a2.Id())
   642  }
   643  
   644  func (s *ActionSuite) TestAddActionLifecycle(c *gc.C) {
   645  	unit, err := s.State.Unit(s.unit.Name())
   646  	c.Assert(err, jc.ErrorIsNil)
   647  	preventUnitDestroyRemove(c, unit)
   648  
   649  	// make unit state Dying
   650  	err = unit.Destroy()
   651  	c.Assert(err, jc.ErrorIsNil)
   652  
   653  	// can add action to a dying unit
   654  	operationID, err := s.Model.EnqueueOperation("a test", 2)
   655  	c.Assert(err, jc.ErrorIsNil)
   656  	_, err = s.Model.AddAction(unit, operationID, "snapshot", map[string]interface{}{}, nil, nil)
   657  	c.Assert(err, jc.ErrorIsNil)
   658  
   659  	// make sure unit is dead
   660  	err = unit.EnsureDead()
   661  	c.Assert(err, jc.ErrorIsNil)
   662  
   663  	// cannot add action to a dead unit
   664  	_, err = s.Model.AddAction(unit, operationID, "snapshot", map[string]interface{}{}, nil, nil)
   665  	c.Assert(err, gc.Equals, stateerrors.ErrDead)
   666  }
   667  
   668  func (s *ActionSuite) TestAddActionFailsOnDeadUnitInTransaction(c *gc.C) {
   669  	unit, err := s.State.Unit(s.unit.Name())
   670  	c.Assert(err, jc.ErrorIsNil)
   671  	preventUnitDestroyRemove(c, unit)
   672  
   673  	killUnit := jujutxn.TestHook{
   674  		Before: func() {
   675  			c.Assert(unit.Destroy(), gc.IsNil)
   676  			c.Assert(unit.EnsureDead(), gc.IsNil)
   677  		},
   678  	}
   679  	defer state.SetTestHooks(c, s.State, killUnit).Check()
   680  
   681  	operationID, err := s.Model.EnqueueOperation("a test", 1)
   682  	c.Assert(err, jc.ErrorIsNil)
   683  	_, err = s.Model.AddAction(unit, operationID, "snapshot", map[string]interface{}{}, nil, nil)
   684  	c.Assert(err, gc.Equals, stateerrors.ErrDead)
   685  }
   686  
   687  func (s *ActionSuite) TestFail(c *gc.C) {
   688  	// get unit, add an action, retrieve that action
   689  	unit, err := s.State.Unit(s.unit.Name())
   690  	c.Assert(err, jc.ErrorIsNil)
   691  	preventUnitDestroyRemove(c, unit)
   692  
   693  	operationID, err := s.Model.EnqueueOperation("a test", 1)
   694  	c.Assert(err, jc.ErrorIsNil)
   695  	a, err := s.Model.AddAction(unit, operationID, "snapshot", nil, nil, nil)
   696  	c.Assert(err, jc.ErrorIsNil)
   697  
   698  	model, err := s.State.Model()
   699  	c.Assert(err, jc.ErrorIsNil)
   700  
   701  	action, err := model.Action(a.Id())
   702  	c.Assert(err, jc.ErrorIsNil)
   703  
   704  	// ensure no action results for this action
   705  	results, err := unit.CompletedActions()
   706  	c.Assert(err, jc.ErrorIsNil)
   707  	c.Assert(len(results), gc.Equals, 0)
   708  
   709  	// fail the action, and verify that it succeeds
   710  	reason := "test fail reason"
   711  	result, err := action.Finish(state.ActionResults{Status: state.ActionFailed, Message: reason})
   712  	c.Assert(err, jc.ErrorIsNil)
   713  
   714  	// ensure we now have a result for this action
   715  	results, err = unit.CompletedActions()
   716  	c.Assert(err, jc.ErrorIsNil)
   717  	c.Assert(len(results), gc.Equals, 1)
   718  	c.Assert(results[0], gc.DeepEquals, result)
   719  
   720  	c.Assert(results[0].Name(), gc.Equals, action.Name())
   721  	c.Assert(results[0].Status(), gc.Equals, state.ActionFailed)
   722  
   723  	// Verify the Action Completed time was within a reasonable
   724  	// time of the Enqueued time.
   725  	diff := results[0].Completed().Sub(action.Enqueued())
   726  	c.Assert(diff >= 0, jc.IsTrue)
   727  	c.Assert(diff < coretesting.LongWait, jc.IsTrue)
   728  
   729  	res, errstr := results[0].Results()
   730  	c.Assert(errstr, gc.Equals, reason)
   731  	c.Assert(res, gc.DeepEquals, map[string]interface{}{})
   732  
   733  	// validate that a pending action is no longer returned by UnitActions.
   734  	actions, err := unit.PendingActions()
   735  	c.Assert(err, jc.ErrorIsNil)
   736  	c.Assert(len(actions), gc.Equals, 0)
   737  }
   738  
   739  func (s *ActionSuite) TestErrorAfterEnqueuingFail(c *gc.C) {
   740  	// get unit, add an action, retrieve that action
   741  	unit, err := s.State.Unit(s.unit.Name())
   742  	c.Assert(err, jc.ErrorIsNil)
   743  	preventUnitDestroyRemove(c, unit)
   744  
   745  	unit2, err := s.State.Unit(s.unit2.Name())
   746  	c.Assert(err, jc.ErrorIsNil)
   747  	preventUnitDestroyRemove(c, unit2)
   748  
   749  	operationID, err := s.Model.EnqueueOperation("enqueuing test", 3)
   750  	c.Assert(err, jc.ErrorIsNil)
   751  	a, err := s.Model.AddAction(unit, operationID, "snapshot", nil, nil, nil)
   752  	c.Assert(err, jc.ErrorIsNil)
   753  	a2, err := s.Model.AddAction(unit2, operationID, "snapshot", nil, nil, nil)
   754  	c.Assert(err, jc.ErrorIsNil)
   755  
   756  	err = s.model.FailOperationEnqueuing(operationID, "fail for test", 2)
   757  	c.Assert(err, jc.ErrorIsNil)
   758  
   759  	model, err := s.State.Model()
   760  	c.Assert(err, jc.ErrorIsNil)
   761  
   762  	action, err := model.Action(a.Id())
   763  	c.Assert(err, jc.ErrorIsNil)
   764  	action2, err := model.Action(a2.Id())
   765  	c.Assert(err, jc.ErrorIsNil)
   766  
   767  	// complete the action, and verify that it succeeds
   768  	output := map[string]interface{}{"output": "action ran successfully"}
   769  	_, err = action.Finish(state.ActionResults{Status: state.ActionCompleted, Results: output})
   770  	c.Assert(err, jc.ErrorIsNil)
   771  	_, err = action2.Finish(state.ActionResults{Status: state.ActionCompleted, Results: output})
   772  	c.Assert(err, jc.ErrorIsNil)
   773  
   774  	operation, err := s.model.Operation(operationID)
   775  	c.Assert(err, jc.ErrorIsNil)
   776  	c.Assert(operation.Status(), gc.Equals, state.ActionError)
   777  	c.Assert(operation.Fail(), gc.Equals, "fail for test")
   778  }
   779  
   780  func (s *ActionSuite) TestComplete(c *gc.C) {
   781  	// get unit, add an action, retrieve that action
   782  	unit, err := s.State.Unit(s.unit.Name())
   783  	c.Assert(err, jc.ErrorIsNil)
   784  	preventUnitDestroyRemove(c, unit)
   785  
   786  	operationID, err := s.Model.EnqueueOperation("a test", 1)
   787  	c.Assert(err, jc.ErrorIsNil)
   788  	a, err := s.Model.AddAction(unit, operationID, "snapshot", nil, nil, nil)
   789  	c.Assert(err, jc.ErrorIsNil)
   790  
   791  	model, err := s.State.Model()
   792  	c.Assert(err, jc.ErrorIsNil)
   793  
   794  	action, err := model.Action(a.Id())
   795  	c.Assert(err, jc.ErrorIsNil)
   796  
   797  	// ensure no action results for this action
   798  	results, err := unit.CompletedActions()
   799  	c.Assert(err, jc.ErrorIsNil)
   800  	c.Assert(len(results), gc.Equals, 0)
   801  
   802  	// complete the action, and verify that it succeeds
   803  	output := map[string]interface{}{"output": "action ran successfully"}
   804  	result, err := action.Finish(state.ActionResults{Status: state.ActionCompleted, Results: output})
   805  	c.Assert(err, jc.ErrorIsNil)
   806  
   807  	// ensure we now have a result for this action
   808  	results, err = unit.CompletedActions()
   809  	c.Assert(err, jc.ErrorIsNil)
   810  	c.Assert(len(results), gc.Equals, 1)
   811  	c.Assert(results[0], gc.DeepEquals, result)
   812  
   813  	c.Assert(results[0].Name(), gc.Equals, action.Name())
   814  	c.Assert(results[0].Status(), gc.Equals, state.ActionCompleted)
   815  	res, errstr := results[0].Results()
   816  	c.Assert(errstr, gc.Equals, "")
   817  	c.Assert(res, gc.DeepEquals, output)
   818  
   819  	// validate that a pending action is no longer returned by UnitActions.
   820  	actions, err := unit.PendingActions()
   821  	c.Assert(err, jc.ErrorIsNil)
   822  	c.Assert(len(actions), gc.Equals, 0)
   823  }
   824  
   825  func (s *ActionSuite) TestFindActionsByName(c *gc.C) {
   826  	actions := []struct {
   827  		Name       string
   828  		Parameters map[string]interface{}
   829  	}{
   830  		{Name: "action-1", Parameters: map[string]interface{}{}},
   831  		{Name: "fake", Parameters: map[string]interface{}{"yeah": true, "take": nil}},
   832  		{Name: "action-1", Parameters: map[string]interface{}{"yeah": true, "take": nil}},
   833  		{Name: "action-9", Parameters: map[string]interface{}{"district": 9}},
   834  		{Name: "blarney", Parameters: map[string]interface{}{"conversation": []string{"what", "now"}}},
   835  	}
   836  
   837  	operationID, err := s.Model.EnqueueOperation("a test", len(actions))
   838  	c.Assert(err, jc.ErrorIsNil)
   839  	for _, action := range actions {
   840  		_, err := s.model.EnqueueAction(operationID, s.unit.Tag(), action.Name, action.Parameters, false, "", nil)
   841  		c.Assert(err, gc.Equals, nil)
   842  	}
   843  
   844  	results, err := s.model.FindActionsByName("action-1")
   845  	c.Assert(err, jc.ErrorIsNil)
   846  
   847  	c.Assert(len(results), gc.Equals, 2)
   848  	for _, result := range results {
   849  		c.Check(result.Name(), gc.Equals, "action-1")
   850  	}
   851  }
   852  
   853  func (s *ActionSuite) TestActionsWatcherEmitsInitialChanges(c *gc.C) {
   854  	// LP-1391914 :: idPrefixWatcher fails watcher contract to send
   855  	// initial Change event
   856  	//
   857  	// state/idPrefixWatcher does not send an initial event in response
   858  	// to the first time Changes() is called if all of the pending
   859  	// events are removed before the first consumption of Changes().
   860  	// The watcher contract specifies that the first call to Changes()
   861  	// should always return at a minimum an empty change set to notify
   862  	// clients of it's initial state
   863  
   864  	// preamble
   865  	app := s.AddTestingApplication(c, "dummy3", s.charm)
   866  	unit, err := app.AddUnit(state.AddUnitParams{})
   867  	c.Assert(err, jc.ErrorIsNil)
   868  	u, err := s.State.Unit(unit.Name())
   869  	c.Assert(err, jc.ErrorIsNil)
   870  	preventUnitDestroyRemove(c, u)
   871  
   872  	operationID, err := s.Model.EnqueueOperation("a test", 2)
   873  	c.Assert(err, jc.ErrorIsNil)
   874  	// queue up actions
   875  	a1, err := s.Model.AddAction(u, operationID, "snapshot", nil, nil, nil)
   876  	c.Assert(err, jc.ErrorIsNil)
   877  	a2, err := s.Model.AddAction(u, operationID, "snapshot", nil, nil, nil)
   878  	c.Assert(err, jc.ErrorIsNil)
   879  
   880  	// start watcher but don't consume Changes() yet
   881  	w := u.WatchPendingActionNotifications()
   882  	defer statetesting.AssertStop(c, w)
   883  	wc := statetesting.NewStringsWatcherC(c, w)
   884  
   885  	// remove actions
   886  	reason := "removed"
   887  	_, err = a1.Finish(state.ActionResults{Status: state.ActionFailed, Message: reason})
   888  	c.Assert(err, jc.ErrorIsNil)
   889  	_, err = a2.Finish(state.ActionResults{Status: state.ActionFailed, Message: reason})
   890  	c.Assert(err, jc.ErrorIsNil)
   891  
   892  	// per contract, there should be at minimum an initial empty Change() result
   893  	wc.AssertChangeMaybeIncluding(expectActionIds(a1, a2)...)
   894  	wc.AssertNoChange()
   895  }
   896  
   897  func (s *ActionSuite) TestUnitWatchActionNotifications(c *gc.C) {
   898  	// get units
   899  	unit1, err := s.State.Unit(s.unit.Name())
   900  	c.Assert(err, jc.ErrorIsNil)
   901  	preventUnitDestroyRemove(c, unit1)
   902  
   903  	unit2, err := s.State.Unit(s.unit2.Name())
   904  	c.Assert(err, jc.ErrorIsNil)
   905  	preventUnitDestroyRemove(c, unit2)
   906  
   907  	// queue some actions before starting the watcher
   908  	operationID, err := s.Model.EnqueueOperation("a test", 2)
   909  	c.Assert(err, jc.ErrorIsNil)
   910  	fa1, err := s.Model.AddAction(unit1, operationID, "snapshot", nil, nil, nil)
   911  	c.Assert(err, jc.ErrorIsNil)
   912  	fa2, err := s.Model.AddAction(unit1, operationID, "snapshot", nil, nil, nil)
   913  	c.Assert(err, jc.ErrorIsNil)
   914  	s.WaitForModelWatchersIdle(c, s.State.ModelUUID())
   915  
   916  	// set up watcher on first unit
   917  	w := unit1.WatchPendingActionNotifications()
   918  	defer statetesting.AssertStop(c, w)
   919  	wc := statetesting.NewStringsWatcherC(c, w)
   920  	// make sure the previously pending actions are sent on the watcher
   921  	expect := expectActionIds(fa1, fa2)
   922  	wc.AssertChange(expect...)
   923  	wc.AssertNoChange()
   924  
   925  	// add watcher on unit2
   926  	w2 := unit2.WatchPendingActionNotifications()
   927  	defer statetesting.AssertStop(c, w2)
   928  	wc2 := statetesting.NewStringsWatcherC(c, w2)
   929  	wc2.AssertChange()
   930  	wc2.AssertNoChange()
   931  
   932  	// add action on unit2 and makes sure unit1 watcher doesn't trigger
   933  	// and unit2 watcher does
   934  	fa3, err := s.Model.AddAction(unit2, operationID, "snapshot", nil, nil, nil)
   935  	c.Assert(err, jc.ErrorIsNil)
   936  	wc.AssertNoChange()
   937  	expect2 := expectActionIds(fa3)
   938  	wc2.AssertChange(expect2...)
   939  	wc2.AssertNoChange()
   940  
   941  	// add a couple actions on unit1 and make sure watcher sees events
   942  	fa4, err := s.Model.AddAction(unit1, operationID, "snapshot", nil, nil, nil)
   943  	c.Assert(err, jc.ErrorIsNil)
   944  	fa5, err := s.Model.AddAction(unit1, operationID, "snapshot", nil, nil, nil)
   945  	c.Assert(err, jc.ErrorIsNil)
   946  
   947  	expect = expectActionIds(fa4, fa5)
   948  	wc.AssertChange(expect...)
   949  	wc.AssertNoChange()
   950  }
   951  
   952  func (s *ActionSuite) TestMergeIds(c *gc.C) {
   953  	var tests = []struct {
   954  		changes  string
   955  		adds     string
   956  		removes  string
   957  		expected string
   958  	}{
   959  		{changes: "", adds: "a0,a1", removes: "", expected: "a0,a1"},
   960  		{changes: "a0,a1", adds: "", removes: "a0", expected: "a1"},
   961  		{changes: "a0,a1", adds: "a2", removes: "a0", expected: "a1,a2"},
   962  
   963  		{changes: "", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"},
   964  		{changes: "", adds: "a0,a1,a2", removes: "a0,a1,a2", expected: ""},
   965  
   966  		{changes: "a0", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"},
   967  		{changes: "a1", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"},
   968  		{changes: "a2", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"},
   969  
   970  		{changes: "a3,a4", adds: "a1,a4,a5", removes: "a1,a3", expected: "a4,a5"},
   971  		{changes: "a0,a1,a2", adds: "a1,a4,a5", removes: "a1,a3", expected: "a0,a2,a4,a5"},
   972  	}
   973  
   974  	prefix := state.DocID(s.State, "")
   975  
   976  	for ix, test := range tests {
   977  		updates := mapify(prefix, test.adds, test.removes)
   978  		changes := sliceify("", test.changes)
   979  		expected := sliceify("", test.expected)
   980  
   981  		c.Log(fmt.Sprintf("test number %d %#v", ix, test))
   982  		err := state.WatcherMergeIds(&changes, updates, state.MakeActionIdConverter(s.State))
   983  		c.Assert(err, jc.ErrorIsNil)
   984  		c.Assert(changes, jc.SameContents, expected)
   985  	}
   986  }
   987  
   988  func (s *ActionSuite) TestMergeIdsErrors(c *gc.C) {
   989  
   990  	var tests = []struct {
   991  		name string
   992  		key  interface{}
   993  	}{
   994  		{name: "bool", key: true},
   995  		{name: "int", key: 0},
   996  		{name: "chan string", key: make(chan string)},
   997  	}
   998  
   999  	for _, test := range tests {
  1000  		changes, updates := []string{}, map[interface{}]bool{}
  1001  		updates[test.key] = true
  1002  		err := state.WatcherMergeIds(&changes, updates, state.MakeActionIdConverter(s.State))
  1003  		c.Assert(err, gc.ErrorMatches, "id is not of type string, got "+test.name)
  1004  	}
  1005  }
  1006  
  1007  func (s *ActionSuite) TestEnsureSuffix(c *gc.C) {
  1008  	marker := "-marker-"
  1009  	fn := state.WatcherEnsureSuffixFn(marker)
  1010  	c.Assert(fn, gc.Not(gc.IsNil))
  1011  
  1012  	var tests = []struct {
  1013  		given  string
  1014  		expect string
  1015  	}{
  1016  		{given: marker, expect: marker},
  1017  		{given: "", expect: "" + marker},
  1018  		{given: "asdf", expect: "asdf" + marker},
  1019  		{given: "asdf" + marker, expect: "asdf" + marker},
  1020  		{given: "asdf" + marker + "qwerty", expect: "asdf" + marker + "qwerty" + marker},
  1021  	}
  1022  
  1023  	for _, test := range tests {
  1024  		c.Assert(fn(test.given), gc.Equals, test.expect)
  1025  	}
  1026  }
  1027  
  1028  func (s *ActionSuite) TestMakeIdFilter(c *gc.C) {
  1029  	marker := "-marker-"
  1030  	badmarker := "-bad-"
  1031  	fn := state.WatcherMakeIdFilter(s.State, marker)
  1032  	c.Assert(fn, gc.IsNil)
  1033  
  1034  	ar1 := mockAR{id: "mock/1"}
  1035  	ar2 := mockAR{id: "mock/2"}
  1036  	fn = state.WatcherMakeIdFilter(s.State, marker, ar1, ar2)
  1037  	c.Assert(fn, gc.Not(gc.IsNil))
  1038  
  1039  	var tests = []struct {
  1040  		id    string
  1041  		match bool
  1042  	}{
  1043  		{id: "mock/1" + marker + "", match: true},
  1044  		{id: "mock/1" + marker + "asdf", match: true},
  1045  		{id: "mock/2" + marker + "", match: true},
  1046  		{id: "mock/2" + marker + "asdf", match: true},
  1047  
  1048  		{id: "mock/1" + badmarker + "", match: false},
  1049  		{id: "mock/1" + badmarker + "asdf", match: false},
  1050  		{id: "mock/2" + badmarker + "", match: false},
  1051  		{id: "mock/2" + badmarker + "asdf", match: false},
  1052  
  1053  		{id: "mock/1" + marker + "0", match: true},
  1054  		{id: "mock/10" + marker + "0", match: false},
  1055  		{id: "mock/2" + marker + "0", match: true},
  1056  		{id: "mock/20" + marker + "0", match: false},
  1057  		{id: "mock" + marker + "0", match: false},
  1058  
  1059  		{id: "" + marker + "0", match: false},
  1060  		{id: "mock/1-0", match: false},
  1061  		{id: "mock/1-0", match: false},
  1062  	}
  1063  
  1064  	for _, test := range tests {
  1065  		c.Assert(fn(state.DocID(s.State, test.id)), gc.Equals, test.match)
  1066  	}
  1067  }
  1068  
  1069  func (s *ActionSuite) TestWatchActionNotifications(c *gc.C) {
  1070  	app := s.AddTestingApplication(c, "dummy2", s.charm)
  1071  	u, err := app.AddUnit(state.AddUnitParams{})
  1072  	c.Assert(err, jc.ErrorIsNil)
  1073  
  1074  	w := u.WatchPendingActionNotifications()
  1075  	defer statetesting.AssertStop(c, w)
  1076  	wc := statetesting.NewStringsWatcherC(c, w)
  1077  	wc.AssertChange()
  1078  	wc.AssertNoChange()
  1079  
  1080  	// add 3 actions
  1081  	operationID, err := s.Model.EnqueueOperation("a test", 3)
  1082  	c.Assert(err, jc.ErrorIsNil)
  1083  	fa1, err := s.Model.AddAction(u, operationID, "snapshot", nil, nil, nil)
  1084  	c.Assert(err, jc.ErrorIsNil)
  1085  	fa2, err := s.Model.AddAction(u, operationID, "snapshot", nil, nil, nil)
  1086  	c.Assert(err, jc.ErrorIsNil)
  1087  	fa3, err := s.Model.AddAction(u, operationID, "snapshot", nil, nil, nil)
  1088  	c.Assert(err, jc.ErrorIsNil)
  1089  
  1090  	model, err := s.State.Model()
  1091  	c.Assert(err, jc.ErrorIsNil)
  1092  
  1093  	// TODO(quiescence): this is a bit racey due to the unpredictable nature of mongo change streams
  1094  	// once we have some quiescence built into the watcher, we can re-enable this.
  1095  	// fail the middle one
  1096  	_ = model
  1097  	// action, err := model.Action(fa2.Id())
  1098  	// c.Assert(err, jc.ErrorIsNil)
  1099  	// _, err = action.Finish(state.ActionResults{Status: state.ActionFailed, Message: "die scum"})
  1100  	// c.Assert(err, jc.ErrorIsNil)
  1101  
  1102  	// we expect them all even though the second one has already failed.
  1103  	// TODO(quiescence): reimplement some quiescence on the PendingActionNotications watcher
  1104  	expect := expectActionIds(fa1, fa2, fa3)
  1105  	wc.AssertChange(expect...)
  1106  	wc.AssertNoChange()
  1107  }
  1108  
  1109  func expectActionIds(actions ...state.Action) []string {
  1110  	ids := make([]string, len(actions))
  1111  	for i, action := range actions {
  1112  		ids[i] = action.Id()
  1113  	}
  1114  	return ids
  1115  }
  1116  
  1117  func (s *ActionSuite) TestWatchActionLogs(c *gc.C) {
  1118  	unit1, err := s.State.Unit(s.unit.Name())
  1119  	c.Assert(err, jc.ErrorIsNil)
  1120  
  1121  	operationID, err := s.Model.EnqueueOperation("a test", 1)
  1122  	c.Assert(err, jc.ErrorIsNil)
  1123  	// queue some actions before starting the watcher
  1124  	fa1, err := s.Model.AddAction(unit1, operationID, "snapshot", nil, nil, nil)
  1125  	c.Assert(err, jc.ErrorIsNil)
  1126  	fa1, err = fa1.Begin()
  1127  	c.Assert(err, jc.ErrorIsNil)
  1128  	err = fa1.Log("first")
  1129  	c.Assert(err, jc.ErrorIsNil)
  1130  
  1131  	// Ensure no cross contamination - add another action.
  1132  	fa2, err := s.Model.AddAction(unit1, operationID, "snapshot", nil, nil, nil)
  1133  	c.Assert(err, jc.ErrorIsNil)
  1134  	fa2, err = fa2.Begin()
  1135  	c.Assert(err, jc.ErrorIsNil)
  1136  	err = fa2.Log("another")
  1137  	c.Assert(err, jc.ErrorIsNil)
  1138  
  1139  	s.WaitForModelWatchersIdle(c, s.State.ModelUUID())
  1140  
  1141  	startNow := time.Now().UTC()
  1142  	makeTimestamp := func(offset time.Duration) time.Time {
  1143  		return time.Unix(0, startNow.UnixNano()).Add(offset).UTC()
  1144  	}
  1145  
  1146  	checkExpected := func(wc statetesting.StringsWatcherC, expected []actions.ActionMessage) {
  1147  		var ch []string
  1148  		for len(ch) < len(expected) {
  1149  			select {
  1150  			case changes := <-wc.Watcher.Changes():
  1151  				ch = append(ch, changes...)
  1152  			case <-time.After(coretesting.LongWait):
  1153  				c.Fatalf("watcher did not send change")
  1154  			}
  1155  		}
  1156  		var msg []actions.ActionMessage
  1157  		for i, chStr := range ch {
  1158  			var gotMessage actions.ActionMessage
  1159  			err := json.Unmarshal([]byte(chStr), &gotMessage)
  1160  			c.Assert(err, jc.ErrorIsNil)
  1161  			// We can't control the actual time so check for
  1162  			// not nil and then assigned to a known value.
  1163  			c.Assert(gotMessage.Timestamp, gc.NotNil)
  1164  			gotMessage.Timestamp = makeTimestamp(time.Duration(i) * time.Second)
  1165  			msg = append(msg, gotMessage)
  1166  		}
  1167  		c.Assert(msg, jc.DeepEquals, expected)
  1168  		wc.AssertNoChange()
  1169  	}
  1170  
  1171  	w := s.State.WatchActionLogs(fa1.Id())
  1172  	defer statetesting.AssertStop(c, w)
  1173  	wc := statetesting.NewStringsWatcherC(c, w)
  1174  	// make sure the previously pending actions are sent on the watcher
  1175  	expected := []actions.ActionMessage{{
  1176  		Timestamp: startNow,
  1177  		Message:   "first",
  1178  	}}
  1179  	checkExpected(wc, expected)
  1180  
  1181  	// Add 3 more messages; we should only see those on this watcher.
  1182  	err = fa1.Log("another")
  1183  	c.Assert(err, jc.ErrorIsNil)
  1184  
  1185  	err = fa1.Log("yet another")
  1186  	c.Assert(err, jc.ErrorIsNil)
  1187  
  1188  	expected = []actions.ActionMessage{{
  1189  		Timestamp: startNow,
  1190  		Message:   "another",
  1191  	}, {
  1192  		Timestamp: makeTimestamp(1 * time.Second),
  1193  		Message:   "yet another",
  1194  	}}
  1195  	checkExpected(wc, expected)
  1196  
  1197  	// Add the 3rd message separately to ensure the
  1198  	// tracking of already reported messages works.
  1199  	err = fa1.Log("and yet another")
  1200  	c.Assert(err, jc.ErrorIsNil)
  1201  	expected = []actions.ActionMessage{{
  1202  		Timestamp: makeTimestamp(0 * time.Second),
  1203  		Message:   "and yet another",
  1204  	}}
  1205  	checkExpected(wc, expected)
  1206  
  1207  	// But on a new watcher we see all 3 events.
  1208  	w2 := s.State.WatchActionLogs(fa1.Id())
  1209  	defer statetesting.AssertStop(c, w)
  1210  	wc2 := statetesting.NewStringsWatcherC(c, w2)
  1211  	// Make sure the previously pending actions are sent on the watcher.
  1212  	expected = []actions.ActionMessage{{
  1213  		Timestamp: startNow,
  1214  		Message:   "first",
  1215  	}, {
  1216  		Timestamp: makeTimestamp(1 * time.Second),
  1217  		Message:   "another",
  1218  	}, {
  1219  		Timestamp: makeTimestamp(2 * time.Second),
  1220  		Message:   "yet another",
  1221  	}, {
  1222  		Timestamp: makeTimestamp(3 * time.Second),
  1223  		Message:   "and yet another",
  1224  	}}
  1225  	checkExpected(wc2, expected)
  1226  }
  1227  
  1228  // mapify is a convenience method, also to make reading the tests
  1229  // easier. It combines two comma delimited strings representing
  1230  // additions and removals and turns it into the map[interface{}]bool
  1231  // format needed
  1232  func mapify(prefix, adds, removes string) map[interface{}]bool {
  1233  	m := map[interface{}]bool{}
  1234  	for _, v := range sliceify(prefix, adds) {
  1235  		m[v] = true
  1236  	}
  1237  	for _, v := range sliceify(prefix, removes) {
  1238  		m[v] = false
  1239  	}
  1240  	return m
  1241  }
  1242  
  1243  // sliceify turns a comma separated list of strings into a slice
  1244  // trimming white space and excluding empty strings.
  1245  func sliceify(prefix, csvlist string) []string {
  1246  	slice := []string{}
  1247  	if csvlist == "" {
  1248  		return slice
  1249  	}
  1250  	for _, entry := range strings.Split(csvlist, ",") {
  1251  		clean := strings.TrimSpace(entry)
  1252  		if clean != "" {
  1253  			slice = append(slice, prefix+clean)
  1254  		}
  1255  	}
  1256  	return slice
  1257  }
  1258  
  1259  // mockAR is an implementation of ActionReceiver that can be used for
  1260  // testing that requires the ActionReceiver.Tag() call to return a
  1261  // names.Tag
  1262  type mockAR struct {
  1263  	id string
  1264  }
  1265  
  1266  var _ state.ActionReceiver = (*mockAR)(nil)
  1267  
  1268  func (r mockAR) PrepareActionPayload(_ string, _ map[string]interface{}, _ *bool, _ *string) (map[string]interface{}, bool, string, error) {
  1269  	return nil, false, "", nil
  1270  }
  1271  func (r mockAR) CancelAction(_ state.Action) (state.Action, error)     { return nil, nil }
  1272  func (r mockAR) WatchActionNotifications() state.StringsWatcher        { return nil }
  1273  func (r mockAR) WatchPendingActionNotifications() state.StringsWatcher { return nil }
  1274  func (r mockAR) Actions() ([]state.Action, error)                      { return nil, nil }
  1275  func (r mockAR) CompletedActions() ([]state.Action, error)             { return nil, nil }
  1276  func (r mockAR) PendingActions() ([]state.Action, error)               { return nil, nil }
  1277  func (r mockAR) RunningActions() ([]state.Action, error)               { return nil, nil }
  1278  func (r mockAR) Tag() names.Tag                                        { return names.NewUnitTag(r.id) }
  1279  
  1280  type ActionPruningSuite struct {
  1281  	statetesting.StateWithWallClockSuite
  1282  }
  1283  
  1284  var _ = gc.Suite(&ActionPruningSuite{})
  1285  
  1286  func (s *ActionPruningSuite) TestPruneOperationsBySize(c *gc.C) {
  1287  	clock := testclock.NewClock(coretesting.NonZeroTime())
  1288  	err := s.State.SetClockForTesting(clock)
  1289  	c.Assert(err, jc.ErrorIsNil)
  1290  	application := s.Factory.MakeApplication(c, nil)
  1291  	unit := s.Factory.MakeUnit(c, &factory.UnitParams{Application: application})
  1292  
  1293  	// PrimeOperations generates the operations and tasks to be pruned.
  1294  	const numOperationEntries = 15 // At slightly > 500kB per entry
  1295  	const tasksPerOperation = 2
  1296  	const maxLogSize = 5 //MB
  1297  	state.PrimeOperations(c, clock.Now(), unit, numOperationEntries, tasksPerOperation)
  1298  
  1299  	actions, err := unit.Actions()
  1300  	c.Assert(err, jc.ErrorIsNil)
  1301  	c.Assert(actions, gc.HasLen, tasksPerOperation*numOperationEntries)
  1302  	ops, err := s.Model.AllOperations()
  1303  	c.Assert(err, jc.ErrorIsNil)
  1304  	c.Assert(ops, gc.HasLen, numOperationEntries)
  1305  
  1306  	var stop <-chan struct{}
  1307  	err = state.PruneOperations(stop, s.State, 0, maxLogSize)
  1308  	c.Assert(err, jc.ErrorIsNil)
  1309  
  1310  	actions, err = unit.Actions()
  1311  	c.Assert(err, jc.ErrorIsNil)
  1312  	ops, err = s.Model.AllOperations()
  1313  	c.Assert(err, jc.ErrorIsNil)
  1314  
  1315  	// The test here is to see if the remaining count is relatively close to
  1316  	// the max log size x 2. I would expect the number of remaining entries to
  1317  	// be no greater than 2 x 1.5 x the max log size in MB since each entry is
  1318  	// about 500kB (in memory) in size. 1.5x is probably good enough to ensure
  1319  	// this test doesn't flake.
  1320  	c.Assert(float64(len(actions)), jc.LessThan, 2.0*maxLogSize*1.5)
  1321  	c.Assert(float64(len(ops)), gc.Equals, float64(len(actions))/2.0)
  1322  }
  1323  
  1324  func (s *ActionPruningSuite) TestPruneOperationsBySizeOldestFirst(c *gc.C) {
  1325  	clock := testclock.NewClock(coretesting.NonZeroTime())
  1326  	err := s.State.SetClockForTesting(clock)
  1327  	c.Assert(err, jc.ErrorIsNil)
  1328  	application := s.Factory.MakeApplication(c, nil)
  1329  	unit := s.Factory.MakeUnit(c, &factory.UnitParams{Application: application})
  1330  
  1331  	const numOperationEntriesOlder = 5
  1332  	const numOperationEntriesYounger = 5
  1333  	const tasksPerOperation = 3
  1334  	const numOperationEntries = numOperationEntriesOlder + numOperationEntriesYounger
  1335  	const maxLogSize = 5 //MB
  1336  
  1337  	olderTime := clock.Now().Add(-1 * time.Hour)
  1338  	youngerTime := clock.Now()
  1339  
  1340  	state.PrimeOperations(c, olderTime, unit, numOperationEntriesOlder, tasksPerOperation)
  1341  	state.PrimeOperations(c, youngerTime, unit, numOperationEntriesYounger, tasksPerOperation)
  1342  
  1343  	actions, err := unit.Actions()
  1344  	c.Assert(err, jc.ErrorIsNil)
  1345  	c.Assert(actions, gc.HasLen, tasksPerOperation*numOperationEntries)
  1346  	ops, err := s.Model.AllOperations()
  1347  	c.Assert(err, jc.ErrorIsNil)
  1348  	c.Assert(ops, gc.HasLen, numOperationEntries)
  1349  
  1350  	var stop <-chan struct{}
  1351  	err = state.PruneOperations(stop, s.State, 0, maxLogSize)
  1352  	c.Assert(err, jc.ErrorIsNil)
  1353  
  1354  	actions, err = unit.Actions()
  1355  	c.Assert(err, jc.ErrorIsNil)
  1356  
  1357  	var olderEntries []time.Time
  1358  	var youngerEntries []time.Time
  1359  	for _, entry := range actions {
  1360  		if entry.Completed().Before(youngerTime.Round(time.Second)) {
  1361  			olderEntries = append(olderEntries, entry.Completed())
  1362  		} else {
  1363  			youngerEntries = append(youngerEntries, entry.Completed())
  1364  		}
  1365  	}
  1366  	c.Assert(len(youngerEntries), jc.GreaterThan, len(olderEntries))
  1367  
  1368  	_, err = s.Model.AllOperations()
  1369  	c.Assert(err, jc.ErrorIsNil)
  1370  	olderEntries = nil
  1371  	youngerEntries = nil
  1372  	for _, entry := range actions {
  1373  		if entry.Completed().Before(youngerTime.Round(time.Second)) {
  1374  			olderEntries = append(olderEntries, entry.Completed())
  1375  		} else {
  1376  			youngerEntries = append(youngerEntries, entry.Completed())
  1377  		}
  1378  	}
  1379  	c.Assert(len(youngerEntries), jc.GreaterThan, len(olderEntries))
  1380  }
  1381  
  1382  func (s *ActionPruningSuite) TestPruneOperationsBySizeKeepsIncomplete(c *gc.C) {
  1383  	clock := testclock.NewClock(coretesting.NonZeroTime())
  1384  	err := s.State.SetClockForTesting(clock)
  1385  	c.Assert(err, jc.ErrorIsNil)
  1386  	application := s.Factory.MakeApplication(c, nil)
  1387  	unit := s.Factory.MakeUnit(c, &factory.UnitParams{Application: application})
  1388  
  1389  	const numOperationEntriesOlder = 5
  1390  	const numOperationEntriesYounger = 5
  1391  	const numOperationEntriesIncomplete = 5
  1392  	const tasksPerOperation = 3
  1393  	const numOperationEntries = numOperationEntriesOlder + numOperationEntriesYounger + numOperationEntriesIncomplete
  1394  	const maxLogSize = 5 //MB
  1395  
  1396  	olderTime := clock.Now().Add(-1 * time.Hour)
  1397  	youngerTime := clock.Now()
  1398  
  1399  	state.PrimeOperations(c, time.Time{}, unit, numOperationEntriesIncomplete, tasksPerOperation)
  1400  	state.PrimeOperations(c, olderTime, unit, numOperationEntriesOlder, tasksPerOperation)
  1401  	state.PrimeOperations(c, youngerTime, unit, numOperationEntriesYounger, tasksPerOperation)
  1402  
  1403  	actions, err := unit.Actions()
  1404  	c.Assert(err, jc.ErrorIsNil)
  1405  	c.Assert(actions, gc.HasLen, tasksPerOperation*numOperationEntries)
  1406  	ops, err := s.Model.AllOperations()
  1407  	c.Assert(err, jc.ErrorIsNil)
  1408  	c.Assert(ops, gc.HasLen, numOperationEntries)
  1409  
  1410  	var stop <-chan struct{}
  1411  	err = state.PruneOperations(stop, s.State, 0, maxLogSize)
  1412  	c.Assert(err, jc.ErrorIsNil)
  1413  
  1414  	actions, err = unit.Actions()
  1415  	c.Assert(err, jc.ErrorIsNil)
  1416  
  1417  	var olderEntries []time.Time
  1418  	var youngerEntries []time.Time
  1419  	var incompleteEntries []time.Time
  1420  	zero := time.Time{}
  1421  	for _, entry := range actions {
  1422  		if entry.Completed() == zero {
  1423  			incompleteEntries = append(incompleteEntries, entry.Completed())
  1424  		} else if entry.Completed().Before(youngerTime.Round(time.Second)) {
  1425  			olderEntries = append(olderEntries, entry.Completed())
  1426  		} else {
  1427  			youngerEntries = append(youngerEntries, entry.Completed())
  1428  		}
  1429  	}
  1430  	c.Assert(youngerEntries, gc.HasLen, 0)
  1431  	c.Assert(olderEntries, gc.HasLen, 0)
  1432  	c.Assert(len(incompleteEntries), gc.Not(gc.Equals), 0)
  1433  
  1434  	ops, err = s.Model.AllOperations()
  1435  	c.Assert(err, jc.ErrorIsNil)
  1436  
  1437  	// The test here is to see if the remaining count is relatively close to
  1438  	// the max log size x 2. I would expect the number of remaining entries to
  1439  	// be no greater than 2 x 1.5 x the max log size in MB since each entry is
  1440  	// about 500kB (in memory) in size. 1.5x is probably good enough to ensure
  1441  	// this test doesn't flake.
  1442  	c.Assert(float64(len(actions)), jc.LessThan, 2.0*maxLogSize*1.5)
  1443  	c.Assert(float64(len(ops)), gc.Equals, float64(len(actions))/3.0)
  1444  }
  1445  
  1446  func (s *ActionPruningSuite) TestPruneOperationsByAge(c *gc.C) {
  1447  	clock := testclock.NewClock(time.Now())
  1448  	err := s.State.SetClockForTesting(clock)
  1449  	c.Assert(err, jc.ErrorIsNil)
  1450  	application := s.Factory.MakeApplication(c, nil)
  1451  	unit := s.Factory.MakeUnit(c, &factory.UnitParams{Application: application})
  1452  
  1453  	const numCurrentOperationEntries = 5
  1454  	const numExpiredOperationEntries = 5
  1455  	const tasksPerOperation = 3
  1456  	const ageOfExpired = 10 * time.Hour
  1457  
  1458  	state.PrimeOperations(c, clock.Now(), unit, numCurrentOperationEntries, tasksPerOperation)
  1459  	state.PrimeOperations(c, clock.Now().Add(-1*ageOfExpired), unit, numExpiredOperationEntries, tasksPerOperation)
  1460  
  1461  	actions, err := unit.Actions()
  1462  	c.Assert(err, jc.ErrorIsNil)
  1463  	c.Assert(actions, gc.HasLen, tasksPerOperation*(numCurrentOperationEntries+numExpiredOperationEntries))
  1464  	ops, err := s.Model.AllOperations()
  1465  	c.Assert(err, jc.ErrorIsNil)
  1466  	c.Assert(ops, gc.HasLen, numCurrentOperationEntries+numExpiredOperationEntries)
  1467  
  1468  	var stop <-chan struct{}
  1469  	err = state.PruneOperations(stop, s.State, 1*time.Hour, 0)
  1470  	c.Assert(err, jc.ErrorIsNil)
  1471  
  1472  	actions, err = unit.Actions()
  1473  	c.Assert(err, jc.ErrorIsNil)
  1474  	ops, err = s.Model.AllOperations()
  1475  	c.Assert(err, jc.ErrorIsNil)
  1476  
  1477  	c.Log(actions)
  1478  	c.Assert(actions, gc.HasLen, tasksPerOperation*numCurrentOperationEntries)
  1479  	c.Assert(ops, gc.HasLen, numCurrentOperationEntries)
  1480  }
  1481  
  1482  // Pruner should not prune operations with age of epoch time since the epoch is a
  1483  // special value denoting an incomplete operation.
  1484  func (s *ActionPruningSuite) TestDoNotPruneIncompleteOperations(c *gc.C) {
  1485  	clock := testclock.NewClock(time.Now())
  1486  	err := s.State.SetClockForTesting(clock)
  1487  	c.Assert(err, jc.ErrorIsNil)
  1488  	application := s.Factory.MakeApplication(c, nil)
  1489  	unit := s.Factory.MakeUnit(c, &factory.UnitParams{Application: application})
  1490  
  1491  	// Completed times with the zero value are designated not complete
  1492  	const numZeroValueEntries = 5
  1493  	const tasksPerOperation = 3
  1494  	state.PrimeOperations(c, time.Time{}, unit, numZeroValueEntries, tasksPerOperation)
  1495  
  1496  	_, err = unit.Actions()
  1497  	c.Assert(err, jc.ErrorIsNil)
  1498  	_, err = s.Model.AllOperations()
  1499  	c.Assert(err, jc.ErrorIsNil)
  1500  
  1501  	var stop <-chan struct{}
  1502  	err = state.PruneOperations(stop, s.State, 1*time.Hour, 0)
  1503  	c.Assert(err, jc.ErrorIsNil)
  1504  
  1505  	actions, err := unit.Actions()
  1506  	c.Assert(err, jc.ErrorIsNil)
  1507  	ops, err := s.Model.AllOperations()
  1508  	c.Assert(err, jc.ErrorIsNil)
  1509  
  1510  	c.Assert(len(actions), gc.Equals, tasksPerOperation*numZeroValueEntries)
  1511  	c.Assert(len(ops), gc.Equals, numZeroValueEntries)
  1512  }
  1513  
  1514  func (s *ActionPruningSuite) TestPruneLegacyActions(c *gc.C) {
  1515  	clock := testclock.NewClock(time.Now())
  1516  	err := s.State.SetClockForTesting(clock)
  1517  	c.Assert(err, jc.ErrorIsNil)
  1518  	application := s.Factory.MakeApplication(c, nil)
  1519  	unit := s.Factory.MakeUnit(c, &factory.UnitParams{Application: application})
  1520  
  1521  	const numCurrentOperationEntries = 5
  1522  	const numExpiredOperationEntries = 5
  1523  	const tasksPerOperation = 3
  1524  	const ageOfExpired = 10 * time.Hour
  1525  
  1526  	state.PrimeOperations(c, clock.Now(), unit, numCurrentOperationEntries, tasksPerOperation)
  1527  	state.PrimeOperations(c, clock.Now().Add(-1*ageOfExpired), unit, numExpiredOperationEntries, tasksPerOperation)
  1528  	state.PrimeLegacyActions(c, clock.Now().Add(-1*ageOfExpired), unit, numExpiredOperationEntries)
  1529  
  1530  	actions, err := unit.Actions()
  1531  	c.Assert(err, jc.ErrorIsNil)
  1532  	c.Assert(actions, gc.HasLen, tasksPerOperation*(numCurrentOperationEntries+numExpiredOperationEntries)+numExpiredOperationEntries)
  1533  	ops, err := s.Model.AllOperations()
  1534  	c.Assert(err, jc.ErrorIsNil)
  1535  	c.Assert(ops, gc.HasLen, numCurrentOperationEntries+numExpiredOperationEntries)
  1536  
  1537  	var stop <-chan struct{}
  1538  	err = state.PruneOperations(stop, s.State, 1*time.Hour, 0)
  1539  	c.Assert(err, jc.ErrorIsNil)
  1540  
  1541  	actions, err = unit.Actions()
  1542  	c.Assert(err, jc.ErrorIsNil)
  1543  	ops, err = s.Model.AllOperations()
  1544  	c.Assert(err, jc.ErrorIsNil)
  1545  
  1546  	c.Log(actions)
  1547  	c.Assert(actions, gc.HasLen, tasksPerOperation*numCurrentOperationEntries)
  1548  	c.Assert(ops, gc.HasLen, numCurrentOperationEntries)
  1549  }