Feat: calculate systemInfo everyday periodically and store them in datastore (#3689)

* add framework

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

add

finish the framework

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

finish test manually

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

add update time

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

adding test

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

finish test

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

abs

fix test

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

* move start func to leader election call back funcs

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

* resolve the recycle import problecm

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

fix issue

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

fix ci
This commit is contained in:
wyike 2022-04-19 10:24:54 +08:00 committed by GitHub
parent c5e1855a55
commit 2ac4ddad03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 682 additions and 9 deletions

View File

@ -46,6 +46,7 @@ func main() {
flag.StringVar(&s.restCfg.LeaderConfig.LockName, "lock-name", "apiserver-lock", "the lease lock resource name")
flag.DurationVar(&s.restCfg.LeaderConfig.Duration, "duration", time.Second*5, "the lease lock resource name")
flag.DurationVar(&s.restCfg.AddonCacheTime, "addon-cache-duration", time.Minute*10, "how long between two addon cache operation")
flag.BoolVar(&s.restCfg.DisableStatisticCronJob, "disable-statistic-cronJob", false, "close the system statistic info calculating cronJob")
flag.Parse()
if len(os.Args) > 2 && os.Args[1] == "build-swagger" {

1
go.mod
View File

@ -232,6 +232,7 @@ require (
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/robfig/cron/v3 v3.0.1
github.com/rogpeppe/go-internal v1.8.0 // indirect
github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 // indirect
github.com/russross/blackfriday v1.5.2 // indirect

2
go.sum
View File

@ -1447,6 +1447,8 @@ github.com/rancher/wrangler v0.4.0/go.mod h1:1cR91WLhZgkZ+U4fV9nVuXqKurWbgXcIReU
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=

View File

@ -0,0 +1,100 @@
/*
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 collect
import (
"context"
"fmt"
"math/rand"
"testing"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/client-go/rest"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore/kubeapi"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore/mongodb"
"github.com/oam-dev/kubevela/pkg/utils/common"
)
var cfg *rest.Config
var k8sClient client.Client
var testEnv *envtest.Environment
func TestCalculateJob(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Caclculate systemInfo cronJob")
}
var _ = BeforeSuite(func(done Done) {
rand.Seed(time.Now().UnixNano())
By("bootstrapping test environment")
testEnv = &envtest.Environment{
ControlPlaneStartTimeout: time.Minute * 3,
ControlPlaneStopTimeout: time.Minute,
UseExistingCluster: pointer.BoolPtr(false),
CRDDirectoryPaths: []string{"../../../charts/vela-core/crds"},
}
By("start kube test env")
var err error
cfg, err = testEnv.Start()
Expect(err).Should(BeNil())
Expect(cfg).ToNot(BeNil())
By("new kube client")
cfg.Timeout = time.Minute * 2
k8sClient, err = client.New(cfg, client.Options{Scheme: common.Scheme})
Expect(err).Should(BeNil())
Expect(k8sClient).ToNot(BeNil())
By("new kube client success")
clients.SetKubeClient(k8sClient)
Expect(err).Should(BeNil())
close(done)
}, 240)
var _ = AfterSuite(func() {
By("tearing down the test environment")
err := testEnv.Stop()
Expect(err).ToNot(HaveOccurred())
})
func NewDatastore(cfg datastore.Config) (ds datastore.DataStore, err error) {
switch cfg.Type {
case "mongodb":
ds, err = mongodb.New(context.Background(), cfg)
if err != nil {
return nil, fmt.Errorf("create mongodb datastore instance failure %w", err)
}
case "kubeapi":
ds, err = kubeapi.New(context.Background(), cfg)
if err != nil {
return nil, fmt.Errorf("create mongodb datastore instance failure %w", err)
}
default:
return nil, fmt.Errorf("not support datastore type %s", cfg.Type)
}
return ds, nil
}

View File

@ -0,0 +1,282 @@
/*
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 collect
import (
"context"
"sort"
"time"
client2 "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
"github.com/robfig/cron/v3"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/retry"
)
// TopKFrequent top frequency component or trait definition
var TopKFrequent = 5
// CrontabSpec the cron spec of job running
var CrontabSpec = "0 0 * * *"
// maximum tires is 5, initial duration is 1 minute
var waitBackOff = wait.Backoff{
Steps: 5,
Duration: 1 * time.Minute,
Factor: 5.0,
Jitter: 0.1,
}
// InfoCalculateCronJob is the cronJob to calculate the system info store in db
type InfoCalculateCronJob struct {
ds datastore.DataStore
}
// StartCalculatingInfoCronJob will start the system info calculating job.
func StartCalculatingInfoCronJob(ds datastore.DataStore) {
i := InfoCalculateCronJob{
ds: ds,
}
// run calculate job in 0:00 of every day
i.start(CrontabSpec)
}
func (i InfoCalculateCronJob) start(cronSpec string) {
c := cron.New(cron.WithChain(
// don't let job panic crash whole api-server process
cron.Recover(cron.DefaultLogger),
))
// ignore the entityId and error, the cron spec is defined by hard code, mustn't generate error
_, _ = c.AddFunc(cronSpec, func() {
// ExponentialBackoff retry this job
err := retry.OnError(waitBackOff, func(err error) bool {
// always retry
return true
}, func() error {
if err := i.run(); err != nil {
log.Logger.Errorf("Fialed to calculate systemInfo, will try again after several minute error %v", err)
return err
}
log.Logger.Info("Successfully to calculate systemInfo")
return nil
})
if err != nil {
log.Logger.Errorf("After 5 tries the calculating cronJob failed: %v", err)
}
})
c.Start()
}
func (i InfoCalculateCronJob) run() error {
ctx := context.Background()
systemInfo := model.SystemInfo{}
e, err := i.ds.List(ctx, &systemInfo, &datastore.ListOptions{})
if err != nil {
return err
}
// if no systemInfo means velaux have not have not send get requestso skip calculate job
if len(e) == 0 {
return nil
}
info, ok := e[0].(*model.SystemInfo)
if !ok {
return nil
}
// if disable collection skip calculate job
if !info.EnableCollection {
return nil
}
if err := i.calculateAndUpdate(ctx, *info); err != nil {
return err
}
return nil
}
func (i InfoCalculateCronJob) calculateAndUpdate(ctx context.Context, systemInfo model.SystemInfo) error {
appCount, topKComp, topKTrait, err := i.calculateAppInfo(ctx)
if err != nil {
return err
}
enabledAddon, err := i.calculateAddonInfo(ctx)
if err != nil {
return err
}
clusterCount, err := i.calculateClusterInfo(ctx)
if err != nil {
return err
}
statisticInfo := model.StatisticInfo{
AppCount: genCountInfo(appCount),
TopKCompDef: topKComp,
TopKTraitDef: topKTrait,
ClusterCount: genCountInfo(clusterCount),
EnabledAddon: enabledAddon,
UpdateTime: time.Now(),
}
systemInfo.StatisticInfo = statisticInfo
if err := i.ds.Put(ctx, &systemInfo); err != nil {
return err
}
return nil
}
func (i InfoCalculateCronJob) calculateAppInfo(ctx context.Context) (int, []string, []string, error) {
var err error
var appCount int
compDef := map[string]int{}
traitDef := map[string]int{}
var app = model.Application{}
entities, err := i.ds.List(ctx, &app, &datastore.ListOptions{})
if err != nil {
return 0, nil, nil, err
}
for _, entity := range entities {
appModel, ok := entity.(*model.Application)
if !ok {
continue
}
appCount++
comp := model.ApplicationComponent{
AppPrimaryKey: appModel.Name,
}
comps, err := i.ds.List(ctx, &comp, &datastore.ListOptions{})
if err != nil {
return 0, nil, nil, err
}
for _, e := range comps {
c, ok := e.(*model.ApplicationComponent)
if !ok {
continue
}
compDef[c.Type]++
for _, t := range c.Traits {
traitDef[t.Type]++
}
}
}
return appCount, topKFrequent(compDef, TopKFrequent), topKFrequent(traitDef, TopKFrequent), nil
}
func (i InfoCalculateCronJob) calculateAddonInfo(ctx context.Context) (map[string]string, error) {
client, err := clients.GetKubeClient()
if err != nil {
return nil, err
}
apps := &v1beta1.ApplicationList{}
if err := client.List(ctx, apps, client2.InNamespace(types.DefaultKubeVelaNS), client2.HasLabels{oam.LabelAddonName}); err != nil {
return nil, err
}
res := map[string]string{}
for _, application := range apps.Items {
if addonName := application.Labels[oam.LabelAddonName]; addonName != "" {
var status string
switch application.Status.Phase {
case common.ApplicationRunning:
status = "enabled"
case common.ApplicationDeleting:
status = "disabling"
default:
status = "enabling"
}
res[addonName] = status
}
}
return res, nil
}
func (i InfoCalculateCronJob) calculateClusterInfo(ctx context.Context) (int, error) {
client, err := clients.GetKubeClient()
if err != nil {
return 0, err
}
cs, err := multicluster.ListVirtualClusters(ctx, client)
if err != nil {
return 0, err
}
return len(cs), nil
}
type defPair struct {
name string
count int
}
func topKFrequent(defs map[string]int, k int) []string {
var pairs []defPair
var res []string
for name, num := range defs {
pairs = append(pairs, defPair{name: name, count: num})
}
sort.Slice(pairs, func(i, j int) bool {
return pairs[i].count >= pairs[j].count
})
i := 0
for _, pair := range pairs {
res = append(res, pair.name)
i++
if i == k {
break
}
}
return res
}
func genCountInfo(num int) string {
switch {
case num < 10:
return "<10"
case num < 50:
return "<50"
case num < 100:
return "<100"
case num < 500:
return "<500"
case num < 2000:
return "<2000"
case num < 5000:
return "<5000"
case num < 10000:
return "<10000"
default:
return ">=10000"
}
}

View File

@ -0,0 +1,246 @@
/*
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 collect
import (
"context"
"errors"
"testing"
"time"
"github.com/onsi/gomega/format"
"gotest.tools/assert"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
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/v1beta1"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
var _ = Describe("Test calculate cronJob", func() {
var (
ds datastore.DataStore
testProject string
i InfoCalculateCronJob
ctx = context.Background()
)
mockDataInDs := func() {
app1 := model.Application{BaseModel: model.BaseModel{CreateTime: time.Now()}, Name: "app1", Project: testProject}
app2 := model.Application{BaseModel: model.BaseModel{CreateTime: time.Now()}, Name: "app2", Project: testProject}
trait1 := model.ApplicationTrait{Type: "rollout"}
trait2 := model.ApplicationTrait{Type: "expose"}
trait3 := model.ApplicationTrait{Type: "rollout"}
trait4 := model.ApplicationTrait{Type: "patch"}
trait5 := model.ApplicationTrait{Type: "patch"}
trait6 := model.ApplicationTrait{Type: "rollout"}
appComp1 := model.ApplicationComponent{AppPrimaryKey: app1.PrimaryKey(), Name: "comp1", Type: "helm", Traits: []model.ApplicationTrait{trait1, trait4}}
appComp2 := model.ApplicationComponent{AppPrimaryKey: app2.PrimaryKey(), Name: "comp2", Type: "webservice", Traits: []model.ApplicationTrait{trait3}}
appComp3 := model.ApplicationComponent{AppPrimaryKey: app2.PrimaryKey(), Name: "comp3", Type: "webservice", Traits: []model.ApplicationTrait{trait2, trait5, trait6}}
Expect(ds.Add(ctx, &app1)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
Expect(ds.Add(ctx, &app2)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
Expect(ds.Add(ctx, &appComp1)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
Expect(ds.Add(ctx, &appComp2)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
Expect(ds.Add(ctx, &appComp3)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "vela-system"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
Expect(k8sClient.Create(ctx, &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "vela-system", Name: "addon-fluxcd", Labels: map[string]string{oam.LabelAddonName: "fluxcd"}}, Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{},
}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
Expect(k8sClient.Create(ctx, &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "vela-system", Name: "addon-rollout", Labels: map[string]string{oam.LabelAddonName: "rollout"}}, Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{},
}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
}
BeforeEach(func() {
var err error
ds, err = NewDatastore(datastore.Config{Type: "kubeapi", Database: "target-test-kubevela"})
Expect(ds).ShouldNot(BeNil())
Expect(err).Should(BeNil())
testProject = "test-cronjob-project"
mockDataInDs()
i = InfoCalculateCronJob{
ds: ds,
}
systemInfo := model.SystemInfo{InstallID: "test-id", EnableCollection: true}
Expect(ds.Add(ctx, &systemInfo)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
})
It("Test calculate app Info", func() {
appNum, topKCom, topKTrait, err := i.calculateAppInfo(ctx)
Expect(err).Should(BeNil())
Expect(appNum).Should(BeEquivalentTo(2))
Expect(topKCom).Should(BeEquivalentTo([]string{"webservice", "helm"}))
Expect(topKTrait).Should(BeEquivalentTo([]string{"rollout", "patch", "expose"}))
})
It("Test calculate addon Info", func() {
enabledAddon, err := i.calculateAddonInfo(ctx)
Expect(err).Should(BeNil())
Expect(enabledAddon).Should(BeEquivalentTo(map[string]string{
"fluxcd": "enabling",
"rollout": "enabling",
}))
})
It("Test calculate cluster Info", func() {
clusterNum, err := i.calculateClusterInfo(ctx)
Expect(err).Should(BeNil())
Expect(clusterNum).Should(BeEquivalentTo(1))
})
It("Test calculateAndUpdate func", func() {
systemInfo := model.SystemInfo{}
es, err := ds.List(ctx, &systemInfo, &datastore.ListOptions{})
Expect(err).Should(BeNil())
Expect(len(es)).Should(BeEquivalentTo(1))
info, ok := es[0].(*model.SystemInfo)
Expect(ok).Should(BeTrue())
Expect(info.InstallID).Should(BeEquivalentTo("test-id"))
Expect(i.calculateAndUpdate(ctx, *info)).Should(BeNil())
systemInfo = model.SystemInfo{}
es, err = ds.List(ctx, &systemInfo, &datastore.ListOptions{})
Expect(err).Should(BeNil())
Expect(len(es)).Should(BeEquivalentTo(1))
info, ok = es[0].(*model.SystemInfo)
Expect(ok).Should(BeTrue())
Expect(info.InstallID).Should(BeEquivalentTo("test-id"))
Expect(info.StatisticInfo.AppCount).Should(BeEquivalentTo("<10"))
Expect(info.StatisticInfo.ClusterCount).Should(BeEquivalentTo("<10"))
Expect(info.StatisticInfo.TopKCompDef).Should(BeEquivalentTo([]string{"webservice", "helm"}))
Expect(info.StatisticInfo.TopKTraitDef).Should(BeEquivalentTo([]string{"rollout", "patch", "expose"}))
Expect(info.StatisticInfo.EnabledAddon).Should(BeEquivalentTo(map[string]string{
"fluxcd": "enabling",
"rollout": "enabling",
}))
})
It("Test run func", func() {
app3 := model.Application{BaseModel: model.BaseModel{CreateTime: time.Now()}, Name: "app3", Project: testProject}
Expect(ds.Add(ctx, &app3)).Should(BeNil())
systemInfo := model.SystemInfo{InstallID: "test-id", EnableCollection: false}
Expect(ds.Put(ctx, &systemInfo)).Should(BeNil())
Expect(i.run()).Should(BeNil())
})
})
func TestGenCountInfo(t *testing.T) {
testcases := []struct {
count int
res string
}{
{
count: 3,
res: "<10",
},
{
count: 14,
res: "<50",
},
{
count: 80,
res: "<100",
},
{
count: 350,
res: "<500",
},
{
count: 1800,
res: "<2000",
},
{
count: 4000,
res: "<5000",
},
{
count: 9000,
res: "<10000",
},
{
count: 30000,
res: ">=10000",
},
}
for _, testcase := range testcases {
assert.Equal(t, genCountInfo(testcase.count), testcase.res)
}
}
func TestTopKFrequent(t *testing.T) {
testCases := []struct {
def map[string]int
k int
res []string
}{
{
def: map[string]int{
"rollout": 4,
"patch": 3,
"expose": 6,
},
k: 3,
res: []string{"expose", "rollout", "patch"},
},
{
// just return top2
def: map[string]int{
"rollout": 4,
"patch": 3,
"expose": 6,
},
k: 2,
res: []string{"expose", "rollout"},
},
}
for _, testCase := range testCases {
assert.DeepEqual(t, topKFrequent(testCase.def, testCase.k), testCase.res)
}
}
type DataExistMatcher struct{}
// Match matches error.
func (matcher DataExistMatcher) Match(actual interface{}) (success bool, err error) {
if actual == nil {
return false, nil
}
actualError := actual.(error)
return errors.Is(actualError, datastore.ErrRecordExist), nil
}
// FailureMessage builds an error message.
func (matcher DataExistMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to be already exist")
}
// NegatedFailureMessage builds an error message.
func (matcher DataExistMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to be already exist")
}

View File

@ -16,6 +16,8 @@ limitations under the License.
package model
import "time"
func init() {
RegisterModel(&SystemInfo{})
}
@ -30,10 +32,11 @@ const (
// SystemInfo systemInfo model
type SystemInfo struct {
BaseModel
InstallID string `json:"installID"`
EnableCollection bool `json:"enableCollection"`
LoginType string `json:"loginType"`
DexConfig DexConfig `json:"dexConfig,omitempty"`
InstallID string `json:"installID"`
EnableCollection bool `json:"enableCollection"`
LoginType string `json:"loginType"`
DexConfig DexConfig `json:"dexConfig,omitempty"`
StatisticInfo StatisticInfo `json:"statisticInfo,omitempty"`
}
// DexConfig dex config
@ -46,6 +49,16 @@ type DexConfig struct {
EnablePasswordDB bool `json:"enablePasswordDB"`
}
// StatisticInfo the system statistic info
type StatisticInfo struct {
ClusterCount string `json:"clusterCount,omitempty"`
AppCount string `json:"appCount,omitempty"`
EnabledAddon map[string]string `json:"enabledAddon,omitempty"`
TopKCompDef []string `json:"topKCompDef,omitempty"`
TopKTraitDef []string `json:"topKTraitDef,omitempty"`
UpdateTime time.Time `json:"updateTime,omitempty"`
}
// DexStorage dex storage
type DexStorage struct {
Type string `json:"type"`

View File

@ -1119,6 +1119,7 @@ type DetailRevisionResponse struct {
type SystemInfoResponse struct {
SystemInfo
SystemVersion SystemVersion `json:"systemVersion"`
StatisticInfo StatisticInfo `json:"statisticInfo,omitempty"`
}
// SystemInfo system info
@ -1128,6 +1129,16 @@ type SystemInfo struct {
LoginType string `json:"loginType"`
}
// StatisticInfo generated by cronJob running in backend
type StatisticInfo struct {
ClusterCount string `json:"clusterCount,omitempty"`
AppCount string `json:"appCount,omitempty"`
EnabledAddon map[string]string `json:"enabledAddon,omitempty"`
TopKCompDef []string `json:"topKCompDef,omitempty"`
TopKTraitDef []string `json:"topKTraitDef,omitempty"`
UpdateTime time.Time `json:"updateTime,omitempty"`
}
// SystemInfoRequest request by update SystemInfo
type SystemInfoRequest struct {
EnableCollection bool `json:"enableCollection"`

View File

@ -23,6 +23,8 @@ import (
"os"
"time"
"github.com/oam-dev/kubevela/pkg/apiserver/collect"
restfulspec "github.com/emicklei/go-restful-openapi/v2"
"github.com/emicklei/go-restful/v3"
"github.com/go-openapi/spec"
@ -60,6 +62,9 @@ type Config struct {
// AddonCacheTime is how long between two cache operations
AddonCacheTime time.Duration
// DisableStatisticCronJob close the calculate system info cronJob
DisableStatisticCronJob bool
}
type leaderConfig struct {
@ -142,6 +147,9 @@ func (s *restServer) setupLeaderElection() (*leaderelection.LeaderElectionConfig
OnStartedLeading: func(ctx context.Context) {
go velasync.Start(ctx, s.dataStore, restCfg, s.usecases)
s.runWorkflowRecordSync(ctx, s.cfg.LeaderConfig.Duration)
if !s.cfg.DisableStatisticCronJob {
collect.StartCalculatingInfoCronJob(s.dataStore)
}
},
OnStoppedLeading: func() {
klog.Infof("leader lost: %s", s.cfg.LeaderConfig.ID)

View File

@ -100,6 +100,14 @@ func (u systemInfoUsecaseImpl) GetSystemInfo(ctx context.Context) (*v1.SystemInf
VelaVersion: version.VelaVersion,
GitVersion: version.GitRevision,
},
StatisticInfo: v1.StatisticInfo{
AppCount: info.StatisticInfo.AppCount,
ClusterCount: info.StatisticInfo.ClusterCount,
EnabledAddon: info.StatisticInfo.EnabledAddon,
TopKCompDef: info.StatisticInfo.TopKCompDef,
TopKTraitDef: info.StatisticInfo.TopKTraitDef,
UpdateTime: info.StatisticInfo.UpdateTime,
},
}, nil
}
@ -115,6 +123,7 @@ func (u systemInfoUsecaseImpl) UpdateSystemInfo(ctx context.Context, sysInfo v1.
BaseModel: model.BaseModel{
CreateTime: info.CreateTime,
},
StatisticInfo: info.StatisticInfo,
}
if sysInfo.LoginType == model.LoginTypeDex {

View File

@ -22,11 +22,11 @@ import (
"net/http"
"time"
"github.com/emicklei/go-restful/v3"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase"
"github.com/emicklei/go-restful/v3"
)
// versionPrefix API version prefix.

View File

@ -21,13 +21,13 @@ import (
"reflect"
"sort"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/types"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
)
// JSONMarshal returns the JSON encoding