Skip to content

commands,pkg/test: support single namespace mode for local test #546

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
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ script:
- operator-sdk test local .
# test operator-sdk test flags
- operator-sdk test local . --global-manifest deploy/crd.yaml --namespaced-manifest deploy/namespace-init.yaml --go-test-flags "-parallel 1" --kubeconfig $HOME/.kube/config
# test operator-sdk test local single namespace mode
- kubectl create namespace test-memcached
- operator-sdk test local . --namespace=test-memcached
- kubectl delete namespace test-memcached
# go back to project root
- cd ../..
- go vet ./...
Expand Down
12 changes: 11 additions & 1 deletion commands/operator-sdk/cmd/test/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type testLocalConfig struct {
globalManPath string
namespacedManPath string
goTestFlags string
namespace string
}

var tlConfig testLocalConfig
Expand All @@ -52,6 +53,7 @@ func NewTestLocalCmd() *cobra.Command {
testCmd.Flags().StringVar(&tlConfig.globalManPath, "global-manifest", "deploy/crd.yaml", "Path to manifest for Global resources (e.g. CRD manifest)")
testCmd.Flags().StringVar(&tlConfig.namespacedManPath, "namespaced-manifest", "", "Path to manifest for per-test, namespaced resources (e.g. RBAC and Operator manifest)")
testCmd.Flags().StringVar(&tlConfig.goTestFlags, "go-test-flags", "", "Additional flags to pass to go test")
testCmd.Flags().StringVar(&tlConfig.namespace, "namespace", "", "If non-empty, single namespace to run tests in")

return testCmd
}
Expand Down Expand Up @@ -96,8 +98,16 @@ func testLocalFunc(cmd *cobra.Command, args []string) {
testArgs = append(testArgs, "-"+test.NamespacedManPathFlag, tlConfig.namespacedManPath)
testArgs = append(testArgs, "-"+test.GlobalManPathFlag, tlConfig.globalManPath)
testArgs = append(testArgs, "-"+test.ProjRootFlag, mustGetwd())
testArgs = append(testArgs, strings.Split(tlConfig.goTestFlags, " ")...)
// if we do the append using an empty go flags, it inserts an empty arg, which causes
// any later flags to be ignored
if tlConfig.goTestFlags != "" {
testArgs = append(testArgs, strings.Split(tlConfig.goTestFlags, " ")...)
}
if tlConfig.namespace != "" {
testArgs = append(testArgs, "-"+test.SingleNamespaceFlag, "-parallel=1")
}
dc := exec.Command("go", testArgs...)
dc.Env = append(os.Environ(), fmt.Sprintf("%v=%v", test.TestNamespaceEnv, tlConfig.namespace))
dc.Dir = mustGetwd()
dc.Stdout = os.Stdout
dc.Stderr = os.Stderr
Expand Down
1 change: 1 addition & 0 deletions doc/sdk-cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ Runs the tests locally
* `--kubeconfig` string - location of kubeconfig for kubernetes cluster (default "~/.kube/config")
* `--global-manifest` string - path to manifest for global resources (default "deploy/crd.yaml)
* `--namespaced-manifest` string - path to manifest for per-test, namespaced resources (default: combines deploy/sa.yaml, deploy/rbac.yaml, and deploy/operator.yaml)
* `--namespace` string - if non-empty, single namespace to run tests in (e.g. "operator-test") (default: "")
* `--go-test-flags` string - extra arguments to pass to `go test` (e.g. -f "-v -parallel=2")
* `-h, --help` - help for local

Expand Down
8 changes: 8 additions & 0 deletions doc/test-framework/writing-e2e-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,13 @@ as an argument. You can use `--help` to view the other configuration options and
$ operator-sdk test local ./test/e2e --go-test-flags "-v -parallel=2"
```

If you wish to run all the tests in 1 namespace (which also forces `-parallel=1`), you can use the `--namespace` flag:

```shell
$ kubectl create namespace operator-test
$ operator-sdk test local ./test/e2e --namespace operator-test
```

For more documentation on the `operator-sdk test local` command, see the [SDK CLI Reference][sdk-cli-ref] doc.

For advanced use cases, it is possible to run the tests via `go test` directly. As long as all flags defined
Expand Down Expand Up @@ -258,6 +265,7 @@ Test Successfully Completed
```

The `test cluster` command will deploy a test pod in the given namespace that will run the e2e tests packaged in the image.
The tests run sequentially in the namespace (`-parallel=1`), the same as running `operator-sdk test local --namespace <namespace>`.
The command will wait until the tests succeed (pod phase=`Succeeded`) or fail (pod phase=`Failed`).
If the tests fail, the command will output the test pod logs which should be the standard go test error logs.

Expand Down
3 changes: 3 additions & 0 deletions hack/ci/setup-openshift.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ tar xvzOf oc.tar.gz openshift-origin-client-tools-v3.10.0-dd10d17-linux-64bit/oc
oc cluster up
# Become cluster admin
oc login -u system:admin

# kubectl is needed for the single namespace local test
curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/v1.10.1/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/
22 changes: 15 additions & 7 deletions pkg/test/framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ type Framework struct {
DynamicClient dynclient.Client
DynamicDecoder runtime.Decoder
NamespacedManPath *string
InCluster bool
SingleNamespace bool
Namespace string
}

func setup(kubeconfigPath, namespacedManPath *string) error {
func setup(kubeconfigPath, namespacedManPath *string, singleNamespace *bool) error {
var err error
var kubeconfig *rest.Config
inCluster := false
if *kubeconfigPath == "incluster" {
// Work around https://github.com/kubernetes/kubernetes/issues/40973
if len(os.Getenv("KUBERNETES_SERVICE_HOST")) == 0 {
Expand All @@ -72,7 +72,7 @@ func setup(kubeconfigPath, namespacedManPath *string) error {
os.Setenv("KUBERNETES_SERVICE_PORT", "443")
}
kubeconfig, err = rest.InClusterConfig()
inCluster = true
*singleNamespace = true
} else {
kubeconfig, err = clientcmd.BuildConfigFromFlags("", *kubeconfigPath)
}
Expand All @@ -95,6 +95,13 @@ func setup(kubeconfigPath, namespacedManPath *string) error {
return fmt.Errorf("failed to build the dynamic client: %v", err)
}
dynDec := serializer.NewCodecFactory(scheme).UniversalDeserializer()
namespace := ""
if *singleNamespace {
namespace = os.Getenv(TestNamespaceEnv)
if len(namespace) == 0 {
return fmt.Errorf("namespace set in %s cannot be empty", TestNamespaceEnv)
}
}
Global = &Framework{
KubeConfig: kubeconfig,
KubeClient: kubeclient,
Expand All @@ -103,7 +110,8 @@ func setup(kubeconfigPath, namespacedManPath *string) error {
DynamicClient: dynClient,
DynamicDecoder: dynDec,
NamespacedManPath: namespacedManPath,
InCluster: inCluster,
SingleNamespace: *singleNamespace,
Namespace: namespace,
}
return nil
}
Expand Down Expand Up @@ -136,8 +144,8 @@ func AddToFrameworkScheme(addToScheme addToSchemeFunc, obj runtime.Object) error
Global.RestMapper.Reset()
Global.DynamicClient, err = dynclient.New(Global.KubeConfig, dynclient.Options{Scheme: Global.Scheme, Mapper: Global.RestMapper})
err = wait.PollImmediate(time.Second, time.Second*10, func() (done bool, err error) {
if Global.InCluster {
err = Global.DynamicClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: os.Getenv(TestNamespaceEnv)}, obj)
if Global.SingleNamespace {
err = Global.DynamicClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: Global.Namespace}, obj)
} else {
err = Global.DynamicClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: "default"}, obj)
}
Expand Down
6 changes: 4 additions & 2 deletions pkg/test/main_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
KubeConfigFlag = "kubeconfig"
NamespacedManPathFlag = "namespacedMan"
GlobalManPathFlag = "globalMan"
SingleNamespaceFlag = "singleNamespace"
TestNamespaceEnv = "TEST_NAMESPACE"
)

Expand All @@ -35,13 +36,14 @@ func MainEntry(m *testing.M) {
kubeconfigPath := flag.String(KubeConfigFlag, "", "path to kubeconfig")
globalManPath := flag.String(GlobalManPathFlag, "", "path to operator manifest")
namespacedManPath := flag.String(NamespacedManPathFlag, "", "path to rbac manifest")
singleNamespace := flag.Bool(SingleNamespaceFlag, false, "enable single namespace mode")
flag.Parse()
// go test always runs from the test directory; change to project root
err := os.Chdir(*projRoot)
if err != nil {
log.Fatalf("failed to change directory to project root: %v", err)
}
if err := setup(kubeconfigPath, namespacedManPath); err != nil {
if err := setup(kubeconfigPath, namespacedManPath, singleNamespace); err != nil {
log.Fatalf("failed to set up framework: %v", err)
}
// setup context to use when setting up crd
Expand All @@ -54,7 +56,7 @@ func MainEntry(m *testing.M) {
os.Exit(exitCode)
}()
// create crd
if !Global.InCluster {
if *kubeconfigPath != "incluster" {
globalYAML, err := ioutil.ReadFile(*globalManPath)
if err != nil {
log.Fatalf("failed to read global resource manifest: %v", err)
Expand Down
5 changes: 2 additions & 3 deletions pkg/test/resource_creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
goctx "context"
"fmt"
"io/ioutil"
"os"

yaml "gopkg.in/yaml.v2"
core "k8s.io/api/core/v1"
Expand All @@ -31,8 +30,8 @@ func (ctx *TestCtx) GetNamespace() (string, error) {
if ctx.Namespace != "" {
return ctx.Namespace, nil
}
if Global.InCluster {
ctx.Namespace = os.Getenv(TestNamespaceEnv)
if Global.SingleNamespace {
ctx.Namespace = Global.Namespace
return ctx.Namespace, nil
}
// create namespace
Expand Down