github.com/containers/podman/v4@v4.9.4/test/e2e/common_test.go (about) 1 package integration 2 3 import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "fmt" 8 "io" 9 "math/rand" 10 "net" 11 "net/url" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "sort" 16 "strconv" 17 "strings" 18 "sync" 19 "syscall" 20 "testing" 21 "time" 22 23 "github.com/containers/common/pkg/cgroups" 24 "github.com/containers/podman/v4/libpod/define" 25 "github.com/containers/podman/v4/pkg/inspect" 26 "github.com/containers/podman/v4/pkg/util" 27 . "github.com/containers/podman/v4/test/utils" 28 "github.com/containers/storage/pkg/lockfile" 29 "github.com/containers/storage/pkg/reexec" 30 "github.com/containers/storage/pkg/stringid" 31 jsoniter "github.com/json-iterator/go" 32 . "github.com/onsi/ginkgo/v2" 33 . "github.com/onsi/gomega" 34 . "github.com/onsi/gomega/gexec" 35 "github.com/sirupsen/logrus" 36 "golang.org/x/sys/unix" 37 ) 38 39 var ( 40 //lint:ignore ST1003 41 PODMAN_BINARY string //nolint:revive,stylecheck 42 INTEGRATION_ROOT string //nolint:revive,stylecheck 43 CGROUP_MANAGER = "systemd" //nolint:revive,stylecheck 44 RESTORE_IMAGES = []string{ALPINE, BB, NGINX_IMAGE} //nolint:revive,stylecheck 45 defaultWaitTimeout = 90 46 CGROUPSV2, _ = cgroups.IsCgroup2UnifiedMode() 47 ) 48 49 // PodmanTestIntegration struct for command line options 50 type PodmanTestIntegration struct { 51 PodmanTest 52 ConmonBinary string 53 QuadletBinary string 54 Root string 55 NetworkConfigDir string 56 OCIRuntime string 57 RunRoot string 58 StorageOptions string 59 SignaturePolicyPath string 60 CgroupManager string 61 Host HostOS 62 TmpDir string 63 } 64 65 var LockTmpDir string 66 67 // PodmanSessionIntegration struct for command line session 68 type PodmanSessionIntegration struct { 69 *PodmanSession 70 } 71 72 type testResult struct { 73 name string 74 length float64 75 } 76 77 type testResultsSorted []testResult 78 79 func (a testResultsSorted) Len() int { return len(a) } 80 func (a testResultsSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 81 82 type testResultsSortedLength struct{ testResultsSorted } 83 84 func (a testResultsSorted) Less(i, j int) bool { return a[i].length < a[j].length } 85 86 func TestMain(m *testing.M) { 87 if reexec.Init() { 88 return 89 } 90 os.Exit(m.Run()) 91 } 92 93 // TestLibpod ginkgo master function 94 func TestLibpod(t *testing.T) { 95 if os.Getenv("NOCACHE") == "1" { 96 CACHE_IMAGES = []string{} 97 RESTORE_IMAGES = []string{} 98 } 99 RegisterFailHandler(Fail) 100 RunSpecs(t, "Libpod Suite") 101 } 102 103 var ( 104 tempdir string 105 err error 106 podmanTest *PodmanTestIntegration 107 safeIPOctets [2]uint8 108 timingsFile *os.File 109 110 _ = BeforeEach(func() { 111 tempdir, err = CreateTempDirInTempDir() 112 Expect(err).ToNot(HaveOccurred()) 113 podmanTest = PodmanTestCreate(tempdir) 114 podmanTest.Setup() 115 // see GetSafeIPAddress() below 116 safeIPOctets[0] = uint8(GinkgoT().ParallelProcess()) + 128 117 safeIPOctets[1] = 2 118 }) 119 120 _ = AfterEach(func() { 121 // First unset CONTAINERS_CONF before doing Cleanup() to prevent 122 // invalid containers.conf files to fail the cleanup. 123 os.Unsetenv("CONTAINERS_CONF") 124 os.Unsetenv("CONTAINERS_CONF_OVERRIDE") 125 podmanTest.Cleanup() 126 f := CurrentSpecReport() 127 processTestResult(f) 128 }) 129 ) 130 131 const ( 132 // lockdir - do not use directly use LockTmpDir 133 lockdir = "libpodlock" 134 // imageCacheDir - do not use directly use ImageCacheDir 135 imageCacheDir = "imagecachedir" 136 ) 137 138 var _ = SynchronizedBeforeSuite(func() []byte { 139 globalTmpDir := GinkgoT().TempDir() 140 141 // make cache dir 142 ImageCacheDir = filepath.Join(globalTmpDir, imageCacheDir) 143 if err := os.MkdirAll(ImageCacheDir, 0700); err != nil { 144 GinkgoWriter.Printf("%q\n", err) 145 os.Exit(1) 146 } 147 148 // Cache images 149 cwd, _ := os.Getwd() 150 INTEGRATION_ROOT = filepath.Join(cwd, "../../") 151 podman := PodmanTestSetup(GinkgoT().TempDir()) 152 153 // Pull cirros but don't put it into the cache 154 pullImages := []string{CIRROS_IMAGE, fedoraToolbox, volumeTest} 155 pullImages = append(pullImages, CACHE_IMAGES...) 156 for _, image := range pullImages { 157 podman.createArtifact(image) 158 } 159 160 if err := os.MkdirAll(filepath.Join(ImageCacheDir, podman.ImageCacheFS+"-images"), 0777); err != nil { 161 GinkgoWriter.Printf("%q\n", err) 162 os.Exit(1) 163 } 164 podman.Root = ImageCacheDir 165 // If running localized tests, the cache dir is created and populated. if the 166 // tests are remote, this is a no-op 167 populateCache(podman) 168 169 if err := os.MkdirAll(filepath.Join(globalTmpDir, lockdir), 0700); err != nil { 170 GinkgoWriter.Printf("%q\n", err) 171 os.Exit(1) 172 } 173 174 // If running remote, we need to stop the associated podman system service 175 if podman.RemoteTest { 176 podman.StopRemoteService() 177 } 178 179 // remove temporary podman files, images are now cached in ImageCacheDir 180 rmAll(podman.PodmanBinary, podman.TempDir) 181 182 return []byte(globalTmpDir) 183 }, func(data []byte) { 184 cwd, _ := os.Getwd() 185 INTEGRATION_ROOT = filepath.Join(cwd, "../../") 186 globalTmpDir := string(data) 187 ImageCacheDir = filepath.Join(globalTmpDir, imageCacheDir) 188 LockTmpDir = filepath.Join(globalTmpDir, lockdir) 189 190 timingsFile, err = os.Create(fmt.Sprintf("%s/timings-%d", LockTmpDir, GinkgoParallelProcess())) 191 Expect(err).ToNot(HaveOccurred()) 192 }) 193 194 func (p *PodmanTestIntegration) Setup() { 195 cwd, _ := os.Getwd() 196 INTEGRATION_ROOT = filepath.Join(cwd, "../../") 197 } 198 199 var _ = SynchronizedAfterSuite(func() { 200 timingsFile.Close() 201 timingsFile = nil 202 }, 203 func() { 204 testTimings := make(testResultsSorted, 0, 2000) 205 for i := 1; i <= GinkgoT().ParallelTotal(); i++ { 206 f, err := os.Open(fmt.Sprintf("%s/timings-%d", LockTmpDir, i)) 207 Expect(err).ToNot(HaveOccurred()) 208 defer f.Close() 209 scanner := bufio.NewScanner(f) 210 for scanner.Scan() { 211 text := scanner.Text() 212 timing := strings.SplitN(text, "\t\t", 2) 213 if len(timing) != 2 { 214 Fail(fmt.Sprintf("incorrect timing line: %q", text)) 215 } 216 name := timing[0] 217 duration, err := strconv.ParseFloat(timing[1], 64) 218 Expect(err).ToNot(HaveOccurred(), "failed to parse float from timings file") 219 testTimings = append(testTimings, testResult{name: name, length: duration}) 220 } 221 if err := scanner.Err(); err != nil { 222 Expect(err).ToNot(HaveOccurred(), "read timings %d", i) 223 } 224 } 225 sort.Sort(testResultsSortedLength{testTimings}) 226 GinkgoWriter.Println("integration timing results") 227 for _, result := range testTimings { 228 GinkgoWriter.Printf("%s\t\t%f\n", result.name, result.length) 229 } 230 231 cwd, _ := os.Getwd() 232 rmAll(getPodmanBinary(cwd), ImageCacheDir) 233 }) 234 235 func getPodmanBinary(cwd string) string { 236 podmanBinary := filepath.Join(cwd, "../../bin/podman") 237 if os.Getenv("PODMAN_BINARY") != "" { 238 podmanBinary = os.Getenv("PODMAN_BINARY") 239 } 240 return podmanBinary 241 } 242 243 // PodmanTestCreate creates a PodmanTestIntegration instance for the tests 244 func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { 245 var podmanRemoteBinary string 246 247 host := GetHostDistributionInfo() 248 cwd, _ := os.Getwd() 249 250 root := filepath.Join(tempDir, "root") 251 podmanBinary := getPodmanBinary(cwd) 252 253 podmanRemoteBinary = filepath.Join(cwd, "../../bin/podman-remote") 254 if os.Getenv("PODMAN_REMOTE_BINARY") != "" { 255 podmanRemoteBinary = os.Getenv("PODMAN_REMOTE_BINARY") 256 } 257 258 quadletBinary := filepath.Join(cwd, "../../bin/quadlet") 259 if os.Getenv("QUADLET_BINARY") != "" { 260 quadletBinary = os.Getenv("QUADLET_BINARY") 261 } 262 263 conmonBinary := "/usr/libexec/podman/conmon" 264 altConmonBinary := "/usr/bin/conmon" 265 if _, err := os.Stat(conmonBinary); os.IsNotExist(err) { 266 conmonBinary = altConmonBinary 267 } 268 if os.Getenv("CONMON_BINARY") != "" { 269 conmonBinary = os.Getenv("CONMON_BINARY") 270 } 271 storageOptions := STORAGE_OPTIONS 272 if os.Getenv("STORAGE_OPTIONS") != "" { 273 storageOptions = os.Getenv("STORAGE_OPTIONS") 274 } 275 276 cgroupManager := CGROUP_MANAGER 277 if isRootless() { 278 cgroupManager = "cgroupfs" 279 } 280 if os.Getenv("CGROUP_MANAGER") != "" { 281 cgroupManager = os.Getenv("CGROUP_MANAGER") 282 } 283 284 ociRuntime := os.Getenv("OCI_RUNTIME") 285 if ociRuntime == "" { 286 ociRuntime = "crun" 287 } 288 os.Setenv("DISABLE_HC_SYSTEMD", "true") 289 290 dbBackend := "sqlite" 291 if os.Getenv("PODMAN_DB") == "boltdb" { 292 dbBackend = "boltdb" 293 } 294 295 networkBackend := Netavark 296 networkConfigDir := "/etc/containers/networks" 297 if isRootless() { 298 networkConfigDir = filepath.Join(root, "etc", "networks") 299 } 300 301 if strings.ToLower(os.Getenv("NETWORK_BACKEND")) == "cni" { 302 networkBackend = CNI 303 networkConfigDir = "/etc/cni/net.d" 304 if isRootless() { 305 networkConfigDir = filepath.Join(os.Getenv("HOME"), ".config/cni/net.d") 306 } 307 } 308 309 if err := os.MkdirAll(root, 0755); err != nil { 310 panic(err) 311 } 312 313 if err := os.MkdirAll(networkConfigDir, 0755); err != nil { 314 panic(err) 315 } 316 317 storageFs := STORAGE_FS 318 if isRootless() { 319 storageFs = ROOTLESS_STORAGE_FS 320 } 321 if os.Getenv("STORAGE_FS") != "" { 322 storageFs = os.Getenv("STORAGE_FS") 323 storageOptions = "--storage-driver " + storageFs 324 } 325 326 p := &PodmanTestIntegration{ 327 PodmanTest: PodmanTest{ 328 PodmanBinary: podmanBinary, 329 RemotePodmanBinary: podmanRemoteBinary, 330 TempDir: tempDir, 331 RemoteTest: remote, 332 ImageCacheFS: storageFs, 333 ImageCacheDir: ImageCacheDir, 334 NetworkBackend: networkBackend, 335 DatabaseBackend: dbBackend, 336 }, 337 ConmonBinary: conmonBinary, 338 QuadletBinary: quadletBinary, 339 Root: root, 340 TmpDir: tempDir, 341 NetworkConfigDir: networkConfigDir, 342 OCIRuntime: ociRuntime, 343 RunRoot: filepath.Join(tempDir, "runroot"), 344 StorageOptions: storageOptions, 345 SignaturePolicyPath: filepath.Join(INTEGRATION_ROOT, "test/policy.json"), 346 CgroupManager: cgroupManager, 347 Host: host, 348 } 349 350 if remote { 351 var pathPrefix string 352 if !isRootless() { 353 pathPrefix = "/run/podman/podman" 354 } else { 355 runtimeDir := os.Getenv("XDG_RUNTIME_DIR") 356 pathPrefix = filepath.Join(runtimeDir, "podman") 357 } 358 // We want to avoid collisions in socket paths, but using the 359 // socket directly for a collision check doesn’t work; bind(2) on AF_UNIX 360 // creates the file, and we need to pass a unique path now before the bind(2) 361 // happens. So, use a podman-%s.sock-lock empty file as a marker. 362 tries := 0 363 for { 364 uuid := stringid.GenerateRandomID() 365 lockPath := fmt.Sprintf("%s-%s.sock-lock", pathPrefix, uuid) 366 lockFile, err := os.OpenFile(lockPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0700) 367 if err == nil { 368 lockFile.Close() 369 p.RemoteSocketLock = lockPath 370 p.RemoteSocket = fmt.Sprintf("unix://%s-%s.sock", pathPrefix, uuid) 371 break 372 } 373 tries++ 374 if tries >= 1000 { 375 panic("Too many RemoteSocket collisions") 376 } 377 } 378 } 379 380 // Set up registries.conf ENV variable 381 p.setDefaultRegistriesConfigEnv() 382 // Rewrite the PodmanAsUser function 383 p.PodmanMakeOptions = p.makeOptions 384 return p 385 } 386 387 func (p PodmanTestIntegration) AddImageToRWStore(image string) { 388 if err := p.RestoreArtifact(image); err != nil { 389 logrus.Errorf("Unable to restore %s to RW store", image) 390 } 391 } 392 393 func imageTarPath(image string) string { 394 cacheDir := os.Getenv("PODMAN_TEST_IMAGE_CACHE_DIR") 395 if cacheDir == "" { 396 cacheDir = os.Getenv("TMPDIR") 397 if cacheDir == "" { 398 cacheDir = "/tmp" 399 } 400 } 401 402 // e.g., registry.com/fubar:latest -> registry.com-fubar-latest.tar 403 imageCacheName := strings.ReplaceAll(strings.ReplaceAll(image, ":", "-"), "/", "-") + ".tar" 404 405 return filepath.Join(cacheDir, imageCacheName) 406 } 407 408 // createArtifact creates a cached image tarball in a local directory 409 func (p *PodmanTestIntegration) createArtifact(image string) { 410 if os.Getenv("NO_TEST_CACHE") != "" { 411 return 412 } 413 destName := imageTarPath(image) 414 if _, err := os.Stat(destName); os.IsNotExist(err) { 415 GinkgoWriter.Printf("Caching %s at %s...\n", image, destName) 416 for try := 0; try < 3; try++ { 417 pull := p.PodmanNoCache([]string{"pull", image}) 418 pull.Wait(440) 419 if pull.ExitCode() == 0 { 420 break 421 } 422 if try == 2 { 423 Expect(pull).Should(Exit(0), "Failed after many retries") 424 } 425 426 GinkgoWriter.Println("Will wait and retry") 427 time.Sleep(time.Duration(try+1) * 5 * time.Second) 428 } 429 430 save := p.PodmanNoCache([]string{"save", "-o", destName, image}) 431 save.Wait(90) 432 Expect(save).Should(Exit(0)) 433 GinkgoWriter.Printf("\n") 434 } else { 435 GinkgoWriter.Printf("[image already cached: %s]\n", destName) 436 } 437 } 438 439 // InspectImageJSON takes the session output of an inspect 440 // image and returns json 441 func (s *PodmanSessionIntegration) InspectImageJSON() []inspect.ImageData { 442 var i []inspect.ImageData 443 err := jsoniter.Unmarshal(s.Out.Contents(), &i) 444 Expect(err).ToNot(HaveOccurred()) 445 return i 446 } 447 448 // InspectContainer returns a container's inspect data in JSON format 449 func (p *PodmanTestIntegration) InspectContainer(name string) []define.InspectContainerData { 450 cmd := []string{"inspect", name} 451 session := p.Podman(cmd) 452 session.WaitWithDefaultTimeout() 453 Expect(session).Should(Exit(0)) 454 return session.InspectContainerToJSON() 455 } 456 457 func processTestResult(r SpecReport) { 458 tr := testResult{length: r.RunTime.Seconds(), name: r.FullText()} 459 _, err := timingsFile.WriteString(fmt.Sprintf("%s\t\t%f\n", tr.name, tr.length)) 460 Expect(err).ToNot(HaveOccurred(), "write timings") 461 } 462 463 func GetPortLock(port string) *lockfile.LockFile { 464 lockFile := filepath.Join(LockTmpDir, port) 465 lock, err := lockfile.GetLockFile(lockFile) 466 if err != nil { 467 GinkgoWriter.Println(err) 468 os.Exit(1) 469 } 470 lock.Lock() 471 return lock 472 } 473 474 // GetSafeIPAddress returns a sequentially allocated IP address that _should_ 475 // be safe and unique across parallel tasks 476 // 477 // Used by tests which want to use "--ip SOMETHING-SAFE". Picking at random 478 // just doesn't work: we get occasional collisions. Our current approach 479 // allocates a /24 subnet for each ginkgo process, starting at .128.x, see 480 // BeforeEach() above. Unfortunately, CNI remembers each address assigned 481 // and assigns <previous+1> by default -- so other parallel jobs may 482 // get IPs in our block. The +10 leaves a gap for that. (Netavark works 483 // differently, allocating sequentially from .0.0, hence our .128.x). 484 // This heuristic will fail if run in parallel on >127 processors or if 485 // one test calls us more than 25 times or if some other test runs more 486 // than ten networked containers at the same time as any test that 487 // relies on GetSafeIPAddress(). I'm finding it hard to care. 488 // 489 // DO NOT USE THIS FUNCTION unless there is no possible alternative. In 490 // most cases you should use 'podman network create' + 'podman run --network'. 491 func GetSafeIPAddress() string { 492 safeIPOctets[1] += 10 493 return fmt.Sprintf("10.88.%d.%d", safeIPOctets[0], safeIPOctets[1]) 494 } 495 496 // RunTopContainer runs a simple container in the background that 497 // runs top. If the name passed != "", it will have a name 498 func (p *PodmanTestIntegration) RunTopContainer(name string) *PodmanSessionIntegration { 499 return p.RunTopContainerWithArgs(name, nil) 500 } 501 502 // RunTopContainerWithArgs runs a simple container in the background that 503 // runs top. If the name passed != "", it will have a name, command args can also be passed in 504 func (p *PodmanTestIntegration) RunTopContainerWithArgs(name string, args []string) *PodmanSessionIntegration { 505 // In proxy environment, some tests need to the --http-proxy=false option (#16684) 506 var podmanArgs = []string{"run", "--http-proxy=false"} 507 if name != "" { 508 podmanArgs = append(podmanArgs, "--name", name) 509 } 510 podmanArgs = append(podmanArgs, args...) 511 podmanArgs = append(podmanArgs, "-d", ALPINE, "top") 512 return p.Podman(podmanArgs) 513 } 514 515 // RunLsContainer runs a simple container in the background that 516 // simply runs ls. If the name passed != "", it will have a name 517 func (p *PodmanTestIntegration) RunLsContainer(name string) (*PodmanSessionIntegration, int, string) { 518 var podmanArgs = []string{"run"} 519 if name != "" { 520 podmanArgs = append(podmanArgs, "--name", name) 521 } 522 podmanArgs = append(podmanArgs, "-d", ALPINE, "ls") 523 session := p.Podman(podmanArgs) 524 session.WaitWithDefaultTimeout() 525 if session.ExitCode() != 0 { 526 return session, session.ExitCode(), session.OutputToString() 527 } 528 cid := session.OutputToString() 529 530 wsession := p.Podman([]string{"wait", cid}) 531 wsession.WaitWithDefaultTimeout() 532 return session, wsession.ExitCode(), cid 533 } 534 535 // RunNginxWithHealthCheck runs the alpine nginx container with an optional name and adds a healthcheck into it 536 func (p *PodmanTestIntegration) RunNginxWithHealthCheck(name string) (*PodmanSessionIntegration, string) { 537 var podmanArgs = []string{"run"} 538 if name != "" { 539 podmanArgs = append(podmanArgs, "--name", name) 540 } 541 // curl without -f exits 0 even if http code >= 400! 542 podmanArgs = append(podmanArgs, "-dt", "-P", "--health-cmd", "curl -f http://localhost/", NGINX_IMAGE) 543 session := p.Podman(podmanArgs) 544 session.WaitWithDefaultTimeout() 545 return session, session.OutputToString() 546 } 547 548 // RunContainerWithNetworkTest runs the fedoraMinimal curl with the specified network mode. 549 func (p *PodmanTestIntegration) RunContainerWithNetworkTest(mode string) *PodmanSessionIntegration { 550 var podmanArgs = []string{"run"} 551 if mode != "" { 552 podmanArgs = append(podmanArgs, "--network", mode) 553 } 554 podmanArgs = append(podmanArgs, fedoraMinimal, "curl", "-s", "-S", "-k", "-o", "/dev/null", "http://www.redhat.com:80") 555 session := p.Podman(podmanArgs) 556 return session 557 } 558 559 func (p *PodmanTestIntegration) RunLsContainerInPod(name, pod string) (*PodmanSessionIntegration, int, string) { 560 var podmanArgs = []string{"run", "--pod", pod} 561 if name != "" { 562 podmanArgs = append(podmanArgs, "--name", name) 563 } 564 podmanArgs = append(podmanArgs, "-d", ALPINE, "ls") 565 session := p.Podman(podmanArgs) 566 session.WaitWithDefaultTimeout() 567 if session.ExitCode() != 0 { 568 return session, session.ExitCode(), session.OutputToString() 569 } 570 cid := session.OutputToString() 571 572 wsession := p.Podman([]string{"wait", cid}) 573 wsession.WaitWithDefaultTimeout() 574 return session, wsession.ExitCode(), cid 575 } 576 577 // BuildImage uses podman build and buildah to build an image 578 // called imageName based on a string dockerfile 579 func (p *PodmanTestIntegration) BuildImage(dockerfile, imageName string, layers string) string { 580 return p.buildImage(dockerfile, imageName, layers, "") 581 } 582 583 // BuildImageWithLabel uses podman build and buildah to build an image 584 // called imageName based on a string dockerfile, adds desired label to paramset 585 func (p *PodmanTestIntegration) BuildImageWithLabel(dockerfile, imageName string, layers string, label string) string { 586 return p.buildImage(dockerfile, imageName, layers, label) 587 } 588 589 // PodmanPID execs podman and returns its PID 590 func (p *PodmanTestIntegration) PodmanPID(args []string) (*PodmanSessionIntegration, int) { 591 podmanOptions := p.MakeOptions(args, false, false) 592 GinkgoWriter.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " ")) 593 594 command := exec.Command(p.PodmanBinary, podmanOptions...) 595 session, err := Start(command, GinkgoWriter, GinkgoWriter) 596 if err != nil { 597 Fail("unable to run podman command: " + strings.Join(podmanOptions, " ")) 598 } 599 podmanSession := &PodmanSession{Session: session} 600 return &PodmanSessionIntegration{podmanSession}, command.Process.Pid 601 } 602 603 func (p *PodmanTestIntegration) Quadlet(args []string, sourceDir string) *PodmanSessionIntegration { 604 GinkgoWriter.Printf("Running: %s %s with QUADLET_UNIT_DIRS=%s\n", p.QuadletBinary, strings.Join(args, " "), sourceDir) 605 606 // quadlet uses PODMAN env to get a stable podman path 607 podmanPath, found := os.LookupEnv("PODMAN") 608 if !found { 609 podmanPath = p.PodmanBinary 610 } 611 612 command := exec.Command(p.QuadletBinary, args...) 613 command.Env = []string{ 614 fmt.Sprintf("QUADLET_UNIT_DIRS=%s", sourceDir), 615 fmt.Sprintf("PODMAN=%s", podmanPath), 616 } 617 session, err := Start(command, GinkgoWriter, GinkgoWriter) 618 if err != nil { 619 Fail("unable to run quadlet command: " + strings.Join(args, " ")) 620 } 621 quadletSession := &PodmanSession{Session: session} 622 return &PodmanSessionIntegration{quadletSession} 623 } 624 625 // Cleanup cleans up the temporary store 626 func (p *PodmanTestIntegration) Cleanup() { 627 // ginkgo v2 still goes into AfterEach() when Skip() was called, 628 // some tests call skip before the podman test is initialized. 629 if p == nil { 630 return 631 } 632 633 // first stop everything, rm -fa is unreliable 634 // https://github.com/containers/podman/issues/18180 635 stop := p.Podman([]string{"stop", "--all", "-t", "0"}) 636 stop.WaitWithDefaultTimeout() 637 638 // Remove all pods... 639 podrm := p.Podman([]string{"pod", "rm", "-fa", "-t", "0"}) 640 podrm.WaitWithDefaultTimeout() 641 642 // ...and containers 643 rmall := p.Podman([]string{"rm", "-fa", "-t", "0"}) 644 rmall.WaitWithDefaultTimeout() 645 646 p.StopRemoteService() 647 // Nuke tempdir 648 rmAll(p.PodmanBinary, p.TempDir) 649 650 // Clean up the registries configuration file ENV variable set in Create 651 resetRegistriesConfigEnv() 652 653 // Make sure to only check exit codes after all cleanup is done. 654 // An error would cause it to stop and return early otherwise. 655 Expect(stop).To(Exit(0), "command: %v\nstdout: %s\nstderr: %s", stop.Command.Args, stop.OutputToString(), stop.ErrorToString()) 656 Expect(podrm).To(Exit(0), "command: %v\nstdout: %s\nstderr: %s", podrm.Command.Args, podrm.OutputToString(), podrm.ErrorToString()) 657 Expect(rmall).To(Exit(0), "command: %v\nstdout: %s\nstderr: %s", rmall.Command.Args, rmall.OutputToString(), rmall.ErrorToString()) 658 } 659 660 // CleanupVolume cleans up the volumes and containers. 661 // This already calls Cleanup() internally. 662 func (p *PodmanTestIntegration) CleanupVolume() { 663 // Remove all containers 664 session := p.Podman([]string{"volume", "rm", "-fa"}) 665 session.WaitWithDefaultTimeout() 666 Expect(session).To(Exit(0), "command: %v\nstdout: %s\nstderr: %s", session.Command.Args, session.OutputToString(), session.ErrorToString()) 667 } 668 669 // CleanupSecret cleans up the secrets and containers. 670 // This already calls Cleanup() internally. 671 func (p *PodmanTestIntegration) CleanupSecrets() { 672 // Remove all containers 673 session := p.Podman([]string{"secret", "rm", "-a"}) 674 session.Wait(90) 675 Expect(session).To(Exit(0), "command: %v\nstdout: %s\nstderr: %s", session.Command.Args, session.OutputToString(), session.ErrorToString()) 676 } 677 678 // InspectContainerToJSON takes the session output of an inspect 679 // container and returns json 680 func (s *PodmanSessionIntegration) InspectContainerToJSON() []define.InspectContainerData { 681 var i []define.InspectContainerData 682 err := jsoniter.Unmarshal(s.Out.Contents(), &i) 683 Expect(err).ToNot(HaveOccurred()) 684 return i 685 } 686 687 // InspectPodToJSON takes the sessions output from a pod inspect and returns json 688 func (s *PodmanSessionIntegration) InspectPodToJSON() define.InspectPodData { 689 var i define.InspectPodData 690 err := jsoniter.Unmarshal(s.Out.Contents(), &i) 691 Expect(err).ToNot(HaveOccurred()) 692 return i 693 } 694 695 // InspectPodToJSON takes the sessions output from an inspect and returns json 696 func (s *PodmanSessionIntegration) InspectPodArrToJSON() []define.InspectPodData { 697 var i []define.InspectPodData 698 err := jsoniter.Unmarshal(s.Out.Contents(), &i) 699 Expect(err).ToNot(HaveOccurred()) 700 return i 701 } 702 703 // CreatePod creates a pod with no infra container 704 // it optionally takes a pod name 705 func (p *PodmanTestIntegration) CreatePod(options map[string][]string) (*PodmanSessionIntegration, int, string) { 706 var args = []string{"pod", "create", "--infra=false", "--share", ""} 707 for k, values := range options { 708 for _, v := range values { 709 args = append(args, k+"="+v) 710 } 711 } 712 713 session := p.Podman(args) 714 session.WaitWithDefaultTimeout() 715 return session, session.ExitCode(), session.OutputToString() 716 } 717 718 func (p *PodmanTestIntegration) CreateVolume(options map[string][]string) (*PodmanSessionIntegration, int, string) { 719 var args = []string{"volume", "create"} 720 for k, values := range options { 721 for _, v := range values { 722 args = append(args, k+"="+v) 723 } 724 } 725 726 session := p.Podman(args) 727 session.WaitWithDefaultTimeout() 728 return session, session.ExitCode(), session.OutputToString() 729 } 730 731 func (p *PodmanTestIntegration) RunTopContainerInPod(name, pod string) *PodmanSessionIntegration { 732 return p.RunTopContainerWithArgs(name, []string{"--pod", pod}) 733 } 734 735 func (p *PodmanTestIntegration) RunHealthCheck(cid string) error { 736 for i := 0; i < 10; i++ { 737 hc := p.Podman([]string{"healthcheck", "run", cid}) 738 hc.WaitWithDefaultTimeout() 739 if hc.ExitCode() == 0 { 740 return nil 741 } 742 // Restart container if it's not running 743 ps := p.Podman([]string{"ps", "--no-trunc", "--quiet", "--filter", fmt.Sprintf("id=%s", cid)}) 744 ps.WaitWithDefaultTimeout() 745 if ps.ExitCode() == 0 { 746 if !strings.Contains(ps.OutputToString(), cid) { 747 GinkgoWriter.Printf("Container %s is not running, restarting", cid) 748 restart := p.Podman([]string{"restart", cid}) 749 restart.WaitWithDefaultTimeout() 750 if restart.ExitCode() != 0 { 751 return fmt.Errorf("unable to restart %s", cid) 752 } 753 } 754 } 755 GinkgoWriter.Printf("Waiting for %s to pass healthcheck\n", cid) 756 time.Sleep(1 * time.Second) 757 } 758 return fmt.Errorf("unable to detect %s as running", cid) 759 } 760 761 func (p *PodmanTestIntegration) CreateSeccompJSON(in []byte) (string, error) { 762 jsonFile := filepath.Join(p.TempDir, "seccomp.json") 763 err := WriteJSONFile(in, jsonFile) 764 if err != nil { 765 return "", err 766 } 767 return jsonFile, nil 768 } 769 770 func checkReason(reason string) { 771 if len(reason) < 5 { 772 panic("Test must specify a reason to skip") 773 } 774 } 775 776 func SkipIfRunc(p *PodmanTestIntegration, reason string) { 777 checkReason(reason) 778 if p.OCIRuntime == "runc" { 779 Skip("[runc]: " + reason) 780 } 781 } 782 783 func SkipIfRootlessCgroupsV1(reason string) { 784 checkReason(reason) 785 if isRootless() && !CGROUPSV2 { 786 Skip("[rootless]: " + reason) 787 } 788 } 789 790 func SkipIfRootless(reason string) { 791 checkReason(reason) 792 if isRootless() { 793 Skip("[rootless]: " + reason) 794 } 795 } 796 797 func SkipIfNotRootless(reason string) { 798 checkReason(reason) 799 if !isRootless() { 800 Skip("[notRootless]: " + reason) 801 } 802 } 803 804 func SkipIfSystemdNotRunning(reason string) { 805 checkReason(reason) 806 807 cmd := exec.Command("systemctl", "list-units") 808 err := cmd.Run() 809 if err != nil { 810 if _, ok := err.(*exec.Error); ok { 811 Skip("[notSystemd]: not running " + reason) 812 } 813 Expect(err).ToNot(HaveOccurred()) 814 } 815 } 816 817 func SkipIfNotSystemd(manager, reason string) { 818 checkReason(reason) 819 if manager != "systemd" { 820 Skip("[notSystemd]: " + reason) 821 } 822 } 823 824 func SkipOnOSVersion(os, version string) { 825 info := GetHostDistributionInfo() 826 if info.Distribution == os && info.Version == version { 827 Skip(fmt.Sprintf("Test doesn't work on %s %s", os, version)) 828 } 829 } 830 831 func SkipIfNotFedora(reason string) { 832 info := GetHostDistributionInfo() 833 if info.Distribution != "fedora" { 834 Skip(reason) 835 } 836 } 837 838 type journaldTests struct { 839 journaldSkip bool 840 journaldOnce sync.Once 841 } 842 843 var journald journaldTests 844 845 func SkipIfJournaldUnavailable() { 846 f := func() { 847 journald.journaldSkip = false 848 849 // Check if journalctl is unavailable 850 cmd := exec.Command("journalctl", "-n", "1") 851 if err := cmd.Run(); err != nil { 852 journald.journaldSkip = true 853 } 854 } 855 journald.journaldOnce.Do(f) 856 857 // In container, journalctl does not return an error even if 858 // journald is unavailable 859 SkipIfInContainer("[journald]: journalctl inside a container doesn't work correctly") 860 if journald.journaldSkip { 861 Skip("[journald]: journald is unavailable") 862 } 863 } 864 865 // Use isRootless() instead of rootless.IsRootless() 866 // This function can detect to join the user namespace by mistake 867 func isRootless() bool { 868 return os.Geteuid() != 0 869 } 870 871 func isCgroupsV1() bool { 872 return !CGROUPSV2 873 } 874 875 func SkipIfCgroupV1(reason string) { 876 checkReason(reason) 877 if isCgroupsV1() { 878 Skip(reason) 879 } 880 } 881 882 func SkipIfCgroupV2(reason string) { 883 checkReason(reason) 884 if CGROUPSV2 { 885 Skip(reason) 886 } 887 } 888 889 func isContainerized() bool { 890 // This is set to "podman" by podman automatically 891 return os.Getenv("container") != "" 892 } 893 894 func SkipIfContainerized(reason string) { 895 checkReason(reason) 896 if isContainerized() { 897 Skip(reason) 898 } 899 } 900 901 func SkipIfRemote(reason string) { 902 checkReason(reason) 903 if !IsRemote() { 904 return 905 } 906 Skip("[remote]: " + reason) 907 } 908 909 func SkipIfNotRemote(reason string) { 910 checkReason(reason) 911 if IsRemote() { 912 return 913 } 914 Skip("[local]: " + reason) 915 } 916 917 // SkipIfInContainer skips a test if the test is run inside a container 918 func SkipIfInContainer(reason string) { 919 checkReason(reason) 920 if os.Getenv("TEST_ENVIRON") == "container" { 921 Skip("[container]: " + reason) 922 } 923 } 924 925 // SkipIfNotActive skips a test if the given systemd unit is not active 926 func SkipIfNotActive(unit string, reason string) { 927 checkReason(reason) 928 929 cmd := exec.Command("systemctl", "is-active", unit) 930 cmd.Stdout = GinkgoWriter 931 cmd.Stderr = GinkgoWriter 932 err := cmd.Run() 933 if cmd.ProcessState.ExitCode() == 0 { 934 return 935 } 936 Skip(fmt.Sprintf("[systemd]: unit %s is not active (%v): %s", unit, err, reason)) 937 } 938 939 func SkipIfCNI(p *PodmanTestIntegration) { 940 if p.NetworkBackend == CNI { 941 Skip("this test is not compatible with the CNI network backend") 942 } 943 } 944 945 func SkipIfNetavark(p *PodmanTestIntegration) { 946 if p.NetworkBackend == Netavark { 947 Skip("This test is not compatible with the netavark network backend") 948 } 949 } 950 951 // PodmanAsUser is the exec call to podman on the filesystem with the specified uid/gid and environment 952 func (p *PodmanTestIntegration) PodmanAsUser(args []string, uid, gid uint32, cwd string, env []string) *PodmanSessionIntegration { 953 podmanSession := p.PodmanAsUserBase(args, uid, gid, cwd, env, false, false, nil, nil) 954 return &PodmanSessionIntegration{podmanSession} 955 } 956 957 // RestartRemoteService stop and start API Server, usually to change config 958 func (p *PodmanTestIntegration) RestartRemoteService() { 959 p.StopRemoteService() 960 p.StartRemoteService() 961 } 962 963 // RestoreArtifactToCache populates the imagecache from tarballs that were cached earlier 964 func (p *PodmanTestIntegration) RestoreArtifactToCache(image string) error { 965 tarball := imageTarPath(image) 966 if _, err := os.Stat(tarball); err == nil { 967 GinkgoWriter.Printf("Restoring %s...\n", image) 968 p.Root = p.ImageCacheDir 969 restore := p.PodmanNoEvents([]string{"load", "-q", "-i", tarball}) 970 restore.WaitWithDefaultTimeout() 971 } 972 return nil 973 } 974 975 func populateCache(podman *PodmanTestIntegration) { 976 for _, image := range CACHE_IMAGES { 977 err := podman.RestoreArtifactToCache(image) 978 Expect(err).ToNot(HaveOccurred()) 979 } 980 // logformatter uses this to recognize the first test 981 GinkgoWriter.Printf("-----------------------------\n") 982 } 983 984 // rmAll removes the directory and its content, when running rootless we use 985 // podman unshare to prevent any subuid/gid problems 986 func rmAll(podmanBin string, path string) { 987 // Remove cache dirs 988 if isRootless() { 989 // If rootless, os.RemoveAll() is failed due to permission denied 990 cmd := exec.Command(podmanBin, "unshare", "rm", "-rf", path) 991 cmd.Stdout = GinkgoWriter 992 cmd.Stderr = GinkgoWriter 993 if err := cmd.Run(); err != nil { 994 GinkgoWriter.Printf("%v\n", err) 995 } 996 } else { 997 // When using overlay as root, podman leaves a stray mount behind. 998 // This leak causes remote tests to take a loooooong time, which 999 // then causes Cirrus to time out. Unmount that stray. 1000 overlayPath := path + "/root/overlay" 1001 if _, err := os.Stat(overlayPath); err == nil { 1002 if err = unix.Unmount(overlayPath, unix.MNT_DETACH); err != nil { 1003 GinkgoWriter.Printf("Error unmounting %s: %v\n", overlayPath, err) 1004 } 1005 } 1006 1007 if err = os.RemoveAll(path); err != nil { 1008 GinkgoWriter.Printf("%q\n", err) 1009 } 1010 } 1011 } 1012 1013 // PodmanNoCache calls the podman command with no configured imagecache 1014 func (p *PodmanTestIntegration) PodmanNoCache(args []string) *PodmanSessionIntegration { 1015 podmanSession := p.PodmanBase(args, false, true) 1016 return &PodmanSessionIntegration{podmanSession} 1017 } 1018 1019 func PodmanTestSetup(tempDir string) *PodmanTestIntegration { 1020 return PodmanTestCreateUtil(tempDir, false) 1021 } 1022 1023 // PodmanNoEvents calls the Podman command without an imagecache and without an 1024 // events backend. It is used mostly for caching and uncaching images. 1025 func (p *PodmanTestIntegration) PodmanNoEvents(args []string) *PodmanSessionIntegration { 1026 podmanSession := p.PodmanBase(args, true, true) 1027 return &PodmanSessionIntegration{podmanSession} 1028 } 1029 1030 // MakeOptions assembles all the podman main options 1031 func (p *PodmanTestIntegration) makeOptions(args []string, noEvents, noCache bool) []string { 1032 if p.RemoteTest { 1033 if !util.StringInSlice("--remote", args) { 1034 return append([]string{"--remote", "--url", p.RemoteSocket}, args...) 1035 } 1036 return args 1037 } 1038 1039 var debug string 1040 if _, ok := os.LookupEnv("E2E_DEBUG"); ok { 1041 debug = "--log-level=debug --syslog=true " 1042 } 1043 1044 eventsType := "file" 1045 if noEvents { 1046 eventsType = "none" 1047 } 1048 1049 podmanOptions := strings.Split(fmt.Sprintf("%s--root %s --runroot %s --runtime %s --conmon %s --network-config-dir %s --network-backend %s --cgroup-manager %s --tmpdir %s --events-backend %s --db-backend %s", 1050 debug, p.Root, p.RunRoot, p.OCIRuntime, p.ConmonBinary, p.NetworkConfigDir, p.NetworkBackend.ToString(), p.CgroupManager, p.TmpDir, eventsType, p.DatabaseBackend), " ") 1051 1052 podmanOptions = append(podmanOptions, strings.Split(p.StorageOptions, " ")...) 1053 if !noCache { 1054 cacheOptions := []string{"--storage-opt", 1055 fmt.Sprintf("%s.imagestore=%s", p.PodmanTest.ImageCacheFS, p.PodmanTest.ImageCacheDir)} 1056 podmanOptions = append(cacheOptions, podmanOptions...) 1057 } 1058 podmanOptions = append(podmanOptions, args...) 1059 return podmanOptions 1060 } 1061 1062 func writeConf(conf []byte, confPath string) { 1063 if _, err := os.Stat(filepath.Dir(confPath)); os.IsNotExist(err) { 1064 if err := os.MkdirAll(filepath.Dir(confPath), 0o777); err != nil { 1065 GinkgoWriter.Println(err) 1066 } 1067 } 1068 if err := os.WriteFile(confPath, conf, 0o777); err != nil { 1069 GinkgoWriter.Println(err) 1070 } 1071 } 1072 1073 func removeConf(confPath string) { 1074 if err := os.Remove(confPath); err != nil { 1075 GinkgoWriter.Println(err) 1076 } 1077 } 1078 1079 // generateNetworkConfig generates a CNI or Netavark config with a random name 1080 // it returns the network name and the filepath 1081 func generateNetworkConfig(p *PodmanTestIntegration) (string, string) { 1082 var ( 1083 path string 1084 conf string 1085 ) 1086 // generate a random name to prevent conflicts with other tests 1087 name := "net" + stringid.GenerateRandomID() 1088 if p.NetworkBackend != Netavark { 1089 path = filepath.Join(p.NetworkConfigDir, fmt.Sprintf("%s.conflist", name)) 1090 conf = fmt.Sprintf(`{ 1091 "cniVersion": "0.3.0", 1092 "name": "%s", 1093 "plugins": [ 1094 { 1095 "type": "bridge", 1096 "bridge": "cni1", 1097 "isGateway": true, 1098 "ipMasq": true, 1099 "ipam": { 1100 "type": "host-local", 1101 "subnet": "10.99.0.0/16", 1102 "routes": [ 1103 { "dst": "0.0.0.0/0" } 1104 ] 1105 } 1106 }, 1107 { 1108 "type": "portmap", 1109 "capabilities": { 1110 "portMappings": true 1111 } 1112 } 1113 ] 1114 }`, name) 1115 } else { 1116 path = filepath.Join(p.NetworkConfigDir, fmt.Sprintf("%s.json", name)) 1117 conf = fmt.Sprintf(` 1118 { 1119 "name": "%s", 1120 "id": "e1ef2749024b88f5663ca693a9118e036d6bfc48bcfe460faf45e9614a513e5c", 1121 "driver": "bridge", 1122 "network_interface": "netavark1", 1123 "created": "2022-01-05T14:15:10.975493521-06:00", 1124 "subnets": [ 1125 { 1126 "subnet": "10.100.0.0/16", 1127 "gateway": "10.100.0.1" 1128 } 1129 ], 1130 "ipv6_enabled": false, 1131 "internal": false, 1132 "dns_enabled": true, 1133 "ipam_options": { 1134 "driver": "host-local" 1135 } 1136 } 1137 `, name) 1138 } 1139 writeConf([]byte(conf), path) 1140 return name, path 1141 } 1142 1143 func (p *PodmanTestIntegration) removeNetwork(name string) { 1144 session := p.Podman([]string{"network", "rm", "-f", name}) 1145 session.WaitWithDefaultTimeout() 1146 Expect(session.ExitCode()).To(BeNumerically("<=", 1), "Exit code must be 0 or 1") 1147 } 1148 1149 // generatePolicyFile generates a signature verification policy file. 1150 // it returns the policy file path. 1151 func generatePolicyFile(tempDir string) string { 1152 keyPath := filepath.Join(tempDir, "key.gpg") 1153 policyPath := filepath.Join(tempDir, "policy.json") 1154 conf := fmt.Sprintf(` 1155 { 1156 "default": [ 1157 { 1158 "type": "insecureAcceptAnything" 1159 } 1160 ], 1161 "transports": { 1162 "docker": { 1163 "localhost:5000": [ 1164 { 1165 "type": "signedBy", 1166 "keyType": "GPGKeys", 1167 "keyPath": "%s" 1168 } 1169 ], 1170 "localhost:5000/sigstore-signed": [ 1171 { 1172 "type": "sigstoreSigned", 1173 "keyPath": "testdata/sigstore-key.pub" 1174 } 1175 ], 1176 "localhost:5000/sigstore-signed-params": [ 1177 { 1178 "type": "sigstoreSigned", 1179 "keyPath": "testdata/sigstore-key.pub" 1180 } 1181 ] 1182 } 1183 } 1184 } 1185 `, keyPath) 1186 writeConf([]byte(conf), policyPath) 1187 return policyPath 1188 } 1189 1190 func (s *PodmanSessionIntegration) jq(jqCommand string) (string, error) { 1191 var out bytes.Buffer 1192 cmd := exec.Command("jq", jqCommand) 1193 cmd.Stdin = strings.NewReader(s.OutputToString()) 1194 cmd.Stdout = &out 1195 err := cmd.Run() 1196 return strings.TrimRight(out.String(), "\n"), err 1197 } 1198 1199 func (p *PodmanTestIntegration) buildImage(dockerfile, imageName string, layers string, label string) string { 1200 dockerfilePath := filepath.Join(p.TempDir, "Dockerfile-"+stringid.GenerateRandomID()) 1201 err := os.WriteFile(dockerfilePath, []byte(dockerfile), 0755) 1202 Expect(err).ToNot(HaveOccurred()) 1203 cmd := []string{"build", "--pull-never", "--layers=" + layers, "--file", dockerfilePath} 1204 if label != "" { 1205 cmd = append(cmd, "--label="+label) 1206 } 1207 if len(imageName) > 0 { 1208 cmd = append(cmd, []string{"-t", imageName}...) 1209 } 1210 cmd = append(cmd, p.TempDir) 1211 session := p.Podman(cmd) 1212 session.Wait(240) 1213 Expect(session).Should(Exit(0), fmt.Sprintf("BuildImage session output: %q", session.OutputToString())) 1214 output := session.OutputToStringArray() 1215 return output[len(output)-1] 1216 } 1217 1218 func writeYaml(content string, fileName string) error { 1219 f, err := os.Create(fileName) 1220 if err != nil { 1221 return err 1222 } 1223 defer f.Close() 1224 1225 _, err = f.WriteString(content) 1226 if err != nil { 1227 return err 1228 } 1229 1230 return nil 1231 } 1232 1233 // GetPort finds an unused TCP/IP port on the system, in the range 5000-5999 1234 func GetPort() int { 1235 portMin := 5000 1236 portMax := 5999 1237 rng := rand.New(rand.NewSource(time.Now().UnixNano())) 1238 1239 // Avoid dup-allocation races between parallel ginkgo processes 1240 nProcs := GinkgoT().ParallelTotal() 1241 myProc := GinkgoT().ParallelProcess() - 1 1242 1243 for i := 0; i < 50; i++ { 1244 // Random port within that range 1245 port := portMin + rng.Intn((portMax-portMin)/nProcs)*nProcs + myProc 1246 1247 used, err := net.Listen("tcp", "0.0.0.0:"+strconv.Itoa(port)) 1248 if err == nil { 1249 // it's open. Return it. 1250 err = used.Close() 1251 Expect(err).ToNot(HaveOccurred(), "closing random port") 1252 return port 1253 } 1254 } 1255 1256 Fail(fmt.Sprintf("unable to get free port in range %d-%d", portMin, portMax)) 1257 return 0 // notreached 1258 } 1259 1260 func ncz(port int) bool { 1261 timeout := 500 * time.Millisecond 1262 for i := 0; i < 5; i++ { 1263 ncCmd := []string{"-z", "localhost", strconv.Itoa(port)} 1264 GinkgoWriter.Printf("Running: nc %s\n", strings.Join(ncCmd, " ")) 1265 check := SystemExec("nc", ncCmd) 1266 if check.ExitCode() == 0 { 1267 return true 1268 } 1269 time.Sleep(timeout) 1270 timeout++ 1271 } 1272 return false 1273 } 1274 1275 func createNetworkName(name string) string { 1276 return name + stringid.GenerateRandomID()[:10] 1277 } 1278 1279 var IPRegex = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}` 1280 1281 // digShort execs into the given container and does a dig lookup with a timeout 1282 // backoff. If it gets a response, it ensures that the output is in the correct 1283 // format and iterates a string array for match 1284 func digShort(container, lookupName, expectedIP string, p *PodmanTestIntegration) { 1285 digInterval := time.Millisecond * 250 1286 for i := 0; i < 6; i++ { 1287 time.Sleep(digInterval * time.Duration(i)) 1288 dig := p.Podman([]string{"exec", container, "dig", "+short", lookupName}) 1289 dig.WaitWithDefaultTimeout() 1290 output := dig.OutputToString() 1291 if dig.ExitCode() == 0 && output != "" { 1292 Expect(output).To(Equal(expectedIP)) 1293 // success 1294 return 1295 } 1296 } 1297 Fail("dns is not responding") 1298 } 1299 1300 // WaitForFile to be created in defaultWaitTimeout seconds, returns false if file not created 1301 func WaitForFile(path string) (err error) { 1302 until := time.Now().Add(time.Duration(defaultWaitTimeout) * time.Second) 1303 for time.Now().Before(until) { 1304 _, err = os.Stat(path) 1305 switch { 1306 case err == nil: 1307 return nil 1308 case errors.Is(err, os.ErrNotExist): 1309 time.Sleep(10 * time.Millisecond) 1310 default: 1311 return err 1312 } 1313 } 1314 return err 1315 } 1316 1317 // WaitForService blocks for defaultWaitTimeout seconds, waiting for some service listening on given host:port 1318 func WaitForService(address url.URL) { 1319 // Wait for podman to be ready 1320 var err error 1321 until := time.Now().Add(time.Duration(defaultWaitTimeout) * time.Second) 1322 for time.Now().Before(until) { 1323 var conn net.Conn 1324 conn, err = net.Dial("tcp", address.Host) 1325 if err == nil { 1326 conn.Close() 1327 break 1328 } 1329 1330 // Podman not available yet... 1331 time.Sleep(10 * time.Millisecond) 1332 } 1333 Expect(err).ShouldNot(HaveOccurred()) 1334 } 1335 1336 // useCustomNetworkDir makes sure this test uses a custom network dir. 1337 // This needs to be called for all test they may remove networks from other tests, 1338 // so netwokr prune, system prune, or system reset. 1339 // see https://github.com/containers/podman/issues/17946 1340 func useCustomNetworkDir(podmanTest *PodmanTestIntegration, tempdir string) { 1341 // set custom network directory to prevent flakes since the dir is shared with all tests by default 1342 podmanTest.NetworkConfigDir = tempdir 1343 if IsRemote() { 1344 podmanTest.RestartRemoteService() 1345 } 1346 } 1347 1348 // copy directories recursively from source path to destination path 1349 func CopyDirectory(srcDir, dest string) error { 1350 entries, err := os.ReadDir(srcDir) 1351 if err != nil { 1352 return err 1353 } 1354 for _, entry := range entries { 1355 sourcePath := filepath.Join(srcDir, entry.Name()) 1356 destPath := filepath.Join(dest, entry.Name()) 1357 1358 fileInfo, err := os.Stat(sourcePath) 1359 if err != nil { 1360 return err 1361 } 1362 1363 stat, ok := fileInfo.Sys().(*syscall.Stat_t) 1364 if !ok { 1365 return fmt.Errorf("failed to get raw syscall.Stat_t data for %q", sourcePath) 1366 } 1367 1368 switch fileInfo.Mode() & os.ModeType { 1369 case os.ModeDir: 1370 if err := os.MkdirAll(destPath, 0755); err != nil { 1371 return fmt.Errorf("failed to create directory: %q, error: %q", destPath, err.Error()) 1372 } 1373 if err := CopyDirectory(sourcePath, destPath); err != nil { 1374 return err 1375 } 1376 case os.ModeSymlink: 1377 if err := CopySymLink(sourcePath, destPath); err != nil { 1378 return err 1379 } 1380 default: 1381 if err := Copy(sourcePath, destPath); err != nil { 1382 return err 1383 } 1384 } 1385 1386 if err := os.Lchown(destPath, int(stat.Uid), int(stat.Gid)); err != nil { 1387 return err 1388 } 1389 1390 fInfo, err := entry.Info() 1391 if err != nil { 1392 return err 1393 } 1394 1395 isSymlink := fInfo.Mode()&os.ModeSymlink != 0 1396 if !isSymlink { 1397 if err := os.Chmod(destPath, fInfo.Mode()); err != nil { 1398 return err 1399 } 1400 } 1401 } 1402 return nil 1403 } 1404 1405 func Copy(srcFile, dstFile string) error { 1406 out, err := os.Create(dstFile) 1407 if err != nil { 1408 return err 1409 } 1410 1411 defer out.Close() 1412 1413 in, err := os.Open(srcFile) 1414 if err != nil { 1415 return err 1416 } 1417 1418 _, err = io.Copy(out, in) 1419 if err != nil { 1420 return err 1421 } 1422 defer in.Close() 1423 return nil 1424 } 1425 1426 func CopySymLink(source, dest string) error { 1427 link, err := os.Readlink(source) 1428 if err != nil { 1429 return err 1430 } 1431 return os.Symlink(link, dest) 1432 }