github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/container/broker/host_preparer_test.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package broker_test 5 6 import ( 7 "time" 8 9 "github.com/juju/errors" 10 "github.com/juju/loggo" 11 "github.com/juju/mutex/v2" 12 "github.com/juju/names/v5" 13 gitjujutesting "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 gc "gopkg.in/check.v1" 16 17 "github.com/juju/juju/container/broker" 18 corenetwork "github.com/juju/juju/core/network" 19 "github.com/juju/juju/network" 20 "github.com/juju/juju/rpc/params" 21 coretesting "github.com/juju/juju/testing" 22 ) 23 24 type fakePrepareAPI struct { 25 *gitjujutesting.Stub 26 requestedBridges []network.DeviceToBridge 27 reconfigureDelay int 28 } 29 30 var _ broker.PrepareAPI = (*fakePrepareAPI)(nil) 31 32 func (api *fakePrepareAPI) HostChangesForContainer(tag names.MachineTag) ([]network.DeviceToBridge, int, error) { 33 api.Stub.MethodCall(api, "HostChangesForContainer", tag) 34 if err := api.Stub.NextErr(); err != nil { 35 return nil, 0, err 36 } 37 return api.requestedBridges, api.reconfigureDelay, nil 38 } 39 40 func (api *fakePrepareAPI) SetHostMachineNetworkConfig(tag names.MachineTag, config []params.NetworkConfig) error { 41 api.Stub.MethodCall(api, "SetHostMachineNetworkConfig", tag, config) 42 if err := api.Stub.NextErr(); err != nil { 43 return err 44 } 45 return nil 46 } 47 48 type hostPreparerSuite struct { 49 Stub *gitjujutesting.Stub 50 } 51 52 var _ = gc.Suite(&hostPreparerSuite{}) 53 54 func (s *hostPreparerSuite) SetUpTest(c *gc.C) { 55 s.Stub = &gitjujutesting.Stub{} 56 } 57 58 type stubReleaser struct { 59 *gitjujutesting.Stub 60 } 61 62 func (r *stubReleaser) Release() { 63 r.MethodCall(r, "Release") 64 } 65 66 func (s *hostPreparerSuite) acquireStubLock(_ string, _ <-chan struct{}) (func(), error) { 67 s.Stub.AddCall("AcquireLock") 68 if err := s.Stub.NextErr(); err != nil { 69 return nil, err 70 } 71 releaser := &stubReleaser{ 72 Stub: s.Stub, 73 } 74 return releaser.Release, nil 75 } 76 77 type stubBridger struct { 78 *gitjujutesting.Stub 79 } 80 81 var _ network.Bridger = (*stubBridger)(nil) 82 83 func (br *stubBridger) Bridge(devices []network.DeviceToBridge, reconfigureDelay int) error { 84 br.Stub.MethodCall(br, "Bridge", devices, reconfigureDelay) 85 if err := br.Stub.NextErr(); err != nil { 86 return err 87 } 88 return nil 89 } 90 91 func (s *hostPreparerSuite) createStubBridger() (network.Bridger, error) { 92 s.Stub.AddCall("CreateBridger") 93 if err := s.Stub.NextErr(); err != nil { 94 return nil, err 95 } 96 return &stubBridger{ 97 s.Stub, 98 }, nil 99 } 100 101 type cannedNetworkObserver struct { 102 *gitjujutesting.Stub 103 config []params.NetworkConfig 104 } 105 106 func (cno *cannedNetworkObserver) ObserveNetwork() ([]params.NetworkConfig, error) { 107 cno.Stub.AddCall("ObserveNetwork") 108 if err := cno.Stub.NextErr(); err != nil { 109 return nil, err 110 } 111 return cno.config, nil 112 } 113 114 func (s *hostPreparerSuite) createPreparerParams(bridges []network.DeviceToBridge, observed []params.NetworkConfig) broker.HostPreparerParams { 115 observer := &cannedNetworkObserver{ 116 Stub: s.Stub, 117 config: observed, 118 } 119 return broker.HostPreparerParams{ 120 API: &fakePrepareAPI{ 121 Stub: s.Stub, 122 requestedBridges: bridges, 123 }, 124 AcquireLockFunc: s.acquireStubLock, 125 CreateBridger: s.createStubBridger, 126 ObserveNetworkFunc: observer.ObserveNetwork, 127 MachineTag: names.NewMachineTag("1"), 128 Logger: loggo.GetLogger("prepare-host.test"), 129 } 130 } 131 132 func (s *hostPreparerSuite) createPreparer(bridges []network.DeviceToBridge, observed []params.NetworkConfig) *broker.HostPreparer { 133 params := s.createPreparerParams(bridges, observed) 134 return broker.NewHostPreparer(params) 135 } 136 137 func (s *hostPreparerSuite) TestPrepareHostNoChanges(c *gc.C) { 138 preparer := s.createPreparer(nil, nil) 139 containerTag := names.NewMachineTag("1/lxd/0") 140 err := preparer.Prepare(containerTag) 141 c.Assert(err, jc.ErrorIsNil) 142 // If HostChangesForContainer returns nothing to change, then we don't 143 // instantiate a Bridger, or do any bridging. 144 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 145 { 146 FuncName: "AcquireLock", 147 }, { 148 FuncName: "HostChangesForContainer", 149 Args: []interface{}{containerTag}, 150 }, 151 { 152 FuncName: "Release", 153 }, 154 }) 155 } 156 157 var cannedObservedNetworkConfig = []params.NetworkConfig{{ 158 DeviceIndex: 0, 159 MACAddress: "aa:bb:cc:dd:ee:ff", 160 CIDR: "127.0.0.1/24", 161 MTU: 1500, 162 InterfaceName: "lo", 163 ParentInterfaceName: "", 164 InterfaceType: string(corenetwork.LoopbackDevice), 165 Disabled: false, 166 NoAutoStart: false, 167 ConfigType: string(corenetwork.ConfigLoopback), 168 }, { 169 DeviceIndex: 1, 170 MACAddress: "bb:cc:dd:ee:ff:00", 171 CIDR: "", 172 MTU: 1500, 173 InterfaceName: "eth0", 174 ParentInterfaceName: "br-eth0", 175 InterfaceType: string(corenetwork.EthernetDevice), 176 Disabled: false, 177 NoAutoStart: false, 178 ConfigType: string(corenetwork.ConfigStatic), 179 }, { 180 DeviceIndex: 2, 181 MACAddress: "bb:cc:dd:ee:ff:00", 182 CIDR: "", 183 MTU: 1500, 184 InterfaceName: "br-eth0", 185 ParentInterfaceName: "", 186 InterfaceType: string(corenetwork.BridgeDevice), 187 Disabled: false, 188 NoAutoStart: false, 189 ConfigType: string(corenetwork.ConfigStatic), 190 }} 191 192 func (s *hostPreparerSuite) TestPrepareHostCreateBridge(c *gc.C) { 193 devices := []network.DeviceToBridge{{ 194 DeviceName: "eth0", 195 BridgeName: "br-eth0", 196 }} 197 preparer := s.createPreparer(devices, cannedObservedNetworkConfig) 198 containerTag := names.NewMachineTag("1/lxd/0") 199 err := preparer.Prepare(containerTag) 200 c.Assert(err, jc.ErrorIsNil) 201 // This should be the normal flow if there are changes necessary. We read 202 // the changes, grab a bridger, then acquire a lock, do the bridging, 203 // observe the results, report the results, and release the lock. 204 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 205 { 206 FuncName: "AcquireLock", 207 }, { 208 FuncName: "HostChangesForContainer", 209 Args: []interface{}{containerTag}, 210 }, { 211 FuncName: "CreateBridger", 212 }, { 213 FuncName: "Bridge", 214 Args: []interface{}{devices, 0}, 215 }, { 216 FuncName: "ObserveNetwork", 217 }, { 218 FuncName: "SetHostMachineNetworkConfig", 219 Args: []interface{}{names.NewMachineTag("1"), cannedObservedNetworkConfig}, 220 }, { 221 FuncName: "Release", 222 }, 223 }) 224 } 225 226 func (s *hostPreparerSuite) TestPrepareHostNothingObserved(c *gc.C) { 227 devices := []network.DeviceToBridge{{ 228 DeviceName: "eth0", 229 BridgeName: "br-eth0", 230 }} 231 observed := []params.NetworkConfig(nil) 232 preparer := s.createPreparer(devices, observed) 233 containerTag := names.NewMachineTag("1/lxd/0") 234 err := preparer.Prepare(containerTag) 235 c.Assert(err, jc.ErrorIsNil) 236 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 237 { 238 FuncName: "AcquireLock", 239 }, { 240 FuncName: "HostChangesForContainer", 241 Args: []interface{}{containerTag}, 242 }, { 243 FuncName: "CreateBridger", 244 }, { 245 FuncName: "Bridge", 246 Args: []interface{}{devices, 0}, 247 }, { 248 FuncName: "ObserveNetwork", 249 // We don't call SetHostMachineNetworkConfig if ObserveNetwork returns nothing 250 }, { 251 FuncName: "Release", 252 }, 253 }) 254 } 255 256 func (s *hostPreparerSuite) TestPrepareHostChangesUnsupported(c *gc.C) { 257 // ensure that errors calling HostChangesForContainer are treated as 258 // provisioning errors, instead of assuming we can continue creating a 259 // container. 260 s.Stub.SetErrors( 261 nil, 262 errors.NotSupportedf("container address allocation"), 263 nil, 264 ) 265 preparer := s.createPreparer(nil, nil) 266 containerTag := names.NewMachineTag("1/lxd/0") 267 err := preparer.Prepare(containerTag) 268 c.Assert(err, gc.ErrorMatches, "unable to setup network: container address allocation not supported") 269 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 270 { 271 FuncName: "AcquireLock", 272 }, { 273 FuncName: "HostChangesForContainer", 274 Args: []interface{}{containerTag}, 275 }, { 276 FuncName: "Release", 277 }, 278 }) 279 } 280 281 func (s *hostPreparerSuite) TestPrepareHostNoBridger(c *gc.C) { 282 s.Stub.SetErrors( 283 nil, // AcquireLock 284 nil, // HostChangesForContainer 285 errors.New("unable to find python interpreter"), // CreateBridger 286 nil, //Release 287 ) 288 devices := []network.DeviceToBridge{{ 289 DeviceName: "eth0", 290 BridgeName: "br-eth0", 291 }} 292 preparer := s.createPreparer(devices, nil) 293 containerTag := names.NewMachineTag("1/lxd/0") 294 err := preparer.Prepare(containerTag) 295 c.Check(err, gc.ErrorMatches, "unable to find python interpreter") 296 297 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 298 { 299 FuncName: "AcquireLock", 300 }, { 301 FuncName: "HostChangesForContainer", 302 Args: []interface{}{containerTag}, 303 }, { 304 FuncName: "CreateBridger", 305 }, { 306 FuncName: "Release", 307 }, 308 }) 309 } 310 311 func (s *hostPreparerSuite) TestPrepareHostNoLock(c *gc.C) { 312 s.Stub.SetErrors( 313 mutex.ErrTimeout, // AcquireLock 314 ) 315 devices := []network.DeviceToBridge{{ 316 DeviceName: "eth0", 317 BridgeName: "br-eth0", 318 }} 319 preparer := s.createPreparer(devices, nil) 320 containerTag := names.NewMachineTag("1/lxd/0") 321 err := preparer.Prepare(containerTag) 322 c.Check(err, gc.ErrorMatches, `failed to acquire machine lock for bridging: timeout acquiring mutex`) 323 324 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 325 { 326 FuncName: "AcquireLock", 327 }, 328 }) 329 } 330 331 func (s *hostPreparerSuite) TestPrepareHostBridgeFailure(c *gc.C) { 332 s.Stub.SetErrors( 333 nil, // HostChangesForContainer 334 nil, // CreateBridger 335 nil, // AcquireLock 336 errors.New("script invocation error: IOError"), // Bridge 337 ) 338 devices := []network.DeviceToBridge{{ 339 DeviceName: "eth0", 340 BridgeName: "br-eth0", 341 }} 342 preparer := s.createPreparer(devices, nil) 343 containerTag := names.NewMachineTag("1/lxd/0") 344 err := preparer.Prepare(containerTag) 345 c.Check(err, gc.ErrorMatches, `failed to bridge devices: script invocation error: IOError`) 346 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 347 { 348 FuncName: "AcquireLock", 349 }, { 350 FuncName: "HostChangesForContainer", 351 Args: []interface{}{containerTag}, 352 }, { 353 FuncName: "CreateBridger", 354 }, { 355 FuncName: "Bridge", 356 Args: []interface{}{devices, 0}, 357 }, { 358 // We don't observe the network information. 359 // TODO(jam): 2017-02-15 This is possibly wrong, we might consider 360 // a) Forcibly restarting if Bridge() fails, 361 // b) Still observing and reporting our observation so that we at least 362 // know what state we ended up in. 363 FuncName: "Release", 364 }, 365 }) 366 } 367 368 func (s *hostPreparerSuite) TestPrepareHostObserveFailure(c *gc.C) { 369 s.Stub.SetErrors( 370 nil, // HostChangesForContainer 371 nil, // CreateBridger 372 nil, // AcquireLock 373 nil, // BridgeBridgeFailure 374 errors.New("cannot get network interfaces: enoent"), // GetObservedNetworkConfig 375 ) 376 devices := []network.DeviceToBridge{{ 377 DeviceName: "eth0", 378 BridgeName: "br-eth0", 379 }} 380 preparer := s.createPreparer(devices, nil) 381 containerTag := names.NewMachineTag("1/lxd/0") 382 err := preparer.Prepare(containerTag) 383 c.Check(err, gc.ErrorMatches, `cannot discover observed network config: cannot get network interfaces: enoent`) 384 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 385 { 386 FuncName: "AcquireLock", 387 }, { 388 FuncName: "HostChangesForContainer", 389 Args: []interface{}{containerTag}, 390 }, { 391 FuncName: "CreateBridger", 392 }, { 393 FuncName: "Bridge", 394 Args: []interface{}{devices, 0}, 395 }, { 396 FuncName: "ObserveNetwork", 397 }, { 398 // We don't SetHostMachineNetworkConfig, but we still release the lock. 399 FuncName: "Release", 400 }, 401 }) 402 } 403 404 func (s *hostPreparerSuite) TestPrepareHostObservedFailure(c *gc.C) { 405 s.Stub.SetErrors( 406 nil, // HostChangesForContainer 407 nil, // CreateBridger 408 nil, // AcquireLock 409 nil, // BridgeBridgeFailure 410 nil, // ObserveNetwork 411 errors.Unauthorizedf("failure"), // SetHostMachineNetworkConfig 412 ) 413 devices := []network.DeviceToBridge{{ 414 DeviceName: "eth0", 415 BridgeName: "br-eth0", 416 }} 417 preparer := s.createPreparer(devices, cannedObservedNetworkConfig) 418 containerTag := names.NewMachineTag("1/lxd/0") 419 err := preparer.Prepare(containerTag) 420 c.Check(err, gc.ErrorMatches, `failure`) 421 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 422 { 423 FuncName: "AcquireLock", 424 }, { 425 FuncName: "HostChangesForContainer", 426 Args: []interface{}{containerTag}, 427 }, { 428 FuncName: "CreateBridger", 429 }, { 430 FuncName: "Bridge", 431 Args: []interface{}{devices, 0}, 432 }, { 433 FuncName: "ObserveNetwork", 434 }, { 435 FuncName: "SetHostMachineNetworkConfig", 436 Args: []interface{}{names.NewMachineTag("1"), cannedObservedNetworkConfig}, 437 }, { 438 FuncName: "Release", 439 }, 440 }) 441 } 442 443 func (s *hostPreparerSuite) TestPrepareHostCancel(c *gc.C) { 444 devices := []network.DeviceToBridge{{ 445 DeviceName: "eth0", 446 BridgeName: "br-eth0", 447 }} 448 args := s.createPreparerParams(devices, nil) 449 ch := make(chan struct{}) 450 close(ch) 451 args.AbortChan = ch 452 // This is what the AcquireLock should look like. 453 args.AcquireLockFunc = func(_ string, abort <-chan struct{}) (func(), error) { 454 s.Stub.AddCall("AcquireLockFunc") 455 // Make sure that the right channel got passed in. 456 c.Check(abort, gc.Equals, (<-chan struct{})(ch)) 457 select { 458 case <-abort: 459 return nil, errors.Errorf("AcquireLock cancelled") 460 case <-time.After(coretesting.LongWait): 461 c.Fatalf("timeout waiting for channel to return aborted") 462 return nil, errors.Errorf("timeout triggered") 463 } 464 } 465 preparer := broker.NewHostPreparer(args) 466 // Now when we prepare, we should fail with "cancelled". 467 containerTag := names.NewMachineTag("1/lxd/0") 468 err := preparer.Prepare(containerTag) 469 c.Check(err, gc.ErrorMatches, `failed to acquire machine lock for bridging: AcquireLock cancelled`) 470 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 471 { 472 FuncName: "AcquireLockFunc", 473 }, 474 }) 475 }