github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/instancepoller/instancepoller_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package instancepoller_test
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	jujutesting "github.com/juju/testing"
    11  	jc "github.com/juju/testing/checkers"
    12  	"github.com/juju/utils/clock"
    13  	gc "gopkg.in/check.v1"
    14  
    15  	"github.com/juju/juju/apiserver/common"
    16  	"github.com/juju/juju/apiserver/instancepoller"
    17  	"github.com/juju/juju/apiserver/params"
    18  	apiservertesting "github.com/juju/juju/apiserver/testing"
    19  	"github.com/juju/juju/network"
    20  	"github.com/juju/juju/state"
    21  	statetesting "github.com/juju/juju/state/testing"
    22  	"github.com/juju/juju/status"
    23  	coretesting "github.com/juju/juju/testing"
    24  )
    25  
    26  type InstancePollerSuite struct {
    27  	coretesting.BaseSuite
    28  
    29  	st         *mockState
    30  	api        *instancepoller.InstancePollerAPI
    31  	authoriser apiservertesting.FakeAuthorizer
    32  	resources  *common.Resources
    33  
    34  	machineEntities     params.Entities
    35  	machineErrorResults params.ErrorResults
    36  
    37  	mixedEntities     params.Entities
    38  	mixedErrorResults params.ErrorResults
    39  
    40  	clock clock.Clock
    41  }
    42  
    43  var _ = gc.Suite(&InstancePollerSuite{})
    44  
    45  func (s *InstancePollerSuite) SetUpTest(c *gc.C) {
    46  	s.BaseSuite.SetUpTest(c)
    47  
    48  	s.authoriser = apiservertesting.FakeAuthorizer{
    49  		EnvironManager: true,
    50  	}
    51  	s.resources = common.NewResources()
    52  	s.AddCleanup(func(*gc.C) { s.resources.StopAll() })
    53  
    54  	s.st = NewMockState()
    55  	instancepoller.PatchState(s, s.st)
    56  
    57  	var err error
    58  	s.clock = jujutesting.NewClock(time.Now())
    59  	s.api, err = instancepoller.NewInstancePollerAPI(nil, s.resources, s.authoriser, s.clock)
    60  	c.Assert(err, jc.ErrorIsNil)
    61  
    62  	s.machineEntities = params.Entities{
    63  		Entities: []params.Entity{
    64  			{Tag: "machine-1"},
    65  			{Tag: "machine-2"},
    66  			{Tag: "machine-3"},
    67  		}}
    68  	s.machineErrorResults = params.ErrorResults{
    69  		Results: []params.ErrorResult{
    70  			{Error: apiservertesting.ServerError("pow!")},
    71  			{Error: apiservertesting.ServerError("FAIL")},
    72  			{Error: apiservertesting.NotProvisionedError("42")},
    73  		}}
    74  
    75  	s.mixedEntities = params.Entities{
    76  		Entities: []params.Entity{
    77  			{Tag: "machine-1"},
    78  			{Tag: "machine-2"},
    79  			{Tag: "machine-42"},
    80  			{Tag: "application-unknown"},
    81  			{Tag: "invalid-tag"},
    82  			{Tag: "unit-missing-1"},
    83  			{Tag: ""},
    84  			{Tag: "42"},
    85  		}}
    86  	s.mixedErrorResults = params.ErrorResults{
    87  		Results: []params.ErrorResult{
    88  			{Error: nil},
    89  			{Error: nil},
    90  			{Error: apiservertesting.NotFoundError("machine 42")},
    91  			{Error: apiservertesting.ServerError(`"application-unknown" is not a valid machine tag`)},
    92  			{Error: apiservertesting.ServerError(`"invalid-tag" is not a valid tag`)},
    93  			{Error: apiservertesting.ServerError(`"unit-missing-1" is not a valid machine tag`)},
    94  			{Error: apiservertesting.ServerError(`"" is not a valid tag`)},
    95  			{Error: apiservertesting.ServerError(`"42" is not a valid tag`)},
    96  		}}
    97  }
    98  
    99  func (s *InstancePollerSuite) TestNewInstancePollerAPIRequiresEnvironManager(c *gc.C) {
   100  	anAuthoriser := s.authoriser
   101  	anAuthoriser.EnvironManager = false
   102  	api, err := instancepoller.NewInstancePollerAPI(nil, s.resources, anAuthoriser, s.clock)
   103  	c.Assert(api, gc.IsNil)
   104  	c.Assert(err, gc.ErrorMatches, "permission denied")
   105  }
   106  
   107  func (s *InstancePollerSuite) TestModelConfigFailure(c *gc.C) {
   108  	s.st.SetErrors(errors.New("boom"))
   109  
   110  	result, err := s.api.ModelConfig()
   111  	c.Assert(err, gc.ErrorMatches, "boom")
   112  	c.Assert(result, jc.DeepEquals, params.ModelConfigResult{})
   113  
   114  	s.st.CheckCallNames(c, "ModelConfig")
   115  }
   116  
   117  func (s *InstancePollerSuite) TestModelConfigSuccess(c *gc.C) {
   118  	envConfig := coretesting.ModelConfig(c)
   119  	s.st.SetConfig(c, envConfig)
   120  
   121  	result, err := s.api.ModelConfig()
   122  	c.Assert(err, jc.ErrorIsNil)
   123  	c.Assert(result, jc.DeepEquals, params.ModelConfigResult{
   124  		Config: envConfig.AllAttrs(),
   125  	})
   126  
   127  	s.st.CheckCallNames(c, "ModelConfig")
   128  }
   129  
   130  func (s *InstancePollerSuite) TestWatchForModelConfigChangesFailure(c *gc.C) {
   131  	// Force the Changes() method of the mock watcher to return a
   132  	// closed channel by setting an error.
   133  	s.st.SetErrors(errors.New("boom"))
   134  
   135  	result, err := s.api.WatchForModelConfigChanges()
   136  	c.Assert(err, gc.ErrorMatches, "boom")
   137  	c.Assert(result, jc.DeepEquals, params.NotifyWatchResult{})
   138  
   139  	c.Assert(s.resources.Count(), gc.Equals, 0) // no watcher registered
   140  	s.st.CheckCallNames(c, "WatchForModelConfigChanges")
   141  }
   142  
   143  func (s *InstancePollerSuite) TestWatchForModelConfigChangesSuccess(c *gc.C) {
   144  	result, err := s.api.WatchForModelConfigChanges()
   145  	c.Assert(err, jc.ErrorIsNil)
   146  	c.Assert(result, jc.DeepEquals, params.NotifyWatchResult{
   147  		Error: nil, NotifyWatcherId: "1",
   148  	})
   149  
   150  	// Verify the watcher resource was registered.
   151  	c.Assert(s.resources.Count(), gc.Equals, 1)
   152  	resource := s.resources.Get("1")
   153  	defer statetesting.AssertStop(c, resource)
   154  
   155  	// Check that the watcher has consumed the initial event
   156  	wc := statetesting.NewNotifyWatcherC(c, s.st, resource.(state.NotifyWatcher))
   157  	wc.AssertNoChange()
   158  
   159  	s.st.CheckCallNames(c, "WatchForModelConfigChanges")
   160  
   161  	// Try changing the config to verify an event is reported.
   162  	envConfig := coretesting.ModelConfig(c)
   163  	s.st.SetConfig(c, envConfig)
   164  	wc.AssertOneChange()
   165  }
   166  
   167  func (s *InstancePollerSuite) TestWatchModelMachinesFailure(c *gc.C) {
   168  	// Force the Changes() method of the mock watcher to return a
   169  	// closed channel by setting an error.
   170  	s.st.SetErrors(errors.Errorf("boom"))
   171  
   172  	result, err := s.api.WatchModelMachines()
   173  	c.Assert(err, gc.ErrorMatches, "cannot obtain initial model machines: boom")
   174  	c.Assert(result, jc.DeepEquals, params.StringsWatchResult{})
   175  
   176  	c.Assert(s.resources.Count(), gc.Equals, 0) // no watcher registered
   177  	s.st.CheckCallNames(c, "WatchModelMachines")
   178  }
   179  
   180  func (s *InstancePollerSuite) TestWatchModelMachinesSuccess(c *gc.C) {
   181  	// Add a couple of machines.
   182  	s.st.SetMachineInfo(c, machineInfo{id: "2"})
   183  	s.st.SetMachineInfo(c, machineInfo{id: "1"})
   184  
   185  	expectedResult := params.StringsWatchResult{
   186  		Error:            nil,
   187  		StringsWatcherId: "1",
   188  		Changes:          []string{"1", "2"}, // initial event (sorted ids)
   189  	}
   190  	result, err := s.api.WatchModelMachines()
   191  	c.Assert(err, jc.ErrorIsNil)
   192  	c.Assert(result, jc.DeepEquals, expectedResult)
   193  
   194  	// Verify the watcher resource was registered.
   195  	c.Assert(s.resources.Count(), gc.Equals, 1)
   196  	resource1 := s.resources.Get("1")
   197  	defer func() {
   198  		if resource1 != nil {
   199  			statetesting.AssertStop(c, resource1)
   200  		}
   201  	}()
   202  
   203  	// Check that the watcher has consumed the initial event
   204  	wc1 := statetesting.NewStringsWatcherC(c, s.st, resource1.(state.StringsWatcher))
   205  	wc1.AssertNoChange()
   206  
   207  	s.st.CheckCallNames(c, "WatchModelMachines")
   208  
   209  	// Add another watcher to verify events coalescence.
   210  	result, err = s.api.WatchModelMachines()
   211  	c.Assert(err, jc.ErrorIsNil)
   212  	expectedResult.StringsWatcherId = "2"
   213  	c.Assert(result, jc.DeepEquals, expectedResult)
   214  	s.st.CheckCallNames(c, "WatchModelMachines", "WatchModelMachines")
   215  	c.Assert(s.resources.Count(), gc.Equals, 2)
   216  	resource2 := s.resources.Get("2")
   217  	defer statetesting.AssertStop(c, resource2)
   218  	wc2 := statetesting.NewStringsWatcherC(c, s.st, resource2.(state.StringsWatcher))
   219  	wc2.AssertNoChange()
   220  
   221  	// Remove machine 1, check it's reported.
   222  	s.st.RemoveMachine(c, "1")
   223  	wc1.AssertChangeInSingleEvent("1")
   224  
   225  	// Make separate changes, check they're combined.
   226  	s.st.SetMachineInfo(c, machineInfo{id: "2", life: state.Dying})
   227  	s.st.SetMachineInfo(c, machineInfo{id: "3"})
   228  	s.st.RemoveMachine(c, "42") // ignored
   229  	wc1.AssertChangeInSingleEvent("2", "3")
   230  	wc2.AssertChangeInSingleEvent("1", "2", "3")
   231  
   232  	// Stop the first watcher and assert its changes chan is closed.
   233  	c.Assert(resource1.Stop(), jc.ErrorIsNil)
   234  	wc1.AssertClosed()
   235  	resource1 = nil
   236  }
   237  
   238  func (s *InstancePollerSuite) TestLifeSuccess(c *gc.C) {
   239  	s.st.SetMachineInfo(c, machineInfo{id: "1", life: state.Alive})
   240  	s.st.SetMachineInfo(c, machineInfo{id: "2", life: state.Dying})
   241  
   242  	result, err := s.api.Life(s.mixedEntities)
   243  	c.Assert(err, jc.ErrorIsNil)
   244  	c.Assert(result, jc.DeepEquals, params.LifeResults{
   245  		Results: []params.LifeResult{
   246  			{Life: params.Alive},
   247  			{Life: params.Dying},
   248  			{Error: apiservertesting.NotFoundError("machine 42")},
   249  			{Error: apiservertesting.ErrUnauthorized},
   250  			{Error: apiservertesting.ErrUnauthorized},
   251  			{Error: apiservertesting.ErrUnauthorized},
   252  			{Error: apiservertesting.ErrUnauthorized},
   253  			{Error: apiservertesting.ErrUnauthorized},
   254  		}},
   255  	)
   256  
   257  	s.st.CheckFindEntityCall(c, 0, "1")
   258  	s.st.CheckCall(c, 1, "Life")
   259  	s.st.CheckFindEntityCall(c, 2, "2")
   260  	s.st.CheckCall(c, 3, "Life")
   261  	s.st.CheckFindEntityCall(c, 4, "42")
   262  }
   263  
   264  func (s *InstancePollerSuite) TestLifeFailure(c *gc.C) {
   265  	s.st.SetErrors(
   266  		errors.New("pow!"),                   // m1 := FindEntity("1"); Life not called
   267  		nil,                                  // m2 := FindEntity("2")
   268  		errors.New("FAIL"),                   // m2.Life() - unused
   269  		errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved)
   270  	)
   271  	s.st.SetMachineInfo(c, machineInfo{id: "1", life: state.Alive})
   272  	s.st.SetMachineInfo(c, machineInfo{id: "2", life: state.Dead})
   273  	s.st.SetMachineInfo(c, machineInfo{id: "3", life: state.Dying})
   274  
   275  	result, err := s.api.Life(s.machineEntities)
   276  	c.Assert(err, jc.ErrorIsNil)
   277  	c.Assert(result, jc.DeepEquals, params.LifeResults{
   278  		Results: []params.LifeResult{
   279  			{Error: apiservertesting.ServerError("pow!")},
   280  			{Life: params.Dead},
   281  			{Error: apiservertesting.NotProvisionedError("42")},
   282  		}},
   283  	)
   284  
   285  	s.st.CheckFindEntityCall(c, 0, "1")
   286  	s.st.CheckFindEntityCall(c, 1, "2")
   287  	s.st.CheckCall(c, 2, "Life")
   288  	s.st.CheckFindEntityCall(c, 3, "3")
   289  }
   290  
   291  func (s *InstancePollerSuite) TestInstanceIdSuccess(c *gc.C) {
   292  	s.st.SetMachineInfo(c, machineInfo{id: "1", instanceId: "i-foo"})
   293  	s.st.SetMachineInfo(c, machineInfo{id: "2", instanceId: ""})
   294  
   295  	result, err := s.api.InstanceId(s.mixedEntities)
   296  	c.Assert(err, jc.ErrorIsNil)
   297  	c.Assert(result, jc.DeepEquals, params.StringResults{
   298  		Results: []params.StringResult{
   299  			{Result: "i-foo"},
   300  			{Result: ""},
   301  			{Error: apiservertesting.NotFoundError("machine 42")},
   302  			{Error: apiservertesting.ErrUnauthorized},
   303  			{Error: apiservertesting.ErrUnauthorized},
   304  			{Error: apiservertesting.ErrUnauthorized},
   305  			{Error: apiservertesting.ErrUnauthorized},
   306  			{Error: apiservertesting.ErrUnauthorized},
   307  		}},
   308  	)
   309  
   310  	s.st.CheckFindEntityCall(c, 0, "1")
   311  	s.st.CheckCall(c, 1, "InstanceId")
   312  	s.st.CheckFindEntityCall(c, 2, "2")
   313  	s.st.CheckCall(c, 3, "InstanceId")
   314  	s.st.CheckFindEntityCall(c, 4, "42")
   315  }
   316  
   317  func (s *InstancePollerSuite) TestInstanceIdFailure(c *gc.C) {
   318  	s.st.SetErrors(
   319  		errors.New("pow!"),                   // m1 := FindEntity("1"); InstanceId not called
   320  		nil,                                  // m2 := FindEntity("2")
   321  		errors.New("FAIL"),                   // m2.InstanceId()
   322  		errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved)
   323  	)
   324  	s.st.SetMachineInfo(c, machineInfo{id: "1", instanceId: ""})
   325  	s.st.SetMachineInfo(c, machineInfo{id: "2", instanceId: "i-bar"})
   326  
   327  	result, err := s.api.InstanceId(s.machineEntities)
   328  	c.Assert(err, jc.ErrorIsNil)
   329  	c.Assert(result, jc.DeepEquals, params.StringResults{
   330  		Results: []params.StringResult{
   331  			{Error: apiservertesting.ServerError("pow!")},
   332  			{Error: apiservertesting.ServerError("FAIL")},
   333  			{Error: apiservertesting.NotProvisionedError("42")},
   334  		}},
   335  	)
   336  
   337  	s.st.CheckFindEntityCall(c, 0, "1")
   338  	s.st.CheckFindEntityCall(c, 1, "2")
   339  	s.st.CheckCall(c, 2, "InstanceId")
   340  	s.st.CheckFindEntityCall(c, 3, "3")
   341  }
   342  
   343  func (s *InstancePollerSuite) TestStatusSuccess(c *gc.C) {
   344  	now := time.Now()
   345  	s1 := status.StatusInfo{
   346  		Status:  status.Error,
   347  		Message: "not really",
   348  		Data: map[string]interface{}{
   349  			"price": 4.2,
   350  			"bool":  false,
   351  			"bar":   []string{"a", "b"},
   352  		},
   353  		Since: &now,
   354  	}
   355  	s2 := status.StatusInfo{}
   356  	s.st.SetMachineInfo(c, machineInfo{id: "1", status: s1})
   357  	s.st.SetMachineInfo(c, machineInfo{id: "2", status: s2})
   358  
   359  	result, err := s.api.Status(s.mixedEntities)
   360  	c.Assert(err, jc.ErrorIsNil)
   361  	c.Assert(result, jc.DeepEquals, params.StatusResults{
   362  		Results: []params.StatusResult{
   363  			{
   364  				Status: status.Error.String(),
   365  				Info:   s1.Message,
   366  				Data:   s1.Data,
   367  				Since:  s1.Since,
   368  			},
   369  			{Status: "", Info: "", Data: nil, Since: nil},
   370  			{Error: apiservertesting.NotFoundError("machine 42")},
   371  			{Error: apiservertesting.ErrUnauthorized},
   372  			{Error: apiservertesting.ServerError(`"invalid-tag" is not a valid tag`)},
   373  			{Error: apiservertesting.ErrUnauthorized},
   374  			{Error: apiservertesting.ServerError(`"" is not a valid tag`)},
   375  			{Error: apiservertesting.ServerError(`"42" is not a valid tag`)},
   376  		}},
   377  	)
   378  
   379  	s.st.CheckFindEntityCall(c, 0, "1")
   380  	s.st.CheckCall(c, 1, "Status")
   381  	s.st.CheckFindEntityCall(c, 2, "2")
   382  	s.st.CheckCall(c, 3, "Status")
   383  	s.st.CheckFindEntityCall(c, 4, "42")
   384  }
   385  
   386  func (s *InstancePollerSuite) TestStatusFailure(c *gc.C) {
   387  	s.st.SetErrors(
   388  		errors.New("pow!"),                   // m1 := FindEntity("1"); Status not called
   389  		nil,                                  // m2 := FindEntity("2")
   390  		errors.New("FAIL"),                   // m2.Status()
   391  		errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved)
   392  	)
   393  	s.st.SetMachineInfo(c, machineInfo{id: "1"})
   394  	s.st.SetMachineInfo(c, machineInfo{id: "2"})
   395  
   396  	result, err := s.api.Status(s.machineEntities)
   397  	c.Assert(err, jc.ErrorIsNil)
   398  	c.Assert(result, jc.DeepEquals, params.StatusResults{
   399  		Results: []params.StatusResult{
   400  			{Error: apiservertesting.ServerError("pow!")},
   401  			{Error: apiservertesting.ServerError("FAIL")},
   402  			{Error: apiservertesting.NotProvisionedError("42")},
   403  		}},
   404  	)
   405  
   406  	s.st.CheckFindEntityCall(c, 0, "1")
   407  	s.st.CheckFindEntityCall(c, 1, "2")
   408  	s.st.CheckCall(c, 2, "Status")
   409  	s.st.CheckFindEntityCall(c, 3, "3")
   410  }
   411  
   412  func (s *InstancePollerSuite) TestProviderAddressesSuccess(c *gc.C) {
   413  	addrs := network.NewAddresses("0.1.2.3", "127.0.0.1", "8.8.8.8")
   414  	expectedAddresses := params.FromNetworkAddresses(addrs...)
   415  	s.st.SetMachineInfo(c, machineInfo{id: "1", providerAddresses: addrs})
   416  	s.st.SetMachineInfo(c, machineInfo{id: "2", providerAddresses: nil})
   417  
   418  	result, err := s.api.ProviderAddresses(s.mixedEntities)
   419  	c.Assert(err, jc.ErrorIsNil)
   420  	c.Assert(result, jc.DeepEquals, params.MachineAddressesResults{
   421  		Results: []params.MachineAddressesResult{
   422  			{Addresses: expectedAddresses},
   423  			{Addresses: nil},
   424  			{Error: apiservertesting.NotFoundError("machine 42")},
   425  			{Error: apiservertesting.ServerError(`"application-unknown" is not a valid machine tag`)},
   426  			{Error: apiservertesting.ServerError(`"invalid-tag" is not a valid tag`)},
   427  			{Error: apiservertesting.ServerError(`"unit-missing-1" is not a valid machine tag`)},
   428  			{Error: apiservertesting.ServerError(`"" is not a valid tag`)},
   429  			{Error: apiservertesting.ServerError(`"42" is not a valid tag`)},
   430  		}},
   431  	)
   432  
   433  	s.st.CheckFindEntityCall(c, 0, "1")
   434  	s.st.CheckCall(c, 1, "ProviderAddresses")
   435  	s.st.CheckFindEntityCall(c, 2, "2")
   436  	s.st.CheckCall(c, 3, "ProviderAddresses")
   437  	s.st.CheckFindEntityCall(c, 4, "42")
   438  }
   439  
   440  func (s *InstancePollerSuite) TestProviderAddressesFailure(c *gc.C) {
   441  	s.st.SetErrors(
   442  		errors.New("pow!"),                   // m1 := FindEntity("1")
   443  		nil,                                  // m2 := FindEntity("2")
   444  		errors.New("FAIL"),                   // m2.ProviderAddresses()- unused
   445  		errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved)
   446  	)
   447  	s.st.SetMachineInfo(c, machineInfo{id: "1"})
   448  	s.st.SetMachineInfo(c, machineInfo{id: "2"})
   449  
   450  	result, err := s.api.ProviderAddresses(s.machineEntities)
   451  	c.Assert(err, jc.ErrorIsNil)
   452  	c.Assert(result, jc.DeepEquals, params.MachineAddressesResults{
   453  		Results: []params.MachineAddressesResult{
   454  			{Error: apiservertesting.ServerError("pow!")},
   455  			{Addresses: nil},
   456  			{Error: apiservertesting.NotProvisionedError("42")},
   457  		}},
   458  	)
   459  
   460  	s.st.CheckFindEntityCall(c, 0, "1")
   461  	s.st.CheckFindEntityCall(c, 1, "2")
   462  	s.st.CheckCall(c, 2, "ProviderAddresses")
   463  	s.st.CheckFindEntityCall(c, 3, "3")
   464  }
   465  
   466  func (s *InstancePollerSuite) TestSetProviderAddressesSuccess(c *gc.C) {
   467  	oldAddrs := network.NewAddresses("0.1.2.3", "127.0.0.1", "8.8.8.8")
   468  	newAddrs := network.NewAddresses("1.2.3.4", "8.4.4.8", "2001:db8::")
   469  	s.st.SetMachineInfo(c, machineInfo{id: "1", providerAddresses: oldAddrs})
   470  	s.st.SetMachineInfo(c, machineInfo{id: "2", providerAddresses: nil})
   471  
   472  	result, err := s.api.SetProviderAddresses(params.SetMachinesAddresses{
   473  		MachineAddresses: []params.MachineAddresses{
   474  			{Tag: "machine-1", Addresses: nil},
   475  			{Tag: "machine-2", Addresses: params.FromNetworkAddresses(newAddrs...)},
   476  			{Tag: "machine-42"},
   477  			{Tag: "application-unknown"},
   478  			{Tag: "invalid-tag"},
   479  			{Tag: "unit-missing-1"},
   480  			{Tag: ""},
   481  			{Tag: "42"},
   482  		}},
   483  	)
   484  	c.Assert(err, jc.ErrorIsNil)
   485  	c.Assert(result, jc.DeepEquals, s.mixedErrorResults)
   486  
   487  	s.st.CheckFindEntityCall(c, 0, "1")
   488  	s.st.CheckSetProviderAddressesCall(c, 1, []network.Address{})
   489  	s.st.CheckFindEntityCall(c, 2, "2")
   490  	s.st.CheckSetProviderAddressesCall(c, 3, newAddrs)
   491  	s.st.CheckFindEntityCall(c, 4, "42")
   492  
   493  	// Ensure machines were updated.
   494  	machine, err := s.st.Machine("1")
   495  	c.Assert(err, jc.ErrorIsNil)
   496  	c.Assert(machine.ProviderAddresses(), gc.HasLen, 0)
   497  
   498  	machine, err = s.st.Machine("2")
   499  	c.Assert(err, jc.ErrorIsNil)
   500  	c.Assert(machine.ProviderAddresses(), jc.DeepEquals, newAddrs)
   501  }
   502  
   503  func (s *InstancePollerSuite) TestSetProviderAddressesFailure(c *gc.C) {
   504  	s.st.SetErrors(
   505  		errors.New("pow!"),                   // m1 := FindEntity("1")
   506  		nil,                                  // m2 := FindEntity("2")
   507  		errors.New("FAIL"),                   // m2.SetProviderAddresses()
   508  		errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved)
   509  	)
   510  	oldAddrs := network.NewAddresses("0.1.2.3", "127.0.0.1", "8.8.8.8")
   511  	newAddrs := network.NewAddresses("1.2.3.4", "8.4.4.8", "2001:db8::")
   512  	s.st.SetMachineInfo(c, machineInfo{id: "1", providerAddresses: oldAddrs})
   513  	s.st.SetMachineInfo(c, machineInfo{id: "2", providerAddresses: nil})
   514  
   515  	result, err := s.api.SetProviderAddresses(params.SetMachinesAddresses{
   516  		MachineAddresses: []params.MachineAddresses{
   517  			{Tag: "machine-1"},
   518  			{Tag: "machine-2", Addresses: params.FromNetworkAddresses(newAddrs...)},
   519  			{Tag: "machine-3"},
   520  		}},
   521  	)
   522  	c.Assert(err, jc.ErrorIsNil)
   523  	c.Assert(result, jc.DeepEquals, s.machineErrorResults)
   524  
   525  	s.st.CheckFindEntityCall(c, 0, "1")
   526  	s.st.CheckFindEntityCall(c, 1, "2")
   527  	s.st.CheckSetProviderAddressesCall(c, 2, newAddrs)
   528  	s.st.CheckFindEntityCall(c, 3, "3")
   529  
   530  	// Ensure machine 2 wasn't updated.
   531  	machine, err := s.st.Machine("2")
   532  	c.Assert(err, jc.ErrorIsNil)
   533  	c.Assert(machine.ProviderAddresses(), gc.HasLen, 0)
   534  }
   535  
   536  func (s *InstancePollerSuite) TestInstanceStatusSuccess(c *gc.C) {
   537  	s.st.SetMachineInfo(c, machineInfo{id: "1", instanceStatus: statusInfo("foo")})
   538  	s.st.SetMachineInfo(c, machineInfo{id: "2", instanceStatus: statusInfo("")})
   539  
   540  	result, err := s.api.InstanceStatus(s.mixedEntities)
   541  	c.Assert(err, jc.ErrorIsNil)
   542  	c.Assert(result, jc.DeepEquals, params.StatusResults{
   543  		Results: []params.StatusResult{
   544  			{Status: "foo"},
   545  			{Status: ""},
   546  			{Error: apiservertesting.NotFoundError("machine 42")},
   547  			{Error: apiservertesting.ServerError(`"application-unknown" is not a valid machine tag`)},
   548  			{Error: apiservertesting.ServerError(`"invalid-tag" is not a valid tag`)},
   549  			{Error: apiservertesting.ServerError(`"unit-missing-1" is not a valid machine tag`)},
   550  			{Error: apiservertesting.ServerError(`"" is not a valid tag`)},
   551  			{Error: apiservertesting.ServerError(`"42" is not a valid tag`)},
   552  		},
   553  	},
   554  	)
   555  
   556  	s.st.CheckFindEntityCall(c, 0, "1")
   557  	s.st.CheckCall(c, 1, "InstanceStatus")
   558  	s.st.CheckFindEntityCall(c, 2, "2")
   559  	s.st.CheckCall(c, 3, "InstanceStatus")
   560  	s.st.CheckFindEntityCall(c, 4, "42")
   561  }
   562  
   563  func (s *InstancePollerSuite) TestInstanceStatusFailure(c *gc.C) {
   564  	s.st.SetErrors(
   565  		errors.New("pow!"),                   // m1 := FindEntity("1")
   566  		nil,                                  // m2 := FindEntity("2")
   567  		errors.New("FAIL"),                   // m2.InstanceStatus()
   568  		errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved)
   569  	)
   570  	s.st.SetMachineInfo(c, machineInfo{id: "1", instanceStatus: statusInfo("foo")})
   571  	s.st.SetMachineInfo(c, machineInfo{id: "2", instanceStatus: statusInfo("")})
   572  
   573  	result, err := s.api.InstanceStatus(s.machineEntities)
   574  	c.Assert(err, jc.ErrorIsNil)
   575  	c.Assert(result, jc.DeepEquals, params.StatusResults{
   576  		Results: []params.StatusResult{
   577  			{Error: apiservertesting.ServerError("pow!")},
   578  			{Error: apiservertesting.ServerError("FAIL")},
   579  			{Error: apiservertesting.NotProvisionedError("42")},
   580  		}},
   581  	)
   582  
   583  	s.st.CheckFindEntityCall(c, 0, "1")
   584  	s.st.CheckFindEntityCall(c, 1, "2")
   585  	s.st.CheckCall(c, 2, "InstanceStatus")
   586  	s.st.CheckFindEntityCall(c, 3, "3")
   587  }
   588  
   589  func (s *InstancePollerSuite) TestSetInstanceStatusSuccess(c *gc.C) {
   590  	s.st.SetMachineInfo(c, machineInfo{id: "1", instanceStatus: statusInfo("foo")})
   591  	s.st.SetMachineInfo(c, machineInfo{id: "2", instanceStatus: statusInfo("")})
   592  
   593  	result, err := s.api.SetInstanceStatus(params.SetStatus{
   594  		Entities: []params.EntityStatusArgs{
   595  			{Tag: "machine-1", Status: ""},
   596  			{Tag: "machine-2", Status: "new status"},
   597  			{Tag: "machine-42", Status: ""},
   598  			{Tag: "application-unknown", Status: ""},
   599  			{Tag: "invalid-tag", Status: ""},
   600  			{Tag: "unit-missing-1", Status: ""},
   601  			{Tag: "", Status: ""},
   602  			{Tag: "42", Status: ""},
   603  		}},
   604  	)
   605  	c.Assert(err, jc.ErrorIsNil)
   606  	c.Assert(result, jc.DeepEquals, s.mixedErrorResults)
   607  
   608  	now := s.clock.Now()
   609  	s.st.CheckFindEntityCall(c, 0, "1")
   610  	s.st.CheckCall(c, 1, "SetInstanceStatus", status.StatusInfo{Status: status.Status(""), Since: &now})
   611  	s.st.CheckFindEntityCall(c, 2, "2")
   612  	s.st.CheckCall(c, 3, "SetInstanceStatus", status.StatusInfo{Status: status.Status("new status"), Since: &now})
   613  	s.st.CheckFindEntityCall(c, 4, "42")
   614  
   615  	// Ensure machines were updated.
   616  	machine, err := s.st.Machine("1")
   617  	c.Assert(err, jc.ErrorIsNil)
   618  	// TODO (perrito666) there should not be an empty StatusInfo here,
   619  	// this is certainly a smell.
   620  	setStatus, err := machine.InstanceStatus()
   621  	c.Assert(err, jc.ErrorIsNil)
   622  	setStatus.Since = nil
   623  	c.Assert(setStatus, gc.DeepEquals, status.StatusInfo{})
   624  
   625  	machine, err = s.st.Machine("2")
   626  	c.Assert(err, jc.ErrorIsNil)
   627  	setStatus, err = machine.InstanceStatus()
   628  	c.Assert(err, jc.ErrorIsNil)
   629  	setStatus.Since = nil
   630  	c.Assert(setStatus, gc.DeepEquals, status.StatusInfo{Status: "new status"})
   631  }
   632  
   633  func (s *InstancePollerSuite) TestSetInstanceStatusFailure(c *gc.C) {
   634  	s.st.SetErrors(
   635  		errors.New("pow!"),                   // m1 := FindEntity("1")
   636  		nil,                                  // m2 := FindEntity("2")
   637  		errors.New("FAIL"),                   // m2.SetInstanceStatus()
   638  		errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved)
   639  	)
   640  	s.st.SetMachineInfo(c, machineInfo{id: "1", instanceStatus: statusInfo("foo")})
   641  	s.st.SetMachineInfo(c, machineInfo{id: "2", instanceStatus: statusInfo("")})
   642  
   643  	result, err := s.api.SetInstanceStatus(params.SetStatus{
   644  		Entities: []params.EntityStatusArgs{
   645  			{Tag: "machine-1", Status: "new"},
   646  			{Tag: "machine-2", Status: "invalid"},
   647  			{Tag: "machine-3", Status: ""},
   648  		}},
   649  	)
   650  	c.Assert(err, jc.ErrorIsNil)
   651  	c.Assert(result, jc.DeepEquals, s.machineErrorResults)
   652  
   653  	s.st.CheckFindEntityCall(c, 0, "1")
   654  	s.st.CheckFindEntityCall(c, 1, "2")
   655  	now := s.clock.Now()
   656  	s.st.CheckCall(c, 2, "SetInstanceStatus", status.StatusInfo{Status: "invalid", Since: &now})
   657  	s.st.CheckFindEntityCall(c, 3, "3")
   658  }
   659  
   660  func (s *InstancePollerSuite) TestAreManuallyProvisionedSuccess(c *gc.C) {
   661  	s.st.SetMachineInfo(c, machineInfo{id: "1", isManual: true})
   662  	s.st.SetMachineInfo(c, machineInfo{id: "2", isManual: false})
   663  
   664  	result, err := s.api.AreManuallyProvisioned(s.mixedEntities)
   665  	c.Assert(err, jc.ErrorIsNil)
   666  	c.Assert(result, jc.DeepEquals, params.BoolResults{
   667  		Results: []params.BoolResult{
   668  			{Result: true},
   669  			{Result: false},
   670  			{Error: apiservertesting.NotFoundError("machine 42")},
   671  			{Error: apiservertesting.ServerError(`"application-unknown" is not a valid machine tag`)},
   672  			{Error: apiservertesting.ServerError(`"invalid-tag" is not a valid tag`)},
   673  			{Error: apiservertesting.ServerError(`"unit-missing-1" is not a valid machine tag`)},
   674  			{Error: apiservertesting.ServerError(`"" is not a valid tag`)},
   675  			{Error: apiservertesting.ServerError(`"42" is not a valid tag`)},
   676  		}},
   677  	)
   678  
   679  	s.st.CheckFindEntityCall(c, 0, "1")
   680  	s.st.CheckCall(c, 1, "IsManual")
   681  	s.st.CheckFindEntityCall(c, 2, "2")
   682  	s.st.CheckCall(c, 3, "IsManual")
   683  	s.st.CheckFindEntityCall(c, 4, "42")
   684  }
   685  
   686  func (s *InstancePollerSuite) TestAreManuallyProvisionedFailure(c *gc.C) {
   687  	s.st.SetErrors(
   688  		errors.New("pow!"),                   // m1 := FindEntity("1")
   689  		nil,                                  // m2 := FindEntity("2")
   690  		errors.New("FAIL"),                   // m2.IsManual()
   691  		errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved)
   692  	)
   693  	s.st.SetMachineInfo(c, machineInfo{id: "1", isManual: true})
   694  	s.st.SetMachineInfo(c, machineInfo{id: "2", isManual: false})
   695  
   696  	result, err := s.api.AreManuallyProvisioned(s.machineEntities)
   697  	c.Assert(err, jc.ErrorIsNil)
   698  	c.Assert(result, jc.DeepEquals, params.BoolResults{
   699  		Results: []params.BoolResult{
   700  			{Error: apiservertesting.ServerError("pow!")},
   701  			{Error: apiservertesting.ServerError("FAIL")},
   702  			{Error: apiservertesting.NotProvisionedError("42")},
   703  		}},
   704  	)
   705  
   706  	s.st.CheckFindEntityCall(c, 0, "1")
   707  	s.st.CheckFindEntityCall(c, 1, "2")
   708  	s.st.CheckCall(c, 2, "IsManual")
   709  	s.st.CheckFindEntityCall(c, 3, "3")
   710  }
   711  
   712  func statusInfo(st string) status.StatusInfo {
   713  	return status.StatusInfo{Status: status.Status(st)}
   714  }