Skip to content

Commit e1a3066

Browse files
committed
nodes: add wait until node selected
This PR adds WaitUntilNodeSelected which allows for waiting until a node can be selected by provided ListOptions. Additionally, unit tests for this function and the List function are included.
1 parent ac6fdf7 commit e1a3066

File tree

3 files changed

+186
-0
lines changed

3 files changed

+186
-0
lines changed

pkg/nodes/list.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ const (
1818

1919
// List returns node inventory.
2020
func List(apiClient *clients.Settings, options ...metav1.ListOptions) ([]*Builder, error) {
21+
if apiClient == nil {
22+
glog.V(100).Infof("Nodes 'apiClient' parameter can not be empty")
23+
24+
return nil, fmt.Errorf("failed to list node objects, 'apiClient' parameter is empty")
25+
}
26+
2127
passedOptions := metav1.ListOptions{}
2228
logMessage := "Listing all node resources"
2329

@@ -186,3 +192,29 @@ func WaitForAllNodesToReboot(apiClient *clients.Settings,
186192

187193
return false, err
188194
}
195+
196+
// WaitUntilNodeSelected waits up to timeout until a node is selected by the provided options.
197+
func WaitUntilNodeSelected(apiClient *clients.Settings, options metav1.ListOptions, timeout time.Duration) error {
198+
if apiClient == nil {
199+
glog.V(100).Infof("Nodes 'apiClient' parameter can not be empty")
200+
201+
return fmt.Errorf("nodes apiClient parameter is empty")
202+
}
203+
204+
glog.V(100).Info("Waiting up to %v for any nodes to be selected by options %v", timeout, options)
205+
206+
return wait.PollUntilContextTimeout(context.TODO(), backoff, timeout, true, func(ctx context.Context) (bool, error) {
207+
nodes, err := List(apiClient, options)
208+
if err != nil {
209+
return false, nil
210+
}
211+
212+
if len(nodes) == 0 {
213+
return false, nil
214+
}
215+
216+
glog.V(100).Infof("Found %d nodes matching provided options", len(nodes))
217+
218+
return true, nil
219+
})
220+
}

pkg/nodes/list_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package nodes
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"testing"
7+
"time"
8+
9+
"github.com/openshift-kni/eco-goinfra/pkg/clients"
10+
"github.com/stretchr/testify/assert"
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
"k8s.io/apimachinery/pkg/runtime"
13+
)
14+
15+
func TestList(t *testing.T) {
16+
testCases := []struct {
17+
nodes []*Builder
18+
listOptions []metav1.ListOptions
19+
client bool
20+
expectedError error
21+
}{
22+
{
23+
nodes: []*Builder{buildValidNodeTestBuilder(buildTestClientWithDummyNode())},
24+
listOptions: nil,
25+
client: true,
26+
expectedError: nil,
27+
},
28+
{
29+
nodes: []*Builder{buildValidNodeTestBuilder(buildTestClientWithDummyNode())},
30+
listOptions: []metav1.ListOptions{{LabelSelector: "test"}},
31+
client: true,
32+
expectedError: nil,
33+
},
34+
{
35+
nodes: []*Builder{buildValidNodeTestBuilder(buildTestClientWithDummyNode())},
36+
listOptions: []metav1.ListOptions{{LabelSelector: "test"}, {LabelSelector: "test"}},
37+
client: true,
38+
expectedError: fmt.Errorf("error: more than one ListOptions was passed"),
39+
},
40+
{
41+
nodes: []*Builder{buildValidNodeTestBuilder(buildTestClientWithDummyNode())},
42+
listOptions: nil,
43+
client: false,
44+
expectedError: fmt.Errorf("failed to list node objects, 'apiClient' parameter is empty"),
45+
},
46+
}
47+
48+
for _, testCase := range testCases {
49+
var testSettings *clients.Settings
50+
51+
if testCase.client {
52+
testSettings = buildTestClientWithDummyNode()
53+
}
54+
55+
nodeBuilders, err := List(testSettings, testCase.listOptions...)
56+
assert.Equal(t, err, testCase.expectedError)
57+
58+
if testCase.expectedError == nil && len(testCase.listOptions) == 0 {
59+
assert.Equal(t, len(nodeBuilders), len(testCase.nodes))
60+
}
61+
}
62+
}
63+
64+
func TestWaitUntilNodeSelected(t *testing.T) {
65+
testCases := []struct {
66+
selected bool
67+
client bool
68+
expectedError error
69+
}{
70+
{
71+
selected: true,
72+
client: true,
73+
expectedError: nil,
74+
},
75+
{
76+
selected: false,
77+
client: true,
78+
expectedError: context.DeadlineExceeded,
79+
},
80+
{
81+
selected: true,
82+
client: false,
83+
expectedError: fmt.Errorf("nodes apiClient parameter is empty"),
84+
},
85+
}
86+
87+
for _, testCase := range testCases {
88+
var testSettings *clients.Settings
89+
90+
if testCase.client {
91+
node := buildDummyNode(defaultNodeName)
92+
93+
if testCase.selected {
94+
node.Labels = map[string]string{defaultNodeLabel: ""}
95+
}
96+
97+
testSettings = clients.GetTestClients(clients.TestClientParams{
98+
K8sMockObjects: []runtime.Object{node},
99+
})
100+
}
101+
102+
err := WaitUntilNodeSelected(testSettings, metav1.ListOptions{
103+
LabelSelector: fmt.Sprintf("%s=", defaultNodeLabel),
104+
}, time.Second)
105+
106+
assert.Equal(t, testCase.expectedError, err)
107+
}
108+
}

pkg/nodes/nodes_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,47 @@
11
package nodes
2+
3+
import (
4+
"github.com/openshift-kni/eco-goinfra/pkg/clients"
5+
corev1 "k8s.io/api/core/v1"
6+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
7+
"k8s.io/apimachinery/pkg/runtime"
8+
)
9+
10+
const (
11+
defaultNodeName = "test-node"
12+
defaultNodeLabel = "node-role.kubernetes.io/control-plane"
13+
)
14+
15+
// buildDummyNode returns a Node with the provided name.
16+
func buildDummyNode(name string) *corev1.Node {
17+
return &corev1.Node{
18+
ObjectMeta: metav1.ObjectMeta{
19+
Name: name,
20+
},
21+
}
22+
}
23+
24+
// buildTestClientWithDummyNode returns a client with a dummy node.
25+
func buildTestClientWithDummyNode() *clients.Settings {
26+
return clients.GetTestClients(clients.TestClientParams{
27+
K8sMockObjects: []runtime.Object{buildDummyNode(defaultNodeName)},
28+
})
29+
}
30+
31+
func buildValidNodeTestBuilder(apiClient *clients.Settings) *Builder {
32+
return newNodeBuilder(apiClient, defaultNodeName)
33+
}
34+
35+
// newNodeBuilder creates a new Builder instances for testing purposes.
36+
func newNodeBuilder(apiClient *clients.Settings, name string) *Builder {
37+
if apiClient == nil {
38+
return nil
39+
}
40+
41+
builder := Builder{
42+
apiClient: apiClient.K8sClient,
43+
Definition: buildDummyNode(name),
44+
}
45+
46+
return &builder
47+
}

0 commit comments

Comments
 (0)