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 }