From f39a3fb7923e269acbc4659f68ac2c4fd1c554b6 Mon Sep 17 00:00:00 2001 From: wyike Date: Fri, 14 Jan 2022 10:16:31 +0800 Subject: [PATCH] Feat: system information collection logic in apiserver (#3082) * Feat: userInfoCollection Signed-off-by: wangyike * Signed-off-by: wangyike change enable/disable to update interface Signed-off-by: wangyike --- pkg/apiserver/model/system_info.go | 47 +++++++ pkg/apiserver/rest/apis/v1/types.go | 17 +++ pkg/apiserver/rest/usecase/system_info.go | 93 +++++++++++++ pkg/apiserver/rest/webservice/system_info.go | 116 ++++++++++++++++ pkg/apiserver/rest/webservice/webservice.go | 3 + test/e2e-apiserver-test/system_info_test.go | 138 +++++++++++++++++++ 6 files changed, 414 insertions(+) create mode 100644 pkg/apiserver/model/system_info.go create mode 100644 pkg/apiserver/rest/usecase/system_info.go create mode 100644 pkg/apiserver/rest/webservice/system_info.go create mode 100644 test/e2e-apiserver-test/system_info_test.go diff --git a/pkg/apiserver/model/system_info.go b/pkg/apiserver/model/system_info.go new file mode 100644 index 000000000..a75c75717 --- /dev/null +++ b/pkg/apiserver/model/system_info.go @@ -0,0 +1,47 @@ +/* +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 model + +func init() { + RegistModel(&SystemInfo{}) +} + +// SystemInfo systemInfo model +type SystemInfo struct { + BaseModel + InstallID string `json:"installID"` + EnableCollection bool `json:"enableCollection"` +} + +// TableName return custom table name +func (u *SystemInfo) TableName() string { + return tableNamePrefix + "system_info" +} + +// PrimaryKey return custom primary key +func (u *SystemInfo) PrimaryKey() string { + return u.InstallID +} + +// Index return custom index +func (u *SystemInfo) Index() map[string]string { + index := make(map[string]string) + if u.InstallID != "" { + index["installID"] = u.InstallID + } + return index +} diff --git a/pkg/apiserver/rest/apis/v1/types.go b/pkg/apiserver/rest/apis/v1/types.go index 7778db068..b72012af0 100644 --- a/pkg/apiserver/rest/apis/v1/types.go +++ b/pkg/apiserver/rest/apis/v1/types.go @@ -987,3 +987,20 @@ type ListRevisionsResponse struct { type DetailRevisionResponse struct { model.ApplicationRevision } + +// SystemInfoResponse get SystemInfo +type SystemInfoResponse struct { + model.SystemInfo + SystemVersion SystemVersion +} + +// SystemInfoRequest request by update SystemInfo +type SystemInfoRequest struct { + EnableCollection bool +} + +// SystemVersion contains KubeVela version +type SystemVersion struct { + KubeVelaVersion string `json:"KubeVelaVersion"` + GitVersion string `json:"gitVersion"` +} diff --git a/pkg/apiserver/rest/usecase/system_info.go b/pkg/apiserver/rest/usecase/system_info.go new file mode 100644 index 000000000..28dbf9197 --- /dev/null +++ b/pkg/apiserver/rest/usecase/system_info.go @@ -0,0 +1,93 @@ +/* +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 usecase + +import ( + "context" + + "github.com/oam-dev/kubevela/version" + + v1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1" + + "k8s.io/apimachinery/pkg/util/rand" + + "github.com/oam-dev/kubevela/pkg/apiserver/model" + + "github.com/oam-dev/kubevela/pkg/apiserver/datastore" +) + +// SystemInfoUsecase is usecase for systemInfoCollection +type SystemInfoUsecase interface { + GetSystemInfo(ctx context.Context) (*v1.SystemInfoResponse, error) + DeleteSystemInfo(ctx context.Context) error + UpdateSystemInfo(ctx context.Context, sysInfo v1.SystemInfoRequest) (*v1.SystemInfoResponse, error) +} + +type systemInfoUsecaseImpl struct { + ds datastore.DataStore +} + +// NewSystemInfoUsecase return a systemInfoCollectionUsecase +func NewSystemInfoUsecase(ds datastore.DataStore) SystemInfoUsecase { + return &systemInfoUsecaseImpl{ds: ds} +} + +func (u systemInfoUsecaseImpl) GetSystemInfo(ctx context.Context) (*v1.SystemInfoResponse, error) { + // first get request will init systemInfoCollection{installId: {random}, enableCollection: true} + info := &model.SystemInfo{} + entities, err := u.ds.List(ctx, info, &datastore.ListOptions{}) + if err != nil { + return nil, err + } + if len(entities) != 0 { + info := entities[0].(*model.SystemInfo) + return &v1.SystemInfoResponse{SystemInfo: *info, SystemVersion: v1.SystemVersion{KubeVelaVersion: version.VelaVersion, GitVersion: version.GitRevision}}, nil + } + installID := rand.String(16) + info.InstallID = installID + info.EnableCollection = true + err = u.ds.Add(ctx, info) + if err != nil { + return nil, err + } + return &v1.SystemInfoResponse{SystemInfo: *info, SystemVersion: v1.SystemVersion{KubeVelaVersion: version.VelaVersion, GitVersion: version.GitRevision}}, nil +} + +func (u systemInfoUsecaseImpl) UpdateSystemInfo(ctx context.Context, sysInfo v1.SystemInfoRequest) (*v1.SystemInfoResponse, error) { + info, err := u.GetSystemInfo(ctx) + if err != nil { + return nil, err + } + modifiedInfo := model.SystemInfo{InstallID: info.InstallID, EnableCollection: sysInfo.EnableCollection} + err = u.ds.Put(ctx, &modifiedInfo) + if err != nil { + return nil, err + } + return &v1.SystemInfoResponse{SystemInfo: modifiedInfo, SystemVersion: v1.SystemVersion{KubeVelaVersion: version.VelaVersion, GitVersion: version.GitRevision}}, nil +} + +func (u systemInfoUsecaseImpl) DeleteSystemInfo(ctx context.Context) error { + info, err := u.GetSystemInfo(ctx) + if err != nil { + return err + } + err = u.ds.Delete(ctx, info) + if err != nil { + return err + } + return nil +} diff --git a/pkg/apiserver/rest/webservice/system_info.go b/pkg/apiserver/rest/webservice/system_info.go new file mode 100644 index 000000000..c1e9e3d68 --- /dev/null +++ b/pkg/apiserver/rest/webservice/system_info.go @@ -0,0 +1,116 @@ +/* +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 webservice + +import ( + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" + + apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1" + "github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase" + "github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode" +) + +type systemInfoWebService struct { + useCase usecase.SystemInfoUsecase +} + +// NewSystemInfoWebService return systemInfo webservice +func NewSystemInfoWebService(systemInfoUseCase usecase.SystemInfoUsecase) WebService { + return &systemInfoWebService{useCase: systemInfoUseCase} +} + +// GetWebService return systemInfo webservice +func (u systemInfoWebService) GetWebService() *restful.WebService { + ws := new(restful.WebService) + ws.Path(versionPrefix+"/system_info").Consumes(restful.MIME_XML, restful.MIME_JSON). + Produces(restful.MIME_JSON, restful.MIME_XML). + Doc("api for systemInfo management") + + tags := []string{"systemInfo"} + + // Get + ws.Route(ws.GET("/").To(u.getSystemInfo). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "", apis.SystemInfoResponse{}). + Returns(400, "", bcode.Bcode{}). + Writes(apis.SystemInfoResponse{})) + + // Delete + ws.Route(ws.DELETE("/").To(u.deleteSystemInfo). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "", apis.SystemInfoResponse{}). + Returns(400, "", bcode.Bcode{}). + Writes(apis.SystemInfoResponse{})) + + // Post + ws.Route(ws.PUT("/").To(u.updateSystemInfo). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(apis.SystemInfoRequest{}). + Returns(200, "", apis.SystemInfoResponse{}). + Returns(400, "", bcode.Bcode{}). + Writes(apis.SystemInfoResponse{})) + + return ws +} + +func (u systemInfoWebService) getSystemInfo(req *restful.Request, res *restful.Response) { + info, err := u.useCase.GetSystemInfo(req.Request.Context()) + if err != nil { + bcode.ReturnError(req, res, err) + return + } + if err := res.WriteEntity(info); err != nil { + bcode.ReturnError(req, res, err) + return + } +} + +func (u systemInfoWebService) updateSystemInfo(req *restful.Request, res *restful.Response) { + var systemInfoReq apis.SystemInfoRequest + var args []byte + _, err := req.Request.Body.Read(args) + if err == nil { + err := req.ReadEntity(&systemInfoReq) + if err != nil { + bcode.ReturnError(req, res, err) + return + } + if err = validate.Struct(&systemInfoReq); err != nil { + bcode.ReturnError(req, res, err) + return + } + } + + info, err := u.useCase.UpdateSystemInfo(req.Request.Context(), systemInfoReq) + if err != nil { + bcode.ReturnError(req, res, err) + return + } + if err := res.WriteEntity(info); err != nil { + bcode.ReturnError(req, res, err) + return + } +} + +func (u systemInfoWebService) deleteSystemInfo(req *restful.Request, res *restful.Response) { + err := u.useCase.DeleteSystemInfo(req.Request.Context()) + if err != nil { + bcode.ReturnError(req, res, err) + return + } +} diff --git a/pkg/apiserver/rest/webservice/webservice.go b/pkg/apiserver/rest/webservice/webservice.go index 445c2804d..55e2c8757 100644 --- a/pkg/apiserver/rest/webservice/webservice.go +++ b/pkg/apiserver/rest/webservice/webservice.go @@ -71,6 +71,7 @@ func Init(ds datastore.DataStore, addonCacheTime time.Duration) { envBindingUsecase := usecase.NewEnvBindingUsecase(ds, workflowUsecase, definitionUsecase, envUsecase) applicationUsecase := usecase.NewApplicationUsecase(ds, workflowUsecase, envBindingUsecase, envUsecase, targetUsecase, definitionUsecase, projectUsecase) webhookUsecase := usecase.NewWebhookUsecase(ds, applicationUsecase) + systemInfoUsecase := usecase.NewSystemInfoUsecase(ds) // init for default values @@ -93,4 +94,6 @@ func Init(ds datastore.DataStore, addonCacheTime time.Duration) { RegisterWebService(NewTargetWebService(targetUsecase, applicationUsecase)) RegisterWebService(NewVelaQLWebService(velaQLUsecase)) RegisterWebService(NewWebhookWebService(webhookUsecase, applicationUsecase)) + + RegisterWebService(NewSystemInfoWebService(systemInfoUsecase)) } diff --git a/test/e2e-apiserver-test/system_info_test.go b/test/e2e-apiserver-test/system_info_test.go new file mode 100644 index 000000000..8a4ce7da4 --- /dev/null +++ b/test/e2e-apiserver-test/system_info_test.go @@ -0,0 +1,138 @@ +/* +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 e2e_apiserver_test + +import ( + "bytes" + "encoding/json" + "net/http" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1" +) + +func put(path string, body interface{}) *http.Response { + b, err := json.Marshal(body) + Expect(err).Should(BeNil()) + + req, err := http.NewRequest(http.MethodPut, baseURL+path, bytes.NewBuffer(b)) + Expect(err).Should(BeNil()) + req.Header.Set("Content-Type", "application/json") + res, err := http.DefaultClient.Do(req) + Expect(err).Should(BeNil()) + Expect(res).ShouldNot(BeNil()) + Expect(res.StatusCode).Should(Equal(200)) + return res +} + +var _ = Describe("Test system info rest api", func() { + BeforeEach(func() { + req, err := http.NewRequest(http.MethodDelete, baseURL+"/api/v1/system_info/", nil) + Expect(err).Should(BeNil()) + deleteRes, err := http.DefaultClient.Do(req) + Expect(err).Should(BeNil()) + Expect(deleteRes).ShouldNot(BeNil()) + Expect(deleteRes.StatusCode).Should(Equal(200)) + }) + + It("Test get SystemInfo", func() { + response := get("/api/v1/system_info/") + Expect(response).ShouldNot(BeNil()) + Expect(response.Body).ShouldNot(BeNil()) + Expect(response.StatusCode).Should(Equal(200)) + + defer response.Body.Close() + + var info apisv1.SystemInfoResponse + err := json.NewDecoder(response.Body).Decode(&info) + Expect(err).Should(BeNil()) + Expect(len(info.InstallID)).ShouldNot(BeEquivalentTo(0)) + Expect(info.EnableCollection).Should(BeEquivalentTo(true)) + systemID := info.InstallID + + // check several times the systemID should not change + for i := 0; i < 5; i++ { + check := get("/api/v1/system_info/") + Expect(check).ShouldNot(BeNil()) + Expect(check.Body).ShouldNot(BeNil()) + Expect(check.StatusCode).Should(Equal(200)) + + var checkInfo apisv1.SystemInfoResponse + err := json.NewDecoder(check.Body).Decode(&checkInfo) + Expect(err).Should(BeNil()) + Expect(checkInfo.InstallID).Should(BeEquivalentTo(systemID)) + } + }) + + It("Test disable/enable systemInfoCollection", func() { + response := get("/api/v1/system_info/") + Expect(response).ShouldNot(BeNil()) + Expect(response.Body).ShouldNot(BeNil()) + Expect(response.StatusCode).Should(Equal(200)) + + defer response.Body.Close() + + var info apisv1.SystemInfoResponse + err := json.NewDecoder(response.Body).Decode(&info) + Expect(err).Should(BeNil()) + Expect(len(info.InstallID)).ShouldNot(BeEquivalentTo(0)) + Expect(info.EnableCollection).Should(BeEquivalentTo(true)) + installID := info.InstallID + + response = put("/api/v1/system_info/", apisv1.SystemInfoRequest{EnableCollection: false}) + info = apisv1.SystemInfoResponse{} + err = json.NewDecoder(response.Body).Decode(&info) + Expect(err).Should(BeNil()) + Expect(len(info.InstallID)).ShouldNot(BeEquivalentTo(0)) + Expect(info.EnableCollection).Should(BeEquivalentTo(false)) + + getRes := get("/api/v1/system_info/") + Expect(getRes).ShouldNot(BeNil()) + Expect(getRes.Body).ShouldNot(BeNil()) + Expect(getRes.StatusCode).Should(Equal(200)) + + var checkInfo apisv1.SystemInfoResponse + err = json.NewDecoder(getRes.Body).Decode(&checkInfo) + Expect(err).Should(BeNil()) + Expect(checkInfo.InstallID).Should(BeEquivalentTo(installID)) + Expect(checkInfo.EnableCollection).Should(BeEquivalentTo(false)) + + response = put("/api/v1/system_info/", apisv1.SystemInfoRequest{EnableCollection: true}) + Expect(response).ShouldNot(BeNil()) + Expect(response.StatusCode).Should(Equal(200)) + + var enableInfo apisv1.SystemInfoResponse + err = json.NewDecoder(response.Body).Decode(&enableInfo) + Expect(err).Should(BeNil()) + Expect(len(enableInfo.InstallID)).ShouldNot(BeEquivalentTo(0)) + Expect(enableInfo.EnableCollection).Should(BeEquivalentTo(true)) + Expect(enableInfo.InstallID).Should(BeEquivalentTo(installID)) + + getAgainRes := get("/api/v1/system_info/") + Expect(getRes).ShouldNot(BeNil()) + Expect(getRes.Body).ShouldNot(BeNil()) + Expect(getRes.StatusCode).Should(Equal(200)) + + var checkAgainInfo apisv1.SystemInfoResponse + err = json.NewDecoder(getAgainRes.Body).Decode(&checkAgainInfo) + Expect(err).Should(BeNil()) + Expect(checkAgainInfo.InstallID).Should(BeEquivalentTo(installID)) + Expect(checkAgainInfo.EnableCollection).Should(BeEquivalentTo(true)) + }) +})