Allow admin user to explicitly unschedule the node

Setting Unschedulable on the node will not touch any existing pods
on the node but will block scheduling of new pods on the node.
This commit is contained in:
Ravi Sankar Penta 2015-02-17 12:03:14 -08:00
parent 07c2035630
commit f6ecec5880
17 changed files with 257 additions and 34 deletions

View File

@ -38,13 +38,16 @@ must have appropriate conditions, see below.
### Node Condition ### Node Condition
Node Condition describes the conditions of `Running` nodes. Current valid Node Condition describes the conditions of `Running` nodes. Current valid
conditions are `NodeReachable` and `NodeReady`. In the future, we plan to conditions are `NodeReachable`, `NodeReady` and `NodeSchedulable`. In the
add more. `NodeReachable` means the node can be reached within the cluster. future, we plan to add more. `NodeReachable` means the node can be reached
`NodeReady` means the kubelet returns StatusOK for HTTP health check. Different within the cluster. `NodeReady` means the kubelet returns StatusOK for HTTP
condition provides different level of understanding for node health. Kubernetes health check. `NodeSchedulable` means node is allowed to schedule any new
will make a comprehensive scheduling decision based on the information. Node pods and is controlled by 'unschedulable' field in node spec.
condition is represented as a json object. For example, the following conditions Different condition provides different level of understanding for node
mean the node is reachable from its cluster, but not ready to accept pods: health. Kubernetes will make a comprehensive scheduling decision based on the
information. Node condition is represented as a json object. For example, the
following conditions mean the node is reachable from its cluster, node is in
sane state but not allowed to accept new pods:
```json ```json
"conditions": [ "conditions": [
{ {
@ -53,8 +56,12 @@ mean the node is reachable from its cluster, but not ready to accept pods:
}, },
{ {
"kind": "Ready", "kind": "Ready",
"status": "Full",
},
{
"kind": "Schedulable",
"status": "None", "status": "None",
} },
] ]
``` ```
@ -125,3 +132,9 @@ A Kubernetes administrator typically uses `kubectl` to manage `Node`. Similar
to Node Controller, `kubectl` command only creates/deletes node representation. to Node Controller, `kubectl` command only creates/deletes node representation.
Note if Kubernetes is running on cloud provider, `kubectl create` a node will Note if Kubernetes is running on cloud provider, `kubectl create` a node will
be refused if Node Controller has already synchronized nodes from cloud provider. be refused if Node Controller has already synchronized nodes from cloud provider.
Admin can choose to make the node unschedulable using `kubectl`. Unscheduling the node
will not affect any existing pods on the node but it will disable creation of
any new pods on the node. Node unschedulable example:
```
kubectl update nodes 10.1.2.3 --patch='{"apiVersion": "v1beta1", "unschedulable": true}'
```

View File

@ -782,13 +782,18 @@ type EndpointsList struct {
// NodeSpec describes the attributes that a node is created with. // NodeSpec describes the attributes that a node is created with.
type NodeSpec struct { type NodeSpec struct {
// Capacity represents the available resources of a node // Capacity represents the available resources of a node.
Capacity ResourceList `json:"capacity,omitempty"` Capacity ResourceList `json:"capacity,omitempty"`
// PodCIDR represents the pod IP range assigned to the node // PodCIDR represents the pod IP range assigned to the node
// Note: assigning IP ranges to nodes might need to be revisited when we support migratable IPs. // Note: assigning IP ranges to nodes might need to be revisited when we support migratable IPs.
PodCIDR string `json:"podCIDR,omitempty"` PodCIDR string `json:"podCIDR,omitempty"`
// External ID of the node assigned by some machine database (e.g. a cloud provider) // External ID of the node assigned by some machine database (e.g. a cloud provider)
ExternalID string `json:"externalID,omitempty"` ExternalID string `json:"externalID,omitempty"`
// Unschedulable controls node schedulability of new pods. By default node is schedulable.
Unschedulable bool `json:"unschedulable,omitempty"`
} }
// NodeSystemInfo is a set of ids/uuids to uniquely identify the node. // NodeSystemInfo is a set of ids/uuids to uniquely identify the node.
@ -842,6 +847,8 @@ const (
NodeReachable NodeConditionType = "Reachable" NodeReachable NodeConditionType = "Reachable"
// NodeReady means the node returns StatusOK for HTTP health check. // NodeReady means the node returns StatusOK for HTTP health check.
NodeReady NodeConditionType = "Ready" NodeReady NodeConditionType = "Ready"
// NodeSchedulable means the node is ready to accept new pods.
NodeSchedulable NodeConditionType = "Schedulable"
) )
type NodeCondition struct { type NodeCondition struct {

View File

@ -711,6 +711,7 @@ func init() {
} }
out.PodCIDR = in.Spec.PodCIDR out.PodCIDR = in.Spec.PodCIDR
out.ExternalID = in.Spec.ExternalID out.ExternalID = in.Spec.ExternalID
out.Unschedulable = in.Spec.Unschedulable
return s.Convert(&in.Spec.Capacity, &out.NodeResources.Capacity, 0) return s.Convert(&in.Spec.Capacity, &out.NodeResources.Capacity, 0)
}, },
func(in *Minion, out *newer.Node, s conversion.Scope) error { func(in *Minion, out *newer.Node, s conversion.Scope) error {
@ -742,6 +743,7 @@ func init() {
} }
out.Spec.PodCIDR = in.PodCIDR out.Spec.PodCIDR = in.PodCIDR
out.Spec.ExternalID = in.ExternalID out.Spec.ExternalID = in.ExternalID
out.Spec.Unschedulable = in.Unschedulable
return s.Convert(&in.NodeResources.Capacity, &out.Spec.Capacity, 0) return s.Convert(&in.NodeResources.Capacity, &out.Spec.Capacity, 0)
}, },

View File

@ -677,6 +677,8 @@ const (
NodeReachable NodeConditionKind = "Reachable" NodeReachable NodeConditionKind = "Reachable"
// NodeReady means the node returns StatusOK for HTTP health check. // NodeReady means the node returns StatusOK for HTTP health check.
NodeReady NodeConditionKind = "Ready" NodeReady NodeConditionKind = "Ready"
// NodeSchedulable means the node is ready to accept new pods.
NodeSchedulable NodeConditionKind = "Schedulable"
) )
type NodeCondition struct { type NodeCondition struct {
@ -731,6 +733,8 @@ type Minion struct {
NodeResources NodeResources `json:"resources,omitempty" description:"characterization of node resources"` NodeResources NodeResources `json:"resources,omitempty" description:"characterization of node resources"`
// Pod IP range assigned to the node // Pod IP range assigned to the node
PodCIDR string `json:"podCIDR,omitempty" description:"IP range assigned to the node"` PodCIDR string `json:"podCIDR,omitempty" description:"IP range assigned to the node"`
// Unschedulable controls node schedulability of new pods. By default node is schedulable.
Unschedulable bool `json:"unschedulable,omitempty" description:"disable pod scheduling on the node"`
// Status describes the current status of a node // Status describes the current status of a node
Status NodeStatus `json:"status,omitempty" description:"current status of node"` Status NodeStatus `json:"status,omitempty" description:"current status of node"`
// Labels for the node // Labels for the node

View File

@ -631,6 +631,7 @@ func init() {
} }
out.PodCIDR = in.Spec.PodCIDR out.PodCIDR = in.Spec.PodCIDR
out.ExternalID = in.Spec.ExternalID out.ExternalID = in.Spec.ExternalID
out.Unschedulable = in.Spec.Unschedulable
return s.Convert(&in.Spec.Capacity, &out.NodeResources.Capacity, 0) return s.Convert(&in.Spec.Capacity, &out.NodeResources.Capacity, 0)
}, },
func(in *Minion, out *newer.Node, s conversion.Scope) error { func(in *Minion, out *newer.Node, s conversion.Scope) error {
@ -662,6 +663,7 @@ func init() {
} }
out.Spec.PodCIDR = in.PodCIDR out.Spec.PodCIDR = in.PodCIDR
out.Spec.ExternalID = in.ExternalID out.Spec.ExternalID = in.ExternalID
out.Spec.Unschedulable = in.Unschedulable
return s.Convert(&in.NodeResources.Capacity, &out.Spec.Capacity, 0) return s.Convert(&in.NodeResources.Capacity, &out.Spec.Capacity, 0)
}, },

View File

@ -690,6 +690,8 @@ const (
NodeReachable NodeConditionKind = "Reachable" NodeReachable NodeConditionKind = "Reachable"
// NodeReady means the node returns StatusOK for HTTP health check. // NodeReady means the node returns StatusOK for HTTP health check.
NodeReady NodeConditionKind = "Ready" NodeReady NodeConditionKind = "Ready"
// NodeSchedulable means the node is ready to accept new pods.
NodeSchedulable NodeConditionKind = "Schedulable"
) )
// Described the conditions of a running node. // Described the conditions of a running node.
@ -750,6 +752,8 @@ type Minion struct {
NodeResources NodeResources `json:"resources,omitempty" description:"characterization of node resources"` NodeResources NodeResources `json:"resources,omitempty" description:"characterization of node resources"`
// Pod IP range assigned to the node // Pod IP range assigned to the node
PodCIDR string `json:"podCIDR,omitempty" description:"IP range assigned to the node"` PodCIDR string `json:"podCIDR,omitempty" description:"IP range assigned to the node"`
// Unschedulable controls node schedulability of new pods. By default node is schedulable.
Unschedulable bool `json:"unschedulable,omitempty" description:"disable pod scheduling on the node"`
// Status describes the current status of a node // Status describes the current status of a node
Status NodeStatus `json:"status,omitempty" description:"current status of node"` Status NodeStatus `json:"status,omitempty" description:"current status of node"`
// Labels for the node // Labels for the node

View File

@ -813,13 +813,15 @@ type EndpointsList struct {
// NodeSpec describes the attributes that a node is created with. // NodeSpec describes the attributes that a node is created with.
type NodeSpec struct { type NodeSpec struct {
// Capacity represents the available resources of a node // Capacity represents the available resources of a node.
// see https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/resources.md for more details. // see https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/resources.md for more details.
Capacity ResourceList `json:"capacity,omitempty" description:"compute resource capacity of the node; https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/resources.md"` Capacity ResourceList `json:"capacity,omitempty" description:"compute resource capacity of the node; https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/resources.md"`
// PodCIDR represents the pod IP range assigned to the node // PodCIDR represents the pod IP range assigned to the node
PodCIDR string `json:"podCIDR,omitempty" description:"pod IP range assigned to the node"` PodCIDR string `json:"podCIDR,omitempty" description:"pod IP range assigned to the node"`
// External ID of the node assigned by some machine database (e.g. a cloud provider) // External ID of the node assigned by some machine database (e.g. a cloud provider)
ExternalID string `json:"externalID,omitempty" description:"external ID assigned to the node by some machine database (e.g. a cloud provider)"` ExternalID string `json:"externalID,omitempty" description:"external ID assigned to the node by some machine database (e.g. a cloud provider)"`
// Unschedulable controls node schedulability of new pods. By default node is schedulable.
Unschedulable bool `json:"unschedulable,omitempty" description:"disable pod scheduling on the node"`
} }
// NodeSystemInfo is a set of ids/uuids to uniquely identify the node. // NodeSystemInfo is a set of ids/uuids to uniquely identify the node.
@ -873,6 +875,8 @@ const (
NodeReachable NodeConditionType = "Reachable" NodeReachable NodeConditionType = "Reachable"
// NodeReady means the node returns StatusOK for HTTP health check. // NodeReady means the node returns StatusOK for HTTP health check.
NodeReady NodeConditionType = "Ready" NodeReady NodeConditionType = "Ready"
// NodeSchedulable means the node is ready to accept new pods.
NodeSchedulable NodeConditionType = "Schedulable"
) )
type NodeCondition struct { type NodeCondition struct {

View File

@ -862,6 +862,8 @@ func ValidateMinionUpdate(oldMinion *api.Node, minion *api.Node) errs.Validation
oldMinion.ObjectMeta = minion.ObjectMeta oldMinion.ObjectMeta = minion.ObjectMeta
// Allow users to update capacity // Allow users to update capacity
oldMinion.Spec.Capacity = minion.Spec.Capacity oldMinion.Spec.Capacity = minion.Spec.Capacity
// Allow users to unschedule node
oldMinion.Spec.Unschedulable = minion.Spec.Unschedulable
// Clear status // Clear status
oldMinion.Status = minion.Status oldMinion.Status = minion.Status

View File

@ -2106,6 +2106,21 @@ func TestValidateMinionUpdate(t *testing.T) {
Labels: map[string]string{"Foo": "baz"}, Labels: map[string]string{"Foo": "baz"},
}, },
}, true}, }, true},
{api.Node{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Spec: api.NodeSpec{
Unschedulable: false,
},
}, api.Node{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Spec: api.NodeSpec{
Unschedulable: true,
},
}, true},
} }
for i, test := range tests { for i, test := range tests {
errs := ValidateMinionUpdate(&test.oldMinion, &test.minion) errs := ValidateMinionUpdate(&test.oldMinion, &test.minion)

View File

@ -749,6 +749,7 @@ func TestCreateMinion(t *testing.T) {
api.ResourceCPU: resource.MustParse("1000m"), api.ResourceCPU: resource.MustParse("1000m"),
api.ResourceMemory: resource.MustParse("1Mi"), api.ResourceMemory: resource.MustParse("1Mi"),
}, },
Unschedulable: false,
}, },
} }
c := &testClient{ c := &testClient{
@ -782,6 +783,7 @@ func TestUpdateMinion(t *testing.T) {
api.ResourceCPU: resource.MustParse("1000m"), api.ResourceCPU: resource.MustParse("1000m"),
api.ResourceMemory: resource.MustParse("1Mi"), api.ResourceMemory: resource.MustParse("1Mi"),
}, },
Unschedulable: true,
}, },
} }
c := &testClient{ c := &testClient{

View File

@ -301,7 +301,7 @@ func (s *NodeController) PopulateAddresses(nodes *api.NodeList) (*api.NodeList,
return nodes, nil return nodes, nil
} }
// UpdateNodesStatus performs health checking for given list of nodes. // UpdateNodesStatus performs various condition checks for given list of nodes.
func (s *NodeController) UpdateNodesStatus(nodes *api.NodeList) *api.NodeList { func (s *NodeController) UpdateNodesStatus(nodes *api.NodeList) *api.NodeList {
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(len(nodes.Items)) wg.Add(len(nodes.Items))
@ -330,21 +330,14 @@ func (s *NodeController) updateNodeInfo(node *api.Node) error {
return nil return nil
} }
// DoCheck performs health checking for given node. // DoCheck performs various condition checks for given node.
func (s *NodeController) DoCheck(node *api.Node) []api.NodeCondition { func (s *NodeController) DoCheck(node *api.Node) []api.NodeCondition {
var conditions []api.NodeCondition var conditions []api.NodeCondition
// Check Condition: NodeReady. TODO: More node conditions. // Check Condition: NodeReady. TODO: More node conditions.
oldReadyCondition := s.getCondition(node, api.NodeReady) oldReadyCondition := s.getCondition(node, api.NodeReady)
newReadyCondition := s.checkNodeReady(node) newReadyCondition := s.checkNodeReady(node)
if oldReadyCondition != nil && oldReadyCondition.Status == newReadyCondition.Status { s.updateLastTransitionTime(oldReadyCondition, newReadyCondition)
// If node status doesn't change, transition time is same as last time.
newReadyCondition.LastTransitionTime = oldReadyCondition.LastTransitionTime
} else {
// Set transition time to Now() if node status changes or `oldReadyCondition` is nil, which
// happens only when the node is checked for the first time.
newReadyCondition.LastTransitionTime = util.Now()
}
if newReadyCondition.Status != api.ConditionFull { if newReadyCondition.Status != api.ConditionFull {
// Node is not ready for this probe, we need to check if pods need to be deleted. // Node is not ready for this probe, we need to check if pods need to be deleted.
@ -355,12 +348,48 @@ func (s *NodeController) DoCheck(node *api.Node) []api.NodeCondition {
s.deletePods(node.Name) s.deletePods(node.Name)
} }
} }
conditions = append(conditions, *newReadyCondition) conditions = append(conditions, *newReadyCondition)
// Check Condition: NodeSchedulable
oldSchedulableCondition := s.getCondition(node, api.NodeSchedulable)
newSchedulableCondition := s.checkNodeSchedulable(node)
s.updateLastTransitionTime(oldSchedulableCondition, newSchedulableCondition)
conditions = append(conditions, *newSchedulableCondition)
return conditions return conditions
} }
// updateLastTransitionTime updates LastTransitionTime for the newCondition based on oldCondition.
func (s *NodeController) updateLastTransitionTime(oldCondition, newCondition *api.NodeCondition) {
if oldCondition != nil && oldCondition.Status == newCondition.Status {
// If node status doesn't change, transition time is same as last time.
newCondition.LastTransitionTime = oldCondition.LastTransitionTime
} else {
// Set transition time to Now() if node status changes or `oldCondition` is nil, which
// happens only when the node is checked for the first time.
newCondition.LastTransitionTime = util.Now()
}
}
// checkNodeSchedulable checks node schedulable condition, without transition timestamp set.
func (s *NodeController) checkNodeSchedulable(node *api.Node) *api.NodeCondition {
if node.Spec.Unschedulable {
return &api.NodeCondition{
Type: api.NodeSchedulable,
Status: api.ConditionNone,
Reason: "User marked unschedulable during node create/update",
LastProbeTime: util.Now(),
}
} else {
return &api.NodeCondition{
Type: api.NodeSchedulable,
Status: api.ConditionFull,
Reason: "Node is schedulable by default",
LastProbeTime: util.Now(),
}
}
}
// checkNodeReady checks raw node ready condition, without transition timestamp set. // checkNodeReady checks raw node ready condition, without transition timestamp set.
func (s *NodeController) checkNodeReady(node *api.Node) *api.NodeCondition { func (s *NodeController) checkNodeReady(node *api.Node) *api.NodeCondition {
switch status, err := s.kubeletClient.HealthCheck(node.Name); { switch status, err := s.kubeletClient.HealthCheck(node.Name); {

View File

@ -550,13 +550,15 @@ func TestSyncCloudDeletePods(t *testing.T) {
} }
} }
func TestHealthCheckNode(t *testing.T) { func TestNodeConditionsCheck(t *testing.T) {
table := []struct { table := []struct {
node *api.Node node *api.Node
fakeKubeletClient *FakeKubeletClient fakeKubeletClient *FakeKubeletClient
expectedConditions []api.NodeCondition expectedConditions []api.NodeCondition
}{ }{
{ {
// Node with default spec and kubelet /healthz probe returns success.
// Expected node condition to be ready and marked schedulable.
node: newNode("node0"), node: newNode("node0"),
fakeKubeletClient: &FakeKubeletClient{ fakeKubeletClient: &FakeKubeletClient{
Status: probe.Success, Status: probe.Success,
@ -568,10 +570,17 @@ func TestHealthCheckNode(t *testing.T) {
Status: api.ConditionFull, Status: api.ConditionFull,
Reason: "Node health check succeeded: kubelet /healthz endpoint returns ok", Reason: "Node health check succeeded: kubelet /healthz endpoint returns ok",
}, },
{
Type: api.NodeSchedulable,
Status: api.ConditionFull,
Reason: "Node is schedulable by default",
},
}, },
}, },
{ {
node: newNode("node0"), // User specified node as schedulable and kubelet /healthz probe returns failure with no error.
// Expected node condition to be not ready and marked schedulable.
node: &api.Node{ObjectMeta: api.ObjectMeta{Name: "node0"}, Spec: api.NodeSpec{Unschedulable: false}},
fakeKubeletClient: &FakeKubeletClient{ fakeKubeletClient: &FakeKubeletClient{
Status: probe.Failure, Status: probe.Failure,
Err: nil, Err: nil,
@ -582,10 +591,17 @@ func TestHealthCheckNode(t *testing.T) {
Status: api.ConditionNone, Status: api.ConditionNone,
Reason: "Node health check failed: kubelet /healthz endpoint returns not ok", Reason: "Node health check failed: kubelet /healthz endpoint returns not ok",
}, },
{
Type: api.NodeSchedulable,
Status: api.ConditionFull,
Reason: "Node is schedulable by default",
},
}, },
}, },
{ {
node: newNode("node0"), // User specified node as unschedulable and kubelet /healthz probe returns failure with some error.
// Expected node condition to be not ready and marked unschedulable.
node: &api.Node{ObjectMeta: api.ObjectMeta{Name: "node0"}, Spec: api.NodeSpec{Unschedulable: true}},
fakeKubeletClient: &FakeKubeletClient{ fakeKubeletClient: &FakeKubeletClient{
Status: probe.Failure, Status: probe.Failure,
Err: errors.New("Error"), Err: errors.New("Error"),
@ -596,6 +612,11 @@ func TestHealthCheckNode(t *testing.T) {
Status: api.ConditionUnknown, Status: api.ConditionUnknown,
Reason: "Node health check error: Error", Reason: "Node health check error: Error",
}, },
{
Type: api.NodeSchedulable,
Status: api.ConditionNone,
Reason: "User marked unschedulable during node create/update",
},
}, },
}, },
} }
@ -663,11 +684,13 @@ func TestSyncNodeStatusTransitionTime(t *testing.T) {
expectedTransitionTimeChange bool expectedTransitionTimeChange bool
}{ }{
{ {
// Existing node is healthy, current porbe is healthy too. // Existing node is healthy, current probe is healthy too.
// Existing node is schedulable, again explicitly mark node as schedulable.
fakeNodeHandler: &FakeNodeHandler{ fakeNodeHandler: &FakeNodeHandler{
Existing: []*api.Node{ Existing: []*api.Node{
{ {
ObjectMeta: api.ObjectMeta{Name: "node0"}, ObjectMeta: api.ObjectMeta{Name: "node0"},
Spec: api.NodeSpec{Unschedulable: false},
Status: api.NodeStatus{ Status: api.NodeStatus{
Conditions: []api.NodeCondition{ Conditions: []api.NodeCondition{
{ {
@ -676,6 +699,12 @@ func TestSyncNodeStatusTransitionTime(t *testing.T) {
Reason: "Node health check succeeded: kubelet /healthz endpoint returns ok", Reason: "Node health check succeeded: kubelet /healthz endpoint returns ok",
LastTransitionTime: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), LastTransitionTime: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
}, },
{
Type: api.NodeSchedulable,
Status: api.ConditionFull,
Reason: "Node is schedulable by default",
LastTransitionTime: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
},
}, },
}, },
}, },
@ -689,11 +718,13 @@ func TestSyncNodeStatusTransitionTime(t *testing.T) {
expectedTransitionTimeChange: false, expectedTransitionTimeChange: false,
}, },
{ {
// Existing node is healthy, current porbe is unhealthy. // Existing node is healthy, current probe is unhealthy.
// Existing node is schedulable, mark node as unschedulable.
fakeNodeHandler: &FakeNodeHandler{ fakeNodeHandler: &FakeNodeHandler{
Existing: []*api.Node{ Existing: []*api.Node{
{ {
ObjectMeta: api.ObjectMeta{Name: "node0"}, ObjectMeta: api.ObjectMeta{Name: "node0"},
Spec: api.NodeSpec{Unschedulable: true},
Status: api.NodeStatus{ Status: api.NodeStatus{
Conditions: []api.NodeCondition{ Conditions: []api.NodeCondition{
{ {
@ -702,6 +733,12 @@ func TestSyncNodeStatusTransitionTime(t *testing.T) {
Reason: "Node health check succeeded: kubelet /healthz endpoint returns ok", Reason: "Node health check succeeded: kubelet /healthz endpoint returns ok",
LastTransitionTime: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), LastTransitionTime: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
}, },
{
Type: api.NodeSchedulable,
Status: api.ConditionFull,
Reason: "Node is schedulable by default",
LastTransitionTime: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
},
}, },
}, },
}, },
@ -865,7 +902,7 @@ func TestSyncNodeStatusDeletePods(t *testing.T) {
expectedActions []client.FakeAction expectedActions []client.FakeAction
}{ }{
{ {
// Existing node is healthy, current porbe is healthy too. // Existing node is healthy, current probe is healthy too.
fakeNodeHandler: &FakeNodeHandler{ fakeNodeHandler: &FakeNodeHandler{
Existing: []*api.Node{ Existing: []*api.Node{
{ {
@ -894,7 +931,7 @@ func TestSyncNodeStatusDeletePods(t *testing.T) {
expectedActions: nil, expectedActions: nil,
}, },
{ {
// Existing node is healthy, current porbe is unhealthy, i.e. node just becomes unhealthy. // Existing node is healthy, current probe is unhealthy, i.e. node just becomes unhealthy.
// Do not delete pods. // Do not delete pods.
fakeNodeHandler: &FakeNodeHandler{ fakeNodeHandler: &FakeNodeHandler{
Existing: []*api.Node{ Existing: []*api.Node{
@ -924,7 +961,7 @@ func TestSyncNodeStatusDeletePods(t *testing.T) {
expectedActions: nil, expectedActions: nil,
}, },
{ {
// Existing node unhealthy, current porbe is unhealthy. Node is still within grace peroid. // Existing node unhealthy, current probe is unhealthy. Node is still within grace peroid.
fakeNodeHandler: &FakeNodeHandler{ fakeNodeHandler: &FakeNodeHandler{
Existing: []*api.Node{ Existing: []*api.Node{
{ {
@ -956,7 +993,7 @@ func TestSyncNodeStatusDeletePods(t *testing.T) {
expectedActions: nil, expectedActions: nil,
}, },
{ {
// Existing node unhealthy, current porbe is unhealthy. Node exceeds grace peroid. // Existing node unhealthy, current probe is unhealthy. Node exceeds grace peroid.
fakeNodeHandler: &FakeNodeHandler{ fakeNodeHandler: &FakeNodeHandler{
Existing: []*api.Node{ Existing: []*api.Node{
{ {
@ -1036,6 +1073,11 @@ func TestSyncNodeStatus(t *testing.T) {
Status: api.ConditionFull, Status: api.ConditionFull,
Reason: "Node health check succeeded: kubelet /healthz endpoint returns ok", Reason: "Node health check succeeded: kubelet /healthz endpoint returns ok",
}, },
{
Type: api.NodeSchedulable,
Status: api.ConditionFull,
Reason: "Node is schedulable by default",
},
}, },
Addresses: []api.NodeAddress{ Addresses: []api.NodeAddress{
{Type: api.NodeLegacyHostIP, Address: "1.2.3.4"}, {Type: api.NodeLegacyHostIP, Address: "1.2.3.4"},
@ -1051,6 +1093,11 @@ func TestSyncNodeStatus(t *testing.T) {
Status: api.ConditionFull, Status: api.ConditionFull,
Reason: "Node health check succeeded: kubelet /healthz endpoint returns ok", Reason: "Node health check succeeded: kubelet /healthz endpoint returns ok",
}, },
{
Type: api.NodeSchedulable,
Status: api.ConditionFull,
Reason: "Node is schedulable by default",
},
}, },
Addresses: []api.NodeAddress{ Addresses: []api.NodeAddress{
{Type: api.NodeLegacyHostIP, Address: "1.2.3.4"}, {Type: api.NodeLegacyHostIP, Address: "1.2.3.4"},

View File

@ -432,7 +432,7 @@ func printSecretList(list *api.SecretList, w io.Writer) error {
func printNode(node *api.Node, w io.Writer) error { func printNode(node *api.Node, w io.Writer) error {
conditionMap := make(map[api.NodeConditionType]*api.NodeCondition) conditionMap := make(map[api.NodeConditionType]*api.NodeCondition)
NodeAllConditions := []api.NodeConditionType{api.NodeReady, api.NodeReachable} NodeAllConditions := []api.NodeConditionType{api.NodeSchedulable, api.NodeReady, api.NodeReachable}
for i := range node.Status.Conditions { for i := range node.Status.Conditions {
cond := node.Status.Conditions[i] cond := node.Status.Conditions[i]
conditionMap[cond.Type] = &cond conditionMap[cond.Type] = &cond

View File

@ -567,6 +567,26 @@ func TestPrintMinionStatus(t *testing.T) {
}, },
status: "Unknown", status: "Unknown",
}, },
{
minion: api.Node{
ObjectMeta: api.ObjectMeta{Name: "foo7"},
Status: api.NodeStatus{Conditions: []api.NodeCondition{
{Type: api.NodeSchedulable, Status: api.ConditionFull},
{Type: api.NodeReady, Status: api.ConditionFull},
{Type: api.NodeReachable, Status: api.ConditionFull}}},
},
status: "Schedulable,Ready,Reachable",
},
{
minion: api.Node{
ObjectMeta: api.ObjectMeta{Name: "foo8"},
Status: api.NodeStatus{Conditions: []api.NodeCondition{
{Type: api.NodeSchedulable, Status: api.ConditionNone},
{Type: api.NodeReady, Status: api.ConditionNone},
{Type: api.NodeReachable, Status: api.ConditionFull}}},
},
status: "NotSchedulable,NotReady,Reachable",
},
} }
for _, test := range table { for _, test := range table {

View File

@ -46,7 +46,6 @@ func NewREST(m Registry) *REST {
} }
var ErrDoesNotExist = errors.New("The requested resource does not exist.") var ErrDoesNotExist = errors.New("The requested resource does not exist.")
var ErrNotHealty = errors.New("The requested minion is not healthy.")
// Create satisfies the RESTStorage interface. // Create satisfies the RESTStorage interface.
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) { func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {

View File

@ -207,6 +207,11 @@ func (factory *ConfigFactory) pollMinions() (cache.Enumerator, error) {
cond := node.Status.Conditions[i] cond := node.Status.Conditions[i]
conditionMap[cond.Type] = &cond conditionMap[cond.Type] = &cond
} }
if condition, ok := conditionMap[api.NodeSchedulable]; ok {
if condition.Status != api.ConditionFull {
continue
}
}
if condition, ok := conditionMap[api.NodeReady]; ok { if condition, ok := conditionMap[api.NodeReady]; ok {
if condition.Status == api.ConditionFull { if condition.Status == api.ConditionFull {
nodes.Items = append(nodes.Items, node) nodes.Items = append(nodes.Items, node)

View File

@ -156,7 +156,15 @@ func TestPollMinions(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: api.ObjectMeta{Name: "baz"}, ObjectMeta: api.ObjectMeta{Name: "fiz"},
Status: api.NodeStatus{
Conditions: []api.NodeCondition{
{Type: api.NodeSchedulable, Status: api.ConditionFull},
},
},
},
{
ObjectMeta: api.ObjectMeta{Name: "biz"},
Status: api.NodeStatus{ Status: api.NodeStatus{
Conditions: []api.NodeCondition{ Conditions: []api.NodeCondition{
{Type: api.NodeReady, Status: api.ConditionFull}, {Type: api.NodeReady, Status: api.ConditionFull},
@ -173,8 +181,47 @@ func TestPollMinions(t *testing.T) {
}, },
}, },
}, },
{
ObjectMeta: api.ObjectMeta{Name: "fuz"},
Status: api.NodeStatus{
Conditions: []api.NodeCondition{
{Type: api.NodeSchedulable, Status: api.ConditionFull},
{Type: api.NodeReady, Status: api.ConditionFull},
{Type: api.NodeReachable, Status: api.ConditionFull},
}, },
expectedCount: 4, },
},
{
ObjectMeta: api.ObjectMeta{Name: "buz"},
Status: api.NodeStatus{
Conditions: []api.NodeCondition{
{Type: api.NodeSchedulable, Status: api.ConditionNone},
{Type: api.NodeReady, Status: api.ConditionFull},
{Type: api.NodeReachable, Status: api.ConditionFull},
},
},
},
{
ObjectMeta: api.ObjectMeta{Name: "foobar"},
Status: api.NodeStatus{
Conditions: []api.NodeCondition{
{Type: api.NodeSchedulable, Status: api.ConditionFull},
{Type: api.NodeReady, Status: api.ConditionNone},
{Type: api.NodeReachable, Status: api.ConditionFull},
},
},
},
{
ObjectMeta: api.ObjectMeta{Name: "fizbiz"},
Status: api.NodeStatus{
Conditions: []api.NodeCondition{
{Type: api.NodeSchedulable, Status: api.ConditionFull},
{Type: api.NodeReachable, Status: api.ConditionNone},
},
},
},
},
expectedCount: 6,
}, },
{ {
minions: []api.Node{ minions: []api.Node{
@ -197,6 +244,27 @@ func TestPollMinions(t *testing.T) {
}, },
expectedCount: 1, expectedCount: 1,
}, },
{
minions: []api.Node{
{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Status: api.NodeStatus{
Conditions: []api.NodeCondition{
{Type: api.NodeSchedulable, Status: api.ConditionFull},
},
},
},
{
ObjectMeta: api.ObjectMeta{Name: "bar"},
Status: api.NodeStatus{
Conditions: []api.NodeCondition{
{Type: api.NodeSchedulable, Status: api.ConditionNone},
},
},
},
},
expectedCount: 1,
},
{ {
minions: []api.Node{ minions: []api.Node{
{ {