/* * Copyright 2020 The Dragonfly Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package e2e import ( "fmt" "math/rand" "strconv" "strings" "time" . "github.com/onsi/ginkgo/v2" //nolint . "github.com/onsi/gomega" //nolint "d7y.io/dragonfly/v2/pkg/net/http" "d7y.io/dragonfly/v2/test/e2e/e2eutil" ) var _ = Describe("Download with dfget and proxy", func() { Context("dfget", func() { singleDfgetTest("dfget daemon download should be ok", dragonflyNamespace, "component=dfdaemon", "dragonfly-dfdaemon-", "dfdaemon") for i := 0; i < 3; i++ { singleDfgetTest( fmt.Sprintf("dfget daemon proxy-%d should be ok", i), dragonflyE2ENamespace, fmt.Sprintf("statefulset.kubernetes.io/pod-name=proxy-%d", i), "proxy-", "proxy") } }) }) func getFileSizes() map[string]int { var ( details = map[string]int{} files = e2eutil.GetFileList() ) if featureGates.Enabled(featureGateEmptyFile) { fmt.Printf("dfget-empty-file feature gate enabled\n") files = append(files, "/tmp/empty-file") } for _, path := range files { out, err := e2eutil.DockerCommand("stat", "--printf=%s", path).CombinedOutput() if err != nil { fmt.Printf("stat %s erro: %s, stdout: %s", path, err, string(out)) } Expect(err).NotTo(HaveOccurred()) size, err := strconv.Atoi(string(out)) Expect(err).NotTo(HaveOccurred()) details[path] = size } return details } func getRandomRange(size int) *http.Range { if size == 0 { return &http.Range{ Start: 0, Length: 0, } } rnd := rand.New(rand.NewSource(time.Now().UnixNano())) r1 := rnd.Intn(size - 1) r2 := rnd.Intn(size - 1) var start, end int if r1 > r2 { start, end = r2, r1 } else { start, end = r1, r2 } // range for [start, end] rg := &http.Range{ Start: int64(start), Length: int64(end + 1 - start), } return rg } func singleDfgetTest(name, ns, label, podNamePrefix, container string) { It(name, Label("download", "normal"), func() { fileDetails := getFileSizes() out, err := e2eutil.KubeCtlCommand("-n", ns, "get", "pod", "-l", label, "-o", "jsonpath='{range .items[*]}{.metadata.name}{end}'").CombinedOutput() podName := strings.Trim(string(out), "'") Expect(err).NotTo(HaveOccurred()) fmt.Println("test in pod: " + podName) Expect(strings.HasPrefix(podName, podNamePrefix)).Should(BeTrue()) // copy test tools into container if featureGates.Enabled(featureGateRange) { out, err = e2eutil.KubeCtlCommand("-n", ns, "cp", "-c", container, "/tmp/sha256sum-offset", fmt.Sprintf("%s:/bin/", podName)).CombinedOutput() if err != nil { fmt.Println(string(out)) } Expect(err).NotTo(HaveOccurred()) } pod := e2eutil.NewPodExec(ns, podName, container) // install curl out, err = pod.Command("apk", "add", "-U", "curl").CombinedOutput() fmt.Println("apk output: " + string(out)) Expect(err).NotTo(HaveOccurred()) for path, size := range fileDetails { // skip empty file if size == 0 { continue } url1 := e2eutil.GetFileURL(path) url2 := e2eutil.GetNoContentLengthFileURL(path) // make ranged requests to invoke prefetch feature if featureGates.Enabled(featureGateRange) { rg1, rg2 := getRandomRange(size), getRandomRange(size) downloadSingleFile(ns, pod, path, url1, size, rg1, rg1.String()) if featureGates.Enabled(featureGateNoLength) { downloadSingleFile(ns, pod, path, url2, size, rg2, rg2.String()) } if featureGates.Enabled(featureGateOpenRange) { rg3, rg4 := getRandomRange(size), getRandomRange(size) // set target length rg3.Length = int64(size) - rg3.Start rg4.Length = int64(size) - rg4.Start downloadSingleFile(ns, pod, path, url1, size, rg3, fmt.Sprintf("bytes=%d-", rg3.Start)) if featureGates.Enabled(featureGateNoLength) { downloadSingleFile(ns, pod, path, url2, size, rg4, fmt.Sprintf("bytes=%d-", rg4.Start)) } } } downloadSingleFile(ns, pod, path, url1, size, nil, "") if featureGates.Enabled(featureGateNoLength) { downloadSingleFile(ns, pod, path, url2, size, nil, "") } } }) It(name+" - recursive with dfget", Label("download", "recursive", "dfget"), func() { if !featureGates.Enabled(featureGateRecursive) { fmt.Println("feature gate recursive is disable, skip") return } // prepared data in minio pod // test bucket minio-test-bucket // test path /dragonfly-test/usr // test sub dirs (no empty dirs) // sha256sum txt: /host/tmp/dragonfly-test.sha256sum.txt subDirs := []string{"bin", "lib64", "libexec", "sbin"} out, err := e2eutil.KubeCtlCommand("-n", ns, "get", "pod", "-l", label, "-o", "jsonpath='{range .items[*]}{.metadata.name}{end}'").CombinedOutput() podName := strings.Trim(string(out), "'") Expect(err).NotTo(HaveOccurred()) fmt.Println("test in pod: " + podName) pod := e2eutil.NewPodExec(ns, podName, container) for _, dir := range subDirs { var dfget []string dfget = append(dfget, "/opt/dragonfly/bin/dfget", "--disable-back-source", "--recursive", "--level=100", "-H", "awsEndpoint: http://minio.dragonfly-e2e.svc:9000", "-H", "awsRegion: us-west-1", "-H", "awsAccessKeyID: root", "-H", "awsSecretAccessKey: password", "-H", "awsS3ForcePathStyle: true", "-O", fmt.Sprintf("/var/lib/dragonfly-test/usr/%s", dir), fmt.Sprintf("s3://minio-test-bucket/dragonfly-test/usr/%s", dir), ) // recursive download file via dfget start := time.Now() out, err = pod.Command(dfget...).CombinedOutput() end := time.Now() fmt.Println(string(out)) Expect(err).NotTo(HaveOccurred()) // slow download Expect(end.Sub(start).Seconds() < 200.0).To(Equal(true)) } // calculate downloaded files sha256sum out, err = pod.Command("/bin/sh", "-c", `cd /var/lib/dragonfly-test && find . -type f | sort | xargs -n 1 sha256sum`).CombinedOutput() Expect(err).NotTo(HaveOccurred()) sha256sum1 := strings.TrimSpace(string(out)) // get the original sha256sum in minio pod minioPod := e2eutil.NewPodExec("dragonfly-e2e", "minio-0", "minio") out, err = minioPod.Command("cat", "/host/tmp/dragonfly-test.sha256sum.txt").CombinedOutput() Expect(err).NotTo(HaveOccurred()) sha256sum2 := strings.TrimSpace(string(out)) fmt.Printf("sha256sum1(10 lines):\n%s\nsha256sum2(10 lines):\n%s\n", strings.Join(strings.Split(string(sha256sum1), "\n")[:10], "\n"), strings.Join(strings.Split(string(sha256sum2), "\n")[:10], "\n")) // ensure same sha256sum Expect(sha256sum1).To(Equal(sha256sum2)) }) It(name+" - recursive with grpc", Label("download", "recursive", "grpc"), func() { if !featureGates.Enabled(featureGateRecursive) { fmt.Println("feature gate recursive is disable, skip") return } // prepared data in minio pod // test bucket minio-test-bucket // test path /dragonfly-test/usr // test sub dirs (no empty dirs) // sha256sum txt: /host/tmp/dragonfly-test.sha256sum.txt subDirs := []string{"bin", "lib64", "libexec", "sbin"} out, err := e2eutil.KubeCtlCommand("-n", ns, "get", "pod", "-l", label, "-o", "jsonpath='{range .items[*]}{.metadata.name}{end}'").CombinedOutput() podName := strings.Trim(string(out), "'") Expect(err).NotTo(HaveOccurred()) fmt.Println("test in pod: " + podName) pod := e2eutil.NewPodExec(ns, podName, container) // copy test tools into container out, err = e2eutil.KubeCtlCommand("-n", ns, "cp", "-c", container, "/tmp/download-grpc-test", fmt.Sprintf("%s:/bin/", podName)).CombinedOutput() if err != nil { fmt.Println(string(out)) } Expect(err).NotTo(HaveOccurred()) for _, dir := range subDirs { dfget := []string{"/bin/download-grpc-test", "-sub-dir", dir} // recursive download file via dfget start := time.Now() out, err = pod.Command(dfget...).CombinedOutput() end := time.Now() fmt.Println(string(out)) Expect(err).NotTo(HaveOccurred()) // slow download Expect(end.Sub(start).Seconds() < 200.0).To(Equal(true)) } // calculate downloaded files sha256sum out, err = pod.Command("/bin/sh", "-c", `cd /var/lib/dragonfly-grpc-test && find . -type f | sort | xargs -n 1 sha256sum`).CombinedOutput() Expect(err).NotTo(HaveOccurred()) sha256sum1 := strings.TrimSpace(string(out)) // get the original sha256sum in minio pod minioPod := e2eutil.NewPodExec("dragonfly-e2e", "minio-0", "minio") out, err = minioPod.Command("cat", "/host/tmp/dragonfly-test.sha256sum.txt").CombinedOutput() Expect(err).NotTo(HaveOccurred()) sha256sum2 := strings.TrimSpace(string(out)) fmt.Printf("sha256sum1(10 lines):\n%s\nsha256sum2(10 lines):\n%s\n", strings.Join(strings.Split(string(sha256sum1), "\n")[:10], "\n"), strings.Join(strings.Split(string(sha256sum2), "\n")[:10], "\n")) // ensure same sha256sum Expect(sha256sum1).To(Equal(sha256sum2)) }) } func downloadSingleFile(ns string, pod *e2eutil.PodExec, path, url string, size int, rg *http.Range, rawRg string) { var ( sha256sum []string dfget []string curl []string sha256sumOffset []string dfgetOffset []string ) if rg == nil { sha256sum = append(sha256sum, "/usr/bin/sha256sum", path) dfget = append(dfget, "/opt/dragonfly/bin/dfget", "--disable-back-source", "-O", "/tmp/d7y.out", url) curl = append(curl, "/usr/bin/curl", "-x", "http://127.0.0.1:65001", "-s", "--dump-header", "-", "-o", "/tmp/curl.out", url) } else { sha256sum = append(sha256sum, "sh", "-c", fmt.Sprintf("/bin/sha256sum-offset -file %s -offset %d -length %d", path, rg.Start, rg.Length)) dfget = append(dfget, "/opt/dragonfly/bin/dfget", "--disable-back-source", "-O", "/tmp/d7y.out", "-H", fmt.Sprintf("Range: %s", rawRg), url) curl = append(curl, "/usr/bin/curl", "-x", "http://127.0.0.1:65001", "-s", "--dump-header", "-", "-o", "/tmp/curl.out", "--header", fmt.Sprintf("Range: %s", rawRg), url) sha256sumOffset = append(sha256sumOffset, "sh", "-c", fmt.Sprintf("/bin/sha256sum-offset -file %s -offset %d -length %d", "/var/lib/dragonfly/d7y.offset.out", rg.Start, rg.Length)) dfgetOffset = append(dfgetOffset, "/opt/dragonfly/bin/dfget", "--disable-back-source", "--original-offset", "-O", "/var/lib/dragonfly/d7y.offset.out", "-H", fmt.Sprintf("Range: %s", rawRg), url) } fmt.Printf("--------------------------------------------------------------------------------\n\n") if rg == nil { fmt.Printf("download %s, size %d\n", url, size) } else { fmt.Printf("download %s, size %d, request range: %s, target length: %d\n", url, size, rawRg, rg.Length) } // get original file digest out, err := e2eutil.DockerCommand(sha256sum...).CombinedOutput() fmt.Println("original sha256sum: " + string(out)) Expect(err).NotTo(HaveOccurred()) sha256sum1 := strings.Split(string(out), " ")[0] var ( start time.Time end time.Time ) // download file via dfget start = time.Now() out, err = pod.Command(dfget...).CombinedOutput() end = time.Now() fmt.Println(string(out)) Expect(err).NotTo(HaveOccurred()) // get dfget downloaded file digest out, err = pod.Command("/usr/bin/sha256sum", "/tmp/d7y.out").CombinedOutput() fmt.Println("dfget sha256sum: " + string(out)) Expect(err).NotTo(HaveOccurred()) sha256sum2 := strings.Split(string(out), " ")[0] Expect(sha256sum1).To(Equal(sha256sum2)) // slow download Expect(end.Sub(start).Seconds() < 50.0).To(Equal(true)) // download file via dfget with offset if rg != nil { // move output for next cases and debugging _, _ = pod.Command("/bin/sh", "-c", ` rm -f /var/lib/dragonfly/d7y.offset.out.last cp -l /var/lib/dragonfly/d7y.offset.out /var/lib/dragonfly/d7y.offset.out.last rm -f /var/lib/dragonfly/d7y.offset.out `).CombinedOutput() start = time.Now() out, err = pod.Command(dfgetOffset...).CombinedOutput() end = time.Now() fmt.Println(string(out)) Expect(err).NotTo(HaveOccurred()) // get dfget downloaded file digest out, err = pod.Command(sha256sumOffset...).CombinedOutput() fmt.Println("dfget with offset sha256sum: " + string(out)) Expect(err).NotTo(HaveOccurred()) sha256sumz := strings.Split(string(out), " ")[0] Expect(sha256sum1).To(Equal(sha256sumz)) // slow download Expect(end.Sub(start).Seconds() < 50.0).To(Equal(true)) } // skip dfdaemon if ns == dragonflyNamespace { fmt.Println("skip " + dragonflyNamespace + " namespace proxy tests") return } // download file via proxy start = time.Now() out, err = pod.Command(curl...).CombinedOutput() end = time.Now() fmt.Print(string(out)) Expect(err).NotTo(HaveOccurred()) // get proxy downloaded file digest out, err = pod.Command("/usr/bin/sha256sum", "/tmp/curl.out").CombinedOutput() fmt.Println("curl sha256sum: " + string(out)) Expect(err).NotTo(HaveOccurred()) sha256sum3 := strings.Split(string(out), " ")[0] Expect(sha256sum1).To(Equal(sha256sum3)) // slow download Expect(end.Sub(start).Seconds() < 50.0).To(Equal(true)) }