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  }