github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/container/kvm/kvm_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package kvm_test
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"sync"
    12  
    13  	"github.com/juju/loggo"
    14  	jc "github.com/juju/testing/checkers"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	"github.com/juju/juju/container"
    18  	"github.com/juju/juju/container/kvm"
    19  	"github.com/juju/juju/container/kvm/mock"
    20  	kvmtesting "github.com/juju/juju/container/kvm/testing"
    21  	containertesting "github.com/juju/juju/container/testing"
    22  	"github.com/juju/juju/core/arch"
    23  	"github.com/juju/juju/core/base"
    24  	"github.com/juju/juju/core/constraints"
    25  	"github.com/juju/juju/core/instance"
    26  	"github.com/juju/juju/core/network"
    27  	"github.com/juju/juju/core/status"
    28  	"github.com/juju/juju/environs/config"
    29  	"github.com/juju/juju/environs/imagemetadata"
    30  	coretesting "github.com/juju/juju/testing"
    31  )
    32  
    33  type KVMSuite struct {
    34  	kvmtesting.TestSuite
    35  	manager container.Manager
    36  }
    37  
    38  var _ = gc.Suite(&KVMSuite{})
    39  
    40  func (s *KVMSuite) SetUpTest(c *gc.C) {
    41  	s.TestSuite.SetUpTest(c)
    42  	var err error
    43  	s.manager, err = kvm.NewContainerManager(container.ManagerConfig{
    44  		container.ConfigModelUUID:      coretesting.ModelTag.Id(),
    45  		config.ContainerImageStreamKey: imagemetadata.ReleasedStream,
    46  	})
    47  	c.Assert(err, jc.ErrorIsNil)
    48  }
    49  
    50  func (*KVMSuite) TestManagerModelUUIDNeeded(c *gc.C) {
    51  	manager, err := kvm.NewContainerManager(container.ManagerConfig{container.ConfigModelUUID: ""})
    52  	c.Assert(err, gc.ErrorMatches, "model UUID is required")
    53  	c.Assert(manager, gc.IsNil)
    54  }
    55  
    56  func (*KVMSuite) TestManagerWarnsAboutUnknownOption(c *gc.C) {
    57  	_, err := kvm.NewContainerManager(container.ManagerConfig{
    58  		container.ConfigModelUUID: coretesting.ModelTag.Id(),
    59  		"shazam":                  "Captain Marvel",
    60  	})
    61  	c.Assert(err, jc.ErrorIsNil)
    62  	c.Assert(c.GetTestLog(), jc.Contains, `INFO juju.container unused config option: "shazam" -> "Captain Marvel"`)
    63  }
    64  
    65  func (s *KVMSuite) TestListInitiallyEmpty(c *gc.C) {
    66  	containers, err := s.manager.ListContainers()
    67  	c.Assert(err, jc.ErrorIsNil)
    68  	c.Assert(containers, gc.HasLen, 0)
    69  }
    70  
    71  func (s *KVMSuite) createRunningContainer(c *gc.C, name string) kvm.Container {
    72  	kvmContainer := s.ContainerFactory.New(name)
    73  
    74  	nics := network.InterfaceInfos{{
    75  		InterfaceName: "eth0",
    76  		InterfaceType: network.EthernetDevice,
    77  		ConfigType:    network.ConfigDHCP,
    78  	}}
    79  	net := container.BridgeNetworkConfig(0, nics)
    80  	c.Assert(kvmContainer.Start(kvm.StartParams{
    81  		Version:      "12.10",
    82  		Arch:         arch.HostArch(),
    83  		UserDataFile: "userdata.txt",
    84  		Network:      net}), gc.IsNil)
    85  	return kvmContainer
    86  }
    87  
    88  func (s *KVMSuite) TestListMatchesManagerName(c *gc.C) {
    89  	s.createRunningContainer(c, "juju-06f00d-match1")
    90  	s.createRunningContainer(c, "juju-06f00d-match2")
    91  	s.createRunningContainer(c, "testNoMatch")
    92  	s.createRunningContainer(c, "other")
    93  	containers, err := s.manager.ListContainers()
    94  	c.Assert(err, jc.ErrorIsNil)
    95  	c.Assert(containers, gc.HasLen, 2)
    96  	expectedIds := []instance.Id{"juju-06f00d-match1", "juju-06f00d-match2"}
    97  	ids := []instance.Id{containers[0].Id(), containers[1].Id()}
    98  	c.Assert(ids, jc.SameContents, expectedIds)
    99  }
   100  
   101  func (s *KVMSuite) TestListMatchesRunningContainers(c *gc.C) {
   102  	running := s.createRunningContainer(c, "juju-06f00d-running")
   103  	s.ContainerFactory.New("juju-06f00d-stopped")
   104  	containers, err := s.manager.ListContainers()
   105  	c.Assert(err, jc.ErrorIsNil)
   106  	c.Assert(containers, gc.HasLen, 1)
   107  	c.Assert(string(containers[0].Id()), gc.Equals, running.Name())
   108  }
   109  
   110  func (s *KVMSuite) TestCreateContainer(c *gc.C) {
   111  	inst := containertesting.CreateContainer(c, s.manager, "1/kvm/0")
   112  	name := string(inst.Id())
   113  	cloudInitFilename := filepath.Join(s.ContainerDir, name, "cloud-init")
   114  	containertesting.AssertCloudInit(c, cloudInitFilename)
   115  }
   116  
   117  func (s *KVMSuite) TestCreateContainerNoDefaultImageMetadata(c *gc.C) {
   118  	var err error
   119  	s.manager, err = kvm.NewContainerManager(container.ManagerConfig{
   120  		container.ConfigModelUUID:                        coretesting.ModelTag.Id(),
   121  		config.ContainerImageStreamKey:                   imagemetadata.ReleasedStream,
   122  		config.ContainerImageMetadataDefaultsDisabledKey: "true",
   123  	})
   124  	c.Assert(err, jc.ErrorIsNil)
   125  	instanceConfig, err := containertesting.MockMachineConfig("1/kvm/0")
   126  	c.Assert(err, jc.ErrorIsNil)
   127  	_, _, err = s.manager.CreateContainer(context.Background(), instanceConfig, constraints.Value{}, base.Base{}, nil, nil,
   128  		func(settableStatus status.Status, info string, data map[string]interface{}) error { return nil })
   129  	c.Assert(err, gc.ErrorMatches, `no image metadata source configured: default sources disabled`)
   130  }
   131  
   132  // This test will pass regular unit tests, but is intended for the
   133  // race-checking CI job to assert concurrent creation safety.
   134  func (s *KVMSuite) TestCreateContainerConcurrent(c *gc.C) {
   135  	var wg sync.WaitGroup
   136  	for i := 0; i < 10; i++ {
   137  		wg.Add(1)
   138  		go func(idx int) {
   139  			_ = containertesting.CreateContainer(c, s.manager, fmt.Sprintf("1/kvm/%d", idx))
   140  			wg.Done()
   141  		}(i)
   142  	}
   143  	wg.Wait()
   144  }
   145  
   146  func (s *KVMSuite) TestDestroyContainer(c *gc.C) {
   147  	inst := containertesting.CreateContainer(c, s.manager, "1/kvm/0")
   148  
   149  	err := s.manager.DestroyContainer(inst.Id())
   150  	c.Assert(err, jc.ErrorIsNil)
   151  
   152  	name := string(inst.Id())
   153  	// Check that the container dir is no longer in the container dir
   154  	c.Assert(filepath.Join(s.ContainerDir, name), jc.DoesNotExist)
   155  	// but instead, in the removed container dir
   156  	c.Assert(filepath.Join(s.RemovedDir, name), jc.IsDirectory)
   157  }
   158  
   159  // Test that CreateContainer creates proper startParams.
   160  func (s *KVMSuite) TestCreateContainerUsesReleaseSimpleStream(c *gc.C) {
   161  
   162  	// Mock machineConfig with a mocked simple stream URL.
   163  	instanceConfig, err := containertesting.MockMachineConfig("1/kvm/0")
   164  	c.Assert(err, jc.ErrorIsNil)
   165  
   166  	inst := containertesting.CreateContainerWithMachineConfig(c, s.manager, instanceConfig)
   167  	startParams := kvm.ContainerFromInstance(inst).(*mock.MockContainer).StartParams
   168  	c.Assert(startParams.ImageDownloadURL, gc.Equals, "")
   169  	c.Assert(startParams.Stream, gc.Equals, "released")
   170  }
   171  
   172  // Test that CreateContainer creates proper startParams.
   173  func (s *KVMSuite) TestCreateContainerUsesDailySimpleStream(c *gc.C) {
   174  
   175  	// Mock machineConfig with a mocked simple stream URL.
   176  	instanceConfig, err := containertesting.MockMachineConfig("1/kvm/0")
   177  	c.Assert(err, jc.ErrorIsNil)
   178  
   179  	s.manager, err = kvm.NewContainerManager(container.ManagerConfig{
   180  		container.ConfigModelUUID:      coretesting.ModelTag.Id(),
   181  		config.ContainerImageStreamKey: "daily",
   182  	})
   183  	c.Assert(err, jc.ErrorIsNil)
   184  
   185  	inst := containertesting.CreateContainerWithMachineConfig(c, s.manager, instanceConfig)
   186  	startParams := kvm.ContainerFromInstance(inst).(*mock.MockContainer).StartParams
   187  	c.Assert(startParams.ImageDownloadURL, gc.Equals, "http://cloud-images.ubuntu.com/daily")
   188  	c.Assert(startParams.Stream, gc.Equals, "daily")
   189  }
   190  
   191  func (s *KVMSuite) TestCreateContainerUsesSetImageMetadataURL(c *gc.C) {
   192  
   193  	// Mock machineConfig with a mocked simple stream URL.
   194  	instanceConfig, err := containertesting.MockMachineConfig("1/kvm/0")
   195  	c.Assert(err, jc.ErrorIsNil)
   196  
   197  	s.manager, err = kvm.NewContainerManager(container.ManagerConfig{
   198  		container.ConfigModelUUID:           coretesting.ModelTag.Id(),
   199  		config.ContainerImageMetadataURLKey: "https://images.linuxcontainers.org",
   200  	})
   201  	c.Assert(err, jc.ErrorIsNil)
   202  
   203  	inst := containertesting.CreateContainerWithMachineConfig(c, s.manager, instanceConfig)
   204  	startParams := kvm.ContainerFromInstance(inst).(*mock.MockContainer).StartParams
   205  	c.Assert(startParams.ImageDownloadURL, gc.Equals, "https://images.linuxcontainers.org")
   206  }
   207  
   208  func (s *KVMSuite) TestImageAcquisitionUsesSimpleStream(c *gc.C) {
   209  
   210  	startParams := kvm.StartParams{
   211  		Version:          "mocked-version",
   212  		Arch:             "mocked-arch",
   213  		Stream:           "released",
   214  		ImageDownloadURL: "mocked-url",
   215  	}
   216  	mockedContainer := kvm.NewEmptyKvmContainer()
   217  
   218  	// We are testing only the logging side-effect, so the error is ignored.
   219  	_ = mockedContainer.EnsureCachedImage(startParams)
   220  
   221  	expectedArgs := fmt.Sprintf(
   222  		"synchronise images for %s %s %s %s",
   223  		startParams.Arch,
   224  		startParams.Version,
   225  		startParams.Stream,
   226  		startParams.ImageDownloadURL,
   227  	)
   228  	c.Assert(c.GetTestLog(), jc.Contains, expectedArgs)
   229  }
   230  
   231  type ConstraintsSuite struct {
   232  	coretesting.BaseSuite
   233  }
   234  
   235  var _ = gc.Suite(&ConstraintsSuite{})
   236  
   237  func (s *ConstraintsSuite) TestDefaults(c *gc.C) {
   238  	testCases := []struct {
   239  		cons     string
   240  		expected kvm.StartParams
   241  		infoLog  []string
   242  	}{{
   243  		expected: kvm.StartParams{
   244  			Memory:   kvm.DefaultMemory,
   245  			CpuCores: kvm.DefaultCpu,
   246  			RootDisk: kvm.DefaultDisk,
   247  		},
   248  	}, {
   249  		cons: "mem=256M",
   250  		expected: kvm.StartParams{
   251  			Memory:   kvm.MinMemory,
   252  			CpuCores: kvm.DefaultCpu,
   253  			RootDisk: kvm.DefaultDisk,
   254  		},
   255  	}, {
   256  		cons: "mem=4G",
   257  		expected: kvm.StartParams{
   258  			Memory:   4 * 1024,
   259  			CpuCores: kvm.DefaultCpu,
   260  			RootDisk: kvm.DefaultDisk,
   261  		},
   262  	}, {
   263  		cons: "cores=4",
   264  		expected: kvm.StartParams{
   265  			Memory:   kvm.DefaultMemory,
   266  			CpuCores: 4,
   267  			RootDisk: kvm.DefaultDisk,
   268  		},
   269  	}, {
   270  		cons: "cores=0",
   271  		expected: kvm.StartParams{
   272  			Memory:   kvm.DefaultMemory,
   273  			CpuCores: kvm.MinCpu,
   274  			RootDisk: kvm.DefaultDisk,
   275  		},
   276  	}, {
   277  		cons: "root-disk=512M",
   278  		expected: kvm.StartParams{
   279  			Memory:   kvm.DefaultMemory,
   280  			CpuCores: kvm.DefaultCpu,
   281  			RootDisk: kvm.MinDisk,
   282  		},
   283  	}, {
   284  		cons: "root-disk=4G",
   285  		expected: kvm.StartParams{
   286  			Memory:   kvm.DefaultMemory,
   287  			CpuCores: kvm.DefaultCpu,
   288  			RootDisk: 4,
   289  		},
   290  	}, {
   291  		cons: "arch=arm64",
   292  		expected: kvm.StartParams{
   293  			Memory:   kvm.DefaultMemory,
   294  			CpuCores: kvm.DefaultCpu,
   295  			RootDisk: kvm.DefaultDisk,
   296  		},
   297  		infoLog: []string{
   298  			`arch constraint of "arm64" being ignored as not supported`,
   299  		},
   300  	}, {
   301  		cons: "container=lxd",
   302  		expected: kvm.StartParams{
   303  			Memory:   kvm.DefaultMemory,
   304  			CpuCores: kvm.DefaultCpu,
   305  			RootDisk: kvm.DefaultDisk,
   306  		},
   307  		infoLog: []string{
   308  			`container constraint of "lxd" being ignored as not supported`,
   309  		},
   310  	}, {
   311  		cons: "cpu-power=100",
   312  		expected: kvm.StartParams{
   313  			Memory:   kvm.DefaultMemory,
   314  			CpuCores: kvm.DefaultCpu,
   315  			RootDisk: kvm.DefaultDisk,
   316  		},
   317  		infoLog: []string{
   318  			`cpu-power constraint of 100 being ignored as not supported`,
   319  		},
   320  	}, {
   321  		cons: "tags=foo,bar",
   322  		expected: kvm.StartParams{
   323  			Memory:   kvm.DefaultMemory,
   324  			CpuCores: kvm.DefaultCpu,
   325  			RootDisk: kvm.DefaultDisk,
   326  		},
   327  		infoLog: []string{
   328  			`tags constraint of "foo,bar" being ignored as not supported`,
   329  		},
   330  	}, {
   331  		cons: "mem=4G cores=4 root-disk=20G arch=arm64 cpu-power=100 container=lxd tags=foo,bar",
   332  		expected: kvm.StartParams{
   333  			Memory:   4 * 1024,
   334  			CpuCores: 4,
   335  			RootDisk: 20,
   336  		},
   337  		infoLog: []string{
   338  			`arch constraint of "arm64" being ignored as not supported`,
   339  			`container constraint of "lxd" being ignored as not supported`,
   340  			`cpu-power constraint of 100 being ignored as not supported`,
   341  			`tags constraint of "foo,bar" being ignored as not supported`,
   342  		},
   343  	}}
   344  
   345  	for _, test := range testCases {
   346  		c.Logf("testing %q", test.cons)
   347  
   348  		var tw loggo.TestWriter
   349  		c.Assert(loggo.RegisterWriter("constraint-tester", &tw), gc.IsNil)
   350  		cons := constraints.MustParse(test.cons)
   351  		params := kvm.ParseConstraintsToStartParams(cons)
   352  		c.Check(params, gc.DeepEquals, test.expected)
   353  		c.Check(tw.Log(), jc.LogMatches, test.infoLog)
   354  		_, _ = loggo.RemoveWriter("constraint-tester")
   355  	}
   356  }
   357  
   358  // Test the output when no binary can be found.
   359  func (s *KVMSuite) TestIsKVMSupportedKvmOkNotFound(c *gc.C) {
   360  	// With no path, and no backup directory, we should fail.
   361  	s.PatchEnvironment("PATH", "")
   362  	s.PatchValue(kvm.KVMPath, "")
   363  
   364  	supported, err := kvm.IsKVMSupported()
   365  	c.Check(supported, jc.IsFalse)
   366  	c.Assert(err, gc.ErrorMatches, "kvm-ok executable not found")
   367  }
   368  
   369  // Test the output when the binary is found, but errors out.
   370  func (s *KVMSuite) TestIsKVMSupportedBinaryErrorsOut(c *gc.C) {
   371  	// Clear path so real binary is not found.
   372  	s.PatchEnvironment("PATH", "")
   373  
   374  	// Create mocked binary which returns an error and give the test access.
   375  	tmpDir := c.MkDir()
   376  	err := os.WriteFile(filepath.Join(tmpDir, "kvm-ok"), []byte("#!/bin/bash\nexit 127"), 0777)
   377  	c.Assert(err, jc.ErrorIsNil)
   378  	s.PatchValue(kvm.KVMPath, tmpDir)
   379  
   380  	supported, err := kvm.IsKVMSupported()
   381  	c.Check(supported, jc.IsFalse)
   382  	c.Assert(err, gc.ErrorMatches, "exit status 127")
   383  }
   384  
   385  // Test the case where kvm-ok is not in the path, but is in the
   386  // specified directory.
   387  func (s *KVMSuite) TestIsKVMSupportedNoPath(c *gc.C) {
   388  	// Create a mocked binary so that this test does not fail for
   389  	// developers without kvm-ok.
   390  	s.PatchEnvironment("PATH", "")
   391  	tmpDir := c.MkDir()
   392  	err := os.WriteFile(filepath.Join(tmpDir, "kvm-ok"), []byte("#!/bin/bash"), 0777)
   393  	c.Assert(err, jc.ErrorIsNil)
   394  	s.PatchValue(kvm.KVMPath, tmpDir)
   395  
   396  	supported, err := kvm.IsKVMSupported()
   397  	c.Check(supported, jc.IsTrue)
   398  	c.Assert(err, jc.ErrorIsNil)
   399  }
   400  
   401  // Test the case that kvm-ok is found in the path.
   402  func (s *KVMSuite) TestIsKVMSupportedOnlyPath(c *gc.C) {
   403  	// Create a mocked binary so that this test does not fail for
   404  	// developers without kvm-ok.
   405  	tmpDir := c.MkDir()
   406  	err := os.WriteFile(filepath.Join(tmpDir, "kvm-ok"), []byte("#!/bin/bash"), 0777)
   407  	c.Check(err, jc.ErrorIsNil)
   408  	s.PatchEnvironment("PATH", tmpDir)
   409  
   410  	supported, err := kvm.IsKVMSupported()
   411  	c.Check(supported, jc.IsTrue)
   412  	c.Assert(err, jc.ErrorIsNil)
   413  }
   414  
   415  func (s *KVMSuite) TestKVMPathIsCorrect(c *gc.C) {
   416  	c.Assert(*kvm.KVMPath, gc.Equals, "/usr/sbin")
   417  }