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