github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/machineundertaker/undertaker_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package machineundertaker_test
     5  
     6  import (
     7  	stdcontext "context"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"github.com/juju/names/v5"
    12  	"github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/worker/v3"
    15  	"github.com/juju/worker/v3/workertest"
    16  	gc "gopkg.in/check.v1"
    17  	"gopkg.in/tomb.v2"
    18  
    19  	"github.com/juju/juju/core/network"
    20  	"github.com/juju/juju/core/watcher"
    21  	"github.com/juju/juju/environs"
    22  	"github.com/juju/juju/environs/context"
    23  	"github.com/juju/juju/worker/machineundertaker"
    24  )
    25  
    26  type undertakerSuite struct {
    27  	testing.IsolationSuite
    28  }
    29  
    30  var _ = gc.Suite(&undertakerSuite{})
    31  
    32  // Some tests to check that the handler is wired up to the
    33  // NotifyWorker first.
    34  
    35  func (s *undertakerSuite) TestErrorWatching(c *gc.C) {
    36  	api := s.makeAPIWithWatcher()
    37  	api.SetErrors(errors.New("blam"))
    38  	w, err := machineundertaker.NewWorker(
    39  		api, &fakeEnviron{}, &fakeCredentialAPI{}, loggo.GetLogger("test"))
    40  	c.Assert(err, jc.ErrorIsNil)
    41  	err = workertest.CheckKilled(c, w)
    42  	c.Check(err, gc.ErrorMatches, "blam")
    43  	api.CheckCallNames(c, "WatchMachineRemovals")
    44  }
    45  
    46  func (s *undertakerSuite) TestErrorGettingRemovals(c *gc.C) {
    47  	api := s.makeAPIWithWatcher()
    48  	api.SetErrors(nil, errors.New("explodo"))
    49  	w, err := machineundertaker.NewWorker(
    50  		api, &fakeEnviron{}, &fakeCredentialAPI{}, loggo.GetLogger("test"))
    51  	c.Assert(err, jc.ErrorIsNil)
    52  	err = workertest.CheckKilled(c, w)
    53  	c.Check(err, gc.ErrorMatches, "explodo")
    54  	api.CheckCallNames(c, "WatchMachineRemovals", "AllMachineRemovals")
    55  }
    56  
    57  // It's really fiddly trying to test the code behind the worker, so
    58  // the rest of the tests use the Undertaker directly to test the
    59  // Handle and MaybeReleaseAddresses methods. This is much simpler
    60  // because everything happens in the same goroutine (and it's safe
    61  // since all of the clever/tricky lifecycle management is taken care
    62  // of in NotifyWorker instead).
    63  
    64  func (*undertakerSuite) TestMaybeReleaseAddresses_NoNetworking(c *gc.C) {
    65  	api := fakeAPI{Stub: &testing.Stub{}}
    66  	u := machineundertaker.Undertaker{API: &api, Logger: loggo.GetLogger("test")}
    67  	err := u.MaybeReleaseAddresses(names.NewMachineTag("3"))
    68  	c.Assert(err, jc.ErrorIsNil)
    69  	api.CheckCallNames(c)
    70  }
    71  
    72  func (*undertakerSuite) TestMaybeReleaseAddresses_NotContainer(c *gc.C) {
    73  	api := fakeAPI{Stub: &testing.Stub{}}
    74  	releaser := fakeReleaser{}
    75  	u := machineundertaker.Undertaker{
    76  		API:      &api,
    77  		Releaser: &releaser,
    78  		Logger:   loggo.GetLogger("test"),
    79  	}
    80  	err := u.MaybeReleaseAddresses(names.NewMachineTag("4"))
    81  	c.Assert(err, jc.ErrorIsNil)
    82  	api.CheckCallNames(c)
    83  }
    84  
    85  func (*undertakerSuite) TestMaybeReleaseAddresses_ErrorGettingInfo(c *gc.C) {
    86  	api := fakeAPI{Stub: &testing.Stub{}}
    87  	api.SetErrors(errors.New("a funny thing happened on the way"))
    88  	releaser := fakeReleaser{}
    89  	u := machineundertaker.Undertaker{
    90  		API:      &api,
    91  		Releaser: &releaser,
    92  		Logger:   loggo.GetLogger("test"),
    93  	}
    94  	err := u.MaybeReleaseAddresses(names.NewMachineTag("4/lxd/2"))
    95  	c.Assert(err, gc.ErrorMatches, "a funny thing happened on the way")
    96  }
    97  
    98  func (*undertakerSuite) TestMaybeReleaseAddresses_NoAddresses(c *gc.C) {
    99  	api := fakeAPI{Stub: &testing.Stub{}}
   100  	releaser := fakeReleaser{Stub: &testing.Stub{}}
   101  	u := machineundertaker.Undertaker{
   102  		API:      &api,
   103  		Releaser: &releaser,
   104  		Logger:   loggo.GetLogger("test"),
   105  		CallContextFunc: func(_ stdcontext.Context) context.ProviderCallContext {
   106  			return context.NewEmptyCloudCallContext()
   107  		},
   108  	}
   109  	err := u.MaybeReleaseAddresses(names.NewMachineTag("4/lxd/4"))
   110  	c.Assert(err, jc.ErrorIsNil)
   111  	releaser.CheckCallNames(c)
   112  }
   113  
   114  func (*undertakerSuite) TestMaybeReleaseAddresses_NotSupported(c *gc.C) {
   115  	api := fakeAPI{
   116  		Stub: &testing.Stub{},
   117  		interfaces: map[string][]network.ProviderInterfaceInfo{
   118  			"4/lxd/4": {
   119  				{InterfaceName: "chloe"},
   120  			},
   121  		},
   122  	}
   123  	releaser := fakeReleaser{Stub: &testing.Stub{}}
   124  	releaser.SetErrors(errors.NotSupportedf("this sort of thing"))
   125  	u := machineundertaker.Undertaker{
   126  		API:      &api,
   127  		Releaser: &releaser,
   128  		Logger:   loggo.GetLogger("test"),
   129  		CallContextFunc: func(_ stdcontext.Context) context.ProviderCallContext {
   130  			return context.NewEmptyCloudCallContext()
   131  		},
   132  	}
   133  	err := u.MaybeReleaseAddresses(names.NewMachineTag("4/lxd/4"))
   134  	c.Assert(err, jc.ErrorIsNil)
   135  	releaser.CheckCall(c, 0, "ReleaseContainerAddresses",
   136  		[]network.ProviderInterfaceInfo{{InterfaceName: "chloe"}},
   137  	)
   138  }
   139  
   140  func (*undertakerSuite) TestMaybeReleaseAddresses_ErrorReleasing(c *gc.C) {
   141  	api := fakeAPI{
   142  		Stub: &testing.Stub{},
   143  		interfaces: map[string][]network.ProviderInterfaceInfo{
   144  			"4/lxd/4": {
   145  				{InterfaceName: "chloe"},
   146  			},
   147  		},
   148  	}
   149  	releaser := fakeReleaser{Stub: &testing.Stub{}}
   150  	releaser.SetErrors(errors.New("something unexpected"))
   151  	u := machineundertaker.Undertaker{
   152  		API:      &api,
   153  		Releaser: &releaser,
   154  		Logger:   loggo.GetLogger("test"),
   155  		CallContextFunc: func(_ stdcontext.Context) context.ProviderCallContext {
   156  			return context.NewEmptyCloudCallContext()
   157  		},
   158  	}
   159  	err := u.MaybeReleaseAddresses(names.NewMachineTag("4/lxd/4"))
   160  	c.Assert(err, gc.ErrorMatches, "something unexpected")
   161  	releaser.CheckCall(c, 0, "ReleaseContainerAddresses",
   162  		[]network.ProviderInterfaceInfo{{InterfaceName: "chloe"}},
   163  	)
   164  }
   165  
   166  func (*undertakerSuite) TestMaybeReleaseAddresses_Success(c *gc.C) {
   167  	api := fakeAPI{
   168  		Stub: &testing.Stub{},
   169  		interfaces: map[string][]network.ProviderInterfaceInfo{
   170  			"4/lxd/4": {
   171  				{InterfaceName: "chloe"},
   172  			},
   173  		},
   174  	}
   175  	releaser := fakeReleaser{Stub: &testing.Stub{}}
   176  	u := machineundertaker.Undertaker{
   177  		API:      &api,
   178  		Releaser: &releaser,
   179  		Logger:   loggo.GetLogger("test"),
   180  		CallContextFunc: func(_ stdcontext.Context) context.ProviderCallContext {
   181  			return context.NewEmptyCloudCallContext()
   182  		},
   183  	}
   184  	err := u.MaybeReleaseAddresses(names.NewMachineTag("4/lxd/4"))
   185  	c.Assert(err, jc.ErrorIsNil)
   186  	releaser.CheckCall(c, 0, "ReleaseContainerAddresses",
   187  		[]network.ProviderInterfaceInfo{{InterfaceName: "chloe"}},
   188  	)
   189  }
   190  
   191  func (*undertakerSuite) TestHandle_CompletesRemoval(c *gc.C) {
   192  	api := fakeAPI{
   193  		Stub:     &testing.Stub{},
   194  		removals: []string{"3", "4/lxd/4"},
   195  		interfaces: map[string][]network.ProviderInterfaceInfo{
   196  			"4/lxd/4": {
   197  				{InterfaceName: "chloe"},
   198  			},
   199  		},
   200  	}
   201  	releaser := fakeReleaser{Stub: &testing.Stub{}}
   202  	u := machineundertaker.Undertaker{
   203  		API:      &api,
   204  		Releaser: &releaser,
   205  		Logger:   loggo.GetLogger("test"),
   206  		CallContextFunc: func(_ stdcontext.Context) context.ProviderCallContext {
   207  			return context.NewEmptyCloudCallContext()
   208  		},
   209  	}
   210  	err := u.Handle(nil)
   211  	c.Assert(err, jc.ErrorIsNil)
   212  
   213  	c.Assert(releaser.Calls(), gc.HasLen, 1)
   214  	releaser.CheckCall(c, 0, "ReleaseContainerAddresses",
   215  		[]network.ProviderInterfaceInfo{{InterfaceName: "chloe"}},
   216  	)
   217  
   218  	checkRemovalsMatch(c, api.Stub, "3", "4/lxd/4")
   219  }
   220  
   221  func (*undertakerSuite) TestHandle_NoRemovalOnErrorReleasing(c *gc.C) {
   222  	api := fakeAPI{
   223  		Stub:     &testing.Stub{},
   224  		removals: []string{"3", "4/lxd/4", "5"},
   225  		interfaces: map[string][]network.ProviderInterfaceInfo{
   226  			"4/lxd/4": {
   227  				{InterfaceName: "chloe"},
   228  			},
   229  		},
   230  	}
   231  	releaser := fakeReleaser{Stub: &testing.Stub{}}
   232  	releaser.SetErrors(errors.New("couldn't release address"))
   233  	u := machineundertaker.Undertaker{
   234  		API:      &api,
   235  		Releaser: &releaser,
   236  		Logger:   loggo.GetLogger("test"),
   237  		CallContextFunc: func(_ stdcontext.Context) context.ProviderCallContext {
   238  			return context.NewEmptyCloudCallContext()
   239  		},
   240  	}
   241  	err := u.Handle(nil)
   242  	c.Assert(err, jc.ErrorIsNil)
   243  
   244  	c.Assert(releaser.Calls(), gc.HasLen, 1)
   245  	releaser.CheckCall(c, 0, "ReleaseContainerAddresses",
   246  		[]network.ProviderInterfaceInfo{{InterfaceName: "chloe"}},
   247  	)
   248  
   249  	checkRemovalsMatch(c, api.Stub, "3", "5")
   250  }
   251  
   252  func (*undertakerSuite) TestHandle_ErrorOnRemoval(c *gc.C) {
   253  	api := fakeAPI{
   254  		Stub:     &testing.Stub{},
   255  		removals: []string{"3", "4/lxd/4"},
   256  	}
   257  	api.SetErrors(nil, errors.New("couldn't remove machine 3"))
   258  	u := machineundertaker.Undertaker{API: &api, Logger: loggo.GetLogger("test")}
   259  	err := u.Handle(nil)
   260  	c.Assert(err, jc.ErrorIsNil)
   261  	checkRemovalsMatch(c, api.Stub, "3", "4/lxd/4")
   262  }
   263  
   264  func checkRemovalsMatch(c *gc.C, stub *testing.Stub, expected ...string) {
   265  	var completedRemovals []string
   266  	for _, call := range stub.Calls() {
   267  		if call.FuncName == "CompleteRemoval" {
   268  			machineId := call.Args[0].(names.MachineTag).Id()
   269  			completedRemovals = append(completedRemovals, machineId)
   270  		}
   271  	}
   272  	c.Check(completedRemovals, gc.DeepEquals, expected)
   273  }
   274  
   275  func (s *undertakerSuite) makeAPIWithWatcher() *fakeAPI {
   276  	return &fakeAPI{
   277  		Stub:    &testing.Stub{},
   278  		watcher: s.newMockNotifyWatcher(),
   279  	}
   280  }
   281  
   282  func (s *undertakerSuite) newMockNotifyWatcher() *mockNotifyWatcher {
   283  	m := &mockNotifyWatcher{
   284  		changes: make(chan struct{}, 1),
   285  	}
   286  	m.tomb.Go(func() error {
   287  		<-m.tomb.Dying()
   288  		return nil
   289  	})
   290  	s.AddCleanup(func(c *gc.C) {
   291  		err := worker.Stop(m)
   292  		c.Check(err, jc.ErrorIsNil)
   293  	})
   294  	m.Change()
   295  	return m
   296  }
   297  
   298  type fakeEnviron struct {
   299  	environs.NetworkingEnviron
   300  }
   301  
   302  type fakeReleaser struct {
   303  	*testing.Stub
   304  }
   305  
   306  func (r *fakeReleaser) ReleaseContainerAddresses(ctx context.ProviderCallContext, interfaces []network.ProviderInterfaceInfo) error {
   307  	r.Stub.AddCall("ReleaseContainerAddresses", interfaces)
   308  	return r.Stub.NextErr()
   309  }
   310  
   311  type fakeAPI struct {
   312  	machineundertaker.Facade
   313  
   314  	*testing.Stub
   315  	watcher    *mockNotifyWatcher
   316  	removals   []string
   317  	interfaces map[string][]network.ProviderInterfaceInfo
   318  }
   319  
   320  func (a *fakeAPI) WatchMachineRemovals() (watcher.NotifyWatcher, error) {
   321  	a.Stub.AddCall("WatchMachineRemovals")
   322  	return a.watcher, a.Stub.NextErr()
   323  }
   324  
   325  func (a *fakeAPI) AllMachineRemovals() ([]names.MachineTag, error) {
   326  	a.Stub.AddCall("AllMachineRemovals")
   327  	result := make([]names.MachineTag, len(a.removals))
   328  	for i := range a.removals {
   329  		result[i] = names.NewMachineTag(a.removals[i])
   330  	}
   331  	return result, a.Stub.NextErr()
   332  }
   333  
   334  func (a *fakeAPI) GetProviderInterfaceInfo(machine names.MachineTag) ([]network.ProviderInterfaceInfo, error) {
   335  	a.Stub.AddCall("GetProviderInterfaceInfo", machine)
   336  	return a.interfaces[machine.Id()], a.Stub.NextErr()
   337  }
   338  
   339  func (a *fakeAPI) CompleteRemoval(machine names.MachineTag) error {
   340  	a.Stub.AddCall("CompleteRemoval", machine)
   341  	return a.Stub.NextErr()
   342  }
   343  
   344  type mockNotifyWatcher struct {
   345  	watcher.NotifyWatcher
   346  
   347  	tomb    tomb.Tomb
   348  	changes chan struct{}
   349  }
   350  
   351  func (m *mockNotifyWatcher) Kill() {
   352  	m.tomb.Kill(nil)
   353  }
   354  
   355  func (m *mockNotifyWatcher) Wait() error {
   356  	return m.tomb.Wait()
   357  }
   358  
   359  func (m *mockNotifyWatcher) Changes() watcher.NotifyChannel {
   360  	return m.changes
   361  }
   362  
   363  func (m *mockNotifyWatcher) Change() {
   364  	m.changes <- struct{}{}
   365  }