Fix(envbinding): fix the way that envbinding stores resources after configuration (#2175)

* Refactor: replace #op.ApplyEnvBindComponnet with #op.ApplyEnvBindApp

* Fix: fix patch strategy for envbinding
This commit is contained in:
yangsoon
2021-08-27 21:38:57 +08:00
committed by GitHub
parent 950411dbe8
commit 73be37b73b
8 changed files with 284 additions and 85 deletions
@@ -15,16 +15,14 @@ spec:
"vela/op"
)
component: op.#ApplyEnvBindComponent & {
env: parameter.env
policy: parameter.policy
component: parameter.component
app: op.#ApplyEnvBindApp & {
env: parameter.env
policy: parameter.policy
app: context.name
// context.namespace indicates the namespace of the app
namespace: context.namespace
}
parameter: {
// +usage=Declare the name of the component
component: string
// +usage=Declare the name of the policy
policy: string
// +usage=Declare the name of the env in policy
+1 -2
View File
@@ -46,5 +46,4 @@ spec:
type: deploy2cluster
properties:
env: prod
policy: prod-env
component: podinfo-server
policy: prod-env
@@ -62,27 +62,34 @@ func NewEnvBindApp(base *v1beta1.Application, envConfig *v1alpha1.EnvConfig) *En
// generateConfiguredApplication patch component parameters to base Application
func (e *EnvBindApp) generateConfiguredApplication() error {
newApp := e.baseApp.DeepCopy()
var baseComponent *common.ApplicationComponent
var matchIdx int
var misMatchedIdxs []int
for patchIdx := range e.envConfig.Patch.Components {
var matchedIdx int
isMatched := false
patchComponent := e.envConfig.Patch.Components[patchIdx]
var isMatched bool
for baseIdx := range e.baseApp.Spec.Components {
component := e.baseApp.Spec.Components[baseIdx]
if patchComponent.Name == component.Name && patchComponent.Type == component.Type {
matchIdx, baseComponent = baseIdx, &component
matchedIdx, baseComponent = baseIdx, &component
isMatched = true
break
}
}
if !isMatched || baseComponent == nil {
return errors.Errorf("fail to match component %s", patchComponent.Name)
misMatchedIdxs = append(misMatchedIdxs, patchIdx)
continue
}
targetComponent, err := PatchComponent(baseComponent, &patchComponent)
if err != nil {
return err
}
newApp.Spec.Components[matchIdx] = *targetComponent
newApp.Spec.Components[matchedIdx] = *targetComponent
}
for _, idx := range misMatchedIdxs {
newApp.Spec.Components = append(newApp.Spec.Components, e.envConfig.Patch.Components[idx])
}
e.patchedApp = newApp
return nil
@@ -203,25 +210,33 @@ func PatchComponent(baseComponent *common.ApplicationComponent, patchComponent *
targetComponent.Properties = util.Object2RawExtension(mergedProperties)
var baseTrait *common.ApplicationTrait
var matchIdx int
for _, patchTrait := range patchComponent.Traits {
var isMatched bool
var misMatchedIdxs []int
for patchIdx := range patchComponent.Traits {
var matchedIdx int
isMatched := false
patchTrait := patchComponent.Traits[patchIdx]
for index := range targetComponent.Traits {
trait := targetComponent.Traits[index]
if patchTrait.Type == trait.Type {
matchIdx, baseTrait = index, &trait
matchedIdx, baseTrait = index, &trait
isMatched = true
break
}
}
if !isMatched || baseTrait == nil {
return nil, errors.Errorf("fail to match trait %s", patchTrait.Type)
misMatchedIdxs = append(misMatchedIdxs, patchIdx)
continue
}
mergedProperties, err = PatchProperties(baseTrait.Properties, patchTrait.Properties)
if err != nil {
return nil, err
}
targetComponent.Traits[matchIdx].Properties = util.Object2RawExtension(mergedProperties)
targetComponent.Traits[matchedIdx].Properties = util.Object2RawExtension(mergedProperties)
}
for _, idx := range misMatchedIdxs {
targetComponent.Traits = append(targetComponent.Traits, patchComponent.Traits[idx])
}
return targetComponent, nil
}
@@ -0,0 +1,197 @@
/*
Copyright 2021. The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package envbinding
import (
"testing"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
func Test_EnvBindApp_GenerateConfiguredApplication(t *testing.T) {
testcases := []struct {
baseApp *v1beta1.Application
envConfig *v1alpha1.EnvConfig
expectedApp *v1beta1.Application
}{{
baseApp: baseApp,
envConfig: &v1alpha1.EnvConfig{
Name: "prod",
Patch: v1alpha1.EnvPatch{
Components: []common.ApplicationComponent{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
}),
Traits: []common.ApplicationTrait{{
Type: "ingress-1-20",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "newTestsvc.example.com",
}),
}},
}},
},
},
expectedApp: &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1beta1",
Kind: "Application",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
"port": 8000,
}),
Traits: []common.ApplicationTrait{{
Type: "ingress-1-20",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "newTestsvc.example.com",
"http": map[string]interface{}{
"/": 8000,
},
}),
}},
}},
},
},
}, {
baseApp: baseApp,
envConfig: &v1alpha1.EnvConfig{
Name: "prod",
Patch: v1alpha1.EnvPatch{
Components: []common.ApplicationComponent{{
Name: "express-server",
Type: "webservice",
Traits: []common.ApplicationTrait{{
Type: "labels",
Properties: util.Object2RawExtension(map[string]interface{}{
"test": "label",
}),
}},
}, {
Name: "new-server",
Type: "worker",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
"cmd": []string{"sleep", "1000"},
}),
Traits: []common.ApplicationTrait{{
Type: "labels",
Properties: util.Object2RawExtension(map[string]interface{}{
"test": "label",
}),
}},
}},
},
},
expectedApp: &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1beta1",
Kind: "Application",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "crccheck/hello-world",
"port": 8000,
}),
Traits: []common.ApplicationTrait{{
Type: "ingress-1-20",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "testsvc.example.com",
"http": map[string]interface{}{
"/": 8000,
},
}),
}, {
Type: "labels",
Properties: util.Object2RawExtension(map[string]interface{}{
"test": "label",
}),
}},
}, {
Name: "new-server",
Type: "worker",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
"cmd": []string{"sleep", "1000"},
}),
Traits: []common.ApplicationTrait{{
Type: "labels",
Properties: util.Object2RawExtension(map[string]interface{}{
"test": "label",
}),
}},
}},
},
},
}}
for _, testcase := range testcases {
envBindApp := NewEnvBindApp(testcase.baseApp, testcase.envConfig)
err := envBindApp.generateConfiguredApplication()
assert.NoError(t, err)
assert.Equal(t, envBindApp.patchedApp, testcase.expectedApp)
}
}
var baseApp = &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1beta1",
Kind: "Application",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "crccheck/hello-world",
"port": 8000,
}),
Traits: []common.ApplicationTrait{{
Type: "ingress-1-20",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "testsvc.example.com",
"http": map[string]interface{}{
"/": 8000,
},
}),
}},
}},
},
}
@@ -116,32 +116,31 @@ func (o *OCMEngine) schedule(ctx context.Context, apps []*EnvBindApp) ([]v1alpha
for i := range apps {
app := apps[i]
app.ScheduledManifests = make(map[string]*unstructured.Unstructured, len(app.assembledManifests))
app.ScheduledManifests = make(map[string]*unstructured.Unstructured, 1)
clusterName := o.clusterDecisions[app.envConfig.Name]
for componentName, manifest := range app.assembledManifests {
manifestWork := new(ocmworkv1.ManifestWork)
manifestWork.SetNamespace(clusterName)
workloads := make([]ocmworkv1.Manifest, len(manifest))
for j, workload := range manifest {
workloads[j] = ocmworkv1.Manifest{
RawExtension: util.Object2RawExtension(workload),
}
manifestWork := new(ocmworkv1.ManifestWork)
workloads := make([]ocmworkv1.Manifest, 0, len(app.assembledManifests))
for _, component := range app.patchedApp.Spec.Components {
manifest := app.assembledManifests[component.Name]
for j := range manifest {
workloads = append(workloads, ocmworkv1.Manifest{
RawExtension: util.Object2RawExtension(manifest[j]),
})
}
manifestWork.Spec.Workload.Manifests = workloads
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(manifestWork)
if err != nil {
return nil, err
}
unstructuredManifestWork := &unstructured.Unstructured{
Object: obj,
}
unstructuredManifestWork.SetGroupVersionKind(ocmworkv1.GroupVersion.WithKind(reflect.TypeOf(ocmworkv1.ManifestWork{}).Name()))
envBindComponentName := fmt.Sprintf("%s-%s-%s", o.envBindingName, app.envConfig.Name, componentName)
unstructuredManifestWork.SetName(envBindComponentName)
app.ScheduledManifests[envBindComponentName] = unstructuredManifestWork
}
manifestWork.Spec.Workload.Manifests = workloads
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(manifestWork)
if err != nil {
return nil, err
}
unstructuredManifestWork := &unstructured.Unstructured{
Object: obj,
}
unstructuredManifestWork.SetGroupVersionKind(ocmworkv1.GroupVersion.WithKind(reflect.TypeOf(ocmworkv1.ManifestWork{}).Name()))
envBindAppName := constructEnvBindAppName(o.envBindingName, app.envConfig.Name, o.appName)
unstructuredManifestWork.SetName(envBindAppName)
unstructuredManifestWork.SetNamespace(clusterName)
app.ScheduledManifests[envBindAppName] = unstructuredManifestWork
}
for env, cluster := range o.clusterDecisions {
@@ -268,12 +267,12 @@ func (s *SingleClusterEngine) schedule(ctx context.Context, apps []*EnvBindApp)
return nil, err
}
app.ScheduledManifests = make(map[string]*unstructured.Unstructured, len(app.assembledManifests))
app.ScheduledManifests = make(map[string]*unstructured.Unstructured, 1)
unstructuredApp, err := util.Object2Unstructured(app.patchedApp)
if err != nil {
return nil, err
}
envBindAppName := fmt.Sprintf("%s-%s-%s", s.envBindingName, app.envConfig.Name, s.appName)
envBindAppName := constructEnvBindAppName(s.envBindingName, app.envConfig.Name, s.appName)
unstructuredApp.SetName(envBindAppName)
unstructuredApp.SetNamespace(selectedNamespace)
app.ScheduledManifests[envBindAppName] = unstructuredApp
@@ -303,9 +302,9 @@ func (s *SingleClusterEngine) dispatch(ctx context.Context, envBinding *v1alpha1
return nil
}
func (s *SingleClusterEngine) getSelectedNamespace(ctx context.Context, envbindApp *EnvBindApp) (string, error) {
if envbindApp.envConfig.Placement.NamespaceSelector != nil {
selector := envbindApp.envConfig.Placement.NamespaceSelector
func (s *SingleClusterEngine) getSelectedNamespace(ctx context.Context, envBindApp *EnvBindApp) (string, error) {
if envBindApp.envConfig.Placement.NamespaceSelector != nil {
selector := envBindApp.envConfig.Placement.NamespaceSelector
if len(selector.Name) != 0 {
return selector.Name, nil
}
@@ -316,12 +315,12 @@ func (s *SingleClusterEngine) getSelectedNamespace(ctx context.Context, envbindA
}
err := s.cli.List(ctx, namespaceList, listOpts...)
if err != nil || len(namespaceList.Items) == 0 {
return "", errors.Wrapf(err, "fail to list selected namespace for env %s", envbindApp.envConfig.Name)
return "", errors.Wrapf(err, "fail to list selected namespace for env %s", envBindApp.envConfig.Name)
}
return namespaceList.Items[0].Name, nil
}
}
return envbindApp.patchedApp.Namespace, nil
return envBindApp.patchedApp.Namespace, nil
}
func validatePlacement(envBinding *v1alpha1.EnvBinding) error {
@@ -334,3 +333,7 @@ func validatePlacement(envBinding *v1alpha1.EnvBinding) error {
}
return nil
}
func constructEnvBindAppName(envBindingName, envName, appName string) string {
return fmt.Sprintf("%s-%s-%s", envBindingName, envName, appName)
}
@@ -201,24 +201,20 @@ var _ = Describe("EnvBinding Normal tests", func() {
}, 30*time.Second, 1*time.Second).Should(BeNil())
By("Check whether the parameter is patched")
mw1 := new(ocmworkv1.ManifestWork)
mw1Yaml := cm.Data[fmt.Sprintf("%s-%s-%s", envBinding.Name, envBinding.Spec.Envs[0].Name, envBinding.Spec.Envs[0].Patch.Components[0].Name)]
Expect(yaml.Unmarshal([]byte(mw1Yaml), mw1)).Should(BeNil())
mw := new(ocmworkv1.ManifestWork)
mwYaml := cm.Data[fmt.Sprintf("%s-%s-%s", envBinding.Name, envBinding.Spec.Envs[0].Name, appTemplate.Name)]
Expect(yaml.Unmarshal([]byte(mwYaml), mw)).Should(BeNil())
workload1 := new(v1.Deployment)
Expect(yaml.Unmarshal(mw1.Spec.Workload.Manifests[0].Raw, workload1)).Should(BeNil())
Expect(yaml.Unmarshal(mw.Spec.Workload.Manifests[0].Raw, workload1)).Should(BeNil())
Expect(workload1.Spec.Template.GetLabels()["hello"]).Should(Equal("patch"))
Expect(workload1.Spec.Template.Spec.Containers[0].Image).Should(Equal("busybox"))
mw2 := new(ocmworkv1.ManifestWork)
mw2Yaml := cm.Data[fmt.Sprintf("%s-%s-%s", envBinding.Name, envBinding.Spec.Envs[0].Name, envBinding.Spec.Envs[0].Patch.Components[1].Name)]
Expect(yaml.Unmarshal([]byte(mw2Yaml), mw2)).Should(BeNil())
workload2 := new(v1.Deployment)
Expect(yaml.Unmarshal(mw2.Spec.Workload.Manifests[0].Raw, workload2)).Should(BeNil())
Expect(yaml.Unmarshal(mw.Spec.Workload.Manifests[1].Raw, workload2)).Should(BeNil())
Expect(workload2.Spec.Template.Spec.Containers[0].Ports[0].ContainerPort).Should(Equal(int32(8080)))
By("Check whether the cluster is selected correctly")
Expect(mw1.GetNamespace()).Should(Equal(spokeClusterName))
Expect(mw2.GetNamespace()).Should(Equal(spokeClusterName))
Expect(mw.GetNamespace()).Should(Equal(spokeClusterName))
})
It("Test EnvBinding select cluster by label", func() {
@@ -256,7 +252,7 @@ var _ = Describe("EnvBinding Normal tests", func() {
By("Check whether the parameter is patched")
mw := new(ocmworkv1.ManifestWork)
mwYaml := cm.Data[fmt.Sprintf("%s-%s-%s", envBinding.Name, envBinding.Spec.Envs[0].Name, envBinding.Spec.Envs[0].Patch.Components[0].Name)]
mwYaml := cm.Data[fmt.Sprintf("%s-%s-%s", envBinding.Name, envBinding.Spec.Envs[0].Name, appTemplate.Name)]
Expect(yaml.Unmarshal([]byte(mwYaml), mw)).Should(BeNil())
workload := new(v1.Deployment)
Expect(yaml.Unmarshal(mw.Spec.Workload.Manifests[0].Raw, workload)).Should(BeNil())
@@ -323,7 +319,7 @@ var _ = Describe("EnvBinding Normal tests", func() {
By("Check whether the parameter is patched")
mw1 := new(ocmworkv1.ManifestWork)
mw1Yaml := cm.Data[fmt.Sprintf("%s-%s-%s", envBinding.Name, envBinding.Spec.Envs[0].Name, envBinding.Spec.Envs[0].Patch.Components[0].Name)]
mw1Yaml := cm.Data[fmt.Sprintf("%s-%s-%s", envBinding.Name, envBinding.Spec.Envs[0].Name, appTemplate.Name)]
Expect(yaml.Unmarshal([]byte(mw1Yaml), mw1)).Should(BeNil())
workload1 := new(v1.Deployment)
Expect(yaml.Unmarshal(mw1.Spec.Workload.Manifests[0].Raw, workload1)).Should(BeNil())
@@ -331,7 +327,7 @@ var _ = Describe("EnvBinding Normal tests", func() {
Expect(workload1.Spec.Template.Spec.Containers[0].Image).Should(Equal("busybox"))
mw2 := new(ocmworkv1.ManifestWork)
mw2Yaml := cm.Data[fmt.Sprintf("%s-%s-%s", envBinding.Name, envBinding.Spec.Envs[1].Name, envBinding.Spec.Envs[1].Patch.Components[0].Name)]
mw2Yaml := cm.Data[fmt.Sprintf("%s-%s-%s", envBinding.Name, envBinding.Spec.Envs[1].Name, appTemplate.Name)]
Expect(yaml.Unmarshal([]byte(mw2Yaml), mw2)).Should(BeNil())
workload2 := new(v1.Deployment)
Expect(yaml.Unmarshal(mw2.Spec.Workload.Manifests[0].Raw, workload2)).Should(BeNil())
@@ -396,7 +392,7 @@ var _ = Describe("EnvBinding Normal tests", func() {
}, 30*time.Second, 1*time.Second).Should(BeNil())
mw := new(ocmworkv1.ManifestWork)
mwYaml := cm.Data[fmt.Sprintf("%s-%s-%s", envBinding.Name, envBinding.Spec.Envs[0].Name, envBinding.Spec.Envs[0].Patch.Components[0].Name)]
mwYaml := cm.Data[fmt.Sprintf("%s-%s-%s", envBinding.Name, envBinding.Spec.Envs[0].Name, appTemplate.Name)]
Expect(yaml.Unmarshal([]byte(mwYaml), mw)).Should(BeNil())
Expect(len(mw.Spec.Workload.Manifests)).Should(Equal(3))
})
@@ -420,31 +416,24 @@ var _ = Describe("EnvBinding Normal tests", func() {
testutil.ReconcileRetry(&r, req)
By("Check whether create manifestWork")
mw1 := new(ocmworkv1.ManifestWork)
mw1Name := fmt.Sprintf("%s-%s-%s", envBinding.Name, envBinding.Spec.Envs[0].Name, envBinding.Spec.Envs[0].Patch.Components[0].Name)
mw := new(ocmworkv1.ManifestWork)
mwName := fmt.Sprintf("%s-%s-%s", envBinding.Name, envBinding.Spec.Envs[0].Name, appTemplate.Name)
Eventually(func() error {
return k8sClient.Get(ctx, client.ObjectKey{Name: mw1Name, Namespace: spokeClusterName}, mw1)
}, 3*time.Second, 1*time.Second).Should(BeNil())
mw2 := new(ocmworkv1.ManifestWork)
mw2Name := fmt.Sprintf("%s-%s-%s", envBinding.Name, envBinding.Spec.Envs[0].Name, envBinding.Spec.Envs[0].Patch.Components[1].Name)
Eventually(func() error {
return k8sClient.Get(ctx, client.ObjectKey{Name: mw2Name, Namespace: spokeClusterName}, mw2)
return k8sClient.Get(ctx, client.ObjectKey{Name: mwName, Namespace: spokeClusterName}, mw)
}, 3*time.Second, 1*time.Second).Should(BeNil())
By("Check whether the parameter is patched")
workload1 := new(v1.Deployment)
Expect(yaml.Unmarshal(mw1.Spec.Workload.Manifests[0].Raw, workload1)).Should(BeNil())
Expect(yaml.Unmarshal(mw.Spec.Workload.Manifests[0].Raw, workload1)).Should(BeNil())
Expect(workload1.Spec.Template.GetLabels()["hello"]).Should(Equal("patch"))
Expect(workload1.Spec.Template.Spec.Containers[0].Image).Should(Equal("busybox"))
workload2 := new(v1.Deployment)
Expect(yaml.Unmarshal(mw2.Spec.Workload.Manifests[0].Raw, workload2)).Should(BeNil())
Expect(yaml.Unmarshal(mw.Spec.Workload.Manifests[1].Raw, workload2)).Should(BeNil())
Expect(workload2.Spec.Template.Spec.Containers[0].Ports[0].ContainerPort).Should(Equal(int32(8080)))
By("Check whether the cluster is selected correctly")
Expect(mw1.GetNamespace()).Should(Equal(spokeClusterName))
Expect(mw2.GetNamespace()).Should(Equal(spokeClusterName))
Expect(mw.GetNamespace()).Should(Equal(spokeClusterName))
})
})
+3 -3
View File
@@ -106,10 +106,10 @@ import (
}
}
#ApplyEnvBindComponent: #Steps & {
#ApplyEnvBindApp: #Steps & {
env: string
policy: string
component: string
app: string
namespace: string
_namespace: namespace
@@ -140,7 +140,7 @@ import (
}
} @step(3)
target: "\(policy)-\(env)-\(component)"
target: "\(policy)-\(env)-\(app)"
apply: kube.#Apply & {
value: {
yaml.Unmarshal(configMap.value.data[target])
@@ -9,17 +9,15 @@ import (
description: "Apply env binding component"
}
template: {
component: op.#ApplyEnvBindComponent & {
env: parameter.env
policy: parameter.policy
component: parameter.component
app: op.#ApplyEnvBindApp & {
env: parameter.env
policy: parameter.policy
app: context.name
// context.namespace indicates the namespace of the app
namespace: context.namespace
}
parameter: {
// +usage=Declare the name of the component
component: string
// +usage=Declare the name of the policy
policy: string
// +usage=Declare the name of the env in policy