480 lines
15 KiB
Diff
480 lines
15 KiB
Diff
From 790d53dc581874575aef1777122856a59bcdbf8b Mon Sep 17 00:00:00 2001
|
|
From: liyuanr <liyuanrong1@huawei.com>
|
|
Date: Thu, 3 Aug 2023 22:18:23 +0800
|
|
Subject: [PATCH 01/17] 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 <liyuanrong1@huawei.com>
|
|
---
|
|
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 f180fb5..fd61274 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 0000000..d7133c3
|
|
--- /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 23e596b..0b6ee35 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 9987939..2dbf337 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 c8a72c3..7134d74 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
|
|
+}
|
|
--
|
|
2.39.0
|
|
|