Merge pull request #937 from wonderflow/outputs

allow multiple outputs for workloaddefintion
This commit is contained in:
Jianbo Sun
2021-01-29 12:39:36 +08:00
committed by GitHub
9 changed files with 330 additions and 8 deletions
+14
View File
@@ -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
+89
View File
@@ -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
@@ -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) {
+23
View File
@@ -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
}
+3 -1
View File
@@ -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
}
+2 -2
View File
@@ -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))
+6
View File
@@ -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{
+2 -4
View File
@@ -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"