Skip to content

Commit 1a3f18b

Browse files
authored
commands,pkg/test: support single namespace mode for local test (#546)
* commands,pkg/test: support single namespace mode for local test This commit brings support for running the tests in a single namespace when using `test local`, which was previously only used by `test cluster` (where it was a requirement to properly function) * doc: update docs with new test local --namespace flag * pkg/test/resource_creator.go: check namespace env value * pkg/test: change handling of TestNamespaceEnv
1 parent 5c53f65 commit 1a3f18b

File tree

8 files changed

+48
-13
lines changed

8 files changed

+48
-13
lines changed

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ script:
3333
- operator-sdk test local .
3434
# test operator-sdk test flags
3535
- operator-sdk test local . --global-manifest deploy/crd.yaml --namespaced-manifest deploy/namespace-init.yaml --go-test-flags "-parallel 1" --kubeconfig $HOME/.kube/config
36+
# test operator-sdk test local single namespace mode
37+
- kubectl create namespace test-memcached
38+
- operator-sdk test local . --namespace=test-memcached
39+
- kubectl delete namespace test-memcached
3640
# go back to project root
3741
- cd ../..
3842
- go vet ./...

commands/operator-sdk/cmd/test/local.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type testLocalConfig struct {
3333
globalManPath string
3434
namespacedManPath string
3535
goTestFlags string
36+
namespace string
3637
}
3738

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

5658
return testCmd
5759
}
@@ -96,8 +98,16 @@ func testLocalFunc(cmd *cobra.Command, args []string) {
9698
testArgs = append(testArgs, "-"+test.NamespacedManPathFlag, tlConfig.namespacedManPath)
9799
testArgs = append(testArgs, "-"+test.GlobalManPathFlag, tlConfig.globalManPath)
98100
testArgs = append(testArgs, "-"+test.ProjRootFlag, mustGetwd())
99-
testArgs = append(testArgs, strings.Split(tlConfig.goTestFlags, " ")...)
101+
// if we do the append using an empty go flags, it inserts an empty arg, which causes
102+
// any later flags to be ignored
103+
if tlConfig.goTestFlags != "" {
104+
testArgs = append(testArgs, strings.Split(tlConfig.goTestFlags, " ")...)
105+
}
106+
if tlConfig.namespace != "" {
107+
testArgs = append(testArgs, "-"+test.SingleNamespaceFlag, "-parallel=1")
108+
}
100109
dc := exec.Command("go", testArgs...)
110+
dc.Env = append(os.Environ(), fmt.Sprintf("%v=%v", test.TestNamespaceEnv, tlConfig.namespace))
101111
dc.Dir = mustGetwd()
102112
dc.Stdout = os.Stdout
103113
dc.Stderr = os.Stderr

doc/sdk-cli-reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ Runs the tests locally
175175
* `--kubeconfig` string - location of kubeconfig for kubernetes cluster (default "~/.kube/config")
176176
* `--global-manifest` string - path to manifest for global resources (default "deploy/crd.yaml)
177177
* `--namespaced-manifest` string - path to manifest for per-test, namespaced resources (default: combines deploy/sa.yaml, deploy/rbac.yaml, and deploy/operator.yaml)
178+
* `--namespace` string - if non-empty, single namespace to run tests in (e.g. "operator-test") (default: "")
178179
* `--go-test-flags` string - extra arguments to pass to `go test` (e.g. -f "-v -parallel=2")
179180
* `-h, --help` - help for local
180181

doc/test-framework/writing-e2e-tests.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,13 @@ as an argument. You can use `--help` to view the other configuration options and
207207
$ operator-sdk test local ./test/e2e --go-test-flags "-v -parallel=2"
208208
```
209209

210+
If you wish to run all the tests in 1 namespace (which also forces `-parallel=1`), you can use the `--namespace` flag:
211+
212+
```shell
213+
$ kubectl create namespace operator-test
214+
$ operator-sdk test local ./test/e2e --namespace operator-test
215+
```
216+
210217
For more documentation on the `operator-sdk test local` command, see the [SDK CLI Reference][sdk-cli-ref] doc.
211218

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

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

hack/ci/setup-openshift.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ tar xvzOf oc.tar.gz openshift-origin-client-tools-v3.10.0-dd10d17-linux-64bit/oc
1010
oc cluster up
1111
# Become cluster admin
1212
oc login -u system:admin
13+
14+
# kubectl is needed for the single namespace local test
15+
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/

pkg/test/framework.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@ type Framework struct {
5252
DynamicClient dynclient.Client
5353
DynamicDecoder runtime.Decoder
5454
NamespacedManPath *string
55-
InCluster bool
55+
SingleNamespace bool
56+
Namespace string
5657
}
5758

58-
func setup(kubeconfigPath, namespacedManPath *string) error {
59+
func setup(kubeconfigPath, namespacedManPath *string, singleNamespace *bool) error {
5960
var err error
6061
var kubeconfig *rest.Config
61-
inCluster := false
6262
if *kubeconfigPath == "incluster" {
6363
// Work around https://github.com/kubernetes/kubernetes/issues/40973
6464
if len(os.Getenv("KUBERNETES_SERVICE_HOST")) == 0 {
@@ -72,7 +72,7 @@ func setup(kubeconfigPath, namespacedManPath *string) error {
7272
os.Setenv("KUBERNETES_SERVICE_PORT", "443")
7373
}
7474
kubeconfig, err = rest.InClusterConfig()
75-
inCluster = true
75+
*singleNamespace = true
7676
} else {
7777
kubeconfig, err = clientcmd.BuildConfigFromFlags("", *kubeconfigPath)
7878
}
@@ -95,6 +95,13 @@ func setup(kubeconfigPath, namespacedManPath *string) error {
9595
return fmt.Errorf("failed to build the dynamic client: %v", err)
9696
}
9797
dynDec := serializer.NewCodecFactory(scheme).UniversalDeserializer()
98+
namespace := ""
99+
if *singleNamespace {
100+
namespace = os.Getenv(TestNamespaceEnv)
101+
if len(namespace) == 0 {
102+
return fmt.Errorf("namespace set in %s cannot be empty", TestNamespaceEnv)
103+
}
104+
}
98105
Global = &Framework{
99106
KubeConfig: kubeconfig,
100107
KubeClient: kubeclient,
@@ -103,7 +110,8 @@ func setup(kubeconfigPath, namespacedManPath *string) error {
103110
DynamicClient: dynClient,
104111
DynamicDecoder: dynDec,
105112
NamespacedManPath: namespacedManPath,
106-
InCluster: inCluster,
113+
SingleNamespace: *singleNamespace,
114+
Namespace: namespace,
107115
}
108116
return nil
109117
}
@@ -136,8 +144,8 @@ func AddToFrameworkScheme(addToScheme addToSchemeFunc, obj runtime.Object) error
136144
Global.RestMapper.Reset()
137145
Global.DynamicClient, err = dynclient.New(Global.KubeConfig, dynclient.Options{Scheme: Global.Scheme, Mapper: Global.RestMapper})
138146
err = wait.PollImmediate(time.Second, time.Second*10, func() (done bool, err error) {
139-
if Global.InCluster {
140-
err = Global.DynamicClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: os.Getenv(TestNamespaceEnv)}, obj)
147+
if Global.SingleNamespace {
148+
err = Global.DynamicClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: Global.Namespace}, obj)
141149
} else {
142150
err = Global.DynamicClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: "default"}, obj)
143151
}

pkg/test/main_entry.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const (
2727
KubeConfigFlag = "kubeconfig"
2828
NamespacedManPathFlag = "namespacedMan"
2929
GlobalManPathFlag = "globalMan"
30+
SingleNamespaceFlag = "singleNamespace"
3031
TestNamespaceEnv = "TEST_NAMESPACE"
3132
)
3233

@@ -35,13 +36,14 @@ func MainEntry(m *testing.M) {
3536
kubeconfigPath := flag.String(KubeConfigFlag, "", "path to kubeconfig")
3637
globalManPath := flag.String(GlobalManPathFlag, "", "path to operator manifest")
3738
namespacedManPath := flag.String(NamespacedManPathFlag, "", "path to rbac manifest")
39+
singleNamespace := flag.Bool(SingleNamespaceFlag, false, "enable single namespace mode")
3840
flag.Parse()
3941
// go test always runs from the test directory; change to project root
4042
err := os.Chdir(*projRoot)
4143
if err != nil {
4244
log.Fatalf("failed to change directory to project root: %v", err)
4345
}
44-
if err := setup(kubeconfigPath, namespacedManPath); err != nil {
46+
if err := setup(kubeconfigPath, namespacedManPath, singleNamespace); err != nil {
4547
log.Fatalf("failed to set up framework: %v", err)
4648
}
4749
// setup context to use when setting up crd
@@ -54,7 +56,7 @@ func MainEntry(m *testing.M) {
5456
os.Exit(exitCode)
5557
}()
5658
// create crd
57-
if !Global.InCluster {
59+
if *kubeconfigPath != "incluster" {
5860
globalYAML, err := ioutil.ReadFile(*globalManPath)
5961
if err != nil {
6062
log.Fatalf("failed to read global resource manifest: %v", err)

pkg/test/resource_creator.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
goctx "context"
2020
"fmt"
2121
"io/ioutil"
22-
"os"
2322

2423
yaml "gopkg.in/yaml.v2"
2524
core "k8s.io/api/core/v1"
@@ -31,8 +30,8 @@ func (ctx *TestCtx) GetNamespace() (string, error) {
3130
if ctx.Namespace != "" {
3231
return ctx.Namespace, nil
3332
}
34-
if Global.InCluster {
35-
ctx.Namespace = os.Getenv(TestNamespaceEnv)
33+
if Global.SingleNamespace {
34+
ctx.Namespace = Global.Namespace
3635
return ctx.Namespace, nil
3736
}
3837
// create namespace

0 commit comments

Comments
 (0)