Skip to content

Rewrite travis script in golang #346

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 38 commits into from
Jul 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b2392a8
test: add test written in go
AlexNPavel Jul 13, 2018
a57918e
.travis.yml: make travis use new test framework
AlexNPavel Jul 13, 2018
fcfc7a2
test/test.sh: link travis build correctly
AlexNPavel Jul 13, 2018
1f179a0
.travis.yml: fix test script path
AlexNPavel Jul 13, 2018
f52a29a
test/test.sh: remove unnecessary dep ensure
AlexNPavel Jul 13, 2018
26ea087
test/test.sh: link vendor dirs
AlexNPavel Jul 13, 2018
3f0e127
test/main.go: add loop timeout
AlexNPavel Jul 13, 2018
915410a
test/main.go: create deploymentReplicaCheck function
AlexNPavel Jul 13, 2018
ec330c3
test/main.go: create kubectlWrapper function
AlexNPavel Jul 13, 2018
e655853
test/main.go: remove unnecessary type conversion
AlexNPavel Jul 13, 2018
00f46dc
test/main.go: fail if replica count not reached
AlexNPavel Jul 13, 2018
2b81257
test/main.go: fix log message
AlexNPavel Jul 13, 2018
a25429c
test/main.go: always use log.fatal on error
AlexNPavel Jul 13, 2018
4036c16
test/test.sh: remove unnecessary linking
AlexNPavel Jul 16, 2018
a662255
test/main.go: clean up replica check func a bit
AlexNPavel Jul 16, 2018
4a573cb
test/main.go: fix some comments
AlexNPavel Jul 16, 2018
6a13af8
test/main.go: rename clientset and use kubeclient directly
AlexNPavel Jul 16, 2018
17d72fe
test/main.go: put everything into a namespace
AlexNPavel Jul 16, 2018
2cfdc87
test/main.go: fix kubectl namespace option
AlexNPavel Jul 16, 2018
4a327ab
test/main.go: create namespace before trying to use it
AlexNPavel Jul 16, 2018
f040222
test/{main.go,test.sh}: move resource cleanup into go code
AlexNPavel Jul 16, 2018
92768af
test/main.go: create and use retryutil
AlexNPavel Jul 16, 2018
b6e0952
test/main.go: use kubeclient to create namespace
AlexNPavel Jul 16, 2018
a5b73bc
test/main.go: add IncludeUninitialized to GetOptions
AlexNPavel Jul 16, 2018
c7fbf2d
test/main.go: use restclient to update memcached resource
AlexNPavel Jul 17, 2018
f15b46d
test/main.go: move more kubectl commands to native go code
AlexNPavel Jul 18, 2018
1ab11ba
test/main.go: remove last bits of kubectl command usage
AlexNPavel Jul 18, 2018
92729e6
test/main.go: move most of test.sh into the go code
AlexNPavel Jul 18, 2018
c14c476
test/main.go: infer group and version from crd
AlexNPavel Jul 18, 2018
cd61002
test/main.go: correctly get cr group and version
AlexNPavel Jul 18, 2018
f2b0e49
test/main.go: use createFromYAML for all resources
AlexNPavel Jul 18, 2018
57e39c1
test/main.go: make a little more generic for custom resources
AlexNPavel Jul 18, 2018
db538cf
test/main.go: remove all kubectl references
AlexNPavel Jul 18, 2018
6e99dc3
test/main.go: add a new line of output
AlexNPavel Jul 18, 2018
98cd7c0
test: transform the e2e test into a standard go test
AlexNPavel Jul 19, 2018
ffd00c8
test/e2e/memcached_test.go: re-enable linking of travis build dir
AlexNPavel Jul 19, 2018
a92ae50
test/e2e: move retryutil to e2eutils
AlexNPavel Jul 19, 2018
6328103
.travis.yml: run unit tests and e2e tests separately
AlexNPavel Jul 19, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 3 additions & 55 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,62 +30,10 @@ install:
- go get -t -d ./...

script:
# Unit test and verify formatting
- go test ./...
- go vet ./...
- make install
# Create example operator
- cd $GOPATH/src/github.com/example-inc
- operator-sdk new memcached-operator --api-version=cache.example.com/v1alpha1 --kind=Memcached
- cd memcached-operator
- rm -rf vendor/github.com/operator-framework/operator-sdk/pkg
- ln -s ${TRAVIS_BUILD_DIR}/pkg vendor/github.com/operator-framework/operator-sdk/pkg
- curl https://raw.githubusercontent.com/operator-framework/operator-sdk/master/example/memcached-operator/handler.go.tmpl -o pkg/stub/handler.go
- head -n -6 pkg/apis/cache/v1alpha1/types.go > tmp.txt
- mv tmp.txt pkg/apis/cache/v1alpha1/types.go
- echo 'type MemcachedSpec struct { Size int32 `json:"size"`}' >> pkg/apis/cache/v1alpha1/types.go
- echo 'type MemcachedStatus struct {Nodes []string `json:"nodes"`}' >> pkg/apis/cache/v1alpha1/types.go
- operator-sdk generate k8s
- operator-sdk build quay.io/example/memcached-operator:v0.0.1
- |
sed -ie 's/imagePullPolicy: Always/imagePullPolicy: Never/g' deploy/operator.yaml
# Run the operator as a pod
- kubectl create -f deploy/rbac.yaml
- kubectl create -f deploy/crd.yaml
- kubectl create -f deploy/operator.yaml
# Sleep until operator is ready, TODO poll api in retry loop
- sleep 10s
- kubectl get deployment
- kubectl get pods
# Create memcached cr
- |
echo "apiVersion: \"cache.example.com/v1alpha1\"" > deploy/cr.yaml
- |
echo "kind: \"Memcached\"" >> deploy/cr.yaml
- echo "metadata:" >> deploy/cr.yaml
- |
echo " name: \"example-memcached\"" >> deploy/cr.yaml
- echo "spec:" >> deploy/cr.yaml
- |
echo " size: 3" >> deploy/cr.yaml
- kubectl apply -f deploy/cr.yaml
# Sleep until 3 pods are up, TODO poll api in retry loop
- sleep 10s
- kubectl get deployment
- kubectl get pods
- kubectl get memcached/example-memcached -o yaml
# Expand to size to 4
- |
sed -ie 's/size: 3/size: 4/g' deploy/cr.yaml
- kubectl apply -f deploy/cr.yaml
# Sleep until 4 pods are up, TODO poll api in retry loop
- sleep 10s
- kubectl get deployment
- kubectl get pods
- kubectl get memcached/example-memcached -o yaml
# Cleanup
- kubectl delete -f deploy/cr.yaml
- kubectl delete -f deploy/operator.yaml
- go test ./pkg/...
- go vet ./...
- go test ./test/e2e/...

after_success:
- echo 'Build succeeded, operator was generated, memcached operator is running on minikube, and unit/integration tests pass'
Expand Down
14 changes: 13 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
name = "k8s.io/apimachinery"
version = "kubernetes-1.9.3"

[[override]]
name = "k8s.io/apiextensions-apiserver"
version = "kubernetes-1.9.3"

[[override]]
name = "k8s.io/client-go"
version = "kubernetes-1.9.3"
Expand Down
1 change: 1 addition & 0 deletions test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test
36 changes: 36 additions & 0 deletions test/e2e/e2eutil/check_util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package e2eutil

import (
"testing"
"time"

apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)

var retryInterval = time.Second * 5

func DeploymentReplicaCheck(t *testing.T, kubeclient *kubernetes.Clientset, namespace, name string, replicas, retries int) error {
err := Retry(retryInterval, retries, func() (done bool, err error) {
deployment, err := kubeclient.AppsV1().Deployments(namespace).Get(name, metav1.GetOptions{IncludeUninitialized: true})
if err != nil {
if apierrors.IsNotFound(err) {
t.Logf("Waiting for availability of %s deployment\n", name)
return false, nil
}
return false, err
}

if int(deployment.Status.AvailableReplicas) == replicas {
return true, nil
}
t.Logf("Waiting for full availability of %s deployment (%d/%d)\n", name, deployment.Status.AvailableReplicas, replicas)
return false, nil
})
if err != nil {
return err
}
t.Logf("Deployment available (%d/%d)\n", replicas, replicas)
return nil
}
111 changes: 111 additions & 0 deletions test/e2e/e2eutil/create_util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package e2eutil

import (
"strings"
"testing"

y2j "github.com/ghodss/yaml"
yaml "gopkg.in/yaml.v2"
apps "k8s.io/api/apps/v1"
"k8s.io/api/rbac/v1beta1"
crd "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
extensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
extensions_scheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
)

func GetCRClient(t *testing.T, config *rest.Config, yamlCR []byte) *rest.RESTClient {
// get new RESTClient for custom resources
crConfig := config
m := make(map[interface{}]interface{})
err := yaml.Unmarshal(yamlCR, &m)
groupVersion := strings.Split(m["apiVersion"].(string), "/")
crGV := schema.GroupVersion{Group: groupVersion[0], Version: groupVersion[1]}
crConfig.GroupVersion = &crGV
crConfig.APIPath = "/apis"
crConfig.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}

if crConfig.UserAgent == "" {
crConfig.UserAgent = rest.DefaultKubernetesUserAgent()
}
crRESTClient, err := rest.RESTClientFor(crConfig)
if err != nil {
t.Fatal(err)
}
return crRESTClient
}

func createCRFromYAML(t *testing.T, yamlFile []byte, kubeconfig *rest.Config, namespace, resourceName string) error {
client := GetCRClient(t, kubeconfig, yamlFile)
jsonDat, err := y2j.YAMLToJSON(yamlFile)
err = client.Post().
Namespace(namespace).
Resource(resourceName).
Body(jsonDat).
Do().
Error()
return err
}

func createCRDFromYAML(t *testing.T, yamlFile []byte, extensionsClient *extensions.Clientset) error {
decode := extensions_scheme.Codecs.UniversalDeserializer().Decode
obj, _, err := decode(yamlFile, nil, nil)

if err != nil {
t.Log("Failed to deserialize CustomResourceDefinition")
t.Fatal(err)
}
switch o := obj.(type) {
case *crd.CustomResourceDefinition:
_, err = extensionsClient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(o)
return err
}
return nil
}

func CreateFromYAML(t *testing.T, yamlFile []byte, kubeclient *kubernetes.Clientset, kubeconfig *rest.Config, namespace string) error {
m := make(map[interface{}]interface{})
err := yaml.Unmarshal(yamlFile, &m)
kind := m["kind"].(string)
switch kind {
case "Role":
case "RoleBinding":
case "Deployment":
case "CustomResourceDefinition":
extensionclient, err := extensions.NewForConfig(kubeconfig)
if err != nil {
t.Fatal(err)
}
return createCRDFromYAML(t, yamlFile, extensionclient)
// we assume that all custom resources are from operator-sdk and thus follow
// a common naming convention
default:
return createCRFromYAML(t, yamlFile, kubeconfig, namespace, strings.ToLower(kind)+"s")
}
decode := scheme.Codecs.UniversalDeserializer().Decode
obj, _, err := decode(yamlFile, nil, nil)

if err != nil {
t.Log("Unable to deserialize resource; is it a custom resource?")
t.Fatal(err)
}

switch o := obj.(type) {
case *v1beta1.Role:
_, err = kubeclient.RbacV1beta1().Roles(namespace).Create(o)
return err
case *v1beta1.RoleBinding:
_, err = kubeclient.RbacV1beta1().RoleBindings(namespace).Create(o)
return err
case *apps.Deployment:
_, err = kubeclient.AppsV1().Deployments(namespace).Create(o)
return err
default:
t.Fatalf("unknown type: %s", o)
}
return nil
}
62 changes: 62 additions & 0 deletions test/e2e/e2eutil/retry_util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2018 The Operator-SDK 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 e2eutil

import (
"fmt"
"time"
)

type RetryError struct {
n int
}

func (e *RetryError) Error() string {
return fmt.Sprintf("still failing after %d retries", e.n)
}

func IsRetryFailure(err error) bool {
_, ok := err.(*RetryError)
return ok
}

type ConditionFunc func() (bool, error)

// Retry retries f every interval until after maxRetries.
// The interval won't be affected by how long f takes.
// For example, if interval is 3s, f takes 1s, another f will be called 2s later.
// However, if f takes longer than interval, it will be delayed.
func Retry(interval time.Duration, maxRetries int, f ConditionFunc) error {
if maxRetries <= 0 {
return fmt.Errorf("maxRetries (%d) should be > 0", maxRetries)
}
tick := time.NewTicker(interval)
defer tick.Stop()

for i := 0; ; i++ {
ok, err := f()
if err != nil {
return err
}
if ok {
return nil
}
if i == maxRetries {
break
}
<-tick.C
}
return &RetryError{maxRetries}
}
10 changes: 10 additions & 0 deletions test/e2e/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package e2e

import (
"testing"
)

func TestMain(m *testing.M) {
// TODO: create a setup step for the framework here
m.Run()
}
Loading