Feat: createOrUpdateApplication support dryRun operation (#4860)

Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>

Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>
This commit is contained in:
nuclearwu
2022-10-18 17:25:28 +08:00
committed by GitHub
parent f1107c5018
commit 2175bb519e
6 changed files with 162 additions and 5 deletions
@@ -23,10 +23,12 @@ import (
v1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
"github.com/oam-dev/kubevela/pkg/utils/common"
)
// OAMApplicationService oam_application service
@@ -34,6 +36,7 @@ type OAMApplicationService interface {
CreateOrUpdateOAMApplication(context.Context, apisv1.ApplicationRequest, string, string) error
GetOAMApplication(context.Context, string, string) (*apisv1.ApplicationResponse, error)
DeleteOAMApplication(context.Context, string, string) error
DryRunOAMApplication(context.Context, apisv1.ApplicationRequest, string, string) error
}
// NewOAMApplicationService new oam_application service
@@ -43,6 +46,7 @@ func NewOAMApplicationService() OAMApplicationService {
type oamApplicationServiceImpl struct {
KubeClient client.Client `inject:"kubeClient"`
KubeConfig *rest.Config `inject:"kubeConfig"`
}
// CreateOrUpdateOAMApplication create or update application
@@ -104,3 +108,53 @@ func (o oamApplicationServiceImpl) DeleteOAMApplication(ctx context.Context, nam
},
}))
}
// DryRunOAMApplication dryRun create or update application
func (o oamApplicationServiceImpl) DryRunOAMApplication(ctx context.Context, request apisv1.ApplicationRequest, name, namespace string) error {
ns := new(v1.Namespace)
err := o.KubeClient.Get(ctx, client.ObjectKey{Name: namespace}, ns)
if kerrors.IsNotFound(err) {
ns.Name = namespace
if err = o.KubeClient.Create(ctx, ns); err != nil {
return err
}
}
app := &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: v1beta1.ApplicationSpec{
Components: request.Components,
Policies: request.Policies,
Workflow: request.Workflow,
},
}
args := common.Args{
Schema: common.Scheme,
}
_ = args.SetConfig(o.KubeConfig)
args.SetClient(o.KubeClient)
_, err = dryRunApplication(ctx, args, app)
if err != nil {
return err
}
existApp := new(v1beta1.Application)
err = o.KubeClient.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, existApp)
if err != nil {
if kerrors.IsNotFound(err) {
return o.KubeClient.Create(ctx, app, client.DryRunAll)
}
return err
}
existApp.Spec = app.Spec
return o.KubeClient.Update(ctx, existApp, client.DryRunAll)
}
@@ -47,6 +47,7 @@ var _ = Describe("Test oam application service function", func() {
ns = corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
oamAppService = &oamApplicationServiceImpl{
KubeClient: k8sClient,
KubeConfig: cfg,
}
Expect(common.ReadYamlToObject("./testdata/example-app.yaml", &baseApp)).Should(BeNil())
@@ -117,4 +118,30 @@ var _ = Describe("Test oam application service function", func() {
err := k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: baseApp.Name}, app)
Expect(kerrors.IsNotFound(err)).Should(BeTrue())
})
It("Test DryRunOAMApplication function", func() {
By("test dryRun create application")
appName := "test-new-app"
appNs := randomNamespaceName("test-new-app")
var newApp v1beta1.Application
Expect(common.ReadYamlToObject("./testdata/dryrun-app.yaml", &newApp)).Should(BeNil())
newApp.SetNamespace(namespace)
Eventually(func() error {
return k8sClient.Create(ctx, &newApp)
}, time.Second*3, time.Microsecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
req := apiv1.ApplicationRequest{
Components: newApp.Spec.Components,
Policies: newApp.Spec.Policies,
Workflow: newApp.Spec.Workflow,
}
Expect(oamAppService.DryRunOAMApplication(ctx, req, appName, appNs)).Should(BeNil())
By("test dryRun update application")
updateReq := apiv1.ApplicationRequest{
Components: newApp.Spec.Components[1:],
}
Expect(oamAppService.DryRunOAMApplication(ctx, updateReq, appName, appNs)).Should(BeNil())
})
})
+16
View File
@@ -0,0 +1,16 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: app-dryrun
namespace: default
spec:
components:
- name: hello-world
type: webservice
properties:
image: crccheck/hello-world
port: 8000
traits:
- type: scaler
properties:
replicas: 1
@@ -60,6 +60,9 @@ func (c *oamApplicationAPIInterface) GetWebServiceRoute() *restful.WebService {
Filter(c.RbacService.CheckPerm("application", "deploy")).
Param(ws.PathParameter("namespace", "identifier of the namespace").DataType("string")).
Param(ws.PathParameter("appname", "identifier of the oam application").DataType("string")).
Param(ws.QueryParameter("dryRun", "When present, indicates that modifications should not be persisted. "+
"An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. "+
"Valid values are: - All: all dry run stages will be processed").DataType("string").Required(false)).
Reads(apis.ApplicationRequest{}))
ws.Route(ws.DELETE("/namespaces/{namespace}/applications/{appname}").To(c.deleteApplication).
@@ -101,11 +104,23 @@ func (c *oamApplicationAPIInterface) createOrUpdateApplication(req *restful.Requ
return
}
err := c.OamApplicationService.CreateOrUpdateOAMApplication(req.Request.Context(), createReq, appName, namespace)
if err != nil {
log.Logger.Errorf("create application failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
dryRun := req.QueryParameter("dryRun")
if len(dryRun) != 0 {
if dryRun != "All" {
bcode.ReturnError(req, res, bcode.ErrApplicationDryRunFailed.SetMessage("Invalid dryRun parameter. Must be 'All'"))
return
}
if err := c.OamApplicationService.DryRunOAMApplication(req.Request.Context(), createReq, appName, namespace); err != nil {
log.Logger.Errorf("dryrun application failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
} else {
if err := c.OamApplicationService.CreateOrUpdateOAMApplication(req.Request.Context(), createReq, appName, namespace); err != nil {
log.Logger.Errorf("create application failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
}
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
@@ -97,4 +97,33 @@ var _ = Describe("Test oam application rest api", func() {
Expect(res).ShouldNot(BeNil())
Expect(cmp.Diff(res.StatusCode, 200)).Should(BeEmpty())
})
It("Test dryRun create and update oam app", func() {
defer GinkgoRecover()
By("test dryRun create app")
Expect(common.ReadYamlToObject("./testdata/dryrun-app.yaml", &app)).Should(BeNil())
req := apiv1.ApplicationRequest{
Components: app.Spec.Components,
Policies: app.Spec.Policies,
Workflow: app.Spec.Workflow,
}
res := post(fmt.Sprintf("/v1/namespaces/%s/applications/%s?dryRun=All", namespace, appName), req)
Expect(res).ShouldNot(BeNil())
Expect(cmp.Diff(res.StatusCode, 200)).Should(BeEmpty())
Expect(res.Body).ShouldNot(BeNil())
defer res.Body.Close()
By("test dryRun update app")
updateReq := apiv1.ApplicationRequest{
Components: app.Spec.Components[1:],
}
Eventually(func(g Gomega) {
res = post(fmt.Sprintf("/v1/namespaces/%s/applications/%s?dryRun=All", namespace, appName), updateReq)
g.Expect(res).ShouldNot(BeNil())
g.Expect(cmp.Diff(res.StatusCode, 200)).Should(BeEmpty())
g.Expect(res.Body).ShouldNot(BeNil())
defer res.Body.Close()
}, time.Minute).Should(Succeed())
})
})
+16
View File
@@ -0,0 +1,16 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: app-dryrun
namespace: default
spec:
components:
- name: hello-world
type: webservice
properties:
image: crccheck/hello-world
port: 8000
traits:
- type: scaler
properties:
replicas: 1