/
waitforready.go
129 lines (113 loc) · 3.5 KB
/
waitforready.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/*
Copyright 2019 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.
*/
// Package waitforready implements the wait for ready action
package waitforready
import (
"fmt"
"strings"
"time"
"sigs.k8s.io/kind/pkg/cluster/internal/create/actions"
"sigs.k8s.io/kind/pkg/cluster/nodes"
"sigs.k8s.io/kind/pkg/cluster/nodeutils"
"sigs.k8s.io/kind/pkg/exec"
)
// Action implements an action for waiting for the cluster to be ready
type Action struct {
waitTime time.Duration
}
// NewAction returns a new action for waiting for the cluster to be ready
func NewAction(waitTime time.Duration) actions.Action {
return &Action{
waitTime: waitTime,
}
}
// Execute runs the action
func (a *Action) Execute(ctx *actions.ActionContext) error {
// skip entirely if the wait time is 0
if a.waitTime == time.Duration(0) {
return nil
}
ctx.Status.Start(
fmt.Sprintf(
"Waiting ≤ %s for control-plane = Ready ⏳",
formatDuration(a.waitTime),
),
)
allNodes, err := ctx.Nodes()
if err != nil {
return err
}
// get a control plane node to use to check cluster status
controlPlanes, err := nodeutils.ControlPlaneNodes(allNodes)
if err != nil {
return err
}
node := controlPlanes[0] // kind expects at least one always
// Wait for the nodes to reach Ready status.
startTime := time.Now()
isReady := waitForReady(node, startTime.Add(a.waitTime))
if !isReady {
ctx.Status.End(false)
ctx.Logger.V(0).Info(" • WARNING: Timed out waiting for Ready ⚠️")
return nil
}
// mark success
ctx.Status.End(true)
ctx.Logger.V(0).Infof(" • Ready after %s 💚", formatDuration(time.Since(startTime)))
return nil
}
// WaitForReady uses kubectl inside the "node" container to check if the
// control plane nodes are "Ready".
func waitForReady(node nodes.Node, until time.Time) bool {
return tryUntil(until, func() bool {
cmd := node.Command(
"kubectl",
"--kubeconfig=/etc/kubernetes/admin.conf",
"get",
"nodes",
"--selector=node-role.kubernetes.io/master",
// When the node reaches status ready, the status field will be set
// to true.
"-o=jsonpath='{.items..status.conditions[-1:].status}'",
)
lines, err := exec.OutputLines(cmd)
if err != nil {
return false
}
// 'lines' will return the status of all nodes labeled as master. For
// example, if we have three control plane nodes, and all are ready,
// then the status will have the following format: `True True True'.
status := strings.Fields(lines[0])
for _, s := range status {
// Check node status. If node is ready then this will be 'True',
// 'False' or 'Unknown' otherwise.
if !strings.Contains(s, "True") {
return false
}
}
return true
})
}
// helper that calls `try()`` in a loop until the deadline `until`
// has passed or `try()`returns true, returns whether try ever returned true
func tryUntil(until time.Time, try func() bool) bool {
for until.After(time.Now()) {
if try() {
return true
}
}
return false
}
func formatDuration(duration time.Duration) string {
return duration.Round(time.Second).String()
}