Repository: openyurtio/openyurt
Pull request #932 (2022-07-26 10:39:34)

remove redundant sig storage label in pr template
<!--  Thanks for sending a pull request!  Here are some tips for you:
https://github.com/openyurtio/openyurt/blob/master/CONTRIBUTING.md 
-->


#### What type of PR is this?
/kind documentation




#### What this PR does / why we need it:

#### Which issue(s) this PR fixes:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #

#### Special notes for your reviewer:
<!--
use this label to assign your reviewer
/assign @your_reviewer
-->


#### Does this PR introduce 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".

-->
```release-note

```

#### other Note
<!--
If your current PR is still working in process, start the PR title name with [WIP], such as: [WIP] add new crd for yurt-app-manager
If the PR title name begins with [WIP], OpenYurt-bot automatically adds a do-not-merge/work-in-progress label for your pr 
-->


openyurt-bot: @Congrool: GitHub didn't allow me to assign the following users: your_reviewer.

Note that only [openyurtio members](https://github.com/orgs/openyurtio/people), repo collaborators and people who have commented on this issue/PR can be assigned. Additionally, issues/PRs can only have 10 assignees at the same time.
For more information please see [the contributor guide](https://git.k8s.io/community/contributors/guide/#issue-assignment-in-github)

<details>

In response to [this](https://github.com/openyurtio/openyurt/pull/932):

><!--  Thanks for sending a pull request!  Here are some tips for you:
>https://github.com/openyurtio/openyurt/blob/master/CONTRIBUTING.md 
>-->
>
>
>#### What type of PR is this?
>/kind documentation
>
>
>
>
>#### What this PR does / why we need it:
>
>#### Which issue(s) this PR fixes:
><!--
>*Automatically closes linked issue when PR is merged.
>Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
>-->
>Fixes #
>
>#### Special notes for your reviewer:
><!--
>use this label to assign your reviewer
>/assign @your_reviewer
>-->
>
>
>#### Does this PR introduce 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".
>
>-->
>```release-note
>
>```
>
>#### other Note
><!--
>If your current PR is still working in process, start the PR title name with [WIP], such as: [WIP] add new crd for yurt-app-manager
>If the PR title name begins with [WIP], OpenYurt-bot automatically adds a do-not-merge/work-in-progress label for your pr 
>-->
>


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
openyurt-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/openyurtio/openyurt/pull/932#" title="Author self-approved">Congrool</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **fei-guo**
You can assign the PR to them by writing `/assign @fei-guo` 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=openyurtio%2Fopenyurt).

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

- **[OWNERS](https://github.com/openyurtio/openyurt/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":["fei-guo"]} -->
codecov[bot]: # [Codecov](https://codecov.io/gh/openyurtio/openyurt/pull/932?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openyurtio) Report
> Merging [#932](https://codecov.io/gh/openyurtio/openyurt/pull/932?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openyurtio) (6883c41) into [master](https://codecov.io/gh/openyurtio/openyurt/commit/bdcb53d3de3871d00ac60d0db42ec0f1f4cb9607?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openyurtio) (bdcb53d) will **not change** coverage.
> The diff coverage is `n/a`.

```diff
@@           Coverage Diff           @@
##           master     #932   +/-   ##
=======================================
  Coverage   35.37%   35.37%           
=======================================
  Files          76       76           
  Lines        9913     9913           
=======================================
  Hits         3507     3507           
  Misses       6141     6141           
  Partials      265      265           
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `35.37% <ø> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openyurtio#carryforward-flags-in-the-pull-request-comment) to find out more.


Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openyurtio).

openyurt-bot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/openyurtio/openyurt/pull/932#" title="Author self-approved">Congrool</a>*, *<a href="https://github.com/openyurtio/openyurt/pull/932#pullrequestreview-1051815002" title="Approved">rambohe-ch</a>*

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

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

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

- ~~[OWNERS](https://github.com/openyurtio/openyurt/blob/master/OWNERS)~~ [rambohe-ch]

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":[]} -->
rambohe-ch: /lgtm
/approve

Patch:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 6290062d..ae604bd3 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -2,9 +2,8 @@
 https://github.com/openyurtio/openyurt/blob/master/CONTRIBUTING.md 
 -->
 
-
 #### What type of PR is this?
-> Uncomment only one ` /kind <>` line, hit enter to put that in a new line, and remove leading whitespace from that line:
+> Uncomment only one `/kind <>` line, hit enter to put that in a new line, and remove leading whitespace from that line:
 > /kind bug
 > /kind documentation
 > /kind enhancement
@@ -16,9 +15,6 @@ https://github.com/openyurtio/openyurt/blob/master/CONTRIBUTING.md
 > /sig iot
 > /sig network
 > /sig storage
-> /sig storage
-
-
 
 #### What this PR does / why we need it:
 
@@ -35,7 +31,6 @@ use this label to assign your reviewer
 /assign @your_reviewer
 -->
 
-
 #### Does this PR introduce a user-facing change?
 <!--
 If no, just write "NONE" in the release-note block below.
diff --git a/go.mod b/go.mod
index 2bcccfa8..b7bd9e50 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@ require (
 	github.com/daviddengcn/go-colortext v1.0.0
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/emicklei/go-restful v2.12.0+incompatible // indirect
+	github.com/evanphx/json-patch v4.11.0+incompatible
 	github.com/fsnotify/fsnotify v1.4.10-0.20200417215612-7f4cf4dd2b52 // indirect
 	github.com/google/uuid v1.1.2
 	github.com/gorilla/mux v1.7.4
diff --git a/pkg/controller/kubernetes/util/node/node.go b/pkg/controller/kubernetes/util/node/node.go
index 01efb201..47600bcf 100644
--- a/pkg/controller/kubernetes/util/node/node.go
+++ b/pkg/controller/kubernetes/util/node/node.go
@@ -15,8 +15,19 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
+/*
+This file was derived from k8s.io/kubernetes/pkg/util/node/node.go
+at commit: 27522a29feb.
+
+CHANGELOG from OpenYurt Authors:
+1. Pick constant NodeUnreachablePodReason and NodeUnreachablePodMessage.
+2. Pick function GetZoneKey.
+*/
+
 package node
 
+import v1 "k8s.io/api/core/v1"
+
 const (
 	// NodeUnreachablePodReason is the reason on a pod when its state cannot be confirmed as kubelet is unresponsive
 	// on the node it is (was) running.
@@ -25,3 +36,38 @@ const (
 	// on the node it is (was) running.
 	NodeUnreachablePodMessage = "Node %v which was running pod %v is unresponsive"
 )
+
+// GetZoneKey is a helper function that builds a string identifier that is unique per failure-zone;
+// it returns empty-string for no zone.
+// Since there are currently two separate zone keys:
+//   * "failure-domain.beta.kubernetes.io/zone"
+//   * "topology.kubernetes.io/zone"
+// GetZoneKey will first check failure-domain.beta.kubernetes.io/zone and if not exists, will then check
+// topology.kubernetes.io/zone
+func GetZoneKey(node *v1.Node) string {
+	labels := node.Labels
+	if labels == nil {
+		return ""
+	}
+
+	// TODO: prefer stable labels for zone in v1.18
+	zone, ok := labels[v1.LabelFailureDomainBetaZone]
+	if !ok {
+		zone, _ = labels[v1.LabelTopologyZone]
+	}
+
+	// TODO: prefer stable labels for region in v1.18
+	region, ok := labels[v1.LabelFailureDomainBetaRegion]
+	if !ok {
+		region, _ = labels[v1.LabelTopologyRegion]
+	}
+
+	if region == "" && zone == "" {
+		return ""
+	}
+
+	// We include the null character just in case region or failureDomain has a colon
+	// (We do assume there's no null characters in a region or failureDomain)
+	// As a nice side-benefit, the null character is not printed by fmt.Print or glog
+	return region + ":\x00:" + zone
+}


Test patch:
diff --git a/pkg/controller/kubernetes/controller/testutil/test_utils.go b/pkg/controller/kubernetes/controller/testutil/test_utils.go
new file mode 100644
index 00000000..4be401d5
--- /dev/null
+++ b/pkg/controller/kubernetes/controller/testutil/test_utils.go
@@ -0,0 +1,485 @@
+/*
+Copyright 2022 The OpenYurt Authors.
+Copyright 2016 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+/*
+This file was derived from k8s.io/kubernetes/pkg/controller/testutil/test_utils.go
+at commit: 27522a29feb.
+
+CHANGELOG from OpenYurt Authors:
+1. Replace FakeNodeHandler.Create with an empty function
+2. Remove FakeNodeHandler.GetUpdatedNodesCopy
+3. Remove function GetKey
+4. Remove keyFunc
+5. Add empty functions: FakeNodeHandler.Apply, FakeNodeHandler.ApplyStatus
+6. Use runtime.NewScheme() instead legacyscheme.Scheme in FakeRecorder.generateEvent
+*/
+
+package testutil
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"sync"
+	"time"
+
+	jsonpatch "github.com/evanphx/json-patch"
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/clock"
+	"k8s.io/apimachinery/pkg/util/sets"
+	"k8s.io/apimachinery/pkg/util/strategicpatch"
+	"k8s.io/apimachinery/pkg/watch"
+	corev1 "k8s.io/client-go/applyconfigurations/core/v1"
+	"k8s.io/client-go/kubernetes/fake"
+	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
+	ref "k8s.io/client-go/tools/reference"
+	"k8s.io/klog/v2"
+
+	utilnode "github.com/openyurtio/openyurt/pkg/controller/kubernetes/util/node"
+)
+
+// FakeNodeHandler is a fake implementation of NodesInterface and NodeInterface. It
+// allows test cases to have fine-grained control over mock behaviors. We also need
+// PodsInterface and PodInterface to test list & delete pods, which is implemented in
+// the embedded client.Fake field.
+type FakeNodeHandler struct {
+	*fake.Clientset
+
+	// Input: Hooks determine if request is valid or not
+	CreateHook func(*FakeNodeHandler, *v1.Node) bool
+	Existing   []*v1.Node
+
+	// Output
+	CreatedNodes        []*v1.Node
+	DeletedNodes        []*v1.Node
+	UpdatedNodes        []*v1.Node
+	UpdatedNodeStatuses []*v1.Node
+	RequestCount        int
+
+	// Synchronization
+	lock           sync.Mutex
+	DeleteWaitChan chan struct{}
+	PatchWaitChan  chan struct{}
+}
+
+// FakeLegacyHandler is a fake implementation of CoreV1Interface.
+type FakeLegacyHandler struct {
+	v1core.CoreV1Interface
+	n *FakeNodeHandler
+}
+
+// Core returns fake CoreInterface.
+func (m *FakeNodeHandler) Core() v1core.CoreV1Interface {
+	return &FakeLegacyHandler{m.Clientset.CoreV1(), m}
+}
+
+// CoreV1 returns fake CoreV1Interface
+func (m *FakeNodeHandler) CoreV1() v1core.CoreV1Interface {
+	return &FakeLegacyHandler{m.Clientset.CoreV1(), m}
+}
+
+// Nodes return fake NodeInterfaces.
+func (m *FakeLegacyHandler) Nodes() v1core.NodeInterface {
+	return m.n
+}
+
+// TODO:
+// We add this empty function, because the code of test_utils.go is from kubernetes v1.20.11, while the
+// client-go is v0.22.3. We can fulfill it when we bump our controller code to v1.22.3.
+func (m *FakeNodeHandler) Apply(_ context.Context, node *corev1.NodeApplyConfiguration, opts metav1.ApplyOptions) (result *v1.Node, err error) {
+	return nil, nil
+}
+
+// TODO:
+// We add this empty function, because the code of test_utils.go is from kubernetes v1.20.11, while the
+// client-go is v0.22.3. We can fulfill it when we bump our controller code to v1.22.3.
+func (m *FakeNodeHandler) ApplyStatus(ctx context.Context, node *corev1.NodeApplyConfiguration, opts metav1.ApplyOptions) (result *v1.Node, err error) {
+	return nil, nil
+}
+
+// Create adds a new Node to the fake store.
+func (m *FakeNodeHandler) Create(_ context.Context, node *v1.Node, _ metav1.CreateOptions) (*v1.Node, error) {
+	// OpenYurt Authors:
+	// We make this function empty, because currently we don't use it.
+	// And the original code will import "k8s.io/kubernetes/pkg/apis/core".
+	return nil, nil
+}
+
+// Get returns a Node from the fake store.
+func (m *FakeNodeHandler) Get(_ context.Context, name string, opts metav1.GetOptions) (*v1.Node, error) {
+	m.lock.Lock()
+	defer func() {
+		m.RequestCount++
+		m.lock.Unlock()
+	}()
+	for i := range m.UpdatedNodes {
+		if m.UpdatedNodes[i].Name == name {
+			nodeCopy := *m.UpdatedNodes[i]
+			return &nodeCopy, nil
+		}
+	}
+	for i := range m.Existing {
+		if m.Existing[i].Name == name {
+			nodeCopy := *m.Existing[i]
+			return &nodeCopy, nil
+		}
+	}
+	return nil, nil
+}
+
+// List returns a list of Nodes from the fake store.
+func (m *FakeNodeHandler) List(_ context.Context, opts metav1.ListOptions) (*v1.NodeList, error) {
+	m.lock.Lock()
+	defer func() {
+		m.RequestCount++
+		m.lock.Unlock()
+	}()
+	var nodes []*v1.Node
+	for i := 0; i < len(m.UpdatedNodes); i++ {
+		if !contains(m.UpdatedNodes[i], m.DeletedNodes) {
+			nodes = append(nodes, m.UpdatedNodes[i])
+		}
+	}
+	for i := 0; i < len(m.Existing); i++ {
+		if !contains(m.Existing[i], m.DeletedNodes) && !contains(m.Existing[i], nodes) {
+			nodes = append(nodes, m.Existing[i])
+		}
+	}
+	for i := 0; i < len(m.CreatedNodes); i++ {
+		if !contains(m.CreatedNodes[i], m.DeletedNodes) && !contains(m.CreatedNodes[i], nodes) {
+			nodes = append(nodes, m.CreatedNodes[i])
+		}
+	}
+	nodeList := &v1.NodeList{}
+	for _, node := range nodes {
+		nodeList.Items = append(nodeList.Items, *node)
+	}
+	return nodeList, nil
+}
+
+// Delete deletes a Node from the fake store.
+func (m *FakeNodeHandler) Delete(_ context.Context, id string, opt metav1.DeleteOptions) error {
+	m.lock.Lock()
+	defer func() {
+		m.RequestCount++
+		if m.DeleteWaitChan != nil {
+			m.DeleteWaitChan <- struct{}{}
+		}
+		m.lock.Unlock()
+	}()
+	m.DeletedNodes = append(m.DeletedNodes, NewNode(id))
+	return nil
+}
+
+// DeleteCollection deletes a collection of Nodes from the fake store.
+func (m *FakeNodeHandler) DeleteCollection(_ context.Context, opt metav1.DeleteOptions, listOpts metav1.ListOptions) error {
+	return nil
+}
+
+// Update updates a Node in the fake store.
+func (m *FakeNodeHandler) Update(_ context.Context, node *v1.Node, _ metav1.UpdateOptions) (*v1.Node, error) {
+	m.lock.Lock()
+	defer func() {
+		m.RequestCount++
+		m.lock.Unlock()
+	}()
+
+	nodeCopy := *node
+	for i, updateNode := range m.UpdatedNodes {
+		if updateNode.Name == nodeCopy.Name {
+			m.UpdatedNodes[i] = &nodeCopy
+			return node, nil
+		}
+	}
+	m.UpdatedNodes = append(m.UpdatedNodes, &nodeCopy)
+	return node, nil
+}
+
+// UpdateStatus updates a status of a Node in the fake store.
+func (m *FakeNodeHandler) UpdateStatus(_ context.Context, node *v1.Node, _ metav1.UpdateOptions) (*v1.Node, error) {
+	m.lock.Lock()
+	defer func() {
+		m.RequestCount++
+		m.lock.Unlock()
+	}()
+
+	var origNodeCopy v1.Node
+	found := false
+	for i := range m.Existing {
+		if m.Existing[i].Name == node.Name {
+			origNodeCopy = *m.Existing[i]
+			found = true
+			break
+		}
+	}
+	updatedNodeIndex := -1
+	for i := range m.UpdatedNodes {
+		if m.UpdatedNodes[i].Name == node.Name {
+			origNodeCopy = *m.UpdatedNodes[i]
+			updatedNodeIndex = i
+			found = true
+			break
+		}
+	}
+
+	if !found {
+		return nil, fmt.Errorf("not found node %v", node)
+	}
+
+	origNodeCopy.Status = node.Status
+	if updatedNodeIndex < 0 {
+		m.UpdatedNodes = append(m.UpdatedNodes, &origNodeCopy)
+	} else {
+		m.UpdatedNodes[updatedNodeIndex] = &origNodeCopy
+	}
+
+	nodeCopy := *node
+	m.UpdatedNodeStatuses = append(m.UpdatedNodeStatuses, &nodeCopy)
+	return node, nil
+}
+
+// PatchStatus patches a status of a Node in the fake store.
+func (m *FakeNodeHandler) PatchStatus(ctx context.Context, nodeName string, data []byte) (*v1.Node, error) {
+	m.RequestCount++
+	return m.Patch(ctx, nodeName, types.StrategicMergePatchType, data, metav1.PatchOptions{}, "status")
+}
+
+// Watch watches Nodes in a fake store.
+func (m *FakeNodeHandler) Watch(_ context.Context, opts metav1.ListOptions) (watch.Interface, error) {
+	return watch.NewFake(), nil
+}
+
+// Patch patches a Node in the fake store.
+func (m *FakeNodeHandler) Patch(_ context.Context, name string, pt types.PatchType, data []byte, _ metav1.PatchOptions, subresources ...string) (*v1.Node, error) {
+	m.lock.Lock()
+	defer func() {
+		m.RequestCount++
+		if m.PatchWaitChan != nil {
+			m.PatchWaitChan <- struct{}{}
+		}
+		m.lock.Unlock()
+	}()
+	var nodeCopy v1.Node
+	for i := range m.Existing {
+		if m.Existing[i].Name == name {
+			nodeCopy = *m.Existing[i]
+		}
+	}
+	updatedNodeIndex := -1
+	for i := range m.UpdatedNodes {
+		if m.UpdatedNodes[i].Name == name {
+			nodeCopy = *m.UpdatedNodes[i]
+			updatedNodeIndex = i
+		}
+	}
+
+	originalObjJS, err := json.Marshal(nodeCopy)
+	if err != nil {
+		klog.Errorf("Failed to marshal %v", nodeCopy)
+		return nil, nil
+	}
+	var originalNode v1.Node
+	if err = json.Unmarshal(originalObjJS, &originalNode); err != nil {
+		klog.Errorf("Failed to unmarshal original object: %v", err)
+		return nil, nil
+	}
+
+	var patchedObjJS []byte
+	switch pt {
+	case types.JSONPatchType:
+		patchObj, err := jsonpatch.DecodePatch(data)
+		if err != nil {
+			klog.Error(err.Error())
+			return nil, nil
+		}
+		if patchedObjJS, err = patchObj.Apply(originalObjJS); err != nil {
+			klog.Error(err.Error())
+			return nil, nil
+		}
+	case types.MergePatchType:
+		if patchedObjJS, err = jsonpatch.MergePatch(originalObjJS, data); err != nil {
+			klog.Error(err.Error())
+			return nil, nil
+		}
+	case types.StrategicMergePatchType:
+		if patchedObjJS, err = strategicpatch.StrategicMergePatch(originalObjJS, data, originalNode); err != nil {
+			klog.Error(err.Error())
+			return nil, nil
+		}
+	default:
+		klog.Errorf("unknown Content-Type header for patch: %v", pt)
+		return nil, nil
+	}
+
+	var updatedNode v1.Node
+	if err = json.Unmarshal(patchedObjJS, &updatedNode); err != nil {
+		klog.Errorf("Failed to unmarshal patched object: %v", err)
+		return nil, nil
+	}
+
+	if updatedNodeIndex < 0 {
+		m.UpdatedNodes = append(m.UpdatedNodes, &updatedNode)
+	} else {
+		m.UpdatedNodes[updatedNodeIndex] = &updatedNode
+	}
+
+	return &updatedNode, nil
+}
+
+// FakeRecorder is used as a fake during testing.
+type FakeRecorder struct {
+	sync.Mutex
+	source v1.EventSource
+	Events []*v1.Event
+	clock  clock.Clock
+}
+
+// Event emits a fake event to the fake recorder
+func (f *FakeRecorder) Event(obj runtime.Object, eventtype, reason, message string) {
+	f.generateEvent(obj, metav1.Now(), eventtype, reason, message)
+}
+
+// Eventf emits a fake formatted event to the fake recorder
+func (f *FakeRecorder) Eventf(obj runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
+	f.Event(obj, eventtype, reason, fmt.Sprintf(messageFmt, args...))
+}
+
+// AnnotatedEventf emits a fake formatted event to the fake recorder
+func (f *FakeRecorder) AnnotatedEventf(obj runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) {
+	f.Eventf(obj, eventtype, reason, messageFmt, args...)
+}
+
+func (f *FakeRecorder) generateEvent(obj runtime.Object, timestamp metav1.Time, eventtype, reason, message string) {
+	f.Lock()
+	defer f.Unlock()
+	legacyScheme := runtime.NewScheme()
+	ref, err := ref.GetReference(legacyScheme, obj)
+	if err != nil {
+		klog.Errorf("Encountered error while getting reference: %v", err)
+		return
+	}
+	event := f.makeEvent(ref, eventtype, reason, message)
+	event.Source = f.source
+	if f.Events != nil {
+		f.Events = append(f.Events, event)
+	}
+}
+
+func (f *FakeRecorder) makeEvent(ref *v1.ObjectReference, eventtype, reason, message string) *v1.Event {
+	t := metav1.Time{Time: f.clock.Now()}
+	namespace := ref.Namespace
+	if namespace == "" {
+		namespace = metav1.NamespaceDefault
+	}
+
+	clientref := v1.ObjectReference{
+		Kind:            ref.Kind,
+		Namespace:       ref.Namespace,
+		Name:            ref.Name,
+		UID:             ref.UID,
+		APIVersion:      ref.APIVersion,
+		ResourceVersion: ref.ResourceVersion,
+		FieldPath:       ref.FieldPath,
+	}
+
+	return &v1.Event{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      fmt.Sprintf("%v.%x", ref.Name, t.UnixNano()),
+			Namespace: namespace,
+		},
+		InvolvedObject: clientref,
+		Reason:         reason,
+		Message:        message,
+		FirstTimestamp: t,
+		LastTimestamp:  t,
+		Count:          1,
+		Type:           eventtype,
+	}
+}
+
+// NewFakeRecorder returns a pointer to a newly constructed FakeRecorder.
+func NewFakeRecorder() *FakeRecorder {
+	return &FakeRecorder{
+		source: v1.EventSource{Component: "nodeControllerTest"},
+		Events: []*v1.Event{},
+		clock:  clock.NewFakeClock(time.Now()),
+	}
+}
+
+// NewNode is a helper function for creating Nodes for testing.
+func NewNode(name string) *v1.Node {
+	return &v1.Node{
+		ObjectMeta: metav1.ObjectMeta{Name: name},
+		Status: v1.NodeStatus{
+			Capacity: v1.ResourceList{
+				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+				v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+			},
+		},
+	}
+}
+
+// NewPod is a helper function for creating Pods for testing.
+func NewPod(name, host string) *v1.Pod {
+	pod := &v1.Pod{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "default",
+			Name:      name,
+		},
+		Spec: v1.PodSpec{
+			NodeName: host,
+		},
+		Status: v1.PodStatus{
+			Conditions: []v1.PodCondition{
+				{
+					Type:   v1.PodReady,
+					Status: v1.ConditionTrue,
+				},
+			},
+		},
+	}
+
+	return pod
+}
+
+func contains(node *v1.Node, nodes []*v1.Node) bool {
+	for i := 0; i < len(nodes); i++ {
+		if node.Name == nodes[i].Name {
+			return true
+		}
+	}
+	return false
+}
+
+// GetZones returns list of zones for all Nodes stored in FakeNodeHandler
+func GetZones(nodeHandler *FakeNodeHandler) []string {
+	nodes, _ := nodeHandler.List(context.TODO(), metav1.ListOptions{})
+	zones := sets.NewString()
+	for _, node := range nodes.Items {
+		zones.Insert(utilnode.GetZoneKey(&node))
+	}
+	return zones.List()
+}
+
+// CreateZoneID returns a single zoneID for a given region and zone.
+func CreateZoneID(region, zone string) string {
+	return region + ":\x00:" + zone
+}
diff --git a/pkg/controller/kubernetes/util/node/node_test.go b/pkg/controller/kubernetes/util/node/node_test.go
new file mode 100644
index 00000000..863a5fd9
--- /dev/null
+++ b/pkg/controller/kubernetes/util/node/node_test.go
@@ -0,0 +1,114 @@
+/*
+Copyright 2022 The OpenYurt Authors.
+Copyright 2015 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+/*
+This file was derived from k8s.io/kubernetes/pkg/util/node/node.go
+at commit: 27522a29feb.
+
+CHANGELOG from OpenYurt Authors:
+1. Pick Test_GetZoneKey.
+*/
+
+package node
+
+import (
+	"testing"
+
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func Test_GetZoneKey(t *testing.T) {
+	tests := []struct {
+		name string
+		node *v1.Node
+		zone string
+	}{
+		{
+			name: "has no zone or region keys",
+			node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: map[string]string{},
+				},
+			},
+			zone: "",
+		},
+		{
+			name: "has beta zone and region keys",
+			node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: map[string]string{
+						v1.LabelFailureDomainBetaZone:   "zone1",
+						v1.LabelFailureDomainBetaRegion: "region1",
+					},
+				},
+			},
+			zone: "region1:\x00:zone1",
+		},
+		{
+			name: "has GA zone and region keys",
+			node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: map[string]string{
+						v1.LabelTopologyZone:   "zone1",
+						v1.LabelTopologyRegion: "region1",
+					},
+				},
+			},
+			zone: "region1:\x00:zone1",
+		},
+		{
+			name: "has both beta and GA zone and region keys",
+			node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: map[string]string{
+						v1.LabelTopologyZone:            "zone1",
+						v1.LabelTopologyRegion:          "region1",
+						v1.LabelFailureDomainBetaZone:   "zone1",
+						v1.LabelFailureDomainBetaRegion: "region1",
+					},
+				},
+			},
+			zone: "region1:\x00:zone1",
+		},
+		{
+			name: "has both beta and GA zone and region keys, beta labels take precedent",
+			node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: map[string]string{
+						v1.LabelTopologyZone:            "zone1",
+						v1.LabelTopologyRegion:          "region1",
+						v1.LabelFailureDomainBetaZone:   "zone2",
+						v1.LabelFailureDomainBetaRegion: "region2",
+					},
+				},
+			},
+			zone: "region2:\x00:zone2",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			zone := GetZoneKey(test.node)
+			if zone != test.zone {
+				t.Logf("actual zone key: %q", zone)
+				t.Logf("expected zone key: %q", test.zone)
+				t.Errorf("unexpected zone key")
+			}
+		})
+	}
+}
diff --git a/pkg/controller/kubernetes/util/taints/taints_test.go b/pkg/controller/kubernetes/util/taints/taints_test.go
new file mode 100644
index 00000000..d8900450
--- /dev/null
+++ b/pkg/controller/kubernetes/util/taints/taints_test.go
@@ -0,0 +1,460 @@
+/*
+Copyright 2022 The OpenYurt Authors.
+Copyright 2015 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+/*
+This file was derived from k8s.io/kubernetes/pkg/util/node/node.go
+at commit: 27522a29feb.
+
+CHANGELOG from OpenYurt Authors:
+1. Pick TestDeleteTaint, TestRemoveTaint, TestAddOrUpdateTaint, TestTaintExists.
+2. Add TestTaintSetFilter which is picked from f31bf3ff1.
+*/
+
+package taints
+
+import (
+	"reflect"
+	"testing"
+
+	v1 "k8s.io/api/core/v1"
+)
+
+func TestDeleteTaint(t *testing.T) {
+	cases := []struct {
+		name           string
+		taints         []v1.Taint
+		taintToDelete  *v1.Taint
+		expectedTaints []v1.Taint
+		expectedResult bool
+	}{
+		{
+			name: "delete taint with different name",
+			taints: []v1.Taint{
+				{
+					Key:    "foo",
+					Effect: v1.TaintEffectNoSchedule,
+				},
+			},
+			taintToDelete: &v1.Taint{Key: "foo_1", Effect: v1.TaintEffectNoSchedule},
+			expectedTaints: []v1.Taint{
+				{
+					Key:    "foo",
+					Effect: v1.TaintEffectNoSchedule,
+				},
+			},
+			expectedResult: false,
+		},
+		{
+			name: "delete taint with different effect",
+			taints: []v1.Taint{
+				{
+					Key:    "foo",
+					Effect: v1.TaintEffectNoSchedule,
+				},
+			},
+			taintToDelete: &v1.Taint{Key: "foo", Effect: v1.TaintEffectNoExecute},
+			expectedTaints: []v1.Taint{
+				{
+					Key:    "foo",
+					Effect: v1.TaintEffectNoSchedule,
+				},
+			},
+			expectedResult: false,
+		},
+		{
+			name: "delete taint successfully",
+			taints: []v1.Taint{
+				{
+					Key:    "foo",
+					Effect: v1.TaintEffectNoSchedule,
+				},
+			},
+			taintToDelete:  &v1.Taint{Key: "foo", Effect: v1.TaintEffectNoSchedule},
+			expectedTaints: []v1.Taint{},
+			expectedResult: true,
+		},
+		{
+			name:           "delete taint from empty taint array",
+			taints:         []v1.Taint{},
+			taintToDelete:  &v1.Taint{Key: "foo", Effect: v1.TaintEffectNoSchedule},
+			expectedTaints: []v1.Taint{},
+			expectedResult: false,
+		},
+	}
+
+	for _, c := range cases {
+		taints, result := DeleteTaint(c.taints, c.taintToDelete)
+		if result != c.expectedResult {
+			t.Errorf("[%s] should return %t, but got: %t", c.name, c.expectedResult, result)
+		}
+		if !reflect.DeepEqual(taints, c.expectedTaints) {
+			t.Errorf("[%s] the result taints should be %v, but got: %v", c.name, c.expectedTaints, taints)
+		}
+	}
+}
+
+func TestRemoveTaint(t *testing.T) {
+	cases := []struct {
+		name           string
+		node           *v1.Node
+		taintToRemove  *v1.Taint
+		expectedTaints []v1.Taint
+		expectedResult bool
+	}{
+		{
+			name: "remove taint unsuccessfully",
+			node: &v1.Node{
+				Spec: v1.NodeSpec{
+					Taints: []v1.Taint{
+						{
+							Key:    "foo",
+							Effect: v1.TaintEffectNoSchedule,
+						},
+					},
+				},
+			},
+			taintToRemove: &v1.Taint{
+				Key:    "foo_1",
+				Effect: v1.TaintEffectNoSchedule,
+			},
+			expectedTaints: []v1.Taint{
+				{
+					Key:    "foo",
+					Effect: v1.TaintEffectNoSchedule,
+				},
+			},
+			expectedResult: false,
+		},
+		{
+			name: "remove taint successfully",
+			node: &v1.Node{
+				Spec: v1.NodeSpec{
+					Taints: []v1.Taint{
+						{
+							Key:    "foo",
+							Effect: v1.TaintEffectNoSchedule,
+						},
+					},
+				},
+			},
+			taintToRemove: &v1.Taint{
+				Key:    "foo",
+				Effect: v1.TaintEffectNoSchedule,
+			},
+			expectedTaints: []v1.Taint{},
+			expectedResult: true,
+		},
+		{
+			name: "remove taint from node with no taint",
+			node: &v1.Node{
+				Spec: v1.NodeSpec{
+					Taints: []v1.Taint{},
+				},
+			},
+			taintToRemove: &v1.Taint{
+				Key:    "foo",
+				Effect: v1.TaintEffectNoSchedule,
+			},
+			expectedTaints: []v1.Taint{},
+			expectedResult: false,
+		},
+	}
+
+	for _, c := range cases {
+		newNode, result, err := RemoveTaint(c.node, c.taintToRemove)
+		if err != nil {
+			t.Errorf("[%s] should not raise error but got: %v", c.name, err)
+		}
+		if result != c.expectedResult {
+			t.Errorf("[%s] should return %t, but got: %t", c.name, c.expectedResult, result)
+		}
+		if !reflect.DeepEqual(newNode.Spec.Taints, c.expectedTaints) {
+			t.Errorf("[%s] the new node object should have taints %v, but got: %v", c.name, c.expectedTaints, newNode.Spec.Taints)
+		}
+	}
+}
+
+func TestAddOrUpdateTaint(t *testing.T) {
+	node := &v1.Node{}
+
+	taint := &v1.Taint{
+		Key:    "foo",
+		Value:  "bar",
+		Effect: v1.TaintEffectNoSchedule,
+	}
+
+	checkResult := func(testCaseName string, newNode *v1.Node, expectedTaint *v1.Taint, result, expectedResult bool, err error) {
+		if err != nil {
+			t.Errorf("[%s] should not raise error but got %v", testCaseName, err)
+		}
+		if result != expectedResult {
+			t.Errorf("[%s] should return %t, but got: %t", testCaseName, expectedResult, result)
+		}
+		if len(newNode.Spec.Taints) != 1 || !reflect.DeepEqual(newNode.Spec.Taints[0], *expectedTaint) {
+			t.Errorf("[%s] node should only have one taint: %v, but got: %v", testCaseName, *expectedTaint, newNode.Spec.Taints)
+		}
+	}
+
+	// Add a new Taint.
+	newNode, result, err := AddOrUpdateTaint(node, taint)
+	checkResult("Add New Taint", newNode, taint, result, true, err)
+
+	// Update a Taint.
+	taint.Value = "bar_1"
+	newNode, result, err = AddOrUpdateTaint(node, taint)
+	checkResult("Update Taint", newNode, taint, result, true, err)
+
+	// Add a duplicate Taint.
+	node = newNode
+	newNode, result, err = AddOrUpdateTaint(node, taint)
+	checkResult("Add Duplicate Taint", newNode, taint, result, false, err)
+}
+
+func TestTaintExists(t *testing.T) {
+	testingTaints := []v1.Taint{
+		{
+			Key:    "foo_1",
+			Value:  "bar_1",
+			Effect: v1.TaintEffectNoExecute,
+		},
+		{
+			Key:    "foo_2",
+			Value:  "bar_2",
+			Effect: v1.TaintEffectNoSchedule,
+		},
+	}
+
+	cases := []struct {
+		name           string
+		taintToFind    *v1.Taint
+		expectedResult bool
+	}{
+		{
+			name:           "taint exists",
+			taintToFind:    &v1.Taint{Key: "foo_1", Value: "bar_1", Effect: v1.TaintEffectNoExecute},
+			expectedResult: true,
+		},
+		{
+			name:           "different key",
+			taintToFind:    &v1.Taint{Key: "no_such_key", Value: "bar_1", Effect: v1.TaintEffectNoExecute},
+			expectedResult: false,
+		},
+		{
+			name:           "different effect",
+			taintToFind:    &v1.Taint{Key: "foo_1", Value: "bar_1", Effect: v1.TaintEffectNoSchedule},
+			expectedResult: false,
+		},
+	}
+
+	for _, c := range cases {
+		result := TaintExists(testingTaints, c.taintToFind)
+
+		if result != c.expectedResult {
+			t.Errorf("[%s] unexpected results: %v", c.name, result)
+			continue
+		}
+	}
+}
+
+func TestTaintSetDiff(t *testing.T) {
+	cases := []struct {
+		name                   string
+		t1                     []v1.Taint
+		t2                     []v1.Taint
+		expectedTaintsToAdd    []*v1.Taint
+		expectedTaintsToRemove []*v1.Taint
+	}{
+		{
+			name:                   "two_taints_are_nil",
+			expectedTaintsToAdd:    nil,
+			expectedTaintsToRemove: nil,
+		},
+		{
+			name: "one_taint_is_nil_and_the_other_is_not_nil",
+			t1: []v1.Taint{
+				{
+					Key:    "foo_1",
+					Value:  "bar_1",
+					Effect: v1.TaintEffectNoExecute,
+				},
+				{
+					Key:    "foo_2",
+					Value:  "bar_2",
+					Effect: v1.TaintEffectNoSchedule,
+				},
+			},
+			expectedTaintsToAdd: []*v1.Taint{
+				{
+					Key:    "foo_1",
+					Value:  "bar_1",
+					Effect: v1.TaintEffectNoExecute,
+				},
+				{
+					Key:    "foo_2",
+					Value:  "bar_2",
+					Effect: v1.TaintEffectNoSchedule,
+				},
+			},
+			expectedTaintsToRemove: nil,
+		},
+		{
+			name: "shared_taints_with_the_same_key_value_effect",
+			t1: []v1.Taint{
+				{
+					Key:    "foo_1",
+					Value:  "bar_1",
+					Effect: v1.TaintEffectNoExecute,
+				},
+				{
+					Key:    "foo_2",
+					Value:  "bar_2",
+					Effect: v1.TaintEffectNoSchedule,
+				},
+			},
+			t2: []v1.Taint{
+				{
+					Key:    "foo_3",
+					Value:  "bar_3",
+					Effect: v1.TaintEffectNoExecute,
+				},
+				{
+					Key:    "foo_2",
+					Value:  "bar_2",
+					Effect: v1.TaintEffectNoSchedule,
+				},
+			},
+			expectedTaintsToAdd: []*v1.Taint{
+				{
+					Key:    "foo_1",
+					Value:  "bar_1",
+					Effect: v1.TaintEffectNoExecute,
+				},
+			},
+			expectedTaintsToRemove: []*v1.Taint{
+				{
+					Key:    "foo_3",
+					Value:  "bar_3",
+					Effect: v1.TaintEffectNoExecute,
+				},
+			},
+		},
+		{
+			name: "shared_taints_with_the_same_key_effect_different_value",
+			t1: []v1.Taint{
+				{
+					Key:    "foo_1",
+					Value:  "bar_1",
+					Effect: v1.TaintEffectNoExecute,
+				},
+				{
+					Key:    "foo_2",
+					Value:  "different-value",
+					Effect: v1.TaintEffectNoSchedule,
+				},
+			},
+			t2: []v1.Taint{
+				{
+					Key:    "foo_3",
+					Value:  "bar_3",
+					Effect: v1.TaintEffectNoExecute,
+				},
+				{
+					Key:    "foo_2",
+					Value:  "bar_2",
+					Effect: v1.TaintEffectNoSchedule,
+				},
+			},
+			expectedTaintsToAdd: []*v1.Taint{
+				{
+					Key:    "foo_1",
+					Value:  "bar_1",
+					Effect: v1.TaintEffectNoExecute,
+				},
+			},
+			expectedTaintsToRemove: []*v1.Taint{
+				{
+					Key:    "foo_3",
+					Value:  "bar_3",
+					Effect: v1.TaintEffectNoExecute,
+				},
+			},
+		},
+		{
+			name: "shared_taints_with_the_same_key_different_value_effect",
+			t1: []v1.Taint{
+				{
+					Key:    "foo_1",
+					Value:  "bar_1",
+					Effect: v1.TaintEffectNoExecute,
+				},
+				{
+					Key:    "foo_2",
+					Value:  "different-value",
+					Effect: v1.TaintEffectNoExecute,
+				},
+			},
+			t2: []v1.Taint{
+				{
+					Key:    "foo_3",
+					Value:  "bar_3",
+					Effect: v1.TaintEffectNoExecute,
+				},
+				{
+					Key:    "foo_2",
+					Value:  "bar_2",
+					Effect: v1.TaintEffectNoSchedule,
+				},
+			},
+			expectedTaintsToAdd: []*v1.Taint{
+				{
+					Key:    "foo_1",
+					Value:  "bar_1",
+					Effect: v1.TaintEffectNoExecute,
+				},
+				{
+					Key:    "foo_2",
+					Value:  "different-value",
+					Effect: v1.TaintEffectNoExecute,
+				},
+			},
+			expectedTaintsToRemove: []*v1.Taint{
+				{
+					Key:    "foo_3",
+					Value:  "bar_3",
+					Effect: v1.TaintEffectNoExecute,
+				},
+				{
+					Key:    "foo_2",
+					Value:  "bar_2",
+					Effect: v1.TaintEffectNoSchedule,
+				},
+			},
+		},
+	}
+
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			add, remove := TaintSetDiff(tt.t1, tt.t2)
+			if !reflect.DeepEqual(add, tt.expectedTaintsToAdd) {
+				t.Errorf("taintsToAdd: %v should equal %v, but get unexpected results", add, tt.expectedTaintsToAdd)
+			}
+			if !reflect.DeepEqual(remove, tt.expectedTaintsToRemove) {
+				t.Errorf("taintsToRemove: %v should equal %v, but get unexpected results", remove, tt.expectedTaintsToRemove)
+			}
+		})
+	}
+}
diff --git a/pkg/controller/nodelifecycle/node_lifecycle_controller_test.go b/pkg/controller/nodelifecycle/node_lifecycle_controller_test.go
new file mode 100644
index 00000000..6d854c0d
--- /dev/null
+++ b/pkg/controller/nodelifecycle/node_lifecycle_controller_test.go
@@ -0,0 +1,3941 @@
+/*
+Copyright 2020 The OpenYurt Authors.
+Copyright 2017 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+/*
+This file was derived from k8s.io/kubernetes/pkg/controller/nodelifecycle/node_lifecycle_controller_test.go
+at commit: 27522a29feb.
+
+CHANGELOG from OpenYurt Authors:
+1. Remove Test_isNodeExcludedFromDisruptionChecks.
+2. Use LabelOS and LabelArch in node_lifecycle_controller.go instead of kubeletapi.
+3. Change cases in TestReconcileNodeLabels, details can be found in comment.
+4. Remove master node role case in TestMonitorNodeHealthEvictPodsWithDisruption, details can be found in comment.
+5. Add autonomy test case in TestMonitorNodeHealthEvictPods.
+6. Add autonomy test case in TestMonitorNodeHealthMarkPodsNotReady.
+*/
+
+package nodelifecycle
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"testing"
+	"time"
+
+	apps "k8s.io/api/apps/v1"
+	coordv1 "k8s.io/api/coordination/v1"
+	v1 "k8s.io/api/core/v1"
+	apiequality "k8s.io/apimachinery/pkg/api/equality"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/fields"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/util/diff"
+	"k8s.io/client-go/informers"
+	appsinformers "k8s.io/client-go/informers/apps/v1"
+	coordinformers "k8s.io/client-go/informers/coordination/v1"
+	coreinformers "k8s.io/client-go/informers/core/v1"
+	clientset "k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/kubernetes/fake"
+	testcore "k8s.io/client-go/testing"
+	"k8s.io/utils/pointer"
+
+	"github.com/openyurtio/openyurt/pkg/controller/kubernetes/controller"
+	"github.com/openyurtio/openyurt/pkg/controller/kubernetes/controller/testutil"
+	"github.com/openyurtio/openyurt/pkg/controller/kubernetes/util/node"
+	taintutils "github.com/openyurtio/openyurt/pkg/controller/kubernetes/util/taints"
+	"github.com/openyurtio/openyurt/pkg/controller/nodelifecycle/scheduler"
+	nodeutil "github.com/openyurtio/openyurt/pkg/controller/util/node"
+)
+
+const (
+	testNodeMonitorGracePeriod = 40 * time.Second
+	testNodeStartupGracePeriod = 60 * time.Second
+	testNodeMonitorPeriod      = 5 * time.Second
+	testRateLimiterQPS         = float32(10000)
+	testLargeClusterThreshold  = 20
+	testUnhealthyThreshold     = float32(0.55)
+)
+
+func alwaysReady() bool { return true }
+
+func fakeGetPodsAssignedToNode(c *fake.Clientset) func(string) ([]*v1.Pod, error) {
+	return func(nodeName string) ([]*v1.Pod, error) {
+		selector := fields.SelectorFromSet(fields.Set{"spec.nodeName": nodeName})
+		pods, err := c.CoreV1().Pods(v1.NamespaceAll).List(context.TODO(), metav1.ListOptions{
+			FieldSelector: selector.String(),
+			LabelSelector: labels.Everything().String(),
+		})
+		if err != nil {
+			return nil, fmt.Errorf("failed to get Pods assigned to node %v", nodeName)
+		}
+		rPods := make([]*v1.Pod, len(pods.Items))
+		for i := range pods.Items {
+			rPods[i] = &pods.Items[i]
+		}
+		return rPods, nil
+	}
+}
+
+type nodeLifecycleController struct {
+	*Controller
+	leaseInformer     coordinformers.LeaseInformer
+	nodeInformer      coreinformers.NodeInformer
+	daemonSetInformer appsinformers.DaemonSetInformer
+}
+
+// doEviction does the fake eviction and returns the status of eviction operation.
+func (nc *nodeLifecycleController) doEviction(fakeNodeHandler *testutil.FakeNodeHandler) bool {
+	nc.evictorLock.Lock()
+	defer nc.evictorLock.Unlock()
+	zones := testutil.GetZones(fakeNodeHandler)
+	for _, zone := range zones {
+		nc.zonePodEvictor[zone].Try(func(value scheduler.TimedValue) (bool, time.Duration) {
+			uid, _ := value.UID.(string)
+			pods, _ := nc.getPodsAssignedToNode(value.Value)
+			nodeutil.DeletePods(fakeNodeHandler, pods, nc.recorder, value.Value, uid, nc.daemonSetStore)
+			_ = nc.nodeEvictionMap.setStatus(value.Value, evicted)
+			return true, 0
+		})
+	}
+
+	for _, action := range fakeNodeHandler.Actions() {
+		if action.GetVerb() == "delete" && action.GetResource().Resource == "pods" {
+			return true
+		}
+	}
+	return false
+}
+
+func createNodeLease(nodeName string, renewTime metav1.MicroTime) *coordv1.Lease {
+	return &coordv1.Lease{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      nodeName,
+			Namespace: v1.NamespaceNodeLease,
+		},
+		Spec: coordv1.LeaseSpec{
+			HolderIdentity: pointer.StringPtr(nodeName),
+			RenewTime:      &renewTime,
+		},
+	}
+}
+
+func (nc *nodeLifecycleController) syncLeaseStore(lease *coordv1.Lease) error {
+	if lease == nil {
+		return nil
+	}
+	newElems := make([]interface{}, 0, 1)
+	newElems = append(newElems, lease)
+	return nc.leaseInformer.Informer().GetStore().Replace(newElems, "newRV")
+}
+
+func (nc *nodeLifecycleController) syncNodeStore(fakeNodeHandler *testutil.FakeNodeHandler) error {
+	nodes, err := fakeNodeHandler.List(context.TODO(), metav1.ListOptions{})
+	if err != nil {
+		return err
+	}
+	newElems := make([]interface{}, 0, len(nodes.Items))
+	for i := range nodes.Items {
+		newElems = append(newElems, &nodes.Items[i])
+	}
+	return nc.nodeInformer.Informer().GetStore().Replace(newElems, "newRV")
+}
+
+func newNodeLifecycleControllerFromClient(
+	kubeClient clientset.Interface,
+	podEvictionTimeout time.Duration,
+	evictionLimiterQPS float32,
+	secondaryEvictionLimiterQPS float32,
+	largeClusterThreshold int32,
+	unhealthyZoneThreshold float32,
+	nodeMonitorGracePeriod time.Duration,
+	nodeStartupGracePeriod time.Duration,
+	nodeMonitorPeriod time.Duration,
+	useTaints bool,
+) (*nodeLifecycleController, error) {
+
+	factory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc())
+
+	leaseInformer := factory.Coordination().V1().Leases()
+	nodeInformer := factory.Core().V1().Nodes()
+	daemonSetInformer := factory.Apps().V1().DaemonSets()
+
+	nc, err := NewNodeLifecycleController(
+		leaseInformer,
+		factory.Core().V1().Pods(),
+		nodeInformer,
+		daemonSetInformer,
+		kubeClient,
+		nodeMonitorPeriod,
+		nodeStartupGracePeriod,
+		nodeMonitorGracePeriod,
+		podEvictionTimeout,
+		evictionLimiterQPS,
+		secondaryEvictionLimiterQPS,
+		largeClusterThreshold,
+		unhealthyZoneThreshold,
+		useTaints,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	nc.leaseInformerSynced = alwaysReady
+	nc.podInformerSynced = alwaysReady
+	nc.nodeInformerSynced = alwaysReady
+	nc.daemonSetInformerSynced = alwaysReady
+
+	return &nodeLifecycleController{nc, leaseInformer, nodeInformer, daemonSetInformer}, nil
+}
+
+func TestMonitorNodeHealthEvictPods(t *testing.T) {
+	fakeNow := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC)
+	evictionTimeout := 10 * time.Minute
+	labels := map[string]string{
+		v1.LabelTopologyRegion:          "region1",
+		v1.LabelTopologyZone:            "zone1",
+		v1.LabelFailureDomainBetaRegion: "region1",
+		v1.LabelFailureDomainBetaZone:   "zone1",
+	}
+
+	// Because of the logic that prevents NC from evicting anything when all Nodes are NotReady
+	// we need second healthy node in tests. Because of how the tests are written we need to update
+	// the status of this Node.
+	healthyNodeNewStatus := v1.NodeStatus{
+		Conditions: []v1.NodeCondition{
+			{
+				Type:   v1.NodeReady,
+				Status: v1.ConditionTrue,
+				// Node status has just been updated, and is NotReady for 10min.
+				LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 9, 0, 0, time.UTC),
+				LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+			},
+		},
+	}
+
+	table := []struct {
+		fakeNodeHandler     *testutil.FakeNodeHandler
+		daemonSets          []apps.DaemonSet
+		timeToPass          time.Duration
+		newNodeStatus       v1.NodeStatus
+		secondNodeNewStatus v1.NodeStatus
+		expectedEvictPods   bool
+		description         string
+	}{
+		// Node created recently, with no status (happens only at cluster startup).
+		{
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: fakeNow,
+							Labels: map[string]string{
+								v1.LabelTopologyRegion:          "region1",
+								v1.LabelTopologyZone:            "zone1",
+								v1.LabelFailureDomainBetaRegion: "region1",
+								v1.LabelFailureDomainBetaZone:   "zone1",
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node1",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+							Labels: map[string]string{
+								v1.LabelTopologyRegion:          "region1",
+								v1.LabelTopologyZone:            "zone1",
+								v1.LabelFailureDomainBetaRegion: "region1",
+								v1.LabelFailureDomainBetaZone:   "zone1",
+							},
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionTrue,
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			daemonSets:          nil,
+			timeToPass:          0,
+			newNodeStatus:       v1.NodeStatus{},
+			secondNodeNewStatus: healthyNodeNewStatus,
+			expectedEvictPods:   false,
+			description:         "Node created recently, with no status.",
+		},
+		// Node created recently without FailureDomain labels which is added back later, with no status (happens only at cluster startup).
+		{
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: fakeNow,
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node1",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionTrue,
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			daemonSets:          nil,
+			timeToPass:          0,
+			newNodeStatus:       v1.NodeStatus{},
+			secondNodeNewStatus: healthyNodeNewStatus,
+			expectedEvictPods:   false,
+			description:         "Node created recently without FailureDomain labels which is added back later, with no status (happens only at cluster startup).",
+		},
+		// Node created long time ago, and kubelet posted NotReady for a short period of time.
+		{
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+							Labels: map[string]string{
+								v1.LabelTopologyRegion:          "region1",
+								v1.LabelTopologyZone:            "zone1",
+								v1.LabelFailureDomainBetaRegion: "region1",
+								v1.LabelFailureDomainBetaZone:   "zone1",
+							},
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionFalse,
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node1",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+							Labels: map[string]string{
+								v1.LabelTopologyRegion:          "region1",
+								v1.LabelTopologyZone:            "zone1",
+								v1.LabelFailureDomainBetaRegion: "region1",
+								v1.LabelFailureDomainBetaZone:   "zone1",
+							},
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionTrue,
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			daemonSets: nil,
+			timeToPass: evictionTimeout,
+			newNodeStatus: v1.NodeStatus{
+				Conditions: []v1.NodeCondition{
+					{
+						Type:   v1.NodeReady,
+						Status: v1.ConditionFalse,
+						// Node status has just been updated, and is NotReady for 10min.
+						LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 9, 0, 0, time.UTC),
+						LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+					},
+				},
+			},
+			secondNodeNewStatus: healthyNodeNewStatus,
+			expectedEvictPods:   false,
+			description:         "Node created long time ago, and kubelet posted NotReady for a short period of time.",
+		},
+		// Pod is ds-managed, and kubelet posted NotReady for a long period of time.
+		{
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+							Labels: map[string]string{
+								v1.LabelTopologyRegion:          "region1",
+								v1.LabelTopologyZone:            "zone1",
+								v1.LabelFailureDomainBetaRegion: "region1",
+								v1.LabelFailureDomainBetaZone:   "zone1",
+							},
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionFalse,
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node1",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+							Labels: map[string]string{
+								v1.LabelTopologyRegion:          "region1",
+								v1.LabelTopologyZone:            "zone1",
+								v1.LabelFailureDomainBetaRegion: "region1",
+								v1.LabelFailureDomainBetaZone:   "zone1",
+							},
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionTrue,
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(
+					&v1.PodList{
+						Items: []v1.Pod{
+							{
+								ObjectMeta: metav1.ObjectMeta{
+									Name:      "pod0",
+									Namespace: "default",
+									Labels:    map[string]string{"daemon": "yes"},
+								},
+								Spec: v1.PodSpec{
+									NodeName: "node0",
+								},
+							},
+						},
+					},
+				),
+			},
+			daemonSets: []apps.DaemonSet{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "ds0",
+						Namespace: "default",
+					},
+					Spec: apps.DaemonSetSpec{
+						Selector: &metav1.LabelSelector{
+							MatchLabels: map[string]string{"daemon": "yes"},
+						},
+					},
+				},
+			},
+			timeToPass: time.Hour,
+			newNodeStatus: v1.NodeStatus{
+				Conditions: []v1.NodeCondition{
+					{
+						Type:   v1.NodeReady,
+						Status: v1.ConditionFalse,
+						// Node status has just been updated, and is NotReady for 1hr.
+						LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 59, 0, 0, time.UTC),
+						LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+					},
+				},
+			},
+			secondNodeNewStatus: healthyNodeNewStatus,
+			expectedEvictPods:   false,
+			description:         "Pod is ds-managed, and kubelet posted NotReady for a long period of time.",
+		},
+		// Node created long time ago, and kubelet posted NotReady for a long period of time.
+		{
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+							Labels: map[string]string{
+								v1.LabelTopologyRegion:          "region1",
+								v1.LabelTopologyZone:            "zone1",
+								v1.LabelFailureDomainBetaRegion: "region1",
+								v1.LabelFailureDomainBetaZone:   "zone1",
+							},
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionFalse,
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node1",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+							Labels: map[string]string{
+								v1.LabelTopologyRegion:          "region1",
+								v1.LabelTopologyZone:            "zone1",
+								v1.LabelFailureDomainBetaRegion: "region1",
+								v1.LabelFailureDomainBetaZone:   "zone1",
+							},
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionTrue,
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			daemonSets: nil,
+			timeToPass: time.Hour,
+			newNodeStatus: v1.NodeStatus{
+				Conditions: []v1.NodeCondition{
+					{
+						Type:   v1.NodeReady,
+						Status: v1.ConditionFalse,
+						// Node status has just been updated, and is NotReady for 1hr.
+						LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 59, 0, 0, time.UTC),
+						LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+					},
+				},
+			},
+			secondNodeNewStatus: healthyNodeNewStatus,
+			expectedEvictPods:   true,
+			description:         "Node created long time ago, and kubelet posted NotReady for a long period of time.",
+		},
+		// Node created long time ago, node controller posted Unknown for a short period of time.
+		{
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+							Labels: map[string]string{
+								v1.LabelTopologyRegion:          "region1",
+								v1.LabelTopologyZone:            "zone1",
+								v1.LabelFailureDomainBetaRegion: "region1",
+								v1.LabelFailureDomainBetaZone:   "zone1",
+							},
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionUnknown,
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node1",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+							Labels: map[string]string{
+								v1.LabelTopologyRegion:          "region1",
+								v1.LabelTopologyZone:            "zone1",
+								v1.LabelFailureDomainBetaRegion: "region1",
+								v1.LabelFailureDomainBetaZone:   "zone1",
+							},
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionTrue,
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			daemonSets: nil,
+			timeToPass: evictionTimeout - testNodeMonitorGracePeriod,
+			newNodeStatus: v1.NodeStatus{
+				Conditions: []v1.NodeCondition{
+					{
+						Type:   v1.NodeReady,
+						Status: v1.ConditionUnknown,
+						// Node status was updated by nodecontroller 10min ago
+						LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+					},
+				},
+			},
+			secondNodeNewStatus: healthyNodeNewStatus,
+			expectedEvictPods:   false,
+			description:         "Node created long time ago, node controller posted Unknown for a short period of time.",
+		},
+		// Node created long time ago, node controller posted Unknown for a long period of time.
+		{
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+							Labels: map[string]string{
+								v1.LabelTopologyRegion:          "region1",
+								v1.LabelTopologyZone:            "zone1",
+								v1.LabelFailureDomainBetaRegion: "region1",
+								v1.LabelFailureDomainBetaZone:   "zone1",
+							},
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionUnknown,
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node1",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+							Labels: map[string]string{
+								v1.LabelTopologyRegion:          "region1",
+								v1.LabelTopologyZone:            "zone1",
+								v1.LabelFailureDomainBetaRegion: "region1",
+								v1.LabelFailureDomainBetaZone:   "zone1",
+							},
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionTrue,
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			daemonSets: nil,
+			timeToPass: 60 * time.Minute,
+			newNodeStatus: v1.NodeStatus{
+				Conditions: []v1.NodeCondition{
+					{
+						Type:   v1.NodeReady,
+						Status: v1.ConditionUnknown,
+						// Node status was updated by nodecontroller 1hr ago
+						LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+					},
+				},
+			},
+			secondNodeNewStatus: healthyNodeNewStatus,
+			expectedEvictPods:   true,
+			description:         "Node created long time ago, node controller posted Unknown for a long period of time.",
+		},
+		// From OpenYurt Authors:
+		// When node runs in autonomy mode, do not evict pods on it even its ready status is Unknown.
+		// Node created long time ago but run in autonomy mod, node controller posted Unknown for a short period of time.
+		{
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+							Annotations: map[string]string{
+								nodeutil.AnnotationKeyNodeAutonomy: "true",
+							},
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionUnknown,
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node1",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionTrue,
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			daemonSets: nil,
+			timeToPass: 60 * time.Minute,
+			newNodeStatus: v1.NodeStatus{
+				Conditions: []v1.NodeCondition{
+					{
+						Type:               v1.NodeReady,
+						Status:             v1.ConditionUnknown,
+						LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+					},
+				},
+			},
+			secondNodeNewStatus: healthyNodeNewStatus,
+			expectedEvictPods:   false,
+			description:         "Node created long time ago and is running in autonomy mode, node controller posted Unknown for a long period of time",
+		},
+	}
+
+	for _, item := range table {
+		nodeController, _ := newNodeLifecycleControllerFromClient(
+			item.fakeNodeHandler,
+			evictionTimeout,
+			testRateLimiterQPS,
+			testRateLimiterQPS,
+			testLargeClusterThreshold,
+			testUnhealthyThreshold,
+			testNodeMonitorGracePeriod,
+			testNodeStartupGracePeriod,
+			testNodeMonitorPeriod,
+			false)
+		nodeController.now = func() metav1.Time { return fakeNow }
+		nodeController.recorder = testutil.NewFakeRecorder()
+		nodeController.getPodsAssignedToNode = fakeGetPodsAssignedToNode(item.fakeNodeHandler.Clientset)
+		for _, ds := range item.daemonSets {
+			nodeController.daemonSetInformer.Informer().GetStore().Add(&ds)
+		}
+		if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		if err := nodeController.monitorNodeHealth(); err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		if item.timeToPass > 0 {
+			nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(item.timeToPass)} }
+			item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus
+			item.fakeNodeHandler.Existing[1].Status = item.secondNodeNewStatus
+		}
+		if len(item.fakeNodeHandler.Existing[0].Labels) == 0 && len(item.fakeNodeHandler.Existing[1].Labels) == 0 {
+			item.fakeNodeHandler.Existing[0].Labels = labels
+			item.fakeNodeHandler.Existing[1].Labels = labels
+		}
+		if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		if err := nodeController.monitorNodeHealth(); err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		zones := testutil.GetZones(item.fakeNodeHandler)
+		for _, zone := range zones {
+			if _, ok := nodeController.zonePodEvictor[zone]; ok {
+				nodeController.zonePodEvictor[zone].Try(func(value scheduler.TimedValue) (bool, time.Duration) {
+					nodeUID, _ := value.UID.(string)
+					pods, err := nodeController.getPodsAssignedToNode(value.Value)
+					if err != nil {
+						t.Errorf("unexpected error: %v", err)
+					}
+					t.Logf("listed pods %d for node %v", len(pods), value.Value)
+					nodeutil.DeletePods(item.fakeNodeHandler, pods, nodeController.recorder, value.Value, nodeUID, nodeController.daemonSetInformer.Lister())
+					return true, 0
+				})
+			} else {
+				t.Fatalf("Zone %v was uninitialized!", zone)
+			}
+		}
+
+		podEvicted := false
+		for _, action := range item.fakeNodeHandler.Actions() {
+			if action.GetVerb() == "delete" && action.GetResource().Resource == "pods" {
+				podEvicted = true
+			}
+		}
+
+		if item.expectedEvictPods != podEvicted {
+			t.Errorf("expected pod eviction: %+v, got %+v for %+v", item.expectedEvictPods,
+				podEvicted, item.description)
+		}
+	}
+}
+
+func TestPodStatusChange(t *testing.T) {
+	fakeNow := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC)
+	evictionTimeout := 10 * time.Minute
+
+	// Because of the logic that prevents NC from evicting anything when all Nodes are NotReady
+	// we need second healthy node in tests. Because of how the tests are written we need to update
+	// the status of this Node.
+	healthyNodeNewStatus := v1.NodeStatus{
+		Conditions: []v1.NodeCondition{
+			{
+				Type:   v1.NodeReady,
+				Status: v1.ConditionTrue,
+				// Node status has just been updated, and is NotReady for 10min.
+				LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 9, 0, 0, time.UTC),
+				LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+			},
+		},
+	}
+
+	// Node created long time ago, node controller posted Unknown for a long period of time.
+	table := []struct {
+		fakeNodeHandler     *testutil.FakeNodeHandler
+		timeToPass          time.Duration
+		newNodeStatus       v1.NodeStatus
+		secondNodeNewStatus v1.NodeStatus
+		expectedPodUpdate   bool
+		expectedReason      string
+		description         string
+	}{
+		{
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+							Labels: map[string]string{
+								v1.LabelTopologyRegion:          "region1",
+								v1.LabelTopologyZone:            "zone1",
+								v1.LabelFailureDomainBetaRegion: "region1",
+								v1.LabelFailureDomainBetaZone:   "zone1",
+							},
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionUnknown,
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node1",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+							Labels: map[string]string{
+								v1.LabelFailureDomainBetaRegion: "region1",
+								v1.LabelFailureDomainBetaZone:   "zone1",
+							},
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionTrue,
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			timeToPass: 60 * time.Minute,
+			newNodeStatus: v1.NodeStatus{
+				Conditions: []v1.NodeCondition{
+					{
+						Type:   v1.NodeReady,
+						Status: v1.ConditionUnknown,
+						// Node status was updated by nodecontroller 1hr ago
+						LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+					},
+				},
+			},
+			secondNodeNewStatus: healthyNodeNewStatus,
+			expectedPodUpdate:   true,
+			expectedReason:      node.NodeUnreachablePodReason,
+			description: "Node created long time ago, node controller posted Unknown for a " +
+				"long period of time, the pod status must include reason for termination.",
+		},
+	}
+
+	for _, item := range table {
+		nodeController, _ := newNodeLifecycleControllerFromClient(
+			item.fakeNodeHandler,
+			evictionTimeout,
+			testRateLimiterQPS,
+			testRateLimiterQPS,
+			testLargeClusterThreshold,
+			testUnhealthyThreshold,
+			testNodeMonitorGracePeriod,
+			testNodeStartupGracePeriod,
+			testNodeMonitorPeriod,
+			false)
+		nodeController.now = func() metav1.Time { return fakeNow }
+		nodeController.recorder = testutil.NewFakeRecorder()
+		nodeController.getPodsAssignedToNode = fakeGetPodsAssignedToNode(item.fakeNodeHandler.Clientset)
+		if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		if err := nodeController.monitorNodeHealth(); err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		if item.timeToPass > 0 {
+			nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(item.timeToPass)} }
+			item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus
+			item.fakeNodeHandler.Existing[1].Status = item.secondNodeNewStatus
+		}
+		if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		if err := nodeController.monitorNodeHealth(); err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		zones := testutil.GetZones(item.fakeNodeHandler)
+		for _, zone := range zones {
+			nodeController.zonePodEvictor[zone].Try(func(value scheduler.TimedValue) (bool, time.Duration) {
+				nodeUID, _ := value.UID.(string)
+				pods, err := nodeController.getPodsAssignedToNode(value.Value)
+				if err != nil {
+					t.Errorf("unexpected error: %v", err)
+				}
+				nodeutil.DeletePods(item.fakeNodeHandler, pods, nodeController.recorder, value.Value, nodeUID, nodeController.daemonSetStore)
+				return true, 0
+			})
+		}
+
+		podReasonUpdate := false
+		for _, action := range item.fakeNodeHandler.Actions() {
+			if action.GetVerb() == "update" && action.GetResource().Resource == "pods" {
+				updateReason := action.(testcore.UpdateActionImpl).GetObject().(*v1.Pod).Status.Reason
+				podReasonUpdate = true
+				if updateReason != item.expectedReason {
+					t.Errorf("expected pod status reason: %+v, got %+v for %+v", item.expectedReason, updateReason, item.description)
+				}
+			}
+		}
+
+		if podReasonUpdate != item.expectedPodUpdate {
+			t.Errorf("expected pod update: %+v, got %+v for %+v", item.expectedPodUpdate, podReasonUpdate, item.description)
+		}
+	}
+}
+
+func TestMonitorNodeHealthEvictPodsWithDisruption(t *testing.T) {
+	fakeNow := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC)
+	evictionTimeout := 10 * time.Minute
+	timeToPass := 60 * time.Minute
+
+	// Because of the logic that prevents NC from evicting anything when all Nodes are NotReady
+	// we need second healthy node in tests. Because of how the tests are written we need to update
+	// the status of this Node.
+	healthyNodeNewStatus := v1.NodeStatus{
+		Conditions: []v1.NodeCondition{
+			{
+				Type:               v1.NodeReady,
+				Status:             v1.ConditionTrue,
+				LastHeartbeatTime:  metav1.Date(2015, 1, 1, 13, 0, 0, 0, time.UTC),
+				LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+			},
+		},
+	}
+	unhealthyNodeNewStatus := v1.NodeStatus{
+		Conditions: []v1.NodeCondition{
+			{
+				Type:   v1.NodeReady,
+				Status: v1.ConditionUnknown,
+				// Node status was updated by nodecontroller 1hr ago
+				LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+				LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+			},
+		},
+	}
+
+	table := []struct {
+		nodeList                []*v1.Node
+		podList                 []v1.Pod
+		updatedNodeStatuses     []v1.NodeStatus
+		expectedInitialStates   map[string]ZoneState
+		expectedFollowingStates map[string]ZoneState
+		expectedEvictPods       bool
+		description             string
+	}{
+		// NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period of time on both Nodes.
+		// Only zone is down - eviction shouldn't take place
+		{
+			nodeList: []*v1.Node{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node0",
+						CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						Labels: map[string]string{
+							v1.LabelTopologyRegion:          "region1",
+							v1.LabelTopologyZone:            "zone1",
+							v1.LabelFailureDomainBetaRegion: "region1",
+							v1.LabelFailureDomainBetaZone:   "zone1",
+						},
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionUnknown,
+								LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node1",
+						CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						Labels: map[string]string{
+							v1.LabelTopologyRegion:          "region1",
+							v1.LabelTopologyZone:            "zone1",
+							v1.LabelFailureDomainBetaRegion: "region1",
+							v1.LabelFailureDomainBetaZone:   "zone1",
+						},
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionUnknown,
+								LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+			},
+			podList: []v1.Pod{*testutil.NewPod("pod0", "node0")},
+			updatedNodeStatuses: []v1.NodeStatus{
+				unhealthyNodeNewStatus,
+				unhealthyNodeNewStatus,
+			},
+			expectedInitialStates:   map[string]ZoneState{testutil.CreateZoneID("region1", "zone1"): stateFullDisruption},
+			expectedFollowingStates: map[string]ZoneState{testutil.CreateZoneID("region1", "zone1"): stateFullDisruption},
+			expectedEvictPods:       false,
+			description:             "Network Disruption: Only zone is down - eviction shouldn't take place.",
+		},
+		// NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period of time on both Nodes.
+		// Both zones down - eviction shouldn't take place
+		{
+			nodeList: []*v1.Node{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node0",
+						CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						Labels: map[string]string{
+							v1.LabelTopologyRegion:          "region1",
+							v1.LabelTopologyZone:            "zone1",
+							v1.LabelFailureDomainBetaRegion: "region1",
+							v1.LabelFailureDomainBetaZone:   "zone1",
+						},
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionUnknown,
+								LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node1",
+						CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						Labels: map[string]string{
+							v1.LabelTopologyRegion:          "region2",
+							v1.LabelTopologyZone:            "zone2",
+							v1.LabelFailureDomainBetaRegion: "region2",
+							v1.LabelFailureDomainBetaZone:   "zone2",
+						},
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionUnknown,
+								LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+			},
+
+			podList: []v1.Pod{*testutil.NewPod("pod0", "node0")},
+			updatedNodeStatuses: []v1.NodeStatus{
+				unhealthyNodeNewStatus,
+				unhealthyNodeNewStatus,
+			},
+			expectedInitialStates: map[string]ZoneState{
+				testutil.CreateZoneID("region1", "zone1"): stateFullDisruption,
+				testutil.CreateZoneID("region2", "zone2"): stateFullDisruption,
+			},
+			expectedFollowingStates: map[string]ZoneState{
+				testutil.CreateZoneID("region1", "zone1"): stateFullDisruption,
+				testutil.CreateZoneID("region2", "zone2"): stateFullDisruption,
+			},
+			expectedEvictPods: false,
+			description:       "Network Disruption: Both zones down - eviction shouldn't take place.",
+		},
+		// NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period of time on both Nodes.
+		// One zone is down - eviction should take place
+		{
+			nodeList: []*v1.Node{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node0",
+						CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						Labels: map[string]string{
+							v1.LabelTopologyRegion:          "region1",
+							v1.LabelTopologyZone:            "zone1",
+							v1.LabelFailureDomainBetaRegion: "region1",
+							v1.LabelFailureDomainBetaZone:   "zone1",
+						},
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionUnknown,
+								LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node1",
+						CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						Labels: map[string]string{
+							v1.LabelTopologyRegion:          "region1",
+							v1.LabelTopologyZone:            "zone2",
+							v1.LabelFailureDomainBetaRegion: "region1",
+							v1.LabelFailureDomainBetaZone:   "zone2",
+						},
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionTrue,
+								LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+			},
+			podList: []v1.Pod{*testutil.NewPod("pod0", "node0")},
+			updatedNodeStatuses: []v1.NodeStatus{
+				unhealthyNodeNewStatus,
+				healthyNodeNewStatus,
+			},
+			expectedInitialStates: map[string]ZoneState{
+				testutil.CreateZoneID("region1", "zone1"): stateFullDisruption,
+				testutil.CreateZoneID("region1", "zone2"): stateNormal,
+			},
+			expectedFollowingStates: map[string]ZoneState{
+				testutil.CreateZoneID("region1", "zone1"): stateFullDisruption,
+				testutil.CreateZoneID("region1", "zone2"): stateNormal,
+			},
+			expectedEvictPods: true,
+			description:       "Network Disruption: One zone is down - eviction should take place.",
+		},
+		// OpenYurt Authors:
+		// We don't use master node role to determine whether exclude this node from disruption check in
+		// func isNodeExcludedFromDisruptionChecks in node_lifecycle_controller.go. So, do not run this case.
+		// NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period
+		// of on first Node, eviction should stop even though -master Node is healthy.
+		// {
+		// 	nodeList: []*v1.Node{
+		// 		{
+		// 			ObjectMeta: metav1.ObjectMeta{
+		// 				Name:              "node0",
+		// 				CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+		// 				Labels: map[string]string{
+		// 					v1.LabelTopologyRegion:          "region1",
+		// 					v1.LabelTopologyZone:            "zone1",
+		// 					v1.LabelFailureDomainBetaRegion: "region1",
+		// 					v1.LabelFailureDomainBetaZone:   "zone1",
+		// 				},
+		// 			},
+		// 			Status: v1.NodeStatus{
+		// 				Conditions: []v1.NodeCondition{
+		// 					{
+		// 						Type:               v1.NodeReady,
+		// 						Status:             v1.ConditionUnknown,
+		// 						LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+		// 						LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+		// 					},
+		// 				},
+		// 			},
+		// 		},
+		// 		{
+		// 			ObjectMeta: metav1.ObjectMeta{
+		// 				Name:              "node-master",
+		// 				CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+		// 				Labels: map[string]string{
+		// 					v1.LabelTopologyRegion:          "region1",
+		// 					v1.LabelTopologyZone:            "zone1",
+		// 					v1.LabelFailureDomainBetaRegion: "region1",
+		// 					v1.LabelFailureDomainBetaZone:   "zone1",
+		// 				},
+		// 			},
+		// 			Status: v1.NodeStatus{
+		// 				Conditions: []v1.NodeCondition{
+		// 					{
+		// 						Type:               v1.NodeReady,
+		// 						Status:             v1.ConditionTrue,
+		// 						LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+		// 						LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+		// 					},
+		// 				},
+		// 			},
+		// 		},
+		// 	},
+		// 	podList: []v1.Pod{*testutil.NewPod("pod0", "node0")},
+		// 	updatedNodeStatuses: []v1.NodeStatus{
+		// 		unhealthyNodeNewStatus,
+		// 		healthyNodeNewStatus,
+		// 	},
+		// 	expectedInitialStates: map[string]ZoneState{
+		// 		testutil.CreateZoneID("region1", "zone1"): stateFullDisruption,
+		// 	},
+		// 	expectedFollowingStates: map[string]ZoneState{
+		// 		testutil.CreateZoneID("region1", "zone1"): stateFullDisruption,
+		// 	},
+		// 	expectedEvictPods: false,
+		// 	description:       "NetworkDisruption: eviction should stop, only -master Node is healthy",
+		// },
+		// NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period of time on both Nodes.
+		// Initially both zones down, one comes back - eviction should take place
+		{
+			nodeList: []*v1.Node{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node0",
+						CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						Labels: map[string]string{
+							v1.LabelTopologyRegion:          "region1",
+							v1.LabelTopologyZone:            "zone1",
+							v1.LabelFailureDomainBetaRegion: "region1",
+							v1.LabelFailureDomainBetaZone:   "zone1",
+						},
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionUnknown,
+								LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node1",
+						CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						Labels: map[string]string{
+							v1.LabelTopologyRegion:          "region1",
+							v1.LabelTopologyZone:            "zone2",
+							v1.LabelFailureDomainBetaRegion: "region1",
+							v1.LabelFailureDomainBetaZone:   "zone2",
+						},
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionUnknown,
+								LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+			},
+
+			podList: []v1.Pod{*testutil.NewPod("pod0", "node0")},
+			updatedNodeStatuses: []v1.NodeStatus{
+				unhealthyNodeNewStatus,
+				healthyNodeNewStatus,
+			},
+			expectedInitialStates: map[string]ZoneState{
+				testutil.CreateZoneID("region1", "zone1"): stateFullDisruption,
+				testutil.CreateZoneID("region1", "zone2"): stateFullDisruption,
+			},
+			expectedFollowingStates: map[string]ZoneState{
+				testutil.CreateZoneID("region1", "zone1"): stateFullDisruption,
+				testutil.CreateZoneID("region1", "zone2"): stateNormal,
+			},
+			expectedEvictPods: true,
+			description:       "Initially both zones down, one comes back - eviction should take place",
+		},
+		// NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period of time on both Nodes.
+		// Zone is partially disrupted - eviction should take place
+		{
+			nodeList: []*v1.Node{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node0",
+						CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						Labels: map[string]string{
+							v1.LabelTopologyRegion:          "region1",
+							v1.LabelTopologyZone:            "zone1",
+							v1.LabelFailureDomainBetaRegion: "region1",
+							v1.LabelFailureDomainBetaZone:   "zone1",
+						},
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionUnknown,
+								LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node1",
+						CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						Labels: map[string]string{
+							v1.LabelTopologyRegion:          "region1",
+							v1.LabelTopologyZone:            "zone1",
+							v1.LabelFailureDomainBetaRegion: "region1",
+							v1.LabelFailureDomainBetaZone:   "zone1",
+						},
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionUnknown,
+								LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node2",
+						CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						Labels: map[string]string{
+							v1.LabelTopologyRegion:          "region1",
+							v1.LabelTopologyZone:            "zone1",
+							v1.LabelFailureDomainBetaRegion: "region1",
+							v1.LabelFailureDomainBetaZone:   "zone1",
+						},
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionUnknown,
+								LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node3",
+						CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						Labels: map[string]string{
+							v1.LabelTopologyRegion:          "region1",
+							v1.LabelTopologyZone:            "zone1",
+							v1.LabelFailureDomainBetaRegion: "region1",
+							v1.LabelFailureDomainBetaZone:   "zone1",
+						},
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionTrue,
+								LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node4",
+						CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						Labels: map[string]string{
+							v1.LabelTopologyRegion:          "region1",
+							v1.LabelTopologyZone:            "zone1",
+							v1.LabelFailureDomainBetaRegion: "region1",
+							v1.LabelFailureDomainBetaZone:   "zone1",
+						},
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionTrue,
+								LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+			},
+
+			podList: []v1.Pod{*testutil.NewPod("pod0", "node0")},
+			updatedNodeStatuses: []v1.NodeStatus{
+				unhealthyNodeNewStatus,
+				unhealthyNodeNewStatus,
+				unhealthyNodeNewStatus,
+				healthyNodeNewStatus,
+				healthyNodeNewStatus,
+			},
+			expectedInitialStates: map[string]ZoneState{
+				testutil.CreateZoneID("region1", "zone1"): statePartialDisruption,
+			},
+			expectedFollowingStates: map[string]ZoneState{
+				testutil.CreateZoneID("region1", "zone1"): statePartialDisruption,
+			},
+			expectedEvictPods: true,
+			description:       "Zone is partially disrupted - eviction should take place.",
+		},
+	}
+
+	for _, item := range table {
+		fakeNodeHandler := &testutil.FakeNodeHandler{
+			Existing:  item.nodeList,
+			Clientset: fake.NewSimpleClientset(&v1.PodList{Items: item.podList}),
+		}
+		nodeController, _ := newNodeLifecycleControllerFromClient(
+			fakeNodeHandler,
+			evictionTimeout,
+			testRateLimiterQPS,
+			testRateLimiterQPS,
+			testLargeClusterThreshold,
+			testUnhealthyThreshold,
+			testNodeMonitorGracePeriod,
+			testNodeStartupGracePeriod,
+			testNodeMonitorPeriod,
+			false)
+		nodeController.now = func() metav1.Time { return fakeNow }
+		nodeController.recorder = testutil.NewFakeRecorder()
+		nodeController.getPodsAssignedToNode = fakeGetPodsAssignedToNode(fakeNodeHandler.Clientset)
+		nodeController.enterPartialDisruptionFunc = func(nodeNum int) float32 {
+			return testRateLimiterQPS
+		}
+		nodeController.enterFullDisruptionFunc = func(nodeNum int) float32 {
+			return testRateLimiterQPS
+		}
+		if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		if err := nodeController.monitorNodeHealth(); err != nil {
+			t.Errorf("%v: unexpected error: %v", item.description, err)
+		}
+
+		for zone, state := range item.expectedInitialStates {
+			if state != nodeController.zoneStates[zone] {
+				t.Errorf("%v: Unexpected zone state: %v: %v instead %v", item.description, zone, nodeController.zoneStates[zone], state)
+			}
+		}
+
+		nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(timeToPass)} }
+		for i := range item.updatedNodeStatuses {
+			fakeNodeHandler.Existing[i].Status = item.updatedNodeStatuses[i]
+		}
+
+		if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		if err := nodeController.monitorNodeHealth(); err != nil {
+			t.Errorf("%v: unexpected error: %v", item.description, err)
+		}
+		for zone, state := range item.expectedFollowingStates {
+			if state != nodeController.zoneStates[zone] {
+				t.Errorf("%v: Unexpected zone state: %v: %v instead %v", item.description, zone, nodeController.zoneStates[zone], state)
+			}
+		}
+		var podEvicted bool
+		start := time.Now()
+		// Infinite loop, used for retrying in case ratelimiter fails to reload for Try function.
+		// this breaks when we have the status that we need for test case or when we don't see the
+		// intended result after 1 minute.
+		for {
+			podEvicted = nodeController.doEviction(fakeNodeHandler)
+			if podEvicted == item.expectedEvictPods || time.Since(start) > 1*time.Minute {
+				break
+			}
+		}
+		if item.expectedEvictPods != podEvicted {
+			t.Errorf("%v: expected pod eviction: %+v, got %+v", item.description, item.expectedEvictPods, podEvicted)
+		}
+	}
+}
+
+func TestMonitorNodeHealthUpdateStatus(t *testing.T) {
+	fakeNow := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC)
+	table := []struct {
+		fakeNodeHandler         *testutil.FakeNodeHandler
+		timeToPass              time.Duration
+		newNodeStatus           v1.NodeStatus
+		expectedRequestCount    int
+		expectedNodes           []*v1.Node
+		expectedPodStatusUpdate bool
+	}{
+		// Node created long time ago, without status:
+		// Expect Unknown status posted from node controller.
+		{
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			expectedRequestCount: 2, // List+Update
+			expectedNodes: []*v1.Node{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node0",
+						CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+								LastTransitionTime: fakeNow,
+							},
+							{
+								Type:               v1.NodeMemoryPressure,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+								LastTransitionTime: fakeNow,
+							},
+							{
+								Type:               v1.NodeDiskPressure,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+								LastTransitionTime: fakeNow,
+							},
+							{
+								Type:               v1.NodePIDPressure,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+								LastTransitionTime: fakeNow,
+							},
+						},
+					},
+				},
+			},
+			expectedPodStatusUpdate: false, // Pod was never scheduled
+		},
+		// Node created recently, without status.
+		// Expect no action from node controller (within startup grace period).
+		{
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: fakeNow,
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			expectedRequestCount:    1, // List
+			expectedNodes:           nil,
+			expectedPodStatusUpdate: false,
+		},
+		// Node created long time ago, with status updated by kubelet exceeds grace period.
+		// Expect Unknown status posted from node controller.
+		{
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:   v1.NodeReady,
+									Status: v1.ConditionTrue,
+									// Node status hasn't been updated for 1hr.
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+							Capacity: v1.ResourceList{
+								v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+								v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+							},
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			expectedRequestCount: 3, // (List+)List+Update
+			timeToPass:           time.Hour,
+			newNodeStatus: v1.NodeStatus{
+				Conditions: []v1.NodeCondition{
+					{
+						Type:   v1.NodeReady,
+						Status: v1.ConditionTrue,
+						// Node status hasn't been updated for 1hr.
+						LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+					},
+				},
+				Capacity: v1.ResourceList{
+					v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+					v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+				},
+			},
+			expectedNodes: []*v1.Node{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node0",
+						CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusUnknown",
+								Message:            "Kubelet stopped posting node status.",
+								LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								LastTransitionTime: metav1.Time{Time: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC).Add(time.Hour)},
+							},
+							{
+								Type:               v1.NodeMemoryPressure,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), // should default to node creation time if condition was never updated
+								LastTransitionTime: metav1.Time{Time: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC).Add(time.Hour)},
+							},
+							{
+								Type:               v1.NodeDiskPressure,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), // should default to node creation time if condition was never updated
+								LastTransitionTime: metav1.Time{Time: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC).Add(time.Hour)},
+							},
+							{
+								Type:               v1.NodePIDPressure,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), // should default to node creation time if condition was never updated
+								LastTransitionTime: metav1.Time{Time: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC).Add(time.Hour)},
+							},
+						},
+						Capacity: v1.ResourceList{
+							v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+							v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+						},
+					},
+				},
+			},
+			expectedPodStatusUpdate: true,
+		},
+		// Node created long time ago, with status updated recently.
+		// Expect no action from node controller (within monitor grace period).
+		{
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:   v1.NodeReady,
+									Status: v1.ConditionTrue,
+									// Node status has just been updated.
+									LastHeartbeatTime:  fakeNow,
+									LastTransitionTime: fakeNow,
+								},
+							},
+							Capacity: v1.ResourceList{
+								v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+								v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+							},
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			expectedRequestCount:    1, // List
+			expectedNodes:           nil,
+			expectedPodStatusUpdate: false,
+		},
+	}
+	for i, item := range table {
+		nodeController, _ := newNodeLifecycleControllerFromClient(
+			item.fakeNodeHandler,
+			5*time.Minute,
+			testRateLimiterQPS,
+			testRateLimiterQPS,
+			testLargeClusterThreshold,
+			testUnhealthyThreshold,
+			testNodeMonitorGracePeriod,
+			testNodeStartupGracePeriod,
+			testNodeMonitorPeriod,
+			false)
+		nodeController.now = func() metav1.Time { return fakeNow }
+		nodeController.recorder = testutil.NewFakeRecorder()
+		nodeController.getPodsAssignedToNode = fakeGetPodsAssignedToNode(item.fakeNodeHandler.Clientset)
+		if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		if err := nodeController.monitorNodeHealth(); err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		if item.timeToPass > 0 {
+			nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(item.timeToPass)} }
+			item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus
+			if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil {
+				t.Errorf("unexpected error: %v", err)
+			}
+			if err := nodeController.monitorNodeHealth(); err != nil {
+				t.Errorf("unexpected error: %v", err)
+			}
+		}
+		if item.expectedRequestCount != item.fakeNodeHandler.RequestCount {
+			t.Errorf("expected %v call, but got %v.", item.expectedRequestCount, item.fakeNodeHandler.RequestCount)
+		}
+		if len(item.fakeNodeHandler.UpdatedNodes) > 0 && !apiequality.Semantic.DeepEqual(item.expectedNodes, item.fakeNodeHandler.UpdatedNodes) {
+			t.Errorf("Case[%d] unexpected nodes: %s", i, diff.ObjectDiff(item.expectedNodes[0], item.fakeNodeHandler.UpdatedNodes[0]))
+		}
+		if len(item.fakeNodeHandler.UpdatedNodeStatuses) > 0 && !apiequality.Semantic.DeepEqual(item.expectedNodes, item.fakeNodeHandler.UpdatedNodeStatuses) {
+			t.Errorf("Case[%d] unexpected nodes: %s", i, diff.ObjectDiff(item.expectedNodes[0], item.fakeNodeHandler.UpdatedNodeStatuses[0]))
+		}
+
+		podStatusUpdated := false
+		for _, action := range item.fakeNodeHandler.Actions() {
+			if action.GetVerb() == "update" && action.GetResource().Resource == "pods" && action.GetSubresource() == "status" {
+				podStatusUpdated = true
+			}
+		}
+		if podStatusUpdated != item.expectedPodStatusUpdate {
+			t.Errorf("Case[%d] expect pod status updated to be %v, but got %v", i, item.expectedPodStatusUpdate, podStatusUpdated)
+		}
+	}
+}
+
+func TestMonitorNodeHealthUpdateNodeAndPodStatusWithLease(t *testing.T) {
+	nodeCreationTime := metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC)
+	fakeNow := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC)
+	testcases := []struct {
+		description             string
+		fakeNodeHandler         *testutil.FakeNodeHandler
+		lease                   *coordv1.Lease
+		timeToPass              time.Duration
+		newNodeStatus           v1.NodeStatus
+		newLease                *coordv1.Lease
+		expectedRequestCount    int
+		expectedNodes           []*v1.Node
+		expectedPodStatusUpdate bool
+	}{
+		// Node created recently, without status. Node lease is missing.
+		// Expect no action from node controller (within startup grace period).
+		{
+			description: "Node created recently, without status. Node lease is missing.",
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: fakeNow,
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			expectedRequestCount:    1, // List
+			expectedNodes:           nil,
+			expectedPodStatusUpdate: false,
+		},
+		// Node created recently, without status. Node lease is renewed recently.
+		// Expect no action from node controller (within startup grace period).
+		{
+			description: "Node created recently, without status. Node lease is renewed recently.",
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: fakeNow,
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			lease:                   createNodeLease("node0", metav1.NewMicroTime(fakeNow.Time)),
+			expectedRequestCount:    1, // List
+			expectedNodes:           nil,
+			expectedPodStatusUpdate: false,
+		},
+		// Node created long time ago, without status. Node lease is missing.
+		// Expect Unknown status posted from node controller.
+		{
+			description: "Node created long time ago, without status. Node lease is missing.",
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: nodeCreationTime,
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			expectedRequestCount: 2, // List+Update
+			expectedNodes: []*v1.Node{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node0",
+						CreationTimestamp: nodeCreationTime,
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  nodeCreationTime,
+								LastTransitionTime: fakeNow,
+							},
+							{
+								Type:               v1.NodeMemoryPressure,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  nodeCreationTime,
+								LastTransitionTime: fakeNow,
+							},
+							{
+								Type:               v1.NodeDiskPressure,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  nodeCreationTime,
+								LastTransitionTime: fakeNow,
+							},
+							{
+								Type:               v1.NodePIDPressure,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  nodeCreationTime,
+								LastTransitionTime: fakeNow,
+							},
+						},
+					},
+				},
+			},
+			expectedPodStatusUpdate: false, // Pod was never scheduled because the node was never ready.
+		},
+		// Node created long time ago, without status. Node lease is renewed recently.
+		// Expect no action from node controller (within monitor grace period).
+		{
+			description: "Node created long time ago, without status. Node lease is renewed recently.",
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: nodeCreationTime,
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			lease:                createNodeLease("node0", metav1.NewMicroTime(fakeNow.Time)),
+			timeToPass:           time.Hour,
+			newLease:             createNodeLease("node0", metav1.NewMicroTime(fakeNow.Time.Add(time.Hour))), // Lease is renewed after 1 hour.
+			expectedRequestCount: 2,                                                                          // List+List
+			expectedNodes: []*v1.Node{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node0",
+						CreationTimestamp: nodeCreationTime,
+					},
+				},
+			},
+			expectedPodStatusUpdate: false,
+		},
+		// Node created long time ago, without status. Node lease is expired.
+		// Expect Unknown status posted from node controller.
+		{
+			description: "Node created long time ago, without status. Node lease is expired.",
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: nodeCreationTime,
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			lease:                createNodeLease("node0", metav1.NewMicroTime(fakeNow.Time)),
+			timeToPass:           time.Hour,
+			newLease:             createNodeLease("node0", metav1.NewMicroTime(fakeNow.Time)), // Lease is not renewed after 1 hour.
+			expectedRequestCount: 3,                                                           // List+List+Update
+			expectedNodes: []*v1.Node{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node0",
+						CreationTimestamp: nodeCreationTime,
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  nodeCreationTime,
+								LastTransitionTime: metav1.Time{Time: fakeNow.Add(time.Hour)},
+							},
+							{
+								Type:               v1.NodeMemoryPressure,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  nodeCreationTime,
+								LastTransitionTime: metav1.Time{Time: fakeNow.Add(time.Hour)},
+							},
+							{
+								Type:               v1.NodeDiskPressure,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  nodeCreationTime,
+								LastTransitionTime: metav1.Time{Time: fakeNow.Add(time.Hour)},
+							},
+							{
+								Type:               v1.NodePIDPressure,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  nodeCreationTime,
+								LastTransitionTime: metav1.Time{Time: fakeNow.Add(time.Hour)},
+							},
+						},
+					},
+				},
+			},
+			expectedPodStatusUpdate: false,
+		},
+		// Node created long time ago, with status updated by kubelet exceeds grace period. Node lease is renewed.
+		// Expect no action from node controller (within monitor grace period).
+		{
+			description: "Node created long time ago, with status updated by kubelet exceeds grace period. Node lease is renewed.",
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: nodeCreationTime,
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionTrue,
+									LastHeartbeatTime:  fakeNow,
+									LastTransitionTime: fakeNow,
+								},
+								{
+									Type:               v1.NodeDiskPressure,
+									Status:             v1.ConditionFalse,
+									LastHeartbeatTime:  fakeNow,
+									LastTransitionTime: fakeNow,
+								},
+							},
+							Capacity: v1.ResourceList{
+								v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+								v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+							},
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			lease:                createNodeLease("node0", metav1.NewMicroTime(fakeNow.Time)),
+			expectedRequestCount: 2, // List+List
+			timeToPass:           time.Hour,
+			newNodeStatus: v1.NodeStatus{
+				// Node status hasn't been updated for 1 hour.
+				Conditions: []v1.NodeCondition{
+					{
+						Type:               v1.NodeReady,
+						Status:             v1.ConditionTrue,
+						LastHeartbeatTime:  fakeNow,
+						LastTransitionTime: fakeNow,
+					},
+					{
+						Type:               v1.NodeDiskPressure,
+						Status:             v1.ConditionFalse,
+						LastHeartbeatTime:  fakeNow,
+						LastTransitionTime: fakeNow,
+					},
+				},
+				Capacity: v1.ResourceList{
+					v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+					v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+				},
+			},
+			newLease: createNodeLease("node0", metav1.NewMicroTime(fakeNow.Time.Add(time.Hour))), // Lease is renewed after 1 hour.
+			expectedNodes: []*v1.Node{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node0",
+						CreationTimestamp: nodeCreationTime,
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionTrue,
+								LastHeartbeatTime:  fakeNow,
+								LastTransitionTime: fakeNow,
+							},
+							{
+								Type:               v1.NodeDiskPressure,
+								Status:             v1.ConditionFalse,
+								LastHeartbeatTime:  fakeNow,
+								LastTransitionTime: fakeNow,
+							},
+						},
+						Capacity: v1.ResourceList{
+							v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+							v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+						},
+					},
+				},
+			},
+			expectedPodStatusUpdate: false,
+		},
+		// Node created long time ago, with status updated by kubelet recently. Node lease is expired.
+		// Expect no action from node controller (within monitor grace period).
+		{
+			description: "Node created long time ago, with status updated by kubelet recently. Node lease is expired.",
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: nodeCreationTime,
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionTrue,
+									LastHeartbeatTime:  fakeNow,
+									LastTransitionTime: fakeNow,
+								},
+								{
+									Type:               v1.NodeDiskPressure,
+									Status:             v1.ConditionFalse,
+									LastHeartbeatTime:  fakeNow,
+									LastTransitionTime: fakeNow,
+								},
+							},
+							Capacity: v1.ResourceList{
+								v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+								v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+							},
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			lease:                createNodeLease("node0", metav1.NewMicroTime(fakeNow.Time)),
+			expectedRequestCount: 2, // List+List
+			timeToPass:           time.Hour,
+			newNodeStatus: v1.NodeStatus{
+				// Node status is updated after 1 hour.
+				Conditions: []v1.NodeCondition{
+					{
+						Type:               v1.NodeReady,
+						Status:             v1.ConditionTrue,
+						LastHeartbeatTime:  metav1.Time{Time: fakeNow.Add(time.Hour)},
+						LastTransitionTime: fakeNow,
+					},
+					{
+						Type:               v1.NodeDiskPressure,
+						Status:             v1.ConditionFalse,
+						LastHeartbeatTime:  metav1.Time{Time: fakeNow.Add(time.Hour)},
+						LastTransitionTime: fakeNow,
+					},
+				},
+				Capacity: v1.ResourceList{
+					v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+					v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+				},
+			},
+			newLease: createNodeLease("node0", metav1.NewMicroTime(fakeNow.Time)), // Lease is not renewed after 1 hour.
+			expectedNodes: []*v1.Node{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node0",
+						CreationTimestamp: nodeCreationTime,
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionTrue,
+								LastHeartbeatTime:  metav1.Time{Time: fakeNow.Add(time.Hour)},
+								LastTransitionTime: fakeNow,
+							},
+							{
+								Type:               v1.NodeDiskPressure,
+								Status:             v1.ConditionFalse,
+								LastHeartbeatTime:  metav1.Time{Time: fakeNow.Add(time.Hour)},
+								LastTransitionTime: fakeNow,
+							},
+						},
+						Capacity: v1.ResourceList{
+							v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+							v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+						},
+					},
+				},
+			},
+			expectedPodStatusUpdate: false,
+		},
+		// Node created long time ago, with status updated by kubelet exceeds grace period. Node lease is also expired.
+		// Expect Unknown status posted from node controller.
+		{
+			description: "Node created long time ago, with status updated by kubelet exceeds grace period. Node lease is also expired.",
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: nodeCreationTime,
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:               v1.NodeReady,
+									Status:             v1.ConditionTrue,
+									LastHeartbeatTime:  fakeNow,
+									LastTransitionTime: fakeNow,
+								},
+							},
+							Capacity: v1.ResourceList{
+								v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+								v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+							},
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			lease:                createNodeLease("node0", metav1.NewMicroTime(fakeNow.Time)),
+			expectedRequestCount: 3, // List+List+Update
+			timeToPass:           time.Hour,
+			newNodeStatus: v1.NodeStatus{
+				// Node status hasn't been updated for 1 hour.
+				Conditions: []v1.NodeCondition{
+					{
+						Type:               v1.NodeReady,
+						Status:             v1.ConditionTrue,
+						LastHeartbeatTime:  fakeNow,
+						LastTransitionTime: fakeNow,
+					},
+				},
+				Capacity: v1.ResourceList{
+					v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+					v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+				},
+			},
+			newLease: createNodeLease("node0", metav1.NewMicroTime(fakeNow.Time)), // Lease is not renewed after 1 hour.
+			expectedNodes: []*v1.Node{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:              "node0",
+						CreationTimestamp: nodeCreationTime,
+					},
+					Status: v1.NodeStatus{
+						Conditions: []v1.NodeCondition{
+							{
+								Type:               v1.NodeReady,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusUnknown",
+								Message:            "Kubelet stopped posting node status.",
+								LastHeartbeatTime:  fakeNow,
+								LastTransitionTime: metav1.Time{Time: fakeNow.Add(time.Hour)},
+							},
+							{
+								Type:               v1.NodeMemoryPressure,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  nodeCreationTime, // should default to node creation time if condition was never updated
+								LastTransitionTime: metav1.Time{Time: fakeNow.Add(time.Hour)},
+							},
+							{
+								Type:               v1.NodeDiskPressure,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  nodeCreationTime, // should default to node creation time if condition was never updated
+								LastTransitionTime: metav1.Time{Time: fakeNow.Add(time.Hour)},
+							},
+							{
+								Type:               v1.NodePIDPressure,
+								Status:             v1.ConditionUnknown,
+								Reason:             "NodeStatusNeverUpdated",
+								Message:            "Kubelet never posted node status.",
+								LastHeartbeatTime:  nodeCreationTime, // should default to node creation time if condition was never updated
+								LastTransitionTime: metav1.Time{Time: fakeNow.Add(time.Hour)},
+							},
+						},
+						Capacity: v1.ResourceList{
+							v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+							v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+						},
+					},
+				},
+			},
+			expectedPodStatusUpdate: true,
+		},
+	}
+
+	for _, item := range testcases {
+		t.Run(item.description, func(t *testing.T) {
+			nodeController, _ := newNodeLifecycleControllerFromClient(
+				item.fakeNodeHandler,
+				5*time.Minute,
+				testRateLimiterQPS,
+				testRateLimiterQPS,
+				testLargeClusterThreshold,
+				testUnhealthyThreshold,
+				testNodeMonitorGracePeriod,
+				testNodeStartupGracePeriod,
+				testNodeMonitorPeriod,
+				false)
+			nodeController.now = func() metav1.Time { return fakeNow }
+			nodeController.recorder = testutil.NewFakeRecorder()
+			nodeController.getPodsAssignedToNode = fakeGetPodsAssignedToNode(item.fakeNodeHandler.Clientset)
+			if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+			if err := nodeController.syncLeaseStore(item.lease); err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+			if err := nodeController.monitorNodeHealth(); err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+			if item.timeToPass > 0 {
+				nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(item.timeToPass)} }
+				item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus
+				if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil {
+					t.Fatalf("unexpected error: %v", err)
+				}
+				if err := nodeController.syncLeaseStore(item.newLease); err != nil {
+					t.Fatalf("unexpected error: %v", err)
+				}
+				if err := nodeController.monitorNodeHealth(); err != nil {
+					t.Fatalf("unexpected error: %v", err)
+				}
+			}
+			if item.expectedRequestCount != item.fakeNodeHandler.RequestCount {
+				t.Errorf("expected %v call, but got %v.", item.expectedRequestCount, item.fakeNodeHandler.RequestCount)
+			}
+			if len(item.fakeNodeHandler.UpdatedNodes) > 0 && !apiequality.Semantic.DeepEqual(item.expectedNodes, item.fakeNodeHandler.UpdatedNodes) {
+				t.Errorf("unexpected nodes: %s", diff.ObjectDiff(item.expectedNodes[0], item.fakeNodeHandler.UpdatedNodes[0]))
+			}
+			if len(item.fakeNodeHandler.UpdatedNodeStatuses) > 0 && !apiequality.Semantic.DeepEqual(item.expectedNodes, item.fakeNodeHandler.UpdatedNodeStatuses) {
+				t.Errorf("unexpected nodes: %s", diff.ObjectDiff(item.expectedNodes[0], item.fakeNodeHandler.UpdatedNodeStatuses[0]))
+			}
+
+			podStatusUpdated := false
+			for _, action := range item.fakeNodeHandler.Actions() {
+				if action.GetVerb() == "update" && action.GetResource().Resource == "pods" && action.GetSubresource() == "status" {
+					podStatusUpdated = true
+				}
+			}
+			if podStatusUpdated != item.expectedPodStatusUpdate {
+				t.Errorf("expect pod status updated to be %v, but got %v", item.expectedPodStatusUpdate, podStatusUpdated)
+			}
+		})
+	}
+}
+
+func TestMonitorNodeHealthMarkPodsNotReady(t *testing.T) {
+	fakeNow := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC)
+	table := []struct {
+		fakeNodeHandler         *testutil.FakeNodeHandler
+		timeToPass              time.Duration
+		newNodeStatus           v1.NodeStatus
+		expectedPodStatusUpdate bool
+	}{
+		// Node created recently, without status.
+		// Expect no action from node controller (within startup grace period).
+		{
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: fakeNow,
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			expectedPodStatusUpdate: false,
+		},
+		// Node created long time ago, with status updated recently.
+		// Expect no action from node controller (within monitor grace period).
+		{
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:   v1.NodeReady,
+									Status: v1.ConditionTrue,
+									// Node status has just been updated.
+									LastHeartbeatTime:  fakeNow,
+									LastTransitionTime: fakeNow,
+								},
+							},
+							Capacity: v1.ResourceList{
+								v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+								v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+							},
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			expectedPodStatusUpdate: false,
+		},
+		// Node created long time ago, with status updated by kubelet exceeds grace period.
+		// Expect pods status updated and Unknown node status posted from node controller
+		{
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:   v1.NodeReady,
+									Status: v1.ConditionTrue,
+									// Node status hasn't been updated for 1hr.
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+							Capacity: v1.ResourceList{
+								v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+								v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+							},
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			timeToPass: 1 * time.Minute,
+			newNodeStatus: v1.NodeStatus{
+				Conditions: []v1.NodeCondition{
+					{
+						Type:   v1.NodeReady,
+						Status: v1.ConditionTrue,
+						// Node status hasn't been updated for 1hr.
+						LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+					},
+				},
+				Capacity: v1.ResourceList{
+					v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+					v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+				},
+			},
+			expectedPodStatusUpdate: true,
+		},
+		// From OpenYurt Authors:
+		// Node created long time ago and run in autonomy mode, with status updated by kubelet exceeds grace period.
+		// Expect pods status is not updated and Unknown node status posted from node controller.
+		{
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Existing: []*v1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "node0",
+							CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+							Annotations: map[string]string{
+								nodeutil.AnnotationKeyNodeAutonomy: "true",
+							},
+						},
+						Status: v1.NodeStatus{
+							Conditions: []v1.NodeCondition{
+								{
+									Type:   v1.NodeReady,
+									Status: v1.ConditionTrue,
+									// Node status hasn't been updated for 1hr.
+									LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+									LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+								},
+							},
+						},
+					},
+				},
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			timeToPass: 1 * time.Minute,
+			newNodeStatus: v1.NodeStatus{
+				Conditions: []v1.NodeCondition{
+					{
+						Type:   v1.NodeReady,
+						Status: v1.ConditionTrue,
+						// Node status hasn't been updated for 1hr.
+						LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+					},
+				},
+			},
+			expectedPodStatusUpdate: false,
+		},
+	}
+
+	for i, item := range table {
+		nodeController, _ := newNodeLifecycleControllerFromClient(
+			item.fakeNodeHandler,
+			5*time.Minute,
+			testRateLimiterQPS,
+			testRateLimiterQPS,
+			testLargeClusterThreshold,
+			testUnhealthyThreshold,
+			testNodeMonitorGracePeriod,
+			testNodeStartupGracePeriod,
+			testNodeMonitorPeriod,
+			false)
+		nodeController.now = func() metav1.Time { return fakeNow }
+		nodeController.recorder = testutil.NewFakeRecorder()
+		nodeController.getPodsAssignedToNode = fakeGetPodsAssignedToNode(item.fakeNodeHandler.Clientset)
+		if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		if err := nodeController.monitorNodeHealth(); err != nil {
+			t.Errorf("Case[%d] unexpected error: %v", i, err)
+		}
+		if item.timeToPass > 0 {
+			nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(item.timeToPass)} }
+			item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus
+			if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil {
+				t.Errorf("unexpected error: %v", err)
+			}
+			if err := nodeController.monitorNodeHealth(); err != nil {
+				t.Errorf("Case[%d] unexpected error: %v", i, err)
+			}
+		}
+
+		podStatusUpdated := false
+		for _, action := range item.fakeNodeHandler.Actions() {
+			if action.GetVerb() == "update" && action.GetResource().Resource == "pods" && action.GetSubresource() == "status" {
+				podStatusUpdated = true
+			}
+		}
+		if podStatusUpdated != item.expectedPodStatusUpdate {
+			t.Errorf("Case[%d] expect pod status updated to be %v, but got %v", i, item.expectedPodStatusUpdate, podStatusUpdated)
+		}
+	}
+}
+
+func TestMonitorNodeHealthMarkPodsNotReadyRetry(t *testing.T) {
+	type nodeIteration struct {
+		timeToPass time.Duration
+		newNodes   []*v1.Node
+	}
+	timeNow := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC)
+	timePlusTwoMinutes := metav1.Date(2015, 1, 1, 12, 0, 2, 0, time.UTC)
+	makeNodes := func(status v1.ConditionStatus, lastHeartbeatTime, lastTransitionTime metav1.Time) []*v1.Node {
+		return []*v1.Node{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: timeNow,
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             status,
+							LastHeartbeatTime:  lastHeartbeatTime,
+							LastTransitionTime: lastTransitionTime,
+						},
+					},
+				},
+			},
+		}
+	}
+	table := []struct {
+		desc                      string
+		fakeNodeHandler           *testutil.FakeNodeHandler
+		updateReactor             func(action testcore.Action) (bool, runtime.Object, error)
+		fakeGetPodsAssignedToNode func(c *fake.Clientset) func(string) ([]*v1.Pod, error)
+		nodeIterations            []nodeIteration
+		expectedPodStatusUpdates  int
+	}{
+		// Node created long time ago, with status updated by kubelet exceeds grace period.
+		// First monitorNodeHealth check will update pod status to NotReady.
+		// Second monitorNodeHealth check will do no updates (no retry).
+		{
+			desc: "successful pod status update, no retry required",
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			fakeGetPodsAssignedToNode: fakeGetPodsAssignedToNode,
+			nodeIterations: []nodeIteration{
+				{
+					timeToPass: 0,
+					newNodes:   makeNodes(v1.ConditionTrue, timeNow, timeNow),
+				},
+				{
+					timeToPass: 1 * time.Minute,
+					newNodes:   makeNodes(v1.ConditionTrue, timeNow, timeNow),
+				},
+				{
+					timeToPass: 1 * time.Minute,
+					newNodes:   makeNodes(v1.ConditionFalse, timePlusTwoMinutes, timePlusTwoMinutes),
+				},
+			},
+			expectedPodStatusUpdates: 1,
+		},
+		// Node created long time ago, with status updated by kubelet exceeds grace period.
+		// First monitorNodeHealth check will fail to update pod status to NotReady.
+		// Second monitorNodeHealth check will update pod status to NotReady (retry).
+		{
+			desc: "unsuccessful pod status update, retry required",
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			updateReactor: func() func(action testcore.Action) (bool, runtime.Object, error) {
+				i := 0
+				return func(action testcore.Action) (bool, runtime.Object, error) {
+					if action.GetVerb() == "update" && action.GetResource().Resource == "pods" && action.GetSubresource() == "status" {
+						i++
+						switch i {
+						case 1:
+							return true, nil, fmt.Errorf("fake error")
+						default:
+							return true, testutil.NewPod("pod0", "node0"), nil
+						}
+					}
+
+					return true, nil, fmt.Errorf("unsupported action")
+				}
+			}(),
+			fakeGetPodsAssignedToNode: fakeGetPodsAssignedToNode,
+			nodeIterations: []nodeIteration{
+				{
+					timeToPass: 0,
+					newNodes:   makeNodes(v1.ConditionTrue, timeNow, timeNow),
+				},
+				{
+					timeToPass: 1 * time.Minute,
+					newNodes:   makeNodes(v1.ConditionTrue, timeNow, timeNow),
+				},
+				{
+					timeToPass: 1 * time.Minute,
+					newNodes:   makeNodes(v1.ConditionFalse, timePlusTwoMinutes, timePlusTwoMinutes),
+				},
+			},
+			expectedPodStatusUpdates: 2, // One failed and one retry.
+		},
+		// Node created long time ago, with status updated by kubelet exceeds grace period.
+		// First monitorNodeHealth check will fail to list pods.
+		// Second monitorNodeHealth check will update pod status to NotReady (retry).
+		{
+			desc: "unsuccessful pod list, retry required",
+			fakeNodeHandler: &testutil.FakeNodeHandler{
+				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+			},
+			fakeGetPodsAssignedToNode: func(c *fake.Clientset) func(string) ([]*v1.Pod, error) {
+				i := 0
+				f := fakeGetPodsAssignedToNode(c)
+				return func(nodeName string) ([]*v1.Pod, error) {
+					i++
+					if i == 1 {
+						return nil, fmt.Errorf("fake error")
+					}
+					return f(nodeName)
+				}
+			},
+			nodeIterations: []nodeIteration{
+				{
+					timeToPass: 0,
+					newNodes:   makeNodes(v1.ConditionTrue, timeNow, timeNow),
+				},
+				{
+					timeToPass: 1 * time.Minute,
+					newNodes:   makeNodes(v1.ConditionTrue, timeNow, timeNow),
+				},
+				{
+					timeToPass: 1 * time.Minute,
+					newNodes:   makeNodes(v1.ConditionFalse, timePlusTwoMinutes, timePlusTwoMinutes),
+				},
+			},
+			expectedPodStatusUpdates: 1,
+		},
+	}
+
+	for _, item := range table {
+		t.Run(item.desc, func(t *testing.T) {
+			nodeController, _ := newNodeLifecycleControllerFromClient(
+				item.fakeNodeHandler,
+				5*time.Minute,
+				testRateLimiterQPS,
+				testRateLimiterQPS,
+				testLargeClusterThreshold,
+				testUnhealthyThreshold,
+				testNodeMonitorGracePeriod,
+				testNodeStartupGracePeriod,
+				testNodeMonitorPeriod,
+				false)
+			if item.updateReactor != nil {
+				item.fakeNodeHandler.Clientset.PrependReactor("update", "pods", item.updateReactor)
+			}
+			nodeController.now = func() metav1.Time { return timeNow }
+			nodeController.recorder = testutil.NewFakeRecorder()
+			nodeController.getPodsAssignedToNode = item.fakeGetPodsAssignedToNode(item.fakeNodeHandler.Clientset)
+			for _, itertion := range item.nodeIterations {
+				nodeController.now = func() metav1.Time { return metav1.Time{Time: timeNow.Add(itertion.timeToPass)} }
+				item.fakeNodeHandler.Existing = itertion.newNodes
+				if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil {
+					t.Errorf("unexpected error: %v", err)
+				}
+				if err := nodeController.monitorNodeHealth(); err != nil {
+					t.Errorf("unexpected error: %v", err)
+				}
+			}
+
+			podStatusUpdates := 0
+			for _, action := range item.fakeNodeHandler.Actions() {
+				if action.GetVerb() == "update" && action.GetResource().Resource == "pods" && action.GetSubresource() == "status" {
+					podStatusUpdates++
+				}
+			}
+			if podStatusUpdates != item.expectedPodStatusUpdates {
+				t.Errorf("expect pod status updated to happen %d times, but got %d", item.expectedPodStatusUpdates, podStatusUpdates)
+			}
+		})
+	}
+}
+
+// TestApplyNoExecuteTaints, ensures we just have a NoExecute taint applied to node.
+// NodeController is just responsible for enqueuing the node to tainting queue from which taint manager picks up
+// and evicts the pods on the node.
+func TestApplyNoExecuteTaints(t *testing.T) {
+	fakeNow := metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC)
+	evictionTimeout := 10 * time.Minute
+
+	fakeNodeHandler := &testutil.FakeNodeHandler{
+		Existing: []*v1.Node{
+			// Unreachable Taint with effect 'NoExecute' should be applied to this node.
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					Labels: map[string]string{
+						v1.LabelTopologyRegion:          "region1",
+						v1.LabelTopologyZone:            "zone1",
+						v1.LabelFailureDomainBetaRegion: "region1",
+						v1.LabelFailureDomainBetaZone:   "zone1",
+					},
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionUnknown,
+							LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+			},
+			// Because of the logic that prevents NC from evicting anything when all Nodes are NotReady
+			// we need second healthy node in tests.
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node1",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					Labels: map[string]string{
+						v1.LabelTopologyRegion:          "region1",
+						v1.LabelTopologyZone:            "zone1",
+						v1.LabelFailureDomainBetaRegion: "region1",
+						v1.LabelFailureDomainBetaZone:   "zone1",
+					},
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionTrue,
+							LastHeartbeatTime:  metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+			},
+			// NotReady Taint with NoExecute effect should be applied to this node.
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node2",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					Labels: map[string]string{
+						v1.LabelTopologyRegion:          "region1",
+						v1.LabelTopologyZone:            "zone1",
+						v1.LabelFailureDomainBetaRegion: "region1",
+						v1.LabelFailureDomainBetaZone:   "zone1",
+					},
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionFalse,
+							LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+			},
+		},
+		Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+	}
+	healthyNodeNewStatus := v1.NodeStatus{
+		Conditions: []v1.NodeCondition{
+			{
+				Type:               v1.NodeReady,
+				Status:             v1.ConditionTrue,
+				LastHeartbeatTime:  metav1.Date(2017, 1, 1, 12, 10, 0, 0, time.UTC),
+				LastTransitionTime: metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC),
+			},
+		},
+	}
+	originalTaint := UnreachableTaintTemplate
+	nodeController, _ := newNodeLifecycleControllerFromClient(
+		fakeNodeHandler,
+		evictionTimeout,
+		testRateLimiterQPS,
+		testRateLimiterQPS,
+		testLargeClusterThreshold,
+		testUnhealthyThreshold,
+		testNodeMonitorGracePeriod,
+		testNodeStartupGracePeriod,
+		testNodeMonitorPeriod,
+		true)
+	nodeController.now = func() metav1.Time { return fakeNow }
+	nodeController.recorder = testutil.NewFakeRecorder()
+	nodeController.getPodsAssignedToNode = fakeGetPodsAssignedToNode(fakeNodeHandler.Clientset)
+	if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	if err := nodeController.monitorNodeHealth(); err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	nodeController.doNoExecuteTaintingPass()
+	node0, err := fakeNodeHandler.Get(context.TODO(), "node0", metav1.GetOptions{})
+	if err != nil {
+		t.Errorf("Can't get current node0...")
+		return
+	}
+	if !taintutils.TaintExists(node0.Spec.Taints, UnreachableTaintTemplate) {
+		t.Errorf("Can't find taint %v in %v", originalTaint, node0.Spec.Taints)
+	}
+	node2, err := fakeNodeHandler.Get(context.TODO(), "node2", metav1.GetOptions{})
+	if err != nil {
+		t.Errorf("Can't get current node2...")
+		return
+	}
+	if !taintutils.TaintExists(node2.Spec.Taints, NotReadyTaintTemplate) {
+		t.Errorf("Can't find taint %v in %v", NotReadyTaintTemplate, node2.Spec.Taints)
+	}
+
+	// Make node3 healthy again.
+	node2.Status = healthyNodeNewStatus
+	_, err = fakeNodeHandler.UpdateStatus(context.TODO(), node2, metav1.UpdateOptions{})
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+	if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	if err := nodeController.monitorNodeHealth(); err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	nodeController.doNoExecuteTaintingPass()
+
+	node2, err = fakeNodeHandler.Get(context.TODO(), "node2", metav1.GetOptions{})
+	if err != nil {
+		t.Errorf("Can't get current node2...")
+		return
+	}
+	// We should not see any taint on the node(especially the Not-Ready taint with NoExecute effect).
+	if taintutils.TaintExists(node2.Spec.Taints, NotReadyTaintTemplate) || len(node2.Spec.Taints) > 0 {
+		t.Errorf("Found taint %v in %v, which should not be present", NotReadyTaintTemplate, node2.Spec.Taints)
+	}
+}
+
+// TestApplyNoExecuteTaintsToNodesEnqueueTwice ensures we taint every node with NoExecute even if enqueued twice
+func TestApplyNoExecuteTaintsToNodesEnqueueTwice(t *testing.T) {
+	fakeNow := metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC)
+	evictionTimeout := 10 * time.Minute
+
+	fakeNodeHandler := &testutil.FakeNodeHandler{
+		Existing: []*v1.Node{
+			// Unreachable Taint with effect 'NoExecute' should be applied to this node.
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					Labels: map[string]string{
+						v1.LabelTopologyRegion:          "region1",
+						v1.LabelTopologyZone:            "zone1",
+						v1.LabelFailureDomainBetaRegion: "region1",
+						v1.LabelFailureDomainBetaZone:   "zone1",
+					},
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionUnknown,
+							LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+			},
+			// Because of the logic that prevents NC from evicting anything when all Nodes are NotReady
+			// we need second healthy node in tests.
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node1",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					Labels: map[string]string{
+						v1.LabelTopologyRegion:          "region1",
+						v1.LabelTopologyZone:            "zone1",
+						v1.LabelFailureDomainBetaRegion: "region1",
+						v1.LabelFailureDomainBetaZone:   "zone1",
+					},
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionTrue,
+							LastHeartbeatTime:  metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+			},
+			// NotReady Taint with NoExecute effect should be applied to this node.
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node2",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					Labels: map[string]string{
+						v1.LabelTopologyRegion:          "region1",
+						v1.LabelTopologyZone:            "zone1",
+						v1.LabelFailureDomainBetaRegion: "region1",
+						v1.LabelFailureDomainBetaZone:   "zone1",
+					},
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionFalse,
+							LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+			},
+		},
+		Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+	}
+	healthyNodeNewStatus := v1.NodeStatus{
+		Conditions: []v1.NodeCondition{
+			{
+				Type:               v1.NodeReady,
+				Status:             v1.ConditionTrue,
+				LastHeartbeatTime:  metav1.Date(2017, 1, 1, 12, 10, 0, 0, time.UTC),
+				LastTransitionTime: metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC),
+			},
+		},
+	}
+	nodeController, _ := newNodeLifecycleControllerFromClient(
+		fakeNodeHandler,
+		evictionTimeout,
+		testRateLimiterQPS,
+		testRateLimiterQPS,
+		testLargeClusterThreshold,
+		testUnhealthyThreshold,
+		testNodeMonitorGracePeriod,
+		testNodeStartupGracePeriod,
+		testNodeMonitorPeriod,
+		true)
+	nodeController.now = func() metav1.Time { return fakeNow }
+	nodeController.recorder = testutil.NewFakeRecorder()
+	nodeController.getPodsAssignedToNode = fakeGetPodsAssignedToNode(fakeNodeHandler.Clientset)
+	if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	// 1. monitor node health twice, add untainted node once
+	if err := nodeController.monitorNodeHealth(); err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	if err := nodeController.monitorNodeHealth(); err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+
+	// 2. mark node0 healthy
+	node0, err := fakeNodeHandler.Get(context.TODO(), "node0", metav1.GetOptions{})
+	if err != nil {
+		t.Errorf("Can't get current node0...")
+		return
+	}
+	node0.Status = healthyNodeNewStatus
+	_, err = fakeNodeHandler.UpdateStatus(context.TODO(), node0, metav1.UpdateOptions{})
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+
+	// add other notReady nodes
+	fakeNodeHandler.Existing = append(fakeNodeHandler.Existing, []*v1.Node{
+		// Unreachable Taint with effect 'NoExecute' should be applied to this node.
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:              "node3",
+				CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+				Labels: map[string]string{
+					v1.LabelTopologyRegion:          "region1",
+					v1.LabelTopologyZone:            "zone1",
+					v1.LabelFailureDomainBetaRegion: "region1",
+					v1.LabelFailureDomainBetaZone:   "zone1",
+				},
+			},
+			Status: v1.NodeStatus{
+				Conditions: []v1.NodeCondition{
+					{
+						Type:               v1.NodeReady,
+						Status:             v1.ConditionUnknown,
+						LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+					},
+				},
+			},
+		},
+		// Because of the logic that prevents NC from evicting anything when all Nodes are NotReady
+		// we need second healthy node in tests.
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:              "node4",
+				CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+				Labels: map[string]string{
+					v1.LabelTopologyRegion:          "region1",
+					v1.LabelTopologyZone:            "zone1",
+					v1.LabelFailureDomainBetaRegion: "region1",
+					v1.LabelFailureDomainBetaZone:   "zone1",
+				},
+			},
+			Status: v1.NodeStatus{
+				Conditions: []v1.NodeCondition{
+					{
+						Type:               v1.NodeReady,
+						Status:             v1.ConditionTrue,
+						LastHeartbeatTime:  metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC),
+						LastTransitionTime: metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC),
+					},
+				},
+			},
+		},
+		// NotReady Taint with NoExecute effect should be applied to this node.
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:              "node5",
+				CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+				Labels: map[string]string{
+					v1.LabelTopologyRegion:          "region1",
+					v1.LabelTopologyZone:            "zone1",
+					v1.LabelFailureDomainBetaRegion: "region1",
+					v1.LabelFailureDomainBetaZone:   "zone1",
+				},
+			},
+			Status: v1.NodeStatus{
+				Conditions: []v1.NodeCondition{
+					{
+						Type:               v1.NodeReady,
+						Status:             v1.ConditionFalse,
+						LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+					},
+				},
+			},
+		},
+	}...)
+	if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	// 3. start monitor node health again, add untainted node twice, construct UniqueQueue with duplicated node cache
+	if err := nodeController.monitorNodeHealth(); err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+
+	// 4. do NoExecute taint pass
+	// when processing with node0, condition.Status is NodeReady, and return true with default case
+	// then remove the set value and queue value both, the taint job never stuck
+	nodeController.doNoExecuteTaintingPass()
+
+	// 5. get node3 and node5, see if it has ready got NoExecute taint
+	node3, err := fakeNodeHandler.Get(context.TODO(), "node3", metav1.GetOptions{})
+	if err != nil {
+		t.Errorf("Can't get current node3...")
+		return
+	}
+	if !taintutils.TaintExists(node3.Spec.Taints, UnreachableTaintTemplate) || len(node3.Spec.Taints) == 0 {
+		t.Errorf("Not found taint %v in %v, which should be present in %s", UnreachableTaintTemplate, node3.Spec.Taints, node3.Name)
+	}
+	node5, err := fakeNodeHandler.Get(context.TODO(), "node5", metav1.GetOptions{})
+	if err != nil {
+		t.Errorf("Can't get current node5...")
+		return
+	}
+	if !taintutils.TaintExists(node5.Spec.Taints, NotReadyTaintTemplate) || len(node5.Spec.Taints) == 0 {
+		t.Errorf("Not found taint %v in %v, which should be present in %s", NotReadyTaintTemplate, node5.Spec.Taints, node5.Name)
+	}
+}
+
+func TestSwapUnreachableNotReadyTaints(t *testing.T) {
+	fakeNow := metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC)
+	evictionTimeout := 10 * time.Minute
+
+	fakeNodeHandler := &testutil.FakeNodeHandler{
+		Existing: []*v1.Node{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					Labels: map[string]string{
+						v1.LabelTopologyRegion:          "region1",
+						v1.LabelTopologyZone:            "zone1",
+						v1.LabelFailureDomainBetaRegion: "region1",
+						v1.LabelFailureDomainBetaZone:   "zone1",
+					},
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionUnknown,
+							LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+			},
+			// Because of the logic that prevents NC from evicting anything when all Nodes are NotReady
+			// we need second healthy node in tests. Because of how the tests are written we need to update
+			// the status of this Node.
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node1",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					Labels: map[string]string{
+						v1.LabelTopologyRegion:          "region1",
+						v1.LabelTopologyZone:            "zone1",
+						v1.LabelFailureDomainBetaRegion: "region1",
+						v1.LabelFailureDomainBetaZone:   "zone1",
+					},
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionTrue,
+							LastHeartbeatTime:  metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+			},
+		},
+		Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+	}
+	timeToPass := evictionTimeout
+	newNodeStatus := v1.NodeStatus{
+		Conditions: []v1.NodeCondition{
+			{
+				Type:   v1.NodeReady,
+				Status: v1.ConditionFalse,
+				// Node status has just been updated, and is NotReady for 10min.
+				LastHeartbeatTime:  metav1.Date(2017, 1, 1, 12, 9, 0, 0, time.UTC),
+				LastTransitionTime: metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC),
+			},
+		},
+	}
+	healthyNodeNewStatus := v1.NodeStatus{
+		Conditions: []v1.NodeCondition{
+			{
+				Type:               v1.NodeReady,
+				Status:             v1.ConditionTrue,
+				LastHeartbeatTime:  metav1.Date(2017, 1, 1, 12, 10, 0, 0, time.UTC),
+				LastTransitionTime: metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC),
+			},
+		},
+	}
+	originalTaint := UnreachableTaintTemplate
+	updatedTaint := NotReadyTaintTemplate
+
+	nodeController, _ := newNodeLifecycleControllerFromClient(
+		fakeNodeHandler,
+		evictionTimeout,
+		testRateLimiterQPS,
+		testRateLimiterQPS,
+		testLargeClusterThreshold,
+		testUnhealthyThreshold,
+		testNodeMonitorGracePeriod,
+		testNodeStartupGracePeriod,
+		testNodeMonitorPeriod,
+		true)
+	nodeController.now = func() metav1.Time { return fakeNow }
+	nodeController.recorder = testutil.NewFakeRecorder()
+	nodeController.getPodsAssignedToNode = fakeGetPodsAssignedToNode(fakeNodeHandler.Clientset)
+	if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	if err := nodeController.monitorNodeHealth(); err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	nodeController.doNoExecuteTaintingPass()
+
+	node0, err := fakeNodeHandler.Get(context.TODO(), "node0", metav1.GetOptions{})
+	if err != nil {
+		t.Errorf("Can't get current node0...")
+		return
+	}
+	node1, err := fakeNodeHandler.Get(context.TODO(), "node1", metav1.GetOptions{})
+	if err != nil {
+		t.Errorf("Can't get current node1...")
+		return
+	}
+
+	if originalTaint != nil && !taintutils.TaintExists(node0.Spec.Taints, originalTaint) {
+		t.Errorf("Can't find taint %v in %v", originalTaint, node0.Spec.Taints)
+	}
+
+	nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(timeToPass)} }
+
+	node0.Status = newNodeStatus
+	node1.Status = healthyNodeNewStatus
+	_, err = fakeNodeHandler.UpdateStatus(context.TODO(), node0, metav1.UpdateOptions{})
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+	_, err = fakeNodeHandler.UpdateStatus(context.TODO(), node1, metav1.UpdateOptions{})
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+
+	if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	if err := nodeController.monitorNodeHealth(); err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	nodeController.doNoExecuteTaintingPass()
+
+	node0, err = fakeNodeHandler.Get(context.TODO(), "node0", metav1.GetOptions{})
+	if err != nil {
+		t.Errorf("Can't get current node0...")
+		return
+	}
+	if updatedTaint != nil {
+		if !taintutils.TaintExists(node0.Spec.Taints, updatedTaint) {
+			t.Errorf("Can't find taint %v in %v", updatedTaint, node0.Spec.Taints)
+		}
+	}
+}
+
+func TestTaintsNodeByCondition(t *testing.T) {
+	fakeNow := metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC)
+	evictionTimeout := 10 * time.Minute
+
+	fakeNodeHandler := &testutil.FakeNodeHandler{
+		Existing: []*v1.Node{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					Labels: map[string]string{
+						v1.LabelTopologyRegion:          "region1",
+						v1.LabelTopologyZone:            "zone1",
+						v1.LabelFailureDomainBetaRegion: "region1",
+						v1.LabelFailureDomainBetaZone:   "zone1",
+					},
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionTrue,
+							LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+			},
+		},
+		Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+	}
+
+	nodeController, _ := newNodeLifecycleControllerFromClient(
+		fakeNodeHandler,
+		evictionTimeout,
+		testRateLimiterQPS,
+		testRateLimiterQPS,
+		testLargeClusterThreshold,
+		testUnhealthyThreshold,
+		testNodeMonitorGracePeriod,
+		testNodeStartupGracePeriod,
+		testNodeMonitorPeriod,
+		true)
+	nodeController.now = func() metav1.Time { return fakeNow }
+	nodeController.recorder = testutil.NewFakeRecorder()
+	nodeController.getPodsAssignedToNode = fakeGetPodsAssignedToNode(fakeNodeHandler.Clientset)
+
+	networkUnavailableTaint := &v1.Taint{
+		Key:    v1.TaintNodeNetworkUnavailable,
+		Effect: v1.TaintEffectNoSchedule,
+	}
+	notReadyTaint := &v1.Taint{
+		Key:    v1.TaintNodeNotReady,
+		Effect: v1.TaintEffectNoSchedule,
+	}
+	unreachableTaint := &v1.Taint{
+		Key:    v1.TaintNodeUnreachable,
+		Effect: v1.TaintEffectNoSchedule,
+	}
+
+	tests := []struct {
+		Name           string
+		Node           *v1.Node
+		ExpectedTaints []*v1.Taint
+	}{
+		{
+			Name: "NetworkUnavailable is true",
+			Node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					Labels: map[string]string{
+						v1.LabelTopologyRegion:          "region1",
+						v1.LabelTopologyZone:            "zone1",
+						v1.LabelFailureDomainBetaRegion: "region1",
+						v1.LabelFailureDomainBetaZone:   "zone1",
+					},
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionTrue,
+							LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+						{
+							Type:               v1.NodeNetworkUnavailable,
+							Status:             v1.ConditionTrue,
+							LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+			},
+			ExpectedTaints: []*v1.Taint{networkUnavailableTaint},
+		},
+		{
+			Name: "NetworkUnavailable is true",
+			Node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					Labels: map[string]string{
+						v1.LabelTopologyRegion:          "region1",
+						v1.LabelTopologyZone:            "zone1",
+						v1.LabelFailureDomainBetaRegion: "region1",
+						v1.LabelFailureDomainBetaZone:   "zone1",
+					},
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionTrue,
+							LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+						{
+							Type:               v1.NodeNetworkUnavailable,
+							Status:             v1.ConditionTrue,
+							LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+			},
+			ExpectedTaints: []*v1.Taint{networkUnavailableTaint},
+		},
+		{
+			Name: "Ready is false",
+			Node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					Labels: map[string]string{
+						v1.LabelTopologyRegion:          "region1",
+						v1.LabelTopologyZone:            "zone1",
+						v1.LabelFailureDomainBetaRegion: "region1",
+						v1.LabelFailureDomainBetaZone:   "zone1",
+					},
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionFalse,
+							LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+			},
+			ExpectedTaints: []*v1.Taint{notReadyTaint},
+		},
+		{
+			Name: "Ready is unknown",
+			Node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					Labels: map[string]string{
+						v1.LabelTopologyRegion:          "region1",
+						v1.LabelTopologyZone:            "zone1",
+						v1.LabelFailureDomainBetaRegion: "region1",
+						v1.LabelFailureDomainBetaZone:   "zone1",
+					},
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionUnknown,
+							LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+			},
+			ExpectedTaints: []*v1.Taint{unreachableTaint},
+		},
+	}
+
+	for _, test := range tests {
+		fakeNodeHandler.Update(context.TODO(), test.Node, metav1.UpdateOptions{})
+		if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		nodeController.doNoScheduleTaintingPass(test.Node.Name)
+		if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		node0, err := nodeController.nodeLister.Get("node0")
+		if err != nil {
+			t.Errorf("Can't get current node0...")
+			return
+		}
+		if len(node0.Spec.Taints) != len(test.ExpectedTaints) {
+			t.Errorf("%s: Unexpected number of taints: expected %d, got %d",
+				test.Name, len(test.ExpectedTaints), len(node0.Spec.Taints))
+		}
+		for _, taint := range test.ExpectedTaints {
+			if !taintutils.TaintExists(node0.Spec.Taints, taint) {
+				t.Errorf("%s: Can't find taint %v in %v", test.Name, taint, node0.Spec.Taints)
+			}
+		}
+	}
+}
+
+func TestNodeEventGeneration(t *testing.T) {
+	fakeNow := metav1.Date(2016, 9, 10, 12, 0, 0, 0, time.UTC)
+	fakeNodeHandler := &testutil.FakeNodeHandler{
+		Existing: []*v1.Node{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					UID:               "1234567890",
+					CreationTimestamp: metav1.Date(2015, 8, 10, 0, 0, 0, 0, time.UTC),
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionUnknown,
+							LastHeartbeatTime:  metav1.Date(2015, 8, 10, 0, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2015, 8, 10, 0, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+			},
+		},
+		Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+	}
+
+	nodeController, _ := newNodeLifecycleControllerFromClient(
+		fakeNodeHandler,
+		5*time.Minute,
+		testRateLimiterQPS,
+		testRateLimiterQPS,
+		testLargeClusterThreshold,
+		testUnhealthyThreshold,
+		testNodeMonitorGracePeriod,
+		testNodeStartupGracePeriod,
+		testNodeMonitorPeriod,
+		false)
+	nodeController.now = func() metav1.Time { return fakeNow }
+	fakeRecorder := testutil.NewFakeRecorder()
+	nodeController.recorder = fakeRecorder
+	nodeController.getPodsAssignedToNode = fakeGetPodsAssignedToNode(fakeNodeHandler.Clientset)
+
+	if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	if err := nodeController.monitorNodeHealth(); err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	if len(fakeRecorder.Events) != 1 {
+		t.Fatalf("unexpected events, got %v, expected %v: %+v", len(fakeRecorder.Events), 1, fakeRecorder.Events)
+	}
+	if fakeRecorder.Events[0].Reason != "RegisteredNode" {
+		var reasons []string
+		for _, event := range fakeRecorder.Events {
+			reasons = append(reasons, event.Reason)
+		}
+		t.Fatalf("unexpected events generation: %v", strings.Join(reasons, ","))
+	}
+	for _, event := range fakeRecorder.Events {
+		involvedObject := event.InvolvedObject
+		actualUID := string(involvedObject.UID)
+		if actualUID != "1234567890" {
+			t.Fatalf("unexpected event uid: %v", actualUID)
+		}
+	}
+}
+
+func TestReconcileNodeLabels(t *testing.T) {
+	fakeNow := metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC)
+	evictionTimeout := 10 * time.Minute
+
+	fakeNodeHandler := &testutil.FakeNodeHandler{
+		Existing: []*v1.Node{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					Labels: map[string]string{
+						v1.LabelTopologyRegion:          "region1",
+						v1.LabelTopologyZone:            "zone1",
+						v1.LabelFailureDomainBetaRegion: "region1",
+						v1.LabelFailureDomainBetaZone:   "zone1",
+					},
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionTrue,
+							LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+			},
+		},
+		Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+	}
+
+	nodeController, _ := newNodeLifecycleControllerFromClient(
+		fakeNodeHandler,
+		evictionTimeout,
+		testRateLimiterQPS,
+		testRateLimiterQPS,
+		testLargeClusterThreshold,
+		testUnhealthyThreshold,
+		testNodeMonitorGracePeriod,
+		testNodeStartupGracePeriod,
+		testNodeMonitorPeriod,
+		true)
+	nodeController.now = func() metav1.Time { return fakeNow }
+	nodeController.recorder = testutil.NewFakeRecorder()
+	nodeController.getPodsAssignedToNode = fakeGetPodsAssignedToNode(fakeNodeHandler.Clientset)
+
+	tests := []struct {
+		Name           string
+		Node           *v1.Node
+		ExpectedLabels map[string]string
+	}{
+		{
+			Name: "No-op if node has no labels",
+			Node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+				},
+			},
+			ExpectedLabels: nil,
+		},
+		{
+			Name: "No-op if no target labels present",
+			Node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					Labels: map[string]string{
+						v1.LabelTopologyRegion: "region1",
+					},
+				},
+			},
+			ExpectedLabels: map[string]string{
+				v1.LabelTopologyRegion: "region1",
+			},
+		},
+		{
+			// OpenYurt Authors:
+			// Currently in node_lifecycle_controller.go, we have
+			// - primaryKey: "beta.kubernetes.io"
+			// - secondaryKey: "kubernetes.io"
+			// But in node_lifecycle_controller_test.go, which is from k8s 1.20.11,
+			// assuming that
+			// - primaryKey: "kubernetes.io"
+			// - secondaryKey: "beta.kubernetes.io"
+			// So we change this case.
+			Name: "Create OS/arch stable labels when they don't exist",
+			Node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					Labels: map[string]string{
+						LabelOS:   "linux",
+						LabelArch: "amd64",
+					},
+				},
+			},
+			ExpectedLabels: map[string]string{
+				LabelOS:            "linux",
+				LabelArch:          "amd64",
+				v1.LabelOSStable:   "linux",
+				v1.LabelArchStable: "amd64",
+			},
+		},
+		{
+			// OpenYurt Authors:
+			// Currently in node_lifecycle_controller.go, we have
+			// - primaryKey: "beta.kubernetes.io"
+			// - secondaryKey: "kubernetes.io"
+			// But in node_lifecycle_controller_test.go, which is from k8s 1.20.11,
+			// assuming that
+			// - primaryKey: "kubernetes.io"
+			// - secondaryKey: "beta.kubernetes.io"
+			// So we change this case.
+			Name: "Reconcile OS/arch stable labels to match beta labels",
+			Node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
+					Labels: map[string]string{
+						LabelOS:            "linux",
+						LabelArch:          "amd64",
+						v1.LabelOSStable:   "windows",
+						v1.LabelArchStable: "arm",
+					},
+				},
+			},
+			ExpectedLabels: map[string]string{
+				LabelOS:            "linux",
+				LabelArch:          "amd64",
+				v1.LabelOSStable:   "linux",
+				v1.LabelArchStable: "amd64",
+			},
+		},
+	}
+
+	for _, test := range tests {
+		fakeNodeHandler.Update(context.TODO(), test.Node, metav1.UpdateOptions{})
+		if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil {
+			t.Fatalf("unexpected error: %v", err)
+		}
+		nodeController.reconcileNodeLabels(test.Node.Name)
+		if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil {
+			t.Fatalf("unexpected error: %v", err)
+		}
+		node0, err := nodeController.nodeLister.Get("node0")
+		if err != nil {
+			t.Fatalf("Can't get current node0...")
+		}
+		if len(node0.Labels) != len(test.ExpectedLabels) {
+			t.Errorf("%s: Unexpected number of taints: expected %d, got %d",
+				test.Name, len(test.ExpectedLabels), len(node0.Labels))
+		}
+		for key, expectedValue := range test.ExpectedLabels {
+			actualValue, ok := node0.Labels[key]
+			if !ok {
+				t.Errorf("%s: Can't find label %v in %v", test.Name, key, node0.Labels)
+			}
+			if actualValue != expectedValue {
+				t.Errorf("%s: label %q: expected value %q, got value %q", test.Name, key, expectedValue, actualValue)
+			}
+		}
+	}
+}
+
+func TestTryUpdateNodeHealth(t *testing.T) {
+	fakeNow := metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC)
+	fakeOld := metav1.Date(2016, 1, 1, 12, 0, 0, 0, time.UTC)
+	evictionTimeout := 10 * time.Minute
+
+	fakeNodeHandler := &testutil.FakeNodeHandler{
+		Existing: []*v1.Node{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: fakeNow,
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionTrue,
+							LastHeartbeatTime:  fakeNow,
+							LastTransitionTime: fakeNow,
+						},
+					},
+				},
+			},
+		},
+		Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
+	}
+
+	nodeController, _ := newNodeLifecycleControllerFromClient(
+		fakeNodeHandler,
+		evictionTimeout,
+		testRateLimiterQPS,
+		testRateLimiterQPS,
+		testLargeClusterThreshold,
+		testUnhealthyThreshold,
+		testNodeMonitorGracePeriod,
+		testNodeStartupGracePeriod,
+		testNodeMonitorPeriod,
+		true)
+	nodeController.now = func() metav1.Time { return fakeNow }
+	nodeController.recorder = testutil.NewFakeRecorder()
+	nodeController.getPodsAssignedToNode = fakeGetPodsAssignedToNode(fakeNodeHandler.Clientset)
+
+	getStatus := func(cond *v1.NodeCondition) *v1.ConditionStatus {
+		if cond == nil {
+			return nil
+		}
+		return &cond.Status
+	}
+
+	tests := []struct {
+		name string
+		node *v1.Node
+	}{
+		{
+			name: "Status true",
+			node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: fakeNow,
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionTrue,
+							LastHeartbeatTime:  fakeNow,
+							LastTransitionTime: fakeNow,
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "Status false",
+			node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: fakeNow,
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionFalse,
+							LastHeartbeatTime:  fakeNow,
+							LastTransitionTime: fakeNow,
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "Status unknown",
+			node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: fakeNow,
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionUnknown,
+							LastHeartbeatTime:  fakeNow,
+							LastTransitionTime: fakeNow,
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "Status nil",
+			node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: fakeNow,
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{},
+				},
+			},
+		},
+		{
+			name: "Status true - after grace period",
+			node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: fakeOld,
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionTrue,
+							LastHeartbeatTime:  fakeOld,
+							LastTransitionTime: fakeOld,
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "Status false - after grace period",
+			node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: fakeOld,
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionFalse,
+							LastHeartbeatTime:  fakeOld,
+							LastTransitionTime: fakeOld,
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "Status unknown - after grace period",
+			node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: fakeOld,
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionUnknown,
+							LastHeartbeatTime:  fakeOld,
+							LastTransitionTime: fakeOld,
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "Status nil - after grace period",
+			node: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:              "node0",
+					CreationTimestamp: fakeOld,
+				},
+				Status: v1.NodeStatus{
+					Conditions: []v1.NodeCondition{},
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			nodeController.nodeHealthMap.set(test.node.Name, &nodeHealthData{
+				status:                   &test.node.Status,
+				probeTimestamp:           test.node.CreationTimestamp,
+				readyTransitionTimestamp: test.node.CreationTimestamp,
+			})
+			_, _, currentReadyCondition, err := nodeController.tryUpdateNodeHealth(test.node)
+			if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+			_, savedReadyCondition := nodeutil.GetNodeCondition(nodeController.nodeHealthMap.getDeepCopy(test.node.Name).status, v1.NodeReady)
+			savedStatus := getStatus(savedReadyCondition)
+			currentStatus := getStatus(currentReadyCondition)
+			if !apiequality.Semantic.DeepEqual(currentStatus, savedStatus) {
+				t.Errorf("expected %v, got %v", savedStatus, currentStatus)
+			}
+		})
+	}
+}
diff --git a/pkg/controller/nodelifecycle/scheduler/taint_manager_test.go b/pkg/controller/nodelifecycle/scheduler/taint_manager_test.go
new file mode 100644
index 00000000..73857cfc
--- /dev/null
+++ b/pkg/controller/nodelifecycle/scheduler/taint_manager_test.go
@@ -0,0 +1,941 @@
+/*
+Copyright 2022 The OpenYurt Authors.
+Copyright 2017 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+/*
+This file was derived from k8s.io/kubernetes/pkg/controller/nodelifecycle/scheduler/taint_manager_test.go
+at commit: 27522a29feb.
+
+CHANGELOG from OpenYurt Authors:
+1. Remove temporary->infinite taints case in TestUpdateNodeWithMultipleTaints.
+2. Add autonomy case in TestUpdateNode.
+*/
+
+package scheduler
+
+import (
+	"context"
+	"fmt"
+	"sort"
+	"sync"
+	"testing"
+	"time"
+
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/fields"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/client-go/kubernetes/fake"
+	clienttesting "k8s.io/client-go/testing"
+
+	"github.com/openyurtio/openyurt/pkg/controller/kubernetes/controller/testutil"
+	"github.com/openyurtio/openyurt/pkg/controller/util/node"
+)
+
+var timeForControllerToProgress = 500 * time.Millisecond
+
+func getPodFromClientset(clientset *fake.Clientset) GetPodFunc {
+	return func(name, namespace string) (*v1.Pod, error) {
+		return clientset.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+	}
+}
+
+func getPodsAssignedToNode(c *fake.Clientset) GetPodsByNodeNameFunc {
+	return func(nodeName string) ([]*v1.Pod, error) {
+		selector := fields.SelectorFromSet(fields.Set{"spec.nodeName": nodeName})
+		pods, err := c.CoreV1().Pods(v1.NamespaceAll).List(context.TODO(), metav1.ListOptions{
+			FieldSelector: selector.String(),
+			LabelSelector: labels.Everything().String(),
+		})
+		if err != nil {
+			return []*v1.Pod{}, fmt.Errorf("failed to get Pods assigned to node %v", nodeName)
+		}
+		rPods := make([]*v1.Pod, len(pods.Items))
+		for i := range pods.Items {
+			rPods[i] = &pods.Items[i]
+		}
+		return rPods, nil
+	}
+}
+
+func getNodeFromClientset(clientset *fake.Clientset) GetNodeFunc {
+	return func(name string) (*v1.Node, error) {
+		return clientset.CoreV1().Nodes().Get(context.TODO(), name, metav1.GetOptions{})
+	}
+}
+
+type podHolder struct {
+	pod *v1.Pod
+	sync.Mutex
+}
+
+func (p *podHolder) getPod(name, namespace string) (*v1.Pod, error) {
+	p.Lock()
+	defer p.Unlock()
+	return p.pod, nil
+}
+func (p *podHolder) setPod(pod *v1.Pod) {
+	p.Lock()
+	defer p.Unlock()
+	p.pod = pod
+}
+
+type nodeHolder struct {
+	lock sync.Mutex
+
+	node *v1.Node
+}
+
+func (n *nodeHolder) setNode(node *v1.Node) {
+	n.lock.Lock()
+	defer n.lock.Unlock()
+	n.node = node
+}
+
+func (n *nodeHolder) getNode(name string) (*v1.Node, error) {
+	n.lock.Lock()
+	defer n.lock.Unlock()
+	return n.node, nil
+}
+
+func createNoExecuteTaint(index int) v1.Taint {
+	now := metav1.Now()
+	return v1.Taint{
+		Key:       "testTaint" + fmt.Sprintf("%v", index),
+		Value:     "test" + fmt.Sprintf("%v", index),
+		Effect:    v1.TaintEffectNoExecute,
+		TimeAdded: &now,
+	}
+}
+
+func addToleration(pod *v1.Pod, index int, duration int64) *v1.Pod {
+	if pod.Annotations == nil {
+		pod.Annotations = map[string]string{}
+	}
+	if duration < 0 {
+		pod.Spec.Tolerations = []v1.Toleration{{Key: "testTaint" + fmt.Sprintf("%v", index), Value: "test" + fmt.Sprintf("%v", index), Effect: v1.TaintEffectNoExecute}}
+
+	} else {
+		pod.Spec.Tolerations = []v1.Toleration{{Key: "testTaint" + fmt.Sprintf("%v", index), Value: "test" + fmt.Sprintf("%v", index), Effect: v1.TaintEffectNoExecute, TolerationSeconds: &duration}}
+	}
+	return pod
+}
+
+func addTaintsToNode(node *v1.Node, key, value string, indices []int) *v1.Node {
+	taints := []v1.Taint{}
+	for _, index := range indices {
+		taints = append(taints, createNoExecuteTaint(index))
+	}
+	node.Spec.Taints = taints
+	return node
+}
+
+type timestampedPod struct {
+	names     []string
+	timestamp time.Duration
+}
+
+type durationSlice []timestampedPod
+
+func (a durationSlice) Len() int           { return len(a) }
+func (a durationSlice) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a durationSlice) Less(i, j int) bool { return a[i].timestamp < a[j].timestamp }
+
+func TestFilterNoExecuteTaints(t *testing.T) {
+	taints := []v1.Taint{
+		{
+			Key:    "one",
+			Value:  "one",
+			Effect: v1.TaintEffectNoExecute,
+		},
+		{
+			Key:    "two",
+			Value:  "two",
+			Effect: v1.TaintEffectNoSchedule,
+		},
+	}
+	taints = getNoExecuteTaints(taints)
+	if len(taints) != 1 || taints[0].Key != "one" {
+		t.Errorf("Filtering doesn't work. Got %v", taints)
+	}
+}
+
+func TestCreatePod(t *testing.T) {
+	testCases := []struct {
+		description  string
+		pod          *v1.Pod
+		taintedNodes map[string][]v1.Taint
+		expectDelete bool
+	}{
+		{
+			description:  "not scheduled - ignore",
+			pod:          testutil.NewPod("pod1", ""),
+			taintedNodes: map[string][]v1.Taint{},
+			expectDelete: false,
+		},
+		{
+			description:  "scheduled on untainted Node",
+			pod:          testutil.NewPod("pod1", "node1"),
+			taintedNodes: map[string][]v1.Taint{},
+			expectDelete: false,
+		},
+		{
+			description: "schedule on tainted Node",
+			pod:         testutil.NewPod("pod1", "node1"),
+			taintedNodes: map[string][]v1.Taint{
+				"node1": {createNoExecuteTaint(1)},
+			},
+			expectDelete: true,
+		},
+		{
+			description: "schedule on tainted Node with finite toleration",
+			pod:         addToleration(testutil.NewPod("pod1", "node1"), 1, 100),
+			taintedNodes: map[string][]v1.Taint{
+				"node1": {createNoExecuteTaint(1)},
+			},
+			expectDelete: false,
+		},
+		{
+			description: "schedule on tainted Node with infinite toleration",
+			pod:         addToleration(testutil.NewPod("pod1", "node1"), 1, -1),
+			taintedNodes: map[string][]v1.Taint{
+				"node1": {createNoExecuteTaint(1)},
+			},
+			expectDelete: false,
+		},
+		{
+			description: "schedule on tainted Node with infinite ivalid toleration",
+			pod:         addToleration(testutil.NewPod("pod1", "node1"), 2, -1),
+			taintedNodes: map[string][]v1.Taint{
+				"node1": {createNoExecuteTaint(1)},
+			},
+			expectDelete: true,
+		},
+	}
+
+	for _, item := range testCases {
+		stopCh := make(chan struct{})
+		fakeClientset := fake.NewSimpleClientset()
+		controller := NewNoExecuteTaintManager(fakeClientset, (&podHolder{pod: item.pod}).getPod, getNodeFromClientset(fakeClientset), getPodsAssignedToNode(fakeClientset))
+		controller.recorder = testutil.NewFakeRecorder()
+		go controller.Run(stopCh)
+		controller.taintedNodes = item.taintedNodes
+		controller.PodUpdated(nil, item.pod)
+		// wait a bit
+		time.Sleep(timeForControllerToProgress)
+
+		podDeleted := false
+		for _, action := range fakeClientset.Actions() {
+			if action.GetVerb() == "delete" && action.GetResource().Resource == "pods" {
+				podDeleted = true
+			}
+		}
+		if podDeleted != item.expectDelete {
+			t.Errorf("%v: Unexpected test result. Expected delete %v, got %v", item.description, item.expectDelete, podDeleted)
+		}
+		close(stopCh)
+	}
+}
+
+func TestDeletePod(t *testing.T) {
+	stopCh := make(chan struct{})
+	fakeClientset := fake.NewSimpleClientset()
+	controller := NewNoExecuteTaintManager(fakeClientset, getPodFromClientset(fakeClientset), getNodeFromClientset(fakeClientset), getPodsAssignedToNode(fakeClientset))
+	controller.recorder = testutil.NewFakeRecorder()
+	go controller.Run(stopCh)
+	controller.taintedNodes = map[string][]v1.Taint{
+		"node1": {createNoExecuteTaint(1)},
+	}
+	controller.PodUpdated(testutil.NewPod("pod1", "node1"), nil)
+	// wait a bit to see if nothing will panic
+	time.Sleep(timeForControllerToProgress)
+	close(stopCh)
+}
+
+func TestUpdatePod(t *testing.T) {
+	testCases := []struct {
+		description     string
+		prevPod         *v1.Pod
+		newPod          *v1.Pod
+		taintedNodes    map[string][]v1.Taint
+		expectDelete    bool
+		additionalSleep time.Duration
+	}{
+		{
+			description: "scheduling onto tainted Node",
+			prevPod:     testutil.NewPod("pod1", ""),
+			newPod:      testutil.NewPod("pod1", "node1"),
+			taintedNodes: map[string][]v1.Taint{
+				"node1": {createNoExecuteTaint(1)},
+			},
+			expectDelete: true,
+		},
+		{
+			description: "scheduling onto tainted Node with toleration",
+			prevPod:     addToleration(testutil.NewPod("pod1", ""), 1, -1),
+			newPod:      addToleration(testutil.NewPod("pod1", "node1"), 1, -1),
+			taintedNodes: map[string][]v1.Taint{
+				"node1": {createNoExecuteTaint(1)},
+			},
+			expectDelete: false,
+		},
+		{
+			description: "removing toleration",
+			prevPod:     addToleration(testutil.NewPod("pod1", "node1"), 1, 100),
+			newPod:      testutil.NewPod("pod1", "node1"),
+			taintedNodes: map[string][]v1.Taint{
+				"node1": {createNoExecuteTaint(1)},
+			},
+			expectDelete: true,
+		},
+		{
+			description: "lengthening toleration shouldn't work",
+			prevPod:     addToleration(testutil.NewPod("pod1", "node1"), 1, 1),
+			newPod:      addToleration(testutil.NewPod("pod1", "node1"), 1, 100),
+			taintedNodes: map[string][]v1.Taint{
+				"node1": {createNoExecuteTaint(1)},
+			},
+			expectDelete:    true,
+			additionalSleep: 1500 * time.Millisecond,
+		},
+	}
+
+	for _, item := range testCases {
+		stopCh := make(chan struct{})
+		fakeClientset := fake.NewSimpleClientset()
+		holder := &podHolder{}
+		controller := NewNoExecuteTaintManager(fakeClientset, holder.getPod, getNodeFromClientset(fakeClientset), getPodsAssignedToNode(fakeClientset))
+		controller.recorder = testutil.NewFakeRecorder()
+		go controller.Run(stopCh)
+		controller.taintedNodes = item.taintedNodes
+
+		holder.setPod(item.prevPod)
+		controller.PodUpdated(nil, item.prevPod)
+		fakeClientset.ClearActions()
+		time.Sleep(timeForControllerToProgress)
+		holder.setPod(item.newPod)
+		controller.PodUpdated(item.prevPod, item.newPod)
+		// wait a bit
+		time.Sleep(timeForControllerToProgress)
+		if item.additionalSleep > 0 {
+			time.Sleep(item.additionalSleep)
+		}
+
+		podDeleted := false
+		for _, action := range fakeClientset.Actions() {
+			if action.GetVerb() == "delete" && action.GetResource().Resource == "pods" {
+				podDeleted = true
+			}
+		}
+		if podDeleted != item.expectDelete {
+			t.Errorf("%v: Unexpected test result. Expected delete %v, got %v", item.description, item.expectDelete, podDeleted)
+		}
+		close(stopCh)
+	}
+}
+
+func TestCreateNode(t *testing.T) {
+	testCases := []struct {
+		description  string
+		pods         []v1.Pod
+		node         *v1.Node
+		expectDelete bool
+	}{
+		{
+			description: "Creating Node matching already assigned Pod",
+			pods: []v1.Pod{
+				*testutil.NewPod("pod1", "node1"),
+			},
+			node:         testutil.NewNode("node1"),
+			expectDelete: false,
+		},
+		{
+			description: "Creating tainted Node matching already assigned Pod",
+			pods: []v1.Pod{
+				*testutil.NewPod("pod1", "node1"),
+			},
+			node:         addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1}),
+			expectDelete: true,
+		},
+		{
+			description: "Creating tainted Node matching already assigned tolerating Pod",
+			pods: []v1.Pod{
+				*addToleration(testutil.NewPod("pod1", "node1"), 1, -1),
+			},
+			node:         addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1}),
+			expectDelete: false,
+		},
+	}
+
+	for _, item := range testCases {
+		stopCh := make(chan struct{})
+		fakeClientset := fake.NewSimpleClientset(&v1.PodList{Items: item.pods})
+		controller := NewNoExecuteTaintManager(fakeClientset, getPodFromClientset(fakeClientset), (&nodeHolder{node: item.node}).getNode, getPodsAssignedToNode(fakeClientset))
+		controller.recorder = testutil.NewFakeRecorder()
+		go controller.Run(stopCh)
+		controller.NodeUpdated(nil, item.node)
+		// wait a bit
+		time.Sleep(timeForControllerToProgress)
+
+		podDeleted := false
+		for _, action := range fakeClientset.Actions() {
+			if action.GetVerb() == "delete" && action.GetResource().Resource == "pods" {
+				podDeleted = true
+			}
+		}
+		if podDeleted != item.expectDelete {
+			t.Errorf("%v: Unexpected test result. Expected delete %v, got %v", item.description, item.expectDelete, podDeleted)
+		}
+		close(stopCh)
+	}
+}
+
+func TestDeleteNode(t *testing.T) {
+	stopCh := make(chan struct{})
+	fakeClientset := fake.NewSimpleClientset()
+	controller := NewNoExecuteTaintManager(fakeClientset, getPodFromClientset(fakeClientset), getNodeFromClientset(fakeClientset), getPodsAssignedToNode(fakeClientset))
+	controller.recorder = testutil.NewFakeRecorder()
+	controller.taintedNodes = map[string][]v1.Taint{
+		"node1": {createNoExecuteTaint(1)},
+	}
+	go controller.Run(stopCh)
+	controller.NodeUpdated(testutil.NewNode("node1"), nil)
+	// wait a bit to see if nothing will panic
+	time.Sleep(timeForControllerToProgress)
+	controller.taintedNodesLock.Lock()
+	if _, ok := controller.taintedNodes["node1"]; ok {
+		t.Error("Node should have been deleted from taintedNodes list")
+	}
+	controller.taintedNodesLock.Unlock()
+	close(stopCh)
+}
+
+func TestUpdateNode(t *testing.T) {
+	testCases := []struct {
+		description     string
+		pods            []v1.Pod
+		oldNode         *v1.Node
+		newNode         *v1.Node
+		expectDelete    bool
+		additionalSleep time.Duration
+	}{
+		{
+			description: "Added taint",
+			pods: []v1.Pod{
+				*testutil.NewPod("pod1", "node1"),
+			},
+			oldNode:      testutil.NewNode("node1"),
+			newNode:      addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1}),
+			expectDelete: true,
+		},
+		{
+			description: "Added tolerated taint",
+			pods: []v1.Pod{
+				*addToleration(testutil.NewPod("pod1", "node1"), 1, 100),
+			},
+			oldNode:      testutil.NewNode("node1"),
+			newNode:      addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1}),
+			expectDelete: false,
+		},
+		{
+			description: "Only one added taint tolerated",
+			pods: []v1.Pod{
+				*addToleration(testutil.NewPod("pod1", "node1"), 1, 100),
+			},
+			oldNode:      testutil.NewNode("node1"),
+			newNode:      addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1, 2}),
+			expectDelete: true,
+		},
+		{
+			description: "Taint removed",
+			pods: []v1.Pod{
+				*addToleration(testutil.NewPod("pod1", "node1"), 1, 1),
+			},
+			oldNode:         addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1}),
+			newNode:         testutil.NewNode("node1"),
+			expectDelete:    false,
+			additionalSleep: 1500 * time.Millisecond,
+		},
+		{
+			description: "Pod with multiple tolerations are evicted when first one runs out",
+			pods: []v1.Pod{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "default",
+						Name:      "pod1",
+					},
+					Spec: v1.PodSpec{
+						NodeName: "node1",
+						Tolerations: []v1.Toleration{
+							{Key: "testTaint1", Value: "test1", Effect: v1.TaintEffectNoExecute, TolerationSeconds: &[]int64{1}[0]},
+							{Key: "testTaint2", Value: "test2", Effect: v1.TaintEffectNoExecute, TolerationSeconds: &[]int64{100}[0]},
+						},
+					},
+					Status: v1.PodStatus{
+						Conditions: []v1.PodCondition{
+							{
+								Type:   v1.PodReady,
+								Status: v1.ConditionTrue,
+							},
+						},
+					},
+				},
+			},
+			oldNode:         testutil.NewNode("node1"),
+			newNode:         addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1, 2}),
+			expectDelete:    true,
+			additionalSleep: 1500 * time.Millisecond,
+		},
+		// From OpenYurt Authors:
+		// When node runs in autonomy mode, and get NoExecute taint, do not evict pods on it.
+		{
+			description: "Added taint, and node runs in autonomy mode",
+			pods: []v1.Pod{
+				*testutil.NewPod("pod1", "node1"),
+			},
+			oldNode: &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "node1",
+					Annotations: map[string]string{
+						node.AnnotationKeyNodeAutonomy: "true",
+					},
+				},
+				Status: v1.NodeStatus{
+					Capacity: v1.ResourceList{
+						v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+						v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+					},
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionUnknown,
+							LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+			},
+			newNode: addTaintsToNode(&v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "node1",
+					Annotations: map[string]string{
+						node.AnnotationKeyNodeAutonomy: "true",
+					},
+				},
+				Status: v1.NodeStatus{
+					Capacity: v1.ResourceList{
+						v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+						v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+					},
+					Conditions: []v1.NodeCondition{
+						{
+							Type:               v1.NodeReady,
+							Status:             v1.ConditionUnknown,
+							LastHeartbeatTime:  metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+							LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC),
+						},
+					},
+				},
+			}, "testTaint1", "taint1", []int{1}),
+			expectDelete: false,
+		},
+	}
+
+	for _, item := range testCases {
+		stopCh := make(chan struct{})
+		fakeClientset := fake.NewSimpleClientset(&v1.PodList{Items: item.pods})
+		controller := NewNoExecuteTaintManager(fakeClientset, getPodFromClientset(fakeClientset), (&nodeHolder{node: item.newNode}).getNode, getPodsAssignedToNode(fakeClientset))
+		controller.recorder = testutil.NewFakeRecorder()
+		go controller.Run(stopCh)
+		controller.NodeUpdated(item.oldNode, item.newNode)
+		// wait a bit
+		time.Sleep(timeForControllerToProgress)
+		if item.additionalSleep > 0 {
+			time.Sleep(item.additionalSleep)
+		}
+
+		podDeleted := false
+		for _, action := range fakeClientset.Actions() {
+			if action.GetVerb() == "delete" && action.GetResource().Resource == "pods" {
+				podDeleted = true
+			}
+		}
+		if podDeleted != item.expectDelete {
+			t.Errorf("%v: Unexpected test result. Expected delete %v, got %v", item.description, item.expectDelete, podDeleted)
+		}
+		close(stopCh)
+	}
+}
+
+func TestUpdateNodeWithMultipleTaints(t *testing.T) {
+	taint1 := createNoExecuteTaint(1)
+	taint2 := createNoExecuteTaint(2)
+
+	minute := int64(60)
+	pod := testutil.NewPod("pod1", "node1")
+	pod.Spec.Tolerations = []v1.Toleration{
+		{Key: taint1.Key, Operator: v1.TolerationOpExists, Effect: v1.TaintEffectNoExecute},
+		{Key: taint2.Key, Operator: v1.TolerationOpExists, Effect: v1.TaintEffectNoExecute, TolerationSeconds: &minute},
+	}
+	podNamespacedName := types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name}
+
+	untaintedNode := testutil.NewNode("node1")
+
+	doubleTaintedNode := testutil.NewNode("node1")
+	doubleTaintedNode.Spec.Taints = []v1.Taint{taint1, taint2}
+
+	singleTaintedNode := testutil.NewNode("node1")
+	singleTaintedNode.Spec.Taints = []v1.Taint{taint1}
+
+	stopCh := make(chan struct{})
+	fakeClientset := fake.NewSimpleClientset(pod)
+	holder := &nodeHolder{node: untaintedNode}
+	controller := NewNoExecuteTaintManager(fakeClientset, getPodFromClientset(fakeClientset), (holder).getNode, getPodsAssignedToNode(fakeClientset))
+	controller.recorder = testutil.NewFakeRecorder()
+	go controller.Run(stopCh)
+
+	// no taint
+	holder.setNode(untaintedNode)
+	controller.handleNodeUpdate(nodeUpdateItem{"node1"})
+	// verify pod is not queued for deletion
+	if controller.taintEvictionQueue.GetWorkerUnsafe(podNamespacedName.String()) != nil {
+		t.Fatalf("pod queued for deletion with no taints")
+	}
+
+	// no taint -> infinitely tolerated taint
+	holder.setNode(singleTaintedNode)
+	controller.handleNodeUpdate(nodeUpdateItem{"node1"})
+	// verify pod is not queued for deletion
+	if controller.taintEvictionQueue.GetWorkerUnsafe(podNamespacedName.String()) != nil {
+		t.Fatalf("pod queued for deletion with permanently tolerated taint")
+	}
+
+	// infinitely tolerated taint -> temporarily tolerated taint
+	holder.setNode(doubleTaintedNode)
+	controller.handleNodeUpdate(nodeUpdateItem{"node1"})
+	// verify pod is queued for deletion
+	if controller.taintEvictionQueue.GetWorkerUnsafe(podNamespacedName.String()) == nil {
+		t.Fatalf("pod not queued for deletion after addition of temporarily tolerated taint")
+	}
+
+	// OpenYurt Authors:
+	// In processPodOnNode of taint_manager.go, we will not cancel deletion
+	// if it's already scheduled. So, do not run this case:
+	// temporarily tolerated taint -> infinitely tolerated taint
+	// holder.setNode(singleTaintedNode)
+	// controller.handleNodeUpdate(nodeUpdateItem{"node1"})
+	// // verify pod is not queued for deletion
+	// if controller.taintEvictionQueue.GetWorkerUnsafe(podNamespacedName.String()) != nil {
+	// 	t.Fatalf("pod queued for deletion after removal of temporarily tolerated taint")
+	// }
+
+	// verify pod is not deleted
+	for _, action := range fakeClientset.Actions() {
+		if action.GetVerb() == "delete" && action.GetResource().Resource == "pods" {
+			t.Error("Unexpected deletion")
+		}
+	}
+	close(stopCh)
+}
+
+func TestUpdateNodeWithMultiplePods(t *testing.T) {
+	testCases := []struct {
+		description         string
+		pods                []v1.Pod
+		oldNode             *v1.Node
+		newNode             *v1.Node
+		expectedDeleteTimes durationSlice
+	}{
+		{
+			description: "Pods with different toleration times are evicted appropriately",
+			pods: []v1.Pod{
+				*testutil.NewPod("pod1", "node1"),
+				*addToleration(testutil.NewPod("pod2", "node1"), 1, 1),
+				*addToleration(testutil.NewPod("pod3", "node1"), 1, -1),
+			},
+			oldNode: testutil.NewNode("node1"),
+			newNode: addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1}),
+			expectedDeleteTimes: durationSlice{
+				{[]string{"pod1"}, 0},
+				{[]string{"pod2"}, time.Second},
+			},
+		},
+		{
+			description: "Evict all pods not matching all taints instantly",
+			pods: []v1.Pod{
+				*testutil.NewPod("pod1", "node1"),
+				*addToleration(testutil.NewPod("pod2", "node1"), 1, 1),
+				*addToleration(testutil.NewPod("pod3", "node1"), 1, -1),
+			},
+			oldNode: testutil.NewNode("node1"),
+			newNode: addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1, 2}),
+			expectedDeleteTimes: durationSlice{
+				{[]string{"pod1", "pod2", "pod3"}, 0},
+			},
+		},
+	}
+
+	for _, item := range testCases {
+		t.Logf("Starting testcase %q", item.description)
+
+		stopCh := make(chan struct{})
+		fakeClientset := fake.NewSimpleClientset(&v1.PodList{Items: item.pods})
+		sort.Sort(item.expectedDeleteTimes)
+		controller := NewNoExecuteTaintManager(fakeClientset, getPodFromClientset(fakeClientset), (&nodeHolder{node: item.newNode}).getNode, getPodsAssignedToNode(fakeClientset))
+		controller.recorder = testutil.NewFakeRecorder()
+		go controller.Run(stopCh)
+		controller.NodeUpdated(item.oldNode, item.newNode)
+
+		startedAt := time.Now()
+		for i := range item.expectedDeleteTimes {
+			if i == 0 || item.expectedDeleteTimes[i-1].timestamp != item.expectedDeleteTimes[i].timestamp {
+				// compute a grace duration to give controller time to process updates. Choose big
+				// enough intervals in the test cases above to avoid flakes.
+				var increment time.Duration
+				if i == len(item.expectedDeleteTimes)-1 || item.expectedDeleteTimes[i+1].timestamp == item.expectedDeleteTimes[i].timestamp {
+					increment = 500 * time.Millisecond
+				} else {
+					increment = ((item.expectedDeleteTimes[i+1].timestamp - item.expectedDeleteTimes[i].timestamp) / time.Duration(2))
+				}
+
+				sleepTime := item.expectedDeleteTimes[i].timestamp - time.Since(startedAt) + increment
+				if sleepTime < 0 {
+					sleepTime = 0
+				}
+				t.Logf("Sleeping for %v", sleepTime)
+				time.Sleep(sleepTime)
+			}
+
+			for delay, podName := range item.expectedDeleteTimes[i].names {
+				deleted := false
+				for _, action := range fakeClientset.Actions() {
+					deleteAction, ok := action.(clienttesting.DeleteActionImpl)
+					if !ok {
+						t.Logf("Found not-delete action with verb %v. Ignoring.", action.GetVerb())
+						continue
+					}
+					if deleteAction.GetResource().Resource != "pods" {
+						continue
+					}
+					if podName == deleteAction.GetName() {
+						deleted = true
+					}
+				}
+				if !deleted {
+					t.Errorf("Failed to deleted pod %v after %v", podName, delay)
+				}
+			}
+			for _, action := range fakeClientset.Actions() {
+				deleteAction, ok := action.(clienttesting.DeleteActionImpl)
+				if !ok {
+					t.Logf("Found not-delete action with verb %v. Ignoring.", action.GetVerb())
+					continue
+				}
+				if deleteAction.GetResource().Resource != "pods" {
+					continue
+				}
+				deletedPodName := deleteAction.GetName()
+				expected := false
+				for _, podName := range item.expectedDeleteTimes[i].names {
+					if podName == deletedPodName {
+						expected = true
+					}
+				}
+				if !expected {
+					t.Errorf("Pod %v was deleted even though it shouldn't have", deletedPodName)
+				}
+			}
+			fakeClientset.ClearActions()
+		}
+
+		close(stopCh)
+	}
+}
+
+func TestGetMinTolerationTime(t *testing.T) {
+	one := int64(1)
+	two := int64(2)
+	oneSec := 1 * time.Second
+
+	tests := []struct {
+		tolerations []v1.Toleration
+		expected    time.Duration
+	}{
+		{
+			tolerations: []v1.Toleration{},
+			expected:    0,
+		},
+		{
+			tolerations: []v1.Toleration{
+				{
+					TolerationSeconds: nil,
+				},
+			},
+			expected: -1,
+		},
+		{
+			tolerations: []v1.Toleration{
+				{
+					TolerationSeconds: &one,
+				},
+				{
+					TolerationSeconds: &two,
+				},
+			},
+			expected: oneSec,
+		},
+
+		{
+			tolerations: []v1.Toleration{
+				{
+					TolerationSeconds: &one,
+				},
+				{
+					TolerationSeconds: nil,
+				},
+			},
+			expected: oneSec,
+		},
+		{
+			tolerations: []v1.Toleration{
+				{
+					TolerationSeconds: nil,
+				},
+				{
+					TolerationSeconds: &one,
+				},
+			},
+			expected: oneSec,
+		},
+	}
+
+	for _, test := range tests {
+		got := getMinTolerationTime(test.tolerations)
+		if got != test.expected {
+			t.Errorf("Incorrect min toleration time: got %v, expected %v", got, test.expected)
+		}
+	}
+}
+
+// TestEventualConsistency verifies if getPodsAssignedToNode returns incomplete data
+// (e.g. due to watch latency), it will reconcile the remaining pods eventually.
+// This scenario is partially covered by TestUpdatePods, but given this is an important
+// property of TaintManager, it's better to have explicit test for this.
+func TestEventualConsistency(t *testing.T) {
+	testCases := []struct {
+		description  string
+		pods         []v1.Pod
+		prevPod      *v1.Pod
+		newPod       *v1.Pod
+		oldNode      *v1.Node
+		newNode      *v1.Node
+		expectDelete bool
+	}{
+		{
+			description: "existing pod2 scheduled onto tainted Node",
+			pods: []v1.Pod{
+				*testutil.NewPod("pod1", "node1"),
+			},
+			prevPod:      testutil.NewPod("pod2", ""),
+			newPod:       testutil.NewPod("pod2", "node1"),
+			oldNode:      testutil.NewNode("node1"),
+			newNode:      addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1}),
+			expectDelete: true,
+		},
+		{
+			description: "existing pod2 with taint toleration scheduled onto tainted Node",
+			pods: []v1.Pod{
+				*testutil.NewPod("pod1", "node1"),
+			},
+			prevPod:      addToleration(testutil.NewPod("pod2", ""), 1, 100),
+			newPod:       addToleration(testutil.NewPod("pod2", "node1"), 1, 100),
+			oldNode:      testutil.NewNode("node1"),
+			newNode:      addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1}),
+			expectDelete: false,
+		},
+		{
+			description: "new pod2 created on tainted Node",
+			pods: []v1.Pod{
+				*testutil.NewPod("pod1", "node1"),
+			},
+			prevPod:      nil,
+			newPod:       testutil.NewPod("pod2", "node1"),
+			oldNode:      testutil.NewNode("node1"),
+			newNode:      addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1}),
+			expectDelete: true,
+		},
+		{
+			description: "new pod2 with tait toleration created on tainted Node",
+			pods: []v1.Pod{
+				*testutil.NewPod("pod1", "node1"),
+			},
+			prevPod:      nil,
+			newPod:       addToleration(testutil.NewPod("pod2", "node1"), 1, 100),
+			oldNode:      testutil.NewNode("node1"),
+			newNode:      addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1}),
+			expectDelete: false,
+		},
+	}
+
+	for _, item := range testCases {
+		stopCh := make(chan struct{})
+		fakeClientset := fake.NewSimpleClientset(&v1.PodList{Items: item.pods})
+		holder := &podHolder{}
+		controller := NewNoExecuteTaintManager(fakeClientset, holder.getPod, (&nodeHolder{node: item.newNode}).getNode, getPodsAssignedToNode(fakeClientset))
+		controller.recorder = testutil.NewFakeRecorder()
+		go controller.Run(stopCh)
+
+		if item.prevPod != nil {
+			holder.setPod(item.prevPod)
+			controller.PodUpdated(nil, item.prevPod)
+		}
+
+		// First we simulate NodeUpdate that should delete 'pod1'. It doesn't know about 'pod2' yet.
+		controller.NodeUpdated(item.oldNode, item.newNode)
+		// TODO(mborsz): Remove this sleep and other sleeps in this file.
+		time.Sleep(timeForControllerToProgress)
+
+		podDeleted := false
+		for _, action := range fakeClientset.Actions() {
+			if action.GetVerb() == "delete" && action.GetResource().Resource == "pods" {
+				podDeleted = true
+			}
+		}
+		if !podDeleted {
+			t.Errorf("%v: Unexpected test result. Expected delete, got: %v", item.description, podDeleted)
+		}
+		fakeClientset.ClearActions()
+
+		// And now the delayed update of 'pod2' comes to the TaintManager. We should delete it as well.
+		holder.setPod(item.newPod)
+		controller.PodUpdated(item.prevPod, item.newPod)
+		// wait a bit
+		time.Sleep(timeForControllerToProgress)
+
+		podDeleted = false
+		for _, action := range fakeClientset.Actions() {
+			if action.GetVerb() == "delete" && action.GetResource().Resource == "pods" {
+				podDeleted = true
+			}
+		}
+		if podDeleted != item.expectDelete {
+			t.Errorf("%v: Unexpected test result. Expected delete %v, got %v", item.description, item.expectDelete, podDeleted)
+		}
+		close(stopCh)
+	}
+}