diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index ca4589ac0..10286efa9 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -34,14 +34,33 @@ jobs: with: version: "v0.7.0" - - name: Run Make - run: make + # TODO(zzxwill) This step and the follow one need to be removed after rudr admin:init is ready + - name: Install OAM runtime and prepare WorkloadDefinitions/TraitDefinitions + run: | + sudo apt-get install -y apt-transport-https gnupg2 + curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - + echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee -a /etc/apt/sources.list.d/kubernetes.list + sudo apt-get install -y kubectl - - name: Run Make test - run: make test + curl https://helm.baltorepo.com/organization/signing.asc | sudo apt-key add - + sudo apt-get install apt-transport-https --yes + echo "deb https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list + sudo apt-get update + sudo apt-get install helm + kubectl create namespace oam-system + helm repo add crossplane-master https://charts.crossplane.io/master/ + helm install oam --namespace oam-system crossplane-master/oam-kubernetes-runtime --devel + + kubectl apply -R -f config/samples - name: Run e2e tests run: | make e2e-setup make e2e-test - make e2e-cleanup \ No newline at end of file + make e2e-cleanup + + - name: Run Make + run: make + + - name: Run Make test + run: make test diff --git a/.gitignore b/.gitignore index ab35b85b1..cf2f71b49 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *.so *.dylib bin +e2e/vela # Test binary, build with `go test -c` *.test diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 268f6116c..bccacfed3 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -9,6 +9,7 @@ contributing to `RudrX` or build a PoC (Proof of Concept). 2. Kubernetes version v1.15+ with `~/.kube/config` configured. 3. OAM Kubernetes Runtime installed. 4. Kustomize version 3.8+ +5. ginkgo 1.14.0+ (just for [E2E test](https://github.com/cloud-native-application/RudrX/blob/master/DEVELOPMENT.md#e2e-test)) ## Build * Clone this project @@ -64,10 +65,49 @@ workloaddefinition.core.oam.dev/deployments.apps deployment workloaddefinition.core.oam.dev/statefulsets.apps statefulsets.apps ``` -## Test +## E2E test ``` -$ go test ./pkg/test -ok github.com/cloud-native-application/vela/pkg/test 14.662s +$ make e2e-test +Running Suite: Trait Suite +========================== +Random Seed: 1596559178 +Will run 5 of 5 specs + +Trait env init + should print env initiation successful message + /Users/zhouzhengxi/Programming/golang/src/github.com/zzxwill/RudrX/e2e/commonContext.go:14 +Create env succeed, current env is default +• +------------------------------ +Trait env switch + should show env switch message + /Users/zhouzhengxi/Programming/golang/src/github.com/zzxwill/RudrX/e2e/commonContext.go:40 +Switch env succeed, current env is default +• +------------------------------ +Trait run + should print successful creation information + /Users/zhouzhengxi/Programming/golang/src/github.com/zzxwill/RudrX/e2e/commonContext.go:76 +Creating AppConfig app-trait-basic +SUCCEED +• +------------------------------ +Trait rudr attach trait + should print successful attached information + /Users/zhouzhengxi/Programming/golang/src/github.com/zzxwill/RudrX/e2e/trait/trait_test.go:24 +Applying trait for app +Succeeded! +• +------------------------------ +Trait delete + should print successful deletion information + /Users/zhouzhengxi/Programming/golang/src/github.com/zzxwill/RudrX/e2e/commonContext.go:85 +Deleting AppConfig "app-trait-basic" +DELETE SUCCEED +• +Ran 5 of 5 Specs in 9.717 seconds +SUCCESS! -- 5 Passed | 0 Failed | 0 Pending | 0 Skipped +PASS ``` ## Make a pull request diff --git a/Makefile b/Makefile index c78a86ae5..51c94a56f 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ all: build # Run tests test: fmt vet - go test ./... -coverprofile cover.out + go test ./pkg/... -coverprofile cover.out # Build manager binary build: fmt vet @@ -45,7 +45,7 @@ e2e-setup: e2e-test: # Run e2e test - go test ./pkg/test + ginkgo -v -r e2e e2e-cleanup: # Clean up \ No newline at end of file diff --git a/e2e/application/application_suite_test.go b/e2e/application/application_suite_test.go new file mode 100644 index 000000000..ee64504bc --- /dev/null +++ b/e2e/application/application_suite_test.go @@ -0,0 +1,23 @@ +package e2e + +import ( + "testing" + + "github.com/cloud-native-application/rudrx/e2e" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" +) + +var rudrPath string + +var _ = ginkgo.BeforeSuite(func() { + p, err := e2e.GetCliBinary() + rudrPath = p + gomega.Expect(err).NotTo(gomega.HaveOccurred()) +}) + +func TestApplication(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + ginkgo.RunSpecs(t, "Application Suite") +} diff --git a/e2e/application/application_test.go b/e2e/application/application_test.go new file mode 100644 index 000000000..e16baa4b8 --- /dev/null +++ b/e2e/application/application_test.go @@ -0,0 +1,33 @@ +package e2e + +import ( + "fmt" + + "github.com/cloud-native-application/rudrx/e2e" + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" +) + +var ( + envName = "env-application" + applicationName = "app-ls-basic" +) + +var _ = ginkgo.Describe("Application", func() { + e2e.RefreshContext("refresh") + e2e.EnvInitContext("env init", envName) + e2e.EnvShowContext("env show", envName) + e2e.EnvSwitchContext("env switch", envName) + e2e.WorkloadRunContext("run", fmt.Sprintf("vela containerized:run %s -p 80 --image nginx:1.9.4", applicationName)) + + ginkgo.Context("ls", func() { + ginkgo.It("should list all applications", func() { + output, err := e2e.Exec("vela app:ls") + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(output).To(gomega.ContainSubstring("NAME")) + gomega.Expect(output).To(gomega.ContainSubstring(applicationName)) + }) + }) + + e2e.WorkloadDeleteContext("delete", applicationName) +}) diff --git a/e2e/cli.go b/e2e/cli.go new file mode 100644 index 000000000..2b491a1d7 --- /dev/null +++ b/e2e/cli.go @@ -0,0 +1,44 @@ +package e2e + +import ( + "os" + "os/exec" + "path" + "strings" + "time" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega/gexec" +) + +var ( + rudrPath = "" +) + +//GetCliBinary is to build rudr binary. +func GetCliBinary() (string, error) { + // TODO(zzxwill) Need to check before building from scratch every time + cwd, _ := os.Getwd() + rudrPath = path.Join(cwd, "..") + mainPath := path.Join(rudrPath, "../cmd/vela/main.go") + cmd := exec.Command("go", "build", "-o", path.Join(rudrPath, "vela"), mainPath) + + _, err := cmd.Output() + return rudrPath, err + +} + +func Exec(cli string) (string, error) { + c := strings.Fields(cli) + commandName := path.Join(rudrPath, c[0]) + command := exec.Command(commandName, c[1:]...) + + var output []byte + session, err := gexec.Start(command, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter) + if err != nil { + return string(output), err + } + s := session.Wait(10 * time.Second) + return string(s.Out.Contents()) + string(s.Err.Contents()), nil + +} diff --git a/e2e/commonContext.go b/e2e/commonContext.go new file mode 100644 index 000000000..8f1a78372 --- /dev/null +++ b/e2e/commonContext.go @@ -0,0 +1,105 @@ +package e2e + +import ( + "fmt" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" +) + +var ( + // Refresh + RefreshContext = func(context string) bool { + return ginkgo.Context(context, func() { + ginkgo.It("Sync commands from your Kubernetes cluster and locally cached them", func() { + output, err := Exec("vela refresh") + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(output).To(gomega.ContainSubstring("syncing workload definitions from cluster...")) + gomega.Expect(output).To(gomega.ContainSubstring("successfully synced")) + }) + }) + } + + // Env + EnvInitContext = func(context string, envName string) bool { + return ginkgo.Context(context, func() { + ginkgo.It("should print env initiation successful message", func() { + cli := fmt.Sprintf("vela env:init %s --namespace %s", envName, envName) + output, err := Exec(cli) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + expectedOutput := fmt.Sprintf("Create env succeed, current env is %s", envName) + gomega.Expect(output).To(gomega.ContainSubstring(expectedOutput)) + }) + }) + } + + EnvShowContext = func(context string, envName string) bool { + return ginkgo.Context(context, func() { + ginkgo.It("should show detailed env message", func() { + cli := fmt.Sprintf("vela env %s", envName) + output, err := Exec(cli) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + expectedOutput := fmt.Sprintf("%s\t%s", envName, envName) + gomega.Expect(output).To(gomega.ContainSubstring("NAME")) + gomega.Expect(output).To(gomega.ContainSubstring("NAMESPACE")) + gomega.Expect(output).To(gomega.ContainSubstring(expectedOutput)) + }) + }) + } + + EnvSwitchContext = func(context string, envName string) bool { + return ginkgo.Context(context, func() { + ginkgo.It("should show env switch message", func() { + cli := fmt.Sprintf("vela env:sw %s", envName) + output, err := Exec(cli) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + expectedOutput := fmt.Sprintf("Switch env succeed, current env is %s", envName) + gomega.Expect(output).To(gomega.ContainSubstring(expectedOutput)) + }) + }) + } + + EnvDeleteContext = func(context string, envName string) bool { + return ginkgo.Context(context, func() { + ginkgo.It("should delete all envs", func() { + cli := fmt.Sprintf("vela env:delete %s", envName) + output, err := Exec(cli) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + expectedOutput := fmt.Sprintf("%s deleted", envName) + gomega.Expect(output).To(gomega.ContainSubstring(expectedOutput)) + }) + }) + } + EnvDeleteCurrentUsingContext = func(context string, envName string) bool { + return ginkgo.Context(context, func() { + ginkgo.It("should delete all envs", func() { + cli := fmt.Sprintf("vela env:delete %s", envName) + output, err := Exec(cli) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + expectedOutput := fmt.Sprintf("Error: you can't delete current using env %s", envName) + gomega.Expect(output).To(gomega.ContainSubstring(expectedOutput)) + }) + }) + } + + //Workload + WorkloadRunContext = func(context string, cli string) bool { + return ginkgo.Context(context, func() { + ginkgo.It("should print successful creation information", func() { + output, err := Exec(cli) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(output).To(gomega.ContainSubstring("SUCCEED")) + }) + }) + } + WorkloadDeleteContext = func(context string, applicationName string) bool { + return ginkgo.Context(context, func() { + ginkgo.It("should print successful deletion information", func() { + cli := fmt.Sprintf("vela app:delete %s", applicationName) + output, err := Exec(cli) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(output).To(gomega.ContainSubstring("DELETE SUCCEED")) + }) + }) + } +) diff --git a/e2e/env/env_suite_test.go b/e2e/env/env_suite_test.go new file mode 100644 index 000000000..82f0456cf --- /dev/null +++ b/e2e/env/env_suite_test.go @@ -0,0 +1,23 @@ +package e2e + +import ( + "testing" + + "github.com/cloud-native-application/rudrx/e2e" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" +) + +var rudrPath string + +var _ = ginkgo.BeforeSuite(func() { + p, err := e2e.GetCliBinary() + rudrPath = p + gomega.Expect(err).NotTo(gomega.HaveOccurred()) +}) + +func TestEnv(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + ginkgo.RunSpecs(t, "Env Suite") +} diff --git a/e2e/env/env_test.go b/e2e/env/env_test.go new file mode 100644 index 000000000..3c933256b --- /dev/null +++ b/e2e/env/env_test.go @@ -0,0 +1,34 @@ +package e2e + +import ( + "github.com/cloud-native-application/rudrx/e2e" + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" +) + +var ( + envName = "env-hello" + envName2 = "env-world" +) + +var _ = ginkgo.Describe("Env", func() { + e2e.RefreshContext("refresh") + e2e.EnvInitContext("env init", envName) + e2e.EnvInitContext("env init another one", envName2) + e2e.EnvShowContext("env show", envName) + e2e.EnvSwitchContext("env sw", envName) + + ginkgo.Context("env list", func() { + ginkgo.It("should list all envs", func() { + output, err := e2e.Exec("vela env") + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(output).To(gomega.ContainSubstring("NAME")) + gomega.Expect(output).To(gomega.ContainSubstring("NAMESPACE")) + gomega.Expect(output).To(gomega.ContainSubstring(envName)) + gomega.Expect(output).To(gomega.ContainSubstring(envName2)) + }) + }) + + e2e.EnvDeleteContext("env delete", envName2) + e2e.EnvDeleteCurrentUsingContext("env delete currently using one", envName) +}) diff --git a/e2e/trait/trait_suite_test.go b/e2e/trait/trait_suite_test.go new file mode 100644 index 000000000..7b97fd4bd --- /dev/null +++ b/e2e/trait/trait_suite_test.go @@ -0,0 +1,23 @@ +package e2e + +import ( + "testing" + + "github.com/cloud-native-application/rudrx/e2e" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" +) + +var rudrPath string + +var _ = ginkgo.BeforeSuite(func() { + p, err := e2e.GetCliBinary() + rudrPath = p + gomega.Expect(err).NotTo(gomega.HaveOccurred()) +}) + +func TestEnv(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + ginkgo.RunSpecs(t, "Trait Suite") +} diff --git a/e2e/trait/trait_test.go b/e2e/trait/trait_test.go new file mode 100644 index 000000000..07640230b --- /dev/null +++ b/e2e/trait/trait_test.go @@ -0,0 +1,42 @@ +package e2e + +import ( + "fmt" + + "github.com/cloud-native-application/rudrx/e2e" + "github.com/onsi/ginkgo" +) + +var ( + envName = "env-trait" + applicationName = "app-trait-basic" +) + +var _ = ginkgo.Describe("Trait", func() { + e2e.RefreshContext("refresh") + e2e.EnvInitContext("env init", envName) + e2e.EnvSwitchContext("env switch", envName) + e2e.WorkloadRunContext("run", fmt.Sprintf("vela containerized:run %s -p 80 --image nginx:1.9.4", applicationName)) + + //ginkgo.Context("vela attach trait", func() { + // ginkgo.It("should print successful attached information", func() { + // cli := fmt.Sprintf("vela manualscaler %s --replicaCount 4", applicationName) + // output, err := e2e.Exec(cli) + // gomega.Expect(err).NotTo(gomega.HaveOccurred()) + // gomega.Expect(output).To(gomega.ContainSubstring("Applying trait for app")) + // gomega.Expect(output).To(gomega.ContainSubstring("Succeeded")) + // }) + //}) + + //ginkgo.Context("vela detach trait", func() { + // ginkgo.It("should print successful detached information", func() { + // cli := fmt.Sprintf("vela ManualScaler %s --detach", applicationName) + // output, err := e2e.Exec(cli) + // gomega.Expect(err).NotTo(gomega.HaveOccurred()) + // gomega.Expect(output).To(gomega.ContainSubstring("Applying trait for app")) + // gomega.Expect(output).To(gomega.ContainSubstring("Succeeded")) + // }) + //}) + + e2e.WorkloadDeleteContext("delete", applicationName) +}) diff --git a/e2e/workload/workload_suite_test.go b/e2e/workload/workload_suite_test.go new file mode 100644 index 000000000..224cb673c --- /dev/null +++ b/e2e/workload/workload_suite_test.go @@ -0,0 +1,23 @@ +package e2e + +import ( + "testing" + + "github.com/cloud-native-application/rudrx/e2e" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" +) + +var rudrPath string + +var _ = ginkgo.BeforeSuite(func() { + p, err := e2e.GetCliBinary() + rudrPath = p + gomega.Expect(err).NotTo(gomega.HaveOccurred()) +}) + +func TestWorkload(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + ginkgo.RunSpecs(t, "Workload Suite") +} diff --git a/e2e/workload/workload_test.go b/e2e/workload/workload_test.go new file mode 100644 index 000000000..80b1fb090 --- /dev/null +++ b/e2e/workload/workload_test.go @@ -0,0 +1,21 @@ +package e2e + +import ( + "fmt" + + "github.com/cloud-native-application/rudrx/e2e" + "github.com/onsi/ginkgo" +) + +var ( + envName = "env-workload" + applicationName = "app-testworkloadrun-basic" +) + +var _ = ginkgo.Describe("Workload", func() { + e2e.RefreshContext("refresh") + e2e.EnvInitContext("env init", envName) + e2e.EnvSwitchContext("env switch", envName) + e2e.WorkloadRunContext("run", fmt.Sprintf("vela containerized:run %s -p 80 --image nginx:1.9.4", applicationName)) + e2e.WorkloadDeleteContext("delete", applicationName) +}) diff --git a/go.sum b/go.sum index bbf594583..a3e7f4cfe 100644 --- a/go.sum +++ b/go.sum @@ -565,6 +565,7 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.4.0 h1:LUa41nrWTQNGhzdsZ5lTnkwbNjj6rXTdazA1cSdjkOY= github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.6.0 h1:IZRgg4sfrDH7nsAD1Y/Nwj+GzIfEwpJSLjCaNC3SbsI= github.com/rogpeppe/go-internal v1.6.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rubenv/sql-migrate v0.0.0-20200212082348-64f95ea68aa3 h1:xkBtI5JktwbW/vf4vopBbhYsRFTGfQWHYXzC0/qYwxI= diff --git a/pkg/cmd/util/helpers.go b/pkg/cmd/util/helpers.go index 7606b80d4..9468f411e 100644 --- a/pkg/cmd/util/helpers.go +++ b/pkg/cmd/util/helpers.go @@ -126,29 +126,30 @@ func RetrieveApplicationsByName(ctx context.Context, c client.Client, applicatio } for _, a := range applicationList.Items { - componentName := a.Spec.Components[0].ComponentName - - component, err := GetComponent(ctx, c, componentName, namespace) - if err != nil { - return applicationMetaList, err - } - - var workload corev1alpha2.WorkloadDefinition - if err := json.Unmarshal(component.Spec.Workload.Raw, &workload); err == nil { - workloadName := workload.TypeMeta.Kind - - traitNames := GetTraitNamesByApplicationConfiguration(a) - - applicationMetaList = append(applicationMetaList, ApplicationMeta{ - Name: a.Name, - Workload: workloadName, - Traits: traitNames, - Status: string(a.Status.Conditions[0].Status), - CreatedTime: a.ObjectMeta.CreationTimestamp.String(), - }) + for _, com := range a.Spec.Components { + componentName := com.ComponentName + component, err := GetComponent(ctx, c, componentName, namespace) + if err != nil { + return applicationMetaList, err + } + var workload corev1alpha2.WorkloadDefinition + if err := json.Unmarshal(component.Spec.Workload.Raw, &workload); err == nil { + workloadName := workload.TypeMeta.Kind + traitNames := GetTraitNamesByApplicationConfiguration(a) + var status = "UNKNOWN" + if len(a.Status.Conditions) != 0 { + status = string(a.Status.Conditions[0].Status) + } + applicationMetaList = append(applicationMetaList, ApplicationMeta{ + Name: a.Name, + Workload: workloadName, + Traits: traitNames, + Status: status, + CreatedTime: a.ObjectMeta.CreationTimestamp.String(), + }) + } } } - return applicationMetaList, nil }