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  }