Skip to content

Commit c55e199

Browse files
support waiting for components (#171)
* support waiting for components * add simple wait tf test * implement wait validation and parse special options * add wait acceptance test
1 parent 3bb2d55 commit c55e199

File tree

6 files changed

+316
-15
lines changed

6 files changed

+316
-15
lines changed

minikube/lib/wait_validator.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package lib
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
var standardOptions = []string{
9+
"apiserver",
10+
"system_pods",
11+
"default_sa",
12+
"apps_running",
13+
"node_ready",
14+
"kubelet",
15+
}
16+
17+
var specialOptions = []string{
18+
"all",
19+
"none",
20+
"true",
21+
"false",
22+
}
23+
24+
func ValidateWait(v map[string]bool) error {
25+
var invalidOptions []string
26+
27+
for key := range v {
28+
if !contains(standardOptions, key) || contains(specialOptions, key) {
29+
invalidOptions = append(invalidOptions, key)
30+
}
31+
}
32+
33+
if len(invalidOptions) > 0 {
34+
return fmt.Errorf("invalid wait option(s): %s", strings.Join(invalidOptions, ", "))
35+
}
36+
37+
return nil
38+
}
39+
40+
func contains(slice []string, item string) bool {
41+
for _, s := range slice {
42+
if s == item {
43+
return true
44+
}
45+
}
46+
return false
47+
}
48+
49+
func ResolveSpecialWaitOptions(input map[string]bool) map[string]bool {
50+
if input["all"] || input["true"] {
51+
result := make(map[string]bool)
52+
for _, opt := range standardOptions {
53+
result[opt] = true
54+
}
55+
return result
56+
}
57+
58+
if input["none"] || input["false"] {
59+
return make(map[string]bool)
60+
}
61+
62+
return input
63+
}

minikube/lib/wait_validator_test.go

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package lib
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
)
7+
8+
func TestValidateWait(t *testing.T) {
9+
tests := []struct {
10+
name string
11+
input map[string]bool
12+
expectedError string
13+
}{
14+
{
15+
name: "Valid options",
16+
input: map[string]bool{"apiserver": true, "system_pods": true},
17+
expectedError: "",
18+
},
19+
{
20+
name: "Invalid option",
21+
input: map[string]bool{"invalid_option": true},
22+
expectedError: "invalid wait option(s): invalid_option",
23+
},
24+
{
25+
name: "Multiple invalid options",
26+
input: map[string]bool{"invalid1": true, "invalid2": true, "apiserver": true},
27+
expectedError: "invalid wait option(s): invalid1, invalid2",
28+
},
29+
{
30+
name: "Special option",
31+
input: map[string]bool{"all": true},
32+
expectedError: "invalid wait option(s): all",
33+
},
34+
{
35+
name: "Empty input",
36+
input: map[string]bool{},
37+
expectedError: "",
38+
},
39+
}
40+
41+
for _, tt := range tests {
42+
t.Run(tt.name, func(t *testing.T) {
43+
err := ValidateWait(tt.input)
44+
if (err == nil && tt.expectedError != "") || (err != nil && err.Error() != tt.expectedError) {
45+
t.Errorf("ValidateWait() error = %v, expectedError %v", err, tt.expectedError)
46+
}
47+
})
48+
}
49+
}
50+
51+
func TestResolveSpecialWaitOptions(t *testing.T) {
52+
tests := []struct {
53+
name string
54+
input map[string]bool
55+
expected map[string]bool
56+
}{
57+
{
58+
name: "All true",
59+
input: map[string]bool{"all": true},
60+
expected: map[string]bool{"apiserver": true, "system_pods": true, "default_sa": true, "apps_running": true, "node_ready": true, "kubelet": true},
61+
},
62+
{
63+
name: "True",
64+
input: map[string]bool{"true": true},
65+
expected: map[string]bool{"apiserver": true, "system_pods": true, "default_sa": true, "apps_running": true, "node_ready": true, "kubelet": true},
66+
},
67+
{
68+
name: "None",
69+
input: map[string]bool{"none": true},
70+
expected: map[string]bool{},
71+
},
72+
{
73+
name: "False",
74+
input: map[string]bool{"false": true},
75+
expected: map[string]bool{},
76+
},
77+
{
78+
name: "Standard options",
79+
input: map[string]bool{"apiserver": true, "system_pods": true},
80+
expected: map[string]bool{"apiserver": true, "system_pods": true},
81+
},
82+
{
83+
name: "Empty input",
84+
input: map[string]bool{},
85+
expected: map[string]bool{},
86+
},
87+
}
88+
89+
for _, tt := range tests {
90+
t.Run(tt.name, func(t *testing.T) {
91+
result := ResolveSpecialWaitOptions(tt.input)
92+
if !reflect.DeepEqual(result, tt.expected) {
93+
t.Errorf("ResolveSpecialWaitOptions() = %v, want %v", result, tt.expected)
94+
}
95+
})
96+
}
97+
}
98+
99+
func TestContains(t *testing.T) {
100+
tests := []struct {
101+
name string
102+
slice []string
103+
item string
104+
expected bool
105+
}{
106+
{
107+
name: "Item present",
108+
slice: []string{"a", "b", "c"},
109+
item: "b",
110+
expected: true,
111+
},
112+
{
113+
name: "Item not present",
114+
slice: []string{"a", "b", "c"},
115+
item: "d",
116+
expected: false,
117+
},
118+
{
119+
name: "Empty slice",
120+
slice: []string{},
121+
item: "a",
122+
expected: false,
123+
},
124+
}
125+
126+
for _, tt := range tests {
127+
t.Run(tt.name, func(t *testing.T) {
128+
result := contains(tt.slice, tt.item)
129+
if result != tt.expected {
130+
t.Errorf("contains() = %v, want %v", result, tt.expected)
131+
}
132+
})
133+
}
134+
}

minikube/resource_cluster.go

+17-15
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, m interf
7575
if d.HasChange("addons") {
7676
config := client.GetConfig()
7777
oldAddons, newAddons := d.GetChange("addons")
78-
oldAddonStrings := getAddons(oldAddons.(*schema.Set))
79-
newAddonStrings := getAddons(newAddons.(*schema.Set))
78+
oldAddonStrings := state_utils.SetToSlice(oldAddons.(*schema.Set))
79+
newAddonStrings := state_utils.SetToSlice(newAddons.(*schema.Set))
8080

8181
client.SetConfig(lib.MinikubeClientConfig{
8282
ClusterConfig: config.ClusterConfig,
@@ -248,7 +248,7 @@ func initialiseMinikubeClient(d *schema.ResourceData, m interface{}) (lib.Cluste
248248
addons = &schema.Set{}
249249
}
250250

251-
addonStrings := getAddons(addons.(*schema.Set))
251+
addonStrings := state_utils.SetToSlice(addons.(*schema.Set))
252252

253253
defaultIsos, ok := d.GetOk("iso_url")
254254
if !ok {
@@ -357,6 +357,19 @@ func initialiseMinikubeClient(d *schema.ResourceData, m interface{}) (lib.Cluste
357357
return nil, errors.New("at least 3 nodes is required for high availability")
358358
}
359359

360+
vcs := state_utils.SetToSlice(d.Get("wait").(*schema.Set))
361+
vc := make(map[string]bool)
362+
for _, c := range vcs {
363+
vc[c] = true
364+
}
365+
366+
err = lib.ValidateWait(vc)
367+
if err != nil {
368+
return nil, err
369+
}
370+
371+
vc = lib.ResolveSpecialWaitOptions(vc)
372+
360373
cc := config.ClusterConfig{
361374
Addons: addonConfig,
362375
APIServerPort: d.Get("apiserver_port").(int),
@@ -422,6 +435,7 @@ func initialiseMinikubeClient(d *schema.ResourceData, m interface{}) (lib.Cluste
422435
GPUs: d.Get("gpus").(string),
423436
SocketVMnetPath: d.Get("socket_vmnet_path").(string),
424437
SocketVMnetClientPath: d.Get("socket_vmnet_client_path").(string),
438+
VerifyComponents: vc,
425439
}
426440

427441
clusterClient.SetConfig(lib.MinikubeClientConfig{
@@ -441,15 +455,3 @@ func initialiseMinikubeClient(d *schema.ResourceData, m interface{}) (lib.Cluste
441455

442456
return clusterClient, nil
443457
}
444-
445-
func getAddons(addons *schema.Set) []string {
446-
addonStrings := make([]string, addons.Len())
447-
addonObjects := addons.List()
448-
for i, v := range addonObjects {
449-
addonStrings[i] = v.(string)
450-
}
451-
452-
sort.Strings(addonStrings) //to ensure consistency with TF state
453-
454-
return addonStrings
455-
}

minikube/resource_cluster_test.go

+52
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ func TestClusterHA(t *testing.T) {
8080
},
8181
})
8282
}
83+
func TestClusterWait(t *testing.T) {
84+
resource.Test(t, resource.TestCase{
85+
IsUnitTest: true,
86+
Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterCreationWait", 1, 0}))},
87+
Steps: []resource.TestStep{
88+
{
89+
Config: testUnitClusterWaitConfig("some_driver", "TestClusterCreationWait"),
90+
},
91+
},
92+
})
93+
}
8394

8495
func TestClusterCreation_Docker(t *testing.T) {
8596
resource.Test(t, resource.TestCase{
@@ -226,6 +237,21 @@ func TestClusterCreation_HAControlPlane(t *testing.T) {
226237
})
227238
}
228239

240+
func TestClusterCreation_Wait(t *testing.T) {
241+
resource.Test(t, resource.TestCase{
242+
Providers: map[string]*schema.Provider{"minikube": Provider()},
243+
CheckDestroy: verifyDelete,
244+
Steps: []resource.TestStep{
245+
{
246+
Config: testAcceptanceClusterConfig_Wait("docker", "TestClusterCreationDocker"),
247+
Check: resource.ComposeTestCheckFunc(
248+
testPropertyExists("minikube_cluster.new", "TestClusterCreationDocker"),
249+
),
250+
},
251+
},
252+
})
253+
}
254+
229255
func TestClusterCreation_Hyperkit(t *testing.T) {
230256
if runtime.GOOS != "darwin" {
231257
t.Skip("Hyperkit is only supported on macOS")
@@ -522,6 +548,17 @@ func testUnitClusterHAConfig(driver string, clusterName string) string {
522548
`, driver, clusterName)
523549
}
524550

551+
func testUnitClusterWaitConfig(driver string, clusterName string) string {
552+
return fmt.Sprintf(`
553+
resource "minikube_cluster" "new" {
554+
driver = "%s"
555+
cluster_name = "%s"
556+
557+
wait = [ "apiserver" ]
558+
}
559+
`, driver, clusterName)
560+
}
561+
525562
func testUnitClusterConfig_Update(driver string, clusterName string) string {
526563
return fmt.Sprintf(`
527564
resource "minikube_cluster" "new" {
@@ -696,6 +733,21 @@ func testAcceptanceClusterConfig_HAControlPlane(driver string, clusterName strin
696733
`, driver, clusterName)
697734
}
698735

736+
func testAcceptanceClusterConfig_Wait(driver string, clusterName string) string {
737+
return fmt.Sprintf(`
738+
resource "minikube_cluster" "new" {
739+
driver = "%s"
740+
cluster_name = "%s"
741+
cpus = 2
742+
memory = "6000GiB"
743+
744+
wait = [
745+
"apps_running"
746+
]
747+
}
748+
`, driver, clusterName)
749+
}
750+
699751
func verifyDelete(s *terraform.State) error {
700752
for _, rs := range s.RootModule().Resources {
701753
if rs.Type != "minikube_cluster" {

minikube/state_utils/slice.go

+12
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,15 @@ func ReadSliceState(slice interface{}) []string {
3838

3939
return stringSlice
4040
}
41+
42+
func SetToSlice(s *schema.Set) []string {
43+
ss := make([]string, s.Len())
44+
so := s.List()
45+
for i, v := range so {
46+
ss[i] = v.(string)
47+
}
48+
49+
sort.Strings(ss) //to ensure consistency with TF state
50+
51+
return ss
52+
}

0 commit comments

Comments
 (0)