From 790d53dc581874575aef1777122856a59bcdbf8b Mon Sep 17 00:00:00 2001 From: liyuanr Date: Thu, 3 Aug 2023 22:18:23 +0800 Subject: [PATCH] KubeOS:add unit tests of containerd and docker,modify code for cleancode Add unit tests of using containerd or docker to upgrade. Modify code for cleancode and fix issue of ctr images pull Signed-off-by: liyuanr --- cmd/agent/server/containerd_image.go | 8 +- cmd/agent/server/containerd_image_test.go | 139 ++++++++++++++++++++++ cmd/agent/server/docker_image.go | 2 +- cmd/agent/server/docker_image_test.go | 88 ++++++++++++-- cmd/agent/server/utils.go | 102 ++++++++-------- 5 files changed, 272 insertions(+), 67 deletions(-) create mode 100644 cmd/agent/server/containerd_image_test.go diff --git a/cmd/agent/server/containerd_image.go b/cmd/agent/server/containerd_image.go index f180fb54..fd612745 100644 --- a/cmd/agent/server/containerd_image.go +++ b/cmd/agent/server/containerd_image.go @@ -23,9 +23,7 @@ import ( pb "openeuler.org/KubeOS/cmd/agent/api" ) -var ( - defaultNamespace = "k8s.io" -) +const defaultNamespace = "k8s.io" type conImageHandler struct{} @@ -56,7 +54,7 @@ func (c conImageHandler) getRootfsArchive(req *pb.UpdateRequest, neededPath prep } } else { containerdCommand = "ctr" - if err := runCommand("ctr", "-n", defaultNamespace, "images", "pull", "--host-dir", + if err := runCommand("ctr", "-n", defaultNamespace, "images", "pull", "--hosts-dir", "/etc/containerd/certs.d", imageName); err != nil { return "", err } @@ -76,7 +74,7 @@ func (c conImageHandler) getRootfsArchive(req *pb.UpdateRequest, neededPath prep return "", err } defer checkAndCleanMount(mountPath) - if err := copyFile(neededPath.tarPath, mountPath+"/"+rootfsArchive); err != nil { + if err := copyFile(neededPath.tarPath, mountPath+"/"+neededPath.rootfsFile); err != nil { return "", err } return "", nil diff --git a/cmd/agent/server/containerd_image_test.go b/cmd/agent/server/containerd_image_test.go new file mode 100644 index 00000000..d7133c37 --- /dev/null +++ b/cmd/agent/server/containerd_image_test.go @@ -0,0 +1,139 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +// Package server implements server of os-agent and listener of os-agent server. The server uses gRPC interface. +package server + +import ( + "os" + "testing" + + "github.com/agiledragon/gomonkey/v2" + pb "openeuler.org/KubeOS/cmd/agent/api" +) + +func Test_conImageHandler_downloadImage(t *testing.T) { + type args struct { + req *pb.UpdateRequest + } + tests := []struct { + name string + c conImageHandler + args args + want string + wantErr bool + }{ + + { + name: "pullImageError", + c: conImageHandler{}, + args: args{ + req: &pb.UpdateRequest{ContainerImage: "testError"}, + }, + want: "", + wantErr: true, + }, + { + name: "checkSumError", + c: conImageHandler{}, + args: args{ + req: &pb.UpdateRequest{ContainerImage: "docker.io/library/hello-world:latest"}, + }, + want: "", + wantErr: true, + }, + { + name: "normal", + c: conImageHandler{}, + args: args{ + req: &pb.UpdateRequest{ + ContainerImage: "docker.io/library/hello-world:latest", + }, + }, + want: "update-test1/upadte.img", + wantErr: false, + }, + } + patchPrepareEnv := gomonkey.ApplyFunc(prepareEnv, func() (preparePath, error) { + return preparePath{updatePath: "update-test1/", + mountPath: "update-test1/mountPath", + tarPath: "update-test1/mountPath/hello", + imagePath: "update-test1/upadte.img", + rootfsFile: "hello"}, nil + }) + defer patchPrepareEnv.Reset() + patchCreateOSImage := gomonkey.ApplyFunc(createOSImage, func(neededPath preparePath) (string, error) { + return "update-test1/upadte.img", nil + }) + defer patchCreateOSImage.Reset() + + if err := os.MkdirAll("update-test1/mountPath", os.ModePerm); err != nil { + t.Errorf("create test dir error = %v", err) + return + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := conImageHandler{} + if tt.name == "normal" { + imageDigests, err := getOCIImageDigest("crictl", "docker.io/library/hello-world:latest") + if err != nil { + t.Errorf("conImageHandler.getRootfsArchive() get oci image digests error = %v", err) + } + tt.args.req.CheckSum = imageDigests + } + got, err := c.downloadImage(tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("conImageHandler.downloadImage() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("conImageHandler.downloadImage() = %v, want %v", got, tt.want) + } + }) + } + defer func() { + if err := runCommand("crictl", "rmi", "docker.io/library/hello-world:latest"); err != nil { + t.Errorf("remove kubeos-temp container error = %v", err) + } + if err := os.RemoveAll("update-test1"); err != nil { + t.Errorf("remove update-test error = %v", err) + } + }() +} + +func Test_copyFile(t *testing.T) { + type args struct { + dstFileName string + srcFileName string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "srcFileNotExist", + args: args{ + dstFileName: "bbb.txt", + srcFileName: "aaa.txt", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := copyFile(tt.args.dstFileName, tt.args.srcFileName); (err != nil) != tt.wantErr { + t.Errorf("copyFile() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/cmd/agent/server/docker_image.go b/cmd/agent/server/docker_image.go index 23e596b3..0b6ee35b 100644 --- a/cmd/agent/server/docker_image.go +++ b/cmd/agent/server/docker_image.go @@ -61,7 +61,7 @@ func (d dockerImageHandler) getRootfsArchive(req *pb.UpdateRequest, neededPath p if err != nil { return "", err } - if err := runCommand("docker", "cp", containerId+":/"+rootfsArchive, neededPath.updatePath); err != nil { + if err := runCommand("docker", "cp", containerId+":/"+neededPath.rootfsFile, neededPath.updatePath); err != nil { return "", err } defer func() { diff --git a/cmd/agent/server/docker_image_test.go b/cmd/agent/server/docker_image_test.go index 99879393..2dbf3370 100644 --- a/cmd/agent/server/docker_image_test.go +++ b/cmd/agent/server/docker_image_test.go @@ -17,38 +17,102 @@ import ( "os" "testing" + "github.com/agiledragon/gomonkey/v2" pb "openeuler.org/KubeOS/cmd/agent/api" ) -func TestpullOSImage(t *testing.T) { +func Test_dockerImageHandler_downloadImage(t *testing.T) { type args struct { req *pb.UpdateRequest } - os.Mkdir("/persist", os.ModePerm) tests := []struct { name string + d dockerImageHandler args args want string wantErr bool }{ - {name: "pull image error", args: args{req: &pb.UpdateRequest{ - DockerImage: "test", - }}, want: "", wantErr: true}, - {name: "normal", args: args{req: &pb.UpdateRequest{ - DockerImage: "centos", - }}, want: "/persist/update.img", wantErr: false}, + { + name: "pullImageError", + d: dockerImageHandler{}, + args: args{ + req: &pb.UpdateRequest{ContainerImage: "testError"}, + }, + want: "", + wantErr: true, + }, + + { + name: "checkSumError", + d: dockerImageHandler{}, + args: args{ + req: &pb.UpdateRequest{ContainerImage: "hello-world", CheckSum: "aaaaaa"}, + }, + want: "", + wantErr: true, + }, + + { + name: "normal", + d: dockerImageHandler{}, + args: args{ + req: &pb.UpdateRequest{ContainerImage: "hello-world"}, + }, + want: "update-test/upadte.img", + wantErr: false, + }, + } + patchPrepareEnv := gomonkey.ApplyFunc(prepareEnv, func() (preparePath, error) { + return preparePath{updatePath: "update-test/", + mountPath: "update-test/mountPath", + tarPath: "update-test/mountPath/hello", + imagePath: "update-test/upadte.img", + rootfsFile: "hello"}, nil + }) + defer patchPrepareEnv.Reset() + + patchCreateOSImage := gomonkey.ApplyFunc(createOSImage, func(neededPath preparePath) (string, error) { + return "update-test/upadte.img", nil + }) + defer patchCreateOSImage.Reset() + + if err := os.MkdirAll("update-test/mountPath", os.ModePerm); err != nil { + t.Errorf("create test dir error = %v", err) + return } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := pullOSImage(tt.args.req) + if tt.name == "normal" { + _, err := runCommandWithOut("docker", "create", "--name", "kubeos-temp", "hello-world") + if err != nil { + t.Errorf("Test_dockerImageHandler_getRootfsArchive create container error = %v", err) + return + } + imageDigests, err := getOCIImageDigest("docker", "hello-world") + + if err != nil { + t.Errorf("Test_dockerImageHandler_getRootfsArchive get oci image digests error = %v", err) + } + tt.args.req.CheckSum = imageDigests + } + d := dockerImageHandler{} + got, err := d.downloadImage(tt.args.req) if (err != nil) != tt.wantErr { - t.Errorf("pullOSImage() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("dockerImageHandler.downloadImage() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { - t.Errorf("pullOSImage() = %v, want %v", got, tt.want) + t.Errorf("dockerImageHandler.downloadImage() = %v, want %v", got, tt.want) } }) } - defer os.RemoveAll("/persist") + defer func() { + if err := runCommand("docker", "rmi", "hello-world"); err != nil { + t.Errorf("remove kubeos-temp container error = %v", err) + } + if err := os.RemoveAll("update-test"); err != nil { + t.Errorf("remove update-test error = %v", err) + } + }() } diff --git a/cmd/agent/server/utils.go b/cmd/agent/server/utils.go index c8a72c3e..7134d74f 100644 --- a/cmd/agent/server/utils.go +++ b/cmd/agent/server/utils.go @@ -31,10 +31,7 @@ import ( const ( needGBSize = 3 // the max size of update files needed // KB is 1024 B - KB = 1024 -) - -var ( + KB = 1024 rootfsArchive = "os.tar" updateDir = "KubeOS-Update" mountDir = "kubeos-update" @@ -51,6 +48,7 @@ type preparePath struct { mountPath string tarPath string imagePath string + rootfsFile string } func runCommand(name string, args ...string) error { @@ -192,9 +190,10 @@ func prepareEnv() (preparePath, error) { if err := checkDiskSize(needGBSize, PersistDir); err != nil { return preparePath{}, err } + rootfsFile := rootfsArchive updatePath := splicePath(PersistDir, updateDir) mountPath := splicePath(updatePath, mountDir) - tarPath := splicePath(updatePath, rootfsArchive) + tarPath := splicePath(updatePath, rootfsFile) imagePath := splicePath(PersistDir, osImageName) if err := cleanSpace(updatePath, mountPath, imagePath); err != nil { @@ -208,6 +207,7 @@ func prepareEnv() (preparePath, error) { mountPath: mountPath, tarPath: tarPath, imagePath: imagePath, + rootfsFile: rootfsFile, } return upgradePath, nil } @@ -284,50 +284,9 @@ func checkFileExist(path string) (bool, error) { } func checkOCIImageDigestMatch(containerRuntime string, imageName string, checkSum string) error { - var cmdOutput string - var err error - switch containerRuntime { - case "crictl": - cmdOutput, err = runCommandWithOut("crictl", "inspecti", "--output", "go-template", - "--template", "{{.status.repoDigests}}", imageName) - if err != nil { - return err - } - case "docker": - cmdOutput, err = runCommandWithOut("docker", "inspect", "--format", "{{.RepoDigests}}", imageName) - if err != nil { - return err - } - case "ctr": - cmdOutput, err = runCommandWithOut("ctr", "-n", "k8s.io", "images", "ls", "name=="+imageName) - if err != nil { - return err - } - // after Fields, we get slice like [REF TYPE DIGEST SIZE PLATFORMS LABELS x x x x x x] - // the digest is the position 8 element - imageDigest := strings.Split(strings.Fields(cmdOutput)[8], ":")[1] - if imageDigest != checkSum { - logrus.Errorln("checkSumFailed ", imageDigest, " mismatch to ", checkSum) - return fmt.Errorf("checkSumFailed %s mismatch to %s", imageDigest, checkSum) - } - return nil - default: - logrus.Errorln("containerRuntime ", containerRuntime, " cannot be recognized") - return fmt.Errorf("containerRuntime %s cannot be recognized", containerRuntime) - } - // cmdOutput format is as follows: - // [imageRepository/imageName:imageTag@sha256:digests] - // parse the output and get digest - var imageDigests string - outArray := strings.Split(cmdOutput, "@") - if strings.HasPrefix(outArray[len(outArray)-1], "sha256") { - pasredArray := strings.Split(strings.TrimSuffix(outArray[len(outArray)-1], "]"), ":") - // 2 is the expected length of the array after dividing "imageName:imageTag@sha256:digests" based on ':' - rightLen := 2 - if len(pasredArray) == rightLen { - digestIndex := 1 // 1 is the index of digest data in pasredArray - imageDigests = pasredArray[digestIndex] - } + imageDigests, err := getOCIImageDigest(containerRuntime, imageName) + if err != nil { + return err } if imageDigests == "" { logrus.Errorln("error when get ", imageName, " digests") @@ -367,3 +326,48 @@ func isValidImageName(image string) error { } return nil } + +func getOCIImageDigest(containerRuntime string, imageName string) (string, error) { + var cmdOutput string + var err error + var imageDigests string + switch containerRuntime { + case "crictl": + cmdOutput, err = runCommandWithOut("crictl", "inspecti", "--output", "go-template", + "--template", "{{.status.repoDigests}}", imageName) + if err != nil { + return "", err + } + case "docker": + cmdOutput, err = runCommandWithOut("docker", "inspect", "--format", "{{.RepoDigests}}", imageName) + if err != nil { + return "", err + } + case "ctr": + cmdOutput, err = runCommandWithOut("ctr", "-n", "k8s.io", "images", "ls", "name=="+imageName) + if err != nil { + return "", err + } + // after Fields, we get slice like [REF TYPE DIGEST SIZE PLATFORMS LABELS x x x x x x] + // the digest is the position 8 element + imageDigest := strings.Split(strings.Fields(cmdOutput)[8], ":")[1] + return imageDigest, nil + default: + logrus.Errorln("containerRuntime ", containerRuntime, " cannot be recognized") + return "", fmt.Errorf("containerRuntime %s cannot be recognized", containerRuntime) + } + // cmdOutput format is as follows: + // [imageRepository/imageName:imageTag@sha256:digests] + // parse the output and get digest + outArray := strings.Split(cmdOutput, "@") + if strings.HasPrefix(outArray[len(outArray)-1], "sha256") { + pasredArray := strings.Split(strings.TrimSuffix(outArray[len(outArray)-1], "]"), ":") + // 2 is the expected length of the array after dividing "imageName:imageTag@sha256:digests" based on ':' + rightLen := 2 + if len(pasredArray) == rightLen { + digestIndex := 1 // 1 is the index of digest data in pasredArray + imageDigests = pasredArray[digestIndex] + } + } + return imageDigests, nil +} -- Gitee