mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 04:26:39 +00:00
Implement Workload Run API
implemented workload run api and write e2e test cases for workload run cli and API
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/e2e"
|
||||
|
||||
@@ -19,15 +20,33 @@ import (
|
||||
"github.com/onsi/ginkgo"
|
||||
)
|
||||
|
||||
var envHelloMeta = types.EnvMeta{
|
||||
Name: "env-e2e-hello",
|
||||
Namespace: "env-e2e-hello",
|
||||
}
|
||||
var (
|
||||
envHelloMeta = types.EnvMeta{
|
||||
Name: "env-e2e-hello",
|
||||
Namespace: "env-e2e-hello",
|
||||
}
|
||||
|
||||
var envWorldMeta = types.EnvMeta{
|
||||
Name: "env-e2e-world",
|
||||
Namespace: "env-e2e-world",
|
||||
}
|
||||
envWorldMeta = types.EnvMeta{
|
||||
Name: "env-e2e-world",
|
||||
Namespace: "env-e2e-world",
|
||||
}
|
||||
|
||||
workloadType = "containerized"
|
||||
workloadName = "app-e2e-api-hello"
|
||||
|
||||
workloadRunBodyWithoutImageFlag = apis.WorkloadRunBody{
|
||||
EnvName: envHelloMeta.Name,
|
||||
WorkloadName: workloadName,
|
||||
WorkloadType: workloadType,
|
||||
Flags: []apis.WorkloadFlag{{Name: "port", Value: "80"}},
|
||||
}
|
||||
workloadRunBody = apis.WorkloadRunBody{
|
||||
EnvName: envHelloMeta.Name,
|
||||
WorkloadName: workloadName,
|
||||
WorkloadType: workloadType,
|
||||
Flags: []apis.WorkloadFlag{{Name: "image", Value: "nginx:1.9.4"}, {Name: "port", Value: "80"}},
|
||||
}
|
||||
)
|
||||
|
||||
var notExistedEnvMeta = types.EnvMeta{
|
||||
Name: "env-e2e-api-NOT-EXISTED-JUST-FOR-TEST",
|
||||
@@ -119,3 +138,38 @@ var _ = ginkgo.Describe("API Env", func() {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = ginkgo.Describe("API Workload", func() {
|
||||
|
||||
ginkgo.Context("Post /workloads/", func() {
|
||||
ginkgo.It("run workload", func() {
|
||||
data, err := json.Marshal(&workloadRunBody)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
resp, err := http.Post(util.URL("/workloads/"), "application/json", strings.NewReader(string(data)))
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
defer resp.Body.Close()
|
||||
result, err := ioutil.ReadAll(resp.Body)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
var r apis.Response
|
||||
err = json.Unmarshal(result, &r)
|
||||
gomega.Expect(http.StatusOK).Should(gomega.Equal(r.Code))
|
||||
output := fmt.Sprintf("Creating App %s\nSUCCEED", workloadName)
|
||||
gomega.Expect(r.Data.(string)).To(gomega.ContainSubstring(output))
|
||||
})
|
||||
|
||||
ginkgo.It("run workload without compulsory flag", func() {
|
||||
data, err := json.Marshal(&workloadRunBodyWithoutImageFlag)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
resp, err := http.Post(util.URL("/workloads/"), "application/json", strings.NewReader(string(data)))
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
defer resp.Body.Close()
|
||||
result, err := ioutil.ReadAll(resp.Body)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
var r apis.Response
|
||||
err = json.Unmarshal(result, &r)
|
||||
gomega.Expect(http.StatusOK).Should(gomega.Equal(r.Code))
|
||||
output := fmt.Sprintf("required flag(s) \"image\" not set")
|
||||
gomega.Expect(r.Data.(string)).To(gomega.ContainSubstring(output))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
var (
|
||||
envName = "env-application"
|
||||
workloadType = "containerized"
|
||||
applicationName = "app-basic"
|
||||
traitAlias = "scale"
|
||||
)
|
||||
@@ -17,10 +18,12 @@ var _ = ginkgo.Describe("Application", func() {
|
||||
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))
|
||||
e2e.WorkloadRunContext("run", fmt.Sprintf("vela %s:run %s -p 80 --image nginx:1.9.4",
|
||||
workloadType, applicationName))
|
||||
e2e.ApplicationListContext("app ls", applicationName, "")
|
||||
e2e.TraitManualScalerAttachContext("vela attach trait", traitAlias, applicationName)
|
||||
//e2e.ApplicationListContext("app ls", applicationName, traitAlias)
|
||||
e2e.ApplicationStatusContext("app status", applicationName)
|
||||
e2e.ApplicationShowContext("app show", applicationName, workloadType)
|
||||
e2e.ApplicationStatusContext("app status", applicationName, workloadType)
|
||||
e2e.WorkloadDeleteContext("delete", applicationName)
|
||||
})
|
||||
|
||||
+17
-1
@@ -134,6 +134,7 @@ var (
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
WorkloadDeleteContext = func(context string, applicationName string) bool {
|
||||
return ginkgo.Context(context, func() {
|
||||
ginkgo.It("should print successful deletion information", func() {
|
||||
@@ -173,18 +174,33 @@ var (
|
||||
})
|
||||
}
|
||||
|
||||
ApplicationStatusContext = func(context string, applicationName string) bool {
|
||||
ApplicationStatusContext = func(context string, applicationName string, workloadType string) bool {
|
||||
return ginkgo.Context(context, func() {
|
||||
ginkgo.It("should get status for the application", func() {
|
||||
cli := fmt.Sprintf("vela app:status %s", applicationName)
|
||||
output, err := Exec(cli)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
gomega.Expect(output).To(gomega.ContainSubstring(applicationName))
|
||||
// TODO(zzxwill) need to check workloadType after app:status is refined
|
||||
//gomega.Expect(output).To(gomega.ContainSubstring(workloadType))
|
||||
gomega.Expect(output).To(gomega.ContainSubstring("Workload"))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
ApplicationShowContext = func(context string, applicationName string, workloadType string) bool {
|
||||
return ginkgo.Context(context, func() {
|
||||
ginkgo.It("should show app information", func() {
|
||||
cli := fmt.Sprintf("vela app:show %s", applicationName)
|
||||
output, err := Exec(cli)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
// TODO(zzxwill) need to check workloadType after app:show is refined
|
||||
//gomega.Expect(output).To(gomega.ContainSubstring(workloadType))
|
||||
gomega.Expect(output).To(gomega.ContainSubstring(applicationName))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// APIServer
|
||||
APIEnvInitContext = func(context string, envMeta types.EnvMeta) bool {
|
||||
return ginkgo.Context("Post /envs/", func() {
|
||||
|
||||
@@ -5,11 +5,13 @@ import (
|
||||
|
||||
"github.com/cloud-native-application/rudrx/e2e"
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var (
|
||||
envName = "env-workload"
|
||||
applicationName = "app-testworkloadrun-basic"
|
||||
envName = "env-workload"
|
||||
applicationName = "app-workload-basic"
|
||||
notEnoughFlagsApplicationName = "app-workload-basic"
|
||||
)
|
||||
|
||||
var _ = ginkgo.Describe("Workload", func() {
|
||||
@@ -17,5 +19,15 @@ var _ = ginkgo.Describe("Workload", func() {
|
||||
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("run without enough flags", func() {
|
||||
ginkgo.It("should throw error message: some flags are NOT set", func() {
|
||||
cli := fmt.Sprintf("vela containerized:run %s -p 80", notEnoughFlagsApplicationName)
|
||||
output, err := e2e.Exec(cli)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
gomega.Expect(output).To(gomega.ContainSubstring("required flag(s) \"image\" not set"))
|
||||
})
|
||||
})
|
||||
|
||||
e2e.WorkloadDeleteContext("delete", applicationName)
|
||||
})
|
||||
|
||||
@@ -16,6 +16,7 @@ require (
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.6.1
|
||||
go.uber.org/zap v1.10.0
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||
|
||||
+17
-64
@@ -3,15 +3,13 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/pkg/oam"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/api/types"
|
||||
"github.com/cloud-native-application/rudrx/pkg/application"
|
||||
"github.com/cloud-native-application/rudrx/pkg/cmd/util"
|
||||
"github.com/cloud-native-application/rudrx/pkg/plugins"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"github.com/spf13/cobra"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
@@ -19,16 +17,7 @@ import (
|
||||
const Staging = "staging"
|
||||
const App = "app"
|
||||
|
||||
type runOptions struct {
|
||||
Template types.Capability
|
||||
Env *types.EnvMeta
|
||||
workloadName string
|
||||
client client.Client
|
||||
app *application.Application
|
||||
appName string
|
||||
staging bool
|
||||
util.IOStreams
|
||||
}
|
||||
type runOptions oam.RunOptions
|
||||
|
||||
func newRunOptions(ioStreams util.IOStreams) *runOptions {
|
||||
return &runOptions{IOStreams: ioStreams}
|
||||
@@ -53,11 +42,12 @@ func AddWorkloadCommands(parentCmd *cobra.Command, c types.Args, ioStreams util.
|
||||
Example: "vela " + name + ":run frontend",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
o := newRunOptions(ioStreams)
|
||||
o.WorkloadType = name
|
||||
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.client = newClient
|
||||
o.KubeClient = newClient
|
||||
o.Env, err = GetEnv(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -89,48 +79,15 @@ func (o *runOptions) Complete(cmd *cobra.Command, args []string, ctx context.Con
|
||||
if argsLength < 1 {
|
||||
return errors.New("must specify name for workload")
|
||||
}
|
||||
o.workloadName = args[0]
|
||||
if app := cmd.Flag(App).Value.String(); app != "" {
|
||||
o.appName = app
|
||||
} else {
|
||||
o.appName = o.workloadName
|
||||
}
|
||||
app, err := application.Load(o.Env.Name, o.appName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
app.Name = o.appName
|
||||
workloadName := args[0]
|
||||
template := o.Template
|
||||
appGroup := cmd.Flag(App).Value.String()
|
||||
|
||||
if app.Components == nil {
|
||||
app.Components = make(map[string]map[string]interface{})
|
||||
}
|
||||
tp, workloadData := app.GetWorkload(o.workloadName)
|
||||
if tp == "" {
|
||||
// Not exist
|
||||
tp = o.Template.Name
|
||||
}
|
||||
|
||||
for _, v := range o.Template.Parameters {
|
||||
flagSet := cmd.Flag(v.Name)
|
||||
switch v.Type {
|
||||
case cue.IntKind:
|
||||
d, _ := strconv.ParseInt(flagSet.Value.String(), 10, 64)
|
||||
workloadData[v.Name] = d
|
||||
case cue.StringKind:
|
||||
workloadData[v.Name] = flagSet.Value.String()
|
||||
case cue.BoolKind:
|
||||
d, _ := strconv.ParseBool(flagSet.Value.String())
|
||||
workloadData[v.Name] = d
|
||||
case cue.NumberKind, cue.FloatKind:
|
||||
d, _ := strconv.ParseFloat(flagSet.Value.String(), 64)
|
||||
workloadData[v.Name] = d
|
||||
}
|
||||
}
|
||||
if err = app.SetWorkload(o.workloadName, tp, workloadData); err != nil {
|
||||
return err
|
||||
}
|
||||
o.app = app
|
||||
return app.Save(o.Env.Name, o.appName)
|
||||
envName := o.Env.Name
|
||||
var flagSet = cmd.Flags()
|
||||
app, err := oam.BaseComplete(envName, workloadName, appGroup, flagSet, template)
|
||||
o.App = app
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *runOptions) Run(cmd *cobra.Command) error {
|
||||
@@ -138,14 +95,10 @@ func (o *runOptions) Run(cmd *cobra.Command) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if staging {
|
||||
o.Info("Staging saved")
|
||||
return nil
|
||||
msg, err := oam.BaseRun(staging, o.App, o.KubeClient, o.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Infof("Creating App %s\n", o.app.Name)
|
||||
if err := o.app.Run(context.Background(), o.client, o.Env); err != nil {
|
||||
return fmt.Errorf("create app err: %s", err)
|
||||
}
|
||||
o.Info("SUCCEED")
|
||||
o.Info(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
+1
-2
@@ -3,7 +3,6 @@ package oam
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -74,7 +73,7 @@ func ListEnvs(envName string) ([]*types.EnvMeta, error) {
|
||||
env, err := GetEnvByName(envName)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = errors.New(fmt.Sprintf("env %s not exist", envName))
|
||||
err = fmt.Errorf("env %s not exist", envName)
|
||||
}
|
||||
return envList, err
|
||||
}
|
||||
|
||||
+1
-2
@@ -3,7 +3,6 @@ package oam
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -80,5 +79,5 @@ func GetTraitDefinitionByKind(ctx context.Context, c client.Client, traitKind st
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return traitDefinition, errors.New(fmt.Sprintf("Could not find TraitDefinition by kind %s", traitKind))
|
||||
return traitDefinition, fmt.Errorf("could not find TraitDefinition by kind %s", traitKind)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package oam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/api/types"
|
||||
"github.com/cloud-native-application/rudrx/pkg/cmd/util"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"github.com/cloud-native-application/rudrx/pkg/application"
|
||||
)
|
||||
|
||||
type RunOptions struct {
|
||||
Template types.Capability
|
||||
Env *types.EnvMeta
|
||||
WorkloadType string
|
||||
WorkloadName string
|
||||
KubeClient client.Client
|
||||
App *application.Application
|
||||
AppName string
|
||||
Staging bool
|
||||
util.IOStreams
|
||||
}
|
||||
|
||||
func BaseComplete(envName string, workloadName string, appGroup string, flagSet *pflag.FlagSet, template types.Capability) (*application.Application, error) {
|
||||
var appName string
|
||||
if appGroup != "" {
|
||||
appName = appGroup
|
||||
} else {
|
||||
appName = workloadName
|
||||
}
|
||||
app, err := application.Load(envName, appName)
|
||||
if err != nil {
|
||||
return app, err
|
||||
}
|
||||
app.Name = appName
|
||||
|
||||
if app.Components == nil {
|
||||
app.Components = make(map[string]map[string]interface{})
|
||||
}
|
||||
tp, workloadData := app.GetWorkload(workloadName)
|
||||
if tp == "" {
|
||||
// Not exist
|
||||
tp = template.Name
|
||||
}
|
||||
|
||||
for _, v := range template.Parameters {
|
||||
flagValue, _ := flagSet.GetString(v.Name)
|
||||
// Cli can check required flag before make a request to backend, but API itself could not, so validate flags here
|
||||
if v.Required && v.Name != "name" && flagValue == "" {
|
||||
return app, fmt.Errorf("required flag(s) \"%s\" not set", v.Name)
|
||||
}
|
||||
switch v.Type {
|
||||
case cue.IntKind:
|
||||
d, _ := strconv.ParseInt(flagValue, 10, 64)
|
||||
workloadData[v.Name] = d
|
||||
case cue.StringKind:
|
||||
workloadData[v.Name] = flagValue
|
||||
case cue.BoolKind:
|
||||
d, _ := strconv.ParseBool(flagValue)
|
||||
workloadData[v.Name] = d
|
||||
case cue.NumberKind, cue.FloatKind:
|
||||
d, _ := strconv.ParseFloat(flagValue, 64)
|
||||
workloadData[v.Name] = d
|
||||
}
|
||||
}
|
||||
if err = app.SetWorkload(workloadName, tp, workloadData); err != nil {
|
||||
return app, err
|
||||
}
|
||||
return app, app.Save(envName, appName)
|
||||
}
|
||||
|
||||
func BaseRun(staging bool, App *application.Application, kubeClient client.Client, Env *types.EnvMeta) (string, error) {
|
||||
if staging {
|
||||
return "Staging saved", nil
|
||||
}
|
||||
var msg string
|
||||
msg = fmt.Sprintf("Creating App %s\n", App.Name)
|
||||
if err := App.Run(context.Background(), kubeClient, Env); err != nil {
|
||||
err = fmt.Errorf("create app err: %s", err)
|
||||
return "", err
|
||||
}
|
||||
msg += "SUCCEED"
|
||||
return msg, nil
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package apis
|
||||
|
||||
import "k8s.io/apimachinery/pkg/runtime"
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
type Environment struct {
|
||||
EnvironmentName string `json:"environmentName" binding:"required,min=1,max=32"`
|
||||
@@ -18,3 +20,15 @@ type Response struct {
|
||||
Code int `json:"code"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
type WorkloadFlag struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
type WorkloadRunBody struct {
|
||||
EnvName string `json:"env_name"`
|
||||
WorkloadType string `json:"workload_type"`
|
||||
WorkloadName string `json:"workload_name"`
|
||||
AppGroup string `json:"app_group"`
|
||||
Flags []WorkloadFlag `json:"flags"`
|
||||
Staging bool `json:"staging"`
|
||||
}
|
||||
|
||||
@@ -1,9 +1,49 @@
|
||||
package handler
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
import (
|
||||
"github.com/cloud-native-application/rudrx/api/types"
|
||||
"github.com/cloud-native-application/rudrx/pkg/oam"
|
||||
"github.com/cloud-native-application/rudrx/pkg/plugins"
|
||||
"github.com/spf13/pflag"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/pkg/server/apis"
|
||||
"github.com/cloud-native-application/rudrx/pkg/server/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Workload related handlers
|
||||
func CreateWorkload(c *gin.Context) {
|
||||
kubeClient := c.MustGet("KubeClient")
|
||||
var body apis.WorkloadRunBody
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
util.HandleError(c, util.InvalidArgument, "the workload run request body is invalid")
|
||||
return
|
||||
}
|
||||
fs := pflag.NewFlagSet("workload", pflag.ContinueOnError)
|
||||
for _, f := range body.Flags {
|
||||
fs.String(f.Name, f.Value, "")
|
||||
}
|
||||
evnName := body.EnvName
|
||||
var template types.Capability
|
||||
|
||||
template, err := plugins.LoadCapabilityByName(body.WorkloadType)
|
||||
appObj, err := oam.BaseComplete(evnName, body.WorkloadName, body.AppGroup, fs, template)
|
||||
if err != nil {
|
||||
util.HandleError(c, util.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
env, err := oam.GetEnvByName(evnName)
|
||||
if err != nil {
|
||||
util.HandleError(c, util.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
msg, err := oam.BaseRun(body.Staging, appObj, kubeClient.(client.Client), env)
|
||||
if err != nil {
|
||||
util.HandleError(c, util.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
util.AssembleResponse(c, msg, err)
|
||||
}
|
||||
|
||||
func UpdateWorkload(c *gin.Context) {
|
||||
|
||||
Reference in New Issue
Block a user