From d59b227c2a5d11062df52d35ec1eca135b957bc4 Mon Sep 17 00:00:00 2001 From: Yuhang Wei Date: Mon, 28 Aug 2023 11:14:34 +0800 Subject: [PATCH] KubeOS:sync code from source master branch Signed-off-by: Yuhang Wei --- ...tests-of-containerd-and-docker-modif.patch | 479 +++++ ...beOS-refactor-assignUpgrade-function.patch | 346 +++ ...-the-hostshell-cannot-obtain-the-lib.patch | 4 +- ...beOS-add-agent-proxy-and-operator-ut.patch | 1413 +++++++++++++ 0005-KubeOS-fix-validate-image-name-bug.patch | 155 ++ ...-a-bug-that-failed-to-parse-key-with.patch | 156 ++ ...KubeOS-add-warning-log-during-config.patch | 52 + ...-KubeOS-modify-log-level-and-content.patch | 26 + 0009-KubeOS-fix-updating-key-to-kv.patch | 151 ++ 0010-KubeOS-fix-proxy-requeue-bug.patch | 37 + ...ix-operator-bug-of-missing-deep-copy.patch | 665 ++++++ 0012-KubeOS-add-unit-test.patch | 191 ++ 0013-KubeOS-fix-clean-space-problems.patch | 126 ++ 0014-KubeOS-update-version.patch | 20 + 0015-KubeOS-add-line-breaks.patch | 36 + 0016-KubeOS-modify-code-for-clean-code.patch | 46 + ...ssue-that-osinstance-is-not-updated-.patch | 176 ++ 0018-KubeOS-update-config-log-contents.patch | 39 + 0019-KubeOS-add-unit-tests.patch | 1069 ++++++++++ 0020-KubeOS-modify-code-for-clean-code.patch | 86 + ...-delete-raw-and-docker-image-upgrade.patch | 1867 +++++++++++++++++ ...elete-scripts-except-admin-container.patch | 1671 +++++++++++++++ KubeOS.spec | 31 +- 23 files changed, 8838 insertions(+), 4 deletions(-) create mode 100644 0001-KubeOS-add-unit-tests-of-containerd-and-docker-modif.patch create mode 100644 0002-KubeOS-refactor-assignUpgrade-function.patch rename 0001-KubeOS-fix-the-hostshell-cannot-obtain-the-lib.patch => 0003-KubeOS-fix-the-hostshell-cannot-obtain-the-lib.patch (96%) create mode 100644 0004-KubeOS-add-agent-proxy-and-operator-ut.patch create mode 100644 0005-KubeOS-fix-validate-image-name-bug.patch create mode 100644 0006-KubeOS-fix-a-bug-that-failed-to-parse-key-with.patch create mode 100644 0007-KubeOS-add-warning-log-during-config.patch create mode 100644 0008-KubeOS-modify-log-level-and-content.patch create mode 100644 0009-KubeOS-fix-updating-key-to-kv.patch create mode 100644 0010-KubeOS-fix-proxy-requeue-bug.patch create mode 100644 0011-KubeOS-fix-operator-bug-of-missing-deep-copy.patch create mode 100644 0012-KubeOS-add-unit-test.patch create mode 100644 0013-KubeOS-fix-clean-space-problems.patch create mode 100644 0014-KubeOS-update-version.patch create mode 100644 0015-KubeOS-add-line-breaks.patch create mode 100644 0016-KubeOS-modify-code-for-clean-code.patch create mode 100644 0017-KubeOS-fix-the-issue-that-osinstance-is-not-updated-.patch create mode 100644 0018-KubeOS-update-config-log-contents.patch create mode 100644 0019-KubeOS-add-unit-tests.patch create mode 100644 0020-KubeOS-modify-code-for-clean-code.patch create mode 100644 0021-KubeOS-delete-raw-and-docker-image-upgrade.patch create mode 100644 0022-KubeOS-delete-scripts-except-admin-container.patch diff --git a/0001-KubeOS-add-unit-tests-of-containerd-and-docker-modif.patch b/0001-KubeOS-add-unit-tests-of-containerd-and-docker-modif.patch new file mode 100644 index 0000000..3b1f049 --- /dev/null +++ b/0001-KubeOS-add-unit-tests-of-containerd-and-docker-modif.patch @@ -0,0 +1,479 @@ +From 790d53dc581874575aef1777122856a59bcdbf8b Mon Sep 17 00:00:00 2001 +From: liyuanr +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 +--- + 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 + diff --git a/0002-KubeOS-refactor-assignUpgrade-function.patch b/0002-KubeOS-refactor-assignUpgrade-function.patch new file mode 100644 index 0000000..aa76b87 --- /dev/null +++ b/0002-KubeOS-refactor-assignUpgrade-function.patch @@ -0,0 +1,346 @@ +From 64e36143591c014609fc517bcd6ee0f3ae1087fc Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Thu, 3 Aug 2023 11:01:22 +0800 +Subject: [PATCH 02/17] KubeOS: refactor assignUpgrade function the + assignUpgrade function is refactored for maintainability replace + "osinstance-node" to variable + +Signed-off-by: Yuhang Wei +--- + cmd/operator/controllers/os_controller.go | 73 ++++++----- + .../controllers/os_controller_test.go | 121 +++++++++++++++++- + cmd/proxy/controllers/os_controller.go | 2 +- + cmd/proxy/controllers/os_controller_test.go | 9 ++ + pkg/values/values.go | 6 +- + 5 files changed, 176 insertions(+), 35 deletions(-) + +diff --git a/cmd/operator/controllers/os_controller.go b/cmd/operator/controllers/os_controller.go +index f86a0b2..620739b 100644 +--- a/cmd/operator/controllers/os_controller.go ++++ b/cmd/operator/controllers/os_controller.go +@@ -109,7 +109,7 @@ func (r *OSReconciler) SetupWithManager(mgr ctrl.Manager) error { + func (r *OSReconciler) DeleteOSInstance(e event.DeleteEvent, q workqueue.RateLimitingInterface) { + ctx := context.Background() + hostname := e.Object.GetName() +- labelSelector := labels.SelectorFromSet(labels.Set{"upgrade.openeuler.org/osinstance-node": hostname}) ++ labelSelector := labels.SelectorFromSet(labels.Set{values.LabelOSinstance: hostname}) + osInstanceList := &upgradev1.OSInstanceList{} + if err := r.List(ctx, osInstanceList, client.MatchingLabelsSelector{Selector: labelSelector}); err != nil { + log.Error(err, "unable to list osInstances") +@@ -157,12 +157,23 @@ func assignUpgrade(ctx context.Context, r common.ReadStatusWriter, os upgradev1. + return false, err + } + +- nodes, err := getNodes(ctx, r, limit+1, *requirement, *reqMaster) // one more to see if all node updated ++ nodes, err := getNodes(ctx, r, limit+1, *requirement, *reqMaster) // one more to see if all nodes updated + if err != nil { + return false, err + } + +- var count = 0 ++ // Upgrade OS for selected nodes ++ count, err := upgradeNodes(ctx, r, &os, nodes, limit) ++ if err != nil { ++ return false, err ++ } ++ ++ return count >= limit, nil ++} ++ ++func upgradeNodes(ctx context.Context, r common.ReadStatusWriter, os *upgradev1.OS, ++ nodes []corev1.Node, limit int) (int, error) { ++ var count int + for _, node := range nodes { + if count >= limit { + break +@@ -170,43 +181,45 @@ func assignUpgrade(ctx context.Context, r common.ReadStatusWriter, os upgradev1. + osVersionNode := node.Status.NodeInfo.OSImage + if os.Spec.OSVersion != osVersionNode { + var osInstance upgradev1.OSInstance +- if err = r.Get(ctx, types.NamespacedName{Namespace: nameSpace, Name: node.Name}, &osInstance); err != nil { ++ if err := r.Get(ctx, types.NamespacedName{Namespace: os.GetObjectMeta().GetNamespace(), Name: node.Name}, &osInstance); err != nil { + if err = client.IgnoreNotFound(err); err != nil { + log.Error(err, "failed to get osInstance "+node.Name) +- return false, err ++ return count, err + } + continue + } ++ updateNodeAndOSins(ctx, r, os, &node, &osInstance) + count++ +- node.Labels[values.LabelUpgrading] = "" +- expUpVersion := os.Spec.UpgradeConfigs.Version +- osiUpVersion := osInstance.Spec.UpgradeConfigs.Version +- if osiUpVersion != expUpVersion { +- osInstance.Spec.UpgradeConfigs = os.Spec.UpgradeConfigs +- } +- expSysVersion := os.Spec.SysConfigs.Version +- osiSysVersion := osInstance.Spec.SysConfigs.Version +- if osiSysVersion != expSysVersion { +- osInstance.Spec.SysConfigs = os.Spec.SysConfigs +- for i, config := range osInstance.Spec.SysConfigs.Configs { +- if config.Model == "grub.cmdline.current" { +- osInstance.Spec.SysConfigs.Configs[i].Model = "grub.cmdline.next" +- } +- if config.Model == "grub.cmdline.next" { +- osInstance.Spec.SysConfigs.Configs[i].Model = "grub.cmdline.current" +- } +- } +- } +- osInstance.Spec.NodeStatus = values.NodeStatusUpgrade.String() +- if err = r.Update(ctx, &osInstance); err != nil { +- log.Error(err, "unable to update", "osInstance", osInstance.Name) ++ } ++ } ++ return count, nil ++} ++ ++func updateNodeAndOSins(ctx context.Context, r common.ReadStatusWriter, os *upgradev1.OS, ++ node *corev1.Node, osInstance *upgradev1.OSInstance) { ++ if osInstance.Spec.UpgradeConfigs.Version != os.Spec.UpgradeConfigs.Version { ++ osInstance.Spec.UpgradeConfigs = os.Spec.UpgradeConfigs ++ } ++ if osInstance.Spec.SysConfigs.Version != os.Spec.SysConfigs.Version { ++ osInstance.Spec.SysConfigs = os.Spec.SysConfigs ++ // exchange "grub.cmdline.current" and "grub.cmdline.next" ++ for i, config := range osInstance.Spec.SysConfigs.Configs { ++ if config.Model == "grub.cmdline.current" { ++ osInstance.Spec.SysConfigs.Configs[i].Model = "grub.cmdline.next" + } +- if err = r.Update(ctx, &node); err != nil { +- log.Error(err, "unable to label", "node", node.Name) ++ if config.Model == "grub.cmdline.next" { ++ osInstance.Spec.SysConfigs.Configs[i].Model = "grub.cmdline.current" + } + } + } +- return count >= limit, nil ++ osInstance.Spec.NodeStatus = values.NodeStatusUpgrade.String() ++ if err := r.Update(ctx, osInstance); err != nil { ++ log.Error(err, "unable to update", "osInstance", osInstance.Name) ++ } ++ node.Labels[values.LabelUpgrading] = "" ++ if err := r.Update(ctx, node); err != nil { ++ log.Error(err, "unable to label", "node", node.Name) ++ } + } + + func assignConfig(ctx context.Context, r common.ReadStatusWriter, sysConfigs upgradev1.SysConfigs, +diff --git a/cmd/operator/controllers/os_controller_test.go b/cmd/operator/controllers/os_controller_test.go +index 30a773c..a391005 100644 +--- a/cmd/operator/controllers/os_controller_test.go ++++ b/cmd/operator/controllers/os_controller_test.go +@@ -134,6 +134,9 @@ var _ = Describe("OsController", func() { + ObjectMeta: metav1.ObjectMeta{ + Name: node1Name, + Namespace: testNamespace, ++ Labels: map[string]string{ ++ values.LabelOSinstance: node1Name, ++ }, + }, + Spec: upgradev1.OSInstanceSpec{ + SysConfigs: upgradev1.SysConfigs{ +@@ -189,7 +192,7 @@ var _ = Describe("OsController", func() { + }, timeout, interval).Should(BeTrue()) + Expect(createdOS.Spec.OSVersion).Should(Equal("KubeOS v1")) + +- time.Sleep(2 * time.Second) // sleep a while to make sure Reconcile finished ++ time.Sleep(1 * time.Second) // sleep a while to make sure Reconcile finished + osInsCRLookupKey = types.NamespacedName{Name: node1Name, Namespace: testNamespace} + createdOSIns = &upgradev1.OSInstance{} + Eventually(func() bool { +@@ -301,7 +304,7 @@ var _ = Describe("OsController", func() { + }, timeout, interval).Should(BeTrue()) + Expect(createdOS.Spec.OSVersion).Should(Equal("KubeOS v1")) + +- time.Sleep(2 * time.Second) // sleep a while to make sure Reconcile finished ++ time.Sleep(1 * time.Second) // sleep a while to make sure Reconcile finished + configedOSIns := &upgradev1.OSInstance{} + Eventually(func() bool { + err := k8sClient.Get(ctx, osInsCRLookupKey, configedOSIns) +@@ -388,4 +391,118 @@ var _ = Describe("OsController", func() { + }) + }) + ++ Context("When we want to upgrade and do sysconfigs which contain grub.cmd.current and .next", func() { ++ It("Should exchange .current and .next", func() { ++ ctx := context.Background() ++ ++ // create Node ++ node1Name = "test-node-" + uuid.New().String() ++ node1 := &v1.Node{ ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: node1Name, ++ Namespace: testNamespace, ++ Labels: map[string]string{ ++ "beta.kubernetes.io/os": "linux", ++ }, ++ }, ++ TypeMeta: metav1.TypeMeta{ ++ APIVersion: "v1", ++ Kind: "Node", ++ }, ++ Status: v1.NodeStatus{ ++ NodeInfo: v1.NodeSystemInfo{ ++ OSImage: "KubeOS v1", ++ }, ++ }, ++ } ++ err := k8sClient.Create(ctx, node1) ++ Expect(err).ToNot(HaveOccurred()) ++ existingNode := &v1.Node{} ++ Eventually(func() bool { ++ err := k8sClient.Get(context.Background(), ++ types.NamespacedName{Name: node1Name, Namespace: testNamespace}, existingNode) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ ++ // create OSInstance ++ OSIns := &upgradev1.OSInstance{ ++ TypeMeta: metav1.TypeMeta{ ++ Kind: "OSInstance", ++ APIVersion: "upgrade.openeuler.org/v1alpha1", ++ }, ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: node1Name, ++ Namespace: testNamespace, ++ Labels: map[string]string{ ++ values.LabelOSinstance: node1Name, ++ }, ++ }, ++ Spec: upgradev1.OSInstanceSpec{ ++ SysConfigs: upgradev1.SysConfigs{ ++ Version: "v1", ++ Configs: []upgradev1.SysConfig{}, ++ }, ++ UpgradeConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}}, ++ }, ++ } ++ Expect(k8sClient.Create(ctx, OSIns)).Should(Succeed()) ++ ++ // Check that the corresponding OSIns CR has been created ++ osInsCRLookupKey := types.NamespacedName{Name: node1Name, Namespace: testNamespace} ++ createdOSIns := &upgradev1.OSInstance{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osInsCRLookupKey, createdOSIns) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ Expect(createdOSIns.ObjectMeta.Name).Should(Equal(node1Name)) ++ ++ // create OS CR ++ OS := &upgradev1.OS{ ++ TypeMeta: metav1.TypeMeta{ ++ APIVersion: "upgrade.openeuler.org/v1alpha1", ++ Kind: "OS", ++ }, ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: OSName, ++ Namespace: testNamespace, ++ }, ++ Spec: upgradev1.OSSpec{ ++ OpsType: "upgrade", ++ MaxUnavailable: 3, ++ OSVersion: "KubeOS v2", ++ FlagSafe: true, ++ MTLS: false, ++ EvictPodForce: true, ++ SysConfigs: upgradev1.SysConfigs{ ++ Version: "v2", ++ Configs: []upgradev1.SysConfig{ ++ {Model: "grub.cmdline.current", Contents: []upgradev1.Content{{Key: "a", Value: "1"}}}, ++ {Model: "grub.cmdline.next", Contents: []upgradev1.Content{{Key: "b", Value: "2"}}}, ++ }, ++ }, ++ UpgradeConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}}, ++ }, ++ } ++ Expect(k8sClient.Create(ctx, OS)).Should(Succeed()) ++ ++ // Check that the corresponding OS CR has been created ++ osCRLookupKey := types.NamespacedName{Name: OSName, Namespace: testNamespace} ++ createdOS := &upgradev1.OS{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osCRLookupKey, createdOS) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ Expect(createdOS.Spec.OSVersion).Should(Equal("KubeOS v2")) ++ ++ time.Sleep(1 * time.Second) // sleep a while to make sure Reconcile finished ++ osInsCRLookupKey = types.NamespacedName{Name: node1Name, Namespace: testNamespace} ++ createdOSIns = &upgradev1.OSInstance{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osInsCRLookupKey, createdOSIns) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ Expect(createdOSIns.Spec.SysConfigs.Configs[0]).Should(Equal(upgradev1.SysConfig{Model: "grub.cmdline.next", Contents: []upgradev1.Content{{Key: "a", Value: "1"}}})) ++ Expect(createdOSIns.Spec.SysConfigs.Configs[1]).Should(Equal(upgradev1.SysConfig{Model: "grub.cmdline.current", Contents: []upgradev1.Content{{Key: "b", Value: "2"}}})) ++ }) ++ }) + }) +diff --git a/cmd/proxy/controllers/os_controller.go b/cmd/proxy/controllers/os_controller.go +index a17afac..b0b17e7 100644 +--- a/cmd/proxy/controllers/os_controller.go ++++ b/cmd/proxy/controllers/os_controller.go +@@ -261,7 +261,7 @@ func checkOsiExist(ctx context.Context, r common.ReadStatusWriter, nameSpace str + Namespace: nameSpace, + Name: nodeName, + Labels: map[string]string{ +- "upgrade.openeuler.org/osinstance-node": nodeName, ++ values.LabelOSinstance: nodeName, + }, + }, + } +diff --git a/cmd/proxy/controllers/os_controller_test.go b/cmd/proxy/controllers/os_controller_test.go +index ff12f64..e6cd5b7 100644 +--- a/cmd/proxy/controllers/os_controller_test.go ++++ b/cmd/proxy/controllers/os_controller_test.go +@@ -110,6 +110,9 @@ var _ = Describe("OsController", func() { + ObjectMeta: metav1.ObjectMeta{ + Name: node1Name, + Namespace: testNamespace, ++ Labels: map[string]string{ ++ values.LabelOSinstance: node1Name, ++ }, + }, + Spec: upgradev1.OSInstanceSpec{ + NodeStatus: values.NodeStatusConfig.String(), +@@ -245,6 +248,9 @@ var _ = Describe("OsController", func() { + ObjectMeta: metav1.ObjectMeta{ + Name: node1Name, + Namespace: testNamespace, ++ Labels: map[string]string{ ++ values.LabelOSinstance: node1Name, ++ }, + }, + Spec: upgradev1.OSInstanceSpec{ + NodeStatus: values.NodeStatusUpgrade.String(), +@@ -426,6 +432,9 @@ var _ = Describe("OsController", func() { + return err == nil + }, timeout, interval).Should(BeTrue()) + Expect(createdOSIns.Spec.NodeStatus).Should(Equal(values.NodeStatusIdle.String())) ++ hostname, ok := createdOSIns.ObjectMeta.Labels[values.LabelOSinstance] ++ Expect(ok).Should(BeTrue()) ++ Expect(hostname).Should(Equal(node1Name)) + }) + }) + +diff --git a/pkg/values/values.go b/pkg/values/values.go +index 30261bd..f488ae5 100644 +--- a/pkg/values/values.go ++++ b/pkg/values/values.go +@@ -23,8 +23,10 @@ const ( + // LabelUpgrading is the key of the upgrading label for nodes + LabelUpgrading = "upgrade.openeuler.org/upgrading" + // LabelMaster is the key of the master-node label for nodes +- LabelMaster = "node-role.kubernetes.io/control-plane" +- defaultPeriod = 15 * time.Second ++ LabelMaster = "node-role.kubernetes.io/control-plane" ++ // LabelOSinstance is used to select the osinstance with the nodeName by label ++ LabelOSinstance = "upgrade.openeuler.org/osinstance-node" ++ defaultPeriod = 15 * time.Second + // OsiStatusName is param name of nodeStatus in osInstance + OsiStatusName = "nodestatus" + // UpgradeConfigName is param name of UpgradeConfig +-- +2.39.0 + diff --git a/0001-KubeOS-fix-the-hostshell-cannot-obtain-the-lib.patch b/0003-KubeOS-fix-the-hostshell-cannot-obtain-the-lib.patch similarity index 96% rename from 0001-KubeOS-fix-the-hostshell-cannot-obtain-the-lib.patch rename to 0003-KubeOS-fix-the-hostshell-cannot-obtain-the-lib.patch index f0cfb25..bf46e8b 100644 --- a/0001-KubeOS-fix-the-hostshell-cannot-obtain-the-lib.patch +++ b/0003-KubeOS-fix-the-hostshell-cannot-obtain-the-lib.patch @@ -1,7 +1,7 @@ From 470e190db2de92b65e7fa720864823e7245c51e9 Mon Sep 17 00:00:00 2001 From: liyuanr Date: Mon, 7 Aug 2023 19:09:18 +0800 -Subject: [PATCH] KubeOS: fix the hostshell cannot obtain the lib +Subject: [PATCH 03/17] KubeOS: fix the hostshell cannot obtain the lib Fix the hostshell cannot obtain the lib @@ -71,5 +71,5 @@ index f6a7293..5fa0838 100644 + return pathEnv +} -- -2.33.0.windows.2 +2.39.0 diff --git a/0004-KubeOS-add-agent-proxy-and-operator-ut.patch b/0004-KubeOS-add-agent-proxy-and-operator-ut.patch new file mode 100644 index 0000000..87d9cdf --- /dev/null +++ b/0004-KubeOS-add-agent-proxy-and-operator-ut.patch @@ -0,0 +1,1413 @@ +From 923529f49a9f4a8d9678e087735459ea875ac5d6 Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Fri, 4 Aug 2023 10:17:24 +0800 +Subject: [PATCH 04/17] KubeOS: add agent, proxy and operator ut add + disk_image, server, config, proxy and operator unit testing fix the bug that + agent allows configuring kv with nil key + +Signed-off-by: Yuhang Wei +--- + Makefile | 4 +- + cmd/agent/server/config.go | 4 + + cmd/agent/server/config_test.go | 130 +++++++-- + cmd/agent/server/disk_image.go | 8 +- + cmd/agent/server/disk_image_test.go | 262 ++++++++++++------ + cmd/agent/server/server.go | 4 + + cmd/agent/server/server_test.go | 86 +++++- + cmd/agent/server/utils_test.go | 146 +++++++++- + .../controllers/os_controller_test.go | 67 +++++ + cmd/operator/controllers/suite_test.go | 2 +- + cmd/proxy/controllers/os_controller.go | 6 + + cmd/proxy/controllers/os_controller_test.go | 153 +++++++++- + cmd/proxy/controllers/suite_test.go | 2 +- + 13 files changed, 748 insertions(+), 126 deletions(-) + +diff --git a/Makefile b/Makefile +index fbabda6..b5b6161 100644 +--- a/Makefile ++++ b/Makefile +@@ -133,14 +133,14 @@ kustomize: + $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) + + ARCH := $(shell uname -m) +-TEST_CMD := go test ./... -race -count=1 -timeout=300s -cover -gcflags=all=-l ++TEST_CMD := go test ./... -race -count=1 -timeout=300s -cover -gcflags=all=-l -p 1 + + ifeq ($(ARCH), aarch64) + TEST_CMD := ETCD_UNSUPPORTED_ARCH=arm64 $(TEST_CMD) + endif + + .PHONY: test +-test: manifests fmt vet envtest ## Run tests. ++test: fmt vet envtest ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" $(TEST_CMD) + + .PHONY: envtest +diff --git a/cmd/agent/server/config.go b/cmd/agent/server/config.go +index e7110f8..653f913 100644 +--- a/cmd/agent/server/config.go ++++ b/cmd/agent/server/config.go +@@ -374,6 +374,10 @@ func handleUpdateKey(config []string, configInfo *agent.KeyInfo, isFound bool) s + func handleAddKey(m map[string]*agent.KeyInfo, isOnlyKeyValid bool) []string { + var configs []string + for key, keyInfo := range m { ++ if key == "" { ++ logrus.Warnln("Failed to add nil key") ++ continue ++ } + if keyInfo.Operation == "delete" { + logrus.Warnf("Failed to delete inexistent key %s", key) + continue +diff --git a/cmd/agent/server/config_test.go b/cmd/agent/server/config_test.go +index 903af87..2deb15f 100644 +--- a/cmd/agent/server/config_test.go ++++ b/cmd/agent/server/config_test.go +@@ -67,6 +67,15 @@ func TestKernelSysctl_SetConfig(t *testing.T) { + }, + }}, + }, ++ { ++ name: "nil value", ++ k: KernelSysctl{}, ++ args: args{config: &agent.SysConfig{ ++ Contents: map[string]*agent.KeyInfo{ ++ "d": {Value: ""}, ++ }, ++ }}, ++ }, + } + tmpDir := t.TempDir() + patchGetProcPath := gomonkey.ApplyFuncReturn(getDefaultProcPath, tmpDir+"/") +@@ -84,6 +93,7 @@ func TestKernelSysctl_SetConfig(t *testing.T) { + func TestKerSysctlPersist_SetConfig(t *testing.T) { + tmpDir := t.TempDir() + persistPath := tmpDir + "/test-persist.conf" ++ comment := `# This file is managed by KubeOS for unit testing.` + type args struct { + config *agent.SysConfig + } +@@ -94,6 +104,7 @@ func TestKerSysctlPersist_SetConfig(t *testing.T) { + want []string + wantErr bool + }{ ++ {name: "create file", args: args{config: &agent.SysConfig{ConfigPath: persistPath}}, want: []string{comment}, wantErr: false}, + { + name: "add configs", + args: args{ +@@ -103,12 +114,15 @@ func TestKerSysctlPersist_SetConfig(t *testing.T) { + "a": {Value: "1"}, + "b": {Value: "2"}, + "c": {Value: ""}, ++ "": {Value: "4"}, ++ "e": {Value: "5"}, + }, + }, + }, + want: []string{ + "a=1", + "b=2", ++ "e=5", + }, + wantErr: false, + }, +@@ -126,6 +140,7 @@ func TestKerSysctlPersist_SetConfig(t *testing.T) { + want: []string{ + "a=2", + "b=2", ++ "e=5", + }, + wantErr: false, + }, +@@ -137,11 +152,16 @@ func TestKerSysctlPersist_SetConfig(t *testing.T) { + Contents: map[string]*agent.KeyInfo{ + "a": {Value: "1", Operation: "delete"}, + "b": {Value: "2", Operation: "delete"}, ++ "c": {Value: "3", Operation: "delete"}, ++ "e": {Value: "5", Operation: "remove"}, ++ "f": {Value: "6", Operation: "remove"}, + }, + }, + }, + want: []string{ + "a=2", ++ "e=5", ++ "f=6", + }, + wantErr: false, + }, +@@ -152,14 +172,21 @@ func TestKerSysctlPersist_SetConfig(t *testing.T) { + if err := k.SetConfig(tt.args.config); (err != nil) != tt.wantErr { + t.Errorf("KerSysctlPersist.SetConfig() error = %v, wantErr %v", err, tt.wantErr) + } ++ if tt.name == "create file" { ++ if err := os.WriteFile(persistPath, []byte(comment), 0644); err != nil { ++ t.Fatalf("failed to write file %s", persistPath) ++ } ++ } + data, err := os.ReadFile(persistPath) + if err != nil { + t.Errorf("failed to read file %s", persistPath) + } + lines := strings.Split(string(data), "\n") +- // remove the last empty line +- lines = lines[:len(lines)-1] +- sort.Strings(lines) ++ if tt.name != "create file" { ++ // remove the comment and the last empty line ++ lines = lines[1 : len(lines)-1] ++ sort.Strings(lines) ++ } + if !reflect.DeepEqual(lines, tt.want) { + t.Errorf("KerSysctlPersist file contents not equal, expect: %v, get: %v", tt.want, lines) + } +@@ -210,17 +237,36 @@ menuentry 'B' --class KubeOS --class gnu-linux --class gnu --class os --unrestri + args: args{ + config: &agent.SysConfig{ + Contents: map[string]*agent.KeyInfo{ +- "panic": {Value: "5"}, // update existent kv +- "quiet": {Value: "", Operation: "delete"}, // delete existent key +- "oops": {Value: ""}, // update existent kv with null value +- "selinux": {Value: "1", Operation: "delete"}, // failed to delete inconsistent kv +- "acpi": {Value: "off", Operation: "delete"}, // failed to delete inexistent kv +- "debug": {}, // add key +- "pci": {Value: "nomis"}, // add kv ++ "debug": {}, // add key ++ "pci": {Value: "nomis"}, // add kv ++ "quiet": {Value: "", Operation: "delete"}, // delete existent key ++ "panic": {Value: "5"}, // update existent kv ++ "nomodeset": {Operation: "update"}, // invalid operation, default to update existent key ++ "softlockup_panic": {Value: "0", Operation: "update"}, // invalid operation, default to update existent kv ++ "oops": {Value: ""}, // warning, skip, update existent kv with null value ++ "": {Value: "test"}, // warning, skip, failed to add kv with empty key ++ "selinux": {Value: "1", Operation: "delete"}, // failed to delete inconsistent kv ++ "acpi": {Value: "off", Operation: "delete"}, // failed to delete inexistent kv + }, + }, + }, +- pattern: `(?m)^\s+linux\s+\/boot\/vmlinuz\s+root=UUID=[0-1]\s+ro\s+rootfstype=ext4\s+nomodeset\s+oops=panic\s+softlockup_panic=1\s+nmi_watchdog=1\s+rd\.shell=0\s+selinux=0\s+crashkernel=256M\s+panic=5\s+(debug\spci=nomis|pci=nomis\sdebug)$`, ++ pattern: `(?m)^\s+linux\s+\/boot\/vmlinuz\s+root=UUID=[0-1]\s+ro\s+rootfstype=ext4\s+nomodeset\s+oops=panic\s+softlockup_panic=0\s+nmi_watchdog=1\s+rd\.shell=0\s+selinux=0\s+crashkernel=256M\s+panic=5\s+(debug\spci=nomis|pci=nomis\sdebug)$`, ++ wantErr: false, ++ }, ++ { ++ name: "delete and invalid operation", ++ g: GrubCmdline{isCurPartition: true}, ++ args: args{ ++ config: &agent.SysConfig{ ++ Contents: map[string]*agent.KeyInfo{ ++ "debug": {Operation: "delete"}, // delete key ++ "pci": {Value: "nomis", Operation: "delete"}, // delete kv ++ "debugpat": {Value: "", Operation: "add"}, // passed key, operation is invalid, default to add key ++ "audit": {Value: "1", Operation: "add"}, // passed kv, key is inexistent, operation is invalid, default to add kv ++ }, ++ }, ++ }, ++ pattern: `(?m)^\s+linux\s+\/boot\/vmlinuz\s+root=UUID=[0-1]\s+ro\s+rootfstype=ext4\s+nomodeset\s+oops=panic\s+softlockup_panic=0\s+nmi_watchdog=1\s+rd\.shell=0\s+selinux=0\s+crashkernel=256M\s+panic=5\s+(debugpat\saudit=1|audit=1\sdebugpat)$`, + wantErr: false, + }, + { +@@ -229,23 +275,27 @@ menuentry 'B' --class KubeOS --class gnu-linux --class gnu --class os --unrestri + args: args{ + config: &agent.SysConfig{ + Contents: map[string]*agent.KeyInfo{ +- "panic": {Value: "4"}, +- "quiet": {Value: "", Operation: "delete"}, +- "oops": {Value: ""}, // update existent kv with null value +- "selinux": {Value: "1", Operation: "delete"}, +- "acpi": {Value: "off", Operation: "delete"}, +- "debug": {}, +- "pci": {Value: "nomis"}, ++ "debug": {}, ++ "pci": {Value: "nomis"}, ++ "quiet": {Value: "", Operation: "delete"}, ++ "panic": {Value: "4"}, ++ "nomodeset": {Operation: "update"}, // invalid operation, default to update existent key ++ "softlockup_panic": {Value: "0", Operation: "update"}, // invalid operation, default to update existent kv ++ "oops": {Value: ""}, // update existent kv with null value ++ "": {Value: "test"}, // warning, skip, failed to add kv with empty key ++ "selinux": {Value: "1", Operation: "delete"}, ++ "acpi": {Value: "off", Operation: "delete"}, + }, + }, + }, +- pattern: `(?m)^\s+linux\s+\/boot\/vmlinuz\s+root=UUID=[0-1]\s+ro\s+rootfstype=ext4\s+nomodeset\s+oops=panic\s+softlockup_panic=1\s+nmi_watchdog=1\s+rd\.shell=0\s+selinux=0\s+crashkernel=256M\s+panic=4\s+(debug\spci=nomis|pci=nomis\sdebug)$`, ++ pattern: `(?m)^\s+linux\s+\/boot\/vmlinuz\s+root=UUID=[0-1]\s+ro\s+rootfstype=ext4\s+nomodeset\s+oops=panic\s+softlockup_panic=0\s+nmi_watchdog=1\s+rd\.shell=0\s+selinux=0\s+crashkernel=256M\s+panic=4\s+(debug\spci=nomis|pci=nomis\sdebug)$`, + wantErr: false, + }, + } + patchGetGrubPath := gomonkey.ApplyFuncReturn(getGrubCfgPath, grubCfgPath) + defer patchGetGrubPath.Reset() + patchGetConfigPartition := gomonkey.ApplyFuncSeq(getConfigPartition, []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{false, nil}}, + {Values: gomonkey.Params{false, nil}}, + {Values: gomonkey.Params{true, nil}}, + }) +@@ -375,3 +425,45 @@ func Test_getGrubCfgPath(t *testing.T) { + }) + } + } ++ ++func Test_getConfigPartition(t *testing.T) { ++ type args struct { ++ isCurPartition bool ++ } ++ tests := []struct { ++ name string ++ args args ++ want bool ++ wantErr bool ++ }{ ++ { ++ name: "get current partition", ++ args: args{isCurPartition: true}, ++ want: false, ++ wantErr: false, ++ }, ++ { ++ name: "get next partition", ++ args: args{isCurPartition: false}, ++ want: true, ++ wantErr: false, ++ }, ++ } ++ patchRootfsDisks := gomonkey.ApplyFuncReturn(getRootfsDisks, "/dev/sda2", "/dev/sda3", nil) ++ defer patchRootfsDisks.Reset() ++ // assume now is partition A, want to swiching to partition B ++ patchGetNextPartition := gomonkey.ApplyFuncReturn(getNextPart, "/dev/sda3", "B", nil) ++ defer patchGetNextPartition.Reset() ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ got, err := getConfigPartition(tt.args.isCurPartition) ++ if (err != nil) != tt.wantErr { ++ t.Errorf("getConfigPartition() error = %v, wantErr %v", err, tt.wantErr) ++ return ++ } ++ if got != tt.want { ++ t.Errorf("getConfigPartition() = %v, want %v", got, tt.want) ++ } ++ }) ++ } ++} +diff --git a/cmd/agent/server/disk_image.go b/cmd/agent/server/disk_image.go +index c89003b..8bd6bf6 100644 +--- a/cmd/agent/server/disk_image.go ++++ b/cmd/agent/server/disk_image.go +@@ -153,7 +153,7 @@ func loadCaCerts(caCert string) (*http.Client, error) { + if err != nil { + return &http.Client{}, err + } +- ca, err := ioutil.ReadFile(certPath + caCert) ++ ca, err := ioutil.ReadFile(getCertPath() + caCert) + if err != nil { + return &http.Client{}, fmt.Errorf("read the ca certificate error %s", err) + } +@@ -173,7 +173,7 @@ func loadClientCerts(caCert, clientCert, clientKey string) (*http.Client, error) + if err != nil { + return &http.Client{}, err + } +- ca, err := ioutil.ReadFile(certPath + caCert) ++ ca, err := ioutil.ReadFile(getCertPath() + caCert) + if err != nil { + return &http.Client{}, err + } +@@ -186,7 +186,7 @@ func loadClientCerts(caCert, clientCert, clientKey string) (*http.Client, error) + if err != nil { + return &http.Client{}, err + } +- cliCrt, err := tls.LoadX509KeyPair(certPath+clientCert, certPath+clientKey) ++ cliCrt, err := tls.LoadX509KeyPair(getCertPath()+clientCert, getCertPath()+clientKey) + if err != nil { + return &http.Client{}, err + } +@@ -206,7 +206,7 @@ func certExist(certFile string) error { + if certFile == "" { + return fmt.Errorf("please provide the certificate") + } +- _, err := os.Stat(certPath + certFile) ++ _, err := os.Stat(getCertPath() + certFile) + if err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("certificate is not exist %s ", err) +diff --git a/cmd/agent/server/disk_image_test.go b/cmd/agent/server/disk_image_test.go +index 3c82113..71c5de7 100644 +--- a/cmd/agent/server/disk_image_test.go ++++ b/cmd/agent/server/disk_image_test.go +@@ -14,19 +14,35 @@ + package server + + import ( +- "crypto/tls" ++ "crypto/rand" ++ "crypto/rsa" ++ "crypto/sha256" + "crypto/x509" ++ "crypto/x509/pkix" ++ "encoding/hex" ++ "encoding/pem" ++ "io" ++ "math/big" + "net/http" + "os" + "reflect" ++ "strings" ++ "syscall" + "testing" ++ "time" + + "github.com/agiledragon/gomonkey/v2" +- + pb "openeuler.org/KubeOS/cmd/agent/api" + ) + +-func Testdownload(t *testing.T) { ++func Test_download(t *testing.T) { ++ tmpDir := t.TempDir() ++ tmpFileForDownload := tmpDir + "/tmpFileForDownload" ++ tmpFile, err := os.Create(tmpFileForDownload) ++ if err != nil { ++ t.Errorf("open file error: %v", err) ++ } ++ defer tmpFile.Close() + type args struct { + req *pb.UpdateRequest + } +@@ -36,14 +52,35 @@ func Testdownload(t *testing.T) { + want string + wantErr bool + }{ +- {name: "errornil", args: args{&pb.UpdateRequest{Certs: &pb.CertsInfo{}}}, want: "", wantErr: true}, +- {name: "normal", args: args{&pb.UpdateRequest{ImageUrl: "http://www.openeuler.org/zh/", FlagSafe: true, Certs: &pb.CertsInfo{}}}, want: "/persist/update.img", wantErr: false}, +- {name: "errornodir", args: args{&pb.UpdateRequest{ImageUrl: "http://www.openeuler.org/zh/", FlagSafe: true, Certs: &pb.CertsInfo{}}}, want: "", wantErr: true}, ++ // {name: "errornil", args: args{&pb.UpdateRequest{Certs: &pb.CertsInfo{}}}, want: "", wantErr: true}, ++ // {name: "normal", args: args{&pb.UpdateRequest{ImageUrl: "http://www.openeuler.org/zh/", FlagSafe: true, Certs: &pb.CertsInfo{}}}, want: "/persist/update.img", wantErr: false}, ++ // {name: "errornodir", args: args{&pb.UpdateRequest{ImageUrl: "http://www.openeuler.org/zh/", FlagSafe: true, Certs: &pb.CertsInfo{}}}, want: "", wantErr: true}, ++ { ++ name: "normal", ++ args: args{ ++ req: &pb.UpdateRequest{ ++ ImageUrl: "http://www.openeuler.org/zh/", ++ }, ++ }, ++ want: tmpFileForDownload, ++ wantErr: false, ++ }, + } ++ patchStatfs := gomonkey.ApplyFunc(syscall.Statfs, func(path string, stat *syscall.Statfs_t) error { ++ stat.Bfree = 3000 ++ stat.Bsize = 4096 ++ return nil ++ }) ++ defer patchStatfs.Reset() ++ patchGetImageUrl := gomonkey.ApplyFuncReturn(getImageURL, &http.Response{ ++ StatusCode: http.StatusOK, ++ ContentLength: 5, ++ Body: io.NopCloser(strings.NewReader("hello")), ++ }, nil) ++ defer patchGetImageUrl.Reset() ++ patchOSCreate := gomonkey.ApplyFuncReturn(os.Create, tmpFile, nil) ++ defer patchOSCreate.Reset() + for _, tt := range tests { +- if tt.name == "normal" { +- os.Mkdir("/persist", os.ModePerm) +- } + t.Run(tt.name, func(t *testing.T) { + got, err := download(tt.args.req) + if (err != nil) != tt.wantErr { +@@ -54,47 +91,42 @@ func Testdownload(t *testing.T) { + t.Errorf("download() got = %v, want %v", got, tt.want) + } + }) +- if tt.name == "normal" { +- os.RemoveAll("/persist") +- } + } + } + +-func TestcheckSumMatch(t *testing.T) { ++func Test_checkSumMatch(t *testing.T) { ++ tmpDir := t.TempDir() ++ tmpFileForCheckSum := tmpDir + "/tmpFileForCheckSum" ++ err := os.WriteFile(tmpFileForCheckSum, []byte("hello"), 0644) ++ if err != nil { ++ t.Errorf("open file error: %v", err) ++ } + type args struct { + filePath string + checkSum string + } +- ff, _ := os.Create("aa.txt") +- ff.Chmod(os.ModePerm) + tests := []struct { + name string + args args + wantErr bool + }{ +- {name: "error", args: args{filePath: "aaa", checkSum: "aaa"}, wantErr: true}, +- {name: "errordir", args: args{filePath: "/aaa", checkSum: "/aaa"}, wantErr: true}, +- {name: "errortxt", args: args{filePath: "aa.txt", checkSum: "aa.txt"}, wantErr: true}, ++ { ++ name: "normal", ++ args: args{filePath: tmpFileForCheckSum, checkSum: calculateChecksum("hello")}, ++ wantErr: false, ++ }, ++ {name: "error", args: args{filePath: tmpFileForCheckSum, checkSum: "aaa"}, wantErr: true}, + } + for _, tt := range tests { +- if tt.name == "errordir" { +- os.Mkdir("/aaa", os.ModePerm) +- } + t.Run(tt.name, func(t *testing.T) { + if err := checkSumMatch(tt.args.filePath, tt.args.checkSum); (err != nil) != tt.wantErr { + t.Errorf("checkSumMatch() error = %v, wantErr %v", err, tt.wantErr) + } + }) +- if tt.name == "errordir" { +- os.RemoveAll("/aaa") +- } + } +- defer os.Remove("aa.txt") +- defer ff.Close() +- + } + +-func TestgetImageURL(t *testing.T) { ++func Test_getImageURL(t *testing.T) { + type args struct { + req *pb.UpdateRequest + } +@@ -105,23 +137,29 @@ func TestgetImageURL(t *testing.T) { + wantErr bool + }{ + {name: "httpNotSafe", args: args{req: &pb.UpdateRequest{ +- ImageUrl: "http://www.openeuler.org/zh/", ++ ImageUrl: "http://www.openeuler.abc/zh/", + FlagSafe: false, + MTLS: false, + Certs: &pb.CertsInfo{}, + }}, want: &http.Response{}, wantErr: true}, +- {name: "mTLSError", args: args{req: &pb.UpdateRequest{ +- ImageUrl: "http://www.openeuler.org/zh/", ++ {name: "httpSuccess", args: args{req: &pb.UpdateRequest{ ++ ImageUrl: "http://www.openeuler.abc/zh/", ++ FlagSafe: true, ++ MTLS: false, ++ Certs: &pb.CertsInfo{}, ++ }}, want: &http.Response{StatusCode: http.StatusOK}, wantErr: false}, ++ {name: "mTLSGetSuccess", args: args{req: &pb.UpdateRequest{ ++ ImageUrl: "https://www.openeuler.abc/zh/", + FlagSafe: true, + MTLS: true, + Certs: &pb.CertsInfo{}, +- }}, want: &http.Response{}, wantErr: true}, +- {name: "httpsError", args: args{req: &pb.UpdateRequest{ +- ImageUrl: "https://www.openeuler.org/zh/", ++ }}, want: &http.Response{StatusCode: http.StatusOK}, wantErr: false}, ++ {name: "httpsGetSuccess", args: args{req: &pb.UpdateRequest{ ++ ImageUrl: "https://www.openeuler.abc/zh/", + FlagSafe: true, + MTLS: false, + Certs: &pb.CertsInfo{}, +- }}, want: &http.Response{}, wantErr: true}, ++ }}, want: &http.Response{StatusCode: http.StatusOK}, wantErr: false}, + } + patchLoadClientCerts := gomonkey.ApplyFunc(loadClientCerts, func(caCert, clientCert, clientKey string) (*http.Client, error) { + return &http.Client{}, nil +@@ -131,8 +169,20 @@ func TestgetImageURL(t *testing.T) { + return &http.Client{}, nil + }) + defer patchLoadCaCerts.Reset() ++ patchGet := gomonkey.ApplyFunc(http.Get, func(url string) (resp *http.Response, err error) { ++ return &http.Response{StatusCode: http.StatusOK}, nil ++ }) ++ defer patchGet.Reset() ++ patchClientGet := gomonkey.ApplyMethod(reflect.TypeOf(&http.Client{}), "Get", func(_ *http.Client, url string) (resp *http.Response, err error) { ++ return &http.Response{StatusCode: http.StatusOK}, nil ++ }) ++ defer patchClientGet.Reset() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { ++ if tt.name == "httpSuccess" { ++ patchGet := gomonkey.ApplyFuncReturn(http.Get, &http.Response{StatusCode: http.StatusOK}, nil) ++ defer patchGet.Reset() ++ } + got, err := getImageURL(tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("getImageURL() error = %v, wantErr %v", err, tt.wantErr) +@@ -145,20 +195,28 @@ func TestgetImageURL(t *testing.T) { + } + } + +-func TestloadCaCerts(t *testing.T) { ++func Test_loadCaCerts(t *testing.T) { ++ tmpDir := t.TempDir() ++ caPath := tmpDir + "/fake.crt" ++ createFakeCertKey(caPath, "") + type args struct { + caCert string + } + tests := []struct { + name string + args args +- want *http.Client + wantErr bool + }{ +- {name: "noCaCertError", args: args{caCert: "bb.txt"}, want: &http.Client{}, wantErr: true}, ++ { ++ name: "normal", ++ args: args{ ++ caCert: caPath, ++ }, ++ wantErr: false, ++ }, + } +- os.MkdirAll(certPath, 0644) +- defer os.RemoveAll(certPath) ++ patchGetCertPath := gomonkey.ApplyFuncReturn(getCertPath, "") ++ defer patchGetCertPath.Reset() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := loadCaCerts(tt.args.caCert) +@@ -166,48 +224,39 @@ func TestloadCaCerts(t *testing.T) { + t.Errorf("loadCaCerts() error = %v, wantErr %v", err, tt.wantErr) + return + } +- if !reflect.DeepEqual(got, tt.want) { +- t.Errorf("loadCaCerts() = %v, want %v", got, tt.want) ++ if got == nil { ++ t.Errorf("loadCaCerts() = %v", got) + } + }) + } + + } + +-func TestloadClientCerts(t *testing.T) { ++func Test_loadClientCerts(t *testing.T) { ++ tmpDir := t.TempDir() ++ clientCertPath := tmpDir + "/fakeClientCert.crt" ++ clientKeyPath := tmpDir + "/fakeClientKey.crt" ++ createFakeCertKey(clientCertPath, clientKeyPath) + type args struct { + caCert string + clientCert string + clientKey string + } +- pool := &x509.CertPool{} + tests := []struct { + name string + args args +- want *http.Client + wantErr bool + }{ +- {name: "noCaCertError", args: args{" dd.txt", "bb.txt", "cc.txt"}, want: &http.Client{}, wantErr: true}, +- {name: "noClientCertError", args: args{"ca.crt", "bb.txt", "cc.txt"}, want: &http.Client{}, wantErr: true}, +- {name: "noClientKeyError", args: args{"ca.crt", "client.crt", "cc.txt"}, want: &http.Client{}, wantErr: true}, +- } +- os.MkdirAll(certPath, 0644) +- caFile, _ := os.Create(certPath + "ca.crt") +- clientCertFile, _ := os.Create(certPath + "client.crt") +- clientKeyFile, _ := os.Create(certPath + "client.key") +- +- patchNewCertPool := gomonkey.ApplyFunc(x509.NewCertPool, func() *x509.CertPool { +- return pool +- }) +- defer patchNewCertPool.Reset() +- patchAppendCertsFromPEM := gomonkey.ApplyMethod(reflect.TypeOf(pool), "AppendCertsFromPEM", func(_ *x509.CertPool, _ []byte) (ok bool) { +- return true +- }) +- defer patchAppendCertsFromPEM.Reset() +- patchLoadX509KeyPair := gomonkey.ApplyFunc(tls.LoadX509KeyPair, func(certFile string, keyFile string) (tls.Certificate, error) { +- return tls.Certificate{}, nil +- }) +- defer patchLoadX509KeyPair.Reset() ++ { ++ name: "normal", ++ args: args{ ++ caCert: clientCertPath, clientCert: clientCertPath, clientKey: clientKeyPath, ++ }, ++ wantErr: false, ++ }, ++ } ++ patchGetCertPath := gomonkey.ApplyFuncReturn(getCertPath, "") ++ defer patchGetCertPath.Reset() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := loadClientCerts(tt.args.caCert, tt.args.clientCert, tt.args.clientKey) +@@ -215,19 +264,14 @@ func TestloadClientCerts(t *testing.T) { + t.Errorf("loadClientCerts() error = %v, wantErr %v", err, tt.wantErr) + return + } +- if !reflect.DeepEqual(got, tt.want) { +- t.Errorf("loadClientCerts() got = %v, want %v", got, tt.want) ++ if got == nil { ++ t.Errorf("loadClientCerts() got = %v", got) + } + }) + } +- caFile.Close() +- clientCertFile.Close() +- clientKeyFile.Close() +- defer os.RemoveAll("/etc/KubeOS") +- + } + +-func TestcertExist(t *testing.T) { ++func Test_certExist(t *testing.T) { + type args struct { + certFile string + } +@@ -238,10 +282,7 @@ func TestcertExist(t *testing.T) { + }{ + {name: "fileEmpty", args: args{certFile: ""}, wantErr: true}, + {name: "fileNotExist", args: args{certFile: "bb.txt"}, wantErr: true}, +- {name: "normal", args: args{certFile: "aa.txt"}, wantErr: false}, + } +- os.MkdirAll(certPath, 0644) +- ff, _ := os.Create(certPath + "aa.txt") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := certExist(tt.args.certFile); (err != nil) != tt.wantErr { +@@ -249,6 +290,71 @@ func TestcertExist(t *testing.T) { + } + }) + } +- ff.Close() + defer os.RemoveAll("/etc/KubeOS/") + } ++ ++func createFakeCertKey(certPath, keyPath string) { ++ privateKey, _ := rsa.GenerateKey(rand.Reader, 2048) ++ template := x509.Certificate{ ++ SerialNumber: big.NewInt(1), ++ Subject: pkix.Name{ ++ CommonName: "Fake Client Certificate", ++ }, ++ NotBefore: time.Now(), ++ NotAfter: time.Now().AddDate(1, 0, 0), ++ KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ++ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, ++ BasicConstraintsValid: true, ++ } ++ certBytes, _ := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) ++ certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) ++ keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}) ++ os.WriteFile(certPath, certPEM, 0644) ++ if keyPath != "" { ++ os.WriteFile(keyPath, keyPEM, 0644) ++ } ++} ++ ++func calculateChecksum(data string) string { ++ hash := sha256.New() ++ hash.Write([]byte(data)) ++ return hex.EncodeToString(hash.Sum(nil)) ++} ++ ++func Test_diskHandler_getRootfsArchive(t *testing.T) { ++ type args struct { ++ req *pb.UpdateRequest ++ neededPath preparePath ++ } ++ tests := []struct { ++ name string ++ d diskHandler ++ args args ++ want string ++ wantErr bool ++ }{ ++ { ++ name: "normal", d: diskHandler{}, ++ args: args{req: &pb.UpdateRequest{ImageUrl: "http://www.openeuler.org/zh/"}, neededPath: preparePath{}}, ++ want: "/persist/update.img", ++ wantErr: false, ++ }, ++ } ++ patchDownload := gomonkey.ApplyFuncReturn(download, "/persist/update.img", nil) ++ defer patchDownload.Reset() ++ patchCheckSumMatch := gomonkey.ApplyFuncReturn(checkSumMatch, nil) ++ defer patchCheckSumMatch.Reset() ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ d := diskHandler{} ++ got, err := d.getRootfsArchive(tt.args.req, tt.args.neededPath) ++ if (err != nil) != tt.wantErr { ++ t.Errorf("diskHandler.getRootfsArchive() error = %v, wantErr %v", err, tt.wantErr) ++ return ++ } ++ if got != tt.want { ++ t.Errorf("diskHandler.getRootfsArchive() = %v, want %v", got, tt.want) ++ } ++ }) ++ } ++} +diff --git a/cmd/agent/server/server.go b/cmd/agent/server/server.go +index b41ebc4..8ac6ffd 100644 +--- a/cmd/agent/server/server.go ++++ b/cmd/agent/server/server.go +@@ -171,3 +171,7 @@ func (s *Server) reboot() error { + } + return syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART) + } ++ ++func getCertPath() string { ++ return certPath ++} +diff --git a/cmd/agent/server/server_test.go b/cmd/agent/server/server_test.go +index 0aac36a..74e2ead 100644 +--- a/cmd/agent/server/server_test.go ++++ b/cmd/agent/server/server_test.go +@@ -89,7 +89,21 @@ func TestServerUpdate(t *testing.T) { + {name: "error", fields: fields{UnimplementedOSServer: pb.UnimplementedOSServer{}, disableReboot: true}, + args: args{in0: context.Background(), req: &pb.UpdateRequest{Version: "test", Certs: &pb.CertsInfo{}}}, + want: &pb.UpdateResponse{}, wantErr: true}, ++ {name: "success", fields: fields{UnimplementedOSServer: pb.UnimplementedOSServer{}, disableReboot: true}, ++ args: args{in0: context.Background(), req: &pb.UpdateRequest{Version: "test", Certs: &pb.CertsInfo{}, ImageType: "containerd"}}, ++ want: &pb.UpdateResponse{}, wantErr: false}, + } ++ patchRootfsDisks := gomonkey.ApplyFuncReturn(getRootfsDisks, "/dev/sda2", "/dev/sda3", nil) ++ defer patchRootfsDisks.Reset() ++ // assume now is partition A, want to swiching to partition B ++ patchGetNextPartition := gomonkey.ApplyFuncReturn(getNextPart, "/dev/sda3", "B", nil) ++ defer patchGetNextPartition.Reset() ++ patchDownloadImage := gomonkey.ApplyPrivateMethod(conImageHandler{}, "downloadImage", func(_ conImageHandler, req *pb.UpdateRequest) (string, error) { ++ return "", nil ++ }) ++ defer patchDownloadImage.Reset() ++ patchInstall := gomonkey.ApplyFuncReturn(install, nil) ++ defer patchInstall.Reset() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Server{ +@@ -129,11 +143,26 @@ func TestServerRollback(t *testing.T) { + {name: "error", fields: fields{UnimplementedOSServer: pb.UnimplementedOSServer{}, disableReboot: true}, + args: args{in0: context.Background(), req: &pb.RollbackRequest{}}, + want: &pb.RollbackResponse{}, wantErr: true}, ++ {name: "success", fields: fields{UnimplementedOSServer: pb.UnimplementedOSServer{}, disableReboot: true}, ++ args: args{in0: context.Background(), req: &pb.RollbackRequest{}}, ++ want: &pb.RollbackResponse{}, wantErr: false}, + } +- patchGetNextPart := gomonkey.ApplyFunc(getNextPart, func(partA string, partB string) (string, string, error) { +- return "", "", fmt.Errorf("rollbak test error") ++ patchRootfsDisks := gomonkey.ApplyFuncReturn(getRootfsDisks, "/dev/sda2", "/dev/sda3", nil) ++ defer patchRootfsDisks.Reset() ++ // assume now is partition A, want to swiching to partition B ++ patchGetNextPartition := gomonkey.ApplyFuncSeq(getNextPart, []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{"", "", fmt.Errorf("rollbak test error")}}, ++ {Values: gomonkey.Params{"/dev/sda3", "B", nil}}, + }) +- defer patchGetNextPart.Reset() ++ defer patchGetNextPartition.Reset() ++ patchDownloadImage := gomonkey.ApplyPrivateMethod(conImageHandler{}, "downloadImage", func(_ conImageHandler, req *pb.UpdateRequest) (string, error) { ++ return "", nil ++ }) ++ defer patchDownloadImage.Reset() ++ patchInstall := gomonkey.ApplyFuncReturn(install, nil) ++ defer patchInstall.Reset() ++ patchRunCommand := gomonkey.ApplyFuncReturn(runCommand, nil) ++ defer patchRunCommand.Reset() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Server{ +@@ -179,9 +208,9 @@ func TestServerupdate(t *testing.T) { + }}, + wantErr: true}, + {name: "errordocker", args: args{&pb.UpdateRequest{ +- DockerImage: "", +- ImageType: "docker", +- Certs: &pb.CertsInfo{}, ++ ContainerImage: "", ++ ImageType: "docker", ++ Certs: &pb.CertsInfo{}, + }}, + wantErr: true}, + } +@@ -264,3 +293,48 @@ func TestServerreboot(t *testing.T) { + }) + } + } ++ ++func TestServer_Configure(t *testing.T) { ++ type fields struct { ++ UnimplementedOSServer pb.UnimplementedOSServer ++ mutex Lock ++ disableReboot bool ++ } ++ type args struct { ++ in0 context.Context ++ req *pb.ConfigureRequest ++ } ++ tests := []struct { ++ name string ++ fields fields ++ args args ++ want *pb.ConfigureResponse ++ wantErr bool ++ }{ ++ // TODO: Add test cases. ++ { ++ name: "nil", ++ fields: fields{UnimplementedOSServer: pb.UnimplementedOSServer{}, disableReboot: true}, ++ args: args{in0: context.Background(), req: &pb.ConfigureRequest{}}, ++ want: &pb.ConfigureResponse{}, ++ wantErr: false, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ s := &Server{ ++ UnimplementedOSServer: tt.fields.UnimplementedOSServer, ++ mutex: tt.fields.mutex, ++ disableReboot: tt.fields.disableReboot, ++ } ++ got, err := s.Configure(tt.args.in0, tt.args.req) ++ if (err != nil) != tt.wantErr { ++ t.Errorf("Server.Configure() error = %v, wantErr %v", err, tt.wantErr) ++ return ++ } ++ if !reflect.DeepEqual(got, tt.want) { ++ t.Errorf("Server.Configure() = %v, want %v", got, tt.want) ++ } ++ }) ++ } ++} +diff --git a/cmd/agent/server/utils_test.go b/cmd/agent/server/utils_test.go +index 8e7fd90..89b2c3b 100644 +--- a/cmd/agent/server/utils_test.go ++++ b/cmd/agent/server/utils_test.go +@@ -14,13 +14,17 @@ + package server + + import ( ++ "archive/tar" + "os" + "os/exec" +- "strings" ++ "reflect" + "testing" ++ "time" ++ ++ "github.com/agiledragon/gomonkey/v2" + ) + +-func TestrunCommand(t *testing.T) { ++func Test_runCommand(t *testing.T) { + type args struct { + name string + args []string +@@ -41,23 +45,21 @@ func TestrunCommand(t *testing.T) { + } + } + +-func Testinstall(t *testing.T) { ++func Test_install(t *testing.T) { + type args struct { + imagePath string + side string + next string + } +- out, _ := exec.Command("bash", "-c", "df -h | grep '/$' | awk '{print $1}'").CombinedOutput() +- mountPart := strings.TrimSpace(string(out)) + tests := []struct { + name string + args args + wantErr bool + }{ +- {name: "normal", args: args{imagePath: "aa.txt", side: mountPart, next: ""}, wantErr: false}, ++ {name: "normal", args: args{imagePath: "aa.txt", side: "/dev/sda3", next: "A"}, wantErr: false}, + } +- ff, _ := os.Create("aa.txt") +- ff.Chmod(os.ModePerm) ++ patchRunCommand := gomonkey.ApplyFuncReturn(runCommand, nil) ++ defer patchRunCommand.Reset() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := install(tt.args.imagePath, tt.args.side, tt.args.next); (err != nil) != tt.wantErr { +@@ -65,17 +67,13 @@ func Testinstall(t *testing.T) { + } + }) + } +- ff.Close() +- defer os.Remove("aa.txt") + } + +-func TestgetNextPart(t *testing.T) { ++func Test_getNextPart(t *testing.T) { + type args struct { + partA string + partB string + } +- out, _ := exec.Command("bash", "-c", "df -h | grep '/$' | awk '{print $1}'").CombinedOutput() +- mountPart := strings.TrimSpace(string(out)) + tests := []struct { + name string + args args +@@ -83,8 +81,14 @@ func TestgetNextPart(t *testing.T) { + want1 string + wantErr bool + }{ +- {name: "normal", args: args{partA: mountPart, partB: "testB"}, want: "testB", want1: "B", wantErr: false}, ++ {name: "switch to sda3", args: args{partA: "/dev/sda2", partB: "/dev/sda3"}, want: "/dev/sda3", want1: "B", wantErr: false}, ++ {name: "switch to sda2", args: args{partA: "/dev/sda2", partB: "/dev/sda3"}, want: "/dev/sda2", want1: "A", wantErr: false}, + } ++ patchExecCommand := gomonkey.ApplyMethodSeq(&exec.Cmd{}, "CombinedOutput", []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{[]byte("/"), nil}}, ++ {Values: gomonkey.Params{[]byte(""), nil}}, ++ }) ++ defer patchExecCommand.Reset() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1, err := getNextPart(tt.args.partA, tt.args.partB) +@@ -101,3 +105,117 @@ func TestgetNextPart(t *testing.T) { + }) + } + } ++ ++func Test_prepareEnv(t *testing.T) { ++ mountPath := "/persist/KubeOS-Update/kubeos-update" ++ if err := os.MkdirAll(mountPath, 0644); err != nil { ++ t.Fatalf("mkdir err %v", err) ++ } ++ defer os.RemoveAll("/persist") ++ tests := []struct { ++ name string ++ want preparePath ++ wantErr bool ++ }{ ++ { ++ name: "success", ++ want: preparePath{ ++ updatePath: "/persist/KubeOS-Update", ++ mountPath: "/persist/KubeOS-Update/kubeos-update", ++ tarPath: "/persist/KubeOS-Update/os.tar", ++ imagePath: "/persist/update.img", ++ rootfsFile: "os.tar", ++ }, ++ wantErr: false, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ got, err := prepareEnv() ++ if (err != nil) != tt.wantErr { ++ t.Errorf("prepareEnv() error = %v, wantErr %v", err, tt.wantErr) ++ return ++ } ++ if !reflect.DeepEqual(got, tt.want) { ++ t.Errorf("prepareEnv() = %v, want %v", got, tt.want) ++ } ++ }) ++ } ++} ++ ++func Test_createOSImage(t *testing.T) { ++ mountPath := "/persist/KubeOS-Update/kubeos-update" ++ if err := os.MkdirAll(mountPath, 0644); err != nil { ++ t.Fatalf("mkdir err %v", err) ++ } ++ defer os.RemoveAll("/persist") ++ tarPath := "/persist/KubeOS-Update/os.tar" ++ path, err := createTmpTarFile(tarPath) ++ if path != tarPath && err != nil { ++ t.Fatalf("create temp zip file err %v", err) ++ } ++ type args struct { ++ neededPath preparePath ++ } ++ tests := []struct { ++ name string ++ args args ++ want string ++ wantErr bool ++ }{ ++ { ++ name: "normal", ++ args: args{ ++ neededPath: preparePath{ ++ updatePath: "/persist/KubeOS-Update", ++ mountPath: "/persist/KubeOS-Update/kubeos-update", ++ tarPath: "/persist/KubeOS-Update/os.tar", ++ imagePath: "/persist/update.img", ++ }, ++ }, ++ want: "/persist/update.img", ++ wantErr: false, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ got, err := createOSImage(tt.args.neededPath) ++ if (err != nil) != tt.wantErr { ++ t.Errorf("createOSImage() error = %v, wantErr %v", err, tt.wantErr) ++ return ++ } ++ if got != tt.want { ++ t.Errorf("createOSImage() = %v, want %v", got, tt.want) ++ } ++ }) ++ } ++} ++ ++func createTmpTarFile(tarPath string) (string, error) { ++ tempFile, err := os.Create(tarPath) ++ if err != nil { ++ return "", err ++ } ++ defer tempFile.Close() ++ ++ tarWriter := tar.NewWriter(tempFile) ++ fakeData := []byte("This is a fake file") ++ fakeFile := "fakefile.txt" ++ header := &tar.Header{ ++ Name: fakeFile, ++ Size: int64(len(fakeData)), ++ Mode: 0644, ++ ModTime: time.Now(), ++ } ++ ++ if err = tarWriter.WriteHeader(header); err != nil { ++ return "", err ++ } ++ if _, err := tarWriter.Write(fakeData); err != nil { ++ return "", err ++ } ++ if err := tarWriter.Flush(); err != nil { ++ return "", err ++ } ++ return tempFile.Name(), nil ++} +diff --git a/cmd/operator/controllers/os_controller_test.go b/cmd/operator/controllers/os_controller_test.go +index a391005..98de6d0 100644 +--- a/cmd/operator/controllers/os_controller_test.go ++++ b/cmd/operator/controllers/os_controller_test.go +@@ -14,16 +14,22 @@ package controllers + + import ( + "context" ++ "testing" + "time" + ++ "github.com/agiledragon/gomonkey/v2" + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ++ "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" ++ "k8s.io/client-go/kubernetes/scheme" ++ "k8s.io/client-go/util/workqueue" + "sigs.k8s.io/controller-runtime/pkg/client" ++ "sigs.k8s.io/controller-runtime/pkg/event" + + upgradev1 "openeuler.org/KubeOS/api/v1alpha1" + "openeuler.org/KubeOS/pkg/values" +@@ -506,3 +512,64 @@ var _ = Describe("OsController", func() { + }) + }) + }) ++ ++func TestOSReconciler_DeleteOSInstance(t *testing.T) { ++ type fields struct { ++ Scheme *runtime.Scheme ++ Client client.Client ++ } ++ kClient, _ := client.New(cfg, client.Options{Scheme: scheme.Scheme}) ++ type args struct { ++ e event.DeleteEvent ++ q workqueue.RateLimitingInterface ++ } ++ tests := []struct { ++ name string ++ fields fields ++ args args ++ }{ ++ { ++ name: "delete osinstance", ++ fields: fields{ ++ Scheme: nil, ++ Client: kClient, ++ }, ++ args: args{ ++ e: event.DeleteEvent{ ++ Object: &upgradev1.OSInstance{ ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: "test-node1", ++ Namespace: "test", ++ }, ++ }, ++ }, ++ q: nil, ++ }, ++ }, ++ } ++ var patchList *gomonkey.Patches ++ var patchDelete *gomonkey.Patches ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ r := &OSReconciler{ ++ Scheme: tt.fields.Scheme, ++ Client: tt.fields.Client, ++ } ++ patchList = gomonkey.ApplyMethodFunc(r.Client, "List", func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { ++ list.(*upgradev1.OSInstanceList).Items = []upgradev1.OSInstance{ ++ { ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: "test-node1", ++ Namespace: "test", ++ }, ++ }, ++ } ++ return nil ++ }) ++ patchDelete = gomonkey.ApplyMethodReturn(r.Client, "Delete", nil) ++ r.DeleteOSInstance(tt.args.e, tt.args.q) ++ }) ++ } ++ defer patchDelete.Reset() ++ defer patchList.Reset() ++} +diff --git a/cmd/operator/controllers/suite_test.go b/cmd/operator/controllers/suite_test.go +index 889789e..aa6deea 100644 +--- a/cmd/operator/controllers/suite_test.go ++++ b/cmd/operator/controllers/suite_test.go +@@ -51,7 +51,7 @@ var _ = BeforeSuite(func() { + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ +- CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd")}, ++ CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "docs", "example", "config", "crd")}, + ErrorIfCRDPathMissing: true, + } + +diff --git a/cmd/proxy/controllers/os_controller.go b/cmd/proxy/controllers/os_controller.go +index b0b17e7..09d61c0 100644 +--- a/cmd/proxy/controllers/os_controller.go ++++ b/cmd/proxy/controllers/os_controller.go +@@ -87,6 +87,9 @@ func (r *OSReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Re + sameOSVersion := checkVersion(osCr.Spec.OSVersion, node.Status.NodeInfo.OSImage) + if sameOSVersion { + configOps, err := checkConfigVersion(osCr, osInstance, values.SysConfigName) ++ if err != nil { ++ return values.RequeueNow, err ++ } + if configOps == values.Reassign { + if err = r.refreshNode(ctx, &node, osInstance, osCr.Spec.SysConfigs.Version, values.SysConfigName); err != nil { + return values.RequeueNow, err +@@ -113,6 +116,9 @@ func (r *OSReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Re + return values.RequeueNow, err + } + configOps, err := checkConfigVersion(osCr, osInstance, values.UpgradeConfigName) ++ if err != nil { ++ return values.RequeueNow, err ++ } + if configOps == values.Reassign { + if err = r.refreshNode(ctx, &node, osInstance, osCr.Spec.UpgradeConfigs.Version, + values.UpgradeConfigName); err != nil { +diff --git a/cmd/proxy/controllers/os_controller_test.go b/cmd/proxy/controllers/os_controller_test.go +index e6cd5b7..d63f176 100644 +--- a/cmd/proxy/controllers/os_controller_test.go ++++ b/cmd/proxy/controllers/os_controller_test.go +@@ -67,6 +67,148 @@ var _ = Describe("OsController", func() { + testNamespace = existingNamespace.Name + }) + ++ Context("When we want to rollback", func() { ++ It("Should be able to rollback to previous version", func() { ++ ctx := context.Background() ++ ++ By("Creating a worker node") ++ node1Name = "test-node-" + uuid.New().String() ++ node1 := &v1.Node{ ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: node1Name, ++ Namespace: testNamespace, ++ Labels: map[string]string{ ++ "beta.kubernetes.io/os": "linux", ++ values.LabelUpgrading: "", ++ }, ++ }, ++ TypeMeta: metav1.TypeMeta{ ++ APIVersion: "v1", ++ Kind: "Node", ++ }, ++ Status: v1.NodeStatus{ ++ NodeInfo: v1.NodeSystemInfo{ ++ OSImage: "KubeOS v2", ++ }, ++ }, ++ } ++ err := k8sClient.Create(ctx, node1) ++ Expect(err).ToNot(HaveOccurred()) ++ existingNode := &v1.Node{} ++ Eventually(func() bool { ++ err := k8sClient.Get(context.Background(), ++ types.NamespacedName{Name: node1Name, Namespace: testNamespace}, existingNode) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ reconciler.hostName = node1Name ++ ++ By("Creating the corresponding OSInstance") ++ OSIns := &upgradev1.OSInstance{ ++ TypeMeta: metav1.TypeMeta{ ++ Kind: "OSInstance", ++ APIVersion: "upgrade.openeuler.org/v1alpha1", ++ }, ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: node1Name, ++ Namespace: testNamespace, ++ Labels: map[string]string{ ++ values.LabelOSinstance: node1Name, ++ }, ++ }, ++ Spec: upgradev1.OSInstanceSpec{ ++ NodeStatus: values.NodeStatusUpgrade.String(), ++ SysConfigs: upgradev1.SysConfigs{}, ++ UpgradeConfigs: upgradev1.SysConfigs{}, ++ }, ++ Status: upgradev1.OSInstanceStatus{}, ++ } ++ Expect(k8sClient.Create(ctx, OSIns)).Should(Succeed()) ++ ++ // Check that the corresponding OSIns CR has been created ++ osInsCRLookupKey := types.NamespacedName{Name: node1Name, Namespace: testNamespace} ++ createdOSIns := &upgradev1.OSInstance{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osInsCRLookupKey, createdOSIns) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ Expect(createdOSIns.Spec.NodeStatus).Should(Equal(values.NodeStatusUpgrade.String())) ++ ++ // stub r.Connection.RollbackSpec() ++ patchRollback := gomonkey.ApplyMethodReturn(reconciler.Connection, "RollbackSpec", nil) ++ defer patchRollback.Reset() ++ patchConfigure := gomonkey.ApplyMethodReturn(reconciler.Connection, "ConfigureSpec", nil) ++ defer patchConfigure.Reset() ++ ++ By("Creating a OS custom resource") ++ OS := &upgradev1.OS{ ++ TypeMeta: metav1.TypeMeta{ ++ APIVersion: "upgrade.openeuler.org/v1alpha1", ++ Kind: "OS", ++ }, ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: OSName, ++ Namespace: testNamespace, ++ }, ++ Spec: upgradev1.OSSpec{ ++ OpsType: "rollback", ++ MaxUnavailable: 3, ++ OSVersion: "KubeOS v1", ++ FlagSafe: true, ++ MTLS: false, ++ EvictPodForce: true, ++ SysConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}}, ++ UpgradeConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}}, ++ }, ++ } ++ Expect(k8sClient.Create(ctx, OS)).Should(Succeed()) ++ ++ osCRLookupKey := types.NamespacedName{Name: OSName, Namespace: testNamespace} ++ createdOS := &upgradev1.OS{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osCRLookupKey, createdOS) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ Expect(createdOS.Spec.OSVersion).Should(Equal("KubeOS v1")) ++ Expect(createdOS.Spec.OpsType).Should(Equal("rollback")) ++ ++ By("Changing the nodeinfo OSImage to previous version, pretending the rollback success") ++ existingNode = &v1.Node{} ++ Eventually(func() bool { ++ err := k8sClient.Get(context.Background(), ++ types.NamespacedName{Name: node1Name, Namespace: testNamespace}, existingNode) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ existingNode.Status.NodeInfo.OSImage = "KubeOS v1" ++ Expect(k8sClient.Status().Update(ctx, existingNode)).Should(Succeed()) ++ ++ By("Changing the OS Spec config to trigger reconcile") ++ createdOS = &upgradev1.OS{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osCRLookupKey, createdOS) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ createdOS.Spec.SysConfigs = upgradev1.SysConfigs{Version: "v1", Configs: []upgradev1.SysConfig{}} ++ Expect(k8sClient.Update(ctx, createdOS)).Should(Succeed()) ++ ++ time.Sleep(2 * time.Second) // sleep a while to make sure Reconcile finished ++ createdOSIns = &upgradev1.OSInstance{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osInsCRLookupKey, createdOSIns) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ // NodeStatus changes to idle then operator can reassign configs to this node ++ Expect(createdOSIns.Spec.NodeStatus).Should(Equal(values.NodeStatusIdle.String())) ++ existingNode = &v1.Node{} ++ Eventually(func() bool { ++ err := k8sClient.Get(context.Background(), ++ types.NamespacedName{Name: node1Name, Namespace: testNamespace}, existingNode) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ _, ok := existingNode.Labels[values.LabelUpgrading] ++ Expect(ok).Should(Equal(false)) ++ }) ++ }) ++ + Context("When we have a sysconfig whose version is different from current OSInstance config version", func() { + It("Should configure the node", func() { + ctx := context.Background() +@@ -482,6 +624,9 @@ var _ = Describe("OsController", func() { + ObjectMeta: metav1.ObjectMeta{ + Name: node1Name, + Namespace: testNamespace, ++ Labels: map[string]string{ ++ values.LabelOSinstance: node1Name, ++ }, + }, + Spec: upgradev1.OSInstanceSpec{ + NodeStatus: values.NodeStatusConfig.String(), +@@ -634,6 +779,9 @@ var _ = Describe("OsController", func() { + ObjectMeta: metav1.ObjectMeta{ + Name: node1Name, + Namespace: testNamespace, ++ Labels: map[string]string{ ++ values.LabelOSinstance: node1Name, ++ }, + }, + Spec: upgradev1.OSInstanceSpec{ + NodeStatus: values.NodeStatusUpgrade.String(), +@@ -798,6 +946,9 @@ var _ = Describe("OsController", func() { + ObjectMeta: metav1.ObjectMeta{ + Name: node1Name, + Namespace: testNamespace, ++ Labels: map[string]string{ ++ values.LabelOSinstance: node1Name, ++ }, + }, + Spec: upgradev1.OSInstanceSpec{ + NodeStatus: values.NodeStatusUpgrade.String(), +@@ -915,7 +1066,7 @@ var _ = Describe("OsController", func() { + Expect(createdOS.Spec.OSVersion).Should(Equal("KubeOS v2")) + + By("Checking the OSInstance status config version failed to be updated") +- time.Sleep(2 * time.Second) // sleep a while to make sure Reconcile finished ++ time.Sleep(1 * time.Second) // sleep a while to make sure Reconcile finished + osInsCRLookupKey = types.NamespacedName{Name: node1Name, Namespace: testNamespace} + createdOSIns = &upgradev1.OSInstance{} + Eventually(func() bool { +diff --git a/cmd/proxy/controllers/suite_test.go b/cmd/proxy/controllers/suite_test.go +index a52c18f..00eebbf 100644 +--- a/cmd/proxy/controllers/suite_test.go ++++ b/cmd/proxy/controllers/suite_test.go +@@ -53,7 +53,7 @@ var _ = BeforeSuite(func() { + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ +- CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd")}, ++ CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "docs", "example", "config", "crd")}, + ErrorIfCRDPathMissing: true, + } + +-- +2.39.0 + diff --git a/0005-KubeOS-fix-validate-image-name-bug.patch b/0005-KubeOS-fix-validate-image-name-bug.patch new file mode 100644 index 0000000..bc274c9 --- /dev/null +++ b/0005-KubeOS-fix-validate-image-name-bug.patch @@ -0,0 +1,155 @@ +From 5c5922a922f225a9ebc5f99a5008ddcf182b7da6 Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Thu, 10 Aug 2023 17:24:50 +0800 +Subject: [PATCH 05/17] KubeOS: fix validate image name bug fix the bug that + incorrectly granted illegal image names path:tag@sha256 + +Signed-off-by: Yuhang Wei +--- + cmd/agent/server/utils.go | 2 +- + cmd/agent/server/utils_test.go | 109 ++++++++++++++++++++++++++++++++- + 2 files changed, 109 insertions(+), 2 deletions(-) + +diff --git a/cmd/agent/server/utils.go b/cmd/agent/server/utils.go +index 7134d74..b42db18 100644 +--- a/cmd/agent/server/utils.go ++++ b/cmd/agent/server/utils.go +@@ -316,7 +316,7 @@ func isCommandAvailable(name string) bool { + } + + func isValidImageName(image string) error { +- pattern := `^((?:[\w.-]+)(?::\d+)?\/)*(?:[\w.-]+)(?::[\w_.-]+)?(?:@sha256:[a-fA-F0-9]+)?$` ++ pattern := `^((?:[\w.-]+)(?::\d+)?\/)*(?:[\w.-]+)((?::[\w_.-]+)?|(?:@sha256:[a-fA-F0-9]+)?)$` + regEx, err := regexp.Compile(pattern) + if err != nil { + return err +diff --git a/cmd/agent/server/utils_test.go b/cmd/agent/server/utils_test.go +index 89b2c3b..0796bce 100644 +--- a/cmd/agent/server/utils_test.go ++++ b/cmd/agent/server/utils_test.go +@@ -56,10 +56,16 @@ func Test_install(t *testing.T) { + args args + wantErr bool + }{ +- {name: "normal", args: args{imagePath: "aa.txt", side: "/dev/sda3", next: "A"}, wantErr: false}, ++ {name: "normal uefi", args: args{imagePath: "aa.txt", side: "/dev/sda3", next: "A"}, wantErr: false}, ++ {name: "normal legacy", args: args{imagePath: "aa.txt", side: "/dev/sda3", next: "A"}, wantErr: false}, + } + patchRunCommand := gomonkey.ApplyFuncReturn(runCommand, nil) + defer patchRunCommand.Reset() ++ patchGetBootMode := gomonkey.ApplyFuncSeq(getBootMode, []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{"uefi", nil}}, ++ {Values: gomonkey.Params{"legacy", nil}}, ++ }) ++ defer patchGetBootMode.Reset() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := install(tt.args.imagePath, tt.args.side, tt.args.next); (err != nil) != tt.wantErr { +@@ -219,3 +225,104 @@ func createTmpTarFile(tarPath string) (string, error) { + } + return tempFile.Name(), nil + } ++ ++func Test_getBootMode(t *testing.T) { ++ tests := []struct { ++ name string ++ want string ++ wantErr bool ++ }{ ++ { ++ name: "uefi", ++ want: "uefi", ++ wantErr: false, ++ }, ++ { ++ name: "legacy", ++ want: "legacy", ++ wantErr: false, ++ }, ++ } ++ patchOSStat := gomonkey.ApplyFuncSeq(os.Stat, []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{nil, nil}}, ++ {Values: gomonkey.Params{nil, os.ErrNotExist}}, ++ }) ++ defer patchOSStat.Reset() ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ got, err := getBootMode() ++ if (err != nil) != tt.wantErr { ++ t.Errorf("getBootMode() error = %v, wantErr %v", err, tt.wantErr) ++ return ++ } ++ if got != tt.want { ++ t.Errorf("getBootMode() = %v, want %v", got, tt.want) ++ } ++ }) ++ } ++} ++ ++func Test_isValidImageName(t *testing.T) { ++ type args struct { ++ image string ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ {name: "valid", args: args{image: "alpine"}, wantErr: false}, ++ {name: "valid", args: args{image: "alpine:latest"}, wantErr: false}, ++ {name: "valid", args: args{image: "localhost:1234/test"}, wantErr: false}, ++ {name: "valid", args: args{image: "alpine:3.7"}, wantErr: false}, ++ {name: "valid", args: args{image: "docker.example.edu/gmr/alpine:3.7"}, wantErr: false}, ++ {name: "valid", args: args{image: "docker.example.com:5000/gmr/alpine@sha256:11111111111111111111111111111111"}, wantErr: false}, ++ {name: "valid", args: args{image: "registry.dobby.org/dobby/dobby-servers/arthound:2019-08-08"}, wantErr: false}, ++ {name: "valid", args: args{image: "registry.dobby.org/dobby/dobby-servers/lerphound:latest"}, wantErr: false}, ++ {name: "valid", args: args{image: "registry.dobby.org/dobby/dobby-servers/loophole@sha256:5a156ff125e5a12ac7ff43ee5120fa249cf62248337b6d04abc574c8"}, wantErr: false}, ++ {name: "valid", args: args{image: "sosedoff/pgweb@sha256:5a156ff125e5a12ac7ff43ee5120fa249cf62248337b6d04574c8"}, wantErr: false}, ++ {name: "valid", args: args{image: "registry.dobby.org/dobby/antique-penguin:release-production"}, wantErr: false}, ++ {name: "valid", args: args{image: "dalprodictus/halcon:6.7.5"}, wantErr: false}, ++ {name: "valid", args: args{image: "antigua/antigua:v31"}, wantErr: false}, ++ {name: "invalid ;", args: args{image: "alpine;v1.0"}, wantErr: true}, ++ {name: "invalid tag and digest1", args: args{image: "alpine:latest@sha256:11111111111111111111111111111111"}, wantErr: true}, ++ {name: "invalid |", args: args{image: "alpine|v1.0"}, wantErr: true}, ++ {name: "invalid &", args: args{image: "alpine&v1.0"}, wantErr: true}, ++ {name: "invalid tag and digest2", args: args{image: "sosedoff/pgweb:latest@sha256:5a156ff125e5a12ac7ff43ee5120fa249cf62248337b6d04574c8"}, wantErr: true}, ++ {name: "invalid tag and digest3", args: args{image: "192.168.122.123:5000/kubeos_uefi-x86_64:euleros_v2_docker-2023-01@sha256:1a1a1a1a1a1a1a1a1a1a1a1a1a1a"}, wantErr: true}, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := isValidImageName(tt.args.image); (err != nil) != tt.wantErr { ++ t.Errorf("isValidImageName() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} ++ ++func Test_checkOCIImageDigestMatch(t *testing.T) { ++ type args struct { ++ containerRuntime string ++ imageName string ++ checkSum string ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ {name: "invalid container runtion", args: args{containerRuntime: "dockctl", imageName: "docker.io/library/hello-world:latest", checkSum: "1abf18abf9bf9baa0a4a38d1afad4abf0d7da4544e163186e036c906c09c94fe"}, wantErr: true}, ++ {name: "nil image digets", args: args{containerRuntime: "crictl", imageName: "docker.io/library/hello-world:latest", checkSum: "1abf18abf9bf9baa0a4a38d1afad4abf0d7da4544e163186e036c906c09c94fe"}, wantErr: true}, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if tt.name == "nil image digets" { ++ patchGetOCIImageDigest := gomonkey.ApplyFuncReturn(getOCIImageDigest, "", nil) ++ defer patchGetOCIImageDigest.Reset() ++ } ++ if err := checkOCIImageDigestMatch(tt.args.containerRuntime, tt.args.imageName, tt.args.checkSum); (err != nil) != tt.wantErr { ++ t.Errorf("checkOCIImageDigestMatch() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} +-- +2.39.0 + diff --git a/0006-KubeOS-fix-a-bug-that-failed-to-parse-key-with.patch b/0006-KubeOS-fix-a-bug-that-failed-to-parse-key-with.patch new file mode 100644 index 0000000..87bda7b --- /dev/null +++ b/0006-KubeOS-fix-a-bug-that-failed-to-parse-key-with.patch @@ -0,0 +1,156 @@ +From 911fbd04ba55a2560e8da8595ff379a742e5fa30 Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Thu, 10 Aug 2023 17:47:54 +0800 +Subject: [PATCH 06/17] KubeOS: fix a bug that failed to parse key with = + +Signed-off-by: Yuhang Wei +--- + cmd/agent/server/config.go | 6 ++-- + cmd/agent/server/config_test.go | 55 +++++++++++++++++++++++++++++---- + 2 files changed, 52 insertions(+), 9 deletions(-) + +diff --git a/cmd/agent/server/config.go b/cmd/agent/server/config.go +index 653f913..bcd9fde 100644 +--- a/cmd/agent/server/config.go ++++ b/cmd/agent/server/config.go +@@ -259,7 +259,7 @@ func getAndSetConfigsFromFile(expectConfigs map[string]*agent.KeyInfo, path stri + configsWrite = append(configsWrite, line) + continue + } +- configKV := strings.Split(line, "=") ++ configKV := strings.SplitN(line, "=", kvPair) + if len(configKV) != kvPair { + logrus.Errorf("could not parse systctl config %s", line) + return nil, fmt.Errorf("could not parse systctl config %s", line) +@@ -374,8 +374,8 @@ func handleUpdateKey(config []string, configInfo *agent.KeyInfo, isFound bool) s + func handleAddKey(m map[string]*agent.KeyInfo, isOnlyKeyValid bool) []string { + var configs []string + for key, keyInfo := range m { +- if key == "" { +- logrus.Warnln("Failed to add nil key") ++ if key == "" || strings.Contains(key, "=") { ++ logrus.Warnf("Failed to add nil key or key containing =, key: %s", key) + continue + } + if keyInfo.Operation == "delete" { +diff --git a/cmd/agent/server/config_test.go b/cmd/agent/server/config_test.go +index 2deb15f..ae2a2cf 100644 +--- a/cmd/agent/server/config_test.go ++++ b/cmd/agent/server/config_test.go +@@ -23,7 +23,6 @@ import ( + "testing" + + "github.com/agiledragon/gomonkey/v2" +- + agent "openeuler.org/KubeOS/cmd/agent/api" + ) + +@@ -105,17 +104,27 @@ func TestKerSysctlPersist_SetConfig(t *testing.T) { + wantErr bool + }{ + {name: "create file", args: args{config: &agent.SysConfig{ConfigPath: persistPath}}, want: []string{comment}, wantErr: false}, ++ { ++ name: "nil path", ++ args: args{ ++ config: &agent.SysConfig{}, ++ }, ++ want: []string{}, ++ wantErr: false, ++ }, + { + name: "add configs", + args: args{ + config: &agent.SysConfig{ + ConfigPath: persistPath, + Contents: map[string]*agent.KeyInfo{ +- "a": {Value: "1"}, +- "b": {Value: "2"}, +- "c": {Value: ""}, +- "": {Value: "4"}, +- "e": {Value: "5"}, ++ "a": {Value: "1"}, ++ "b": {Value: "2"}, ++ "c": {Value: ""}, ++ "": {Value: "4"}, ++ "e": {Value: "5"}, ++ "y=1": {Value: "26"}, ++ "z": {Value: "x=1"}, + }, + }, + }, +@@ -123,6 +132,7 @@ func TestKerSysctlPersist_SetConfig(t *testing.T) { + "a=1", + "b=2", + "e=5", ++ "z=x=1", + }, + wantErr: false, + }, +@@ -134,6 +144,7 @@ func TestKerSysctlPersist_SetConfig(t *testing.T) { + Contents: map[string]*agent.KeyInfo{ + "a": {Value: "2"}, + "b": {Value: ""}, ++ "z": {Value: "x=2"}, + }, + }, + }, +@@ -141,6 +152,7 @@ func TestKerSysctlPersist_SetConfig(t *testing.T) { + "a=2", + "b=2", + "e=5", ++ "z=x=2", + }, + wantErr: false, + }, +@@ -155,6 +167,7 @@ func TestKerSysctlPersist_SetConfig(t *testing.T) { + "c": {Value: "3", Operation: "delete"}, + "e": {Value: "5", Operation: "remove"}, + "f": {Value: "6", Operation: "remove"}, ++ "z": {Value: "x=2", Operation: "delete"}, + }, + }, + }, +@@ -166,6 +179,8 @@ func TestKerSysctlPersist_SetConfig(t *testing.T) { + wantErr: false, + }, + } ++ patchGetKernelConPath := gomonkey.ApplyFuncReturn(getKernelConPath, persistPath) ++ defer patchGetKernelConPath.Reset() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k := KerSysctlPersist{} +@@ -467,3 +482,31 @@ func Test_getConfigPartition(t *testing.T) { + }) + } + } ++ ++func Test_ConfigFactoryTemplate(t *testing.T) { ++ type args struct { ++ configType string ++ config *agent.SysConfig ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ { ++ name: "error", ++ args: args{ ++ configType: "test", ++ config: &agent.SysConfig{}, ++ }, ++ wantErr: true, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := ConfigFactoryTemplate(tt.args.configType, tt.args.config); (err != nil) != tt.wantErr { ++ t.Errorf("ConfigFactoryTemplate() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} +-- +2.39.0 + diff --git a/0007-KubeOS-add-warning-log-during-config.patch b/0007-KubeOS-add-warning-log-during-config.patch new file mode 100644 index 0000000..2d913b4 --- /dev/null +++ b/0007-KubeOS-add-warning-log-during-config.patch @@ -0,0 +1,52 @@ +From 59473394cca1e227ec579236d71ec54deb79b068 Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Thu, 10 Aug 2023 20:49:39 +0800 +Subject: [PATCH 07/17] KubeOS: add warning log during config add warning log + when configuring kv with unknown operation + +Signed-off-by: Yuhang Wei +--- + cmd/agent/server/config.go | 4 ++++ + cmd/agent/server/config_test.go | 4 ++-- + 2 files changed, 6 insertions(+), 2 deletions(-) + +diff --git a/cmd/agent/server/config.go b/cmd/agent/server/config.go +index bcd9fde..78dfd01 100644 +--- a/cmd/agent/server/config.go ++++ b/cmd/agent/server/config.go +@@ -382,6 +382,10 @@ func handleAddKey(m map[string]*agent.KeyInfo, isOnlyKeyValid bool) []string { + logrus.Warnf("Failed to delete inexistent key %s", key) + continue + } ++ if keyInfo.Operation != "" { ++ logrus.Warnf("Unknown operation %s, adding key %s with value %s by default", ++ keyInfo.Operation, key, keyInfo.Value) ++ } + k, v := strings.TrimSpace(key), strings.TrimSpace(keyInfo.Value) + if keyInfo.Value == "" && isOnlyKeyValid { + logrus.Infoln("add configuration ", k) +diff --git a/cmd/agent/server/config_test.go b/cmd/agent/server/config_test.go +index ae2a2cf..6424885 100644 +--- a/cmd/agent/server/config_test.go ++++ b/cmd/agent/server/config_test.go +@@ -122,7 +122,7 @@ func TestKerSysctlPersist_SetConfig(t *testing.T) { + "b": {Value: "2"}, + "c": {Value: ""}, + "": {Value: "4"}, +- "e": {Value: "5"}, ++ "e": {Value: "5", Operation: "xxx"}, + "y=1": {Value: "26"}, + "z": {Value: "x=1"}, + }, +@@ -144,7 +144,7 @@ func TestKerSysctlPersist_SetConfig(t *testing.T) { + Contents: map[string]*agent.KeyInfo{ + "a": {Value: "2"}, + "b": {Value: ""}, +- "z": {Value: "x=2"}, ++ "z": {Value: "x=2", Operation: "zzz"}, + }, + }, + }, +-- +2.39.0 + diff --git a/0008-KubeOS-modify-log-level-and-content.patch b/0008-KubeOS-modify-log-level-and-content.patch new file mode 100644 index 0000000..94f650f --- /dev/null +++ b/0008-KubeOS-modify-log-level-and-content.patch @@ -0,0 +1,26 @@ +From 38ed69d2ad089d124ee72a4431fcc96ef3d9cf28 Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Thu, 10 Aug 2023 21:10:29 +0800 +Subject: [PATCH 08/17] KubeOS: modify log level and content + +Signed-off-by: Yuhang Wei +--- + cmd/agent/server/config.go | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/cmd/agent/server/config.go b/cmd/agent/server/config.go +index 78dfd01..c474f59 100644 +--- a/cmd/agent/server/config.go ++++ b/cmd/agent/server/config.go +@@ -57,7 +57,7 @@ func (k KernelSysctl) SetConfig(config *agent.SysConfig) error { + } + logrus.Infof("Configured kernel.sysctl %s=%s", key, keyInfo.Value) + } else { +- logrus.Errorf("Failed to parse kernel.sysctl config operation %s value %s", keyInfo.Operation, keyInfo.Value) ++ logrus.Warnf("Failed to parse kernel.sysctl key %s value %s operation %s", key, keyInfo.Value, keyInfo.Operation) + } + } + return nil +-- +2.39.0 + diff --git a/0009-KubeOS-fix-updating-key-to-kv.patch b/0009-KubeOS-fix-updating-key-to-kv.patch new file mode 100644 index 0000000..a7e9c66 --- /dev/null +++ b/0009-KubeOS-fix-updating-key-to-kv.patch @@ -0,0 +1,151 @@ +From dcfb6e0397e649631ec42dcf51c3e98e914f8269 Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Fri, 11 Aug 2023 19:33:56 +0800 +Subject: [PATCH 09/17] KubeOS: fix updating key to kv + +when configuring kernel boot parameters, it should be able to update +key to kv + +Signed-off-by: Yuhang Wei +--- + cmd/agent/server/config.go | 37 ++++++++++++++++++++++----------- + cmd/agent/server/config_test.go | 15 +++++++------ + 2 files changed, 34 insertions(+), 18 deletions(-) + +diff --git a/cmd/agent/server/config.go b/cmd/agent/server/config.go +index c474f59..20af267 100644 +--- a/cmd/agent/server/config.go ++++ b/cmd/agent/server/config.go +@@ -57,7 +57,7 @@ func (k KernelSysctl) SetConfig(config *agent.SysConfig) error { + } + logrus.Infof("Configured kernel.sysctl %s=%s", key, keyInfo.Value) + } else { +- logrus.Warnf("Failed to parse kernel.sysctl key %s value %s operation %s", key, keyInfo.Value, keyInfo.Operation) ++ logrus.Warnf("Failed to parse kernel.sysctl key: %s value: %s operation: %s", key, keyInfo.Value, keyInfo.Operation) + } + } + return nil +@@ -317,7 +317,7 @@ func createConfigPath(configPath string) error { + if err != nil { + return err + } +- defer f.Close() ++ f.Close() + return nil + } + +@@ -335,11 +335,16 @@ func getGrubCfgPath() string { + + // handleDeleteKey deletes key if oldValue==newValue and returns "" string. Otherwier, it returns key=oldValue + func handleDeleteKey(config []string, configInfo *agent.KeyInfo) string { +- if len(config) == onlyKey { +- logrus.Infoln("delete configuration ", config[0]) ++ key := config[0] ++ if len(config) == onlyKey && configInfo.Value == "" { ++ logrus.Infoln("delete configuration ", key) + return "" ++ } else if len(config) == onlyKey && configInfo.Value != "" { ++ logrus.Warnf("Failed to delete key %s with inconsistent values "+ ++ "nil and %s", key, configInfo.Value) ++ return key + } +- key, oldValue := config[0], config[1] ++ oldValue := config[1] + if oldValue != configInfo.Value { + logrus.Warnf("Failed to delete key %s with inconsistent values "+ + "%s and %s", key, oldValue, configInfo.Value) +@@ -351,22 +356,30 @@ func handleDeleteKey(config []string, configInfo *agent.KeyInfo) string { + + // handleUpdateKey updates key if key is found, otherwise it returns old config. + func handleUpdateKey(config []string, configInfo *agent.KeyInfo, isFound bool) string { +- if len(config) == onlyKey { +- return config[0] ++ key := config[0] ++ if !isFound && len(config) == onlyKey { ++ return key + } +- key, oldValue := config[0], config[1] +- if !isFound { +- return key + "=" + oldValue ++ if !isFound && len(config) == kvPair { ++ return key + "=" + config[1] + } + if configInfo.Operation != "" { + logrus.Warnf("Unknown operation %s, updating key %s with value %s by default", + configInfo.Operation, key, configInfo.Value) + } ++ if len(config) == onlyKey && configInfo.Value == "" { ++ return key ++ } ++ newValue := strings.TrimSpace(configInfo.Value) ++ if len(config) == onlyKey && configInfo.Value != "" { ++ logrus.Infof("update configuration %s=%s", key, newValue) ++ return key + "=" + newValue ++ } ++ oldValue := config[1] + if configInfo.Value == "" { + logrus.Warnf("Failed to update key %s with null value", key) + return key + "=" + oldValue + } +- newValue := strings.TrimSpace(configInfo.Value) + logrus.Infof("update configuration %s=%s", key, newValue) + return key + "=" + newValue + } +@@ -388,7 +401,7 @@ func handleAddKey(m map[string]*agent.KeyInfo, isOnlyKeyValid bool) []string { + } + k, v := strings.TrimSpace(key), strings.TrimSpace(keyInfo.Value) + if keyInfo.Value == "" && isOnlyKeyValid { +- logrus.Infoln("add configuration ", k) ++ logrus.Infoln("add configuration", k) + configs = append(configs, k) + } else if keyInfo.Value == "" { + logrus.Warnf("Failed to add key %s with null value", k) +diff --git a/cmd/agent/server/config_test.go b/cmd/agent/server/config_test.go +index 6424885..08daf99 100644 +--- a/cmd/agent/server/config_test.go ++++ b/cmd/agent/server/config_test.go +@@ -262,10 +262,11 @@ menuentry 'B' --class KubeOS --class gnu-linux --class gnu --class os --unrestri + "": {Value: "test"}, // warning, skip, failed to add kv with empty key + "selinux": {Value: "1", Operation: "delete"}, // failed to delete inconsistent kv + "acpi": {Value: "off", Operation: "delete"}, // failed to delete inexistent kv ++ "ro": {Value: "1"}, // update key to kv + }, + }, + }, +- pattern: `(?m)^\s+linux\s+\/boot\/vmlinuz\s+root=UUID=[0-1]\s+ro\s+rootfstype=ext4\s+nomodeset\s+oops=panic\s+softlockup_panic=0\s+nmi_watchdog=1\s+rd\.shell=0\s+selinux=0\s+crashkernel=256M\s+panic=5\s+(debug\spci=nomis|pci=nomis\sdebug)$`, ++ pattern: `(?m)^\s+linux\s+\/boot\/vmlinuz\s+root=UUID=[0-1]\s+ro=1\s+rootfstype=ext4\s+nomodeset\s+oops=panic\s+softlockup_panic=0\s+nmi_watchdog=1\s+rd\.shell=0\s+selinux=0\s+crashkernel=256M\s+panic=5\s+(debug\spci=nomis|pci=nomis\sdebug)$`, + wantErr: false, + }, + { +@@ -274,14 +275,15 @@ menuentry 'B' --class KubeOS --class gnu-linux --class gnu --class os --unrestri + args: args{ + config: &agent.SysConfig{ + Contents: map[string]*agent.KeyInfo{ +- "debug": {Operation: "delete"}, // delete key +- "pci": {Value: "nomis", Operation: "delete"}, // delete kv +- "debugpat": {Value: "", Operation: "add"}, // passed key, operation is invalid, default to add key +- "audit": {Value: "1", Operation: "add"}, // passed kv, key is inexistent, operation is invalid, default to add kv ++ "debug": {Operation: "delete"}, // delete key ++ "pci": {Value: "nomis", Operation: "delete"}, // delete kv ++ "debugpat": {Value: "", Operation: "add"}, // passed key, operation is invalid, default to add key ++ "audit": {Value: "1", Operation: "add"}, // passed kv, key is inexistent, operation is invalid, default to add kv ++ "nomodeset": {Value: "1", Operation: "delete"}, // delete key with inconsistent value + }, + }, + }, +- pattern: `(?m)^\s+linux\s+\/boot\/vmlinuz\s+root=UUID=[0-1]\s+ro\s+rootfstype=ext4\s+nomodeset\s+oops=panic\s+softlockup_panic=0\s+nmi_watchdog=1\s+rd\.shell=0\s+selinux=0\s+crashkernel=256M\s+panic=5\s+(debugpat\saudit=1|audit=1\sdebugpat)$`, ++ pattern: `(?m)^\s+linux\s+\/boot\/vmlinuz\s+root=UUID=[0-1]\s+ro=1\s+rootfstype=ext4\s+nomodeset\s+oops=panic\s+softlockup_panic=0\s+nmi_watchdog=1\s+rd\.shell=0\s+selinux=0\s+crashkernel=256M\s+panic=5\s+(debugpat\saudit=1|audit=1\sdebugpat)$`, + wantErr: false, + }, + { +@@ -300,6 +302,7 @@ menuentry 'B' --class KubeOS --class gnu-linux --class gnu --class os --unrestri + "": {Value: "test"}, // warning, skip, failed to add kv with empty key + "selinux": {Value: "1", Operation: "delete"}, + "acpi": {Value: "off", Operation: "delete"}, ++ "ro": {Value: ""}, + }, + }, + }, +-- +2.39.0 + diff --git a/0010-KubeOS-fix-proxy-requeue-bug.patch b/0010-KubeOS-fix-proxy-requeue-bug.patch new file mode 100644 index 0000000..751dba8 --- /dev/null +++ b/0010-KubeOS-fix-proxy-requeue-bug.patch @@ -0,0 +1,37 @@ +From 657e3e5c9bcc0fac3a079f51842d59a9e6b5e163 Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Mon, 14 Aug 2023 09:53:16 +0800 +Subject: [PATCH 10/17] KubeOS: fix proxy requeue bug + +fix the bug that proxy may work after resuming normal from error status + +Signed-off-by: Yuhang Wei +--- + cmd/proxy/controllers/os_controller.go | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/cmd/proxy/controllers/os_controller.go b/cmd/proxy/controllers/os_controller.go +index 09d61c0..30f9839 100644 +--- a/cmd/proxy/controllers/os_controller.go ++++ b/cmd/proxy/controllers/os_controller.go +@@ -94,7 +94,7 @@ func (r *OSReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Re + if err = r.refreshNode(ctx, &node, osInstance, osCr.Spec.SysConfigs.Version, values.SysConfigName); err != nil { + return values.RequeueNow, err + } +- return values.RequeueNow, nil ++ return values.Requeue, nil + } + if configOps == values.UpdateConfig { + osInstance.Spec.SysConfigs = osCr.Spec.SysConfigs +@@ -124,7 +124,7 @@ func (r *OSReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Re + values.UpgradeConfigName); err != nil { + return values.RequeueNow, err + } +- return values.RequeueNow, nil ++ return values.Requeue, nil + } + if err := r.setConfig(ctx, osInstance, values.UpgradeConfigName); err != nil { + return values.RequeueNow, err +-- +2.39.0 + diff --git a/0011-KubeOS-fix-operator-bug-of-missing-deep-copy.patch b/0011-KubeOS-fix-operator-bug-of-missing-deep-copy.patch new file mode 100644 index 0000000..0e15987 --- /dev/null +++ b/0011-KubeOS-fix-operator-bug-of-missing-deep-copy.patch @@ -0,0 +1,665 @@ +From a87666e17d729feb67f987fa7038ef83b3fa2e1b Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Mon, 14 Aug 2023 16:46:06 +0800 +Subject: [PATCH 11/17] KubeOS: fix operator bug of missing deep copy + +Fixed bug where operator didn't perform deep copy +add operator ut, testing in multiple nodes env + +Signed-off-by: Yuhang Wei +--- + cmd/operator/controllers/os_controller.go | 44 +- + .../controllers/os_controller_test.go | 378 ++++++++++++++---- + 2 files changed, 330 insertions(+), 92 deletions(-) + +diff --git a/cmd/operator/controllers/os_controller.go b/cmd/operator/controllers/os_controller.go +index 620739b..d36f15d 100644 +--- a/cmd/operator/controllers/os_controller.go ++++ b/cmd/operator/controllers/os_controller.go +@@ -15,6 +15,8 @@ package controllers + + import ( + "context" ++ "encoding/json" ++ "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" +@@ -188,7 +190,9 @@ func upgradeNodes(ctx context.Context, r common.ReadStatusWriter, os *upgradev1. + } + continue + } +- updateNodeAndOSins(ctx, r, os, &node, &osInstance) ++ if err := updateNodeAndOSins(ctx, r, os, &node, &osInstance); err != nil { ++ continue ++ } + count++ + } + } +@@ -196,12 +200,16 @@ func upgradeNodes(ctx context.Context, r common.ReadStatusWriter, os *upgradev1. + } + + func updateNodeAndOSins(ctx context.Context, r common.ReadStatusWriter, os *upgradev1.OS, +- node *corev1.Node, osInstance *upgradev1.OSInstance) { ++ node *corev1.Node, osInstance *upgradev1.OSInstance) error { + if osInstance.Spec.UpgradeConfigs.Version != os.Spec.UpgradeConfigs.Version { +- osInstance.Spec.UpgradeConfigs = os.Spec.UpgradeConfigs ++ if err := deepCopySpecConfigs(os, osInstance, values.UpgradeConfigName); err != nil { ++ return err ++ } + } + if osInstance.Spec.SysConfigs.Version != os.Spec.SysConfigs.Version { +- osInstance.Spec.SysConfigs = os.Spec.SysConfigs ++ if err := deepCopySpecConfigs(os, osInstance, values.SysConfigName); err != nil { ++ return err ++ } + // exchange "grub.cmdline.current" and "grub.cmdline.next" + for i, config := range osInstance.Spec.SysConfigs.Configs { + if config.Model == "grub.cmdline.current" { +@@ -215,11 +223,14 @@ func updateNodeAndOSins(ctx context.Context, r common.ReadStatusWriter, os *upgr + osInstance.Spec.NodeStatus = values.NodeStatusUpgrade.String() + if err := r.Update(ctx, osInstance); err != nil { + log.Error(err, "unable to update", "osInstance", osInstance.Name) ++ return err + } + node.Labels[values.LabelUpgrading] = "" + if err := r.Update(ctx, node); err != nil { + log.Error(err, "unable to label", "node", node.Name) ++ return err + } ++ return nil + } + + func assignConfig(ctx context.Context, r common.ReadStatusWriter, sysConfigs upgradev1.SysConfigs, +@@ -307,3 +318,28 @@ func min(a, b int) int { + } + return b + } ++ ++func deepCopySpecConfigs(os *upgradev1.OS, osinstance *upgradev1.OSInstance, configType string) error { ++ switch configType { ++ case values.UpgradeConfigName: ++ data, err := json.Marshal(os.Spec.UpgradeConfigs) ++ if err != nil { ++ return err ++ } ++ if err = json.Unmarshal(data, &osinstance.Spec.UpgradeConfigs); err != nil { ++ return err ++ } ++ case values.SysConfigName: ++ data, err := json.Marshal(os.Spec.SysConfigs) ++ if err != nil { ++ return err ++ } ++ if err = json.Unmarshal(data, &osinstance.Spec.SysConfigs); err != nil { ++ return err ++ } ++ default: ++ log.Error(nil, "configType "+configType+" cannot be recognized") ++ return fmt.Errorf("configType %s cannot be recognized", configType) ++ } ++ return nil ++} +diff --git a/cmd/operator/controllers/os_controller_test.go b/cmd/operator/controllers/os_controller_test.go +index 98de6d0..e59ce7e 100644 +--- a/cmd/operator/controllers/os_controller_test.go ++++ b/cmd/operator/controllers/os_controller_test.go +@@ -17,19 +17,12 @@ import ( + "testing" + "time" + +- "github.com/agiledragon/gomonkey/v2" + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + v1 "k8s.io/api/core/v1" +- "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +- "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" +- "k8s.io/client-go/kubernetes/scheme" +- "k8s.io/client-go/util/workqueue" +- "sigs.k8s.io/controller-runtime/pkg/client" +- "sigs.k8s.io/controller-runtime/pkg/event" + + upgradev1 "openeuler.org/KubeOS/api/v1alpha1" + "openeuler.org/KubeOS/pkg/values" +@@ -37,8 +30,7 @@ import ( + + var _ = Describe("OsController", func() { + const ( +- OSName = "test-os" +- ++ OSName = "test-os" + timeout = time.Second * 20 + interval = time.Millisecond * 500 + ) +@@ -72,26 +64,33 @@ var _ = Describe("OsController", func() { + }) + + AfterEach(func() { +- desiredTestNamespace := &v1.Namespace{ +- TypeMeta: metav1.TypeMeta{ +- APIVersion: "v1", +- Kind: "Namespace", +- }, +- ObjectMeta: metav1.ObjectMeta{ +- Name: testNamespace, +- }, ++ // delete all nodes ++ nodeList := &v1.NodeList{} ++ err := k8sClient.List(context.Background(), nodeList) ++ Expect(err).ToNot(HaveOccurred()) ++ for _, node := range nodeList.Items { ++ k8sClient.Delete(context.Background(), &node) + } +- // Add any teardown steps that needs to be executed after each test +- err := k8sClient.Delete(context.Background(), desiredTestNamespace, +- client.PropagationPolicy(metav1.DeletePropagationForeground)) ++ nodeList = &v1.NodeList{} ++ Eventually(func() bool { ++ err = k8sClient.List(context.Background(), nodeList) ++ if err != nil || len(nodeList.Items) != 0 { ++ return false ++ } ++ return true ++ }, timeout, interval).Should(BeTrue()) + ++ // delete all OS CRs ++ osList := &upgradev1.OSList{} ++ err = k8sClient.List(context.Background(), osList) + Expect(err).ToNot(HaveOccurred()) +- +- existingNamespace := &v1.Namespace{} ++ for _, os := range osList.Items { ++ k8sClient.Delete(context.Background(), &os) ++ } ++ osList = &upgradev1.OSList{} + Eventually(func() bool { +- err := k8sClient.Get(context.Background(), types.NamespacedName{Name: testNamespace}, +- existingNamespace) +- if err != nil && errors.IsNotFound(err) { ++ err = k8sClient.List(context.Background(), osList) ++ if err != nil || len(osList.Items) != 0 { + return false + } + return true +@@ -102,7 +101,7 @@ var _ = Describe("OsController", func() { + It("Should label the osinstance's nodestatus to upgrading", func() { + ctx := context.Background() + +- // create Node ++ // create Node1 + node1Name = "test-node-" + uuid.New().String() + node1 := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ +@@ -131,7 +130,7 @@ var _ = Describe("OsController", func() { + return err == nil + }, timeout, interval).Should(BeTrue()) + +- // create OSInstance ++ // create OSInstance1 + OSIns := &upgradev1.OSInstance{ + TypeMeta: metav1.TypeMeta{ + Kind: "OSInstance", +@@ -163,6 +162,67 @@ var _ = Describe("OsController", func() { + }, timeout, interval).Should(BeTrue()) + Expect(createdOSIns.ObjectMeta.Name).Should(Equal(node1Name)) + ++ // create Node2 ++ node2Name := "test-node-" + uuid.New().String() ++ node2 := &v1.Node{ ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: node2Name, ++ Namespace: testNamespace, ++ Labels: map[string]string{ ++ "beta.kubernetes.io/os": "linux", ++ }, ++ }, ++ TypeMeta: metav1.TypeMeta{ ++ APIVersion: "v1", ++ Kind: "Node", ++ }, ++ Status: v1.NodeStatus{ ++ NodeInfo: v1.NodeSystemInfo{ ++ OSImage: "KubeOS v2", ++ }, ++ }, ++ } ++ err = k8sClient.Create(ctx, node2) ++ Expect(err).ToNot(HaveOccurred()) ++ existingNode = &v1.Node{} ++ Eventually(func() bool { ++ err := k8sClient.Get(context.Background(), ++ types.NamespacedName{Name: node2Name, Namespace: testNamespace}, existingNode) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ ++ // create OSInstance2 ++ OSIns = &upgradev1.OSInstance{ ++ TypeMeta: metav1.TypeMeta{ ++ Kind: "OSInstance", ++ APIVersion: "upgrade.openeuler.org/v1alpha1", ++ }, ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: node2Name, ++ Namespace: testNamespace, ++ Labels: map[string]string{ ++ values.LabelOSinstance: node2Name, ++ }, ++ }, ++ Spec: upgradev1.OSInstanceSpec{ ++ SysConfigs: upgradev1.SysConfigs{ ++ Version: "v1", ++ Configs: []upgradev1.SysConfig{}, ++ }, ++ UpgradeConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}, Version: "v1"}, ++ }, ++ } ++ Expect(k8sClient.Create(ctx, OSIns)).Should(Succeed()) ++ ++ // Check that the corresponding OSIns CR has been created ++ osInsCRLookupKey2 := types.NamespacedName{Name: node2Name, Namespace: testNamespace} ++ createdOSIns = &upgradev1.OSInstance{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osInsCRLookupKey2, createdOSIns) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ Expect(createdOSIns.ObjectMeta.Name).Should(Equal(node2Name)) ++ + // create OS CR + OS := &upgradev1.OS{ + TypeMeta: metav1.TypeMeta{ +@@ -206,13 +266,20 @@ var _ = Describe("OsController", func() { + return err == nil + }, timeout, interval).Should(BeTrue()) + Expect(createdOSIns.Spec.NodeStatus).Should(Equal(values.NodeStatusUpgrade.String())) ++ ++ createdOSIns2 := &upgradev1.OSInstance{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osInsCRLookupKey2, createdOSIns2) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ Expect(createdOSIns2.Spec.NodeStatus).Should(Equal(values.NodeStatusUpgrade.String())) + }) + }) + + Context("When we want to configure node", func() { + It("Should update OSInstance spec and update NodeStatus to config", func() { + ctx := context.Background() +- // create Node ++ // create Node1 + node1Name = "test-node-" + uuid.New().String() + node1 := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ +@@ -228,7 +295,7 @@ var _ = Describe("OsController", func() { + }, + Status: v1.NodeStatus{ + NodeInfo: v1.NodeSystemInfo{ +- OSImage: "KubeOS v2", ++ OSImage: "KubeOS v1", + }, + }, + } +@@ -241,6 +308,7 @@ var _ = Describe("OsController", func() { + return err == nil + }, timeout, interval).Should(BeTrue()) + ++ // create OSInstance1 + OSIns := &upgradev1.OSInstance{ + TypeMeta: metav1.TypeMeta{ + Kind: "OSInstance", +@@ -249,6 +317,9 @@ var _ = Describe("OsController", func() { + ObjectMeta: metav1.ObjectMeta{ + Name: node1Name, + Namespace: testNamespace, ++ Labels: map[string]string{ ++ values.LabelOSinstance: node1Name, ++ }, + }, + Spec: upgradev1.OSInstanceSpec{ + SysConfigs: upgradev1.SysConfigs{ +@@ -261,14 +332,76 @@ var _ = Describe("OsController", func() { + } + Expect(k8sClient.Create(ctx, OSIns)).Should(Succeed()) + +- osInsCRLookupKey := types.NamespacedName{Name: node1Name, Namespace: testNamespace} ++ osInsCRLookupKey1 := types.NamespacedName{Name: node1Name, Namespace: testNamespace} + createdOSIns := &upgradev1.OSInstance{} + Eventually(func() bool { +- err := k8sClient.Get(ctx, osInsCRLookupKey, createdOSIns) ++ err := k8sClient.Get(ctx, osInsCRLookupKey1, createdOSIns) + return err == nil + }, timeout, interval).Should(BeTrue()) + Expect(createdOSIns.ObjectMeta.Name).Should(Equal(node1Name)) + ++ // create Node2 ++ node2Name := "test-node-" + uuid.New().String() ++ node2 := &v1.Node{ ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: node2Name, ++ Namespace: testNamespace, ++ Labels: map[string]string{ ++ "beta.kubernetes.io/os": "linux", ++ }, ++ }, ++ TypeMeta: metav1.TypeMeta{ ++ APIVersion: "v1", ++ Kind: "Node", ++ }, ++ Status: v1.NodeStatus{ ++ NodeInfo: v1.NodeSystemInfo{ ++ OSImage: "KubeOS v1", ++ }, ++ }, ++ } ++ err = k8sClient.Create(ctx, node2) ++ Expect(err).ToNot(HaveOccurred()) ++ existingNode = &v1.Node{} ++ Eventually(func() bool { ++ err := k8sClient.Get(context.Background(), ++ types.NamespacedName{Name: node2Name, Namespace: testNamespace}, existingNode) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ ++ // create OSInstance2 ++ OSIns = &upgradev1.OSInstance{ ++ TypeMeta: metav1.TypeMeta{ ++ Kind: "OSInstance", ++ APIVersion: "upgrade.openeuler.org/v1alpha1", ++ }, ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: node2Name, ++ Namespace: testNamespace, ++ Labels: map[string]string{ ++ values.LabelOSinstance: node2Name, ++ }, ++ }, ++ Spec: upgradev1.OSInstanceSpec{ ++ SysConfigs: upgradev1.SysConfigs{ ++ Version: "v1", ++ Configs: []upgradev1.SysConfig{}, ++ }, ++ UpgradeConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}, Version: "v1"}, ++ NodeStatus: values.NodeStatusIdle.String(), ++ }, ++ } ++ Expect(k8sClient.Create(ctx, OSIns)).Should(Succeed()) ++ ++ // Check that the corresponding OSIns CR has been created ++ osInsCRLookupKey2 := types.NamespacedName{Name: node2Name, Namespace: testNamespace} ++ createdOSIns = &upgradev1.OSInstance{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osInsCRLookupKey2, createdOSIns) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ Expect(createdOSIns.ObjectMeta.Name).Should(Equal(node2Name)) ++ + OS := &upgradev1.OS{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "upgrade.openeuler.org/v1alpha1", +@@ -311,13 +444,21 @@ var _ = Describe("OsController", func() { + Expect(createdOS.Spec.OSVersion).Should(Equal("KubeOS v1")) + + time.Sleep(1 * time.Second) // sleep a while to make sure Reconcile finished +- configedOSIns := &upgradev1.OSInstance{} ++ configedOSIns1 := &upgradev1.OSInstance{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osInsCRLookupKey1, configedOSIns1) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ Expect(configedOSIns1.Spec.NodeStatus).Should(Equal(values.NodeStatusConfig.String())) ++ Expect(configedOSIns1.Spec.SysConfigs.Version).Should(Equal("v2")) ++ ++ configedOSIns2 := &upgradev1.OSInstance{} + Eventually(func() bool { +- err := k8sClient.Get(ctx, osInsCRLookupKey, configedOSIns) ++ err := k8sClient.Get(ctx, osInsCRLookupKey2, configedOSIns2) + return err == nil + }, timeout, interval).Should(BeTrue()) +- Expect(configedOSIns.Spec.NodeStatus).Should(Equal(values.NodeStatusConfig.String())) +- Expect(configedOSIns.Spec.SysConfigs.Version).Should(Equal("v2")) ++ Expect(configedOSIns2.Spec.NodeStatus).Should(Equal(values.NodeStatusConfig.String())) ++ Expect(configedOSIns2.Spec.SysConfigs.Version).Should(Equal("v2")) + }) + }) + +@@ -394,6 +535,9 @@ var _ = Describe("OsController", func() { + }, timeout, interval).Should(BeTrue()) + _, ok := existingNode.Labels[values.LabelUpgrading] + Expect(ok).Should(Equal(false)) ++ ++ createdOS.Spec.OpsType = "test" ++ Expect(k8sClient.Update(ctx, createdOS)).Should(Succeed()) + }) + }) + +@@ -401,7 +545,7 @@ var _ = Describe("OsController", func() { + It("Should exchange .current and .next", func() { + ctx := context.Background() + +- // create Node ++ // create Node1 + node1Name = "test-node-" + uuid.New().String() + node1 := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ +@@ -430,7 +574,7 @@ var _ = Describe("OsController", func() { + return err == nil + }, timeout, interval).Should(BeTrue()) + +- // create OSInstance ++ // create OSInstance1 + OSIns := &upgradev1.OSInstance{ + TypeMeta: metav1.TypeMeta{ + Kind: "OSInstance", +@@ -454,14 +598,75 @@ var _ = Describe("OsController", func() { + Expect(k8sClient.Create(ctx, OSIns)).Should(Succeed()) + + // Check that the corresponding OSIns CR has been created +- osInsCRLookupKey := types.NamespacedName{Name: node1Name, Namespace: testNamespace} ++ osInsCRLookupKey1 := types.NamespacedName{Name: node1Name, Namespace: testNamespace} + createdOSIns := &upgradev1.OSInstance{} + Eventually(func() bool { +- err := k8sClient.Get(ctx, osInsCRLookupKey, createdOSIns) ++ err := k8sClient.Get(ctx, osInsCRLookupKey1, createdOSIns) + return err == nil + }, timeout, interval).Should(BeTrue()) + Expect(createdOSIns.ObjectMeta.Name).Should(Equal(node1Name)) + ++ // create Node2 ++ node2Name := "test-node-" + uuid.New().String() ++ node2 := &v1.Node{ ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: node2Name, ++ Namespace: testNamespace, ++ Labels: map[string]string{ ++ "beta.kubernetes.io/os": "linux", ++ }, ++ }, ++ TypeMeta: metav1.TypeMeta{ ++ APIVersion: "v1", ++ Kind: "Node", ++ }, ++ Status: v1.NodeStatus{ ++ NodeInfo: v1.NodeSystemInfo{ ++ OSImage: "KubeOS v1", ++ }, ++ }, ++ } ++ err = k8sClient.Create(ctx, node2) ++ Expect(err).ToNot(HaveOccurred()) ++ existingNode = &v1.Node{} ++ Eventually(func() bool { ++ err := k8sClient.Get(context.Background(), ++ types.NamespacedName{Name: node2Name, Namespace: testNamespace}, existingNode) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ ++ // create OSInstance2 ++ OSIns = &upgradev1.OSInstance{ ++ TypeMeta: metav1.TypeMeta{ ++ Kind: "OSInstance", ++ APIVersion: "upgrade.openeuler.org/v1alpha1", ++ }, ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: node2Name, ++ Namespace: testNamespace, ++ Labels: map[string]string{ ++ values.LabelOSinstance: node2Name, ++ }, ++ }, ++ Spec: upgradev1.OSInstanceSpec{ ++ SysConfigs: upgradev1.SysConfigs{ ++ Version: "v1", ++ Configs: []upgradev1.SysConfig{}, ++ }, ++ UpgradeConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}, Version: "v1"}, ++ }, ++ } ++ Expect(k8sClient.Create(ctx, OSIns)).Should(Succeed()) ++ ++ // Check that the corresponding OSIns CR has been created ++ osInsCRLookupKey2 := types.NamespacedName{Name: node2Name, Namespace: testNamespace} ++ createdOSIns = &upgradev1.OSInstance{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osInsCRLookupKey2, createdOSIns) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ Expect(createdOSIns.ObjectMeta.Name).Should(Equal(node2Name)) ++ + // create OS CR + OS := &upgradev1.OS{ + TypeMeta: metav1.TypeMeta{ +@@ -486,7 +691,13 @@ var _ = Describe("OsController", func() { + {Model: "grub.cmdline.next", Contents: []upgradev1.Content{{Key: "b", Value: "2"}}}, + }, + }, +- UpgradeConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}}, ++ UpgradeConfigs: upgradev1.SysConfigs{ ++ Version: "v2", ++ Configs: []upgradev1.SysConfig{ ++ {Model: "grub.cmdline.current", Contents: []upgradev1.Content{{Key: "a", Value: "1"}}}, ++ {Model: "grub.cmdline.next", Contents: []upgradev1.Content{{Key: "b", Value: "2"}}}, ++ }, ++ }, + }, + } + Expect(k8sClient.Create(ctx, OS)).Should(Succeed()) +@@ -501,75 +712,66 @@ var _ = Describe("OsController", func() { + Expect(createdOS.Spec.OSVersion).Should(Equal("KubeOS v2")) + + time.Sleep(1 * time.Second) // sleep a while to make sure Reconcile finished +- osInsCRLookupKey = types.NamespacedName{Name: node1Name, Namespace: testNamespace} ++ // check node1 osinstance + createdOSIns = &upgradev1.OSInstance{} + Eventually(func() bool { +- err := k8sClient.Get(ctx, osInsCRLookupKey, createdOSIns) ++ err := k8sClient.Get(ctx, osInsCRLookupKey1, createdOSIns) + return err == nil + }, timeout, interval).Should(BeTrue()) + Expect(createdOSIns.Spec.SysConfigs.Configs[0]).Should(Equal(upgradev1.SysConfig{Model: "grub.cmdline.next", Contents: []upgradev1.Content{{Key: "a", Value: "1"}}})) + Expect(createdOSIns.Spec.SysConfigs.Configs[1]).Should(Equal(upgradev1.SysConfig{Model: "grub.cmdline.current", Contents: []upgradev1.Content{{Key: "b", Value: "2"}}})) ++ Expect(createdOSIns.Spec.UpgradeConfigs.Configs[0]).Should(Equal(upgradev1.SysConfig{Model: "grub.cmdline.current", Contents: []upgradev1.Content{{Key: "a", Value: "1"}}})) ++ Expect(createdOSIns.Spec.NodeStatus).Should(Equal(values.NodeStatusUpgrade.String())) ++ ++ // check node2 osinstance ++ createdOSIns2 := &upgradev1.OSInstance{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osInsCRLookupKey2, createdOSIns2) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ Expect(createdOSIns2.Spec.NodeStatus).Should(Equal(values.NodeStatusUpgrade.String())) ++ Expect(createdOSIns2.Spec.SysConfigs.Configs[0]).Should(Equal(upgradev1.SysConfig{Model: "grub.cmdline.next", Contents: []upgradev1.Content{{Key: "a", Value: "1"}}})) ++ Expect(createdOSIns2.Spec.SysConfigs.Configs[1]).Should(Equal(upgradev1.SysConfig{Model: "grub.cmdline.current", Contents: []upgradev1.Content{{Key: "b", Value: "2"}}})) ++ Expect(createdOSIns2.Spec.UpgradeConfigs.Configs[0]).Should(Equal(upgradev1.SysConfig{Model: "grub.cmdline.current", Contents: []upgradev1.Content{{Key: "a", Value: "1"}}})) ++ ++ // check os cr spec ++ osCRLookupKey = types.NamespacedName{Name: OSName, Namespace: testNamespace} ++ createdOS = &upgradev1.OS{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osCRLookupKey, createdOS) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ Expect(createdOS.Spec.SysConfigs.Configs[0]).Should(Equal(upgradev1.SysConfig{Model: "grub.cmdline.current", Contents: []upgradev1.Content{{Key: "a", Value: "1"}}})) ++ Expect(createdOS.Spec.SysConfigs.Configs[1]).Should(Equal(upgradev1.SysConfig{Model: "grub.cmdline.next", Contents: []upgradev1.Content{{Key: "b", Value: "2"}}})) + }) + }) + }) + +-func TestOSReconciler_DeleteOSInstance(t *testing.T) { +- type fields struct { +- Scheme *runtime.Scheme +- Client client.Client +- } +- kClient, _ := client.New(cfg, client.Options{Scheme: scheme.Scheme}) ++func Test_deepCopySpecConfigs(t *testing.T) { + type args struct { +- e event.DeleteEvent +- q workqueue.RateLimitingInterface ++ os *upgradev1.OS ++ osinstance *upgradev1.OSInstance ++ configType string + } + tests := []struct { +- name string +- fields fields +- args args ++ name string ++ args args ++ wantErr bool + }{ + { +- name: "delete osinstance", +- fields: fields{ +- Scheme: nil, +- Client: kClient, +- }, ++ name: "error", + args: args{ +- e: event.DeleteEvent{ +- Object: &upgradev1.OSInstance{ +- ObjectMeta: metav1.ObjectMeta{ +- Name: "test-node1", +- Namespace: "test", +- }, +- }, +- }, +- q: nil, +- }, ++ os: &upgradev1.OS{}, ++ osinstance: &upgradev1.OSInstance{}, ++ configType: "test"}, ++ wantErr: true, + }, + } +- var patchList *gomonkey.Patches +- var patchDelete *gomonkey.Patches + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { +- r := &OSReconciler{ +- Scheme: tt.fields.Scheme, +- Client: tt.fields.Client, ++ if err := deepCopySpecConfigs(tt.args.os, tt.args.osinstance, tt.args.configType); (err != nil) != tt.wantErr { ++ t.Errorf("deepCopySpecConfigs() error = %v, wantErr %v", err, tt.wantErr) + } +- patchList = gomonkey.ApplyMethodFunc(r.Client, "List", func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { +- list.(*upgradev1.OSInstanceList).Items = []upgradev1.OSInstance{ +- { +- ObjectMeta: metav1.ObjectMeta{ +- Name: "test-node1", +- Namespace: "test", +- }, +- }, +- } +- return nil +- }) +- patchDelete = gomonkey.ApplyMethodReturn(r.Client, "Delete", nil) +- r.DeleteOSInstance(tt.args.e, tt.args.q) + }) + } +- defer patchDelete.Reset() +- defer patchList.Reset() + } +-- +2.39.0 + diff --git a/0012-KubeOS-add-unit-test.patch b/0012-KubeOS-add-unit-test.patch new file mode 100644 index 0000000..9852ccf --- /dev/null +++ b/0012-KubeOS-add-unit-test.patch @@ -0,0 +1,191 @@ +From 9bd13d2a1b8b7282d7247caf9ba54f11bb297cd5 Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Fri, 11 Aug 2023 20:25:46 +0800 +Subject: [PATCH 12/17] KubeOS: add unit test + +add containerd_image and disk_image unit test +modify make test command for only testing server and controllers + +Signed-off-by: Yuhang Wei +--- + Makefile | 2 +- + cmd/agent/server/containerd_image_test.go | 10 ++- + cmd/agent/server/disk_image_test.go | 83 +++++++++++++++++++---- + 3 files changed, 80 insertions(+), 15 deletions(-) + +diff --git a/Makefile b/Makefile +index b5b6161..eddf9e6 100644 +--- a/Makefile ++++ b/Makefile +@@ -133,7 +133,7 @@ kustomize: + $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) + + ARCH := $(shell uname -m) +-TEST_CMD := go test ./... -race -count=1 -timeout=300s -cover -gcflags=all=-l -p 1 ++TEST_CMD := go test `go list ./cmd/... | grep -E 'server|controllers'` -race -count=1 -timeout=300s -cover -gcflags=all=-l -p 1 + + ifeq ($(ARCH), aarch64) + TEST_CMD := ETCD_UNSUPPORTED_ARCH=arm64 $(TEST_CMD) +diff --git a/cmd/agent/server/containerd_image_test.go b/cmd/agent/server/containerd_image_test.go +index d7133c3..85347c8 100644 +--- a/cmd/agent/server/containerd_image_test.go ++++ b/cmd/agent/server/containerd_image_test.go +@@ -32,7 +32,6 @@ func Test_conImageHandler_downloadImage(t *testing.T) { + want string + wantErr bool + }{ +- + { + name: "pullImageError", + c: conImageHandler{}, +@@ -62,6 +61,15 @@ func Test_conImageHandler_downloadImage(t *testing.T) { + want: "update-test1/upadte.img", + wantErr: false, + }, ++ { ++ name: "invalid image name", ++ c: conImageHandler{}, ++ args: args{ ++ req: &pb.UpdateRequest{ContainerImage: "nginx;v1"}, ++ }, ++ want: "", ++ wantErr: true, ++ }, + } + patchPrepareEnv := gomonkey.ApplyFunc(prepareEnv, func() (preparePath, error) { + return preparePath{updatePath: "update-test1/", +diff --git a/cmd/agent/server/disk_image_test.go b/cmd/agent/server/disk_image_test.go +index 71c5de7..265b323 100644 +--- a/cmd/agent/server/disk_image_test.go ++++ b/cmd/agent/server/disk_image_test.go +@@ -21,6 +21,7 @@ import ( + "crypto/x509/pkix" + "encoding/hex" + "encoding/pem" ++ "fmt" + "io" + "math/big" + "net/http" +@@ -52,36 +53,78 @@ func Test_download(t *testing.T) { + want string + wantErr bool + }{ +- // {name: "errornil", args: args{&pb.UpdateRequest{Certs: &pb.CertsInfo{}}}, want: "", wantErr: true}, +- // {name: "normal", args: args{&pb.UpdateRequest{ImageUrl: "http://www.openeuler.org/zh/", FlagSafe: true, Certs: &pb.CertsInfo{}}}, want: "/persist/update.img", wantErr: false}, +- // {name: "errornodir", args: args{&pb.UpdateRequest{ImageUrl: "http://www.openeuler.org/zh/", FlagSafe: true, Certs: &pb.CertsInfo{}}}, want: "", wantErr: true}, ++ {name: "errornil", args: args{&pb.UpdateRequest{Certs: &pb.CertsInfo{}}}, want: "", wantErr: true}, ++ {name: "error response", args: args{&pb.UpdateRequest{ImageUrl: "http://www.openeuler.abc", FlagSafe: true, Certs: &pb.CertsInfo{}}}, want: "", wantErr: true}, + { + name: "normal", + args: args{ + req: &pb.UpdateRequest{ + ImageUrl: "http://www.openeuler.org/zh/", ++ FlagSafe: true, ++ Certs: &pb.CertsInfo{}, + }, + }, + want: tmpFileForDownload, + wantErr: false, + }, ++ { ++ name: "disk space not enough", ++ args: args{ ++ req: &pb.UpdateRequest{ ++ ImageUrl: "http://www.openeuler.org/zh/", ++ FlagSafe: true, ++ Certs: &pb.CertsInfo{}, ++ }, ++ }, ++ want: "", ++ wantErr: true, ++ }, + } +- patchStatfs := gomonkey.ApplyFunc(syscall.Statfs, func(path string, stat *syscall.Statfs_t) error { ++ var patchStatfs *gomonkey.Patches ++ patchStatfs = gomonkey.ApplyFunc(syscall.Statfs, func(path string, stat *syscall.Statfs_t) error { + stat.Bfree = 3000 + stat.Bsize = 4096 + return nil + }) + defer patchStatfs.Reset() +- patchGetImageUrl := gomonkey.ApplyFuncReturn(getImageURL, &http.Response{ +- StatusCode: http.StatusOK, +- ContentLength: 5, +- Body: io.NopCloser(strings.NewReader("hello")), +- }, nil) ++ patchGetImageUrl := gomonkey.ApplyFuncSeq(getImageURL, ++ []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{&http.Response{}, fmt.Errorf("error")}}, ++ {Values: gomonkey.Params{&http.Response{StatusCode: http.StatusBadRequest, Body: io.NopCloser(strings.NewReader(""))}, nil}}, ++ { ++ Values: gomonkey.Params{ ++ &http.Response{ ++ StatusCode: http.StatusOK, ++ ContentLength: 5, ++ Body: io.NopCloser(strings.NewReader("hello")), ++ }, ++ nil, ++ }, ++ }, ++ { ++ Values: gomonkey.Params{ ++ &http.Response{ ++ StatusCode: http.StatusOK, ++ ContentLength: 5, ++ Body: io.NopCloser(strings.NewReader("hello")), ++ }, ++ nil, ++ }, ++ }, ++ }, ++ ) + defer patchGetImageUrl.Reset() + patchOSCreate := gomonkey.ApplyFuncReturn(os.Create, tmpFile, nil) + defer patchOSCreate.Reset() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { ++ if tt.name == "disk space not enough" { ++ patchStatfs = gomonkey.ApplyFunc(syscall.Statfs, func(path string, stat *syscall.Statfs_t) error { ++ stat.Bfree = 1 ++ stat.Bsize = 4096 ++ return nil ++ }) ++ } + got, err := download(tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("download() error = %v, wantErr %v", err, tt.wantErr) +@@ -160,13 +203,27 @@ func Test_getImageURL(t *testing.T) { + MTLS: false, + Certs: &pb.CertsInfo{}, + }}, want: &http.Response{StatusCode: http.StatusOK}, wantErr: false}, ++ {name: "httpsLoadCertsError", args: args{req: &pb.UpdateRequest{ ++ ImageUrl: "https://www.openeuler.abc/zh/", ++ FlagSafe: true, ++ MTLS: false, ++ Certs: &pb.CertsInfo{}, ++ }}, want: &http.Response{}, wantErr: true}, ++ {name: "httpsMLTSLoadCertsError", args: args{req: &pb.UpdateRequest{ ++ ImageUrl: "https://www.openeuler.abc/zh/", ++ FlagSafe: true, ++ MTLS: true, ++ Certs: &pb.CertsInfo{}, ++ }}, want: &http.Response{}, wantErr: true}, + } +- patchLoadClientCerts := gomonkey.ApplyFunc(loadClientCerts, func(caCert, clientCert, clientKey string) (*http.Client, error) { +- return &http.Client{}, nil ++ patchLoadClientCerts := gomonkey.ApplyFuncSeq(loadClientCerts, []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{&http.Client{}, nil}}, ++ {Values: gomonkey.Params{&http.Client{}, fmt.Errorf("error")}}, + }) + defer patchLoadClientCerts.Reset() +- patchLoadCaCerts := gomonkey.ApplyFunc(loadCaCerts, func(caCert string) (*http.Client, error) { +- return &http.Client{}, nil ++ patchLoadCaCerts := gomonkey.ApplyFuncSeq(loadCaCerts, []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{&http.Client{}, nil}}, ++ {Values: gomonkey.Params{&http.Client{}, fmt.Errorf("error")}}, + }) + defer patchLoadCaCerts.Reset() + patchGet := gomonkey.ApplyFunc(http.Get, func(url string) (resp *http.Response, err error) { +-- +2.39.0 + diff --git a/0013-KubeOS-fix-clean-space-problems.patch b/0013-KubeOS-fix-clean-space-problems.patch new file mode 100644 index 0000000..57a7428 --- /dev/null +++ b/0013-KubeOS-fix-clean-space-problems.patch @@ -0,0 +1,126 @@ +From 1bc41e7e58e99e23cd8be522cc1c2d30090975d2 Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Tue, 15 Aug 2023 16:33:11 +0800 +Subject: [PATCH 13/17] KubeOS: fix clean space problems + +add clean space function in server +change docker rm position in docker_image + +Signed-off-by: Yuhang Wei +--- + cmd/agent/main.go | 4 +--- + cmd/agent/server/docker_image.go | 6 +++--- + cmd/agent/server/server.go | 9 +++++++++ + cmd/agent/server/utils.go | 27 +++++++++++++-------------- + 4 files changed, 26 insertions(+), 20 deletions(-) + +diff --git a/cmd/agent/main.go b/cmd/agent/main.go +index 67c7e2d..7b1ed3d 100644 +--- a/cmd/agent/main.go ++++ b/cmd/agent/main.go +@@ -13,8 +13,6 @@ + package main + + import ( +- "fmt" +- + "github.com/sirupsen/logrus" + "google.golang.org/grpc" + +@@ -24,7 +22,7 @@ import ( + ) + + func main() { +- fmt.Println("Version is:", version.Version) ++ logrus.Infoln("Version is:", version.Version) + l, err := server.NewListener(server.SockDir, server.SockName) + if err != nil { + logrus.Errorln("listen error" + err.Error()) +diff --git a/cmd/agent/server/docker_image.go b/cmd/agent/server/docker_image.go +index 0b6ee35..16bcea5 100644 +--- a/cmd/agent/server/docker_image.go ++++ b/cmd/agent/server/docker_image.go +@@ -61,13 +61,13 @@ func (d dockerImageHandler) getRootfsArchive(req *pb.UpdateRequest, neededPath p + if err != nil { + return "", err + } +- if err := runCommand("docker", "cp", containerId+":/"+neededPath.rootfsFile, neededPath.updatePath); err != nil { +- return "", err +- } + defer func() { + if err := runCommand("docker", "rm", containerId); err != nil { + logrus.Errorln("remove kubeos-temp container error", err) + } + }() ++ if err := runCommand("docker", "cp", containerId+":/"+neededPath.rootfsFile, neededPath.updatePath); err != nil { ++ return "", err ++ } + return neededPath.tarPath, nil + } +diff --git a/cmd/agent/server/server.go b/cmd/agent/server/server.go +index 8ac6ffd..f8cbb41 100644 +--- a/cmd/agent/server/server.go ++++ b/cmd/agent/server/server.go +@@ -112,6 +112,15 @@ func (s *Server) update(req *pb.UpdateRequest) error { + return fmt.Errorf("image type %s cannot be recognized", action) + } + imagePath, err := handler.downloadImage(req) ++ defer func() { ++ if err != nil { ++ path := newPreparePath() ++ if err := cleanSpace(path.updatePath, path.mountPath, path.imagePath); err != nil { ++ logrus.Errorln("clean space error " + err.Error()) ++ } ++ logrus.Infoln("clean space success") ++ } ++ }() + if err != nil { + return err + } +diff --git a/cmd/agent/server/utils.go b/cmd/agent/server/utils.go +index b42db18..d2d0946 100644 +--- a/cmd/agent/server/utils.go ++++ b/cmd/agent/server/utils.go +@@ -190,26 +190,25 @@ 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, rootfsFile) +- imagePath := splicePath(PersistDir, osImageName) +- +- if err := cleanSpace(updatePath, mountPath, imagePath); err != nil { ++ upgradePath := newPreparePath() ++ if err := cleanSpace(upgradePath.updatePath, upgradePath.mountPath, upgradePath.imagePath); err != nil { + return preparePath{}, err + } +- if err := os.MkdirAll(mountPath, imgPermission); err != nil { ++ if err := os.MkdirAll(upgradePath.mountPath, imgPermission); err != nil { + return preparePath{}, err + } +- upgradePath := preparePath{ ++ return upgradePath, nil ++} ++ ++func newPreparePath() preparePath { ++ updatePath := splicePath(PersistDir, updateDir) ++ return preparePath{ + updatePath: updatePath, +- mountPath: mountPath, +- tarPath: tarPath, +- imagePath: imagePath, +- rootfsFile: rootfsFile, ++ mountPath: splicePath(updatePath, mountDir), ++ tarPath: splicePath(updatePath, rootfsArchive), ++ imagePath: splicePath(PersistDir, osImageName), ++ rootfsFile: rootfsArchive, + } +- return upgradePath, nil + } + + func checkDiskSize(needGBSize int, path string) error { +-- +2.39.0 + diff --git a/0014-KubeOS-update-version.patch b/0014-KubeOS-update-version.patch new file mode 100644 index 0000000..54eab51 --- /dev/null +++ b/0014-KubeOS-update-version.patch @@ -0,0 +1,20 @@ +From 9f0f03251a1bd0449f4e7e7ab5699e4771fbaa56 Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Fri, 18 Aug 2023 17:19:53 +0800 +Subject: [PATCH 15/17] KubeOS: update version + +Signed-off-by: Yuhang Wei +--- + VERSION | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/VERSION b/VERSION +index 7dea76e..ee90284 100644 +--- a/VERSION ++++ b/VERSION +@@ -1 +1 @@ +-1.0.1 ++1.0.4 +-- +2.39.0 + diff --git a/0015-KubeOS-add-line-breaks.patch b/0015-KubeOS-add-line-breaks.patch new file mode 100644 index 0000000..109b96b --- /dev/null +++ b/0015-KubeOS-add-line-breaks.patch @@ -0,0 +1,36 @@ +From 235a13b34e65b8d4ec4bde59e36247445b0778e8 Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Tue, 15 Aug 2023 16:24:25 +0800 +Subject: [PATCH] KubeOS: add line breaks + +Signed-off-by: Yuhang Wei +--- + scripts/admin-container/set-ssh-pub-key.service | 2 +- + scripts/admin-container/set-ssh-pub-key.sh | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/scripts/admin-container/set-ssh-pub-key.service b/scripts/admin-container/set-ssh-pub-key.service +index cf21406..84dd12d 100644 +--- a/scripts/admin-container/set-ssh-pub-key.service ++++ b/scripts/admin-container/set-ssh-pub-key.service +@@ -12,4 +12,4 @@ + Description="set ssh authorized keys according to the secret which is set by user" + + [Service] +-ExecStart="/usr/local/bin/set-ssh-pub-key.sh" +\ No newline at end of file ++ExecStart="/usr/local/bin/set-ssh-pub-key.sh" +diff --git a/scripts/admin-container/set-ssh-pub-key.sh b/scripts/admin-container/set-ssh-pub-key.sh +index aa706c2..e91a15d 100755 +--- a/scripts/admin-container/set-ssh-pub-key.sh ++++ b/scripts/admin-container/set-ssh-pub-key.sh +@@ -23,4 +23,4 @@ if [ ! -f "$authorized_file" ]; then + chmod 600 "$authorized_file" + fi + +-echo "$ssh_pub" >> "$authorized_file" +\ No newline at end of file ++echo "$ssh_pub" >> "$authorized_file" +-- +2.39.0 + diff --git a/0016-KubeOS-modify-code-for-clean-code.patch b/0016-KubeOS-modify-code-for-clean-code.patch new file mode 100644 index 0000000..dd3d0e3 --- /dev/null +++ b/0016-KubeOS-modify-code-for-clean-code.patch @@ -0,0 +1,46 @@ +From a01e5ebfd9c314b840eb3a652cda71e33954a403 Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Wed, 16 Aug 2023 22:41:44 +0800 +Subject: [PATCH 16/17] KubeOS: modify code for clean code + +Signed-off-by: Yuhang Wei +--- + cmd/agent/server/config.go | 4 ++++ + cmd/operator/controllers/os_controller.go | 6 +++++- + 2 files changed, 9 insertions(+), 1 deletion(-) + +diff --git a/cmd/agent/server/config.go b/cmd/agent/server/config.go +index 20af267..a96370d 100644 +--- a/cmd/agent/server/config.go ++++ b/cmd/agent/server/config.go +@@ -317,6 +317,10 @@ func createConfigPath(configPath string) error { + if err != nil { + return err + } ++ err = f.Chmod(defaultKernelConPermission) ++ if err != nil { ++ return err ++ } + f.Close() + return nil + } +diff --git a/cmd/operator/controllers/os_controller.go b/cmd/operator/controllers/os_controller.go +index 620739b..e152681 100644 +--- a/cmd/operator/controllers/os_controller.go ++++ b/cmd/operator/controllers/os_controller.go +@@ -94,7 +94,11 @@ func Reconcile(ctx context.Context, r common.ReadStatusWriter, req ctrl.Request) + func (r *OSReconciler) SetupWithManager(mgr ctrl.Manager) error { + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &upgradev1.OSInstance{}, values.OsiStatusName, + func(rawObj client.Object) []string { +- osi := rawObj.(*upgradev1.OSInstance) ++ osi, ok := rawObj.(*upgradev1.OSInstance) ++ if !ok { ++ log.Error(nil, "failed to convert to osInstance") ++ return []string{} ++ } + return []string{osi.Spec.NodeStatus} + }); err != nil { + return err +-- +2.39.0 + diff --git a/0017-KubeOS-fix-the-issue-that-osinstance-is-not-updated-.patch b/0017-KubeOS-fix-the-issue-that-osinstance-is-not-updated-.patch new file mode 100644 index 0000000..81e825f --- /dev/null +++ b/0017-KubeOS-fix-the-issue-that-osinstance-is-not-updated-.patch @@ -0,0 +1,176 @@ +From 0c0f3c7ef5749b8806511d9e3312f6cacd4cca04 Mon Sep 17 00:00:00 2001 +From: liyuanr +Date: Tue, 15 Aug 2023 12:29:11 +0000 +Subject: [PATCH 17/17] KubeOS: fix the issue that osinstance is not updated in + time during proxy upgrade. + +Fix the problem that osinstance is not updated in time during proxy upgrade, +but the node upgrade tag has been added. As a result, the configuration is skipped + and the upgrade is directly performed. + +Signed-off-by: liyuanr +--- + cmd/proxy/controllers/os_controller.go | 11 ++ + cmd/proxy/controllers/os_controller_test.go | 126 ++++++++++++++++++++ + 2 files changed, 137 insertions(+) + +diff --git a/cmd/proxy/controllers/os_controller.go b/cmd/proxy/controllers/os_controller.go +index 30f9839..b8d0f80 100644 +--- a/cmd/proxy/controllers/os_controller.go ++++ b/cmd/proxy/controllers/os_controller.go +@@ -126,6 +126,17 @@ func (r *OSReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Re + } + return values.Requeue, nil + } ++ if _, ok := node.Labels[values.LabelUpgrading]; ok && ++ osInstance.Spec.NodeStatus == values.NodeStatusIdle.String() { ++ log.Info("node has upgrade label, but osInstance.spec.nodestaus idle ", ++ "operation:", "refesh node and wait opetaot reassgin") ++ if err = r.refreshNode(ctx, &node, osInstance, osCr.Spec.UpgradeConfigs.Version, ++ values.UpgradeConfigName); err != nil { ++ return values.RequeueNow, err ++ } ++ return values.Requeue, nil ++ } ++ + if err := r.setConfig(ctx, osInstance, values.UpgradeConfigName); err != nil { + return values.RequeueNow, err + } +diff --git a/cmd/proxy/controllers/os_controller_test.go b/cmd/proxy/controllers/os_controller_test.go +index d63f176..27cb0cd 100644 +--- a/cmd/proxy/controllers/os_controller_test.go ++++ b/cmd/proxy/controllers/os_controller_test.go +@@ -1098,4 +1098,130 @@ var _ = Describe("OsController", func() { + Expect(ok).Should(Equal(false)) + }) + }) ++ ++ Context("When node has upgrade label but osinstance.spec.nodestatus is idle", func() { ++ It("Should be able to refresh node and wait operator reassgin upgrade", func() { ++ ctx := context.Background() ++ By("Creating a worker node") ++ node1Name = "test-node-" + uuid.New().String() ++ node1 := &v1.Node{ ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: node1Name, ++ Namespace: testNamespace, ++ Labels: map[string]string{ ++ "beta.kubernetes.io/os": "linux", ++ values.LabelUpgrading: "", ++ }, ++ }, ++ TypeMeta: metav1.TypeMeta{ ++ APIVersion: "v1", ++ Kind: "Node", ++ }, ++ Status: v1.NodeStatus{ ++ NodeInfo: v1.NodeSystemInfo{ ++ OSImage: "KubeOS v2", ++ }, ++ }, ++ } ++ err := k8sClient.Create(ctx, node1) ++ Expect(err).ToNot(HaveOccurred()) ++ existingNode := &v1.Node{} ++ Eventually(func() bool { ++ err := k8sClient.Get(context.Background(), ++ types.NamespacedName{Name: node1Name, Namespace: testNamespace}, existingNode) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ reconciler.hostName = node1Name ++ ++ By("Creating the corresponding OSInstance") ++ OSIns := &upgradev1.OSInstance{ ++ TypeMeta: metav1.TypeMeta{ ++ Kind: "OSInstance", ++ APIVersion: "upgrade.openeuler.org/v1alpha1", ++ }, ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: node1Name, ++ Namespace: testNamespace, ++ Labels: map[string]string{ ++ values.LabelOSinstance: node1Name, ++ }, ++ }, ++ Spec: upgradev1.OSInstanceSpec{ ++ NodeStatus: values.NodeStatusIdle.String(), ++ }, ++ Status: upgradev1.OSInstanceStatus{}, ++ } ++ Expect(k8sClient.Create(ctx, OSIns)).Should(Succeed()) ++ ++ // Check that the corresponding OSIns CR has been created ++ osInsCRLookupKey := types.NamespacedName{Name: node1Name, Namespace: testNamespace} ++ createdOSIns := &upgradev1.OSInstance{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osInsCRLookupKey, createdOSIns) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ Expect(createdOSIns.ObjectMeta.Name).Should(Equal(node1Name)) ++ By("Creating a OS custom resource") ++ OS := &upgradev1.OS{ ++ TypeMeta: metav1.TypeMeta{ ++ APIVersion: "upgrade.openeuler.org/v1alpha1", ++ Kind: "OS", ++ }, ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: OSName, ++ Namespace: testNamespace, ++ }, ++ Spec: upgradev1.OSSpec{ ++ OpsType: "upgrade", ++ MaxUnavailable: 3, ++ OSVersion: "KubeOS v2", ++ FlagSafe: true, ++ MTLS: false, ++ EvictPodForce: true, ++ SysConfigs: upgradev1.SysConfigs{ ++ Version: "v2", ++ Configs: []upgradev1.SysConfig{ ++ { ++ Model: "kernel.sysctl", ++ Contents: []upgradev1.Content{ ++ {Key: "key1", Value: "c"}, ++ {Key: "key2", Value: "d"}, ++ }, ++ }, ++ }, ++ }, ++ UpgradeConfigs: upgradev1.SysConfigs{ ++ Version: "v2", ++ Configs: []upgradev1.SysConfig{ ++ { ++ Model: "kernel.sysctl", ++ Contents: []upgradev1.Content{ ++ {Key: "key1", Value: "a"}, ++ {Key: "key2", Value: "b"}, ++ }, ++ }, ++ }, ++ }, ++ }, ++ } ++ Expect(k8sClient.Create(ctx, OS)).Should(Succeed()) ++ osCRLookupKey := types.NamespacedName{Name: OSName, Namespace: testNamespace} ++ createdOS := &upgradev1.OS{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osCRLookupKey, createdOS) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ Expect(createdOS.Spec.OSVersion).Should(Equal("KubeOS v2")) ++ ++ time.Sleep(2 * time.Second) // sleep a while to make sure Reconcile finished ++ existingNode = &v1.Node{} ++ Eventually(func() bool { ++ err := k8sClient.Get(context.Background(), ++ types.NamespacedName{Name: node1Name, Namespace: testNamespace}, existingNode) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ _, ok := existingNode.Labels[values.LabelUpgrading] ++ Expect(ok).Should(Equal(false)) ++ }) ++ }) + }) +-- +2.39.0 + diff --git a/0018-KubeOS-update-config-log-contents.patch b/0018-KubeOS-update-config-log-contents.patch new file mode 100644 index 0000000..5271458 --- /dev/null +++ b/0018-KubeOS-update-config-log-contents.patch @@ -0,0 +1,39 @@ +From 662f68aa131c09154bb91085e4657beaee00d1d6 Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Mon, 21 Aug 2023 09:58:45 +0800 +Subject: [PATCH 1/5] KubeOS: update config log contents + +Signed-off-by: Yuhang Wei +--- + cmd/agent/server/config.go | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/cmd/agent/server/config.go b/cmd/agent/server/config.go +index a96370d..f186ee6 100644 +--- a/cmd/agent/server/config.go ++++ b/cmd/agent/server/config.go +@@ -57,7 +57,7 @@ func (k KernelSysctl) SetConfig(config *agent.SysConfig) error { + } + logrus.Infof("Configured kernel.sysctl %s=%s", key, keyInfo.Value) + } else { +- logrus.Warnf("Failed to parse kernel.sysctl key: %s value: %s operation: %s", key, keyInfo.Value, keyInfo.Operation) ++ logrus.Warnf("Failed to parse kernel.sysctl, key: %s, value: %s, operation: %s", key, keyInfo.Value, keyInfo.Operation) + } + } + return nil +@@ -97,7 +97,11 @@ type GrubCmdline struct { + + // SetConfig sets grub.cmdline configuration + func (g GrubCmdline) SetConfig(config *agent.SysConfig) error { +- logrus.Info("start set grub.cmdline configuration") ++ if g.isCurPartition { ++ logrus.Info("start set grub.cmdline.current configuration") ++ } else { ++ logrus.Info("start set grub.cmdline.next configuration") ++ } + fileExist, err := checkFileExist(getGrubCfgPath()) + if err != nil { + logrus.Errorf("Failed to find config path: %v", err) +-- +2.39.0 + diff --git a/0019-KubeOS-add-unit-tests.patch b/0019-KubeOS-add-unit-tests.patch new file mode 100644 index 0000000..18f7bb6 --- /dev/null +++ b/0019-KubeOS-add-unit-tests.patch @@ -0,0 +1,1069 @@ +From f35fee982d0afefef4643993dc0f64c7aa243a22 Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Wed, 16 Aug 2023 14:27:36 +0800 +Subject: [PATCH 2/5] KubeOS:add unit tests + +modify proxy ut to avoid panic bug +add config, disk_image, operator, proxy, server and utils unit tests + +Signed-off-by: Yuhang Wei +--- + cmd/agent/server/config_test.go | 41 ++- + cmd/agent/server/disk_image_test.go | 56 +++- + cmd/agent/server/server_test.go | 1 - + cmd/agent/server/utils_test.go | 112 +++++++ + .../controllers/os_controller_test.go | 232 +++++++++++++- + cmd/proxy/controllers/os_controller_test.go | 295 +++++++++++++++--- + 6 files changed, 685 insertions(+), 52 deletions(-) + +diff --git a/cmd/agent/server/config_test.go b/cmd/agent/server/config_test.go +index 08daf99..29bb926 100644 +--- a/cmd/agent/server/config_test.go ++++ b/cmd/agent/server/config_test.go +@@ -75,6 +75,16 @@ func TestKernelSysctl_SetConfig(t *testing.T) { + }, + }}, + }, ++ { ++ name: "nil key", ++ k: KernelSysctl{}, ++ args: args{config: &agent.SysConfig{ ++ Contents: map[string]*agent.KeyInfo{ ++ "": {Value: "1"}, ++ }, ++ }}, ++ wantErr: true, ++ }, + } + tmpDir := t.TempDir() + patchGetProcPath := gomonkey.ApplyFuncReturn(getDefaultProcPath, tmpDir+"/") +@@ -320,8 +330,7 @@ menuentry 'B' --class KubeOS --class gnu-linux --class gnu --class os --unrestri + defer patchGetConfigPartition.Reset() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { +- g := GrubCmdline{} +- if err := g.SetConfig(tt.args.config); (err != nil) != tt.wantErr { ++ if err := tt.g.SetConfig(tt.args.config); (err != nil) != tt.wantErr { + t.Errorf("GrubCmdline.SetConfig() error = %v, wantErr %v", err, tt.wantErr) + } + contents, err := os.ReadFile(grubCfgPath) +@@ -513,3 +522,31 @@ func Test_ConfigFactoryTemplate(t *testing.T) { + }) + } + } ++ ++func Test_convertNewConfigsToString(t *testing.T) { ++ type args struct { ++ newConfigs []string ++ } ++ tests := []struct { ++ name string ++ args args ++ want string ++ wantErr bool ++ }{ ++ {name: "error", args: args{newConfigs: []string{"a"}}, want: "", wantErr: true}, ++ } ++ patchFprintf := gomonkey.ApplyFuncReturn(fmt.Fprintf, 0, fmt.Errorf("error")) ++ defer patchFprintf.Reset() ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ got, err := convertNewConfigsToString(tt.args.newConfigs) ++ if (err != nil) != tt.wantErr { ++ t.Errorf("convertNewConfigsToString() error = %v, wantErr %v", err, tt.wantErr) ++ return ++ } ++ if got != tt.want { ++ t.Errorf("convertNewConfigsToString() = %v, want %v", got, tt.want) ++ } ++ }) ++ } ++} +diff --git a/cmd/agent/server/disk_image_test.go b/cmd/agent/server/disk_image_test.go +index 265b323..f970bd7 100644 +--- a/cmd/agent/server/disk_image_test.go ++++ b/cmd/agent/server/disk_image_test.go +@@ -23,6 +23,7 @@ import ( + "encoding/pem" + "fmt" + "io" ++ "io/fs" + "math/big" + "net/http" + "os" +@@ -159,6 +160,7 @@ func Test_checkSumMatch(t *testing.T) { + wantErr: false, + }, + {name: "error", args: args{filePath: tmpFileForCheckSum, checkSum: "aaa"}, wantErr: true}, ++ {name: "unfound error", args: args{filePath: "", checkSum: "aaa"}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { +@@ -271,6 +273,7 @@ func Test_loadCaCerts(t *testing.T) { + }, + wantErr: false, + }, ++ {name: "no cert", args: args{caCert: ""}, wantErr: true}, + } + patchGetCertPath := gomonkey.ApplyFuncReturn(getCertPath, "") + defer patchGetCertPath.Reset() +@@ -339,12 +342,22 @@ func Test_certExist(t *testing.T) { + }{ + {name: "fileEmpty", args: args{certFile: ""}, wantErr: true}, + {name: "fileNotExist", args: args{certFile: "bb.txt"}, wantErr: true}, ++ {name: "unknow error", args: args{certFile: "cc.txt"}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { ++ var patchStat *gomonkey.Patches ++ if tt.name == "unknow error" { ++ patchStat = gomonkey.ApplyFunc(os.Stat, func(name string) (fs.FileInfo, error) { ++ return fs.FileInfo(nil), fmt.Errorf("error") ++ }) ++ } + if err := certExist(tt.args.certFile); (err != nil) != tt.wantErr { + t.Errorf("certExist() error = %v, wantErr %v", err, tt.wantErr) + } ++ if tt.name == "unknow error" { ++ patchStat.Reset() ++ } + }) + } + defer os.RemoveAll("/etc/KubeOS/") +@@ -396,8 +409,17 @@ func Test_diskHandler_getRootfsArchive(t *testing.T) { + want: "/persist/update.img", + wantErr: false, + }, ++ { ++ name: "error", d: diskHandler{}, ++ args: args{req: &pb.UpdateRequest{ImageUrl: "http://www.openeuler.org/zh/"}, neededPath: preparePath{}}, ++ want: "", ++ wantErr: true, ++ }, + } +- patchDownload := gomonkey.ApplyFuncReturn(download, "/persist/update.img", nil) ++ patchDownload := gomonkey.ApplyFuncSeq(download, []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{"/persist/update.img", nil}}, ++ {Values: gomonkey.Params{"", fmt.Errorf("error")}}, ++ }) + defer patchDownload.Reset() + patchCheckSumMatch := gomonkey.ApplyFuncReturn(checkSumMatch, nil) + defer patchCheckSumMatch.Reset() +@@ -415,3 +437,35 @@ func Test_diskHandler_getRootfsArchive(t *testing.T) { + }) + } + } ++ ++func Test_diskHandler_downloadImage(t *testing.T) { ++ type args struct { ++ req *pb.UpdateRequest ++ } ++ tests := []struct { ++ name string ++ d diskHandler ++ args args ++ want string ++ wantErr bool ++ }{ ++ {name: "normal", d: diskHandler{}, args: args{req: &pb.UpdateRequest{ImageUrl: "http://www.openeuler.org/zh/"}}, want: "/persist/update.img", wantErr: false}, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ d := diskHandler{} ++ patchGetRootfsArchive := gomonkey.ApplyPrivateMethod(reflect.TypeOf(d), "getRootfsArchive", func(_ *diskHandler, _ *pb.UpdateRequest, _ preparePath) (string, error) { ++ return "/persist/update.img", nil ++ }) ++ got, err := d.downloadImage(tt.args.req) ++ if (err != nil) != tt.wantErr { ++ t.Errorf("diskHandler.downloadImage() error = %v, wantErr %v", err, tt.wantErr) ++ return ++ } ++ if got != tt.want { ++ t.Errorf("diskHandler.downloadImage() = %v, want %v", got, tt.want) ++ } ++ patchGetRootfsArchive.Reset() ++ }) ++ } ++} +diff --git a/cmd/agent/server/server_test.go b/cmd/agent/server/server_test.go +index 74e2ead..15b6f5e 100644 +--- a/cmd/agent/server/server_test.go ++++ b/cmd/agent/server/server_test.go +@@ -311,7 +311,6 @@ func TestServer_Configure(t *testing.T) { + want *pb.ConfigureResponse + wantErr bool + }{ +- // TODO: Add test cases. + { + name: "nil", + fields: fields{UnimplementedOSServer: pb.UnimplementedOSServer{}, disableReboot: true}, +diff --git a/cmd/agent/server/utils_test.go b/cmd/agent/server/utils_test.go +index 0796bce..da53c0e 100644 +--- a/cmd/agent/server/utils_test.go ++++ b/cmd/agent/server/utils_test.go +@@ -15,6 +15,7 @@ package server + + import ( + "archive/tar" ++ "fmt" + "os" + "os/exec" + "reflect" +@@ -58,12 +59,14 @@ func Test_install(t *testing.T) { + }{ + {name: "normal uefi", args: args{imagePath: "aa.txt", side: "/dev/sda3", next: "A"}, wantErr: false}, + {name: "normal legacy", args: args{imagePath: "aa.txt", side: "/dev/sda3", next: "A"}, wantErr: false}, ++ {name: "get boot mode error", args: args{imagePath: "aa.txt", side: "/dev/sda3", next: "A"}, wantErr: true}, + } + patchRunCommand := gomonkey.ApplyFuncReturn(runCommand, nil) + defer patchRunCommand.Reset() + patchGetBootMode := gomonkey.ApplyFuncSeq(getBootMode, []gomonkey.OutputCell{ + {Values: gomonkey.Params{"uefi", nil}}, + {Values: gomonkey.Params{"legacy", nil}}, ++ {Values: gomonkey.Params{"", fmt.Errorf("error")}}, + }) + defer patchGetBootMode.Reset() + for _, tt := range tests { +@@ -89,10 +92,12 @@ func Test_getNextPart(t *testing.T) { + }{ + {name: "switch to sda3", args: args{partA: "/dev/sda2", partB: "/dev/sda3"}, want: "/dev/sda3", want1: "B", wantErr: false}, + {name: "switch to sda2", args: args{partA: "/dev/sda2", partB: "/dev/sda3"}, want: "/dev/sda2", want1: "A", wantErr: false}, ++ {name: "error", args: args{partA: "/dev/sda2", partB: "/dev/sda3"}, want: "", want1: "", wantErr: true}, + } + patchExecCommand := gomonkey.ApplyMethodSeq(&exec.Cmd{}, "CombinedOutput", []gomonkey.OutputCell{ + {Values: gomonkey.Params{[]byte("/"), nil}}, + {Values: gomonkey.Params{[]byte(""), nil}}, ++ {Values: gomonkey.Params{[]byte(""), fmt.Errorf("error")}}, + }) + defer patchExecCommand.Reset() + for _, tt := range tests { +@@ -242,10 +247,16 @@ func Test_getBootMode(t *testing.T) { + want: "legacy", + wantErr: false, + }, ++ { ++ name: "error", ++ want: "", ++ wantErr: true, ++ }, + } + patchOSStat := gomonkey.ApplyFuncSeq(os.Stat, []gomonkey.OutputCell{ + {Values: gomonkey.Params{nil, nil}}, + {Values: gomonkey.Params{nil, os.ErrNotExist}}, ++ {Values: gomonkey.Params{nil, fmt.Errorf("fake error")}}, + }) + defer patchOSStat.Reset() + for _, tt := range tests { +@@ -326,3 +337,104 @@ func Test_checkOCIImageDigestMatch(t *testing.T) { + }) + } + } ++ ++func Test_runCommandWithOut(t *testing.T) { ++ type args struct { ++ name string ++ args []string ++ } ++ tests := []struct { ++ name string ++ args args ++ want string ++ wantErr bool ++ }{ ++ {name: "error", args: args{name: "/mmm", args: []string{"", ""}}, wantErr: true}, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ got, err := runCommandWithOut(tt.args.name, tt.args.args...) ++ if (err != nil) != tt.wantErr { ++ t.Errorf("runCommandWithOut() error = %v, wantErr %v", err, tt.wantErr) ++ return ++ } ++ if got != tt.want { ++ t.Errorf("runCommandWithOut() = %v, want %v", got, tt.want) ++ } ++ }) ++ } ++} ++ ++func Test_getRootfsDisks(t *testing.T) { ++ tests := []struct { ++ name string ++ want string ++ want1 string ++ wantErr bool ++ }{ ++ {name: "error", want: "", want1: "", wantErr: true}, ++ } ++ patchRunCommandWithOut := gomonkey.ApplyFuncSeq(runCommandWithOut, []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{"", fmt.Errorf("fake error")}}, ++ }) ++ defer patchRunCommandWithOut.Reset() ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ got, got1, err := getRootfsDisks() ++ if (err != nil) != tt.wantErr { ++ t.Errorf("getRootfsDisks() error = %v, wantErr %v", err, tt.wantErr) ++ return ++ } ++ if got != tt.want { ++ t.Errorf("getRootfsDisks() got = %v, want %v", got, tt.want) ++ } ++ if got1 != tt.want1 { ++ t.Errorf("getRootfsDisks() got1 = %v, want %v", got1, tt.want1) ++ } ++ }) ++ } ++} ++ ++func Test_checkDiskSize(t *testing.T) { ++ type args struct { ++ needGBSize int ++ path string ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ {name: "zero GB need", args: args{needGBSize: 0, path: "/dev/sda"}, wantErr: false}, ++ {name: "disk not enough", args: args{needGBSize: 100000, path: "/dev/sda"}, wantErr: true}, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := checkDiskSize(tt.args.needGBSize, tt.args.path); (err != nil) != tt.wantErr { ++ t.Errorf("checkDiskSize() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} ++ ++func Test_deleteFile(t *testing.T) { ++ type args struct { ++ path string ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ {name: "error", args: args{path: "/mmm"}, wantErr: true}, ++ } ++ patchStat := gomonkey.ApplyFuncReturn(os.Stat, nil, fmt.Errorf("fake error")) ++ defer patchStat.Reset() ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := deleteFile(tt.args.path); (err != nil) != tt.wantErr { ++ t.Errorf("deleteFile() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} +diff --git a/cmd/operator/controllers/os_controller_test.go b/cmd/operator/controllers/os_controller_test.go +index e59ce7e..6cc2760 100644 +--- a/cmd/operator/controllers/os_controller_test.go ++++ b/cmd/operator/controllers/os_controller_test.go +@@ -14,17 +14,22 @@ package controllers + + import ( + "context" ++ "fmt" ++ "reflect" + "testing" + "time" + ++ "github.com/agiledragon/gomonkey/v2" + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ++ corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ++ "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" +- + upgradev1 "openeuler.org/KubeOS/api/v1alpha1" ++ "openeuler.org/KubeOS/pkg/common" + "openeuler.org/KubeOS/pkg/values" + ) + +@@ -775,3 +780,228 @@ func Test_deepCopySpecConfigs(t *testing.T) { + }) + } + } ++ ++func Test_getConfigOSInstances(t *testing.T) { ++ type args struct { ++ ctx context.Context ++ r common.ReadStatusWriter ++ } ++ tests := []struct { ++ name string ++ args args ++ want []upgradev1.OSInstance ++ wantErr bool ++ }{ ++ { ++ name: "list error", ++ args: args{ ++ ctx: context.Background(), ++ r: &OSReconciler{}, ++ }, ++ want: nil, ++ wantErr: true, ++ }, ++ } ++ patchList := gomonkey.ApplyMethodSeq(&OSReconciler{}, "List", []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{fmt.Errorf("list error")}}, ++ }) ++ defer patchList.Reset() ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ got, err := getConfigOSInstances(tt.args.ctx, tt.args.r) ++ if (err != nil) != tt.wantErr { ++ t.Errorf("getConfigOSInstances() error = %v, wantErr %v", err, tt.wantErr) ++ return ++ } ++ if !reflect.DeepEqual(got, tt.want) { ++ t.Errorf("getConfigOSInstances() = %v, want %v", got, tt.want) ++ } ++ }) ++ } ++} ++ ++func Test_checkUpgrading(t *testing.T) { ++ type args struct { ++ ctx context.Context ++ r common.ReadStatusWriter ++ maxUnavailable int ++ } ++ tests := []struct { ++ name string ++ args args ++ want int ++ wantErr bool ++ }{ ++ { ++ name: "label error", ++ args: args{ ++ ctx: context.Background(), ++ r: &OSReconciler{}, ++ }, ++ want: 0, ++ wantErr: true, ++ }, ++ } ++ patchNewRequirement := gomonkey.ApplyFuncSeq(labels.NewRequirement, []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{nil, fmt.Errorf("label error")}}, ++ {Values: gomonkey.Params{nil, nil}}, ++ }) ++ defer patchNewRequirement.Reset() ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ got, err := checkUpgrading(tt.args.ctx, tt.args.r, tt.args.maxUnavailable) ++ if (err != nil) != tt.wantErr { ++ t.Errorf("checkUpgrading() error = %v, wantErr %v", err, tt.wantErr) ++ return ++ } ++ if got != tt.want { ++ t.Errorf("checkUpgrading() = %v, want %v", got, tt.want) ++ } ++ }) ++ } ++} ++ ++func Test_getIdleOSInstances(t *testing.T) { ++ type args struct { ++ ctx context.Context ++ r common.ReadStatusWriter ++ limit int ++ } ++ tests := []struct { ++ name string ++ args args ++ want []upgradev1.OSInstance ++ wantErr bool ++ }{ ++ { ++ name: "list error", ++ args: args{ ++ ctx: context.Background(), ++ r: &OSReconciler{}, ++ limit: 1, ++ }, ++ want: nil, ++ wantErr: true, ++ }, ++ } ++ patchList := gomonkey.ApplyMethodSeq(&OSReconciler{}, "List", []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{fmt.Errorf("list error")}}, ++ }) ++ defer patchList.Reset() ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ got, err := getIdleOSInstances(tt.args.ctx, tt.args.r, tt.args.limit) ++ if (err != nil) != tt.wantErr { ++ t.Errorf("getIdleOSInstances() error = %v, wantErr %v", err, tt.wantErr) ++ return ++ } ++ if !reflect.DeepEqual(got, tt.want) { ++ t.Errorf("getIdleOSInstances() = %v, want %v", got, tt.want) ++ } ++ }) ++ } ++} ++ ++func Test_getNodes(t *testing.T) { ++ type args struct { ++ ctx context.Context ++ r common.ReadStatusWriter ++ limit int ++ reqs []labels.Requirement ++ } ++ tests := []struct { ++ name string ++ args args ++ want []corev1.Node ++ wantErr bool ++ }{ ++ { ++ name: "list error", ++ args: args{ ++ ctx: context.Background(), ++ r: &OSReconciler{}, ++ limit: 1, ++ }, ++ want: nil, ++ wantErr: true, ++ }, ++ } ++ patchList := gomonkey.ApplyMethodSeq(&OSReconciler{}, "List", []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{fmt.Errorf("list error")}}, ++ }) ++ defer patchList.Reset() ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ got, err := getNodes(tt.args.ctx, tt.args.r, tt.args.limit, tt.args.reqs...) ++ if (err != nil) != tt.wantErr { ++ t.Errorf("getNodes() error = %v, wantErr %v", err, tt.wantErr) ++ return ++ } ++ if !reflect.DeepEqual(got, tt.want) { ++ t.Errorf("getNodes() = %v, want %v", got, tt.want) ++ } ++ }) ++ } ++} ++ ++func Test_getAndUpdateOS(t *testing.T) { ++ type args struct { ++ ctx context.Context ++ r common.ReadStatusWriter ++ name types.NamespacedName ++ } ++ tests := []struct { ++ name string ++ args args ++ wantOs upgradev1.OS ++ wantNodeNum int ++ wantErr bool ++ }{ ++ { ++ name: "label error", ++ args: args{ ++ ctx: context.Background(), ++ r: &OSReconciler{}, ++ name: types.NamespacedName{Namespace: "test_ns", Name: "test"}, ++ }, ++ wantOs: upgradev1.OS{}, ++ wantNodeNum: 0, ++ wantErr: true, ++ }, ++ { ++ name: "get nodes error", ++ args: args{ ++ ctx: context.Background(), ++ r: &OSReconciler{}, ++ name: types.NamespacedName{Namespace: "test_ns", Name: "test"}, ++ }, ++ wantOs: upgradev1.OS{}, ++ wantNodeNum: 0, ++ wantErr: true, ++ }, ++ } ++ patchGet := gomonkey.ApplyMethodReturn(&OSReconciler{}, "Get", nil) ++ defer patchGet.Reset() ++ patchNewRequirement := gomonkey.ApplyFuncSeq(labels.NewRequirement, []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{nil, fmt.Errorf("label error")}}, ++ {Values: gomonkey.Params{&labels.Requirement{}, nil}}, ++ }) ++ defer patchNewRequirement.Reset() ++ patchGetNodes := gomonkey.ApplyFuncReturn(getNodes, nil, fmt.Errorf("get nodes error")) ++ defer patchGetNodes.Reset() ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ gotOs, gotNodeNum, err := getAndUpdateOS(tt.args.ctx, tt.args.r, tt.args.name) ++ if (err != nil) != tt.wantErr { ++ t.Errorf("getAndUpdateOS() error = %v, wantErr %v", err, tt.wantErr) ++ return ++ } ++ if !reflect.DeepEqual(gotOs, tt.wantOs) { ++ t.Errorf("getAndUpdateOS() gotOs = %v, want %v", gotOs, tt.wantOs) ++ } ++ if gotNodeNum != tt.wantNodeNum { ++ t.Errorf("getAndUpdateOS() gotNodeNum = %v, want %v", gotNodeNum, tt.wantNodeNum) ++ } ++ }) ++ } ++} +diff --git a/cmd/proxy/controllers/os_controller_test.go b/cmd/proxy/controllers/os_controller_test.go +index 27cb0cd..14b6b66 100644 +--- a/cmd/proxy/controllers/os_controller_test.go ++++ b/cmd/proxy/controllers/os_controller_test.go +@@ -16,25 +16,27 @@ import ( + "context" + "fmt" + "reflect" ++ "testing" + "time" + + "github.com/agiledragon/gomonkey/v2" + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ++ corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +- ++ "k8s.io/kubectl/pkg/drain" + upgradev1 "openeuler.org/KubeOS/api/v1alpha1" + "openeuler.org/KubeOS/pkg/agentclient" ++ "openeuler.org/KubeOS/pkg/common" + "openeuler.org/KubeOS/pkg/values" + ) + + var _ = Describe("OsController", func() { + const ( +- OSName = "test-os" +- ++ OSName = "test-os" + timeout = time.Second * 20 + interval = time.Millisecond * 500 + ) +@@ -67,6 +69,24 @@ var _ = Describe("OsController", func() { + testNamespace = existingNamespace.Name + }) + ++ AfterEach(func() { ++ // delete all OS CRs ++ osList := &upgradev1.OSList{} ++ err := k8sClient.List(context.Background(), osList) ++ Expect(err).ToNot(HaveOccurred()) ++ for _, os := range osList.Items { ++ k8sClient.Delete(context.Background(), &os) ++ } ++ osList = &upgradev1.OSList{} ++ Eventually(func() bool { ++ err = k8sClient.List(context.Background(), osList) ++ if err != nil || len(osList.Items) != 0 { ++ return false ++ } ++ return true ++ }, timeout, interval).Should(BeTrue()) ++ }) ++ + Context("When we want to rollback", func() { + It("Should be able to rollback to previous version", func() { + ctx := context.Background() +@@ -182,6 +202,13 @@ var _ = Describe("OsController", func() { + Expect(k8sClient.Status().Update(ctx, existingNode)).Should(Succeed()) + + By("Changing the OS Spec config to trigger reconcile") ++ createdOSIns = &upgradev1.OSInstance{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osInsCRLookupKey, createdOSIns) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ createdOSIns.Spec.SysConfigs = upgradev1.SysConfigs{Version: "v1", Configs: []upgradev1.SysConfig{}} ++ Expect(k8sClient.Update(ctx, createdOSIns)).Should(Succeed()) + createdOS = &upgradev1.OS{} + Eventually(func() bool { + err := k8sClient.Get(ctx, osCRLookupKey, createdOS) +@@ -190,7 +217,7 @@ var _ = Describe("OsController", func() { + createdOS.Spec.SysConfigs = upgradev1.SysConfigs{Version: "v1", Configs: []upgradev1.SysConfig{}} + Expect(k8sClient.Update(ctx, createdOS)).Should(Succeed()) + +- time.Sleep(2 * time.Second) // sleep a while to make sure Reconcile finished ++ time.Sleep(1 * time.Second) // sleep a while to make sure Reconcile finished + createdOSIns = &upgradev1.OSInstance{} + Eventually(func() bool { + err := k8sClient.Get(ctx, osInsCRLookupKey, createdOSIns) +@@ -335,7 +362,7 @@ var _ = Describe("OsController", func() { + Expect(createdOS.Spec.OSVersion).Should(Equal("KubeOS v1")) + + By("Checking the OSInstance status config version") +- time.Sleep(2 * time.Second) // sleep a while to make sure Reconcile finished ++ time.Sleep(1 * time.Second) // sleep a while to make sure Reconcile finished + osInsCRLookupKey = types.NamespacedName{Name: node1Name, Namespace: testNamespace} + createdOSIns = &upgradev1.OSInstance{} + Eventually(func() bool { +@@ -451,7 +478,7 @@ var _ = Describe("OsController", func() { + OSVersion: "KubeOS v2", + FlagSafe: true, + MTLS: false, +- EvictPodForce: true, ++ EvictPodForce: false, + SysConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}}, + UpgradeConfigs: upgradev1.SysConfigs{ + Version: "v2", +@@ -478,7 +505,7 @@ var _ = Describe("OsController", func() { + Expect(createdOS.Spec.OSVersion).Should(Equal("KubeOS v2")) + + By("Checking the OSInstance status config version") +- time.Sleep(2 * time.Second) // sleep a while to make sure Reconcile finished ++ time.Sleep(1 * time.Second) // sleep a while to make sure Reconcile finished + osInsCRLookupKey = types.NamespacedName{Name: node1Name, Namespace: testNamespace} + createdOSIns = &upgradev1.OSInstance{} + Eventually(func() bool { +@@ -566,7 +593,7 @@ var _ = Describe("OsController", func() { + Expect(createdOS.Spec.OSVersion).Should(Equal("KubeOS v1")) + + By("Checking the existence of new OSInstance") +- time.Sleep(2 * time.Second) // sleep a while to make sure Reconcile finished ++ time.Sleep(1 * time.Second) // sleep a while to make sure Reconcile finished + osInsCRLookupKey := types.NamespacedName{Name: node1Name, Namespace: testNamespace} + createdOSIns := &upgradev1.OSInstance{} + Eventually(func() bool { +@@ -644,7 +671,7 @@ var _ = Describe("OsController", func() { + }, + UpgradeConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}}, + }, +- Status: upgradev1.OSInstanceStatus{}, ++ Status: upgradev1.OSInstanceStatus{SysConfigs: upgradev1.SysConfigs{Version: "v1"}}, + } + Expect(k8sClient.Create(ctx, OSIns)).Should(Succeed()) + +@@ -711,7 +738,7 @@ var _ = Describe("OsController", func() { + Expect(createdOS.Spec.OSVersion).Should(Equal("KubeOS v1")) + + By("Checking the OSInstance status config version failed to be updated") +- time.Sleep(2 * time.Second) // sleep a while to make sure Reconcile finished ++ time.Sleep(1 * time.Second) // sleep a while to make sure Reconcile finished + osInsCRLookupKey = types.NamespacedName{Name: node1Name, Namespace: testNamespace} + createdOSIns = &upgradev1.OSInstance{} + Eventually(func() bool { +@@ -721,10 +748,18 @@ var _ = Describe("OsController", func() { + Expect(createdOSIns.Status.SysConfigs.Version).Should(Equal("v1")) + Expect(createdOSIns.Spec.SysConfigs.Version).Should(Equal("v2")) + +- By("Changing the OS Spec config version to previous one") ++ By("Changing the OS and OSi Spec config version to previous one") + OS.Spec.SysConfigs = upgradev1.SysConfigs{Version: "v1", Configs: []upgradev1.SysConfig{}} + Expect(k8sClient.Update(ctx, OS)).Should(Succeed()) +- time.Sleep(1 * time.Second) // sleep a while to make sure Reconcile finished ++ createdOSIns = &upgradev1.OSInstance{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osInsCRLookupKey, createdOSIns) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ createdOSIns.Spec.SysConfigs = upgradev1.SysConfigs{Version: "v1", Configs: []upgradev1.SysConfig{}} ++ Expect(k8sClient.Update(ctx, createdOSIns)).Should(Succeed()) ++ ++ time.Sleep(2 * time.Second) // sleep a while to make sure Reconcile finished + createdOSIns = &upgradev1.OSInstance{} + Eventually(func() bool { + err := k8sClient.Get(ctx, osInsCRLookupKey, createdOSIns) +@@ -799,7 +834,6 @@ var _ = Describe("OsController", func() { + }, + }, + }, +- Status: upgradev1.OSInstanceStatus{}, + } + Expect(k8sClient.Create(ctx, OSIns)).Should(Succeed()) + +@@ -870,7 +904,7 @@ var _ = Describe("OsController", func() { + Expect(createdOS.Spec.OSVersion).Should(Equal("KubeOS v2")) + + By("Checking the OSInstance status config version failed to be updated") +- time.Sleep(2 * time.Second) // sleep a while to make sure Reconcile finished ++ time.Sleep(1 * time.Second) // sleep a while to make sure Reconcile finished + osInsCRLookupKey = types.NamespacedName{Name: node1Name, Namespace: testNamespace} + createdOSIns = &upgradev1.OSInstance{} + Eventually(func() bool { +@@ -889,6 +923,16 @@ var _ = Describe("OsController", func() { + err := k8sClient.Get(ctx, osInsCRLookupKey, createdOSIns) + return err == nil + }, timeout, interval).Should(BeTrue()) ++ ++ osInsCRLookupKey = types.NamespacedName{Name: node1Name, Namespace: testNamespace} ++ createdOSIns = &upgradev1.OSInstance{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osInsCRLookupKey, createdOSIns) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ createdOSIns.Spec.UpgradeConfigs = upgradev1.SysConfigs{Version: "v1", Configs: []upgradev1.SysConfig{}} ++ Expect(k8sClient.Update(ctx, createdOSIns)).Should(Succeed()) ++ + // NodeStatus changes to idle then operator can reassign configs to this node + Expect(createdOSIns.Spec.NodeStatus).Should(Equal(values.NodeStatusIdle.String())) + existingNode = &v1.Node{} +@@ -906,37 +950,7 @@ var _ = Describe("OsController", func() { + It("Should be able to rollback to previous config version to jump out of error state", func() { + ctx := context.Background() + +- By("Creating a worker node") + node1Name = "test-node-" + uuid.New().String() +- node1 := &v1.Node{ +- ObjectMeta: metav1.ObjectMeta{ +- Name: node1Name, +- Namespace: testNamespace, +- Labels: map[string]string{ +- "beta.kubernetes.io/os": "linux", +- values.LabelUpgrading: "", +- }, +- }, +- TypeMeta: metav1.TypeMeta{ +- APIVersion: "v1", +- Kind: "Node", +- }, +- Status: v1.NodeStatus{ +- NodeInfo: v1.NodeSystemInfo{ +- OSImage: "KubeOS v2", +- }, +- }, +- } +- err := k8sClient.Create(ctx, node1) +- Expect(err).ToNot(HaveOccurred()) +- existingNode := &v1.Node{} +- Eventually(func() bool { +- err := k8sClient.Get(context.Background(), +- types.NamespacedName{Name: node1Name, Namespace: testNamespace}, existingNode) +- return err == nil +- }, timeout, interval).Should(BeTrue()) +- reconciler.hostName = node1Name +- + By("Creating the corresponding OSInstance") + OSIns := &upgradev1.OSInstance{ + TypeMeta: metav1.TypeMeta{ +@@ -1004,6 +1018,37 @@ var _ = Describe("OsController", func() { + createdOSIns.Status.SysConfigs.Version = "v1" + Expect(k8sClient.Status().Update(ctx, createdOSIns)).Should(Succeed()) + Expect(createdOSIns.Status.UpgradeConfigs.Version).Should(Equal("v2")) ++ Expect(createdOSIns.Status.SysConfigs.Version).Should(Equal("v1")) ++ ++ By("Creating a worker node") ++ node1 := &v1.Node{ ++ ObjectMeta: metav1.ObjectMeta{ ++ Name: node1Name, ++ Namespace: testNamespace, ++ Labels: map[string]string{ ++ "beta.kubernetes.io/os": "linux", ++ values.LabelUpgrading: "", ++ }, ++ }, ++ TypeMeta: metav1.TypeMeta{ ++ APIVersion: "v1", ++ Kind: "Node", ++ }, ++ Status: v1.NodeStatus{ ++ NodeInfo: v1.NodeSystemInfo{ ++ OSImage: "KubeOS v2", ++ }, ++ }, ++ } ++ err := k8sClient.Create(ctx, node1) ++ Expect(err).ToNot(HaveOccurred()) ++ existingNode := &v1.Node{} ++ Eventually(func() bool { ++ err := k8sClient.Get(context.Background(), ++ types.NamespacedName{Name: node1Name, Namespace: testNamespace}, existingNode) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ reconciler.hostName = node1Name + + // stub r.Connection.ConfigureSpec() + patchConfigure := gomonkey.ApplyMethod(reflect.TypeOf(reconciler.Connection), +@@ -1067,7 +1112,6 @@ var _ = Describe("OsController", func() { + + By("Checking the OSInstance status config version failed to be updated") + time.Sleep(1 * time.Second) // sleep a while to make sure Reconcile finished +- osInsCRLookupKey = types.NamespacedName{Name: node1Name, Namespace: testNamespace} + createdOSIns = &upgradev1.OSInstance{} + Eventually(func() bool { + err := k8sClient.Get(ctx, osInsCRLookupKey, createdOSIns) +@@ -1077,8 +1121,21 @@ var _ = Describe("OsController", func() { + Expect(createdOSIns.Spec.SysConfigs.Version).Should(Equal("v2")) + + By("Changing the OS Spec config version to previous one") +- OS.Spec.SysConfigs = upgradev1.SysConfigs{Version: "v1", Configs: []upgradev1.SysConfig{}} +- Expect(k8sClient.Update(ctx, OS)).Should(Succeed()) ++ createdOS = &upgradev1.OS{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osCRLookupKey, createdOS) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ createdOS.Spec.SysConfigs = upgradev1.SysConfigs{Version: "v1", Configs: []upgradev1.SysConfig{}} ++ Expect(k8sClient.Update(ctx, createdOS)).Should(Succeed()) ++ getOSIns := &upgradev1.OSInstance{} ++ Eventually(func() bool { ++ err := k8sClient.Get(ctx, osInsCRLookupKey, getOSIns) ++ return err == nil ++ }, timeout, interval).Should(BeTrue()) ++ getOSIns.Spec.SysConfigs = upgradev1.SysConfigs{Version: "v1", Configs: []upgradev1.SysConfig{}} ++ Expect(k8sClient.Update(ctx, getOSIns)).Should(Succeed()) ++ + time.Sleep(2 * time.Second) // sleep a while to make sure Reconcile finished + createdOSIns = &upgradev1.OSInstance{} + Eventually(func() bool { +@@ -1225,3 +1282,147 @@ var _ = Describe("OsController", func() { + }) + }) + }) ++ ++func Test_evictNode(t *testing.T) { ++ type args struct { ++ drainer *drain.Helper ++ node *corev1.Node ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ { ++ name: "node unschedulable", ++ args: args{ ++ drainer: &drain.Helper{}, ++ node: &corev1.Node{Spec: v1.NodeSpec{Unschedulable: true}}, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "runCordonError1", ++ args: args{ ++ drainer: &drain.Helper{}, ++ node: &corev1.Node{}, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "runNodeDrainError", ++ args: args{ ++ drainer: &drain.Helper{}, ++ node: &corev1.Node{}, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "runUncordonError2", ++ args: args{ ++ drainer: &drain.Helper{}, ++ node: &corev1.Node{}, ++ }, ++ wantErr: true, ++ }, ++ } ++ patchRunCordon := gomonkey.ApplyFuncSeq(drain.RunCordonOrUncordon, []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{fmt.Errorf("cordon error")}}, ++ {Values: gomonkey.Params{nil}}, ++ {Values: gomonkey.Params{fmt.Errorf("cordon error")}}, ++ {Values: gomonkey.Params{nil}}, ++ {Values: gomonkey.Params{nil}}, ++ }) ++ defer patchRunCordon.Reset() ++ patchRunNodeDrain := gomonkey.ApplyFuncSeq(drain.RunNodeDrain, []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{fmt.Errorf("node drain error")}}, ++ {Values: gomonkey.Params{fmt.Errorf("node drain error")}}, ++ }) ++ defer patchRunNodeDrain.Reset() ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := evictNode(tt.args.drainer, tt.args.node); (err != nil) != tt.wantErr { ++ t.Errorf("evictNode() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} ++ ++func Test_updateConfigStatus(t *testing.T) { ++ type args struct { ++ ctx context.Context ++ r common.ReadStatusWriter ++ osInstance *upgradev1.OSInstance ++ configType string ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ { ++ name: "invalid config type", ++ args: args{ ++ ctx: context.Background(), ++ r: &OSReconciler{}, ++ osInstance: &upgradev1.OSInstance{}, ++ configType: "invalid", ++ }, ++ wantErr: true, ++ }, ++ } ++ patchUpdate := gomonkey.ApplyMethodReturn(&OSReconciler{}, "Update", fmt.Errorf("update error")) ++ patchStatus := gomonkey.ApplyMethodReturn(&OSReconciler{}, "Status", &OSReconciler{}) ++ defer patchUpdate.Reset() ++ defer patchStatus.Reset() ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := updateConfigStatus(tt.args.ctx, tt.args.r, tt.args.osInstance, tt.args.configType); (err != nil) != tt.wantErr { ++ t.Errorf("updateConfigStatus() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} ++ ++func Test_getOSAndNodeStatus(t *testing.T) { ++ type args struct { ++ ctx context.Context ++ r common.ReadStatusWriter ++ name types.NamespacedName ++ hostName string ++ } ++ tests := []struct { ++ name string ++ args args ++ wantOS upgradev1.OS ++ wantNode corev1.Node ++ }{ ++ { ++ name: "get node error", ++ args: args{ ++ ctx: context.Background(), ++ r: &OSReconciler{}, ++ name: types.NamespacedName{}, ++ hostName: "test-node", ++ }, ++ wantOS: upgradev1.OS{}, ++ wantNode: corev1.Node{}, ++ }, ++ } ++ patchGet := gomonkey.ApplyMethodSeq(&OSReconciler{}, "Get", []gomonkey.OutputCell{ ++ {Values: gomonkey.Params{nil}}, ++ {Values: gomonkey.Params{fmt.Errorf("get node error")}}, ++ }) ++ defer patchGet.Reset() ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ gotOS, gotNode := getOSAndNodeStatus(tt.args.ctx, tt.args.r, tt.args.name, tt.args.hostName) ++ if !reflect.DeepEqual(gotOS, tt.wantOS) { ++ t.Errorf("getOSAndNodeStatus() gotOS = %v, want %v", gotOS, tt.wantOS) ++ } ++ if !reflect.DeepEqual(gotNode, tt.wantNode) { ++ t.Errorf("getOSAndNodeStatus() gotNode = %v, want %v", gotNode, tt.wantNode) ++ } ++ }) ++ } ++} +-- +2.39.0 + diff --git a/0020-KubeOS-modify-code-for-clean-code.patch b/0020-KubeOS-modify-code-for-clean-code.patch new file mode 100644 index 0000000..6e6bb6c --- /dev/null +++ b/0020-KubeOS-modify-code-for-clean-code.patch @@ -0,0 +1,86 @@ +From a9220ad3175c38979a052cd595efd34888d886c6 Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Thu, 24 Aug 2023 11:00:04 +0800 +Subject: [PATCH 3/5] KubeOS: modify code for clean code + +add err handler in NewControllerManager + +Signed-off-by: Yuhang Wei +--- + cmd/operator/main.go | 6 +++++- + cmd/proxy/main.go | 8 ++++++-- + pkg/common/common.go | 7 +++---- + 3 files changed, 14 insertions(+), 7 deletions(-) + +diff --git a/cmd/operator/main.go b/cmd/operator/main.go +index 17b74e1..8249ad2 100644 +--- a/cmd/operator/main.go ++++ b/cmd/operator/main.go +@@ -41,7 +41,11 @@ func init() { + } + + func main() { +- mgr := common.NewControllerManager(setupLog, scheme) ++ mgr, err := common.NewControllerManager(setupLog, scheme) ++ if err != nil { ++ setupLog.Error(err, "unable to start manager") ++ os.Exit(1) ++ } + + if err := (&controllers.OSReconciler{ + Client: mgr.GetClient(), +diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go +index ce1f58d..3a537d9 100644 +--- a/cmd/proxy/main.go ++++ b/cmd/proxy/main.go +@@ -44,10 +44,14 @@ func init() { + } + + func main() { +- mgr := common.NewControllerManager(setupLog, scheme) ++ var err error ++ mgr, err := common.NewControllerManager(setupLog, scheme) ++ if err != nil { ++ setupLog.Error(err, "unable to start manager") ++ os.Exit(1) ++ } + + reconciler := controllers.NewOSReconciler(mgr) +- var err error + if reconciler.Connection, err = agentclient.New("unix://" + filepath.Join(server.SockDir, server.SockName)); err != nil { + setupLog.Error(err, "Error running proxy") + } +diff --git a/pkg/common/common.go b/pkg/common/common.go +index 4653a61..e888179 100644 +--- a/pkg/common/common.go ++++ b/pkg/common/common.go +@@ -15,7 +15,6 @@ package common + + import ( + "flag" +- "os" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" +@@ -33,7 +32,7 @@ type ReadStatusWriter interface { + } + + // NewControllerManager configure and return a manager +-func NewControllerManager(setupLog logr.Logger, scheme *runtime.Scheme) manager.Manager { ++func NewControllerManager(setupLog logr.Logger, scheme *runtime.Scheme) (manager.Manager, error) { + opts := zap.Options{} + opts.BindFlags(flag.CommandLine) + +@@ -46,7 +45,7 @@ func NewControllerManager(setupLog logr.Logger, scheme *runtime.Scheme) manager. + }) + if err != nil { + setupLog.Error(err, "unable to start manager") +- os.Exit(1) ++ return nil, err + } +- return mgr ++ return mgr, nil + } +-- +2.39.0 + diff --git a/0021-KubeOS-delete-raw-and-docker-image-upgrade.patch b/0021-KubeOS-delete-raw-and-docker-image-upgrade.patch new file mode 100644 index 0000000..cd248bf --- /dev/null +++ b/0021-KubeOS-delete-raw-and-docker-image-upgrade.patch @@ -0,0 +1,1867 @@ +From 5c9609914a7418ea34d1ff15dfc21877fb197763 Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Thu, 24 Aug 2023 11:00:26 +0800 +Subject: [PATCH 4/5] KubeOS: delete raw and docker image upgrade + +delete the part of using raw and docker image to upgrade + +Signed-off-by: Yuhang Wei +--- + api/v1alpha1/os_types.go | 9 - + cmd/agent/api/agent.pb.go | 337 ++++--------- + cmd/agent/api/agent.proto | 15 +- + cmd/agent/server/disk_image.go | 217 -------- + cmd/agent/server/disk_image_test.go | 471 ------------------ + cmd/agent/server/docker_image.go | 73 --- + cmd/agent/server/docker_image_test.go | 118 ----- + cmd/agent/server/server.go | 8 - + cmd/agent/server/server_test.go | 23 +- + .../controllers/os_controller_test.go | 8 - + cmd/proxy/controllers/os_controller.go | 6 - + cmd/proxy/controllers/os_controller_test.go | 16 - + .../config/crd/upgrade.openeuler.org_os.yaml | 15 - + pkg/agentclient/connection.go | 15 - + 14 files changed, 115 insertions(+), 1216 deletions(-) + delete mode 100644 cmd/agent/server/disk_image.go + delete mode 100644 cmd/agent/server/disk_image_test.go + delete mode 100644 cmd/agent/server/docker_image.go + delete mode 100644 cmd/agent/server/docker_image_test.go + +diff --git a/api/v1alpha1/os_types.go b/api/v1alpha1/os_types.go +index f9474b7..b4655e8 100644 +--- a/api/v1alpha1/os_types.go ++++ b/api/v1alpha1/os_types.go +@@ -19,22 +19,13 @@ import ( + // OSSpec defines the desired state of OS + type OSSpec struct { + OSVersion string `json:"osversion"` +- ImageURL string `json:"imageurl"` + MaxUnavailable int `json:"maxunavailable"` + CheckSum string `json:"checksum"` +- FlagSafe bool `json:"flagSafe"` +- MTLS bool `json:"mtls"` + ImageType string `json:"imagetype"` + ContainerImage string `json:"containerimage"` + OpsType string `json:"opstype"` + EvictPodForce bool `json:"evictpodforce"` + // +kubebuilder:validation:Optional +- CaCert string `json:"cacert"` +- // +kubebuilder:validation:Optional +- ClientCert string `json:"clientcert"` +- // +kubebuilder:validation:Optional +- ClientKey string `json:"clientkey"` +- // +kubebuilder:validation:Optional + SysConfigs SysConfigs `json:"sysconfigs"` + // +kubebuilder:validation:Optional + UpgradeConfigs SysConfigs `json:"upgradeconfigs"` +diff --git a/cmd/agent/api/agent.pb.go b/cmd/agent/api/agent.pb.go +index 077a57e..034337d 100644 +--- a/cmd/agent/api/agent.pb.go ++++ b/cmd/agent/api/agent.pb.go +@@ -36,14 +36,10 @@ type UpdateRequest struct { + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + +- Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` +- ImageUrl string `protobuf:"bytes,2,opt,name=image_url,json=imageUrl,proto3" json:"image_url,omitempty"` +- CheckSum string `protobuf:"bytes,3,opt,name=check_sum,json=checkSum,proto3" json:"check_sum,omitempty"` +- FlagSafe bool `protobuf:"varint,4,opt,name=flagSafe,proto3" json:"flagSafe,omitempty"` +- MTLS bool `protobuf:"varint,5,opt,name=mTLS,proto3" json:"mTLS,omitempty"` +- ImageType string `protobuf:"bytes,6,opt,name=image_type,json=imageType,proto3" json:"image_type,omitempty"` +- ContainerImage string `protobuf:"bytes,7,opt,name=container_image,json=containerImage,proto3" json:"container_image,omitempty"` +- Certs *CertsInfo `protobuf:"bytes,8,opt,name=certs,proto3" json:"certs,omitempty"` ++ Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` ++ CheckSum string `protobuf:"bytes,2,opt,name=check_sum,json=checkSum,proto3" json:"check_sum,omitempty"` ++ ImageType string `protobuf:"bytes,3,opt,name=image_type,json=imageType,proto3" json:"image_type,omitempty"` ++ ContainerImage string `protobuf:"bytes,4,opt,name=container_image,json=containerImage,proto3" json:"container_image,omitempty"` + } + + func (x *UpdateRequest) Reset() { +@@ -85,13 +81,6 @@ func (x *UpdateRequest) GetVersion() string { + return "" + } + +-func (x *UpdateRequest) GetImageUrl() string { +- if x != nil { +- return x.ImageUrl +- } +- return "" +-} +- + func (x *UpdateRequest) GetCheckSum() string { + if x != nil { + return x.CheckSum +@@ -99,20 +88,6 @@ func (x *UpdateRequest) GetCheckSum() string { + return "" + } + +-func (x *UpdateRequest) GetFlagSafe() bool { +- if x != nil { +- return x.FlagSafe +- } +- return false +-} +- +-func (x *UpdateRequest) GetMTLS() bool { +- if x != nil { +- return x.MTLS +- } +- return false +-} +- + func (x *UpdateRequest) GetImageType() string { + if x != nil { + return x.ImageType +@@ -127,76 +102,6 @@ func (x *UpdateRequest) GetContainerImage() string { + return "" + } + +-func (x *UpdateRequest) GetCerts() *CertsInfo { +- if x != nil { +- return x.Certs +- } +- return nil +-} +- +-type CertsInfo struct { +- state protoimpl.MessageState +- sizeCache protoimpl.SizeCache +- unknownFields protoimpl.UnknownFields +- +- CaCaert string `protobuf:"bytes,1,opt,name=ca_caert,json=caCaert,proto3" json:"ca_caert,omitempty"` +- ClientCert string `protobuf:"bytes,2,opt,name=client_cert,json=clientCert,proto3" json:"client_cert,omitempty"` +- ClientKey string `protobuf:"bytes,3,opt,name=client_key,json=clientKey,proto3" json:"client_key,omitempty"` +-} +- +-func (x *CertsInfo) Reset() { +- *x = CertsInfo{} +- if protoimpl.UnsafeEnabled { +- mi := &file_api_agent_proto_msgTypes[1] +- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) +- ms.StoreMessageInfo(mi) +- } +-} +- +-func (x *CertsInfo) String() string { +- return protoimpl.X.MessageStringOf(x) +-} +- +-func (*CertsInfo) ProtoMessage() {} +- +-func (x *CertsInfo) ProtoReflect() protoreflect.Message { +- mi := &file_api_agent_proto_msgTypes[1] +- if protoimpl.UnsafeEnabled && x != nil { +- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) +- if ms.LoadMessageInfo() == nil { +- ms.StoreMessageInfo(mi) +- } +- return ms +- } +- return mi.MessageOf(x) +-} +- +-// Deprecated: Use CertsInfo.ProtoReflect.Descriptor instead. +-func (*CertsInfo) Descriptor() ([]byte, []int) { +- return file_api_agent_proto_rawDescGZIP(), []int{1} +-} +- +-func (x *CertsInfo) GetCaCaert() string { +- if x != nil { +- return x.CaCaert +- } +- return "" +-} +- +-func (x *CertsInfo) GetClientCert() string { +- if x != nil { +- return x.ClientCert +- } +- return "" +-} +- +-func (x *CertsInfo) GetClientKey() string { +- if x != nil { +- return x.ClientKey +- } +- return "" +-} +- + type UpdateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache +@@ -208,7 +113,7 @@ type UpdateResponse struct { + func (x *UpdateResponse) Reset() { + *x = UpdateResponse{} + if protoimpl.UnsafeEnabled { +- mi := &file_api_agent_proto_msgTypes[2] ++ mi := &file_api_agent_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +@@ -221,7 +126,7 @@ func (x *UpdateResponse) String() string { + func (*UpdateResponse) ProtoMessage() {} + + func (x *UpdateResponse) ProtoReflect() protoreflect.Message { +- mi := &file_api_agent_proto_msgTypes[2] ++ mi := &file_api_agent_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { +@@ -234,7 +139,7 @@ func (x *UpdateResponse) ProtoReflect() protoreflect.Message { + + // Deprecated: Use UpdateResponse.ProtoReflect.Descriptor instead. + func (*UpdateResponse) Descriptor() ([]byte, []int) { +- return file_api_agent_proto_rawDescGZIP(), []int{2} ++ return file_api_agent_proto_rawDescGZIP(), []int{1} + } + + func (x *UpdateResponse) GetErr() int32 { +@@ -253,7 +158,7 @@ type RollbackRequest struct { + func (x *RollbackRequest) Reset() { + *x = RollbackRequest{} + if protoimpl.UnsafeEnabled { +- mi := &file_api_agent_proto_msgTypes[3] ++ mi := &file_api_agent_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +@@ -266,7 +171,7 @@ func (x *RollbackRequest) String() string { + func (*RollbackRequest) ProtoMessage() {} + + func (x *RollbackRequest) ProtoReflect() protoreflect.Message { +- mi := &file_api_agent_proto_msgTypes[3] ++ mi := &file_api_agent_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { +@@ -279,7 +184,7 @@ func (x *RollbackRequest) ProtoReflect() protoreflect.Message { + + // Deprecated: Use RollbackRequest.ProtoReflect.Descriptor instead. + func (*RollbackRequest) Descriptor() ([]byte, []int) { +- return file_api_agent_proto_rawDescGZIP(), []int{3} ++ return file_api_agent_proto_rawDescGZIP(), []int{2} + } + + type RollbackResponse struct { +@@ -293,7 +198,7 @@ type RollbackResponse struct { + func (x *RollbackResponse) Reset() { + *x = RollbackResponse{} + if protoimpl.UnsafeEnabled { +- mi := &file_api_agent_proto_msgTypes[4] ++ mi := &file_api_agent_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +@@ -306,7 +211,7 @@ func (x *RollbackResponse) String() string { + func (*RollbackResponse) ProtoMessage() {} + + func (x *RollbackResponse) ProtoReflect() protoreflect.Message { +- mi := &file_api_agent_proto_msgTypes[4] ++ mi := &file_api_agent_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { +@@ -319,7 +224,7 @@ func (x *RollbackResponse) ProtoReflect() protoreflect.Message { + + // Deprecated: Use RollbackResponse.ProtoReflect.Descriptor instead. + func (*RollbackResponse) Descriptor() ([]byte, []int) { +- return file_api_agent_proto_rawDescGZIP(), []int{4} ++ return file_api_agent_proto_rawDescGZIP(), []int{3} + } + + func (x *RollbackResponse) GetErr() int32 { +@@ -340,7 +245,7 @@ type ConfigureRequest struct { + func (x *ConfigureRequest) Reset() { + *x = ConfigureRequest{} + if protoimpl.UnsafeEnabled { +- mi := &file_api_agent_proto_msgTypes[5] ++ mi := &file_api_agent_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +@@ -353,7 +258,7 @@ func (x *ConfigureRequest) String() string { + func (*ConfigureRequest) ProtoMessage() {} + + func (x *ConfigureRequest) ProtoReflect() protoreflect.Message { +- mi := &file_api_agent_proto_msgTypes[5] ++ mi := &file_api_agent_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { +@@ -366,7 +271,7 @@ func (x *ConfigureRequest) ProtoReflect() protoreflect.Message { + + // Deprecated: Use ConfigureRequest.ProtoReflect.Descriptor instead. + func (*ConfigureRequest) Descriptor() ([]byte, []int) { +- return file_api_agent_proto_rawDescGZIP(), []int{5} ++ return file_api_agent_proto_rawDescGZIP(), []int{4} + } + + func (x *ConfigureRequest) GetConfigs() []*SysConfig { +@@ -387,7 +292,7 @@ type ConfigureResponse struct { + func (x *ConfigureResponse) Reset() { + *x = ConfigureResponse{} + if protoimpl.UnsafeEnabled { +- mi := &file_api_agent_proto_msgTypes[6] ++ mi := &file_api_agent_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +@@ -400,7 +305,7 @@ func (x *ConfigureResponse) String() string { + func (*ConfigureResponse) ProtoMessage() {} + + func (x *ConfigureResponse) ProtoReflect() protoreflect.Message { +- mi := &file_api_agent_proto_msgTypes[6] ++ mi := &file_api_agent_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { +@@ -413,7 +318,7 @@ func (x *ConfigureResponse) ProtoReflect() protoreflect.Message { + + // Deprecated: Use ConfigureResponse.ProtoReflect.Descriptor instead. + func (*ConfigureResponse) Descriptor() ([]byte, []int) { +- return file_api_agent_proto_rawDescGZIP(), []int{6} ++ return file_api_agent_proto_rawDescGZIP(), []int{5} + } + + func (x *ConfigureResponse) GetErr() int32 { +@@ -436,7 +341,7 @@ type SysConfig struct { + func (x *SysConfig) Reset() { + *x = SysConfig{} + if protoimpl.UnsafeEnabled { +- mi := &file_api_agent_proto_msgTypes[7] ++ mi := &file_api_agent_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +@@ -449,7 +354,7 @@ func (x *SysConfig) String() string { + func (*SysConfig) ProtoMessage() {} + + func (x *SysConfig) ProtoReflect() protoreflect.Message { +- mi := &file_api_agent_proto_msgTypes[7] ++ mi := &file_api_agent_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { +@@ -462,7 +367,7 @@ func (x *SysConfig) ProtoReflect() protoreflect.Message { + + // Deprecated: Use SysConfig.ProtoReflect.Descriptor instead. + func (*SysConfig) Descriptor() ([]byte, []int) { +- return file_api_agent_proto_rawDescGZIP(), []int{7} ++ return file_api_agent_proto_rawDescGZIP(), []int{6} + } + + func (x *SysConfig) GetModel() string { +@@ -498,7 +403,7 @@ type KeyInfo struct { + func (x *KeyInfo) Reset() { + *x = KeyInfo{} + if protoimpl.UnsafeEnabled { +- mi := &file_api_agent_proto_msgTypes[8] ++ mi := &file_api_agent_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +@@ -511,7 +416,7 @@ func (x *KeyInfo) String() string { + func (*KeyInfo) ProtoMessage() {} + + func (x *KeyInfo) ProtoReflect() protoreflect.Message { +- mi := &file_api_agent_proto_msgTypes[8] ++ mi := &file_api_agent_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { +@@ -524,7 +429,7 @@ func (x *KeyInfo) ProtoReflect() protoreflect.Message { + + // Deprecated: Use KeyInfo.ProtoReflect.Descriptor instead. + func (*KeyInfo) Descriptor() ([]byte, []int) { +- return file_api_agent_proto_rawDescGZIP(), []int{8} ++ return file_api_agent_proto_rawDescGZIP(), []int{7} + } + + func (x *KeyInfo) GetValue() string { +@@ -545,74 +450,60 @@ var File_api_agent_proto protoreflect.FileDescriptor + + var file_api_agent_proto_rawDesc = []byte{ + 0x0a, 0x0f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, +- 0x6f, 0x12, 0x05, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x22, 0x83, 0x02, 0x0a, 0x0d, 0x55, 0x70, 0x64, ++ 0x6f, 0x12, 0x05, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x22, 0x8e, 0x01, 0x0a, 0x0d, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, +- 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x75, 0x72, +- 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x55, 0x72, +- 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x03, +- 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x75, 0x6d, 0x12, 0x1a, +- 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x53, 0x61, 0x66, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, +- 0x52, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x53, 0x61, 0x66, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x54, +- 0x4c, 0x53, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x6d, 0x54, 0x4c, 0x53, 0x12, 0x1d, +- 0x0a, 0x0a, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, +- 0x28, 0x09, 0x52, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x27, 0x0a, +- 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, +- 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, +- 0x72, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x26, 0x0a, 0x05, 0x63, 0x65, 0x72, 0x74, 0x73, 0x18, +- 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x65, +- 0x72, 0x74, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x63, 0x65, 0x72, 0x74, 0x73, 0x22, 0x66, +- 0x0a, 0x09, 0x43, 0x65, 0x72, 0x74, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x63, +- 0x61, 0x5f, 0x63, 0x61, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, +- 0x61, 0x43, 0x61, 0x65, 0x72, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, +- 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6c, 0x69, +- 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x69, 0x65, 0x6e, +- 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x69, +- 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x22, 0x22, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, +- 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, +- 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0x11, 0x0a, 0x0f, 0x52, 0x6f, +- 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x24, 0x0a, +- 0x10, 0x52, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, +- 0x65, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, +- 0x65, 0x72, 0x72, 0x22, 0x3e, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, +- 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x66, 0x69, +- 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, +- 0x2e, 0x53, 0x79, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x66, +- 0x69, 0x67, 0x73, 0x22, 0x25, 0x0a, 0x11, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, +- 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, +- 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0xca, 0x01, 0x0a, 0x09, 0x53, +- 0x79, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, +- 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x1e, +- 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, +- 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x61, 0x74, 0x68, 0x12, 0x3a, +- 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, +- 0x32, 0x1e, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x79, 0x73, 0x43, 0x6f, 0x6e, 0x66, +- 0x69, 0x67, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, +- 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0x4b, 0x0a, 0x0d, 0x43, 0x6f, +- 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, +- 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, +- 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, +- 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4b, 0x65, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x76, 0x61, +- 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x3d, 0x0a, 0x07, 0x4b, 0x65, 0x79, 0x49, 0x6e, +- 0x66, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, +- 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, +- 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x70, 0x65, +- 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x32, 0xbe, 0x01, 0x0a, 0x02, 0x4f, 0x53, 0x12, 0x37, 0x0a, +- 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x14, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, +- 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, +- 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, +- 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x08, 0x52, 0x6f, 0x6c, 0x6c, 0x62, 0x61, +- 0x63, 0x6b, 0x12, 0x16, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x62, +- 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x67, 0x65, +- 0x6e, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, +- 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, +- 0x72, 0x65, 0x12, 0x17, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, +- 0x67, 0x75, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x67, +- 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x52, 0x65, 0x73, +- 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x20, 0x5a, 0x1e, 0x6f, 0x70, 0x65, 0x6e, 0x65, +- 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x6b, 0x75, 0x62, 0x65, 0x6f, 0x73, 0x2f, +- 0x61, 0x70, 0x69, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, +- 0x33, ++ 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x73, 0x75, ++ 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x75, ++ 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, ++ 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, ++ 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x6d, ++ 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x61, ++ 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x22, 0x0a, 0x0e, 0x55, 0x70, 0x64, ++ 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x65, ++ 0x72, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0x11, 0x0a, ++ 0x0f, 0x52, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, ++ 0x22, 0x24, 0x0a, 0x10, 0x52, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, ++ 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, ++ 0x05, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0x3e, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, ++ 0x75, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x63, 0x6f, ++ 0x6e, 0x66, 0x69, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x67, ++ 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x79, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x63, ++ 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x22, 0x25, 0x0a, 0x11, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, ++ 0x75, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x65, ++ 0x72, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0xca, 0x01, ++ 0x0a, 0x09, 0x53, 0x79, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x6d, ++ 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x65, ++ 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x61, 0x74, 0x68, 0x18, ++ 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x61, 0x74, ++ 0x68, 0x12, 0x3a, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, ++ 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x79, 0x73, 0x43, ++ 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x45, 0x6e, ++ 0x74, 0x72, 0x79, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0x4b, 0x0a, ++ 0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, ++ 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, ++ 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, ++ 0x0e, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4b, 0x65, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, ++ 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x3d, 0x0a, 0x07, 0x4b, 0x65, ++ 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, ++ 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6f, ++ 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, ++ 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x32, 0xbe, 0x01, 0x0a, 0x02, 0x4f, 0x53, ++ 0x12, 0x37, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x14, 0x2e, 0x61, 0x67, 0x65, ++ 0x6e, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, ++ 0x1a, 0x15, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, ++ 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x08, 0x52, 0x6f, 0x6c, ++ 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x16, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x6f, ++ 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, ++ 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, ++ 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x66, ++ 0x69, 0x67, 0x75, 0x72, 0x65, 0x12, 0x17, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, ++ 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, ++ 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, ++ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x20, 0x5a, 0x1e, 0x6f, 0x70, ++ 0x65, 0x6e, 0x65, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x6b, 0x75, 0x62, 0x65, ++ 0x6f, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, ++ 0x6f, 0x74, 0x6f, 0x33, + } + + var ( +@@ -627,35 +518,33 @@ func file_api_agent_proto_rawDescGZIP() []byte { + return file_api_agent_proto_rawDescData + } + +-var file_api_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 10) ++var file_api_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 9) + var file_api_agent_proto_goTypes = []interface{}{ + (*UpdateRequest)(nil), // 0: agent.UpdateRequest +- (*CertsInfo)(nil), // 1: agent.CertsInfo +- (*UpdateResponse)(nil), // 2: agent.UpdateResponse +- (*RollbackRequest)(nil), // 3: agent.RollbackRequest +- (*RollbackResponse)(nil), // 4: agent.RollbackResponse +- (*ConfigureRequest)(nil), // 5: agent.ConfigureRequest +- (*ConfigureResponse)(nil), // 6: agent.ConfigureResponse +- (*SysConfig)(nil), // 7: agent.SysConfig +- (*KeyInfo)(nil), // 8: agent.KeyInfo +- nil, // 9: agent.SysConfig.ContentsEntry ++ (*UpdateResponse)(nil), // 1: agent.UpdateResponse ++ (*RollbackRequest)(nil), // 2: agent.RollbackRequest ++ (*RollbackResponse)(nil), // 3: agent.RollbackResponse ++ (*ConfigureRequest)(nil), // 4: agent.ConfigureRequest ++ (*ConfigureResponse)(nil), // 5: agent.ConfigureResponse ++ (*SysConfig)(nil), // 6: agent.SysConfig ++ (*KeyInfo)(nil), // 7: agent.KeyInfo ++ nil, // 8: agent.SysConfig.ContentsEntry + } + var file_api_agent_proto_depIdxs = []int32{ +- 1, // 0: agent.UpdateRequest.certs:type_name -> agent.CertsInfo +- 7, // 1: agent.ConfigureRequest.configs:type_name -> agent.SysConfig +- 9, // 2: agent.SysConfig.contents:type_name -> agent.SysConfig.ContentsEntry +- 8, // 3: agent.SysConfig.ContentsEntry.value:type_name -> agent.KeyInfo +- 0, // 4: agent.OS.Update:input_type -> agent.UpdateRequest +- 3, // 5: agent.OS.Rollback:input_type -> agent.RollbackRequest +- 5, // 6: agent.OS.Configure:input_type -> agent.ConfigureRequest +- 2, // 7: agent.OS.Update:output_type -> agent.UpdateResponse +- 4, // 8: agent.OS.Rollback:output_type -> agent.RollbackResponse +- 6, // 9: agent.OS.Configure:output_type -> agent.ConfigureResponse +- 7, // [7:10] is the sub-list for method output_type +- 4, // [4:7] is the sub-list for method input_type +- 4, // [4:4] is the sub-list for extension type_name +- 4, // [4:4] is the sub-list for extension extendee +- 0, // [0:4] is the sub-list for field type_name ++ 6, // 0: agent.ConfigureRequest.configs:type_name -> agent.SysConfig ++ 8, // 1: agent.SysConfig.contents:type_name -> agent.SysConfig.ContentsEntry ++ 7, // 2: agent.SysConfig.ContentsEntry.value:type_name -> agent.KeyInfo ++ 0, // 3: agent.OS.Update:input_type -> agent.UpdateRequest ++ 2, // 4: agent.OS.Rollback:input_type -> agent.RollbackRequest ++ 4, // 5: agent.OS.Configure:input_type -> agent.ConfigureRequest ++ 1, // 6: agent.OS.Update:output_type -> agent.UpdateResponse ++ 3, // 7: agent.OS.Rollback:output_type -> agent.RollbackResponse ++ 5, // 8: agent.OS.Configure:output_type -> agent.ConfigureResponse ++ 6, // [6:9] is the sub-list for method output_type ++ 3, // [3:6] is the sub-list for method input_type ++ 3, // [3:3] is the sub-list for extension type_name ++ 3, // [3:3] is the sub-list for extension extendee ++ 0, // [0:3] is the sub-list for field type_name + } + + func init() { file_api_agent_proto_init() } +@@ -677,18 +566,6 @@ func file_api_agent_proto_init() { + } + } + file_api_agent_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { +- switch v := v.(*CertsInfo); i { +- case 0: +- return &v.state +- case 1: +- return &v.sizeCache +- case 2: +- return &v.unknownFields +- default: +- return nil +- } +- } +- file_api_agent_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateResponse); i { + case 0: + return &v.state +@@ -700,7 +577,7 @@ func file_api_agent_proto_init() { + return nil + } + } +- file_api_agent_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { ++ file_api_agent_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RollbackRequest); i { + case 0: + return &v.state +@@ -712,7 +589,7 @@ func file_api_agent_proto_init() { + return nil + } + } +- file_api_agent_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { ++ file_api_agent_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RollbackResponse); i { + case 0: + return &v.state +@@ -724,7 +601,7 @@ func file_api_agent_proto_init() { + return nil + } + } +- file_api_agent_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { ++ file_api_agent_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfigureRequest); i { + case 0: + return &v.state +@@ -736,7 +613,7 @@ func file_api_agent_proto_init() { + return nil + } + } +- file_api_agent_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { ++ file_api_agent_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfigureResponse); i { + case 0: + return &v.state +@@ -748,7 +625,7 @@ func file_api_agent_proto_init() { + return nil + } + } +- file_api_agent_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { ++ file_api_agent_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SysConfig); i { + case 0: + return &v.state +@@ -760,7 +637,7 @@ func file_api_agent_proto_init() { + return nil + } + } +- file_api_agent_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { ++ file_api_agent_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeyInfo); i { + case 0: + return &v.state +@@ -779,7 +656,7 @@ func file_api_agent_proto_init() { + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_api_agent_proto_rawDesc, + NumEnums: 0, +- NumMessages: 10, ++ NumMessages: 9, + NumExtensions: 0, + NumServices: 1, + }, +diff --git a/cmd/agent/api/agent.proto b/cmd/agent/api/agent.proto +index fd15b9f..e81ad54 100644 +--- a/cmd/agent/api/agent.proto ++++ b/cmd/agent/api/agent.proto +@@ -24,20 +24,11 @@ service OS { + + message UpdateRequest { + string version = 1; +- string image_url = 2; +- string check_sum = 3; +- bool flagSafe = 4; +- bool mTLS = 5; +- string image_type = 6; +- string container_image = 7; +- CertsInfo certs = 8; ++ string check_sum = 2; ++ string image_type = 3; ++ string container_image = 4; + } + +-message CertsInfo { +- string ca_caert = 1; +- string client_cert = 2; +- string client_key = 3; +-} + + message UpdateResponse { + int32 err = 1; +diff --git a/cmd/agent/server/disk_image.go b/cmd/agent/server/disk_image.go +deleted file mode 100644 +index 8bd6bf6..0000000 +--- a/cmd/agent/server/disk_image.go ++++ /dev/null +@@ -1,217 +0,0 @@ +-/* +- * Copyright (c) Huawei Technologies Co., Ltd. 2022. 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 ( +- "crypto/sha256" +- "crypto/tls" +- "crypto/x509" +- "encoding/hex" +- "fmt" +- "io" +- "io/ioutil" +- "net/http" +- "os" +- "path/filepath" +- "strings" +- "syscall" +- +- "github.com/sirupsen/logrus" +- +- pb "openeuler.org/KubeOS/cmd/agent/api" +-) +- +-type diskHandler struct{} +- +-func (d diskHandler) downloadImage(req *pb.UpdateRequest) (string, error) { +- imagePath, err := d.getRootfsArchive(req, preparePath{}) +- if err != nil { +- return "", err +- } +- return imagePath, nil +-} +- +-func (d diskHandler) getRootfsArchive(req *pb.UpdateRequest, neededPath preparePath) (string, error) { +- imagePath, err := download(req) +- if err != nil { +- return "", err +- } +- if err = checkSumMatch(imagePath, req.CheckSum); err != nil { +- return "", err +- } +- return imagePath, nil +-} +- +-func download(req *pb.UpdateRequest) (string, error) { +- resp, err := getImageURL(req) +- if err != nil { +- return "", err +- } +- defer resp.Body.Close() +- if resp.StatusCode != http.StatusOK { +- return "", fmt.Errorf("URL %s returns error %s", req.ImageUrl, resp.Status) +- } +- fs := syscall.Statfs_t{} +- if err = syscall.Statfs(PersistDir, &fs); err != nil { +- return "", err +- } +- if int64(fs.Bfree)*fs.Bsize < resp.ContentLength+buffer { // these data come from disk size, will not overflow +- return "", fmt.Errorf("space is not enough for downloaing") +- } +- +- out, err := os.Create(filepath.Join(PersistDir, "update.img")) +- if err != nil { +- return "", err +- } +- defer out.Close() +- err = os.Chmod(out.Name(), imgPermission) +- if err != nil { +- return "", err +- } +- logrus.Infoln("downloading to file " + out.Name()) +- if _, err = io.Copy(out, resp.Body); err != nil { +- if errRemove := os.Remove(out.Name()); errRemove != nil { +- logrus.Errorln("remove " + out.Name() + " error " + errRemove.Error()) +- } +- return "", err +- } +- return out.Name(), nil +-} +- +-func checkSumMatch(filePath, checkSum string) error { +- file, err := os.Open(filePath) +- if err != nil { +- return err +- } +- defer file.Close() +- hash := sha256.New() +- if _, err := io.Copy(hash, file); err != nil { +- return err +- } +- if calSum := hex.EncodeToString(hash.Sum(nil)); calSum != checkSum { +- defer os.Remove(filePath) +- return fmt.Errorf("checkSum %s mismatch to %s", calSum, checkSum) +- } +- return nil +-} +- +-func getImageURL(req *pb.UpdateRequest) (*http.Response, error) { +- imageURL := req.ImageUrl +- flagSafe := req.FlagSafe +- mTLS := req.MTLS +- caCert := req.Certs.CaCaert +- clientCert := req.Certs.ClientCert +- clientKey := req.Certs.ClientKey +- +- if !strings.HasPrefix(imageURL, "https://") { +- if !flagSafe { +- logrus.Errorln("this imageUrl is not safe") +- return &http.Response{}, fmt.Errorf("this imageUrl is not safe") +- } +- resp, err := http.Get(imageURL) +- if err != nil { +- return &http.Response{}, err +- } +- return resp, nil +- } else if mTLS { +- client, err := loadClientCerts(caCert, clientCert, clientKey) +- if err != nil { +- return &http.Response{}, err +- } +- resp, err := client.Get(imageURL) +- if err != nil { +- return &http.Response{}, err +- } +- return resp, nil +- } else { +- client, err := loadCaCerts(caCert) +- if err != nil { +- return &http.Response{}, err +- } +- resp, err := client.Get(imageURL) +- if err != nil { +- return &http.Response{}, err +- } +- return resp, nil +- } +-} +- +-func loadCaCerts(caCert string) (*http.Client, error) { +- pool := x509.NewCertPool() +- err := certExist(caCert) +- if err != nil { +- return &http.Client{}, err +- } +- ca, err := ioutil.ReadFile(getCertPath() + caCert) +- if err != nil { +- return &http.Client{}, fmt.Errorf("read the ca certificate error %s", err) +- } +- pool.AppendCertsFromPEM(ca) +- tr := &http.Transport{ +- TLSClientConfig: &tls.Config{ +- RootCAs: pool, +- }, +- } +- client := &http.Client{Transport: tr} +- return client, nil +-} +- +-func loadClientCerts(caCert, clientCert, clientKey string) (*http.Client, error) { +- pool := x509.NewCertPool() +- err := certExist(caCert) +- if err != nil { +- return &http.Client{}, err +- } +- ca, err := ioutil.ReadFile(getCertPath() + caCert) +- if err != nil { +- return &http.Client{}, err +- } +- pool.AppendCertsFromPEM(ca) +- err = certExist(clientCert) +- if err != nil { +- return &http.Client{}, err +- } +- err = certExist(clientKey) +- if err != nil { +- return &http.Client{}, err +- } +- cliCrt, err := tls.LoadX509KeyPair(getCertPath()+clientCert, getCertPath()+clientKey) +- if err != nil { +- return &http.Client{}, err +- } +- +- tr := &http.Transport{ +- TLSClientConfig: &tls.Config{ +- RootCAs: pool, +- Certificates: []tls.Certificate{cliCrt}, +- }, +- } +- +- client := &http.Client{Transport: tr} +- return client, nil +-} +- +-func certExist(certFile string) error { +- if certFile == "" { +- return fmt.Errorf("please provide the certificate") +- } +- _, err := os.Stat(getCertPath() + certFile) +- if err != nil { +- if os.IsNotExist(err) { +- return fmt.Errorf("certificate is not exist %s ", err) +- } +- return fmt.Errorf("certificate has an error %s", err) +- } +- return nil +-} +diff --git a/cmd/agent/server/disk_image_test.go b/cmd/agent/server/disk_image_test.go +deleted file mode 100644 +index f970bd7..0000000 +--- a/cmd/agent/server/disk_image_test.go ++++ /dev/null +@@ -1,471 +0,0 @@ +-/* +- * Copyright (c) Huawei Technologies Co., Ltd. 2022. 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 ( +- "crypto/rand" +- "crypto/rsa" +- "crypto/sha256" +- "crypto/x509" +- "crypto/x509/pkix" +- "encoding/hex" +- "encoding/pem" +- "fmt" +- "io" +- "io/fs" +- "math/big" +- "net/http" +- "os" +- "reflect" +- "strings" +- "syscall" +- "testing" +- "time" +- +- "github.com/agiledragon/gomonkey/v2" +- pb "openeuler.org/KubeOS/cmd/agent/api" +-) +- +-func Test_download(t *testing.T) { +- tmpDir := t.TempDir() +- tmpFileForDownload := tmpDir + "/tmpFileForDownload" +- tmpFile, err := os.Create(tmpFileForDownload) +- if err != nil { +- t.Errorf("open file error: %v", err) +- } +- defer tmpFile.Close() +- type args struct { +- req *pb.UpdateRequest +- } +- tests := []struct { +- name string +- args args +- want string +- wantErr bool +- }{ +- {name: "errornil", args: args{&pb.UpdateRequest{Certs: &pb.CertsInfo{}}}, want: "", wantErr: true}, +- {name: "error response", args: args{&pb.UpdateRequest{ImageUrl: "http://www.openeuler.abc", FlagSafe: true, Certs: &pb.CertsInfo{}}}, want: "", wantErr: true}, +- { +- name: "normal", +- args: args{ +- req: &pb.UpdateRequest{ +- ImageUrl: "http://www.openeuler.org/zh/", +- FlagSafe: true, +- Certs: &pb.CertsInfo{}, +- }, +- }, +- want: tmpFileForDownload, +- wantErr: false, +- }, +- { +- name: "disk space not enough", +- args: args{ +- req: &pb.UpdateRequest{ +- ImageUrl: "http://www.openeuler.org/zh/", +- FlagSafe: true, +- Certs: &pb.CertsInfo{}, +- }, +- }, +- want: "", +- wantErr: true, +- }, +- } +- var patchStatfs *gomonkey.Patches +- patchStatfs = gomonkey.ApplyFunc(syscall.Statfs, func(path string, stat *syscall.Statfs_t) error { +- stat.Bfree = 3000 +- stat.Bsize = 4096 +- return nil +- }) +- defer patchStatfs.Reset() +- patchGetImageUrl := gomonkey.ApplyFuncSeq(getImageURL, +- []gomonkey.OutputCell{ +- {Values: gomonkey.Params{&http.Response{}, fmt.Errorf("error")}}, +- {Values: gomonkey.Params{&http.Response{StatusCode: http.StatusBadRequest, Body: io.NopCloser(strings.NewReader(""))}, nil}}, +- { +- Values: gomonkey.Params{ +- &http.Response{ +- StatusCode: http.StatusOK, +- ContentLength: 5, +- Body: io.NopCloser(strings.NewReader("hello")), +- }, +- nil, +- }, +- }, +- { +- Values: gomonkey.Params{ +- &http.Response{ +- StatusCode: http.StatusOK, +- ContentLength: 5, +- Body: io.NopCloser(strings.NewReader("hello")), +- }, +- nil, +- }, +- }, +- }, +- ) +- defer patchGetImageUrl.Reset() +- patchOSCreate := gomonkey.ApplyFuncReturn(os.Create, tmpFile, nil) +- defer patchOSCreate.Reset() +- for _, tt := range tests { +- t.Run(tt.name, func(t *testing.T) { +- if tt.name == "disk space not enough" { +- patchStatfs = gomonkey.ApplyFunc(syscall.Statfs, func(path string, stat *syscall.Statfs_t) error { +- stat.Bfree = 1 +- stat.Bsize = 4096 +- return nil +- }) +- } +- got, err := download(tt.args.req) +- if (err != nil) != tt.wantErr { +- t.Errorf("download() error = %v, wantErr %v", err, tt.wantErr) +- return +- } +- if got != tt.want { +- t.Errorf("download() got = %v, want %v", got, tt.want) +- } +- }) +- } +-} +- +-func Test_checkSumMatch(t *testing.T) { +- tmpDir := t.TempDir() +- tmpFileForCheckSum := tmpDir + "/tmpFileForCheckSum" +- err := os.WriteFile(tmpFileForCheckSum, []byte("hello"), 0644) +- if err != nil { +- t.Errorf("open file error: %v", err) +- } +- type args struct { +- filePath string +- checkSum string +- } +- tests := []struct { +- name string +- args args +- wantErr bool +- }{ +- { +- name: "normal", +- args: args{filePath: tmpFileForCheckSum, checkSum: calculateChecksum("hello")}, +- wantErr: false, +- }, +- {name: "error", args: args{filePath: tmpFileForCheckSum, checkSum: "aaa"}, wantErr: true}, +- {name: "unfound error", args: args{filePath: "", checkSum: "aaa"}, wantErr: true}, +- } +- for _, tt := range tests { +- t.Run(tt.name, func(t *testing.T) { +- if err := checkSumMatch(tt.args.filePath, tt.args.checkSum); (err != nil) != tt.wantErr { +- t.Errorf("checkSumMatch() error = %v, wantErr %v", err, tt.wantErr) +- } +- }) +- } +-} +- +-func Test_getImageURL(t *testing.T) { +- type args struct { +- req *pb.UpdateRequest +- } +- tests := []struct { +- name string +- args args +- want *http.Response +- wantErr bool +- }{ +- {name: "httpNotSafe", args: args{req: &pb.UpdateRequest{ +- ImageUrl: "http://www.openeuler.abc/zh/", +- FlagSafe: false, +- MTLS: false, +- Certs: &pb.CertsInfo{}, +- }}, want: &http.Response{}, wantErr: true}, +- {name: "httpSuccess", args: args{req: &pb.UpdateRequest{ +- ImageUrl: "http://www.openeuler.abc/zh/", +- FlagSafe: true, +- MTLS: false, +- Certs: &pb.CertsInfo{}, +- }}, want: &http.Response{StatusCode: http.StatusOK}, wantErr: false}, +- {name: "mTLSGetSuccess", args: args{req: &pb.UpdateRequest{ +- ImageUrl: "https://www.openeuler.abc/zh/", +- FlagSafe: true, +- MTLS: true, +- Certs: &pb.CertsInfo{}, +- }}, want: &http.Response{StatusCode: http.StatusOK}, wantErr: false}, +- {name: "httpsGetSuccess", args: args{req: &pb.UpdateRequest{ +- ImageUrl: "https://www.openeuler.abc/zh/", +- FlagSafe: true, +- MTLS: false, +- Certs: &pb.CertsInfo{}, +- }}, want: &http.Response{StatusCode: http.StatusOK}, wantErr: false}, +- {name: "httpsLoadCertsError", args: args{req: &pb.UpdateRequest{ +- ImageUrl: "https://www.openeuler.abc/zh/", +- FlagSafe: true, +- MTLS: false, +- Certs: &pb.CertsInfo{}, +- }}, want: &http.Response{}, wantErr: true}, +- {name: "httpsMLTSLoadCertsError", args: args{req: &pb.UpdateRequest{ +- ImageUrl: "https://www.openeuler.abc/zh/", +- FlagSafe: true, +- MTLS: true, +- Certs: &pb.CertsInfo{}, +- }}, want: &http.Response{}, wantErr: true}, +- } +- patchLoadClientCerts := gomonkey.ApplyFuncSeq(loadClientCerts, []gomonkey.OutputCell{ +- {Values: gomonkey.Params{&http.Client{}, nil}}, +- {Values: gomonkey.Params{&http.Client{}, fmt.Errorf("error")}}, +- }) +- defer patchLoadClientCerts.Reset() +- patchLoadCaCerts := gomonkey.ApplyFuncSeq(loadCaCerts, []gomonkey.OutputCell{ +- {Values: gomonkey.Params{&http.Client{}, nil}}, +- {Values: gomonkey.Params{&http.Client{}, fmt.Errorf("error")}}, +- }) +- defer patchLoadCaCerts.Reset() +- patchGet := gomonkey.ApplyFunc(http.Get, func(url string) (resp *http.Response, err error) { +- return &http.Response{StatusCode: http.StatusOK}, nil +- }) +- defer patchGet.Reset() +- patchClientGet := gomonkey.ApplyMethod(reflect.TypeOf(&http.Client{}), "Get", func(_ *http.Client, url string) (resp *http.Response, err error) { +- return &http.Response{StatusCode: http.StatusOK}, nil +- }) +- defer patchClientGet.Reset() +- for _, tt := range tests { +- t.Run(tt.name, func(t *testing.T) { +- if tt.name == "httpSuccess" { +- patchGet := gomonkey.ApplyFuncReturn(http.Get, &http.Response{StatusCode: http.StatusOK}, nil) +- defer patchGet.Reset() +- } +- got, err := getImageURL(tt.args.req) +- if (err != nil) != tt.wantErr { +- t.Errorf("getImageURL() error = %v, wantErr %v", err, tt.wantErr) +- return +- } +- if !reflect.DeepEqual(got, tt.want) { +- t.Errorf("getImageURL() got = %v, want %v", got, tt.want) +- } +- }) +- } +-} +- +-func Test_loadCaCerts(t *testing.T) { +- tmpDir := t.TempDir() +- caPath := tmpDir + "/fake.crt" +- createFakeCertKey(caPath, "") +- type args struct { +- caCert string +- } +- tests := []struct { +- name string +- args args +- wantErr bool +- }{ +- { +- name: "normal", +- args: args{ +- caCert: caPath, +- }, +- wantErr: false, +- }, +- {name: "no cert", args: args{caCert: ""}, wantErr: true}, +- } +- patchGetCertPath := gomonkey.ApplyFuncReturn(getCertPath, "") +- defer patchGetCertPath.Reset() +- for _, tt := range tests { +- t.Run(tt.name, func(t *testing.T) { +- got, err := loadCaCerts(tt.args.caCert) +- if (err != nil) != tt.wantErr { +- t.Errorf("loadCaCerts() error = %v, wantErr %v", err, tt.wantErr) +- return +- } +- if got == nil { +- t.Errorf("loadCaCerts() = %v", got) +- } +- }) +- } +- +-} +- +-func Test_loadClientCerts(t *testing.T) { +- tmpDir := t.TempDir() +- clientCertPath := tmpDir + "/fakeClientCert.crt" +- clientKeyPath := tmpDir + "/fakeClientKey.crt" +- createFakeCertKey(clientCertPath, clientKeyPath) +- type args struct { +- caCert string +- clientCert string +- clientKey string +- } +- tests := []struct { +- name string +- args args +- wantErr bool +- }{ +- { +- name: "normal", +- args: args{ +- caCert: clientCertPath, clientCert: clientCertPath, clientKey: clientKeyPath, +- }, +- wantErr: false, +- }, +- } +- patchGetCertPath := gomonkey.ApplyFuncReturn(getCertPath, "") +- defer patchGetCertPath.Reset() +- for _, tt := range tests { +- t.Run(tt.name, func(t *testing.T) { +- got, err := loadClientCerts(tt.args.caCert, tt.args.clientCert, tt.args.clientKey) +- if (err != nil) != tt.wantErr { +- t.Errorf("loadClientCerts() error = %v, wantErr %v", err, tt.wantErr) +- return +- } +- if got == nil { +- t.Errorf("loadClientCerts() got = %v", got) +- } +- }) +- } +-} +- +-func Test_certExist(t *testing.T) { +- type args struct { +- certFile string +- } +- tests := []struct { +- name string +- args args +- wantErr bool +- }{ +- {name: "fileEmpty", args: args{certFile: ""}, wantErr: true}, +- {name: "fileNotExist", args: args{certFile: "bb.txt"}, wantErr: true}, +- {name: "unknow error", args: args{certFile: "cc.txt"}, wantErr: true}, +- } +- for _, tt := range tests { +- t.Run(tt.name, func(t *testing.T) { +- var patchStat *gomonkey.Patches +- if tt.name == "unknow error" { +- patchStat = gomonkey.ApplyFunc(os.Stat, func(name string) (fs.FileInfo, error) { +- return fs.FileInfo(nil), fmt.Errorf("error") +- }) +- } +- if err := certExist(tt.args.certFile); (err != nil) != tt.wantErr { +- t.Errorf("certExist() error = %v, wantErr %v", err, tt.wantErr) +- } +- if tt.name == "unknow error" { +- patchStat.Reset() +- } +- }) +- } +- defer os.RemoveAll("/etc/KubeOS/") +-} +- +-func createFakeCertKey(certPath, keyPath string) { +- privateKey, _ := rsa.GenerateKey(rand.Reader, 2048) +- template := x509.Certificate{ +- SerialNumber: big.NewInt(1), +- Subject: pkix.Name{ +- CommonName: "Fake Client Certificate", +- }, +- NotBefore: time.Now(), +- NotAfter: time.Now().AddDate(1, 0, 0), +- KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, +- ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, +- BasicConstraintsValid: true, +- } +- certBytes, _ := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) +- certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) +- keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}) +- os.WriteFile(certPath, certPEM, 0644) +- if keyPath != "" { +- os.WriteFile(keyPath, keyPEM, 0644) +- } +-} +- +-func calculateChecksum(data string) string { +- hash := sha256.New() +- hash.Write([]byte(data)) +- return hex.EncodeToString(hash.Sum(nil)) +-} +- +-func Test_diskHandler_getRootfsArchive(t *testing.T) { +- type args struct { +- req *pb.UpdateRequest +- neededPath preparePath +- } +- tests := []struct { +- name string +- d diskHandler +- args args +- want string +- wantErr bool +- }{ +- { +- name: "normal", d: diskHandler{}, +- args: args{req: &pb.UpdateRequest{ImageUrl: "http://www.openeuler.org/zh/"}, neededPath: preparePath{}}, +- want: "/persist/update.img", +- wantErr: false, +- }, +- { +- name: "error", d: diskHandler{}, +- args: args{req: &pb.UpdateRequest{ImageUrl: "http://www.openeuler.org/zh/"}, neededPath: preparePath{}}, +- want: "", +- wantErr: true, +- }, +- } +- patchDownload := gomonkey.ApplyFuncSeq(download, []gomonkey.OutputCell{ +- {Values: gomonkey.Params{"/persist/update.img", nil}}, +- {Values: gomonkey.Params{"", fmt.Errorf("error")}}, +- }) +- defer patchDownload.Reset() +- patchCheckSumMatch := gomonkey.ApplyFuncReturn(checkSumMatch, nil) +- defer patchCheckSumMatch.Reset() +- for _, tt := range tests { +- t.Run(tt.name, func(t *testing.T) { +- d := diskHandler{} +- got, err := d.getRootfsArchive(tt.args.req, tt.args.neededPath) +- if (err != nil) != tt.wantErr { +- t.Errorf("diskHandler.getRootfsArchive() error = %v, wantErr %v", err, tt.wantErr) +- return +- } +- if got != tt.want { +- t.Errorf("diskHandler.getRootfsArchive() = %v, want %v", got, tt.want) +- } +- }) +- } +-} +- +-func Test_diskHandler_downloadImage(t *testing.T) { +- type args struct { +- req *pb.UpdateRequest +- } +- tests := []struct { +- name string +- d diskHandler +- args args +- want string +- wantErr bool +- }{ +- {name: "normal", d: diskHandler{}, args: args{req: &pb.UpdateRequest{ImageUrl: "http://www.openeuler.org/zh/"}}, want: "/persist/update.img", wantErr: false}, +- } +- for _, tt := range tests { +- t.Run(tt.name, func(t *testing.T) { +- d := diskHandler{} +- patchGetRootfsArchive := gomonkey.ApplyPrivateMethod(reflect.TypeOf(d), "getRootfsArchive", func(_ *diskHandler, _ *pb.UpdateRequest, _ preparePath) (string, error) { +- return "/persist/update.img", nil +- }) +- got, err := d.downloadImage(tt.args.req) +- if (err != nil) != tt.wantErr { +- t.Errorf("diskHandler.downloadImage() error = %v, wantErr %v", err, tt.wantErr) +- return +- } +- if got != tt.want { +- t.Errorf("diskHandler.downloadImage() = %v, want %v", got, tt.want) +- } +- patchGetRootfsArchive.Reset() +- }) +- } +-} +diff --git a/cmd/agent/server/docker_image.go b/cmd/agent/server/docker_image.go +deleted file mode 100644 +index 16bcea5..0000000 +--- a/cmd/agent/server/docker_image.go ++++ /dev/null +@@ -1,73 +0,0 @@ +-/* +- * Copyright (c) Huawei Technologies Co., Ltd. 2022. 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 ( +- "github.com/sirupsen/logrus" +- +- pb "openeuler.org/KubeOS/cmd/agent/api" +-) +- +-type dockerImageHandler struct{} +- +-func (d dockerImageHandler) downloadImage(req *pb.UpdateRequest) (string, error) { +- neededPath, err := prepareEnv() +- if err != nil { +- return "", err +- } +- if _, err = d.getRootfsArchive(req, neededPath); err != nil { +- return "", err +- } +- return createOSImage(neededPath) +-} +- +-func (d dockerImageHandler) getRootfsArchive(req *pb.UpdateRequest, neededPath preparePath) (string, error) { +- imageName := req.ContainerImage +- if err := isValidImageName(imageName); err != nil { +- return "", err +- } +- logrus.Infof("start pull %s", imageName) +- if err := runCommand("docker", "pull", imageName); err != nil { +- return "", err +- } +- if err := checkOCIImageDigestMatch("docker", imageName, req.CheckSum); err != nil { +- return "", err +- } +- containerName := "kubeos-temp" +- dockerPsCmd := "docker ps -a -f=name=" + containerName + "| awk 'NR==2' | awk '{print $1}'" +- existId, err := runCommandWithOut("bash", "-c", dockerPsCmd) +- if err != nil { +- return "", err +- } +- if existId != "" { +- logrus.Infoln("kubeos-temp container exist,start clean environment first") +- if err := runCommand("docker", "rm", existId); err != nil { +- return "", err +- } +- } +- logrus.Infof("start get rootfs") +- containerId, err := runCommandWithOut("docker", "create", "--name", containerName, imageName) +- if err != nil { +- return "", err +- } +- defer func() { +- if err := runCommand("docker", "rm", containerId); err != nil { +- logrus.Errorln("remove kubeos-temp container error", err) +- } +- }() +- if err := runCommand("docker", "cp", containerId+":/"+neededPath.rootfsFile, neededPath.updatePath); err != nil { +- return "", err +- } +- return neededPath.tarPath, nil +-} +diff --git a/cmd/agent/server/docker_image_test.go b/cmd/agent/server/docker_image_test.go +deleted file mode 100644 +index 2dbf337..0000000 +--- a/cmd/agent/server/docker_image_test.go ++++ /dev/null +@@ -1,118 +0,0 @@ +-/* +- * Copyright (c) Huawei Technologies Co., Ltd. 2022. 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_dockerImageHandler_downloadImage(t *testing.T) { +- type args struct { +- req *pb.UpdateRequest +- } +- tests := []struct { +- name string +- d dockerImageHandler +- args args +- want string +- wantErr bool +- }{ +- { +- 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) { +- 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("dockerImageHandler.downloadImage() error = %v, wantErr %v", err, tt.wantErr) +- return +- } +- if got != tt.want { +- t.Errorf("dockerImageHandler.downloadImage() = %v, want %v", got, tt.want) +- } +- }) +- } +- 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/server.go b/cmd/agent/server/server.go +index f8cbb41..23ab1c6 100644 +--- a/cmd/agent/server/server.go ++++ b/cmd/agent/server/server.go +@@ -102,12 +102,8 @@ func (s *Server) update(req *pb.UpdateRequest) error { + action := req.ImageType + var handler imageDownload + switch action { +- case "docker": +- handler = dockerImageHandler{} + case "containerd": + handler = conImageHandler{} +- case "disk": +- handler = diskHandler{} + default: + return fmt.Errorf("image type %s cannot be recognized", action) + } +@@ -180,7 +176,3 @@ func (s *Server) reboot() error { + } + return syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART) + } +- +-func getCertPath() string { +- return certPath +-} +diff --git a/cmd/agent/server/server_test.go b/cmd/agent/server/server_test.go +index 15b6f5e..22ed38c 100644 +--- a/cmd/agent/server/server_test.go ++++ b/cmd/agent/server/server_test.go +@@ -87,10 +87,10 @@ func TestServerUpdate(t *testing.T) { + wantErr bool + }{ + {name: "error", fields: fields{UnimplementedOSServer: pb.UnimplementedOSServer{}, disableReboot: true}, +- args: args{in0: context.Background(), req: &pb.UpdateRequest{Version: "test", Certs: &pb.CertsInfo{}}}, ++ args: args{in0: context.Background(), req: &pb.UpdateRequest{Version: "test"}}, + want: &pb.UpdateResponse{}, wantErr: true}, + {name: "success", fields: fields{UnimplementedOSServer: pb.UnimplementedOSServer{}, disableReboot: true}, +- args: args{in0: context.Background(), req: &pb.UpdateRequest{Version: "test", Certs: &pb.CertsInfo{}, ImageType: "containerd"}}, ++ args: args{in0: context.Background(), req: &pb.UpdateRequest{Version: "test", ImageType: "containerd"}}, + want: &pb.UpdateResponse{}, wantErr: false}, + } + patchRootfsDisks := gomonkey.ApplyFuncReturn(getRootfsDisks, "/dev/sda2", "/dev/sda3", nil) +@@ -197,22 +197,9 @@ func TestServerupdate(t *testing.T) { + args args + wantErr bool + }{ +- {name: "errortype", args: args{&pb.UpdateRequest{Certs: &pb.CertsInfo{}}}, wantErr: true}, +- +- {name: "errordisk", args: args{&pb.UpdateRequest{ +- ImageUrl: "http://w3.huawei.com/", +- FlagSafe: true, +- CheckSum: "", +- ImageType: "disk", +- Certs: &pb.CertsInfo{}, +- }}, +- wantErr: true}, +- {name: "errordocker", args: args{&pb.UpdateRequest{ +- ContainerImage: "", +- ImageType: "docker", +- Certs: &pb.CertsInfo{}, +- }}, +- wantErr: true}, ++ {name: "errortype", args: args{&pb.UpdateRequest{}}, wantErr: true}, ++ {name: "errordisk", args: args{&pb.UpdateRequest{CheckSum: "", ImageType: "disk"}}, wantErr: true}, ++ {name: "errordocker", args: args{&pb.UpdateRequest{ContainerImage: "", ImageType: "docker"}}, wantErr: true}, + } + for _, tt := range tests { + if tt.name == "errordisk" { +diff --git a/cmd/operator/controllers/os_controller_test.go b/cmd/operator/controllers/os_controller_test.go +index 6cc2760..66aa1a4 100644 +--- a/cmd/operator/controllers/os_controller_test.go ++++ b/cmd/operator/controllers/os_controller_test.go +@@ -242,8 +242,6 @@ var _ = Describe("OsController", func() { + OpsType: "rollback", + MaxUnavailable: 3, + OSVersion: "KubeOS v1", +- FlagSafe: true, +- MTLS: false, + EvictPodForce: true, + SysConfigs: upgradev1.SysConfigs{ + Version: "v1", +@@ -420,8 +418,6 @@ var _ = Describe("OsController", func() { + OpsType: "config", + MaxUnavailable: 3, + OSVersion: "KubeOS v1", +- FlagSafe: true, +- MTLS: false, + EvictPodForce: true, + SysConfigs: upgradev1.SysConfigs{ + Version: "v2", +@@ -512,8 +508,6 @@ var _ = Describe("OsController", func() { + OpsType: "upgrade", + MaxUnavailable: 3, + OSVersion: "KubeOS v2", +- FlagSafe: true, +- MTLS: false, + EvictPodForce: true, + SysConfigs: upgradev1.SysConfigs{ + Configs: []upgradev1.SysConfig{}, +@@ -686,8 +680,6 @@ var _ = Describe("OsController", func() { + OpsType: "upgrade", + MaxUnavailable: 3, + OSVersion: "KubeOS v2", +- FlagSafe: true, +- MTLS: false, + EvictPodForce: true, + SysConfigs: upgradev1.SysConfigs{ + Version: "v2", +diff --git a/cmd/proxy/controllers/os_controller.go b/cmd/proxy/controllers/os_controller.go +index b8d0f80..d7da343 100644 +--- a/cmd/proxy/controllers/os_controller.go ++++ b/cmd/proxy/controllers/os_controller.go +@@ -211,13 +211,7 @@ func (r *OSReconciler) upgradeNode(ctx context.Context, osCr *upgradev1.OS, node + case "upgrade": + version := osVersionSpec + downloadInfo := &agentclient.DownloadInfo{ +- ImageURL: osCr.Spec.ImageURL, +- FlagSafe: osCr.Spec.FlagSafe, + CheckSum: osCr.Spec.CheckSum, +- CaCert: osCr.Spec.CaCert, +- ClientCert: osCr.Spec.ClientCert, +- ClientKey: osCr.Spec.ClientKey, +- MTLS: osCr.Spec.MTLS, + ImageType: osCr.Spec.ImageType, + ContainerImage: osCr.Spec.ContainerImage, + } +diff --git a/cmd/proxy/controllers/os_controller_test.go b/cmd/proxy/controllers/os_controller_test.go +index 14b6b66..847f22e 100644 +--- a/cmd/proxy/controllers/os_controller_test.go ++++ b/cmd/proxy/controllers/os_controller_test.go +@@ -173,8 +173,6 @@ var _ = Describe("OsController", func() { + OpsType: "rollback", + MaxUnavailable: 3, + OSVersion: "KubeOS v1", +- FlagSafe: true, +- MTLS: false, + EvictPodForce: true, + SysConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}}, + UpgradeConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}}, +@@ -333,8 +331,6 @@ var _ = Describe("OsController", func() { + OpsType: "config", + MaxUnavailable: 3, + OSVersion: "KubeOS v1", +- FlagSafe: true, +- MTLS: false, + EvictPodForce: true, + SysConfigs: upgradev1.SysConfigs{ + Version: "v2", +@@ -476,8 +472,6 @@ var _ = Describe("OsController", func() { + OpsType: "upgrade", + MaxUnavailable: 3, + OSVersion: "KubeOS v2", +- FlagSafe: true, +- MTLS: false, + EvictPodForce: false, + SysConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}}, + UpgradeConfigs: upgradev1.SysConfigs{ +@@ -564,8 +558,6 @@ var _ = Describe("OsController", func() { + OpsType: "config", + MaxUnavailable: 3, + OSVersion: "KubeOS v1", +- FlagSafe: true, +- MTLS: false, + EvictPodForce: true, + SysConfigs: upgradev1.SysConfigs{ + Version: "v1", +@@ -709,8 +701,6 @@ var _ = Describe("OsController", func() { + OpsType: "config", + MaxUnavailable: 3, + OSVersion: "KubeOS v1", +- FlagSafe: true, +- MTLS: false, + EvictPodForce: true, + SysConfigs: upgradev1.SysConfigs{ + Version: "v2", +@@ -875,8 +865,6 @@ var _ = Describe("OsController", func() { + OpsType: "upgrade", + MaxUnavailable: 3, + OSVersion: "KubeOS v2", +- FlagSafe: true, +- MTLS: false, + EvictPodForce: true, + SysConfigs: upgradev1.SysConfigs{Configs: []upgradev1.SysConfig{}}, + UpgradeConfigs: upgradev1.SysConfigs{ +@@ -1071,8 +1059,6 @@ var _ = Describe("OsController", func() { + OpsType: "upgrade", + MaxUnavailable: 3, + OSVersion: "KubeOS v2", +- FlagSafe: true, +- MTLS: false, + EvictPodForce: true, + SysConfigs: upgradev1.SysConfigs{ + Version: "v2", +@@ -1232,8 +1218,6 @@ var _ = Describe("OsController", func() { + OpsType: "upgrade", + MaxUnavailable: 3, + OSVersion: "KubeOS v2", +- FlagSafe: true, +- MTLS: false, + EvictPodForce: true, + SysConfigs: upgradev1.SysConfigs{ + Version: "v2", +diff --git a/docs/example/config/crd/upgrade.openeuler.org_os.yaml b/docs/example/config/crd/upgrade.openeuler.org_os.yaml +index 3bb1333..8fadb2c 100644 +--- a/docs/example/config/crd/upgrade.openeuler.org_os.yaml ++++ b/docs/example/config/crd/upgrade.openeuler.org_os.yaml +@@ -32,28 +32,16 @@ spec: + spec: + description: OSSpec defines the desired state of OS + properties: +- cacert: +- type: string + checksum: + type: string +- clientcert: +- type: string +- clientkey: +- type: string + containerimage: + type: string + evictpodforce: + type: boolean +- flagSafe: +- type: boolean + imagetype: + type: string +- imageurl: +- type: string + maxunavailable: + type: integer +- mtls: +- type: boolean + opstype: + type: string + osversion: +@@ -118,11 +106,8 @@ spec: + - checksum + - containerimage + - evictpodforce +- - flagSafe + - imagetype +- - imageurl + - maxunavailable +- - mtls + - opstype + - osversion + type: object +diff --git a/pkg/agentclient/connection.go b/pkg/agentclient/connection.go +index 7b53a14..ad31da4 100644 +--- a/pkg/agentclient/connection.go ++++ b/pkg/agentclient/connection.go +@@ -32,13 +32,7 @@ type Client struct { + + // DownloadInfo contains the information required for image download + type DownloadInfo struct { +- ImageURL string +- FlagSafe bool + CheckSum string +- CaCert string +- ClientCert string +- ClientKey string +- MTLS bool + ImageType string + ContainerImage string + } +@@ -83,19 +77,10 @@ func New(sockAddr string) (*Client, error) { + + // UpdateSpec send update requests to the server in os-agent + func (c *Client) UpdateSpec(version string, downloadInfo *DownloadInfo) error { +- certs := &pb.CertsInfo{ +- CaCaert: downloadInfo.CaCert, +- ClientCert: downloadInfo.ClientCert, +- ClientKey: downloadInfo.ClientKey, +- } + _, err := c.client.Update(context.Background(), + &pb.UpdateRequest{ + Version: version, +- ImageUrl: downloadInfo.ImageURL, +- FlagSafe: downloadInfo.FlagSafe, + CheckSum: downloadInfo.CheckSum, +- MTLS: downloadInfo.MTLS, +- Certs: certs, + ImageType: downloadInfo.ImageType, + ContainerImage: downloadInfo.ContainerImage, + }) +-- +2.39.0 + diff --git a/0022-KubeOS-delete-scripts-except-admin-container.patch b/0022-KubeOS-delete-scripts-except-admin-container.patch new file mode 100644 index 0000000..32faa4a --- /dev/null +++ b/0022-KubeOS-delete-scripts-except-admin-container.patch @@ -0,0 +1,1671 @@ +From aaf7a75f0feacafcfcccd8c3812b1654694c26c0 Mon Sep 17 00:00:00 2001 +From: Yuhang Wei +Date: Mon, 28 Aug 2023 18:01:42 +0800 +Subject: [PATCH 5/5] KubeOS: delete scripts except admin container + +Signed-off-by: Yuhang Wei +--- + scripts/00bootup/Global.cfg | 16 -- + scripts/00bootup/module-setup.sh | 28 -- + scripts/00bootup/mount.sh | 341 ------------------------ + scripts/Dockerfile | 3 - + scripts/admin-container/Dockerfile | 28 -- + scripts/bootloader.sh | 42 --- + scripts/common/globalVariables.sh | 22 -- + scripts/common/log.sh | 20 -- + scripts/common/utils.sh | 191 -------------- + scripts/create/imageCreate.sh | 122 --------- + scripts/create/rootfsCreate.sh | 105 -------- + scripts/grub.cfg | 173 ------------- + scripts/kbimg.sh | 402 ----------------------------- + scripts/rpmlist | 22 -- + scripts/set_in_chroot.sh | 20 -- + 15 files changed, 1535 deletions(-) + delete mode 100644 scripts/00bootup/Global.cfg + delete mode 100644 scripts/00bootup/module-setup.sh + delete mode 100644 scripts/00bootup/mount.sh + delete mode 100644 scripts/Dockerfile + delete mode 100644 scripts/admin-container/Dockerfile + delete mode 100644 scripts/bootloader.sh + delete mode 100644 scripts/common/globalVariables.sh + delete mode 100644 scripts/common/log.sh + delete mode 100644 scripts/common/utils.sh + delete mode 100644 scripts/create/imageCreate.sh + delete mode 100644 scripts/create/rootfsCreate.sh + delete mode 100644 scripts/grub.cfg + delete mode 100644 scripts/kbimg.sh + delete mode 100644 scripts/rpmlist + delete mode 100644 scripts/set_in_chroot.sh + +diff --git a/scripts/00bootup/Global.cfg b/scripts/00bootup/Global.cfg +deleted file mode 100644 +index dd78617..0000000 +--- a/scripts/00bootup/Global.cfg ++++ /dev/null +@@ -1,16 +0,0 @@ +-# rootfs file name +-rootfs_name=kubeos.tar +- +-# select the target disk to install kubeOS +-disk=/dev/sda +- +-# pxe server ip address where stores the rootfs on the http server +-server_ip=192.168.1.50 +-# target machine ip +-local_ip=192.168.1.100 +-# target machine route +-route_ip=192.168.1.1 +-# target machine netmask +-netmask=255.255.255.0 +-# target machine netDevice name +-net_name=eth0 +diff --git a/scripts/00bootup/module-setup.sh b/scripts/00bootup/module-setup.sh +deleted file mode 100644 +index 5460b2b..0000000 +--- a/scripts/00bootup/module-setup.sh ++++ /dev/null +@@ -1,28 +0,0 @@ +-#!/bin/bash +- +-check() { +- return 0 +-} +- +-depends() { +- echo systemd +-} +- +-install() { +- inst_multiple -o grub2-mkimage mkfs.ext4 mkfs.vfat lsblk tar cpio gunzip lspci parted dhclient ifconfig curl hwinfo head tee arch df awk route +- inst_hook mount 00 "$moddir/mount.sh" +- inst_simple "$moddir/mount.sh" "/mount.sh" +- inst_simple "$moddir/Global.cfg" "/Global.cfg" +-} +- +-installkernel() { +- hostonly='' \ +- instmods \ +- =drivers/ata \ +- =drivers/nvme \ +- =drivers/scsi \ +- =drivers/net \ +- =fs/fat \ +- =fs/nls +-} +- +diff --git a/scripts/00bootup/mount.sh b/scripts/00bootup/mount.sh +deleted file mode 100644 +index 7f00fd6..0000000 +--- a/scripts/00bootup/mount.sh ++++ /dev/null +@@ -1,341 +0,0 @@ +-#!/bin/bash +-arch=$(arch) +-min_size=8 +-log=/install.log +- +-source /Global.cfg +- +-function CheckSpace() { +- local disk_ava="$(parted -l | grep ${disk} | awk '{print $3}')" +- if echo "${disk_ava}" | grep [GT]B$; then +- if echo "${disk_ava}" | grep GB$; then +- disk_ava="$(echo ${disk_ava} | awk -F G '{print $1}' | awk -F . '{print $1}')" +- if [ "${disk_ava}" -lt ${min_size} ]; then +- echo "The available disk space is not enough, at least ${min_size}GB." | tee -a ${log} +- return 1 +- fi +- fi +- else +- echo "The available disk space is not enough, at least ${min_size}G." | tee -a ${log} +- return 1 +- fi +- +- return 0 +-} +- +-function mount_proc_dev_sys() { +- local tmp_root=$1 +- mount -t proc none "${tmp_root}/proc" +- mount --bind /dev "${tmp_root}/dev" +- mount --bind /dev/pts "${tmp_root}/dev/pts" +- mount -t sysfs none "${tmp_root}/sys" +-} +- +-function GetDisk() { +- disks=(`hwinfo --disk --short 2>&1 | grep -vi "^disk" | awk '{print $1}'`) +- if [ ${#disks[*]} -gt 0 ]; then +- if [ -n "${disk}" ] && echo "${disks[@]}" | grep -wq "${disk}" ; then +- echo "${disk} exists, start partition" | tee -a ${log} +- else +- echo "disk not exist, please choose correct disk" | tee -a ${log} +- fi +- else +- echo "no disk found" | tee -a ${log} +- return 1 +- fi +- CheckSpace +- if [ $? -ne 0 ]; then +- echo "no enough space on ${disk}" | tee -a ${log} +- return 1 +- fi +- +- return 0 +-} +- +-function PartitionAndFormatting() { +- echo "Partitioning and formatting disk $disk..." +- # partition and format +- parted ${disk} -s mklabel gpt >> ${log} 2>&1 +- if [ $? -ne 0 ]; then +- echo "partition failed" | tee -a ${log} +- return 1 +- fi +- +- parted ${disk} -s mkpart primary fat16 1M 100M >> ${log} 2>&1 +- if [ $? -ne 0 ]; then +- echo "partition failed" | tee -a ${log} +- return 1 +- fi +- +- parted ${disk} -s mkpart primary ext4 100M 2600M >> ${log} 2>&1 +- if [ $? -ne 0 ]; then +- echo "partition failed" | tee -a ${log} +- return 1 +- fi +- +- parted ${disk} -s mkpart primary ext4 2600M 5100M >> ${log} 2>&1 +- if [ $? -ne 0 ]; then +- echo "partition failed" | tee -a ${log} +- return 1 +- fi +- +- parted ${disk} -s mkpart primary ext4 5100M 100% >> ${log} 2>&1 +- if [ $? -ne 0 ]; then +- echo "partition failed" | tee -a ${log} +- return 1 +- fi +- +- parted ${disk} -s set 1 boot on >> ${log} 2>&1 +- if [ $? -ne 0 ]; then +- echo "partition failed" | tee -a ${log} +- return 1 +- fi +- +- mkfs.vfat -n "BOOT" ${disk}1 >> ${log} 2>&1 +- if [ $? -ne 0 ]; then +- echo "format failed" | tee -a ${log} +- return 1 +- fi +- +- mkfs.ext4 -L "ROOT-A" ${disk}2 >> ${log} 2>&1 +- if [ $? -ne 0 ]; then +- echo "format failed" | tee -a ${log} +- return 1 +- fi +- +- mkfs.ext4 -L "ROOT-B" ${disk}3 >> ${log} 2>&1 +- if [ $? -ne 0 ]; then +- echo "format failed" | tee -a ${log} +- return 1 +- fi +- +- mkfs.ext4 -L "PERSIST" ${disk}4 >> ${log} 2>&1 +- if [ $? -ne 0 ]; then +- echo "format failed" | tee -a ${log} +- return 1 +- fi +- +- return 0 +-} +- +-function InitNetwork() { +- echo "Initializing network..." +- netNames=(`ifconfig -a | awk '{print $1}' | grep : | grep '^e' | awk -F: '{print $1}'`) +- if [ ${#netNames[*]} -gt 0 ]; then +- if [ -n "${net_name}" ] && echo "${netNames[@]}" | grep -wq "${net_name}" ; then +- echo "${net_name} exists, start set ip" | tee -a ${log} +- else +- echo "net_name not exist, choose default net" | tee -a ${log} +- net_name=${netNames[0]} +- fi +- else +- echo "no net Device found" | tee -a ${log} +- return 1 +- fi +- +- ifconfig ${net_name} up +- if [ $? -ne 0 ]; then +- echo "load net card failed" | tee -a ${log} +- return 1 +- fi +- sleep 3 +- +- ifconfig ${net_name} ${local_ip} netmask ${netmask} >> ${log} 2>&1 +- if [ $? -ne 0 ]; then +- echo "ip set failed" | tee -a ${log} +- return 1 +- fi +- sleep 3 +- +- route add default gw ${route_ip} >> ${log} 2>&1 +- if [ $? -ne 0 ]; then +- echo "add route failed" | tee -a ${log} +- return 1 +- fi +- sleep 3 +- return 0 +-} +- +-function MountRoot() { +- echo "Mounting rootfs..." +- # mount rootfs +- mount ${disk}2 /sysroot >> ${log} 2>&1 +- if [ $? -ne 0 ]; then +- echo "mount rootfs failed" | tee -a ${log} +- return 1 +- fi +- +- return 0 +-} +- +-function MountPersist() { +- echo "Mounting persist" +- mount ${disk}4 /sysroot/persist >> ${log} 2>&1 +- if [ $? -ne 0 ]; then +- echo "mount persist failed" | tee -a ${log} +- return 1 +- fi +- mkdir /sysroot/persist/{var,etc,etcwork} +- mkdir -p /sysroot/persist/etc/KubeOS/certs +- return 0 +-} +- +-function MountBoot() { +- echo "Mounting boot" +- mkdir -p /sysroot/boot/efi +- mount ${disk}1 /sysroot/boot/efi >> ${log} 2>&1 +- if [ $? -ne 0 ]; then +- echo "mount boot failed" | tee -a ${log} +- return 1 +- fi +- return 0 +-} +- +-function GetRootfs() { +- echo "Downloading rootfs..." +- +- curl -o /${rootfs_name} http://${server_ip}/${rootfs_name} +- if [ ! -e "/${rootfs_name}" ]; then +- echo "download rootfs failed" | tee -a ${log} +- return 1 +- fi +- +- tar -xf /${rootfs_name} -C /sysroot +- if [ $? -ne 0 ]; then +- echo "decompose rootfs failed" | tee -a ${log} +- return 1 +- fi +- +- rm -rf /${rootfs_name} +- mount -o remount,ro ${disk}2 /sysroot >> ${log} 2>&1 +- return 0 +-} +- +-function Inst_Grub2_x86() { +- # copy the files that boot need +- cp -r /sysroot/usr/lib/grub/x86_64-efi /sysroot/boot/efi/EFI/openEuler +- eval "grub2-mkimage -d /sysroot/usr/lib/grub/x86_64-efi -O x86_64-efi --output=/sysroot/boot/efi/EFI/openEuler/grubx64.efi '--prefix=(,gpt1)/EFI/openEuler' fat part_gpt part_msdos linux" >> ${log} 2>&1 +- if [ $? -ne 0 ]; then +- echo "grub2-mkimage on x86 failed" | tee -a ${log} +- return 1 +- fi +- +- mkdir -p /sysroot/boot/efi/EFI/BOOT/ +- cp -f /sysroot/boot/efi/EFI/openEuler/grubx64.efi /sysroot/boot/efi/EFI/BOOT/BOOTX64.EFI +- +- return 0 +-} +- +-function Inst_Grub2_aarch64() { +- cp -r /sysroot/usr/lib/grub/arm64-efi /sysroot/boot/efi/EFI/openEuler/ +- eval "grub2-mkimage -d /sysroot/usr/lib/grub/arm64-efi -O arm64-efi --output=/sysroot/boot/efi/EFI/openEuler/grubaa64.efi '--prefix=(,gpt1)/EFI/openEuler' fat part_gpt part_msdos linux" >> ${log} 2>&1 +- if [ $? -ne 0 ]; then +- echo "grub2-mkimage on aarch64 failed" | tee -a ${log} +- return 1 +- fi +- +- mkdir -p /sysroot/boot/efi/EFI/BOOT/ +- cp -f /sysroot/boot/efi/EFI/openEuler/grubaa64.efi /sysroot/boot/efi/EFI/BOOT/BOOTAA64.EFI +- +- return 0 +-} +- +-function SetBoot() { +- # mount boot +- echo "Setting boot" +- +- if [ $arch == "x86_64" ]; then +- Inst_Grub2_x86 +- if [ $? -ne 0 ]; then +- echo "install grub on x86 failed" | tee -a ${log} +- return 1 +- fi +- fi +- +- if [ $arch == "aarch64" ]; then +- Inst_Grub2_aarch64 +- if [ $? -ne 0 ]; then +- echo "install grub on aarch64 failed" | tee -a ${log} +- return 1 +- fi +- fi +- sed -i 's#/dev/sda#'${disk}'#g' /sysroot/boot/efi/EFI/openEuler/grub.cfg +- +- return 0 +-} +- +-function Bootup_Main() { +- # get disk +- echo "Checking disk info..." | tee -a ${log} +- GetDisk +- if [ $? -ne 0 ]; then +- echo "Checking disk info failed" | tee -a ${log} +- return 1 +- fi +- +- # partition and format disk +- echo "Partion and formatting..." | tee -a ${log} +- PartitionAndFormatting +- if [ $? -ne 0 ]; then +- echo "Partition and formatting disk failed" | tee -a ${log} +- return 1 +- fi +- +- # init network +- echo "Initializing network..." | tee -a ${log} +- InitNetwork +- if [ $? -ne 0 ]; then +- echo "Initializing network failed" | tee -a ${log} +- return 1 +- fi +- +- # mount partitions +- +- # mount boot +- echo "Mounting root..." | tee -a ${log} +- MountRoot +- if [ $? -ne 0 ]; then +- echo "Mounting root failed" | tee -a ${log} +- return 1 +- fi +- +- echo "Mounting boot..." | tee -a ${log} +- MountBoot +- if [ $? -ne 0 ]; then +- echo "Mounting boot failed" | tee -a ${log} +- return 1 +- fi +- +- # download rootfs +- echo "Downloading rootfs..." | tee -a ${log} +- GetRootfs +- if [ $? -ne 0 ]; then +- echo "Downloading rootfs failed" | tee -a ${log} +- return 1 +- fi +- mount_proc_dev_sys /sysroot +- # set boot +- echo "Setting boot..." | tee -a ${log} +- SetBoot +- if [ $? -ne 0 ]; then +- echo "Setting boot failed" | tee -a ${log} +- return 1 +- fi +- # mount persist +- echo "Mounting persist..." | tee -a ${log} +- MountPersist +- if [ $? -ne 0 ]; then +- echo "Mounting persist failed" | tee -a ${log} +- return 1 +- fi +- return 0 +-} +- +-Bootup_Main +-ret=$? +-if [ ${ret} -eq 0 ]; then +- echo "kubeOS install success! switch to root" | tee -a ${log} +- cp ${log} /sysroot/persist +-else +- echo "kubeOS install failed, see install.log" | tee -a ${log} +-fi +diff --git a/scripts/Dockerfile b/scripts/Dockerfile +deleted file mode 100644 +index 3da4708..0000000 +--- a/scripts/Dockerfile ++++ /dev/null +@@ -1,3 +0,0 @@ +-FROM scratch +-COPY os.tar / +-CMD ["/bin/sh"] +diff --git a/scripts/admin-container/Dockerfile b/scripts/admin-container/Dockerfile +deleted file mode 100644 +index d4ddd06..0000000 +--- a/scripts/admin-container/Dockerfile ++++ /dev/null +@@ -1,28 +0,0 @@ +-## 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. +- +-FROM openeuler-22.03-lts +-MAINTAINER +- +-RUN yum -y install openssh-clients util-linux +- +- +-ADD ./sysmaster-0.2.3-1.oe2203.aarch64.rpm /home +-RUN rpm -ivh /home/sysmaster-0.2.3-1.oe2203.aarch64.rpm +- +-COPY ./hostshell /usr/bin/ +-COPY ./set-ssh-pub-key.sh /usr/local/bin +-COPY ./set-ssh-pub-key.service /usr/lib/sysmaster +- +-EXPOSE 22 +-# set sshd.service and set-ssh-pub-key.service pulled up by default +-RUN sed -i 's/sysinit.target/sysinit.target;sshd.service;set-ssh-pub-key.service/g' /usr/lib/sysmaster/basic.target +- +-CMD ["/usr/lib/sysmaster/init"] +diff --git a/scripts/bootloader.sh b/scripts/bootloader.sh +deleted file mode 100644 +index 75096a3..0000000 +--- a/scripts/bootloader.sh ++++ /dev/null +@@ -1,42 +0,0 @@ +-#!/bin/bash +-set -eu +-set -o pipefail +-set -x +-ARCH=`arch` +- +-function install_grub2_x86 () +-{ +- if [ "$BOOT_MODE" = "legacy" ]; then +- # make boot.img/core.img and setup, to support legacy boot mode +- GRUBNAME=$(which grub2-install) +- echo "Installing GRUB2..." +- GRUB_OPTS=${GRUB_OPTS:-"--force"} +- GRUB_OPTS="$GRUB_OPTS --target=i386-pc" +- +- $GRUBNAME --modules="biosdisk part_msdos" $GRUB_OPTS $DEVICE +- else +- # make efi file, and save in FAT16 partition, to support UEFI boot mode +- cp -r /usr/lib/grub/x86_64-efi boot/efi/EFI/openEuler +- eval "grub2-mkimage -d /usr/lib/grub/x86_64-efi -O x86_64-efi --output=/boot/efi/EFI/openEuler/grubx64.efi '--prefix=(,gpt1)/EFI/openEuler' fat part_gpt part_msdos linux" +- +- mkdir -p /boot/EFI/BOOT/ +- cp -f /boot/efi/EFI/openEuler/grubx64.efi /boot/efi/EFI/BOOT/BOOTX64.EFI +- fi +-} +- +-function install_grub2_efi () +-{ +- cp -r /usr/lib/grub/arm64-efi /boot/efi/EFI/openEuler/ +- eval "grub2-mkimage -d /usr/lib/grub/arm64-efi -O arm64-efi --output=/boot/efi/EFI/openEuler/grubaa64.efi '--prefix=(,gpt1)/EFI/openEuler' fat part_gpt part_msdos linux" +- +- mkdir -p /boot/EFI/BOOT/ +- cp -f /boot/efi/EFI/openEuler/grubaa64.efi /boot/efi/EFI/BOOT/BOOTAA64.EFI +-} +- +-if [ $ARCH == "x86_64" ]; then +- install_grub2_x86 +-fi +- +-if [ $ARCH == "aarch64" ]; then +- install_grub2_efi +-fi +diff --git a/scripts/common/globalVariables.sh b/scripts/common/globalVariables.sh +deleted file mode 100644 +index 95af9c8..0000000 +--- a/scripts/common/globalVariables.sh ++++ /dev/null +@@ -1,22 +0,0 @@ +-#!/bin/bash +-## Copyright (c) Huawei Technologies Co., Ltd. 2022. 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. +- +-IMG_SIZE=20 +-PWD="$(pwd)" +-TMP_MOUNT_PATH="${PWD}/mnt" +-RPM_ROOT="${PWD}/rootfs" +-ARCH=$(arch) +- +-export IMG_SIZE +-export PWD +-export TMP_MOUNT_PATH +-export RPM_ROOT +-export ARCH +diff --git a/scripts/common/log.sh b/scripts/common/log.sh +deleted file mode 100644 +index 4d3ed2b..0000000 +--- a/scripts/common/log.sh ++++ /dev/null +@@ -1,20 +0,0 @@ +-#!/bin/bash +-## Copyright (c) Huawei Technologies Co., Ltd. 2022. 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. +- +-function log_error_print(){ +- local logmsg="[ ERROR ] - ""`date "+%b %d %Y %H:%M:%S"`"" $1" +- echo $logmsg +-} +- +-function log_info_print(){ +- local logmsg="[ INFO ] - ""`date "+%b %d %Y %H:%M:%S"`"" $1" +- echo $logmsg +-} +diff --git a/scripts/common/utils.sh b/scripts/common/utils.sh +deleted file mode 100644 +index ec244b7..0000000 +--- a/scripts/common/utils.sh ++++ /dev/null +@@ -1,191 +0,0 @@ +-#!/bin/bash +-## Copyright (c) Huawei Technologies Co., Ltd. 2022. 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. +- +-CHECK_REGEX='\||;|&|&&|\|\||>|>>|<|,|#|!|\$' +- +-function mount_proc_dev_sys() { +- local tmp_root=$1 +- mount -t proc none "${tmp_root}/proc" +- mount --bind /dev "${tmp_root}/dev" +- mount --bind /dev/pts "${tmp_root}/dev/pts" +- mount -t sysfs none "${tmp_root}/sys" +-} +- +-function unmount_dir() { +- local dir=$1 +- +- if [ -L "${dir}" ] || [ -f "${dir}" ]; then +- log_error_print "${dir} is not a directory, please check it." +- return 1 +- fi +- +- if [ ! -d "${dir}" ]; then +- return 0 +- fi +- +- local real_dir=$(readlink -e "${dir}") +- local mnts=$(awk '{print $2}' < /proc/mounts | grep "^${real_dir}" | sort -r) +- for m in ${mnts}; do +- log_info_print "Unmount ${m}" +- umount -f "${m}" || true +- done +- +- return 0 +-} +- +-function init_part() { +- local offset=$(fdisk -l system.img | grep $1 | awk '{print $2}') +- local sizelimit=$(fdisk -l system.img | grep $1 | awk '{print $3}') +- sizelimit=$(echo "($sizelimit - $offset)*512" | bc) +- offset=$(echo "${offset}*512" | bc) +- local loop=$(losetup -f) +- losetup -o "${offset}" --sizelimit "${sizelimit}" "${loop}" system.img +- if [ $2 == "BOOT" ];then +- mkfs.vfat -n "$2" "${loop}" +- mount -t vfat "${loop}" "$3" +- else +- mkfs.ext4 -L "$2" "${loop}" +- mount -t ext4 "${loop}" "$3" +- rm -rf "$3/lost+found" +- fi +-} +- +-function delete_dir() { +- local ret=0 +- local dir="$1" +- unmount_dir "${dir}" +- ret=$? +- if [ "${ret}" -eq 0 ]; then +- rm -rf "${dir}" +- return 0 +- else +- log_error_print "${dir} is failed to unmount , can not delete $dir." +- return 1 +- fi +-} +- +-function delete_file() { +- local file="$1" +- if [ ! -e "${file}" ]; then +- return 0 +- fi +- +- if [ ! -f "${file}" ]; then +- log_error_print "${file} is not a file." +- return 1 +- fi +- +- rm -f "${file}" +- return 0 +-} +- +-function check_file_valid() { +- local file="$1" +- local mesg="$2" +- if [ ! -e "${file}" ]; then +- log_error_print "${mesg} is not exist." +- exit 3 +- fi +- if [ ! -f "${file}" ];then +- log_error_print "${mesg} is not a file." +- exit 3 +- fi +-} +- +-function check_conf_valid() { +- local conf_path="${PWD}/00bootup/Global.cfg" +- check_file_valid ${conf_path} "Globab.cfg" +- if [ $# != 7 ];then +- log_error_print "configure configured in Global.cfg is empty." +- exit 3 +- fi +- for addr in ${server_ip} ${local_ip} ${route_ip} ${netmask}; do +- check_ip_valid $addr +- done +-} +- +-function check_ip_valid() { +- local ipaddr="$1"; +- if [[ ! $ipaddr =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] ; then +- log_error_print "ip address configured in Global.cfg is not valid." +- exit 3; +- fi +- for quad in $(echo "${ipaddr//./ }"); do +- if [ $quad -ge 0 ] && [ $quad -le 255 ];then +- continue +- fi +- log_error_print "ip address configured in Global.cfg is not valid." +- exit 3; +- done +- +-} +- +-function check_binary_exist() { +- check_file_valid "$1" "os-agent binary" +-} +- +-function check_repo_path() { +- check_file_valid $1 "REPO file" +- if [ -d "${RPM_ROOT}" ]; then +- log_error_print "there is a rootfs folder. please confirm if rootfs is being used, if not, please remove ${RPM_ROOT} first." +- exit 5 +- fi +-} +- +-function check_disk_space() { +- local disk_ava="$(df ${PWD} | awk 'NR==2{print}' | awk '{print $4}')" +- case $1 in +- docker) +- local maxsize=$((6*1024*1024)) +- if [ "${disk_ava}" -lt "${maxsize}" ]; then +- log_error_print "The available disk space is not enough, at least 6GiB." +- exit 6 +- fi +- ;; +- vm) +- local maxsize=$((25*1024*1024)) +- if [ "${disk_ava}" -lt "${maxsize}" ]; then +- log_error_print "The available disk space is not enough, at least 25GiB." +- exit 6 +- fi +- ;; +- pxe) +- local maxsize=$((5*1024*1024)) +- if [ "${disk_ava}" -lt "${maxsize}" ]; then +- log_error_print "The available disk space is not enough, at least 5GiB." +- exit 6 +- fi +- ;; +- esac +-} +- +-function check_param() { +- set +eE +- local arg=$1 +- echo "${arg}" | grep -v -E -q ${CHECK_REGEX} +- filterParam=$(echo "${arg}" | grep -v -E ${CHECK_REGEX}) +- if [[ "${filterParam}" != "${arg}" ]]; then +- log_error_print "params ${arg} is invalid, please check it." +- exit 3 +- fi +- set -eE +-} +- +-function check_docker_exist() { +- if [[ "$(docker images -q $1 2> /dev/null)" == "" ]]; then +- log_error_print "docker is not exist please pull $1 first " +- exit 9 +- fi +-} +- +-function check_docker_file() { +- check_file_valid $1 "admin-container Dockerfile" +-} +\ No newline at end of file +diff --git a/scripts/create/imageCreate.sh b/scripts/create/imageCreate.sh +deleted file mode 100644 +index 4d02f9d..0000000 +--- a/scripts/create/imageCreate.sh ++++ /dev/null +@@ -1,122 +0,0 @@ +-#!/bin/bash +-## Copyright (c) Huawei Technologies Co., Ltd. 2022. 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. +- +-TMP_MOUNT_PATH="${PWD}/mnt" +-RPM_ROOT="${PWD}/rootfs" +-IMG_SIZE=20 +-PWD="$(pwd)" +-function create_img() { +- local BOOT_MODE=$1 +- rm -f system.img update.img +- qemu-img create system.img ${IMG_SIZE}G +- if [ "$BOOT_MODE" = "legacy" ]; then +- local BOOT_PATH=${TMP_MOUNT_PATH}/boot/grub2 +- parted system.img -s mklabel msdos +- parted system.img -s mkpart primary ext4 1MiB 60MiB +- else +- local BOOT_PATH=${TMP_MOUNT_PATH}/boot/efi +- parted system.img -s mklabel gpt +- parted system.img -s mkpart primary fat32 1MiB 60MiB +- fi +- parted system.img -s mkpart primary ext4 60MiB 2160MiB +- parted system.img -s mkpart primary ext4 2160MiB 4260MiB +- parted system.img -s mkpart primary ext4 4260MiB 100% +- local device=$(losetup -f) +- losetup "${device}" system.img +- +- mkdir -p "${TMP_MOUNT_PATH}" +- +- init_part system.img2 ROOT-A "${TMP_MOUNT_PATH}" +- +- mkdir -p ${BOOT_PATH} +- chmod 755 ${BOOT_PATH} +- if [ "$BOOT_MODE" = "legacy" ]; then +- init_part system.img1 GRUB2 "${BOOT_PATH}" +- else +- init_part system.img1 BOOT "${BOOT_PATH}" +- fi +- tar -x -C ${TMP_MOUNT_PATH} -f os.tar +- if [ "$BOOT_MODE" = "legacy" ]; then +- sed -i "s/insmod part_gpt/insmod part_msdos/g; \ +-s/set root='hd0,gpt2'/set root='hd0,msdos2'/g; \ +-s/set root='hd0,gpt3'/set root='hd0,msdos3'/g" \ +-"${TMP_MOUNT_PATH}"/boot/grub2/grub.cfg +- fi +- sync +- cp bootloader.sh "${TMP_MOUNT_PATH}" +- mount_proc_dev_sys "${TMP_MOUNT_PATH}" +- DEVICE="${device}" BOOT_MODE="${BOOT_MODE}" chroot "${TMP_MOUNT_PATH}" bash bootloader.sh +- rm -rf "${TMP_MOUNT_PATH}/bootloader.sh" +- sync +- +- dd if=/dev/disk/by-label/ROOT-A of=update.img bs=8M +- sync +- unmount_dir "${TMP_MOUNT_PATH}" +- init_part system.img3 ROOT-B "${TMP_MOUNT_PATH}" +- umount "${TMP_MOUNT_PATH}" +- +- init_part system.img4 PERSIST "${TMP_MOUNT_PATH}" +- mkdir ${TMP_MOUNT_PATH}/{var,etc,etcwork} +- mkdir -p ${TMP_MOUNT_PATH}/etc/KubeOS/certs +- umount "${TMP_MOUNT_PATH}" +- +- losetup -D +- parted system.img -- set 1 boot on +- qemu-img convert system.img -O qcow2 system.qcow2 +-} +- +-function create_pxe_img() { +- rm -rf initramfs.img kubeos.tar +- local opt=$1 +- shift +- case $opt in +- "repo") +- create_os_tar_from_repo "$@" +- ;; +- "docker") +- create_os_tar_from_docker "$@" +- ;; +- esac +- tar -xvf os.tar ./initramfs.img +- mv os.tar kubeos.tar +-} +- +-function create_docker_image() { +- local DOCKER_IMG="$6" +- create_os_tar_from_repo "$@" +- docker build -t ${DOCKER_IMG} -f ./Dockerfile . +-} +- +-function create_vm_img() { +- local opt=$1 +- shift +- local BOOT_MODE=$5 +- case $opt in +- "repo") +- create_os_tar_from_repo "$@" +- create_img "${BOOT_MODE}" +- ;; +- "docker") +- create_os_tar_from_docker "$@" +- create_img "${BOOT_MODE}" +- ;; +- esac +- +-} +- +-function create_admin_img() { +- local DOCKERFILE="$1" +- local DOCKER_IMG="$2" +- local ADMIN_CONTAINER_DIR="$3" +- cp ../bin/hostshell ${ADMIN_CONTAINER_DIR} +- docker build -t ${DOCKER_IMG} -f ${DOCKERFILE} ${ADMIN_CONTAINER_DIR} +- rm -rf ${ADMIN_CONTAINER_DIR}/hostshell +-} +\ No newline at end of file +diff --git a/scripts/create/rootfsCreate.sh b/scripts/create/rootfsCreate.sh +deleted file mode 100644 +index 377cbf8..0000000 +--- a/scripts/create/rootfsCreate.sh ++++ /dev/null +@@ -1,105 +0,0 @@ +-#!/bin/bash +-## Copyright (c) Huawei Technologies Co., Ltd. 2022. 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. +- +-function prepare_yum() { +- # init rpmdb +- local REPO=$1 +- rpm --root "${RPM_ROOT}" --initdb +- mkdir -p "${RPM_ROOT}"{/etc/yum.repos.d,/persist,/proc,/dev/pts,/sys} +- mount_proc_dev_sys "${RPM_ROOT}" +- # init yum repo +- local iso_repo="${RPM_ROOT}/etc/yum.repos.d/iso.repo" +- cat "${REPO}" > ${RPM_ROOT}/etc/yum.repos.d/iso.repo +-} +- +-function install_packages() { +- local REPO=$1 +- local BOOT_MODE=$2 +- prepare_yum ${REPO} +- +- echo "install package.." +- +- local filesize=$(stat -c "%s" ./rpmlist) +- local maxsize=$((1024*1024)) +- if [ "${filesize}" -gt "${maxsize}" ]; then +- echo "please check if rpmlist is too big or something wrong" +- exit 7 +- fi +- +- local rpms=$(cat ./rpmlist | tr "\n" " ") +- if [ "${ARCH}" == "x86_64" ]; then +- if [ "${BOOT_MODE}" = "legacy" ]; then +- rpms+=" grub2" +- else +- rpms+=" grub2-efi grub2-tools grub2-efi-x64-modules grub2-pc-modules" +- fi +- yum -y --installroot="${RPM_ROOT}" install --nogpgcheck --setopt install_weak_deps=False ${rpms} +- elif [ "${ARCH}" == "aarch64" ]; then +- yum -y --installroot="${RPM_ROOT}" install --nogpgcheck --setopt install_weak_deps=False ${rpms} grub2-efi grub2-tools grub2-efi-aa64-modules +- fi +- yum -y --installroot="${RPM_ROOT}" clean all +-} +- +-function install_misc() { +- local VERSION=$1 +- local AGENT_PATH=$2 +- local PASSWD=$3 +- local BOOT_MODE=$4 +- local DNS_CONF="${PWD}/resolv.conf" +- cp ../files/*mount ../files/os-agent.service "${RPM_ROOT}/usr/lib/systemd/system/" +- cp ../files/os-release "${RPM_ROOT}/usr/lib/" +- cp "${AGENT_PATH}" "${RPM_ROOT}/usr/bin" +- rm "${RPM_ROOT}/etc/os-release" +- +- cat < "${RPM_ROOT}/usr/lib/os-release" +-NAME=${NAME} +-ID=${NAME} +-EOF +- echo "PRETTY_NAME=\"${NAME} ${VERSION}\"" >> "${RPM_ROOT}/usr/lib/os-release" +- echo "VERSION_ID=${VERSION}" >> "${RPM_ROOT}/usr/lib/os-release" +- mv "${RPM_ROOT}"/boot/vmlinuz* "${RPM_ROOT}/boot/vmlinuz" +- mv "${RPM_ROOT}"/boot/initramfs* "${RPM_ROOT}/boot/initramfs.img" +- if [ "$BOOT_MODE" = "legacy" ]; then +- cp grub.cfg "${RPM_ROOT}"/boot/grub2 +- sed -i "s/insmod part_gpt/insmod part_msdos/g; \ +-s/set root='hd0,gpt2'/set root='hd0,msdos2'/g; \ +-s/set root='hd0,gpt3'/set root='hd0,msdos3'/g" \ +-"${RPM_ROOT}"/boot/grub2/grub.cfg +- else +- cp grub.cfg "${RPM_ROOT}"/boot/efi/EFI/openEuler +- fi +- cp -r ./00bootup ${RPM_ROOT}/usr/lib/dracut/modules.d/ +- cp set_in_chroot.sh "${RPM_ROOT}" +- ROOT_PWD="${PASSWD}" BOOT_MODE="${BOOT_MODE}" chroot "${RPM_ROOT}" bash /set_in_chroot.sh +- rm "${RPM_ROOT}/set_in_chroot.sh" +- if [ -e "${DNS_CONF}" ]; then +- cp "${DNS_CONF}" "${RPM_ROOT}/etc/resolv.conf" +- fi +-} +- +-function create_os_tar_from_repo() { +- local REPO=$1 +- local VERSION=$2 +- local AGENT_PATH=$3 +- local PASSWD=$4 +- local BOOT_MODE=$5 +- install_packages ${REPO} ${BOOT_MODE} +- install_misc ${VERSION} ${AGENT_PATH} ${PASSWD} ${BOOT_MODE} +- unmount_dir "${RPM_ROOT}" +- tar -C "$RPM_ROOT" -cf ./os.tar . +-} +-function create_os_tar_from_docker() { +- local DOCKER_IMG=$1 +- container_id=$(docker create ${DOCKER_IMG}) +- echo "$container_id" +- docker cp $container_id:/os.tar ./ +- docker rm $container_id +-} +diff --git a/scripts/grub.cfg b/scripts/grub.cfg +deleted file mode 100644 +index 984b161..0000000 +--- a/scripts/grub.cfg ++++ /dev/null +@@ -1,173 +0,0 @@ +-## Copyright (c) Huawei Technologies Co., Ltd. 2021. 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. +-set pager=1 +- +-if [ -f ${config_directory}/grubenv ]; then +- load_env -f ${config_directory}/grubenv +-elif [ -s $prefix/grubenv ]; then +- load_env +-fi +-if [ "${next_entry}" ] ; then +- set default="${next_entry}" +- set next_entry= +- save_env next_entry +- set boot_once=true +-else +- set default="${saved_entry}" +-fi +- +-if [ x"${feature_menuentry_id}" = xy ]; then +- menuentry_id_option="--id" +-else +- menuentry_id_option="" +-fi +- +-export menuentry_id_option +- +-if [ "${prev_saved_entry}" ]; then +- set saved_entry="${prev_saved_entry}" +- save_env saved_entry +- set prev_saved_entry= +- save_env prev_saved_entry +- set boot_once=true +-fi +- +-function savedefault { +- if [ -z "${boot_once}" ]; then +- saved_entry="${chosen}" +- save_env saved_entry +- fi +-} +- +-function load_video { +- if [ x$feature_all_video_module = xy ]; then +- insmod all_video +- else +- insmod efi_gop +- insmod efi_uga +- insmod ieee1275_fb +- insmod vbe +- insmod vga +- insmod video_bochs +- insmod video_cirrus +- fi +-} +- +-terminal_output console +-if [ x$feature_timeout_style = xy ] ; then +- set timeout_style=menu +- set timeout=5 +-# Fallback normal timeout code in case the timeout_style feature is +-# unavailable. +-else +- set timeout=5 +-fi +-set superusers="root" +-### END /etc/grub.d/00_header ### +- +-### BEGIN /etc/grub.d/01_users ### +-if [ -f ${prefix}/user.cfg ]; then +- source ${prefix}/user.cfg +- if [ -n "${GRUB2_PASSWORD}" ]; then +- set superusers="root" +- export superusers +- password_pbkdf2 root ${GRUB2_PASSWORD} +- fi +-fi +-### END /etc/grub.d/01_users ### +- +-### BEGIN /etc/grub.d/10_linux ### +-menuentry 'A' --class KubeOS --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'KubeOS-A' { +- load_video +- set gfxpayload=keep +- insmod gzio +- insmod part_gpt +- insmod ext2 +- set root='hd0,gpt2' +- linux /boot/vmlinuz root=/dev/sda2 ro rootfstype=ext4 nomodeset quiet oops=panic softlockup_panic=1 nmi_watchdog=1 rd.shell=0 selinux=0 crashkernel=256M panic=3 +- initrd /boot/initramfs.img +-} +- +-menuentry 'B' --class KubeOS --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'KubeOS-B' { +- load_video +- set gfxpayload=keep +- insmod gzio +- insmod part_gpt +- insmod ext2 +- set root='hd0,gpt3' +- linux /boot/vmlinuz root=/dev/sda3 ro rootfstype=ext4 nomodeset quiet oops=panic softlockup_panic=1 nmi_watchdog=1 rd.shell=0 selinux=0 crashkernel=256M panic=3 +- initrd /boot/initramfs.img +-} +- +-### END /etc/grub.d/10_linux ### +- +-### BEGIN /etc/grub.d/10_reset_boot_success ### +-# Hiding the menu is ok if last boot was ok or if this is a first boot attempt to boot the entry +-if [ "${boot_success}" = "1" -o "${boot_indeterminate}" = "1" ]; then +- set menu_hide_ok=1 +-else +- set menu_hide_ok=0 +-fi +-# Reset boot_indeterminate after a successful boot +-if [ "${boot_success}" = "1" ] ; then +- set boot_indeterminate=0 +-# Avoid boot_indeterminate causing the menu to be hidden more then once +-elif [ "${boot_indeterminate}" = "1" ]; then +- set boot_indeterminate=2 +-fi +-# Reset boot_success for current boot +-set boot_success=0 +-save_env boot_success boot_indeterminate +-### END /etc/grub.d/10_reset_boot_success ### +- +-### BEGIN /etc/grub.d/12_menu_auto_hide ### +-if [ x$feature_timeout_style = xy ] ; then +- if [ "${menu_show_once}" ]; then +- unset menu_show_once +- save_env menu_show_once +- set timeout_style=menu +- set timeout=60 +- elif [ "${menu_auto_hide}" -a "${menu_hide_ok}" = "1" ]; then +- set orig_timeout_style=${timeout_style} +- set orig_timeout=${timeout} +- if [ "${fastboot}" = "1" ]; then +- # timeout_style=menu + timeout=0 avoids the countdown code keypress check +- set timeout_style=menu +- set timeout=0 +- else +- set timeout_style=hidden +- set timeout=1 +- fi +- fi +-fi +-### END /etc/grub.d/12_menu_auto_hide ### +- +-### BEGIN /etc/grub.d/20_linux_xen ### +-### END /etc/grub.d/20_linux_xen ### +- +-### BEGIN /etc/grub.d/20_ppc_terminfo ### +-### END /etc/grub.d/20_ppc_terminfo ### +- +-### BEGIN /etc/grub.d/30_uefi-firmware ### +-### END /etc/grub.d/30_uefi-firmware ### +- +-### BEGIN /etc/grub.d/40_custom ### +-# This file provides an easy way to add custom menu entries. Simply type the +-# menu entries you want to add after this comment. Be careful not to change +-# the 'exec tail' line above. +-### END /etc/grub.d/40_custom ### +- +-### BEGIN /etc/grub.d/41_custom ### +-if [ -f ${config_directory}/custom.cfg ]; then +- source ${config_directory}/custom.cfg +-elif [ -z "${config_directory}" -a -f $prefix/custom.cfg ]; then +- source $prefix/custom.cfg; +-fi +-### END /etc/grub.d/41_custom ### +diff --git a/scripts/kbimg.sh b/scripts/kbimg.sh +deleted file mode 100644 +index 0f75f0d..0000000 +--- a/scripts/kbimg.sh ++++ /dev/null +@@ -1,402 +0,0 @@ +-#!/bin/bash +-## Copyright (c) Huawei Technologies Co., Ltd. 2022. 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. +- +-set -e +- +-NAME=KubeOS +-REPO="" +-VERSION="" +-AGENT_PATH="" +-PASSWD="" +-DOCKER_IMG="" +-DOCKERFILE="" +-LOCK=./test.lock +-ADMIN_CONTAINER_DIR=./admin-container +-BOOT_MODE=efi +- +-source common/globalVariables.sh &>/dev/null +-source common/log.sh &>/dev/null +-source common/utils.sh &>/dev/null +-source create/rootfsCreate.sh &>/dev/null +-source create/imageCreate.sh &>/dev/null +-source 00bootup/Global.cfg &>/dev/null +- +-function show_options() { +- cat << EOF +- +-Usage : sh kbimg [COMMAND] [OPTIONS] +- +-kbimg is a tool used to handle KubeOS image , like create KubeOS images +- +-Commands: +- create create KubeOS images +-Options: +- -h,--help show help information +- +-Run 'kbimg COMMAND --help' for more information on a command. +-EOF +-} +- +-function show_create_usage() { +- cat << EOF +- +-Usage : kbimg create [COMMAND] [OPTIONS] +- +-commands: +- upgrade-image create KubeOS OCI image used for installation and upgrade +- vm-image create KubeOS virtual machine image +- pxe-image create images required for KubeOS PXE installation on physical machines +- admin-image create KubeOS admin container OCI image used for debug of worker nodes in clusters +-options: +- -h,--help show help information +- +-Run 'kbimg create COMMAND --help' for more information on a command. +-EOF +-} +- +-function show_upgrade_image_usage() { +- cat << EOF +- +-Usage : kbimg create upgrade-image -p isopath -v osversion -b osagentdir -e ospassword -d repository/name:tag +- +-options: +- -p repo path +- -v KubeOS version +- -b directory of os-agent binary +- -e os encrypted password +- -d docker image like repository/name:tag +- -l boot to legacy BIOS mode, if not specify, then UEFI mode +- -h,--help show help information +-EOF +-} +- +-function show_vm_pxe_image_usage() { +- cat << EOF +- +-Usage : kbimg create [vm-image|pxe-image] -p iso-path -v os-version -b os-agent-dir -e os-password +- or +- kbimg create [vm-image|pxe-image] -d repository/name:tag +- +-options: +- -p repo path +- -v KubeOS version +- -b directory of os-agent binary +- -e os encrypted password +- -d docker image like repository/name:tag +- -l boot to legacy BIOS mode, if not specify, then UEFI mode +- -h,--help show help information +-EOF +-} +- +-function show_admin_image_usage() { +- cat << EOF +- +-Usage : kbimg create admin-image -f dockerfile-path -d repository/name:tag +- +-options: +- -f Dockerfile path +- -d admin container image like repository/name:tag +- -h,--help show help information +-EOF +-} +- +-function file_lock() { +- local lock_file=$1 +- exec {lock_fd}>"${lock_file}" +- flock -xn "${lock_fd}" +-} +- +-function test_lock() { +- file_lock "${LOCK}" +- if [ $? -ne 0 ]; then +- log_error_print "There is already an generate process running." +- exit 203 +- fi +-} +- +-function clean_space() { +- delete_dir "${RPM_ROOT}" +- delete_dir "${TMP_MOUNT_PATH}" +- delete_file os.tar +- rm -rf "${LOCK}" +- delete_file ${ADMIN_CONTAINER_DIR}/hostshell +-} +- +-function clean_img() { +- delete_file system.img +- delete_file update.img +- delete_file initramfs.img +- delete_file kubeos.tar +-} +- +-function verify_upgrade_image_input() { +- set +eE +- for i in "p" "v" "b" "e" "d" +- do +- echo "$@" | grep -q "\-$i " +- if [ "$?" -ne 0 ];then +- log_error_print "option -$i is mandatory, please check input" +- show_upgrade_image_usage +- exit 3 +- fi +- done +- set -eE +- while getopts "p:v:e:b:d:l" opt +- do +- case $opt in +- p) +- check_param $OPTARG +- REPO="$OPTARG" +- ;; +- v) +- check_param $OPTARG +- VERSION="$OPTARG" +- ;; +- b) +- check_param $OPTARG +- AGENT_PATH="$OPTARG" +- ;; +- e) +- # encrypted password contains special characters.,not verify. +- PASSWD="$OPTARG" +- ;; +- d) +- check_param $OPTARG +- DOCKER_IMG="$OPTARG" +- ;; +- l) +- BOOT_MODE=legacy +- ;; +- *) +- log_error_print "option $opt not found" +- show_upgrade_image_usage +- exit 3 +- ;; +- esac +- done +-} +- +-function verify_repo_input() { +- set +eE +- for i in "p" "v" "b" "e" +- do +- echo "$@" | grep -q "\-$i " +- if [ "$?" -ne 0 ];then +- log_error_print "option -$i is mandatory, please check input" +- show_vm_pxe_image_usage +- exit 3 +- fi +- done +- set -eE +- while getopts "p:v:e:b:l" opt +- do +- case $opt in +- p) +- check_param $OPTARG +- REPO="$OPTARG" +- ;; +- v) +- check_param $OPTARG +- VERSION="$OPTARG" +- ;; +- b) +- check_param $OPTARG +- AGENT_PATH="$OPTARG" +- ;; +- e) +- # encrypted password contains special characters.,not verify. +- PASSWD="$OPTARG" +- ;; +- l) +- BOOT_MODE=legacy +- ;; +- *) +- log_error_print "option $opt not found" +- show_vm_pxe_image_usage +- exit 3 +- ;; +- esac +- done +-} +- +-function verify_docker_input() { +- if [ $1 != "-d" ]; then +- log_error_print "option $1 not found" +- show_vm_pxe_image_usage +- exit 3 +- fi +- check_param $2 +- DOCKER_IMG=$2 +-} +- +-function verify_admin_input() { +- set +eE +- for i in "f" "d" +- do +- echo "$@" | grep -q "\-$i " +- if [ "$?" -ne 0 ];then +- log_error_print "option -$i is mandatory, please check input" +- show_admin_image_usage +- exit 3 +- fi +- done +- set -eE +- while getopts "f:d:" opt +- do +- case $opt in +- f) +- check_param $OPTARG +- DOCKERFILE="$OPTARG" +- ;; +- d) +- check_param $OPTARG +- DOCKER_IMG="$OPTARG" +- ;; +- *) +- log_error_print "option $opt not found" +- show_admin_image_usage +- exit 3 +- ;; +- esac +- done +-} +- +-function verify_create_input() { +- local ret= +- local cmd=$1 +- case $1 in +- "upgrade-image") +- shift +- if [ $# -eq 1 ]; then +- if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then +- show_upgrade_image_usage +- exit 0 +- fi +- fi +- if [[ $# -ne 10 && $# -ne 11 ]]; then +- log_error_print "the number of parameters is incorrect, please check it." +- show_upgrade_image_usage +- exit 3 +- fi +- check_disk_space "docker" +- verify_upgrade_image_input "$@" +- check_repo_path "${REPO}" +- check_binary_exist "${AGENT_PATH}" +- create_docker_image "${REPO}" "${VERSION}" "${AGENT_PATH}" "${PASSWD}" "${BOOT_MODE}" "${DOCKER_IMG}" +- ;; +- "vm-image") +- shift +- if [ $# -eq 1 ]; then +- if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then +- show_vm_pxe_image_usage +- exit 0 +- fi +- fi +- check_disk_space "vm" +- if [[ $# -eq 8 || $# -eq 9 ]]; then +- verify_repo_input "$@" +- check_repo_path "${REPO}" +- check_binary_exist "${AGENT_PATH}" +- create_vm_img "repo" "${REPO}" "${VERSION}" "${AGENT_PATH}" "${PASSWD}" "${BOOT_MODE}" +- elif [ $# -eq 2 ]; then +- verify_docker_input "$@" +- check_docker_exist "${DOCKER_IMG}" +- create_vm_img "docker" "${DOCKER_IMG}" +- else +- log_error_print "the number of parameters is incorrect, please check it." +- show_vm_pxe_image_usage +- exit 3 +- fi +- ;; +- "pxe-image") +- shift +- if [ $# -eq 1 ]; then +- if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then +- show_vm_pxe_image_usage +- exit 0 +- fi +- fi +- check_disk_space "pxe" +- check_conf_valid ${rootfs_name} ${disk} ${server_ip} ${local_ip} ${route_ip} ${netmask} ${net_name} +- if [ $# -eq 8 ]; then +- verify_repo_input "$@" +- check_repo_path "${REPO}" +- check_binary_exist "${AGENT_PATH}" +- create_pxe_img "repo" "${REPO}" "${VERSION}" "${AGENT_PATH}" "${PASSWD}" +- elif [ $# -eq 2 ]; then +- verify_docker_input "$@" +- check_docker_exist "${DOCKER_IMG}" +- create_pxe_img "docker" "${DOCKER_IMG}" +- else +- log_error_print "the number of parameters is incorrect, please check it." +- show_vm_pxe_image_usage +- exit 3 +- fi +- ;; +- "admin-image") +- shift +- if [ $# -eq 1 ]; then +- if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then +- show_admin_image_usage +- exit 0 +- fi +- fi +- if [ $# -ne 4 ]; then +- log_error_print "the number of parameters is incorrect, please check it." +- show_admin_image_usage +- exit 3 +- fi +- verify_admin_input "$@" +- check_docker_file "${DOCKERFILE}" +- create_admin_img "${DOCKERFILE}" "${DOCKER_IMG}" "${ADMIN_CONTAINER_DIR}" +- ;; +- "-h"|"--help") +- show_create_usage +- ;; +- *) +- log_error_print "error command $1 not found" +- show_create_usage +- exit 3 +- esac +-} +- +-function kubeos_image_main() { +- local ret= +- local cmd=$1 +- if [ "$#" -eq 1 ]; then +- case $1 in +- -h|--help) +- show_options +- exit 0;; +- *) +- log_error_print "params is invalid,please check it." +- show_options +- exit 3;; +- esac +- fi +- case $cmd in +- create) +- shift +- verify_create_input "$@" +- ;; +- *) +- log_error_print "command $1 not found" +- show_options +- exit 3 +- ;; +- esac +-} +- +-test_lock +-trap clean_space EXIT +-trap clean_img ERR +- +-kubeos_image_main "$@" +diff --git a/scripts/rpmlist b/scripts/rpmlist +deleted file mode 100644 +index fb6f238..0000000 +--- a/scripts/rpmlist ++++ /dev/null +@@ -1,22 +0,0 @@ +-kernel +-passwd +-dhcp +-NetworkManager +-openssh-server +-docker +-kubernetes-kubeadm +-kubernetes-kubelet +-containernetworking-plugins +-socat +-conntrack-tools +-ebtables +-ethtool +-rsyslog +-vi +-net-tools +-hwinfo +-dracut +-coreutils +-gawk +-parted +-dosfstools +\ No newline at end of file +diff --git a/scripts/set_in_chroot.sh b/scripts/set_in_chroot.sh +deleted file mode 100644 +index 80b5a91..0000000 +--- a/scripts/set_in_chroot.sh ++++ /dev/null +@@ -1,20 +0,0 @@ +-#!/bin/bash +-ln -s /usr/lib/systemd/system/os-agent.service /usr/lib/systemd/system/multi-user.target.wants/os-agent.service +-ln -s /usr/lib/systemd/system/kubelet.service /usr/lib/systemd/system/multi-user.target.wants/kubelet.service +-if [ "$BOOT_MODE" = "legacy" ]; then +- ln -s /usr/lib/systemd/system/boot-grub2.mount /lib/systemd/system/local-fs.target.wants/boot-grub2.mount +-else +- ln -s /usr/lib/systemd/system/boot-efi.mount /lib/systemd/system/local-fs.target.wants/boot-efi.mount +-fi +-ln -s /usr/lib/systemd/system/etc.mount /lib/systemd/system/local-fs.target.wants/etc.mount +- +-str=`sed -n '/^root:/p' /etc/shadow | awk -F "root:" '{print $2}'` +-umask 0666 +-mv /etc/shadow /etc/shadow_bak +-sed -i '/^root:/d' /etc/shadow_bak +-echo "root:"${ROOT_PWD}${str:1} > /etc/shadow +-cat /etc/shadow_bak >> /etc/shadow +-rm -rf /etc/shadow_bak +- +-dracut -f -v --add bootup /initramfs.img --kver `ls /lib/modules` +-rm -rf /usr/lib/dracut/modules.d/00bootup +\ No newline at end of file +-- +2.39.0 + diff --git a/KubeOS.spec b/KubeOS.spec index 1e01e62..b992901 100644 --- a/KubeOS.spec +++ b/KubeOS.spec @@ -2,11 +2,32 @@ Name: KubeOS Version: 1.0.4 -Release: 2 +Release: 3 Summary: O&M platform used to update the whole OS as an entirety License: Mulan PSL v2 Source0: https://gitee.com/openeuler/KubeOS/repository/archive/v%{version}.tar.gz -Patch1: 0001-KubeOS-fix-the-hostshell-cannot-obtain-the-lib.patch +Patch1: 0001-KubeOS-add-unit-tests-of-containerd-and-docker-modif.patch +Patch2: 0002-KubeOS-refactor-assignUpgrade-function.patch +Patch3: 0003-KubeOS-fix-the-hostshell-cannot-obtain-the-lib.patch +Patch4: 0004-KubeOS-add-agent-proxy-and-operator-ut.patch +Patch5: 0005-KubeOS-fix-validate-image-name-bug.patch +Patch6: 0006-KubeOS-fix-a-bug-that-failed-to-parse-key-with.patch +Patch7: 0007-KubeOS-add-warning-log-during-config.patch +Patch8: 0008-KubeOS-modify-log-level-and-content.patch +Patch9: 0009-KubeOS-fix-updating-key-to-kv.patch +Patch10: 0010-KubeOS-fix-proxy-requeue-bug.patch +Patch11: 0011-KubeOS-fix-operator-bug-of-missing-deep-copy.patch +Patch12: 0012-KubeOS-add-unit-test.patch +Patch13: 0013-KubeOS-fix-clean-space-problems.patch +Patch14: 0014-KubeOS-update-version.patch +Patch15: 0015-KubeOS-add-line-breaks.patch +Patch16: 0016-KubeOS-modify-code-for-clean-code.patch +Patch17: 0017-KubeOS-fix-the-issue-that-osinstance-is-not-updated-.patch +Patch18: 0018-KubeOS-update-config-log-contents.patch +Patch19: 0019-KubeOS-add-unit-tests.patch +Patch20: 0020-KubeOS-modify-code-for-clean-code.patch +Patch21: 0021-KubeOS-delete-raw-and-docker-image-upgrade.patch +Patch22: 0022-KubeOS-delete-scripts-except-admin-container.patch BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRequires: make @@ -83,6 +104,12 @@ install -p -m 0600 ./scripts/admin-container/set-ssh-pub-key.service %{buildroot rm -rfv %{buildroot} %changelog +* Thu Aug 24 2023 Yuhang Wei - 1.0.4-3 +- Type:requirement +- CVE:NA +- SUG:restart +- DESC:sync code from source master branch + * Mon Aug 07 2023 liyuanrong - 1.0.4-2 - Type:requirement - CVE:NA -- Gitee