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