github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/controller/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/clock"
    10  	"github.com/juju/clock/testclock"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/mgo/v3/txn"
    13  	"github.com/juju/testing"
    14  	jc "github.com/juju/testing/checkers"
    15  	"go.uber.org/mock/gomock"
    16  	gc "gopkg.in/check.v1"
    17  
    18  	"github.com/juju/juju/apiserver/common"
    19  	"github.com/juju/juju/apiserver/common/networkingcommon"
    20  	"github.com/juju/juju/apiserver/common/networkingcommon/mocks"
    21  	"github.com/juju/juju/apiserver/facades/controller/instancepoller"
    22  	apiservertesting "github.com/juju/juju/apiserver/testing"
    23  	"github.com/juju/juju/core/life"
    24  	"github.com/juju/juju/core/network"
    25  	"github.com/juju/juju/core/status"
    26  	"github.com/juju/juju/rpc/params"
    27  	"github.com/juju/juju/state"
    28  	statetesting "github.com/juju/juju/state/testing"
    29  	jujutesting "github.com/juju/juju/testing"
    30  )
    31  
    32  type InstancePollerSuite struct {
    33  	testing.IsolationSuite
    34  
    35  	st         *mockState
    36  	api        *instancepoller.InstancePollerAPI
    37  	authoriser apiservertesting.FakeAuthorizer
    38  	resources  *common.Resources
    39  
    40  	machineEntities     params.Entities
    41  	machineErrorResults params.ErrorResults
    42  
    43  	mixedEntities     params.Entities
    44  	mixedErrorResults params.ErrorResults
    45  
    46  	clock clock.Clock
    47  }
    48  
    49  var _ = gc.Suite(&InstancePollerSuite{})
    50  
    51  func (s *InstancePollerSuite) SetUpTest(c *gc.C) {
    52  	s.IsolationSuite.SetUpTest(c)
    53  
    54  	s.authoriser = apiservertesting.FakeAuthorizer{
    55  		Controller: true,
    56  	}
    57  	s.resources = common.NewResources()
    58  	s.AddCleanup(func(*gc.C) { s.resources.StopAll() })
    59  
    60  	s.st = NewMockState()
    61  	instancepoller.PatchState(s, s.st)
    62  
    63  	var err error
    64  	s.clock = testclock.NewClock(time.Now())
    65  	s.api, err = instancepoller.NewInstancePollerAPI(nil, nil, s.resources, s.authoriser, s.clock)
    66  	c.Assert(err, jc.ErrorIsNil)
    67  
    68  	s.machineEntities = params.Entities{
    69  		Entities: []params.Entity{
    70  			{Tag: "machine-1"},
    71  			{Tag: "machine-2"},
    72  			{Tag: "machine-3"},
    73  		}}
    74  	s.machineErrorResults = params.ErrorResults{
    75  		Results: []params.ErrorResult{
    76  			{Error: apiservertesting.ServerError("pow!")},
    77  			{Error: apiservertesting.ServerError("FAIL")},
    78  			{Error: apiservertesting.NotProvisionedError("42")},
    79  		}}
    80  
    81  	s.mixedEntities = params.Entities{
    82  		Entities: []params.Entity{
    83  			{Tag: "machine-1"},
    84  			{Tag: "machine-2"},
    85  			{Tag: "machine-42"},
    86  			{Tag: "application-unknown"},
    87  			{Tag: "invalid-tag"},
    88  			{Tag: "unit-missing-1"},
    89  			{Tag: ""},
    90  			{Tag: "42"},
    91  		}}
    92  	s.mixedErrorResults = params.ErrorResults{
    93  		Results: []params.ErrorResult{
    94  			{Error: nil},
    95  			{Error: nil},
    96  			{Error: apiservertesting.NotFoundError("machine 42")},
    97  			{Error: apiservertesting.ServerError(`"application-unknown" is not a valid machine tag`)},
    98  			{Error: apiservertesting.ServerError(`"invalid-tag" is not a valid tag`)},
    99  			{Error: apiservertesting.ServerError(`"unit-missing-1" is not a valid machine tag`)},
   100  			{Error: apiservertesting.ServerError(`"" is not a valid tag`)},
   101  			{Error: apiservertesting.ServerError(`"42" is not a valid tag`)},
   102  		}}
   103  }
   104  
   105  func (s *InstancePollerSuite) TestNewInstancePollerAPIRequiresController(c *gc.C) {
   106  	anAuthoriser := s.authoriser
   107  	anAuthoriser.Controller = false
   108  	api, err := instancepoller.NewInstancePollerAPI(nil, nil, s.resources, anAuthoriser, s.clock)
   109  	c.Assert(api, gc.IsNil)
   110  	c.Assert(err, gc.ErrorMatches, "permission denied")
   111  }
   112  
   113  func (s *InstancePollerSuite) TestModelConfigFailure(c *gc.C) {
   114  	s.st.SetErrors(errors.New("boom"))
   115  
   116  	result, err := s.api.ModelConfig()
   117  	c.Assert(err, gc.ErrorMatches, "boom")
   118  	c.Assert(result, jc.DeepEquals, params.ModelConfigResult{})
   119  
   120  	s.st.CheckCallNames(c, "ModelConfig")
   121  }
   122  
   123  func (s *InstancePollerSuite) TestModelConfigSuccess(c *gc.C) {
   124  	modelConfig := jujutesting.ModelConfig(c)
   125  	s.st.SetConfig(c, modelConfig)
   126  
   127  	result, err := s.api.ModelConfig()
   128  	c.Assert(err, jc.ErrorIsNil)
   129  	c.Assert(result, jc.DeepEquals, params.ModelConfigResult{
   130  		Config: modelConfig.AllAttrs(),
   131  	})
   132  
   133  	s.st.CheckCallNames(c, "ModelConfig")
   134  }
   135  
   136  func (s *InstancePollerSuite) TestWatchForModelConfigChangesFailure(c *gc.C) {
   137  	// Force the Changes() method of the mock watcher to return a
   138  	// closed channel by setting an error.
   139  	s.st.SetErrors(errors.New("boom"))
   140  
   141  	result, err := s.api.WatchForModelConfigChanges()
   142  	c.Assert(err, gc.ErrorMatches, "boom")
   143  	c.Assert(result, jc.DeepEquals, params.NotifyWatchResult{})
   144  
   145  	c.Assert(s.resources.Count(), gc.Equals, 0) // no watcher registered
   146  	s.st.CheckCallNames(c, "WatchForModelConfigChanges")
   147  }
   148  
   149  func (s *InstancePollerSuite) TestWatchForModelConfigChangesSuccess(c *gc.C) {
   150  	result, err := s.api.WatchForModelConfigChanges()
   151  	c.Assert(err, jc.ErrorIsNil)
   152  	c.Assert(result, jc.DeepEquals, params.NotifyWatchResult{
   153  		Error: nil, NotifyWatcherId: "1",
   154  	})
   155  
   156  	// Verify the watcher resource was registered.
   157  	c.Assert(s.resources.Count(), gc.Equals, 1)
   158  	resource := s.resources.Get("1")
   159  	defer statetesting.AssertStop(c, resource)
   160  
   161  	// Check that the watcher has consumed the initial event
   162  	wc := statetesting.NewNotifyWatcherC(c, resource.(state.NotifyWatcher))
   163  	wc.AssertNoChange()
   164  
   165  	s.st.CheckCallNames(c, "WatchForModelConfigChanges")
   166  
   167  	// Try changing the config to verify an event is reported.
   168  	modelConfig := jujutesting.ModelConfig(c)
   169  	s.st.SetConfig(c, modelConfig)
   170  	wc.AssertOneChange()
   171  }
   172  
   173  func (s *InstancePollerSuite) TestWatchModelMachinesFailure(c *gc.C) {
   174  	s.assertMachineWatcherFails(c, "WatchModelMachines", s.api.WatchModelMachines)
   175  }
   176  
   177  func (s *InstancePollerSuite) TestWatchModelMachinesSuccess(c *gc.C) {
   178  	s.assertMachineWatcherSucceeds(c, "WatchModelMachines", s.api.WatchModelMachines)
   179  }
   180  
   181  func (s *InstancePollerSuite) TestWatchModelMachineStartTimesFailure(c *gc.C) {
   182  	s.assertMachineWatcherFails(c, "WatchModelMachineStartTimes", s.api.WatchModelMachineStartTimes)
   183  }
   184  
   185  func (s *InstancePollerSuite) TestWatchModelMachineStartTimesSuccess(c *gc.C) {
   186  	s.assertMachineWatcherFails(c, "WatchModelMachineStartTimes", s.api.WatchModelMachineStartTimes)
   187  }
   188  
   189  func (s *InstancePollerSuite) assertMachineWatcherFails(c *gc.C, watchFacadeName string, getWatcherFn func() (params.StringsWatchResult, error)) {
   190  	// Force the Changes() method of the mock watcher to return a
   191  	// closed channel by setting an error.
   192  	s.st.SetErrors(errors.Errorf("boom"))
   193  
   194  	result, err := getWatcherFn()
   195  	c.Assert(err, gc.ErrorMatches, "cannot obtain initial model machines: boom")
   196  	c.Assert(result, jc.DeepEquals, params.StringsWatchResult{})
   197  
   198  	c.Assert(s.resources.Count(), gc.Equals, 0) // no watcher registered
   199  	s.st.CheckCallNames(c, watchFacadeName)
   200  }
   201  
   202  func (s *InstancePollerSuite) assertMachineWatcherSucceeds(c *gc.C, watchFacadeName string, getWatcherFn func() (params.StringsWatchResult, error)) {
   203  	// Add a couple of machines.
   204  	s.st.SetMachineInfo(c, machineInfo{id: "2"})
   205  	s.st.SetMachineInfo(c, machineInfo{id: "1"})
   206  
   207  	expectedResult := params.StringsWatchResult{
   208  		Error:            nil,
   209  		StringsWatcherId: "1",
   210  		Changes:          []string{"1", "2"}, // initial event (sorted ids)
   211  	}
   212  	result, err := getWatcherFn()
   213  	c.Assert(err, jc.ErrorIsNil)
   214  	c.Assert(result, jc.DeepEquals, expectedResult)
   215  
   216  	// Verify the watcher resource was registered.
   217  	c.Assert(s.resources.Count(), gc.Equals, 1)
   218  	resource1 := s.resources.Get("1")
   219  	defer func() {
   220  		if resource1 != nil {
   221  			statetesting.AssertStop(c, resource1)
   222  		}
   223  	}()
   224  
   225  	// Check that the watcher has consumed the initial event
   226  	wc1 := statetesting.NewStringsWatcherC(c, resource1.(state.StringsWatcher))
   227  	wc1.AssertNoChange()
   228  
   229  	s.st.CheckCallNames(c, watchFacadeName)
   230  
   231  	// Add another watcher to verify events coalescence.
   232  	result, err = getWatcherFn()
   233  	c.Assert(err, jc.ErrorIsNil)
   234  	expectedResult.StringsWatcherId = "2"
   235  	c.Assert(result, jc.DeepEquals, expectedResult)
   236  	s.st.CheckCallNames(c, watchFacadeName, watchFacadeName)
   237  	c.Assert(s.resources.Count(), gc.Equals, 2)
   238  	resource2 := s.resources.Get("2")
   239  	defer statetesting.AssertStop(c, resource2)
   240  	wc2 := statetesting.NewStringsWatcherC(c, resource2.(state.StringsWatcher))
   241  	wc2.AssertNoChange()
   242  
   243  	// Remove machine 1, check it's reported.
   244  	s.st.RemoveMachine(c, "1")
   245  	wc1.AssertChangeInSingleEvent("1")
   246  
   247  	// Make separate changes, check they're combined.
   248  	s.st.SetMachineInfo(c, machineInfo{id: "2", life: state.Dying})
   249  	s.st.SetMachineInfo(c, machineInfo{id: "3"})
   250  	s.st.RemoveMachine(c, "42") // ignored
   251  	wc1.AssertChangeInSingleEvent("2", "3")
   252  	wc2.AssertChangeInSingleEvent("1", "2", "3")
   253  
   254  	// Stop the first watcher and assert its changes chan is closed.
   255  	c.Assert(resource1.Stop(), jc.ErrorIsNil)
   256  	wc1.AssertClosed()
   257  	resource1 = nil
   258  }
   259  
   260  func (s *InstancePollerSuite) TestLifeSuccess(c *gc.C) {
   261  	s.st.SetMachineInfo(c, machineInfo{id: "1", life: state.Alive})
   262  	s.st.SetMachineInfo(c, machineInfo{id: "2", life: state.Dying})
   263  
   264  	result, err := s.api.Life(s.mixedEntities)
   265  	c.Assert(err, jc.ErrorIsNil)
   266  	c.Assert(result, jc.DeepEquals, params.LifeResults{
   267  		Results: []params.LifeResult{
   268  			{Life: life.Alive},
   269  			{Life: life.Dying},
   270  			{Error: apiservertesting.NotFoundError("machine 42")},
   271  			{Error: apiservertesting.ErrUnauthorized},
   272  			{Error: apiservertesting.ErrUnauthorized},
   273  			{Error: apiservertesting.ErrUnauthorized},
   274  			{Error: apiservertesting.ErrUnauthorized},
   275  			{Error: apiservertesting.ErrUnauthorized},
   276  		}},
   277  	)
   278  
   279  	s.st.CheckFindEntityCall(c, 0, "1")
   280  	s.st.CheckCall(c, 1, "Life")
   281  	s.st.CheckFindEntityCall(c, 2, "2")
   282  	s.st.CheckCall(c, 3, "Life")
   283  	s.st.CheckFindEntityCall(c, 4, "42")
   284  }
   285  
   286  func (s *InstancePollerSuite) TestLifeFailure(c *gc.C) {
   287  	s.st.SetErrors(
   288  		errors.New("pow!"),                   // m1 := FindEntity("1"); Life not called
   289  		nil,                                  // m2 := FindEntity("2")
   290  		errors.New("FAIL"),                   // m2.Life() - unused
   291  		errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved)
   292  	)
   293  	s.st.SetMachineInfo(c, machineInfo{id: "1", life: state.Alive})
   294  	s.st.SetMachineInfo(c, machineInfo{id: "2", life: state.Dead})
   295  	s.st.SetMachineInfo(c, machineInfo{id: "3", life: state.Dying})
   296  
   297  	result, err := s.api.Life(s.machineEntities)
   298  	c.Assert(err, jc.ErrorIsNil)
   299  	c.Assert(result, jc.DeepEquals, params.LifeResults{
   300  		Results: []params.LifeResult{
   301  			{Error: apiservertesting.ServerError("pow!")},
   302  			{Life: life.Dead},
   303  			{Error: apiservertesting.NotProvisionedError("42")},
   304  		}},
   305  	)
   306  
   307  	s.st.CheckFindEntityCall(c, 0, "1")
   308  	s.st.CheckFindEntityCall(c, 1, "2")
   309  	s.st.CheckCall(c, 2, "Life")
   310  	s.st.CheckFindEntityCall(c, 3, "3")
   311  }
   312  
   313  func (s *InstancePollerSuite) TestInstanceIdSuccess(c *gc.C) {
   314  	s.st.SetMachineInfo(c, machineInfo{id: "1", instanceId: "i-foo"})
   315  	s.st.SetMachineInfo(c, machineInfo{id: "2", instanceId: ""})
   316  
   317  	result, err := s.api.InstanceId(s.mixedEntities)
   318  	c.Assert(err, jc.ErrorIsNil)
   319  	c.Assert(result, jc.DeepEquals, params.StringResults{
   320  		Results: []params.StringResult{
   321  			{Result: "i-foo"},
   322  			{Result: ""},
   323  			{Error: apiservertesting.NotFoundError("machine 42")},
   324  			{Error: apiservertesting.ErrUnauthorized},
   325  			{Error: apiservertesting.ErrUnauthorized},
   326  			{Error: apiservertesting.ErrUnauthorized},
   327  			{Error: apiservertesting.ErrUnauthorized},
   328  			{Error: apiservertesting.ErrUnauthorized},
   329  		}},
   330  	)
   331  
   332  	s.st.CheckFindEntityCall(c, 0, "1")
   333  	s.st.CheckCall(c, 1, "InstanceId")
   334  	s.st.CheckFindEntityCall(c, 2, "2")
   335  	s.st.CheckCall(c, 3, "InstanceId")
   336  	s.st.CheckFindEntityCall(c, 4, "42")
   337  }
   338  
   339  func (s *InstancePollerSuite) TestInstanceIdFailure(c *gc.C) {
   340  	s.st.SetErrors(
   341  		errors.New("pow!"),                   // m1 := FindEntity("1"); InstanceId not called
   342  		nil,                                  // m2 := FindEntity("2")
   343  		errors.New("FAIL"),                   // m2.InstanceId()
   344  		errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved)
   345  	)
   346  	s.st.SetMachineInfo(c, machineInfo{id: "1", instanceId: ""})
   347  	s.st.SetMachineInfo(c, machineInfo{id: "2", instanceId: "i-bar"})
   348  
   349  	result, err := s.api.InstanceId(s.machineEntities)
   350  	c.Assert(err, jc.ErrorIsNil)
   351  	c.Assert(result, jc.DeepEquals, params.StringResults{
   352  		Results: []params.StringResult{
   353  			{Error: apiservertesting.ServerError("pow!")},
   354  			{Error: apiservertesting.ServerError("FAIL")},
   355  			{Error: apiservertesting.NotProvisionedError("42")},
   356  		}},
   357  	)
   358  
   359  	s.st.CheckFindEntityCall(c, 0, "1")
   360  	s.st.CheckFindEntityCall(c, 1, "2")
   361  	s.st.CheckCall(c, 2, "InstanceId")
   362  	s.st.CheckFindEntityCall(c, 3, "3")
   363  }
   364  
   365  func (s *InstancePollerSuite) TestStatusSuccess(c *gc.C) {
   366  	now := time.Now()
   367  	s1 := status.StatusInfo{
   368  		Status:  status.Error,
   369  		Message: "not really",
   370  		Data: map[string]interface{}{
   371  			"price": 4.2,
   372  			"bool":  false,
   373  			"bar":   []string{"a", "b"},
   374  		},
   375  		Since: &now,
   376  	}
   377  	s2 := status.StatusInfo{}
   378  	s.st.SetMachineInfo(c, machineInfo{id: "1", status: s1})
   379  	s.st.SetMachineInfo(c, machineInfo{id: "2", status: s2})
   380  
   381  	result, err := s.api.Status(s.mixedEntities)
   382  	c.Assert(err, jc.ErrorIsNil)
   383  	c.Assert(result, jc.DeepEquals, params.StatusResults{
   384  		Results: []params.StatusResult{
   385  			{
   386  				Status: status.Error.String(),
   387  				Info:   s1.Message,
   388  				Data:   s1.Data,
   389  				Since:  s1.Since,
   390  			},
   391  			{Status: "", Info: "", Data: nil, Since: nil},
   392  			{Error: apiservertesting.NotFoundError("machine 42")},
   393  			{Error: apiservertesting.ErrUnauthorized},
   394  			{Error: apiservertesting.ServerError(`"invalid-tag" is not a valid tag`)},
   395  			{Error: apiservertesting.ErrUnauthorized},
   396  			{Error: apiservertesting.ServerError(`"" is not a valid tag`)},
   397  			{Error: apiservertesting.ServerError(`"42" is not a valid tag`)},
   398  		}},
   399  	)
   400  
   401  	s.st.CheckFindEntityCall(c, 0, "1")
   402  	s.st.CheckCall(c, 1, "Status")
   403  	s.st.CheckFindEntityCall(c, 2, "2")
   404  	s.st.CheckCall(c, 3, "Status")
   405  	s.st.CheckFindEntityCall(c, 4, "42")
   406  }
   407  
   408  func (s *InstancePollerSuite) TestStatusFailure(c *gc.C) {
   409  	s.st.SetErrors(
   410  		errors.New("pow!"),                   // m1 := FindEntity("1"); Status not called
   411  		nil,                                  // m2 := FindEntity("2")
   412  		errors.New("FAIL"),                   // m2.Status()
   413  		errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved)
   414  	)
   415  	s.st.SetMachineInfo(c, machineInfo{id: "1"})
   416  	s.st.SetMachineInfo(c, machineInfo{id: "2"})
   417  
   418  	result, err := s.api.Status(s.machineEntities)
   419  	c.Assert(err, jc.ErrorIsNil)
   420  	c.Assert(result, jc.DeepEquals, params.StatusResults{
   421  		Results: []params.StatusResult{
   422  			{Error: apiservertesting.ServerError("pow!")},
   423  			{Error: apiservertesting.ServerError("FAIL")},
   424  			{Error: apiservertesting.NotProvisionedError("42")},
   425  		}},
   426  	)
   427  
   428  	s.st.CheckFindEntityCall(c, 0, "1")
   429  	s.st.CheckFindEntityCall(c, 1, "2")
   430  	s.st.CheckCall(c, 2, "Status")
   431  	s.st.CheckFindEntityCall(c, 3, "3")
   432  }
   433  
   434  func (s *InstancePollerSuite) TestProviderAddressesSuccess(c *gc.C) {
   435  	addrs := network.NewSpaceAddresses("0.1.2.3", "127.0.0.1", "8.8.8.8")
   436  	s.st.SetMachineInfo(c, machineInfo{id: "1", providerAddresses: addrs})
   437  	s.st.SetMachineInfo(c, machineInfo{id: "2", providerAddresses: nil})
   438  
   439  	result, err := s.api.ProviderAddresses(s.mixedEntities)
   440  	c.Assert(err, jc.ErrorIsNil)
   441  	c.Assert(result, jc.DeepEquals, params.MachineAddressesResults{
   442  		Results: []params.MachineAddressesResult{
   443  			{Addresses: toParamAddresses(addrs)},
   444  			{Addresses: nil},
   445  			{Error: apiservertesting.NotFoundError("machine 42")},
   446  			{Error: apiservertesting.ServerError(`"application-unknown" is not a valid machine tag`)},
   447  			{Error: apiservertesting.ServerError(`"invalid-tag" is not a valid tag`)},
   448  			{Error: apiservertesting.ServerError(`"unit-missing-1" is not a valid machine tag`)},
   449  			{Error: apiservertesting.ServerError(`"" is not a valid tag`)},
   450  			{Error: apiservertesting.ServerError(`"42" is not a valid tag`)},
   451  		}},
   452  	)
   453  
   454  	s.st.CheckMachineCall(c, 0, "1")
   455  	s.st.CheckCall(c, 1, "ProviderAddresses")
   456  	s.st.CheckCall(c, 2, "AllSpaceInfos")
   457  	s.st.CheckMachineCall(c, 3, "2")
   458  	s.st.CheckCall(c, 4, "ProviderAddresses")
   459  	s.st.CheckMachineCall(c, 5, "42")
   460  }
   461  
   462  func (s *InstancePollerSuite) TestProviderAddressesFailure(c *gc.C) {
   463  	s.st.SetErrors(
   464  		errors.New("pow!"),                   // m1 := FindEntity("1")
   465  		nil,                                  // m2 := FindEntity("2")
   466  		errors.New("FAIL"),                   // m2.ProviderAddresses()- unused
   467  		errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved)
   468  	)
   469  	s.st.SetMachineInfo(c, machineInfo{id: "1"})
   470  	s.st.SetMachineInfo(c, machineInfo{id: "2"})
   471  
   472  	result, err := s.api.ProviderAddresses(s.machineEntities)
   473  	c.Assert(err, jc.ErrorIsNil)
   474  	c.Assert(result, jc.DeepEquals, params.MachineAddressesResults{
   475  		Results: []params.MachineAddressesResult{
   476  			{Error: apiservertesting.ServerError("pow!")},
   477  			{Addresses: nil},
   478  			{Error: apiservertesting.NotProvisionedError("42")},
   479  		}},
   480  	)
   481  
   482  	s.st.CheckMachineCall(c, 0, "1")
   483  	s.st.CheckMachineCall(c, 1, "2")
   484  	s.st.CheckCall(c, 2, "ProviderAddresses")
   485  	s.st.CheckMachineCall(c, 3, "3")
   486  }
   487  
   488  func (s *InstancePollerSuite) TestSetProviderAddressesSuccess(c *gc.C) {
   489  	oldAddrs := network.NewSpaceAddresses("0.1.2.3", "127.0.0.1", "8.8.8.8")
   490  	newAddrs := network.SpaceAddresses{
   491  		network.NewSpaceAddress("1.2.3.4", network.WithCIDR("1.2.3.0/24")),
   492  		network.NewSpaceAddress("8.4.4.8", network.WithCIDR("8.4.4.0/24")),
   493  		network.NewSpaceAddress("2001:db8::"),
   494  	}
   495  
   496  	s.st.SetMachineInfo(c, machineInfo{id: "1", providerAddresses: oldAddrs})
   497  	s.st.SetMachineInfo(c, machineInfo{id: "2", providerAddresses: nil})
   498  
   499  	result, err := s.api.SetProviderAddresses(params.SetMachinesAddresses{
   500  		MachineAddresses: []params.MachineAddresses{
   501  			{Tag: "machine-1", Addresses: nil},
   502  			{Tag: "machine-2", Addresses: toParamAddresses(newAddrs)},
   503  			{Tag: "machine-42"},
   504  			{Tag: "application-unknown"},
   505  			{Tag: "invalid-tag"},
   506  			{Tag: "unit-missing-1"},
   507  			{Tag: ""},
   508  			{Tag: "42"},
   509  		}},
   510  	)
   511  	c.Assert(err, jc.ErrorIsNil)
   512  	c.Assert(result, jc.DeepEquals, s.mixedErrorResults)
   513  
   514  	s.st.CheckMachineCall(c, 0, "1")
   515  	s.st.CheckSetProviderAddressesCall(c, 1, []network.SpaceAddress{})
   516  	s.st.CheckMachineCall(c, 2, "2")
   517  	s.st.CheckCall(c, 3, "AllSpaceInfos")
   518  	s.st.CheckSetProviderAddressesCall(c, 4, newAddrs)
   519  	s.st.CheckMachineCall(c, 5, "42")
   520  
   521  	// Ensure machines were updated.
   522  	machine, err := s.st.Machine("1")
   523  	c.Assert(err, jc.ErrorIsNil)
   524  	c.Assert(machine.ProviderAddresses(), gc.HasLen, 0)
   525  
   526  	machine, err = s.st.Machine("2")
   527  	c.Assert(err, jc.ErrorIsNil)
   528  	c.Assert(machine.ProviderAddresses(), jc.DeepEquals, newAddrs)
   529  }
   530  
   531  func (s *InstancePollerSuite) TestSetProviderAddressesFailure(c *gc.C) {
   532  	s.st.SetErrors(
   533  		errors.New("pow!"),                   // m1 := FindEntity("1")
   534  		nil,                                  // m2 := FindEntity("2")
   535  		errors.New("FAIL"),                   // m2.SetProviderAddresses()
   536  		errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved)
   537  	)
   538  	oldAddrs := network.NewSpaceAddresses("0.1.2.3", "127.0.0.1", "8.8.8.8")
   539  	newAddrs := network.NewSpaceAddresses("1.2.3.4", "8.4.4.8", "2001:db8::")
   540  	s.st.SetMachineInfo(c, machineInfo{id: "1", providerAddresses: oldAddrs})
   541  	s.st.SetMachineInfo(c, machineInfo{id: "2", providerAddresses: nil})
   542  
   543  	result, err := s.api.SetProviderAddresses(params.SetMachinesAddresses{
   544  		MachineAddresses: []params.MachineAddresses{
   545  			{Tag: "machine-1"},
   546  			{Tag: "machine-2", Addresses: toParamAddresses(newAddrs)},
   547  			{Tag: "machine-3"},
   548  		}},
   549  	)
   550  	c.Assert(err, jc.ErrorIsNil)
   551  	c.Check(result, jc.DeepEquals, s.machineErrorResults)
   552  
   553  	s.st.CheckMachineCall(c, 0, "1")
   554  	s.st.CheckMachineCall(c, 1, "2")
   555  	s.st.CheckCall(c, 2, "AllSpaceInfos")
   556  	s.st.CheckSetProviderAddressesCall(c, 3, newAddrs)
   557  	s.st.CheckMachineCall(c, 4, "3")
   558  
   559  	// Ensure machine 2 wasn't updated.
   560  	machine, err := s.st.Machine("2")
   561  	c.Assert(err, jc.ErrorIsNil)
   562  	c.Assert(machine.ProviderAddresses(), gc.HasLen, 0)
   563  }
   564  
   565  func toParamAddresses(addrs network.SpaceAddresses) []params.Address {
   566  	paramAddrs := make([]params.Address, len(addrs))
   567  	for i, addr := range addrs {
   568  		paramAddrs[i] = params.Address{
   569  			Value: addr.Value,
   570  			Type:  string(addr.Type),
   571  			Scope: string(addr.Scope),
   572  			CIDR:  addr.CIDR,
   573  		}
   574  	}
   575  	return paramAddrs
   576  }
   577  
   578  func (s *InstancePollerSuite) TestInstanceStatusSuccess(c *gc.C) {
   579  	s.st.SetMachineInfo(c, machineInfo{id: "1", instanceStatus: statusInfo("foo")})
   580  	s.st.SetMachineInfo(c, machineInfo{id: "2", instanceStatus: statusInfo("")})
   581  
   582  	result, err := s.api.InstanceStatus(s.mixedEntities)
   583  	c.Assert(err, jc.ErrorIsNil)
   584  	c.Assert(result, jc.DeepEquals, params.StatusResults{
   585  		Results: []params.StatusResult{
   586  			{Status: "foo"},
   587  			{Status: ""},
   588  			{Error: apiservertesting.NotFoundError("machine 42")},
   589  			{Error: apiservertesting.ServerError(`"application-unknown" is not a valid machine tag`)},
   590  			{Error: apiservertesting.ServerError(`"invalid-tag" is not a valid tag`)},
   591  			{Error: apiservertesting.ServerError(`"unit-missing-1" is not a valid machine tag`)},
   592  			{Error: apiservertesting.ServerError(`"" is not a valid tag`)},
   593  			{Error: apiservertesting.ServerError(`"42" is not a valid tag`)},
   594  		},
   595  	},
   596  	)
   597  
   598  	s.st.CheckMachineCall(c, 0, "1")
   599  	s.st.CheckCall(c, 1, "InstanceStatus")
   600  	s.st.CheckMachineCall(c, 2, "2")
   601  	s.st.CheckCall(c, 3, "InstanceStatus")
   602  	s.st.CheckMachineCall(c, 4, "42")
   603  }
   604  
   605  func (s *InstancePollerSuite) TestInstanceStatusFailure(c *gc.C) {
   606  	s.st.SetErrors(
   607  		errors.New("pow!"),                   // m1 := FindEntity("1")
   608  		nil,                                  // m2 := FindEntity("2")
   609  		errors.New("FAIL"),                   // m2.InstanceStatus()
   610  		errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved)
   611  	)
   612  	s.st.SetMachineInfo(c, machineInfo{id: "1", instanceStatus: statusInfo("foo")})
   613  	s.st.SetMachineInfo(c, machineInfo{id: "2", instanceStatus: statusInfo("")})
   614  
   615  	result, err := s.api.InstanceStatus(s.machineEntities)
   616  	c.Assert(err, jc.ErrorIsNil)
   617  	c.Assert(result, jc.DeepEquals, params.StatusResults{
   618  		Results: []params.StatusResult{
   619  			{Error: apiservertesting.ServerError("pow!")},
   620  			{Error: apiservertesting.ServerError("FAIL")},
   621  			{Error: apiservertesting.NotProvisionedError("42")},
   622  		}},
   623  	)
   624  
   625  	s.st.CheckMachineCall(c, 0, "1")
   626  	s.st.CheckMachineCall(c, 1, "2")
   627  	s.st.CheckCall(c, 2, "InstanceStatus")
   628  	s.st.CheckMachineCall(c, 3, "3")
   629  }
   630  
   631  func (s *InstancePollerSuite) TestSetInstanceStatusSuccess(c *gc.C) {
   632  	s.st.SetMachineInfo(c, machineInfo{id: "1", instanceStatus: statusInfo("foo")})
   633  	s.st.SetMachineInfo(c, machineInfo{id: "2", instanceStatus: statusInfo("")})
   634  
   635  	result, err := s.api.SetInstanceStatus(params.SetStatus{
   636  		Entities: []params.EntityStatusArgs{
   637  			{Tag: "machine-1", Status: ""},
   638  			{Tag: "machine-2", Status: "new status"},
   639  			{Tag: "machine-42", Status: ""},
   640  			{Tag: "application-unknown", Status: ""},
   641  			{Tag: "invalid-tag", Status: ""},
   642  			{Tag: "unit-missing-1", Status: ""},
   643  			{Tag: "", Status: ""},
   644  			{Tag: "42", Status: ""},
   645  		}},
   646  	)
   647  	c.Assert(err, jc.ErrorIsNil)
   648  	c.Assert(result, jc.DeepEquals, s.mixedErrorResults)
   649  
   650  	now := s.clock.Now()
   651  	s.st.CheckMachineCall(c, 0, "1")
   652  	s.st.CheckCall(c, 1, "SetInstanceStatus", status.StatusInfo{Status: "", Since: &now})
   653  	s.st.CheckMachineCall(c, 2, "2")
   654  	s.st.CheckCall(c, 3, "SetInstanceStatus", status.StatusInfo{Status: "new status", Since: &now})
   655  	s.st.CheckMachineCall(c, 4, "42")
   656  
   657  	// Ensure machines were updated.
   658  	machine, err := s.st.Machine("1")
   659  	c.Assert(err, jc.ErrorIsNil)
   660  	// TODO (perrito666) there should not be an empty StatusInfo here,
   661  	// this is certainly a smell.
   662  	setStatus, err := machine.InstanceStatus()
   663  	c.Assert(err, jc.ErrorIsNil)
   664  	setStatus.Since = nil
   665  	c.Assert(setStatus, gc.DeepEquals, status.StatusInfo{})
   666  
   667  	machine, err = s.st.Machine("2")
   668  	c.Assert(err, jc.ErrorIsNil)
   669  	setStatus, err = machine.InstanceStatus()
   670  	c.Assert(err, jc.ErrorIsNil)
   671  	setStatus.Since = nil
   672  	c.Assert(setStatus, gc.DeepEquals, status.StatusInfo{Status: "new status"})
   673  }
   674  
   675  func (s *InstancePollerSuite) TestSetInstanceStatusFailure(c *gc.C) {
   676  	s.st.SetErrors(
   677  		errors.New("pow!"),                   // m1 := FindEntity("1")
   678  		nil,                                  // m2 := FindEntity("2")
   679  		errors.New("FAIL"),                   // m2.SetInstanceStatus()
   680  		errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved)
   681  	)
   682  	s.st.SetMachineInfo(c, machineInfo{id: "1", instanceStatus: statusInfo("foo")})
   683  	s.st.SetMachineInfo(c, machineInfo{id: "2", instanceStatus: statusInfo("")})
   684  
   685  	result, err := s.api.SetInstanceStatus(params.SetStatus{
   686  		Entities: []params.EntityStatusArgs{
   687  			{Tag: "machine-1", Status: "new"},
   688  			{Tag: "machine-2", Status: "invalid"},
   689  			{Tag: "machine-3", Status: ""},
   690  		}},
   691  	)
   692  	c.Assert(err, jc.ErrorIsNil)
   693  	c.Assert(result, jc.DeepEquals, s.machineErrorResults)
   694  
   695  	s.st.CheckMachineCall(c, 0, "1")
   696  	s.st.CheckMachineCall(c, 1, "2")
   697  	now := s.clock.Now()
   698  	s.st.CheckCall(c, 2, "SetInstanceStatus", status.StatusInfo{Status: "invalid", Since: &now})
   699  	s.st.CheckMachineCall(c, 3, "3")
   700  }
   701  
   702  func (s *InstancePollerSuite) TestAreManuallyProvisionedSuccess(c *gc.C) {
   703  	s.st.SetMachineInfo(c, machineInfo{id: "1", isManual: true})
   704  	s.st.SetMachineInfo(c, machineInfo{id: "2", isManual: false})
   705  
   706  	result, err := s.api.AreManuallyProvisioned(s.mixedEntities)
   707  	c.Assert(err, jc.ErrorIsNil)
   708  	c.Assert(result, jc.DeepEquals, params.BoolResults{
   709  		Results: []params.BoolResult{
   710  			{Result: true},
   711  			{Result: false},
   712  			{Error: apiservertesting.NotFoundError("machine 42")},
   713  			{Error: apiservertesting.ServerError(`"application-unknown" is not a valid machine tag`)},
   714  			{Error: apiservertesting.ServerError(`"invalid-tag" is not a valid tag`)},
   715  			{Error: apiservertesting.ServerError(`"unit-missing-1" is not a valid machine tag`)},
   716  			{Error: apiservertesting.ServerError(`"" is not a valid tag`)},
   717  			{Error: apiservertesting.ServerError(`"42" is not a valid tag`)},
   718  		}},
   719  	)
   720  
   721  	s.st.CheckMachineCall(c, 0, "1")
   722  	s.st.CheckCall(c, 1, "IsManual")
   723  	s.st.CheckMachineCall(c, 2, "2")
   724  	s.st.CheckCall(c, 3, "IsManual")
   725  	s.st.CheckMachineCall(c, 4, "42")
   726  }
   727  
   728  func (s *InstancePollerSuite) TestAreManuallyProvisionedFailure(c *gc.C) {
   729  	s.st.SetErrors(
   730  		errors.New("pow!"),                   // m1 := FindEntity("1")
   731  		nil,                                  // m2 := FindEntity("2")
   732  		errors.New("FAIL"),                   // m2.IsManual()
   733  		errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved)
   734  	)
   735  	s.st.SetMachineInfo(c, machineInfo{id: "1", isManual: true})
   736  	s.st.SetMachineInfo(c, machineInfo{id: "2", isManual: false})
   737  
   738  	result, err := s.api.AreManuallyProvisioned(s.machineEntities)
   739  	c.Assert(err, jc.ErrorIsNil)
   740  	c.Assert(result, jc.DeepEquals, params.BoolResults{
   741  		Results: []params.BoolResult{
   742  			{Error: apiservertesting.ServerError("pow!")},
   743  			{Error: apiservertesting.ServerError("FAIL")},
   744  			{Error: apiservertesting.NotProvisionedError("42")},
   745  		}},
   746  	)
   747  
   748  	s.st.CheckMachineCall(c, 0, "1")
   749  	s.st.CheckMachineCall(c, 1, "2")
   750  	s.st.CheckCall(c, 2, "IsManual")
   751  	s.st.CheckMachineCall(c, 3, "3")
   752  }
   753  
   754  func (s *InstancePollerSuite) TestSetProviderNetworkConfigSuccess(c *gc.C) {
   755  	s.setDefaultSpaceInfo()
   756  
   757  	s.st.SetMachineInfo(c, machineInfo{id: "1", instanceStatus: statusInfo("foo")})
   758  
   759  	results, err := s.api.SetProviderNetworkConfig(params.SetProviderNetworkConfig{
   760  		Args: []params.ProviderNetworkConfig{
   761  			{
   762  				Tag: "machine-1",
   763  				Configs: []params.NetworkConfig{
   764  					{
   765  						// TODO (manadart 2021-05-31): This tests that we
   766  						// consider the individual CIDRs and not the deprecated
   767  						// CIDR for the device.
   768  						// Remove for Juju 3/4.
   769  						CIDR: "10.0.0.0/24",
   770  						Addresses: []params.Address{
   771  							{
   772  								Value: "10.0.0.42",
   773  								Scope: "local-cloud",
   774  								CIDR:  "10.0.0.0/24",
   775  							},
   776  							{
   777  								Value: "10.73.37.110",
   778  								Scope: "local-cloud",
   779  								CIDR:  "10.73.37.0/24",
   780  							},
   781  							// Resolved by provider's space ID.
   782  							{
   783  								Value:           "10.73.37.111",
   784  								Scope:           "local-cloud",
   785  								ProviderSpaceID: "my-space-on-maas",
   786  							},
   787  							// This address does not match any of the CIDRs in
   788  							// the known spaces; we expect this to end up in
   789  							// alpha space.
   790  							{
   791  								Value: "192.168.0.1",
   792  								Scope: "local-cloud",
   793  							},
   794  						},
   795  						ShadowAddresses: []params.Address{
   796  							{
   797  								Value: "1.1.1.42",
   798  								Scope: "public",
   799  							},
   800  						},
   801  					},
   802  				},
   803  			},
   804  		},
   805  	})
   806  	c.Assert(err, jc.ErrorIsNil)
   807  	c.Assert(results.Results, gc.HasLen, 1)
   808  	result := results.Results[0]
   809  	c.Assert(result.Modified, jc.IsTrue)
   810  	c.Assert(result.Addresses, gc.HasLen, 5)
   811  	c.Assert(result.Addresses[0], gc.DeepEquals, params.Address{
   812  		Value:     "10.0.0.42",
   813  		Type:      "ipv4",
   814  		Scope:     "local-cloud",
   815  		SpaceName: "space1",
   816  	})
   817  	c.Assert(result.Addresses[1], gc.DeepEquals, params.Address{
   818  		Value:     "10.73.37.110",
   819  		Type:      "ipv4",
   820  		Scope:     "local-cloud",
   821  		SpaceName: "my-space-on-maas",
   822  	})
   823  	c.Assert(result.Addresses[2], gc.DeepEquals, params.Address{
   824  		Value:     "10.73.37.111",
   825  		Type:      "ipv4",
   826  		Scope:     "local-cloud",
   827  		SpaceName: "my-space-on-maas",
   828  	})
   829  	c.Assert(result.Addresses[3], gc.DeepEquals, params.Address{
   830  		Value:     "192.168.0.1",
   831  		Type:      "ipv4",
   832  		Scope:     "local-cloud",
   833  		SpaceName: "alpha",
   834  	})
   835  	c.Assert(result.Addresses[4], gc.DeepEquals, params.Address{
   836  		Value:     "1.1.1.42",
   837  		Type:      "ipv4",
   838  		Scope:     "public",
   839  		SpaceName: "alpha",
   840  	})
   841  
   842  	machine, err := s.st.Machine("1")
   843  	c.Assert(err, jc.ErrorIsNil)
   844  	providerAddrs := machine.ProviderAddresses()
   845  
   846  	c.Assert(providerAddrs, gc.DeepEquals, network.SpaceAddresses{
   847  		makeSpaceAddress("10.0.0.42", network.ScopeCloudLocal, "1"),
   848  		makeSpaceAddress("10.73.37.110", network.ScopeCloudLocal, "2"),
   849  		makeSpaceAddress("10.73.37.111", network.ScopeCloudLocal, "2"),
   850  		makeSpaceAddress("192.168.0.1", network.ScopeCloudLocal, network.AlphaSpaceId),
   851  		makeSpaceAddress("1.1.1.42", network.ScopePublic, network.AlphaSpaceId),
   852  	})
   853  }
   854  
   855  func (s *InstancePollerSuite) TestSetProviderNetworkConfigNoChange(c *gc.C) {
   856  	s.setDefaultSpaceInfo()
   857  
   858  	s.st.SetMachineInfo(c, machineInfo{
   859  		id:             "1",
   860  		instanceStatus: statusInfo("foo"),
   861  		providerAddresses: network.SpaceAddresses{
   862  			makeSpaceAddress("10.0.0.42", network.ScopeCloudLocal, "1"),
   863  			makeSpaceAddress("10.73.37.111", network.ScopeCloudLocal, "2"),
   864  			makeSpaceAddress("192.168.0.1", network.ScopeCloudLocal, network.AlphaSpaceId),
   865  			makeSpaceAddress("1.1.1.42", network.ScopePublic, network.AlphaSpaceId),
   866  		},
   867  	})
   868  
   869  	results, err := s.api.SetProviderNetworkConfig(params.SetProviderNetworkConfig{
   870  		Args: []params.ProviderNetworkConfig{
   871  			{
   872  				Tag: "machine-1",
   873  				Configs: []params.NetworkConfig{
   874  					{
   875  						Addresses: []params.Address{
   876  							{
   877  								Value: "10.0.0.42",
   878  								Scope: "local-cloud",
   879  							},
   880  							{
   881  								Value:           "10.73.37.111",
   882  								Scope:           "local-cloud",
   883  								ProviderSpaceID: "my-space-on-maas",
   884  							},
   885  							// This address does not match any of the CIDRs in
   886  							// the known spaces; we expect this to end up in
   887  							// alpha space.
   888  							{
   889  								Value: "192.168.0.1",
   890  								Scope: "local-cloud",
   891  							},
   892  						},
   893  						ShadowAddresses: []params.Address{
   894  							{
   895  								Value: "1.1.1.42",
   896  								Scope: "public",
   897  							},
   898  						},
   899  					},
   900  				},
   901  			},
   902  		},
   903  	})
   904  	c.Assert(err, jc.ErrorIsNil)
   905  	c.Assert(results.Results, gc.HasLen, 1)
   906  	result := results.Results[0]
   907  	c.Assert(result.Modified, jc.IsFalse)
   908  	c.Assert(result.Addresses, gc.HasLen, 4)
   909  	c.Assert(result.Addresses[0], gc.DeepEquals, params.Address{
   910  		Value:     "10.0.0.42",
   911  		Type:      "ipv4",
   912  		Scope:     "local-cloud",
   913  		SpaceName: "space1",
   914  	})
   915  	c.Assert(result.Addresses[1], gc.DeepEquals, params.Address{
   916  		Value:     "10.73.37.111",
   917  		Type:      "ipv4",
   918  		Scope:     "local-cloud",
   919  		SpaceName: "my-space-on-maas",
   920  	})
   921  	c.Assert(result.Addresses[2], gc.DeepEquals, params.Address{
   922  		Value:     "192.168.0.1",
   923  		Type:      "ipv4",
   924  		Scope:     "local-cloud",
   925  		SpaceName: "alpha",
   926  	})
   927  	c.Assert(result.Addresses[3], gc.DeepEquals, params.Address{
   928  		Value:     "1.1.1.42",
   929  		Type:      "ipv4",
   930  		Scope:     "public",
   931  		SpaceName: "alpha",
   932  	})
   933  }
   934  
   935  func (s *InstancePollerSuite) TestSetProviderNetworkConfigNotAlive(c *gc.C) {
   936  	s.st.SetMachineInfo(c, machineInfo{id: "1", life: state.Dying})
   937  
   938  	results, err := s.api.SetProviderNetworkConfig(params.SetProviderNetworkConfig{
   939  		Args: []params.ProviderNetworkConfig{{
   940  			Tag: "machine-1",
   941  			Configs: []params.NetworkConfig{{
   942  				Addresses: []params.Address{{Value: "10.0.0.42", Scope: "local-cloud"}},
   943  			}},
   944  		}},
   945  	})
   946  	c.Assert(err, jc.ErrorIsNil)
   947  	c.Check(results, jc.DeepEquals, params.SetProviderNetworkConfigResults{
   948  		Results: []params.SetProviderNetworkConfigResult{{}},
   949  	})
   950  
   951  	// We should just return after seeing that the machine is dying.
   952  	s.st.Stub.CheckCallNames(c, "AllSpaceInfos", "Machine", "Life", "Id")
   953  }
   954  
   955  func (s *InstancePollerSuite) TestSetProviderNetworkConfigRelinquishUnseen(c *gc.C) {
   956  	ctrl := gomock.NewController(c)
   957  	defer ctrl.Finish()
   958  
   959  	s.setDefaultSpaceInfo()
   960  
   961  	// Hardware address not matched.
   962  	dev := mocks.NewMockLinkLayerDevice(ctrl)
   963  	dExp := dev.EXPECT()
   964  	dExp.MACAddress().Return("01:01:01:01:01:01").MinTimes(1)
   965  	dExp.Name().Return("eth0").MinTimes(1)
   966  	dExp.SetProviderIDOps(network.Id("")).Return([]txn.Op{{C: "dev-provider-id"}}, nil)
   967  
   968  	// Address should be set back to machine origin.
   969  	addr := mocks.NewMockLinkLayerAddress(ctrl)
   970  	addr.EXPECT().DeviceName().Return("eth0")
   971  	addr.EXPECT().SetOriginOps(network.OriginMachine).Return([]txn.Op{{C: "address-origin-manual"}})
   972  
   973  	s.st.SetMachineInfo(c, machineInfo{
   974  		id:               "1",
   975  		instanceStatus:   statusInfo("foo"),
   976  		linkLayerDevices: []networkingcommon.LinkLayerDevice{dev},
   977  		addresses:        []networkingcommon.LinkLayerAddress{addr},
   978  	})
   979  
   980  	result, err := s.api.SetProviderNetworkConfig(params.SetProviderNetworkConfig{
   981  		Args: []params.ProviderNetworkConfig{
   982  			{
   983  				Tag:     "machine-1",
   984  				Configs: []params.NetworkConfig{{MACAddress: "00:00:00:00:00:00"}},
   985  			},
   986  		},
   987  	})
   988  	c.Assert(err, jc.ErrorIsNil)
   989  	c.Assert(result.Results, gc.HasLen, 1)
   990  	c.Assert(result.Results[0].Error, gc.IsNil)
   991  
   992  	var buildCalled bool
   993  	for _, call := range s.st.Calls() {
   994  		if call.FuncName == "ApplyOperation.Build" {
   995  			buildCalled = true
   996  			c.Check(call.Args, gc.DeepEquals, []interface{}{[]txn.Op{
   997  				{C: "machine-alive"},
   998  				{C: "dev-provider-id"},
   999  				{C: "address-origin-manual"},
  1000  			}})
  1001  		}
  1002  	}
  1003  	c.Assert(buildCalled, jc.IsTrue)
  1004  }
  1005  
  1006  func (s *InstancePollerSuite) TestSetProviderNetworkClaimProviderOrigin(c *gc.C) {
  1007  	ctrl := gomock.NewController(c)
  1008  	defer ctrl.Finish()
  1009  
  1010  	s.setDefaultSpaceInfo()
  1011  
  1012  	// Hardware address will match; provider ID will be set.
  1013  	dev := mocks.NewMockLinkLayerDevice(ctrl)
  1014  	dExp := dev.EXPECT()
  1015  	dExp.MACAddress().Return("00:00:00:00:00:00").MinTimes(1)
  1016  	dExp.Name().Return("eth0").MinTimes(1)
  1017  	dExp.ProviderID().Return(network.Id(""))
  1018  	dExp.SetProviderIDOps(network.Id("p-dev")).Return([]txn.Op{{C: "dev-provider-id"}}, nil)
  1019  
  1020  	// Address matched on device/value will have provider IDs set.
  1021  	addr := mocks.NewMockLinkLayerAddress(ctrl)
  1022  	aExp := addr.EXPECT()
  1023  	aExp.DeviceName().Return("eth0")
  1024  	aExp.Value().Return("10.0.0.42")
  1025  	aExp.SetProviderIDOps(network.Id("p-addr")).Return([]txn.Op{{C: "addr-provider-id"}}, nil)
  1026  	aExp.SetProviderNetIDsOps(network.Id("p-net"), network.Id("p-sub")).Return([]txn.Op{{C: "addr-provider-net-ids"}})
  1027  
  1028  	s.st.SetMachineInfo(c, machineInfo{
  1029  		id:               "1",
  1030  		instanceStatus:   statusInfo("foo"),
  1031  		linkLayerDevices: []networkingcommon.LinkLayerDevice{dev},
  1032  		addresses:        []networkingcommon.LinkLayerAddress{addr},
  1033  	})
  1034  
  1035  	result, err := s.api.SetProviderNetworkConfig(params.SetProviderNetworkConfig{
  1036  		Args: []params.ProviderNetworkConfig{
  1037  			{
  1038  				Tag: "machine-1",
  1039  				Configs: []params.NetworkConfig{
  1040  					{
  1041  						// This should still be matched based on hardware address.
  1042  						InterfaceName:     "",
  1043  						MACAddress:        "00:00:00:00:00:00",
  1044  						ProviderId:        "p-dev",
  1045  						ProviderAddressId: "p-addr",
  1046  						ProviderNetworkId: "p-net",
  1047  						ProviderSubnetId:  "p-sub",
  1048  						CIDR:              "10.0.0.0/24",
  1049  						Addresses:         []params.Address{{Value: "10.0.0.42"}},
  1050  					},
  1051  					{
  1052  						// A duplicate (MAC and addresses) should make no difference.
  1053  						InterfaceName:     "",
  1054  						MACAddress:        "00:00:00:00:00:00",
  1055  						ProviderId:        "p-dev",
  1056  						ProviderAddressId: "p-addr",
  1057  						ProviderNetworkId: "p-net",
  1058  						ProviderSubnetId:  "p-sub",
  1059  						CIDR:              "10.0.0.0/24",
  1060  						Addresses:         []params.Address{{Value: "10.0.0.42"}},
  1061  					},
  1062  				},
  1063  			},
  1064  		},
  1065  	})
  1066  	c.Assert(err, jc.ErrorIsNil)
  1067  	c.Assert(result.Results, gc.HasLen, 1)
  1068  	c.Assert(result.Results[0].Error, gc.IsNil)
  1069  
  1070  	var buildCalled bool
  1071  	for _, call := range s.st.Calls() {
  1072  		if call.FuncName == "ApplyOperation.Build" {
  1073  			buildCalled = true
  1074  			c.Check(call.Args, gc.DeepEquals, []interface{}{[]txn.Op{
  1075  				{C: "machine-alive"},
  1076  				{C: "dev-provider-id"},
  1077  				{C: "addr-provider-id"},
  1078  				{C: "addr-provider-net-ids"},
  1079  			}})
  1080  		}
  1081  	}
  1082  	c.Assert(buildCalled, jc.IsTrue)
  1083  }
  1084  
  1085  func (s *InstancePollerSuite) TestSetProviderNetworkProviderIDGoesToEthernetDev(c *gc.C) {
  1086  	ctrl := gomock.NewController(c)
  1087  	defer ctrl.Finish()
  1088  
  1089  	s.setDefaultSpaceInfo()
  1090  
  1091  	// Ethernet device will have the provider ID set.
  1092  	ethDev := mocks.NewMockLinkLayerDevice(ctrl)
  1093  	ethExp := ethDev.EXPECT()
  1094  	ethExp.MACAddress().Return("00:00:00:00:00:00").MinTimes(1)
  1095  	ethExp.Name().Return("eth0").MinTimes(1)
  1096  	ethExp.ProviderID().Return(network.Id("")).MinTimes(1)
  1097  	ethExp.SetProviderIDOps(network.Id("p-dev")).Return([]txn.Op{{C: "dev-provider-id"}}, nil)
  1098  
  1099  	// Bridge has the same MAC, but will not get the provider ID.
  1100  	brDev := mocks.NewMockLinkLayerDevice(ctrl)
  1101  	brExp := brDev.EXPECT()
  1102  	brExp.MACAddress().Return("00:00:00:00:00:00").MinTimes(1)
  1103  	brExp.Name().Return("br-eth0").AnyTimes()
  1104  	brExp.Type().Return(network.BridgeDevice)
  1105  
  1106  	s.st.SetMachineInfo(c, machineInfo{
  1107  		id:               "1",
  1108  		instanceStatus:   statusInfo("foo"),
  1109  		linkLayerDevices: []networkingcommon.LinkLayerDevice{ethDev, brDev},
  1110  		addresses:        []networkingcommon.LinkLayerAddress{},
  1111  	})
  1112  
  1113  	result, err := s.api.SetProviderNetworkConfig(params.SetProviderNetworkConfig{
  1114  		Args: []params.ProviderNetworkConfig{
  1115  			{
  1116  				Tag: "machine-1",
  1117  				Configs: []params.NetworkConfig{
  1118  					{
  1119  						// This should still be matched based on hardware address.
  1120  						InterfaceName: "",
  1121  						MACAddress:    "00:00:00:00:00:00",
  1122  						ProviderId:    "p-dev",
  1123  					},
  1124  				},
  1125  			},
  1126  		},
  1127  	})
  1128  	c.Assert(err, jc.ErrorIsNil)
  1129  	c.Assert(result.Results, gc.HasLen, 1)
  1130  	c.Assert(result.Results[0].Error, gc.IsNil)
  1131  
  1132  	var buildCalled bool
  1133  	for _, call := range s.st.Calls() {
  1134  		if call.FuncName == "ApplyOperation.Build" {
  1135  			buildCalled = true
  1136  		}
  1137  	}
  1138  	c.Assert(buildCalled, jc.IsTrue)
  1139  }
  1140  
  1141  func (s *InstancePollerSuite) TestSetProviderNetworkProviderIDMultipleRefsError(c *gc.C) {
  1142  	ctrl := gomock.NewController(c)
  1143  	defer ctrl.Finish()
  1144  
  1145  	s.setDefaultSpaceInfo()
  1146  
  1147  	ethDev := mocks.NewMockLinkLayerDevice(ctrl)
  1148  	ethExp := ethDev.EXPECT()
  1149  	ethExp.Name().Return("eth0").MinTimes(1)
  1150  	ethExp.ProviderID().Return(network.Id("")).MinTimes(1)
  1151  	ethExp.SetProviderIDOps(network.Id("p-dev")).Return([]txn.Op{{C: "dev-provider-id"}}, nil)
  1152  
  1153  	brDev := mocks.NewMockLinkLayerDevice(ctrl)
  1154  	brExp := brDev.EXPECT()
  1155  	brExp.Name().Return("br-eth0").AnyTimes()
  1156  	brExp.ProviderID().Return(network.Id("")).MinTimes(1)
  1157  	// Note no calls to SetProviderIDOps.
  1158  
  1159  	s.st.SetMachineInfo(c, machineInfo{
  1160  		id:               "1",
  1161  		instanceStatus:   statusInfo("foo"),
  1162  		linkLayerDevices: []networkingcommon.LinkLayerDevice{ethDev, brDev},
  1163  		addresses:        []networkingcommon.LinkLayerAddress{},
  1164  	})
  1165  
  1166  	// Same provider ID for both.
  1167  	result, err := s.api.SetProviderNetworkConfig(params.SetProviderNetworkConfig{
  1168  		Args: []params.ProviderNetworkConfig{
  1169  			{
  1170  				Tag: "machine-1",
  1171  				Configs: []params.NetworkConfig{
  1172  					{
  1173  						InterfaceName: "eth0",
  1174  						MACAddress:    "aa:00:00:00:00:00",
  1175  						ProviderId:    "p-dev",
  1176  					},
  1177  					{
  1178  						InterfaceName: "br-eth0",
  1179  						MACAddress:    "bb:00:00:00:00:00",
  1180  						ProviderId:    "p-dev",
  1181  					},
  1182  				},
  1183  			},
  1184  		},
  1185  	})
  1186  
  1187  	// The error is logged but not returned.
  1188  	c.Assert(err, jc.ErrorIsNil)
  1189  	c.Assert(result.Results, gc.HasLen, 1)
  1190  	c.Assert(result.Results[0].Error, gc.IsNil)
  1191  
  1192  	// But we should not have registered a successful call to Build.
  1193  	// This returns an error.
  1194  	var buildCalled bool
  1195  	for _, call := range s.st.Calls() {
  1196  		if call.FuncName == "ApplyOperation.Build" {
  1197  			buildCalled = true
  1198  		}
  1199  	}
  1200  	c.Assert(buildCalled, jc.IsFalse)
  1201  }
  1202  
  1203  func (s *InstancePollerSuite) setDefaultSpaceInfo() {
  1204  	s.st.SetSpaceInfo(network.SpaceInfos{
  1205  		{ID: network.AlphaSpaceId, Name: network.AlphaSpaceName},
  1206  		{ID: "1", Name: "space1", Subnets: []network.SubnetInfo{{CIDR: "10.0.0.0/24"}}},
  1207  		{ID: "2", Name: "my-space-on-maas", ProviderId: "my-space-on-maas", Subnets: []network.SubnetInfo{{CIDR: "10.73.37.0/24"}}},
  1208  	})
  1209  }
  1210  
  1211  func makeSpaceAddress(ip string, scope network.Scope, spaceID string) network.SpaceAddress {
  1212  	addr := network.NewSpaceAddress(ip, network.WithScope(scope))
  1213  	addr.SpaceID = spaceID
  1214  	return addr
  1215  }
  1216  
  1217  func statusInfo(st string) status.StatusInfo {
  1218  	return status.StatusInfo{Status: status.Status(st)}
  1219  }