Skip to content

Commit b19e56f

Browse files
authored
Operator SDK Test Framework (#377)
Adds initial test framework and sample memcached tests that use the framework.
1 parent fa4ea18 commit b19e56f

File tree

14 files changed

+802
-1
lines changed

14 files changed

+802
-1
lines changed

Gopkg.lock

Lines changed: 9 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,7 @@
2525
[[constraint]]
2626
name = "github.com/sergi/go-diff"
2727
version = "1.0.0"
28+
29+
[[constraint]]
30+
name = "sigs.k8s.io/controller-runtime"
31+
revision = "60bb251ad86f9b313653618aad0c2c53f41a6625"

commands/operator-sdk/cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func NewRootCmd() *cobra.Command {
3232
cmd.AddCommand(NewGenerateCmd())
3333
cmd.AddCommand(NewUpCmd())
3434
cmd.AddCommand(NewCompletionCmd())
35+
cmd.AddCommand(NewTestCmd())
3536

3637
return cmd
3738
}

commands/operator-sdk/cmd/test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2018 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cmd
16+
17+
import (
18+
"os"
19+
20+
"github.com/operator-framework/operator-sdk/pkg/test"
21+
22+
"github.com/spf13/cobra"
23+
)
24+
25+
var (
26+
testLocation string
27+
kubeconfig string
28+
crdManifestPath string
29+
opManifestPath string
30+
rbacManifestPath string
31+
verbose bool
32+
)
33+
34+
// TODO: allow users to pass flags through to `go test`
35+
func NewTestCmd() *cobra.Command {
36+
testCmd := &cobra.Command{
37+
Use: "test --test-location <path to tests directory> [flags]",
38+
Short: "Run End-To-End tests",
39+
Run: testFunc,
40+
}
41+
defaultKubeConfig := ""
42+
homedir, ok := os.LookupEnv("HOME")
43+
if ok {
44+
defaultKubeConfig = homedir + "/.kube/config"
45+
}
46+
testCmd.Flags().StringVarP(&testLocation, "test-location", "t", "", "Location of test files (e.g. ./test/e2e/)")
47+
testCmd.MarkFlagRequired("test-location")
48+
testCmd.Flags().StringVarP(&kubeconfig, "kubeconfig", "k", defaultKubeConfig, "Kubeconfig path")
49+
testCmd.Flags().StringVarP(&crdManifestPath, "crd", "c", "deploy/crd.yaml", "Path to CRD manifest")
50+
testCmd.Flags().StringVarP(&opManifestPath, "operator", "o", "deploy/operator.yaml", "Path to operator manifest")
51+
testCmd.Flags().StringVarP(&rbacManifestPath, "rbac", "r", "deploy/rbac.yaml", "Path to RBAC manifest")
52+
testCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose go test")
53+
54+
return testCmd
55+
}
56+
57+
func testFunc(cmd *cobra.Command, args []string) {
58+
testArgs := []string{"test", testLocation + "/..."}
59+
if verbose {
60+
testArgs = append(testArgs, "-v")
61+
}
62+
testArgs = append(testArgs, "-"+test.KubeConfigFlag, kubeconfig)
63+
testArgs = append(testArgs, "-"+test.CrdManPathFlag, crdManifestPath)
64+
testArgs = append(testArgs, "-"+test.OpManPathFlag, opManifestPath)
65+
testArgs = append(testArgs, "-"+test.RbacManPathFlag, rbacManifestPath)
66+
testArgs = append(testArgs, "-"+test.ProjRootFlag, mustGetwd())
67+
execCmd(os.Stdout, "go", testArgs...)
68+
}

pkg/test/context.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright 2018 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package test
16+
17+
import (
18+
"log"
19+
"strconv"
20+
"strings"
21+
"testing"
22+
"time"
23+
24+
"k8s.io/client-go/rest"
25+
)
26+
27+
type TestCtx struct {
28+
ID string
29+
CleanUpFns []finalizerFn
30+
Namespace string
31+
CRClient *rest.RESTClient
32+
}
33+
34+
type finalizerFn func() error
35+
36+
func NewTestCtx(t *testing.T) TestCtx {
37+
var prefix string
38+
if t != nil {
39+
// TestCtx is used among others for namespace names where '/' is forbidden
40+
prefix = strings.TrimPrefix(
41+
strings.Replace(
42+
strings.ToLower(t.Name()),
43+
"/",
44+
"-",
45+
-1,
46+
),
47+
"test",
48+
)
49+
} else {
50+
prefix = "main"
51+
}
52+
53+
id := prefix + "-" + strconv.FormatInt(time.Now().Unix(), 10)
54+
return TestCtx{
55+
ID: id,
56+
}
57+
}
58+
59+
func (ctx *TestCtx) GetID() string {
60+
return ctx.ID
61+
}
62+
63+
func (ctx *TestCtx) Cleanup(t *testing.T) {
64+
for i := len(ctx.CleanUpFns) - 1; i >= 0; i-- {
65+
err := ctx.CleanUpFns[i]()
66+
if err != nil {
67+
t.Errorf("a cleanup function failed with error: %v\n", err)
68+
}
69+
}
70+
}
71+
72+
// CleanupNoT is a modified version of Cleanup; does not use t for logging, instead uses log
73+
// intended for use by MainEntry, which does not have a testing.T
74+
func (ctx *TestCtx) CleanupNoT() {
75+
failed := false
76+
for i := len(ctx.CleanUpFns) - 1; i >= 0; i-- {
77+
err := ctx.CleanUpFns[i]()
78+
if err != nil {
79+
failed = true
80+
log.Printf("a cleanup function failed with error: %v\n", err)
81+
}
82+
}
83+
if failed {
84+
log.Fatal("a cleanup function failed")
85+
}
86+
}
87+
88+
func (ctx *TestCtx) AddFinalizerFn(fn finalizerFn) {
89+
ctx.CleanUpFns = append(ctx.CleanUpFns, fn)
90+
}

pkg/test/framework.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2018 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package test
16+
17+
import (
18+
"fmt"
19+
20+
extensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
21+
extscheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
22+
"k8s.io/apimachinery/pkg/runtime"
23+
"k8s.io/apimachinery/pkg/runtime/serializer"
24+
"k8s.io/client-go/kubernetes"
25+
cgoscheme "k8s.io/client-go/kubernetes/scheme"
26+
"k8s.io/client-go/rest"
27+
"k8s.io/client-go/tools/clientcmd"
28+
dynclient "sigs.k8s.io/controller-runtime/pkg/client"
29+
)
30+
31+
var Global *Framework
32+
33+
type Framework struct {
34+
KubeConfig *rest.Config
35+
KubeClient kubernetes.Interface
36+
ExtensionsClient *extensions.Clientset
37+
DynamicClient dynclient.Client
38+
DynamicDecoder runtime.Decoder
39+
CrdManPath *string
40+
OpManPath *string
41+
RbacManPath *string
42+
}
43+
44+
func setup(kubeconfigPath, crdManPath, opManPath, rbacManPath *string) error {
45+
kubeconfig, err := clientcmd.BuildConfigFromFlags("", *kubeconfigPath)
46+
if err != nil {
47+
return fmt.Errorf("failed to build the kubeconfig: %v", err)
48+
}
49+
kubeclient, err := kubernetes.NewForConfig(kubeconfig)
50+
if err != nil {
51+
return fmt.Errorf("failed to build the kubeclient: %v", err)
52+
}
53+
extensionsClient, err := extensions.NewForConfig(kubeconfig)
54+
if err != nil {
55+
return fmt.Errorf("failed to build the extensionsClient: %v", err)
56+
}
57+
scheme := runtime.NewScheme()
58+
cgoscheme.AddToScheme(scheme)
59+
extscheme.AddToScheme(scheme)
60+
dynClient, err := dynclient.New(kubeconfig, dynclient.Options{Scheme: scheme})
61+
if err != nil {
62+
return fmt.Errorf("failed to build the dynamic client: %v", err)
63+
}
64+
dynDec := serializer.NewCodecFactory(scheme).UniversalDeserializer()
65+
Global = &Framework{
66+
KubeConfig: kubeconfig,
67+
KubeClient: kubeclient,
68+
ExtensionsClient: extensionsClient,
69+
DynamicClient: dynClient,
70+
DynamicDecoder: dynDec,
71+
CrdManPath: crdManPath,
72+
OpManPath: opManPath,
73+
RbacManPath: rbacManPath,
74+
}
75+
return nil
76+
}

pkg/test/main_entry.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2018 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package test
16+
17+
import (
18+
"flag"
19+
"io/ioutil"
20+
"log"
21+
"os"
22+
"testing"
23+
)
24+
25+
const (
26+
ProjRootFlag = "root"
27+
KubeConfigFlag = "kubeconfig"
28+
CrdManPathFlag = "crd"
29+
OpManPathFlag = "op"
30+
RbacManPathFlag = "rbac"
31+
)
32+
33+
func MainEntry(m *testing.M) {
34+
projRoot := flag.String("root", "", "path to project root")
35+
kubeconfigPath := flag.String("kubeconfig", "", "path to kubeconfig")
36+
crdManPath := flag.String("crd", "", "path to crd manifest")
37+
opManPath := flag.String("op", "", "path to operator manifest")
38+
rbacManPath := flag.String("rbac", "", "path to rbac manifest")
39+
flag.Parse()
40+
// go test always runs from the test directory; change to project root
41+
err := os.Chdir(*projRoot)
42+
if err != nil {
43+
log.Fatalf("failed to change directory to project root: %v", err)
44+
}
45+
if err := setup(kubeconfigPath, crdManPath, opManPath, rbacManPath); err != nil {
46+
log.Fatalf("failed to set up framework: %v", err)
47+
}
48+
// setup context to use when setting up crd
49+
ctx := NewTestCtx(nil)
50+
// os.Exit stops the program before the deferred functions run
51+
// to fix this, we put the exit in the defer as well
52+
defer func() {
53+
exitCode := m.Run()
54+
ctx.CleanupNoT()
55+
os.Exit(exitCode)
56+
}()
57+
// create crd
58+
crdYAML, err := ioutil.ReadFile(*Global.CrdManPath)
59+
if err != nil {
60+
log.Fatalf("failed to read crd file: %v", err)
61+
}
62+
err = ctx.CreateFromYAML(crdYAML)
63+
if err != nil {
64+
log.Fatalf("failed to create crd resource: %v", err)
65+
}
66+
}

0 commit comments

Comments
 (0)