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 }