github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/container/lxd/initialisation_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  //go:build linux
     5  
     6  package lxd
     7  
     8  import (
     9  	"os/exec"
    10  
    11  	lxd "github.com/canonical/lxd/client"
    12  	"github.com/canonical/lxd/shared/api"
    13  	"github.com/juju/packaging/v3/commands"
    14  	"github.com/juju/packaging/v3/manager"
    15  	"github.com/juju/proxy"
    16  	"github.com/juju/testing"
    17  	jc "github.com/juju/testing/checkers"
    18  	"go.uber.org/mock/gomock"
    19  	gc "gopkg.in/check.v1"
    20  
    21  	"github.com/juju/juju/container/lxd/mocks"
    22  	lxdtesting "github.com/juju/juju/container/lxd/testing"
    23  	"github.com/juju/juju/core/base"
    24  	coretesting "github.com/juju/juju/testing"
    25  )
    26  
    27  type initialiserTestSuite struct {
    28  	coretesting.BaseSuite
    29  	testing.PatchExecHelper
    30  }
    31  
    32  // patchDF100GB ensures that df always returns 100GB.
    33  func (s *initialiserTestSuite) patchDF100GB() {
    34  	df100 := func(path string) (uint64, error) {
    35  		return 100 * 1024 * 1024 * 1024, nil
    36  	}
    37  	s.PatchValue(&df, df100)
    38  }
    39  
    40  type InitialiserSuite struct {
    41  	initialiserTestSuite
    42  	calledCmds []string
    43  }
    44  
    45  var _ = gc.Suite(&InitialiserSuite{})
    46  
    47  const lxdSnapChannel = "latest/stable"
    48  
    49  func (s *InitialiserSuite) SetUpTest(c *gc.C) {
    50  	coretesting.SkipLXDNotSupported(c)
    51  	s.initialiserTestSuite.SetUpTest(c)
    52  	s.calledCmds = []string{}
    53  	s.PatchValue(&manager.RunCommandWithRetry, getMockRunCommandWithRetry(&s.calledCmds))
    54  
    55  	nonRandomizedOctetRange := func() []int {
    56  		// chosen by fair dice roll
    57  		// guaranteed to be random :)
    58  		// intentionally not random to allow for deterministic tests
    59  		return []int{4, 5, 6, 7, 8}
    60  	}
    61  	s.PatchValue(&randomizedOctetRange, nonRandomizedOctetRange)
    62  	// Fake the lxc executable for all the tests.
    63  	testing.PatchExecutableAsEchoArgs(c, s, "lxc")
    64  	testing.PatchExecutableAsEchoArgs(c, s, "lxd")
    65  }
    66  
    67  // getMockRunCommandWithRetry is a helper function which returns a function
    68  // with an identical signature to manager.RunCommandWithRetry which saves each
    69  // command it receives in a slice and always returns no output, error code 0
    70  // and a nil error.
    71  func getMockRunCommandWithRetry(calledCmds *[]string) func(string, manager.Retryable, manager.RetryPolicy) (string, int, error) {
    72  	return func(cmd string, _ manager.Retryable, _ manager.RetryPolicy) (string, int, error) {
    73  		*calledCmds = append(*calledCmds, cmd)
    74  		return "", 0, nil
    75  	}
    76  }
    77  
    78  func (s *initialiserTestSuite) containerInitialiser(svr lxd.InstanceServer, lxdIsRunning bool, containerNetworkingMethod string) *containerInitialiser {
    79  	result := NewContainerInitialiser(lxdSnapChannel, containerNetworkingMethod).(*containerInitialiser)
    80  	result.configureLxdProxies = func(proxy.Settings, func() (bool, error), func() (*Server, error)) error { return nil }
    81  	result.newLocalServer = func() (*Server, error) { return NewServer(svr) }
    82  	result.isRunningLocally = func() (bool, error) {
    83  		return lxdIsRunning, nil
    84  	}
    85  	return result
    86  }
    87  
    88  func (s *InitialiserSuite) TestSnapInstalled(c *gc.C) {
    89  	PatchLXDViaSnap(s, true)
    90  	PatchHostBase(s, base.MustParseBaseFromString("ubuntu@22.04"))
    91  
    92  	ctrl := gomock.NewController(c)
    93  	defer ctrl.Finish()
    94  
    95  	mgr := mocks.NewMockSnapManager(ctrl)
    96  	mgr.EXPECT().InstalledChannel("lxd").Return("latest/stable")
    97  	PatchGetSnapManager(s, mgr)
    98  
    99  	err := s.containerInitialiser(nil, true, "local").Initialise()
   100  	c.Assert(err, jc.ErrorIsNil)
   101  
   102  	c.Assert(s.calledCmds, gc.DeepEquals, []string{})
   103  }
   104  
   105  func (s *InitialiserSuite) TestSnapChannelMismatch(c *gc.C) {
   106  	PatchLXDViaSnap(s, true)
   107  	PatchHostBase(s, base.MustParseBaseFromString("ubuntu@20.04"))
   108  
   109  	ctrl := gomock.NewController(c)
   110  	defer ctrl.Finish()
   111  
   112  	mgr := mocks.NewMockSnapManager(ctrl)
   113  	gomock.InOrder(
   114  		mgr.EXPECT().InstalledChannel("lxd").Return("3.2/stable"),
   115  		mgr.EXPECT().ChangeChannel("lxd", lxdSnapChannel),
   116  	)
   117  	PatchGetSnapManager(s, mgr)
   118  
   119  	err := s.containerInitialiser(nil, true, "local").Initialise()
   120  	c.Assert(err, jc.ErrorIsNil)
   121  }
   122  
   123  func (s *InitialiserSuite) TestSnapChannelPrefixMatch(c *gc.C) {
   124  	PatchLXDViaSnap(s, true)
   125  	PatchHostBase(s, base.MustParseBaseFromString("ubuntu@20.04"))
   126  
   127  	ctrl := gomock.NewController(c)
   128  	defer ctrl.Finish()
   129  
   130  	mgr := mocks.NewMockSnapManager(ctrl)
   131  	gomock.InOrder(
   132  		// The channel for the installed lxd snap also includes the
   133  		// branch for the focal release. The "track/risk" prefix is
   134  		// the same however so the container manager should not attempt
   135  		// to change the channel.
   136  		mgr.EXPECT().InstalledChannel("lxd").Return("latest/stable/ubuntu-20.04"),
   137  	)
   138  	PatchGetSnapManager(s, mgr)
   139  
   140  	err := s.containerInitialiser(nil, true, "local").Initialise()
   141  	c.Assert(err, jc.ErrorIsNil)
   142  }
   143  
   144  func (s *InitialiserSuite) TestInstallViaSnap(c *gc.C) {
   145  	PatchLXDViaSnap(s, false)
   146  
   147  	PatchHostBase(s, base.MustParseBaseFromString("ubuntu@20.04"))
   148  
   149  	paccmder := commands.NewSnapPackageCommander()
   150  
   151  	err := s.containerInitialiser(nil, true, "local").Initialise()
   152  	c.Assert(err, jc.ErrorIsNil)
   153  
   154  	c.Assert(s.calledCmds, gc.DeepEquals, []string{
   155  		paccmder.InstallCmd("--classic --channel latest/stable lxd"),
   156  	})
   157  }
   158  
   159  func (s *InitialiserSuite) TestLXDAlreadyInitialized(c *gc.C) {
   160  	s.patchDF100GB()
   161  	PatchHostBase(s, base.MustParseBaseFromString("ubuntu@20.04"))
   162  
   163  	ci := s.containerInitialiser(nil, true, "local")
   164  	ci.getExecCommand = s.PatchExecHelper.GetExecCommand(testing.PatchExecConfig{
   165  		Stderr:   `error: You have existing containers or images. lxd init requires an empty LXD.`,
   166  		ExitCode: 1,
   167  	})
   168  
   169  	// the above error should be ignored by the code that calls lxd init.
   170  	err := ci.Initialise()
   171  	c.Assert(err, jc.ErrorIsNil)
   172  }
   173  
   174  func (s *InitialiserSuite) TestInitializeSetsProxies(c *gc.C) {
   175  	PatchHostBase(s, base.MustParseBaseFromString("ubuntu@20.04"))
   176  
   177  	ctrl := gomock.NewController(c)
   178  	defer ctrl.Finish()
   179  	cSvr := lxdtesting.NewMockInstanceServer(ctrl)
   180  
   181  	s.PatchEnvironment("http_proxy", "http://test.local/http/proxy")
   182  	s.PatchEnvironment("https_proxy", "http://test.local/https/proxy")
   183  	s.PatchEnvironment("no_proxy", "test.local,localhost")
   184  
   185  	var calls []string
   186  	updateReq := api.ServerPut{Config: map[string]interface{}{
   187  		"core.proxy_http":         "http://test.local/http/proxy",
   188  		"core.proxy_https":        "http://test.local/https/proxy",
   189  		"core.proxy_ignore_hosts": "test.local,localhost",
   190  	}}
   191  	gomock.InOrder(
   192  		cSvr.EXPECT().GetServer().Return(&api.Server{}, lxdtesting.ETag, nil).Times(2),
   193  		cSvr.EXPECT().UpdateServer(updateReq, lxdtesting.ETag).DoAndReturn(func(_ api.ServerPut, _ string) error {
   194  			calls = append(calls, "update server")
   195  			return nil
   196  		}),
   197  	)
   198  
   199  	ci := s.containerInitialiser(cSvr, true, "local")
   200  	ci.configureLxdProxies = internalConfigureLXDProxies
   201  	ci.getExecCommand = func(cmd string, args ...string) *exec.Cmd {
   202  		calls = append(calls, "exec command")
   203  		return exec.Command(cmd, args...)
   204  	}
   205  	err := ci.Initialise()
   206  	c.Assert(err, jc.ErrorIsNil)
   207  
   208  	// We want update server to ve called last, after the lxd init command is run.
   209  	c.Assert(calls, jc.DeepEquals, []string{
   210  		"exec command",
   211  		"update server",
   212  	})
   213  }
   214  
   215  func (s *InitialiserSuite) TestConfigureProxiesLXDNotRunning(c *gc.C) {
   216  	ctrl := gomock.NewController(c)
   217  	defer ctrl.Finish()
   218  	cSvr := lxdtesting.NewMockInstanceServer(ctrl)
   219  
   220  	s.PatchEnvironment("http_proxy", "http://test.local/http/proxy")
   221  	s.PatchEnvironment("https_proxy", "http://test.local/https/proxy")
   222  	s.PatchEnvironment("no_proxy", "test.local,localhost")
   223  
   224  	// No expected calls.
   225  	ci := s.containerInitialiser(cSvr, false, "local")
   226  	err := ci.Initialise()
   227  	c.Assert(err, jc.ErrorIsNil)
   228  }
   229  
   230  type ConfigureInitialiserSuite struct {
   231  	initialiserTestSuite
   232  	testing.PatchExecHelper
   233  }
   234  
   235  var _ = gc.Suite(&ConfigureInitialiserSuite{})
   236  
   237  func (s *ConfigureInitialiserSuite) SetUpTest(c *gc.C) {
   238  	s.initialiserTestSuite.SetUpTest(c)
   239  	// Fake the lxc executable for all the tests.
   240  	testing.PatchExecutableAsEchoArgs(c, s, "lxc")
   241  	testing.PatchExecutableAsEchoArgs(c, s, "lxd")
   242  }