Repository: kubeclipper/kubeclipper
Pull request #532 (2023-01-18 05:29:59)

add some unit test for pkg
Signed-off-by: Liucw <liu.chuwei@99cloud.net>

<!-- Thanks for sending a pull request! Here are some tips for you:

1. If you want **faster** PR reviews, read how: https://github.com/kubeclipper/community/blob/master/developer-guide/development/the-pr-author-guide-to-getting-through-code-review.md
2. In case you want to know how your PR got reviewed, read: https://github.com/kubeclipper/community/blob/master/developer-guide/development/code-review-guide.md
3. Here are some coding convetions followed by Kubeclipper community: https://github.com/kubeclipper/community/blob/master/developer-guide/development/coding-conventions.md
-->

### What type of PR is this?
/kind feature
<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind design

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->


### What this PR does / why we need it:

### Which issue(s) this PR fixes:
<!--
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes https://github.com/kubeclipper/kubeclipper/issues/481

### Special notes for reviewers:
```
```

### Does this PR introduced a user-facing change?
<!--
If no, just write "None" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information on release notes see: https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md
-->
```release-note
None
```

### Additional documentation, usage docs, etc.:
<!--
This section can be blank if this pull request does not require a release note.
Please use the following format for linking documentation or pass the
section below:
- [KEP]: <link>
- [Usage]: <link>
- [Other doc]: <link>
-->
```docs

```
@zhuzhenfan @x893675 

kubeclipper-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubeclipper/kubeclipper/pull/532#" title="Author self-approved">Metrora</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **wu-wenxiang** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @wu-wenxiang` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubeclipper%2Fkubeclipper).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubeclipper/kubeclipper/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["wu-wenxiang"]} -->
codecov-commenter: # [Codecov](https://codecov.io/gh/kubeclipper/kubeclipper/pull/532?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubeclipper) Report
> Merging [#532](https://codecov.io/gh/kubeclipper/kubeclipper/pull/532?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubeclipper) (5a6facb) into [master](https://codecov.io/gh/kubeclipper/kubeclipper/commit/5137c9634cf5d91bf1ca01250a815771f625f060?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubeclipper) (5137c96) will **increase** coverage by `0.17%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/kubeclipper/kubeclipper/pull/532/graphs/tree.svg?width=650&height=150&src=pr&token=3PWFG2MHTE&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubeclipper)](https://codecov.io/gh/kubeclipper/kubeclipper/pull/532?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubeclipper)

```diff
@@            Coverage Diff             @@
##           master     #532      +/-   ##
==========================================
+ Coverage   14.11%   14.29%   +0.17%     
==========================================
  Files         133      137       +4     
  Lines       18851    19163     +312     
==========================================
+ Hits         2661     2739      +78     
- Misses      15901    16129     +228     
- Partials      289      295       +6     
```


| [Impacted Files](https://codecov.io/gh/kubeclipper/kubeclipper/pull/532?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubeclipper) | Coverage Δ | |
|---|---|---|
| [pkg/oplog/operation\_log.go](https://codecov.io/gh/kubeclipper/kubeclipper/pull/532?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubeclipper#diff-cGtnL29wbG9nL29wZXJhdGlvbl9sb2cuZ28=) | `23.52% <0.00%> (ø)` | |
| [pkg/oplog/options.go](https://codecov.io/gh/kubeclipper/kubeclipper/pull/532?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubeclipper#diff-cGtnL29wbG9nL29wdGlvbnMuZ28=) | `0.00% <0.00%> (ø)` | |
| [pkg/errors/errors.go](https://codecov.io/gh/kubeclipper/kubeclipper/pull/532?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubeclipper#diff-cGtnL2Vycm9ycy9lcnJvcnMuZ28=) | `68.75% <0.00%> (ø)` | |
| [pkg/nodestatus/node\_status.go](https://codecov.io/gh/kubeclipper/kubeclipper/pull/532?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubeclipper#diff-cGtnL25vZGVzdGF0dXMvbm9kZV9zdGF0dXMuZ28=) | `11.37% <0.00%> (ø)` | |
| [pkg/models/platform/platformsetting.go](https://codecov.io/gh/kubeclipper/kubeclipper/pull/532?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubeclipper#diff-cGtnL21vZGVscy9wbGF0Zm9ybS9wbGF0Zm9ybXNldHRpbmcuZ28=) | `12.50% <0.00%> (+3.12%)` | :arrow_up: |
| [pkg/logger/logger.go](https://codecov.io/gh/kubeclipper/kubeclipper/pull/532?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubeclipper#diff-cGtnL2xvZ2dlci9sb2dnZXIuZ28=) | `26.78% <0.00%> (+4.76%)` | :arrow_up: |
| [pkg/models/iam/utils.go](https://codecov.io/gh/kubeclipper/kubeclipper/pull/532?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubeclipper#diff-cGtnL21vZGVscy9pYW0vdXRpbHMuZ28=) | `42.85% <0.00%> (+42.85%)` | :arrow_up: |

zhuzhenfan: /lgtm
kubeclipper-bot: LGTM label has been added.  <details>Git tree hash: b410eec2096cd69ed0067f509cac1c8e31583d00</details>

Patch:
diff --git a/pkg/controller/cronbackupcontroller/controller.go b/pkg/controller/cronbackupcontroller/controller.go
index d712f09..6377cc1 100644
--- a/pkg/controller/cronbackupcontroller/controller.go
+++ b/pkg/controller/cronbackupcontroller/controller.go
@@ -338,8 +338,21 @@ func (r *CronBackupReconciler) createBackup(log logger.Logging, cronBackup *v1.C
 
 	steps := make([]v1.Step, 0)
 	var actBackup *k8s.ActBackup
+	var masters component.NodeList
+	for _, no := range nodeList {
+		masters = append(masters, component.Node{
+			ID:       no.Name,
+			IPv4:     no.Status.Ipv4DefaultIP,
+			NodeIPv4: no.Status.NodeIpv4DefaultIP,
+			Region:   no.Labels[common.LabelTopologyRegion],
+			Hostname: no.Labels[common.LabelHostname],
+			Role:     no.Labels[common.LabelNodeRole],
+			//Disable:  no.Labels[common.LabelNodeDisable]
+		})
+	}
 	meta := component.ExtraMetadata{
 		ClusterName: c.Name,
+		Masters:     masters,
 	}
 	meta.Masters = append(meta.Masters, component.Node{ID: backup.PreferredNode})
 	ctx := component.WithExtraMetadata(context.TODO(), meta)
diff --git a/pkg/scheme/core/v1/cluster_types.go b/pkg/scheme/core/v1/cluster_types.go
index 8f8a4b0..903f21c 100644
--- a/pkg/scheme/core/v1/cluster_types.go
+++ b/pkg/scheme/core/v1/cluster_types.go
@@ -257,8 +257,9 @@ type Etcd struct {
 }
 
 type Kubelet struct {
-	RootDir string `json:"rootDir" yaml:"rootDir"`
-	NodeIP  string `json:"nodeIP" yaml:"nodeIP"`
+	RootDir  string `json:"rootDir" yaml:"rootDir"`
+	NodeIP   string `json:"nodeIP" yaml:"nodeIP"`
+	IPAsName bool   `json:"ipAsName" yaml:"ipAsName"`
 }
 
 type KubeProxy struct {
diff --git a/pkg/scheme/core/v1/k8s/template.go b/pkg/scheme/core/v1/k8s/template.go
index ff1cce2..489e26e 100644
--- a/pkg/scheme/core/v1/k8s/template.go
+++ b/pkg/scheme/core/v1/k8s/template.go
@@ -94,6 +94,9 @@ localAPIEndpoint:
   advertiseAddress: {{.AdvertiseAddress}}
   bindPort: 6443
 nodeRegistration:
+{{- if .Kubelet.IPAsName }}
+  name: "{{.Kubelet.NodeIP}}"
+{{- end}}
 {{- if eq .ContainerRuntime  "containerd"}}
   criSocket: /run/containerd/containerd.sock
 {{end}}
@@ -118,6 +121,9 @@ controlPlane:
     bindPort: 6443
   certificateKey: {{.CertificateKey}}{{end}}
 nodeRegistration:
+{{- if .Kubelet.IPAsName }}
+  name: "{{.Kubelet.NodeIP}}"
+{{- end}}
 {{- if eq .ContainerRuntime  "containerd"}}
   criSocket: /run/containerd/containerd.sock
 {{end}}


Test patch:
diff --git a/pkg/errors/errors_test.go b/pkg/errors/errors_test.go
new file mode 100644
index 0000000..c06421f
--- /dev/null
+++ b/pkg/errors/errors_test.go
@@ -0,0 +1,246 @@
+package errors
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestStatusError_Error(t *testing.T) {
+	type fields struct {
+		Message string
+		Reason  StatusReason
+		Details *StatusDetails
+		Code    int32
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		want   string
+	}{
+		{
+			name: "base",
+			fields: fields{
+				Message: "Demo failed",
+				Reason:  StatusReasonUnknown,
+			},
+			want: "Demo failed due to reason ",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := &StatusError{
+				Message: tt.fields.Message,
+				Reason:  tt.fields.Reason,
+				Details: tt.fields.Details,
+				Code:    tt.fields.Code,
+			}
+			if got := s.Error(); got != tt.want {
+				t.Errorf("Error() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestStatusErrorCause(t *testing.T) {
+	type args struct {
+		err  error
+		name CauseType
+	}
+	tests := []struct {
+		name  string
+		args  args
+		want  StatusCause
+		want1 bool
+	}{
+		{
+			name: "base",
+			args: args{
+				err: &StatusError{
+					Message: "This is a demo error",
+					Details: &StatusDetails{
+						Causes: []StatusCause{
+							{
+								Type:    Marshal,
+								Message: "marshal error",
+							},
+						},
+					},
+				},
+				name: Marshal,
+			},
+			want: StatusCause{
+				Message: "marshal error",
+				Type:    Marshal,
+			},
+			want1: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, got1 := StatusErrorCause(tt.args.err, tt.args.name)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("StatusErrorCause() got = %v, want %v", got, tt.want)
+			}
+			if got1 != tt.want1 {
+				t.Errorf("StatusErrorCause() got1 = %v, want %v", got1, tt.want1)
+			}
+		})
+	}
+}
+
+func TestIsConflict(t *testing.T) {
+	type args struct {
+		err error
+	}
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			name: "base",
+			args: args{
+				err: &StatusError{
+					Code: 409,
+				},
+			},
+			want: true,
+		},
+		{
+			name: "not a conflict error",
+			args: args{
+				err: &StatusError{
+					Code: 404,
+				},
+			},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := IsConflict(tt.args.err); got != tt.want {
+				t.Errorf("IsConflict() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestIsNotFound(t *testing.T) {
+	type args struct {
+		err error
+	}
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			name: "base",
+			args: args{
+				err: &StatusError{
+					Code: 404,
+				},
+			},
+			want: true,
+		},
+		{
+			name: "message contains not found",
+			args: args{
+				err: &StatusError{
+					Reason: "This is a error: not found",
+				},
+			},
+			want: true,
+		},
+		{
+			name: "not a not found error",
+			args: args{
+				err: &StatusError{
+					Code: 409,
+				},
+			},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := IsNotFound(tt.args.err); got != tt.want {
+				t.Errorf("IsNotFound() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestIsInternalError(t *testing.T) {
+	type args struct {
+		err error
+	}
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			name: "base",
+			args: args{
+				err: &StatusError{
+					Code: 500,
+				},
+			},
+			want: true,
+		},
+		{
+			name: "not a internal error",
+			args: args{
+				err: &StatusError{
+					Code: 404,
+				},
+			},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := IsInternalError(tt.args.err); got != tt.want {
+				t.Errorf("IsInternalError() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestIsTooManyRequests(t *testing.T) {
+	type args struct {
+		err error
+	}
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			name: "base",
+			args: args{
+				err: &StatusError{
+					Code: 429,
+				},
+			},
+			want: true,
+		},
+		{
+			name: "not a too many request error",
+			args: args{
+				err: &StatusError{
+					Code: 404,
+				},
+			},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := IsTooManyRequests(tt.args.err); got != tt.want {
+				t.Errorf("IsTooManyRequests() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/pkg/logger/logger_test.go b/pkg/logger/logger_test.go
index a89ae48..2a9207d 100644
--- a/pkg/logger/logger_test.go
+++ b/pkg/logger/logger_test.go
@@ -21,6 +21,8 @@ package logger
 import (
 	"testing"
 
+	"go.uber.org/zap/zapcore"
+
 	"go.uber.org/zap"
 )
 
@@ -85,3 +87,83 @@ func BenchmarkInfoWithJsonEncode(b *testing.B) {
 	})
 	b.StopTimer()
 }
+
+func Test_convertZapLogLevel(t *testing.T) {
+	type args struct {
+		level string
+	}
+	tests := []struct {
+		name string
+		args args
+		want zapcore.Level
+	}{
+		{
+			name: "info log",
+			args: args{
+				level: "info",
+			},
+			want: 0,
+		},
+		{
+			name: "debug log",
+			args: args{
+				level: "debug",
+			},
+			want: -1,
+		},
+		{
+			name: "warn log",
+			args: args{
+				level: "warn",
+			},
+			want: 1,
+		},
+		{
+			name: "error log",
+			args: args{
+				level: "error",
+			},
+			want: 2,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := convertZapLogLevel(tt.args.level); got != tt.want {
+				t.Errorf("convertZapLogLevel() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_convertZapLogEncode(t *testing.T) {
+	type args struct {
+		encode string
+	}
+	tests := []struct {
+		name string
+		args args
+		want EncodeType
+	}{
+		{
+			name: "Json encode",
+			args: args{
+				encode: "json",
+			},
+			want: 2,
+		},
+		{
+			name: "console encode",
+			args: args{
+				encode: "console",
+			},
+			want: 1,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := convertZapLogEncode(tt.args.encode); got != tt.want {
+				t.Errorf("convertZapLogEncode() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/pkg/models/iam/utils_test.go b/pkg/models/iam/utils_test.go
new file mode 100644
index 0000000..9a82ba6
--- /dev/null
+++ b/pkg/models/iam/utils_test.go
@@ -0,0 +1,154 @@
+package iam
+
+import (
+	"testing"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func Test_userList_Len(t *testing.T) {
+	tests := []struct {
+		name string
+		l    userList
+		want int
+	}{
+		{
+			name: "base",
+			l: userList{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "Tom",
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "Jack",
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "Lisa",
+					},
+				},
+			},
+			want: 3,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.l.Len(); got != tt.want {
+				t.Errorf("Len() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_userList_Swap(t *testing.T) {
+	type args struct {
+		i int
+		j int
+	}
+	tests := []struct {
+		name string
+		l    userList
+		args args
+		want userList
+	}{
+		{
+			name: "base",
+			l: userList{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "Tom",
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "Jack",
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "Lisa",
+					},
+				},
+			},
+			args: args{
+				i: 1,
+				j: 2,
+			},
+			want: userList{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "Tom",
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "Lisa",
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "Jack",
+					},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			tt.l.Swap(tt.args.i, tt.args.j)
+		})
+	}
+}
+
+func Test_userList_Less(t *testing.T) {
+	now := time.Now()
+	af := time.Now()
+	type args struct {
+		i int
+		j int
+	}
+	tests := []struct {
+		name string
+		l    userList
+		args args
+		want bool
+	}{
+		{
+			name: "base",
+			l: userList{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "Tom",
+						CreationTimestamp: metav1.Time{
+							Time: now,
+						},
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "Jack",
+						CreationTimestamp: metav1.Time{
+							Time: af,
+						},
+					},
+				},
+			},
+			args: args{
+				i: 0,
+				j: 1,
+			},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.l.Less(tt.args.i, tt.args.j); got != tt.want {
+				t.Errorf("Less() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/pkg/models/platform/platformsetting_test.go b/pkg/models/platform/platformsetting_test.go
index 64969f4..4195e12 100644
--- a/pkg/models/platform/platformsetting_test.go
+++ b/pkg/models/platform/platformsetting_test.go
@@ -81,3 +81,35 @@ func Test_platformOperator_eventFilter(t *testing.T) {
 		})
 	}
 }
+
+func Test_eventCustomFilter(t *testing.T) {
+	type args struct {
+		ev    *v1.Event
+		key   string
+		value string
+	}
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			name: "base",
+			args: args{
+				ev: &v1.Event{
+					Username: "Tom",
+				},
+				key:   "name",
+				value: "Tom",
+			},
+			want: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := eventCustomFilter(tt.args.ev, tt.args.key, tt.args.value); got != tt.want {
+				t.Errorf("eventCustomFilter() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/pkg/nodestatus/node_status_test.go b/pkg/nodestatus/node_status_test.go
new file mode 100644
index 0000000..c921419
--- /dev/null
+++ b/pkg/nodestatus/node_status_test.go
@@ -0,0 +1,111 @@
+package nodestatus
+
+import (
+	"reflect"
+	"testing"
+
+	v1 "github.com/kubeclipper/kubeclipper/pkg/scheme/core/v1"
+	"github.com/kubeclipper/kubeclipper/pkg/utils/sysutil"
+)
+
+func Test_attachedVolumes(t *testing.T) {
+	type args struct {
+		d sysutil.Disk
+	}
+	tests := []struct {
+		name string
+		args args
+		want []v1.AttachedVolume
+	}{
+		{
+			name: "base",
+			args: args{
+				d: sysutil.Disk{
+					DiskDevices: []sysutil.DiskDevice{
+						{
+							Device:     "device1",
+							Mountpoint: "/root/point1",
+						},
+						{
+							Device:     "device2",
+							Mountpoint: "/root/point2",
+						},
+						{
+							Device:     "device3",
+							Mountpoint: "/root/point3",
+						},
+					},
+				},
+			},
+			want: []v1.AttachedVolume{
+				{
+					Name:       "device1",
+					DevicePath: "/root/point1",
+				},
+				{
+					Name:       "device2",
+					DevicePath: "/root/point2",
+				},
+				{
+					Name:       "device3",
+					DevicePath: "/root/point3",
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := attachedVolumes(tt.args.d); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("attachedVolumes() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_toNodeAddress(t *testing.T) {
+	type args struct {
+		n []sysutil.Net
+	}
+	tests := []struct {
+		name string
+		args args
+		want []v1.NodeAddress
+	}{
+		{
+			name: "base",
+			args: args{
+				n: []sysutil.Net{
+					{
+						Addrs: []sysutil.InterfaceAddr{
+							{
+								Family: "family1",
+								Addr:   "10.0.0.1",
+							},
+							{
+								Family: "family1",
+								Addr:   "10.0.0.2",
+							},
+						},
+					},
+				},
+			},
+			want: []v1.NodeAddress{
+				{
+					Type:    "family1",
+					Address: "10.0.0.1",
+				},
+				{
+					Type:    "family1",
+					Address: "10.0.0.2",
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := toNodeAddress(tt.args.n); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("toNodeAddress() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/pkg/oplog/operation_log_test.go b/pkg/oplog/operation_log_test.go
new file mode 100644
index 0000000..6979a91
--- /dev/null
+++ b/pkg/oplog/operation_log_test.go
@@ -0,0 +1,212 @@
+package oplog
+
+import "testing"
+
+func TestOperationLog_GetRootDir(t *testing.T) {
+	type fields struct {
+		cfg    *Options
+		suffix string
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		want   string
+	}{
+		{
+			name: "base",
+			fields: fields{
+				cfg: &Options{
+					Dir: "/root/demo",
+				},
+			},
+			want: "/root/demo",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			op := &OperationLog{
+				cfg:    tt.fields.cfg,
+				suffix: tt.fields.suffix,
+			}
+			if got := op.GetRootDir(); got != tt.want {
+				t.Errorf("GetRootDir() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestOperationLog_CreateOperationDir(t *testing.T) {
+	type fields struct {
+		cfg    *Options
+		suffix string
+	}
+	type args struct {
+		opID string
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "op ID is invalid",
+			fields: fields{
+				cfg: &Options{
+					Dir: "/root/thisisademo",
+				},
+			},
+			args: args{
+				opID: "",
+			},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			op := &OperationLog{
+				cfg:    tt.fields.cfg,
+				suffix: tt.fields.suffix,
+			}
+			if err := op.CreateOperationDir(tt.args.opID); (err != nil) != tt.wantErr {
+				t.Errorf("CreateOperationDir() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestOperationLog_GetOperationDir(t *testing.T) {
+	type fields struct {
+		cfg    *Options
+		suffix string
+	}
+	type args struct {
+		opID string
+	}
+	tests := []struct {
+		name     string
+		fields   fields
+		args     args
+		wantPath string
+		wantErr  bool
+	}{
+		{
+			name: "base",
+			fields: fields{
+				cfg: &Options{
+					Dir: "/root/demo",
+				},
+			},
+			args: args{
+				opID: "123456",
+			},
+			wantPath: "/root/demo/123456",
+			wantErr:  false,
+		},
+		{
+			name: "op ID is invalid",
+			fields: fields{
+				cfg: &Options{
+					Dir: "/root/demo",
+				},
+			},
+			args: args{
+				opID: "",
+			},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			op := &OperationLog{
+				cfg:    tt.fields.cfg,
+				suffix: tt.fields.suffix,
+			}
+			gotPath, err := op.GetOperationDir(tt.args.opID)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("GetOperationDir() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if gotPath != tt.wantPath {
+				t.Errorf("GetOperationDir() gotPath = %v, want %v", gotPath, tt.wantPath)
+			}
+		})
+	}
+}
+
+func TestOperationLog_GetStepLogFile(t *testing.T) {
+	type fields struct {
+		cfg    *Options
+		suffix string
+	}
+	type args struct {
+		opID   string
+		stepID string
+	}
+	tests := []struct {
+		name     string
+		fields   fields
+		args     args
+		wantPath string
+		wantErr  bool
+	}{
+		{
+			name: "base",
+			fields: fields{
+				cfg: &Options{
+					Dir: "/root/demo",
+				},
+			},
+			args: args{
+				opID:   "123456",
+				stepID: "654321",
+			},
+			wantPath: "/root/demo/123456/654321.log",
+			wantErr:  false,
+		},
+		{
+			name: "op ID is empty",
+			fields: fields{
+				cfg: &Options{
+					Dir: "/root/demo",
+				},
+			},
+			args: args{
+				opID:   "",
+				stepID: "12345",
+			},
+			wantPath: "",
+			wantErr:  true,
+		},
+		{
+			name: "step ID is empty",
+			fields: fields{
+				cfg: &Options{
+					Dir: "/root/demo",
+				},
+			},
+			args: args{
+				opID:   "12345",
+				stepID: "",
+			},
+			wantPath: "",
+			wantErr:  true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			op := &OperationLog{
+				cfg:    tt.fields.cfg,
+				suffix: tt.fields.suffix,
+			}
+			gotPath, err := op.GetStepLogFile(tt.args.opID, tt.args.stepID)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("GetStepLogFile() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if gotPath != tt.wantPath {
+				t.Errorf("GetStepLogFile() gotPath = %v, want %v", gotPath, tt.wantPath)
+			}
+		})
+	}
+}
diff --git a/pkg/scheme/core/v1/k8s/kubeadm_test.go b/pkg/scheme/core/v1/k8s/kubeadm_test.go
index 80913e4..8f64711 100644
--- a/pkg/scheme/core/v1/k8s/kubeadm_test.go
+++ b/pkg/scheme/core/v1/k8s/kubeadm_test.go
@@ -81,6 +81,29 @@ func TestKubeadmConfig_renderTo(t *testing.T) {
 				LocalRegistry:        "127.0.0.1:5000",
 			},
 		},
+		{
+			name: "node-ip as node-name",
+			fields: fields{
+				ClusterConfigAPIVersion: "v1beta3",
+				ContainerRuntime:        "containerd",
+				Etcd:                    v1.Etcd{DataDir: "/var/lib/etcd"},
+				Networking: v1.Networking{
+					IPFamily:      v1.IPFamilyIPv4,
+					Services:      v1.NetworkRanges{CIDRBlocks: []string{constatns.ClusterServiceSubnet}},
+					Pods:          v1.NetworkRanges{CIDRBlocks: []string{constatns.ClusterPodSubnet}},
+					DNSDomain:     "cluster.local",
+					ProxyMode:     "ipvs",
+					WorkerNodeVip: "8.8.8.8",
+				},
+				KubeProxy:            v1.KubeProxy{},
+				Kubelet:              v1.Kubelet{RootDir: "/var/lib/kubelet", NodeIP: "127.0.0.1", IPAsName: true},
+				ClusterName:          "test-cluster",
+				KubernetesVersion:    "v1.23.6",
+				ControlPlaneEndpoint: "apiserver.cluster.local:6443",
+				CertSANs:             []string{"127.0.0.1"},
+				LocalRegistry:        "127.0.0.1:5000",
+			},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -214,6 +237,34 @@ func TestKubeadmConfig_renderJoin(t *testing.T) {
 				CertificateKey:       "CertificateKey",
 			},
 		},
+		{
+			name: "node-ip as node-name",
+			fields: fields{
+				ClusterConfigAPIVersion: "v1beta3",
+				ContainerRuntime:        "containerd",
+				Etcd:                    v1.Etcd{DataDir: "/var/lib/etcd"},
+				Networking: v1.Networking{
+					IPFamily:      v1.IPFamilyIPv4,
+					Services:      v1.NetworkRanges{CIDRBlocks: []string{constatns.ClusterServiceSubnet}},
+					Pods:          v1.NetworkRanges{CIDRBlocks: []string{constatns.ClusterPodSubnet}},
+					DNSDomain:     "cluster.local",
+					ProxyMode:     "ipvs",
+					WorkerNodeVip: "8.8.8.8",
+				},
+				KubeProxy:            v1.KubeProxy{},
+				Kubelet:              v1.Kubelet{RootDir: "/var/lib/kubelet", NodeIP: "127.0.0.1", IPAsName: true},
+				ClusterName:          "test-cluster",
+				KubernetesVersion:    "v1.23.6",
+				ControlPlaneEndpoint: "apiserver.cluster.local:6443",
+				CertSANs:             []string{"127.0.0.1"},
+				LocalRegistry:        "127.0.0.1:5000",
+				Offline:              true,
+				IsControlPlane:       true,
+				CACertHashes:         "hash1",
+				BootstrapToken:       "BootstrapToken",
+				CertificateKey:       "CertificateKey",
+			},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {