mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-27 16:17:34 +00:00
Merge pull request #937 from wonderflow/outputs
allow multiple outputs for workloaddefintion
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: Application
|
||||
metadata:
|
||||
name: testapp4
|
||||
spec:
|
||||
components:
|
||||
- name: express-server4
|
||||
type: webserver
|
||||
settings:
|
||||
cmd:
|
||||
- node
|
||||
- server.js
|
||||
image: oamdev/testapp:v1
|
||||
port: 8080
|
||||
@@ -0,0 +1,89 @@
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: webserver
|
||||
annotations:
|
||||
definition.oam.dev/description: "webserver was composed by deployment and service"
|
||||
spec:
|
||||
definitionRef:
|
||||
name: deployments.apps
|
||||
extension:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
spec: {
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
|
||||
if parameter["env"] != _|_ {
|
||||
env: parameter.env
|
||||
}
|
||||
|
||||
if context["config"] != _|_ {
|
||||
env: context.config
|
||||
}
|
||||
|
||||
ports: [{
|
||||
containerPort: parameter.port
|
||||
}]
|
||||
|
||||
if parameter["cpu"] != _|_ {
|
||||
resources: {
|
||||
limits:
|
||||
cpu: parameter.cpu
|
||||
requests:
|
||||
cpu: parameter.cpu
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// workload can have extra object composition by using 'outputs' keyword
|
||||
outputs: service: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
spec: {
|
||||
selector: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
ports: [
|
||||
{
|
||||
port: parameter.port
|
||||
targetPort: parameter.port
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
image: string
|
||||
cmd?: [...string]
|
||||
port: *80 | int
|
||||
env?: [...{
|
||||
name: string
|
||||
value?: string
|
||||
valueFrom?: {
|
||||
secretKeyRef: {
|
||||
name: string
|
||||
key: string
|
||||
}
|
||||
}
|
||||
}]
|
||||
cpu?: string
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import (
|
||||
"net/http/httptest"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
. "github.com/onsi/ginkgo"
|
||||
@@ -154,6 +156,9 @@ var _ = Describe("Test Application Controller", func() {
|
||||
wd := &v1alpha2.WorkloadDefinition{}
|
||||
wDDefJson, _ := yaml.YAMLToJSON([]byte(wDDefYaml))
|
||||
|
||||
webserverwd := &v1alpha2.WorkloadDefinition{}
|
||||
webserverwdJson, _ := yaml.YAMLToJSON([]byte(webserverYaml))
|
||||
|
||||
td := &v1alpha2.TraitDefinition{}
|
||||
tDDefJson, _ := yaml.YAMLToJSON([]byte(tDDefYaml))
|
||||
|
||||
@@ -176,6 +181,9 @@ var _ = Describe("Test Application Controller", func() {
|
||||
Expect(json.Unmarshal(sdDefJson, sd)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, sd.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
Expect(json.Unmarshal(webserverwdJson, webserverwd)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, webserverwd.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
})
|
||||
AfterEach(func() {
|
||||
})
|
||||
@@ -326,6 +334,97 @@ var _ = Describe("Test Application Controller", func() {
|
||||
Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
|
||||
})
|
||||
|
||||
It("app-with-composedworkload-trait will create workload and trait", func() {
|
||||
compName := "myweb-composed-3"
|
||||
expDeployment := getExpDeployment(compName)
|
||||
ns := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "vela-test-with-composedworkload-trait",
|
||||
},
|
||||
}
|
||||
var appname = "app-with-composedworkload-trait"
|
||||
appWithComposedWorkload := appwithNoTrait.DeepCopy()
|
||||
appWithComposedWorkload.Spec.Components[0].WorkloadType = "webserver"
|
||||
appWithComposedWorkload.SetName(appname)
|
||||
appWithComposedWorkload.Spec.Components[0].Traits = []v1alpha2.ApplicationTrait{
|
||||
{
|
||||
Name: "scaler",
|
||||
Properties: runtime.RawExtension{Raw: []byte(`{"replicas":2}`)},
|
||||
},
|
||||
}
|
||||
appWithComposedWorkload.Spec.Components[0].Name = compName
|
||||
appWithComposedWorkload.SetNamespace(ns.Name)
|
||||
Expect(k8sClient.Create(ctx, ns)).Should(BeNil())
|
||||
app := appWithComposedWorkload.DeepCopy()
|
||||
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
|
||||
|
||||
appKey := client.ObjectKey{
|
||||
Name: app.Name,
|
||||
Namespace: app.Namespace,
|
||||
}
|
||||
reconcileRetry(reconciler, reconcile.Request{NamespacedName: appKey})
|
||||
|
||||
By("Check App running successfully")
|
||||
checkApp := &v1alpha2.Application{}
|
||||
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
|
||||
Expect(checkApp.Status.Phase).Should(Equal(v1alpha2.ApplicationRunning))
|
||||
|
||||
By("Check AppConfig and trait created as expected")
|
||||
appConfig := &v1alpha2.ApplicationConfiguration{}
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{
|
||||
Namespace: app.Namespace,
|
||||
Name: app.Name,
|
||||
}, appConfig)).Should(BeNil())
|
||||
|
||||
Expect(len(appConfig.Spec.Components[0].Traits)).Should(BeEquivalentTo(2))
|
||||
|
||||
gotTrait := unstructured.Unstructured{}
|
||||
By("Check the first trait should be service")
|
||||
expectServiceTrait := unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{"trait.oam.dev/type": "AuxiliaryWorkload"},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"ports": []interface{}{
|
||||
map[string]interface{}{"port": int64(80), "targetPort": int64(80)},
|
||||
},
|
||||
"selector": map[string]interface{}{
|
||||
"app.oam.dev/component": compName,
|
||||
},
|
||||
},
|
||||
}}
|
||||
Expect(json.Unmarshal(appConfig.Spec.Components[0].Traits[0].Trait.Raw, &gotTrait)).Should(BeNil())
|
||||
fmt.Println(cmp.Diff(expectServiceTrait, gotTrait))
|
||||
Expect(assert.ObjectsAreEqual(expectServiceTrait, gotTrait)).Should(BeTrue())
|
||||
|
||||
By("Check the second trait should be scaler")
|
||||
gotTrait = unstructured.Unstructured{}
|
||||
Expect(json.Unmarshal(appConfig.Spec.Components[0].Traits[1].Trait.Raw, &gotTrait)).Should(BeNil())
|
||||
Expect(gotTrait).Should(BeEquivalentTo(expectScalerTrait))
|
||||
|
||||
By("Check component created as expected")
|
||||
component := &v1alpha2.Component{}
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{
|
||||
Namespace: app.Namespace,
|
||||
Name: compName,
|
||||
}, component)).Should(BeNil())
|
||||
Expect(component.ObjectMeta.Labels).Should(BeEquivalentTo(map[string]string{"application.oam.dev": appname}))
|
||||
Expect(component.ObjectMeta.OwnerReferences[0].Name).Should(BeEquivalentTo(appname))
|
||||
Expect(component.ObjectMeta.OwnerReferences[0].Kind).Should(BeEquivalentTo("Application"))
|
||||
Expect(component.ObjectMeta.OwnerReferences[0].APIVersion).Should(BeEquivalentTo("core.oam.dev/v1alpha2"))
|
||||
Expect(component.ObjectMeta.OwnerReferences[0].Controller).Should(BeEquivalentTo(pointer.BoolPtr(true)))
|
||||
gotD := &v1.Deployment{}
|
||||
expDeployment.ObjectMeta.Labels["workload.oam.dev/type"] = "webserver"
|
||||
expDeployment.Spec.Template.Spec.Containers[0].Ports = []corev1.ContainerPort{{ContainerPort: 80}}
|
||||
Expect(json.Unmarshal(component.Spec.Workload.Raw, gotD)).Should(BeNil())
|
||||
fmt.Println(cmp.Diff(expDeployment, gotD))
|
||||
Expect(gotD).Should(BeEquivalentTo(expDeployment))
|
||||
|
||||
Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
|
||||
})
|
||||
|
||||
It("app-with-trait-and-scope will create workload, trait and scope", func() {
|
||||
expDeployment := getExpDeployment("myweb4")
|
||||
ns := &corev1.Namespace{
|
||||
@@ -767,6 +866,97 @@ spec:
|
||||
|
||||
cmd?: [...string]
|
||||
}
|
||||
`
|
||||
|
||||
webserverYaml = `apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: webserver
|
||||
annotations:
|
||||
definition.oam.dev/description: "webserver was composed by deployment and service"
|
||||
spec:
|
||||
definitionRef:
|
||||
name: deployments.apps
|
||||
extension:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
spec: {
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
|
||||
if parameter["env"] != _|_ {
|
||||
env: parameter.env
|
||||
}
|
||||
|
||||
if context["config"] != _|_ {
|
||||
env: context.config
|
||||
}
|
||||
|
||||
ports: [{
|
||||
containerPort: parameter.port
|
||||
}]
|
||||
|
||||
if parameter["cpu"] != _|_ {
|
||||
resources: {
|
||||
limits:
|
||||
cpu: parameter.cpu
|
||||
requests:
|
||||
cpu: parameter.cpu
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// workload can have extra object composition by using 'outputs' keyword
|
||||
outputs: service: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
spec: {
|
||||
selector: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
ports: [
|
||||
{
|
||||
port: parameter.port
|
||||
targetPort: parameter.port
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
image: string
|
||||
cmd?: [...string]
|
||||
port: *80 | int
|
||||
env?: [...{
|
||||
name: string
|
||||
value?: string
|
||||
valueFrom?: {
|
||||
secretKeyRef: {
|
||||
name: string
|
||||
key: string
|
||||
}
|
||||
}
|
||||
}]
|
||||
cpu?: string
|
||||
}
|
||||
|
||||
`
|
||||
wDDefWithHealthYaml = `
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
|
||||
+1
-1
@@ -175,7 +175,7 @@ spec:
|
||||
LabelSelector: selector,
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
traitNamePrefix := fmt.Sprintf("%s-dummy-", componentName)
|
||||
traitNamePrefix := fmt.Sprintf("%s-trait-", componentName)
|
||||
var traitExistFlag bool
|
||||
for _, t := range scaleList.Items {
|
||||
if strings.HasPrefix(t.Name, traitNamePrefix) {
|
||||
|
||||
@@ -29,6 +29,12 @@ const (
|
||||
PatchFieldName = "patch"
|
||||
)
|
||||
|
||||
const (
|
||||
// AuxiliaryWorkload defines the extra workload obj from a workloadDefinition,
|
||||
// e.g. a workload composed by deployment and service, the service will be marked as AuxiliaryWorkload
|
||||
AuxiliaryWorkload = "AuxiliaryWorkload"
|
||||
)
|
||||
|
||||
var (
|
||||
metadataAccessor = meta.NewAccessor()
|
||||
)
|
||||
@@ -99,6 +105,23 @@ func (wd *workloadDef) Complete(ctx process.Context) error {
|
||||
return errors.WithMessagef(err, "workloadDef %s new base", wd.name)
|
||||
}
|
||||
ctx.SetBase(base)
|
||||
|
||||
// we will support outputs for workload composition, and it will become trait in AppConfig.
|
||||
outputs := inst.Lookup(OutputsFieldName)
|
||||
st, err := outputs.Struct()
|
||||
if err == nil {
|
||||
for i := 0; i < st.Len(); i++ {
|
||||
fieldInfo := st.Field(i)
|
||||
if fieldInfo.IsDefinition || fieldInfo.IsHidden || fieldInfo.IsOptional {
|
||||
continue
|
||||
}
|
||||
other, err := model.NewOther(fieldInfo.Value)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "parse WorkloadDefinition %s outputs(%s)", wd.name, fieldInfo.Name)
|
||||
}
|
||||
ctx.PutAssistants(process.Assistant{Ins: other, Type: AuxiliaryWorkload})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ type Context interface {
|
||||
|
||||
// Assistant are objects rendered by definition template.
|
||||
type Assistant struct {
|
||||
Ins model.Instance
|
||||
Ins model.Instance
|
||||
// Type will be used to mark definition label for OAM runtime to get the CRD
|
||||
// It's now required for trait and main workload object. Extra workload CR object will not have the type.
|
||||
Type string
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ const (
|
||||
Dummy = "dummy"
|
||||
|
||||
// DummyTraitMessage is a message for trait which don't have definition found
|
||||
DummyTraitMessage = "No valid TraitDefinition found, all framework capabilities will work as default or disabled"
|
||||
DummyTraitMessage = "No TraitDefinition found, all framework capabilities will work as default"
|
||||
|
||||
// DefinitionNamespaceEnv is env key for specifying a namespace to fetch definition
|
||||
DefinitionNamespaceEnv = "DEFINITION_NAMESPACE"
|
||||
@@ -417,7 +417,7 @@ func RawExtension2Map(raw *runtime.RawExtension) (map[string]interface{}, error)
|
||||
// GenTraitName generate trait name
|
||||
func GenTraitName(componentName string, ct *v1alpha2.ComponentTrait, traitType string) string {
|
||||
var traitMiddleName = TraitPrefixKey
|
||||
if traitType != "" {
|
||||
if traitType != "" && traitType != Dummy {
|
||||
traitMiddleName = strings.ToLower(traitType)
|
||||
}
|
||||
return fmt.Sprintf("%s-%s-%s", componentName, traitMiddleName, ComputeHash(ct))
|
||||
|
||||
@@ -851,6 +851,12 @@ func TestGenTraitName(t *testing.T) {
|
||||
definitionName: "",
|
||||
exp: "simple-trait-67b8949f8d",
|
||||
},
|
||||
{
|
||||
name: "service",
|
||||
template: &v1alpha2.ComponentTrait{},
|
||||
definitionName: "dummy",
|
||||
exp: "service-trait-67b8949f8d",
|
||||
},
|
||||
{
|
||||
name: "simple",
|
||||
template: &v1alpha2.ComponentTrait{
|
||||
|
||||
@@ -16,12 +16,10 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/plugins"
|
||||
"github.com/oam-dev/kubevela/pkg/server/apis"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/helm"
|
||||
|
||||
Reference in New Issue
Block a user