mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-24 14:54:06 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d08aa7d12c | ||
|
|
f9755a405f | ||
|
|
d751d95bac | ||
|
|
e86eec07e0 | ||
|
|
4abb5c6ced | ||
|
|
32d9a9ec94 | ||
|
|
f6f9ef4ded | ||
|
|
166c93d548 | ||
|
|
8f767068bf | ||
|
|
8d9e2a71e7 | ||
|
|
58b3bca537 |
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
@@ -57,7 +57,7 @@ jobs:
|
||||
restore-keys: ${{ runner.os }}-pkg-
|
||||
|
||||
- name: Install StaticCheck
|
||||
run: GO111MODULE=off go get honnef.co/go/tools/cmd/staticcheck
|
||||
run: GO111MODULE=on go get honnef.co/go/tools/cmd/staticcheck@v0.3.0
|
||||
|
||||
- name: Static Check
|
||||
run: staticcheck ./...
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/interfaces"
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/errors"
|
||||
)
|
||||
@@ -121,7 +122,11 @@ func (in ManagedResource) NamespacedName() types.NamespacedName {
|
||||
// ResourceKey computes the key for managed resource, resources with the same key points to the same resource
|
||||
func (in ManagedResource) ResourceKey() string {
|
||||
gv, kind := in.GroupVersionKind().ToAPIVersionAndKind()
|
||||
return strings.Join([]string{gv, kind, in.Cluster, in.Namespace, in.Name}, "/")
|
||||
cluster := in.Cluster
|
||||
if cluster == "" {
|
||||
cluster = velatypes.ClusterLocalName
|
||||
}
|
||||
return strings.Join([]string{gv, kind, cluster, in.Namespace, in.Name}, "/")
|
||||
}
|
||||
|
||||
// ComponentKey computes the key for the component which managed resource belongs to
|
||||
|
||||
@@ -22,6 +22,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// ClusterLocalName the name for the hub cluster
|
||||
ClusterLocalName = "local"
|
||||
|
||||
// CredentialTypeInternal identifies the virtual cluster from internal kubevela system
|
||||
CredentialTypeInternal v1alpha1.CredentialType = "Internal"
|
||||
// CredentialTypeOCMManagedCluster identifies the virtual cluster from ocm
|
||||
|
||||
@@ -73,8 +73,8 @@ const (
|
||||
LabelConfigSyncToMultiCluster = "config.oam.dev/multi-cluster"
|
||||
// LabelConfigIdentifier is the label for config identifier
|
||||
LabelConfigIdentifier = "config.oam.dev/identifier"
|
||||
// LabelConfigDescription is the label for config description
|
||||
LabelConfigDescription = "config.oam.dev/description"
|
||||
// AnnotationConfigDescription is the annotation for config description
|
||||
AnnotationConfigDescription = "config.oam.dev/description"
|
||||
// AnnotationConfigAlias is the annotation for config alias
|
||||
AnnotationConfigAlias = "config.oam.dev/alias"
|
||||
)
|
||||
@@ -148,3 +148,8 @@ const (
|
||||
// HelmRepository is the config type for Helm chart repository
|
||||
HelmRepository = "config-helm-repository"
|
||||
)
|
||||
|
||||
const (
|
||||
// TerrfaormComponentPrefix is the prefix of component type of terraform-xxx
|
||||
TerrfaormComponentPrefix = "terraform-"
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ metadata:
|
||||
custom.definition.oam.dev/catalog.config.oam.dev: velacore-config
|
||||
custom.definition.oam.dev/multi-cluster.config.oam.dev: "true"
|
||||
custom.definition.oam.dev/type.config.oam.dev: image-registry
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: config-image-registry
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
|
||||
@@ -455,7 +455,7 @@ spec:
|
||||
readinessProbe?: #HealthProbe
|
||||
|
||||
// +usage=Specify the hostAliases to add
|
||||
hostAliases: [...{
|
||||
hostAliases?: [...{
|
||||
ip: string
|
||||
hostnames: [...string]
|
||||
}]
|
||||
|
||||
@@ -10,6 +10,7 @@ metadata:
|
||||
custom.definition.oam.dev/catalog.config.oam.dev: velacore-config
|
||||
custom.definition.oam.dev/multi-cluster.config.oam.dev: "true"
|
||||
custom.definition.oam.dev/type.config.oam.dev: image-registry
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: config-image-registry
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
|
||||
@@ -455,7 +455,7 @@ spec:
|
||||
readinessProbe?: #HealthProbe
|
||||
|
||||
// +usage=Specify the hostAliases to add
|
||||
hostAliases: [...{
|
||||
hostAliases?: [...{
|
||||
ip: string
|
||||
hostnames: [...string]
|
||||
}]
|
||||
|
||||
23
e2e/addon/mock/testdata/mock-addon/metadata.yaml
vendored
Normal file
23
e2e/addon/mock/testdata/mock-addon/metadata.yaml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: mock-addon
|
||||
version: 1.0.0
|
||||
description: Extended workload to do continuous and progressive delivery
|
||||
icon: https://raw.githubusercontent.com/fluxcd/flux/master/docs/_files/weave-flux.png
|
||||
url: https://fluxcd.io
|
||||
|
||||
tags:
|
||||
- extended_workload
|
||||
- gitops
|
||||
- only_example
|
||||
|
||||
deployTo:
|
||||
control_plane: true
|
||||
runtime_cluster: false
|
||||
|
||||
dependencies: []
|
||||
#- name: addon_name
|
||||
|
||||
# set invisible means this won't be list and will be enabled when depended on
|
||||
# for example, terraform-alibaba depends on terraform which is invisible,
|
||||
# when terraform-alibaba is enabled, terraform will be enabled automatically
|
||||
# default: false
|
||||
invisible: false
|
||||
14
e2e/addon/mock/testdata/mock-addon/template.yaml
vendored
Normal file
14
e2e/addon/mock/testdata/mock-addon/template.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: mock-addon
|
||||
namespace: vela-system
|
||||
spec:
|
||||
components:
|
||||
- name: ns-example-system
|
||||
type: raw
|
||||
properties:
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: mock-system
|
||||
@@ -129,7 +129,7 @@ var _ = Describe("Test Kubectl Plugin", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(output).Should(ContainSubstring(showTdResult))
|
||||
})
|
||||
PIt("Test show componentDefinition use Helm Charts as Workload", func() {
|
||||
It("Test show componentDefinition use Helm Charts as Workload", func() {
|
||||
Eventually(func() string {
|
||||
cdName := "test-webapp-chart"
|
||||
output, _ := e2e.Exec(fmt.Sprintf("kubectl-vela show %s -n default", cdName))
|
||||
|
||||
2
go.mod
2
go.mod
@@ -97,6 +97,8 @@ require (
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
)
|
||||
|
||||
require github.com/robfig/cron/v3 v3.0.1
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.81.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -1437,6 +1437,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=
|
||||
|
||||
@@ -25,7 +25,7 @@ ifeq (, $(shell which staticcheck))
|
||||
@{ \
|
||||
set -e ;\
|
||||
echo 'installing honnef.co/go/tools/cmd/staticcheck ' ;\
|
||||
GO111MODULE=off go get honnef.co/go/tools/cmd/staticcheck ;\
|
||||
GO111MODULE=on go get honnef.co/go/tools/cmd/staticcheck@v0.3.0 ;\
|
||||
}
|
||||
STATICCHECK=$(GOBIN)/staticcheck
|
||||
else
|
||||
|
||||
@@ -49,6 +49,7 @@ e2e-apiserver-test:
|
||||
pkill vela_addon_mock_server || true
|
||||
go run ./e2e/addon/mock/vela_addon_mock_server.go &
|
||||
go test -v -coverpkg=./... -coverprofile=/tmp/e2e_apiserver_test.out ./test/e2e-apiserver-test
|
||||
sleep 15
|
||||
@$(OK) tests pass
|
||||
|
||||
.PHONY: e2e-test
|
||||
|
||||
100
pkg/apiserver/collect/suit_test.go
Normal file
100
pkg/apiserver/collect/suit_test.go
Normal 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
|
||||
}
|
||||
331
pkg/apiserver/collect/system_info_collect.go
Normal file
331
pkg/apiserver/collect/system_info_collect.go
Normal file
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
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("Failed 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 request,so 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, topWorkflowStep, topKPolicy, 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,
|
||||
TopKWorkflowStepDef: topWorkflowStep,
|
||||
TopKPolicyDef: topKPolicy,
|
||||
ClusterCount: genClusterCountInfo(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, []string, []string, error) {
|
||||
var err error
|
||||
var appCount int
|
||||
compDef := map[string]int{}
|
||||
traitDef := map[string]int{}
|
||||
workflowDef := map[string]int{}
|
||||
policyDef := map[string]int{}
|
||||
|
||||
var app = model.Application{}
|
||||
entities, err := i.ds.List(ctx, &app, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return 0, nil, nil, 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, 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]++
|
||||
}
|
||||
}
|
||||
|
||||
workflow := model.Workflow{
|
||||
AppPrimaryKey: app.PrimaryKey(),
|
||||
}
|
||||
workflows, err := i.ds.List(ctx, &workflow, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return 0, nil, nil, nil, nil, err
|
||||
}
|
||||
for _, e := range workflows {
|
||||
w, ok := e.(*model.Workflow)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, step := range w.Steps {
|
||||
workflowDef[step.Type]++
|
||||
}
|
||||
}
|
||||
|
||||
policy := model.ApplicationPolicy{
|
||||
AppPrimaryKey: app.PrimaryKey(),
|
||||
}
|
||||
policies, err := i.ds.List(ctx, &policy, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return 0, nil, nil, nil, nil, err
|
||||
}
|
||||
for _, e := range policies {
|
||||
p, ok := e.(*model.ApplicationPolicy)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
policyDef[p.Type]++
|
||||
}
|
||||
}
|
||||
|
||||
return appCount, topKFrequent(compDef, TopKFrequent), topKFrequent(traitDef, TopKFrequent), topKFrequent(workflowDef, TopKFrequent), topKFrequent(policyDef, 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"
|
||||
}
|
||||
}
|
||||
|
||||
func genClusterCountInfo(num int) string {
|
||||
switch {
|
||||
case num < 3:
|
||||
return "<3"
|
||||
case num < 10:
|
||||
return "<10"
|
||||
case num < 50:
|
||||
return "<50"
|
||||
default:
|
||||
return ">=50"
|
||||
}
|
||||
}
|
||||
273
pkg/apiserver/collect/system_info_collect_test.go
Normal file
273
pkg/apiserver/collect/system_info_collect_test.go
Normal file
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
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("<3"))
|
||||
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 TestGenClusterCountInfo(t *testing.T) {
|
||||
testcases := []struct {
|
||||
count int
|
||||
res string
|
||||
}{
|
||||
{
|
||||
count: 2,
|
||||
res: "<3",
|
||||
},
|
||||
{
|
||||
count: 7,
|
||||
res: "<10",
|
||||
},
|
||||
{
|
||||
count: 34,
|
||||
res: "<50",
|
||||
},
|
||||
{
|
||||
count: 100,
|
||||
res: ">=50",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
assert.Equal(t, genClusterCountInfo(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")
|
||||
}
|
||||
@@ -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,18 @@ 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"`
|
||||
TopKWorkflowStepDef []string `json:"topKWorkflowStepDef,omitempty"`
|
||||
TopKPolicyDef []string `json:"topKPolicyDef,omitempty"`
|
||||
UpdateTime time.Time `json:"updateTime,omitempty"`
|
||||
}
|
||||
|
||||
// DexStorage dex storage
|
||||
type DexStorage struct {
|
||||
Type string `json:"type"`
|
||||
|
||||
@@ -199,6 +199,7 @@ type Config struct {
|
||||
Name string `json:"name"`
|
||||
Project string `json:"project"`
|
||||
Identifier string `json:"identifier"`
|
||||
Alias string `json:"alias"`
|
||||
Description string `json:"description"`
|
||||
CreatedTime *time.Time `json:"createdTime"`
|
||||
UpdatedTime *time.Time `json:"updatedTime"`
|
||||
@@ -408,6 +409,7 @@ type CreateApplicationRequest struct {
|
||||
type CreateConfigRequest struct {
|
||||
Name string `json:"name" validate:"checkname"`
|
||||
Alias string `json:"alias"`
|
||||
Description string `json:"description"`
|
||||
Project string `json:"project"`
|
||||
ComponentType string `json:"componentType" validate:"checkname"`
|
||||
Properties string `json:"properties,omitempty"`
|
||||
@@ -1114,13 +1116,27 @@ type DetailRevisionResponse struct {
|
||||
type SystemInfoResponse struct {
|
||||
SystemInfo
|
||||
SystemVersion SystemVersion `json:"systemVersion"`
|
||||
StatisticInfo StatisticInfo `json:"statisticInfo,omitempty"`
|
||||
}
|
||||
|
||||
// SystemInfo system info
|
||||
type SystemInfo struct {
|
||||
InstallID string `json:"installID"`
|
||||
EnableCollection bool `json:"enableCollection"`
|
||||
LoginType string `json:"loginType"`
|
||||
PlatformID string `json:"platformID"`
|
||||
EnableCollection bool `json:"enableCollection"`
|
||||
LoginType string `json:"loginType"`
|
||||
InstallTime time.Time `json:"installTime,omitempty"`
|
||||
}
|
||||
|
||||
// StatisticInfo generated by cronJob running in backend
|
||||
type StatisticInfo struct {
|
||||
ClusterCount string `json:"clusterCount,omitempty"`
|
||||
AppCount string `json:"appCount,omitempty"`
|
||||
EnableAddonList map[string]string `json:"enableAddonList,omitempty"`
|
||||
ComponentDefinitionTopList []string `json:"componentDefinitionTopList,omitempty"`
|
||||
TraitDefinitionTopList []string `json:"traitDefinitionTopList,omitempty"`
|
||||
WorkflowDefinitionTopList []string `json:"workflowDefinitionTopList,omitempty"`
|
||||
PolicyDefinitionTopList []string `json:"policyDefinitionTopList,omitempty"`
|
||||
UpdateTime time.Time `json:"updateTime,omitempty"`
|
||||
}
|
||||
|
||||
// SystemInfoRequest request by update SystemInfo
|
||||
|
||||
@@ -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 {
|
||||
@@ -141,6 +146,10 @@ func (s *restServer) setupLeaderElection() (*leaderelection.LeaderElectionConfig
|
||||
Callbacks: leaderelection.LeaderCallbacks{
|
||||
OnStartedLeading: func(ctx context.Context) {
|
||||
go velasync.Start(ctx, s.dataStore, restCfg, s.usecases)
|
||||
if !s.cfg.DisableStatisticCronJob {
|
||||
collect.StartCalculatingInfoCronJob(s.dataStore)
|
||||
}
|
||||
// this process would block the whole process, any other handler should start before this func
|
||||
s.runWorkflowRecordSync(ctx, s.cfg.LeaderConfig.Duration)
|
||||
},
|
||||
OnStoppedLeading: func() {
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
set "github.com/deckarep/golang-set"
|
||||
"github.com/pkg/errors"
|
||||
@@ -134,12 +135,27 @@ func (u *configUseCaseImpl) GetConfigType(ctx context.Context, configType string
|
||||
}
|
||||
|
||||
func (u *configUseCaseImpl) CreateConfig(ctx context.Context, req apis.CreateConfigRequest) error {
|
||||
p := req.Properties
|
||||
// If the component is Terraform type, set the provider name same as the application name and the component name
|
||||
if strings.HasPrefix(req.ComponentType, types.TerrfaormComponentPrefix) {
|
||||
var properties map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(p), &properties); err != nil {
|
||||
return errors.Wrapf(err, "unable to process the properties of %s", req.ComponentType)
|
||||
}
|
||||
properties["name"] = req.Name
|
||||
tmp, err := json.Marshal(properties)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to process the properties of %s", req.ComponentType)
|
||||
}
|
||||
p = string(tmp)
|
||||
}
|
||||
app := v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: req.Name,
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Annotations: map[string]string{
|
||||
types.AnnotationConfigAlias: req.Alias,
|
||||
types.AnnotationConfigAlias: req.Alias,
|
||||
types.AnnotationConfigDescription: req.Description,
|
||||
},
|
||||
Labels: map[string]string{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
@@ -153,7 +169,7 @@ func (u *configUseCaseImpl) CreateConfig(ctx context.Context, req apis.CreateCon
|
||||
{
|
||||
Name: req.Name,
|
||||
Type: req.ComponentType,
|
||||
Properties: &runtime.RawExtension{Raw: []byte(req.Properties)},
|
||||
Properties: &runtime.RawExtension{Raw: []byte(p)},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -201,12 +217,7 @@ func (u *configUseCaseImpl) getConfigsByConfigType(ctx context.Context, configTy
|
||||
|
||||
configs := make([]*apis.Config, len(apps.Items))
|
||||
for i, a := range apps.Items {
|
||||
configs[i] = &apis.Config{
|
||||
ConfigType: a.Labels[types.LabelConfigType],
|
||||
Name: a.Name,
|
||||
Project: a.Labels[types.LabelConfigProject],
|
||||
CreatedTime: &(a.CreationTimestamp.Time),
|
||||
}
|
||||
configs[i] = retrieveConfigFromApplication(a, a.Labels[types.LabelConfigProject])
|
||||
switch a.Status.Phase {
|
||||
case common.ApplicationRunning:
|
||||
configs[i].Status = configIsReady
|
||||
|
||||
@@ -236,6 +236,11 @@ func TestCreateConfig(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
properties, err := json.Marshal(map[string]interface{}{
|
||||
"name": "default",
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
@@ -252,6 +257,18 @@ func TestCreateConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create terraform-alibaba config",
|
||||
args: args{
|
||||
h: h,
|
||||
req: apis.CreateConfigRequest{
|
||||
Name: "n1",
|
||||
ComponentType: "terraform-alibaba",
|
||||
Project: "p1",
|
||||
Properties: string(properties),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
|
||||
@@ -520,16 +520,10 @@ func (p *projectUsecaseImpl) GetConfigs(ctx context.Context, projectName, config
|
||||
for _, a := range apps.Items {
|
||||
appProject := a.Labels[types.LabelConfigProject]
|
||||
if a.Status.Phase != common.ApplicationRunning || (appProject != "" && appProject != projectName) ||
|
||||
!strings.Contains(a.Labels[types.LabelConfigType], "terraform-") {
|
||||
!strings.Contains(a.Labels[types.LabelConfigType], types.TerrfaormComponentPrefix) {
|
||||
continue
|
||||
}
|
||||
configs = append(configs, &apisv1.Config{
|
||||
ConfigType: a.Labels[types.LabelConfigType],
|
||||
Name: a.Name,
|
||||
Project: appProject,
|
||||
CreatedTime: &(a.CreationTimestamp.Time),
|
||||
ApplicationStatus: a.Status.Phase,
|
||||
})
|
||||
configs = append(configs, retrieveConfigFromApplication(a, appProject))
|
||||
}
|
||||
|
||||
configs = append(configs, legacyTerraformProviders...)
|
||||
@@ -539,13 +533,7 @@ func (p *projectUsecaseImpl) GetConfigs(ctx context.Context, projectName, config
|
||||
if appProject != "" && appProject != projectName {
|
||||
continue
|
||||
}
|
||||
configs = append(configs, &apisv1.Config{
|
||||
ConfigType: a.Labels[types.LabelConfigType],
|
||||
Name: a.Name,
|
||||
Project: appProject,
|
||||
CreatedTime: &(a.CreationTimestamp.Time),
|
||||
ApplicationStatus: a.Status.Phase,
|
||||
})
|
||||
configs = append(configs, retrieveConfigFromApplication(a, appProject))
|
||||
}
|
||||
configs = append(configs, legacyTerraformProviders...)
|
||||
case types.DexConnector, types.HelmRepository, types.ImageRegistry:
|
||||
@@ -556,13 +544,7 @@ func (p *projectUsecaseImpl) GetConfigs(ctx context.Context, projectName, config
|
||||
continue
|
||||
}
|
||||
if a.Labels[types.LabelConfigType] == t {
|
||||
configs = append(configs, &apisv1.Config{
|
||||
ConfigType: a.Labels[types.LabelConfigType],
|
||||
Name: a.Name,
|
||||
Project: appProject,
|
||||
CreatedTime: &(a.CreationTimestamp.Time),
|
||||
ApplicationStatus: a.Status.Phase,
|
||||
})
|
||||
configs = append(configs, retrieveConfigFromApplication(a, appProject))
|
||||
}
|
||||
}
|
||||
default:
|
||||
@@ -616,3 +598,15 @@ func ConvertProjectUserModel2Base(user *model.ProjectUser) *apisv1.ProjectUserBa
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func retrieveConfigFromApplication(a v1beta1.Application, project string) *apisv1.Config {
|
||||
return &apisv1.Config{
|
||||
ConfigType: a.Labels[types.LabelConfigType],
|
||||
Name: a.Name,
|
||||
Project: project,
|
||||
CreatedTime: &(a.CreationTimestamp.Time),
|
||||
ApplicationStatus: a.Status.Phase,
|
||||
Alias: a.Annotations[types.AnnotationConfigAlias],
|
||||
Description: a.Annotations[types.AnnotationConfigDescription],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -81,6 +82,7 @@ func (u systemInfoUsecaseImpl) Get(ctx context.Context) (*model.SystemInfo, erro
|
||||
info.InstallID = installID
|
||||
info.EnableCollection = true
|
||||
info.LoginType = model.LoginTypeLocal
|
||||
info.BaseModel = model.BaseModel{CreateTime: time.Now()}
|
||||
err = u.ds.Add(ctx, info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -100,6 +102,16 @@ 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,
|
||||
EnableAddonList: info.StatisticInfo.EnabledAddon,
|
||||
ComponentDefinitionTopList: info.StatisticInfo.TopKCompDef,
|
||||
TraitDefinitionTopList: info.StatisticInfo.TopKTraitDef,
|
||||
WorkflowDefinitionTopList: info.StatisticInfo.TopKWorkflowStepDef,
|
||||
PolicyDefinitionTopList: info.StatisticInfo.TopKPolicyDef,
|
||||
UpdateTime: info.StatisticInfo.UpdateTime,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -114,7 +126,9 @@ func (u systemInfoUsecaseImpl) UpdateSystemInfo(ctx context.Context, sysInfo v1.
|
||||
LoginType: sysInfo.LoginType,
|
||||
BaseModel: model.BaseModel{
|
||||
CreateTime: info.CreateTime,
|
||||
UpdateTime: time.Now(),
|
||||
},
|
||||
StatisticInfo: info.StatisticInfo,
|
||||
}
|
||||
|
||||
if sysInfo.LoginType == model.LoginTypeDex {
|
||||
@@ -135,9 +149,11 @@ func (u systemInfoUsecaseImpl) UpdateSystemInfo(ctx context.Context, sysInfo v1.
|
||||
}
|
||||
return &v1.SystemInfoResponse{
|
||||
SystemInfo: v1.SystemInfo{
|
||||
InstallID: modifiedInfo.InstallID,
|
||||
PlatformID: modifiedInfo.InstallID,
|
||||
EnableCollection: modifiedInfo.EnableCollection,
|
||||
LoginType: modifiedInfo.LoginType,
|
||||
// always use the initial createTime as system's installTime
|
||||
InstallTime: info.CreateTime,
|
||||
},
|
||||
SystemVersion: v1.SystemVersion{VelaVersion: version.VelaVersion, GitVersion: version.GitRevision},
|
||||
}, nil
|
||||
@@ -155,9 +171,10 @@ func (u systemInfoUsecaseImpl) Init(ctx context.Context) error {
|
||||
|
||||
func convertInfoToBase(info *model.SystemInfo) v1.SystemInfo {
|
||||
return v1.SystemInfo{
|
||||
InstallID: info.InstallID,
|
||||
PlatformID: info.InstallID,
|
||||
EnableCollection: info.EnableCollection,
|
||||
LoginType: info.LoginType,
|
||||
InstallTime: info.CreateTime,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -296,7 +296,7 @@ func (s *enabledAddonWebService) GetWebService() *restful.WebService {
|
||||
Filter(s.rbacUsecase.CheckPerm("addon", "list")).
|
||||
Param(ws.QueryParameter("registry", "filter addons from given registry").DataType("string")).
|
||||
Param(ws.QueryParameter("query", "Fuzzy search based on name and description.").DataType("string")).
|
||||
Returns(200, "OK", apis.ListAddonResponse{}).
|
||||
Returns(200, "OK", apis.ListEnabledAddonResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ListAddonResponse{}))
|
||||
|
||||
|
||||
@@ -117,8 +117,16 @@ func convertStepProperties(step *v1beta1.WorkflowStep, app *v1beta1.Application)
|
||||
return err
|
||||
}
|
||||
|
||||
var componentNames []string
|
||||
for _, c := range app.Spec.Components {
|
||||
componentNames = append(componentNames, c.Name)
|
||||
}
|
||||
|
||||
for _, c := range app.Spec.Components {
|
||||
if c.Name == o.Component {
|
||||
if dcName, ok := checkDependsOnValidComponent(c.DependsOn, componentNames); !ok {
|
||||
return errors.Errorf("component %s not found, which is depended by %s", dcName, c.Name)
|
||||
}
|
||||
step.Inputs = append(step.Inputs, c.Inputs...)
|
||||
for index := range step.Inputs {
|
||||
parameterKey := strings.TrimSpace(step.Inputs[index].ParameterKey)
|
||||
@@ -135,11 +143,23 @@ func convertStepProperties(step *v1beta1.WorkflowStep, app *v1beta1.Application)
|
||||
step.Properties = util.Object2RawExtension(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
return errors.Errorf("component %s not found", o.Component)
|
||||
}
|
||||
|
||||
func checkDependsOnValidComponent(dependsOnComponentNames, allComponentNames []string) (string, bool) {
|
||||
// does not depends on other components
|
||||
if dependsOnComponentNames == nil {
|
||||
return "", true
|
||||
}
|
||||
for _, dc := range dependsOnComponentNames {
|
||||
if !utils.StringsContain(allComponentNames, dc) {
|
||||
return dc, false
|
||||
}
|
||||
}
|
||||
return "", true
|
||||
}
|
||||
|
||||
func (h *AppHandler) renderComponentFunc(appParser *appfile.Parser, appRev *v1beta1.ApplicationRevision, af *appfile.Appfile) oamProvider.ComponentRender {
|
||||
return func(comp common.ApplicationComponent, patcher *value.Value, clusterName string, overrideNamespace string, env string) (*unstructured.Unstructured, []*unstructured.Unstructured, error) {
|
||||
ctx := multicluster.ContextWithClusterName(context.Background(), clusterName)
|
||||
|
||||
@@ -240,4 +240,118 @@ var _ = Describe("Test Application workflow generator", func() {
|
||||
_, _, err = renderFunc(comp, nil, "", "", "")
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test generate application workflow with dependsOn", func() {
|
||||
app := &oamcore.Application{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Application",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-with-input-output",
|
||||
Namespace: namespaceName,
|
||||
},
|
||||
Spec: oamcore.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "myweb1",
|
||||
Type: "worker-with-health",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
|
||||
},
|
||||
{
|
||||
Name: "myweb2",
|
||||
Type: "worker-with-health",
|
||||
DependsOn: []string{"myweb1"},
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox","lives": "i am lives","enemies": "empty"}`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
af, err := appParser.GenerateAppFile(ctx, app)
|
||||
Expect(err).Should(BeNil())
|
||||
appRev := &oamcore.ApplicationRevision{}
|
||||
|
||||
handler, err := NewAppHandler(ctx, reconciler, app, appParser)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
taskRunner, err := handler.GenerateApplicationSteps(ctx, app, appParser, af, appRev)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(len(taskRunner)).Should(BeEquivalentTo(2))
|
||||
Expect(taskRunner[0].Name()).Should(BeEquivalentTo("myweb1"))
|
||||
Expect(taskRunner[1].Name()).Should(BeEquivalentTo("myweb2"))
|
||||
})
|
||||
|
||||
It("Test generate application workflow with invalid dependsOn", func() {
|
||||
app := &oamcore.Application{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Application",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-with-input-output",
|
||||
Namespace: namespaceName,
|
||||
},
|
||||
Spec: oamcore.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "myweb1",
|
||||
Type: "worker-with-health",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
|
||||
},
|
||||
{
|
||||
Name: "myweb2",
|
||||
Type: "worker-with-health",
|
||||
DependsOn: []string{"myweb0"},
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox","lives": "i am lives","enemies": "empty"}`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
af, err := appParser.GenerateAppFile(ctx, app)
|
||||
Expect(err).Should(BeNil())
|
||||
appRev := &oamcore.ApplicationRevision{}
|
||||
|
||||
handler, err := NewAppHandler(ctx, reconciler, app, appParser)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
_, err = handler.GenerateApplicationSteps(ctx, app, appParser, af, appRev)
|
||||
Expect(err).NotTo(BeNil())
|
||||
})
|
||||
|
||||
It("Test generate application workflow with multiple invalid dependsOn", func() {
|
||||
app := &oamcore.Application{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Application",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-with-input-output",
|
||||
Namespace: namespaceName,
|
||||
},
|
||||
Spec: oamcore.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "myweb1",
|
||||
Type: "worker-with-health",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
|
||||
},
|
||||
{
|
||||
Name: "myweb2",
|
||||
Type: "worker-with-health",
|
||||
DependsOn: []string{"myweb1", "myweb0", "myweb3"},
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox","lives": "i am lives","enemies": "empty"}`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
af, err := appParser.GenerateAppFile(ctx, app)
|
||||
Expect(err).Should(BeNil())
|
||||
appRev := &oamcore.ApplicationRevision{}
|
||||
|
||||
handler, err := NewAppHandler(ctx, reconciler, app, appParser)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
_, err = handler.GenerateApplicationSteps(ctx, app, appParser, af, appRev)
|
||||
Expect(err).NotTo(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
@@ -29,12 +29,15 @@ const (
|
||||
LegacyObjectTypeIdentifier featuregate.Feature = "LegacyObjectTypeIdentifier"
|
||||
// DeprecatedObjectLabelSelector enable the use of deprecated object label selector for selecting ref-object
|
||||
DeprecatedObjectLabelSelector featuregate.Feature = "DeprecatedObjectLabelSelector"
|
||||
// LegacyResourceTrackerGC enable the gc of legacy resource tracker in managed clusters
|
||||
LegacyResourceTrackerGC featuregate.Feature = "LegacyResourceTrackerGC"
|
||||
)
|
||||
|
||||
var defaultFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
|
||||
DeprecatedPolicySpec: {Default: false, PreRelease: featuregate.Alpha},
|
||||
LegacyObjectTypeIdentifier: {Default: false, PreRelease: featuregate.Alpha},
|
||||
DeprecatedObjectLabelSelector: {Default: false, PreRelease: featuregate.Alpha},
|
||||
LegacyResourceTrackerGC: {Default: true, PreRelease: featuregate.Alpha},
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
errors3 "github.com/oam-dev/kubevela/pkg/utils/errors"
|
||||
@@ -46,7 +47,7 @@ const (
|
||||
// ClusterContextKey is the name of cluster using in client http context
|
||||
ClusterContextKey = contextKey("ClusterName")
|
||||
// ClusterLocalName specifies the local cluster
|
||||
ClusterLocalName = "local"
|
||||
ClusterLocalName = velatypes.ClusterLocalName
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -30,10 +30,12 @@ import (
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"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/features"
|
||||
"github.com/oam-dev/kubevela/pkg/monitor/metrics"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
@@ -319,6 +321,10 @@ func (h *gcHandler) GarbageCollectComponentRevisionResourceTracker(ctx context.C
|
||||
const velaVersionNumberToUpgradeResourceTracker = "v1.2.0"
|
||||
|
||||
func (h *gcHandler) GarbageCollectLegacyResourceTrackers(ctx context.Context) error {
|
||||
// skip legacy gc if controller not enable this feature
|
||||
if !utilfeature.DefaultMutableFeatureGate.Enabled(features.LegacyResourceTrackerGC) {
|
||||
return nil
|
||||
}
|
||||
// skip legacy gc if application is not handled by new version rt
|
||||
if h.app.GetDeletionTimestamp() == nil && h.resourceKeeper._currentRT == nil {
|
||||
return nil
|
||||
|
||||
@@ -136,6 +136,7 @@ func (c *AppCollector) FindResourceFromResourceTrackerSpec(app *v1beta1.Applicat
|
||||
ctx := context.Background()
|
||||
rootRT, currentRT, historyRTs, _, err := resourcetracker.ListApplicationResourceTrackers(ctx, c.k8sClient, app)
|
||||
if err != nil {
|
||||
klog.Errorf("query the resourcetrackers failure %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
var resources = []Resource{}
|
||||
@@ -151,11 +152,19 @@ func (c *AppCollector) FindResourceFromResourceTrackerSpec(app *v1beta1.Applicat
|
||||
existResources[managedResource.ClusterObjectReference] = true
|
||||
obj, err := managedResource.ToUnstructuredWithData()
|
||||
if err != nil {
|
||||
klog.Errorf("get obj from resource tracker failure %s", err.Error())
|
||||
continue
|
||||
// For the application with apply once policy, there is no data in RT.
|
||||
_, obj, err = getObjectCreatedByComponent(c.k8sClient, managedResource.ObjectReference, managedResource.Cluster)
|
||||
if err != nil {
|
||||
klog.Errorf("get obj from the cluster failure %s", err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
clusterName := managedResource.Cluster
|
||||
if clusterName == "" {
|
||||
clusterName = multicluster.ClusterLocalName
|
||||
}
|
||||
resources = append(resources, Resource{
|
||||
Cluster: managedResource.Cluster,
|
||||
Cluster: clusterName,
|
||||
Revision: oam.GetPublishVersion(rt),
|
||||
Component: managedResource.Component,
|
||||
Object: obj,
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/klog"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
@@ -83,7 +84,8 @@ var _ = Describe("Test Query Provider", func() {
|
||||
Name: "test",
|
||||
Namespace: "test",
|
||||
Annotations: map[string]string{
|
||||
"oam.dev/kubevela-version": "v1.2.0-beta.2",
|
||||
oam.AnnotationKubeVelaVersion: "v1.3.1",
|
||||
oam.AnnotationPublishVersion: "v1",
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
@@ -154,6 +156,53 @@ var _ = Describe("Test Query Provider", func() {
|
||||
})
|
||||
Expect(k8sClient.Create(ctx, appService)).Should(BeNil())
|
||||
|
||||
rt := &v1beta1.ResourceTracker{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%s-v1-%s", oldApp.Name, oldApp.Namespace),
|
||||
Labels: map[string]string{
|
||||
oam.LabelAppName: oldApp.Name,
|
||||
oam.LabelAppNamespace: oldApp.Namespace,
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
oam.AnnotationPublishVersion: "v1",
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ResourceTrackerSpec{
|
||||
ManagedResources: []v1beta1.ManagedResource{
|
||||
{
|
||||
ClusterObjectReference: common.ClusterObjectReference{
|
||||
Cluster: "",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "Service",
|
||||
Namespace: namespace,
|
||||
Name: "web",
|
||||
},
|
||||
},
|
||||
OAMObjectReference: common.OAMObjectReference{
|
||||
Component: "web",
|
||||
},
|
||||
},
|
||||
{
|
||||
ClusterObjectReference: common.ClusterObjectReference{
|
||||
Cluster: "",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Namespace: namespace,
|
||||
Name: "web",
|
||||
},
|
||||
},
|
||||
OAMObjectReference: common.OAMObjectReference{
|
||||
Component: "web",
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: v1beta1.ResourceTrackerTypeVersioned,
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, rt)).Should(BeNil())
|
||||
|
||||
prd := provider{cli: k8sClient}
|
||||
opt := `app: {
|
||||
name: "test"
|
||||
@@ -170,6 +219,9 @@ var _ = Describe("Test Query Provider", func() {
|
||||
|
||||
appResList := new(AppResourcesList)
|
||||
Expect(v.UnmarshalTo(appResList)).Should(BeNil())
|
||||
if appResList.Err != "" {
|
||||
klog.Error(appResList.Err)
|
||||
}
|
||||
|
||||
Expect(len(appResList.List)).Should(Equal(2))
|
||||
|
||||
@@ -180,7 +232,7 @@ var _ = Describe("Test Query Provider", func() {
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(&app), updateApp)).Should(BeNil())
|
||||
|
||||
updateApp.ObjectMeta.Annotations = map[string]string{
|
||||
"oam.dev/kubevela-version": "master",
|
||||
oam.AnnotationKubeVelaVersion: "v1.1.0",
|
||||
}
|
||||
Expect(k8sClient.Update(ctx, updateApp)).Should(BeNil())
|
||||
newValue, err := value.NewValue(opt, nil, "")
|
||||
|
||||
@@ -19,7 +19,7 @@ package template
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"path/filepath"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -60,7 +60,8 @@ func (loader *WorkflowStepLoader) LoadTaskTemplate(ctx context.Context, name str
|
||||
staticFilename := name + ".cue"
|
||||
for _, file := range files {
|
||||
if staticFilename == file.Name() {
|
||||
content, err := templateFS.ReadFile(filepath.Join(templateDir, file.Name()))
|
||||
fileName := fmt.Sprintf("%s/%s", templateDir, file.Name())
|
||||
content, err := templateFS.ReadFile(fileName)
|
||||
return string(content), err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ func prepareProviderAddSubCommand(c common.Args, ioStreams cmdutil.IOStreams) ([
|
||||
}
|
||||
data, err := json.Marshal(properties)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to authentiate Terraform cloud provier %s", providerType)
|
||||
return fmt.Errorf("failed to authenticate Terraform cloud provider %s", providerType)
|
||||
}
|
||||
providerAppName := fmt.Sprintf("config-terraform-provider-%s", name)
|
||||
a := &v1beta1.Application{}
|
||||
@@ -217,12 +217,12 @@ func prepareProviderAddSubCommand(c common.Args, ioStreams cmdutil.IOStreams) ([
|
||||
},
|
||||
}
|
||||
if err := k8sClient.Create(ctx, a); err != nil {
|
||||
return fmt.Errorf("failed to authentiate Terraform cloud provier %s", providerType)
|
||||
return fmt.Errorf("failed to authenticate Terraform cloud provider %s", providerType)
|
||||
}
|
||||
ioStreams.Infof("Successfully authentiate provider %s for %s\n", name, providerType)
|
||||
ioStreams.Infof("Successfully authenticate provider %s for %s\n", name, providerType)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to authentiate Terraform cloud provier %s", providerType)
|
||||
return fmt.Errorf("failed to authenticate Terraform cloud provider %s", providerType)
|
||||
}
|
||||
return fmt.Errorf("terraform provider %s for %s already exists", name, providerType)
|
||||
}
|
||||
|
||||
@@ -17,92 +17,170 @@ limitations under the License.
|
||||
package e2e_apiserver_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/addon"
|
||||
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
)
|
||||
|
||||
var _ = Describe("Test addon rest api", func() {
|
||||
registryName := "test-addon-registry"
|
||||
createReq := apis.CreateAddonRegistryRequest{
|
||||
Name: registryName,
|
||||
Oss: &addon.OSSAddonSource{
|
||||
Endpoint: "https://oss-cn-hangzhou.aliyuncs.com",
|
||||
Bucket: "fake-kubevela-addons",
|
||||
},
|
||||
}
|
||||
It("should add and delete a registry, list addons from default registry", func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
By("add registry")
|
||||
createRes := post("/addon_registries", createReq)
|
||||
var rmeta apis.AddonRegistry
|
||||
Expect(decodeResponseBody(createRes, &rmeta)).Should(Succeed())
|
||||
Expect(rmeta.Name).Should(Equal(createReq.Name))
|
||||
Expect(rmeta.Git).Should(Equal(createReq.Git))
|
||||
Expect(rmeta.OSS).Should(Equal(createReq.Oss))
|
||||
Describe("addon registry apiServer test", func() {
|
||||
It("list addon registry", func() {
|
||||
resp := get("/addon_registries")
|
||||
defer resp.Body.Close()
|
||||
var addonRegistry apisv1.ListAddonRegistryResponse
|
||||
Expect(decodeResponseBody(resp, &addonRegistry)).Should(Succeed())
|
||||
Expect(len(addonRegistry.Registries)).Should(BeEquivalentTo(1))
|
||||
})
|
||||
|
||||
deleteRes := delete("/addon_registries/" + createReq.Name)
|
||||
Expect(decodeResponseBody(deleteRes, nil)).Should(Succeed())
|
||||
})
|
||||
|
||||
It("list addons", func() {
|
||||
DefaultRegistry := "KubeVela"
|
||||
listRes := get("/addons/")
|
||||
var lres apis.ListAddonResponse
|
||||
Expect(decodeResponseBody(listRes, &lres)).Should(Succeed())
|
||||
Expect(lres.Addons).ShouldNot(BeZero())
|
||||
Expect(lres.Addons[0].RegistryName).To(Equal(DefaultRegistry))
|
||||
|
||||
By("get addon detail")
|
||||
detailRes := get("/addons/terraform-alibaba")
|
||||
var dres apis.DetailAddonResponse
|
||||
Expect(decodeResponseBody(detailRes, &dres)).Should(Succeed())
|
||||
Expect(dres.Meta).ShouldNot(BeNil())
|
||||
Expect(dres.UISchema).ShouldNot(BeNil())
|
||||
Expect(dres.APISchema).ShouldNot(BeNil())
|
||||
Expect(dres.RegistryName).Should(Equal(DefaultRegistry))
|
||||
})
|
||||
|
||||
PIt("should enable and disable an addon", func() {
|
||||
defer GinkgoRecover()
|
||||
req := apis.EnableAddonRequest{
|
||||
Args: map[string]interface{}{
|
||||
"example": "test-args",
|
||||
},
|
||||
}
|
||||
testAddon := "example"
|
||||
res := post("/addons/"+testAddon+"/enable", req)
|
||||
var statusRes apis.AddonStatusResponse
|
||||
Expect(decodeResponseBody(res, &statusRes)).Should(Succeed())
|
||||
Expect(statusRes.Phase).Should(Equal(apis.AddonPhaseEnabling))
|
||||
|
||||
// Wait for addon enabled
|
||||
|
||||
period := 30 * time.Second
|
||||
timeout := 2 * time.Minute
|
||||
Eventually(func() error {
|
||||
res = get("/addons/" + testAddon + "/status")
|
||||
defer res.Body.Close()
|
||||
err := json.NewDecoder(res.Body).Decode(&statusRes)
|
||||
Expect(err).Should(BeNil())
|
||||
if statusRes.Phase == apis.AddonPhaseEnabled {
|
||||
return nil
|
||||
It("add addon registry", func() {
|
||||
req := apisv1.CreateAddonRegistryRequest{
|
||||
Name: "test-registry",
|
||||
Git: &addon.GitAddonSource{
|
||||
URL: "github.com/test-path",
|
||||
},
|
||||
}
|
||||
return errors.New("not ready")
|
||||
}, timeout, period).Should(BeNil())
|
||||
res := post("/addon_registries", req)
|
||||
defer res.Body.Close()
|
||||
var registry apisv1.AddonRegistry
|
||||
Expect(decodeResponseBody(res, ®istry)).Should(Succeed())
|
||||
Expect(registry.Git).ShouldNot(BeNil())
|
||||
Expect(registry.Git.URL).Should(BeEquivalentTo("github.com/test-path"))
|
||||
|
||||
res = post("/addons/"+testAddon+"/disable", req)
|
||||
Expect(decodeResponseBody(res, &statusRes)).Should(Succeed())
|
||||
resp := get("/addon_registries")
|
||||
var addonRegistry apisv1.ListAddonRegistryResponse
|
||||
Expect(decodeResponseBody(resp, &addonRegistry)).Should(Succeed())
|
||||
Expect(len(addonRegistry.Registries)).Should(BeEquivalentTo(2))
|
||||
})
|
||||
|
||||
It("update an addon registry", func() {
|
||||
req := apisv1.UpdateAddonRegistryRequest{
|
||||
Git: &addon.GitAddonSource{
|
||||
URL: "github.com/another-path",
|
||||
},
|
||||
}
|
||||
res := put("/addon_registries"+"/test-registry", req)
|
||||
defer res.Body.Close()
|
||||
var registry apisv1.AddonRegistry
|
||||
Expect(decodeResponseBody(res, ®istry)).Should(Succeed())
|
||||
Expect(registry.Git).ShouldNot(BeNil())
|
||||
Expect(registry.Git.URL).Should(BeEquivalentTo("github.com/another-path"))
|
||||
|
||||
resp := get("/addon_registries")
|
||||
var addonRegistry apisv1.ListAddonRegistryResponse
|
||||
Expect(decodeResponseBody(resp, &addonRegistry)).Should(Succeed())
|
||||
Expect(len(addonRegistry.Registries)).Should(BeEquivalentTo(2))
|
||||
Expect(addonRegistry.Registries[1].Git.URL).Should(BeEquivalentTo("github.com/another-path"))
|
||||
})
|
||||
|
||||
It("delete an addon registry", func() {
|
||||
res := delete("/addon_registries" + "/test-registry")
|
||||
defer res.Body.Close()
|
||||
var registry apisv1.AddonRegistry
|
||||
Expect(decodeResponseBody(res, ®istry)).Should(Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
It("should delete test registry", func() {
|
||||
defer GinkgoRecover()
|
||||
Describe("addon apiServer test", func() {
|
||||
It("list addons", func() {
|
||||
res := get("/addons")
|
||||
defer res.Body.Close()
|
||||
var addons apisv1.ListAddonResponse
|
||||
Expect(decodeResponseBody(res, &addons)).Should(Succeed())
|
||||
Expect(len(addons.Addons)).ShouldNot(BeEquivalentTo(0))
|
||||
})
|
||||
|
||||
It("get addon detail", func() {
|
||||
res := get("/addons/mock-addon")
|
||||
defer res.Body.Close()
|
||||
var addon apisv1.DetailAddonResponse
|
||||
Expect(decodeResponseBody(res, &addon)).Should(Succeed())
|
||||
Expect(addon.Name).Should(BeEquivalentTo("mock-addon"))
|
||||
})
|
||||
|
||||
It("enable addon ", func() {
|
||||
req := apisv1.EnableAddonRequest{
|
||||
Args: map[string]interface{}{
|
||||
"testkey": "testvalue",
|
||||
},
|
||||
}
|
||||
res := post("/addons/mock-addon/enable", req)
|
||||
defer res.Body.Close()
|
||||
var addon apisv1.AddonStatusResponse
|
||||
Expect(decodeResponseBody(res, &addon)).Should(Succeed())
|
||||
Expect(addon.Name).Should(BeEquivalentTo("mock-addon"))
|
||||
Expect(len(addon.Args)).Should(BeEquivalentTo(1))
|
||||
Expect(addon.Args["testkey"]).Should(BeEquivalentTo("testvalue"))
|
||||
})
|
||||
|
||||
It("addon status", func() {
|
||||
res := get("/addons/mock-addon/status")
|
||||
defer res.Body.Close()
|
||||
var addonStatus apisv1.AddonStatusResponse
|
||||
Expect(decodeResponseBody(res, &addonStatus)).Should(Succeed())
|
||||
Expect(addonStatus.Name).Should(BeEquivalentTo("mock-addon"))
|
||||
Expect(len(addonStatus.Args)).Should(BeEquivalentTo(1))
|
||||
Expect(addonStatus.Args["testkey"]).Should(BeEquivalentTo("testvalue"))
|
||||
})
|
||||
|
||||
It("not enabled addon status", func() {
|
||||
res := get("/addons/example/status")
|
||||
defer res.Body.Close()
|
||||
var addonStatus apisv1.AddonStatusResponse
|
||||
Expect(decodeResponseBody(res, &addonStatus)).Should(Succeed())
|
||||
Expect(addonStatus.Name).Should(BeEquivalentTo("example"))
|
||||
Expect(addonStatus.Phase).Should(BeEquivalentTo("disabled"))
|
||||
})
|
||||
|
||||
It("update addon ", func() {
|
||||
req := apisv1.EnableAddonRequest{
|
||||
Args: map[string]interface{}{
|
||||
"testkey": "new-testvalue",
|
||||
},
|
||||
}
|
||||
res := put("/addons/mock-addon/update", req)
|
||||
defer res.Body.Close()
|
||||
var addonStatus apisv1.AddonStatusResponse
|
||||
Expect(decodeResponseBody(res, &addonStatus)).Should(Succeed())
|
||||
Expect(addonStatus.Name).Should(BeEquivalentTo("mock-addon"))
|
||||
Expect(len(addonStatus.Args)).Should(BeEquivalentTo(1))
|
||||
Expect(addonStatus.Args["testkey"]).Should(BeEquivalentTo("new-testvalue"))
|
||||
|
||||
status := get("/addons/mock-addon/status")
|
||||
var newaddonStatus apisv1.AddonStatusResponse
|
||||
Expect(decodeResponseBody(status, &newaddonStatus)).Should(Succeed())
|
||||
Expect(newaddonStatus.Name).Should(BeEquivalentTo("mock-addon"))
|
||||
Expect(len(newaddonStatus.Args)).Should(BeEquivalentTo(1))
|
||||
Expect(newaddonStatus.Args["testkey"]).Should(BeEquivalentTo("new-testvalue"))
|
||||
})
|
||||
|
||||
It("list enabled addon", func() {
|
||||
Eventually(func() error {
|
||||
res := get("/enabled_addon/")
|
||||
defer res.Body.Close()
|
||||
var addonList apisv1.ListEnabledAddonResponse
|
||||
err := decodeResponseBody(res, &addonList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(addonList.EnabledAddons) == 0 {
|
||||
return fmt.Errorf("error number")
|
||||
}
|
||||
return nil
|
||||
}, 30*time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
})
|
||||
|
||||
It("disable addon ", func() {
|
||||
res := post("/addons/mock-addon/disable", nil)
|
||||
defer res.Body.Close()
|
||||
var addonStatus apisv1.AddonStatusResponse
|
||||
Expect(decodeResponseBody(res, &addonStatus)).Should(Succeed())
|
||||
Expect(addonStatus.Name).Should(BeEquivalentTo("mock-addon"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -200,7 +200,6 @@ func delete(path string) *http.Response {
|
||||
req.Header.Add("Authorization", token)
|
||||
response, err := client.Do(req)
|
||||
Expect(err).Should(BeNil())
|
||||
defer response.Body.Close()
|
||||
return response
|
||||
}
|
||||
|
||||
|
||||
@@ -29,16 +29,16 @@ var _ = Describe("Test system info rest api", func() {
|
||||
res := get("/system_info/")
|
||||
var info apisv1.SystemInfoResponse
|
||||
Expect(decodeResponseBody(res, &info)).Should(Succeed())
|
||||
Expect(len(info.InstallID)).ShouldNot(BeEquivalentTo(0))
|
||||
Expect(len(info.PlatformID)).ShouldNot(BeEquivalentTo(0))
|
||||
Expect(info.EnableCollection).Should(BeEquivalentTo(true))
|
||||
systemID := info.InstallID
|
||||
systemID := info.PlatformID
|
||||
|
||||
// check several times the systemID should not change
|
||||
for i := 0; i < 5; i++ {
|
||||
res := get("/system_info/")
|
||||
var checkInfo apisv1.SystemInfoResponse
|
||||
Expect(decodeResponseBody(res, &checkInfo)).Should(Succeed())
|
||||
Expect(checkInfo.InstallID).Should(BeEquivalentTo(systemID))
|
||||
Expect(checkInfo.PlatformID).Should(BeEquivalentTo(systemID))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -46,33 +46,33 @@ var _ = Describe("Test system info rest api", func() {
|
||||
res := get("/system_info/")
|
||||
var info apisv1.SystemInfoResponse
|
||||
Expect(decodeResponseBody(res, &info)).Should(Succeed())
|
||||
Expect(len(info.InstallID)).ShouldNot(BeEquivalentTo(0))
|
||||
Expect(len(info.PlatformID)).ShouldNot(BeEquivalentTo(0))
|
||||
Expect(info.EnableCollection).Should(BeEquivalentTo(true))
|
||||
installID := info.InstallID
|
||||
installID := info.PlatformID
|
||||
|
||||
res = put("/system_info/", apisv1.SystemInfoRequest{EnableCollection: false})
|
||||
info = apisv1.SystemInfoResponse{}
|
||||
Expect(decodeResponseBody(res, &info)).Should(Succeed())
|
||||
Expect(len(info.InstallID)).ShouldNot(BeEquivalentTo(0))
|
||||
Expect(len(info.PlatformID)).ShouldNot(BeEquivalentTo(0))
|
||||
Expect(info.EnableCollection).Should(BeEquivalentTo(false))
|
||||
|
||||
res = get("/system_info/")
|
||||
var checkInfo apisv1.SystemInfoResponse
|
||||
Expect(decodeResponseBody(res, &checkInfo)).Should(Succeed())
|
||||
Expect(checkInfo.InstallID).Should(BeEquivalentTo(installID))
|
||||
Expect(checkInfo.PlatformID).Should(BeEquivalentTo(installID))
|
||||
Expect(checkInfo.EnableCollection).Should(BeEquivalentTo(false))
|
||||
|
||||
res = put("/system_info/", apisv1.SystemInfoRequest{EnableCollection: true})
|
||||
var enableInfo apisv1.SystemInfoResponse
|
||||
Expect(decodeResponseBody(res, &enableInfo)).Should(Succeed())
|
||||
Expect(len(enableInfo.InstallID)).ShouldNot(BeEquivalentTo(0))
|
||||
Expect(len(enableInfo.PlatformID)).ShouldNot(BeEquivalentTo(0))
|
||||
Expect(enableInfo.EnableCollection).Should(BeEquivalentTo(true))
|
||||
Expect(enableInfo.InstallID).Should(BeEquivalentTo(installID))
|
||||
Expect(enableInfo.PlatformID).Should(BeEquivalentTo(installID))
|
||||
|
||||
res = get("/system_info/")
|
||||
var checkAgainInfo apisv1.SystemInfoResponse
|
||||
Expect(decodeResponseBody(res, &checkAgainInfo)).Should(Succeed())
|
||||
Expect(checkAgainInfo.InstallID).Should(BeEquivalentTo(installID))
|
||||
Expect(checkAgainInfo.PlatformID).Should(BeEquivalentTo(installID))
|
||||
Expect(checkAgainInfo.EnableCollection).Should(BeEquivalentTo(true))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -203,7 +203,7 @@ var _ = Describe("Test velaQL rest api", func() {
|
||||
}, 2*time.Minute, 3*time.Microsecond).Should(BeNil())
|
||||
})
|
||||
|
||||
PIt("Test collect pod from helmRelease", func() {
|
||||
It("Test collect pod from helmRelease", func() {
|
||||
appWithHelm := new(v1beta1.Application)
|
||||
Expect(yaml.Unmarshal([]byte(podInfoApp), appWithHelm)).Should(BeNil())
|
||||
req := apiv1.ApplicationRequest{
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"catalog.config.oam.dev": "velacore-config"
|
||||
"type.config.oam.dev": "image-registry"
|
||||
"multi-cluster.config.oam.dev": "true"
|
||||
"ui-hidden": "true"
|
||||
}
|
||||
description: "Config information to authenticate image registry"
|
||||
attributes: workload: type: "autodetects.core.oam.dev"
|
||||
|
||||
@@ -501,7 +501,7 @@ template: {
|
||||
readinessProbe?: #HealthProbe
|
||||
|
||||
// +usage=Specify the hostAliases to add
|
||||
hostAliases: [...{
|
||||
hostAliases?: [...{
|
||||
ip: string
|
||||
hostnames: [...string]
|
||||
}]
|
||||
|
||||
Reference in New Issue
Block a user