Skip to content

Commit b64f2f0

Browse files
chore(kubernetes): add WaitForKubernetesNodeGroupState method (#272)
1 parent 5080211 commit b64f2f0

4 files changed

Lines changed: 130 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
44
See updating [Changelog example here](https://keepachangelog.com/en/1.0.0/)
55

66
## [Unreleased]
7+
## Added
8+
- kubernetes: `WaitForKubernetesNodeGroupState` method for waiting the node group to achieve a desired state
9+
710
## [6.8.2]
811
### Added
912
- account: `NetworkPeerings`, `NTPExcessGiB`, `StorageMaxIOPS`, and `LoadBalancers` fields to the `ResourceLimits` struct.

upcloud/request/kubernetes.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,19 @@ func (r *WaitForKubernetesClusterStateRequest) RequestURL() string {
100100
return fmt.Sprintf("%s/%s", kubernetesClusterBasePath, r.UUID)
101101
}
102102

103+
// WaitForKubernetesNodeGroupStateRequest represents a request to wait for a Kubernetes node group
104+
// to enter a desired state
105+
type WaitForKubernetesNodeGroupStateRequest struct {
106+
DesiredState upcloud.KubernetesNodeGroupState `json:"-"`
107+
Timeout time.Duration `json:"-"`
108+
ClusterUUID string `json:"-"`
109+
Name string `json:"-"`
110+
}
111+
112+
func (r *WaitForKubernetesNodeGroupStateRequest) RequestURL() string {
113+
return fmt.Sprintf("%s/%s/node-groups/%s", kubernetesClusterBasePath, r.ClusterUUID, r.Name)
114+
}
115+
103116
// GetKubernetesKubeconfigRequest represents a request to get kubeconfig for a Kubernetes cluster
104117
type GetKubernetesKubeconfigRequest struct {
105118
UUID string `json:"-"`

upcloud/service/kubernetes.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type Kubernetes interface {
2525
GetKubernetesNodeGroup(ctx context.Context, r *request.GetKubernetesNodeGroupRequest) (*upcloud.KubernetesNodeGroupDetails, error)
2626
CreateKubernetesNodeGroup(ctx context.Context, r *request.CreateKubernetesNodeGroupRequest) (*upcloud.KubernetesNodeGroup, error)
2727
ModifyKubernetesNodeGroup(ctx context.Context, r *request.ModifyKubernetesNodeGroupRequest) (*upcloud.KubernetesNodeGroup, error)
28+
WaitForKubernetesNodeGroupState(ctx context.Context, r *request.WaitForKubernetesNodeGroupStateRequest) (*upcloud.KubernetesNodeGroup, error)
2829
DeleteKubernetesNodeGroup(ctx context.Context, r *request.DeleteKubernetesNodeGroupRequest) error
2930
DeleteKubernetesNodeGroupNode(ctx context.Context, r *request.DeleteKubernetesNodeGroupNodeRequest) error
3031
GetKubernetesPlans(ctx context.Context, r *request.GetKubernetesPlansRequest) ([]upcloud.KubernetesPlan, error)
@@ -72,8 +73,8 @@ func (s *Service) DeleteKubernetesCluster(ctx context.Context, r *request.Delete
7273
return s.delete(ctx, r)
7374
}
7475

75-
// WaitForKubernetesClusterState (EXPERIMENTAL) blocks execution until the specified Kubernetes cluster has entered the
76-
// specified state. If the state changes favorably, cluster details is returned. The method will give up
76+
// WaitForKubernetesClusterState blocks execution until the specified Kubernetes cluster has entered the
77+
// specified state. If the state changes favorably, cluster details are returned. The method will give up
7778
// after the specified timeout
7879
func (s *Service) WaitForKubernetesClusterState(ctx context.Context, r *request.WaitForKubernetesClusterStateRequest) (*upcloud.KubernetesCluster, error) {
7980
attempts := 0
@@ -107,6 +108,42 @@ func (s *Service) WaitForKubernetesClusterState(ctx context.Context, r *request.
107108
}
108109
}
109110

111+
// WaitForKubernetesNodeGroupState blocks execution until the specified Kubernetes node group has entered the
112+
// specified state. If the state changes favorably, node group is returned. The method will give up
113+
// after the specified timeout
114+
func (s *Service) WaitForKubernetesNodeGroupState(ctx context.Context, r *request.WaitForKubernetesNodeGroupStateRequest) (*upcloud.KubernetesNodeGroup, error) {
115+
attempts := 0
116+
sleepDuration := time.Second * 5
117+
118+
for {
119+
attempts++
120+
121+
ng, err := s.GetKubernetesNodeGroup(ctx, &request.GetKubernetesNodeGroupRequest{
122+
ClusterUUID: r.ClusterUUID,
123+
Name: r.Name,
124+
})
125+
if err != nil {
126+
// Ignore first two 404 responses to avoid errors caused by possible false NOT_FOUND responses right after cluster has been created.
127+
var ucErr *upcloud.Problem
128+
if errors.As(err, &ucErr) && ucErr.Status == http.StatusNotFound && attempts < 3 {
129+
log.Printf("ERROR: %+v", err)
130+
} else {
131+
return nil, err
132+
}
133+
}
134+
135+
if ng.State == r.DesiredState {
136+
return &ng.KubernetesNodeGroup, nil
137+
}
138+
139+
time.Sleep(sleepDuration)
140+
141+
if time.Duration(attempts)*sleepDuration >= r.Timeout {
142+
return nil, fmt.Errorf("timeout reached while waiting for Kubernetes node group to enter state \"%s\"", r.DesiredState)
143+
}
144+
}
145+
}
146+
110147
// GetKubernetesKubeconfig retrieves kubeconfig of a Kubernetes cluster.
111148
func (s *Service) GetKubernetesKubeconfig(ctx context.Context, r *request.GetKubernetesKubeconfigRequest) (string, error) {
112149
// TODO: should timeout be part of GetKubernetesKubeconfigRequest ?

upcloud/service/kubernetes_test.go

Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ func TestGetKubernetesClusters(t *testing.T) {
157157
srv, svc := setupTestServerAndService(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
158158
assert.Equal(t, http.MethodGet, r.Method)
159159
assert.Equal(t, fmt.Sprintf("/%s/kubernetes", client.APIVersion), r.URL.Path)
160-
fmt.Fprintf(w, "[%s]", exampleClusterResponse)
160+
_, _ = fmt.Fprintf(w, "[%s]", exampleClusterResponse)
161161
}))
162162
defer srv.Close()
163163
res, err := svc.GetKubernetesClusters(context.Background(), &request.GetKubernetesClustersRequest{})
@@ -197,7 +197,7 @@ func TestGetKubernetesClusterDetails(t *testing.T) {
197197
srv, svc := setupTestServerAndService(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
198198
assert.Equal(t, http.MethodGet, r.Method)
199199
assert.Equal(t, fmt.Sprintf("/%s/kubernetes/_UUID_", client.APIVersion), r.URL.Path)
200-
fmt.Fprint(w, exampleClusterResponse)
200+
_, _ = fmt.Fprint(w, exampleClusterResponse)
201201
}))
202202
defer srv.Close()
203203
res, err := svc.GetKubernetesCluster(context.Background(), &request.GetKubernetesClusterRequest{UUID: "_UUID_"})
@@ -216,7 +216,7 @@ func TestCreateKubernetesCluster(t *testing.T) {
216216
srv, svc := setupTestServerAndService(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
217217
// CreateKubernetesCluster method first makes a request to /network/:uuid to check network CIDR
218218
if r.Method == http.MethodGet && r.URL.Path == fmt.Sprintf("/%s/network/03e4970d-7791-4b80-a892-682ae0faf46b", client.APIVersion) {
219-
fmt.Fprint(w, exampleNetworkResponse)
219+
_, _ = fmt.Fprint(w, exampleNetworkResponse)
220220
return
221221
}
222222

@@ -225,7 +225,7 @@ func TestCreateKubernetesCluster(t *testing.T) {
225225
err := json.NewDecoder(r.Body).Decode(&payload)
226226
assert.NoError(t, err)
227227

228-
fmt.Fprint(w, exampleClusterResponse)
228+
_, _ = fmt.Fprint(w, exampleClusterResponse)
229229
return
230230
}
231231

@@ -307,7 +307,7 @@ func TestGetKubernetesNodeGroups(t *testing.T) {
307307
srv, svc := setupTestServerAndService(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
308308
assert.Equal(t, http.MethodGet, r.Method)
309309
assert.Equal(t, fmt.Sprintf("/%s/kubernetes/_UUID_/node-groups", client.APIVersion), r.URL.Path)
310-
fmt.Fprintf(w, "[%s]", exampleNodeGroupResponse)
310+
_, _ = fmt.Fprintf(w, "[%s]", exampleNodeGroupResponse)
311311
}))
312312
defer srv.Close()
313313

@@ -324,7 +324,7 @@ func TestGetKubernetesNodeGroup(t *testing.T) {
324324
srv, svc := setupTestServerAndService(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
325325
assert.Equal(t, http.MethodGet, r.Method)
326326
assert.Equal(t, fmt.Sprintf("/%s/kubernetes/_UUID_/node-groups/_NAME_", client.APIVersion), r.URL.Path)
327-
fmt.Fprint(w, exampleNodeGroupResponse)
327+
_, _ = fmt.Fprint(w, exampleNodeGroupResponse)
328328
}))
329329
defer srv.Close()
330330

@@ -342,7 +342,7 @@ func TestGetKubernetesNodeGroupDetails(t *testing.T) {
342342
srv, svc := setupTestServerAndService(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
343343
assert.Equal(t, http.MethodGet, r.Method)
344344
assert.Equal(t, fmt.Sprintf("/%s/kubernetes/_UUID_/node-groups/_NAME_", client.APIVersion), r.URL.Path)
345-
fmt.Fprint(w, exampleNodeGroupDetailsResponse)
345+
_, _ = fmt.Fprint(w, exampleNodeGroupDetailsResponse)
346346
}))
347347
defer srv.Close()
348348

@@ -368,7 +368,7 @@ func TestCreateKubernetesNodeGroup(t *testing.T) {
368368
err := json.NewDecoder(r.Body).Decode(&payload)
369369
assert.NoError(t, err)
370370

371-
fmt.Fprint(w, exampleNodeGroupResponse)
371+
_, _ = fmt.Fprint(w, exampleNodeGroupResponse)
372372
}))
373373
defer srv.Close()
374374

@@ -421,7 +421,7 @@ func TestModifyKubernetesNodeGroup(t *testing.T) {
421421
err := json.NewDecoder(r.Body).Decode(&payload)
422422
assert.NoError(t, err)
423423

424-
fmt.Fprint(w, exampleNodeGroupResponse)
424+
_, _ = fmt.Fprint(w, exampleNodeGroupResponse)
425425
}))
426426
defer srv.Close()
427427

@@ -440,7 +440,7 @@ func TestDeleteKubernetesNodeGroup(t *testing.T) {
440440
srv, svc := setupTestServerAndService(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
441441
assert.Equal(t, http.MethodDelete, r.Method)
442442
assert.Equal(t, fmt.Sprintf("/%s/kubernetes/_UUID_/node-groups/_NAME_", client.APIVersion), r.URL.Path)
443-
fmt.Fprint(w, exampleNodeGroupResponse)
443+
_, _ = fmt.Fprint(w, exampleNodeGroupResponse)
444444
}))
445445
defer srv.Close()
446446

@@ -480,7 +480,7 @@ func TestWaitForKubernetesClusterState(t *testing.T) {
480480
requestsMade++
481481

482482
if requestsCounter >= 2 {
483-
fmt.Fprint(w, `
483+
_, _ = fmt.Fprint(w, `
484484
{
485485
"name":"test-name",
486486
"network":"03e4970d-7791-4b80-a892-682ae0faf46b",
@@ -493,7 +493,7 @@ func TestWaitForKubernetesClusterState(t *testing.T) {
493493
`)
494494
} else {
495495
requestsCounter++
496-
fmt.Fprint(w, `
496+
_, _ = fmt.Fprint(w, `
497497
{
498498
"name":"test-name",
499499
"network":"03e4970d-7791-4b80-a892-682ae0faf46b",
@@ -517,19 +517,80 @@ func TestWaitForKubernetesClusterState(t *testing.T) {
517517
assert.Equal(t, 3, requestsMade)
518518
}
519519

520+
func TestWaitForKubernetesNodeGroupState(t *testing.T) {
521+
t.Parallel()
522+
523+
requestsCounter := 0
524+
requestsMade := 0
525+
526+
srv, svc := setupTestServerAndService(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
527+
assert.Equal(t, http.MethodGet, r.Method)
528+
assert.Equal(t, fmt.Sprintf("/%s/kubernetes/_UUID_/node-groups/_NAME_", client.APIVersion), r.URL.Path)
529+
530+
requestsMade++
531+
532+
if requestsCounter >= 2 {
533+
_, _ = fmt.Fprint(w, `
534+
{
535+
"anti_affinity": false,
536+
"count": 1,
537+
"kubelet_args": [],
538+
"labels": [],
539+
"name": "test-name",
540+
"plan": "1xCPU-1GB",
541+
"ssh_keys": [
542+
"test-key"
543+
],
544+
"state": "running",
545+
"storage": "01000000-0000-4000-8000-000160020100",
546+
"utility_network_access": false
547+
}
548+
`)
549+
} else {
550+
requestsCounter++
551+
_, _ = fmt.Fprint(w, `
552+
{
553+
"anti_affinity": false,
554+
"count": 1,
555+
"kubelet_args": [],
556+
"labels": [],
557+
"name": "test-name",
558+
"plan": "1xCPU-1GB",
559+
"ssh_keys": [
560+
"test-key"
561+
],
562+
"state": "scaling-up",
563+
"storage": "01000000-0000-4000-8000-000160020100",
564+
"utility_network_access": false
565+
}
566+
`)
567+
}
568+
}))
569+
defer srv.Close()
570+
571+
_, err := svc.WaitForKubernetesNodeGroupState(context.Background(), &request.WaitForKubernetesNodeGroupStateRequest{
572+
ClusterUUID: "_UUID_",
573+
DesiredState: upcloud.KubernetesNodeGroupStateRunning,
574+
Timeout: time.Second * 20,
575+
Name: "_NAME_",
576+
})
577+
assert.NoError(t, err)
578+
assert.Equal(t, 3, requestsMade)
579+
}
580+
520581
func TestGetKubernetesKubeconfig(t *testing.T) {
521582
t.Parallel()
522583

523584
srv, svc := setupTestServerAndService(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
524585
// GetKubernetesKubeconfig first fetches cluster details to check for running state, so we must
525586
// take care of both requests
526587
if r.Method == http.MethodGet && r.URL.Path == fmt.Sprintf("/%s/kubernetes/_UUID_", client.APIVersion) {
527-
fmt.Fprint(w, exampleClusterResponse)
588+
_, _ = fmt.Fprint(w, exampleClusterResponse)
528589
return
529590
}
530591

531592
if r.Method == http.MethodGet && r.URL.Path == fmt.Sprintf("/%s/kubernetes/_UUID_/kubeconfig", client.APIVersion) {
532-
fmt.Fprint(w, exampleKubeconfigResponse)
593+
_, _ = fmt.Fprint(w, exampleKubeconfigResponse)
533594
return
534595
}
535596

0 commit comments

Comments
 (0)