Skip to content

Commit

Permalink
Merge pull request #139 from Charliekenney23/feat/waitForLKEClusterReady
Browse files Browse the repository at this point in the history
implement WaitForLKEClusterReady
  • Loading branch information
0xch4z committed Apr 29, 2020
2 parents 7f61434 + ff89405 commit 5ddccd2
Show file tree
Hide file tree
Showing 14 changed files with 937 additions and 40 deletions.
1 change: 1 addition & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ func (c *Client) SetRetries() *Client {
c.
addRetryConditional(linodeBusyRetryCondition).
addRetryConditional(tooManyRequestsRetryCondition).
addRetryConditional(serviceUnavailableRetryCondition).
SetRetryMaxWaitTime(APIRetryMaxWaitTime)
configureRetries(c)
return c
Expand Down
11 changes: 4 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@ module github.com/linode/linodego
require (
github.com/dnaeon/go-vcr v1.0.1
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48
github.com/golang/protobuf v1.2.0 // indirect
github.com/google/go-cmp v0.4.0
github.com/kr/pretty v0.1.0 // indirect
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
google.golang.org/appengine v1.1.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v2 v2.2.1 // indirect
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
k8s.io/api v0.17.5
k8s.io/apimachinery v0.17.5
k8s.io/client-go v0.17.5
)

go 1.13
176 changes: 169 additions & 7 deletions go.sum

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions internal/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package kubernetes

import (
"fmt"

"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/transport"
)

// Clientset is an alias to k8s.io/client-go/kubernetes.Interface
type Clientset kubernetes.Interface

// NewClientsetFromBytes builds a Clientset from a given Kubeconfig.
//
// Takes an optional transport.WrapperFunc to add request/response middleware to
// api-server requests.
func BuildClientsetFromConfig(
kubeconfigBytes []byte,
transportWrapper transport.WrapperFunc,
) (Clientset, error) {
config, err := clientcmd.NewClientConfigFromBytes(kubeconfigBytes)
if err != nil {
return nil, fmt.Errorf("failed to parse LKE cluster kubeconfig: %s", err)
}

restClientConfig, err := config.ClientConfig()
if err != nil {
return nil, fmt.Errorf("failed to get REST client config: %s", err)
}

if transportWrapper != nil {
restClientConfig.Wrap(transportWrapper)
}

clientset, err := kubernetes.NewForConfig(restClientConfig)
if err != nil {
return nil, fmt.Errorf("failed to build k8s client from LKE cluster kubeconfig: %s", err)
}
return clientset, nil
}
28 changes: 14 additions & 14 deletions lke_clusters.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,23 @@ const (

// LKECluster represents a LKECluster object
type LKECluster struct {
ID int `json:"id"`
Created *time.Time `json:"-"`
Updated *time.Time `json:"-"`
Label string `json:"label"`
Region string `json:"region"`
Status LKEClusterStatus `json:"status"`
Version string `json:"version"`
Tags []string `json:"tags"`
ID int `json:"id"`
Created *time.Time `json:"-"`
Updated *time.Time `json:"-"`
Label string `json:"label"`
Region string `json:"region"`
Status LKEClusterStatus `json:"status"`
K8sVersion string `json:"k8s_version"`
Tags []string `json:"tags"`
}

// LKEClusterCreateOptions fields are those accepted by CreateLKECluster
type LKEClusterCreateOptions struct {
NodePools []LKEClusterPoolCreateOptions `json:"node_pools"`
Label string `json:"label"`
Region string `json:"region"`
Version string `json:"version"`
Tags []string `json:"tags,omitempty"`
NodePools []LKEClusterPoolCreateOptions `json:"node_pools"`
Label string `json:"label"`
Region string `json:"region"`
K8sVersion string `json:"k8s_version"`
Tags []string `json:"tags,omitempty"`
}

// LKEClusterUpdateOptions fields are those accepted by UpdateLKECluster
Expand Down Expand Up @@ -85,7 +85,7 @@ func (i *LKECluster) UnmarshalJSON(b []byte) error {
func (i LKECluster) GetCreateOptions() (o LKEClusterCreateOptions) {
o.Label = i.Label
o.Region = i.Region
o.Version = i.Version
o.K8sVersion = i.K8sVersion
o.Tags = i.Tags
// @TODO copy NodePools?
return
Expand Down
3 changes: 3 additions & 0 deletions pkg/condition/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Package condition provides strategies for waiting for infrastructure
// to reach a desired state through conditional predicates.
package condition
34 changes: 34 additions & 0 deletions pkg/condition/lke.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package condition

import (
"context"
"errors"
"fmt"

"github.com/linode/linodego/internal/kubernetes"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// ClusterConditionFunc represents a function that tests a condition against an LKE cluster,
// returns true if the condition has been reached, false if it has not yet been reached.
type ClusterConditionFunc func(context.Context, kubernetes.Clientset) (bool, error)

// ClusterHasReadyNode is a ClusterConditionFunc which polls for at least one node to have the
// condition NodeReady=True.
func ClusterHasReadyNode(ctx context.Context, clientset kubernetes.Clientset) (bool, error) {
nodes, err := clientset.CoreV1().Nodes().List(v1.ListOptions{})
if err != nil {
return false, fmt.Errorf("failed to get nodes for cluster: %s", err)
}

for _, node := range nodes.Items {
for _, condition := range node.Status.Conditions {
if condition.Type == corev1.NodeReady && condition.Status == corev1.ConditionTrue {
return true, nil
}
}
}

return false, errors.New("no nodes in cluster are ready")
}
8 changes: 7 additions & 1 deletion retries.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/go-resty/resty/v2"
)

const retryAfterHeaderName = "Retry-After"

// type RetryConditional func(r *resty.Response) (shouldRetry bool)
type RetryConditional resty.RetryConditionFunc

Expand Down Expand Up @@ -48,8 +50,12 @@ func tooManyRequestsRetryCondition(r *resty.Response, _ error) bool {
return r.StatusCode() == http.StatusTooManyRequests
}

func serviceUnavailableRetryCondition(r *resty.Response, _ error) bool {
return r.StatusCode() == http.StatusServiceUnavailable
}

func respectRetryAfter(client *resty.Client, resp *resty.Response) (time.Duration, error) {
retryAfterStr := resp.Header().Get("Retry-After")
retryAfterStr := resp.Header().Get(retryAfterHeaderName)
if retryAfterStr == "" {
return 0, nil
}
Expand Down
24 changes: 23 additions & 1 deletion retries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package linodego
import (
"net/http"
"testing"
"time"

"github.com/go-resty/resty/v2"
)
Expand All @@ -25,7 +26,7 @@ func TestLinodeBusyRetryCondition(t *testing.T) {

apiError := APIError{
Errors: []APIErrorReason{
APIErrorReason{Reason: "Linode busy."},
{Reason: "Linode busy."},
},
}
request.SetError(&apiError)
Expand All @@ -36,3 +37,24 @@ func TestLinodeBusyRetryCondition(t *testing.T) {
t.Errorf("Should have retried")
}
}

func TestLinodeServiceUnavailableRetryCondition(t *testing.T) {
request := resty.Request{}
rawResponse := http.Response{StatusCode: http.StatusServiceUnavailable, Header: http.Header{
retryAfterHeaderName: []string{"20"},
}}
response := resty.Response{
Request: &request,
RawResponse: &rawResponse,
}

if retry := serviceUnavailableRetryCondition(&response, nil); !retry {
t.Error("expected request to be retried")
}

if retryAfter, err := respectRetryAfter(NewClient(nil).resty, &response); err != nil {
t.Errorf("expected error to be nil but got %s", err)
} else if retryAfter != time.Second*20 {
t.Errorf("expected retryAfter to be 20 but got %d", retryAfter)
}
}
Loading

0 comments on commit 5ddccd2

Please sign in to comment.